diff --git a/src/agents/acp-spawn.test.ts b/src/agents/acp-spawn.test.ts index d26932af536..b9a4657e9db 100644 --- a/src/agents/acp-spawn.test.ts +++ b/src/agents/acp-spawn.test.ts @@ -1319,6 +1319,57 @@ describe("spawnAcpDirect", () => { expect(agentCall?.params?.channel).toBe("telegram"); }); + it("preserves topic-qualified Telegram targets without a separate threadId", async () => { + replaceSpawnConfig({ + ...hoisted.state.cfg, + channels: { + ...hoisted.state.cfg.channels, + telegram: { + threadBindings: { + enabled: true, + }, + }, + }, + }); + registerSessionBindingAdapter({ + channel: "telegram", + accountId: "default", + capabilities: createSessionBindingCapabilities(), + bind: async (input) => await hoisted.sessionBindingBindMock(input), + listBySession: (targetSessionKey) => + hoisted.sessionBindingListBySessionMock(targetSessionKey), + resolveByConversation: (ref) => hoisted.sessionBindingResolveByConversationMock(ref), + unbind: async (input) => await hoisted.sessionBindingUnbindMock(input), + }); + + const result = await spawnAcpDirect( + { + task: "Investigate flaky tests", + agentId: "codex", + mode: "session", + thread: true, + }, + { + agentSessionKey: "agent:main:telegram:group:-1003342490704:topic:2", + agentChannel: "telegram", + agentAccountId: "default", + agentTo: "telegram:group:-1003342490704:topic:2", + }, + ); + + expect(result.status).toBe("accepted"); + expect(hoisted.sessionBindingBindMock).toHaveBeenCalledWith( + expect.objectContaining({ + placement: "current", + conversation: expect.objectContaining({ + channel: "telegram", + accountId: "default", + conversationId: "-1003342490704:topic:2", + }), + }), + ); + }); + it("disposes pre-registered parent relay when initial ACP dispatch fails", async () => { const relayHandle = createRelayHandle(); hoisted.startAcpSpawnParentStreamRelayMock.mockReturnValueOnce(relayHandle); diff --git a/src/agents/acp-spawn.ts b/src/agents/acp-spawn.ts index 155e491bf29..841293965d4 100644 --- a/src/agents/acp-spawn.ts +++ b/src/agents/acp-spawn.ts @@ -385,10 +385,16 @@ function resolveConversationIdForThreadBinding(params: { } chatId = next; } - chatId = chatId - .replace(/:topic:\d+$/i, "") - .replace(/:\d+$/i, "") - .trim(); + const topicMatch = /^(.*?):topic:(\d+)$/i.exec(chatId); + if (topicMatch?.[1] && /^-?\d+$/.test(topicMatch[1].trim())) { + const topicId = normalizedThreadId ?? topicMatch[2]; + return `${topicMatch[1].trim()}:topic:${topicId}`; + } + const shorthandTopicMatch = /^(.*?):(\d+)$/i.exec(chatId); + if (shorthandTopicMatch?.[1] && /^-?\d+$/.test(shorthandTopicMatch[1].trim())) { + const topicId = normalizedThreadId ?? shorthandTopicMatch[2]; + return `${shorthandTopicMatch[1].trim()}:topic:${topicId}`; + } if (/^-?\d+$/.test(chatId)) { return normalizedThreadId ? `${chatId}:topic:${normalizedThreadId}` : chatId; }