diff --git a/container/agent-runner/src/integration.test.ts b/container/agent-runner/src/integration.test.ts index 3447c3894..f309cc381 100644 --- a/container/agent-runner/src/integration.test.ts +++ b/container/agent-runner/src/integration.test.ts @@ -74,6 +74,44 @@ describe('poll loop integration', () => { await loopPromise.catch(() => {}); }); + it('should resolve thread_id per-destination, not from global routing', async () => { + // Seed a second destination + getInboundDb() + .prepare( + `INSERT INTO destinations (name, display_name, type, channel_type, platform_id, agent_group_id) + VALUES ('slack-test', 'Slack Test', 'channel', 'slack', 'chan-2', NULL)`, + ) + .run(); + + // Insert messages from each destination with distinct thread IDs + insertMessage('m-discord', { sender: 'Alice', text: 'from discord' }, { platformId: 'chan-1', channelType: 'discord', threadId: 'discord-thread-1' }); + insertMessage('m-slack', { sender: 'Bob', text: 'from slack' }, { platformId: 'chan-2', channelType: 'slack', threadId: 'slack-thread-99' }); + + // Agent replies to both destinations + const provider = new MockProvider({}, () => + 'reply-dreply-s', + ); + const controller = new AbortController(); + const loopPromise = runPollLoopWithTimeout(provider, controller.signal, 2000); + + await waitFor(() => getUndeliveredMessages().length >= 2, 2000); + controller.abort(); + + const out = getUndeliveredMessages(); + const discordOut = out.find((m) => m.platform_id === 'chan-1'); + const slackOut = out.find((m) => m.platform_id === 'chan-2'); + + expect(discordOut).toBeDefined(); + expect(discordOut!.thread_id).toBe('discord-thread-1'); + expect(discordOut!.in_reply_to).toBe('m-discord'); + + expect(slackOut).toBeDefined(); + expect(slackOut!.thread_id).toBe('slack-thread-99'); + expect(slackOut!.in_reply_to).toBe('m-slack'); + + await loopPromise.catch(() => {}); + }); + it('should process messages arriving after loop starts', async () => { const provider = new MockProvider({}, () => 'Processed'); const controller = new AbortController(); diff --git a/container/agent-runner/src/poll-loop.ts b/container/agent-runner/src/poll-loop.ts index 076d29d5e..35abb8399 100644 --- a/container/agent-runner/src/poll-loop.ts +++ b/container/agent-runner/src/poll-loop.ts @@ -478,8 +478,8 @@ function resolveDestinationThread( ) .get(channelType, platformId) as { thread_id: string | null; id: string } | undefined; if (row) return { threadId: row.thread_id, inReplyTo: row.id }; - } catch { - // Fall through — DB may not have these columns on older sessions + } catch (err) { + log(`resolveDestinationThread error: ${err instanceof Error ? err.message : String(err)}`); } return null; } diff --git a/src/group-init.ts b/src/group-init.ts index 0e6aeb15b..b3251506e 100644 --- a/src/group-init.ts +++ b/src/group-init.ts @@ -89,7 +89,6 @@ export function initGroupFilesystem(group: AgentGroup, opts?: { instructions?: s // Skills directory — created empty here; symlinks are synced at spawn // time by container-runner.ts based on container.json skills selection. - // (ensurePreCompactHook is defined after the main function.) const skillsDst = path.join(claudeDir, 'skills'); if (!fs.existsSync(skillsDst)) { fs.mkdirSync(skillsDst, { recursive: true });