diff --git a/scripts/init-first-agent.ts b/scripts/init-first-agent.ts index fc61b9c..61a17d6 100644 --- a/scripts/init-first-agent.ts +++ b/scripts/init-first-agent.ts @@ -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:" for group chats. WhatsApp sends JIDs containing - * '@' (@s.whatsapp.net, @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)}`; } diff --git a/setup/register.ts b/setup/register.ts index ff194fc..7bd5ae3 100644 --- a/setup/register.ts +++ b/setup/register.ts @@ -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 { 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 { 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 { 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(), }); diff --git a/src/platform-id.ts b/src/platform-id.ts new file mode 100644 index 0000000..1c49325 --- /dev/null +++ b/src/platform-id.ts @@ -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:' 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}`; +}