pi-subagents 0.40.0 → 0.41.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 +76 -0
- package/README.md +246 -525
- package/agents/oracle.md +1 -0
- package/package.json +12 -4
- package/prompts/parallel-context-build.md +1 -1
- package/prompts/parallel-handoff-plan.md +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/skills/pi-subagents/references/constraints-and-recipes.md +19 -26
- package/skills/pi-subagents/references/execution-controls.md +98 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +18 -27
- package/src/agents/agent-management.ts +155 -65
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +154 -71
- package/src/agents/chain-serializer.ts +10 -7
- package/src/agents/frontmatter.ts +5 -3
- package/src/agents/identity.ts +1 -1
- package/src/agents/proactive-skills.ts +13 -10
- package/src/agents/skills.ts +23 -6
- package/src/api/control-channel.ts +4 -0
- package/src/api/delegation.ts +26 -194
- package/src/api/external-runs.ts +129 -0
- package/src/api/intercom-bridge.ts +3 -0
- package/src/api/pi-args.ts +5 -0
- package/src/api/preflight.ts +3 -3
- package/src/api/shared-types.ts +19 -0
- package/src/extension/config.ts +10 -0
- package/src/extension/control-notices.ts +5 -39
- package/src/extension/doctor.ts +10 -9
- package/src/extension/fanout-child.ts +7 -4
- package/src/extension/index.ts +232 -68
- package/src/extension/rpc.ts +18 -12
- package/src/extension/schemas.ts +48 -37
- package/src/extension/tool-description.ts +36 -86
- package/src/inspectors/herdr/actions.ts +229 -0
- package/src/inspectors/herdr/client.ts +130 -0
- package/src/inspectors/herdr/inspector-runner.ts +141 -0
- package/src/inspectors/herdr/project-panes.ts +154 -0
- package/src/integrations/herdr-status.ts +330 -0
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/result-intercom.ts +5 -1
- package/src/missions/actions.ts +372 -0
- package/src/missions/lifecycle.ts +314 -0
- package/src/missions/store.ts +442 -0
- package/src/missions/types.ts +135 -0
- package/src/policy/authority.ts +46 -0
- package/src/profiles/profiles.ts +29 -3
- package/src/runs/background/async-execution.ts +98 -49
- package/src/runs/background/async-job-tracker.ts +10 -2
- package/src/runs/background/async-resume.ts +6 -6
- package/src/runs/background/async-status.ts +29 -1
- package/src/runs/background/auto-drain.ts +3 -3
- package/src/runs/background/chain-append.ts +3 -2
- package/src/runs/background/control-channel.ts +9 -7
- package/src/runs/background/fleet-view.ts +3 -4
- package/src/runs/background/notify.ts +2 -1
- package/src/runs/background/process-terminal.ts +5 -5
- package/src/runs/background/result-watcher.ts +13 -5
- package/src/runs/background/run-id-resolver.ts +3 -3
- package/src/runs/background/run-status.ts +35 -8
- package/src/runs/background/scheduled-runs.ts +602 -375
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +608 -445
- package/src/runs/background/subagent-wait.ts +50 -9
- package/src/runs/background/wait-subscriptions.ts +253 -0
- package/src/runs/background/wait-tool.ts +12 -4
- package/src/runs/foreground/async-steering-action.ts +3 -3
- package/src/runs/foreground/chain-clarify.ts +8 -4
- package/src/runs/foreground/chain-execution.ts +56 -30
- package/src/runs/foreground/execution.ts +15 -2
- package/src/runs/foreground/subagent-executor.ts +1023 -273
- package/src/runs/shared/acceptance.ts +28 -6
- package/src/runs/shared/child-protocol.ts +302 -22
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/external-cli-runner.ts +130 -0
- package/src/runs/shared/long-running-guard.ts +42 -1
- package/src/runs/shared/nested-events.ts +59 -5
- package/src/runs/shared/nested-render.ts +9 -4
- package/src/runs/shared/parallel-handoff.ts +86 -2
- package/src/runs/shared/parallel-utils.ts +11 -2
- package/src/runs/shared/permissions.ts +95 -0
- package/src/runs/shared/pi-args.ts +11 -1
- package/src/runs/shared/pi-spawn.ts +11 -1
- package/src/runs/shared/run-history.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
- package/src/runs/shared/subagent-startup-retry.ts +5 -2
- package/src/runs/shared/turn-budget.ts +6 -6
- package/src/runs/shared/worktree.ts +122 -12
- package/src/shared/accessible-dir.ts +29 -7
- package/src/shared/artifacts.ts +18 -1
- package/src/shared/fork-context.ts +3 -2
- package/src/shared/launch-contract.ts +1 -0
- package/src/shared/settings.ts +10 -0
- package/src/shared/types.ts +156 -14
- package/src/shared/utils.ts +8 -6
- package/src/slash/delegation-adapters.ts +32 -194
- package/src/slash/delegation-request.ts +43 -126
- package/src/slash/prompt-template-bridge.ts +158 -205
- package/src/slash/prompt-workflows.ts +21 -57
- package/src/slash/slash-bridge.ts +14 -0
- package/src/slash/slash-commands.ts +31 -632
- package/src/slash/subagents-admin.ts +18 -14
- package/src/tui/fleet-status.ts +156 -21
- package/src/tui/fleet-transcript.ts +110 -5
- package/src/tui/fleet.ts +56 -24
- package/src/tui/render.ts +291 -109
- package/src/types/pi-runtime-compat.d.ts +14 -0
- package/src/watchdog/lsp-diagnostics.ts +12 -7
- package/src/watchdog/model-selection.ts +2 -2
- package/src/watchdog/permission-arbiter.ts +145 -0
- package/src/watchdog/register-child.ts +1 -1
- package/src/watchdog/register-main.ts +1 -1
- package/src/watchdog/review.ts +4 -1
- package/src/watchdog/runtime.ts +3 -2
- package/src/workflows/chat-progress.ts +140 -0
- package/src/workflows/scripted-workflow.ts +415 -0
- package/agents/advisor.md +0 -73
- package/src/extension/chain-validation.ts +0 -181
|
@@ -4,7 +4,8 @@ import * as path from "node:path";
|
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { resolveAgentName, type AgentConfig, type AgentScope } from "../../agents/agents.ts";
|
|
7
|
-
import { getArtifactsDir,
|
|
7
|
+
import { getArtifactsDir, getChainRunsDir } from "../../shared/artifacts.ts";
|
|
8
|
+
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
8
9
|
import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
|
|
9
10
|
import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
10
11
|
import { executeChain } from "./chain-execution.ts";
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
20
21
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
21
22
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
22
|
-
import { clearPendingForegroundControlNotices } from "../../extension/control-notices.ts";
|
|
23
23
|
import { runSync } from "./execution.ts";
|
|
24
24
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
25
25
|
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
@@ -37,6 +37,9 @@ import {
|
|
|
37
37
|
suppressProgressForReadOnlyTask,
|
|
38
38
|
taskDisallowsFileUpdates,
|
|
39
39
|
type ChainStep,
|
|
40
|
+
type DynamicParallelStep,
|
|
41
|
+
type ParallelStep,
|
|
42
|
+
type ParallelTaskItem,
|
|
40
43
|
type ResolvedStepBehavior,
|
|
41
44
|
type SequentialStep,
|
|
42
45
|
type StepOverrides,
|
|
@@ -61,7 +64,7 @@ import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOut
|
|
|
61
64
|
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
62
65
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
63
66
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
64
|
-
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup } from "../shared/parallel-handoff.ts";
|
|
67
|
+
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
65
68
|
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
66
69
|
import {
|
|
67
70
|
attachNestedChildrenToResultChildren,
|
|
@@ -83,6 +86,13 @@ import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/
|
|
|
83
86
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
84
87
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
85
88
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
89
|
+
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
90
|
+
import { attachMissionToLaunchResult, prepareMissionLaunch, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
91
|
+
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
92
|
+
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
93
|
+
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
94
|
+
import { runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
95
|
+
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
86
96
|
import {
|
|
87
97
|
cleanupWorktrees,
|
|
88
98
|
createWorktrees,
|
|
@@ -94,6 +104,7 @@ import {
|
|
|
94
104
|
} from "../shared/worktree.ts";
|
|
95
105
|
import {
|
|
96
106
|
type AgentProgress,
|
|
107
|
+
type AsyncJobState,
|
|
97
108
|
type AsyncStatus,
|
|
98
109
|
type AcceptanceInput,
|
|
99
110
|
type AgentContract,
|
|
@@ -118,10 +129,9 @@ import {
|
|
|
118
129
|
type UsageBudgetConfig,
|
|
119
130
|
type SubagentRunMode,
|
|
120
131
|
type SubagentState,
|
|
121
|
-
|
|
132
|
+
DIRS,
|
|
122
133
|
DEFAULT_ARTIFACT_CONFIG,
|
|
123
134
|
DEFAULT_FORK_PREAMBLE,
|
|
124
|
-
RESULTS_DIR,
|
|
125
135
|
SUBAGENT_ACTIONS,
|
|
126
136
|
SUBAGENT_CONTROL_EVENT,
|
|
127
137
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
@@ -134,7 +144,39 @@ import {
|
|
|
134
144
|
wrapForkTask,
|
|
135
145
|
} from "../../shared/types.ts";
|
|
136
146
|
|
|
137
|
-
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure"]);
|
|
147
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
148
|
+
|
|
149
|
+
type UndefinedOmitted<T extends object> = {
|
|
150
|
+
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
|
|
151
|
+
} & {
|
|
152
|
+
[K in keyof T as undefined extends T[K] ? K : never]?: Exclude<T[K], undefined>;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// These helpers mutate their argument, so keep calls scoped to fresh object literals or shallow copies.
|
|
156
|
+
function omitUndefinedProperties<T extends object>(value: T): UndefinedOmitted<T> {
|
|
157
|
+
for (const key of Object.keys(value) as Array<keyof T>) {
|
|
158
|
+
if (value[key] === undefined) delete value[key];
|
|
159
|
+
}
|
|
160
|
+
return value as UndefinedOmitted<T>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type WithUndefinedOptionals<T extends object> = {
|
|
164
|
+
[K in keyof T]: {} extends Pick<T, K> ? T[K] | undefined : T[K];
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
type RequiredKeysAllowingUndefined<T extends object> = {
|
|
168
|
+
[K in keyof T]-?: {} extends Pick<T, K> ? never : undefined extends T[K] ? K : never;
|
|
169
|
+
}[keyof T];
|
|
170
|
+
|
|
171
|
+
function compactOptional<T extends object>(
|
|
172
|
+
value: WithUndefinedOptionals<T> & (RequiredKeysAllowingUndefined<T> extends never ? unknown : never),
|
|
173
|
+
): T {
|
|
174
|
+
for (const key of Object.keys(value) as Array<keyof T>) {
|
|
175
|
+
if (value[key] === undefined) delete value[key];
|
|
176
|
+
}
|
|
177
|
+
return value as T;
|
|
178
|
+
}
|
|
179
|
+
|
|
138
180
|
interface TaskParam {
|
|
139
181
|
agent: string;
|
|
140
182
|
task: string;
|
|
@@ -157,13 +199,26 @@ export interface SubagentParamsLike {
|
|
|
157
199
|
id?: string;
|
|
158
200
|
runId?: string;
|
|
159
201
|
dir?: string;
|
|
202
|
+
handoffPath?: string;
|
|
160
203
|
index?: number;
|
|
161
204
|
view?: "fleet" | "transcript";
|
|
162
205
|
lines?: number;
|
|
206
|
+
chainName?: string;
|
|
207
|
+
config?: unknown;
|
|
208
|
+
name?: string;
|
|
209
|
+
type?: string;
|
|
163
210
|
agent?: string;
|
|
164
211
|
task?: string;
|
|
165
212
|
message?: string;
|
|
166
213
|
steeringRecovery?: boolean;
|
|
214
|
+
workflowScript?: string;
|
|
215
|
+
chatProgress?: "auto" | "off" | "terminal" | "milestones" | "live-card";
|
|
216
|
+
step?: ChainStep;
|
|
217
|
+
/** Internal workflow ownership metadata; not part of the public schema. */
|
|
218
|
+
workflowParentRunId?: string;
|
|
219
|
+
workflowKey?: string;
|
|
220
|
+
suppressRoutineResultIntercom?: boolean;
|
|
221
|
+
/** Internal durable-run compatibility fields. Public callers must use workflowScript. */
|
|
167
222
|
chain?: ChainStep[];
|
|
168
223
|
tasks?: TaskParam[];
|
|
169
224
|
concurrency?: number;
|
|
@@ -190,6 +245,7 @@ export interface SubagentParamsLike {
|
|
|
190
245
|
thinking?: string | false;
|
|
191
246
|
scope?: string;
|
|
192
247
|
target?: string;
|
|
248
|
+
focus?: boolean;
|
|
193
249
|
skill?: string | string[] | boolean;
|
|
194
250
|
output?: string | boolean;
|
|
195
251
|
outputMode?: "inline" | "file-only";
|
|
@@ -200,11 +256,25 @@ export interface SubagentParamsLike {
|
|
|
200
256
|
agentContract?: AgentContract;
|
|
201
257
|
schedule?: string;
|
|
202
258
|
scheduleName?: string;
|
|
259
|
+
at?: string;
|
|
260
|
+
every?: string;
|
|
261
|
+
on?: string | number;
|
|
262
|
+
timezone?: string;
|
|
263
|
+
overlap?: "skip";
|
|
264
|
+
catchUp?: "none" | "latest";
|
|
203
265
|
additional?: number;
|
|
266
|
+
missionId?: string;
|
|
267
|
+
mission?: unknown;
|
|
268
|
+
missionUpdate?: unknown;
|
|
269
|
+
missionStatus?: string;
|
|
270
|
+
missionScope?: string;
|
|
271
|
+
runMode?: string;
|
|
272
|
+
runStatus?: string;
|
|
273
|
+
summary?: string;
|
|
204
274
|
}
|
|
205
275
|
|
|
206
276
|
function rememberParentModel(state: { currentSessionId?: string | null; lastParentModel?: ParentModel }, sessionId: string | null, model: unknown): ParentModel | undefined {
|
|
207
|
-
if (state.currentSessionId !== sessionId) state.lastParentModel
|
|
277
|
+
if (state.currentSessionId !== sessionId) delete state.lastParentModel;
|
|
208
278
|
state.currentSessionId = sessionId;
|
|
209
279
|
const parentModel = normalizeParentModel(model);
|
|
210
280
|
if (!sessionId) return parentModel;
|
|
@@ -263,6 +333,7 @@ interface ExecutionContextData {
|
|
|
263
333
|
modelScope?: ModelScopeConfig;
|
|
264
334
|
parentModel?: ParentModel;
|
|
265
335
|
parentSessionId: string | null;
|
|
336
|
+
parentPiSessionId?: string;
|
|
266
337
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
267
338
|
}
|
|
268
339
|
|
|
@@ -273,7 +344,6 @@ function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefine
|
|
|
273
344
|
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
274
345
|
const control = state.foregroundControls.get(runId);
|
|
275
346
|
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
276
|
-
clearPendingForegroundControlNotices(state, runId);
|
|
277
347
|
state.foregroundControls.delete(runId);
|
|
278
348
|
if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
|
|
279
349
|
return true;
|
|
@@ -429,7 +499,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
429
499
|
agent: result.agent,
|
|
430
500
|
index,
|
|
431
501
|
...(result.context ? { context: result.context } : {}),
|
|
432
|
-
status: resolveSubagentResultStatus({
|
|
502
|
+
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
433
503
|
exitCode: result.exitCode,
|
|
434
504
|
interrupted: result.interrupted,
|
|
435
505
|
detached: result.detached,
|
|
@@ -437,7 +507,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
437
507
|
timedOut: result.timedOut,
|
|
438
508
|
stopped: result.stopped,
|
|
439
509
|
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
440
|
-
}),
|
|
510
|
+
})),
|
|
441
511
|
...foregroundChildActivityFromProgress(result.progress),
|
|
442
512
|
updatedAt,
|
|
443
513
|
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
@@ -491,7 +561,7 @@ function applyControlEventToRememberedForegroundRun(state: SubagentState, event:
|
|
|
491
561
|
};
|
|
492
562
|
}
|
|
493
563
|
|
|
494
|
-
function updateRememberedForegroundChild(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; index: number; result: SingleResult; events: IntercomEventBus }): void {
|
|
564
|
+
function updateRememberedForegroundChild(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; index: number; result: SingleResult; events: IntercomEventBus; notify?: boolean }): void {
|
|
495
565
|
state.foregroundRuns ??= new Map();
|
|
496
566
|
const updatedAt = Date.now();
|
|
497
567
|
let run = state.foregroundRuns.get(input.runId);
|
|
@@ -500,7 +570,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
500
570
|
state.foregroundRuns.set(input.runId, run);
|
|
501
571
|
}
|
|
502
572
|
run.updatedAt = updatedAt;
|
|
503
|
-
const terminalStatus = resolveSubagentResultStatus({
|
|
573
|
+
const terminalStatus = resolveSubagentResultStatus(omitUndefinedProperties({
|
|
504
574
|
exitCode: input.result.exitCode,
|
|
505
575
|
...(input.result.acceptance?.status === "rejected" ? { success: false } : {}),
|
|
506
576
|
interrupted: input.result.interrupted,
|
|
@@ -509,9 +579,9 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
509
579
|
timedOut: input.result.timedOut,
|
|
510
580
|
stopped: input.result.stopped,
|
|
511
581
|
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
512
|
-
});
|
|
582
|
+
}));
|
|
513
583
|
const child = run.children[input.index] ?? { agent: input.result.agent, index: input.index, status: "detached" as const };
|
|
514
|
-
run.children[input.index] = {
|
|
584
|
+
run.children[input.index] = omitUndefinedProperties({
|
|
515
585
|
...child,
|
|
516
586
|
agent: input.result.agent,
|
|
517
587
|
index: input.index,
|
|
@@ -539,7 +609,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
539
609
|
...(input.result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: input.result.runtimeAcknowledgedExtensions } : {}),
|
|
540
610
|
...(input.result.capabilityCeiling ? { capabilityCeiling: input.result.capabilityCeiling } : {}),
|
|
541
611
|
...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
|
|
542
|
-
};
|
|
612
|
+
});
|
|
543
613
|
trimRememberedForegroundRuns(state);
|
|
544
614
|
const output = getSingleResultOutput(input.result).trim();
|
|
545
615
|
const success = terminalStatus === "completed";
|
|
@@ -548,7 +618,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
548
618
|
: output || input.result.error || "Detached child exited without final output.";
|
|
549
619
|
// A detached callback may outlive its extension runtime. Stale sessions are
|
|
550
620
|
// intentionally dropped rather than routed through a replacement runtime.
|
|
551
|
-
if (!input.sessionId || input.sessionId !== state.currentSessionId) return;
|
|
621
|
+
if (input.notify === false || !input.sessionId || input.sessionId !== state.currentSessionId) return;
|
|
552
622
|
input.events.emit(SUBAGENT_FOREGROUND_COMPLETE_EVENT, {
|
|
553
623
|
id: `${input.runId}:${input.index}`,
|
|
554
624
|
runId: input.runId,
|
|
@@ -572,7 +642,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
572
642
|
});
|
|
573
643
|
}
|
|
574
644
|
|
|
575
|
-
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode:
|
|
645
|
+
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
|
|
576
646
|
const requested = (params.id ?? params.runId)?.trim();
|
|
577
647
|
if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
|
|
578
648
|
const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId === state.currentSessionId);
|
|
@@ -617,6 +687,9 @@ type NestedResumeSourceTarget = {
|
|
|
617
687
|
index: number;
|
|
618
688
|
cwd?: string;
|
|
619
689
|
sessionFile: string;
|
|
690
|
+
model?: string;
|
|
691
|
+
thinking?: AgentConfig["thinking"];
|
|
692
|
+
launchContractDigest?: string;
|
|
620
693
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
621
694
|
};
|
|
622
695
|
type ResumeSourceTarget = AsyncResumeSourceTarget | ForegroundResumeSourceTarget | NestedResumeSourceTarget;
|
|
@@ -658,10 +731,10 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, o
|
|
|
658
731
|
try {
|
|
659
732
|
asyncTarget = {
|
|
660
733
|
source: "async",
|
|
661
|
-
...resolveAsyncResumeTarget(params, {}, {
|
|
734
|
+
...resolveAsyncResumeTarget(params, {}, compactOptional<NonNullable<Parameters<typeof resolveAsyncResumeTarget>[2]>>({
|
|
662
735
|
requireSessionFile: options.asyncRequireSessionFile,
|
|
663
736
|
sessionId: state.currentSessionId ?? undefined,
|
|
664
|
-
}),
|
|
737
|
+
})),
|
|
665
738
|
};
|
|
666
739
|
} catch (error) {
|
|
667
740
|
asyncError = error;
|
|
@@ -753,7 +826,7 @@ function interruptAsyncRun(
|
|
|
753
826
|
): AgentToolResult<Details> | null {
|
|
754
827
|
const target = getAsyncInterruptTarget(state, runId, location);
|
|
755
828
|
if (!target) return null;
|
|
756
|
-
const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
|
|
829
|
+
const status = reconcileAsyncRun(target.asyncDir, omitUndefinedProperties({ kill })).status;
|
|
757
830
|
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
758
831
|
return {
|
|
759
832
|
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId ?? "current"}'.` }],
|
|
@@ -761,11 +834,19 @@ function interruptAsyncRun(
|
|
|
761
834
|
details: { mode: "management", results: [] },
|
|
762
835
|
};
|
|
763
836
|
}
|
|
837
|
+
const activeSteps = status.steps?.filter((step) => step.status === "running") ?? [];
|
|
838
|
+
if (activeSteps.length > 0 && activeSteps.every((step) => step.runner?.type === "external-cli")) {
|
|
839
|
+
return {
|
|
840
|
+
content: [{ type: "text", text: `Interrupt is unsupported for one-shot external CLI async run ${target.asyncId}; use stop instead.` }],
|
|
841
|
+
isError: true,
|
|
842
|
+
details: { mode: "management", results: [] },
|
|
843
|
+
};
|
|
844
|
+
}
|
|
764
845
|
try {
|
|
765
|
-
deliverInterruptRequest({ asyncDir: target.asyncDir, pid: status.pid, kill, source: "interrupt-action" });
|
|
846
|
+
deliverInterruptRequest(omitUndefinedProperties({ asyncDir: target.asyncDir, pid: status.pid, kill, source: "interrupt-action" }));
|
|
766
847
|
const tracked = state.asyncJobs.get(target.asyncId);
|
|
767
848
|
if (tracked) {
|
|
768
|
-
tracked.activityState
|
|
849
|
+
delete tracked.activityState;
|
|
769
850
|
tracked.updatedAt = Date.now();
|
|
770
851
|
}
|
|
771
852
|
return {
|
|
@@ -807,14 +888,15 @@ function appendStepToAsyncChain(input: {
|
|
|
807
888
|
details: { mode: "management", results: [] },
|
|
808
889
|
};
|
|
809
890
|
}
|
|
810
|
-
if (!input.params.
|
|
891
|
+
if (!input.params.step) {
|
|
811
892
|
return {
|
|
812
|
-
content: [{ type: "text", text: "action='append-step' requires
|
|
893
|
+
content: [{ type: "text", text: "action='append-step' requires step." }],
|
|
813
894
|
isError: true,
|
|
814
895
|
details: { mode: "management", results: [] },
|
|
815
896
|
};
|
|
816
897
|
}
|
|
817
|
-
const
|
|
898
|
+
const chain = [input.params.step];
|
|
899
|
+
const acceptanceErrors = validateExecutionAcceptance({ ...input.params, chain } as Parameters<typeof validateExecutionAcceptance>[0]);
|
|
818
900
|
if (acceptanceErrors.length > 0) {
|
|
819
901
|
return {
|
|
820
902
|
content: [{ type: "text", text: `Cannot append step: ${acceptanceErrors.join(" ")}` }],
|
|
@@ -825,7 +907,7 @@ function appendStepToAsyncChain(input: {
|
|
|
825
907
|
|
|
826
908
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
827
909
|
try {
|
|
828
|
-
resolved = resolveSubagentRunId(targetRunId, { state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) });
|
|
910
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) }));
|
|
829
911
|
} catch (error) {
|
|
830
912
|
const message = error instanceof Error ? error.message : String(error);
|
|
831
913
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -883,7 +965,7 @@ function appendStepToAsyncChain(input: {
|
|
|
883
965
|
...pendingAppendRequests.flatMap((request) => runnerStepOutputNames(request.steps)),
|
|
884
966
|
]);
|
|
885
967
|
try {
|
|
886
|
-
validateChainOutputBindingsWithContext(
|
|
968
|
+
validateChainOutputBindingsWithContext(chain, omitUndefinedProperties({ maxItems: input.deps.config.chain?.dynamicFanout?.maxItems }), {
|
|
887
969
|
priorOutputNames: reservedOutputNames,
|
|
888
970
|
startStepIndex: status.chainStepCount ?? status.steps?.length ?? 0,
|
|
889
971
|
});
|
|
@@ -903,7 +985,7 @@ function appendStepToAsyncChain(input: {
|
|
|
903
985
|
const chainSkillInput = normalizeSkillInput(input.params.skill);
|
|
904
986
|
const chainSkills = chainSkillInput === false ? [] : (chainSkillInput ?? []);
|
|
905
987
|
const parentModel = input.parentModel;
|
|
906
|
-
const asyncCtx = {
|
|
988
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
907
989
|
pi: input.deps.pi,
|
|
908
990
|
cwd: input.ctx.cwd,
|
|
909
991
|
currentSessionId: resolveCurrentSessionId(input.ctx.sessionManager),
|
|
@@ -912,9 +994,10 @@ function appendStepToAsyncChain(input: {
|
|
|
912
994
|
currentModel: parentModel,
|
|
913
995
|
modelScope: discoveredForAppend.modelScope,
|
|
914
996
|
interactive: input.ctx.hasUI,
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
997
|
+
permissions: input.deps.config.permissions,
|
|
998
|
+
});
|
|
999
|
+
const built = buildAsyncRunnerSteps(resolved.id, compactOptional<Parameters<typeof buildAsyncRunnerSteps>[1]>({
|
|
1000
|
+
chain: wrapChainTasksForFork(chain, contextPolicy),
|
|
918
1001
|
task: input.params.task,
|
|
919
1002
|
resultMode: "chain",
|
|
920
1003
|
agents,
|
|
@@ -929,7 +1012,7 @@ function appendStepToAsyncChain(input: {
|
|
|
929
1012
|
asyncDir: resolved.location.asyncDir,
|
|
930
1013
|
validateOutputBindings: false,
|
|
931
1014
|
capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.currentSessionId)),
|
|
932
|
-
});
|
|
1015
|
+
}));
|
|
933
1016
|
if ("error" in built) {
|
|
934
1017
|
return {
|
|
935
1018
|
content: [{ type: "text", text: built.error }],
|
|
@@ -1024,7 +1107,7 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
|
|
|
1024
1107
|
if (!agent) throw new Error(`Could not determine child agent for nested run '${run.id}'.`);
|
|
1025
1108
|
const state = run.state === "complete" || run.state === "failed" || run.state === "paused" ? run.state : "failed";
|
|
1026
1109
|
const asyncDir = resolveNestedAsyncDir(match.match.rootRunId, run);
|
|
1027
|
-
return {
|
|
1110
|
+
return compactOptional<NestedResumeSourceTarget>({
|
|
1028
1111
|
kind: "revive",
|
|
1029
1112
|
source: "nested",
|
|
1030
1113
|
runId: run.id,
|
|
@@ -1034,7 +1117,7 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
|
|
|
1034
1117
|
cwd: asyncDir ? path.dirname(asyncDir) : undefined,
|
|
1035
1118
|
sessionFile: validateNestedSessionFile(run, trustedSessionRoots),
|
|
1036
1119
|
...(run.capabilityCeiling ? { capabilityCeiling: run.capabilityCeiling } : {}),
|
|
1037
|
-
};
|
|
1120
|
+
});
|
|
1038
1121
|
}
|
|
1039
1122
|
|
|
1040
1123
|
async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind: "nested" }, requestId: string, ignoredFiles: ReadonlySet<string>, timeoutMs = 1_000) {
|
|
@@ -1065,7 +1148,7 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
|
|
|
1065
1148
|
const run = target.match.run;
|
|
1066
1149
|
const asyncDir = resolveNestedAsyncDir(target.match.rootRunId, run);
|
|
1067
1150
|
if (!asyncDir) return undefined;
|
|
1068
|
-
const status = reconcileAsyncRun(asyncDir, { resultsDir: path.join(
|
|
1151
|
+
const status = reconcileAsyncRun(asyncDir, { resultsDir: path.join(DIRS.results, "nested", target.match.rootRunId) }).status;
|
|
1069
1152
|
const pid = typeof status?.pid === "number" && status.pid > 0 ? status.pid : run.pid;
|
|
1070
1153
|
if (!status || status.state !== "running" || typeof pid !== "number" || pid <= 0) return undefined;
|
|
1071
1154
|
try {
|
|
@@ -1081,7 +1164,7 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
|
|
|
1081
1164
|
const run = input.target.match.run;
|
|
1082
1165
|
const asyncDir = resolveNestedAsyncDir(input.target.match.rootRunId, run);
|
|
1083
1166
|
if (!asyncDir) return undefined;
|
|
1084
|
-
const status = reconcileAsyncRun(asyncDir, { resultsDir: path.join(
|
|
1167
|
+
const status = reconcileAsyncRun(asyncDir, { resultsDir: path.join(DIRS.results, "nested", input.target.match.rootRunId) }).status;
|
|
1085
1168
|
if (!status || (status.state !== "running" && status.state !== "queued")) return undefined;
|
|
1086
1169
|
const steps = status.steps ?? [];
|
|
1087
1170
|
if (input.index !== undefined) {
|
|
@@ -1111,7 +1194,7 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
|
|
|
1111
1194
|
const scheduled = { requestId, state: "scheduled" as const, sourceRunId: run.id, targets };
|
|
1112
1195
|
return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
|
|
1113
1196
|
}
|
|
1114
|
-
const waited = await waitForSteeringAction({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal });
|
|
1197
|
+
const waited = await waitForSteeringAction(omitUndefinedProperties({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal }));
|
|
1115
1198
|
const result = waited ?? { requestId, state: "pending" as const, sourceRunId: run.id, targets };
|
|
1116
1199
|
const stateText = result.state === "delivered" ? "delivered" : result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : "pending";
|
|
1117
1200
|
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 } };
|
|
@@ -1123,7 +1206,7 @@ async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "neste
|
|
|
1123
1206
|
if (run.state === "failed") return { content: [{ type: "text", text: `Nested run ${run.id} has failed and cannot be interrupted.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1124
1207
|
if (run.state === "paused") return { content: [{ type: "text", text: `Nested run ${run.id} is already paused.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1125
1208
|
const result = await sendNestedControlRequest(target, "interrupt");
|
|
1126
|
-
if (result) return { content: [{ type: "text", text: result.message }],
|
|
1209
|
+
if (result) return { content: [{ type: "text", text: result.message }], ...(result.ok ? {} : { isError: true }), details: { mode: "management", results: [] } };
|
|
1127
1210
|
const direct = directNestedAsyncInterrupt(target);
|
|
1128
1211
|
if (direct) return direct;
|
|
1129
1212
|
return { content: [{ type: "text", text: `Nested run ${run.id} owner is not reachable and no safe direct async interrupt fallback is available.` }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -1132,7 +1215,7 @@ async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "neste
|
|
|
1132
1215
|
async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string }): Promise<AgentToolResult<Details>> {
|
|
1133
1216
|
const run = input.target.match.run;
|
|
1134
1217
|
const result = await sendNestedControlRequest(input.target, "resume", input.message);
|
|
1135
|
-
if (result) return { content: [{ type: "text", text: result.message }],
|
|
1218
|
+
if (result) return { content: [{ type: "text", text: result.message }], ...(result.ok ? {} : { isError: true }), details: { mode: "management", results: [] } };
|
|
1136
1219
|
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: [] } };
|
|
1137
1220
|
}
|
|
1138
1221
|
|
|
@@ -1144,6 +1227,15 @@ async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "
|
|
|
1144
1227
|
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: [] } };
|
|
1145
1228
|
}
|
|
1146
1229
|
|
|
1230
|
+
function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
|
|
1231
|
+
const status = readStatus(asyncDir);
|
|
1232
|
+
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli")) return undefined;
|
|
1233
|
+
const message = action === "steer"
|
|
1234
|
+
? "One-shot external CLI runners do not accept live steer messages."
|
|
1235
|
+
: "One-shot external CLI runners do not persist sessions and cannot be resumed.";
|
|
1236
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1147
1239
|
async function resumeAsyncRun(input: {
|
|
1148
1240
|
params: SubagentParamsLike;
|
|
1149
1241
|
requestCwd: string;
|
|
@@ -1168,7 +1260,7 @@ async function resumeAsyncRun(input: {
|
|
|
1168
1260
|
details: { mode: "management", results: [] },
|
|
1169
1261
|
};
|
|
1170
1262
|
}
|
|
1171
|
-
const acceptanceErrors = validateExecutionAcceptance(input.params);
|
|
1263
|
+
const acceptanceErrors = validateExecutionAcceptance(input.params as Parameters<typeof validateExecutionAcceptance>[0]);
|
|
1172
1264
|
if (acceptanceErrors.length > 0) {
|
|
1173
1265
|
return {
|
|
1174
1266
|
content: [{ type: "text", text: `Cannot resume: ${acceptanceErrors.join(" ")}` }],
|
|
@@ -1184,7 +1276,7 @@ async function resumeAsyncRun(input: {
|
|
|
1184
1276
|
const requestedId = input.params.id ?? input.params.runId;
|
|
1185
1277
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
1186
1278
|
try {
|
|
1187
|
-
resolved = requestedId ? resolveSubagentRunId(requestedId, { state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) }) : undefined;
|
|
1279
|
+
resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) })) : undefined;
|
|
1188
1280
|
} catch (error) {
|
|
1189
1281
|
const message = error instanceof Error ? error.message : "";
|
|
1190
1282
|
const asyncMatches = message.match(/async:/g)?.length ?? 0;
|
|
@@ -1207,6 +1299,10 @@ async function resumeAsyncRun(input: {
|
|
|
1207
1299
|
];
|
|
1208
1300
|
target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
1209
1301
|
} else {
|
|
1302
|
+
if (resolved?.kind === "async" && resolved.location.asyncDir) {
|
|
1303
|
+
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "resume");
|
|
1304
|
+
if (unsupported) return unsupported;
|
|
1305
|
+
}
|
|
1210
1306
|
target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: !attachChain });
|
|
1211
1307
|
}
|
|
1212
1308
|
} catch (error) {
|
|
@@ -1288,7 +1384,7 @@ async function resumeAsyncRun(input: {
|
|
|
1288
1384
|
};
|
|
1289
1385
|
}
|
|
1290
1386
|
const runId = randomUUID().slice(0, 8);
|
|
1291
|
-
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
|
|
1387
|
+
const artifactConfig: ArtifactConfig = omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1292
1388
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1293
1389
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
1294
1390
|
const workflowTask = (input.params.task ?? followUp) || undefined;
|
|
@@ -1296,20 +1392,20 @@ async function resumeAsyncRun(input: {
|
|
|
1296
1392
|
const chain = wrapChainTasksForFork(attachChain, contextPolicy);
|
|
1297
1393
|
const normalized = normalizeSkillInput(input.params.skill);
|
|
1298
1394
|
const parentModel = input.parentModel;
|
|
1299
|
-
const result = executeAsyncChain(runId, {
|
|
1395
|
+
const result = executeAsyncChain(runId, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
1300
1396
|
chain,
|
|
1301
1397
|
task: workflowTask,
|
|
1302
1398
|
goal,
|
|
1303
1399
|
attachRoot: {
|
|
1304
1400
|
runId: target.runId,
|
|
1305
|
-
asyncDir: target.asyncDir ?? path.join(
|
|
1306
|
-
resultPath: resolveAsyncRootResultPath(
|
|
1401
|
+
asyncDir: target.asyncDir ?? path.join(DIRS.async, target.runId),
|
|
1402
|
+
resultPath: resolveAsyncRootResultPath(DIRS.results, target.runId),
|
|
1307
1403
|
index: target.index,
|
|
1308
1404
|
agent: target.agent,
|
|
1309
1405
|
label: `Attached ${target.runId}`,
|
|
1310
1406
|
},
|
|
1311
1407
|
agents,
|
|
1312
|
-
ctx: {
|
|
1408
|
+
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1313
1409
|
pi: input.deps.pi,
|
|
1314
1410
|
cwd: input.requestCwd,
|
|
1315
1411
|
currentSessionId: input.deps.state.currentSessionId,
|
|
@@ -1318,7 +1414,8 @@ async function resumeAsyncRun(input: {
|
|
|
1318
1414
|
currentModel: parentModel,
|
|
1319
1415
|
modelScope,
|
|
1320
1416
|
interactive: input.ctx.hasUI,
|
|
1321
|
-
|
|
1417
|
+
permissions: input.deps.config.permissions,
|
|
1418
|
+
}),
|
|
1322
1419
|
availableModels,
|
|
1323
1420
|
cwd: effectiveCwd,
|
|
1324
1421
|
maxOutput: input.params.maxOutput,
|
|
@@ -1339,7 +1436,7 @@ async function resumeAsyncRun(input: {
|
|
|
1339
1436
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1340
1437
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1341
1438
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1342
|
-
});
|
|
1439
|
+
}));
|
|
1343
1440
|
if (result.isError) return result;
|
|
1344
1441
|
const attachedId = result.details.asyncId ?? runId;
|
|
1345
1442
|
const lines = [
|
|
@@ -1352,18 +1449,22 @@ async function resumeAsyncRun(input: {
|
|
|
1352
1449
|
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
|
|
1353
1450
|
}
|
|
1354
1451
|
|
|
1452
|
+
const revivalSessionFile = target.sessionFile;
|
|
1453
|
+
if (!revivalSessionFile) {
|
|
1454
|
+
return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1455
|
+
}
|
|
1355
1456
|
const runId = randomUUID().slice(0, 8);
|
|
1356
1457
|
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
|
|
1357
|
-
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
|
|
1458
|
+
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1358
1459
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1359
1460
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1360
1461
|
const parentModel = input.parentModel;
|
|
1361
|
-
const result = executeAsyncSingle(runId, {
|
|
1462
|
+
const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
1362
1463
|
agent: target.agent,
|
|
1363
|
-
task: buildRevivedAsyncTask(target, followUp),
|
|
1464
|
+
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], followUp),
|
|
1364
1465
|
goal: followUp,
|
|
1365
1466
|
agentConfig: recoveryAgentConfig,
|
|
1366
|
-
ctx: {
|
|
1467
|
+
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1367
1468
|
pi: input.deps.pi,
|
|
1368
1469
|
cwd: input.requestCwd,
|
|
1369
1470
|
currentSessionId: input.deps.state.currentSessionId,
|
|
@@ -1372,17 +1473,18 @@ async function resumeAsyncRun(input: {
|
|
|
1372
1473
|
currentModel: parentModel,
|
|
1373
1474
|
modelScope,
|
|
1374
1475
|
interactive: input.ctx.hasUI,
|
|
1375
|
-
|
|
1476
|
+
permissions: input.deps.config.permissions,
|
|
1477
|
+
}),
|
|
1376
1478
|
cwd: effectiveCwd,
|
|
1377
1479
|
maxOutput: input.params.maxOutput ?? recoveryDescriptor?.maxOutput,
|
|
1378
1480
|
artifactsDir,
|
|
1379
1481
|
artifactConfig,
|
|
1380
1482
|
shareEnabled: recoveryDescriptor?.share ?? input.params.share === true,
|
|
1381
|
-
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
1483
|
+
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile ?? revivalSessionFile),
|
|
1382
1484
|
...(recoveryDescriptor?.sessionDir ? { sessionDir: recoveryDescriptor.sessionDir } : {}),
|
|
1383
|
-
sessionFile:
|
|
1485
|
+
sessionFile: revivalSessionFile,
|
|
1384
1486
|
revivalLease: {
|
|
1385
|
-
sessionFile:
|
|
1487
|
+
sessionFile: revivalSessionFile,
|
|
1386
1488
|
runId,
|
|
1387
1489
|
sourceRunId: target.runId,
|
|
1388
1490
|
...(input.deps.state.currentSessionId ? { parentSessionId: input.deps.state.currentSessionId } : {}),
|
|
@@ -1410,7 +1512,7 @@ async function resumeAsyncRun(input: {
|
|
|
1410
1512
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1411
1513
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1412
1514
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1413
|
-
});
|
|
1515
|
+
}));
|
|
1414
1516
|
if (result.isError) return result;
|
|
1415
1517
|
|
|
1416
1518
|
const revivedId = result.details.asyncId ?? runId;
|
|
@@ -1467,6 +1569,25 @@ function createForegroundControlNotifier(data: Pick<ExecutionContextData, "contr
|
|
|
1467
1569
|
};
|
|
1468
1570
|
}
|
|
1469
1571
|
|
|
1572
|
+
export function foregroundResultIntercomStatus(result: SingleResult): ReturnType<typeof resolveSubagentResultStatus> {
|
|
1573
|
+
return resolveSubagentResultStatus(omitUndefinedProperties({
|
|
1574
|
+
exitCode: result.exitCode,
|
|
1575
|
+
...(result.acceptance?.status === "rejected" ? { success: false } : {}),
|
|
1576
|
+
interrupted: result.interrupted,
|
|
1577
|
+
detached: result.detached,
|
|
1578
|
+
processSignal: result.processSignal,
|
|
1579
|
+
timedOut: result.timedOut,
|
|
1580
|
+
stopped: result.stopped,
|
|
1581
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
1582
|
+
}));
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
export function shouldSuppressRoutineResultIntercom(input: { suppressRoutineResultIntercom?: boolean; results: SingleResult[] }): boolean {
|
|
1586
|
+
return input.suppressRoutineResultIntercom === true
|
|
1587
|
+
&& input.results.length > 0
|
|
1588
|
+
&& input.results.every((result) => foregroundResultIntercomStatus(result) === "completed");
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1470
1591
|
async function emitForegroundResultIntercom(input: {
|
|
1471
1592
|
pi: ExtensionAPI;
|
|
1472
1593
|
intercomBridge: IntercomBridgeState;
|
|
@@ -1478,24 +1599,16 @@ async function emitForegroundResultIntercom(input: {
|
|
|
1478
1599
|
parallelHandoff?: Details["parallelHandoff"];
|
|
1479
1600
|
}): Promise<ReturnType<typeof buildSubagentResultIntercomPayload> | null> {
|
|
1480
1601
|
if (!input.intercomBridge.active || !input.intercomBridge.resultDelivery || !input.intercomBridge.orchestratorTarget) return null;
|
|
1481
|
-
const children = input.results.flatMap((result, index) => result.detached ? [] : [{
|
|
1602
|
+
const children = input.results.flatMap((result, index) => result.detached ? [] : [omitUndefinedProperties({
|
|
1482
1603
|
agent: result.agent,
|
|
1483
|
-
status:
|
|
1484
|
-
exitCode: result.exitCode,
|
|
1485
|
-
interrupted: result.interrupted,
|
|
1486
|
-
detached: result.detached,
|
|
1487
|
-
processSignal: result.processSignal,
|
|
1488
|
-
timedOut: result.timedOut,
|
|
1489
|
-
stopped: result.stopped,
|
|
1490
|
-
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
1491
|
-
}),
|
|
1604
|
+
status: foregroundResultIntercomStatus(result),
|
|
1492
1605
|
outputState: result.outputState ?? "unknown",
|
|
1493
1606
|
summary: resultSummaryForIntercom(result),
|
|
1494
1607
|
index,
|
|
1495
1608
|
artifactPath: result.artifactPaths?.outputPath,
|
|
1496
1609
|
sessionPath: result.sessionFile,
|
|
1497
1610
|
intercomTarget: resolveSubagentIntercomTarget(input.runId, result.agent, index),
|
|
1498
|
-
}]);
|
|
1611
|
+
})]);
|
|
1499
1612
|
if (children.length === 0) return null;
|
|
1500
1613
|
const payload = buildSubagentResultIntercomPayload({
|
|
1501
1614
|
to: input.intercomBridge.orchestratorTarget,
|
|
@@ -1551,7 +1664,7 @@ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentCo
|
|
|
1551
1664
|
if (params.agent) {
|
|
1552
1665
|
const result = resolve(params.agent);
|
|
1553
1666
|
if (result.error) return { error: result.error };
|
|
1554
|
-
params = { ...params, agent: result.name };
|
|
1667
|
+
params = omitUndefinedProperties({ ...params, agent: result.name });
|
|
1555
1668
|
}
|
|
1556
1669
|
if (params.tasks) {
|
|
1557
1670
|
const tasks: TaskParam[] = [];
|
|
@@ -1568,7 +1681,7 @@ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentCo
|
|
|
1568
1681
|
for (let index = 0; index < params.chain.length; index++) {
|
|
1569
1682
|
const step = params.chain[index]!;
|
|
1570
1683
|
if (isParallelStep(step)) {
|
|
1571
|
-
const parallel:
|
|
1684
|
+
const parallel: ParallelTaskItem[] = [];
|
|
1572
1685
|
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
1573
1686
|
const task = step.parallel[taskIndex]!;
|
|
1574
1687
|
const result = resolve(task.agent, `step ${index + 1}, task ${taskIndex + 1}`);
|
|
@@ -1618,7 +1731,7 @@ function validateExecutionInput(
|
|
|
1618
1731
|
};
|
|
1619
1732
|
}
|
|
1620
1733
|
|
|
1621
|
-
const acceptanceErrors = validateExecutionAcceptance(params);
|
|
1734
|
+
const acceptanceErrors = validateExecutionAcceptance(params as Parameters<typeof validateExecutionAcceptance>[0]);
|
|
1622
1735
|
if (acceptanceErrors.length > 0) {
|
|
1623
1736
|
return {
|
|
1624
1737
|
content: [{ type: "text", text: acceptanceErrors.join(" ") }],
|
|
@@ -1707,7 +1820,7 @@ function validateExecutionInput(
|
|
|
1707
1820
|
function validateExecutionChainBindings(params: SubagentParamsLike, dynamicFanoutMaxItems?: number): AgentToolResult<Details> | null {
|
|
1708
1821
|
if ((params.chain?.length ?? 0) === 0) return null;
|
|
1709
1822
|
try {
|
|
1710
|
-
validateChainOutputBindingsWithContext(params.chain as ChainStep[], { maxItems: dynamicFanoutMaxItems });
|
|
1823
|
+
validateChainOutputBindingsWithContext(params.chain as ChainStep[], dynamicFanoutMaxItems === undefined ? {} : { maxItems: dynamicFanoutMaxItems });
|
|
1711
1824
|
} catch (error) {
|
|
1712
1825
|
if (error instanceof ChainOutputValidationError) {
|
|
1713
1826
|
return {
|
|
@@ -1722,6 +1835,7 @@ function validateExecutionChainBindings(params: SubagentParamsLike, dynamicFanou
|
|
|
1722
1835
|
}
|
|
1723
1836
|
|
|
1724
1837
|
function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
1838
|
+
if (params.workflowScript !== undefined) return "workflow";
|
|
1725
1839
|
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
1726
1840
|
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
1727
1841
|
if (params.agent) return "single";
|
|
@@ -1745,12 +1859,12 @@ function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: Ag
|
|
|
1745
1859
|
const requestedAgentNames = collectRequestedAgentNames(params);
|
|
1746
1860
|
const contextSummary = summarizeContextModes(requestedAgentNames.map((name) => contextForAgent(name)));
|
|
1747
1861
|
const usesFork = contextSummary === "fork" || contextSummary === "mixed";
|
|
1748
|
-
return {
|
|
1862
|
+
return omitUndefinedProperties({
|
|
1749
1863
|
params,
|
|
1750
1864
|
contextForAgent,
|
|
1751
1865
|
contextSummary,
|
|
1752
1866
|
usesFork,
|
|
1753
|
-
};
|
|
1867
|
+
});
|
|
1754
1868
|
}
|
|
1755
1869
|
|
|
1756
1870
|
function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultContextPolicy {
|
|
@@ -1826,7 +1940,8 @@ function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?:
|
|
|
1826
1940
|
if (rawTimeout !== undefined && rawMaxRuntime !== undefined && rawTimeout !== rawMaxRuntime) {
|
|
1827
1941
|
return { error: "timeoutMs and maxRuntimeMs are aliases; provide only one value or use the same value for both." };
|
|
1828
1942
|
}
|
|
1829
|
-
|
|
1943
|
+
const timeoutMs = rawTimeout ?? rawMaxRuntime;
|
|
1944
|
+
return timeoutMs === undefined ? {} : { timeoutMs };
|
|
1830
1945
|
}
|
|
1831
1946
|
|
|
1832
1947
|
function resolveToolBudget(
|
|
@@ -1835,7 +1950,7 @@ function resolveToolBudget(
|
|
|
1835
1950
|
options?: { minimumHard?: 0 | 1 },
|
|
1836
1951
|
): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
1837
1952
|
const resolved = validateToolBudgetConfig(raw, label, options);
|
|
1838
|
-
return { toolBudget: resolved.budget, error: resolved.error };
|
|
1953
|
+
return { ...(resolved.budget === undefined ? {} : { toolBudget: resolved.budget }), ...(resolved.error === undefined ? {} : { error: resolved.error }) };
|
|
1839
1954
|
}
|
|
1840
1955
|
|
|
1841
1956
|
function resolveEffectiveToolBudget(input: { stepBudget?: ToolBudgetConfig; runBudget?: ResolvedToolBudget; agentBudget?: ToolBudgetConfig; configBudget?: ToolBudgetConfig }): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
@@ -1869,7 +1984,7 @@ function expandChainParallelCounts(chain: ChainStep[]): { chain?: ChainStep[]; e
|
|
|
1869
1984
|
expandedChain.push(step);
|
|
1870
1985
|
continue;
|
|
1871
1986
|
}
|
|
1872
|
-
const expandedParallel = [];
|
|
1987
|
+
const expandedParallel: ParallelTaskItem[] = [];
|
|
1873
1988
|
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
1874
1989
|
const task = step.parallel[taskIndex]!;
|
|
1875
1990
|
const rawCount = (task as typeof task & { count?: unknown }).count;
|
|
@@ -1892,14 +2007,14 @@ function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?:
|
|
|
1892
2007
|
if (expandedTasks.error) {
|
|
1893
2008
|
return { error: buildRequestedModeError(params, expandedTasks.error) };
|
|
1894
2009
|
}
|
|
1895
|
-
return { params: { ...params, tasks: expandedTasks.tasks } };
|
|
2010
|
+
return { params: { ...params, ...(expandedTasks.tasks === undefined ? {} : { tasks: expandedTasks.tasks }) } };
|
|
1896
2011
|
}
|
|
1897
2012
|
if (params.chain) {
|
|
1898
2013
|
const expandedChain = expandChainParallelCounts(params.chain);
|
|
1899
2014
|
if (expandedChain.error) {
|
|
1900
2015
|
return { error: buildRequestedModeError(params, expandedChain.error) };
|
|
1901
2016
|
}
|
|
1902
|
-
return { params: { ...params, chain: expandedChain.chain } };
|
|
2017
|
+
return { params: { ...params, ...(expandedChain.chain === undefined ? {} : { chain: expandedChain.chain }) } };
|
|
1903
2018
|
}
|
|
1904
2019
|
return { params };
|
|
1905
2020
|
}
|
|
@@ -2005,6 +2120,87 @@ function collectChainThinkingOverrides(
|
|
|
2005
2120
|
return thinkingOverrides;
|
|
2006
2121
|
}
|
|
2007
2122
|
|
|
2123
|
+
type StaticLaunchSummary = { agent: string; model?: string; thinking?: string };
|
|
2124
|
+
|
|
2125
|
+
function resolveStaticLaunchSummary(input: {
|
|
2126
|
+
agent: string;
|
|
2127
|
+
index: number;
|
|
2128
|
+
explicitModel?: string;
|
|
2129
|
+
agents: AgentConfig[];
|
|
2130
|
+
parentModel?: ParentModel;
|
|
2131
|
+
availableModels: ModelInfo[];
|
|
2132
|
+
currentProvider?: string;
|
|
2133
|
+
modelScope?: ModelScopeConfig;
|
|
2134
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2135
|
+
}): StaticLaunchSummary {
|
|
2136
|
+
const agentConfig = input.agents.find((agent) => agent.name === input.agent);
|
|
2137
|
+
const model = resolveEffectiveSubagentModel(
|
|
2138
|
+
input.explicitModel,
|
|
2139
|
+
agentConfig?.model,
|
|
2140
|
+
input.parentModel,
|
|
2141
|
+
input.availableModels,
|
|
2142
|
+
input.currentProvider,
|
|
2143
|
+
input.modelScope === undefined ? {} : { scope: input.modelScope },
|
|
2144
|
+
);
|
|
2145
|
+
const thinkingOverride = input.thinkingOverrideForTask(input.agent, input.index, model);
|
|
2146
|
+
const thinking = resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
|
|
2147
|
+
return {
|
|
2148
|
+
agent: input.agent,
|
|
2149
|
+
...(model ? { model } : {}),
|
|
2150
|
+
...(thinking ? { thinking } : {}),
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
function collectStaticLaunchSummaries(input: {
|
|
2155
|
+
params: SubagentParamsLike;
|
|
2156
|
+
agents: AgentConfig[];
|
|
2157
|
+
parentModel?: ParentModel;
|
|
2158
|
+
availableModels: ModelInfo[];
|
|
2159
|
+
currentProvider?: string;
|
|
2160
|
+
modelScope?: ModelScopeConfig;
|
|
2161
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2162
|
+
dynamicFanoutMaxItems?: number;
|
|
2163
|
+
}): StaticLaunchSummary[] {
|
|
2164
|
+
const summary = (agent: string, index: number, explicitModel?: string) => resolveStaticLaunchSummary({
|
|
2165
|
+
agent,
|
|
2166
|
+
index,
|
|
2167
|
+
explicitModel,
|
|
2168
|
+
agents: input.agents,
|
|
2169
|
+
parentModel: input.parentModel,
|
|
2170
|
+
availableModels: input.availableModels,
|
|
2171
|
+
currentProvider: input.currentProvider,
|
|
2172
|
+
modelScope: input.modelScope,
|
|
2173
|
+
thinkingOverrideForTask: input.thinkingOverrideForTask,
|
|
2174
|
+
});
|
|
2175
|
+
if (input.params.tasks) return input.params.tasks.map((task, index) => summary(task.agent, index, task.model));
|
|
2176
|
+
if (input.params.chain?.length) {
|
|
2177
|
+
const launches: StaticLaunchSummary[] = [];
|
|
2178
|
+
let flatIndex = 0;
|
|
2179
|
+
for (const step of input.params.chain) {
|
|
2180
|
+
if (isParallelStep(step)) {
|
|
2181
|
+
for (const task of step.parallel) {
|
|
2182
|
+
launches.push(summary(task.agent, flatIndex, task.model));
|
|
2183
|
+
flatIndex++;
|
|
2184
|
+
}
|
|
2185
|
+
continue;
|
|
2186
|
+
}
|
|
2187
|
+
if (isDynamicParallelStep(step)) {
|
|
2188
|
+
const maxItems = step.expand.maxItems ?? input.dynamicFanoutMaxItems ?? 0;
|
|
2189
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
2190
|
+
launches.push(summary(step.parallel.agent, flatIndex, step.parallel.model));
|
|
2191
|
+
flatIndex++;
|
|
2192
|
+
}
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
const sequential = step as SequentialStep;
|
|
2196
|
+
launches.push(summary(sequential.agent, flatIndex, sequential.model));
|
|
2197
|
+
flatIndex++;
|
|
2198
|
+
}
|
|
2199
|
+
return launches;
|
|
2200
|
+
}
|
|
2201
|
+
return input.params.agent ? [summary(input.params.agent, 0, input.params.model as string | undefined)] : [];
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2008
2204
|
function firstChainAgent(chain: ChainStep[]): string | undefined {
|
|
2009
2205
|
const first = chain[0];
|
|
2010
2206
|
if (!first) return undefined;
|
|
@@ -2032,34 +2228,34 @@ function resolveAsyncEventGoal(workflowTask: string | undefined, rawChain: Chain
|
|
|
2032
2228
|
function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
|
|
2033
2229
|
return chain.map((step, stepIndex) => {
|
|
2034
2230
|
if (isParallelStep(step)) {
|
|
2035
|
-
return {
|
|
2231
|
+
return compactOptional<ParallelStep>({
|
|
2036
2232
|
...step,
|
|
2037
|
-
parallel: step.parallel.map((task) => ({
|
|
2233
|
+
parallel: step.parallel.map((task) => compactOptional<ParallelTaskItem>({
|
|
2038
2234
|
...task,
|
|
2039
2235
|
task: shouldForkAgent(contextPolicy, task.agent)
|
|
2040
2236
|
? wrapForkTask(task.task ?? "{previous}")
|
|
2041
2237
|
: task.task,
|
|
2042
2238
|
})),
|
|
2043
|
-
};
|
|
2239
|
+
});
|
|
2044
2240
|
}
|
|
2045
2241
|
if (isDynamicParallelStep(step)) {
|
|
2046
|
-
return {
|
|
2242
|
+
return compactOptional<DynamicParallelStep>({
|
|
2047
2243
|
...step,
|
|
2048
|
-
parallel: {
|
|
2244
|
+
parallel: compactOptional<DynamicParallelStep["parallel"]>({
|
|
2049
2245
|
...step.parallel,
|
|
2050
2246
|
task: shouldForkAgent(contextPolicy, step.parallel.agent)
|
|
2051
2247
|
? wrapForkTask(step.parallel.task ?? "{previous}")
|
|
2052
2248
|
: step.parallel.task,
|
|
2053
|
-
},
|
|
2054
|
-
};
|
|
2249
|
+
}),
|
|
2250
|
+
});
|
|
2055
2251
|
}
|
|
2056
2252
|
const sequential = step as SequentialStep;
|
|
2057
|
-
return {
|
|
2253
|
+
return compactOptional<SequentialStep>({
|
|
2058
2254
|
...sequential,
|
|
2059
2255
|
task: shouldForkAgent(contextPolicy, sequential.agent)
|
|
2060
2256
|
? wrapForkTask(sequential.task ?? (stepIndex === 0 ? "{task}" : "{previous}"))
|
|
2061
2257
|
: sequential.task,
|
|
2062
|
-
};
|
|
2258
|
+
});
|
|
2063
2259
|
});
|
|
2064
2260
|
}
|
|
2065
2261
|
|
|
@@ -2159,16 +2355,17 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2159
2355
|
}
|
|
2160
2356
|
const id = randomUUID();
|
|
2161
2357
|
const parentModel = data.parentModel;
|
|
2162
|
-
const asyncCtx = {
|
|
2358
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2163
2359
|
pi: deps.pi,
|
|
2164
2360
|
cwd: ctx.cwd,
|
|
2165
|
-
currentSessionId:
|
|
2166
|
-
parentSessionId:
|
|
2361
|
+
currentSessionId: data.parentSessionId!,
|
|
2362
|
+
parentSessionId: data.parentPiSessionId,
|
|
2167
2363
|
currentModelProvider: parentModel?.provider,
|
|
2168
2364
|
currentModel: parentModel,
|
|
2169
2365
|
modelScope: data.modelScope,
|
|
2170
2366
|
interactive: ctx.hasUI,
|
|
2171
|
-
|
|
2367
|
+
permissions: deps.config.permissions,
|
|
2368
|
+
});
|
|
2172
2369
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
2173
2370
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
2174
2371
|
const currentProvider = parentModel?.provider;
|
|
@@ -2177,7 +2374,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2177
2374
|
|
|
2178
2375
|
if (hasTasks && params.tasks) {
|
|
2179
2376
|
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
2180
|
-
const parallelTasks = params.tasks.map((task, index) => ({
|
|
2377
|
+
const parallelTasks = params.tasks.map((task, index) => compactOptional<ParallelTaskItem>({
|
|
2181
2378
|
agent: task.agent,
|
|
2182
2379
|
task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
|
|
2183
2380
|
cwd: task.cwd,
|
|
@@ -2192,12 +2389,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2192
2389
|
...(task.agentContract !== undefined ? { agentContract: task.agentContract } : {}),
|
|
2193
2390
|
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
2194
2391
|
}));
|
|
2195
|
-
return executeAsyncChain(id, {
|
|
2196
|
-
chain: [{
|
|
2392
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2393
|
+
chain: [compactOptional<ParallelStep>({
|
|
2197
2394
|
parallel: parallelTasks,
|
|
2198
2395
|
concurrency: resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency),
|
|
2199
2396
|
worktree: params.worktree,
|
|
2200
|
-
}],
|
|
2397
|
+
})],
|
|
2201
2398
|
resultMode: "parallel",
|
|
2202
2399
|
goal: params.tasks[0]?.task ?? "",
|
|
2203
2400
|
agents,
|
|
@@ -2230,7 +2427,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2230
2427
|
configToolBudget: data.configToolBudget,
|
|
2231
2428
|
capabilityCeiling: data.capabilityCeiling,
|
|
2232
2429
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2233
|
-
|
|
2430
|
+
parentWorkflowRunId: params.workflowParentRunId,
|
|
2431
|
+
workflowKey: params.workflowKey,
|
|
2432
|
+
}));
|
|
2234
2433
|
}
|
|
2235
2434
|
|
|
2236
2435
|
if (hasChain && params.chain) {
|
|
@@ -2238,7 +2437,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2238
2437
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
2239
2438
|
const rawChain = params.chain as ChainStep[];
|
|
2240
2439
|
const chain = wrapChainTasksForFork(rawChain, contextPolicy);
|
|
2241
|
-
return executeAsyncChain(id, {
|
|
2440
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2242
2441
|
chain,
|
|
2243
2442
|
task: params.task,
|
|
2244
2443
|
goal: resolveAsyncEventGoal(params.task, rawChain),
|
|
@@ -2273,7 +2472,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2273
2472
|
configToolBudget: data.configToolBudget,
|
|
2274
2473
|
capabilityCeiling: data.capabilityCeiling,
|
|
2275
2474
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2276
|
-
|
|
2475
|
+
parentWorkflowRunId: params.workflowParentRunId,
|
|
2476
|
+
workflowKey: params.workflowKey,
|
|
2477
|
+
}));
|
|
2277
2478
|
}
|
|
2278
2479
|
|
|
2279
2480
|
if (hasSingle) {
|
|
@@ -2291,8 +2492,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2291
2492
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
2292
2493
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
2293
2494
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
2294
|
-
const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
|
|
2295
|
-
return executeAsyncSingle(id, {
|
|
2495
|
+
const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2496
|
+
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2296
2497
|
agent: params.agent!,
|
|
2297
2498
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
2298
2499
|
goal: params.task ?? "",
|
|
@@ -2331,7 +2532,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2331
2532
|
usageBudget: data.usageBudget,
|
|
2332
2533
|
configToolBudget: data.configToolBudget,
|
|
2333
2534
|
capabilityCeiling: data.capabilityCeiling,
|
|
2334
|
-
|
|
2535
|
+
parentWorkflowRunId: params.workflowParentRunId,
|
|
2536
|
+
workflowKey: params.workflowKey,
|
|
2537
|
+
}));
|
|
2335
2538
|
}
|
|
2336
2539
|
|
|
2337
2540
|
return null;
|
|
@@ -2364,8 +2567,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2364
2567
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
2365
2568
|
const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
|
|
2366
2569
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
2367
|
-
const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : { ...ctx, model: data.parentModel };
|
|
2368
|
-
const chainResult = await executeChain({
|
|
2570
|
+
const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : ({ ...ctx, model: data.parentModel } as ExtensionContext);
|
|
2571
|
+
const chainResult = await executeChain(compactOptional<Parameters<typeof executeChain>[0]>({
|
|
2369
2572
|
chain,
|
|
2370
2573
|
task: params.task,
|
|
2371
2574
|
agents,
|
|
@@ -2394,7 +2597,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2394
2597
|
foregroundControl,
|
|
2395
2598
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
2396
2599
|
chainSkills,
|
|
2397
|
-
chainDir: params.chainDir ??
|
|
2600
|
+
chainDir: params.chainDir ?? getChainRunsDir(effectiveCwd, artifactConfig.dir),
|
|
2398
2601
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2399
2602
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2400
2603
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -2416,9 +2619,10 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2416
2619
|
toolBudget: data.toolBudget,
|
|
2417
2620
|
usageBudget: data.usageBudget,
|
|
2418
2621
|
configToolBudget: data.configToolBudget,
|
|
2622
|
+
permissions: deps.config.permissions,
|
|
2419
2623
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2420
2624
|
capabilityCeiling: data.capabilityCeiling,
|
|
2421
|
-
});
|
|
2625
|
+
}));
|
|
2422
2626
|
|
|
2423
2627
|
if (chainResult.requestedAsync) {
|
|
2424
2628
|
if (!isAsyncAvailable()) {
|
|
@@ -2430,20 +2634,21 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2430
2634
|
}
|
|
2431
2635
|
const id = randomUUID();
|
|
2432
2636
|
const parentModel = data.parentModel;
|
|
2433
|
-
const asyncCtx = {
|
|
2637
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2434
2638
|
pi: deps.pi,
|
|
2435
2639
|
cwd: ctx.cwd,
|
|
2436
|
-
currentSessionId:
|
|
2437
|
-
parentSessionId:
|
|
2640
|
+
currentSessionId: data.parentSessionId!,
|
|
2641
|
+
parentSessionId: data.parentPiSessionId,
|
|
2438
2642
|
currentModelProvider: parentModel?.provider,
|
|
2439
2643
|
currentModel: parentModel,
|
|
2440
2644
|
modelScope: data.modelScope,
|
|
2441
2645
|
interactive: ctx.hasUI,
|
|
2442
|
-
|
|
2646
|
+
permissions: deps.config.permissions,
|
|
2647
|
+
});
|
|
2443
2648
|
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
2444
2649
|
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
2445
2650
|
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
2446
|
-
return executeAsyncChain(id, {
|
|
2651
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2447
2652
|
chain: asyncChain,
|
|
2448
2653
|
task: params.task,
|
|
2449
2654
|
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
@@ -2478,15 +2683,17 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2478
2683
|
configToolBudget: data.configToolBudget,
|
|
2479
2684
|
capabilityCeiling: data.capabilityCeiling,
|
|
2480
2685
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2481
|
-
});
|
|
2686
|
+
}));
|
|
2482
2687
|
}
|
|
2483
2688
|
|
|
2484
|
-
const rawChainDetails = chainResult.details ? { ...chainResult.details, runId, timeoutMs: data.timeoutMs } : undefined;
|
|
2689
|
+
const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
|
|
2485
2690
|
if (foregroundControl && rawChainDetails) {
|
|
2486
2691
|
updateForegroundNestedProjection(foregroundControl);
|
|
2487
2692
|
attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
|
|
2488
2693
|
rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
|
|
2489
|
-
|
|
2694
|
+
const usageBudget = usageBudgetState(data.usageBudget, rawChainDetails.totalCost);
|
|
2695
|
+
if (usageBudget === undefined) delete rawChainDetails.usageBudget;
|
|
2696
|
+
else rawChainDetails.usageBudget = usageBudget;
|
|
2490
2697
|
}
|
|
2491
2698
|
const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
|
|
2492
2699
|
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results, checkpoint: chainDetails.checkpoint });
|
|
@@ -2560,6 +2767,7 @@ interface ForegroundParallelRunInput {
|
|
|
2560
2767
|
usageBudget?: UsageBudgetConfig;
|
|
2561
2768
|
toolBudgets: (ResolvedToolBudget | undefined)[];
|
|
2562
2769
|
agentContract?: AgentContract;
|
|
2770
|
+
permissions?: ExtensionConfig["permissions"];
|
|
2563
2771
|
}
|
|
2564
2772
|
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
2565
2773
|
return {
|
|
@@ -2581,13 +2789,13 @@ function createParallelWorktreeSetup(
|
|
|
2581
2789
|
if (!enabled) return {};
|
|
2582
2790
|
try {
|
|
2583
2791
|
return {
|
|
2584
|
-
setup: createWorktrees(cwd, runId, tasks.length, {
|
|
2792
|
+
setup: createWorktrees(cwd, runId, tasks.length, omitUndefinedProperties({
|
|
2585
2793
|
agents: tasks.map((task) => task.agent),
|
|
2586
2794
|
setupHook: setupHook
|
|
2587
|
-
? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
|
|
2795
|
+
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
2588
2796
|
: undefined,
|
|
2589
2797
|
baseDir,
|
|
2590
|
-
}),
|
|
2798
|
+
})),
|
|
2591
2799
|
};
|
|
2592
2800
|
} catch (error) {
|
|
2593
2801
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2643,39 +2851,41 @@ function finalizeParallelWorktreeHandoff(input: {
|
|
|
2643
2851
|
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
2644
2852
|
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
2645
2853
|
const diffs = diffWorktrees(input.worktreeSetup, input.tasks.map((task) => task.agent), diffsDir);
|
|
2646
|
-
const cleanup = cleanupWorktrees(input.worktreeSetup);
|
|
2647
2854
|
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
2671
|
-
}),
|
|
2672
|
-
summary: resultSummaryForIntercom(result),
|
|
2673
|
-
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
2674
|
-
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
2675
|
-
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
2676
|
-
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
2855
|
+
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
2856
|
+
const handoff = {
|
|
2857
|
+
manifestPath,
|
|
2858
|
+
runId: input.runId,
|
|
2859
|
+
mode: "parallel" as const,
|
|
2860
|
+
source: "foreground" as const,
|
|
2861
|
+
cwd: input.cwd,
|
|
2862
|
+
stepIndex: 0,
|
|
2863
|
+
flatStartIndex: 0,
|
|
2864
|
+
setup: input.worktreeSetup,
|
|
2865
|
+
diffs,
|
|
2866
|
+
results: input.results.map((result) => ({
|
|
2867
|
+
agent: result.agent,
|
|
2868
|
+
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
2869
|
+
exitCode: result.exitCode,
|
|
2870
|
+
interrupted: result.interrupted,
|
|
2871
|
+
detached: result.detached,
|
|
2872
|
+
state: result.stopped ? "stopped" : undefined,
|
|
2873
|
+
processSignal: result.processSignal,
|
|
2874
|
+
timedOut: result.timedOut,
|
|
2875
|
+
stopped: result.stopped,
|
|
2876
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
2677
2877
|
})),
|
|
2678
|
-
|
|
2878
|
+
summary: resultSummaryForIntercom(result),
|
|
2879
|
+
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
2880
|
+
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
2881
|
+
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
2882
|
+
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
2883
|
+
})),
|
|
2884
|
+
};
|
|
2885
|
+
try {
|
|
2886
|
+
writeParallelHandoffGroup(handoff);
|
|
2887
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
|
|
2888
|
+
const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
2679
2889
|
return {
|
|
2680
2890
|
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
2681
2891
|
reference,
|
|
@@ -2755,7 +2965,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2755
2965
|
beginForegroundChild(input.foregroundControl, {
|
|
2756
2966
|
index,
|
|
2757
2967
|
agent: task.agent,
|
|
2758
|
-
description: input.taskDescriptions[index],
|
|
2968
|
+
...(input.taskDescriptions[index] === undefined ? {} : { description: input.taskDescriptions[index] }),
|
|
2759
2969
|
...(model ? { model } : {}),
|
|
2760
2970
|
...(thinking ? { thinking } : {}),
|
|
2761
2971
|
interrupt: () => {
|
|
@@ -2769,7 +2979,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2769
2979
|
? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
|
|
2770
2980
|
: undefined;
|
|
2771
2981
|
let detachedReceipt = false;
|
|
2772
|
-
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
2982
|
+
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
2983
|
+
permissions: input.permissions,
|
|
2773
2984
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
2774
2985
|
context: input.contextPolicy.contextForAgent(task.agent),
|
|
2775
2986
|
cwd: taskCwd,
|
|
@@ -2833,17 +3044,17 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2833
3044
|
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
2834
3045
|
input.onUpdate?.({
|
|
2835
3046
|
content: progressUpdate.content,
|
|
2836
|
-
details: {
|
|
3047
|
+
details: compactOptional<Details>({
|
|
2837
3048
|
mode: "parallel",
|
|
2838
3049
|
results: mergedResults,
|
|
2839
3050
|
progress: mergedProgress,
|
|
2840
3051
|
controlEvents: progressUpdate.details?.controlEvents,
|
|
2841
3052
|
totalSteps: input.tasks.length,
|
|
2842
|
-
},
|
|
3053
|
+
}),
|
|
2843
3054
|
});
|
|
2844
3055
|
}
|
|
2845
3056
|
: undefined,
|
|
2846
|
-
}).then((result) => {
|
|
3057
|
+
})).then((result) => {
|
|
2847
3058
|
detachedReceipt = result.detached === true;
|
|
2848
3059
|
return result;
|
|
2849
3060
|
}).finally(() => {
|
|
@@ -2918,7 +3129,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2918
3129
|
);
|
|
2919
3130
|
const toolBudgets: (ResolvedToolBudget | undefined)[] = [];
|
|
2920
3131
|
for (let index = 0; index < tasks.length; index++) {
|
|
2921
|
-
const resolved = resolveEffectiveToolBudget({ stepBudget: tasks[index]?.toolBudget, runBudget: data.toolBudget, agentBudget: agentConfigs[index]?.toolBudget, configBudget: data.configToolBudget });
|
|
3132
|
+
const resolved = resolveEffectiveToolBudget(omitUndefinedProperties({ stepBudget: tasks[index]?.toolBudget, runBudget: data.toolBudget, agentBudget: agentConfigs[index]?.toolBudget, configBudget: data.configToolBudget }));
|
|
2922
3133
|
if (resolved.error) return buildParallelModeError(resolved.error);
|
|
2923
3134
|
toolBudgets.push(resolved.toolBudget);
|
|
2924
3135
|
}
|
|
@@ -2944,7 +3155,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2944
3155
|
...(task.model !== undefined ? { model: task.model } : {}),
|
|
2945
3156
|
}));
|
|
2946
3157
|
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
2947
|
-
resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, { scope: data.modelScope }),
|
|
3158
|
+
resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope }),
|
|
2948
3159
|
);
|
|
2949
3160
|
|
|
2950
3161
|
if (params.clarify === true && ctx.hasUI) {
|
|
@@ -2979,7 +3190,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2979
3190
|
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
2980
3191
|
const override = result.behaviorOverrides[i];
|
|
2981
3192
|
if (override?.model !== undefined) {
|
|
2982
|
-
modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
|
|
3193
|
+
modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2983
3194
|
behaviorOverrides[i]!.model = override.model;
|
|
2984
3195
|
}
|
|
2985
3196
|
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
@@ -3000,20 +3211,21 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3000
3211
|
};
|
|
3001
3212
|
}
|
|
3002
3213
|
const id = randomUUID();
|
|
3003
|
-
const asyncCtx = {
|
|
3214
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3004
3215
|
pi: deps.pi,
|
|
3005
3216
|
cwd: ctx.cwd,
|
|
3006
|
-
currentSessionId:
|
|
3007
|
-
parentSessionId:
|
|
3217
|
+
currentSessionId: data.parentSessionId!,
|
|
3218
|
+
parentSessionId: data.parentPiSessionId,
|
|
3008
3219
|
currentModelProvider: parentModel?.provider,
|
|
3009
3220
|
currentModel: parentModel,
|
|
3010
3221
|
modelScope: data.modelScope,
|
|
3011
3222
|
interactive: ctx.hasUI,
|
|
3012
|
-
|
|
3223
|
+
permissions: deps.config.permissions,
|
|
3224
|
+
});
|
|
3013
3225
|
const parallelTasks = tasks.map((t, i) => {
|
|
3014
3226
|
const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
3015
3227
|
const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
|
|
3016
|
-
return {
|
|
3228
|
+
return compactOptional<ParallelTaskItem>({
|
|
3017
3229
|
agent: t.agent,
|
|
3018
3230
|
task: taskText,
|
|
3019
3231
|
cwd: t.cwd,
|
|
@@ -3027,10 +3239,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3027
3239
|
...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
|
|
3028
3240
|
...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
|
|
3029
3241
|
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
3030
|
-
};
|
|
3242
|
+
});
|
|
3031
3243
|
});
|
|
3032
|
-
return executeAsyncChain(id, {
|
|
3033
|
-
chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }],
|
|
3244
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3245
|
+
chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
|
|
3034
3246
|
resultMode: "parallel",
|
|
3035
3247
|
goal: taskTexts[0] ?? "",
|
|
3036
3248
|
agents,
|
|
@@ -3062,7 +3274,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3062
3274
|
toolBudget: data.toolBudget,
|
|
3063
3275
|
configToolBudget: data.configToolBudget,
|
|
3064
3276
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3065
|
-
});
|
|
3277
|
+
}));
|
|
3066
3278
|
}
|
|
3067
3279
|
}
|
|
3068
3280
|
|
|
@@ -3090,15 +3302,27 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3090
3302
|
|
|
3091
3303
|
let worktreeFinalized = false;
|
|
3092
3304
|
try {
|
|
3305
|
+
if (worktreeSetup) {
|
|
3306
|
+
writePendingParallelHandoff({
|
|
3307
|
+
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3308
|
+
runId,
|
|
3309
|
+
mode: "parallel",
|
|
3310
|
+
source: "foreground",
|
|
3311
|
+
cwd: effectiveCwd,
|
|
3312
|
+
stepIndex: 0,
|
|
3313
|
+
flatStartIndex: 0,
|
|
3314
|
+
setup: worktreeSetup,
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
3093
3317
|
const outputBaseDir = path.join(artifactsDir, "outputs", runId);
|
|
3094
|
-
const duplicateOutputError = findDuplicateParallelOutputPath({
|
|
3318
|
+
const duplicateOutputError = findDuplicateParallelOutputPath(omitUndefinedProperties({
|
|
3095
3319
|
tasks,
|
|
3096
3320
|
behaviors,
|
|
3097
3321
|
paramsCwd: effectiveCwd,
|
|
3098
3322
|
ctxCwd: ctx.cwd,
|
|
3099
3323
|
outputBaseDir,
|
|
3100
3324
|
worktreeSetup,
|
|
3101
|
-
});
|
|
3325
|
+
}));
|
|
3102
3326
|
if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
|
|
3103
3327
|
for (let index = 0; index < tasks.length; index++) {
|
|
3104
3328
|
const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
|
|
@@ -3117,8 +3341,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3117
3341
|
}
|
|
3118
3342
|
|
|
3119
3343
|
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3120
|
-
const results = await runForegroundParallelTasks({
|
|
3344
|
+
const results = await runForegroundParallelTasks(compactOptional<ForegroundParallelRunInput>({
|
|
3121
3345
|
tasks,
|
|
3346
|
+
permissions: deps.config.permissions,
|
|
3122
3347
|
taskTexts,
|
|
3123
3348
|
taskDescriptions,
|
|
3124
3349
|
agents,
|
|
@@ -3166,7 +3391,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3166
3391
|
usageBudget: data.usageBudget,
|
|
3167
3392
|
toolBudgets,
|
|
3168
3393
|
agentContract: params.agentContract,
|
|
3169
|
-
});
|
|
3394
|
+
}));
|
|
3170
3395
|
for (let i = 0; i < results.length; i++) {
|
|
3171
3396
|
const run = results[i]!;
|
|
3172
3397
|
recordRun(run.agent, taskTexts[i]!, run.exitCode, run.progressSummary?.durationMs ?? 0);
|
|
@@ -3188,7 +3413,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3188
3413
|
}
|
|
3189
3414
|
const interrupted = results.find((result) => result.interrupted);
|
|
3190
3415
|
const totalCost = sumResultsCost(results);
|
|
3191
|
-
const details = compactForegroundDetails({
|
|
3416
|
+
const details = compactForegroundDetails(compactOptional<Details>({
|
|
3192
3417
|
mode: "parallel",
|
|
3193
3418
|
runId,
|
|
3194
3419
|
timeoutMs: data.timeoutMs,
|
|
@@ -3199,7 +3424,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3199
3424
|
totalCost,
|
|
3200
3425
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3201
3426
|
...(handoff?.reference ? { parallelHandoff: handoff.reference } : {}),
|
|
3202
|
-
});
|
|
3427
|
+
}));
|
|
3203
3428
|
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
3204
3429
|
if (interrupted) {
|
|
3205
3430
|
return {
|
|
@@ -3216,20 +3441,23 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3216
3441
|
};
|
|
3217
3442
|
}
|
|
3218
3443
|
|
|
3444
|
+
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results });
|
|
3219
3445
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3446
|
+
if (!suppressRoutineResultIntercom) {
|
|
3447
|
+
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
3448
|
+
pi: deps.pi,
|
|
3449
|
+
intercomBridge: data.intercomBridge,
|
|
3450
|
+
runId,
|
|
3451
|
+
mode: "parallel",
|
|
3452
|
+
details,
|
|
3453
|
+
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
3454
|
+
});
|
|
3455
|
+
if (intercomReceipt) {
|
|
3456
|
+
return {
|
|
3457
|
+
content: [{ type: "text", text: intercomReceipt.text }],
|
|
3458
|
+
details: intercomReceipt.details,
|
|
3459
|
+
};
|
|
3460
|
+
}
|
|
3233
3461
|
}
|
|
3234
3462
|
|
|
3235
3463
|
const worktreeSuffix = handoff?.suffix ?? "";
|
|
@@ -3240,8 +3468,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3240
3468
|
agent: result.agent,
|
|
3241
3469
|
output: result.truncation?.text || getSingleResultOutput(result),
|
|
3242
3470
|
exitCode: result.exitCode,
|
|
3243
|
-
error: result.error,
|
|
3244
|
-
timedOut: result.timedOut,
|
|
3471
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
3472
|
+
...(result.timedOut === undefined ? {} : { timedOut: result.timedOut }),
|
|
3245
3473
|
})),
|
|
3246
3474
|
(i, agent) => `=== Task ${i + 1}: ${agent} ===`,
|
|
3247
3475
|
);
|
|
@@ -3291,7 +3519,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3291
3519
|
details: { mode: "single", results: [] },
|
|
3292
3520
|
};
|
|
3293
3521
|
}
|
|
3294
|
-
const effectiveToolBudget = resolveEffectiveToolBudget({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget });
|
|
3522
|
+
const effectiveToolBudget = resolveEffectiveToolBudget(omitUndefinedProperties({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget }));
|
|
3295
3523
|
if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error), data.contextPolicy.contextSummary);
|
|
3296
3524
|
|
|
3297
3525
|
const parentModel = data.parentModel;
|
|
@@ -3304,7 +3532,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3304
3532
|
parentModel,
|
|
3305
3533
|
availableModels,
|
|
3306
3534
|
currentProvider,
|
|
3307
|
-
{ scope: data.modelScope },
|
|
3535
|
+
data.modelScope === undefined ? {} : { scope: data.modelScope },
|
|
3308
3536
|
);
|
|
3309
3537
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
3310
3538
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
@@ -3314,7 +3542,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3314
3542
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
3315
3543
|
|
|
3316
3544
|
if (params.clarify === true && ctx.hasUI) {
|
|
3317
|
-
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput, skills: skillOverride });
|
|
3545
|
+
const behavior = resolveStepBehavior(agentConfig, omitUndefinedProperties({ output: effectiveOutput, skills: skillOverride }));
|
|
3318
3546
|
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
3319
3547
|
|
|
3320
3548
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
@@ -3341,7 +3569,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3341
3569
|
|
|
3342
3570
|
task = result.templates[0]!;
|
|
3343
3571
|
const override = result.behaviorOverrides[0];
|
|
3344
|
-
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
|
|
3572
|
+
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
3345
3573
|
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
3346
3574
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
3347
3575
|
|
|
@@ -3354,17 +3582,18 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3354
3582
|
};
|
|
3355
3583
|
}
|
|
3356
3584
|
const id = randomUUID();
|
|
3357
|
-
const asyncCtx = {
|
|
3585
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3358
3586
|
pi: deps.pi,
|
|
3359
3587
|
cwd: ctx.cwd,
|
|
3360
|
-
currentSessionId:
|
|
3361
|
-
parentSessionId:
|
|
3588
|
+
currentSessionId: data.parentSessionId!,
|
|
3589
|
+
parentSessionId: data.parentPiSessionId,
|
|
3362
3590
|
currentModelProvider: parentModel?.provider,
|
|
3363
3591
|
currentModel: parentModel,
|
|
3364
3592
|
modelScope: data.modelScope,
|
|
3365
3593
|
interactive: ctx.hasUI,
|
|
3366
|
-
|
|
3367
|
-
|
|
3594
|
+
permissions: deps.config.permissions,
|
|
3595
|
+
});
|
|
3596
|
+
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
3368
3597
|
agent: params.agent!,
|
|
3369
3598
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
3370
3599
|
goal: task,
|
|
@@ -3402,7 +3631,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3402
3631
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3403
3632
|
usageBudget: data.usageBudget,
|
|
3404
3633
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3405
|
-
});
|
|
3634
|
+
}));
|
|
3406
3635
|
}
|
|
3407
3636
|
}
|
|
3408
3637
|
|
|
@@ -3431,7 +3660,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3431
3660
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3432
3661
|
if (foregroundControl) {
|
|
3433
3662
|
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride));
|
|
3434
|
-
beginForegroundChild(foregroundControl, {
|
|
3663
|
+
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
3435
3664
|
index: 0,
|
|
3436
3665
|
agent: params.agent!,
|
|
3437
3666
|
description: foregroundControl.description,
|
|
@@ -3443,7 +3672,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3443
3672
|
return true;
|
|
3444
3673
|
},
|
|
3445
3674
|
detach: () => detachForeground?.("user request") === true,
|
|
3446
|
-
});
|
|
3675
|
+
}));
|
|
3447
3676
|
}
|
|
3448
3677
|
|
|
3449
3678
|
const forwardSingleUpdate = onUpdate
|
|
@@ -3454,9 +3683,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3454
3683
|
: undefined;
|
|
3455
3684
|
|
|
3456
3685
|
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3457
|
-
let r: Awaited<ReturnType<typeof runSync
|
|
3686
|
+
let r: Awaited<ReturnType<typeof runSync>> | undefined;
|
|
3458
3687
|
try {
|
|
3459
|
-
r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
3688
|
+
r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3689
|
+
permissions: deps.config.permissions,
|
|
3460
3690
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3461
3691
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3462
3692
|
cwd: effectiveCwd,
|
|
@@ -3498,7 +3728,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3498
3728
|
onDetachedExit: (result) => {
|
|
3499
3729
|
try {
|
|
3500
3730
|
try {
|
|
3501
|
-
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events });
|
|
3731
|
+
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: params.workflowParentRunId === undefined });
|
|
3502
3732
|
} catch {
|
|
3503
3733
|
// Remembered foreground state is best-effort; run history and cleanup must still complete.
|
|
3504
3734
|
}
|
|
@@ -3522,7 +3752,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3522
3752
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3523
3753
|
capabilityCeiling: data.capabilityCeiling,
|
|
3524
3754
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3525
|
-
});
|
|
3755
|
+
}));
|
|
3526
3756
|
} finally {
|
|
3527
3757
|
// An attached runSync rejection still owns its child and structured runtime.
|
|
3528
3758
|
// A successful detached receipt transfers both to onDetachedExit while the
|
|
@@ -3540,7 +3770,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3540
3770
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
3541
3771
|
|
|
3542
3772
|
const fullOutput = getSingleResultOutput(r);
|
|
3543
|
-
const finalizedOutput = finalizeSingleOutput({
|
|
3773
|
+
const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
|
|
3544
3774
|
fullOutput,
|
|
3545
3775
|
truncatedOutput: r.truncation?.text,
|
|
3546
3776
|
outputPath,
|
|
@@ -3549,13 +3779,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3549
3779
|
savedPath: r.savedOutputPath,
|
|
3550
3780
|
outputReference: r.outputReference,
|
|
3551
3781
|
saveError: r.outputSaveError,
|
|
3552
|
-
});
|
|
3782
|
+
}));
|
|
3553
3783
|
if (foregroundControl) {
|
|
3554
3784
|
updateForegroundNestedProjection(foregroundControl);
|
|
3555
3785
|
attachRootChildrenToSteps(runId, [r], foregroundControl.nestedChildren);
|
|
3556
3786
|
}
|
|
3557
3787
|
const totalCost = sumResultsCost([r]);
|
|
3558
|
-
const details = compactForegroundDetails({
|
|
3788
|
+
const details = compactForegroundDetails(compactOptional<Details>({
|
|
3559
3789
|
mode: "single",
|
|
3560
3790
|
runId,
|
|
3561
3791
|
timeoutMs: data.timeoutMs,
|
|
@@ -3568,10 +3798,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3568
3798
|
totalChildUsage: sumResultsUsage([r]),
|
|
3569
3799
|
totalCost,
|
|
3570
3800
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3571
|
-
});
|
|
3801
|
+
}));
|
|
3572
3802
|
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
3573
3803
|
|
|
3574
|
-
|
|
3804
|
+
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
3805
|
+
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
3575
3806
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
3576
3807
|
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
3577
3808
|
pi: deps.pi,
|
|
@@ -3616,7 +3847,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3616
3847
|
};
|
|
3617
3848
|
}
|
|
3618
3849
|
|
|
3619
|
-
function inferExecutionMode(params: SubagentParamsLike):
|
|
3850
|
+
function inferExecutionMode(params: SubagentParamsLike): Details["mode"] {
|
|
3851
|
+
if (params.workflowScript !== undefined) return "workflow";
|
|
3620
3852
|
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
3621
3853
|
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
3622
3854
|
return "single";
|
|
@@ -3633,6 +3865,84 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
3633
3865
|
};
|
|
3634
3866
|
}
|
|
3635
3867
|
|
|
3868
|
+
function workflowChildResult(key: string, result: AgentToolResult<Details>): WorkflowScriptChildResult {
|
|
3869
|
+
const output = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
|
|
3870
|
+
const artifactPaths = new Set<string>();
|
|
3871
|
+
if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
|
|
3872
|
+
if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
|
|
3873
|
+
for (const child of result.details.results) {
|
|
3874
|
+
if (child.savedOutputPath) artifactPaths.add(child.savedOutputPath);
|
|
3875
|
+
if (child.outputReference?.path) artifactPaths.add(child.outputReference.path);
|
|
3876
|
+
if (child.sessionFile) artifactPaths.add(child.sessionFile);
|
|
3877
|
+
}
|
|
3878
|
+
const structured = result.details.results.map((child) => child.structuredOutput).filter((value) => value !== undefined);
|
|
3879
|
+
return {
|
|
3880
|
+
key,
|
|
3881
|
+
ok: result.isError !== true,
|
|
3882
|
+
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
3883
|
+
output,
|
|
3884
|
+
...(result.isError === true ? { error: output || "Child run failed." } : {}),
|
|
3885
|
+
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
3886
|
+
artifactPaths: [...artifactPaths],
|
|
3887
|
+
results: result.details.results,
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsLike {
|
|
3892
|
+
if (params.worktree !== true || !params.agent) return params;
|
|
3893
|
+
const {
|
|
3894
|
+
agent,
|
|
3895
|
+
task = "",
|
|
3896
|
+
model,
|
|
3897
|
+
skill,
|
|
3898
|
+
output,
|
|
3899
|
+
outputMode,
|
|
3900
|
+
outputSchema,
|
|
3901
|
+
acceptance,
|
|
3902
|
+
agentContract,
|
|
3903
|
+
toolBudget,
|
|
3904
|
+
...runParams
|
|
3905
|
+
} = params;
|
|
3906
|
+
return {
|
|
3907
|
+
...runParams,
|
|
3908
|
+
worktree: true,
|
|
3909
|
+
tasks: [{
|
|
3910
|
+
agent,
|
|
3911
|
+
task,
|
|
3912
|
+
...(model !== undefined ? { model } : {}),
|
|
3913
|
+
...(skill !== undefined ? { skill } : {}),
|
|
3914
|
+
...(output !== undefined ? { output } : {}),
|
|
3915
|
+
...(outputMode !== undefined ? { outputMode } : {}),
|
|
3916
|
+
...(outputSchema !== undefined ? { outputSchema } : {}),
|
|
3917
|
+
...(acceptance !== undefined ? { acceptance } : {}),
|
|
3918
|
+
...(agentContract !== undefined ? { agentContract } : {}),
|
|
3919
|
+
...(toolBudget !== undefined ? { toolBudget } : {}),
|
|
3920
|
+
}],
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
function formatWorkflowValue(value: unknown): string {
|
|
3925
|
+
if (value === undefined) return "(undefined)";
|
|
3926
|
+
if (typeof value === "string") return value;
|
|
3927
|
+
try {
|
|
3928
|
+
return JSON.stringify(value, null, 2);
|
|
3929
|
+
} catch {
|
|
3930
|
+
return String(value);
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
function workflowChatProgressUpdate(
|
|
3935
|
+
runId: string,
|
|
3936
|
+
chatProgress: WorkflowChatProgressProjection,
|
|
3937
|
+
workflow: NonNullable<Details["workflow"]>,
|
|
3938
|
+
): AgentToolResult<Details> | undefined {
|
|
3939
|
+
if (chatProgress.mode !== "live-card") return undefined;
|
|
3940
|
+
return {
|
|
3941
|
+
content: [{ type: "text", text: "Workflow running." }],
|
|
3942
|
+
details: { mode: "workflow", runId, results: [], workflow, chatProgress },
|
|
3943
|
+
};
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3636
3946
|
function omitExecutionModeActionAlias(params: SubagentParamsLike): SubagentParamsLike {
|
|
3637
3947
|
const action = params.action?.toLowerCase();
|
|
3638
3948
|
if (action === "single" && (params.agent !== undefined || params.task !== undefined)) {
|
|
@@ -3648,6 +3958,36 @@ function omitExecutionModeActionAlias(params: SubagentParamsLike): SubagentParam
|
|
|
3648
3958
|
return params;
|
|
3649
3959
|
}
|
|
3650
3960
|
|
|
3961
|
+
function createScheduledOwnerState(source: SubagentState, ownerSessionId: string, ctx: ExtensionContext): SubagentState {
|
|
3962
|
+
const ownerSpawns = source.subagentSpawns?.sessionId === ownerSessionId
|
|
3963
|
+
? {
|
|
3964
|
+
...source.subagentSpawns,
|
|
3965
|
+
grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
|
|
3966
|
+
}
|
|
3967
|
+
: undefined;
|
|
3968
|
+
return {
|
|
3969
|
+
...source,
|
|
3970
|
+
baseCwd: ctx.cwd,
|
|
3971
|
+
currentSessionId: ownerSessionId,
|
|
3972
|
+
parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
|
|
3973
|
+
subagentInProgress: false,
|
|
3974
|
+
...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
|
|
3975
|
+
asyncJobs: new Map(),
|
|
3976
|
+
fleetJobs: new Map(),
|
|
3977
|
+
foregroundRuns: new Map(),
|
|
3978
|
+
foregroundControls: new Map(),
|
|
3979
|
+
lastForegroundControlId: null,
|
|
3980
|
+
cleanupTimers: new Map(),
|
|
3981
|
+
lastUiContext: null,
|
|
3982
|
+
poller: null,
|
|
3983
|
+
completionSeen: new Map(),
|
|
3984
|
+
watcher: null,
|
|
3985
|
+
watcherRestartTimer: null,
|
|
3986
|
+
waitSubscriptions: new Map(),
|
|
3987
|
+
workflowControllers: new Map(),
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3651
3991
|
export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
3652
3992
|
execute: (
|
|
3653
3993
|
id: string,
|
|
@@ -3668,34 +4008,319 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3668
4008
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3669
4009
|
ctx: ExtensionContext,
|
|
3670
4010
|
) => Promise<AgentToolResult<Details>>;
|
|
4011
|
+
/** Scheduled launches retain their owning context without replacing the live active session. */
|
|
4012
|
+
executeScheduled: (
|
|
4013
|
+
id: string,
|
|
4014
|
+
params: SubagentParamsLike,
|
|
4015
|
+
signal: AbortSignal,
|
|
4016
|
+
ctx: ExtensionContext,
|
|
4017
|
+
) => Promise<AgentToolResult<Details>>;
|
|
3671
4018
|
} {
|
|
3672
4019
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
3673
4020
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4021
|
+
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
3674
4022
|
const execute = async (
|
|
3675
4023
|
_id: string,
|
|
3676
4024
|
params: SubagentParamsLike,
|
|
3677
4025
|
signal: AbortSignal,
|
|
3678
4026
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3679
4027
|
ctx: ExtensionContext,
|
|
4028
|
+
preserveActiveSession = false,
|
|
3680
4029
|
): Promise<AgentToolResult<Details>> => {
|
|
3681
4030
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
3682
4031
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
3683
|
-
deps.state.baseCwd = ctx.cwd;
|
|
4032
|
+
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
3684
4033
|
deps.state.foregroundRuns ??= new Map();
|
|
3685
4034
|
deps.state.foregroundControls ??= new Map();
|
|
3686
4035
|
deps.state.lastForegroundControlId ??= null;
|
|
3687
4036
|
const requestParams = omitExecutionModeActionAlias(params);
|
|
3688
|
-
|
|
3689
|
-
|
|
4037
|
+
if (requestParams.workflowScript !== undefined) {
|
|
4038
|
+
const invalidMode = requestParams.action !== undefined || requestParams.agent !== undefined || requestParams.step !== undefined || requestParams.tasks !== undefined || requestParams.chain !== undefined;
|
|
4039
|
+
if (invalidMode) {
|
|
4040
|
+
return { content: [{ type: "text", text: "workflowScript is its own execution mode; do not combine it with action, agent, step, tasks, or chain." }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4041
|
+
}
|
|
4042
|
+
if (requestParams.clarify === true) {
|
|
4043
|
+
return { content: [{ type: "text", text: "workflowScript does not support clarify UI." }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4044
|
+
}
|
|
4045
|
+
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
|
|
4046
|
+
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4047
|
+
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4048
|
+
const workflowCwd = resolveRequestedCwd(ctx.cwd, requestParams.cwd);
|
|
4049
|
+
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd: ctx.cwd, workflowCwd, background: requestParams.async !== false });
|
|
4050
|
+
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4051
|
+
const chatProgress = chatProgressResult.projection!;
|
|
4052
|
+
if (requestParams.async !== false) {
|
|
4053
|
+
const workflowRunId = _id;
|
|
4054
|
+
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4055
|
+
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
4056
|
+
const statusPath = path.join(asyncDir, "status.json");
|
|
4057
|
+
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4058
|
+
const startedAt = Date.now();
|
|
4059
|
+
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4060
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
4061
|
+
fs.mkdirSync(DIRS.results, { recursive: true });
|
|
4062
|
+
const controller = new AbortController();
|
|
4063
|
+
deps.state.workflowControllers ??= new Map();
|
|
4064
|
+
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4065
|
+
let status: AsyncStatus = {
|
|
4066
|
+
runId: workflowRunId,
|
|
4067
|
+
sessionId: currentSessionId ?? undefined,
|
|
4068
|
+
mode: "workflow",
|
|
4069
|
+
state: "running",
|
|
4070
|
+
startedAt,
|
|
4071
|
+
lastUpdate: startedAt,
|
|
4072
|
+
deadlineAt: startedAt + timeout,
|
|
4073
|
+
timeoutMs: timeout,
|
|
4074
|
+
cwd: ctx.cwd,
|
|
4075
|
+
pid: process.pid,
|
|
4076
|
+
steps: [],
|
|
4077
|
+
workflow: { trace: [], emits: [], console: [] },
|
|
4078
|
+
};
|
|
4079
|
+
const appendWorkflowEvent = (event: Record<string, unknown>) => fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
4080
|
+
const persist = () => {
|
|
4081
|
+
status.lastUpdate = Date.now();
|
|
4082
|
+
writeAtomicJson(statusPath, status);
|
|
4083
|
+
const job = deps.state.asyncJobs.get(workflowRunId);
|
|
4084
|
+
if (job) {
|
|
4085
|
+
job.status = status.state;
|
|
4086
|
+
job.updatedAt = status.lastUpdate;
|
|
4087
|
+
if (status.steps) {
|
|
4088
|
+
job.steps = status.steps.map((step, index) => ({ ...step, index }));
|
|
4089
|
+
job.agents = status.steps.map((step) => step.agent);
|
|
4090
|
+
} else {
|
|
4091
|
+
delete job.steps;
|
|
4092
|
+
delete job.agents;
|
|
4093
|
+
}
|
|
4094
|
+
job.workflow = status.workflow;
|
|
4095
|
+
}
|
|
4096
|
+
};
|
|
4097
|
+
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: ctx.cwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, timeoutMs: timeout, deadlineAt: startedAt + timeout, workflow: status.workflow };
|
|
4098
|
+
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4099
|
+
deps.state.fleetJobs ??= new Map();
|
|
4100
|
+
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
4101
|
+
persist();
|
|
4102
|
+
appendWorkflowEvent({ type: "subagent.workflow.started" });
|
|
4103
|
+
const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
|
|
4104
|
+
void Promise.resolve().then(async () => {
|
|
4105
|
+
const workflowResults: SingleResult[] = [];
|
|
4106
|
+
const { action: _action, agent: _agent, task: _task, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, ...workflowChildDefaults } = workflowRequest;
|
|
4107
|
+
const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
|
|
4108
|
+
status.workflow = { ...(status.workflow ?? { emits: [], console: [] }), trace };
|
|
4109
|
+
for (const entry of trace.filter((candidate) => candidate.operation === "run")) {
|
|
4110
|
+
const existing = status.steps?.find((step) => step.workflowKey === entry.key);
|
|
4111
|
+
if (entry.state === "reused" && existing) continue;
|
|
4112
|
+
const mapped = entry.state === "started" || entry.state === "reused" ? "running" : entry.state === "completed" ? "completed" : "failed";
|
|
4113
|
+
if (existing) {
|
|
4114
|
+
existing.status = mapped;
|
|
4115
|
+
if (entry.error === undefined) delete existing.error;
|
|
4116
|
+
else existing.error = entry.error;
|
|
4117
|
+
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4118
|
+
else existing.durationMs = entry.durationMs;
|
|
4119
|
+
} else {
|
|
4120
|
+
status.steps?.push({ agent: entry.key, label: entry.key, workflowKey: entry.key, parentWorkflowRunId: workflowRunId, status: mapped });
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
persist();
|
|
4124
|
+
appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
|
|
4125
|
+
};
|
|
4126
|
+
try {
|
|
4127
|
+
const workflow = await runWorkflowScript({
|
|
4128
|
+
script: workflowScript,
|
|
4129
|
+
timeoutMs: timeout,
|
|
4130
|
+
signal: controller.signal,
|
|
4131
|
+
onTrace: updateTrace,
|
|
4132
|
+
onEmit: (emits) => {
|
|
4133
|
+
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
4134
|
+
status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
|
|
4135
|
+
persist();
|
|
4136
|
+
appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
|
|
4137
|
+
},
|
|
4138
|
+
launch: async (key, childParams, workflowSignal) => {
|
|
4139
|
+
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4140
|
+
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4141
|
+
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4142
|
+
const childRequest = prepareWorkflowChildParams({ ...workflowChildDefaults, ...childParams, workflowParentRunId: workflowRunId, workflowKey: key } as SubagentParamsLike);
|
|
4143
|
+
const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
|
|
4144
|
+
workflowResults.push(...result.details.results);
|
|
4145
|
+
const child = workflowChildResult(key, result);
|
|
4146
|
+
if (result.details.asyncId) {
|
|
4147
|
+
const childJob = deps.state.asyncJobs.get(result.details.asyncId);
|
|
4148
|
+
if (childJob) { childJob.parentWorkflowRunId = workflowRunId; childJob.workflowKey = key; }
|
|
4149
|
+
}
|
|
4150
|
+
return child;
|
|
4151
|
+
},
|
|
4152
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4153
|
+
});
|
|
4154
|
+
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
4155
|
+
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
4156
|
+
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
4157
|
+
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4158
|
+
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
4159
|
+
persist();
|
|
4160
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
4161
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({ agent: child.key, output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: ctx.cwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4162
|
+
} catch (error) {
|
|
4163
|
+
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4164
|
+
const stopped = controller.signal.aborted;
|
|
4165
|
+
status = compactOptional<AsyncStatus>({ ...status, state: stopped ? "stopped" : "failed", stopped: stopped || undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
4166
|
+
persist();
|
|
4167
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4168
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ agent: child.key, output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: ctx.cwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4169
|
+
} finally {
|
|
4170
|
+
deps.state.workflowControllers?.delete(workflowRunId);
|
|
4171
|
+
}
|
|
4172
|
+
});
|
|
4173
|
+
return {
|
|
4174
|
+
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4175
|
+
details: { mode: "workflow", runId: workflowRunId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, ...workflowChildDefaults } = requestParams;
|
|
4179
|
+
const workflowResults: SingleResult[] = [];
|
|
4180
|
+
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
4181
|
+
const sendWorkflowProgress = () => {
|
|
4182
|
+
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
|
|
4183
|
+
if (update) onUpdate?.(update);
|
|
4184
|
+
};
|
|
4185
|
+
try {
|
|
4186
|
+
const workflow = await runWorkflowScript({
|
|
4187
|
+
script: requestParams.workflowScript,
|
|
4188
|
+
timeoutMs: timeout,
|
|
4189
|
+
signal,
|
|
4190
|
+
onTrace: (trace) => {
|
|
4191
|
+
liveWorkflow = { ...liveWorkflow, trace };
|
|
4192
|
+
sendWorkflowProgress();
|
|
4193
|
+
},
|
|
4194
|
+
onEmit: (emits) => {
|
|
4195
|
+
liveWorkflow = { ...liveWorkflow, emits };
|
|
4196
|
+
sendWorkflowProgress();
|
|
4197
|
+
},
|
|
4198
|
+
launch: async (key, childParams, workflowSignal) => {
|
|
4199
|
+
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4200
|
+
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4201
|
+
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4202
|
+
const childRequest = prepareWorkflowChildParams({ ...workflowChildDefaults, ...childParams, workflowParentRunId: _id, workflowKey: key, suppressRoutineResultIntercom: chatProgress.mode === "live-card" } as SubagentParamsLike);
|
|
4203
|
+
const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
|
|
4204
|
+
workflowResults.push(...result.details.results);
|
|
4205
|
+
return workflowChildResult(key, result);
|
|
4206
|
+
},
|
|
4207
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4208
|
+
});
|
|
4209
|
+
const traceLines = workflow.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
4210
|
+
const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
|
|
4211
|
+
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4212
|
+
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4213
|
+
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4214
|
+
return {
|
|
4215
|
+
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4216
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, chatProgress }),
|
|
4217
|
+
};
|
|
4218
|
+
} catch (error) {
|
|
4219
|
+
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4220
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
4221
|
+
const traceLines = partial.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
4222
|
+
const sections = [`Workflow failed: ${text}`];
|
|
4223
|
+
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4224
|
+
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4225
|
+
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4226
|
+
return {
|
|
4227
|
+
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4228
|
+
isError: true,
|
|
4229
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, chatProgress }),
|
|
4230
|
+
};
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
const directParams = prepareWorkflowChildParams(requestParams);
|
|
4234
|
+
const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
|
|
4235
|
+
const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
|
|
3690
4236
|
const action = paramsWithResolvedCwd.action;
|
|
4237
|
+
let requestSessionId = "";
|
|
4238
|
+
let requestPiSessionId: string | undefined;
|
|
3691
4239
|
let requestParentModel: ParentModel | undefined;
|
|
3692
4240
|
try {
|
|
3693
|
-
|
|
4241
|
+
requestSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4242
|
+
requestPiSessionId = ctx.sessionManager.getSessionId() ?? undefined;
|
|
4243
|
+
requestParentModel = preserveActiveSession
|
|
4244
|
+
? normalizeParentModel(ctx.model)
|
|
4245
|
+
: rememberParentModel(deps.state, requestSessionId, ctx.model);
|
|
3694
4246
|
} catch (error) {
|
|
3695
4247
|
if (action?.toLowerCase() !== "doctor") throw error;
|
|
3696
4248
|
requestParentModel = normalizeParentModel(ctx.model);
|
|
3697
4249
|
}
|
|
3698
4250
|
if (action) {
|
|
4251
|
+
if (action === "worktree.discard") {
|
|
4252
|
+
if (deps.allowMutatingManagementActions === false) {
|
|
4253
|
+
return { content: [{ type: "text", text: "Action 'worktree.discard' is not available from child-safe subagent fanout mode." }], isError: true, details: { mode: "management", results: [] } };
|
|
4254
|
+
}
|
|
4255
|
+
if (!paramsWithResolvedCwd.handoffPath?.trim()) {
|
|
4256
|
+
return { content: [{ type: "text", text: "worktree.discard requires handoffPath from parallelHandoff.path or async status." }], isError: true, details: { mode: "management", results: [] } };
|
|
4257
|
+
}
|
|
4258
|
+
const decision = resolveAuthorityDecision({ action: "discardWorktree", ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
|
|
4259
|
+
if (decision === "forbid") {
|
|
4260
|
+
return { content: [{ type: "text", text: "Authority policy forbids worktree discard." }], isError: true, details: { mode: "management", results: [] } };
|
|
4261
|
+
}
|
|
4262
|
+
let confirmed = decision === "auto";
|
|
4263
|
+
if (decision === "confirm") {
|
|
4264
|
+
if (!ctx.hasUI) return { content: [{ type: "text", text: "Authority policy requires user confirmation for worktree discard, but this session has no interactive UI. Preserved worktrees were not changed." }], isError: true, details: { mode: "management", results: [] } };
|
|
4265
|
+
confirmed = await ctx.ui.confirm("Discard preserved subagent worktrees?", `This permanently removes preserved worktrees and temporary branches recorded in:\n${paramsWithResolvedCwd.handoffPath}`);
|
|
4266
|
+
}
|
|
4267
|
+
if (!confirmed) return { content: [{ type: "text", text: "Worktree discard canceled; preserved worktrees were not changed." }], details: { mode: "management", results: [] } };
|
|
4268
|
+
try {
|
|
4269
|
+
const discarded = discardPreservedWorktrees(
|
|
4270
|
+
path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath),
|
|
4271
|
+
{ kind: decision === "confirm" ? "confirmed" : "policy", ...(deps.config.authorityPolicy ? { policy: deps.config.authorityPolicy } : {}) },
|
|
4272
|
+
);
|
|
4273
|
+
return { content: [{ type: "text", text: discarded.text }], details: { mode: "management", results: [] } };
|
|
4274
|
+
} catch (error) {
|
|
4275
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
if ((HERDR_PROJECT_PANE_ACTIONS as readonly string[]).includes(action)) {
|
|
4279
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4280
|
+
return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4281
|
+
}
|
|
4282
|
+
return handleHerdrProjectPaneAction(action as (typeof HERDR_PROJECT_PANE_ACTIONS)[number], paramsWithResolvedCwd, { cwd: requestCwd, signal });
|
|
4283
|
+
}
|
|
4284
|
+
if ((HERDR_INSPECTOR_ACTIONS as readonly string[]).includes(action)) {
|
|
4285
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4286
|
+
return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4287
|
+
}
|
|
4288
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4289
|
+
return handleHerdrInspectorAction(action as (typeof HERDR_INSPECTOR_ACTIONS)[number], paramsWithResolvedCwd, {
|
|
4290
|
+
state: deps.state,
|
|
4291
|
+
cwd: requestCwd,
|
|
4292
|
+
...(deps.config.missions ? { missions: deps.config.missions } : {}),
|
|
4293
|
+
...(deps.config.authorityPolicy ? { authorityPolicy: deps.config.authorityPolicy } : {}),
|
|
4294
|
+
signal,
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
if ((MISSION_ACTIONS as readonly string[]).includes(action)) {
|
|
4298
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4299
|
+
return {
|
|
4300
|
+
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
4301
|
+
isError: true,
|
|
4302
|
+
details: { mode: "management", results: [] },
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
const currentSessionId = deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined;
|
|
4306
|
+
return handleMissionAction(action as (typeof MISSION_ACTIONS)[number], paramsWithResolvedCwd, {
|
|
4307
|
+
cwd: requestCwd,
|
|
4308
|
+
...(deps.config.missions ? { config: deps.config.missions } : {}),
|
|
4309
|
+
...(currentSessionId ? { currentSessionId } : {}),
|
|
4310
|
+
});
|
|
4311
|
+
}
|
|
4312
|
+
const policyAction = action === "stop" ? "stopRun" : action === "steer" ? "steerRun" : action === "schedule.create" ? "scheduleCreate" : undefined;
|
|
4313
|
+
if (policyAction) {
|
|
4314
|
+
const decision = resolveAuthorityDecision({ action: policyAction, ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
|
|
4315
|
+
if (decision === "forbid") {
|
|
4316
|
+
return { content: [{ type: "text", text: `Authority policy forbids action '${action}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4317
|
+
}
|
|
4318
|
+
if (decision === "confirm") {
|
|
4319
|
+
if (!ctx.hasUI) return { content: [{ type: "text", text: `Authority policy requires user confirmation for action '${action}', but this session has no interactive UI.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4320
|
+
const confirmed = await ctx.ui.confirm(`Authorize subagent ${action}?`, `Authority policy requires confirmation before '${action}'.`);
|
|
4321
|
+
if (!confirmed) return { content: [{ type: "text", text: `Action '${action}' canceled; authority was not granted.` }], details: { mode: "management", results: [] } };
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
3699
4324
|
if ((WATCHDOG_TOOL_ACTIONS as readonly string[]).includes(action)) {
|
|
3700
4325
|
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
3701
4326
|
return {
|
|
@@ -3740,7 +4365,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3740
4365
|
details: { mode: "management", results: [], spawnBudget: preview.snapshot },
|
|
3741
4366
|
};
|
|
3742
4367
|
}
|
|
3743
|
-
const
|
|
4368
|
+
const authority = resolveAuthorityDecision({ action: "spawnBudgetGrant", ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
|
|
4369
|
+
if (authority === "forbid") {
|
|
4370
|
+
return {
|
|
4371
|
+
content: [{ type: "text", text: "Authority policy forbids spawn budget grants." }],
|
|
4372
|
+
isError: true,
|
|
4373
|
+
details: { mode: "management", results: [], spawnBudget: preview.snapshot },
|
|
4374
|
+
};
|
|
4375
|
+
}
|
|
4376
|
+
const confirmed = authority === "auto" || await ctx.ui.confirm(
|
|
3744
4377
|
"Grant subagent spawn budget?",
|
|
3745
4378
|
`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.`,
|
|
3746
4379
|
);
|
|
@@ -3790,7 +4423,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3790
4423
|
return {
|
|
3791
4424
|
content: [{
|
|
3792
4425
|
type: "text",
|
|
3793
|
-
text: buildDoctorReport({
|
|
4426
|
+
text: buildDoctorReport(omitUndefinedProperties({
|
|
3794
4427
|
cwd: requestCwd,
|
|
3795
4428
|
config: deps.config,
|
|
3796
4429
|
state: deps.state,
|
|
@@ -3801,13 +4434,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3801
4434
|
orchestratorTarget,
|
|
3802
4435
|
sessionError,
|
|
3803
4436
|
expandTilde: deps.expandTilde,
|
|
3804
|
-
}),
|
|
4437
|
+
})),
|
|
3805
4438
|
}],
|
|
3806
4439
|
details: { mode: "management", results: [], spawnBudget },
|
|
3807
4440
|
};
|
|
3808
4441
|
}
|
|
3809
4442
|
if (action === "status") {
|
|
3810
|
-
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4443
|
+
if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3811
4444
|
const withBudget = (result: AgentToolResult<Details>) => withSpawnBudgetStatus(
|
|
3812
4445
|
result,
|
|
3813
4446
|
deps.state,
|
|
@@ -3818,11 +4451,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3818
4451
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
3819
4452
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
3820
4453
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
3821
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
4454
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
3822
4455
|
}
|
|
3823
4456
|
if (targetRunId) {
|
|
3824
4457
|
try {
|
|
3825
|
-
const resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedScope });
|
|
4458
|
+
const resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedScope }));
|
|
3826
4459
|
if (resolved?.kind === "foreground") {
|
|
3827
4460
|
const foreground = getForegroundControl(deps.state, resolved.id);
|
|
3828
4461
|
if (foreground) {
|
|
@@ -3849,7 +4482,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3849
4482
|
});
|
|
3850
4483
|
}
|
|
3851
4484
|
}
|
|
3852
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
4485
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
3853
4486
|
}
|
|
3854
4487
|
|
|
3855
4488
|
if (action === "approve-checkpoint" || action === "reject-checkpoint") {
|
|
@@ -3859,7 +4492,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3859
4492
|
return { content: [{ type: "text", text: `action='${action}' requires id or dir.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3860
4493
|
}
|
|
3861
4494
|
try {
|
|
3862
|
-
const resolved = targetRunId ? resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }) : undefined;
|
|
4495
|
+
const resolved = targetRunId ? resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
|
|
3863
4496
|
const decision = action === "approve-checkpoint" ? "approved" : "rejected";
|
|
3864
4497
|
if (resolved?.kind === "foreground") {
|
|
3865
4498
|
const run = deps.state.foregroundRuns?.get(resolved.id);
|
|
@@ -3870,7 +4503,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3870
4503
|
return { content: [{ type: "text", text: `Run '${resolved.id}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3871
4504
|
}
|
|
3872
4505
|
const decidedAt = Date.now();
|
|
3873
|
-
const checkpoint
|
|
4506
|
+
const checkpoint: NonNullable<Details["checkpoint"]> = decision === "approved"
|
|
4507
|
+
? { ...run.checkpoint, status: "approved", approvedAt: decidedAt }
|
|
4508
|
+
: { ...run.checkpoint, status: "rejected", rejectedAt: decidedAt };
|
|
3874
4509
|
run.checkpoint = checkpoint;
|
|
3875
4510
|
run.updatedAt = decidedAt;
|
|
3876
4511
|
return {
|
|
@@ -3879,7 +4514,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3879
4514
|
};
|
|
3880
4515
|
}
|
|
3881
4516
|
const location = paramsWithResolvedCwd.dir
|
|
3882
|
-
? resolveAsyncRunLocation(paramsWithResolvedCwd,
|
|
4517
|
+
? resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results)
|
|
3883
4518
|
: resolved?.kind === "async"
|
|
3884
4519
|
? resolved.location
|
|
3885
4520
|
: undefined;
|
|
@@ -3905,7 +4540,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3905
4540
|
}
|
|
3906
4541
|
}
|
|
3907
4542
|
if (action === "resume") {
|
|
3908
|
-
return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
|
|
4543
|
+
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
|
|
3909
4544
|
}
|
|
3910
4545
|
if (action === "steer") {
|
|
3911
4546
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
@@ -3914,9 +4549,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3914
4549
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
3915
4550
|
if (paramsWithResolvedCwd.dir) {
|
|
3916
4551
|
try {
|
|
3917
|
-
const location = resolveAsyncRunLocation(paramsWithResolvedCwd,
|
|
4552
|
+
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
|
|
3918
4553
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
3919
|
-
|
|
4554
|
+
if (location.asyncDir) {
|
|
4555
|
+
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
4556
|
+
if (unsupported) return unsupported;
|
|
4557
|
+
}
|
|
4558
|
+
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
3920
4559
|
state: deps.state,
|
|
3921
4560
|
runId,
|
|
3922
4561
|
message,
|
|
@@ -3928,10 +4567,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3928
4567
|
? {}
|
|
3929
4568
|
: {
|
|
3930
4569
|
recover: ({ absoluteDeadlineAt, ...limits }) =>
|
|
3931
|
-
resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, parentModel: requestParentModel, absoluteDeadlineAt }),
|
|
4570
|
+
resumeAsyncRun(omitUndefinedProperties({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, parentModel: requestParentModel, absoluteDeadlineAt })),
|
|
3932
4571
|
}
|
|
3933
4572
|
),
|
|
3934
|
-
});
|
|
4573
|
+
}));
|
|
3935
4574
|
} catch (error) {
|
|
3936
4575
|
const text = error instanceof Error ? error.message : String(error);
|
|
3937
4576
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -3940,15 +4579,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3940
4579
|
if (!targetRunId) return { content: [{ type: "text", text: "action='steer' requires id or dir." }], isError: true, details: { mode: "management", results: [] } };
|
|
3941
4580
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
3942
4581
|
try {
|
|
3943
|
-
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
4582
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
3944
4583
|
} catch (error) {
|
|
3945
4584
|
const text = error instanceof Error ? error.message : String(error);
|
|
3946
4585
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
3947
4586
|
}
|
|
3948
|
-
if (resolved?.kind === "nested") return steerNestedRun({ target: resolved, message, index: paramsWithResolvedCwd.index, signal });
|
|
4587
|
+
if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, index: paramsWithResolvedCwd.index, signal }));
|
|
3949
4588
|
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: [] } };
|
|
3950
4589
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3951
|
-
|
|
4590
|
+
if (resolved.location.asyncDir) {
|
|
4591
|
+
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
4592
|
+
if (unsupported) return unsupported;
|
|
4593
|
+
}
|
|
4594
|
+
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
3952
4595
|
state: deps.state,
|
|
3953
4596
|
runId: resolved.id,
|
|
3954
4597
|
message,
|
|
@@ -3960,22 +4603,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3960
4603
|
? {}
|
|
3961
4604
|
: {
|
|
3962
4605
|
recover: ({ absoluteDeadlineAt, ...limits }) =>
|
|
3963
|
-
resumeAsyncRun({
|
|
4606
|
+
resumeAsyncRun(omitUndefinedProperties({
|
|
3964
4607
|
params: { ...limits, action: "resume", id: resolved!.id, message },
|
|
3965
4608
|
requestCwd,
|
|
3966
4609
|
ctx,
|
|
3967
4610
|
deps,
|
|
3968
4611
|
parentModel: requestParentModel,
|
|
3969
4612
|
absoluteDeadlineAt,
|
|
3970
|
-
}),
|
|
4613
|
+
})),
|
|
3971
4614
|
}
|
|
3972
4615
|
),
|
|
3973
|
-
});
|
|
4616
|
+
}));
|
|
3974
4617
|
}
|
|
3975
4618
|
if (action === "append-step") {
|
|
3976
|
-
return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
|
|
4619
|
+
return appendStepToAsyncChain(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
|
|
3977
4620
|
}
|
|
3978
|
-
if (action
|
|
4621
|
+
if (action.startsWith("schedule.")) {
|
|
4622
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4623
|
+
return {
|
|
4624
|
+
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
4625
|
+
isError: true,
|
|
4626
|
+
details: { mode: "management", results: [] },
|
|
4627
|
+
};
|
|
4628
|
+
}
|
|
3979
4629
|
if (!deps.handleScheduledRunAction) {
|
|
3980
4630
|
return {
|
|
3981
4631
|
content: [{ type: "text", text: `Action '${action}' is not available in this subagent context.` }],
|
|
@@ -3987,11 +4637,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3987
4637
|
}
|
|
3988
4638
|
if (action === "stop") {
|
|
3989
4639
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
4640
|
+
const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
|
|
4641
|
+
if (workflowController) {
|
|
4642
|
+
workflowController.abort(new Error("Workflow stopped by user."));
|
|
4643
|
+
return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
|
|
4644
|
+
}
|
|
3990
4645
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
3991
4646
|
if (paramsWithResolvedCwd.dir) {
|
|
3992
4647
|
try {
|
|
3993
|
-
const location = resolveAsyncRunLocation(paramsWithResolvedCwd,
|
|
3994
|
-
|
|
4648
|
+
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
|
|
4649
|
+
const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location);
|
|
4650
|
+
return stopResult ?? { content: [{ type: "text", text: `No running or queued async run was found for '${targetRunId ?? paramsWithResolvedCwd.dir}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3995
4651
|
} catch (error) {
|
|
3996
4652
|
const text = error instanceof Error ? error.message : String(error);
|
|
3997
4653
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -3999,13 +4655,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3999
4655
|
}
|
|
4000
4656
|
if (!targetRunId) return { content: [{ type: "text", text: "action='stop' requires id or dir." }], isError: true, details: { mode: "management", results: [] } };
|
|
4001
4657
|
try {
|
|
4002
|
-
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
4658
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
4003
4659
|
} catch (error) {
|
|
4004
4660
|
const message = error instanceof Error ? error.message : String(error);
|
|
4005
4661
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
4006
4662
|
}
|
|
4007
4663
|
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: [] } };
|
|
4008
4664
|
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: [] } };
|
|
4665
|
+
if (resolved?.kind === "async") {
|
|
4666
|
+
const existingStatus = readStatus(resolved.location.asyncDir ?? "");
|
|
4667
|
+
if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
|
|
4668
|
+
return { content: [{ type: "text", text: `Workflow ${resolved.id} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4009
4671
|
const stopResult = stopAsyncRun(
|
|
4010
4672
|
deps.state,
|
|
4011
4673
|
resolved?.kind === "async" ? resolved.id : targetRunId,
|
|
@@ -4024,7 +4686,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4024
4686
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
4025
4687
|
if (targetRunId) {
|
|
4026
4688
|
try {
|
|
4027
|
-
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
4689
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
4028
4690
|
} catch (error) {
|
|
4029
4691
|
const message = error instanceof Error ? error.message : String(error);
|
|
4030
4692
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -4036,7 +4698,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4036
4698
|
const interrupted = foreground.interrupt();
|
|
4037
4699
|
if (interrupted) {
|
|
4038
4700
|
foreground.updatedAt = Date.now();
|
|
4039
|
-
foreground.currentActivityState
|
|
4701
|
+
delete foreground.currentActivityState;
|
|
4040
4702
|
return {
|
|
4041
4703
|
content: [{ type: "text", text: `Interrupt requested for foreground run ${foreground.runId}.` }],
|
|
4042
4704
|
details: { mode: "management", results: [] },
|
|
@@ -4171,7 +4833,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4171
4833
|
const spawnPreflight = preflightSpawnBudget(
|
|
4172
4834
|
deps.state,
|
|
4173
4835
|
deps.config,
|
|
4174
|
-
|
|
4836
|
+
requestSessionId,
|
|
4175
4837
|
requestedSpawns,
|
|
4176
4838
|
);
|
|
4177
4839
|
if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
|
|
@@ -4216,6 +4878,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4216
4878
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
4217
4879
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
4218
4880
|
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
4881
|
+
const selectedAgentNames = hasSingle
|
|
4882
|
+
? [effectiveParams.agent!]
|
|
4883
|
+
: hasTasks
|
|
4884
|
+
? (effectiveParams.tasks ?? []).map((task) => task.agent)
|
|
4885
|
+
: (effectiveParams.chain ?? []).flatMap((step) => getStepAgents(step as ChainStep));
|
|
4886
|
+
const externalAgent = selectedAgentNames
|
|
4887
|
+
.map((name) => agents.find((agent) => agent.name === name))
|
|
4888
|
+
.find((agent) => agent?.runner?.type === "external-cli");
|
|
4889
|
+
if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
|
|
4890
|
+
return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='external-cli', which currently supports async/background execution only. Omit async or pass async:true; clarify and foregroundOnly are unsupported.`);
|
|
4891
|
+
}
|
|
4219
4892
|
const foregroundTimeout = resolveForegroundTimeout(
|
|
4220
4893
|
effectiveParams,
|
|
4221
4894
|
effectiveAsync ? undefined : DEFAULT_FOREGROUND_TIMEOUT_MS,
|
|
@@ -4223,11 +4896,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4223
4896
|
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
4224
4897
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
4225
4898
|
|
|
4226
|
-
const artifactConfig: ArtifactConfig = {
|
|
4899
|
+
const artifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
4227
4900
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
4228
4901
|
enabled: effectiveParams.artifacts !== false,
|
|
4229
4902
|
dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
4230
|
-
};
|
|
4903
|
+
});
|
|
4231
4904
|
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
4232
4905
|
|
|
4233
4906
|
let sessionRoot: string;
|
|
@@ -4284,15 +4957,48 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4284
4957
|
}, contextPolicy.contextSummary))
|
|
4285
4958
|
: undefined;
|
|
4286
4959
|
|
|
4960
|
+
let missionBinding: MissionLaunchBinding | undefined;
|
|
4961
|
+
let missionWarning: string | undefined;
|
|
4962
|
+
const explicitMission = effectiveParams.missionId !== undefined || effectiveParams.mission !== undefined;
|
|
4963
|
+
try {
|
|
4964
|
+
missionBinding = prepareMissionLaunch({
|
|
4965
|
+
params: effectiveParams,
|
|
4966
|
+
projectRoot: effectiveCwd,
|
|
4967
|
+
...(deps.config.missions ? { config: deps.config.missions } : {}),
|
|
4968
|
+
ownerSessionId: requestSessionId,
|
|
4969
|
+
});
|
|
4970
|
+
} catch (error) {
|
|
4971
|
+
if (explicitMission) return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
4972
|
+
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
4973
|
+
}
|
|
4974
|
+
|
|
4975
|
+
const attachMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
4976
|
+
if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
|
|
4977
|
+
try {
|
|
4978
|
+
return attachMissionToLaunchResult({ binding: missionBinding, result });
|
|
4979
|
+
} catch (error) {
|
|
4980
|
+
const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
|
|
4981
|
+
if (explicitMission) {
|
|
4982
|
+
return {
|
|
4983
|
+
...result,
|
|
4984
|
+
isError: true,
|
|
4985
|
+
content: [...result.content, { type: "text", text: warning }],
|
|
4986
|
+
details: { ...result.details, missionWarning: warning },
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4989
|
+
return { ...result, details: { ...result.details, missionWarning: warning } };
|
|
4990
|
+
}
|
|
4991
|
+
};
|
|
4992
|
+
|
|
4287
4993
|
const reservation = reserveSpawnBudget(
|
|
4288
4994
|
deps.state,
|
|
4289
4995
|
deps.config,
|
|
4290
|
-
|
|
4996
|
+
requestSessionId,
|
|
4291
4997
|
requestedSpawns,
|
|
4292
4998
|
);
|
|
4293
|
-
if (reservation.error) return spawnBudgetErrorResult(reservation.error, foregroundMode);
|
|
4999
|
+
if (reservation.error) return attachMission(spawnBudgetErrorResult(reservation.error, foregroundMode));
|
|
4294
5000
|
|
|
4295
|
-
const execData: ExecutionContextData = {
|
|
5001
|
+
const execData: ExecutionContextData = omitUndefinedProperties({
|
|
4296
5002
|
params: effectiveParams,
|
|
4297
5003
|
effectiveCwd,
|
|
4298
5004
|
ctx,
|
|
@@ -4322,18 +5028,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4322
5028
|
contextPolicy,
|
|
4323
5029
|
modelScope,
|
|
4324
5030
|
parentModel: requestParentModel,
|
|
4325
|
-
parentSessionId:
|
|
4326
|
-
|
|
4327
|
-
|
|
5031
|
+
parentSessionId: requestSessionId,
|
|
5032
|
+
parentPiSessionId: requestPiSessionId,
|
|
5033
|
+
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
|
|
5034
|
+
});
|
|
4328
5035
|
|
|
4329
5036
|
const foregroundDescription = effectiveParams.task?.trim()
|
|
4330
5037
|
|| effectiveParams.tasks?.[0]?.task?.trim()
|
|
4331
5038
|
|| (effectiveParams.chain ? firstRawChainTask(effectiveParams.chain)?.trim() : undefined);
|
|
4332
5039
|
const foregroundControl: ForegroundRunControl | undefined = effectiveAsync
|
|
4333
5040
|
? undefined
|
|
4334
|
-
: {
|
|
5041
|
+
: compactOptional<ForegroundRunControl>({
|
|
4335
5042
|
runId,
|
|
4336
|
-
|
|
5043
|
+
sessionId: requestSessionId,
|
|
4337
5044
|
mode: foregroundMode,
|
|
4338
5045
|
startedAt: Date.now(),
|
|
4339
5046
|
updatedAt: Date.now(),
|
|
@@ -4347,7 +5054,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4347
5054
|
schedulingOwners: 1,
|
|
4348
5055
|
nestedRoute,
|
|
4349
5056
|
interrupt: undefined,
|
|
4350
|
-
};
|
|
5057
|
+
});
|
|
4351
5058
|
if (foregroundControl) {
|
|
4352
5059
|
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
4353
5060
|
deps.state.lastForegroundControlId = runId;
|
|
@@ -4367,21 +5074,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4367
5074
|
const errorText = result?.isError
|
|
4368
5075
|
? result.content.find((item) => item.type === "text")?.text
|
|
4369
5076
|
: undefined;
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
5077
|
+
let startedLaunches: StaticLaunchSummary[];
|
|
5078
|
+
try {
|
|
5079
|
+
startedLaunches = collectStaticLaunchSummaries({
|
|
5080
|
+
params: effectiveParams,
|
|
5081
|
+
agents,
|
|
5082
|
+
parentModel: requestParentModel,
|
|
5083
|
+
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
5084
|
+
currentProvider: requestParentModel?.provider,
|
|
5085
|
+
modelScope,
|
|
5086
|
+
thinkingOverrideForTask: forkThinkingOverrideForTask,
|
|
5087
|
+
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
5088
|
+
});
|
|
5089
|
+
} catch (error) {
|
|
5090
|
+
console.error("Failed to resolve nested foreground launch metadata:", error);
|
|
5091
|
+
startedLaunches = selectedAgentNames.map((agent) => ({ agent }));
|
|
5092
|
+
}
|
|
5093
|
+
const agentsForSummary = startedLaunches.map((launch) => launch.agent);
|
|
4375
5094
|
const leafIntercomTarget = intercomBridge.active && agentsForSummary[0]
|
|
4376
5095
|
? resolveSubagentIntercomTarget(runId, agentsForSummary[0], 0)
|
|
4377
5096
|
: undefined;
|
|
4378
5097
|
try {
|
|
4379
|
-
writeNestedEvent(inheritedNestedRoute, {
|
|
5098
|
+
writeNestedEvent(inheritedNestedRoute, compactOptional<Parameters<typeof writeNestedEvent>[1]>({
|
|
4380
5099
|
type,
|
|
4381
5100
|
ts: now,
|
|
4382
5101
|
parentRunId: nestedParentAddress.parentRunId,
|
|
4383
5102
|
parentStepIndex: nestedParentAddress.parentStepIndex,
|
|
4384
|
-
child: {
|
|
5103
|
+
child: compactOptional<NestedRunSummary>({
|
|
4385
5104
|
id: runId,
|
|
4386
5105
|
parentRunId: nestedParentAddress.parentRunId,
|
|
4387
5106
|
parentStepIndex: nestedParentAddress.parentStepIndex,
|
|
@@ -4395,19 +5114,32 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4395
5114
|
state,
|
|
4396
5115
|
agent: agentsForSummary[0],
|
|
4397
5116
|
agents: agentsForSummary,
|
|
5117
|
+
...(agentsForSummary.length === 1 && (type === "subagent.nested.started" ? startedLaunches[0]?.model : details?.results[0]?.model) ? { model: type === "subagent.nested.started" ? startedLaunches[0]?.model : details?.results[0]?.model } : {}),
|
|
5118
|
+
...(agentsForSummary.length === 1 && (type === "subagent.nested.started" ? startedLaunches[0]?.thinking : details?.results[0]?.thinking) ? { thinking: type === "subagent.nested.started" ? startedLaunches[0]?.thinking : details?.results[0]?.thinking } : {}),
|
|
4398
5119
|
startedAt: foregroundControl?.startedAt ?? now,
|
|
4399
5120
|
...(state !== "running" ? { endedAt: now } : {}),
|
|
4400
5121
|
lastUpdate: now,
|
|
4401
5122
|
...(details?.totalCost ? { totalCost: details.totalCost } : {}),
|
|
4402
5123
|
...(errorText ? { error: errorText } : {}),
|
|
4403
|
-
...(
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
5124
|
+
...(type === "subagent.nested.started"
|
|
5125
|
+
? { steps: startedLaunches.map((launch) => ({
|
|
5126
|
+
agent: launch.agent,
|
|
5127
|
+
status: "running" as const,
|
|
5128
|
+
...(launch.model ? { model: launch.model } : {}),
|
|
5129
|
+
...(launch.thinking ? { thinking: launch.thinking } : {}),
|
|
5130
|
+
})) }
|
|
5131
|
+
: details?.results.length
|
|
5132
|
+
? { steps: details.results.map((child) => ({
|
|
5133
|
+
agent: child.agent,
|
|
5134
|
+
status: child.interrupted || child.detached ? "paused" as const : child.exitCode === 0 ? "complete" as const : "failed" as const,
|
|
5135
|
+
...(child.model ? { model: child.model } : {}),
|
|
5136
|
+
...(child.thinking ? { thinking: child.thinking } : {}),
|
|
5137
|
+
...(child.sessionFile ? { sessionFile: child.sessionFile } : {}),
|
|
5138
|
+
...(child.error ? { error: child.error } : {}),
|
|
5139
|
+
})) }
|
|
5140
|
+
: {}),
|
|
5141
|
+
}),
|
|
5142
|
+
}));
|
|
4411
5143
|
} catch (error) {
|
|
4412
5144
|
console.error("Failed to emit nested foreground status event:", error);
|
|
4413
5145
|
}
|
|
@@ -4416,7 +5148,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4416
5148
|
let nestedForegroundStarted = false;
|
|
4417
5149
|
try {
|
|
4418
5150
|
const asyncResult = runAsyncPath(execData, deps);
|
|
4419
|
-
if (asyncResult) return withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
5151
|
+
if (asyncResult) return attachMission(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
4420
5152
|
if (foregroundControl) {
|
|
4421
5153
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
4422
5154
|
nestedForegroundStarted = true;
|
|
@@ -4424,22 +5156,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4424
5156
|
if (hasChain && effectiveParams.chain) {
|
|
4425
5157
|
const result = await runChainPath(execData, deps);
|
|
4426
5158
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
4427
|
-
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
5159
|
+
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
4428
5160
|
}
|
|
4429
5161
|
if (hasTasks && effectiveParams.tasks) {
|
|
4430
5162
|
const result = await runParallelPath(execData, deps);
|
|
4431
5163
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
4432
|
-
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
5164
|
+
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
4433
5165
|
}
|
|
4434
5166
|
if (hasSingle) {
|
|
4435
5167
|
const result = await runSinglePath(execData, deps);
|
|
4436
5168
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
4437
|
-
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
5169
|
+
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
4438
5170
|
}
|
|
4439
5171
|
} catch (error) {
|
|
4440
5172
|
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
4441
5173
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
4442
|
-
return errorResult;
|
|
5174
|
+
return attachMission(errorResult);
|
|
4443
5175
|
} finally {
|
|
4444
5176
|
if (foregroundControl) {
|
|
4445
5177
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
@@ -4497,5 +5229,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4497
5229
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
4498
5230
|
};
|
|
4499
5231
|
|
|
4500
|
-
|
|
5232
|
+
const executeScheduled = (
|
|
5233
|
+
id: string,
|
|
5234
|
+
params: SubagentParamsLike,
|
|
5235
|
+
signal: AbortSignal,
|
|
5236
|
+
ctx: ExtensionContext,
|
|
5237
|
+
) => {
|
|
5238
|
+
const ownerSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5239
|
+
let ownerExecutor = scheduledOwnerExecutors.get(ownerSessionId);
|
|
5240
|
+
if (!ownerExecutor) {
|
|
5241
|
+
ownerExecutor = createSubagentExecutor({
|
|
5242
|
+
...deps,
|
|
5243
|
+
state: createScheduledOwnerState(deps.state, ownerSessionId, ctx),
|
|
5244
|
+
});
|
|
5245
|
+
scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
|
|
5246
|
+
}
|
|
5247
|
+
return ownerExecutor.execute(id, params, signal, undefined, ctx);
|
|
5248
|
+
};
|
|
5249
|
+
|
|
5250
|
+
return { execute: executeWithSingleDispatchGuard, executeDelegated, executeScheduled };
|
|
4501
5251
|
}
|