From 33773a4ddebcecc44e47cadfff01f46b0cad6f81 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sat, 21 Mar 2026 08:36:45 +0530 Subject: [PATCH] fix: remove timeout-only subagent path --- src/agents/subagent-announce.ts | 123 +------------------------------- 1 file changed, 1 insertion(+), 122 deletions(-) diff --git a/src/agents/subagent-announce.ts b/src/agents/subagent-announce.ts index a04f0da5e2e..ae7c96eb4d6 100644 --- a/src/agents/subagent-announce.ts +++ b/src/agents/subagent-announce.ts @@ -50,7 +50,7 @@ import { isAnnounceSkip } from "./tools/sessions-send-helpers.js"; const FAST_TEST_MODE = process.env.OPENCLAW_TEST_FAST === "1"; const FAST_TEST_RETRY_INTERVAL_MS = 8; -const DEFAULT_SUBAGENT_ANNOUNCE_TIMEOUT_MS = 60_000; +const DEFAULT_SUBAGENT_ANNOUNCE_TIMEOUT_MS = 90_000; const MAX_TIMER_SAFE_TIMEOUT_MS = 2_147_000_000; let subagentRegistryRuntimePromise: Promise< typeof import("./subagent-registry-runtime.js") @@ -392,103 +392,6 @@ async function readSubagentOutput( return selectSubagentOutputText(summarizeSubagentOutputHistory(messages), outcome); } -/** - * Collect partial progress from a timed-out subagent session. - * - * Unlike `readLatestSubagentOutput` which returns only the last message, - * this function scans the full history to build a progress summary from - * all assistant messages. This is critical for timeout scenarios where - * the subagent may have produced intermediate results across multiple - * tool-call rounds but no final summary. - * - * @see https://github.com/openclaw/openclaw/issues/33827 - */ -async function readSubagentPartialProgress(sessionKey: string): Promise { - let history: { messages?: Array } | undefined; - try { - history = await callGateway<{ messages?: Array }>({ - method: "chat.history", - params: { sessionKey, limit: 100 }, - }); - } catch { - return undefined; - } - const messages = Array.isArray(history?.messages) ? history.messages : []; - if (messages.length === 0) { - return undefined; - } - - // Collect all assistant text fragments (partial results from each turn). - const assistantFragments: string[] = []; - let toolCallCount = 0; - let silentAssistantOverrideText: string | undefined; - - for (const msg of messages) { - if (!msg || typeof msg !== "object") { - continue; - } - const role = (msg as { role?: unknown }).role; - if (role === "assistant") { - const text = extractSubagentOutputText(msg); - if (text?.trim()) { - const trimmedText = text.trim(); - if (isAnnounceSkip(trimmedText) || isSilentReplyText(trimmedText, SILENT_REPLY_TOKEN)) { - // Preserve explicit silence semantics across timeout fallback synthesis. - // If any assistant turn asked to stay silent, do not turn earlier - // partial progress into a user-visible completion. - silentAssistantOverrideText = trimmedText; - } else { - assistantFragments.push(trimmedText); - } - } - // Count tool calls to report progress depth. - const content = (msg as { content?: unknown }).content; - if (Array.isArray(content)) { - for (const block of content) { - if ( - block && - typeof block === "object" && - ((block as { type?: string }).type === "toolCall" || - (block as { type?: string }).type === "tool_use" || - (block as { type?: string }).type === "toolUse" || - (block as { type?: string }).type === "functionCall" || - (block as { type?: string }).type === "function_call") - ) { - toolCallCount += 1; - } - } - } - } - } - - if (silentAssistantOverrideText) { - return silentAssistantOverrideText; - } - - if (assistantFragments.length === 0 && toolCallCount === 0) { - return undefined; - } - - const parts: string[] = []; - if (toolCallCount > 0) { - parts.push(`[Partial progress: ${toolCallCount} tool call(s) executed before timeout]`); - } - if (assistantFragments.length > 0) { - // Return the last (most recent) assistant fragment as the primary result, - // but include earlier fragments if the last one is short. - const lastFragment = assistantFragments[assistantFragments.length - 1]; - if (assistantFragments.length > 1 && lastFragment.length < 200) { - // Include up to 3 most recent fragments for context. - const recentFragments = assistantFragments.slice(-3); - parts.push(recentFragments.join("\n\n---\n\n")); - } else { - parts.push(lastFragment); - } - } - - return parts.join("\n\n") || undefined; -} - async function readLatestSubagentOutputWithRetry(params: { sessionKey: string; maxWaitMs: number; @@ -1497,30 +1400,6 @@ export async function runSubagentAnnounceFlow(params: { }); } - // For timed-out runs, attempt to collect partial progress from the full - // session history. The subagent may have produced useful intermediate - // results across multiple tool-call rounds even though no final assistant - // reply was generated before the timeout. Use the richer partial progress - // when it contains more context than the simple last-message extraction. - if (outcome.status === "timeout") { - const partialProgress = await readSubagentPartialProgress(params.childSessionKey); - // Do not overwrite recognized silent/skip tokens with partial progress — - // that would cause the parent to announce when it should stay silent. - const replyIsSilent = - reply?.trim() && (isAnnounceSkip(reply) || isSilentReplyText(reply, SILENT_REPLY_TOKEN)); - const partialProgressIsSilent = - partialProgress?.trim() && - (isAnnounceSkip(partialProgress) || - isSilentReplyText(partialProgress, SILENT_REPLY_TOKEN)); - if ( - !replyIsSilent && - partialProgress?.trim() && - (partialProgressIsSilent || !reply?.trim() || partialProgress.length > reply.length) - ) { - reply = partialProgress; - } - } - if (!reply?.trim() && fallbackReply && !fallbackIsSilent) { reply = fallbackReply; }