diff --git a/container/agent-runner/src/formatter.ts b/container/agent-runner/src/formatter.ts index 590875c9e3..7b9f44a0aa 100644 --- a/container/agent-runner/src/formatter.ts +++ b/container/agent-runner/src/formatter.ts @@ -82,10 +82,11 @@ export function isRunnerCommand(msg: MessageInRow): boolean { function extractSenderId(msg: MessageInRow, content: any): string | null { const raw: string | null = content?.senderId || content?.author?.userId || null; if (!raw) return null; - // Already namespaced (e.g. "telegram:123") — use as-is. - if (raw.includes(':')) return raw; - // Raw platform id from chat-sdk serialization — prefix with channel type. if (!msg.channel_type) return raw; + // Already namespaced for this channel — use as-is. `includes(':')` would be + // wrong here: Teams Bot Framework user ids like `29:1abc...` natively + // contain a colon and would slip through unprefixed. + if (raw.startsWith(`${msg.channel_type}:`)) return raw; return `${msg.channel_type}:${raw}`; } diff --git a/src/modules/permissions/index.ts b/src/modules/permissions/index.ts index 67adfe771b..d6ca6909da 100644 --- a/src/modules/permissions/index.ts +++ b/src/modules/permissions/index.ts @@ -90,7 +90,11 @@ function extractAndUpsertUser(event: InboundEvent): string | null { const rawHandle = senderIdField ?? senderField ?? authorUserId; if (!rawHandle) return null; - const userId = rawHandle.includes(':') ? rawHandle : `${event.channelType}:${rawHandle}`; + // Namespace the handle as `:` unless it's already namespaced + // for this channel. `includes(':')` is wrong: Teams Bot Framework user ids + // like `29:1abc...` natively contain a colon, so that check would skip the + // prefix and produce `29:1abc...` instead of `teams:29:1abc...`. + const userId = rawHandle.startsWith(`${event.channelType}:`) ? rawHandle : `${event.channelType}:${rawHandle}`; if (!getUser(userId)) { upsertUser({ id: userId, @@ -227,11 +231,11 @@ async function handleSenderApprovalResponse(payload: ResponsePayload): Promise { // Member row added for the stranger against the wired agent group. const member = getDb() .prepare('SELECT 1 AS x FROM agent_group_members WHERE user_id = ? AND agent_group_id = ?') - .get('tg:stranger', 'ag-1'); + .get('telegram:stranger-tg-id', 'ag-1'); expect(member).toBeDefined(); // Pending row cleared. @@ -258,7 +258,7 @@ describe('unknown-sender request_approval flow', () => { expect(count).toBe(0); const member = getDb() .prepare('SELECT 1 AS x FROM agent_group_members WHERE user_id = ? AND agent_group_id = ?') - .get('tg:stranger', 'ag-1'); + .get('telegram:stranger-tg-id', 'ag-1'); expect(member).toBeUndefined(); }); @@ -292,7 +292,7 @@ describe('unknown-sender request_approval flow', () => { // No member added for the stranger. const member = getDb() .prepare('SELECT 1 AS x FROM agent_group_members WHERE user_id = ? AND agent_group_id = ?') - .get('tg:stranger', 'ag-1'); + .get('telegram:stranger-tg-id', 'ag-1'); expect(member).toBeUndefined(); // Pending row is still there — a legitimate approver can still act on it. @@ -338,7 +338,7 @@ describe('unknown-sender request_approval flow', () => { // Stranger admitted thanks to the admin's authority. const member = getDb() .prepare('SELECT 1 AS x FROM agent_group_members WHERE user_id = ? AND agent_group_id = ?') - .get('tg:stranger', 'ag-1'); + .get('telegram:stranger-tg-id', 'ag-1'); expect(member).toBeDefined(); }); });