Agents/Subagents: preserve threadId in nested announce injections

This commit is contained in:
Vincent Koc
2026-02-22 15:56:53 -05:00
parent 3a088c9f4f
commit ec409c37a6
3 changed files with 23 additions and 5 deletions

View File

@@ -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.

View File

@@ -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",

View File

@@ -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,