fix(worker): count sub-agent cost and surface compaction failures

This commit is contained in:
ezl-keygraph
2026-06-15 15:59:55 +05:30
parent 79fb49c159
commit 56241625a4
2 changed files with 28 additions and 1 deletions
+13 -1
View File
@@ -230,12 +230,16 @@ export async function runClaudePrompt(
// Load the code_path deny extension only when a deny config was written; the same
// loader is reused by child task sessions so they inherit the policy.
const resourceLoader = await buildPermissionResourceLoader(sourceDir, logger);
// Accumulates cost from in-process `task` child sessions so the parent's reported
// cost includes sub-agent spend (their getSessionStats is separate from ours).
const childUsage = { cost: 0 };
const customTools: ToolDefinition[] = [
createTaskTool({
model: selection.model,
thinkingLevel: selection.thinkingLevel,
authStorage: selection.authStorage,
cwd: sourceDir,
childUsage,
...(resourceLoader && { resourceLoader }),
}),
createTodoWriteTool(auditLogger),
@@ -292,6 +296,14 @@ export async function runClaudePrompt(
case 'tool_execution_end':
void auditLogger.logToolEnd(event.result);
break;
case 'compaction_end':
if (!event.aborted && !event.willRetry && event.errorMessage) {
pendingError =
pendingError ??
classifyErrorText(event.errorMessage) ??
new PentestError(`Context compaction failed: ${event.errorMessage.slice(0, 200)}`, 'unknown', true);
}
break;
default:
break;
}
@@ -306,7 +318,7 @@ export async function runClaudePrompt(
// 8. Read usage/cost and final text.
const stats = session.getSessionStats();
const totalCost = stats.cost;
const totalCost = stats.cost + childUsage.cost;
const result = session.getLastAssistantText() ?? null;
// 9. Defense-in-depth: detect a spending cap that produced an empty/cheap run.
+15
View File
@@ -37,6 +37,12 @@ export interface TaskToolContext {
cwd: string;
/** When set, child sessions inherit the code_path deny policy. */
resourceLoader?: ResourceLoader;
/**
* Mutable accumulator: each child (sub-agent) session's cost is added here so the
* parent executor can include sub-agent spend in its reported cost. Child sessions
* keep their own `getSessionStats`, separate from the parent's.
*/
childUsage?: { cost: number };
}
/**
@@ -80,6 +86,15 @@ export function createTaskTool(ctx: TaskToolContext): ToolDefinition {
const text = child.getLastAssistantText() ?? '(sub-agent produced no output)';
return { content: [{ type: 'text' as const, text }], details: {} };
} finally {
// Roll the child's cost up to the parent before disposing (best-effort, and
// captured in `finally` so a failed child's partial spend still counts).
if (ctx.childUsage) {
try {
ctx.childUsage.cost += child.getSessionStats().cost;
} catch {
// ignore — cost capture is best-effort
}
}
child.dispose();
}
},