fix(agent-to-agent): route A2A replies back to originating session (#2267)

Squash merge of PR #2267 by ddaniels.

When an agent group has more than one active session, A2A replies landed
in the newest session via findSessionByAgentGroup's ORDER BY created_at
DESC. The session that asked the question never saw the answer.

Adds origin-aware return-path routing with three layers:

1. Direct return-path: if the reply has in_reply_to, look up the
   triggering inbound row's source_session_id and route there.
2. Peer-affinity fallback: find the most recent A2A inbound from this
   peer and use its source_session_id.
3. Legacy fallback: newest active session (pre-migration compat).

Container-side: MCP send_message/send_file now thread the current
batch's in_reply_to through to outbound rows via current-batch.ts.

Also flips our A2A bug-documenting test (#2332) from asserting the
broken behavior to asserting the fixed behavior.

Co-Authored-By: Doug Daniels <ddaniels888@gmail.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-05-08 00:48:10 +03:00
parent 3b07c0ceaf
commit 107945f10c
12 changed files with 517 additions and 39 deletions

View File

@@ -969,12 +969,10 @@ describe('agent-to-agent routing', () => {
expect(JSON.parse(rows[0].content).text).toBe('research this');
});
it('BUG: A2A return path resolves to wrong session when multiple channel sessions exist (#2332)', async () => {
it('A2A return path routes to originating session, not newest (#2332)', async () => {
// PA has Slack session, then gets wired to Discord (newer session).
// Researcher responds to PA. routeAgentMessage calls
// resolveSession('ag-pa', null, null, 'agent-shared') which calls
// findSessionByAgentGroup — picks newest (Discord) instead of the
// Slack session that originated the A2A call.
// Researcher responds to PA. With the return-path fix, the reply
// routes back to the Slack session (originator) not Discord (newest).
const { routeAgentMessage } = await import('./modules/agent-to-agent/agent-route.js');
const { session: paSlackSession } = resolveSession('ag-pa', 'mg-slack', null, 'shared');
@@ -1013,9 +1011,9 @@ describe('agent-to-agent routing', () => {
const discordA2a = discordDb.prepare("SELECT * FROM messages_in WHERE channel_type = 'agent'").all();
discordDb.close();
// Document the bug: response lands in Discord (newest) not Slack (origin)
expect(discordA2a).toHaveLength(1); // BUG: should be 0
expect(slackA2a).toHaveLength(0); // BUG: should be 1
// Fixed: response lands in Slack (origin) not Discord (newest)
expect(slackA2a).toHaveLength(1);
expect(discordA2a).toHaveLength(0);
});
it('BUG: A2A-only session gets null session_routing (#2332)', async () => {