pi-subagents 0.35.0 → 0.36.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 +59 -0
- package/README.md +132 -24
- package/agents/advisor.md +73 -0
- package/package.json +8 -12
- package/skills/pi-subagents/SKILL.md +22 -9
- package/src/agents/agents.ts +22 -5
- package/src/api/delegation.ts +125 -0
- package/src/extension/config.ts +7 -1
- package/src/extension/index.ts +50 -38
- package/src/extension/rpc.ts +27 -2
- package/src/extension/schemas.ts +22 -2
- package/src/extension/tool-description.ts +2 -2
- package/src/intercom/intercom-bridge.ts +1 -1
- package/src/intercom/native-supervisor-channel.ts +45 -6
- package/src/intercom/result-intercom.ts +7 -0
- package/src/runs/background/async-execution.ts +34 -4
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +27 -5
- package/src/runs/background/async-status.ts +76 -3
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/completion-batcher.ts +6 -4
- package/src/runs/background/completion-dedupe.ts +2 -11
- package/src/runs/background/fleet-view.ts +9 -4
- package/src/runs/background/notify.ts +132 -120
- package/src/runs/background/result-watcher.ts +138 -78
- package/src/runs/background/run-status.ts +3 -1
- package/src/runs/background/subagent-runner.ts +225 -43
- package/src/runs/background/subagent-wait.ts +130 -4
- package/src/runs/background/wait-tool.ts +2 -2
- package/src/runs/foreground/chain-execution.ts +176 -111
- package/src/runs/foreground/execution.ts +90 -36
- package/src/runs/foreground/foreground-control.ts +90 -0
- package/src/runs/foreground/subagent-executor.ts +394 -163
- package/src/runs/shared/acceptance.ts +55 -13
- package/src/runs/shared/agent-contract.ts +38 -0
- package/src/runs/shared/child-protocol.ts +1 -1
- package/src/runs/shared/completion-guard.ts +36 -5
- package/src/runs/shared/context-mode.ts +44 -0
- package/src/runs/shared/dynamic-fanout.ts +4 -4
- package/src/runs/shared/long-running-guard.ts +4 -0
- package/src/runs/shared/nested-events.ts +27 -2
- package/src/runs/shared/parallel-handoff.ts +154 -0
- package/src/runs/shared/parallel-utils.ts +6 -0
- package/src/runs/shared/pi-args.ts +23 -14
- package/src/runs/shared/run-history.ts +90 -5
- package/src/runs/shared/structured-output.ts +112 -7
- package/src/runs/shared/subagent-control.ts +4 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +17 -18
- package/src/runs/shared/task-intent.ts +10 -5
- package/src/runs/shared/tool-availability.ts +3 -1
- package/src/runs/shared/tool-budget.ts +11 -5
- package/src/runs/shared/turn-budget.ts +2 -1
- package/src/runs/shared/worktree.ts +63 -14
- package/src/shared/accessible-dir.ts +25 -0
- package/src/shared/artifacts.ts +37 -7
- package/src/shared/atomic-json.ts +14 -42
- package/src/shared/child-transcript.ts +52 -0
- package/src/shared/file-system-retry.ts +47 -0
- package/src/shared/settings.ts +9 -1
- package/src/shared/types.ts +211 -22
- package/src/slash/delegation-adapters.ts +152 -5
- package/src/slash/delegation-json.ts +108 -0
- package/src/slash/delegation-request.ts +182 -36
- package/src/slash/prompt-template-bridge.ts +222 -37
- package/src/slash/selector.ts +147 -0
- package/src/slash/slash-commands.ts +14 -5
- package/src/slash/slash-live-state.ts +2 -2
- package/src/slash/subagents-admin.ts +42 -42
- package/src/tui/fleet-status.ts +362 -0
- package/src/tui/fleet-transcript.ts +472 -0
- package/src/tui/fleet.ts +318 -59
- package/src/tui/render.ts +25 -15
- package/src/watchdog/change-signature.ts +105 -12
- package/src/watchdog/review.ts +7 -2
- package/src/watchdog/runtime.ts +5 -3
- package/src/slash/subagents-editor.ts +0 -86
|
@@ -3,11 +3,12 @@ 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 {
|
|
6
|
+
import type { AgentConfig, 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
9
|
import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
10
10
|
import { executeChain } from "./chain-execution.ts";
|
|
11
|
+
import { beginForegroundChild, finishForegroundChild, updateForegroundChild } from "./foreground-control.ts";
|
|
11
12
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
12
13
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
13
14
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
@@ -46,9 +47,13 @@ import { formatControlIntercomMessage, formatControlNoticeMessage, resolveContro
|
|
|
46
47
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
47
48
|
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
48
49
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
50
|
+
import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
49
51
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
52
|
+
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
50
53
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
51
54
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
55
|
+
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup } from "../shared/parallel-handoff.ts";
|
|
56
|
+
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
52
57
|
import {
|
|
53
58
|
attachNestedChildrenToResultChildren,
|
|
54
59
|
buildSubagentResultIntercomPayload,
|
|
@@ -81,13 +86,16 @@ import {
|
|
|
81
86
|
type AgentProgress,
|
|
82
87
|
type AsyncStatus,
|
|
83
88
|
type AcceptanceInput,
|
|
89
|
+
type AgentContract,
|
|
84
90
|
type ArtifactConfig,
|
|
85
91
|
type ArtifactPaths,
|
|
86
92
|
type ControlConfig,
|
|
87
93
|
type ControlEvent,
|
|
88
94
|
type Details,
|
|
89
95
|
type ExtensionConfig,
|
|
96
|
+
type ForegroundRunControl,
|
|
90
97
|
type IntercomEventBus,
|
|
98
|
+
type JsonSchemaObject,
|
|
91
99
|
type MaxOutputConfig,
|
|
92
100
|
type NestedRouteInfo,
|
|
93
101
|
type NestedRunSummary,
|
|
@@ -127,7 +135,9 @@ interface TaskParam {
|
|
|
127
135
|
progress?: boolean;
|
|
128
136
|
model?: string;
|
|
129
137
|
skill?: string | string[] | boolean;
|
|
138
|
+
outputSchema?: JsonSchemaObject;
|
|
130
139
|
acceptance?: AcceptanceInput;
|
|
140
|
+
agentContract?: AgentContract;
|
|
131
141
|
toolBudget?: ToolBudgetConfig;
|
|
132
142
|
}
|
|
133
143
|
|
|
@@ -142,6 +152,7 @@ export interface SubagentParamsLike {
|
|
|
142
152
|
agent?: string;
|
|
143
153
|
task?: string;
|
|
144
154
|
message?: string;
|
|
155
|
+
steeringRecovery?: boolean;
|
|
145
156
|
chain?: ChainStep[];
|
|
146
157
|
tasks?: TaskParam[];
|
|
147
158
|
concurrency?: number;
|
|
@@ -152,6 +163,8 @@ export interface SubagentParamsLike {
|
|
|
152
163
|
timeoutMs?: number;
|
|
153
164
|
maxRuntimeMs?: number;
|
|
154
165
|
turnBudget?: TurnBudgetConfig;
|
|
166
|
+
/** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
|
|
167
|
+
enforceHardTurnLimit?: boolean;
|
|
155
168
|
toolBudget?: ToolBudgetConfig;
|
|
156
169
|
clarify?: boolean;
|
|
157
170
|
share?: boolean;
|
|
@@ -168,9 +181,11 @@ export interface SubagentParamsLike {
|
|
|
168
181
|
skill?: string | string[] | boolean;
|
|
169
182
|
output?: string | boolean;
|
|
170
183
|
outputMode?: "inline" | "file-only";
|
|
184
|
+
outputSchema?: JsonSchemaObject;
|
|
171
185
|
agentScope?: unknown;
|
|
172
186
|
chainDir?: string;
|
|
173
187
|
acceptance?: AcceptanceInput;
|
|
188
|
+
agentContract?: AgentContract;
|
|
174
189
|
schedule?: string;
|
|
175
190
|
scheduleName?: string;
|
|
176
191
|
additional?: number;
|
|
@@ -220,6 +235,7 @@ interface ExecutionContextData {
|
|
|
220
235
|
deadlineAt?: number;
|
|
221
236
|
turnBudget?: ResolvedTurnBudget;
|
|
222
237
|
toolBudget?: ResolvedToolBudget;
|
|
238
|
+
allowZeroToolBudget?: boolean;
|
|
223
239
|
configToolBudget?: ResolvedToolBudget;
|
|
224
240
|
contextPolicy: AgentDefaultContextPolicy;
|
|
225
241
|
modelScope?: ModelScopeConfig;
|
|
@@ -351,6 +367,19 @@ function trimRememberedForegroundRuns(state: SubagentState): void {
|
|
|
351
367
|
}
|
|
352
368
|
}
|
|
353
369
|
|
|
370
|
+
function foregroundChildActivityFromProgress(progress: SingleResult["progress"] | undefined) {
|
|
371
|
+
return {
|
|
372
|
+
...(progress?.activityState ? { activityState: progress.activityState } : {}),
|
|
373
|
+
...(progress?.lastActivityAt !== undefined ? { lastActivityAt: progress.lastActivityAt } : {}),
|
|
374
|
+
...(progress?.currentTool ? { currentTool: progress.currentTool } : {}),
|
|
375
|
+
...(progress?.currentToolStartedAt !== undefined ? { currentToolStartedAt: progress.currentToolStartedAt } : {}),
|
|
376
|
+
...(progress?.currentPath ? { currentPath: progress.currentPath } : {}),
|
|
377
|
+
...(progress?.turnCount !== undefined ? { turnCount: progress.turnCount } : {}),
|
|
378
|
+
...(progress?.tokens !== undefined ? { tokens: progress.tokens } : {}),
|
|
379
|
+
...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
354
383
|
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
|
|
355
384
|
state.foregroundRuns ??= new Map();
|
|
356
385
|
const previous = state.foregroundRuns.get(input.runId);
|
|
@@ -365,7 +394,9 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
365
394
|
const child = {
|
|
366
395
|
agent: result.agent,
|
|
367
396
|
index,
|
|
397
|
+
...(result.context ? { context: result.context } : {}),
|
|
368
398
|
status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
|
|
399
|
+
...foregroundChildActivityFromProgress(result.progress),
|
|
369
400
|
updatedAt,
|
|
370
401
|
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
371
402
|
...(result.error ? { error: result.error } : {}),
|
|
@@ -387,6 +418,29 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
387
418
|
trimRememberedForegroundRuns(state);
|
|
388
419
|
}
|
|
389
420
|
|
|
421
|
+
function applyControlEventToRememberedForegroundRun(state: SubagentState, event: ControlEvent): void {
|
|
422
|
+
const run = state.foregroundRuns?.get(event.runId);
|
|
423
|
+
if (!run) return;
|
|
424
|
+
const index = event.index ?? (run.children.length === 1 ? run.children[0]?.index : undefined);
|
|
425
|
+
if (index === undefined) return;
|
|
426
|
+
const child = run.children[index];
|
|
427
|
+
if (!child || child.status !== "detached") return;
|
|
428
|
+
const updatedAt = event.ts;
|
|
429
|
+
run.updatedAt = updatedAt;
|
|
430
|
+
run.children[index] = {
|
|
431
|
+
...child,
|
|
432
|
+
activityState: event.to,
|
|
433
|
+
updatedAt,
|
|
434
|
+
...(event.elapsedMs !== undefined ? { lastActivityAt: event.ts - event.elapsedMs } : {}),
|
|
435
|
+
...(event.currentTool ? { currentTool: event.currentTool } : {}),
|
|
436
|
+
...(event.currentToolDurationMs !== undefined ? { currentToolStartedAt: event.ts - event.currentToolDurationMs } : {}),
|
|
437
|
+
...(event.currentPath ? { currentPath: event.currentPath } : {}),
|
|
438
|
+
...(event.turns !== undefined ? { turnCount: event.turns } : {}),
|
|
439
|
+
...(event.tokens !== undefined ? { tokens: event.tokens } : {}),
|
|
440
|
+
...(event.toolCount !== undefined ? { toolCount: event.toolCount } : {}),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
390
444
|
function updateRememberedForegroundChild(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; index: number; result: SingleResult; events: IntercomEventBus }): void {
|
|
391
445
|
state.foregroundRuns ??= new Map();
|
|
392
446
|
const updatedAt = Date.now();
|
|
@@ -401,9 +455,11 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
401
455
|
...child,
|
|
402
456
|
agent: input.result.agent,
|
|
403
457
|
index: input.index,
|
|
458
|
+
...(input.result.context ? { context: input.result.context } : {}),
|
|
404
459
|
status: input.result.acceptance?.status === "rejected"
|
|
405
460
|
? "failed"
|
|
406
461
|
: resolveSubagentResultStatus({ exitCode: input.result.exitCode, interrupted: input.result.interrupted, detached: false }),
|
|
462
|
+
...foregroundChildActivityFromProgress(input.result.progress),
|
|
407
463
|
updatedAt,
|
|
408
464
|
...(input.result.exitCode !== undefined ? { exitCode: input.result.exitCode } : {}),
|
|
409
465
|
...(input.result.error ? { error: input.result.error } : {}),
|
|
@@ -424,6 +480,9 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
424
480
|
const summary = !success && input.result.error
|
|
425
481
|
? `${input.result.error}${output ? `\n\nOutput:\n${output}` : ""}`
|
|
426
482
|
: output || input.result.error || "Detached child exited without final output.";
|
|
483
|
+
// A detached callback may outlive its extension runtime. Stale sessions are
|
|
484
|
+
// intentionally dropped rather than routed through a replacement runtime.
|
|
485
|
+
if (!input.sessionId || input.sessionId !== state.currentSessionId) return;
|
|
427
486
|
input.events.emit(SUBAGENT_FOREGROUND_COMPLETE_EVENT, {
|
|
428
487
|
id: `${input.runId}:${input.index}`,
|
|
429
488
|
runId: input.runId,
|
|
@@ -824,6 +883,7 @@ function appendStepToAsyncChain(input: {
|
|
|
824
883
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
825
884
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
826
885
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
886
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
827
887
|
asyncDir: resolved.location.asyncDir,
|
|
828
888
|
validateOutputBindings: false,
|
|
829
889
|
});
|
|
@@ -1174,7 +1234,7 @@ async function resumeAsyncRun(input: {
|
|
|
1174
1234
|
};
|
|
1175
1235
|
}
|
|
1176
1236
|
const runId = randomUUID().slice(0, 8);
|
|
1177
|
-
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
1237
|
+
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
|
|
1178
1238
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1179
1239
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
1180
1240
|
const workflowTask = (input.params.task ?? followUp) || undefined;
|
|
@@ -1207,11 +1267,12 @@ async function resumeAsyncRun(input: {
|
|
|
1207
1267
|
availableModels,
|
|
1208
1268
|
cwd: effectiveCwd,
|
|
1209
1269
|
maxOutput: input.params.maxOutput,
|
|
1210
|
-
artifactsDir: getArtifactsDir(parentSessionFile, effectiveCwd),
|
|
1270
|
+
artifactsDir: getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir),
|
|
1211
1271
|
artifactConfig,
|
|
1212
1272
|
shareEnabled: input.params.share === true,
|
|
1213
1273
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
1214
1274
|
chainSkills: normalized === false ? [] : (normalized ?? []),
|
|
1275
|
+
agentContract: input.params.agentContract,
|
|
1215
1276
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1216
1277
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1217
1278
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
@@ -1237,8 +1298,8 @@ async function resumeAsyncRun(input: {
|
|
|
1237
1298
|
|
|
1238
1299
|
const runId = randomUUID().slice(0, 8);
|
|
1239
1300
|
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
|
|
1240
|
-
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
1241
|
-
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd);
|
|
1301
|
+
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
|
|
1302
|
+
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1242
1303
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1243
1304
|
const result = executeAsyncSingle(runId, {
|
|
1244
1305
|
agent: target.agent,
|
|
@@ -1283,6 +1344,8 @@ async function resumeAsyncRun(input: {
|
|
|
1283
1344
|
availableModels,
|
|
1284
1345
|
output: recoveryDescriptor?.outputPath,
|
|
1285
1346
|
outputMode: recoveryDescriptor?.outputMode,
|
|
1347
|
+
...(recoveryDescriptor?.agentContract ? { agentContract: recoveryDescriptor.agentContract } : {}),
|
|
1348
|
+
...(recoveryDescriptor?.structuredOutputSchema ? { structuredOutputSchema: recoveryDescriptor.structuredOutputSchema } : {}),
|
|
1286
1349
|
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
1287
1350
|
...(recoveryDescriptor?.acceptance !== undefined && input.params.acceptance === undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
|
|
1288
1351
|
...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
|
|
@@ -1322,19 +1385,22 @@ function formatFailedSingleRunOutput(result: SingleResult, displayOutput: string
|
|
|
1322
1385
|
if (output && output !== error.trim()) {
|
|
1323
1386
|
lines.push("", "Output:", output);
|
|
1324
1387
|
}
|
|
1325
|
-
if (result.artifactPaths?.outputPath) {
|
|
1388
|
+
if (result.artifactPaths?.outputPath && fs.existsSync(result.artifactPaths.outputPath)) {
|
|
1326
1389
|
lines.push("", `Output artifact: ${result.artifactPaths.outputPath}`);
|
|
1327
1390
|
}
|
|
1328
1391
|
return lines.join("\n");
|
|
1329
1392
|
}
|
|
1330
1393
|
|
|
1331
|
-
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi">): (event: ControlEvent) => void {
|
|
1332
|
-
return (event) =>
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1394
|
+
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi" | "state">): (event: ControlEvent) => void {
|
|
1395
|
+
return (event) => {
|
|
1396
|
+
applyControlEventToRememberedForegroundRun(deps.state, event);
|
|
1397
|
+
emitControlNotification({
|
|
1398
|
+
pi: deps.pi,
|
|
1399
|
+
controlConfig: data.controlConfig,
|
|
1400
|
+
intercomBridge: data.intercomBridge,
|
|
1401
|
+
event,
|
|
1402
|
+
});
|
|
1403
|
+
};
|
|
1338
1404
|
}
|
|
1339
1405
|
|
|
1340
1406
|
async function emitForegroundResultIntercom(input: {
|
|
@@ -1345,6 +1411,7 @@ async function emitForegroundResultIntercom(input: {
|
|
|
1345
1411
|
results: SingleResult[];
|
|
1346
1412
|
chainSteps?: number;
|
|
1347
1413
|
nestedChildren?: NestedRunSummary[];
|
|
1414
|
+
parallelHandoff?: Details["parallelHandoff"];
|
|
1348
1415
|
}): Promise<ReturnType<typeof buildSubagentResultIntercomPayload> | null> {
|
|
1349
1416
|
if (!input.intercomBridge.active || !input.intercomBridge.orchestratorTarget) return null;
|
|
1350
1417
|
const children = input.results.flatMap((result, index) => result.detached ? [] : [{
|
|
@@ -1368,6 +1435,7 @@ async function emitForegroundResultIntercom(input: {
|
|
|
1368
1435
|
source: "foreground",
|
|
1369
1436
|
children: attachNestedChildrenToResultChildren(input.runId, children, input.nestedChildren),
|
|
1370
1437
|
...(typeof input.chainSteps === "number" ? { chainSteps: input.chainSteps } : {}),
|
|
1438
|
+
...(input.parallelHandoff ? { parallelHandoff: input.parallelHandoff } : {}),
|
|
1371
1439
|
});
|
|
1372
1440
|
const delivered = await deliverSubagentResultIntercomEvent(input.pi.events, payload);
|
|
1373
1441
|
if (!delivered) return null;
|
|
@@ -1390,6 +1458,7 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1390
1458
|
results: input.details.results,
|
|
1391
1459
|
...(typeof input.details.totalSteps === "number" ? { chainSteps: input.details.totalSteps } : {}),
|
|
1392
1460
|
...(input.nestedChildren?.length ? { nestedChildren: input.nestedChildren } : {}),
|
|
1461
|
+
...(input.details.parallelHandoff ? { parallelHandoff: input.details.parallelHandoff } : {}),
|
|
1393
1462
|
});
|
|
1394
1463
|
if (!payload) return null;
|
|
1395
1464
|
return {
|
|
@@ -1531,7 +1600,8 @@ function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
|
1531
1600
|
|
|
1532
1601
|
interface AgentDefaultContextPolicy {
|
|
1533
1602
|
params: SubagentParamsLike;
|
|
1534
|
-
contextForAgent(agentName: string):
|
|
1603
|
+
contextForAgent(agentName: string): ContextMode;
|
|
1604
|
+
contextSummary?: ContextSummary;
|
|
1535
1605
|
usesFork: boolean;
|
|
1536
1606
|
}
|
|
1537
1607
|
|
|
@@ -1540,12 +1610,15 @@ function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: Ag
|
|
|
1540
1610
|
return resolveExplicitContextPolicy(params);
|
|
1541
1611
|
}
|
|
1542
1612
|
const byName = new Map(agents.map((agent) => [agent.name, agent]));
|
|
1543
|
-
const contextForAgent = (agentName: string):
|
|
1613
|
+
const contextForAgent = (agentName: string): ContextMode =>
|
|
1544
1614
|
byName.get(agentName)?.defaultContext === "fork" ? "fork" : "fresh";
|
|
1545
|
-
const
|
|
1615
|
+
const requestedAgentNames = collectRequestedAgentNames(params);
|
|
1616
|
+
const contextSummary = summarizeContextModes(requestedAgentNames.map((name) => contextForAgent(name)));
|
|
1617
|
+
const usesFork = contextSummary === "fork" || contextSummary === "mixed";
|
|
1546
1618
|
return {
|
|
1547
|
-
params
|
|
1619
|
+
params,
|
|
1548
1620
|
contextForAgent,
|
|
1621
|
+
contextSummary,
|
|
1549
1622
|
usesFork,
|
|
1550
1623
|
};
|
|
1551
1624
|
}
|
|
@@ -1555,6 +1628,7 @@ function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultC
|
|
|
1555
1628
|
return {
|
|
1556
1629
|
params,
|
|
1557
1630
|
contextForAgent: () => context,
|
|
1631
|
+
contextSummary: context,
|
|
1558
1632
|
usesFork: context === "fork",
|
|
1559
1633
|
};
|
|
1560
1634
|
}
|
|
@@ -1571,8 +1645,12 @@ function shouldForkAgent(contextPolicy: AgentDefaultContextPolicy, agentName: st
|
|
|
1571
1645
|
return contextPolicy.contextForAgent(agentName) === "fork";
|
|
1572
1646
|
}
|
|
1573
1647
|
|
|
1648
|
+
function summarizeResultContext(details: Details, fallback: ContextSummary | undefined): ContextSummary | undefined {
|
|
1649
|
+
return summarizeContextModes(details.results.map((result) => result.context)) ?? fallback;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1574
1652
|
function buildRequestedModeError(params: SubagentParamsLike, message: string): AgentToolResult<Details> {
|
|
1575
|
-
return
|
|
1653
|
+
return withResolvedContext(
|
|
1576
1654
|
{
|
|
1577
1655
|
content: [{ type: "text", text: message }],
|
|
1578
1656
|
isError: true,
|
|
@@ -1617,8 +1695,12 @@ function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: num
|
|
|
1617
1695
|
return { timeoutMs: rawTimeout ?? rawMaxRuntime };
|
|
1618
1696
|
}
|
|
1619
1697
|
|
|
1620
|
-
function resolveToolBudget(
|
|
1621
|
-
|
|
1698
|
+
function resolveToolBudget(
|
|
1699
|
+
raw: unknown,
|
|
1700
|
+
label = "toolBudget",
|
|
1701
|
+
options?: { minimumHard?: 0 | 1 },
|
|
1702
|
+
): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
1703
|
+
const resolved = validateToolBudgetConfig(raw, label, options);
|
|
1622
1704
|
return { toolBudget: resolved.budget, error: resolved.error };
|
|
1623
1705
|
}
|
|
1624
1706
|
|
|
@@ -1688,16 +1770,18 @@ function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?:
|
|
|
1688
1770
|
return { params };
|
|
1689
1771
|
}
|
|
1690
1772
|
|
|
1691
|
-
function
|
|
1773
|
+
function withResolvedContext(
|
|
1692
1774
|
result: AgentToolResult<Details>,
|
|
1693
|
-
|
|
1775
|
+
fallback: ContextSummary | undefined,
|
|
1694
1776
|
): AgentToolResult<Details> {
|
|
1695
|
-
if (
|
|
1777
|
+
if (!result.details) return result;
|
|
1778
|
+
const context = summarizeResultContext(result.details, fallback);
|
|
1779
|
+
if (!context) return result;
|
|
1696
1780
|
return {
|
|
1697
1781
|
...result,
|
|
1698
1782
|
details: {
|
|
1699
1783
|
...result.details,
|
|
1700
|
-
context
|
|
1784
|
+
context,
|
|
1701
1785
|
},
|
|
1702
1786
|
};
|
|
1703
1787
|
}
|
|
@@ -1715,15 +1799,15 @@ function withForkThinkingNotes(
|
|
|
1715
1799
|
return { ...result, content: [...result.content, { type: "text", text: note }] };
|
|
1716
1800
|
}
|
|
1717
1801
|
|
|
1718
|
-
function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): AgentToolResult<Details> {
|
|
1802
|
+
function toExecutionErrorResult(params: SubagentParamsLike, error: unknown, contextSummary?: ContextSummary): AgentToolResult<Details> {
|
|
1719
1803
|
const message = error instanceof Error ? error.message : String(error);
|
|
1720
|
-
return
|
|
1804
|
+
return withResolvedContext(
|
|
1721
1805
|
{
|
|
1722
1806
|
content: [{ type: "text", text: message }],
|
|
1723
1807
|
isError: true,
|
|
1724
1808
|
details: { mode: getRequestedModeLabel(params), results: [] },
|
|
1725
1809
|
},
|
|
1726
|
-
|
|
1810
|
+
contextSummary,
|
|
1727
1811
|
);
|
|
1728
1812
|
}
|
|
1729
1813
|
|
|
@@ -1969,6 +2053,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1969
2053
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1970
2054
|
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
1971
2055
|
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
2056
|
+
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
2057
|
+
...(task.agentContract !== undefined ? { agentContract: task.agentContract } : {}),
|
|
1972
2058
|
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
1973
2059
|
}));
|
|
1974
2060
|
return executeAsyncChain(id, {
|
|
@@ -1991,12 +2077,14 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1991
2077
|
chainSkills: [],
|
|
1992
2078
|
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
1993
2079
|
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
2080
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
1994
2081
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1995
2082
|
waitToolEnabled: deps.waitToolEnabled,
|
|
1996
2083
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1997
2084
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1998
2085
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1999
2086
|
controlConfig,
|
|
2087
|
+
agentContract: params.agentContract,
|
|
2000
2088
|
controlIntercomTarget,
|
|
2001
2089
|
childIntercomTarget,
|
|
2002
2090
|
nestedRoute,
|
|
@@ -2029,6 +2117,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2029
2117
|
chainSkills,
|
|
2030
2118
|
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2031
2119
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2120
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
2032
2121
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2033
2122
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2034
2123
|
waitToolEnabled: deps.waitToolEnabled,
|
|
@@ -2036,6 +2125,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2036
2125
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2037
2126
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2038
2127
|
controlConfig,
|
|
2128
|
+
agentContract: params.agentContract,
|
|
2039
2129
|
controlIntercomTarget,
|
|
2040
2130
|
childIntercomTarget,
|
|
2041
2131
|
nestedRoute,
|
|
@@ -2077,6 +2167,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2077
2167
|
shareEnabled,
|
|
2078
2168
|
sessionRoot,
|
|
2079
2169
|
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2170
|
+
context: contextPolicy.contextForAgent(params.agent!),
|
|
2080
2171
|
skills,
|
|
2081
2172
|
output: effectiveOutput,
|
|
2082
2173
|
outputMode: effectiveOutputMode,
|
|
@@ -2092,6 +2183,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2092
2183
|
controlIntercomTarget,
|
|
2093
2184
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
2094
2185
|
nestedRoute,
|
|
2186
|
+
agentContract: params.agentContract,
|
|
2187
|
+
structuredOutputSchema: params.outputSchema,
|
|
2095
2188
|
acceptance: params.acceptance,
|
|
2096
2189
|
timeoutMs: data.timeoutMs,
|
|
2097
2190
|
turnBudget: data.turnBudget,
|
|
@@ -2145,6 +2238,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2145
2238
|
sessionFileForIndex,
|
|
2146
2239
|
sessionFileForTask,
|
|
2147
2240
|
thinkingOverrideForTask,
|
|
2241
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
2148
2242
|
artifactsDir,
|
|
2149
2243
|
artifactConfig,
|
|
2150
2244
|
includeProgress: params.includeProgress,
|
|
@@ -2152,6 +2246,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2152
2246
|
onUpdate,
|
|
2153
2247
|
onControlEvent,
|
|
2154
2248
|
controlConfig,
|
|
2249
|
+
agentContract: params.agentContract,
|
|
2155
2250
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
2156
2251
|
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2157
2252
|
foregroundControl,
|
|
@@ -2210,6 +2305,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2210
2305
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
2211
2306
|
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2212
2307
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2308
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
2213
2309
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2214
2310
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2215
2311
|
waitToolEnabled: deps.waitToolEnabled,
|
|
@@ -2217,6 +2313,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2217
2313
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2218
2314
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2219
2315
|
controlConfig,
|
|
2316
|
+
agentContract: params.agentContract,
|
|
2220
2317
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2221
2318
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2222
2319
|
nestedRoute: data.nestedRoute,
|
|
@@ -2260,6 +2357,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2260
2357
|
interface ForegroundParallelRunInput {
|
|
2261
2358
|
tasks: TaskParam[];
|
|
2262
2359
|
taskTexts: string[];
|
|
2360
|
+
taskDescriptions: string[];
|
|
2263
2361
|
agents: AgentConfig[];
|
|
2264
2362
|
ctx: ExtensionContext;
|
|
2265
2363
|
state: SubagentState;
|
|
@@ -2286,6 +2384,7 @@ interface ForegroundParallelRunInput {
|
|
|
2286
2384
|
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
2287
2385
|
firstProgressIndex: number;
|
|
2288
2386
|
controlConfig: ResolvedControlConfig;
|
|
2387
|
+
contextPolicy: AgentDefaultContextPolicy;
|
|
2289
2388
|
onControlEvent?: (event: ControlEvent) => void;
|
|
2290
2389
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
2291
2390
|
orchestratorIntercomTarget?: string;
|
|
@@ -2300,8 +2399,8 @@ interface ForegroundParallelRunInput {
|
|
|
2300
2399
|
deadlineAt?: number;
|
|
2301
2400
|
turnBudget?: ResolvedTurnBudget;
|
|
2302
2401
|
toolBudgets: (ResolvedToolBudget | undefined)[];
|
|
2402
|
+
agentContract?: AgentContract;
|
|
2303
2403
|
}
|
|
2304
|
-
|
|
2305
2404
|
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
2306
2405
|
return {
|
|
2307
2406
|
content: [{ type: "text", text: message }],
|
|
@@ -2374,15 +2473,52 @@ function resolveParallelTaskCwd(
|
|
|
2374
2473
|
return resolveChildCwd(paramsCwd, task.cwd);
|
|
2375
2474
|
}
|
|
2376
2475
|
|
|
2377
|
-
function
|
|
2378
|
-
worktreeSetup: WorktreeSetup
|
|
2379
|
-
artifactsDir: string
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2476
|
+
function finalizeParallelWorktreeHandoff(input: {
|
|
2477
|
+
worktreeSetup: WorktreeSetup;
|
|
2478
|
+
artifactsDir: string;
|
|
2479
|
+
runId: string;
|
|
2480
|
+
cwd: string;
|
|
2481
|
+
tasks: TaskParam[];
|
|
2482
|
+
results: SingleResult[];
|
|
2483
|
+
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
2484
|
+
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
2485
|
+
const diffs = diffWorktrees(input.worktreeSetup, input.tasks.map((task) => task.agent), diffsDir);
|
|
2486
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup);
|
|
2487
|
+
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
2488
|
+
try {
|
|
2489
|
+
const reference = writeParallelHandoffGroup({
|
|
2490
|
+
manifestPath: parallelHandoffPath(input.artifactsDir, input.runId),
|
|
2491
|
+
runId: input.runId,
|
|
2492
|
+
mode: "parallel",
|
|
2493
|
+
source: "foreground",
|
|
2494
|
+
cwd: input.cwd,
|
|
2495
|
+
stepIndex: 0,
|
|
2496
|
+
flatStartIndex: 0,
|
|
2497
|
+
setup: input.worktreeSetup,
|
|
2498
|
+
diffs,
|
|
2499
|
+
cleanup,
|
|
2500
|
+
results: input.results.map((result) => ({
|
|
2501
|
+
agent: result.agent,
|
|
2502
|
+
status: resolveSubagentResultStatus({
|
|
2503
|
+
exitCode: result.exitCode,
|
|
2504
|
+
interrupted: result.interrupted,
|
|
2505
|
+
detached: result.detached,
|
|
2506
|
+
state: result.stopped ? "stopped" : undefined,
|
|
2507
|
+
}),
|
|
2508
|
+
summary: resultSummaryForIntercom(result),
|
|
2509
|
+
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
2510
|
+
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
2511
|
+
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
2512
|
+
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
2513
|
+
})),
|
|
2514
|
+
});
|
|
2515
|
+
return {
|
|
2516
|
+
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
2517
|
+
reference,
|
|
2518
|
+
};
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
|
|
2521
|
+
}
|
|
2386
2522
|
}
|
|
2387
2523
|
|
|
2388
2524
|
function findDuplicateParallelOutputPath(input: {
|
|
@@ -2435,20 +2571,23 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2435
2571
|
);
|
|
2436
2572
|
const interruptController = new AbortController();
|
|
2437
2573
|
if (input.foregroundControl) {
|
|
2438
|
-
input.foregroundControl
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
};
|
|
2574
|
+
beginForegroundChild(input.foregroundControl, {
|
|
2575
|
+
index,
|
|
2576
|
+
agent: task.agent,
|
|
2577
|
+
description: input.taskDescriptions[index],
|
|
2578
|
+
interrupt: () => {
|
|
2579
|
+
if (interruptController.signal.aborted) return false;
|
|
2580
|
+
interruptController.abort();
|
|
2581
|
+
return true;
|
|
2582
|
+
},
|
|
2583
|
+
});
|
|
2449
2584
|
}
|
|
2585
|
+
const structuredRuntime = task.outputSchema
|
|
2586
|
+
? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
|
|
2587
|
+
: undefined;
|
|
2450
2588
|
return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
2451
2589
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
2590
|
+
context: input.contextPolicy.contextForAgent(task.agent),
|
|
2452
2591
|
cwd: taskCwd,
|
|
2453
2592
|
signal: input.signal,
|
|
2454
2593
|
interruptSignal: interruptController.signal,
|
|
@@ -2478,6 +2617,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2478
2617
|
preferredModelProvider: input.ctx.model?.provider,
|
|
2479
2618
|
modelScope: input.modelScope,
|
|
2480
2619
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
2620
|
+
structuredOutput: structuredRuntime,
|
|
2621
|
+
agentContract: task.agentContract ?? input.agentContract,
|
|
2481
2622
|
acceptance: task.acceptance,
|
|
2482
2623
|
acceptanceContext: { mode: "parallel" },
|
|
2483
2624
|
timeoutMs: input.timeoutMs,
|
|
@@ -2489,18 +2630,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2489
2630
|
const stepResults = progressUpdate.details?.results || [];
|
|
2490
2631
|
const stepProgress = progressUpdate.details?.progress || [];
|
|
2491
2632
|
if (input.foregroundControl && stepProgress.length > 0) {
|
|
2492
|
-
|
|
2493
|
-
input.foregroundControl.currentAgent = task.agent;
|
|
2494
|
-
input.foregroundControl.currentIndex = index;
|
|
2495
|
-
input.foregroundControl.currentActivityState = current?.activityState;
|
|
2496
|
-
input.foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
2497
|
-
input.foregroundControl.currentTool = current?.currentTool;
|
|
2498
|
-
input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
2499
|
-
input.foregroundControl.currentPath = current?.currentPath;
|
|
2500
|
-
input.foregroundControl.turnCount = current?.turnCount;
|
|
2501
|
-
input.foregroundControl.tokens = current?.tokens;
|
|
2502
|
-
input.foregroundControl.toolCount = current?.toolCount;
|
|
2503
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
2633
|
+
updateForegroundChild(input.foregroundControl, index, stepProgress[0]);
|
|
2504
2634
|
}
|
|
2505
2635
|
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
2506
2636
|
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
@@ -2519,10 +2649,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2519
2649
|
}
|
|
2520
2650
|
: undefined,
|
|
2521
2651
|
}).finally(() => {
|
|
2522
|
-
if (input.foregroundControl
|
|
2523
|
-
input.foregroundControl.interrupt = undefined;
|
|
2524
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
2525
|
-
}
|
|
2652
|
+
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
|
|
2526
2653
|
});
|
|
2527
2654
|
}, input.globalSemaphore);
|
|
2528
2655
|
}
|
|
@@ -2687,7 +2814,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2687
2814
|
...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
|
|
2688
2815
|
...(progress !== undefined ? { progress } : {}),
|
|
2689
2816
|
...(t.toolBudget !== undefined ? { toolBudget: t.toolBudget } : {}),
|
|
2817
|
+
...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
|
|
2690
2818
|
...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
|
|
2819
|
+
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
2691
2820
|
};
|
|
2692
2821
|
});
|
|
2693
2822
|
return executeAsyncChain(id, {
|
|
@@ -2706,22 +2835,33 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2706
2835
|
chainSkills: [],
|
|
2707
2836
|
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
|
|
2708
2837
|
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
|
|
2838
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
2709
2839
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2710
2840
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2711
2841
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2712
2842
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2713
2843
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2714
2844
|
controlConfig,
|
|
2845
|
+
agentContract: params.agentContract,
|
|
2715
2846
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2716
2847
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2848
|
+
nestedRoute: data.nestedRoute,
|
|
2717
2849
|
timeoutMs: data.timeoutMs,
|
|
2718
2850
|
turnBudget: data.turnBudget,
|
|
2851
|
+
toolBudget: data.toolBudget,
|
|
2852
|
+
configToolBudget: data.configToolBudget,
|
|
2719
2853
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2720
2854
|
});
|
|
2721
2855
|
}
|
|
2722
2856
|
}
|
|
2723
2857
|
|
|
2724
|
-
const behaviors =
|
|
2858
|
+
const behaviors = tasks.map((task, index) => {
|
|
2859
|
+
let behavior = suppressProgressForReadOnlyTask(resolveStepBehavior(agentConfigs[index]!, behaviorOverrides[index]!), taskTexts[index]);
|
|
2860
|
+
if (behaviorOverrides[index]?.output === undefined && typeof behavior.output === "string" && !path.isAbsolute(behavior.output)) {
|
|
2861
|
+
behavior = { ...behavior, output: path.join("parallel-0", `${index}-${task.agent}`, behavior.output) };
|
|
2862
|
+
}
|
|
2863
|
+
return behavior;
|
|
2864
|
+
});
|
|
2725
2865
|
const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress);
|
|
2726
2866
|
const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
2727
2867
|
const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
@@ -2737,6 +2877,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2737
2877
|
);
|
|
2738
2878
|
if (errorResult) return errorResult;
|
|
2739
2879
|
|
|
2880
|
+
let worktreeFinalized = false;
|
|
2740
2881
|
try {
|
|
2741
2882
|
const outputBaseDir = path.join(artifactsDir, "outputs", runId);
|
|
2742
2883
|
const duplicateOutputError = findDuplicateParallelOutputPath({
|
|
@@ -2759,6 +2900,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2759
2900
|
const parallelProgressDir = path.join(artifactsDir, "progress", runId);
|
|
2760
2901
|
if (parallelProgressPrecreated) writeInitialProgressFile(parallelProgressDir);
|
|
2761
2902
|
|
|
2903
|
+
const taskDescriptions = taskTexts.map((taskText) => taskText.trim());
|
|
2762
2904
|
for (let i = 0; i < taskTexts.length; i++) {
|
|
2763
2905
|
if (shouldForkAgent(contextPolicy, tasks[i]!.agent)) taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
2764
2906
|
}
|
|
@@ -2767,6 +2909,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2767
2909
|
const results = await runForegroundParallelTasks({
|
|
2768
2910
|
tasks,
|
|
2769
2911
|
taskTexts,
|
|
2912
|
+
taskDescriptions,
|
|
2770
2913
|
agents,
|
|
2771
2914
|
ctx,
|
|
2772
2915
|
state: deps.state,
|
|
@@ -2791,6 +2934,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2791
2934
|
behaviors,
|
|
2792
2935
|
firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
|
|
2793
2936
|
controlConfig,
|
|
2937
|
+
contextPolicy,
|
|
2794
2938
|
onControlEvent,
|
|
2795
2939
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
2796
2940
|
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
@@ -2807,6 +2951,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2807
2951
|
deadlineAt,
|
|
2808
2952
|
turnBudget: data.turnBudget,
|
|
2809
2953
|
toolBudgets,
|
|
2954
|
+
agentContract: params.agentContract,
|
|
2810
2955
|
});
|
|
2811
2956
|
for (let i = 0; i < results.length; i++) {
|
|
2812
2957
|
const run = results[i]!;
|
|
@@ -2822,6 +2967,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2822
2967
|
updateForegroundNestedProjection(foregroundControl);
|
|
2823
2968
|
attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
|
|
2824
2969
|
}
|
|
2970
|
+
let handoff: ReturnType<typeof finalizeParallelWorktreeHandoff> | undefined;
|
|
2971
|
+
if (worktreeSetup) {
|
|
2972
|
+
worktreeFinalized = true;
|
|
2973
|
+
handoff = finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
|
|
2974
|
+
}
|
|
2825
2975
|
const interrupted = results.find((result) => result.interrupted);
|
|
2826
2976
|
const details = compactForegroundDetails({
|
|
2827
2977
|
mode: "parallel",
|
|
@@ -2831,6 +2981,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2831
2981
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
2832
2982
|
totalChildUsage: sumResultsUsage(results),
|
|
2833
2983
|
totalCost: sumResultsCost(results),
|
|
2984
|
+
...(handoff?.reference ? { parallelHandoff: handoff.reference } : {}),
|
|
2834
2985
|
});
|
|
2835
2986
|
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
2836
2987
|
if (interrupted) {
|
|
@@ -2864,7 +3015,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2864
3015
|
};
|
|
2865
3016
|
}
|
|
2866
3017
|
|
|
2867
|
-
const worktreeSuffix =
|
|
3018
|
+
const worktreeSuffix = handoff?.suffix ?? "";
|
|
2868
3019
|
const ok = results.filter((result) => result.exitCode === 0).length;
|
|
2869
3020
|
const downgradeNote = backgroundRequestedWhileClarifying ? " (background requested, but clarify kept this run foreground)" : "";
|
|
2870
3021
|
const aggregatedOutput = aggregateParallelOutputs(
|
|
@@ -2888,7 +3039,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2888
3039
|
details,
|
|
2889
3040
|
};
|
|
2890
3041
|
} finally {
|
|
2891
|
-
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
3042
|
+
if (worktreeSetup && !worktreeFinalized) cleanupWorktrees(worktreeSetup);
|
|
2892
3043
|
}
|
|
2893
3044
|
}
|
|
2894
3045
|
|
|
@@ -2924,7 +3075,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2924
3075
|
};
|
|
2925
3076
|
}
|
|
2926
3077
|
const effectiveToolBudget = resolveEffectiveToolBudget({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget });
|
|
2927
|
-
if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error));
|
|
3078
|
+
if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error), data.contextPolicy.contextSummary);
|
|
2928
3079
|
|
|
2929
3080
|
const currentProvider = ctx.model?.provider;
|
|
2930
3081
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -3009,6 +3160,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3009
3160
|
shareEnabled,
|
|
3010
3161
|
sessionRoot,
|
|
3011
3162
|
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
3163
|
+
context: contextPolicy.contextForAgent(params.agent!),
|
|
3012
3164
|
skills: skillOverride === false ? [] : skillOverride,
|
|
3013
3165
|
output: effectiveOutput,
|
|
3014
3166
|
outputMode: effectiveOutputMode,
|
|
@@ -3023,9 +3175,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3023
3175
|
controlConfig,
|
|
3024
3176
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3025
3177
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3178
|
+
nestedRoute: data.nestedRoute,
|
|
3179
|
+
agentContract: params.agentContract,
|
|
3180
|
+
structuredOutputSchema: params.outputSchema,
|
|
3181
|
+
acceptance: params.acceptance,
|
|
3026
3182
|
timeoutMs: data.timeoutMs,
|
|
3027
3183
|
turnBudget: data.turnBudget,
|
|
3028
3184
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3185
|
+
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3029
3186
|
});
|
|
3030
3187
|
}
|
|
3031
3188
|
}
|
|
@@ -3039,6 +3196,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3039
3196
|
if (validationError) {
|
|
3040
3197
|
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
3041
3198
|
}
|
|
3199
|
+
const structuredRuntime = params.outputSchema
|
|
3200
|
+
? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined)
|
|
3201
|
+
: undefined;
|
|
3042
3202
|
task = injectSingleOutputInstruction(task, outputPath, agentConfig);
|
|
3043
3203
|
|
|
3044
3204
|
let effectiveSkills: string[] | undefined;
|
|
@@ -3050,91 +3210,76 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3050
3210
|
const interruptController = new AbortController();
|
|
3051
3211
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3052
3212
|
if (foregroundControl) {
|
|
3053
|
-
foregroundControl
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
};
|
|
3213
|
+
beginForegroundChild(foregroundControl, {
|
|
3214
|
+
index: 0,
|
|
3215
|
+
agent: params.agent!,
|
|
3216
|
+
description: foregroundControl.description,
|
|
3217
|
+
interrupt: () => {
|
|
3218
|
+
if (interruptController.signal.aborted) return false;
|
|
3219
|
+
interruptController.abort();
|
|
3220
|
+
return true;
|
|
3221
|
+
},
|
|
3222
|
+
});
|
|
3064
3223
|
}
|
|
3065
3224
|
|
|
3066
3225
|
const forwardSingleUpdate = onUpdate
|
|
3067
3226
|
? (update: AgentToolResult<Details>) => {
|
|
3068
|
-
if (foregroundControl)
|
|
3069
|
-
const firstProgress = update.details?.progress?.[0];
|
|
3070
|
-
foregroundControl.currentAgent = params.agent;
|
|
3071
|
-
foregroundControl.currentIndex = firstProgress?.index ?? 0;
|
|
3072
|
-
foregroundControl.currentActivityState = firstProgress?.activityState;
|
|
3073
|
-
foregroundControl.lastActivityAt = firstProgress?.lastActivityAt;
|
|
3074
|
-
foregroundControl.currentTool = firstProgress?.currentTool;
|
|
3075
|
-
foregroundControl.currentToolStartedAt = firstProgress?.currentToolStartedAt;
|
|
3076
|
-
foregroundControl.currentPath = firstProgress?.currentPath;
|
|
3077
|
-
foregroundControl.turnCount = firstProgress?.turnCount;
|
|
3078
|
-
foregroundControl.tokens = firstProgress?.tokens;
|
|
3079
|
-
foregroundControl.toolCount = firstProgress?.toolCount;
|
|
3080
|
-
foregroundControl.updatedAt = Date.now();
|
|
3081
|
-
}
|
|
3227
|
+
if (foregroundControl) updateForegroundChild(foregroundControl, 0, update.details?.progress?.[0]);
|
|
3082
3228
|
onUpdate(update);
|
|
3083
3229
|
}
|
|
3084
3230
|
: undefined;
|
|
3085
3231
|
|
|
3086
3232
|
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
cwd
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
foregroundControl.toolCount = r.progress?.toolCount;
|
|
3136
|
-
foregroundControl.updatedAt = Date.now();
|
|
3233
|
+
let r: Awaited<ReturnType<typeof runSync>>;
|
|
3234
|
+
try {
|
|
3235
|
+
r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
3236
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3237
|
+
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3238
|
+
cwd: effectiveCwd,
|
|
3239
|
+
signal,
|
|
3240
|
+
interruptSignal: interruptController.signal,
|
|
3241
|
+
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
3242
|
+
intercomEvents: deps.pi.events,
|
|
3243
|
+
runId,
|
|
3244
|
+
sessionDir: sessionDirForIndex(0),
|
|
3245
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
3246
|
+
share: shareEnabled,
|
|
3247
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3248
|
+
artifactConfig,
|
|
3249
|
+
maxOutput: params.maxOutput,
|
|
3250
|
+
outputPath,
|
|
3251
|
+
outputMode: effectiveOutputMode,
|
|
3252
|
+
maxSubagentDepth,
|
|
3253
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
3254
|
+
onUpdate: forwardSingleUpdate,
|
|
3255
|
+
controlConfig,
|
|
3256
|
+
onControlEvent,
|
|
3257
|
+
intercomSessionName: childIntercomTarget,
|
|
3258
|
+
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3259
|
+
nestedRoute: foregroundControl?.nestedRoute,
|
|
3260
|
+
index: 0,
|
|
3261
|
+
modelOverride,
|
|
3262
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
3263
|
+
availableModels,
|
|
3264
|
+
preferredModelProvider: currentProvider,
|
|
3265
|
+
modelScope: data.modelScope,
|
|
3266
|
+
skills: effectiveSkills,
|
|
3267
|
+
structuredOutput: structuredRuntime,
|
|
3268
|
+
agentContract: params.agentContract,
|
|
3269
|
+
acceptance: params.acceptance,
|
|
3270
|
+
acceptanceContext: { mode: "single" },
|
|
3271
|
+
onDetachedExit: (result) => updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events }),
|
|
3272
|
+
timeoutMs: data.timeoutMs,
|
|
3273
|
+
deadlineAt,
|
|
3274
|
+
turnBudget: data.turnBudget,
|
|
3275
|
+
enforceHardTurnLimit: params.enforceHardTurnLimit,
|
|
3276
|
+
toolBudget: effectiveToolBudget.toolBudget,
|
|
3277
|
+
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3278
|
+
});
|
|
3279
|
+
} finally {
|
|
3280
|
+
if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
|
|
3137
3281
|
}
|
|
3282
|
+
if (foregroundControl) finishForegroundChild(foregroundControl, 0);
|
|
3138
3283
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
3139
3284
|
|
|
3140
3285
|
if (r.progress) allProgress.push(r.progress);
|
|
@@ -3254,7 +3399,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3254
3399
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3255
3400
|
ctx: ExtensionContext,
|
|
3256
3401
|
) => Promise<AgentToolResult<Details>>;
|
|
3402
|
+
/**
|
|
3403
|
+
* Correlated extension-to-extension delegation owns its request IDs and
|
|
3404
|
+
* cancellation controllers, so independent requests may execute concurrently.
|
|
3405
|
+
* The ordinary model-facing tool keeps the one-foreground-call-per-turn guard.
|
|
3406
|
+
*/
|
|
3407
|
+
executeDelegated: (
|
|
3408
|
+
id: string,
|
|
3409
|
+
params: SubagentParamsLike,
|
|
3410
|
+
signal: AbortSignal,
|
|
3411
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3412
|
+
ctx: ExtensionContext,
|
|
3413
|
+
) => Promise<AgentToolResult<Details>>;
|
|
3257
3414
|
} {
|
|
3415
|
+
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
3416
|
+
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
3258
3417
|
const execute = async (
|
|
3259
3418
|
_id: string,
|
|
3260
3419
|
params: SubagentParamsLike,
|
|
@@ -3262,6 +3421,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3262
3421
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3263
3422
|
ctx: ExtensionContext,
|
|
3264
3423
|
): Promise<AgentToolResult<Details>> => {
|
|
3424
|
+
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
3425
|
+
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
3265
3426
|
deps.state.baseCwd = ctx.cwd;
|
|
3266
3427
|
deps.state.foregroundRuns ??= new Map();
|
|
3267
3428
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -3438,7 +3599,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3438
3599
|
try {
|
|
3439
3600
|
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR);
|
|
3440
3601
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
3441
|
-
return steerAsyncRun({
|
|
3602
|
+
return steerAsyncRun({
|
|
3603
|
+
state: deps.state,
|
|
3604
|
+
runId,
|
|
3605
|
+
message,
|
|
3606
|
+
index: paramsWithResolvedCwd.index,
|
|
3607
|
+
kill: deps.kill,
|
|
3608
|
+
location,
|
|
3609
|
+
signal,
|
|
3610
|
+
...(paramsWithResolvedCwd.steeringRecovery === false
|
|
3611
|
+
? {}
|
|
3612
|
+
: {
|
|
3613
|
+
recover: ({ absoluteDeadlineAt, ...limits }) =>
|
|
3614
|
+
resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, absoluteDeadlineAt }),
|
|
3615
|
+
}
|
|
3616
|
+
),
|
|
3617
|
+
});
|
|
3442
3618
|
} catch (error) {
|
|
3443
3619
|
const text = error instanceof Error ? error.message : String(error);
|
|
3444
3620
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -3455,7 +3631,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3455
3631
|
if (resolved?.kind === "nested") return steerNestedRun({ target: resolved, message, index: paramsWithResolvedCwd.index, signal });
|
|
3456
3632
|
if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='steer' currently supports live async Pi child sessions only; use action='interrupt' or action='resume' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
|
|
3457
3633
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3458
|
-
return steerAsyncRun({
|
|
3634
|
+
return steerAsyncRun({
|
|
3635
|
+
state: deps.state,
|
|
3636
|
+
runId: resolved.id,
|
|
3637
|
+
message,
|
|
3638
|
+
index: paramsWithResolvedCwd.index,
|
|
3639
|
+
kill: deps.kill,
|
|
3640
|
+
location: resolved.location,
|
|
3641
|
+
signal,
|
|
3642
|
+
...(paramsWithResolvedCwd.steeringRecovery === false
|
|
3643
|
+
? {}
|
|
3644
|
+
: {
|
|
3645
|
+
recover: ({ absoluteDeadlineAt, ...limits }) =>
|
|
3646
|
+
resumeAsyncRun({
|
|
3647
|
+
params: { ...limits, action: "resume", id: resolved!.id, message },
|
|
3648
|
+
requestCwd,
|
|
3649
|
+
ctx,
|
|
3650
|
+
deps,
|
|
3651
|
+
absoluteDeadlineAt,
|
|
3652
|
+
}),
|
|
3653
|
+
}
|
|
3654
|
+
),
|
|
3655
|
+
});
|
|
3459
3656
|
}
|
|
3460
3657
|
if (action === "append-step") {
|
|
3461
3658
|
return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
@@ -3593,7 +3790,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3593
3790
|
depth,
|
|
3594
3791
|
deps.config.forceTopLevelAsync === true,
|
|
3595
3792
|
);
|
|
3596
|
-
const runToolBudget = resolveToolBudget(
|
|
3793
|
+
const runToolBudget = resolveToolBudget(
|
|
3794
|
+
effectiveParams.toolBudget,
|
|
3795
|
+
"toolBudget",
|
|
3796
|
+
allowZeroToolBudget ? { minimumHard: 0 } : undefined,
|
|
3797
|
+
);
|
|
3597
3798
|
if (runToolBudget.error) return buildRequestedModeError(effectiveParams, runToolBudget.error);
|
|
3598
3799
|
const configToolBudget = resolveToolBudget(deps.config.toolBudget, "config.toolBudget");
|
|
3599
3800
|
if (configToolBudget.error) return buildRequestedModeError(effectiveParams, configToolBudget.error);
|
|
@@ -3615,7 +3816,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3615
3816
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
3616
3817
|
const intercomBridge = resolveIntercomBridge({
|
|
3617
3818
|
config: deps.config.intercomBridge,
|
|
3618
|
-
context: effectiveParams.context,
|
|
3819
|
+
context: effectiveParams.context ?? (contextPolicy.usesFork ? "fork" : undefined),
|
|
3619
3820
|
orchestratorTarget: sessionName,
|
|
3620
3821
|
});
|
|
3621
3822
|
const agents = intercomBridge.active
|
|
@@ -3688,7 +3889,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3688
3889
|
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
3689
3890
|
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
3690
3891
|
} catch (error) {
|
|
3691
|
-
return toExecutionErrorResult(effectiveParams, error);
|
|
3892
|
+
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
3692
3893
|
}
|
|
3693
3894
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
3694
3895
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
@@ -3698,8 +3899,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3698
3899
|
const artifactConfig: ArtifactConfig = {
|
|
3699
3900
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
3700
3901
|
enabled: effectiveParams.artifacts !== false,
|
|
3902
|
+
dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
3701
3903
|
};
|
|
3702
|
-
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd);
|
|
3904
|
+
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
3703
3905
|
|
|
3704
3906
|
let sessionRoot: string;
|
|
3705
3907
|
if (effectiveParams.sessionDir) {
|
|
@@ -3717,6 +3919,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3717
3919
|
return toExecutionErrorResult(
|
|
3718
3920
|
effectiveParams,
|
|
3719
3921
|
new Error(`Failed to create session directory '${sessionRoot}': ${message}`),
|
|
3922
|
+
contextPolicy.contextSummary,
|
|
3720
3923
|
);
|
|
3721
3924
|
}
|
|
3722
3925
|
const sessionDirForIndex = (idx?: number) =>
|
|
@@ -3727,11 +3930,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3727
3930
|
return forkSessionFileForIndex(idx);
|
|
3728
3931
|
};
|
|
3729
3932
|
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride) => {
|
|
3730
|
-
if (!shouldForkAgent(contextPolicy, agentName)) return
|
|
3933
|
+
if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
|
|
3731
3934
|
prepareForkThinking(agentName, idx, modelOverride);
|
|
3732
3935
|
const override = forkThinkingOverrideForIndex(idx);
|
|
3733
3936
|
if (override === "off") forkThinkingDowngrades.set(idx, agentName);
|
|
3734
|
-
return override;
|
|
3937
|
+
return override ?? delegatedThinkingOverride;
|
|
3735
3938
|
};
|
|
3736
3939
|
const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride) =>
|
|
3737
3940
|
forkSessionFileForTask(agentName, idx, modelOverride) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
@@ -3742,13 +3945,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3742
3945
|
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
3743
3946
|
}
|
|
3744
3947
|
} catch (error) {
|
|
3745
|
-
return toExecutionErrorResult(effectiveParams, error);
|
|
3948
|
+
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
3746
3949
|
}
|
|
3747
3950
|
const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
|
|
3748
|
-
if (chainBindingsError) return chainBindingsError;
|
|
3951
|
+
if (chainBindingsError) return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
|
|
3749
3952
|
|
|
3750
3953
|
const onUpdateWithContext = onUpdate
|
|
3751
|
-
? (r: AgentToolResult<Details>) => onUpdate(
|
|
3954
|
+
? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext(r, contextPolicy.contextSummary))
|
|
3752
3955
|
: undefined;
|
|
3753
3956
|
|
|
3754
3957
|
const reservation = reserveSpawnBudget(
|
|
@@ -3783,22 +3986,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3783
3986
|
timeoutMs: foregroundTimeout.timeoutMs,
|
|
3784
3987
|
turnBudget: turnBudget.turnBudget,
|
|
3785
3988
|
toolBudget: runToolBudget.toolBudget,
|
|
3989
|
+
allowZeroToolBudget,
|
|
3786
3990
|
configToolBudget: configToolBudget.toolBudget,
|
|
3787
3991
|
contextPolicy,
|
|
3788
3992
|
modelScope,
|
|
3789
3993
|
parentSessionId: deps.state.currentSessionId,
|
|
3790
3994
|
};
|
|
3791
3995
|
|
|
3792
|
-
const
|
|
3996
|
+
const foregroundDescription = effectiveParams.task?.trim()
|
|
3997
|
+
|| effectiveParams.tasks?.[0]?.task?.trim()
|
|
3998
|
+
|| (effectiveParams.chain ? firstRawChainTask(effectiveParams.chain)?.trim() : undefined);
|
|
3999
|
+
const foregroundControl: ForegroundRunControl | undefined = effectiveAsync
|
|
3793
4000
|
? undefined
|
|
3794
4001
|
: {
|
|
3795
4002
|
runId,
|
|
3796
4003
|
mode: foregroundMode,
|
|
3797
4004
|
startedAt: Date.now(),
|
|
3798
4005
|
updatedAt: Date.now(),
|
|
4006
|
+
cwd: effectiveCwd,
|
|
3799
4007
|
currentAgent: undefined,
|
|
3800
4008
|
currentIndex: undefined,
|
|
4009
|
+
description: foregroundDescription,
|
|
3801
4010
|
currentActivityState: undefined,
|
|
4011
|
+
activeChildren: new Map(),
|
|
3802
4012
|
nestedRoute,
|
|
3803
4013
|
interrupt: undefined,
|
|
3804
4014
|
};
|
|
@@ -3870,7 +4080,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3870
4080
|
let nestedForegroundStarted = false;
|
|
3871
4081
|
try {
|
|
3872
4082
|
const asyncResult = runAsyncPath(execData, deps);
|
|
3873
|
-
if (asyncResult) return
|
|
4083
|
+
if (asyncResult) return withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
3874
4084
|
if (foregroundControl) {
|
|
3875
4085
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
3876
4086
|
nestedForegroundStarted = true;
|
|
@@ -3878,20 +4088,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3878
4088
|
if (hasChain && effectiveParams.chain) {
|
|
3879
4089
|
const result = await runChainPath(execData, deps);
|
|
3880
4090
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3881
|
-
return
|
|
4091
|
+
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
3882
4092
|
}
|
|
3883
4093
|
if (hasTasks && effectiveParams.tasks) {
|
|
3884
4094
|
const result = await runParallelPath(execData, deps);
|
|
3885
4095
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3886
|
-
return
|
|
4096
|
+
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
3887
4097
|
}
|
|
3888
4098
|
if (hasSingle) {
|
|
3889
4099
|
const result = await runSinglePath(execData, deps);
|
|
3890
4100
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3891
|
-
return
|
|
4101
|
+
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
3892
4102
|
}
|
|
3893
4103
|
} catch (error) {
|
|
3894
|
-
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error), forkThinkingDowngrades);
|
|
4104
|
+
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
3895
4105
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
3896
4106
|
return errorResult;
|
|
3897
4107
|
} finally {
|
|
@@ -3904,11 +4114,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3904
4114
|
}
|
|
3905
4115
|
}
|
|
3906
4116
|
|
|
3907
|
-
return
|
|
4117
|
+
return withResolvedContext({
|
|
3908
4118
|
content: [{ type: "text", text: "Invalid params" }],
|
|
3909
4119
|
isError: true,
|
|
3910
4120
|
details: { mode: "single" as const, results: [] },
|
|
3911
|
-
},
|
|
4121
|
+
}, contextPolicy.contextSummary);
|
|
3912
4122
|
};
|
|
3913
4123
|
|
|
3914
4124
|
const executeWithSingleDispatchGuard = async (
|
|
@@ -3933,5 +4143,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3933
4143
|
}
|
|
3934
4144
|
};
|
|
3935
4145
|
|
|
3936
|
-
|
|
4146
|
+
const executeDelegated = async (
|
|
4147
|
+
id: string,
|
|
4148
|
+
params: SubagentParamsLike,
|
|
4149
|
+
signal: AbortSignal,
|
|
4150
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
4151
|
+
ctx: ExtensionContext,
|
|
4152
|
+
): Promise<AgentToolResult<Details>> => {
|
|
4153
|
+
const delegatedParams = { ...params };
|
|
4154
|
+
const privateParams = delegatedParams as SubagentParamsLike & {
|
|
4155
|
+
delegatedThinkingOverride?: AgentConfig["thinking"];
|
|
4156
|
+
delegatedAllowZeroToolBudget?: true;
|
|
4157
|
+
};
|
|
4158
|
+
const thinkingOverride = privateParams.delegatedThinkingOverride;
|
|
4159
|
+
const allowZeroToolBudget = privateParams.delegatedAllowZeroToolBudget === true;
|
|
4160
|
+
delete privateParams.delegatedThinkingOverride;
|
|
4161
|
+
delete privateParams.delegatedAllowZeroToolBudget;
|
|
4162
|
+
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
4163
|
+
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
4164
|
+
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
4165
|
+
};
|
|
4166
|
+
|
|
4167
|
+
return { execute: executeWithSingleDispatchGuard, executeDelegated };
|
|
3937
4168
|
}
|