diff --git a/CHANGELOG.md b/CHANGELOG.md index 77f1561f7f4..1c3327c8520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Agents/Subagents: preserve `threadId` on internal nested subagent announce injections (`deliver: false`) so parent orchestrators keep root thread context when relaying completion updates. (#22118) - Slack/Threading: sessions: keep parent-session forking and thread-history context active beyond first turn by removing first-turn-only gates in session init, thread-history fetch, and reply prompt context injection. (#23843, #23090) Thanks @vincentkoc and @Taskle. - Slack/Threading: respect `replyToMode` when Slack auto-populates top-level `thread_ts`, and ignore inline `replyToId` directive tags when `replyToMode` is `off` so thread forcing stays disabled unless explicitly configured. (#23839, #23320, #23513) Thanks @vincentkoc and @dorukardahan. - Slack/Extension: forward `message read` `threadId` to `readMessages` and use delivery-context `threadId` as outbound `thread_ts` fallback so extension replies/reads stay in the correct Slack thread. (#22216, #22485, #23836) Thanks @vincentkoc, @lan17 and @dorukardahan. diff --git a/src/agents/subagent-announce.format.test.ts b/src/agents/subagent-announce.format.test.ts index a612e9fca02..51ca08c5b04 100644 --- a/src/agents/subagent-announce.format.test.ts +++ b/src/agents/subagent-announce.format.test.ts @@ -1130,7 +1130,7 @@ describe("subagent announce formatting", () => { childRunId: "run-worker-queued", requesterSessionKey: "agent:main:subagent:orchestrator", requesterDisplayKey: "agent:main:subagent:orchestrator", - requesterOrigin: { channel: "whatsapp", to: "+1555", accountId: "acct" }, + requesterOrigin: { channel: "whatsapp", to: "+1555", accountId: "acct", threadId: "777" }, ...defaultOutcomeAnnounce, }); @@ -1142,6 +1142,7 @@ describe("subagent announce formatting", () => { expect(call?.params?.deliver).toBe(false); expect(call?.params?.channel).toBeUndefined(); expect(call?.params?.to).toBeUndefined(); + expect(call?.params?.threadId).toBe("777"); }); it.each([ @@ -1278,7 +1279,12 @@ describe("subagent announce formatting", () => { childSessionKey: "agent:main:subagent:worker", childRunId: "run-worker", requesterSessionKey: "agent:main:subagent:orchestrator", - requesterOrigin: { channel: "whatsapp", accountId: "acct-123", to: "+1555" }, + requesterOrigin: { + channel: "whatsapp", + accountId: "acct-123", + to: "+1555", + threadId: "thread-1", + }, requesterDisplayKey: "agent:main:subagent:orchestrator", ...defaultOutcomeAnnounce, }); @@ -1289,6 +1295,7 @@ describe("subagent announce formatting", () => { expect(call?.params?.deliver).toBe(false); expect(call?.params?.channel).toBeUndefined(); expect(call?.params?.to).toBeUndefined(); + expect(call?.params?.threadId).toBe("thread-1"); }); it("keeps completion-mode announce internal for nested requester subagent sessions", async () => { @@ -1299,7 +1306,12 @@ describe("subagent announce formatting", () => { childSessionKey: "agent:main:subagent:orchestrator:subagent:worker", childRunId: "run-worker-nested-completion", requesterSessionKey: "agent:main:subagent:orchestrator", - requesterOrigin: { channel: "whatsapp", accountId: "acct-123", to: "+1555" }, + requesterOrigin: { + channel: "whatsapp", + accountId: "acct-123", + to: "+1555", + threadId: "thread-2", + }, requesterDisplayKey: "agent:main:subagent:orchestrator", expectsCompletionMessage: true, ...defaultOutcomeAnnounce, @@ -1312,6 +1324,7 @@ describe("subagent announce formatting", () => { expect(call?.params?.deliver).toBe(false); expect(call?.params?.channel).toBeUndefined(); expect(call?.params?.to).toBeUndefined(); + expect(call?.params?.threadId).toBe("thread-2"); const message = typeof call?.params?.message === "string" ? call.params.message : ""; expect(message).toContain( "Convert this completion into a concise internal orchestration update for your parent agent", diff --git a/src/agents/subagent-announce.ts b/src/agents/subagent-announce.ts index e900f8be5f0..8bf794f5bbe 100644 --- a/src/agents/subagent-announce.ts +++ b/src/agents/subagent-announce.ts @@ -490,7 +490,9 @@ async function sendAnnounce(item: AnnounceQueueItem) { channel: requesterIsSubagent ? undefined : origin?.channel, accountId: requesterIsSubagent ? undefined : origin?.accountId, to: requesterIsSubagent ? undefined : origin?.to, - threadId: requesterIsSubagent ? undefined : threadId, + // Keep thread context on internal nested announce injections so parent + // orchestrators can continue thread-aware delivery after processing. + threadId, deliver: !requesterIsSubagent, idempotencyKey, }, @@ -713,7 +715,9 @@ async function sendSubagentAnnounceDirectly(params: { channel: params.requesterIsSubagent ? undefined : directOrigin?.channel, accountId: params.requesterIsSubagent ? undefined : directOrigin?.accountId, to: params.requesterIsSubagent ? undefined : directOrigin?.to, - threadId: params.requesterIsSubagent ? undefined : threadId, + // Preserve root thread context even when we intentionally keep nested + // announce delivery internal (`deliver=false`). + threadId, idempotencyKey: params.directIdempotencyKey, }, expectFinal: true,