pi-subagents 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +85 -13
- package/agents/worker.md +1 -0
- package/package.json +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +4 -3
- package/skills/pi-subagents/references/execution-controls.md +22 -3
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +17 -3
- package/src/agents/agent-management.ts +71 -30
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/agents.ts +60 -1
- package/src/api/preflight.ts +16 -12
- package/src/extension/chain-validation.ts +17 -1
- package/src/extension/rpc.ts +2 -0
- package/src/extension/schemas.ts +16 -3
- package/src/extension/tool-description.ts +6 -5
- package/src/intercom/result-intercom.ts +33 -4
- package/src/runs/background/async-execution.ts +63 -16
- package/src/runs/background/async-job-tracker.ts +5 -1
- package/src/runs/background/async-resume.ts +1 -0
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/chain-append.ts +48 -5
- package/src/runs/background/control-channel.ts +60 -0
- package/src/runs/background/notify.ts +26 -4
- package/src/runs/background/result-watcher.ts +20 -3
- package/src/runs/background/run-status.ts +29 -3
- package/src/runs/background/subagent-runner.ts +302 -18
- package/src/runs/foreground/async-stop-action.ts +65 -0
- package/src/runs/foreground/chain-execution.ts +143 -43
- package/src/runs/foreground/execution.ts +384 -202
- package/src/runs/foreground/foreground-control.ts +30 -0
- package/src/runs/foreground/subagent-executor.ts +314 -77
- package/src/runs/shared/capability-ceiling.ts +51 -19
- package/src/runs/shared/chain-outputs.ts +3 -1
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/nested-events.ts +97 -20
- package/src/runs/shared/parallel-utils.ts +43 -12
- package/src/runs/shared/pi-args.ts +45 -3
- package/src/runs/shared/process-signal.ts +19 -0
- package/src/runs/shared/run-history.ts +45 -9
- package/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +30 -0
- package/src/runs/shared/usage-budget.ts +65 -0
- package/src/runs/shared/workflow-graph.ts +26 -1
- package/src/shared/settings.ts +17 -1
- package/src/shared/types.ts +124 -8
- package/src/shared/utils.ts +12 -1
- package/src/slash/slash-commands.ts +43 -1
- package/src/slash/slash-live-state.ts +5 -3
- package/src/tui/fleet-status.ts +12 -2
- package/src/tui/fleet.ts +181 -8
- package/src/tui/render.ts +4 -2
- package/src/watchdog/register-main.ts +14 -7
- package/src/watchdog/review.ts +4 -3
- package/src/watchdog/runtime.ts +170 -17
- package/src/watchdog/scope.ts +62 -0
- package/src/watchdog/settings.ts +41 -1
- package/src/watchdog/types.ts +10 -0
|
@@ -3,12 +3,19 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import type
|
|
6
|
+
import { resolveAgentName, type AgentConfig, type AgentScope } from "../../agents/agents.ts";
|
|
7
7
|
import { getArtifactsDir, getProjectChainRunsDir } from "../../shared/artifacts.ts";
|
|
8
8
|
import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
|
|
9
|
-
import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
9
|
+
import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
10
10
|
import { executeChain } from "./chain-execution.ts";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
beginForegroundChild,
|
|
13
|
+
finishForegroundChild,
|
|
14
|
+
foregroundSchedulingSettled,
|
|
15
|
+
retainForegroundSchedulingOwner,
|
|
16
|
+
settleForegroundSchedulingOwner,
|
|
17
|
+
updateForegroundChild,
|
|
18
|
+
} from "./foreground-control.ts";
|
|
12
19
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
13
20
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
14
21
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
@@ -47,6 +54,7 @@ import { formatControlIntercomMessage, formatControlNoticeMessage, resolveContro
|
|
|
47
54
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
48
55
|
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
49
56
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
57
|
+
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
50
58
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
51
59
|
import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
52
60
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
@@ -64,12 +72,13 @@ import {
|
|
|
64
72
|
stripDetailsOutputsForIntercomReceipt,
|
|
65
73
|
} from "../../intercom/result-intercom.ts";
|
|
66
74
|
import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
67
|
-
import {
|
|
75
|
+
import { deliverCheckpointDecisionRequest, deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
|
|
68
76
|
import { waitForSteeringAction } from "../background/steering.ts";
|
|
69
77
|
import { steerAsyncRun } from "./async-steering-action.ts";
|
|
78
|
+
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
70
79
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
71
80
|
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
72
|
-
import { attachRootChildrenToSteps, createNestedRoute,
|
|
81
|
+
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
73
82
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
74
83
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
75
84
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
@@ -106,6 +115,7 @@ import {
|
|
|
106
115
|
type SingleResult,
|
|
107
116
|
type ToolBudgetConfig,
|
|
108
117
|
type TurnBudgetConfig,
|
|
118
|
+
type UsageBudgetConfig,
|
|
109
119
|
type SubagentRunMode,
|
|
110
120
|
type SubagentState,
|
|
111
121
|
ASYNC_DIR,
|
|
@@ -167,6 +177,7 @@ export interface SubagentParamsLike {
|
|
|
167
177
|
/** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
|
|
168
178
|
enforceHardTurnLimit?: boolean;
|
|
169
179
|
toolBudget?: ToolBudgetConfig;
|
|
180
|
+
usageBudget?: UsageBudgetConfig;
|
|
170
181
|
clarify?: boolean;
|
|
171
182
|
share?: boolean;
|
|
172
183
|
control?: ControlConfig;
|
|
@@ -245,6 +256,7 @@ interface ExecutionContextData {
|
|
|
245
256
|
deadlineAt?: number;
|
|
246
257
|
turnBudget?: ResolvedTurnBudget;
|
|
247
258
|
toolBudget?: ResolvedToolBudget;
|
|
259
|
+
usageBudget?: UsageBudgetConfig;
|
|
248
260
|
allowZeroToolBudget?: boolean;
|
|
249
261
|
configToolBudget?: ResolvedToolBudget;
|
|
250
262
|
contextPolicy: AgentDefaultContextPolicy;
|
|
@@ -258,6 +270,15 @@ function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefine
|
|
|
258
270
|
return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
259
271
|
}
|
|
260
272
|
|
|
273
|
+
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
274
|
+
const control = state.foregroundControls.get(runId);
|
|
275
|
+
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
276
|
+
clearPendingForegroundControlNotices(state, runId);
|
|
277
|
+
state.foregroundControls.delete(runId);
|
|
278
|
+
if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
261
282
|
function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
262
283
|
if (runId) return state.foregroundControls.get(runId);
|
|
263
284
|
if (state.lastForegroundControlId) {
|
|
@@ -392,7 +413,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
|
|
|
392
413
|
};
|
|
393
414
|
}
|
|
394
415
|
|
|
395
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
|
|
416
|
+
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; checkpoint?: Details["checkpoint"] }): void {
|
|
396
417
|
state.foregroundRuns ??= new Map();
|
|
397
418
|
const previous = state.foregroundRuns.get(input.runId);
|
|
398
419
|
const updatedAt = Date.now();
|
|
@@ -402,17 +423,27 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
402
423
|
cwd: input.cwd,
|
|
403
424
|
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
404
425
|
updatedAt,
|
|
426
|
+
...(input.checkpoint ? { checkpoint: input.checkpoint } : {}),
|
|
405
427
|
children: input.results.map((result, index) => {
|
|
406
428
|
const child = {
|
|
407
429
|
agent: result.agent,
|
|
408
430
|
index,
|
|
409
431
|
...(result.context ? { context: result.context } : {}),
|
|
410
|
-
status: resolveSubagentResultStatus({
|
|
432
|
+
status: resolveSubagentResultStatus({
|
|
433
|
+
exitCode: result.exitCode,
|
|
434
|
+
interrupted: result.interrupted,
|
|
435
|
+
detached: result.detached,
|
|
436
|
+
processSignal: result.processSignal,
|
|
437
|
+
timedOut: result.timedOut,
|
|
438
|
+
stopped: result.stopped,
|
|
439
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
440
|
+
}),
|
|
411
441
|
...foregroundChildActivityFromProgress(result.progress),
|
|
412
442
|
updatedAt,
|
|
413
443
|
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
414
444
|
...(result.error ? { error: result.error } : {}),
|
|
415
445
|
...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
|
|
446
|
+
...(result.outputState ? { outputState: result.outputState } : {}),
|
|
416
447
|
...(result.outputMode ? { outputMode: result.outputMode } : {}),
|
|
417
448
|
...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
|
|
418
449
|
...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
|
|
@@ -425,6 +456,8 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
425
456
|
...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
|
|
426
457
|
...(result.acceptance ? { acceptance: result.acceptance } : {}),
|
|
427
458
|
...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
|
|
459
|
+
...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
|
|
460
|
+
...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
|
|
428
461
|
...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
|
|
429
462
|
...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
|
|
430
463
|
};
|
|
@@ -467,20 +500,29 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
467
500
|
state.foregroundRuns.set(input.runId, run);
|
|
468
501
|
}
|
|
469
502
|
run.updatedAt = updatedAt;
|
|
503
|
+
const terminalStatus = resolveSubagentResultStatus({
|
|
504
|
+
exitCode: input.result.exitCode,
|
|
505
|
+
...(input.result.acceptance?.status === "rejected" ? { success: false } : {}),
|
|
506
|
+
interrupted: input.result.interrupted,
|
|
507
|
+
detached: false,
|
|
508
|
+
processSignal: input.result.processSignal,
|
|
509
|
+
timedOut: input.result.timedOut,
|
|
510
|
+
stopped: input.result.stopped,
|
|
511
|
+
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
512
|
+
});
|
|
470
513
|
const child = run.children[input.index] ?? { agent: input.result.agent, index: input.index, status: "detached" as const };
|
|
471
514
|
run.children[input.index] = {
|
|
472
515
|
...child,
|
|
473
516
|
agent: input.result.agent,
|
|
474
517
|
index: input.index,
|
|
475
518
|
...(input.result.context ? { context: input.result.context } : {}),
|
|
476
|
-
status:
|
|
477
|
-
? "failed"
|
|
478
|
-
: resolveSubagentResultStatus({ exitCode: input.result.exitCode, interrupted: input.result.interrupted, detached: false }),
|
|
519
|
+
status: terminalStatus,
|
|
479
520
|
...foregroundChildActivityFromProgress(input.result.progress),
|
|
480
521
|
updatedAt,
|
|
481
522
|
...(input.result.exitCode !== undefined ? { exitCode: input.result.exitCode } : {}),
|
|
482
523
|
...(input.result.error ? { error: input.result.error } : {}),
|
|
483
524
|
...(input.result.finalOutput ? { finalOutput: input.result.finalOutput } : {}),
|
|
525
|
+
outputState: input.result.outputState,
|
|
484
526
|
outputMode: input.result.outputMode,
|
|
485
527
|
savedOutputPath: input.result.savedOutputPath,
|
|
486
528
|
outputSaveError: input.result.outputSaveError,
|
|
@@ -493,12 +535,14 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
493
535
|
...(input.result.detachedReason ? { detachedReason: input.result.detachedReason } : {}),
|
|
494
536
|
...(input.result.acceptance ? { acceptance: input.result.acceptance } : {}),
|
|
495
537
|
...(input.result.launchContractDigest ? { launchContractDigest: input.result.launchContractDigest } : {}),
|
|
538
|
+
...(input.result.launchResolvedExtensions ? { launchResolvedExtensions: input.result.launchResolvedExtensions } : {}),
|
|
539
|
+
...(input.result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: input.result.runtimeAcknowledgedExtensions } : {}),
|
|
496
540
|
...(input.result.capabilityCeiling ? { capabilityCeiling: input.result.capabilityCeiling } : {}),
|
|
497
541
|
...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
|
|
498
542
|
};
|
|
499
543
|
trimRememberedForegroundRuns(state);
|
|
500
544
|
const output = getSingleResultOutput(input.result).trim();
|
|
501
|
-
const success =
|
|
545
|
+
const success = terminalStatus === "completed";
|
|
502
546
|
const summary = !success && input.result.error
|
|
503
547
|
? `${input.result.error}${output ? `\n\nOutput:\n${output}` : ""}`
|
|
504
548
|
: output || input.result.error || "Detached child exited without final output.";
|
|
@@ -514,7 +558,12 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
514
558
|
success,
|
|
515
559
|
summary,
|
|
516
560
|
exitCode: input.result.exitCode,
|
|
517
|
-
state:
|
|
561
|
+
state: terminalStatus === "completed" ? "complete" : terminalStatus,
|
|
562
|
+
...(input.result.interrupted !== undefined ? { interrupted: input.result.interrupted } : {}),
|
|
563
|
+
...(input.result.stopped !== undefined ? { stopped: input.result.stopped } : {}),
|
|
564
|
+
...(input.result.processSignal !== undefined ? { processSignal: input.result.processSignal } : {}),
|
|
565
|
+
...(input.result.timedOut !== undefined ? { timedOut: input.result.timedOut } : {}),
|
|
566
|
+
...(input.result.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: input.result.turnBudgetExceeded } : {}),
|
|
518
567
|
timestamp: updatedAt,
|
|
519
568
|
cwd: input.cwd,
|
|
520
569
|
sessionFile: input.result.sessionFile,
|
|
@@ -733,50 +782,6 @@ function interruptAsyncRun(
|
|
|
733
782
|
}
|
|
734
783
|
}
|
|
735
784
|
|
|
736
|
-
function stopAsyncRun(
|
|
737
|
-
state: SubagentState,
|
|
738
|
-
runId: string | undefined,
|
|
739
|
-
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean,
|
|
740
|
-
location?: { asyncDir: string | null; resolvedId?: string },
|
|
741
|
-
): AgentToolResult<Details> | null {
|
|
742
|
-
const target = getAsyncInterruptTarget(state, runId, location, { fallbackToNewest: false });
|
|
743
|
-
if (!target) return null;
|
|
744
|
-
const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
|
|
745
|
-
if (state.currentSessionId && status?.sessionId !== state.currentSessionId) {
|
|
746
|
-
return {
|
|
747
|
-
content: [{ type: "text", text: `Async run '${target.asyncId}' was not found in the active session.` }],
|
|
748
|
-
isError: true,
|
|
749
|
-
details: { mode: "management", results: [] },
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
if (!status || (status.state !== "running" && status.state !== "queued")) {
|
|
753
|
-
return {
|
|
754
|
-
content: [{ type: "text", text: `No running or queued async run was found for '${runId ?? "current"}'.` }],
|
|
755
|
-
isError: true,
|
|
756
|
-
details: { mode: "management", results: [] },
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
try {
|
|
760
|
-
deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action" });
|
|
761
|
-
const tracked = state.asyncJobs.get(target.asyncId);
|
|
762
|
-
if (tracked) {
|
|
763
|
-
tracked.activityState = undefined;
|
|
764
|
-
tracked.updatedAt = Date.now();
|
|
765
|
-
}
|
|
766
|
-
return {
|
|
767
|
-
content: [{ type: "text", text: `Stop requested for async run ${target.asyncId}.` }],
|
|
768
|
-
details: { mode: "management", results: [] },
|
|
769
|
-
};
|
|
770
|
-
} catch (error) {
|
|
771
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
772
|
-
return {
|
|
773
|
-
content: [{ type: "text", text: `Failed to stop async run ${target.asyncId}: ${message}` }],
|
|
774
|
-
isError: true,
|
|
775
|
-
details: { mode: "management", results: [] },
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
785
|
function duplicateNames(names: string[]): string[] {
|
|
781
786
|
const seen = new Set<string>();
|
|
782
787
|
const duplicates = new Set<string>();
|
|
@@ -1032,10 +1037,10 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
|
|
|
1032
1037
|
};
|
|
1033
1038
|
}
|
|
1034
1039
|
|
|
1035
|
-
async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind: "nested" }, requestId: string, timeoutMs = 1_000) {
|
|
1040
|
+
async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind: "nested" }, requestId: string, ignoredFiles: ReadonlySet<string>, timeoutMs = 1_000) {
|
|
1036
1041
|
const deadline = Date.now() + timeoutMs;
|
|
1037
1042
|
while (Date.now() < deadline) {
|
|
1038
|
-
const result =
|
|
1043
|
+
const result = findNestedControlResult(target.match.route, requestId, target.match.run.id, ignoredFiles);
|
|
1039
1044
|
if (result) return result;
|
|
1040
1045
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1041
1046
|
}
|
|
@@ -1044,14 +1049,16 @@ async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind
|
|
|
1044
1049
|
|
|
1045
1050
|
async function sendNestedControlRequest(target: ResolvedSubagentRunId & { kind: "nested" }, action: "interrupt" | "resume", message?: string) {
|
|
1046
1051
|
const requestId = randomUUID();
|
|
1052
|
+
const ignoredFiles = snapshotNestedEventFiles(target.match.route);
|
|
1053
|
+
const requestedAt = Date.now();
|
|
1047
1054
|
writeNestedControlRequest(target.match.route, {
|
|
1048
|
-
ts:
|
|
1055
|
+
ts: requestedAt,
|
|
1049
1056
|
requestId,
|
|
1050
1057
|
targetRunId: target.match.run.id,
|
|
1051
1058
|
action,
|
|
1052
1059
|
...(message ? { message } : {}),
|
|
1053
1060
|
});
|
|
1054
|
-
return waitForNestedControlResult(target, requestId);
|
|
1061
|
+
return waitForNestedControlResult(target, requestId, ignoredFiles);
|
|
1055
1062
|
}
|
|
1056
1063
|
|
|
1057
1064
|
function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nested" }): AgentToolResult<Details> | undefined {
|
|
@@ -1477,7 +1484,12 @@ async function emitForegroundResultIntercom(input: {
|
|
|
1477
1484
|
exitCode: result.exitCode,
|
|
1478
1485
|
interrupted: result.interrupted,
|
|
1479
1486
|
detached: result.detached,
|
|
1487
|
+
processSignal: result.processSignal,
|
|
1488
|
+
timedOut: result.timedOut,
|
|
1489
|
+
stopped: result.stopped,
|
|
1490
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
1480
1491
|
}),
|
|
1492
|
+
outputState: result.outputState ?? "unknown",
|
|
1481
1493
|
summary: resultSummaryForIntercom(result),
|
|
1482
1494
|
index,
|
|
1483
1495
|
artifactPath: result.artifactPaths?.outputPath,
|
|
@@ -1524,6 +1536,67 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1524
1536
|
};
|
|
1525
1537
|
}
|
|
1526
1538
|
|
|
1539
|
+
function canonicalizeAgentName(name: string, agents: AgentConfig[]): { name?: string; error?: string } {
|
|
1540
|
+
const resolved = resolveAgentName(name, agents);
|
|
1541
|
+
if (resolved.error) return { error: resolved.error };
|
|
1542
|
+
if (!resolved.agent) return { error: `Unknown agent: ${name}` };
|
|
1543
|
+
return { name: resolved.agent.name };
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[]): { params?: SubagentParamsLike; error?: string } {
|
|
1547
|
+
const resolve = (name: string, location?: string): { name?: string; error?: string } => {
|
|
1548
|
+
const result = canonicalizeAgentName(name, agents);
|
|
1549
|
+
return result.error && location ? { error: `${result.error} (${location})` } : result;
|
|
1550
|
+
};
|
|
1551
|
+
if (params.agent) {
|
|
1552
|
+
const result = resolve(params.agent);
|
|
1553
|
+
if (result.error) return { error: result.error };
|
|
1554
|
+
params = { ...params, agent: result.name };
|
|
1555
|
+
}
|
|
1556
|
+
if (params.tasks) {
|
|
1557
|
+
const tasks: TaskParam[] = [];
|
|
1558
|
+
for (let index = 0; index < params.tasks.length; index++) {
|
|
1559
|
+
const task = params.tasks[index]!;
|
|
1560
|
+
const result = resolve(task.agent, `task ${index + 1}`);
|
|
1561
|
+
if (result.error) return { error: result.error };
|
|
1562
|
+
tasks.push({ ...task, agent: result.name! });
|
|
1563
|
+
}
|
|
1564
|
+
params = { ...params, tasks };
|
|
1565
|
+
}
|
|
1566
|
+
if (params.chain) {
|
|
1567
|
+
const chain: ChainStep[] = [];
|
|
1568
|
+
for (let index = 0; index < params.chain.length; index++) {
|
|
1569
|
+
const step = params.chain[index]!;
|
|
1570
|
+
if (isParallelStep(step)) {
|
|
1571
|
+
const parallel: typeof step.parallel = [];
|
|
1572
|
+
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
1573
|
+
const task = step.parallel[taskIndex]!;
|
|
1574
|
+
const result = resolve(task.agent, `step ${index + 1}, task ${taskIndex + 1}`);
|
|
1575
|
+
if (result.error) return { error: result.error };
|
|
1576
|
+
parallel.push({ ...task, agent: result.name! });
|
|
1577
|
+
}
|
|
1578
|
+
chain.push({ ...step, parallel });
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
if (isDynamicParallelStep(step)) {
|
|
1582
|
+
const result = resolve(step.parallel.agent, `step ${index + 1}`);
|
|
1583
|
+
if (result.error) return { error: result.error };
|
|
1584
|
+
chain.push({ ...step, parallel: { ...step.parallel, agent: result.name! } });
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
if ("agent" in step && typeof step.agent === "string") {
|
|
1588
|
+
const result = resolve(step.agent, `step ${index + 1}`);
|
|
1589
|
+
if (result.error) return { error: result.error };
|
|
1590
|
+
chain.push({ ...step, agent: result.name! });
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
chain.push(step);
|
|
1594
|
+
}
|
|
1595
|
+
params = { ...params, chain };
|
|
1596
|
+
}
|
|
1597
|
+
return { params };
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1527
1600
|
function validateExecutionInput(
|
|
1528
1601
|
params: SubagentParamsLike,
|
|
1529
1602
|
agents: AgentConfig[],
|
|
@@ -2153,6 +2226,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2153
2226
|
timeoutMs: data.timeoutMs,
|
|
2154
2227
|
turnBudget: data.turnBudget,
|
|
2155
2228
|
toolBudget: data.toolBudget,
|
|
2229
|
+
usageBudget: data.usageBudget,
|
|
2156
2230
|
configToolBudget: data.configToolBudget,
|
|
2157
2231
|
capabilityCeiling: data.capabilityCeiling,
|
|
2158
2232
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
@@ -2195,6 +2269,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2195
2269
|
timeoutMs: data.timeoutMs,
|
|
2196
2270
|
turnBudget: data.turnBudget,
|
|
2197
2271
|
toolBudget: data.toolBudget,
|
|
2272
|
+
usageBudget: data.usageBudget,
|
|
2198
2273
|
configToolBudget: data.configToolBudget,
|
|
2199
2274
|
capabilityCeiling: data.capabilityCeiling,
|
|
2200
2275
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
@@ -2253,6 +2328,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2253
2328
|
timeoutMs: data.timeoutMs,
|
|
2254
2329
|
turnBudget: data.turnBudget,
|
|
2255
2330
|
toolBudget: data.toolBudget,
|
|
2331
|
+
usageBudget: data.usageBudget,
|
|
2256
2332
|
configToolBudget: data.configToolBudget,
|
|
2257
2333
|
capabilityCeiling: data.capabilityCeiling,
|
|
2258
2334
|
});
|
|
@@ -2327,8 +2403,18 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2327
2403
|
timeoutMs: data.timeoutMs,
|
|
2328
2404
|
deadlineAt: data.deadlineAt,
|
|
2329
2405
|
turnBudget: data.turnBudget,
|
|
2330
|
-
onDetachedExit: (index, result) =>
|
|
2406
|
+
onDetachedExit: (index, result) => {
|
|
2407
|
+
try {
|
|
2408
|
+
updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events });
|
|
2409
|
+
} finally {
|
|
2410
|
+
removeForegroundControlIfIdle(deps.state, runId);
|
|
2411
|
+
}
|
|
2412
|
+
},
|
|
2413
|
+
onForegroundChildSettled: () => {
|
|
2414
|
+
removeForegroundControlIfIdle(deps.state, runId);
|
|
2415
|
+
},
|
|
2331
2416
|
toolBudget: data.toolBudget,
|
|
2417
|
+
usageBudget: data.usageBudget,
|
|
2332
2418
|
configToolBudget: data.configToolBudget,
|
|
2333
2419
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2334
2420
|
capabilityCeiling: data.capabilityCeiling,
|
|
@@ -2388,6 +2474,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2388
2474
|
timeoutMs: data.timeoutMs,
|
|
2389
2475
|
turnBudget: data.turnBudget,
|
|
2390
2476
|
toolBudget: data.toolBudget,
|
|
2477
|
+
usageBudget: data.usageBudget,
|
|
2391
2478
|
configToolBudget: data.configToolBudget,
|
|
2392
2479
|
capabilityCeiling: data.capabilityCeiling,
|
|
2393
2480
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
@@ -2399,9 +2486,10 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2399
2486
|
updateForegroundNestedProjection(foregroundControl);
|
|
2400
2487
|
attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
|
|
2401
2488
|
rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
|
|
2489
|
+
rawChainDetails.usageBudget = usageBudgetState(data.usageBudget, rawChainDetails.totalCost);
|
|
2402
2490
|
}
|
|
2403
2491
|
const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
|
|
2404
|
-
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results });
|
|
2492
|
+
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results, checkpoint: chainDetails.checkpoint });
|
|
2405
2493
|
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
2406
2494
|
? await maybeBuildForegroundIntercomReceipt({
|
|
2407
2495
|
pi: deps.pi,
|
|
@@ -2469,6 +2557,7 @@ interface ForegroundParallelRunInput {
|
|
|
2469
2557
|
timeoutMs?: number;
|
|
2470
2558
|
deadlineAt?: number;
|
|
2471
2559
|
turnBudget?: ResolvedTurnBudget;
|
|
2560
|
+
usageBudget?: UsageBudgetConfig;
|
|
2472
2561
|
toolBudgets: (ResolvedToolBudget | undefined)[];
|
|
2473
2562
|
agentContract?: AgentContract;
|
|
2474
2563
|
}
|
|
@@ -2575,6 +2664,10 @@ function finalizeParallelWorktreeHandoff(input: {
|
|
|
2575
2664
|
interrupted: result.interrupted,
|
|
2576
2665
|
detached: result.detached,
|
|
2577
2666
|
state: result.stopped ? "stopped" : undefined,
|
|
2667
|
+
processSignal: result.processSignal,
|
|
2668
|
+
timedOut: result.timedOut,
|
|
2669
|
+
stopped: result.stopped,
|
|
2670
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
2578
2671
|
}),
|
|
2579
2672
|
summary: resultSummaryForIntercom(result),
|
|
2580
2673
|
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
@@ -2623,7 +2716,22 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2623
2716
|
for (let i = 0; i < input.tasks.length; i++) {
|
|
2624
2717
|
input.sessionFileForTask(input.tasks[i]!.agent, i, input.modelOverrides[i]);
|
|
2625
2718
|
}
|
|
2719
|
+
const completedResults: SingleResult[] = [];
|
|
2720
|
+
if (input.foregroundControl) retainForegroundSchedulingOwner(input.foregroundControl);
|
|
2626
2721
|
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
2722
|
+
const budgetState = usageBudgetState(input.usageBudget, sumResultsCost(completedResults));
|
|
2723
|
+
if (budgetState?.exhausted) {
|
|
2724
|
+
return {
|
|
2725
|
+
index,
|
|
2726
|
+
agent: task.agent,
|
|
2727
|
+
task: input.taskTexts[index] ?? "(skipped)",
|
|
2728
|
+
exitCode: 1,
|
|
2729
|
+
messages: [],
|
|
2730
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
|
|
2731
|
+
error: usageBudgetExceededMessage(budgetState),
|
|
2732
|
+
skipped: true,
|
|
2733
|
+
} as SingleResult;
|
|
2734
|
+
}
|
|
2627
2735
|
const behavior = input.behaviors[index];
|
|
2628
2736
|
const effectiveSkills = behavior?.skills;
|
|
2629
2737
|
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
@@ -2642,10 +2750,14 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2642
2750
|
);
|
|
2643
2751
|
const interruptController = new AbortController();
|
|
2644
2752
|
if (input.foregroundControl) {
|
|
2753
|
+
const model = input.modelOverrides[index];
|
|
2754
|
+
const thinking = resolveEffectiveThinking(model, input.thinkingOverrideForTask(task.agent, index, model));
|
|
2645
2755
|
beginForegroundChild(input.foregroundControl, {
|
|
2646
2756
|
index,
|
|
2647
2757
|
agent: task.agent,
|
|
2648
2758
|
description: input.taskDescriptions[index],
|
|
2759
|
+
...(model ? { model } : {}),
|
|
2760
|
+
...(thinking ? { thinking } : {}),
|
|
2649
2761
|
interrupt: () => {
|
|
2650
2762
|
if (interruptController.signal.aborted) return false;
|
|
2651
2763
|
interruptController.abort();
|
|
@@ -2656,7 +2768,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2656
2768
|
const structuredRuntime = task.outputSchema
|
|
2657
2769
|
? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
|
|
2658
2770
|
: undefined;
|
|
2659
|
-
|
|
2771
|
+
let detachedReceipt = false;
|
|
2772
|
+
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
2660
2773
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
2661
2774
|
context: input.contextPolicy.contextForAgent(task.agent),
|
|
2662
2775
|
cwd: taskCwd,
|
|
@@ -2679,7 +2792,17 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2679
2792
|
capabilityCeiling: input.capabilityCeiling,
|
|
2680
2793
|
controlConfig: input.controlConfig,
|
|
2681
2794
|
onControlEvent: input.onControlEvent,
|
|
2682
|
-
onDetachedExit: (result) =>
|
|
2795
|
+
onDetachedExit: (result) => {
|
|
2796
|
+
try {
|
|
2797
|
+
updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents });
|
|
2798
|
+
} finally {
|
|
2799
|
+
try {
|
|
2800
|
+
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
|
|
2801
|
+
} finally {
|
|
2802
|
+
removeForegroundControlIfIdle(input.state, input.runId);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
},
|
|
2683
2806
|
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
2684
2807
|
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
2685
2808
|
nestedRoute: input.foregroundControl?.nestedRoute,
|
|
@@ -2720,10 +2843,24 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2720
2843
|
});
|
|
2721
2844
|
}
|
|
2722
2845
|
: undefined,
|
|
2846
|
+
}).then((result) => {
|
|
2847
|
+
detachedReceipt = result.detached === true;
|
|
2848
|
+
return result;
|
|
2723
2849
|
}).finally(() => {
|
|
2724
|
-
|
|
2850
|
+
// mapConcurrent rejects before siblings settle, so every attached child
|
|
2851
|
+
// attempts idle removal after releasing its own control. Detached receipts
|
|
2852
|
+
// transfer both responsibilities to the authoritative exit callback.
|
|
2853
|
+
if (!detachedReceipt) {
|
|
2854
|
+
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
|
|
2855
|
+
removeForegroundControlIfIdle(input.state, input.runId);
|
|
2856
|
+
}
|
|
2725
2857
|
});
|
|
2726
|
-
|
|
2858
|
+
completedResults.push(result);
|
|
2859
|
+
return result;
|
|
2860
|
+
}, input.globalSemaphore, () => {
|
|
2861
|
+
if (input.foregroundControl) settleForegroundSchedulingOwner(input.foregroundControl);
|
|
2862
|
+
removeForegroundControlIfIdle(input.state, input.runId);
|
|
2863
|
+
});
|
|
2727
2864
|
}
|
|
2728
2865
|
|
|
2729
2866
|
async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
@@ -2921,6 +3058,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2921
3058
|
nestedRoute: data.nestedRoute,
|
|
2922
3059
|
timeoutMs: data.timeoutMs,
|
|
2923
3060
|
turnBudget: data.turnBudget,
|
|
3061
|
+
usageBudget: data.usageBudget,
|
|
2924
3062
|
toolBudget: data.toolBudget,
|
|
2925
3063
|
configToolBudget: data.configToolBudget,
|
|
2926
3064
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
@@ -3025,6 +3163,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3025
3163
|
timeoutMs: data.timeoutMs,
|
|
3026
3164
|
deadlineAt,
|
|
3027
3165
|
turnBudget: data.turnBudget,
|
|
3166
|
+
usageBudget: data.usageBudget,
|
|
3028
3167
|
toolBudgets,
|
|
3029
3168
|
agentContract: params.agentContract,
|
|
3030
3169
|
});
|
|
@@ -3048,6 +3187,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3048
3187
|
handoff = finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
|
|
3049
3188
|
}
|
|
3050
3189
|
const interrupted = results.find((result) => result.interrupted);
|
|
3190
|
+
const totalCost = sumResultsCost(results);
|
|
3051
3191
|
const details = compactForegroundDetails({
|
|
3052
3192
|
mode: "parallel",
|
|
3053
3193
|
runId,
|
|
@@ -3056,7 +3196,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3056
3196
|
progress: params.includeProgress ? allProgress : undefined,
|
|
3057
3197
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
3058
3198
|
totalChildUsage: sumResultsUsage(results),
|
|
3059
|
-
totalCost
|
|
3199
|
+
totalCost,
|
|
3200
|
+
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3060
3201
|
...(handoff?.reference ? { parallelHandoff: handoff.reference } : {}),
|
|
3061
3202
|
});
|
|
3062
3203
|
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
@@ -3259,6 +3400,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3259
3400
|
timeoutMs: data.timeoutMs,
|
|
3260
3401
|
turnBudget: data.turnBudget,
|
|
3261
3402
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3403
|
+
usageBudget: data.usageBudget,
|
|
3262
3404
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3263
3405
|
});
|
|
3264
3406
|
}
|
|
@@ -3285,17 +3427,22 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3285
3427
|
effectiveSkills = skillOverride;
|
|
3286
3428
|
}
|
|
3287
3429
|
const interruptController = new AbortController();
|
|
3430
|
+
let detachForeground: ((reason?: string) => boolean) | undefined;
|
|
3288
3431
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3289
3432
|
if (foregroundControl) {
|
|
3433
|
+
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride));
|
|
3290
3434
|
beginForegroundChild(foregroundControl, {
|
|
3291
3435
|
index: 0,
|
|
3292
3436
|
agent: params.agent!,
|
|
3293
3437
|
description: foregroundControl.description,
|
|
3438
|
+
...(modelOverride ? { model: modelOverride } : {}),
|
|
3439
|
+
...(thinking ? { thinking } : {}),
|
|
3294
3440
|
interrupt: () => {
|
|
3295
3441
|
if (interruptController.signal.aborted) return false;
|
|
3296
3442
|
interruptController.abort();
|
|
3297
3443
|
return true;
|
|
3298
3444
|
},
|
|
3445
|
+
detach: () => detachForeground?.("user request") === true,
|
|
3299
3446
|
});
|
|
3300
3447
|
}
|
|
3301
3448
|
|
|
@@ -3345,7 +3492,29 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3345
3492
|
agentContract: params.agentContract,
|
|
3346
3493
|
acceptance: params.acceptance,
|
|
3347
3494
|
acceptanceContext: { mode: "single" },
|
|
3348
|
-
|
|
3495
|
+
onDetachReady: (detach) => {
|
|
3496
|
+
detachForeground = detach;
|
|
3497
|
+
},
|
|
3498
|
+
onDetachedExit: (result) => {
|
|
3499
|
+
try {
|
|
3500
|
+
try {
|
|
3501
|
+
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events });
|
|
3502
|
+
} catch {
|
|
3503
|
+
// Remembered foreground state is best-effort; run history and cleanup must still complete.
|
|
3504
|
+
}
|
|
3505
|
+
} finally {
|
|
3506
|
+
try {
|
|
3507
|
+
if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
|
|
3508
|
+
} finally {
|
|
3509
|
+
try {
|
|
3510
|
+
if (foregroundControl) finishForegroundChild(foregroundControl, 0);
|
|
3511
|
+
} finally {
|
|
3512
|
+
removeForegroundControlIfIdle(deps.state, runId);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
|
|
3517
|
+
},
|
|
3349
3518
|
timeoutMs: data.timeoutMs,
|
|
3350
3519
|
deadlineAt,
|
|
3351
3520
|
turnBudget: data.turnBudget,
|
|
@@ -3355,10 +3524,17 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3355
3524
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3356
3525
|
});
|
|
3357
3526
|
} finally {
|
|
3358
|
-
|
|
3527
|
+
// An attached runSync rejection still owns its child and structured runtime.
|
|
3528
|
+
// A successful detached receipt transfers both to onDetachedExit while the
|
|
3529
|
+
// authoritative completion remains live.
|
|
3530
|
+
if (!r?.detached) {
|
|
3531
|
+
if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
|
|
3532
|
+
if (foregroundControl) finishForegroundChild(foregroundControl, 0);
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
if (!r.detached) {
|
|
3536
|
+
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
3359
3537
|
}
|
|
3360
|
-
if (foregroundControl) finishForegroundChild(foregroundControl, 0);
|
|
3361
|
-
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
3362
3538
|
|
|
3363
3539
|
if (r.progress) allProgress.push(r.progress);
|
|
3364
3540
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
@@ -3378,6 +3554,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3378
3554
|
updateForegroundNestedProjection(foregroundControl);
|
|
3379
3555
|
attachRootChildrenToSteps(runId, [r], foregroundControl.nestedChildren);
|
|
3380
3556
|
}
|
|
3557
|
+
const totalCost = sumResultsCost([r]);
|
|
3381
3558
|
const details = compactForegroundDetails({
|
|
3382
3559
|
mode: "single",
|
|
3383
3560
|
runId,
|
|
@@ -3389,7 +3566,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3389
3566
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
3390
3567
|
truncation: r.truncation,
|
|
3391
3568
|
totalChildUsage: sumResultsUsage([r]),
|
|
3392
|
-
totalCost
|
|
3569
|
+
totalCost,
|
|
3570
|
+
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3393
3571
|
});
|
|
3394
3572
|
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
3395
3573
|
|
|
@@ -3673,6 +3851,59 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3673
3851
|
}
|
|
3674
3852
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
3675
3853
|
}
|
|
3854
|
+
|
|
3855
|
+
if (action === "approve-checkpoint" || action === "reject-checkpoint") {
|
|
3856
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3857
|
+
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
3858
|
+
if (!targetRunId && !paramsWithResolvedCwd.dir) {
|
|
3859
|
+
return { content: [{ type: "text", text: `action='${action}' requires id or dir.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3860
|
+
}
|
|
3861
|
+
try {
|
|
3862
|
+
const resolved = targetRunId ? resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }) : undefined;
|
|
3863
|
+
const decision = action === "approve-checkpoint" ? "approved" : "rejected";
|
|
3864
|
+
if (resolved?.kind === "foreground") {
|
|
3865
|
+
const run = deps.state.foregroundRuns?.get(resolved.id);
|
|
3866
|
+
if (!run?.checkpoint || run.checkpoint.status !== "pending") {
|
|
3867
|
+
return { content: [{ type: "text", text: `Run '${resolved.id}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3868
|
+
}
|
|
3869
|
+
if (deps.state.currentSessionId && run.sessionId !== deps.state.currentSessionId) {
|
|
3870
|
+
return { content: [{ type: "text", text: `Run '${resolved.id}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3871
|
+
}
|
|
3872
|
+
const decidedAt = Date.now();
|
|
3873
|
+
const checkpoint = { ...run.checkpoint, status: decision, ...(decision === "approved" ? { approvedAt: decidedAt } : { rejectedAt: decidedAt }) };
|
|
3874
|
+
run.checkpoint = checkpoint;
|
|
3875
|
+
run.updatedAt = decidedAt;
|
|
3876
|
+
return {
|
|
3877
|
+
content: [{ type: "text", text: `Checkpoint '${checkpoint.name}' ${decision} for foreground run ${resolved.id}.` }],
|
|
3878
|
+
details: { mode: "management", results: [], checkpoint },
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
const location = paramsWithResolvedCwd.dir
|
|
3882
|
+
? resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR)
|
|
3883
|
+
: resolved?.kind === "async"
|
|
3884
|
+
? resolved.location
|
|
3885
|
+
: undefined;
|
|
3886
|
+
if (!location?.asyncDir) {
|
|
3887
|
+
return { content: [{ type: "text", text: `action='${action}' targets paused foreground or async checkpoints. No matching run found.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3888
|
+
}
|
|
3889
|
+
const status = readStatus(location.asyncDir);
|
|
3890
|
+
const runId = status?.runId ?? location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir);
|
|
3891
|
+
if (!status?.checkpoint || status.state !== "paused") {
|
|
3892
|
+
return { content: [{ type: "text", text: `Run '${runId}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3893
|
+
}
|
|
3894
|
+
if (deps.state.currentSessionId && status.sessionId !== deps.state.currentSessionId) {
|
|
3895
|
+
return { content: [{ type: "text", text: `Run '${runId}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3896
|
+
}
|
|
3897
|
+
deliverCheckpointDecisionRequest({ asyncDir: location.asyncDir, decision, source: "subagent-action", ...(paramsWithResolvedCwd.message ? { reason: paramsWithResolvedCwd.message } : {}) });
|
|
3898
|
+
return {
|
|
3899
|
+
content: [{ type: "text", text: `Checkpoint '${status.checkpoint.name}' ${decision} for run ${runId}.` }],
|
|
3900
|
+
details: { mode: "management", results: [], checkpoint: { ...status.checkpoint, status: decision } },
|
|
3901
|
+
};
|
|
3902
|
+
} catch (error) {
|
|
3903
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
3904
|
+
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3676
3907
|
if (action === "resume") {
|
|
3677
3908
|
return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
|
|
3678
3909
|
}
|
|
@@ -3848,6 +4079,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3848
4079
|
...ctx,
|
|
3849
4080
|
cwd: requestCwd,
|
|
3850
4081
|
config: deps.config,
|
|
4082
|
+
currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
|
|
3851
4083
|
});
|
|
3852
4084
|
}
|
|
3853
4085
|
|
|
@@ -3885,12 +4117,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3885
4117
|
if (runToolBudget.error) return buildRequestedModeError(effectiveParams, runToolBudget.error);
|
|
3886
4118
|
const configToolBudget = resolveToolBudget(deps.config.toolBudget, "config.toolBudget");
|
|
3887
4119
|
if (configToolBudget.error) return buildRequestedModeError(effectiveParams, configToolBudget.error);
|
|
4120
|
+
const usageBudget = validateUsageBudgetConfig(effectiveParams.usageBudget ?? deps.config.usageBudget, effectiveParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4121
|
+
if (usageBudget.error) return buildRequestedModeError(effectiveParams, usageBudget.error);
|
|
3888
4122
|
|
|
3889
4123
|
const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
|
|
3890
4124
|
const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
|
|
3891
4125
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
3892
4126
|
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
3893
4127
|
const discoveredAgents = discovered.agents;
|
|
4128
|
+
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents);
|
|
4129
|
+
if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
|
|
4130
|
+
effectiveParams = canonicalParams.params!;
|
|
3894
4131
|
const modelScope = discovered.modelScope;
|
|
3895
4132
|
effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
|
|
3896
4133
|
const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
|
|
@@ -4079,6 +4316,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4079
4316
|
timeoutMs: foregroundTimeout.timeoutMs,
|
|
4080
4317
|
turnBudget: turnBudget.turnBudget,
|
|
4081
4318
|
toolBudget: runToolBudget.toolBudget,
|
|
4319
|
+
usageBudget: usageBudget.budget,
|
|
4082
4320
|
allowZeroToolBudget,
|
|
4083
4321
|
configToolBudget: configToolBudget.toolBudget,
|
|
4084
4322
|
contextPolicy,
|
|
@@ -4105,6 +4343,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4105
4343
|
description: foregroundDescription,
|
|
4106
4344
|
currentActivityState: undefined,
|
|
4107
4345
|
activeChildren: new Map(),
|
|
4346
|
+
// The outer executor owns scheduling until its finally block settles.
|
|
4347
|
+
schedulingOwners: 1,
|
|
4108
4348
|
nestedRoute,
|
|
4109
4349
|
interrupt: undefined,
|
|
4110
4350
|
};
|
|
@@ -4202,11 +4442,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4202
4442
|
return errorResult;
|
|
4203
4443
|
} finally {
|
|
4204
4444
|
if (foregroundControl) {
|
|
4205
|
-
|
|
4206
|
-
deps.state
|
|
4207
|
-
if (deps.state.lastForegroundControlId === runId) {
|
|
4208
|
-
deps.state.lastForegroundControlId = null;
|
|
4209
|
-
}
|
|
4445
|
+
settleForegroundSchedulingOwner(foregroundControl);
|
|
4446
|
+
removeForegroundControlIfIdle(deps.state, runId);
|
|
4210
4447
|
}
|
|
4211
4448
|
}
|
|
4212
4449
|
|