pi-subagents 0.40.0 → 0.42.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 +97 -0
- package/README.md +54 -1879
- package/agents/oracle.md +1 -0
- package/inspector-runner.mjs +11 -0
- package/package.json +13 -5
- 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 +9 -7
- package/skills/pi-subagents/references/constraints-and-recipes.md +24 -32
- package/skills/pi-subagents/references/execution-controls.md +99 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +23 -30
- 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 +374 -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 +110 -50
- package/src/runs/background/async-job-tracker.ts +27 -4
- 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 +34 -9
- package/src/runs/foreground/subagent-executor.ts +1036 -274
- 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/mcp-direct-tool-allowlist.ts +2 -0
- 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 +468 -109
- 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 +143 -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 +57 -25
- package/src/tui/render.ts +368 -152
- package/src/types/pi-runtime-compat.d.ts +8 -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, getProjectArtifactPackagingWarning } 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,
|
|
@@ -1518,6 +1631,7 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1518
1631
|
mode: SubagentRunMode;
|
|
1519
1632
|
details: Details;
|
|
1520
1633
|
nestedChildren?: NestedRunSummary[];
|
|
1634
|
+
preserveDetailsOutputs?: boolean;
|
|
1521
1635
|
}): Promise<{ text: string; details: Details } | null> {
|
|
1522
1636
|
const payload = await emitForegroundResultIntercom({
|
|
1523
1637
|
pi: input.pi,
|
|
@@ -1532,7 +1646,7 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1532
1646
|
if (!payload) return null;
|
|
1533
1647
|
return {
|
|
1534
1648
|
text: formatSubagentResultReceipt({ mode: input.mode, runId: input.runId, payload }),
|
|
1535
|
-
details: stripDetailsOutputsForIntercomReceipt(input.details),
|
|
1649
|
+
details: input.preserveDetailsOutputs ? input.details : stripDetailsOutputsForIntercomReceipt(input.details),
|
|
1536
1650
|
};
|
|
1537
1651
|
}
|
|
1538
1652
|
|
|
@@ -1551,7 +1665,7 @@ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentCo
|
|
|
1551
1665
|
if (params.agent) {
|
|
1552
1666
|
const result = resolve(params.agent);
|
|
1553
1667
|
if (result.error) return { error: result.error };
|
|
1554
|
-
params = { ...params, agent: result.name };
|
|
1668
|
+
params = omitUndefinedProperties({ ...params, agent: result.name });
|
|
1555
1669
|
}
|
|
1556
1670
|
if (params.tasks) {
|
|
1557
1671
|
const tasks: TaskParam[] = [];
|
|
@@ -1568,7 +1682,7 @@ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentCo
|
|
|
1568
1682
|
for (let index = 0; index < params.chain.length; index++) {
|
|
1569
1683
|
const step = params.chain[index]!;
|
|
1570
1684
|
if (isParallelStep(step)) {
|
|
1571
|
-
const parallel:
|
|
1685
|
+
const parallel: ParallelTaskItem[] = [];
|
|
1572
1686
|
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
1573
1687
|
const task = step.parallel[taskIndex]!;
|
|
1574
1688
|
const result = resolve(task.agent, `step ${index + 1}, task ${taskIndex + 1}`);
|
|
@@ -1618,7 +1732,7 @@ function validateExecutionInput(
|
|
|
1618
1732
|
};
|
|
1619
1733
|
}
|
|
1620
1734
|
|
|
1621
|
-
const acceptanceErrors = validateExecutionAcceptance(params);
|
|
1735
|
+
const acceptanceErrors = validateExecutionAcceptance(params as Parameters<typeof validateExecutionAcceptance>[0]);
|
|
1622
1736
|
if (acceptanceErrors.length > 0) {
|
|
1623
1737
|
return {
|
|
1624
1738
|
content: [{ type: "text", text: acceptanceErrors.join(" ") }],
|
|
@@ -1707,7 +1821,7 @@ function validateExecutionInput(
|
|
|
1707
1821
|
function validateExecutionChainBindings(params: SubagentParamsLike, dynamicFanoutMaxItems?: number): AgentToolResult<Details> | null {
|
|
1708
1822
|
if ((params.chain?.length ?? 0) === 0) return null;
|
|
1709
1823
|
try {
|
|
1710
|
-
validateChainOutputBindingsWithContext(params.chain as ChainStep[], { maxItems: dynamicFanoutMaxItems });
|
|
1824
|
+
validateChainOutputBindingsWithContext(params.chain as ChainStep[], dynamicFanoutMaxItems === undefined ? {} : { maxItems: dynamicFanoutMaxItems });
|
|
1711
1825
|
} catch (error) {
|
|
1712
1826
|
if (error instanceof ChainOutputValidationError) {
|
|
1713
1827
|
return {
|
|
@@ -1722,6 +1836,7 @@ function validateExecutionChainBindings(params: SubagentParamsLike, dynamicFanou
|
|
|
1722
1836
|
}
|
|
1723
1837
|
|
|
1724
1838
|
function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
1839
|
+
if (params.workflowScript !== undefined) return "workflow";
|
|
1725
1840
|
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
1726
1841
|
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
1727
1842
|
if (params.agent) return "single";
|
|
@@ -1745,12 +1860,12 @@ function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: Ag
|
|
|
1745
1860
|
const requestedAgentNames = collectRequestedAgentNames(params);
|
|
1746
1861
|
const contextSummary = summarizeContextModes(requestedAgentNames.map((name) => contextForAgent(name)));
|
|
1747
1862
|
const usesFork = contextSummary === "fork" || contextSummary === "mixed";
|
|
1748
|
-
return {
|
|
1863
|
+
return omitUndefinedProperties({
|
|
1749
1864
|
params,
|
|
1750
1865
|
contextForAgent,
|
|
1751
1866
|
contextSummary,
|
|
1752
1867
|
usesFork,
|
|
1753
|
-
};
|
|
1868
|
+
});
|
|
1754
1869
|
}
|
|
1755
1870
|
|
|
1756
1871
|
function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultContextPolicy {
|
|
@@ -1826,7 +1941,8 @@ function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?:
|
|
|
1826
1941
|
if (rawTimeout !== undefined && rawMaxRuntime !== undefined && rawTimeout !== rawMaxRuntime) {
|
|
1827
1942
|
return { error: "timeoutMs and maxRuntimeMs are aliases; provide only one value or use the same value for both." };
|
|
1828
1943
|
}
|
|
1829
|
-
|
|
1944
|
+
const timeoutMs = rawTimeout ?? rawMaxRuntime;
|
|
1945
|
+
return timeoutMs === undefined ? {} : { timeoutMs };
|
|
1830
1946
|
}
|
|
1831
1947
|
|
|
1832
1948
|
function resolveToolBudget(
|
|
@@ -1835,7 +1951,7 @@ function resolveToolBudget(
|
|
|
1835
1951
|
options?: { minimumHard?: 0 | 1 },
|
|
1836
1952
|
): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
1837
1953
|
const resolved = validateToolBudgetConfig(raw, label, options);
|
|
1838
|
-
return { toolBudget: resolved.budget, error: resolved.error };
|
|
1954
|
+
return { ...(resolved.budget === undefined ? {} : { toolBudget: resolved.budget }), ...(resolved.error === undefined ? {} : { error: resolved.error }) };
|
|
1839
1955
|
}
|
|
1840
1956
|
|
|
1841
1957
|
function resolveEffectiveToolBudget(input: { stepBudget?: ToolBudgetConfig; runBudget?: ResolvedToolBudget; agentBudget?: ToolBudgetConfig; configBudget?: ToolBudgetConfig }): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
@@ -1869,7 +1985,7 @@ function expandChainParallelCounts(chain: ChainStep[]): { chain?: ChainStep[]; e
|
|
|
1869
1985
|
expandedChain.push(step);
|
|
1870
1986
|
continue;
|
|
1871
1987
|
}
|
|
1872
|
-
const expandedParallel = [];
|
|
1988
|
+
const expandedParallel: ParallelTaskItem[] = [];
|
|
1873
1989
|
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
1874
1990
|
const task = step.parallel[taskIndex]!;
|
|
1875
1991
|
const rawCount = (task as typeof task & { count?: unknown }).count;
|
|
@@ -1892,14 +2008,14 @@ function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?:
|
|
|
1892
2008
|
if (expandedTasks.error) {
|
|
1893
2009
|
return { error: buildRequestedModeError(params, expandedTasks.error) };
|
|
1894
2010
|
}
|
|
1895
|
-
return { params: { ...params, tasks: expandedTasks.tasks } };
|
|
2011
|
+
return { params: { ...params, ...(expandedTasks.tasks === undefined ? {} : { tasks: expandedTasks.tasks }) } };
|
|
1896
2012
|
}
|
|
1897
2013
|
if (params.chain) {
|
|
1898
2014
|
const expandedChain = expandChainParallelCounts(params.chain);
|
|
1899
2015
|
if (expandedChain.error) {
|
|
1900
2016
|
return { error: buildRequestedModeError(params, expandedChain.error) };
|
|
1901
2017
|
}
|
|
1902
|
-
return { params: { ...params, chain: expandedChain.chain } };
|
|
2018
|
+
return { params: { ...params, ...(expandedChain.chain === undefined ? {} : { chain: expandedChain.chain }) } };
|
|
1903
2019
|
}
|
|
1904
2020
|
return { params };
|
|
1905
2021
|
}
|
|
@@ -2005,6 +2121,87 @@ function collectChainThinkingOverrides(
|
|
|
2005
2121
|
return thinkingOverrides;
|
|
2006
2122
|
}
|
|
2007
2123
|
|
|
2124
|
+
type StaticLaunchSummary = { agent: string; model?: string; thinking?: string };
|
|
2125
|
+
|
|
2126
|
+
function resolveStaticLaunchSummary(input: {
|
|
2127
|
+
agent: string;
|
|
2128
|
+
index: number;
|
|
2129
|
+
explicitModel?: string;
|
|
2130
|
+
agents: AgentConfig[];
|
|
2131
|
+
parentModel?: ParentModel;
|
|
2132
|
+
availableModels: ModelInfo[];
|
|
2133
|
+
currentProvider?: string;
|
|
2134
|
+
modelScope?: ModelScopeConfig;
|
|
2135
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2136
|
+
}): StaticLaunchSummary {
|
|
2137
|
+
const agentConfig = input.agents.find((agent) => agent.name === input.agent);
|
|
2138
|
+
const model = resolveEffectiveSubagentModel(
|
|
2139
|
+
input.explicitModel,
|
|
2140
|
+
agentConfig?.model,
|
|
2141
|
+
input.parentModel,
|
|
2142
|
+
input.availableModels,
|
|
2143
|
+
input.currentProvider,
|
|
2144
|
+
input.modelScope === undefined ? {} : { scope: input.modelScope },
|
|
2145
|
+
);
|
|
2146
|
+
const thinkingOverride = input.thinkingOverrideForTask(input.agent, input.index, model);
|
|
2147
|
+
const thinking = resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
|
|
2148
|
+
return {
|
|
2149
|
+
agent: input.agent,
|
|
2150
|
+
...(model ? { model } : {}),
|
|
2151
|
+
...(thinking ? { thinking } : {}),
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
function collectStaticLaunchSummaries(input: {
|
|
2156
|
+
params: SubagentParamsLike;
|
|
2157
|
+
agents: AgentConfig[];
|
|
2158
|
+
parentModel?: ParentModel;
|
|
2159
|
+
availableModels: ModelInfo[];
|
|
2160
|
+
currentProvider?: string;
|
|
2161
|
+
modelScope?: ModelScopeConfig;
|
|
2162
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2163
|
+
dynamicFanoutMaxItems?: number;
|
|
2164
|
+
}): StaticLaunchSummary[] {
|
|
2165
|
+
const summary = (agent: string, index: number, explicitModel?: string) => resolveStaticLaunchSummary({
|
|
2166
|
+
agent,
|
|
2167
|
+
index,
|
|
2168
|
+
explicitModel,
|
|
2169
|
+
agents: input.agents,
|
|
2170
|
+
parentModel: input.parentModel,
|
|
2171
|
+
availableModels: input.availableModels,
|
|
2172
|
+
currentProvider: input.currentProvider,
|
|
2173
|
+
modelScope: input.modelScope,
|
|
2174
|
+
thinkingOverrideForTask: input.thinkingOverrideForTask,
|
|
2175
|
+
});
|
|
2176
|
+
if (input.params.tasks) return input.params.tasks.map((task, index) => summary(task.agent, index, task.model));
|
|
2177
|
+
if (input.params.chain?.length) {
|
|
2178
|
+
const launches: StaticLaunchSummary[] = [];
|
|
2179
|
+
let flatIndex = 0;
|
|
2180
|
+
for (const step of input.params.chain) {
|
|
2181
|
+
if (isParallelStep(step)) {
|
|
2182
|
+
for (const task of step.parallel) {
|
|
2183
|
+
launches.push(summary(task.agent, flatIndex, task.model));
|
|
2184
|
+
flatIndex++;
|
|
2185
|
+
}
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
if (isDynamicParallelStep(step)) {
|
|
2189
|
+
const maxItems = step.expand.maxItems ?? input.dynamicFanoutMaxItems ?? 0;
|
|
2190
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
2191
|
+
launches.push(summary(step.parallel.agent, flatIndex, step.parallel.model));
|
|
2192
|
+
flatIndex++;
|
|
2193
|
+
}
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
const sequential = step as SequentialStep;
|
|
2197
|
+
launches.push(summary(sequential.agent, flatIndex, sequential.model));
|
|
2198
|
+
flatIndex++;
|
|
2199
|
+
}
|
|
2200
|
+
return launches;
|
|
2201
|
+
}
|
|
2202
|
+
return input.params.agent ? [summary(input.params.agent, 0, input.params.model as string | undefined)] : [];
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2008
2205
|
function firstChainAgent(chain: ChainStep[]): string | undefined {
|
|
2009
2206
|
const first = chain[0];
|
|
2010
2207
|
if (!first) return undefined;
|
|
@@ -2032,34 +2229,34 @@ function resolveAsyncEventGoal(workflowTask: string | undefined, rawChain: Chain
|
|
|
2032
2229
|
function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
|
|
2033
2230
|
return chain.map((step, stepIndex) => {
|
|
2034
2231
|
if (isParallelStep(step)) {
|
|
2035
|
-
return {
|
|
2232
|
+
return compactOptional<ParallelStep>({
|
|
2036
2233
|
...step,
|
|
2037
|
-
parallel: step.parallel.map((task) => ({
|
|
2234
|
+
parallel: step.parallel.map((task) => compactOptional<ParallelTaskItem>({
|
|
2038
2235
|
...task,
|
|
2039
2236
|
task: shouldForkAgent(contextPolicy, task.agent)
|
|
2040
2237
|
? wrapForkTask(task.task ?? "{previous}")
|
|
2041
2238
|
: task.task,
|
|
2042
2239
|
})),
|
|
2043
|
-
};
|
|
2240
|
+
});
|
|
2044
2241
|
}
|
|
2045
2242
|
if (isDynamicParallelStep(step)) {
|
|
2046
|
-
return {
|
|
2243
|
+
return compactOptional<DynamicParallelStep>({
|
|
2047
2244
|
...step,
|
|
2048
|
-
parallel: {
|
|
2245
|
+
parallel: compactOptional<DynamicParallelStep["parallel"]>({
|
|
2049
2246
|
...step.parallel,
|
|
2050
2247
|
task: shouldForkAgent(contextPolicy, step.parallel.agent)
|
|
2051
2248
|
? wrapForkTask(step.parallel.task ?? "{previous}")
|
|
2052
2249
|
: step.parallel.task,
|
|
2053
|
-
},
|
|
2054
|
-
};
|
|
2250
|
+
}),
|
|
2251
|
+
});
|
|
2055
2252
|
}
|
|
2056
2253
|
const sequential = step as SequentialStep;
|
|
2057
|
-
return {
|
|
2254
|
+
return compactOptional<SequentialStep>({
|
|
2058
2255
|
...sequential,
|
|
2059
2256
|
task: shouldForkAgent(contextPolicy, sequential.agent)
|
|
2060
2257
|
? wrapForkTask(sequential.task ?? (stepIndex === 0 ? "{task}" : "{previous}"))
|
|
2061
2258
|
: sequential.task,
|
|
2062
|
-
};
|
|
2259
|
+
});
|
|
2063
2260
|
});
|
|
2064
2261
|
}
|
|
2065
2262
|
|
|
@@ -2159,16 +2356,17 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2159
2356
|
}
|
|
2160
2357
|
const id = randomUUID();
|
|
2161
2358
|
const parentModel = data.parentModel;
|
|
2162
|
-
const asyncCtx = {
|
|
2359
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2163
2360
|
pi: deps.pi,
|
|
2164
2361
|
cwd: ctx.cwd,
|
|
2165
|
-
currentSessionId:
|
|
2166
|
-
parentSessionId:
|
|
2362
|
+
currentSessionId: data.parentSessionId!,
|
|
2363
|
+
parentSessionId: data.parentPiSessionId,
|
|
2167
2364
|
currentModelProvider: parentModel?.provider,
|
|
2168
2365
|
currentModel: parentModel,
|
|
2169
2366
|
modelScope: data.modelScope,
|
|
2170
2367
|
interactive: ctx.hasUI,
|
|
2171
|
-
|
|
2368
|
+
permissions: deps.config.permissions,
|
|
2369
|
+
});
|
|
2172
2370
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
2173
2371
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
2174
2372
|
const currentProvider = parentModel?.provider;
|
|
@@ -2177,7 +2375,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2177
2375
|
|
|
2178
2376
|
if (hasTasks && params.tasks) {
|
|
2179
2377
|
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
2180
|
-
const parallelTasks = params.tasks.map((task, index) => ({
|
|
2378
|
+
const parallelTasks = params.tasks.map((task, index) => compactOptional<ParallelTaskItem>({
|
|
2181
2379
|
agent: task.agent,
|
|
2182
2380
|
task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
|
|
2183
2381
|
cwd: task.cwd,
|
|
@@ -2192,12 +2390,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2192
2390
|
...(task.agentContract !== undefined ? { agentContract: task.agentContract } : {}),
|
|
2193
2391
|
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
2194
2392
|
}));
|
|
2195
|
-
return executeAsyncChain(id, {
|
|
2196
|
-
chain: [{
|
|
2393
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2394
|
+
chain: [compactOptional<ParallelStep>({
|
|
2197
2395
|
parallel: parallelTasks,
|
|
2198
2396
|
concurrency: resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency),
|
|
2199
2397
|
worktree: params.worktree,
|
|
2200
|
-
}],
|
|
2398
|
+
})],
|
|
2201
2399
|
resultMode: "parallel",
|
|
2202
2400
|
goal: params.tasks[0]?.task ?? "",
|
|
2203
2401
|
agents,
|
|
@@ -2230,7 +2428,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2230
2428
|
configToolBudget: data.configToolBudget,
|
|
2231
2429
|
capabilityCeiling: data.capabilityCeiling,
|
|
2232
2430
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2233
|
-
|
|
2431
|
+
parentWorkflowRunId: params.workflowParentRunId,
|
|
2432
|
+
workflowKey: params.workflowKey,
|
|
2433
|
+
}));
|
|
2234
2434
|
}
|
|
2235
2435
|
|
|
2236
2436
|
if (hasChain && params.chain) {
|
|
@@ -2238,7 +2438,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2238
2438
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
2239
2439
|
const rawChain = params.chain as ChainStep[];
|
|
2240
2440
|
const chain = wrapChainTasksForFork(rawChain, contextPolicy);
|
|
2241
|
-
return executeAsyncChain(id, {
|
|
2441
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2242
2442
|
chain,
|
|
2243
2443
|
task: params.task,
|
|
2244
2444
|
goal: resolveAsyncEventGoal(params.task, rawChain),
|
|
@@ -2273,7 +2473,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2273
2473
|
configToolBudget: data.configToolBudget,
|
|
2274
2474
|
capabilityCeiling: data.capabilityCeiling,
|
|
2275
2475
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2276
|
-
|
|
2476
|
+
parentWorkflowRunId: params.workflowParentRunId,
|
|
2477
|
+
workflowKey: params.workflowKey,
|
|
2478
|
+
}));
|
|
2277
2479
|
}
|
|
2278
2480
|
|
|
2279
2481
|
if (hasSingle) {
|
|
@@ -2291,8 +2493,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2291
2493
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
2292
2494
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
2293
2495
|
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, {
|
|
2496
|
+
const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2497
|
+
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2296
2498
|
agent: params.agent!,
|
|
2297
2499
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
2298
2500
|
goal: params.task ?? "",
|
|
@@ -2331,7 +2533,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2331
2533
|
usageBudget: data.usageBudget,
|
|
2332
2534
|
configToolBudget: data.configToolBudget,
|
|
2333
2535
|
capabilityCeiling: data.capabilityCeiling,
|
|
2334
|
-
|
|
2536
|
+
parentWorkflowRunId: params.workflowParentRunId,
|
|
2537
|
+
workflowKey: params.workflowKey,
|
|
2538
|
+
}));
|
|
2335
2539
|
}
|
|
2336
2540
|
|
|
2337
2541
|
return null;
|
|
@@ -2364,8 +2568,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2364
2568
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
2365
2569
|
const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
|
|
2366
2570
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
2367
|
-
const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : { ...ctx, model: data.parentModel };
|
|
2368
|
-
const chainResult = await executeChain({
|
|
2571
|
+
const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : ({ ...ctx, model: data.parentModel } as ExtensionContext);
|
|
2572
|
+
const chainResult = await executeChain(compactOptional<Parameters<typeof executeChain>[0]>({
|
|
2369
2573
|
chain,
|
|
2370
2574
|
task: params.task,
|
|
2371
2575
|
agents,
|
|
@@ -2394,7 +2598,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2394
2598
|
foregroundControl,
|
|
2395
2599
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
2396
2600
|
chainSkills,
|
|
2397
|
-
chainDir: params.chainDir ??
|
|
2601
|
+
chainDir: params.chainDir ?? getChainRunsDir(effectiveCwd, artifactConfig.dir),
|
|
2398
2602
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2399
2603
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2400
2604
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -2416,9 +2620,10 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2416
2620
|
toolBudget: data.toolBudget,
|
|
2417
2621
|
usageBudget: data.usageBudget,
|
|
2418
2622
|
configToolBudget: data.configToolBudget,
|
|
2623
|
+
permissions: deps.config.permissions,
|
|
2419
2624
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2420
2625
|
capabilityCeiling: data.capabilityCeiling,
|
|
2421
|
-
});
|
|
2626
|
+
}));
|
|
2422
2627
|
|
|
2423
2628
|
if (chainResult.requestedAsync) {
|
|
2424
2629
|
if (!isAsyncAvailable()) {
|
|
@@ -2430,20 +2635,21 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2430
2635
|
}
|
|
2431
2636
|
const id = randomUUID();
|
|
2432
2637
|
const parentModel = data.parentModel;
|
|
2433
|
-
const asyncCtx = {
|
|
2638
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2434
2639
|
pi: deps.pi,
|
|
2435
2640
|
cwd: ctx.cwd,
|
|
2436
|
-
currentSessionId:
|
|
2437
|
-
parentSessionId:
|
|
2641
|
+
currentSessionId: data.parentSessionId!,
|
|
2642
|
+
parentSessionId: data.parentPiSessionId,
|
|
2438
2643
|
currentModelProvider: parentModel?.provider,
|
|
2439
2644
|
currentModel: parentModel,
|
|
2440
2645
|
modelScope: data.modelScope,
|
|
2441
2646
|
interactive: ctx.hasUI,
|
|
2442
|
-
|
|
2647
|
+
permissions: deps.config.permissions,
|
|
2648
|
+
});
|
|
2443
2649
|
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
2444
2650
|
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
2445
2651
|
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
2446
|
-
return executeAsyncChain(id, {
|
|
2652
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2447
2653
|
chain: asyncChain,
|
|
2448
2654
|
task: params.task,
|
|
2449
2655
|
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
@@ -2478,15 +2684,17 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2478
2684
|
configToolBudget: data.configToolBudget,
|
|
2479
2685
|
capabilityCeiling: data.capabilityCeiling,
|
|
2480
2686
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2481
|
-
});
|
|
2687
|
+
}));
|
|
2482
2688
|
}
|
|
2483
2689
|
|
|
2484
|
-
const rawChainDetails = chainResult.details ? { ...chainResult.details, runId, timeoutMs: data.timeoutMs } : undefined;
|
|
2690
|
+
const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
|
|
2485
2691
|
if (foregroundControl && rawChainDetails) {
|
|
2486
2692
|
updateForegroundNestedProjection(foregroundControl);
|
|
2487
2693
|
attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
|
|
2488
2694
|
rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
|
|
2489
|
-
|
|
2695
|
+
const usageBudget = usageBudgetState(data.usageBudget, rawChainDetails.totalCost);
|
|
2696
|
+
if (usageBudget === undefined) delete rawChainDetails.usageBudget;
|
|
2697
|
+
else rawChainDetails.usageBudget = usageBudget;
|
|
2490
2698
|
}
|
|
2491
2699
|
const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
|
|
2492
2700
|
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results, checkpoint: chainDetails.checkpoint });
|
|
@@ -2560,6 +2768,7 @@ interface ForegroundParallelRunInput {
|
|
|
2560
2768
|
usageBudget?: UsageBudgetConfig;
|
|
2561
2769
|
toolBudgets: (ResolvedToolBudget | undefined)[];
|
|
2562
2770
|
agentContract?: AgentContract;
|
|
2771
|
+
permissions?: ExtensionConfig["permissions"];
|
|
2563
2772
|
}
|
|
2564
2773
|
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
2565
2774
|
return {
|
|
@@ -2581,13 +2790,13 @@ function createParallelWorktreeSetup(
|
|
|
2581
2790
|
if (!enabled) return {};
|
|
2582
2791
|
try {
|
|
2583
2792
|
return {
|
|
2584
|
-
setup: createWorktrees(cwd, runId, tasks.length, {
|
|
2793
|
+
setup: createWorktrees(cwd, runId, tasks.length, omitUndefinedProperties({
|
|
2585
2794
|
agents: tasks.map((task) => task.agent),
|
|
2586
2795
|
setupHook: setupHook
|
|
2587
|
-
? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
|
|
2796
|
+
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
2588
2797
|
: undefined,
|
|
2589
2798
|
baseDir,
|
|
2590
|
-
}),
|
|
2799
|
+
})),
|
|
2591
2800
|
};
|
|
2592
2801
|
} catch (error) {
|
|
2593
2802
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2643,39 +2852,41 @@ function finalizeParallelWorktreeHandoff(input: {
|
|
|
2643
2852
|
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
2644
2853
|
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
2645
2854
|
const diffs = diffWorktrees(input.worktreeSetup, input.tasks.map((task) => task.agent), diffsDir);
|
|
2646
|
-
const cleanup = cleanupWorktrees(input.worktreeSetup);
|
|
2647
2855
|
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 } : {}),
|
|
2856
|
+
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
2857
|
+
const handoff = {
|
|
2858
|
+
manifestPath,
|
|
2859
|
+
runId: input.runId,
|
|
2860
|
+
mode: "parallel" as const,
|
|
2861
|
+
source: "foreground" as const,
|
|
2862
|
+
cwd: input.cwd,
|
|
2863
|
+
stepIndex: 0,
|
|
2864
|
+
flatStartIndex: 0,
|
|
2865
|
+
setup: input.worktreeSetup,
|
|
2866
|
+
diffs,
|
|
2867
|
+
results: input.results.map((result) => ({
|
|
2868
|
+
agent: result.agent,
|
|
2869
|
+
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
2870
|
+
exitCode: result.exitCode,
|
|
2871
|
+
interrupted: result.interrupted,
|
|
2872
|
+
detached: result.detached,
|
|
2873
|
+
state: result.stopped ? "stopped" : undefined,
|
|
2874
|
+
processSignal: result.processSignal,
|
|
2875
|
+
timedOut: result.timedOut,
|
|
2876
|
+
stopped: result.stopped,
|
|
2877
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
2677
2878
|
})),
|
|
2678
|
-
|
|
2879
|
+
summary: resultSummaryForIntercom(result),
|
|
2880
|
+
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
2881
|
+
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
2882
|
+
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
2883
|
+
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
2884
|
+
})),
|
|
2885
|
+
};
|
|
2886
|
+
try {
|
|
2887
|
+
writeParallelHandoffGroup(handoff);
|
|
2888
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
|
|
2889
|
+
const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
2679
2890
|
return {
|
|
2680
2891
|
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
2681
2892
|
reference,
|
|
@@ -2755,7 +2966,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2755
2966
|
beginForegroundChild(input.foregroundControl, {
|
|
2756
2967
|
index,
|
|
2757
2968
|
agent: task.agent,
|
|
2758
|
-
description: input.taskDescriptions[index],
|
|
2969
|
+
...(input.taskDescriptions[index] === undefined ? {} : { description: input.taskDescriptions[index] }),
|
|
2759
2970
|
...(model ? { model } : {}),
|
|
2760
2971
|
...(thinking ? { thinking } : {}),
|
|
2761
2972
|
interrupt: () => {
|
|
@@ -2769,7 +2980,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2769
2980
|
? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
|
|
2770
2981
|
: undefined;
|
|
2771
2982
|
let detachedReceipt = false;
|
|
2772
|
-
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
2983
|
+
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
2984
|
+
permissions: input.permissions,
|
|
2773
2985
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
2774
2986
|
context: input.contextPolicy.contextForAgent(task.agent),
|
|
2775
2987
|
cwd: taskCwd,
|
|
@@ -2833,17 +3045,17 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2833
3045
|
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
2834
3046
|
input.onUpdate?.({
|
|
2835
3047
|
content: progressUpdate.content,
|
|
2836
|
-
details: {
|
|
3048
|
+
details: compactOptional<Details>({
|
|
2837
3049
|
mode: "parallel",
|
|
2838
3050
|
results: mergedResults,
|
|
2839
3051
|
progress: mergedProgress,
|
|
2840
3052
|
controlEvents: progressUpdate.details?.controlEvents,
|
|
2841
3053
|
totalSteps: input.tasks.length,
|
|
2842
|
-
},
|
|
3054
|
+
}),
|
|
2843
3055
|
});
|
|
2844
3056
|
}
|
|
2845
3057
|
: undefined,
|
|
2846
|
-
}).then((result) => {
|
|
3058
|
+
})).then((result) => {
|
|
2847
3059
|
detachedReceipt = result.detached === true;
|
|
2848
3060
|
return result;
|
|
2849
3061
|
}).finally(() => {
|
|
@@ -2918,7 +3130,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2918
3130
|
);
|
|
2919
3131
|
const toolBudgets: (ResolvedToolBudget | undefined)[] = [];
|
|
2920
3132
|
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 });
|
|
3133
|
+
const resolved = resolveEffectiveToolBudget(omitUndefinedProperties({ stepBudget: tasks[index]?.toolBudget, runBudget: data.toolBudget, agentBudget: agentConfigs[index]?.toolBudget, configBudget: data.configToolBudget }));
|
|
2922
3134
|
if (resolved.error) return buildParallelModeError(resolved.error);
|
|
2923
3135
|
toolBudgets.push(resolved.toolBudget);
|
|
2924
3136
|
}
|
|
@@ -2944,7 +3156,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2944
3156
|
...(task.model !== undefined ? { model: task.model } : {}),
|
|
2945
3157
|
}));
|
|
2946
3158
|
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
2947
|
-
resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, { scope: data.modelScope }),
|
|
3159
|
+
resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope }),
|
|
2948
3160
|
);
|
|
2949
3161
|
|
|
2950
3162
|
if (params.clarify === true && ctx.hasUI) {
|
|
@@ -2979,7 +3191,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2979
3191
|
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
2980
3192
|
const override = result.behaviorOverrides[i];
|
|
2981
3193
|
if (override?.model !== undefined) {
|
|
2982
|
-
modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
|
|
3194
|
+
modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2983
3195
|
behaviorOverrides[i]!.model = override.model;
|
|
2984
3196
|
}
|
|
2985
3197
|
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
@@ -3000,20 +3212,21 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3000
3212
|
};
|
|
3001
3213
|
}
|
|
3002
3214
|
const id = randomUUID();
|
|
3003
|
-
const asyncCtx = {
|
|
3215
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3004
3216
|
pi: deps.pi,
|
|
3005
3217
|
cwd: ctx.cwd,
|
|
3006
|
-
currentSessionId:
|
|
3007
|
-
parentSessionId:
|
|
3218
|
+
currentSessionId: data.parentSessionId!,
|
|
3219
|
+
parentSessionId: data.parentPiSessionId,
|
|
3008
3220
|
currentModelProvider: parentModel?.provider,
|
|
3009
3221
|
currentModel: parentModel,
|
|
3010
3222
|
modelScope: data.modelScope,
|
|
3011
3223
|
interactive: ctx.hasUI,
|
|
3012
|
-
|
|
3224
|
+
permissions: deps.config.permissions,
|
|
3225
|
+
});
|
|
3013
3226
|
const parallelTasks = tasks.map((t, i) => {
|
|
3014
3227
|
const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
3015
3228
|
const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
|
|
3016
|
-
return {
|
|
3229
|
+
return compactOptional<ParallelTaskItem>({
|
|
3017
3230
|
agent: t.agent,
|
|
3018
3231
|
task: taskText,
|
|
3019
3232
|
cwd: t.cwd,
|
|
@@ -3027,10 +3240,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3027
3240
|
...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
|
|
3028
3241
|
...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
|
|
3029
3242
|
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
3030
|
-
};
|
|
3243
|
+
});
|
|
3031
3244
|
});
|
|
3032
|
-
return executeAsyncChain(id, {
|
|
3033
|
-
chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }],
|
|
3245
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3246
|
+
chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
|
|
3034
3247
|
resultMode: "parallel",
|
|
3035
3248
|
goal: taskTexts[0] ?? "",
|
|
3036
3249
|
agents,
|
|
@@ -3062,7 +3275,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3062
3275
|
toolBudget: data.toolBudget,
|
|
3063
3276
|
configToolBudget: data.configToolBudget,
|
|
3064
3277
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3065
|
-
});
|
|
3278
|
+
}));
|
|
3066
3279
|
}
|
|
3067
3280
|
}
|
|
3068
3281
|
|
|
@@ -3090,15 +3303,27 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3090
3303
|
|
|
3091
3304
|
let worktreeFinalized = false;
|
|
3092
3305
|
try {
|
|
3306
|
+
if (worktreeSetup) {
|
|
3307
|
+
writePendingParallelHandoff({
|
|
3308
|
+
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3309
|
+
runId,
|
|
3310
|
+
mode: "parallel",
|
|
3311
|
+
source: "foreground",
|
|
3312
|
+
cwd: effectiveCwd,
|
|
3313
|
+
stepIndex: 0,
|
|
3314
|
+
flatStartIndex: 0,
|
|
3315
|
+
setup: worktreeSetup,
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3093
3318
|
const outputBaseDir = path.join(artifactsDir, "outputs", runId);
|
|
3094
|
-
const duplicateOutputError = findDuplicateParallelOutputPath({
|
|
3319
|
+
const duplicateOutputError = findDuplicateParallelOutputPath(omitUndefinedProperties({
|
|
3095
3320
|
tasks,
|
|
3096
3321
|
behaviors,
|
|
3097
3322
|
paramsCwd: effectiveCwd,
|
|
3098
3323
|
ctxCwd: ctx.cwd,
|
|
3099
3324
|
outputBaseDir,
|
|
3100
3325
|
worktreeSetup,
|
|
3101
|
-
});
|
|
3326
|
+
}));
|
|
3102
3327
|
if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
|
|
3103
3328
|
for (let index = 0; index < tasks.length; index++) {
|
|
3104
3329
|
const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
|
|
@@ -3117,8 +3342,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3117
3342
|
}
|
|
3118
3343
|
|
|
3119
3344
|
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3120
|
-
const results = await runForegroundParallelTasks({
|
|
3345
|
+
const results = await runForegroundParallelTasks(compactOptional<ForegroundParallelRunInput>({
|
|
3121
3346
|
tasks,
|
|
3347
|
+
permissions: deps.config.permissions,
|
|
3122
3348
|
taskTexts,
|
|
3123
3349
|
taskDescriptions,
|
|
3124
3350
|
agents,
|
|
@@ -3166,7 +3392,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3166
3392
|
usageBudget: data.usageBudget,
|
|
3167
3393
|
toolBudgets,
|
|
3168
3394
|
agentContract: params.agentContract,
|
|
3169
|
-
});
|
|
3395
|
+
}));
|
|
3170
3396
|
for (let i = 0; i < results.length; i++) {
|
|
3171
3397
|
const run = results[i]!;
|
|
3172
3398
|
recordRun(run.agent, taskTexts[i]!, run.exitCode, run.progressSummary?.durationMs ?? 0);
|
|
@@ -3188,7 +3414,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3188
3414
|
}
|
|
3189
3415
|
const interrupted = results.find((result) => result.interrupted);
|
|
3190
3416
|
const totalCost = sumResultsCost(results);
|
|
3191
|
-
const details = compactForegroundDetails({
|
|
3417
|
+
const details = compactForegroundDetails(compactOptional<Details>({
|
|
3192
3418
|
mode: "parallel",
|
|
3193
3419
|
runId,
|
|
3194
3420
|
timeoutMs: data.timeoutMs,
|
|
@@ -3199,7 +3425,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3199
3425
|
totalCost,
|
|
3200
3426
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3201
3427
|
...(handoff?.reference ? { parallelHandoff: handoff.reference } : {}),
|
|
3202
|
-
});
|
|
3428
|
+
}));
|
|
3203
3429
|
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
3204
3430
|
if (interrupted) {
|
|
3205
3431
|
return {
|
|
@@ -3216,20 +3442,24 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3216
3442
|
};
|
|
3217
3443
|
}
|
|
3218
3444
|
|
|
3445
|
+
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results });
|
|
3219
3446
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3447
|
+
if (!suppressRoutineResultIntercom) {
|
|
3448
|
+
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
3449
|
+
pi: deps.pi,
|
|
3450
|
+
intercomBridge: data.intercomBridge,
|
|
3451
|
+
runId,
|
|
3452
|
+
mode: "parallel",
|
|
3453
|
+
details,
|
|
3454
|
+
...(params.workflowParentRunId !== undefined ? { preserveDetailsOutputs: true } : {}),
|
|
3455
|
+
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
3456
|
+
});
|
|
3457
|
+
if (intercomReceipt) {
|
|
3458
|
+
return {
|
|
3459
|
+
content: [{ type: "text", text: intercomReceipt.text }],
|
|
3460
|
+
details: intercomReceipt.details,
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3233
3463
|
}
|
|
3234
3464
|
|
|
3235
3465
|
const worktreeSuffix = handoff?.suffix ?? "";
|
|
@@ -3240,8 +3470,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3240
3470
|
agent: result.agent,
|
|
3241
3471
|
output: result.truncation?.text || getSingleResultOutput(result),
|
|
3242
3472
|
exitCode: result.exitCode,
|
|
3243
|
-
error: result.error,
|
|
3244
|
-
timedOut: result.timedOut,
|
|
3473
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
3474
|
+
...(result.timedOut === undefined ? {} : { timedOut: result.timedOut }),
|
|
3245
3475
|
})),
|
|
3246
3476
|
(i, agent) => `=== Task ${i + 1}: ${agent} ===`,
|
|
3247
3477
|
);
|
|
@@ -3291,7 +3521,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3291
3521
|
details: { mode: "single", results: [] },
|
|
3292
3522
|
};
|
|
3293
3523
|
}
|
|
3294
|
-
const effectiveToolBudget = resolveEffectiveToolBudget({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget });
|
|
3524
|
+
const effectiveToolBudget = resolveEffectiveToolBudget(omitUndefinedProperties({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget }));
|
|
3295
3525
|
if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error), data.contextPolicy.contextSummary);
|
|
3296
3526
|
|
|
3297
3527
|
const parentModel = data.parentModel;
|
|
@@ -3304,7 +3534,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3304
3534
|
parentModel,
|
|
3305
3535
|
availableModels,
|
|
3306
3536
|
currentProvider,
|
|
3307
|
-
{ scope: data.modelScope },
|
|
3537
|
+
data.modelScope === undefined ? {} : { scope: data.modelScope },
|
|
3308
3538
|
);
|
|
3309
3539
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
3310
3540
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
@@ -3314,7 +3544,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3314
3544
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
3315
3545
|
|
|
3316
3546
|
if (params.clarify === true && ctx.hasUI) {
|
|
3317
|
-
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput, skills: skillOverride });
|
|
3547
|
+
const behavior = resolveStepBehavior(agentConfig, omitUndefinedProperties({ output: effectiveOutput, skills: skillOverride }));
|
|
3318
3548
|
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
3319
3549
|
|
|
3320
3550
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
@@ -3341,7 +3571,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3341
3571
|
|
|
3342
3572
|
task = result.templates[0]!;
|
|
3343
3573
|
const override = result.behaviorOverrides[0];
|
|
3344
|
-
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
|
|
3574
|
+
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
3345
3575
|
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
3346
3576
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
3347
3577
|
|
|
@@ -3354,17 +3584,18 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3354
3584
|
};
|
|
3355
3585
|
}
|
|
3356
3586
|
const id = randomUUID();
|
|
3357
|
-
const asyncCtx = {
|
|
3587
|
+
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3358
3588
|
pi: deps.pi,
|
|
3359
3589
|
cwd: ctx.cwd,
|
|
3360
|
-
currentSessionId:
|
|
3361
|
-
parentSessionId:
|
|
3590
|
+
currentSessionId: data.parentSessionId!,
|
|
3591
|
+
parentSessionId: data.parentPiSessionId,
|
|
3362
3592
|
currentModelProvider: parentModel?.provider,
|
|
3363
3593
|
currentModel: parentModel,
|
|
3364
3594
|
modelScope: data.modelScope,
|
|
3365
3595
|
interactive: ctx.hasUI,
|
|
3366
|
-
|
|
3367
|
-
|
|
3596
|
+
permissions: deps.config.permissions,
|
|
3597
|
+
});
|
|
3598
|
+
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
3368
3599
|
agent: params.agent!,
|
|
3369
3600
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
3370
3601
|
goal: task,
|
|
@@ -3402,7 +3633,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3402
3633
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3403
3634
|
usageBudget: data.usageBudget,
|
|
3404
3635
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3405
|
-
});
|
|
3636
|
+
}));
|
|
3406
3637
|
}
|
|
3407
3638
|
}
|
|
3408
3639
|
|
|
@@ -3431,7 +3662,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3431
3662
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3432
3663
|
if (foregroundControl) {
|
|
3433
3664
|
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride));
|
|
3434
|
-
beginForegroundChild(foregroundControl, {
|
|
3665
|
+
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
3435
3666
|
index: 0,
|
|
3436
3667
|
agent: params.agent!,
|
|
3437
3668
|
description: foregroundControl.description,
|
|
@@ -3443,7 +3674,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3443
3674
|
return true;
|
|
3444
3675
|
},
|
|
3445
3676
|
detach: () => detachForeground?.("user request") === true,
|
|
3446
|
-
});
|
|
3677
|
+
}));
|
|
3447
3678
|
}
|
|
3448
3679
|
|
|
3449
3680
|
const forwardSingleUpdate = onUpdate
|
|
@@ -3454,9 +3685,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3454
3685
|
: undefined;
|
|
3455
3686
|
|
|
3456
3687
|
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3457
|
-
let r: Awaited<ReturnType<typeof runSync
|
|
3688
|
+
let r: Awaited<ReturnType<typeof runSync>> | undefined;
|
|
3458
3689
|
try {
|
|
3459
|
-
r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
3690
|
+
r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3691
|
+
permissions: deps.config.permissions,
|
|
3460
3692
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3461
3693
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3462
3694
|
cwd: effectiveCwd,
|
|
@@ -3498,7 +3730,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3498
3730
|
onDetachedExit: (result) => {
|
|
3499
3731
|
try {
|
|
3500
3732
|
try {
|
|
3501
|
-
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events });
|
|
3733
|
+
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: params.workflowParentRunId === undefined });
|
|
3502
3734
|
} catch {
|
|
3503
3735
|
// Remembered foreground state is best-effort; run history and cleanup must still complete.
|
|
3504
3736
|
}
|
|
@@ -3522,7 +3754,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3522
3754
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3523
3755
|
capabilityCeiling: data.capabilityCeiling,
|
|
3524
3756
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3525
|
-
});
|
|
3757
|
+
}));
|
|
3526
3758
|
} finally {
|
|
3527
3759
|
// An attached runSync rejection still owns its child and structured runtime.
|
|
3528
3760
|
// A successful detached receipt transfers both to onDetachedExit while the
|
|
@@ -3540,7 +3772,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3540
3772
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
3541
3773
|
|
|
3542
3774
|
const fullOutput = getSingleResultOutput(r);
|
|
3543
|
-
const finalizedOutput = finalizeSingleOutput({
|
|
3775
|
+
const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
|
|
3544
3776
|
fullOutput,
|
|
3545
3777
|
truncatedOutput: r.truncation?.text,
|
|
3546
3778
|
outputPath,
|
|
@@ -3549,13 +3781,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3549
3781
|
savedPath: r.savedOutputPath,
|
|
3550
3782
|
outputReference: r.outputReference,
|
|
3551
3783
|
saveError: r.outputSaveError,
|
|
3552
|
-
});
|
|
3784
|
+
}));
|
|
3553
3785
|
if (foregroundControl) {
|
|
3554
3786
|
updateForegroundNestedProjection(foregroundControl);
|
|
3555
3787
|
attachRootChildrenToSteps(runId, [r], foregroundControl.nestedChildren);
|
|
3556
3788
|
}
|
|
3557
3789
|
const totalCost = sumResultsCost([r]);
|
|
3558
|
-
const details = compactForegroundDetails({
|
|
3790
|
+
const details = compactForegroundDetails(compactOptional<Details>({
|
|
3559
3791
|
mode: "single",
|
|
3560
3792
|
runId,
|
|
3561
3793
|
timeoutMs: data.timeoutMs,
|
|
@@ -3568,10 +3800,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3568
3800
|
totalChildUsage: sumResultsUsage([r]),
|
|
3569
3801
|
totalCost,
|
|
3570
3802
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3571
|
-
});
|
|
3803
|
+
}));
|
|
3572
3804
|
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
3573
3805
|
|
|
3574
|
-
|
|
3806
|
+
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
3807
|
+
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
3575
3808
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
3576
3809
|
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
3577
3810
|
pi: deps.pi,
|
|
@@ -3579,6 +3812,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3579
3812
|
runId,
|
|
3580
3813
|
mode: "single",
|
|
3581
3814
|
details,
|
|
3815
|
+
...(params.workflowParentRunId !== undefined ? { preserveDetailsOutputs: true } : {}),
|
|
3582
3816
|
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
3583
3817
|
});
|
|
3584
3818
|
if (intercomReceipt) {
|
|
@@ -3616,7 +3850,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3616
3850
|
};
|
|
3617
3851
|
}
|
|
3618
3852
|
|
|
3619
|
-
function inferExecutionMode(params: SubagentParamsLike):
|
|
3853
|
+
function inferExecutionMode(params: SubagentParamsLike): Details["mode"] {
|
|
3854
|
+
if (params.workflowScript !== undefined) return "workflow";
|
|
3620
3855
|
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
3621
3856
|
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
3622
3857
|
return "single";
|
|
@@ -3633,6 +3868,87 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
3633
3868
|
};
|
|
3634
3869
|
}
|
|
3635
3870
|
|
|
3871
|
+
function workflowChildResult(key: string, result: AgentToolResult<Details>): WorkflowScriptChildResult {
|
|
3872
|
+
const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
|
|
3873
|
+
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
3874
|
+
? result.details.results[0].finalOutput
|
|
3875
|
+
: receiptOutput;
|
|
3876
|
+
const artifactPaths = new Set<string>();
|
|
3877
|
+
if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
|
|
3878
|
+
if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
|
|
3879
|
+
for (const child of result.details.results) {
|
|
3880
|
+
if (child.savedOutputPath) artifactPaths.add(child.savedOutputPath);
|
|
3881
|
+
if (child.outputReference?.path) artifactPaths.add(child.outputReference.path);
|
|
3882
|
+
if (child.sessionFile) artifactPaths.add(child.sessionFile);
|
|
3883
|
+
}
|
|
3884
|
+
const structured = result.details.results.map((child) => child.structuredOutput).filter((value) => value !== undefined);
|
|
3885
|
+
return {
|
|
3886
|
+
key,
|
|
3887
|
+
ok: result.isError !== true,
|
|
3888
|
+
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
3889
|
+
output,
|
|
3890
|
+
...(result.isError === true ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
3891
|
+
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
3892
|
+
artifactPaths: [...artifactPaths],
|
|
3893
|
+
results: result.details.results,
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsLike {
|
|
3898
|
+
if (params.worktree !== true || !params.agent) return params;
|
|
3899
|
+
const {
|
|
3900
|
+
agent,
|
|
3901
|
+
task = "",
|
|
3902
|
+
model,
|
|
3903
|
+
skill,
|
|
3904
|
+
output,
|
|
3905
|
+
outputMode,
|
|
3906
|
+
outputSchema,
|
|
3907
|
+
acceptance,
|
|
3908
|
+
agentContract,
|
|
3909
|
+
toolBudget,
|
|
3910
|
+
...runParams
|
|
3911
|
+
} = params;
|
|
3912
|
+
return {
|
|
3913
|
+
...runParams,
|
|
3914
|
+
worktree: true,
|
|
3915
|
+
tasks: [{
|
|
3916
|
+
agent,
|
|
3917
|
+
task,
|
|
3918
|
+
...(model !== undefined ? { model } : {}),
|
|
3919
|
+
...(skill !== undefined ? { skill } : {}),
|
|
3920
|
+
...(output !== undefined ? { output } : {}),
|
|
3921
|
+
...(outputMode !== undefined ? { outputMode } : {}),
|
|
3922
|
+
...(outputSchema !== undefined ? { outputSchema } : {}),
|
|
3923
|
+
...(acceptance !== undefined ? { acceptance } : {}),
|
|
3924
|
+
...(agentContract !== undefined ? { agentContract } : {}),
|
|
3925
|
+
...(toolBudget !== undefined ? { toolBudget } : {}),
|
|
3926
|
+
}],
|
|
3927
|
+
};
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
function formatWorkflowValue(value: unknown): string {
|
|
3931
|
+
if (value === undefined) return "(undefined)";
|
|
3932
|
+
if (typeof value === "string") return value;
|
|
3933
|
+
try {
|
|
3934
|
+
return JSON.stringify(value, null, 2);
|
|
3935
|
+
} catch {
|
|
3936
|
+
return String(value);
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
function workflowChatProgressUpdate(
|
|
3941
|
+
runId: string,
|
|
3942
|
+
chatProgress: WorkflowChatProgressProjection,
|
|
3943
|
+
workflow: NonNullable<Details["workflow"]>,
|
|
3944
|
+
): AgentToolResult<Details> | undefined {
|
|
3945
|
+
if (chatProgress.mode !== "live-card") return undefined;
|
|
3946
|
+
return {
|
|
3947
|
+
content: [{ type: "text", text: "Workflow running." }],
|
|
3948
|
+
details: { mode: "workflow", runId, results: [], workflow, chatProgress },
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3636
3952
|
function omitExecutionModeActionAlias(params: SubagentParamsLike): SubagentParamsLike {
|
|
3637
3953
|
const action = params.action?.toLowerCase();
|
|
3638
3954
|
if (action === "single" && (params.agent !== undefined || params.task !== undefined)) {
|
|
@@ -3648,6 +3964,36 @@ function omitExecutionModeActionAlias(params: SubagentParamsLike): SubagentParam
|
|
|
3648
3964
|
return params;
|
|
3649
3965
|
}
|
|
3650
3966
|
|
|
3967
|
+
function createScheduledOwnerState(source: SubagentState, ownerSessionId: string, ctx: ExtensionContext): SubagentState {
|
|
3968
|
+
const ownerSpawns = source.subagentSpawns?.sessionId === ownerSessionId
|
|
3969
|
+
? {
|
|
3970
|
+
...source.subagentSpawns,
|
|
3971
|
+
grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
|
|
3972
|
+
}
|
|
3973
|
+
: undefined;
|
|
3974
|
+
return {
|
|
3975
|
+
...source,
|
|
3976
|
+
baseCwd: ctx.cwd,
|
|
3977
|
+
currentSessionId: ownerSessionId,
|
|
3978
|
+
parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
|
|
3979
|
+
subagentInProgress: false,
|
|
3980
|
+
...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
|
|
3981
|
+
asyncJobs: new Map(),
|
|
3982
|
+
fleetJobs: new Map(),
|
|
3983
|
+
foregroundRuns: new Map(),
|
|
3984
|
+
foregroundControls: new Map(),
|
|
3985
|
+
lastForegroundControlId: null,
|
|
3986
|
+
cleanupTimers: new Map(),
|
|
3987
|
+
lastUiContext: null,
|
|
3988
|
+
poller: null,
|
|
3989
|
+
completionSeen: new Map(),
|
|
3990
|
+
watcher: null,
|
|
3991
|
+
watcherRestartTimer: null,
|
|
3992
|
+
waitSubscriptions: new Map(),
|
|
3993
|
+
workflowControllers: new Map(),
|
|
3994
|
+
};
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3651
3997
|
export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
3652
3998
|
execute: (
|
|
3653
3999
|
id: string,
|
|
@@ -3668,34 +4014,320 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3668
4014
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3669
4015
|
ctx: ExtensionContext,
|
|
3670
4016
|
) => Promise<AgentToolResult<Details>>;
|
|
4017
|
+
/** Scheduled launches retain their owning context without replacing the live active session. */
|
|
4018
|
+
executeScheduled: (
|
|
4019
|
+
id: string,
|
|
4020
|
+
params: SubagentParamsLike,
|
|
4021
|
+
signal: AbortSignal,
|
|
4022
|
+
ctx: ExtensionContext,
|
|
4023
|
+
) => Promise<AgentToolResult<Details>>;
|
|
3671
4024
|
} {
|
|
3672
4025
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
3673
4026
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4027
|
+
const warnedArtifactPackageDirs = new Set<string>();
|
|
4028
|
+
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
3674
4029
|
const execute = async (
|
|
3675
4030
|
_id: string,
|
|
3676
4031
|
params: SubagentParamsLike,
|
|
3677
4032
|
signal: AbortSignal,
|
|
3678
4033
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3679
4034
|
ctx: ExtensionContext,
|
|
4035
|
+
preserveActiveSession = false,
|
|
3680
4036
|
): Promise<AgentToolResult<Details>> => {
|
|
3681
4037
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
3682
4038
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
3683
|
-
deps.state.baseCwd = ctx.cwd;
|
|
4039
|
+
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
3684
4040
|
deps.state.foregroundRuns ??= new Map();
|
|
3685
4041
|
deps.state.foregroundControls ??= new Map();
|
|
3686
4042
|
deps.state.lastForegroundControlId ??= null;
|
|
3687
4043
|
const requestParams = omitExecutionModeActionAlias(params);
|
|
3688
|
-
|
|
3689
|
-
|
|
4044
|
+
if (requestParams.workflowScript !== undefined) {
|
|
4045
|
+
const invalidMode = requestParams.action !== undefined || requestParams.agent !== undefined || requestParams.step !== undefined || requestParams.tasks !== undefined || requestParams.chain !== undefined;
|
|
4046
|
+
if (invalidMode) {
|
|
4047
|
+
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: [] } };
|
|
4048
|
+
}
|
|
4049
|
+
if (requestParams.clarify === true) {
|
|
4050
|
+
return { content: [{ type: "text", text: "workflowScript does not support clarify UI." }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4051
|
+
}
|
|
4052
|
+
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
|
|
4053
|
+
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4054
|
+
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4055
|
+
const workflowCwd = resolveRequestedCwd(ctx.cwd, requestParams.cwd);
|
|
4056
|
+
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd: ctx.cwd, workflowCwd, background: requestParams.async !== false });
|
|
4057
|
+
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4058
|
+
const chatProgress = chatProgressResult.projection!;
|
|
4059
|
+
if (requestParams.async !== false) {
|
|
4060
|
+
const workflowRunId = _id;
|
|
4061
|
+
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4062
|
+
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
4063
|
+
const statusPath = path.join(asyncDir, "status.json");
|
|
4064
|
+
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4065
|
+
const startedAt = Date.now();
|
|
4066
|
+
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4067
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
4068
|
+
fs.mkdirSync(DIRS.results, { recursive: true });
|
|
4069
|
+
const controller = new AbortController();
|
|
4070
|
+
deps.state.workflowControllers ??= new Map();
|
|
4071
|
+
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4072
|
+
let status: AsyncStatus = {
|
|
4073
|
+
runId: workflowRunId,
|
|
4074
|
+
sessionId: currentSessionId ?? undefined,
|
|
4075
|
+
mode: "workflow",
|
|
4076
|
+
state: "running",
|
|
4077
|
+
startedAt,
|
|
4078
|
+
lastUpdate: startedAt,
|
|
4079
|
+
deadlineAt: startedAt + timeout,
|
|
4080
|
+
timeoutMs: timeout,
|
|
4081
|
+
cwd: ctx.cwd,
|
|
4082
|
+
pid: process.pid,
|
|
4083
|
+
steps: [],
|
|
4084
|
+
workflow: { trace: [], emits: [], console: [] },
|
|
4085
|
+
};
|
|
4086
|
+
const appendWorkflowEvent = (event: Record<string, unknown>) => fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
4087
|
+
const persist = () => {
|
|
4088
|
+
status.lastUpdate = Date.now();
|
|
4089
|
+
writeAtomicJson(statusPath, status);
|
|
4090
|
+
const job = deps.state.asyncJobs.get(workflowRunId);
|
|
4091
|
+
if (job) {
|
|
4092
|
+
job.status = status.state;
|
|
4093
|
+
job.updatedAt = status.lastUpdate;
|
|
4094
|
+
if (status.steps) {
|
|
4095
|
+
job.steps = status.steps.map((step, index) => ({ ...step, index }));
|
|
4096
|
+
job.agents = status.steps.map((step) => step.agent);
|
|
4097
|
+
} else {
|
|
4098
|
+
delete job.steps;
|
|
4099
|
+
delete job.agents;
|
|
4100
|
+
}
|
|
4101
|
+
job.workflow = status.workflow;
|
|
4102
|
+
}
|
|
4103
|
+
};
|
|
4104
|
+
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 };
|
|
4105
|
+
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4106
|
+
deps.state.fleetJobs ??= new Map();
|
|
4107
|
+
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
4108
|
+
persist();
|
|
4109
|
+
appendWorkflowEvent({ type: "subagent.workflow.started" });
|
|
4110
|
+
const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
|
|
4111
|
+
void Promise.resolve().then(async () => {
|
|
4112
|
+
const workflowResults: SingleResult[] = [];
|
|
4113
|
+
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;
|
|
4114
|
+
const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
|
|
4115
|
+
status.workflow = { ...(status.workflow ?? { emits: [], console: [] }), trace };
|
|
4116
|
+
for (const entry of trace.filter((candidate) => candidate.operation === "run")) {
|
|
4117
|
+
const existing = status.steps?.find((step) => step.workflowKey === entry.key);
|
|
4118
|
+
if (entry.state === "reused" && existing) continue;
|
|
4119
|
+
const mapped = entry.state === "started" || entry.state === "reused" ? "running" : entry.state === "completed" ? "completed" : "failed";
|
|
4120
|
+
if (existing) {
|
|
4121
|
+
existing.status = mapped;
|
|
4122
|
+
if (entry.error === undefined) delete existing.error;
|
|
4123
|
+
else existing.error = entry.error;
|
|
4124
|
+
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4125
|
+
else existing.durationMs = entry.durationMs;
|
|
4126
|
+
} else {
|
|
4127
|
+
status.steps?.push({ agent: entry.key, label: entry.key, workflowKey: entry.key, parentWorkflowRunId: workflowRunId, status: mapped });
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
persist();
|
|
4131
|
+
appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
|
|
4132
|
+
};
|
|
4133
|
+
try {
|
|
4134
|
+
const workflow = await runWorkflowScript({
|
|
4135
|
+
script: workflowScript,
|
|
4136
|
+
timeoutMs: timeout,
|
|
4137
|
+
signal: controller.signal,
|
|
4138
|
+
onTrace: updateTrace,
|
|
4139
|
+
onEmit: (emits) => {
|
|
4140
|
+
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
4141
|
+
status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
|
|
4142
|
+
persist();
|
|
4143
|
+
appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
|
|
4144
|
+
},
|
|
4145
|
+
launch: async (key, childParams, workflowSignal) => {
|
|
4146
|
+
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4147
|
+
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4148
|
+
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4149
|
+
const childRequest = prepareWorkflowChildParams({ ...workflowChildDefaults, ...childParams, workflowParentRunId: workflowRunId, workflowKey: key } as SubagentParamsLike);
|
|
4150
|
+
const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
|
|
4151
|
+
workflowResults.push(...result.details.results);
|
|
4152
|
+
const child = workflowChildResult(key, result);
|
|
4153
|
+
if (result.details.asyncId) {
|
|
4154
|
+
const childJob = deps.state.asyncJobs.get(result.details.asyncId);
|
|
4155
|
+
if (childJob) { childJob.parentWorkflowRunId = workflowRunId; childJob.workflowKey = key; }
|
|
4156
|
+
}
|
|
4157
|
+
return child;
|
|
4158
|
+
},
|
|
4159
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4160
|
+
});
|
|
4161
|
+
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
4162
|
+
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
4163
|
+
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
4164
|
+
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4165
|
+
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) };
|
|
4166
|
+
persist();
|
|
4167
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
4168
|
+
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 });
|
|
4169
|
+
} catch (error) {
|
|
4170
|
+
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4171
|
+
const stopped = controller.signal.aborted;
|
|
4172
|
+
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 } });
|
|
4173
|
+
persist();
|
|
4174
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4175
|
+
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 });
|
|
4176
|
+
} finally {
|
|
4177
|
+
deps.state.workflowControllers?.delete(workflowRunId);
|
|
4178
|
+
}
|
|
4179
|
+
});
|
|
4180
|
+
return {
|
|
4181
|
+
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4182
|
+
details: { mode: "workflow", runId: workflowRunId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
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;
|
|
4186
|
+
const workflowResults: SingleResult[] = [];
|
|
4187
|
+
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
4188
|
+
const sendWorkflowProgress = () => {
|
|
4189
|
+
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
|
|
4190
|
+
if (update) onUpdate?.(update);
|
|
4191
|
+
};
|
|
4192
|
+
try {
|
|
4193
|
+
const workflow = await runWorkflowScript({
|
|
4194
|
+
script: requestParams.workflowScript,
|
|
4195
|
+
timeoutMs: timeout,
|
|
4196
|
+
signal,
|
|
4197
|
+
onTrace: (trace) => {
|
|
4198
|
+
liveWorkflow = { ...liveWorkflow, trace };
|
|
4199
|
+
sendWorkflowProgress();
|
|
4200
|
+
},
|
|
4201
|
+
onEmit: (emits) => {
|
|
4202
|
+
liveWorkflow = { ...liveWorkflow, emits };
|
|
4203
|
+
sendWorkflowProgress();
|
|
4204
|
+
},
|
|
4205
|
+
launch: async (key, childParams, workflowSignal) => {
|
|
4206
|
+
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4207
|
+
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4208
|
+
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4209
|
+
const childRequest = prepareWorkflowChildParams({ ...workflowChildDefaults, ...childParams, workflowParentRunId: _id, workflowKey: key, suppressRoutineResultIntercom: chatProgress.mode === "live-card" } as SubagentParamsLike);
|
|
4210
|
+
const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
|
|
4211
|
+
workflowResults.push(...result.details.results);
|
|
4212
|
+
return workflowChildResult(key, result);
|
|
4213
|
+
},
|
|
4214
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4215
|
+
});
|
|
4216
|
+
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}` : ""}`);
|
|
4217
|
+
const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
|
|
4218
|
+
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4219
|
+
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4220
|
+
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4221
|
+
return {
|
|
4222
|
+
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4223
|
+
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 }),
|
|
4224
|
+
};
|
|
4225
|
+
} catch (error) {
|
|
4226
|
+
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4227
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
4228
|
+
const traceLines = partial.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
4229
|
+
const sections = [`Workflow failed: ${text}`];
|
|
4230
|
+
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4231
|
+
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4232
|
+
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4233
|
+
return {
|
|
4234
|
+
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4235
|
+
isError: true,
|
|
4236
|
+
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 }),
|
|
4237
|
+
};
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
const directParams = prepareWorkflowChildParams(requestParams);
|
|
4241
|
+
const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
|
|
4242
|
+
const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
|
|
3690
4243
|
const action = paramsWithResolvedCwd.action;
|
|
4244
|
+
let requestSessionId = "";
|
|
4245
|
+
let requestPiSessionId: string | undefined;
|
|
3691
4246
|
let requestParentModel: ParentModel | undefined;
|
|
3692
4247
|
try {
|
|
3693
|
-
|
|
4248
|
+
requestSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4249
|
+
requestPiSessionId = ctx.sessionManager.getSessionId() ?? undefined;
|
|
4250
|
+
requestParentModel = preserveActiveSession
|
|
4251
|
+
? normalizeParentModel(ctx.model)
|
|
4252
|
+
: rememberParentModel(deps.state, requestSessionId, ctx.model);
|
|
3694
4253
|
} catch (error) {
|
|
3695
4254
|
if (action?.toLowerCase() !== "doctor") throw error;
|
|
3696
4255
|
requestParentModel = normalizeParentModel(ctx.model);
|
|
3697
4256
|
}
|
|
3698
4257
|
if (action) {
|
|
4258
|
+
if (action === "worktree.discard") {
|
|
4259
|
+
if (deps.allowMutatingManagementActions === false) {
|
|
4260
|
+
return { content: [{ type: "text", text: "Action 'worktree.discard' is not available from child-safe subagent fanout mode." }], isError: true, details: { mode: "management", results: [] } };
|
|
4261
|
+
}
|
|
4262
|
+
if (!paramsWithResolvedCwd.handoffPath?.trim()) {
|
|
4263
|
+
return { content: [{ type: "text", text: "worktree.discard requires handoffPath from parallelHandoff.path or async status." }], isError: true, details: { mode: "management", results: [] } };
|
|
4264
|
+
}
|
|
4265
|
+
const decision = resolveAuthorityDecision({ action: "discardWorktree", ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
|
|
4266
|
+
if (decision === "forbid") {
|
|
4267
|
+
return { content: [{ type: "text", text: "Authority policy forbids worktree discard." }], isError: true, details: { mode: "management", results: [] } };
|
|
4268
|
+
}
|
|
4269
|
+
let confirmed = decision === "auto";
|
|
4270
|
+
if (decision === "confirm") {
|
|
4271
|
+
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: [] } };
|
|
4272
|
+
confirmed = await ctx.ui.confirm("Discard preserved subagent worktrees?", `This permanently removes preserved worktrees and temporary branches recorded in:\n${paramsWithResolvedCwd.handoffPath}`);
|
|
4273
|
+
}
|
|
4274
|
+
if (!confirmed) return { content: [{ type: "text", text: "Worktree discard canceled; preserved worktrees were not changed." }], details: { mode: "management", results: [] } };
|
|
4275
|
+
try {
|
|
4276
|
+
const discarded = discardPreservedWorktrees(
|
|
4277
|
+
path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath),
|
|
4278
|
+
{ kind: decision === "confirm" ? "confirmed" : "policy", ...(deps.config.authorityPolicy ? { policy: deps.config.authorityPolicy } : {}) },
|
|
4279
|
+
);
|
|
4280
|
+
return { content: [{ type: "text", text: discarded.text }], details: { mode: "management", results: [] } };
|
|
4281
|
+
} catch (error) {
|
|
4282
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
if ((HERDR_PROJECT_PANE_ACTIONS as readonly string[]).includes(action)) {
|
|
4286
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4287
|
+
return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4288
|
+
}
|
|
4289
|
+
return handleHerdrProjectPaneAction(action as (typeof HERDR_PROJECT_PANE_ACTIONS)[number], paramsWithResolvedCwd, { cwd: requestCwd, signal });
|
|
4290
|
+
}
|
|
4291
|
+
if ((HERDR_INSPECTOR_ACTIONS as readonly string[]).includes(action)) {
|
|
4292
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4293
|
+
return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4294
|
+
}
|
|
4295
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4296
|
+
return handleHerdrInspectorAction(action as (typeof HERDR_INSPECTOR_ACTIONS)[number], paramsWithResolvedCwd, {
|
|
4297
|
+
state: deps.state,
|
|
4298
|
+
cwd: requestCwd,
|
|
4299
|
+
...(deps.config.missions ? { missions: deps.config.missions } : {}),
|
|
4300
|
+
...(deps.config.authorityPolicy ? { authorityPolicy: deps.config.authorityPolicy } : {}),
|
|
4301
|
+
signal,
|
|
4302
|
+
});
|
|
4303
|
+
}
|
|
4304
|
+
if ((MISSION_ACTIONS as readonly string[]).includes(action)) {
|
|
4305
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4306
|
+
return {
|
|
4307
|
+
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
4308
|
+
isError: true,
|
|
4309
|
+
details: { mode: "management", results: [] },
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
const currentSessionId = deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined;
|
|
4313
|
+
return handleMissionAction(action as (typeof MISSION_ACTIONS)[number], paramsWithResolvedCwd, {
|
|
4314
|
+
cwd: requestCwd,
|
|
4315
|
+
...(deps.config.missions ? { config: deps.config.missions } : {}),
|
|
4316
|
+
...(currentSessionId ? { currentSessionId } : {}),
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4319
|
+
const policyAction = action === "stop" ? "stopRun" : action === "steer" ? "steerRun" : action === "schedule.create" ? "scheduleCreate" : undefined;
|
|
4320
|
+
if (policyAction) {
|
|
4321
|
+
const decision = resolveAuthorityDecision({ action: policyAction, ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
|
|
4322
|
+
if (decision === "forbid") {
|
|
4323
|
+
return { content: [{ type: "text", text: `Authority policy forbids action '${action}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4324
|
+
}
|
|
4325
|
+
if (decision === "confirm") {
|
|
4326
|
+
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: [] } };
|
|
4327
|
+
const confirmed = await ctx.ui.confirm(`Authorize subagent ${action}?`, `Authority policy requires confirmation before '${action}'.`);
|
|
4328
|
+
if (!confirmed) return { content: [{ type: "text", text: `Action '${action}' canceled; authority was not granted.` }], details: { mode: "management", results: [] } };
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
3699
4331
|
if ((WATCHDOG_TOOL_ACTIONS as readonly string[]).includes(action)) {
|
|
3700
4332
|
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
3701
4333
|
return {
|
|
@@ -3740,7 +4372,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3740
4372
|
details: { mode: "management", results: [], spawnBudget: preview.snapshot },
|
|
3741
4373
|
};
|
|
3742
4374
|
}
|
|
3743
|
-
const
|
|
4375
|
+
const authority = resolveAuthorityDecision({ action: "spawnBudgetGrant", ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
|
|
4376
|
+
if (authority === "forbid") {
|
|
4377
|
+
return {
|
|
4378
|
+
content: [{ type: "text", text: "Authority policy forbids spawn budget grants." }],
|
|
4379
|
+
isError: true,
|
|
4380
|
+
details: { mode: "management", results: [], spawnBudget: preview.snapshot },
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
const confirmed = authority === "auto" || await ctx.ui.confirm(
|
|
3744
4384
|
"Grant subagent spawn budget?",
|
|
3745
4385
|
`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
4386
|
);
|
|
@@ -3790,7 +4430,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3790
4430
|
return {
|
|
3791
4431
|
content: [{
|
|
3792
4432
|
type: "text",
|
|
3793
|
-
text: buildDoctorReport({
|
|
4433
|
+
text: buildDoctorReport(omitUndefinedProperties({
|
|
3794
4434
|
cwd: requestCwd,
|
|
3795
4435
|
config: deps.config,
|
|
3796
4436
|
state: deps.state,
|
|
@@ -3801,13 +4441,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3801
4441
|
orchestratorTarget,
|
|
3802
4442
|
sessionError,
|
|
3803
4443
|
expandTilde: deps.expandTilde,
|
|
3804
|
-
}),
|
|
4444
|
+
})),
|
|
3805
4445
|
}],
|
|
3806
4446
|
details: { mode: "management", results: [], spawnBudget },
|
|
3807
4447
|
};
|
|
3808
4448
|
}
|
|
3809
4449
|
if (action === "status") {
|
|
3810
|
-
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4450
|
+
if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3811
4451
|
const withBudget = (result: AgentToolResult<Details>) => withSpawnBudgetStatus(
|
|
3812
4452
|
result,
|
|
3813
4453
|
deps.state,
|
|
@@ -3818,11 +4458,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3818
4458
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
3819
4459
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
3820
4460
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
3821
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
4461
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
3822
4462
|
}
|
|
3823
4463
|
if (targetRunId) {
|
|
3824
4464
|
try {
|
|
3825
|
-
const resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedScope });
|
|
4465
|
+
const resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedScope }));
|
|
3826
4466
|
if (resolved?.kind === "foreground") {
|
|
3827
4467
|
const foreground = getForegroundControl(deps.state, resolved.id);
|
|
3828
4468
|
if (foreground) {
|
|
@@ -3849,7 +4489,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3849
4489
|
});
|
|
3850
4490
|
}
|
|
3851
4491
|
}
|
|
3852
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
4492
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
3853
4493
|
}
|
|
3854
4494
|
|
|
3855
4495
|
if (action === "approve-checkpoint" || action === "reject-checkpoint") {
|
|
@@ -3859,7 +4499,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3859
4499
|
return { content: [{ type: "text", text: `action='${action}' requires id or dir.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3860
4500
|
}
|
|
3861
4501
|
try {
|
|
3862
|
-
const resolved = targetRunId ? resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }) : undefined;
|
|
4502
|
+
const resolved = targetRunId ? resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
|
|
3863
4503
|
const decision = action === "approve-checkpoint" ? "approved" : "rejected";
|
|
3864
4504
|
if (resolved?.kind === "foreground") {
|
|
3865
4505
|
const run = deps.state.foregroundRuns?.get(resolved.id);
|
|
@@ -3870,7 +4510,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3870
4510
|
return { content: [{ type: "text", text: `Run '${resolved.id}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3871
4511
|
}
|
|
3872
4512
|
const decidedAt = Date.now();
|
|
3873
|
-
const checkpoint
|
|
4513
|
+
const checkpoint: NonNullable<Details["checkpoint"]> = decision === "approved"
|
|
4514
|
+
? { ...run.checkpoint, status: "approved", approvedAt: decidedAt }
|
|
4515
|
+
: { ...run.checkpoint, status: "rejected", rejectedAt: decidedAt };
|
|
3874
4516
|
run.checkpoint = checkpoint;
|
|
3875
4517
|
run.updatedAt = decidedAt;
|
|
3876
4518
|
return {
|
|
@@ -3879,7 +4521,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3879
4521
|
};
|
|
3880
4522
|
}
|
|
3881
4523
|
const location = paramsWithResolvedCwd.dir
|
|
3882
|
-
? resolveAsyncRunLocation(paramsWithResolvedCwd,
|
|
4524
|
+
? resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results)
|
|
3883
4525
|
: resolved?.kind === "async"
|
|
3884
4526
|
? resolved.location
|
|
3885
4527
|
: undefined;
|
|
@@ -3905,7 +4547,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3905
4547
|
}
|
|
3906
4548
|
}
|
|
3907
4549
|
if (action === "resume") {
|
|
3908
|
-
return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
|
|
4550
|
+
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
|
|
3909
4551
|
}
|
|
3910
4552
|
if (action === "steer") {
|
|
3911
4553
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
@@ -3914,9 +4556,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3914
4556
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
3915
4557
|
if (paramsWithResolvedCwd.dir) {
|
|
3916
4558
|
try {
|
|
3917
|
-
const location = resolveAsyncRunLocation(paramsWithResolvedCwd,
|
|
4559
|
+
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
|
|
3918
4560
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
3919
|
-
|
|
4561
|
+
if (location.asyncDir) {
|
|
4562
|
+
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
4563
|
+
if (unsupported) return unsupported;
|
|
4564
|
+
}
|
|
4565
|
+
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
3920
4566
|
state: deps.state,
|
|
3921
4567
|
runId,
|
|
3922
4568
|
message,
|
|
@@ -3928,10 +4574,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3928
4574
|
? {}
|
|
3929
4575
|
: {
|
|
3930
4576
|
recover: ({ absoluteDeadlineAt, ...limits }) =>
|
|
3931
|
-
resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, parentModel: requestParentModel, absoluteDeadlineAt }),
|
|
4577
|
+
resumeAsyncRun(omitUndefinedProperties({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, parentModel: requestParentModel, absoluteDeadlineAt })),
|
|
3932
4578
|
}
|
|
3933
4579
|
),
|
|
3934
|
-
});
|
|
4580
|
+
}));
|
|
3935
4581
|
} catch (error) {
|
|
3936
4582
|
const text = error instanceof Error ? error.message : String(error);
|
|
3937
4583
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -3940,15 +4586,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3940
4586
|
if (!targetRunId) return { content: [{ type: "text", text: "action='steer' requires id or dir." }], isError: true, details: { mode: "management", results: [] } };
|
|
3941
4587
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
3942
4588
|
try {
|
|
3943
|
-
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
4589
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
3944
4590
|
} catch (error) {
|
|
3945
4591
|
const text = error instanceof Error ? error.message : String(error);
|
|
3946
4592
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
3947
4593
|
}
|
|
3948
|
-
if (resolved?.kind === "nested") return steerNestedRun({ target: resolved, message, index: paramsWithResolvedCwd.index, signal });
|
|
4594
|
+
if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, index: paramsWithResolvedCwd.index, signal }));
|
|
3949
4595
|
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
4596
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3951
|
-
|
|
4597
|
+
if (resolved.location.asyncDir) {
|
|
4598
|
+
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
4599
|
+
if (unsupported) return unsupported;
|
|
4600
|
+
}
|
|
4601
|
+
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
3952
4602
|
state: deps.state,
|
|
3953
4603
|
runId: resolved.id,
|
|
3954
4604
|
message,
|
|
@@ -3960,22 +4610,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3960
4610
|
? {}
|
|
3961
4611
|
: {
|
|
3962
4612
|
recover: ({ absoluteDeadlineAt, ...limits }) =>
|
|
3963
|
-
resumeAsyncRun({
|
|
4613
|
+
resumeAsyncRun(omitUndefinedProperties({
|
|
3964
4614
|
params: { ...limits, action: "resume", id: resolved!.id, message },
|
|
3965
4615
|
requestCwd,
|
|
3966
4616
|
ctx,
|
|
3967
4617
|
deps,
|
|
3968
4618
|
parentModel: requestParentModel,
|
|
3969
4619
|
absoluteDeadlineAt,
|
|
3970
|
-
}),
|
|
4620
|
+
})),
|
|
3971
4621
|
}
|
|
3972
4622
|
),
|
|
3973
|
-
});
|
|
4623
|
+
}));
|
|
3974
4624
|
}
|
|
3975
4625
|
if (action === "append-step") {
|
|
3976
|
-
return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
|
|
4626
|
+
return appendStepToAsyncChain(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
|
|
3977
4627
|
}
|
|
3978
|
-
if (action
|
|
4628
|
+
if (action.startsWith("schedule.")) {
|
|
4629
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4630
|
+
return {
|
|
4631
|
+
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
4632
|
+
isError: true,
|
|
4633
|
+
details: { mode: "management", results: [] },
|
|
4634
|
+
};
|
|
4635
|
+
}
|
|
3979
4636
|
if (!deps.handleScheduledRunAction) {
|
|
3980
4637
|
return {
|
|
3981
4638
|
content: [{ type: "text", text: `Action '${action}' is not available in this subagent context.` }],
|
|
@@ -3987,11 +4644,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3987
4644
|
}
|
|
3988
4645
|
if (action === "stop") {
|
|
3989
4646
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
4647
|
+
const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
|
|
4648
|
+
if (workflowController) {
|
|
4649
|
+
workflowController.abort(new Error("Workflow stopped by user."));
|
|
4650
|
+
return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
|
|
4651
|
+
}
|
|
3990
4652
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
3991
4653
|
if (paramsWithResolvedCwd.dir) {
|
|
3992
4654
|
try {
|
|
3993
|
-
const location = resolveAsyncRunLocation(paramsWithResolvedCwd,
|
|
3994
|
-
|
|
4655
|
+
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
|
|
4656
|
+
const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location);
|
|
4657
|
+
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
4658
|
} catch (error) {
|
|
3996
4659
|
const text = error instanceof Error ? error.message : String(error);
|
|
3997
4660
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -3999,13 +4662,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3999
4662
|
}
|
|
4000
4663
|
if (!targetRunId) return { content: [{ type: "text", text: "action='stop' requires id or dir." }], isError: true, details: { mode: "management", results: [] } };
|
|
4001
4664
|
try {
|
|
4002
|
-
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
4665
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
4003
4666
|
} catch (error) {
|
|
4004
4667
|
const message = error instanceof Error ? error.message : String(error);
|
|
4005
4668
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
4006
4669
|
}
|
|
4007
4670
|
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
4671
|
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: [] } };
|
|
4672
|
+
if (resolved?.kind === "async") {
|
|
4673
|
+
const existingStatus = readStatus(resolved.location.asyncDir ?? "");
|
|
4674
|
+
if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
|
|
4675
|
+
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: [] } };
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4009
4678
|
const stopResult = stopAsyncRun(
|
|
4010
4679
|
deps.state,
|
|
4011
4680
|
resolved?.kind === "async" ? resolved.id : targetRunId,
|
|
@@ -4024,7 +4693,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4024
4693
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
4025
4694
|
if (targetRunId) {
|
|
4026
4695
|
try {
|
|
4027
|
-
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
4696
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
4028
4697
|
} catch (error) {
|
|
4029
4698
|
const message = error instanceof Error ? error.message : String(error);
|
|
4030
4699
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -4036,7 +4705,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4036
4705
|
const interrupted = foreground.interrupt();
|
|
4037
4706
|
if (interrupted) {
|
|
4038
4707
|
foreground.updatedAt = Date.now();
|
|
4039
|
-
foreground.currentActivityState
|
|
4708
|
+
delete foreground.currentActivityState;
|
|
4040
4709
|
return {
|
|
4041
4710
|
content: [{ type: "text", text: `Interrupt requested for foreground run ${foreground.runId}.` }],
|
|
4042
4711
|
details: { mode: "management", results: [] },
|
|
@@ -4171,7 +4840,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4171
4840
|
const spawnPreflight = preflightSpawnBudget(
|
|
4172
4841
|
deps.state,
|
|
4173
4842
|
deps.config,
|
|
4174
|
-
|
|
4843
|
+
requestSessionId,
|
|
4175
4844
|
requestedSpawns,
|
|
4176
4845
|
);
|
|
4177
4846
|
if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
|
|
@@ -4216,6 +4885,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4216
4885
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
4217
4886
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
4218
4887
|
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
4888
|
+
const selectedAgentNames = hasSingle
|
|
4889
|
+
? [effectiveParams.agent!]
|
|
4890
|
+
: hasTasks
|
|
4891
|
+
? (effectiveParams.tasks ?? []).map((task) => task.agent)
|
|
4892
|
+
: (effectiveParams.chain ?? []).flatMap((step) => getStepAgents(step as ChainStep));
|
|
4893
|
+
const externalAgent = selectedAgentNames
|
|
4894
|
+
.map((name) => agents.find((agent) => agent.name === name))
|
|
4895
|
+
.find((agent) => agent?.runner?.type === "external-cli");
|
|
4896
|
+
if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
|
|
4897
|
+
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.`);
|
|
4898
|
+
}
|
|
4219
4899
|
const foregroundTimeout = resolveForegroundTimeout(
|
|
4220
4900
|
effectiveParams,
|
|
4221
4901
|
effectiveAsync ? undefined : DEFAULT_FOREGROUND_TIMEOUT_MS,
|
|
@@ -4223,12 +4903,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4223
4903
|
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
4224
4904
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
4225
4905
|
|
|
4226
|
-
const artifactConfig: ArtifactConfig = {
|
|
4906
|
+
const artifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
4227
4907
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
4228
4908
|
enabled: effectiveParams.artifacts !== false,
|
|
4229
4909
|
dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
4230
|
-
};
|
|
4910
|
+
});
|
|
4231
4911
|
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
4912
|
+
if (artifactConfig.dir === "project" && !warnedArtifactPackageDirs.has(effectiveCwd)) {
|
|
4913
|
+
warnedArtifactPackageDirs.add(effectiveCwd);
|
|
4914
|
+
const warning = getProjectArtifactPackagingWarning(effectiveCwd);
|
|
4915
|
+
if (warning) console.warn(`[pi-subagents] ${warning}`);
|
|
4916
|
+
}
|
|
4232
4917
|
|
|
4233
4918
|
let sessionRoot: string;
|
|
4234
4919
|
if (effectiveParams.sessionDir) {
|
|
@@ -4284,15 +4969,48 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4284
4969
|
}, contextPolicy.contextSummary))
|
|
4285
4970
|
: undefined;
|
|
4286
4971
|
|
|
4972
|
+
let missionBinding: MissionLaunchBinding | undefined;
|
|
4973
|
+
let missionWarning: string | undefined;
|
|
4974
|
+
const explicitMission = effectiveParams.missionId !== undefined || effectiveParams.mission !== undefined;
|
|
4975
|
+
try {
|
|
4976
|
+
missionBinding = prepareMissionLaunch({
|
|
4977
|
+
params: effectiveParams,
|
|
4978
|
+
projectRoot: effectiveCwd,
|
|
4979
|
+
...(deps.config.missions ? { config: deps.config.missions } : {}),
|
|
4980
|
+
ownerSessionId: requestSessionId,
|
|
4981
|
+
});
|
|
4982
|
+
} catch (error) {
|
|
4983
|
+
if (explicitMission) return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
4984
|
+
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
4985
|
+
}
|
|
4986
|
+
|
|
4987
|
+
const attachMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
4988
|
+
if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
|
|
4989
|
+
try {
|
|
4990
|
+
return attachMissionToLaunchResult({ binding: missionBinding, result });
|
|
4991
|
+
} catch (error) {
|
|
4992
|
+
const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
|
|
4993
|
+
if (explicitMission) {
|
|
4994
|
+
return {
|
|
4995
|
+
...result,
|
|
4996
|
+
isError: true,
|
|
4997
|
+
content: [...result.content, { type: "text", text: warning }],
|
|
4998
|
+
details: { ...result.details, missionWarning: warning },
|
|
4999
|
+
};
|
|
5000
|
+
}
|
|
5001
|
+
return { ...result, details: { ...result.details, missionWarning: warning } };
|
|
5002
|
+
}
|
|
5003
|
+
};
|
|
5004
|
+
|
|
4287
5005
|
const reservation = reserveSpawnBudget(
|
|
4288
5006
|
deps.state,
|
|
4289
5007
|
deps.config,
|
|
4290
|
-
|
|
5008
|
+
requestSessionId,
|
|
4291
5009
|
requestedSpawns,
|
|
4292
5010
|
);
|
|
4293
|
-
if (reservation.error) return spawnBudgetErrorResult(reservation.error, foregroundMode);
|
|
5011
|
+
if (reservation.error) return attachMission(spawnBudgetErrorResult(reservation.error, foregroundMode));
|
|
4294
5012
|
|
|
4295
|
-
const execData: ExecutionContextData = {
|
|
5013
|
+
const execData: ExecutionContextData = omitUndefinedProperties({
|
|
4296
5014
|
params: effectiveParams,
|
|
4297
5015
|
effectiveCwd,
|
|
4298
5016
|
ctx,
|
|
@@ -4322,18 +5040,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4322
5040
|
contextPolicy,
|
|
4323
5041
|
modelScope,
|
|
4324
5042
|
parentModel: requestParentModel,
|
|
4325
|
-
parentSessionId:
|
|
4326
|
-
|
|
4327
|
-
|
|
5043
|
+
parentSessionId: requestSessionId,
|
|
5044
|
+
parentPiSessionId: requestPiSessionId,
|
|
5045
|
+
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
|
|
5046
|
+
});
|
|
4328
5047
|
|
|
4329
5048
|
const foregroundDescription = effectiveParams.task?.trim()
|
|
4330
5049
|
|| effectiveParams.tasks?.[0]?.task?.trim()
|
|
4331
5050
|
|| (effectiveParams.chain ? firstRawChainTask(effectiveParams.chain)?.trim() : undefined);
|
|
4332
5051
|
const foregroundControl: ForegroundRunControl | undefined = effectiveAsync
|
|
4333
5052
|
? undefined
|
|
4334
|
-
: {
|
|
5053
|
+
: compactOptional<ForegroundRunControl>({
|
|
4335
5054
|
runId,
|
|
4336
|
-
|
|
5055
|
+
sessionId: requestSessionId,
|
|
4337
5056
|
mode: foregroundMode,
|
|
4338
5057
|
startedAt: Date.now(),
|
|
4339
5058
|
updatedAt: Date.now(),
|
|
@@ -4347,7 +5066,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4347
5066
|
schedulingOwners: 1,
|
|
4348
5067
|
nestedRoute,
|
|
4349
5068
|
interrupt: undefined,
|
|
4350
|
-
};
|
|
5069
|
+
});
|
|
4351
5070
|
if (foregroundControl) {
|
|
4352
5071
|
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
4353
5072
|
deps.state.lastForegroundControlId = runId;
|
|
@@ -4367,21 +5086,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4367
5086
|
const errorText = result?.isError
|
|
4368
5087
|
? result.content.find((item) => item.type === "text")?.text
|
|
4369
5088
|
: undefined;
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
5089
|
+
let startedLaunches: StaticLaunchSummary[];
|
|
5090
|
+
try {
|
|
5091
|
+
startedLaunches = collectStaticLaunchSummaries({
|
|
5092
|
+
params: effectiveParams,
|
|
5093
|
+
agents,
|
|
5094
|
+
parentModel: requestParentModel,
|
|
5095
|
+
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
5096
|
+
currentProvider: requestParentModel?.provider,
|
|
5097
|
+
modelScope,
|
|
5098
|
+
thinkingOverrideForTask: forkThinkingOverrideForTask,
|
|
5099
|
+
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
5100
|
+
});
|
|
5101
|
+
} catch (error) {
|
|
5102
|
+
console.error("Failed to resolve nested foreground launch metadata:", error);
|
|
5103
|
+
startedLaunches = selectedAgentNames.map((agent) => ({ agent }));
|
|
5104
|
+
}
|
|
5105
|
+
const agentsForSummary = startedLaunches.map((launch) => launch.agent);
|
|
4375
5106
|
const leafIntercomTarget = intercomBridge.active && agentsForSummary[0]
|
|
4376
5107
|
? resolveSubagentIntercomTarget(runId, agentsForSummary[0], 0)
|
|
4377
5108
|
: undefined;
|
|
4378
5109
|
try {
|
|
4379
|
-
writeNestedEvent(inheritedNestedRoute, {
|
|
5110
|
+
writeNestedEvent(inheritedNestedRoute, compactOptional<Parameters<typeof writeNestedEvent>[1]>({
|
|
4380
5111
|
type,
|
|
4381
5112
|
ts: now,
|
|
4382
5113
|
parentRunId: nestedParentAddress.parentRunId,
|
|
4383
5114
|
parentStepIndex: nestedParentAddress.parentStepIndex,
|
|
4384
|
-
child: {
|
|
5115
|
+
child: compactOptional<NestedRunSummary>({
|
|
4385
5116
|
id: runId,
|
|
4386
5117
|
parentRunId: nestedParentAddress.parentRunId,
|
|
4387
5118
|
parentStepIndex: nestedParentAddress.parentStepIndex,
|
|
@@ -4395,19 +5126,32 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4395
5126
|
state,
|
|
4396
5127
|
agent: agentsForSummary[0],
|
|
4397
5128
|
agents: agentsForSummary,
|
|
5129
|
+
...(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 } : {}),
|
|
5130
|
+
...(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
5131
|
startedAt: foregroundControl?.startedAt ?? now,
|
|
4399
5132
|
...(state !== "running" ? { endedAt: now } : {}),
|
|
4400
5133
|
lastUpdate: now,
|
|
4401
5134
|
...(details?.totalCost ? { totalCost: details.totalCost } : {}),
|
|
4402
5135
|
...(errorText ? { error: errorText } : {}),
|
|
4403
|
-
...(
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
5136
|
+
...(type === "subagent.nested.started"
|
|
5137
|
+
? { steps: startedLaunches.map((launch) => ({
|
|
5138
|
+
agent: launch.agent,
|
|
5139
|
+
status: "running" as const,
|
|
5140
|
+
...(launch.model ? { model: launch.model } : {}),
|
|
5141
|
+
...(launch.thinking ? { thinking: launch.thinking } : {}),
|
|
5142
|
+
})) }
|
|
5143
|
+
: details?.results.length
|
|
5144
|
+
? { steps: details.results.map((child) => ({
|
|
5145
|
+
agent: child.agent,
|
|
5146
|
+
status: child.interrupted || child.detached ? "paused" as const : child.exitCode === 0 ? "complete" as const : "failed" as const,
|
|
5147
|
+
...(child.model ? { model: child.model } : {}),
|
|
5148
|
+
...(child.thinking ? { thinking: child.thinking } : {}),
|
|
5149
|
+
...(child.sessionFile ? { sessionFile: child.sessionFile } : {}),
|
|
5150
|
+
...(child.error ? { error: child.error } : {}),
|
|
5151
|
+
})) }
|
|
5152
|
+
: {}),
|
|
5153
|
+
}),
|
|
5154
|
+
}));
|
|
4411
5155
|
} catch (error) {
|
|
4412
5156
|
console.error("Failed to emit nested foreground status event:", error);
|
|
4413
5157
|
}
|
|
@@ -4416,7 +5160,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4416
5160
|
let nestedForegroundStarted = false;
|
|
4417
5161
|
try {
|
|
4418
5162
|
const asyncResult = runAsyncPath(execData, deps);
|
|
4419
|
-
if (asyncResult) return withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
5163
|
+
if (asyncResult) return attachMission(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
4420
5164
|
if (foregroundControl) {
|
|
4421
5165
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
4422
5166
|
nestedForegroundStarted = true;
|
|
@@ -4424,22 +5168,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4424
5168
|
if (hasChain && effectiveParams.chain) {
|
|
4425
5169
|
const result = await runChainPath(execData, deps);
|
|
4426
5170
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
4427
|
-
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
5171
|
+
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
4428
5172
|
}
|
|
4429
5173
|
if (hasTasks && effectiveParams.tasks) {
|
|
4430
5174
|
const result = await runParallelPath(execData, deps);
|
|
4431
5175
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
4432
|
-
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
5176
|
+
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
4433
5177
|
}
|
|
4434
5178
|
if (hasSingle) {
|
|
4435
5179
|
const result = await runSinglePath(execData, deps);
|
|
4436
5180
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
4437
|
-
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
5181
|
+
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
4438
5182
|
}
|
|
4439
5183
|
} catch (error) {
|
|
4440
5184
|
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
4441
5185
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
4442
|
-
return errorResult;
|
|
5186
|
+
return attachMission(errorResult);
|
|
4443
5187
|
} finally {
|
|
4444
5188
|
if (foregroundControl) {
|
|
4445
5189
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
@@ -4497,5 +5241,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4497
5241
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
4498
5242
|
};
|
|
4499
5243
|
|
|
4500
|
-
|
|
5244
|
+
const executeScheduled = (
|
|
5245
|
+
id: string,
|
|
5246
|
+
params: SubagentParamsLike,
|
|
5247
|
+
signal: AbortSignal,
|
|
5248
|
+
ctx: ExtensionContext,
|
|
5249
|
+
) => {
|
|
5250
|
+
const ownerSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5251
|
+
let ownerExecutor = scheduledOwnerExecutors.get(ownerSessionId);
|
|
5252
|
+
if (!ownerExecutor) {
|
|
5253
|
+
ownerExecutor = createSubagentExecutor({
|
|
5254
|
+
...deps,
|
|
5255
|
+
state: createScheduledOwnerState(deps.state, ownerSessionId, ctx),
|
|
5256
|
+
});
|
|
5257
|
+
scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
|
|
5258
|
+
}
|
|
5259
|
+
return ownerExecutor.execute(id, params, signal, undefined, ctx);
|
|
5260
|
+
};
|
|
5261
|
+
|
|
5262
|
+
return { execute: executeWithSingleDispatchGuard, executeDelegated, executeScheduled };
|
|
4501
5263
|
}
|