pi-subagents 0.34.0 → 0.35.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 +78 -9
- package/README.md +213 -32
- package/index.ts +1 -0
- package/install.mjs +1 -1
- package/package.json +23 -8
- package/prompts/review-loop.md +3 -1
- package/skills/pi-subagents/SKILL.md +87 -25
- package/src/agents/agent-management.ts +82 -15
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +91 -49
- package/src/agents/frontmatter.ts +67 -13
- package/src/agents/skills.ts +25 -12
- package/src/api/background-work.ts +197 -0
- package/src/api/delegation.ts +158 -0
- package/src/extension/chain-validation.ts +165 -0
- package/src/extension/doctor.ts +15 -0
- package/src/extension/fanout-child.ts +3 -1
- package/src/extension/index.ts +65 -124
- package/src/extension/rpc.ts +10 -2
- package/src/extension/schemas.ts +18 -14
- package/src/extension/steering-notices.ts +35 -0
- package/src/extension/tool-description.ts +20 -9
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/native-supervisor-channel.ts +9 -1
- package/src/intercom/result-intercom.ts +4 -0
- package/src/runs/background/async-execution.ts +293 -44
- package/src/runs/background/async-job-tracker.ts +56 -9
- package/src/runs/background/async-resume.ts +159 -52
- package/src/runs/background/async-status.ts +25 -18
- package/src/runs/background/auto-drain.ts +67 -0
- package/src/runs/background/chain-root-attachment.ts +16 -8
- package/src/runs/background/control-channel.ts +260 -13
- package/src/runs/background/fleet-view.ts +23 -2
- package/src/runs/background/notify.ts +79 -10
- package/src/runs/background/result-watcher.ts +12 -9
- package/src/runs/background/run-id-resolver.ts +14 -2
- package/src/runs/background/run-status.ts +23 -15
- package/src/runs/background/scheduled-runs.ts +3 -0
- package/src/runs/background/stale-run-reconciler.ts +32 -10
- package/src/runs/background/steering.ts +237 -0
- package/src/runs/background/subagent-runner.ts +898 -236
- package/src/runs/background/subagent-wait.ts +484 -0
- package/src/runs/background/top-level-async.ts +2 -1
- package/src/runs/background/wait-config.ts +36 -0
- package/src/runs/background/wait-tool.ts +26 -0
- package/src/runs/foreground/async-steering-action.ts +230 -0
- package/src/runs/foreground/chain-clarify.ts +22 -6
- package/src/runs/foreground/chain-execution.ts +50 -32
- package/src/runs/foreground/execution.ts +308 -94
- package/src/runs/foreground/subagent-executor.ts +592 -268
- package/src/runs/shared/acceptance.ts +355 -97
- package/src/runs/shared/child-protocol.ts +121 -0
- package/src/runs/shared/completion-guard.ts +8 -127
- package/src/runs/shared/dynamic-fanout.ts +6 -4
- package/src/runs/shared/model-fallback.ts +36 -0
- package/src/runs/shared/nested-events.ts +9 -4
- package/src/runs/shared/nested-render.ts +4 -1
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +34 -7
- package/src/runs/shared/pi-spawn.ts +18 -12
- package/src/runs/shared/session-lease.ts +279 -0
- package/src/runs/shared/single-output.ts +61 -6
- package/src/runs/shared/spawn-budget.ts +128 -0
- package/src/runs/shared/subagent-control.ts +10 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
- package/src/runs/shared/task-intent.ts +176 -0
- package/src/runs/shared/tool-availability.ts +65 -0
- package/src/runs/shared/turn-budget.ts +49 -4
- package/src/shared/atomic-json.ts +4 -1
- package/src/shared/fork-context.ts +28 -3
- package/src/shared/model-info.ts +7 -4
- package/src/shared/status-format.ts +7 -1
- package/src/shared/types.ts +203 -25
- package/src/shared/utils.ts +35 -7
- package/src/slash/delegation-adapters.ts +457 -0
- package/src/slash/delegation-request.ts +103 -0
- package/src/slash/prompt-template-bridge.ts +167 -344
- package/src/slash/slash-commands.ts +239 -6
- package/src/slash/subagents-admin.ts +428 -0
- package/src/slash/subagents-editor.ts +86 -0
- package/src/tui/fleet.ts +405 -0
- package/src/tui/render.ts +90 -16
- package/src/watchdog/change-signature.ts +127 -0
- package/src/watchdog/child-status.ts +205 -0
- package/src/watchdog/emission-guard.ts +123 -0
- package/src/watchdog/lsp-diagnostics.ts +532 -0
- package/src/watchdog/model-selection.ts +167 -0
- package/src/watchdog/register-child.ts +117 -0
- package/src/watchdog/register-main.ts +433 -0
- package/src/watchdog/render.ts +54 -0
- package/src/watchdog/review.ts +293 -0
- package/src/watchdog/runtime.ts +712 -0
- package/src/watchdog/settings.ts +528 -0
- package/src/watchdog/tool-actions.ts +155 -0
- package/src/watchdog/turn-delta.ts +161 -0
- package/src/watchdog/types.ts +188 -0
- package/src/watchdog/warning-format.ts +73 -0
- package/src/runs/background/wait.ts +0 -394
|
@@ -13,7 +13,9 @@ import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
|
13
13
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
14
14
|
import { clearPendingForegroundControlNotices } from "../../extension/control-notices.ts";
|
|
15
15
|
import { runSync } from "./execution.ts";
|
|
16
|
-
import {
|
|
16
|
+
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
17
|
+
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
18
|
+
import { buildModelCandidates, resolveEffectiveSubagentModel, resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
17
19
|
import type { ModelScopeConfig } from "../shared/model-scope.ts";
|
|
18
20
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
19
21
|
import { recordRun } from "../shared/run-history.ts";
|
|
@@ -36,12 +38,13 @@ import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsy
|
|
|
36
38
|
import type { ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
37
39
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
38
40
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
39
|
-
import {
|
|
40
|
-
import { createForkContextResolver } from "../../shared/fork-context.ts";
|
|
41
|
+
import { validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
42
|
+
import { createForkContextResolver, forkedChildRequiresThinkingOff } from "../../shared/fork-context.ts";
|
|
41
43
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
42
44
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
43
45
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
44
|
-
import {
|
|
46
|
+
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
47
|
+
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
45
48
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
46
49
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
47
50
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
@@ -49,14 +52,15 @@ import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-
|
|
|
49
52
|
import {
|
|
50
53
|
attachNestedChildrenToResultChildren,
|
|
51
54
|
buildSubagentResultIntercomPayload,
|
|
52
|
-
deliverSubagentIntercomMessageEvent,
|
|
53
55
|
deliverSubagentResultIntercomEvent,
|
|
54
56
|
formatSubagentResultReceipt,
|
|
55
57
|
resolveSubagentResultStatus,
|
|
56
58
|
stripDetailsOutputsForIntercomReceipt,
|
|
57
59
|
} from "../../intercom/result-intercom.ts";
|
|
58
|
-
import {
|
|
59
|
-
import { deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
|
|
60
|
+
import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
61
|
+
import { deliverInterruptRequest, deliverStopRequest, requestAsyncSteer } from "../background/control-channel.ts";
|
|
62
|
+
import { waitForSteeringAction } from "../background/steering.ts";
|
|
63
|
+
import { steerAsyncRun } from "./async-steering-action.ts";
|
|
60
64
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
61
65
|
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
62
66
|
import { attachRootChildrenToSteps, createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
@@ -75,6 +79,7 @@ import {
|
|
|
75
79
|
} from "../shared/worktree.ts";
|
|
76
80
|
import {
|
|
77
81
|
type AgentProgress,
|
|
82
|
+
type AsyncStatus,
|
|
78
83
|
type AcceptanceInput,
|
|
79
84
|
type ArtifactConfig,
|
|
80
85
|
type ArtifactPaths,
|
|
@@ -96,12 +101,13 @@ import {
|
|
|
96
101
|
type SubagentState,
|
|
97
102
|
ASYNC_DIR,
|
|
98
103
|
DEFAULT_ARTIFACT_CONFIG,
|
|
104
|
+
DEFAULT_FORK_PREAMBLE,
|
|
99
105
|
RESULTS_DIR,
|
|
100
106
|
SUBAGENT_ACTIONS,
|
|
101
107
|
SUBAGENT_CONTROL_EVENT,
|
|
102
108
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
109
|
+
SUBAGENT_FOREGROUND_COMPLETE_EVENT,
|
|
103
110
|
checkSubagentDepth,
|
|
104
|
-
resolveMaxSubagentSpawnsPerSession,
|
|
105
111
|
resolveTopLevelParallelConcurrency,
|
|
106
112
|
resolveTopLevelParallelMaxTasks,
|
|
107
113
|
resolveChildMaxSubagentDepth,
|
|
@@ -109,8 +115,7 @@ import {
|
|
|
109
115
|
wrapForkTask,
|
|
110
116
|
} from "../../shared/types.ts";
|
|
111
117
|
|
|
112
|
-
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset"]);
|
|
113
|
-
|
|
118
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure"]);
|
|
114
119
|
interface TaskParam {
|
|
115
120
|
agent: string;
|
|
116
121
|
task: string;
|
|
@@ -143,6 +148,7 @@ export interface SubagentParamsLike {
|
|
|
143
148
|
worktree?: boolean;
|
|
144
149
|
context?: "fresh" | "fork";
|
|
145
150
|
async?: boolean;
|
|
151
|
+
foregroundOnly?: boolean;
|
|
146
152
|
timeoutMs?: number;
|
|
147
153
|
maxRuntimeMs?: number;
|
|
148
154
|
turnBudget?: TurnBudgetConfig;
|
|
@@ -156,6 +162,9 @@ export interface SubagentParamsLike {
|
|
|
156
162
|
artifacts?: boolean;
|
|
157
163
|
includeProgress?: boolean;
|
|
158
164
|
model?: string;
|
|
165
|
+
thinking?: string | false;
|
|
166
|
+
scope?: string;
|
|
167
|
+
target?: string;
|
|
159
168
|
skill?: string | string[] | boolean;
|
|
160
169
|
output?: string | boolean;
|
|
161
170
|
outputMode?: "inline" | "file-only";
|
|
@@ -164,6 +173,7 @@ export interface SubagentParamsLike {
|
|
|
164
173
|
acceptance?: AcceptanceInput;
|
|
165
174
|
schedule?: string;
|
|
166
175
|
scheduleName?: string;
|
|
176
|
+
additional?: number;
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
interface ExecutorDeps {
|
|
@@ -171,7 +181,9 @@ interface ExecutorDeps {
|
|
|
171
181
|
state: SubagentState;
|
|
172
182
|
config: ExtensionConfig;
|
|
173
183
|
asyncByDefault: boolean;
|
|
184
|
+
waitToolEnabled?: boolean;
|
|
174
185
|
handleScheduledRunAction?: (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>;
|
|
186
|
+
watchdog?: MainWatchdogRuntime;
|
|
175
187
|
tempArtifactsDir: string;
|
|
176
188
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
177
189
|
expandTilde: (p: string) => string;
|
|
@@ -180,6 +192,9 @@ interface ExecutorDeps {
|
|
|
180
192
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
181
193
|
}
|
|
182
194
|
|
|
195
|
+
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
|
|
196
|
+
type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string) => AgentConfig["thinking"] | undefined;
|
|
197
|
+
|
|
183
198
|
interface ExecutionContextData {
|
|
184
199
|
params: SubagentParamsLike;
|
|
185
200
|
effectiveCwd: string;
|
|
@@ -192,8 +207,8 @@ interface ExecutionContextData {
|
|
|
192
207
|
sessionRoot: string;
|
|
193
208
|
sessionDirForIndex: (idx?: number) => string;
|
|
194
209
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
195
|
-
sessionFileForTask:
|
|
196
|
-
thinkingOverrideForTask:
|
|
210
|
+
sessionFileForTask: ForkSessionFileForTask;
|
|
211
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
197
212
|
artifactConfig: ArtifactConfig;
|
|
198
213
|
artifactsDir: string;
|
|
199
214
|
backgroundRequestedWhileClarifying: boolean;
|
|
@@ -208,6 +223,7 @@ interface ExecutionContextData {
|
|
|
208
223
|
configToolBudget?: ResolvedToolBudget;
|
|
209
224
|
contextPolicy: AgentDefaultContextPolicy;
|
|
210
225
|
modelScope?: ModelScopeConfig;
|
|
226
|
+
parentSessionId: string | null;
|
|
211
227
|
}
|
|
212
228
|
|
|
213
229
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -263,22 +279,34 @@ function trustedSessionRootsForStatus(ctx: ExtensionContext, deps: ExecutorDeps)
|
|
|
263
279
|
return [...new Set(roots)];
|
|
264
280
|
}
|
|
265
281
|
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return
|
|
282
|
+
function spawnBudgetErrorResult(message: string, mode: "single" | "parallel" | "chain"): AgentToolResult<Details> {
|
|
283
|
+
return {
|
|
284
|
+
content: [{ type: "text", text: message }],
|
|
285
|
+
isError: true,
|
|
286
|
+
details: { mode, results: [] },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function withSpawnBudgetStatus(
|
|
291
|
+
result: AgentToolResult<Details>,
|
|
292
|
+
state: SubagentState,
|
|
293
|
+
config: ExtensionConfig,
|
|
294
|
+
sessionId: string | null,
|
|
295
|
+
): AgentToolResult<Details> {
|
|
296
|
+
const spawnBudget = getSpawnBudgetSnapshot(state, config, sessionId);
|
|
297
|
+
return {
|
|
298
|
+
...result,
|
|
299
|
+
content: result.content.map((item, index) => index === 0 && item.type === "text"
|
|
300
|
+
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\n${item.text}` }
|
|
301
|
+
: item),
|
|
302
|
+
details: { ...result.details, spawnBudget },
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function hasActiveSubagentChildren(state: SubagentState): boolean {
|
|
307
|
+
if (state.subagentInProgress || state.foregroundControls.size > 0) return true;
|
|
308
|
+
const isActive = (status: string) => status === "queued" || status === "running";
|
|
309
|
+
return [...state.asyncJobs.values(), ...(state.fleetJobs?.values() ?? [])].some((job) => isActive(job.status));
|
|
282
310
|
}
|
|
283
311
|
|
|
284
312
|
function countRequestedSubagentSpawns(params: SubagentParamsLike, config: ExtensionConfig): number {
|
|
@@ -315,13 +343,15 @@ function foregroundStatusResult(control: SubagentState["foregroundControls"] ext
|
|
|
315
343
|
function trimRememberedForegroundRuns(state: SubagentState): void {
|
|
316
344
|
if (!state.foregroundRuns) return;
|
|
317
345
|
while (state.foregroundRuns.size > 50) {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
346
|
+
const oldestTerminal = [...state.foregroundRuns.values()]
|
|
347
|
+
.filter((run) => !run.children.some((child) => child.status === "detached"))
|
|
348
|
+
.sort((left, right) => left.updatedAt - right.updatedAt)[0];
|
|
349
|
+
if (!oldestTerminal) break;
|
|
350
|
+
state.foregroundRuns.delete(oldestTerminal.runId);
|
|
321
351
|
}
|
|
322
352
|
}
|
|
323
353
|
|
|
324
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; results: SingleResult[] }): void {
|
|
354
|
+
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
|
|
325
355
|
state.foregroundRuns ??= new Map();
|
|
326
356
|
const previous = state.foregroundRuns.get(input.runId);
|
|
327
357
|
const updatedAt = Date.now();
|
|
@@ -329,6 +359,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
329
359
|
runId: input.runId,
|
|
330
360
|
mode: input.mode,
|
|
331
361
|
cwd: input.cwd,
|
|
362
|
+
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
332
363
|
updatedAt,
|
|
333
364
|
children: input.results.map((result, index) => {
|
|
334
365
|
const child = {
|
|
@@ -337,12 +368,17 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
337
368
|
status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
|
|
338
369
|
updatedAt,
|
|
339
370
|
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
371
|
+
...(result.error ? { error: result.error } : {}),
|
|
340
372
|
...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
|
|
373
|
+
...(result.outputMode ? { outputMode: result.outputMode } : {}),
|
|
374
|
+
...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
|
|
375
|
+
...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
|
|
341
376
|
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
342
377
|
...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
|
|
343
378
|
...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
|
|
344
379
|
...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
|
|
345
380
|
...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
|
|
381
|
+
...(result.acceptance ? { acceptance: result.acceptance } : {}),
|
|
346
382
|
};
|
|
347
383
|
const recovered = previous?.children[index];
|
|
348
384
|
return child.status === "detached" && recovered && recovered.status !== "detached" ? recovered : child;
|
|
@@ -351,12 +387,12 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
351
387
|
trimRememberedForegroundRuns(state);
|
|
352
388
|
}
|
|
353
389
|
|
|
354
|
-
function updateRememberedForegroundChild(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; index: number; result: SingleResult }): void {
|
|
390
|
+
function updateRememberedForegroundChild(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; index: number; result: SingleResult; events: IntercomEventBus }): void {
|
|
355
391
|
state.foregroundRuns ??= new Map();
|
|
356
392
|
const updatedAt = Date.now();
|
|
357
393
|
let run = state.foregroundRuns.get(input.runId);
|
|
358
394
|
if (!run) {
|
|
359
|
-
run = { runId: input.runId, mode: input.mode, cwd: input.cwd, updatedAt, children: [] };
|
|
395
|
+
run = { runId: input.runId, mode: input.mode, cwd: input.cwd, ...(input.sessionId ? { sessionId: input.sessionId } : {}), updatedAt, children: [] };
|
|
360
396
|
state.foregroundRuns.set(input.runId, run);
|
|
361
397
|
}
|
|
362
398
|
run.updatedAt = updatedAt;
|
|
@@ -365,38 +401,67 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
365
401
|
...child,
|
|
366
402
|
agent: input.result.agent,
|
|
367
403
|
index: input.index,
|
|
368
|
-
status:
|
|
404
|
+
status: input.result.acceptance?.status === "rejected"
|
|
405
|
+
? "failed"
|
|
406
|
+
: resolveSubagentResultStatus({ exitCode: input.result.exitCode, interrupted: input.result.interrupted, detached: false }),
|
|
369
407
|
updatedAt,
|
|
370
408
|
...(input.result.exitCode !== undefined ? { exitCode: input.result.exitCode } : {}),
|
|
409
|
+
...(input.result.error ? { error: input.result.error } : {}),
|
|
371
410
|
...(input.result.finalOutput ? { finalOutput: input.result.finalOutput } : {}),
|
|
411
|
+
outputMode: input.result.outputMode,
|
|
412
|
+
savedOutputPath: input.result.savedOutputPath,
|
|
413
|
+
outputSaveError: input.result.outputSaveError,
|
|
372
414
|
...(input.result.sessionFile ? { sessionFile: input.result.sessionFile } : {}),
|
|
373
415
|
...(input.result.artifactPaths ? { artifactPaths: input.result.artifactPaths } : {}),
|
|
374
416
|
...(input.result.transcriptPath ? { transcriptPath: input.result.transcriptPath } : {}),
|
|
375
417
|
...(input.result.transcriptError ? { transcriptError: input.result.transcriptError } : {}),
|
|
376
418
|
...(input.result.detachedReason ? { detachedReason: input.result.detachedReason } : {}),
|
|
419
|
+
...(input.result.acceptance ? { acceptance: input.result.acceptance } : {}),
|
|
377
420
|
};
|
|
378
421
|
trimRememberedForegroundRuns(state);
|
|
422
|
+
const output = getSingleResultOutput(input.result).trim();
|
|
423
|
+
const success = input.result.exitCode === 0 && input.result.acceptance?.status !== "rejected";
|
|
424
|
+
const summary = !success && input.result.error
|
|
425
|
+
? `${input.result.error}${output ? `\n\nOutput:\n${output}` : ""}`
|
|
426
|
+
: output || input.result.error || "Detached child exited without final output.";
|
|
427
|
+
input.events.emit(SUBAGENT_FOREGROUND_COMPLETE_EVENT, {
|
|
428
|
+
id: `${input.runId}:${input.index}`,
|
|
429
|
+
runId: input.runId,
|
|
430
|
+
source: "foreground",
|
|
431
|
+
mode: input.mode,
|
|
432
|
+
agent: input.result.agent,
|
|
433
|
+
success,
|
|
434
|
+
summary,
|
|
435
|
+
exitCode: input.result.exitCode,
|
|
436
|
+
state: success ? "complete" : "failed",
|
|
437
|
+
timestamp: updatedAt,
|
|
438
|
+
cwd: input.cwd,
|
|
439
|
+
sessionFile: input.result.sessionFile,
|
|
440
|
+
sessionId: input.sessionId,
|
|
441
|
+
taskIndex: input.index,
|
|
442
|
+
});
|
|
379
443
|
}
|
|
380
444
|
|
|
381
|
-
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: "single" | "parallel" | "chain"; state: "complete"; agent: string; index: number;
|
|
445
|
+
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: "single" | "parallel" | "chain"; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string } | undefined {
|
|
382
446
|
const requested = (params.id ?? params.runId)?.trim();
|
|
383
|
-
if (!requested || !state.foregroundRuns?.size) return undefined;
|
|
384
|
-
const
|
|
385
|
-
const
|
|
447
|
+
if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
|
|
448
|
+
const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId === state.currentSessionId);
|
|
449
|
+
const direct = sessionRuns.find((run) => run.runId === requested);
|
|
450
|
+
const matches = direct ? [direct] : sessionRuns.filter((run) => run.runId.startsWith(requested));
|
|
386
451
|
if (matches.length === 0) return undefined;
|
|
387
452
|
if (matches.length > 1) throw new Error(`Ambiguous foreground run id prefix '${requested}' matched: ${matches.map((run) => run.runId).join(", ")}. Provide a longer id.`);
|
|
388
453
|
const run = matches[0]!;
|
|
454
|
+
if (run.children.some((child) => child.status === "detached")) throw new Error(`Foreground run '${run.runId}' is detached for intercom coordination and cannot be revived safely while any child may still be live. Reply to the supervisor request first, then wait with subagent_wait({ id: "${run.runId}" }); use status to recover the result and do not launch a replacement while it remains detached.`);
|
|
389
455
|
if (run.children.length > 1 && params.index === undefined) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Provide index to choose one.`);
|
|
390
456
|
const index = params.index ?? 0;
|
|
391
457
|
if (!Number.isInteger(index)) throw new Error(`Foreground run '${run.runId}' index must be an integer.`);
|
|
392
458
|
if (index < 0 || index >= run.children.length) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Index ${index} is out of range.`);
|
|
393
459
|
const child = run.children[index]!;
|
|
394
|
-
if (child.status === "detached") throw new Error(`Foreground run '${run.runId}' child ${index} is detached for intercom coordination and cannot be revived safely from the remembered foreground state. Reply to the supervisor request first; after the child exits, start a fresh follow-up if needed.`);
|
|
395
460
|
if (!child.sessionFile) throw new Error(`Foreground run '${run.runId}' child ${index} does not have a persisted session file to resume from.`);
|
|
396
461
|
if (path.extname(child.sessionFile) !== ".jsonl") throw new Error(`Foreground run '${run.runId}' child ${index} session file must be a .jsonl file: ${child.sessionFile}`);
|
|
397
462
|
const sessionFile = path.resolve(child.sessionFile);
|
|
398
463
|
if (!fs.existsSync(sessionFile)) throw new Error(`Foreground run '${run.runId}' child ${index} session file does not exist: ${child.sessionFile}`);
|
|
399
|
-
return { runId: run.runId, mode: run.mode, state: "complete", agent: child.agent, index,
|
|
464
|
+
return { runId: run.runId, mode: run.mode, state: "complete", agent: child.agent, index, cwd: run.cwd, sessionFile };
|
|
400
465
|
}
|
|
401
466
|
|
|
402
467
|
type AsyncResumeSourceTarget = ReturnType<typeof resolveAsyncResumeTarget> & { source: "async" };
|
|
@@ -408,7 +473,6 @@ type NestedResumeSourceTarget = {
|
|
|
408
473
|
state: "complete" | "failed" | "paused";
|
|
409
474
|
agent: string;
|
|
410
475
|
index: number;
|
|
411
|
-
intercomTarget: string;
|
|
412
476
|
cwd?: string;
|
|
413
477
|
sessionFile: string;
|
|
414
478
|
};
|
|
@@ -449,7 +513,13 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, o
|
|
|
449
513
|
foregroundError = error;
|
|
450
514
|
}
|
|
451
515
|
try {
|
|
452
|
-
asyncTarget = {
|
|
516
|
+
asyncTarget = {
|
|
517
|
+
source: "async",
|
|
518
|
+
...resolveAsyncResumeTarget(params, {}, {
|
|
519
|
+
requireSessionFile: options.asyncRequireSessionFile,
|
|
520
|
+
sessionId: state.currentSessionId ?? undefined,
|
|
521
|
+
}),
|
|
522
|
+
};
|
|
453
523
|
} catch (error) {
|
|
454
524
|
asyncError = error;
|
|
455
525
|
}
|
|
@@ -481,6 +551,7 @@ function getAsyncInterruptTarget(
|
|
|
481
551
|
state: SubagentState,
|
|
482
552
|
runId: string | undefined,
|
|
483
553
|
location?: { asyncDir: string | null; resolvedId?: string },
|
|
554
|
+
options: { fallbackToNewest?: boolean } = {},
|
|
484
555
|
): { asyncId: string; asyncDir: string } | undefined {
|
|
485
556
|
if (location?.asyncDir) {
|
|
486
557
|
return {
|
|
@@ -491,6 +562,7 @@ function getAsyncInterruptTarget(
|
|
|
491
562
|
if (runId) {
|
|
492
563
|
const direct = state.asyncJobs.get(runId);
|
|
493
564
|
if (direct) return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
565
|
+
if (options.fallbackToNewest === false) return undefined;
|
|
494
566
|
}
|
|
495
567
|
let newest: { asyncId: string; asyncDir: string; updatedAt: number } | undefined;
|
|
496
568
|
for (const job of state.asyncJobs.values()) {
|
|
@@ -567,64 +639,48 @@ function interruptAsyncRun(
|
|
|
567
639
|
}
|
|
568
640
|
}
|
|
569
641
|
|
|
570
|
-
function
|
|
571
|
-
state: SubagentState
|
|
572
|
-
runId: string
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
642
|
+
function stopAsyncRun(
|
|
643
|
+
state: SubagentState,
|
|
644
|
+
runId: string | undefined,
|
|
645
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean,
|
|
646
|
+
location?: { asyncDir: string | null; resolvedId?: string },
|
|
647
|
+
): AgentToolResult<Details> | null {
|
|
648
|
+
const target = getAsyncInterruptTarget(state, runId, location, { fallbackToNewest: false });
|
|
649
|
+
if (!target) return null;
|
|
650
|
+
const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
|
|
651
|
+
if (state.currentSessionId && status?.sessionId !== state.currentSessionId) {
|
|
579
652
|
return {
|
|
580
|
-
content: [{ type: "text", text: `Async run '${
|
|
653
|
+
content: [{ type: "text", text: `Async run '${target.asyncId}' was not found in the active session.` }],
|
|
581
654
|
isError: true,
|
|
582
655
|
details: { mode: "management", results: [] },
|
|
583
656
|
};
|
|
584
657
|
}
|
|
585
|
-
const status = reconcileAsyncRun(input.location.asyncDir, { kill: input.kill }).status;
|
|
586
658
|
if (!status || (status.state !== "running" && status.state !== "queued")) {
|
|
587
659
|
return {
|
|
588
|
-
content: [{ type: "text", text: `
|
|
660
|
+
content: [{ type: "text", text: `No running or queued async run was found for '${runId ?? "current"}'.` }],
|
|
589
661
|
isError: true,
|
|
590
662
|
details: { mode: "management", results: [] },
|
|
591
663
|
};
|
|
592
664
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
details: { mode: "management", results: [] },
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
const targetStep = steps[input.index];
|
|
603
|
-
if (targetStep && targetStep.status !== "running" && targetStep.status !== "pending") {
|
|
604
|
-
return {
|
|
605
|
-
content: [{ type: "text", text: `Async run '${status.runId}' child ${input.index} is ${targetStep.status} and cannot be steered.` }],
|
|
606
|
-
isError: true,
|
|
607
|
-
details: { mode: "management", results: [] },
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
} else {
|
|
611
|
-
const running = steps.filter((step) => step.status === "running");
|
|
612
|
-
if (running.length === 0 && steps.length > 1) {
|
|
613
|
-
return {
|
|
614
|
-
content: [{ type: "text", text: `Async run '${status.runId}' has no running child yet. Provide index to steer a queued child.` }],
|
|
615
|
-
isError: true,
|
|
616
|
-
details: { mode: "management", results: [] },
|
|
617
|
-
};
|
|
665
|
+
try {
|
|
666
|
+
deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action" });
|
|
667
|
+
const tracked = state.asyncJobs.get(target.asyncId);
|
|
668
|
+
if (tracked) {
|
|
669
|
+
tracked.activityState = undefined;
|
|
670
|
+
tracked.updatedAt = Date.now();
|
|
618
671
|
}
|
|
672
|
+
return {
|
|
673
|
+
content: [{ type: "text", text: `Stop requested for async run ${target.asyncId}.` }],
|
|
674
|
+
details: { mode: "management", results: [] },
|
|
675
|
+
};
|
|
676
|
+
} catch (error) {
|
|
677
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
678
|
+
return {
|
|
679
|
+
content: [{ type: "text", text: `Failed to stop async run ${target.asyncId}: ${message}` }],
|
|
680
|
+
isError: true,
|
|
681
|
+
details: { mode: "management", results: [] },
|
|
682
|
+
};
|
|
619
683
|
}
|
|
620
|
-
requestAsyncSteer(input.location.asyncDir, { message: input.message, targetIndex: input.index, source: "steer-action" });
|
|
621
|
-
const tracked = input.state.asyncJobs.get(status.runId);
|
|
622
|
-
if (tracked) tracked.updatedAt = Date.now();
|
|
623
|
-
const childText = input.index !== undefined ? ` child ${input.index}` : " running child";
|
|
624
|
-
return {
|
|
625
|
-
content: [{ type: "text", text: `Steering queued for async run ${status.runId}${childText}. Delivery requires a live Pi child session that supports mid-run steering.` }],
|
|
626
|
-
details: { mode: "management", results: [] },
|
|
627
|
-
};
|
|
628
684
|
}
|
|
629
685
|
|
|
630
686
|
function duplicateNames(names: string[]): string[] {
|
|
@@ -754,6 +810,7 @@ function appendStepToAsyncChain(input: {
|
|
|
754
810
|
currentModelProvider: input.ctx.model?.provider,
|
|
755
811
|
currentModel: input.ctx.model,
|
|
756
812
|
modelScope: discoveredForAppend.modelScope,
|
|
813
|
+
interactive: input.ctx.hasUI,
|
|
757
814
|
};
|
|
758
815
|
const built = buildAsyncRunnerSteps(resolved.id, {
|
|
759
816
|
chain: wrapChainTasksForFork(input.params.chain, contextPolicy),
|
|
@@ -766,6 +823,7 @@ function appendStepToAsyncChain(input: {
|
|
|
766
823
|
chainSkills,
|
|
767
824
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
768
825
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
826
|
+
waitToolEnabled: input.deps.waitToolEnabled,
|
|
769
827
|
asyncDir: resolved.location.asyncDir,
|
|
770
828
|
validateOutputBindings: false,
|
|
771
829
|
});
|
|
@@ -858,6 +916,7 @@ function validateNestedSessionFile(run: NestedRunSummary, trustedSessionRoots: s
|
|
|
858
916
|
function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "nested" }, trustedSessionRoots: string[]): NestedResumeSourceTarget {
|
|
859
917
|
const run = match.match.run;
|
|
860
918
|
if (run.state === "running" || run.state === "queued") throw new Error(`Nested run '${run.id}' is live; route the follow-up to the owner process instead.`);
|
|
919
|
+
if (run.state === "stopped") throw new Error(`Nested run '${run.id}' was stopped and cannot be resumed. Start a new run instead.`);
|
|
861
920
|
const agent = nestedRunAgent(run);
|
|
862
921
|
if (!agent) throw new Error(`Could not determine child agent for nested run '${run.id}'.`);
|
|
863
922
|
const state = run.state === "complete" || run.state === "failed" || run.state === "paused" ? run.state : "failed";
|
|
@@ -869,7 +928,6 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
|
|
|
869
928
|
state,
|
|
870
929
|
agent,
|
|
871
930
|
index: 0,
|
|
872
|
-
intercomTarget: resolveSubagentIntercomTarget(run.id, agent, 0),
|
|
873
931
|
cwd: asyncDir ? path.dirname(asyncDir) : undefined,
|
|
874
932
|
sessionFile: validateNestedSessionFile(run, trustedSessionRoots),
|
|
875
933
|
};
|
|
@@ -913,7 +971,7 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
|
|
|
913
971
|
}
|
|
914
972
|
}
|
|
915
973
|
|
|
916
|
-
function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number }): AgentToolResult<Details> | undefined {
|
|
974
|
+
async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details> | undefined> {
|
|
917
975
|
const run = input.target.match.run;
|
|
918
976
|
const asyncDir = resolveNestedAsyncDir(input.target.match.rootRunId, run);
|
|
919
977
|
if (!asyncDir) return undefined;
|
|
@@ -925,8 +983,32 @@ function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind:
|
|
|
925
983
|
const step = steps[input.index];
|
|
926
984
|
if (step && step.status !== "running" && step.status !== "pending") return { content: [{ type: "text", text: `Nested async run ${run.id} child ${input.index} is ${step.status} and cannot be steered.` }], isError: true, details: { mode: "management", results: [] } };
|
|
927
985
|
}
|
|
928
|
-
|
|
929
|
-
|
|
986
|
+
const runningIndexes = steps
|
|
987
|
+
.map((step, index) => step.status === "running" ? index : undefined)
|
|
988
|
+
.filter((index): index is number => index !== undefined);
|
|
989
|
+
const effectiveTargetIndex = input.index ?? (status.mode === "single" && runningIndexes.length === 0 && steps[0]?.status === "pending" ? 0 : undefined);
|
|
990
|
+
const targetIndexes = effectiveTargetIndex !== undefined ? [effectiveTargetIndex] : runningIndexes;
|
|
991
|
+
if (targetIndexes.length === 0) return { content: [{ type: "text", text: `Nested async run ${run.id} has no running child to steer.` }], isError: true, details: { mode: "management", results: [] } };
|
|
992
|
+
const requestId = randomUUID();
|
|
993
|
+
try {
|
|
994
|
+
requestAsyncSteer(asyncDir, {
|
|
995
|
+
message: input.message,
|
|
996
|
+
...(effectiveTargetIndex !== undefined ? { targetIndex: effectiveTargetIndex } : { targetIndexes }),
|
|
997
|
+
source: "nested-steer",
|
|
998
|
+
id: requestId,
|
|
999
|
+
});
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
return { content: [{ type: "text", text: `Failed to queue steering for nested async run ${run.id}: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "management", results: [] } };
|
|
1002
|
+
}
|
|
1003
|
+
const targets = targetIndexes.map((index) => ({ index, state: steps[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
|
|
1004
|
+
if (targets.every((target) => target.state === "scheduled")) {
|
|
1005
|
+
const scheduled = { requestId, state: "scheduled" as const, sourceRunId: run.id, targets };
|
|
1006
|
+
return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
|
|
1007
|
+
}
|
|
1008
|
+
const waited = await waitForSteeringAction({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal });
|
|
1009
|
+
const result = waited ?? { requestId, state: "pending" as const, sourceRunId: run.id, targets };
|
|
1010
|
+
const stateText = result.state === "delivered" ? "delivered" : result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : "pending";
|
|
1011
|
+
return { content: [{ type: "text", text: `Steering ${stateText} for nested async run ${run.id} (request ${requestId}).` }], ...(result.state === "failed" || result.state === "partial" ? { isError: true } : {}), details: { mode: "management", results: [], steering: result } };
|
|
930
1012
|
}
|
|
931
1013
|
|
|
932
1014
|
async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<AgentToolResult<Details>> {
|
|
@@ -948,10 +1030,10 @@ async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { ki
|
|
|
948
1030
|
return { content: [{ type: "text", text: `Nested run ${run.id} appears live but its owner route is not reachable. Wait for completion, then retry action='resume'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
949
1031
|
}
|
|
950
1032
|
|
|
951
|
-
function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number }): AgentToolResult<Details
|
|
1033
|
+
async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details>> {
|
|
952
1034
|
const run = input.target.match.run;
|
|
953
1035
|
if (run.state !== "running" && run.state !== "queued") return { content: [{ type: "text", text: `Nested run ${run.id} is ${run.state} and cannot be steered.` }], isError: true, details: { mode: "management", results: [] } };
|
|
954
|
-
const direct = directNestedAsyncSteer(input);
|
|
1036
|
+
const direct = await directNestedAsyncSteer(input);
|
|
955
1037
|
if (direct) return direct;
|
|
956
1038
|
return { content: [{ type: "text", text: `Nested run ${run.id} is not a live async Pi child session with a steering inbox. action='steer' cannot target foreground nested runs.` }], isError: true, details: { mode: "management", results: [] } };
|
|
957
1039
|
}
|
|
@@ -961,6 +1043,7 @@ async function resumeAsyncRun(input: {
|
|
|
961
1043
|
requestCwd: string;
|
|
962
1044
|
ctx: ExtensionContext;
|
|
963
1045
|
deps: ExecutorDeps;
|
|
1046
|
+
absoluteDeadlineAt?: number;
|
|
964
1047
|
}): Promise<AgentToolResult<Details>> {
|
|
965
1048
|
const followUp = (input.params.message ?? input.params.task ?? "").trim();
|
|
966
1049
|
const attachChain = (input.params.chain?.length ?? 0) > 0 ? input.params.chain as ChainStep[] : undefined;
|
|
@@ -971,6 +1054,15 @@ async function resumeAsyncRun(input: {
|
|
|
971
1054
|
details: { mode: "management", results: [] },
|
|
972
1055
|
};
|
|
973
1056
|
}
|
|
1057
|
+
const acceptanceErrors = validateExecutionAcceptance(input.params);
|
|
1058
|
+
if (acceptanceErrors.length > 0) {
|
|
1059
|
+
return {
|
|
1060
|
+
content: [{ type: "text", text: `Cannot resume: ${acceptanceErrors.join(" ")}` }],
|
|
1061
|
+
isError: true,
|
|
1062
|
+
details: { mode: "management", results: [] },
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
input.deps.state.currentSessionId = resolveCurrentSessionId(input.ctx.sessionManager);
|
|
974
1066
|
|
|
975
1067
|
let target: ResumeSourceTarget;
|
|
976
1068
|
const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
|
|
@@ -1009,34 +1101,14 @@ async function resumeAsyncRun(input: {
|
|
|
1009
1101
|
}
|
|
1010
1102
|
|
|
1011
1103
|
if (target.kind === "live" && !attachChain) {
|
|
1012
|
-
const interrupt = interruptLiveAsyncResumeTarget({
|
|
1013
|
-
target,
|
|
1014
|
-
state: input.deps.state,
|
|
1015
|
-
kill: input.deps.kill,
|
|
1016
|
-
resultsDir: RESULTS_DIR,
|
|
1017
|
-
});
|
|
1018
|
-
if (!interrupt.ok) {
|
|
1019
|
-
return {
|
|
1020
|
-
content: [{ type: "text", text: interrupt.message }],
|
|
1021
|
-
isError: true,
|
|
1022
|
-
details: { mode: "management", results: [] },
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
const delivered = await deliverSubagentIntercomMessageEvent(
|
|
1026
|
-
input.deps.pi.events,
|
|
1027
|
-
target.intercomTarget,
|
|
1028
|
-
`Follow-up for async run ${target.runId} (${target.agent}):\n\n${followUp}`,
|
|
1029
|
-
500,
|
|
1030
|
-
{ source: "async-resume", runId: target.runId, agent: target.agent, index: target.index },
|
|
1031
|
-
);
|
|
1032
|
-
if (delivered) {
|
|
1033
|
-
return {
|
|
1034
|
-
content: [{ type: "text", text: [`Interrupted live async child, then delivered follow-up.`, `Run: ${target.runId}`, `Intercom target: ${target.intercomTarget}`].join("\n") }],
|
|
1035
|
-
details: { mode: "management", results: [] },
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
1104
|
return {
|
|
1039
|
-
content: [{
|
|
1105
|
+
content: [{
|
|
1106
|
+
type: "text",
|
|
1107
|
+
text: [
|
|
1108
|
+
`Async child '${target.runId}' index ${target.index} is still running. action='resume' only revives paused, completed, or failed children.`,
|
|
1109
|
+
`Send live input with subagent({ action: "steer", id: "${target.runId}", index: ${target.index}, message: "..." }).`,
|
|
1110
|
+
].join("\n"),
|
|
1111
|
+
}],
|
|
1040
1112
|
isError: true,
|
|
1041
1113
|
details: { mode: "management", results: [] },
|
|
1042
1114
|
};
|
|
@@ -1066,7 +1138,18 @@ async function resumeAsyncRun(input: {
|
|
|
1066
1138
|
const agents = intercomBridge.active
|
|
1067
1139
|
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
1068
1140
|
: discoveredAgents;
|
|
1069
|
-
const
|
|
1141
|
+
const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
|
|
1142
|
+
const discoveredAgentConfig = agents.find((agent) => agent.name === target.agent);
|
|
1143
|
+
const agentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
|
|
1144
|
+
name: recoveryDescriptor.agent,
|
|
1145
|
+
description: "Persisted async recovery contract",
|
|
1146
|
+
systemPrompt: "",
|
|
1147
|
+
systemPromptMode: recoveryDescriptor.systemPromptMode,
|
|
1148
|
+
inheritProjectContext: recoveryDescriptor.inheritProjectContext,
|
|
1149
|
+
inheritSkills: recoveryDescriptor.inheritSkills,
|
|
1150
|
+
source: "project",
|
|
1151
|
+
filePath: recoveryDescriptor.agentFilePath ?? path.join(recoveryDescriptor.cwd, ".pi-subagents-recovery-agent"),
|
|
1152
|
+
} : undefined);
|
|
1070
1153
|
if (!agentConfig) {
|
|
1071
1154
|
return {
|
|
1072
1155
|
content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
|
|
@@ -1094,11 +1177,14 @@ async function resumeAsyncRun(input: {
|
|
|
1094
1177
|
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
1095
1178
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1096
1179
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
1180
|
+
const workflowTask = (input.params.task ?? followUp) || undefined;
|
|
1181
|
+
const goal = resolveAsyncEventGoal(workflowTask, attachChain);
|
|
1097
1182
|
const chain = wrapChainTasksForFork(attachChain, contextPolicy);
|
|
1098
1183
|
const normalized = normalizeSkillInput(input.params.skill);
|
|
1099
1184
|
const result = executeAsyncChain(runId, {
|
|
1100
1185
|
chain,
|
|
1101
|
-
task:
|
|
1186
|
+
task: workflowTask,
|
|
1187
|
+
goal,
|
|
1102
1188
|
attachRoot: {
|
|
1103
1189
|
runId: target.runId,
|
|
1104
1190
|
asyncDir: target.asyncDir ?? path.join(ASYNC_DIR, target.runId),
|
|
@@ -1116,6 +1202,7 @@ async function resumeAsyncRun(input: {
|
|
|
1116
1202
|
currentModelProvider: input.ctx.model?.provider,
|
|
1117
1203
|
currentModel: input.ctx.model,
|
|
1118
1204
|
modelScope,
|
|
1205
|
+
interactive: input.ctx.hasUI,
|
|
1119
1206
|
},
|
|
1120
1207
|
availableModels,
|
|
1121
1208
|
cwd: effectiveCwd,
|
|
@@ -1127,6 +1214,7 @@ async function resumeAsyncRun(input: {
|
|
|
1127
1214
|
chainSkills: normalized === false ? [] : (normalized ?? []),
|
|
1128
1215
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1129
1216
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1217
|
+
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1130
1218
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1131
1219
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1132
1220
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
@@ -1144,17 +1232,19 @@ async function resumeAsyncRun(input: {
|
|
|
1144
1232
|
result.details.asyncDir ? `Async dir: ${result.details.asyncDir}` : undefined,
|
|
1145
1233
|
`Status if needed: subagent({ action: "status", id: "${attachedId}" })`,
|
|
1146
1234
|
].filter((line): line is string => Boolean(line));
|
|
1147
|
-
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n")) }], details: result.details };
|
|
1235
|
+
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
|
|
1148
1236
|
}
|
|
1149
1237
|
|
|
1150
1238
|
const runId = randomUUID().slice(0, 8);
|
|
1151
|
-
const
|
|
1152
|
-
const
|
|
1239
|
+
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);
|
|
1153
1242
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1154
1243
|
const result = executeAsyncSingle(runId, {
|
|
1155
1244
|
agent: target.agent,
|
|
1156
1245
|
task: buildRevivedAsyncTask(target, followUp),
|
|
1157
|
-
|
|
1246
|
+
goal: followUp,
|
|
1247
|
+
agentConfig: recoveryAgentConfig,
|
|
1158
1248
|
ctx: {
|
|
1159
1249
|
pi: input.deps.pi,
|
|
1160
1250
|
cwd: input.requestCwd,
|
|
@@ -1163,23 +1253,42 @@ async function resumeAsyncRun(input: {
|
|
|
1163
1253
|
currentModelProvider: input.ctx.model?.provider,
|
|
1164
1254
|
currentModel: input.ctx.model,
|
|
1165
1255
|
modelScope,
|
|
1256
|
+
interactive: input.ctx.hasUI,
|
|
1166
1257
|
},
|
|
1167
1258
|
cwd: effectiveCwd,
|
|
1168
|
-
maxOutput: input.params.maxOutput,
|
|
1259
|
+
maxOutput: input.params.maxOutput ?? recoveryDescriptor?.maxOutput,
|
|
1169
1260
|
artifactsDir,
|
|
1170
1261
|
artifactConfig,
|
|
1171
|
-
shareEnabled: input.params.share === true,
|
|
1262
|
+
shareEnabled: recoveryDescriptor?.share ?? input.params.share === true,
|
|
1172
1263
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
1264
|
+
...(recoveryDescriptor?.sessionDir ? { sessionDir: recoveryDescriptor.sessionDir } : {}),
|
|
1173
1265
|
sessionFile: target.sessionFile,
|
|
1266
|
+
revivalLease: {
|
|
1267
|
+
sessionFile: target.sessionFile,
|
|
1268
|
+
runId,
|
|
1269
|
+
sourceRunId: target.runId,
|
|
1270
|
+
...(input.deps.state.currentSessionId ? { parentSessionId: input.deps.state.currentSessionId } : {}),
|
|
1271
|
+
},
|
|
1272
|
+
modelOverride: input.params.model ?? recoveryDescriptor?.model ?? target.model,
|
|
1273
|
+
thinkingOverride: input.params.model ? undefined : recoveryDescriptor?.thinking ?? target.thinking,
|
|
1174
1274
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1175
|
-
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1275
|
+
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1276
|
+
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1176
1277
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1177
1278
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1178
1279
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1179
|
-
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1280
|
+
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1180
1281
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1181
1282
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1182
1283
|
availableModels,
|
|
1284
|
+
output: recoveryDescriptor?.outputPath,
|
|
1285
|
+
outputMode: recoveryDescriptor?.outputMode,
|
|
1286
|
+
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
1287
|
+
...(recoveryDescriptor?.acceptance !== undefined && input.params.acceptance === undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
|
|
1288
|
+
...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
|
|
1289
|
+
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
1290
|
+
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1291
|
+
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1183
1292
|
});
|
|
1184
1293
|
if (result.isError) return result;
|
|
1185
1294
|
|
|
@@ -1195,7 +1304,7 @@ async function resumeAsyncRun(input: {
|
|
|
1195
1304
|
revivedTarget ? `Intercom target: ${revivedTarget} (if registered)` : undefined,
|
|
1196
1305
|
`Status if needed: subagent({ action: "status", id: "${revivedId}" })`,
|
|
1197
1306
|
].filter((line): line is string => Boolean(line));
|
|
1198
|
-
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n")) }], details: result.details };
|
|
1307
|
+
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
|
|
1199
1308
|
}
|
|
1200
1309
|
|
|
1201
1310
|
function resultSummaryForIntercom(result: SingleResult): string {
|
|
@@ -1413,25 +1522,6 @@ function validateExecutionChainBindings(params: SubagentParamsLike, dynamicFanou
|
|
|
1413
1522
|
return null;
|
|
1414
1523
|
}
|
|
1415
1524
|
|
|
1416
|
-
function validateExecutionAcceptance(params: SubagentParamsLike): string[] {
|
|
1417
|
-
const errors: string[] = [];
|
|
1418
|
-
errors.push(...validateAcceptanceInput(params.acceptance, "acceptance"));
|
|
1419
|
-
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
1420
|
-
errors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));
|
|
1421
|
-
}
|
|
1422
|
-
for (const [stepIndex, step] of (params.chain ?? []).entries()) {
|
|
1423
|
-
errors.push(...validateAcceptanceInput((step as { acceptance?: unknown }).acceptance, `chain[${stepIndex}].acceptance`));
|
|
1424
|
-
if (isParallelStep(step)) {
|
|
1425
|
-
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
1426
|
-
errors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
1427
|
-
}
|
|
1428
|
-
} else if (isDynamicParallelStep(step)) {
|
|
1429
|
-
errors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
return errors;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
1525
|
function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
1436
1526
|
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
1437
1527
|
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
@@ -1492,6 +1582,25 @@ function buildRequestedModeError(params: SubagentParamsLike, message: string): A
|
|
|
1492
1582
|
);
|
|
1493
1583
|
}
|
|
1494
1584
|
|
|
1585
|
+
function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: AgentConfig[]): SubagentParamsLike {
|
|
1586
|
+
if ((params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || !params.agent) return params;
|
|
1587
|
+
const agent = agents.find((candidate) => candidate.name === params.agent);
|
|
1588
|
+
if (!agent) return params;
|
|
1589
|
+
return {
|
|
1590
|
+
...params,
|
|
1591
|
+
...(params.async === undefined && agent.defaultAsync !== undefined ? { async: agent.defaultAsync } : {}),
|
|
1592
|
+
...(params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs !== undefined
|
|
1593
|
+
? { timeoutMs: agent.defaultTimeoutMs }
|
|
1594
|
+
: {}),
|
|
1595
|
+
...(params.turnBudget === undefined && agent.defaultTurnBudget !== undefined
|
|
1596
|
+
? { turnBudget: agent.defaultTurnBudget }
|
|
1597
|
+
: {}),
|
|
1598
|
+
...(params.acceptance === undefined && agent.defaultAcceptance !== undefined
|
|
1599
|
+
? { acceptance: agent.defaultAcceptance }
|
|
1600
|
+
: {}),
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1495
1604
|
function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: number; error?: string } {
|
|
1496
1605
|
const rawTimeout = params.timeoutMs;
|
|
1497
1606
|
const rawMaxRuntime = params.maxRuntimeMs;
|
|
@@ -1508,20 +1617,6 @@ function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: num
|
|
|
1508
1617
|
return { timeoutMs: rawTimeout ?? rawMaxRuntime };
|
|
1509
1618
|
}
|
|
1510
1619
|
|
|
1511
|
-
function resolveTurnBudget(params: SubagentParamsLike, config: ExtensionConfig): { turnBudget?: ResolvedTurnBudget; error?: string } {
|
|
1512
|
-
const raw = params.turnBudget ?? config.turnBudget;
|
|
1513
|
-
if (raw === undefined) return {};
|
|
1514
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { error: "turnBudget must be an object with maxTurns and optional graceTurns." };
|
|
1515
|
-
if (typeof raw.maxTurns !== "number" || !Number.isInteger(raw.maxTurns) || raw.maxTurns < 1) {
|
|
1516
|
-
return { error: "turnBudget.maxTurns must be an integer >= 1." };
|
|
1517
|
-
}
|
|
1518
|
-
const graceTurns = raw.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS;
|
|
1519
|
-
if (typeof graceTurns !== "number" || !Number.isInteger(graceTurns) || graceTurns < 0) {
|
|
1520
|
-
return { error: "turnBudget.graceTurns must be an integer >= 0." };
|
|
1521
|
-
}
|
|
1522
|
-
return { turnBudget: { maxTurns: raw.maxTurns, graceTurns } };
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
1620
|
function resolveToolBudget(raw: unknown, label = "toolBudget"): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
1526
1621
|
const resolved = validateToolBudgetConfig(raw, label);
|
|
1527
1622
|
return { toolBudget: resolved.budget, error: resolved.error };
|
|
@@ -1607,6 +1702,19 @@ function withForkContext(
|
|
|
1607
1702
|
};
|
|
1608
1703
|
}
|
|
1609
1704
|
|
|
1705
|
+
function withForkThinkingNotes(
|
|
1706
|
+
result: AgentToolResult<Details>,
|
|
1707
|
+
downgrades: Map<number, string>,
|
|
1708
|
+
): AgentToolResult<Details> {
|
|
1709
|
+
if (downgrades.size === 0) return result;
|
|
1710
|
+
const children = [...downgrades.entries()]
|
|
1711
|
+
.sort(([a], [b]) => a - b)
|
|
1712
|
+
.map(([index, agent]) => `${agent} (child ${index})`)
|
|
1713
|
+
.join(", ");
|
|
1714
|
+
const note = `Note: fork context forced thinking off for ${children}. The forked transcript contained signed Anthropic thinking blocks that were sanitized, and Anthropic children cannot resume such a transcript with thinking enabled. Use context: "fresh" when an Anthropic child needs thinking.`;
|
|
1715
|
+
return { ...result, content: [...result.content, { type: "text", text: note }] };
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1610
1718
|
function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): AgentToolResult<Details> {
|
|
1611
1719
|
const message = error instanceof Error ? error.message : String(error);
|
|
1612
1720
|
return withForkContext(
|
|
@@ -1621,7 +1729,7 @@ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): Age
|
|
|
1621
1729
|
|
|
1622
1730
|
function collectChainSessionFiles(
|
|
1623
1731
|
chain: ChainStep[],
|
|
1624
|
-
sessionFileForTask:
|
|
1732
|
+
sessionFileForTask: ForkSessionFileForTask,
|
|
1625
1733
|
dynamicFanoutMaxItems?: number,
|
|
1626
1734
|
): (string | undefined)[] {
|
|
1627
1735
|
const sessionFiles: (string | undefined)[] = [];
|
|
@@ -1629,7 +1737,7 @@ function collectChainSessionFiles(
|
|
|
1629
1737
|
for (const step of chain) {
|
|
1630
1738
|
if (isParallelStep(step)) {
|
|
1631
1739
|
for (const task of step.parallel) {
|
|
1632
|
-
sessionFiles.push(sessionFileForTask(task.agent, flatIndex));
|
|
1740
|
+
sessionFiles.push(sessionFileForTask(task.agent, flatIndex, task.model));
|
|
1633
1741
|
flatIndex++;
|
|
1634
1742
|
}
|
|
1635
1743
|
continue;
|
|
@@ -1637,12 +1745,13 @@ function collectChainSessionFiles(
|
|
|
1637
1745
|
if (isDynamicParallelStep(step)) {
|
|
1638
1746
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1639
1747
|
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
1640
|
-
sessionFiles.push(sessionFileForTask(step.parallel.agent, flatIndex));
|
|
1748
|
+
sessionFiles.push(sessionFileForTask(step.parallel.agent, flatIndex, step.parallel.model));
|
|
1641
1749
|
flatIndex++;
|
|
1642
1750
|
}
|
|
1643
1751
|
continue;
|
|
1644
1752
|
}
|
|
1645
|
-
|
|
1753
|
+
const sequential = step as SequentialStep;
|
|
1754
|
+
sessionFiles.push(sessionFileForTask(sequential.agent, flatIndex, sequential.model));
|
|
1646
1755
|
flatIndex++;
|
|
1647
1756
|
}
|
|
1648
1757
|
return sessionFiles;
|
|
@@ -1650,7 +1759,7 @@ function collectChainSessionFiles(
|
|
|
1650
1759
|
|
|
1651
1760
|
function collectChainThinkingOverrides(
|
|
1652
1761
|
chain: ChainStep[],
|
|
1653
|
-
thinkingOverrideForTask:
|
|
1762
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask,
|
|
1654
1763
|
dynamicFanoutMaxItems?: number,
|
|
1655
1764
|
): (AgentConfig["thinking"] | undefined)[] {
|
|
1656
1765
|
const thinkingOverrides: (AgentConfig["thinking"] | undefined)[] = [];
|
|
@@ -1658,7 +1767,7 @@ function collectChainThinkingOverrides(
|
|
|
1658
1767
|
for (const step of chain) {
|
|
1659
1768
|
if (isParallelStep(step)) {
|
|
1660
1769
|
for (const task of step.parallel) {
|
|
1661
|
-
thinkingOverrides.push(thinkingOverrideForTask(task.agent, flatIndex));
|
|
1770
|
+
thinkingOverrides.push(thinkingOverrideForTask(task.agent, flatIndex, task.model));
|
|
1662
1771
|
flatIndex++;
|
|
1663
1772
|
}
|
|
1664
1773
|
continue;
|
|
@@ -1666,17 +1775,42 @@ function collectChainThinkingOverrides(
|
|
|
1666
1775
|
if (isDynamicParallelStep(step)) {
|
|
1667
1776
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1668
1777
|
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
1669
|
-
thinkingOverrides.push(thinkingOverrideForTask(step.parallel.agent, flatIndex));
|
|
1778
|
+
thinkingOverrides.push(thinkingOverrideForTask(step.parallel.agent, flatIndex, step.parallel.model));
|
|
1670
1779
|
flatIndex++;
|
|
1671
1780
|
}
|
|
1672
1781
|
continue;
|
|
1673
1782
|
}
|
|
1674
|
-
|
|
1783
|
+
const sequential = step as SequentialStep;
|
|
1784
|
+
thinkingOverrides.push(thinkingOverrideForTask(sequential.agent, flatIndex, sequential.model));
|
|
1675
1785
|
flatIndex++;
|
|
1676
1786
|
}
|
|
1677
1787
|
return thinkingOverrides;
|
|
1678
1788
|
}
|
|
1679
1789
|
|
|
1790
|
+
function firstChainAgent(chain: ChainStep[]): string | undefined {
|
|
1791
|
+
const first = chain[0];
|
|
1792
|
+
if (!first) return undefined;
|
|
1793
|
+
if (isParallelStep(first)) return first.parallel[0]?.agent;
|
|
1794
|
+
if (isDynamicParallelStep(first)) return first.parallel.agent;
|
|
1795
|
+
return (first as SequentialStep).agent;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
function firstRawChainTask(chain: ChainStep[]): string | undefined {
|
|
1799
|
+
const first = chain[0];
|
|
1800
|
+
if (!first) return undefined;
|
|
1801
|
+
if (isParallelStep(first)) return first.parallel[0]?.task;
|
|
1802
|
+
if (isDynamicParallelStep(first)) return first.parallel.task;
|
|
1803
|
+
return (first as SequentialStep).task;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
function resolveAsyncEventGoal(workflowTask: string | undefined, rawChain: ChainStep[], unwrapForkFallback = false): string {
|
|
1807
|
+
if (workflowTask?.trim()) return workflowTask;
|
|
1808
|
+
const fallback = firstRawChainTask(rawChain) || "";
|
|
1809
|
+
if (!unwrapForkFallback) return fallback;
|
|
1810
|
+
const forkPrefix = `${DEFAULT_FORK_PREAMBLE}\n\nTask:\n`;
|
|
1811
|
+
return fallback.startsWith(forkPrefix) ? fallback.slice(forkPrefix.length) : fallback;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1680
1814
|
function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
|
|
1681
1815
|
return chain.map((step, stepIndex) => {
|
|
1682
1816
|
if (isParallelStep(step)) {
|
|
@@ -1714,17 +1848,17 @@ function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultCo
|
|
|
1714
1848
|
function preflightForkSessionsForStaticTasks(
|
|
1715
1849
|
params: SubagentParamsLike,
|
|
1716
1850
|
contextPolicy: AgentDefaultContextPolicy,
|
|
1717
|
-
sessionFileForTask:
|
|
1851
|
+
sessionFileForTask: ForkSessionFileForTask,
|
|
1718
1852
|
dynamicFanoutMaxItems?: number,
|
|
1719
1853
|
): void {
|
|
1720
1854
|
if (!contextPolicy.usesFork) return;
|
|
1721
1855
|
if (params.agent) {
|
|
1722
|
-
if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0);
|
|
1856
|
+
if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0, params.model);
|
|
1723
1857
|
return;
|
|
1724
1858
|
}
|
|
1725
1859
|
if (params.tasks) {
|
|
1726
1860
|
params.tasks.forEach((task, index) => {
|
|
1727
|
-
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index);
|
|
1861
|
+
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index, task.model);
|
|
1728
1862
|
});
|
|
1729
1863
|
return;
|
|
1730
1864
|
}
|
|
@@ -1733,7 +1867,7 @@ function preflightForkSessionsForStaticTasks(
|
|
|
1733
1867
|
for (const step of params.chain) {
|
|
1734
1868
|
if (isParallelStep(step)) {
|
|
1735
1869
|
for (const task of step.parallel) {
|
|
1736
|
-
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex);
|
|
1870
|
+
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex, task.model);
|
|
1737
1871
|
flatIndex++;
|
|
1738
1872
|
}
|
|
1739
1873
|
continue;
|
|
@@ -1741,13 +1875,13 @@ function preflightForkSessionsForStaticTasks(
|
|
|
1741
1875
|
if (isDynamicParallelStep(step)) {
|
|
1742
1876
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1743
1877
|
if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
|
|
1744
|
-
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex);
|
|
1878
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
|
|
1745
1879
|
}
|
|
1746
1880
|
flatIndex += maxItems;
|
|
1747
1881
|
continue;
|
|
1748
1882
|
}
|
|
1749
1883
|
const sequential = step as SequentialStep;
|
|
1750
|
-
if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex);
|
|
1884
|
+
if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex, sequential.model);
|
|
1751
1885
|
flatIndex++;
|
|
1752
1886
|
}
|
|
1753
1887
|
}
|
|
@@ -1814,6 +1948,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1814
1948
|
currentModelProvider: ctx.model?.provider,
|
|
1815
1949
|
currentModel: ctx.model,
|
|
1816
1950
|
modelScope: data.modelScope,
|
|
1951
|
+
interactive: ctx.hasUI,
|
|
1817
1952
|
};
|
|
1818
1953
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1819
1954
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
@@ -1822,18 +1957,14 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1822
1957
|
const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
|
|
1823
1958
|
|
|
1824
1959
|
if (hasTasks && params.tasks) {
|
|
1825
|
-
const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent));
|
|
1826
|
-
const modelOverrides = params.tasks.map((task, index) =>
|
|
1827
|
-
resolveSubagentModelOverride(task.model ?? agentConfigs[index]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope, source: task.model ? "explicit" : "inherited" }),
|
|
1828
|
-
);
|
|
1829
1960
|
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
1830
1961
|
const parallelTasks = params.tasks.map((task, index) => ({
|
|
1831
1962
|
agent: task.agent,
|
|
1832
1963
|
task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
|
|
1833
1964
|
cwd: task.cwd,
|
|
1834
|
-
...(
|
|
1965
|
+
...(task.model !== undefined ? { model: task.model } : {}),
|
|
1835
1966
|
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
1836
|
-
...(task.output
|
|
1967
|
+
...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
|
|
1837
1968
|
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
1838
1969
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1839
1970
|
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
@@ -1847,6 +1978,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1847
1978
|
worktree: params.worktree,
|
|
1848
1979
|
}],
|
|
1849
1980
|
resultMode: "parallel",
|
|
1981
|
+
goal: params.tasks[0]?.task ?? "",
|
|
1850
1982
|
agents,
|
|
1851
1983
|
ctx: asyncCtx,
|
|
1852
1984
|
availableModels,
|
|
@@ -1857,9 +1989,10 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1857
1989
|
shareEnabled,
|
|
1858
1990
|
sessionRoot,
|
|
1859
1991
|
chainSkills: [],
|
|
1860
|
-
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
1861
|
-
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
|
|
1992
|
+
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
1993
|
+
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
1862
1994
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1995
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
1863
1996
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1864
1997
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1865
1998
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -1878,10 +2011,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1878
2011
|
if (hasChain && params.chain) {
|
|
1879
2012
|
const normalized = normalizeSkillInput(params.skill);
|
|
1880
2013
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
1881
|
-
const
|
|
2014
|
+
const rawChain = params.chain as ChainStep[];
|
|
2015
|
+
const chain = wrapChainTasksForFork(rawChain, contextPolicy);
|
|
1882
2016
|
return executeAsyncChain(id, {
|
|
1883
2017
|
chain,
|
|
1884
2018
|
task: params.task,
|
|
2019
|
+
goal: resolveAsyncEventGoal(params.task, rawChain),
|
|
1885
2020
|
agents,
|
|
1886
2021
|
ctx: asyncCtx,
|
|
1887
2022
|
availableModels,
|
|
@@ -1896,6 +2031,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1896
2031
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1897
2032
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1898
2033
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2034
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
1899
2035
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1900
2036
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1901
2037
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -1926,10 +2062,11 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1926
2062
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
1927
2063
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
1928
2064
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
1929
|
-
const modelOverride =
|
|
2065
|
+
const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
|
|
1930
2066
|
return executeAsyncSingle(id, {
|
|
1931
2067
|
agent: params.agent!,
|
|
1932
2068
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
2069
|
+
goal: params.task ?? "",
|
|
1933
2070
|
agentConfig: a,
|
|
1934
2071
|
ctx: asyncCtx,
|
|
1935
2072
|
availableModels,
|
|
@@ -1939,14 +2076,15 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1939
2076
|
artifactConfig,
|
|
1940
2077
|
shareEnabled,
|
|
1941
2078
|
sessionRoot,
|
|
1942
|
-
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
2079
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
1943
2080
|
skills,
|
|
1944
2081
|
output: effectiveOutput,
|
|
1945
2082
|
outputMode: effectiveOutputMode,
|
|
1946
2083
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
1947
2084
|
modelOverride,
|
|
1948
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
2085
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
1949
2086
|
maxSubagentDepth,
|
|
2087
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
1950
2088
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1951
2089
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1952
2090
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -2028,7 +2166,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2028
2166
|
timeoutMs: data.timeoutMs,
|
|
2029
2167
|
deadlineAt: data.deadlineAt,
|
|
2030
2168
|
turnBudget: data.turnBudget,
|
|
2031
|
-
onDetachedExit: (index, result) => updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, index, result }),
|
|
2169
|
+
onDetachedExit: (index, result) => updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events }),
|
|
2032
2170
|
toolBudget: data.toolBudget,
|
|
2033
2171
|
configToolBudget: data.configToolBudget,
|
|
2034
2172
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
@@ -2051,11 +2189,15 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2051
2189
|
currentModelProvider: ctx.model?.provider,
|
|
2052
2190
|
currentModel: ctx.model,
|
|
2053
2191
|
modelScope: data.modelScope,
|
|
2192
|
+
interactive: ctx.hasUI,
|
|
2054
2193
|
};
|
|
2055
|
-
const
|
|
2194
|
+
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
2195
|
+
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
2196
|
+
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
2056
2197
|
return executeAsyncChain(id, {
|
|
2057
2198
|
chain: asyncChain,
|
|
2058
2199
|
task: params.task,
|
|
2200
|
+
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
2059
2201
|
agents,
|
|
2060
2202
|
ctx: asyncCtx,
|
|
2061
2203
|
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
@@ -2070,6 +2212,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2070
2212
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2071
2213
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2072
2214
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2215
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2073
2216
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2074
2217
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2075
2218
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -2092,7 +2235,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2092
2235
|
rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
|
|
2093
2236
|
}
|
|
2094
2237
|
const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
|
|
2095
|
-
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results });
|
|
2238
|
+
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results });
|
|
2096
2239
|
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
2097
2240
|
? await maybeBuildForegroundIntercomReceipt({
|
|
2098
2241
|
pi: deps.pi,
|
|
@@ -2121,12 +2264,13 @@ interface ForegroundParallelRunInput {
|
|
|
2121
2264
|
ctx: ExtensionContext;
|
|
2122
2265
|
state: SubagentState;
|
|
2123
2266
|
intercomEvents: IntercomEventBus;
|
|
2267
|
+
parentSessionId: string | null;
|
|
2124
2268
|
signal: AbortSignal;
|
|
2125
2269
|
runId: string;
|
|
2126
2270
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
2127
2271
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
2128
|
-
sessionFileForTask:
|
|
2129
|
-
thinkingOverrideForTask:
|
|
2272
|
+
sessionFileForTask: ForkSessionFileForTask;
|
|
2273
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2130
2274
|
shareEnabled: boolean;
|
|
2131
2275
|
artifactConfig: ArtifactConfig;
|
|
2132
2276
|
artifactsDir: string;
|
|
@@ -2135,6 +2279,7 @@ interface ForegroundParallelRunInput {
|
|
|
2135
2279
|
paramsCwd: string;
|
|
2136
2280
|
progressDir: string;
|
|
2137
2281
|
maxSubagentDepths: number[];
|
|
2282
|
+
waitToolEnabled?: boolean;
|
|
2138
2283
|
availableModels: ModelInfo[];
|
|
2139
2284
|
modelScope?: ModelScopeConfig;
|
|
2140
2285
|
modelOverrides: (string | undefined)[];
|
|
@@ -2268,9 +2413,8 @@ function findDuplicateParallelOutputPath(input: {
|
|
|
2268
2413
|
async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
|
|
2269
2414
|
// Pre-warm fork session files sequentially before concurrent dispatch to avoid
|
|
2270
2415
|
// races where multiple workers simultaneously try to branch the same parent session.
|
|
2271
|
-
// sessionFileForIndex caches results, so these calls return instantly inside mapConcurrent.
|
|
2272
2416
|
for (let i = 0; i < input.tasks.length; i++) {
|
|
2273
|
-
input.
|
|
2417
|
+
input.sessionFileForTask(input.tasks[i]!.agent, i, input.modelOverrides[i]);
|
|
2274
2418
|
}
|
|
2275
2419
|
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
2276
2420
|
const behavior = input.behaviors[index];
|
|
@@ -2283,9 +2427,11 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2283
2427
|
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
|
|
2284
2428
|
: { prefix: "", suffix: "" };
|
|
2285
2429
|
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd, input.outputBaseDir);
|
|
2430
|
+
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
2286
2431
|
const taskText = injectSingleOutputInstruction(
|
|
2287
2432
|
`${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
|
|
2288
2433
|
outputPath,
|
|
2434
|
+
agentConfig,
|
|
2289
2435
|
);
|
|
2290
2436
|
const interruptController = new AbortController();
|
|
2291
2437
|
if (input.foregroundControl) {
|
|
@@ -2301,7 +2447,6 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2301
2447
|
return true;
|
|
2302
2448
|
};
|
|
2303
2449
|
}
|
|
2304
|
-
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
2305
2450
|
return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
2306
2451
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
2307
2452
|
cwd: taskCwd,
|
|
@@ -2312,7 +2457,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2312
2457
|
runId: input.runId,
|
|
2313
2458
|
index,
|
|
2314
2459
|
sessionDir: input.sessionDirForIndex(index),
|
|
2315
|
-
sessionFile: input.sessionFileForTask(task.agent, index),
|
|
2460
|
+
sessionFile: input.sessionFileForTask(task.agent, index, input.modelOverrides[index]),
|
|
2316
2461
|
share: input.shareEnabled,
|
|
2317
2462
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
2318
2463
|
artifactConfig: input.artifactConfig,
|
|
@@ -2320,14 +2465,15 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2320
2465
|
outputPath,
|
|
2321
2466
|
outputMode: behavior?.outputMode,
|
|
2322
2467
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
2468
|
+
waitToolEnabled: input.waitToolEnabled,
|
|
2323
2469
|
controlConfig: input.controlConfig,
|
|
2324
2470
|
onControlEvent: input.onControlEvent,
|
|
2325
|
-
onDetachedExit: (result) => updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, index, result }),
|
|
2471
|
+
onDetachedExit: (result) => updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents }),
|
|
2326
2472
|
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
2327
2473
|
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
2328
2474
|
nestedRoute: input.foregroundControl?.nestedRoute,
|
|
2329
2475
|
modelOverride: input.modelOverrides[index],
|
|
2330
|
-
thinkingOverride: input.thinkingOverrideForTask(task.agent, index),
|
|
2476
|
+
thinkingOverride: input.thinkingOverrideForTask(task.agent, index, input.modelOverrides[index]),
|
|
2331
2477
|
availableModels: input.availableModels,
|
|
2332
2478
|
preferredModelProvider: input.ctx.model?.provider,
|
|
2333
2479
|
modelScope: input.modelScope,
|
|
@@ -2453,15 +2599,15 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2453
2599
|
normalizeSkillInput(t.skill),
|
|
2454
2600
|
);
|
|
2455
2601
|
const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
|
|
2456
|
-
...(task.output !== undefined
|
|
2602
|
+
...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
|
|
2457
2603
|
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
2458
2604
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
2459
2605
|
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
2460
2606
|
...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
|
|
2461
|
-
...(task.model ? { model: task.model } : {}),
|
|
2607
|
+
...(task.model !== undefined ? { model: task.model } : {}),
|
|
2462
2608
|
}));
|
|
2463
2609
|
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
2464
|
-
|
|
2610
|
+
resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope }),
|
|
2465
2611
|
);
|
|
2466
2612
|
|
|
2467
2613
|
if (params.clarify === true && ctx.hasUI) {
|
|
@@ -2495,8 +2641,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2495
2641
|
taskTexts = result.templates;
|
|
2496
2642
|
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
2497
2643
|
const override = result.behaviorOverrides[i];
|
|
2498
|
-
if (override?.model) {
|
|
2499
|
-
modelOverrides[i] =
|
|
2644
|
+
if (override?.model !== undefined) {
|
|
2645
|
+
modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
|
|
2500
2646
|
behaviorOverrides[i]!.model = override.model;
|
|
2501
2647
|
}
|
|
2502
2648
|
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
@@ -2525,6 +2671,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2525
2671
|
currentModelProvider: ctx.model?.provider,
|
|
2526
2672
|
currentModel: ctx.model,
|
|
2527
2673
|
modelScope: data.modelScope,
|
|
2674
|
+
interactive: ctx.hasUI,
|
|
2528
2675
|
};
|
|
2529
2676
|
const parallelTasks = tasks.map((t, i) => {
|
|
2530
2677
|
const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
@@ -2533,7 +2680,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2533
2680
|
agent: t.agent,
|
|
2534
2681
|
task: taskText,
|
|
2535
2682
|
cwd: t.cwd,
|
|
2536
|
-
...(
|
|
2683
|
+
...(behaviorOverrides[i]?.model !== undefined ? { model: behaviorOverrides[i]!.model } : {}),
|
|
2537
2684
|
...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
|
|
2538
2685
|
...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i]!.output } : {}),
|
|
2539
2686
|
...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
|
|
@@ -2546,6 +2693,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2546
2693
|
return executeAsyncChain(id, {
|
|
2547
2694
|
chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }],
|
|
2548
2695
|
resultMode: "parallel",
|
|
2696
|
+
goal: taskTexts[0] ?? "",
|
|
2549
2697
|
agents,
|
|
2550
2698
|
ctx: asyncCtx,
|
|
2551
2699
|
availableModels,
|
|
@@ -2556,9 +2704,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2556
2704
|
shareEnabled,
|
|
2557
2705
|
sessionRoot,
|
|
2558
2706
|
chainSkills: [],
|
|
2559
|
-
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
2560
|
-
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
|
|
2707
|
+
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
|
|
2708
|
+
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
|
|
2561
2709
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2710
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2562
2711
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2563
2712
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2564
2713
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -2622,6 +2771,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2622
2771
|
ctx,
|
|
2623
2772
|
state: deps.state,
|
|
2624
2773
|
intercomEvents: deps.pi.events,
|
|
2774
|
+
parentSessionId: data.parentSessionId,
|
|
2625
2775
|
signal,
|
|
2626
2776
|
runId,
|
|
2627
2777
|
sessionDirForIndex,
|
|
@@ -2648,6 +2798,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2648
2798
|
concurrencyLimit: parallelConcurrency,
|
|
2649
2799
|
globalSemaphore: new Semaphore(deps.config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT),
|
|
2650
2800
|
maxSubagentDepths,
|
|
2801
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2651
2802
|
liveResults,
|
|
2652
2803
|
liveProgress,
|
|
2653
2804
|
onUpdate,
|
|
@@ -2681,7 +2832,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2681
2832
|
totalChildUsage: sumResultsUsage(results),
|
|
2682
2833
|
totalCost: sumResultsCost(results),
|
|
2683
2834
|
});
|
|
2684
|
-
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results });
|
|
2835
|
+
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
2685
2836
|
if (interrupted) {
|
|
2686
2837
|
return {
|
|
2687
2838
|
content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
@@ -2692,7 +2843,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2692
2843
|
const detached = detachedIndex >= 0 ? results[detachedIndex] : undefined;
|
|
2693
2844
|
if (detached) {
|
|
2694
2845
|
return {
|
|
2695
|
-
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first.
|
|
2846
|
+
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
|
|
2696
2847
|
details,
|
|
2697
2848
|
};
|
|
2698
2849
|
}
|
|
@@ -2778,12 +2929,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2778
2929
|
const currentProvider = ctx.model?.provider;
|
|
2779
2930
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
2780
2931
|
let task = params.task ?? "";
|
|
2781
|
-
let modelOverride: string | undefined =
|
|
2782
|
-
|
|
2932
|
+
let modelOverride: string | undefined = resolveEffectiveSubagentModel(
|
|
2933
|
+
params.model as string | undefined,
|
|
2934
|
+
agentConfig.model,
|
|
2783
2935
|
ctx.model,
|
|
2784
2936
|
availableModels,
|
|
2785
2937
|
currentProvider,
|
|
2786
|
-
{ scope: data.modelScope
|
|
2938
|
+
{ scope: data.modelScope },
|
|
2787
2939
|
);
|
|
2788
2940
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
2789
2941
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
@@ -2820,7 +2972,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2820
2972
|
|
|
2821
2973
|
task = result.templates[0]!;
|
|
2822
2974
|
const override = result.behaviorOverrides[0];
|
|
2823
|
-
if (override?.model) modelOverride =
|
|
2975
|
+
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
|
|
2824
2976
|
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
2825
2977
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
2826
2978
|
|
|
@@ -2841,10 +2993,12 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2841
2993
|
currentModelProvider: ctx.model?.provider,
|
|
2842
2994
|
currentModel: ctx.model,
|
|
2843
2995
|
modelScope: data.modelScope,
|
|
2996
|
+
interactive: ctx.hasUI,
|
|
2844
2997
|
};
|
|
2845
2998
|
return executeAsyncSingle(id, {
|
|
2846
2999
|
agent: params.agent!,
|
|
2847
3000
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
3001
|
+
goal: task,
|
|
2848
3002
|
agentConfig,
|
|
2849
3003
|
ctx: asyncCtx,
|
|
2850
3004
|
availableModels,
|
|
@@ -2854,14 +3008,15 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2854
3008
|
artifactConfig,
|
|
2855
3009
|
shareEnabled,
|
|
2856
3010
|
sessionRoot,
|
|
2857
|
-
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
3011
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2858
3012
|
skills: skillOverride === false ? [] : skillOverride,
|
|
2859
3013
|
output: effectiveOutput,
|
|
2860
3014
|
outputMode: effectiveOutputMode,
|
|
2861
3015
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2862
3016
|
modelOverride,
|
|
2863
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
3017
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2864
3018
|
maxSubagentDepth,
|
|
3019
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2865
3020
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2866
3021
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2867
3022
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -2884,7 +3039,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2884
3039
|
if (validationError) {
|
|
2885
3040
|
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
2886
3041
|
}
|
|
2887
|
-
task = injectSingleOutputInstruction(task, outputPath);
|
|
3042
|
+
task = injectSingleOutputInstruction(task, outputPath, agentConfig);
|
|
2888
3043
|
|
|
2889
3044
|
let effectiveSkills: string[] | undefined;
|
|
2890
3045
|
if (skillOverride === false) {
|
|
@@ -2938,7 +3093,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2938
3093
|
intercomEvents: deps.pi.events,
|
|
2939
3094
|
runId,
|
|
2940
3095
|
sessionDir: sessionDirForIndex(0),
|
|
2941
|
-
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
3096
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2942
3097
|
share: shareEnabled,
|
|
2943
3098
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2944
3099
|
artifactConfig,
|
|
@@ -2946,6 +3101,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2946
3101
|
outputPath,
|
|
2947
3102
|
outputMode: effectiveOutputMode,
|
|
2948
3103
|
maxSubagentDepth,
|
|
3104
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2949
3105
|
onUpdate: forwardSingleUpdate,
|
|
2950
3106
|
controlConfig,
|
|
2951
3107
|
onControlEvent,
|
|
@@ -2954,14 +3110,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2954
3110
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
2955
3111
|
index: 0,
|
|
2956
3112
|
modelOverride,
|
|
2957
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
3113
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2958
3114
|
availableModels,
|
|
2959
3115
|
preferredModelProvider: currentProvider,
|
|
2960
3116
|
modelScope: data.modelScope,
|
|
2961
3117
|
skills: effectiveSkills,
|
|
2962
3118
|
acceptance: params.acceptance,
|
|
2963
3119
|
acceptanceContext: { mode: "single" },
|
|
2964
|
-
onDetachedExit: (result) => updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, index: 0, result }),
|
|
3120
|
+
onDetachedExit: (result) => updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events }),
|
|
2965
3121
|
timeoutMs: data.timeoutMs,
|
|
2966
3122
|
deadlineAt,
|
|
2967
3123
|
turnBudget: data.turnBudget,
|
|
@@ -3011,7 +3167,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3011
3167
|
totalChildUsage: sumResultsUsage([r]),
|
|
3012
3168
|
totalCost: sumResultsCost([r]),
|
|
3013
3169
|
});
|
|
3014
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results });
|
|
3170
|
+
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
3015
3171
|
|
|
3016
3172
|
if (!r.detached && !r.interrupted) {
|
|
3017
3173
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
@@ -3034,7 +3190,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3034
3190
|
|
|
3035
3191
|
if (r.detached) {
|
|
3036
3192
|
return {
|
|
3037
|
-
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first.
|
|
3193
|
+
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
|
|
3038
3194
|
details,
|
|
3039
3195
|
};
|
|
3040
3196
|
}
|
|
@@ -3115,6 +3271,80 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3115
3271
|
const paramsWithResolvedCwd = requestParams.cwd === undefined ? requestParams : { ...requestParams, cwd: requestCwd };
|
|
3116
3272
|
const action = paramsWithResolvedCwd.action;
|
|
3117
3273
|
if (action) {
|
|
3274
|
+
if ((WATCHDOG_TOOL_ACTIONS as readonly string[]).includes(action)) {
|
|
3275
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
3276
|
+
return {
|
|
3277
|
+
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
3278
|
+
isError: true,
|
|
3279
|
+
details: { mode: "management" as const, results: [] },
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
return handleWatchdogToolAction(action, paramsWithResolvedCwd, ctx, deps.watchdog);
|
|
3283
|
+
}
|
|
3284
|
+
if (action === "grant-spawn-budget") {
|
|
3285
|
+
if (deps.allowMutatingManagementActions === false || !ctx.hasUI) {
|
|
3286
|
+
return {
|
|
3287
|
+
content: [{ type: "text", text: "Action 'grant-spawn-budget' is available only from the root interactive parent session." }],
|
|
3288
|
+
isError: true,
|
|
3289
|
+
details: { mode: "management", results: [] },
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3293
|
+
if (!deps.state.currentSessionId) {
|
|
3294
|
+
return {
|
|
3295
|
+
content: [{ type: "text", text: "Action 'grant-spawn-budget' requires an active parent session id." }],
|
|
3296
|
+
isError: true,
|
|
3297
|
+
details: { mode: "management", results: [] },
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
if (hasActiveSubagentChildren(deps.state)) {
|
|
3301
|
+
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, deps.state.currentSessionId);
|
|
3302
|
+
return {
|
|
3303
|
+
content: [{ type: "text", text: "Spawn budget grants are rejected while current-session children are queued or running. Wait for them to settle, then retry the explicit grant." }],
|
|
3304
|
+
isError: true,
|
|
3305
|
+
details: { mode: "management", results: [], spawnBudget },
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3308
|
+
const sessionId = deps.state.currentSessionId;
|
|
3309
|
+
const additional = paramsWithResolvedCwd.additional ?? Number.NaN;
|
|
3310
|
+
const preview = preflightSpawnBudgetGrant(deps.state, deps.config, sessionId, additional);
|
|
3311
|
+
if (preview.error) {
|
|
3312
|
+
return {
|
|
3313
|
+
content: [{ type: "text", text: preview.error }],
|
|
3314
|
+
isError: true,
|
|
3315
|
+
details: { mode: "management", results: [], spawnBudget: preview.snapshot },
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
const confirmed = await ctx.ui.confirm(
|
|
3319
|
+
"Grant subagent spawn budget?",
|
|
3320
|
+
`Add ${additional} launches to this logical session?\n\n${formatSpawnBudget(preview.snapshot)}\n\nUsage is not reset. Compaction keeps the same budget; a new parent session starts a fresh one.`,
|
|
3321
|
+
);
|
|
3322
|
+
if (!confirmed) {
|
|
3323
|
+
return {
|
|
3324
|
+
content: [{ type: "text", text: "Spawn budget grant canceled; no capacity was added." }],
|
|
3325
|
+
details: { mode: "management", results: [], spawnBudget: preview.snapshot },
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
const currentBudget = getSpawnBudgetSnapshot(deps.state, deps.config, deps.state.currentSessionId);
|
|
3329
|
+
if (
|
|
3330
|
+
resolveCurrentSessionId(ctx.sessionManager) !== sessionId
|
|
3331
|
+
|| hasActiveSubagentChildren(deps.state)
|
|
3332
|
+
|| currentBudget.used !== preview.snapshot.used
|
|
3333
|
+
|| currentBudget.granted !== preview.snapshot.granted
|
|
3334
|
+
) {
|
|
3335
|
+
return {
|
|
3336
|
+
content: [{ type: "text", text: "Spawn budget grant was not applied because the session, budget, or active-child state changed while confirmation was open." }],
|
|
3337
|
+
isError: true,
|
|
3338
|
+
details: { mode: "management", results: [], spawnBudget: currentBudget },
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
const granted = grantSpawnBudget(deps.state, deps.config, sessionId, additional);
|
|
3342
|
+
return {
|
|
3343
|
+
content: [{ type: "text", text: granted.error ?? `Spawn budget grant applied: +${additional}. ${formatSpawnBudget(granted.snapshot)}` }],
|
|
3344
|
+
...(granted.error ? { isError: true } : {}),
|
|
3345
|
+
details: { mode: "management", results: [], spawnBudget: granted.snapshot },
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3118
3348
|
if (action === "doctor") {
|
|
3119
3349
|
let currentSessionFile: string | null = null;
|
|
3120
3350
|
let currentSessionId = deps.state.currentSessionId;
|
|
@@ -3131,6 +3361,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3131
3361
|
} catch (error) {
|
|
3132
3362
|
if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
3133
3363
|
}
|
|
3364
|
+
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
|
|
3134
3365
|
return {
|
|
3135
3366
|
content: [{
|
|
3136
3367
|
type: "text",
|
|
@@ -3147,15 +3378,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3147
3378
|
expandTilde: deps.expandTilde,
|
|
3148
3379
|
}),
|
|
3149
3380
|
}],
|
|
3150
|
-
details: { mode: "management", results: [] },
|
|
3381
|
+
details: { mode: "management", results: [], spawnBudget },
|
|
3151
3382
|
};
|
|
3152
3383
|
}
|
|
3153
3384
|
if (action === "status") {
|
|
3385
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3386
|
+
const withBudget = (result: AgentToolResult<Details>) => withSpawnBudgetStatus(
|
|
3387
|
+
result,
|
|
3388
|
+
deps.state,
|
|
3389
|
+
deps.config,
|
|
3390
|
+
deps.state.currentSessionId,
|
|
3391
|
+
);
|
|
3154
3392
|
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
3155
3393
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
3156
3394
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
3157
3395
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
3158
|
-
return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots });
|
|
3396
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
3159
3397
|
}
|
|
3160
3398
|
if (targetRunId) {
|
|
3161
3399
|
try {
|
|
@@ -3164,34 +3402,35 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3164
3402
|
const foreground = getForegroundControl(deps.state, resolved.id);
|
|
3165
3403
|
if (foreground) {
|
|
3166
3404
|
if (paramsWithResolvedCwd.view === "transcript") {
|
|
3167
|
-
return {
|
|
3405
|
+
return withBudget({
|
|
3168
3406
|
content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Persisted session transcript becomes inspectable after the foreground run completes when sessions are enabled." }],
|
|
3169
3407
|
details: { mode: "management", results: [] },
|
|
3170
|
-
};
|
|
3408
|
+
});
|
|
3171
3409
|
}
|
|
3172
|
-
return foregroundStatusResult(foreground);
|
|
3410
|
+
return withBudget(foregroundStatusResult(foreground));
|
|
3173
3411
|
}
|
|
3174
3412
|
}
|
|
3175
3413
|
} catch (error) {
|
|
3176
3414
|
const message = error instanceof Error ? error.message : String(error);
|
|
3177
|
-
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
3415
|
+
return withBudget({ content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } });
|
|
3178
3416
|
}
|
|
3179
3417
|
} else {
|
|
3180
3418
|
const foreground = getForegroundControl(deps.state, undefined);
|
|
3181
|
-
if (foreground && paramsWithResolvedCwd.view !== "transcript") return foregroundStatusResult(foreground);
|
|
3419
|
+
if (foreground && paramsWithResolvedCwd.view !== "transcript") return withBudget(foregroundStatusResult(foreground));
|
|
3182
3420
|
if (foreground && paramsWithResolvedCwd.view === "transcript") {
|
|
3183
|
-
return {
|
|
3421
|
+
return withBudget({
|
|
3184
3422
|
content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Pass an async run id to inspect a background transcript." }],
|
|
3185
3423
|
details: { mode: "management", results: [] },
|
|
3186
|
-
};
|
|
3424
|
+
});
|
|
3187
3425
|
}
|
|
3188
3426
|
}
|
|
3189
|
-
return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots });
|
|
3427
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
3190
3428
|
}
|
|
3191
3429
|
if (action === "resume") {
|
|
3192
3430
|
return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
3193
3431
|
}
|
|
3194
3432
|
if (action === "steer") {
|
|
3433
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3195
3434
|
const message = (paramsWithResolvedCwd.message ?? paramsWithResolvedCwd.task ?? "").trim();
|
|
3196
3435
|
if (!message) return { content: [{ type: "text", text: "action='steer' requires message." }], isError: true, details: { mode: "management", results: [] } };
|
|
3197
3436
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
@@ -3199,7 +3438,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3199
3438
|
try {
|
|
3200
3439
|
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR);
|
|
3201
3440
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
3202
|
-
return steerAsyncRun({ state: deps.state, runId, message, index: paramsWithResolvedCwd.index, kill: deps.kill, location });
|
|
3441
|
+
return steerAsyncRun({ state: deps.state, runId, message, index: paramsWithResolvedCwd.index, kill: deps.kill, location, signal, recover: ({ absoluteDeadlineAt, ...limits }) => resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, absoluteDeadlineAt }) });
|
|
3203
3442
|
} catch (error) {
|
|
3204
3443
|
const text = error instanceof Error ? error.message : String(error);
|
|
3205
3444
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -3213,10 +3452,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3213
3452
|
const text = error instanceof Error ? error.message : String(error);
|
|
3214
3453
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
3215
3454
|
}
|
|
3216
|
-
if (resolved?.kind === "nested") return steerNestedRun({ target: resolved, message, index: paramsWithResolvedCwd.index });
|
|
3455
|
+
if (resolved?.kind === "nested") return steerNestedRun({ target: resolved, message, index: paramsWithResolvedCwd.index, signal });
|
|
3217
3456
|
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: [] } };
|
|
3218
3457
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3219
|
-
return steerAsyncRun({ state: deps.state, runId: resolved.id, message, index: paramsWithResolvedCwd.index, kill: deps.kill, location: resolved.location });
|
|
3458
|
+
return steerAsyncRun({ state: deps.state, runId: resolved.id, message, index: paramsWithResolvedCwd.index, kill: deps.kill, location: resolved.location, signal, recover: ({ absoluteDeadlineAt, ...limits }) => resumeAsyncRun({ params: { ...limits, action: "resume", id: resolved!.id, message }, requestCwd, ctx, deps, absoluteDeadlineAt }) });
|
|
3220
3459
|
}
|
|
3221
3460
|
if (action === "append-step") {
|
|
3222
3461
|
return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
@@ -3231,6 +3470,40 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3231
3470
|
}
|
|
3232
3471
|
return deps.handleScheduledRunAction(paramsWithResolvedCwd, ctx);
|
|
3233
3472
|
}
|
|
3473
|
+
if (action === "stop") {
|
|
3474
|
+
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
3475
|
+
let resolved: ResolvedSubagentRunId | undefined;
|
|
3476
|
+
if (paramsWithResolvedCwd.dir) {
|
|
3477
|
+
try {
|
|
3478
|
+
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR);
|
|
3479
|
+
return stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location);
|
|
3480
|
+
} catch (error) {
|
|
3481
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
3482
|
+
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
if (!targetRunId) return { content: [{ type: "text", text: "action='stop' requires id or dir." }], isError: true, details: { mode: "management", results: [] } };
|
|
3486
|
+
try {
|
|
3487
|
+
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
3488
|
+
} catch (error) {
|
|
3489
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3490
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
3491
|
+
}
|
|
3492
|
+
if (resolved?.kind === "nested") return { content: [{ type: "text", text: "action='stop' supports current-session top-level async runs only." }], isError: true, details: { mode: "management", results: [] } };
|
|
3493
|
+
if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='stop' supports async runs only. Use action='interrupt' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
|
|
3494
|
+
const stopResult = stopAsyncRun(
|
|
3495
|
+
deps.state,
|
|
3496
|
+
resolved?.kind === "async" ? resolved.id : targetRunId,
|
|
3497
|
+
deps.kill,
|
|
3498
|
+
resolved?.kind === "async" ? resolved.location : undefined,
|
|
3499
|
+
);
|
|
3500
|
+
if (stopResult) return stopResult;
|
|
3501
|
+
return {
|
|
3502
|
+
content: [{ type: "text", text: "No stoppable async run found in this session." }],
|
|
3503
|
+
isError: true,
|
|
3504
|
+
details: { mode: "management", results: [] },
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
3234
3507
|
if (action === "interrupt") {
|
|
3235
3508
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
3236
3509
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
@@ -3320,10 +3593,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3320
3593
|
depth,
|
|
3321
3594
|
deps.config.forceTopLevelAsync === true,
|
|
3322
3595
|
);
|
|
3323
|
-
const foregroundTimeout = resolveForegroundTimeout(effectiveParams);
|
|
3324
|
-
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
3325
|
-
const turnBudget = resolveTurnBudget(effectiveParams, deps.config);
|
|
3326
|
-
if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
|
|
3327
3596
|
const runToolBudget = resolveToolBudget(effectiveParams.toolBudget, "toolBudget");
|
|
3328
3597
|
if (runToolBudget.error) return buildRequestedModeError(effectiveParams, runToolBudget.error);
|
|
3329
3598
|
const configToolBudget = resolveToolBudget(deps.config.toolBudget, "config.toolBudget");
|
|
@@ -3336,6 +3605,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3336
3605
|
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
3337
3606
|
const discoveredAgents = discovered.agents;
|
|
3338
3607
|
const modelScope = discovered.modelScope;
|
|
3608
|
+
effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
|
|
3609
|
+
const foregroundTimeout = resolveForegroundTimeout(effectiveParams);
|
|
3610
|
+
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
3611
|
+
const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
|
|
3612
|
+
if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
|
|
3339
3613
|
const contextPolicy = resolveAgentDefaultContextPolicy(effectiveParams, discoveredAgents);
|
|
3340
3614
|
effectiveParams = contextPolicy.params;
|
|
3341
3615
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
@@ -3370,10 +3644,47 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3370
3644
|
);
|
|
3371
3645
|
if (validationError) return validationError;
|
|
3372
3646
|
|
|
3647
|
+
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
3648
|
+
const requestedSpawns = countRequestedSubagentSpawns(effectiveParams, deps.config);
|
|
3649
|
+
const spawnPreflight = preflightSpawnBudget(
|
|
3650
|
+
deps.state,
|
|
3651
|
+
deps.config,
|
|
3652
|
+
deps.state.currentSessionId,
|
|
3653
|
+
requestedSpawns,
|
|
3654
|
+
);
|
|
3655
|
+
if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
|
|
3656
|
+
|
|
3373
3657
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
3374
3658
|
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
3659
|
+
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string): void => {};
|
|
3660
|
+
const forkThinkingRequirements = new Map<number, boolean>();
|
|
3661
|
+
const forkThinkingDowngrades = new Map<number, string>();
|
|
3375
3662
|
try {
|
|
3376
|
-
const
|
|
3663
|
+
const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
|
|
3664
|
+
prepareForkThinking = (agentName, index, modelOverride) => {
|
|
3665
|
+
const agentConfig = agents.find((agent) => agent.name === agentName);
|
|
3666
|
+
const primaryModel = resolveEffectiveSubagentModel(
|
|
3667
|
+
modelOverride,
|
|
3668
|
+
agentConfig?.model,
|
|
3669
|
+
ctx.model,
|
|
3670
|
+
forkAvailableModels,
|
|
3671
|
+
ctx.model?.provider,
|
|
3672
|
+
);
|
|
3673
|
+
const candidates = buildModelCandidates(
|
|
3674
|
+
primaryModel,
|
|
3675
|
+
agentConfig?.fallbackModels,
|
|
3676
|
+
forkAvailableModels,
|
|
3677
|
+
ctx.model?.provider,
|
|
3678
|
+
);
|
|
3679
|
+
forkThinkingRequirements.set(
|
|
3680
|
+
index,
|
|
3681
|
+
candidates.length === 0
|
|
3682
|
+
|| candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, ctx.model?.provider)),
|
|
3683
|
+
);
|
|
3684
|
+
};
|
|
3685
|
+
const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
|
|
3686
|
+
forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
|
|
3687
|
+
});
|
|
3377
3688
|
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
3378
3689
|
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
3379
3690
|
} catch (error) {
|
|
@@ -3410,16 +3721,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3410
3721
|
}
|
|
3411
3722
|
const sessionDirForIndex = (idx?: number) =>
|
|
3412
3723
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
3413
|
-
const forkSessionFileForTask = (agentName
|
|
3414
|
-
shouldForkAgent(contextPolicy, agentName)
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3724
|
+
const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride) => {
|
|
3725
|
+
if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
|
|
3726
|
+
prepareForkThinking(agentName, idx, modelOverride);
|
|
3727
|
+
return forkSessionFileForIndex(idx);
|
|
3728
|
+
};
|
|
3729
|
+
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride) => {
|
|
3730
|
+
if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
|
|
3731
|
+
prepareForkThinking(agentName, idx, modelOverride);
|
|
3732
|
+
const override = forkThinkingOverrideForIndex(idx);
|
|
3733
|
+
if (override === "off") forkThinkingDowngrades.set(idx, agentName);
|
|
3734
|
+
return override;
|
|
3735
|
+
};
|
|
3736
|
+
const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride) =>
|
|
3737
|
+
forkSessionFileForTask(agentName, idx, modelOverride) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
3419
3738
|
const childSessionFileForIndex = (idx?: number) =>
|
|
3420
3739
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
3421
3740
|
try {
|
|
3422
|
-
|
|
3741
|
+
if (!(effectiveParams.clarify === true && ctx.hasUI)) {
|
|
3742
|
+
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
3743
|
+
}
|
|
3423
3744
|
} catch (error) {
|
|
3424
3745
|
return toExecutionErrorResult(effectiveParams, error);
|
|
3425
3746
|
}
|
|
@@ -3430,15 +3751,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3430
3751
|
? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
|
|
3431
3752
|
: undefined;
|
|
3432
3753
|
|
|
3433
|
-
const
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
});
|
|
3441
|
-
if (spawnLimitError) return spawnLimitError;
|
|
3754
|
+
const reservation = reserveSpawnBudget(
|
|
3755
|
+
deps.state,
|
|
3756
|
+
deps.config,
|
|
3757
|
+
deps.state.currentSessionId,
|
|
3758
|
+
requestedSpawns,
|
|
3759
|
+
);
|
|
3760
|
+
if (reservation.error) return spawnBudgetErrorResult(reservation.error, foregroundMode);
|
|
3442
3761
|
|
|
3443
3762
|
const execData: ExecutionContextData = {
|
|
3444
3763
|
params: effectiveParams,
|
|
@@ -3467,6 +3786,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3467
3786
|
configToolBudget: configToolBudget.toolBudget,
|
|
3468
3787
|
contextPolicy,
|
|
3469
3788
|
modelScope,
|
|
3789
|
+
parentSessionId: deps.state.currentSessionId,
|
|
3470
3790
|
};
|
|
3471
3791
|
|
|
3472
3792
|
const foregroundControl = effectiveAsync
|
|
@@ -3493,10 +3813,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3493
3813
|
const details = result?.details;
|
|
3494
3814
|
const state = type === "subagent.nested.started"
|
|
3495
3815
|
? "running"
|
|
3496
|
-
:
|
|
3497
|
-
? "
|
|
3498
|
-
: details?.results.some((child) => child.
|
|
3499
|
-
? "
|
|
3816
|
+
: details?.results.some((child) => child.interrupted || child.detached)
|
|
3817
|
+
? "paused"
|
|
3818
|
+
: result?.isError || details?.results.some((child) => child.exitCode !== 0)
|
|
3819
|
+
? "failed"
|
|
3500
3820
|
: "complete";
|
|
3501
3821
|
const errorText = result?.isError
|
|
3502
3822
|
? result.content.find((item) => item.type === "text")?.text
|
|
@@ -3536,7 +3856,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3536
3856
|
...(errorText ? { error: errorText } : {}),
|
|
3537
3857
|
...(details?.results.length ? { steps: details.results.map((child) => ({
|
|
3538
3858
|
agent: child.agent,
|
|
3539
|
-
status: child.interrupted ? "paused" : child.exitCode === 0 ? "complete" : "failed",
|
|
3859
|
+
status: child.interrupted || child.detached ? "paused" : child.exitCode === 0 ? "complete" : "failed",
|
|
3540
3860
|
...(child.sessionFile ? { sessionFile: child.sessionFile } : {}),
|
|
3541
3861
|
...(child.error ? { error: child.error } : {}),
|
|
3542
3862
|
})) } : {}),
|
|
@@ -3550,7 +3870,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3550
3870
|
let nestedForegroundStarted = false;
|
|
3551
3871
|
try {
|
|
3552
3872
|
const asyncResult = runAsyncPath(execData, deps);
|
|
3553
|
-
if (asyncResult) return withForkContext(asyncResult, effectiveParams.context);
|
|
3873
|
+
if (asyncResult) return withForkContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), effectiveParams.context);
|
|
3554
3874
|
if (foregroundControl) {
|
|
3555
3875
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
3556
3876
|
nestedForegroundStarted = true;
|
|
@@ -3558,20 +3878,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3558
3878
|
if (hasChain && effectiveParams.chain) {
|
|
3559
3879
|
const result = await runChainPath(execData, deps);
|
|
3560
3880
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3561
|
-
return withForkContext(result, effectiveParams.context);
|
|
3881
|
+
return withForkContext(withForkThinkingNotes(result, forkThinkingDowngrades), effectiveParams.context);
|
|
3562
3882
|
}
|
|
3563
3883
|
if (hasTasks && effectiveParams.tasks) {
|
|
3564
3884
|
const result = await runParallelPath(execData, deps);
|
|
3565
3885
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3566
|
-
return withForkContext(result, effectiveParams.context);
|
|
3886
|
+
return withForkContext(withForkThinkingNotes(result, forkThinkingDowngrades), effectiveParams.context);
|
|
3567
3887
|
}
|
|
3568
3888
|
if (hasSingle) {
|
|
3569
3889
|
const result = await runSinglePath(execData, deps);
|
|
3570
3890
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3571
|
-
return withForkContext(result, effectiveParams.context);
|
|
3891
|
+
return withForkContext(withForkThinkingNotes(result, forkThinkingDowngrades), effectiveParams.context);
|
|
3572
3892
|
}
|
|
3573
3893
|
} catch (error) {
|
|
3574
|
-
const errorResult = toExecutionErrorResult(effectiveParams, error);
|
|
3894
|
+
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error), forkThinkingDowngrades);
|
|
3575
3895
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
3576
3896
|
return errorResult;
|
|
3577
3897
|
} finally {
|
|
@@ -3600,6 +3920,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3600
3920
|
): Promise<AgentToolResult<Details>> => {
|
|
3601
3921
|
const requestParams = omitExecutionModeActionAlias(params);
|
|
3602
3922
|
if (requestParams.action) return execute(id, requestParams, signal, onUpdate, ctx);
|
|
3923
|
+
const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
3924
|
+
const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
|
|
3925
|
+
const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
|
|
3926
|
+
if (!runsForeground) return execute(id, requestParams, signal, onUpdate, ctx);
|
|
3603
3927
|
if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(requestParams);
|
|
3604
3928
|
deps.state.subagentInProgress = true;
|
|
3605
3929
|
try {
|