From 1d935cce5155b61bd27dc7b00a9ea424d186c0de Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 22:22:44 -0700 Subject: [PATCH 1/5] fix(agents): suppress mid-turn continuation prompts --- CHANGELOG.md | 1 + .../run.overflow-compaction.loop.test.ts | 2 ++ src/agents/pi-embedded-runner/run.ts | 12 ++++++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c05dc83a34..a2b62ee2136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Agents/Pi: suppress persistence for synthetic mid-turn overflow continuation prompts, so transcript-retry recovery does not write the "continue from transcript" prompt as a new user turn. Thanks @vincentkoc. - Exec approvals: detect `env -S` split-string command-carrier risks when `-S`/`-s` is combined with other env short options, so approval explanations do not miss split payloads hidden behind `env -iS...`. Thanks @vincentkoc. - Voice Call: mark realtime calls completed when the realtime provider closes normally, so Twilio/OpenAI/Google realtime stop events do not leave active call records behind. Thanks @vincentkoc. - Exec approvals: treat POSIX `exec` as a command carrier for inline eval, shell-wrapper, and eval/source detection, so approval explanations and command-risk checks do not miss payloads hidden behind `exec`. Thanks @vincentkoc. diff --git a/src/agents/pi-embedded-runner/run.overflow-compaction.loop.test.ts b/src/agents/pi-embedded-runner/run.overflow-compaction.loop.test.ts index 23b09368d78..c8231bd5b3a 100644 --- a/src/agents/pi-embedded-runner/run.overflow-compaction.loop.test.ts +++ b/src/agents/pi-embedded-runner/run.overflow-compaction.loop.test.ts @@ -359,6 +359,7 @@ describe("overflow compaction in run loop", () => { 2, expect.objectContaining({ prompt: expect.stringContaining("Continue from the current transcript"), + suppressNextUserMessagePersistence: true, }), ); expect(mockedRunEmbeddedAttempt).not.toHaveBeenNthCalledWith( @@ -433,6 +434,7 @@ describe("overflow compaction in run loop", () => { 2, expect.objectContaining({ prompt: expect.stringContaining("Continue from the current transcript"), + suppressNextUserMessagePersistence: true, }), ); expect(mockedRunEmbeddedAttempt).not.toHaveBeenNthCalledWith( diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index f2647210207..5fc93d36a66 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -817,6 +817,10 @@ export async function runEmbeddedPiAgent( } params.onUserMessagePersisted?.(message); }; + const continueFromCurrentTranscript = () => { + nextAttemptPromptOverride = MID_TURN_PRECHECK_CONTINUATION_PROMPT; + suppressNextUserMessagePersistence = true; + }; const maybeEscalateRateLimitProfileFallback = (params: { failoverProvider: string; failoverModel: string; @@ -1327,7 +1331,7 @@ export async function runEmbeddedPiAgent( (retryingFromTranscript ? "retrying from current transcript" : "retrying prompt"), ); if (retryingFromTranscript) { - nextAttemptPromptOverride = MID_TURN_PRECHECK_CONTINUATION_PROMPT; + continueFromCurrentTranscript(); } continue; } @@ -1512,7 +1516,7 @@ export async function runEmbeddedPiAgent( `context overflow persisted after in-attempt compaction (attempt ${overflowCompactionAttempts}/${MAX_OVERFLOW_COMPACTION_ATTEMPTS}); retrying prompt without additional compaction for ${provider}/${modelId}`, ); if (preflightRecovery?.source === "mid-turn") { - nextAttemptPromptOverride = MID_TURN_PRECHECK_CONTINUATION_PROMPT; + continueFromCurrentTranscript(); } continue; } @@ -1645,7 +1649,7 @@ export async function runEmbeddedPiAgent( autoCompactionCount += 1; log.info(`auto-compaction succeeded for ${provider}/${modelId}; retrying prompt`); if (preflightRecovery?.source === "mid-turn") { - nextAttemptPromptOverride = MID_TURN_PRECHECK_CONTINUATION_PROMPT; + continueFromCurrentTranscript(); } else if ( params.currentMessageId !== undefined && params.currentMessageId === lastPersistedCurrentMessageId @@ -1696,7 +1700,7 @@ export async function runEmbeddedPiAgent( `[context-overflow-recovery] Truncated ${truncResult.truncatedCount} tool result(s); retrying prompt`, ); if (preflightRecovery?.source === "mid-turn") { - nextAttemptPromptOverride = MID_TURN_PRECHECK_CONTINUATION_PROMPT; + continueFromCurrentTranscript(); } continue; } From 8f75a4ebdf2a6cc5e99ec93c6f25b8ac040b8301 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 22:28:30 -0700 Subject: [PATCH 2/5] ci: preserve Windows Testbox phone-home POST --- .github/workflows/windows-blacksmith-testbox.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-blacksmith-testbox.yml b/.github/workflows/windows-blacksmith-testbox.yml index f49ef6d9eab..a2c41ee18d6 100644 --- a/.github/workflows/windows-blacksmith-testbox.yml +++ b/.github/workflows/windows-blacksmith-testbox.yml @@ -65,7 +65,7 @@ jobs: fi runner_ssh_port="${BLACKSMITH_SSH_PORT:-22}" - response="$(curl -s -f -L -X POST "${api_url}/api/testbox/phone-home" \ + response="$(curl -s -f -L --post302 --post303 -X POST "${api_url}/api/testbox/phone-home" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${auth_token}" \ -d "{ @@ -155,7 +155,7 @@ jobs: } JSON - http_code="$(curl -sS -L -o "$RUNNER_TEMP/testbox-ready.response" -w '%{http_code}' \ + http_code="$(curl -sS -L --post302 --post303 -o "$RUNNER_TEMP/testbox-ready.response" -w '%{http_code}' \ -X POST "${api_url}/api/testbox/phone-home" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${auth_token}" \ From e80de466e5e150b7ecf070954f151d96164fa440 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 22:33:00 -0700 Subject: [PATCH 3/5] fix(agents): preserve full subagent announce output * fix(agents): preserve full subagent announce output * fix(agents): tighten subagent prefix fallback * fix(agents): broaden subagent prefix fallback --- CHANGELOG.md | 1 + docs/tools/subagents.md | 3 +- src/agents/subagent-announce-delivery.test.ts | 217 ++++++++++++++++++ src/agents/subagent-announce-delivery.ts | 74 +++++- 4 files changed, 293 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2b62ee2136..4295707673a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -260,6 +260,7 @@ Docs: https://docs.openclaw.ai - Plugins/install: keep managed npm-root security scans from treating earlier plugin `openclaw` peer links as failures, so one external plugin install cannot poison later official npm installs. Thanks @vincentkoc. - Memory LanceDB: allow installed-but-unconfigured plugin metadata to load so onboarding and setup flows can prompt for embedding config instead of failing the plugin registry first. Thanks @vincentkoc. - CLI/plugins: keep `plugins enable` and `plugins disable` from creating unconfigured channel config sections, so channel plugins with required setup fields no longer fail validation during lifecycle probes. Thanks @vincentkoc. +- Agents/subagents: detect prefix-only completion announce replies and fall back to the captured child result so requester chats no longer lose most of long sub-agent reports silently. Fixes #76412. Thanks @inxaos. - Doctor/config: set `messages.groupChat.visibleReplies: "message_tool"` during compatibility repair for configured-channel configs that omit a visible-reply policy, so upgrades can persist the intended tool-only group/channel reply default. Thanks @kagura-agent. - Agents/sessions: keep delayed `sessions_send` A2A replies alive after soft wait-window timeouts, while preserving terminal run timeouts and avoiding stale target replies in requester sessions. Fixes #76443. Thanks @ryswork1993 and @vincentkoc. - TUI/Control UI: fix `/think` command showing only base thinking levels when the active session uses a different model from the default, so provider-specific levels like DeepSeek V4 Pro's `xhigh` and `max` are now visible and selectable. Fixes #76482. Thanks @amknight. diff --git a/docs/tools/subagents.md b/docs/tools/subagents.md index ef79e4eb3c7..58b4290e870 100644 --- a/docs/tools/subagents.md +++ b/docs/tools/subagents.md @@ -83,7 +83,8 @@ requester chat when the run finishes. - OpenClaw tries direct `agent` delivery first with a stable idempotency key. - - If direct delivery fails, it falls back to queue routing. + - If the requester-agent completion turn fails, produces no visible output, or returns an obviously incomplete prefix of the captured child result, OpenClaw falls back to direct completion delivery from the captured child result. + - If direct delivery cannot be used, it falls back to queue routing. - If queue routing is still not available, the announce is retried with a short exponential backoff before final give-up. - Completion delivery keeps the resolved requester route: thread-bound or conversation-bound completion routes win when available; if the completion origin only provides a channel, OpenClaw fills the missing target/account from the requester session's resolved route (`lastChannel` / `lastTo` / `lastAccountId`) so direct delivery still works. diff --git a/src/agents/subagent-announce-delivery.test.ts b/src/agents/subagent-announce-delivery.test.ts index c65cb29db6a..deae9eae025 100644 --- a/src/agents/subagent-announce-delivery.test.ts +++ b/src/agents/subagent-announce-delivery.test.ts @@ -44,6 +44,13 @@ function createSendMessageMock() { })) as unknown as typeof runtimeSendMessage; } +const longChildCompletionOutput = [ + "34/34 tests pass, clean build. Now docker repro:", + "Root cause: the requester's announce delivery accepted a prefix-only assistant payload as delivered.", + "PR: https://github.com/openclaw/openclaw/pull/12345", + "Verification: pnpm test src/agents/subagent-announce-delivery.test.ts passed with the regression enabled.", +].join("\n"); + async function deliverSlackThreadAnnouncement(params: { callGateway: typeof runtimeCallGateway; isActive: boolean; @@ -583,6 +590,216 @@ describe("deliverSubagentAnnouncement completion delivery", () => { expect(sendMessage).not.toHaveBeenCalled(); }); + it("uses direct fallback when announce-agent delivery returns only a child-result prefix", async () => { + const callGateway = createGatewayMock({ + result: { + payloads: [{ text: "34/34 tests pass, clean build. Now docker repro:" }], + }, + }); + const sendMessage = createSendMessageMock(); + const result = await deliverSlackThreadAnnouncement({ + callGateway, + sendMessage, + sessionId: "requester-session-4", + isActive: false, + expectsCompletionMessage: true, + directIdempotencyKey: "announce-thread-fallback-prefix", + internalEvents: [ + { + type: "task_completion", + source: "subagent", + childSessionKey: "agent:worker:subagent:child", + childSessionId: "child-session-id", + announceType: "subagent task", + taskLabel: "thread completion smoke", + status: "ok", + statusLabel: "completed successfully", + result: longChildCompletionOutput, + replyInstruction: "Summarize the result.", + }, + ], + }); + + expect(result).toEqual( + expect.objectContaining({ + delivered: true, + path: "direct-thread-fallback", + }), + ); + expect(sendMessage).toHaveBeenCalledWith( + expect.objectContaining({ + content: longChildCompletionOutput, + idempotencyKey: "announce-thread-fallback-prefix", + }), + ); + }); + + it("uses direct fallback when announce-agent delivery returns a word-boundary child-result prefix", async () => { + const callGateway = createGatewayMock({ + result: { + payloads: [{ text: "34/34 tests pass, clean build. Now docker repro" }], + }, + }); + const sendMessage = createSendMessageMock(); + const result = await deliverSlackThreadAnnouncement({ + callGateway, + sendMessage, + sessionId: "requester-session-4", + isActive: false, + expectsCompletionMessage: true, + directIdempotencyKey: "announce-thread-fallback-word-prefix", + internalEvents: [ + { + type: "task_completion", + source: "subagent", + childSessionKey: "agent:worker:subagent:child", + childSessionId: "child-session-id", + announceType: "subagent task", + taskLabel: "thread completion smoke", + status: "ok", + statusLabel: "completed successfully", + result: longChildCompletionOutput, + replyInstruction: "Summarize the result.", + }, + ], + }); + + expect(result).toEqual( + expect.objectContaining({ + delivered: true, + path: "direct-thread-fallback", + }), + ); + expect(sendMessage).toHaveBeenCalledWith( + expect.objectContaining({ + content: longChildCompletionOutput, + idempotencyKey: "announce-thread-fallback-word-prefix", + }), + ); + }); + + it("uses direct fallback when announce-agent delivery returns a mid-word child-result prefix", async () => { + const callGateway = createGatewayMock({ + result: { + payloads: [{ text: "34/34 tests pass, clean build. Now dock" }], + }, + }); + const sendMessage = createSendMessageMock(); + const result = await deliverSlackThreadAnnouncement({ + callGateway, + sendMessage, + sessionId: "requester-session-4", + isActive: false, + expectsCompletionMessage: true, + directIdempotencyKey: "announce-thread-fallback-midword-prefix", + internalEvents: [ + { + type: "task_completion", + source: "subagent", + childSessionKey: "agent:worker:subagent:child", + childSessionId: "child-session-id", + announceType: "subagent task", + taskLabel: "thread completion smoke", + status: "ok", + statusLabel: "completed successfully", + result: longChildCompletionOutput, + replyInstruction: "Summarize the result.", + }, + ], + }); + + expect(result).toEqual( + expect.objectContaining({ + delivered: true, + path: "direct-thread-fallback", + }), + ); + expect(sendMessage).toHaveBeenCalledWith( + expect.objectContaining({ + content: longChildCompletionOutput, + idempotencyKey: "announce-thread-fallback-midword-prefix", + }), + ); + }); + + it("keeps concise requester rewrites primary even when child output is long", async () => { + const callGateway = createGatewayMock({ + result: { + payloads: [{ text: "Tests passed and the PR is ready for review." }], + }, + }); + const sendMessage = createSendMessageMock(); + const result = await deliverSlackThreadAnnouncement({ + callGateway, + sendMessage, + sessionId: "requester-session-4", + isActive: false, + expectsCompletionMessage: true, + directIdempotencyKey: "announce-thread-rewrite-primary", + internalEvents: [ + { + type: "task_completion", + source: "subagent", + childSessionKey: "agent:worker:subagent:child", + childSessionId: "child-session-id", + announceType: "subagent task", + taskLabel: "thread completion smoke", + status: "ok", + statusLabel: "completed successfully", + result: longChildCompletionOutput, + replyInstruction: "Summarize the result.", + }, + ], + }); + + expect(result).toEqual( + expect.objectContaining({ + delivered: true, + path: "direct", + }), + ); + expect(sendMessage).not.toHaveBeenCalled(); + }); + + it("keeps copied complete-sentence requester summaries primary", async () => { + const callGateway = createGatewayMock({ + result: { + payloads: [{ text: "34/34 tests pass, clean build." }], + }, + }); + const sendMessage = createSendMessageMock(); + const result = await deliverSlackThreadAnnouncement({ + callGateway, + sendMessage, + sessionId: "requester-session-4", + isActive: false, + expectsCompletionMessage: true, + directIdempotencyKey: "announce-thread-copied-summary-primary", + internalEvents: [ + { + type: "task_completion", + source: "subagent", + childSessionKey: "agent:worker:subagent:child", + childSessionId: "child-session-id", + announceType: "subagent task", + taskLabel: "thread completion smoke", + status: "ok", + statusLabel: "completed successfully", + result: longChildCompletionOutput, + replyInstruction: "Summarize the result.", + }, + ], + }); + + expect(result).toEqual( + expect.objectContaining({ + delivered: true, + path: "direct", + }), + ); + expect(sendMessage).not.toHaveBeenCalled(); + }); + it("uses a direct thread fallback when announce-agent delivery fails", async () => { const callGateway = vi.fn(async () => { throw new Error("UNAVAILABLE: gateway lost final output"); diff --git a/src/agents/subagent-announce-delivery.ts b/src/agents/subagent-announce-delivery.ts index 9b4d9156838..9a7869aa8d5 100644 --- a/src/agents/subagent-announce-delivery.ts +++ b/src/agents/subagent-announce-delivery.ts @@ -54,6 +54,9 @@ import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; const DEFAULT_SUBAGENT_ANNOUNCE_TIMEOUT_MS = 120_000; const MAX_TIMER_SAFE_TIMEOUT_MS = 2_147_000_000; +const MIN_COMPLETION_INTEGRITY_RESULT_LENGTH = 120; +const MIN_COMPLETION_INTEGRITY_PREFIX_LENGTH = 24; +const MAX_COMPLETION_INTEGRITY_PREFIX_RATIO = 0.8; type SubagentAnnounceDeliveryDeps = { callGateway: typeof callGateway; @@ -565,6 +568,75 @@ function hasVisibleGatewayAgentPayload(response: unknown): boolean { ); } +function collectVisibleGatewayAgentText(response: unknown): string { + const result = getGatewayAgentResult(response); + const payloads = result?.payloads; + if (!Array.isArray(payloads)) { + return ""; + } + return payloads + .flatMap((payload) => { + if (!payload || typeof payload !== "object") { + return []; + } + const text = (payload as { text?: unknown; isError?: unknown; isReasoning?: unknown }).text; + if (typeof text !== "string") { + return []; + } + if ( + (payload as { isError?: unknown; isReasoning?: unknown }).isError === true || + (payload as { isError?: unknown; isReasoning?: unknown }).isReasoning === true + ) { + return []; + } + const trimmed = text.trim(); + return trimmed ? [trimmed] : []; + }) + .join("\n") + .trim(); +} + +function normalizeCompletionIntegrityText(value: string): string { + return value.replace(/\s+/g, " ").trim(); +} + +function hasCompleteCompletionSummaryBoundary(value: string): boolean { + const trimmed = value.replace(/[\s"')\]]+$/g, ""); + if (!trimmed) { + return false; + } + return /[.!?]$/.test(trimmed); +} + +function hasIncompleteCompletionPrefix(response: unknown, completionFallbackText: string): boolean { + const result = getGatewayAgentResult(response); + if (!result || hasMessagingToolDeliveryEvidence(result)) { + return false; + } + const expected = normalizeCompletionIntegrityText(completionFallbackText); + if (expected.length < MIN_COMPLETION_INTEGRITY_RESULT_LENGTH) { + return false; + } + const visible = normalizeCompletionIntegrityText(collectVisibleGatewayAgentText(response)); + if ( + visible.length < MIN_COMPLETION_INTEGRITY_PREFIX_LENGTH || + visible.length >= expected.length * MAX_COMPLETION_INTEGRITY_PREFIX_RATIO + ) { + return false; + } + return expected.startsWith(visible) && !hasCompleteCompletionSummaryBoundary(visible); +} + +function shouldSendCompletionFallback(response: unknown, completionFallbackText: string): boolean { + if (!completionFallbackText) { + return false; + } + if (!hasVisibleGatewayAgentPayload(response)) { + return true; + } + return hasIncompleteCompletionPrefix(response, completionFallbackText); +} + async function sendCompletionFallback(params: { cfg: OpenClawConfig; channel?: string; @@ -840,7 +912,7 @@ async function sendSubagentAnnounceDirectly(params: { throw err; } - if (completionFallbackText && !hasVisibleGatewayAgentPayload(directAnnounceResponse)) { + if (shouldSendCompletionFallback(directAnnounceResponse, completionFallbackText)) { const didFallback = await sendCompletionFallback({ cfg, channel: deliveryTarget.channel, From ccb94a6282f747b9a652a1c82f2ab733b6c99839 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 22:31:14 -0700 Subject: [PATCH 4/5] fix(slack): keep newest rich progress lines --- CHANGELOG.md | 1 + extensions/slack/src/progress-blocks.test.ts | 14 +++++++++++--- extensions/slack/src/progress-blocks.ts | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4295707673a..43b8d683d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai - Channels/streaming: add unified `streaming.mode: "progress"` drafts with auto single-word status labels and shared progress configuration across Discord, Telegram, Matrix, Slack, and Microsoft Teams. - Slack/streaming: add `streaming.progress.render: "rich"` for Block Kit progress drafts backed by structured progress line data. +- Slack/streaming: keep the newest rich progress lines when Block Kit limits trim long progress drafts. Thanks @vincentkoc. - Channels/streaming: cap progress-draft tool lines by default so edited progress boxes avoid jumpy reflow from long wrapped lines. - Agents/verbose: use compact explain-mode tool summaries for `/verbose` and progress drafts by default, with `agents.defaults.toolProgressDetail: "raw"` and per-agent overrides for debugging raw command/detail output. - Agents/commands: add `/steer ` for queue-independent steering of the active current-session run without starting a new turn when the session is idle. (#76934) diff --git a/extensions/slack/src/progress-blocks.test.ts b/extensions/slack/src/progress-blocks.test.ts index 854401a8c87..5aa6875293e 100644 --- a/extensions/slack/src/progress-blocks.test.ts +++ b/extensions/slack/src/progress-blocks.test.ts @@ -64,7 +64,7 @@ describe("buildSlackProgressDraftBlocks", () => { }); }); - it("caps rich progress blocks to Slack's maximum while leaving caller text fallback independent", () => { + it("keeps newest rich progress lines when capping Slack blocks", () => { const blocksWithLabel = buildSlackProgressDraftBlocks({ label: "Shelling...", lines: Array.from({ length: 60 }, (_value, index) => progressLine(index)), @@ -74,18 +74,26 @@ describe("buildSlackProgressDraftBlocks", () => { type: "section", text: { text: "*Shelling...*" }, }); + expect(blocksWithLabel?.[1]).toMatchObject({ + type: "section", + fields: [{ text: "🛠️ *Exec 11*" }, { text: "run 11" }], + }); expect(blocksWithLabel?.at(-1)).toMatchObject({ type: "section", - fields: [{ text: "🛠️ *Exec 48*" }, { text: "run 48" }], + fields: [{ text: "🛠️ *Exec 59*" }, { text: "run 59" }], }); const blocksWithoutLabel = buildSlackProgressDraftBlocks({ lines: Array.from({ length: 60 }, (_value, index) => progressLine(index)), }); expect(blocksWithoutLabel).toHaveLength(50); + expect(blocksWithoutLabel?.[0]).toMatchObject({ + type: "section", + fields: [{ text: "🛠️ *Exec 10*" }, { text: "run 10" }], + }); expect(blocksWithoutLabel?.at(-1)).toMatchObject({ type: "section", - fields: [{ text: "🛠️ *Exec 49*" }, { text: "run 49" }], + fields: [{ text: "🛠️ *Exec 59*" }, { text: "run 59" }], }); }); }); diff --git a/extensions/slack/src/progress-blocks.ts b/extensions/slack/src/progress-blocks.ts index b1339a6848e..8b1ba33a9c1 100644 --- a/extensions/slack/src/progress-blocks.ts +++ b/extensions/slack/src/progress-blocks.ts @@ -55,7 +55,7 @@ export function buildSlackProgressDraftBlocks(params: { }); } const availableLineBlocks = Math.max(0, SLACK_MAX_BLOCKS - blocks.length); - for (const line of params.lines.slice(0, availableLineBlocks)) { + for (const line of params.lines.slice(-availableLineBlocks)) { blocks.push({ type: "section", fields: [field(lineTitle(line)), field(lineDetail(line))], From 5ab18100e285e0bcadb7a29830f5c9904f91ff64 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 22:37:16 -0700 Subject: [PATCH 5/5] docs(changelog): credit subagent announce fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43b8d683d59..549269edf5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -261,7 +261,7 @@ Docs: https://docs.openclaw.ai - Plugins/install: keep managed npm-root security scans from treating earlier plugin `openclaw` peer links as failures, so one external plugin install cannot poison later official npm installs. Thanks @vincentkoc. - Memory LanceDB: allow installed-but-unconfigured plugin metadata to load so onboarding and setup flows can prompt for embedding config instead of failing the plugin registry first. Thanks @vincentkoc. - CLI/plugins: keep `plugins enable` and `plugins disable` from creating unconfigured channel config sections, so channel plugins with required setup fields no longer fail validation during lifecycle probes. Thanks @vincentkoc. -- Agents/subagents: detect prefix-only completion announce replies and fall back to the captured child result so requester chats no longer lose most of long sub-agent reports silently. Fixes #76412. Thanks @inxaos. +- Agents/subagents: detect prefix-only completion announce replies and fall back to the captured child result so requester chats no longer lose most of long sub-agent reports silently. Fixes #76412. Thanks @inxaos and @davemorin. - Doctor/config: set `messages.groupChat.visibleReplies: "message_tool"` during compatibility repair for configured-channel configs that omit a visible-reply policy, so upgrades can persist the intended tool-only group/channel reply default. Thanks @kagura-agent. - Agents/sessions: keep delayed `sessions_send` A2A replies alive after soft wait-window timeouts, while preserving terminal run timeouts and avoiding stale target replies in requester sessions. Fixes #76443. Thanks @ryswork1993 and @vincentkoc. - TUI/Control UI: fix `/think` command showing only base thinking levels when the active session uses a different model from the default, so provider-specific levels like DeepSeek V4 Pro's `xhigh` and `max` are now visible and selectable. Fixes #76482. Thanks @amknight.