refactor: unify subagent output selection
This commit is contained in:
parent
277a0e5c13
commit
ea0f2cd5fb
@ -45,7 +45,6 @@ import {
|
||||
import { type AnnounceQueueItem, enqueueAnnounce } from "./subagent-announce-queue.js";
|
||||
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
|
||||
import type { SpawnSubagentMode } from "./subagent-spawn.js";
|
||||
import { readLatestAssistantReply } from "./tools/agent-step.js";
|
||||
import { sanitizeTextContent, extractAssistantText } from "./tools/sessions-helpers.js";
|
||||
import { isAnnounceSkip } from "./tools/sessions-send-helpers.js";
|
||||
|
||||
@ -71,6 +70,14 @@ type ToolResultMessage = {
|
||||
content?: unknown;
|
||||
};
|
||||
|
||||
type SubagentOutputSnapshot = {
|
||||
latestAssistantText?: string;
|
||||
latestSilentText?: string;
|
||||
latestRawText?: string;
|
||||
assistantFragments: string[];
|
||||
toolCallCount: number;
|
||||
};
|
||||
|
||||
function resolveSubagentAnnounceTimeoutMs(cfg: ReturnType<typeof loadConfig>): number {
|
||||
const configured = cfg.agents?.defaults?.subagents?.announceTimeoutMs;
|
||||
if (typeof configured !== "number" || !Number.isFinite(configured)) {
|
||||
@ -275,31 +282,114 @@ function extractSubagentOutputText(message: unknown): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
async function readLatestSubagentOutput(sessionKey: string): Promise<string | undefined> {
|
||||
try {
|
||||
const latestAssistant = await readLatestAssistantReply({
|
||||
sessionKey,
|
||||
limit: 50,
|
||||
});
|
||||
if (latestAssistant?.trim()) {
|
||||
return latestAssistant;
|
||||
}
|
||||
} catch {
|
||||
// Best-effort: fall back to richer history parsing below.
|
||||
function countAssistantToolCalls(content: unknown): number {
|
||||
if (!Array.isArray(content)) {
|
||||
return 0;
|
||||
}
|
||||
let count = 0;
|
||||
for (const block of content) {
|
||||
if (!block || typeof block !== "object") {
|
||||
continue;
|
||||
}
|
||||
const type = (block as { type?: unknown }).type;
|
||||
if (
|
||||
type === "toolCall" ||
|
||||
type === "tool_use" ||
|
||||
type === "toolUse" ||
|
||||
type === "functionCall" ||
|
||||
type === "function_call"
|
||||
) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function summarizeSubagentOutputHistory(messages: Array<unknown>): SubagentOutputSnapshot {
|
||||
const snapshot: SubagentOutputSnapshot = {
|
||||
assistantFragments: [],
|
||||
toolCallCount: 0,
|
||||
};
|
||||
for (const message of messages) {
|
||||
if (!message || typeof message !== "object") {
|
||||
continue;
|
||||
}
|
||||
const role = (message as { role?: unknown }).role;
|
||||
if (role === "assistant") {
|
||||
snapshot.toolCallCount += countAssistantToolCalls((message as { content?: unknown }).content);
|
||||
const text = extractSubagentOutputText(message).trim();
|
||||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
if (isAnnounceSkip(text) || isSilentReplyText(text, SILENT_REPLY_TOKEN)) {
|
||||
snapshot.latestSilentText = text;
|
||||
snapshot.latestAssistantText = undefined;
|
||||
snapshot.assistantFragments = [];
|
||||
continue;
|
||||
}
|
||||
snapshot.latestSilentText = undefined;
|
||||
snapshot.latestAssistantText = text;
|
||||
snapshot.assistantFragments.push(text);
|
||||
continue;
|
||||
}
|
||||
const text = extractSubagentOutputText(message).trim();
|
||||
if (text) {
|
||||
snapshot.latestRawText = text;
|
||||
}
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
function formatSubagentPartialProgress(
|
||||
snapshot: SubagentOutputSnapshot,
|
||||
outcome?: SubagentRunOutcome,
|
||||
): string | undefined {
|
||||
if (snapshot.latestSilentText) {
|
||||
return undefined;
|
||||
}
|
||||
const timedOut = outcome?.status === "timeout";
|
||||
if (snapshot.assistantFragments.length === 0 && (!timedOut || snapshot.toolCallCount === 0)) {
|
||||
return undefined;
|
||||
}
|
||||
const parts: string[] = [];
|
||||
if (timedOut && snapshot.toolCallCount > 0) {
|
||||
parts.push(
|
||||
`[Partial progress: ${snapshot.toolCallCount} tool call(s) executed before timeout]`,
|
||||
);
|
||||
}
|
||||
if (snapshot.assistantFragments.length > 0) {
|
||||
parts.push(snapshot.assistantFragments.slice(-3).join("\n\n---\n\n"));
|
||||
}
|
||||
return parts.join("\n\n") || undefined;
|
||||
}
|
||||
|
||||
function selectSubagentOutputText(
|
||||
snapshot: SubagentOutputSnapshot,
|
||||
outcome?: SubagentRunOutcome,
|
||||
): string | undefined {
|
||||
if (snapshot.latestSilentText) {
|
||||
return snapshot.latestSilentText;
|
||||
}
|
||||
if (snapshot.latestAssistantText) {
|
||||
return snapshot.latestAssistantText;
|
||||
}
|
||||
const partialProgress = formatSubagentPartialProgress(snapshot, outcome);
|
||||
if (partialProgress) {
|
||||
return partialProgress;
|
||||
}
|
||||
return snapshot.latestRawText;
|
||||
}
|
||||
|
||||
async function readSubagentOutput(
|
||||
sessionKey: string,
|
||||
outcome?: SubagentRunOutcome,
|
||||
): Promise<string | undefined> {
|
||||
const history = await callGateway<{ messages?: Array<unknown> }>({
|
||||
method: "chat.history",
|
||||
params: { sessionKey, limit: 50 },
|
||||
params: { sessionKey, limit: 100 },
|
||||
});
|
||||
const messages = Array.isArray(history?.messages) ? history.messages : [];
|
||||
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
||||
const msg = messages[i];
|
||||
const text = extractSubagentOutputText(msg);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return selectSubagentOutputText(summarizeSubagentOutputHistory(messages), outcome);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -402,12 +492,13 @@ async function readSubagentPartialProgress(sessionKey: string): Promise<string |
|
||||
async function readLatestSubagentOutputWithRetry(params: {
|
||||
sessionKey: string;
|
||||
maxWaitMs: number;
|
||||
outcome?: SubagentRunOutcome;
|
||||
}): Promise<string | undefined> {
|
||||
const RETRY_INTERVAL_MS = FAST_TEST_MODE ? FAST_TEST_RETRY_INTERVAL_MS : 100;
|
||||
const deadline = Date.now() + Math.max(0, Math.min(params.maxWaitMs, 15_000));
|
||||
let result: string | undefined;
|
||||
while (Date.now() < deadline) {
|
||||
result = await readLatestSubagentOutput(params.sessionKey);
|
||||
result = await readSubagentOutput(params.sessionKey, params.outcome);
|
||||
if (result?.trim()) {
|
||||
return result;
|
||||
}
|
||||
@ -419,7 +510,7 @@ async function readLatestSubagentOutputWithRetry(params: {
|
||||
export async function captureSubagentCompletionReply(
|
||||
sessionKey: string,
|
||||
): Promise<string | undefined> {
|
||||
const immediate = await readLatestSubagentOutput(sessionKey);
|
||||
const immediate = await readSubagentOutput(sessionKey);
|
||||
if (immediate?.trim()) {
|
||||
return immediate;
|
||||
}
|
||||
@ -1395,13 +1486,14 @@ export async function runSubagentAnnounceFlow(params: {
|
||||
(isAnnounceSkip(fallbackReply) || isSilentReplyText(fallbackReply, SILENT_REPLY_TOKEN));
|
||||
|
||||
if (!reply) {
|
||||
reply = await readLatestSubagentOutput(params.childSessionKey);
|
||||
reply = await readSubagentOutput(params.childSessionKey, outcome);
|
||||
}
|
||||
|
||||
if (!reply?.trim()) {
|
||||
reply = await readLatestSubagentOutputWithRetry({
|
||||
sessionKey: params.childSessionKey,
|
||||
maxWaitMs: params.timeoutMs,
|
||||
outcome,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user