fix(register): wire channels with correct engage fields, skip prefix for native IDs

setup/register.ts had two bugs that prevented new channels from being
registered via `/manage-channels`:

1. createMessagingGroupAgent was called with the legacy field names
   `trigger_rules` and `response_scope`. The SQL INSERT expects
   `engage_mode` / `engage_pattern` / `sender_scope` / `ignored_message_policy`
   (migration 010). Every register call failed with
   `RangeError: Missing named parameter "engage_mode"` after the agent
   and messaging group were partially created — leaving an orphaned pair.

   Now mirrors scripts/init-first-agent.ts:wireIfMissing:
   - Groups (is_group=1) default to engage_mode='mention' (bot only
     responds when addressed).
   - DMs (is_group=0) default to engage_mode='pattern' with '.' (respond
     to every message).
   - An explicit --trigger overrides the pattern regex.

2. The "normalize platform_id" block unconditionally prefixed
   "<channel>:" even for native IDs like WhatsApp JIDs
   ("120363408974444974@g.us"), iMessage emails ("user@example.com"),
   or Signal phones ("+15551234567") / Signal groups ("group:abc"). But
   the router (src/router.ts:158) looks up messaging_groups by the raw
   event.platformId from the adapter, which for these native adapters
   never has a prefix. So the prefixed row was never matched — the
   message was silently dropped with no "Message routed" log.

   Extracted scripts/init-first-agent.ts:namespacedPlatformId into
   src/platform-id.ts so both setup paths use the same heuristic (skip
   the prefix for IDs containing '@', starting with '+', or starting
   with 'group:'). Prevents future drift between the two paths.

Tested by: re-running `setup/index.ts --step register` for a WhatsApp
group JID, confirming the row is created with correct engage fields
and matching platform_id, then sending a test message and observing
"Message routed" with the right agent group.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
grtwrn
2026-04-23 21:04:15 -04:00
committed by gavrielc
parent 88d3da76c3
commit fc375ca72b
3 changed files with 37 additions and 35 deletions

View File

@@ -48,6 +48,7 @@ import { addMember } from '../src/modules/permissions/db/agent-group-members.js'
import { getUserRoles, grantRole } from '../src/modules/permissions/db/user-roles.js';
import { upsertUser } from '../src/modules/permissions/db/users.js';
import { initGroupFilesystem } from '../src/group-init.js';
import { namespacedPlatformId } from '../src/platform-id.js';
import type { AgentGroup, MessagingGroup } from '../src/types.js';
type Role = 'owner' | 'admin' | 'member';
@@ -137,32 +138,6 @@ function namespacedUserId(channel: string, raw: string): string {
return raw.includes(':') ? raw : `${channel}:${raw}`;
}
/**
* Determine whether a platform ID needs a channel-type prefix.
*
* Chat SDK adapters (Telegram, Discord, Slack, Teams, etc.) namespace their
* platform IDs with a channel prefix: "telegram:123456", "discord:guild:chan".
* The router stores `channel_type` and `platform_id` in separate columns, but
* Chat SDK adapters send the prefixed form as the platform_id, so this script
* must match that format.
*
* Native adapters (Signal, WhatsApp) use their own ID formats and send them
* as-is — no channel prefix. Signal sends raw phone numbers (+15551234567)
* for DMs and "group:<id>" for group chats. WhatsApp sends JIDs containing
* '@' (<phone>@s.whatsapp.net, <groupId>@g.us). Prefixing these would cause
* a mismatch between what the adapter sends and what the DB stores, breaking
* message routing.
*/
function namespacedPlatformId(channel: string, raw: string): string {
if (raw.startsWith(`${channel}:`)) return raw;
// Native WhatsApp JIDs contain '@' — no prefix needed.
if (raw.includes('@')) return raw;
// Native Signal IDs: phone numbers (+...) and group IDs (group:...).
if (raw.startsWith('+') || raw.startsWith('group:')) return raw;
// Chat SDK adapters — add the channel prefix.
return `${channel}:${raw}`;
}
function generateId(prefix: string): string {
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}

View File

@@ -20,6 +20,7 @@ import {
import { isValidGroupFolder } from '../src/group-folder.js';
import { initGroupFilesystem } from '../src/group-init.js';
import { log } from '../src/log.js';
import { namespacedPlatformId } from '../src/platform-id.js';
import { resolveSession, writeSessionMessage } from '../src/session-manager.js';
import { emitStatus } from './status.js';
@@ -112,12 +113,10 @@ export async function run(args: string[]): Promise<void> {
process.exit(4);
}
// Chat SDK adapters prefix platform IDs with the channel type
// (e.g. "telegram:123", "discord:guild:channel"). Normalize here so
// the stored ID always matches what the adapter sends at runtime.
if (!parsed.platformId.startsWith(`${parsed.channel}:`)) {
parsed.platformId = `${parsed.channel}:${parsed.platformId}`;
}
// Normalize platform_id to the same shape the adapter will emit at runtime,
// so the router's (channel_type, platform_id) lookup matches what we store.
// Chat SDK adapters prefix, native adapters (WhatsApp/iMessage/Signal) don't.
parsed.platformId = namespacedPlatformId(parsed.channel, parsed.platformId);
log.info('Registering channel', parsed);
@@ -167,8 +166,13 @@ export async function run(args: string[]): Promise<void> {
if (!existing) {
newlyWired = true;
const mgaId = generateId('mga');
const engageMode = parsed.trigger || !parsed.requiresTrigger ? 'pattern' : 'mention';
const engagePattern = parsed.trigger ? parsed.trigger : (!parsed.requiresTrigger ? '.' : null);
// Mirrors scripts/init-first-agent.ts:wireIfMissing so both setup paths
// create rows with the same shape. Groups default to 'mention' (bot only
// responds when addressed); DMs default to 'pattern'/'.' (respond to
// every message). An explicit --trigger overrides the pattern regex.
const isGroup = messagingGroup.is_group === 1;
const engageMode: 'pattern' | 'mention' = isGroup && !parsed.trigger ? 'mention' : 'pattern';
const engagePattern: string | null = engageMode === 'pattern' ? parsed.trigger || '.' : null;
createMessagingGroupAgent({
id: mgaId,
messaging_group_id: messagingGroup.id,
@@ -177,7 +181,7 @@ export async function run(args: string[]): Promise<void> {
engage_pattern: engagePattern,
sender_scope: 'all',
ignored_message_policy: 'drop',
session_mode: parsed.sessionMode,
session_mode: parsed.sessionMode as 'shared' | 'per-thread' | 'agent-shared',
priority: 0,
created_at: new Date().toISOString(),
});

23
src/platform-id.ts Normal file
View File

@@ -0,0 +1,23 @@
/**
* Determine whether a platform ID needs a channel-type prefix.
*
* Chat SDK adapters (Telegram, Discord, Slack, Teams, etc.) namespace their
* platform IDs with a channel prefix: "telegram:123456", "discord:guild:chan".
* The router stores channel_type and platform_id in separate columns, but
* Chat SDK adapters send the prefixed form as the platform_id — so any code
* that writes messaging_groups rows must produce the same shape the adapter
* will later emit as event.platformId, or router lookups miss and messages
* get silently dropped.
*
* Native adapters (Signal, WhatsApp, iMessage) use their own ID formats and
* send them as-is — no channel prefix. WhatsApp/iMessage emit JIDs/emails
* containing '@'. Signal emits raw phone numbers ('+15551234567') for DMs
* and 'group:<id>' for group chats. Prefixing any of these would cause a
* mismatch with what the adapter later emits.
*/
export function namespacedPlatformId(channel: string, raw: string): string {
if (raw.startsWith(`${channel}:`)) return raw;
if (raw.includes('@')) return raw;
if (raw.startsWith('+') || raw.startsWith('group:')) return raw;
return `${channel}:${raw}`;
}