pi-subagents 0.58.0 → 0.59.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 +65 -0
- package/docs/agents.md +5 -3
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +1 -1
- package/docs/models.md +24 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +89 -4
- package/docs/workflows.md +93 -2
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +1 -1
- package/skills/pi-subagents/SKILL.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +5 -1
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +24 -3
- package/src/agents/agent-management.ts +5 -17
- package/src/agents/agent-serializer.ts +4 -2
- package/src/agents/agents.ts +67 -26
- package/src/agents/runtime-agent-registry.ts +9 -16
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +6 -9
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +51 -23
- package/src/extension/public-execution.ts +15 -2
- package/src/extension/schemas.ts +35 -10
- package/src/extension/tool-description.ts +18 -3
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +50 -35
- package/src/runs/background/async-job-tracker.ts +14 -12
- package/src/runs/background/async-resume.ts +23 -25
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +66 -7
- package/src/runs/background/chain-append.ts +6 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +158 -6
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +2 -0
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +18 -8
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +346 -357
- package/src/runs/background/subagent-wait.ts +58 -10
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +22 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +114 -120
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +478 -239
- package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +463 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/mcp-config-sources.ts +42 -6
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
- package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
- package/src/runs/shared/model-fallback.ts +9 -2
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +20 -2
- package/src/runs/shared/single-output.ts +27 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -4
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +5 -2
- package/src/shared/launch-contract.ts +1 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +198 -29
- package/src/shared/utils.ts +35 -55
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/tui/fleet-status.ts +125 -74
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +316 -27
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +230 -0
- package/src/workflows/scripted-workflow.ts +452 -38
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +43 -4
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
|
@@ -4,7 +4,7 @@ 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 { discoverAgents, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
7
|
-
import { getArtifactsDir,
|
|
7
|
+
import { getArtifactsDir, getProjectArtifactPackagingWarning, getProjectSubagentsDir } from "../../shared/artifacts.ts";
|
|
8
8
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
9
9
|
import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
|
|
10
10
|
import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
beginForegroundChild,
|
|
14
14
|
finishForegroundChild,
|
|
15
15
|
foregroundSchedulingSettled,
|
|
16
|
-
retainForegroundSchedulingOwner,
|
|
17
16
|
settleForegroundSchedulingOwner,
|
|
18
17
|
updateForegroundChild,
|
|
19
18
|
} from "./foreground-control.ts";
|
|
@@ -28,36 +27,27 @@ import { normalizePublicSubagentExecution } from "../../extension/public-executi
|
|
|
28
27
|
import { runSync } from "./execution.ts";
|
|
29
28
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
30
29
|
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
31
|
-
import { buildModelCandidates, inheritsParentModel, normalizeParentModel, resolveEffectiveSubagentModel,
|
|
30
|
+
import { buildModelCandidates, inheritsParentModel, normalizeParentModel, resolveEffectiveSubagentModel, type ParentModel } from "../shared/model-fallback.ts";
|
|
32
31
|
import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
|
|
33
32
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
34
|
-
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
35
33
|
import { recordRun } from "../shared/run-history.ts";
|
|
36
34
|
import {
|
|
37
|
-
buildChainInstructions,
|
|
38
|
-
writeInitialProgressFile,
|
|
39
35
|
getStepAgents,
|
|
40
36
|
isParallelStep,
|
|
41
37
|
isDynamicParallelStep,
|
|
42
|
-
resolveChainPath,
|
|
43
38
|
resolveExistingReadPaths,
|
|
44
|
-
resolveStepBehavior,
|
|
45
|
-
suppressProgressForReadOnlyTask,
|
|
46
|
-
taskDisallowsFileUpdates,
|
|
47
39
|
type ChainStep,
|
|
48
40
|
type DynamicParallelStep,
|
|
49
41
|
type ParallelStep,
|
|
50
42
|
type ParallelTaskItem,
|
|
51
|
-
type ResolvedStepBehavior,
|
|
52
43
|
type SequentialStep,
|
|
53
|
-
type StepOverrides,
|
|
54
44
|
} from "../../shared/settings.ts";
|
|
55
|
-
import {
|
|
45
|
+
import { normalizeSkillInput } from "../../agents/skills.ts";
|
|
56
46
|
import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
|
|
57
47
|
import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
58
48
|
import { steeringReceipt } from "../background/steering.ts";
|
|
59
49
|
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
60
|
-
import { isScheduledRunAction
|
|
50
|
+
import { isScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
61
51
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
62
52
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
63
53
|
import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
@@ -67,7 +57,6 @@ import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
|
67
57
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
68
58
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
69
59
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
70
|
-
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
71
60
|
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
72
61
|
import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, decodeRunFanoutBudgetDescriptor, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, RUN_FANOUT_BUDGET_ENV, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
73
62
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
@@ -77,10 +66,9 @@ import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
|
77
66
|
import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
78
67
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
79
68
|
import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
80
|
-
import { compactForegroundDetails, getSingleResultOutput,
|
|
69
|
+
import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.ts";
|
|
81
70
|
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
82
|
-
import {
|
|
83
|
-
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
71
|
+
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
84
72
|
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
85
73
|
import {
|
|
86
74
|
attachNestedChildrenToResultChildren,
|
|
@@ -125,19 +113,22 @@ import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
|
125
113
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
126
114
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
127
115
|
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
116
|
+
import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
|
|
128
117
|
import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
|
|
129
|
-
import {
|
|
118
|
+
import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
|
|
119
|
+
import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
|
|
120
|
+
import { parseWorkflowChildSummary, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
130
121
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
122
|
+
import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.ts";
|
|
131
123
|
import { claimWorkflowChildPermit, validateWorkflowChildPermitRoot, type WorkflowChildPermit, type WorkflowChildPermitContext } from "../../shared/workflow-child-permit.ts";
|
|
132
124
|
import {
|
|
133
125
|
cleanupWorktrees,
|
|
134
126
|
createWorktrees,
|
|
135
127
|
diffWorktrees,
|
|
136
|
-
findWorktreeTaskCwdConflict,
|
|
137
128
|
formatWorktreeDiffSummary,
|
|
138
|
-
formatWorktreeTaskCwdConflict,
|
|
139
129
|
type WorktreeSetup,
|
|
140
130
|
} from "../shared/worktree.ts";
|
|
131
|
+
import { createWorktreeCleanupPlan, formatWorktreeCleanupPlan } from "../shared/worktree-cleanup-plan.ts";
|
|
141
132
|
import {
|
|
142
133
|
type AgentProgress,
|
|
143
134
|
type AsyncJobState,
|
|
@@ -152,6 +143,7 @@ import {
|
|
|
152
143
|
type ExtensionConfig,
|
|
153
144
|
type ForegroundResumeChild,
|
|
154
145
|
type ForegroundRunControl,
|
|
146
|
+
type HostStepNodeV1,
|
|
155
147
|
type IntercomBridgeConfig,
|
|
156
148
|
type IntercomEventBus,
|
|
157
149
|
type JsonSchemaObject,
|
|
@@ -160,14 +152,13 @@ import {
|
|
|
160
152
|
type NestedRunSummary,
|
|
161
153
|
type OutputMode,
|
|
162
154
|
type ResolvedControlConfig,
|
|
163
|
-
type ResolvedTurnBudget,
|
|
164
155
|
type ResolvedToolBudget,
|
|
165
156
|
type RunFanoutBudgetDescriptor,
|
|
166
157
|
type SteeringRecoveryDescriptor,
|
|
167
158
|
type SingleResult,
|
|
168
159
|
type SubagentChildStatusEvent,
|
|
169
160
|
type ToolBudgetConfig,
|
|
170
|
-
type
|
|
161
|
+
type Usage,
|
|
171
162
|
type UsageBudgetConfig,
|
|
172
163
|
type WorkflowTerminalOutcome,
|
|
173
164
|
type SubagentRunMode,
|
|
@@ -180,18 +171,21 @@ import {
|
|
|
180
171
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
181
172
|
SUBAGENT_FOREGROUND_COMPLETE_EVENT,
|
|
182
173
|
checkSubagentDepth,
|
|
183
|
-
resolveTopLevelParallelConcurrency,
|
|
184
|
-
resolveTopLevelParallelMaxTasks,
|
|
185
174
|
resolveChildMaxSubagentDepth,
|
|
186
175
|
resolveCurrentMaxSubagentDepth,
|
|
187
176
|
resolveMaxSubagentSpawnsPerRun,
|
|
188
177
|
wrapForkTask,
|
|
189
178
|
type ScheduleOrigin,
|
|
190
179
|
} from "../../shared/types.ts";
|
|
180
|
+
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
191
181
|
|
|
192
|
-
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
182
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "worktree.cleanup", "lane.recordMerge", "lane.recordSupersession", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
193
183
|
const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "schedule.delete"]);
|
|
194
184
|
|
|
185
|
+
function resolveSteerDeliveryMode(mode: SubagentParamsLike["mode"]): SteerDeliveryMode | undefined {
|
|
186
|
+
return mode === "steer" || mode === "follow_up" || mode === "auto" ? mode : undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
195
189
|
function editDistance(left: string, right: string): number {
|
|
196
190
|
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
197
191
|
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
@@ -287,6 +281,9 @@ export interface SubagentParamsLike {
|
|
|
287
281
|
runId?: string;
|
|
288
282
|
dir?: string;
|
|
289
283
|
handoffPath?: string;
|
|
284
|
+
laneId?: string;
|
|
285
|
+
merge?: unknown;
|
|
286
|
+
supersession?: unknown;
|
|
290
287
|
index?: number;
|
|
291
288
|
childId?: string;
|
|
292
289
|
view?: "fleet" | "transcript";
|
|
@@ -303,20 +300,27 @@ export interface SubagentParamsLike {
|
|
|
303
300
|
resume?: string;
|
|
304
301
|
message?: string;
|
|
305
302
|
steeringRecovery?: boolean;
|
|
306
|
-
mode?: SteerDeliveryMode;
|
|
303
|
+
mode?: SteerDeliveryMode | "plan" | "apply";
|
|
304
|
+
repo?: string;
|
|
305
|
+
planId?: string;
|
|
307
306
|
workflowScript?: string;
|
|
308
307
|
workflowScriptPath?: string;
|
|
308
|
+
preflight?: import("../../shared/types.ts").WorkflowPreflightV1;
|
|
309
309
|
chatProgress?: "auto" | "off" | "live-card";
|
|
310
310
|
isolation?: "none" | "worktree";
|
|
311
311
|
step?: ChainStep;
|
|
312
312
|
/** Internal workflow ownership metadata; not part of the public schema. */
|
|
313
313
|
workflowParentRunId?: string;
|
|
314
314
|
workflowKey?: string;
|
|
315
|
+
lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
|
|
315
316
|
/** Set by the scheduler so this run's completion can name the schedule that produced it. */
|
|
316
317
|
scheduleOrigin?: ScheduleOrigin;
|
|
317
318
|
workflowChildAsyncId?: string;
|
|
318
319
|
workflowAwaitAsync?: boolean;
|
|
320
|
+
/** Internal async-workflow bridge: keep the live VM await pending across supervisor detachment. */
|
|
321
|
+
workflowAwaitDetached?: boolean;
|
|
319
322
|
workflowParentDeadlineAt?: number;
|
|
323
|
+
workflowOutputClaimPath?: string;
|
|
320
324
|
suppressRoutineResultIntercom?: boolean;
|
|
321
325
|
/** Internal inherited cumulative run-tree budget. */
|
|
322
326
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
@@ -338,9 +342,6 @@ export interface SubagentParamsLike {
|
|
|
338
342
|
maxRuntimeMs?: number;
|
|
339
343
|
/** Optional hard per-tool-call timeout (ms). Known-fast tools also have a default. */
|
|
340
344
|
toolTimeoutMs?: number;
|
|
341
|
-
turnBudget?: TurnBudgetConfig;
|
|
342
|
-
/** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
|
|
343
|
-
enforceHardTurnLimit?: boolean;
|
|
344
345
|
toolBudget?: ToolBudgetConfig;
|
|
345
346
|
usageBudget?: UsageBudgetConfig;
|
|
346
347
|
clarify?: boolean;
|
|
@@ -400,12 +401,13 @@ interface ExecutorDeps {
|
|
|
400
401
|
config: ExtensionConfig;
|
|
401
402
|
asyncByDefault: boolean;
|
|
402
403
|
waitToolEnabled?: boolean;
|
|
404
|
+
waitToolDefaultTimeoutMs?: number;
|
|
403
405
|
handleScheduledRunAction?: (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>;
|
|
404
406
|
watchdog?: MainWatchdogRuntime;
|
|
405
407
|
tempArtifactsDir: string;
|
|
406
408
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
407
409
|
expandTilde: (p: string) => string;
|
|
408
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
|
|
410
|
+
discoverAgents: (cwd: string, scope: AgentScope, preferredModelProvider?: string) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
|
|
409
411
|
allowMutatingManagementActions?: boolean;
|
|
410
412
|
activateSupervisorTransport?: () => void;
|
|
411
413
|
refreshResultDelivery?: () => void;
|
|
@@ -447,7 +449,6 @@ interface ExecutionContextData {
|
|
|
447
449
|
deadlineAt?: number;
|
|
448
450
|
/** Raw global config.toolTimeoutMs, for per-step resolution in async runners. */
|
|
449
451
|
configToolTimeoutMs?: number;
|
|
450
|
-
turnBudget?: ResolvedTurnBudget;
|
|
451
452
|
toolBudget?: ResolvedToolBudget;
|
|
452
453
|
usageBudget?: UsageBudgetConfig;
|
|
453
454
|
allowZeroToolBudget?: boolean;
|
|
@@ -504,12 +505,13 @@ function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
|
504
505
|
return newest;
|
|
505
506
|
}
|
|
506
507
|
|
|
507
|
-
function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentParamsLike {
|
|
508
|
+
export function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentParamsLike {
|
|
508
509
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Prompt redo is missing a safe live launch contract.");
|
|
509
510
|
const params = { ...(value as SubagentParamsLike), task: rewrittenTask, async: false };
|
|
510
511
|
delete params.workflowParentRunId;
|
|
511
512
|
delete params.workflowKey;
|
|
512
513
|
delete params.workflowChildAsyncId;
|
|
514
|
+
delete params.workflowAwaitDetached;
|
|
513
515
|
delete params.workflowParentDeadlineAt;
|
|
514
516
|
delete params.suppressRoutineResultIntercom;
|
|
515
517
|
if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
|
|
@@ -700,6 +702,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
700
702
|
const child = {
|
|
701
703
|
agent: result.agent,
|
|
702
704
|
index,
|
|
705
|
+
...(result.sessionName ? { sessionName: result.sessionName } : {}),
|
|
703
706
|
...(result.context ? { context: result.context } : {}),
|
|
704
707
|
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
705
708
|
exitCode: result.exitCode,
|
|
@@ -1254,7 +1257,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1254
1257
|
}
|
|
1255
1258
|
|
|
1256
1259
|
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
1257
|
-
const discoveredForAppend = input.deps.discoverAgents(input.requestCwd, scope);
|
|
1260
|
+
const discoveredForAppend = input.deps.discoverAgents(input.requestCwd, scope, input.parentModel?.provider);
|
|
1258
1261
|
const agents = discoveredForAppend.agents;
|
|
1259
1262
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
1260
1263
|
const chainSkillInput = normalizeSkillInput(input.params.skill);
|
|
@@ -1284,6 +1287,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1284
1287
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1285
1288
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1286
1289
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1290
|
+
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1287
1291
|
contextForAgent: contextPolicy.contextForAgent,
|
|
1288
1292
|
asyncDir: resolved.location.asyncDir,
|
|
1289
1293
|
validateOutputBindings: false,
|
|
@@ -1654,6 +1658,7 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1654
1658
|
...(input.parentSessionFile ? { sessionRoot: input.deps.getSubagentSessionRoot(input.parentSessionFile) } : {}),
|
|
1655
1659
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1656
1660
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1661
|
+
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1657
1662
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1658
1663
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1659
1664
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
@@ -1772,7 +1777,7 @@ async function resumeAsyncRun(input: {
|
|
|
1772
1777
|
input.deps.state.currentSessionId = resolveCurrentSessionId(input.ctx.sessionManager);
|
|
1773
1778
|
const effectiveCwd = target.cwd ?? input.requestCwd;
|
|
1774
1779
|
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
1775
|
-
const discovered = input.deps.discoverAgents(effectiveCwd, scope);
|
|
1780
|
+
const discovered = input.deps.discoverAgents(effectiveCwd, scope, input.parentModel?.provider);
|
|
1776
1781
|
const discoveredAgents = discovered.agents;
|
|
1777
1782
|
const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
|
|
1778
1783
|
const modelScope = discovered.modelScope;
|
|
@@ -1909,6 +1914,7 @@ async function resumeAsyncRun(input: {
|
|
|
1909
1914
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1910
1915
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1911
1916
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1917
|
+
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1912
1918
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1913
1919
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1914
1920
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
@@ -2020,10 +2026,12 @@ async function resumeAsyncRun(input: {
|
|
|
2020
2026
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
2021
2027
|
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
2022
2028
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
2029
|
+
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
2023
2030
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
2024
2031
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
2025
2032
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
2026
2033
|
worktree: input.params.worktree === true,
|
|
2034
|
+
lane: input.params.lane ?? recoveryDescriptor?.lane,
|
|
2027
2035
|
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
2028
2036
|
intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
2029
2037
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
@@ -2037,7 +2045,6 @@ async function resumeAsyncRun(input: {
|
|
|
2037
2045
|
...(input.params.acceptance !== undefined ? { acceptance: input.params.acceptance } : foregroundContract?.acceptance !== undefined ? { acceptance: foregroundContract.acceptance } : recoveryDescriptor?.acceptance !== undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
|
|
2038
2046
|
...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
|
|
2039
2047
|
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
2040
|
-
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
2041
2048
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
2042
2049
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
2043
2050
|
runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
@@ -2085,20 +2092,14 @@ async function resumeAsyncRun(input: {
|
|
|
2085
2092
|
input.signal?.removeEventListener("abort", stopOnAbort);
|
|
2086
2093
|
}
|
|
2087
2094
|
fs.rmSync(resultPath, { force: true });
|
|
2088
|
-
const
|
|
2095
|
+
const usage = importedAsyncRootUsage(completed);
|
|
2089
2096
|
const childResult: SingleResult = {
|
|
2090
2097
|
index: 0,
|
|
2091
2098
|
agent: completed.agent,
|
|
2099
|
+
...(completed.sessionName ? { sessionName: completed.sessionName } : {}),
|
|
2092
2100
|
task: effectiveFollowUp,
|
|
2093
2101
|
exitCode: completed.exitCode,
|
|
2094
|
-
usage
|
|
2095
|
-
input: totalCost?.inputTokens ?? 0,
|
|
2096
|
-
output: totalCost?.outputTokens ?? 0,
|
|
2097
|
-
cacheRead: 0,
|
|
2098
|
-
cacheWrite: 0,
|
|
2099
|
-
cost: totalCost?.costUsd ?? 0,
|
|
2100
|
-
turns: 0,
|
|
2101
|
-
},
|
|
2102
|
+
usage,
|
|
2102
2103
|
finalOutput: completed.output,
|
|
2103
2104
|
outputState: completed.output.trim() ? "present" : "absent",
|
|
2104
2105
|
...(completed.error ? { error: completed.error } : {}),
|
|
@@ -2235,6 +2236,7 @@ async function emitForegroundResultIntercom(input: {
|
|
|
2235
2236
|
if (!input.intercomBridge.active || !input.intercomBridge.resultDelivery || !input.intercomBridge.orchestratorTarget) return null;
|
|
2236
2237
|
const children = input.results.flatMap((result, index) => result.detached ? [] : [omitUndefinedProperties({
|
|
2237
2238
|
agent: result.agent,
|
|
2239
|
+
...(result.sessionName ? { sessionName: result.sessionName } : {}),
|
|
2238
2240
|
status: foregroundResultIntercomStatus(result),
|
|
2239
2241
|
outputState: result.outputState ?? "unknown",
|
|
2240
2242
|
summary: resultSummaryForIntercom(result),
|
|
@@ -2647,9 +2649,6 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
|
|
|
2647
2649
|
? { timeoutMs: agent.defaultTimeoutMs }
|
|
2648
2650
|
: {}),
|
|
2649
2651
|
...(parentTimeoutMs !== undefined ? { timeoutMs: parentTimeoutMs } : {}),
|
|
2650
|
-
...(params.turnBudget === undefined && agent.defaultTurnBudget !== undefined
|
|
2651
|
-
? { turnBudget: agent.defaultTurnBudget }
|
|
2652
|
-
: {}),
|
|
2653
2652
|
...(params.acceptance === undefined && agent.defaultAcceptance !== undefined
|
|
2654
2653
|
? { acceptance: agent.defaultAcceptance }
|
|
2655
2654
|
: {}),
|
|
@@ -2818,6 +2817,14 @@ function withResolvedContext(
|
|
|
2818
2817
|
};
|
|
2819
2818
|
}
|
|
2820
2819
|
|
|
2820
|
+
function withAggregatedToolUsage(result: AgentToolResult<Details>): AgentToolResult<Details> {
|
|
2821
|
+
if (result.details.results.length === 0) return result;
|
|
2822
|
+
const usage = sumResultsUsage(result.details.results);
|
|
2823
|
+
return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0
|
|
2824
|
+
? { ...result, usage: toAgentToolUsage(usage) }
|
|
2825
|
+
: result;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2821
2828
|
function withForkThinkingNotes(
|
|
2822
2829
|
result: AgentToolResult<Details>,
|
|
2823
2830
|
downgrades: Map<number, string>,
|
|
@@ -2843,66 +2850,6 @@ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown, cont
|
|
|
2843
2850
|
);
|
|
2844
2851
|
}
|
|
2845
2852
|
|
|
2846
|
-
function collectChainSessionFiles(
|
|
2847
|
-
chain: ChainStep[],
|
|
2848
|
-
sessionFileForTask: ForkSessionFileForTask,
|
|
2849
|
-
dynamicFanoutMaxItems?: number,
|
|
2850
|
-
): (string | undefined)[] {
|
|
2851
|
-
const sessionFiles: (string | undefined)[] = [];
|
|
2852
|
-
let flatIndex = 0;
|
|
2853
|
-
for (const step of chain) {
|
|
2854
|
-
if (isParallelStep(step)) {
|
|
2855
|
-
for (const task of step.parallel) {
|
|
2856
|
-
sessionFiles.push(sessionFileForTask(task.agent, flatIndex, task.model));
|
|
2857
|
-
flatIndex++;
|
|
2858
|
-
}
|
|
2859
|
-
continue;
|
|
2860
|
-
}
|
|
2861
|
-
if (isDynamicParallelStep(step)) {
|
|
2862
|
-
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
2863
|
-
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
2864
|
-
sessionFiles.push(sessionFileForTask(step.parallel.agent, flatIndex, step.parallel.model));
|
|
2865
|
-
flatIndex++;
|
|
2866
|
-
}
|
|
2867
|
-
continue;
|
|
2868
|
-
}
|
|
2869
|
-
const sequential = step as SequentialStep;
|
|
2870
|
-
sessionFiles.push(sessionFileForTask(sequential.agent, flatIndex, sequential.model));
|
|
2871
|
-
flatIndex++;
|
|
2872
|
-
}
|
|
2873
|
-
return sessionFiles;
|
|
2874
|
-
}
|
|
2875
|
-
|
|
2876
|
-
function collectChainThinkingOverrides(
|
|
2877
|
-
chain: ChainStep[],
|
|
2878
|
-
thinkingOverrideForTask: ForkThinkingOverrideForTask,
|
|
2879
|
-
dynamicFanoutMaxItems?: number,
|
|
2880
|
-
): (AgentConfig["thinking"] | undefined)[] {
|
|
2881
|
-
const thinkingOverrides: (AgentConfig["thinking"] | undefined)[] = [];
|
|
2882
|
-
let flatIndex = 0;
|
|
2883
|
-
for (const step of chain) {
|
|
2884
|
-
if (isParallelStep(step)) {
|
|
2885
|
-
for (const task of step.parallel) {
|
|
2886
|
-
thinkingOverrides.push(thinkingOverrideForTask(task.agent, flatIndex, task.model));
|
|
2887
|
-
flatIndex++;
|
|
2888
|
-
}
|
|
2889
|
-
continue;
|
|
2890
|
-
}
|
|
2891
|
-
if (isDynamicParallelStep(step)) {
|
|
2892
|
-
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
2893
|
-
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
2894
|
-
thinkingOverrides.push(thinkingOverrideForTask(step.parallel.agent, flatIndex, step.parallel.model));
|
|
2895
|
-
flatIndex++;
|
|
2896
|
-
}
|
|
2897
|
-
continue;
|
|
2898
|
-
}
|
|
2899
|
-
const sequential = step as SequentialStep;
|
|
2900
|
-
thinkingOverrides.push(thinkingOverrideForTask(sequential.agent, flatIndex, sequential.model));
|
|
2901
|
-
flatIndex++;
|
|
2902
|
-
}
|
|
2903
|
-
return thinkingOverrides;
|
|
2904
|
-
}
|
|
2905
|
-
|
|
2906
2853
|
type StaticLaunchSummary = { agent: string; model?: string; thinking?: string };
|
|
2907
2854
|
|
|
2908
2855
|
function resolveStaticLaunchSummary(input: {
|
|
@@ -2988,14 +2935,6 @@ function collectStaticLaunchSummaries(input: {
|
|
|
2988
2935
|
return input.params.agent ? [summary(input.params.agent, 0, input.params.model as string | undefined)] : [];
|
|
2989
2936
|
}
|
|
2990
2937
|
|
|
2991
|
-
function firstChainAgent(chain: ChainStep[]): string | undefined {
|
|
2992
|
-
const first = chain[0];
|
|
2993
|
-
if (!first) return undefined;
|
|
2994
|
-
if (isParallelStep(first)) return first.parallel[0]?.agent;
|
|
2995
|
-
if (isDynamicParallelStep(first)) return first.parallel.agent;
|
|
2996
|
-
return (first as SequentialStep).agent;
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
2938
|
function firstRawChainTask(chain: ChainStep[]): string | undefined {
|
|
3000
2939
|
const first = chain[0];
|
|
3001
2940
|
if (!first) return undefined;
|
|
@@ -3087,6 +3026,18 @@ async function preflightForkSessionsForStaticTasks(
|
|
|
3087
3026
|
}
|
|
3088
3027
|
}
|
|
3089
3028
|
|
|
3029
|
+
function importedAsyncRootUsage(completed: Awaited<ReturnType<typeof waitForImportedAsyncRoot>>): Usage {
|
|
3030
|
+
const totalCost = completed.totalCost;
|
|
3031
|
+
return completed.usage ?? {
|
|
3032
|
+
input: totalCost?.inputTokens ?? 0,
|
|
3033
|
+
output: totalCost?.outputTokens ?? 0,
|
|
3034
|
+
cacheRead: 0,
|
|
3035
|
+
cacheWrite: 0,
|
|
3036
|
+
cost: totalCost?.costUsd ?? 0,
|
|
3037
|
+
turns: 0,
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3090
3041
|
async function waitForWorkflowAsyncSingleResult(
|
|
3091
3042
|
params: SubagentParamsLike,
|
|
3092
3043
|
launchResult: AgentToolResult<Details>,
|
|
@@ -3108,20 +3059,14 @@ async function waitForWorkflowAsyncSingleResult(
|
|
|
3108
3059
|
options.signal?.removeEventListener("abort", stopOnAbort);
|
|
3109
3060
|
}
|
|
3110
3061
|
fs.rmSync(resultPath, { force: true });
|
|
3111
|
-
const
|
|
3062
|
+
const usage = importedAsyncRootUsage(completed);
|
|
3112
3063
|
const childResult: SingleResult = omitUndefinedProperties({
|
|
3113
3064
|
index: 0,
|
|
3114
3065
|
agent: completed.agent,
|
|
3066
|
+
...(completed.sessionName ? { sessionName: completed.sessionName } : {}),
|
|
3115
3067
|
task: options.task,
|
|
3116
3068
|
exitCode: completed.exitCode,
|
|
3117
|
-
usage
|
|
3118
|
-
input: totalCost?.inputTokens ?? 0,
|
|
3119
|
-
output: totalCost?.outputTokens ?? 0,
|
|
3120
|
-
cacheRead: 0,
|
|
3121
|
-
cacheWrite: 0,
|
|
3122
|
-
cost: totalCost?.costUsd ?? 0,
|
|
3123
|
-
turns: 0,
|
|
3124
|
-
},
|
|
3069
|
+
usage,
|
|
3125
3070
|
finalOutput: completed.output,
|
|
3126
3071
|
outputState: completed.output.trim() ? "present" as const : "absent" as const,
|
|
3127
3072
|
...(completed.error ? { error: completed.error } : {}),
|
|
@@ -3160,7 +3105,6 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3160
3105
|
ctx,
|
|
3161
3106
|
shareEnabled,
|
|
3162
3107
|
sessionRoot,
|
|
3163
|
-
sessionFileForIndex,
|
|
3164
3108
|
sessionFileForTask,
|
|
3165
3109
|
thinkingOverrideForTask,
|
|
3166
3110
|
artifactConfig,
|
|
@@ -3252,6 +3196,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3252
3196
|
skills,
|
|
3253
3197
|
output: effectiveOutput,
|
|
3254
3198
|
outputMode: effectiveOutputMode,
|
|
3199
|
+
outputClaimPath: params.workflowOutputClaimPath,
|
|
3255
3200
|
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
3256
3201
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
3257
3202
|
modelOverride,
|
|
@@ -3261,6 +3206,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3261
3206
|
thinkingCeiling: a.maxThinking,
|
|
3262
3207
|
maxSubagentDepth,
|
|
3263
3208
|
waitToolEnabled: deps.waitToolEnabled,
|
|
3209
|
+
waitToolDefaultTimeoutMs: deps.waitToolDefaultTimeoutMs,
|
|
3264
3210
|
...(params.worktree === true ? { worktree: true } : {}),
|
|
3265
3211
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3266
3212
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -3275,7 +3221,6 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3275
3221
|
extensionBindings: params.extensionBindings,
|
|
3276
3222
|
acceptance: params.acceptance,
|
|
3277
3223
|
timeoutMs: data.timeoutMs,
|
|
3278
|
-
turnBudget: data.turnBudget,
|
|
3279
3224
|
toolBudget: data.toolBudget,
|
|
3280
3225
|
usageBudget: data.usageBudget,
|
|
3281
3226
|
configToolBudget: data.configToolBudget,
|
|
@@ -3285,6 +3230,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3285
3230
|
runFanoutBudget: data.runFanoutBudget,
|
|
3286
3231
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
3287
3232
|
workflowKey: params.workflowKey,
|
|
3233
|
+
lane: params.lane,
|
|
3288
3234
|
workflowAwaitAsync: params.workflowAwaitAsync,
|
|
3289
3235
|
}));
|
|
3290
3236
|
return waitForWorkflowAsyncSingleResult(params, asyncResult, { runId: id, task: params.task ?? "", signal: data.signal, state: deps.state, kill: deps.kill });
|
|
@@ -3301,6 +3247,7 @@ function createSingleWorktreeSetup(
|
|
|
3301
3247
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
3302
3248
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
3303
3249
|
baseDir: ExtensionConfig["worktreeBaseDir"],
|
|
3250
|
+
beforeCreate?: (setup: WorktreeSetup) => void,
|
|
3304
3251
|
): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
|
|
3305
3252
|
if (!enabled) return {};
|
|
3306
3253
|
try {
|
|
@@ -3311,6 +3258,7 @@ function createSingleWorktreeSetup(
|
|
|
3311
3258
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
3312
3259
|
: undefined,
|
|
3313
3260
|
baseDir,
|
|
3261
|
+
beforeCreate,
|
|
3314
3262
|
})),
|
|
3315
3263
|
};
|
|
3316
3264
|
} catch (error) {
|
|
@@ -3353,10 +3301,30 @@ function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, art
|
|
|
3353
3301
|
return path.join(artifactsDir, "outputs", sanitizeRunPathSegment(workflowRunId), `${workflowKey}.md`);
|
|
3354
3302
|
}
|
|
3355
3303
|
|
|
3304
|
+
function workflowHostCommandRunner(input: {
|
|
3305
|
+
workflowCwd: string;
|
|
3306
|
+
artifactsDir: string;
|
|
3307
|
+
workflowRunId: string;
|
|
3308
|
+
claimedOutputPaths: Map<string, string>;
|
|
3309
|
+
producedOutputPaths: Set<string>;
|
|
3310
|
+
}) {
|
|
3311
|
+
return async (key: string, params: WorkflowHostCommandParams, signal: AbortSignal): Promise<WorkflowHostCommandResult> => {
|
|
3312
|
+
const defaultOutputPath = path.join(input.artifactsDir, "outputs", sanitizeRunPathSegment(input.workflowRunId), "host", `${sanitizeRunPathSegment(key)}.log`);
|
|
3313
|
+
const outputPath = params.output ? path.resolve(input.workflowCwd, params.output) : defaultOutputPath;
|
|
3314
|
+
const claimPath = resolveWorkflowHostOutputClaimPath(outputPath);
|
|
3315
|
+
const previous = input.claimedOutputPaths.get(claimPath);
|
|
3316
|
+
if (previous) throw new Error(`runs.host('${key}') output path is already claimed by '${previous}': ${outputPath}.`);
|
|
3317
|
+
input.claimedOutputPaths.set(claimPath, `host:${key}`);
|
|
3318
|
+
const result = await executeWorkflowHostCommand({ key, params, cwd: input.workflowCwd, defaultOutputPath, claimedOutputPath: claimPath, signal });
|
|
3319
|
+
input.producedOutputPaths.add(resolveWorkflowHostOutputClaimPath(result.outputPath));
|
|
3320
|
+
return result;
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3356
3324
|
function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string, producedChildOutputPaths: ReadonlySet<string>): string | undefined {
|
|
3357
3325
|
if (!outputPath) return undefined;
|
|
3358
3326
|
try {
|
|
3359
|
-
if (producedChildOutputPaths.has(outputPath)) return undefined;
|
|
3327
|
+
if (producedChildOutputPaths.has(resolveWorkflowHostOutputClaimPath(outputPath))) return undefined;
|
|
3360
3328
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
3361
3329
|
fs.writeFileSync(outputPath, text, "utf-8");
|
|
3362
3330
|
return undefined;
|
|
@@ -3427,7 +3395,7 @@ function workflowChildOutputClaims(input: {
|
|
|
3427
3395
|
state: SubagentState;
|
|
3428
3396
|
claimedOutputPaths: Map<string, string>;
|
|
3429
3397
|
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
3430
|
-
}): { error?: string; claims?: Map<string, string>; overrides?: Map<string, string> } {
|
|
3398
|
+
}): { error?: string; claims?: Map<string, string>; childClaims?: Map<string, string>; overrides?: Map<string, string> } {
|
|
3431
3399
|
const resolvedEntries = input.entries.map(({ key, params }) => ({
|
|
3432
3400
|
key,
|
|
3433
3401
|
...resolveWorkflowChildOutputPath({ ...input, key, params }),
|
|
@@ -3435,24 +3403,30 @@ function workflowChildOutputClaims(input: {
|
|
|
3435
3403
|
const paths = new Map<string, number>();
|
|
3436
3404
|
for (const claimedPath of input.claimedOutputPaths.keys()) paths.set(claimedPath, 1);
|
|
3437
3405
|
for (const { path: resolved } of resolvedEntries) {
|
|
3438
|
-
if (resolved)
|
|
3406
|
+
if (resolved) {
|
|
3407
|
+
const claimPath = resolveWorkflowHostOutputClaimPath(resolved);
|
|
3408
|
+
paths.set(claimPath, (paths.get(claimPath) ?? 0) + 1);
|
|
3409
|
+
}
|
|
3439
3410
|
}
|
|
3440
3411
|
const overrides = new Map<string, string>();
|
|
3441
3412
|
for (const entry of resolvedEntries) {
|
|
3442
|
-
if (entry.inherited && entry.path && (paths.get(entry.path) ?? 0) > 1) {
|
|
3413
|
+
if (entry.inherited && entry.path && (paths.get(resolveWorkflowHostOutputClaimPath(entry.path)) ?? 0) > 1) {
|
|
3443
3414
|
const output = workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, entry.key);
|
|
3444
3415
|
overrides.set(entry.key, output);
|
|
3445
3416
|
entry.path = output;
|
|
3446
3417
|
}
|
|
3447
3418
|
}
|
|
3448
3419
|
const claims = new Map(input.claimedOutputPaths);
|
|
3420
|
+
const childClaims = new Map<string, string>();
|
|
3449
3421
|
for (const { key, path: resolved } of resolvedEntries) {
|
|
3450
3422
|
if (!resolved) continue;
|
|
3451
|
-
const
|
|
3423
|
+
const claimPath = resolveWorkflowHostOutputClaimPath(resolved);
|
|
3424
|
+
const previous = claims.get(claimPath);
|
|
3452
3425
|
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
3453
|
-
claims.set(
|
|
3426
|
+
claims.set(claimPath, key);
|
|
3427
|
+
childClaims.set(key, claimPath);
|
|
3454
3428
|
}
|
|
3455
|
-
return { claims, overrides };
|
|
3429
|
+
return { claims, childClaims, overrides };
|
|
3456
3430
|
}
|
|
3457
3431
|
|
|
3458
3432
|
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
@@ -3474,12 +3448,14 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3474
3448
|
agents: AgentConfig[];
|
|
3475
3449
|
workflowAgentScope?: unknown;
|
|
3476
3450
|
outputOverride?: string;
|
|
3477
|
-
|
|
3451
|
+
outputClaimPath?: string;
|
|
3452
|
+
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; awaitDetachedChild?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; capabilityCeiling?: ResolvedSubagentCapabilityCeiling };
|
|
3478
3453
|
}): SubagentParamsLike {
|
|
3479
3454
|
let childParams = input.childParams;
|
|
3480
|
-
|
|
3455
|
+
const usesDefaultOutput = input.childParams.output === undefined && input.childParams.resume === undefined;
|
|
3456
|
+
if (usesDefaultOutput && input.outputOverride !== undefined) {
|
|
3481
3457
|
childParams = { ...input.childParams, output: input.outputOverride };
|
|
3482
|
-
} else if (
|
|
3458
|
+
} else if (usesDefaultOutput && input.aggregateOutputPath !== undefined) {
|
|
3483
3459
|
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3484
3460
|
} else if (input.childParams.resume === undefined) {
|
|
3485
3461
|
const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, agents: input.agents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
|
|
@@ -3492,7 +3468,7 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3492
3468
|
? resolveAgentName(childParams.agent, discoveredAgents).agent ?? resolveAgentName(childParams.agent, input.agents).agent
|
|
3493
3469
|
: undefined;
|
|
3494
3470
|
const externalAsyncRequired = agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job";
|
|
3495
|
-
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired });
|
|
3471
|
+
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired, outputClaimPath: input.outputClaimPath });
|
|
3496
3472
|
}
|
|
3497
3473
|
|
|
3498
3474
|
function finalizeSingleWorktreeHandoff(input: {
|
|
@@ -3502,6 +3478,8 @@ function finalizeSingleWorktreeHandoff(input: {
|
|
|
3502
3478
|
cwd: string;
|
|
3503
3479
|
agent: string;
|
|
3504
3480
|
result: SingleResult;
|
|
3481
|
+
workflowKey?: string;
|
|
3482
|
+
lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
|
|
3505
3483
|
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
3506
3484
|
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
3507
3485
|
const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
|
|
@@ -3516,9 +3494,13 @@ function finalizeSingleWorktreeHandoff(input: {
|
|
|
3516
3494
|
stepIndex: 0,
|
|
3517
3495
|
flatStartIndex: 0,
|
|
3518
3496
|
setup: input.worktreeSetup,
|
|
3497
|
+
laneBindings: input.workflowKey || input.lane ? [{ index: 0, taskIndex: 0, ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}), runId: input.workflowKey ? input.runId : undefined, ...(input.lane ? { lane: input.lane } : {}) }] : undefined,
|
|
3519
3498
|
diffs,
|
|
3520
3499
|
results: [{
|
|
3521
3500
|
agent: input.result.agent,
|
|
3501
|
+
...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
|
|
3502
|
+
...(input.workflowKey ? { runId: input.runId } : {}),
|
|
3503
|
+
...(input.lane ? { lane: input.lane } : {}),
|
|
3522
3504
|
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3523
3505
|
exitCode: input.result.exitCode,
|
|
3524
3506
|
interrupted: input.result.interrupted,
|
|
@@ -3564,10 +3546,16 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3564
3546
|
artifactConfig,
|
|
3565
3547
|
artifactsDir,
|
|
3566
3548
|
onUpdate,
|
|
3567
|
-
sessionRoot,
|
|
3568
3549
|
controlConfig,
|
|
3569
3550
|
contextPolicy,
|
|
3570
3551
|
} = data;
|
|
3552
|
+
let lane: import("../../shared/types.ts").WorkflowLaneMetadata | undefined;
|
|
3553
|
+
try {
|
|
3554
|
+
lane = normalizeWorkflowLaneMetadata(params.lane, "lane");
|
|
3555
|
+
assertWorkflowLaneKey(lane, params.workflowKey, "lane");
|
|
3556
|
+
} catch (error) {
|
|
3557
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
3558
|
+
}
|
|
3571
3559
|
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
3572
3560
|
const childBridgeActive = intercomBridgeAppliesToAgent(data.intercomBridge, contextPolicy, params.agent!);
|
|
3573
3561
|
const childIntercomTarget = childBridgeActive ? resolveSubagentIntercomTarget(runId, params.agent!, 0) : undefined;
|
|
@@ -3588,7 +3576,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3588
3576
|
const currentProvider = parentModel?.provider;
|
|
3589
3577
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
3590
3578
|
const modelScopes = resolveModelScopesForAgent(data.modelScope, agentConfig.name, parentModel);
|
|
3591
|
-
let task = params.task
|
|
3579
|
+
let task = typeof params.task === "string" ? params.task : "";
|
|
3592
3580
|
let modelOverride: string | undefined = resolveEffectiveSubagentModel(
|
|
3593
3581
|
params.model as string | undefined,
|
|
3594
3582
|
agentConfig.model,
|
|
@@ -3608,6 +3596,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3608
3596
|
|
|
3609
3597
|
|
|
3610
3598
|
const sourceCwd = effectiveCwd;
|
|
3599
|
+
let pendingHandoff: Details["parallelHandoff"];
|
|
3611
3600
|
const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
|
|
3612
3601
|
params.worktree,
|
|
3613
3602
|
sourceCwd,
|
|
@@ -3616,12 +3605,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3616
3605
|
deps.config.worktreeSetupHook,
|
|
3617
3606
|
deps.config.worktreeSetupHookTimeoutMs,
|
|
3618
3607
|
deps.config.worktreeBaseDir,
|
|
3619
|
-
|
|
3620
|
-
if (worktreeSetupError) return worktreeSetupError;
|
|
3621
|
-
const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
|
|
3622
|
-
let pendingHandoff: Details["parallelHandoff"];
|
|
3623
|
-
if (worktreeSetup) {
|
|
3624
|
-
try {
|
|
3608
|
+
(plannedSetup) => {
|
|
3625
3609
|
pendingHandoff = writePendingParallelHandoff({
|
|
3626
3610
|
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3627
3611
|
runId,
|
|
@@ -3630,13 +3614,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3630
3614
|
cwd: sourceCwd,
|
|
3631
3615
|
stepIndex: 0,
|
|
3632
3616
|
flatStartIndex: 0,
|
|
3633
|
-
setup:
|
|
3617
|
+
setup: plannedSetup,
|
|
3618
|
+
laneBindings: params.workflowKey || lane ? [{ index: 0, taskIndex: 0, ...(params.workflowKey ? { workflowKey: params.workflowKey, runId } : {}), ...(lane ? { lane } : {}) }] : undefined,
|
|
3634
3619
|
});
|
|
3635
|
-
}
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
}
|
|
3620
|
+
},
|
|
3621
|
+
);
|
|
3622
|
+
if (worktreeSetupError) return worktreeSetupError;
|
|
3623
|
+
const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
|
|
3640
3624
|
|
|
3641
3625
|
const authoredTask = task;
|
|
3642
3626
|
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
@@ -3702,8 +3686,12 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3702
3686
|
|
|
3703
3687
|
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3704
3688
|
let r: Awaited<ReturnType<typeof runSync>> | undefined;
|
|
3689
|
+
let resolveDetachedWorkflowChild: ((result: Awaited<ReturnType<typeof runSync>>) => void) | undefined;
|
|
3690
|
+
const detachedWorkflowChild = params.workflowAwaitDetached === true
|
|
3691
|
+
? new Promise<Awaited<ReturnType<typeof runSync>>>((resolve) => { resolveDetachedWorkflowChild = resolve; })
|
|
3692
|
+
: undefined;
|
|
3705
3693
|
try {
|
|
3706
|
-
|
|
3694
|
+
const launched = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3707
3695
|
permissions: deps.config.permissions,
|
|
3708
3696
|
runtimeSnapshotHost: deps.pi,
|
|
3709
3697
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
@@ -3726,9 +3714,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3726
3714
|
artifactConfig,
|
|
3727
3715
|
maxOutput: params.maxOutput,
|
|
3728
3716
|
outputPath,
|
|
3717
|
+
outputClaimPath: params.workflowOutputClaimPath,
|
|
3729
3718
|
outputMode: effectiveOutputMode,
|
|
3730
3719
|
maxSubagentDepth,
|
|
3731
3720
|
waitToolEnabled: deps.waitToolEnabled,
|
|
3721
|
+
waitToolDefaultTimeoutMs: deps.waitToolDefaultTimeoutMs,
|
|
3732
3722
|
onUpdate: forwardSingleUpdate,
|
|
3733
3723
|
controlConfig,
|
|
3734
3724
|
onControlEvent,
|
|
@@ -3756,9 +3746,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3756
3746
|
detachForeground = detach;
|
|
3757
3747
|
},
|
|
3758
3748
|
onDetachedExit: (result) => {
|
|
3749
|
+
if (resolveDetachedWorkflowChild) {
|
|
3750
|
+
resolveDetachedWorkflowChild(result);
|
|
3751
|
+
return;
|
|
3752
|
+
}
|
|
3759
3753
|
try {
|
|
3760
3754
|
if (worktreeSetup) {
|
|
3761
|
-
finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result });
|
|
3755
|
+
finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result, workflowKey: params.workflowKey, lane });
|
|
3762
3756
|
}
|
|
3763
3757
|
try {
|
|
3764
3758
|
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
|
|
@@ -3793,12 +3787,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3793
3787
|
deadlineAt,
|
|
3794
3788
|
toolTimeoutMs: params.toolTimeoutMs,
|
|
3795
3789
|
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3796
|
-
turnBudget: data.turnBudget,
|
|
3797
|
-
enforceHardTurnLimit: params.enforceHardTurnLimit,
|
|
3798
3790
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3799
3791
|
capabilityCeiling: data.capabilityCeiling,
|
|
3800
3792
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3801
3793
|
}));
|
|
3794
|
+
r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
|
|
3802
3795
|
} catch (error) {
|
|
3803
3796
|
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
3804
3797
|
throw error;
|
|
@@ -3819,7 +3812,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3819
3812
|
if (worktreeSetup) {
|
|
3820
3813
|
worktreeHandoff = r.detached
|
|
3821
3814
|
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3822
|
-
: finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r });
|
|
3815
|
+
: finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r, workflowKey: params.workflowKey, lane });
|
|
3823
3816
|
}
|
|
3824
3817
|
|
|
3825
3818
|
if (r.progress) allProgress.push(r.progress);
|
|
@@ -3848,7 +3841,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3848
3841
|
runId,
|
|
3849
3842
|
timeoutMs: data.timeoutMs,
|
|
3850
3843
|
results: [r],
|
|
3851
|
-
...(data.turnBudget ? { turnBudget: data.turnBudget } : {}),
|
|
3852
3844
|
...(effectiveToolBudget.toolBudget ? { toolBudget: effectiveToolBudget.toolBudget } : {}),
|
|
3853
3845
|
progress: params.includeProgress ? allProgress : undefined,
|
|
3854
3846
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
@@ -3933,7 +3925,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
3933
3925
|
};
|
|
3934
3926
|
}
|
|
3935
3927
|
|
|
3936
|
-
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
3928
|
+
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionName?: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
3937
3929
|
|
|
3938
3930
|
/**
|
|
3939
3931
|
* Terminal-mission retention can remove a mission while its children still
|
|
@@ -4005,7 +3997,7 @@ export function bindMissionWorkflowChildAsyncLaunch(
|
|
|
4005
3997
|
function workflowChildResult(
|
|
4006
3998
|
key: string,
|
|
4007
3999
|
result: AgentToolResult<Details>,
|
|
4008
|
-
childParams: Record<string, unknown> = {},
|
|
4000
|
+
childParams: SubagentParamsLike | Record<string, unknown> = {},
|
|
4009
4001
|
resumeState?: SubagentState,
|
|
4010
4002
|
forcedTerminalOutcome?: WorkflowTerminalOutcome,
|
|
4011
4003
|
): WorkflowScriptChildResult {
|
|
@@ -4066,9 +4058,12 @@ function workflowChildResult(
|
|
|
4066
4058
|
const externalProcess = externalResult?.externalProcess ?? externalStatusStep?.externalProcess;
|
|
4067
4059
|
const externalAdapter = externalRunner ? externalCliReceiptMetadata({ runner: externalRunner, externalProcess, outputReference }) : undefined;
|
|
4068
4060
|
if (externalAdapter) resumability = { state: "not-resumable", reason: externalAdapter.nonResumableReason };
|
|
4061
|
+
const lane = normalizeWorkflowLaneMetadata(childParams.lane, `workflow child '${key}'.lane`);
|
|
4062
|
+
assertWorkflowLaneKey(lane, key, `workflow child '${key}'.lane`);
|
|
4069
4063
|
return {
|
|
4070
4064
|
key,
|
|
4071
4065
|
ok,
|
|
4066
|
+
...(lane ? { lane } : {}),
|
|
4072
4067
|
...(terminalOutcome ? { terminalOutcome } : {}),
|
|
4073
4068
|
...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
|
|
4074
4069
|
...(runId ? { runId } : {}),
|
|
@@ -4090,6 +4085,16 @@ function workflowChildResult(
|
|
|
4090
4085
|
};
|
|
4091
4086
|
}
|
|
4092
4087
|
|
|
4088
|
+
function workflowChildAccountingFields(child: WorkflowScriptChildResult): { usage?: Usage; sessionFile?: string } {
|
|
4089
|
+
if (!child.results?.length) return {};
|
|
4090
|
+
const usage = sumResultsUsage(child.results);
|
|
4091
|
+
const sessionFile = child.results.find((result) => result.sessionFile)?.sessionFile;
|
|
4092
|
+
return {
|
|
4093
|
+
...(usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0 ? { usage } : {}),
|
|
4094
|
+
...(sessionFile ? { sessionFile } : {}),
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4093
4098
|
function workflowOutputPathMappingSummary(children: WorkflowScriptChildResult[]): string {
|
|
4094
4099
|
const mappings = children.flatMap((child) => child.outputPathMapping
|
|
4095
4100
|
? [`'${child.key}': requested ${child.outputPathMapping.requestedPath} -> saved ${child.outputPathMapping.savedPath}`]
|
|
@@ -4114,21 +4119,97 @@ function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): Wo
|
|
|
4114
4119
|
};
|
|
4115
4120
|
}
|
|
4116
4121
|
|
|
4122
|
+
const MAX_WORKFLOW_RESUME_HINT_BYTES = 1024;
|
|
4123
|
+
const MAX_WORKFLOW_CHILD_RUN_ID_BYTES = 256;
|
|
4124
|
+
const WORKFLOW_RESUME_HINT_PARENT_STATES = new Set(["complete", "failed", "partial"]);
|
|
4125
|
+
const WORKFLOW_STEP_STATES = new Set(["pending", "running", "complete", "completed", "failed", "partial", "paused", "stopped", "rejected"]);
|
|
4126
|
+
|
|
4127
|
+
function isSafeWorkflowChildRunId(value: unknown): value is string {
|
|
4128
|
+
if (typeof value !== "string") return false;
|
|
4129
|
+
const runId = value.trim();
|
|
4130
|
+
return value === runId
|
|
4131
|
+
&& Boolean(runId)
|
|
4132
|
+
&& Buffer.byteLength(runId, "utf8") <= MAX_WORKFLOW_CHILD_RUN_ID_BYTES
|
|
4133
|
+
&& path.basename(runId) === runId
|
|
4134
|
+
&& path.win32.basename(runId) === runId
|
|
4135
|
+
&& !/[\\/]/.test(runId)
|
|
4136
|
+
&& !runId.includes("..")
|
|
4137
|
+
&& !/[\u0000-\u001f\u007f]/.test(runId);
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
function isMissingWorkflowReceiptDiagnostic(error: unknown, workflowRunId: string): error is Error {
|
|
4141
|
+
return error instanceof Error
|
|
4142
|
+
&& error.message.startsWith(`Workflow receipt '${workflowRunId}' is not available because the workflow may still be active or terminal receipt writing failed.`);
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
function missingWorkflowReceiptResumeHint(reference: WorkflowReceiptResumeReference, state: SubagentState): string | undefined {
|
|
4146
|
+
try {
|
|
4147
|
+
if (!state.currentSessionId) return undefined;
|
|
4148
|
+
const workflowRunId = reference.workflowRunId.trim();
|
|
4149
|
+
const workflowStatus = readStatus(path.join(DIRS.async, workflowRunId));
|
|
4150
|
+
if (!workflowStatus
|
|
4151
|
+
|| workflowStatus.runId !== workflowRunId
|
|
4152
|
+
|| workflowStatus.mode !== "workflow"
|
|
4153
|
+
|| workflowStatus.sessionId !== state.currentSessionId
|
|
4154
|
+
|| typeof workflowStatus.startedAt !== "number"
|
|
4155
|
+
|| !Number.isFinite(workflowStatus.startedAt)
|
|
4156
|
+
|| !WORKFLOW_RESUME_HINT_PARENT_STATES.has(workflowStatus.state)
|
|
4157
|
+
|| !Array.isArray(workflowStatus.steps)
|
|
4158
|
+
|| workflowStatus.steps.some((step) => {
|
|
4159
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) return true;
|
|
4160
|
+
const record = step as Record<string, unknown>;
|
|
4161
|
+
return typeof record.agent !== "string"
|
|
4162
|
+
|| typeof record.status !== "string"
|
|
4163
|
+
|| !WORKFLOW_STEP_STATES.has(record.status)
|
|
4164
|
+
|| (record.workflowKey !== undefined && typeof record.workflowKey !== "string")
|
|
4165
|
+
|| (record.runId !== undefined && typeof record.runId !== "string");
|
|
4166
|
+
})) return undefined;
|
|
4167
|
+
const matchingSteps = workflowStatus.steps.filter((step) => step.workflowKey === reference.key);
|
|
4168
|
+
if (matchingSteps.length !== 1) return undefined;
|
|
4169
|
+
const childRunId = matchingSteps[0]?.runId;
|
|
4170
|
+
if (!isSafeWorkflowChildRunId(childRunId)) return undefined;
|
|
4171
|
+
|
|
4172
|
+
const workflowChildren = parseWorkflowChildSummary(workflowStatus.workflowChildren);
|
|
4173
|
+
if (workflowChildren) {
|
|
4174
|
+
if (workflowChildren.workflowRunId !== workflowRunId) return undefined;
|
|
4175
|
+
const matchingChildren = workflowChildren.children.filter((child) => child.childId === reference.key);
|
|
4176
|
+
if (matchingChildren.length > 1 || (workflowChildren.inventoryComplete && matchingChildren.length !== 1)) return undefined;
|
|
4177
|
+
if (matchingChildren.length === 1 && matchingChildren[0]?.runId !== childRunId) return undefined;
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
const target = resolveResumeTarget({ id: childRunId }, state, { asyncRequireSessionFile: true, exactOnly: true });
|
|
4181
|
+
if (target.kind !== "revive") return undefined;
|
|
4182
|
+
const hint = `Direct resumable child for workflow key '${reference.key}': subagent({ action: "resume", id: ${JSON.stringify(childRunId)}, message: "..." })`;
|
|
4183
|
+
return Buffer.byteLength(hint, "utf8") <= MAX_WORKFLOW_RESUME_HINT_BYTES ? hint : undefined;
|
|
4184
|
+
} catch {
|
|
4185
|
+
return undefined;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
|
|
4117
4189
|
function resolveKeyedWorkflowResume(
|
|
4118
4190
|
reference: WorkflowReceiptResumeReference,
|
|
4119
4191
|
state: SubagentState,
|
|
4120
4192
|
): { runId: string; runIds: string[] } {
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4193
|
+
try {
|
|
4194
|
+
const entry = resolveWorkflowReceiptResumeEntry({
|
|
4195
|
+
reference,
|
|
4196
|
+
asyncDirRoot: DIRS.async,
|
|
4197
|
+
assertResumable(runId) {
|
|
4198
|
+
const target = resolveResumeTarget({ id: runId }, state, { asyncRequireSessionFile: true, exactOnly: true });
|
|
4199
|
+
if (target.kind !== "revive") throw new Error(`Workflow receipt child '${reference.key}' latest run '${runId}' is still running.`);
|
|
4200
|
+
},
|
|
4201
|
+
});
|
|
4202
|
+
const runId = entry.latestRunId;
|
|
4203
|
+
if (!runId) throw new Error(`Workflow receipt child '${reference.key}' has no retained run id.`);
|
|
4204
|
+
return { runId, runIds: entry.continuation.runIds };
|
|
4205
|
+
} catch (error) {
|
|
4206
|
+
const workflowRunId = reference.workflowRunId.trim();
|
|
4207
|
+
if (isMissingWorkflowReceiptDiagnostic(error, workflowRunId)) {
|
|
4208
|
+
const hint = missingWorkflowReceiptResumeHint(reference, state);
|
|
4209
|
+
if (hint) throw new Error(`${error.message} ${hint}`, { cause: error });
|
|
4210
|
+
}
|
|
4211
|
+
throw error;
|
|
4212
|
+
}
|
|
4132
4213
|
}
|
|
4133
4214
|
|
|
4134
4215
|
function terminalWorkflowReceipt(
|
|
@@ -4137,8 +4218,9 @@ function terminalWorkflowReceipt(
|
|
|
4137
4218
|
children: WorkflowScriptChildResult[],
|
|
4138
4219
|
workflowChildren?: WorkflowReceipt["workflowChildren"],
|
|
4139
4220
|
terminalOutcome?: WorkflowTerminalOutcome,
|
|
4221
|
+
hostSteps?: WorkflowReceipt["hostSteps"],
|
|
4140
4222
|
): WorkflowReceipt {
|
|
4141
|
-
return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome });
|
|
4223
|
+
return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps });
|
|
4142
4224
|
}
|
|
4143
4225
|
|
|
4144
4226
|
function workflowFailureTerminalOutcome(error: unknown, _children: WorkflowScriptChildResult[], usageBudget: ReturnType<typeof usageBudgetState>): WorkflowTerminalOutcome | undefined {
|
|
@@ -4228,9 +4310,11 @@ export function prepareWorkflowLaunchParams(
|
|
|
4228
4310
|
childParams: Record<string, unknown>,
|
|
4229
4311
|
parentWorkflowRunId: string,
|
|
4230
4312
|
workflowKey: string,
|
|
4231
|
-
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; externalAsyncRequired?: boolean; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } = {},
|
|
4313
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; awaitDetachedChild?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; externalAsyncRequired?: boolean; capabilityCeiling?: ResolvedSubagentCapabilityCeiling; outputClaimPath?: string } = {},
|
|
4232
4314
|
): SubagentParamsLike {
|
|
4233
4315
|
const capabilityCeiling = intersectSubagentCapabilityCeilings(workflowDefaults.capabilityCeiling, options.capabilityCeiling);
|
|
4316
|
+
const lane = normalizeWorkflowLaneMetadata(Object.hasOwn(childParams, "lane") ? childParams.lane : workflowDefaults.lane, `workflow child '${workflowKey}'.lane`);
|
|
4317
|
+
assertWorkflowLaneKey(lane, workflowKey, `workflow child '${workflowKey}'.lane`);
|
|
4234
4318
|
const parentTimeoutMs = options.parentDeadlineAt === undefined
|
|
4235
4319
|
|| childParams.timeoutMs !== undefined
|
|
4236
4320
|
|| childParams.maxRuntimeMs !== undefined
|
|
@@ -4246,7 +4330,6 @@ export function prepareWorkflowLaunchParams(
|
|
|
4246
4330
|
throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
|
|
4247
4331
|
}
|
|
4248
4332
|
const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
|
|
4249
|
-
const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
|
|
4250
4333
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4251
4334
|
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4252
4335
|
const worktree = childParams.worktree ?? workflowDefaults.worktree;
|
|
@@ -4266,6 +4349,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4266
4349
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4267
4350
|
workflowParentRunId: parentWorkflowRunId,
|
|
4268
4351
|
workflowKey,
|
|
4352
|
+
...(lane ? { lane } : {}),
|
|
4269
4353
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4270
4354
|
...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
|
|
4271
4355
|
...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
|
|
@@ -4275,7 +4359,6 @@ export function prepareWorkflowLaunchParams(
|
|
|
4275
4359
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4276
4360
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4277
4361
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
4278
|
-
...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
|
|
4279
4362
|
...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
|
|
4280
4363
|
...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
|
|
4281
4364
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
@@ -4289,6 +4372,9 @@ export function prepareWorkflowLaunchParams(
|
|
|
4289
4372
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4290
4373
|
workflowParentRunId: parentWorkflowRunId,
|
|
4291
4374
|
workflowKey,
|
|
4375
|
+
...(options.outputClaimPath ? { workflowOutputClaimPath: options.outputClaimPath } : {}),
|
|
4376
|
+
...(options.awaitDetachedChild ? { workflowAwaitDetached: true } : {}),
|
|
4377
|
+
...(lane ? { lane } : {}),
|
|
4292
4378
|
...(parentTimeoutMs !== undefined ? { workflowParentDeadlineAt: options.parentDeadlineAt } : {}),
|
|
4293
4379
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4294
4380
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
@@ -4330,11 +4416,12 @@ function workflowChatProgressUpdate(
|
|
|
4330
4416
|
chatProgress: WorkflowChatProgressProjection,
|
|
4331
4417
|
workflow: NonNullable<Details["workflow"]>,
|
|
4332
4418
|
workflowChildren?: Details["workflowChildren"],
|
|
4419
|
+
preflight?: import("../../shared/types.ts").WorkflowPreflightV1,
|
|
4333
4420
|
): AgentToolResult<Details> | undefined {
|
|
4334
4421
|
if (chatProgress.mode !== "live-card") return undefined;
|
|
4335
4422
|
return {
|
|
4336
4423
|
content: [{ type: "text", text: "Workflow running." }],
|
|
4337
|
-
details: { mode: "workflow", runId, results: [], workflow, ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
|
|
4424
|
+
details: { mode: "workflow", runId, results: [], workflow, ...(preflight ? { preflight } : {}), ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
|
|
4338
4425
|
};
|
|
4339
4426
|
}
|
|
4340
4427
|
|
|
@@ -4423,14 +4510,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4423
4510
|
preserveActiveSession = false,
|
|
4424
4511
|
parentModelOverride?: ParentModel | null,
|
|
4425
4512
|
): Promise<AgentToolResult<Details>> => {
|
|
4426
|
-
if (params.action?.trim() === "validate") {
|
|
4427
|
-
const validation = validateWorkflowScript(params.workflowScript ?? "");
|
|
4428
|
-
return {
|
|
4429
|
-
content: [{ type: "text", text: JSON.stringify(validation) }],
|
|
4430
|
-
...(validation.ok ? {} : { isError: true }),
|
|
4431
|
-
details: { mode: "management", results: [] },
|
|
4432
|
-
};
|
|
4433
|
-
}
|
|
4434
4513
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4435
4514
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4436
4515
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
@@ -4444,7 +4523,36 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4444
4523
|
deps.state.lastForegroundControlId ??= null;
|
|
4445
4524
|
const normalizedGate = normalizeGateParams(params);
|
|
4446
4525
|
if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
|
|
4447
|
-
|
|
4526
|
+
let requestParams = normalizedGate.params;
|
|
4527
|
+
let workflowPreflight: import("../../shared/types.ts").WorkflowPreflightV1 | undefined;
|
|
4528
|
+
try {
|
|
4529
|
+
if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
|
|
4530
|
+
throw new Error("preflight requires workflowScript or workflowScriptPath.");
|
|
4531
|
+
}
|
|
4532
|
+
workflowPreflight = normalizeWorkflowPreflight(requestParams.preflight);
|
|
4533
|
+
if (workflowPreflight) requestParams = { ...requestParams, preflight: workflowPreflight };
|
|
4534
|
+
} catch (error) {
|
|
4535
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4536
|
+
if (requestParams.action?.trim() === "validate") {
|
|
4537
|
+
const validation = validateWorkflowScript(requestParams.workflowScript ?? "");
|
|
4538
|
+
const invalidValidation = { ...validation, ok: false, errors: [...validation.errors, { message }] };
|
|
4539
|
+
return {
|
|
4540
|
+
content: [{ type: "text", text: JSON.stringify(invalidValidation) }],
|
|
4541
|
+
isError: true,
|
|
4542
|
+
details: { mode: "management", results: [] },
|
|
4543
|
+
};
|
|
4544
|
+
}
|
|
4545
|
+
return buildRequestedModeError(requestParams, message);
|
|
4546
|
+
}
|
|
4547
|
+
if (requestParams.action?.trim() === "validate") {
|
|
4548
|
+
const validation = validateWorkflowScript(requestParams.workflowScript ?? "");
|
|
4549
|
+
const payload = workflowPreflight ? { ...validation, preflight: workflowPreflight } : validation;
|
|
4550
|
+
return {
|
|
4551
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
4552
|
+
...(validation.ok ? {} : { isError: true }),
|
|
4553
|
+
details: { mode: "management", results: [] },
|
|
4554
|
+
};
|
|
4555
|
+
}
|
|
4448
4556
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4449
4557
|
if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
|
|
4450
4558
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
@@ -4473,7 +4581,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4473
4581
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4474
4582
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4475
4583
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
4476
|
-
const
|
|
4584
|
+
const discoverWorkflowAgents = (cwd: string, scope: AgentScope) => deps.discoverAgents(cwd, scope, workflowParentModel?.provider);
|
|
4585
|
+
const workflowAgents = discoverWorkflowAgents(workflowCwd, resolveExecutionAgentScope(requestParams.agentScope)).agents;
|
|
4477
4586
|
const workflowArtifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
4478
4587
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
4479
4588
|
enabled: requestParams.artifacts !== false,
|
|
@@ -4602,6 +4711,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4602
4711
|
...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}),
|
|
4603
4712
|
pid: process.pid,
|
|
4604
4713
|
steps: [],
|
|
4714
|
+
...(workflowPreflight ? { preflight: workflowPreflight } : {}),
|
|
4605
4715
|
workflow: { trace: [], emits: [], console: [] },
|
|
4606
4716
|
workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
|
|
4607
4717
|
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
@@ -4689,6 +4799,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4689
4799
|
liveJob.turnCount = status.turnCount;
|
|
4690
4800
|
liveJob.toolCount = status.toolCount;
|
|
4691
4801
|
liveJob.currentStep = status.currentStep;
|
|
4802
|
+
liveJob.preflight = status.preflight;
|
|
4692
4803
|
if (status.steps) {
|
|
4693
4804
|
liveJob.steps = status.steps.map((step, index) => ({ ...step, index }));
|
|
4694
4805
|
liveJob.agents = status.steps.map((step) => step.agent);
|
|
@@ -4732,7 +4843,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4732
4843
|
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
4733
4844
|
status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
|
|
4734
4845
|
};
|
|
4735
|
-
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow, workflowChildren: status.workflowChildren };
|
|
4846
|
+
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], ...(workflowPreflight ? { preflight: workflowPreflight } : {}), startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow, workflowChildren: status.workflowChildren };
|
|
4736
4847
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4737
4848
|
deps.state.fleetJobs ??= new Map();
|
|
4738
4849
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4752,18 +4863,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4752
4863
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
4753
4864
|
const workflowResults: SingleResult[] = [];
|
|
4754
4865
|
const workflowChildRunIds = new Map<string, string>();
|
|
4755
|
-
const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = workflowRequest;
|
|
4866
|
+
const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, preflight: _preflight, ...workflowChildDefaults } = workflowRequest;
|
|
4756
4867
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
4757
4868
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
4758
|
-
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput,
|
|
4869
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
|
|
4759
4870
|
const claimedOutputPaths = new Map<string, string>();
|
|
4760
4871
|
const childOutputOverrides = new Map<string, string>();
|
|
4872
|
+
const childOutputClaimPaths = new Map<string, string>();
|
|
4761
4873
|
const producedChildOutputPaths = new Set<string>();
|
|
4762
4874
|
const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
|
|
4875
|
+
const runHostCommand = workflowHostCommandRunner({ workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, claimedOutputPaths, producedOutputPaths: producedChildOutputPaths });
|
|
4763
4876
|
let projectedTraceLength = 0;
|
|
4764
4877
|
let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
|
|
4765
4878
|
const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
|
|
4766
|
-
|
|
4879
|
+
const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
|
|
4880
|
+
const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
|
|
4881
|
+
status.workflow = {
|
|
4882
|
+
...(status.workflow ?? { emits: [], console: [] }),
|
|
4883
|
+
trace: projectedTrace,
|
|
4884
|
+
...(preflightWarnings.length ? { preflightWarnings } : {}),
|
|
4885
|
+
};
|
|
4767
4886
|
const rebuild = trace.length < projectedTraceLength
|
|
4768
4887
|
|| (projectedTraceLength > 0 && trace[projectedTraceLength - 1] !== projectedTraceTail);
|
|
4769
4888
|
if (rebuild) {
|
|
@@ -4776,8 +4895,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4776
4895
|
for (let index = projectedTraceLength; index < trace.length; index += 1) {
|
|
4777
4896
|
const entry = trace[index]!;
|
|
4778
4897
|
if (entry.operation !== "run") continue;
|
|
4898
|
+
const entryLabel = entry.label?.trim() || undefined;
|
|
4899
|
+
const entryPhase = entry.phase?.trim() || undefined;
|
|
4900
|
+
const shouldProjectPhase = entryPhase !== undefined && (entryPhase !== "auto-resume" || workflowSteps.get(entry.key)?.phase === undefined);
|
|
4779
4901
|
const existing = workflowSteps.get(entry.key);
|
|
4780
|
-
if (
|
|
4902
|
+
if (existing) {
|
|
4903
|
+
if (entryLabel) existing.label = entryLabel;
|
|
4904
|
+
if (shouldProjectPhase) existing.phase = entryPhase;
|
|
4905
|
+
}
|
|
4906
|
+
if (entry.state === "reused" && existing) {
|
|
4907
|
+
continue;
|
|
4908
|
+
}
|
|
4781
4909
|
const mapped = entry.state === "started" || entry.state === "reused"
|
|
4782
4910
|
? "running"
|
|
4783
4911
|
: entry.state === "completed"
|
|
@@ -4801,13 +4929,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4801
4929
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4802
4930
|
else existing.durationMs = entry.durationMs;
|
|
4803
4931
|
} else {
|
|
4932
|
+
// Naming uses the explicit label only — never the workflow key — so the
|
|
4933
|
+
// placeholder cannot diverge from the task-derived name the child
|
|
4934
|
+
// session actually gets (launch/progress updates overwrite this).
|
|
4935
|
+
const stepSessionName = deriveChildSessionName({ agent: entry.agent ?? entry.key, label: entryLabel });
|
|
4804
4936
|
const step: NonNullable<AsyncStatus["steps"]>[number] = {
|
|
4805
4937
|
agent: entry.agent ?? entry.key,
|
|
4806
|
-
|
|
4938
|
+
...(stepSessionName ? { sessionName: stepSessionName } : {}),
|
|
4939
|
+
label: entryLabel ?? entry.key,
|
|
4807
4940
|
workflowKey: entry.key,
|
|
4808
4941
|
parentWorkflowRunId: workflowRunId,
|
|
4809
4942
|
status: mapped,
|
|
4810
4943
|
startedAt: Date.now(),
|
|
4944
|
+
...(entryPhase ? { phase: entryPhase } : {}),
|
|
4811
4945
|
...(entry.runId ? { runId: entry.runId } : {}),
|
|
4812
4946
|
...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
|
|
4813
4947
|
...(entry.state === "detached" ? { activityState: "needs_attention" as const } : {}),
|
|
@@ -4843,6 +4977,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4843
4977
|
try {
|
|
4844
4978
|
const workflow = await runWorkflowScript({
|
|
4845
4979
|
script: workflowScript,
|
|
4980
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
4846
4981
|
timeoutMs: timeout,
|
|
4847
4982
|
signal: controller.signal,
|
|
4848
4983
|
registerStopChild: (stop) => {
|
|
@@ -4851,11 +4986,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4851
4986
|
},
|
|
4852
4987
|
...(workflowState ? { state: workflowState } : {}),
|
|
4853
4988
|
onTrace: updateTrace,
|
|
4989
|
+
host: runHostCommand,
|
|
4990
|
+
onHostStep: (hostStep) => {
|
|
4991
|
+
status = upsertHostStep({ status, hostStep, persist: (nextStatus) => {
|
|
4992
|
+
status = nextStatus;
|
|
4993
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
4994
|
+
} });
|
|
4995
|
+
},
|
|
4854
4996
|
admit: (calls) => {
|
|
4855
|
-
const outputClaims = workflowChildOutputClaims({ ctxCwd:
|
|
4997
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: parentCwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
4856
4998
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
4857
4999
|
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
4858
5000
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
5001
|
+
if (outputClaims.childClaims) for (const [key, claimPath] of outputClaims.childClaims) childOutputClaimPaths.set(key, claimPath);
|
|
4859
5002
|
if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
|
|
4860
5003
|
persist({ tolerateStatusWriteFailure: true });
|
|
4861
5004
|
},
|
|
@@ -4878,19 +5021,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4878
5021
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4879
5022
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4880
5023
|
});
|
|
5024
|
+
let preparedChildParams: SubagentParamsLike | undefined;
|
|
4881
5025
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4882
5026
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4883
|
-
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd:
|
|
5027
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: parentCwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), outputClaimPath: childOutputClaimPaths.get(key), options: { missionDetached: detachWorkflowChildMissions, awaitDetachedChild: true, runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
|
|
4884
5028
|
missionBinding,
|
|
4885
5029
|
deps.asyncByDefault,
|
|
4886
5030
|
);
|
|
5031
|
+
preparedChildParams = childRequest;
|
|
4887
5032
|
workflowLaunchObservers.set(childRequest, (launch) => {
|
|
4888
5033
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4889
5034
|
if (step) {
|
|
4890
5035
|
step.agent = launch.agent;
|
|
5036
|
+
if (launch.sessionName) step.sessionName = launch.sessionName;
|
|
4891
5037
|
step.sessionFile = launch.sessionFile;
|
|
4892
5038
|
step.async = launch.async;
|
|
4893
5039
|
if (launch.runId) step.runId = launch.runId;
|
|
5040
|
+
if (childRequest.lane) step.lane = childRequest.lane;
|
|
4894
5041
|
persist({ tolerateStatusWriteFailure: true });
|
|
4895
5042
|
}
|
|
4896
5043
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
@@ -4912,6 +5059,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4912
5059
|
step.toolCount = progress.toolCount;
|
|
4913
5060
|
step.model = progress.model;
|
|
4914
5061
|
step.thinking = progress.thinking;
|
|
5062
|
+
if (progress.sessionName) step.sessionName = progress.sessionName;
|
|
4915
5063
|
step.error = progress.error;
|
|
4916
5064
|
projectWorkflowActivity();
|
|
4917
5065
|
persist({ tolerateStatusWriteFailure: true });
|
|
@@ -4923,14 +5071,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4923
5071
|
});
|
|
4924
5072
|
workflowResults.push(...result.details.results);
|
|
4925
5073
|
for (const childResult of result.details.results) {
|
|
4926
|
-
if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
|
|
5074
|
+
if (childResult.savedOutputPath) producedChildOutputPaths.add(resolveWorkflowHostOutputClaimPath(childResult.savedOutputPath));
|
|
4927
5075
|
}
|
|
4928
|
-
const child = workflowChildResult(key, result, childParams, deps.state);
|
|
5076
|
+
const child = workflowChildResult(key, result, preparedChildParams ?? childParams, deps.state);
|
|
4929
5077
|
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
4930
5078
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4931
5079
|
if (step) {
|
|
4932
5080
|
step.async = Boolean(result.details.asyncId || result.details.asyncDir);
|
|
4933
5081
|
if (child.runId) step.runId = child.runId;
|
|
5082
|
+
if (child.lane) step.lane = child.lane;
|
|
4934
5083
|
}
|
|
4935
5084
|
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4936
5085
|
const childStatus = missionWorkflowChildStatus(result);
|
|
@@ -4953,22 +5102,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4953
5102
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
4954
5103
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
4955
5104
|
});
|
|
5105
|
+
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
5106
|
+
const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
|
|
4956
5107
|
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
4957
5108
|
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
4958
|
-
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).${workflowOutputPathMappingSummary(workflow.children)}`;
|
|
5109
|
+
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).${workflowOutputPathMappingSummary(workflow.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
|
|
4959
5110
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
4960
5111
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
4961
5112
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4962
5113
|
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
|
|
4963
|
-
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace:
|
|
4964
|
-
const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren);
|
|
5114
|
+
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
5115
|
+
const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph));
|
|
4965
5116
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
4966
5117
|
try {
|
|
4967
5118
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
4968
5119
|
} catch (receiptError) {
|
|
4969
5120
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4970
5121
|
}
|
|
4971
|
-
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5122
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(status.steps?.find((step) => step.workflowKey === child.key)?.sessionName ? { sessionName: status.steps?.find((step) => step.workflowKey === child.key)?.sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4972
5123
|
persist();
|
|
4973
5124
|
persistClosed = true;
|
|
4974
5125
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
@@ -4992,26 +5143,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4992
5143
|
}
|
|
4993
5144
|
const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
|
|
4994
5145
|
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
|
|
4995
|
-
|
|
5146
|
+
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
|
|
5147
|
+
const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
|
|
5148
|
+
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
|
|
4996
5149
|
if (pauseForDetached) {
|
|
4997
5150
|
const promoted = promotePausedWorkflowIfSettled(status);
|
|
4998
5151
|
if (promoted) status = promoted;
|
|
4999
5152
|
}
|
|
5000
5153
|
const terminalSummary = `${status.state === "complete"
|
|
5001
5154
|
? "Workflow completed after detached child finished."
|
|
5002
|
-
: status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${workflowOutputPathMappingSummary(partial.children)}`;
|
|
5155
|
+
: status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
|
|
5003
5156
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
|
|
5004
5157
|
const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
|
|
5005
5158
|
const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
|
|
5006
5159
|
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5007
|
-
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome);
|
|
5160
|
+
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph));
|
|
5008
5161
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
5009
5162
|
try {
|
|
5010
5163
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
5011
5164
|
} catch (receiptError) {
|
|
5012
5165
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
5013
5166
|
}
|
|
5014
|
-
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5167
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(status.steps?.find((step) => step.workflowKey === child.key)?.sessionName ? { sessionName: status.steps?.find((step) => step.workflowKey === child.key)?.sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5015
5168
|
persist();
|
|
5016
5169
|
persistClosed = true;
|
|
5017
5170
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
@@ -5024,44 +5177,55 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5024
5177
|
}
|
|
5025
5178
|
});
|
|
5026
5179
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5027
|
-
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
5028
|
-
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
5180
|
+
content: [{ type: "text", text: `${workflowPreflight ? `${formatWorkflowPreflight(workflowPreflight)}\n\n` : ""}${formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true)}` }],
|
|
5181
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
5029
5182
|
}, workflowFanoutBudget));
|
|
5030
5183
|
}
|
|
5031
|
-
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
|
|
5184
|
+
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, preflight: _preflight, ...workflowChildDefaults } = requestParams;
|
|
5032
5185
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5033
5186
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5034
5187
|
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id));
|
|
5035
5188
|
const claimedOutputPaths = new Map<string, string>();
|
|
5036
5189
|
const childOutputOverrides = new Map<string, string>();
|
|
5190
|
+
const childOutputClaimPaths = new Map<string, string>();
|
|
5037
5191
|
const producedChildOutputPaths = new Set<string>();
|
|
5038
5192
|
const workflowResults: SingleResult[] = [];
|
|
5039
5193
|
const workflowChildRunIds = new Map<string, string>();
|
|
5194
|
+
const runHostCommand = workflowHostCommandRunner({ workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, claimedOutputPaths, producedOutputPaths: producedChildOutputPaths });
|
|
5195
|
+
const workflowHostSteps = new Map<string, HostStepNodeV1>();
|
|
5040
5196
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
5041
5197
|
let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
|
|
5042
5198
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5043
5199
|
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
5044
5200
|
const sendWorkflowProgress = () => {
|
|
5045
|
-
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren);
|
|
5201
|
+
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight);
|
|
5046
5202
|
if (update) onUpdate?.(update);
|
|
5047
5203
|
};
|
|
5048
5204
|
try {
|
|
5049
5205
|
const workflow = await runWorkflowScript({
|
|
5050
5206
|
script: requestParams.workflowScript,
|
|
5051
5207
|
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
|
|
5208
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
5052
5209
|
timeoutMs: timeout,
|
|
5053
5210
|
signal,
|
|
5054
5211
|
...(workflowState ? { state: workflowState } : {}),
|
|
5212
|
+
...(!delegatedWorkflowPermit ? { host: runHostCommand } : {}),
|
|
5213
|
+
onHostStep: (hostStep) => {
|
|
5214
|
+
workflowHostSteps.set(hostStep.id, { ...hostStep, ...(hostStep.freshness ? { freshness: { ...hostStep.freshness } } : {}) });
|
|
5215
|
+
},
|
|
5055
5216
|
onTrace: (trace) => {
|
|
5056
|
-
|
|
5217
|
+
const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
|
|
5218
|
+
const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
|
|
5219
|
+
liveWorkflow = { ...liveWorkflow, trace: projectedTrace, ...(preflightWarnings.length ? { preflightWarnings } : {}) };
|
|
5057
5220
|
liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false, trace });
|
|
5058
5221
|
sendWorkflowProgress();
|
|
5059
5222
|
},
|
|
5060
5223
|
admit: (calls) => {
|
|
5061
|
-
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents:
|
|
5224
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
5062
5225
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5063
5226
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5064
5227
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
5228
|
+
if (outputClaims.childClaims) for (const [key, claimPath] of outputClaims.childClaims) childOutputClaimPaths.set(key, claimPath);
|
|
5065
5229
|
if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
|
|
5066
5230
|
},
|
|
5067
5231
|
onEmit: (emits) => {
|
|
@@ -5084,17 +5248,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5084
5248
|
...(childPhase ? { phase: childPhase } : {}),
|
|
5085
5249
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
5086
5250
|
});
|
|
5251
|
+
let preparedChildParams: SubagentParamsLike | undefined;
|
|
5087
5252
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
5088
5253
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5089
|
-
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents:
|
|
5254
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), outputClaimPath: childOutputClaimPaths.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
|
|
5090
5255
|
missionBinding,
|
|
5091
5256
|
deps.asyncByDefault,
|
|
5092
5257
|
);
|
|
5258
|
+
preparedChildParams = childRequest;
|
|
5093
5259
|
if (delegatedWorkflowPermit) {
|
|
5094
5260
|
if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
|
|
5095
5261
|
const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
|
|
5096
5262
|
const childAgent = typeof childRequest.agent === "string"
|
|
5097
|
-
? resolveAgentName(childRequest.agent,
|
|
5263
|
+
? resolveAgentName(childRequest.agent, discoverWorkflowAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
|
|
5098
5264
|
: undefined;
|
|
5099
5265
|
if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
|
|
5100
5266
|
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: _id, childKey: key } });
|
|
@@ -5116,10 +5282,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5116
5282
|
});
|
|
5117
5283
|
workflowResults.push(...result.details.results);
|
|
5118
5284
|
for (const childResult of result.details.results) {
|
|
5119
|
-
if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
|
|
5285
|
+
if (childResult.savedOutputPath) producedChildOutputPaths.add(resolveWorkflowHostOutputClaimPath(childResult.savedOutputPath));
|
|
5120
5286
|
}
|
|
5121
5287
|
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
5122
|
-
const child = workflowChildResult(key, result, childParams, deps.state);
|
|
5288
|
+
const child = workflowChildResult(key, result, preparedChildParams ?? childParams, deps.state);
|
|
5123
5289
|
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5124
5290
|
const childStatus = missionWorkflowChildStatus(result);
|
|
5125
5291
|
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
@@ -5137,13 +5303,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5137
5303
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
5138
5304
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5139
5305
|
});
|
|
5306
|
+
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
5307
|
+
const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
|
|
5140
5308
|
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
|
|
5141
|
-
const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren);
|
|
5142
|
-
const traceLines =
|
|
5143
|
-
const sections = [
|
|
5309
|
+
const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()]);
|
|
5310
|
+
const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
5311
|
+
const sections = [
|
|
5312
|
+
...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
|
|
5313
|
+
"Workflow completed.",
|
|
5314
|
+
`Return:\n${formatWorkflowValue(workflow.value)}`,
|
|
5315
|
+
];
|
|
5144
5316
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
5145
5317
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5146
5318
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5319
|
+
if (finalPreflightWarnings.length > 0) sections.push(formatWorkflowPreflightWarnings(finalPreflightWarnings));
|
|
5147
5320
|
const outputMappings = workflowOutputPathMappingSummary(workflow.children).trim();
|
|
5148
5321
|
if (outputMappings) sections.push(outputMappings);
|
|
5149
5322
|
const workflowText = sections.join("\n\n");
|
|
@@ -5151,16 +5324,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5151
5324
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5152
5325
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5153
5326
|
content: [{ type: "text", text: displayText }],
|
|
5154
|
-
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace:
|
|
5327
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5155
5328
|
}, workflowFanoutBudget));
|
|
5156
5329
|
} catch (error) {
|
|
5157
5330
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
5158
5331
|
const text = workflowFailureMessage(error, _id, partial.children);
|
|
5159
|
-
const
|
|
5160
|
-
const
|
|
5332
|
+
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
|
|
5333
|
+
const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
|
|
5334
|
+
const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
5335
|
+
const sections = [
|
|
5336
|
+
...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
|
|
5337
|
+
`Workflow failed: ${text}`,
|
|
5338
|
+
];
|
|
5161
5339
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
5162
5340
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5163
5341
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5342
|
+
if (finalPreflightWarnings.length > 0) sections.push(formatWorkflowPreflightWarnings(finalPreflightWarnings));
|
|
5164
5343
|
const outputMappings = workflowOutputPathMappingSummary(partial.children).trim();
|
|
5165
5344
|
if (outputMappings) sections.push(outputMappings);
|
|
5166
5345
|
const workflowText = sections.join("\n\n");
|
|
@@ -5168,11 +5347,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5168
5347
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5169
5348
|
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
|
|
5170
5349
|
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5171
|
-
const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome);
|
|
5350
|
+
const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()]);
|
|
5172
5351
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5173
5352
|
content: [{ type: "text", text: displayText }],
|
|
5174
5353
|
isError: true,
|
|
5175
|
-
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace:
|
|
5354
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5176
5355
|
}, workflowFanoutBudget));
|
|
5177
5356
|
}
|
|
5178
5357
|
}
|
|
@@ -5197,6 +5376,35 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5197
5376
|
requestParentModel = normalizeParentModel(ctx.model);
|
|
5198
5377
|
}
|
|
5199
5378
|
if (action) {
|
|
5379
|
+
if (action === "worktree.cleanup") {
|
|
5380
|
+
if (deps.allowMutatingManagementActions === false) {
|
|
5381
|
+
return { content: [{ type: "text", text: "Action 'worktree.cleanup' is not available from child-safe subagent fanout mode." }], isError: true, details: { mode: "management", results: [] } };
|
|
5382
|
+
}
|
|
5383
|
+
if (paramsWithResolvedCwd.mode !== "plan") {
|
|
5384
|
+
return { content: [{ type: "text", text: "worktree.cleanup currently supports mode='plan' only; apply/removal is not available yet." }], isError: true, details: { mode: "management", results: [] } };
|
|
5385
|
+
}
|
|
5386
|
+
if (paramsWithResolvedCwd.planId !== undefined) {
|
|
5387
|
+
return { content: [{ type: "text", text: "worktree.cleanup plan mode does not accept planId; apply is not available yet." }], isError: true, details: { mode: "management", results: [] } };
|
|
5388
|
+
}
|
|
5389
|
+
try {
|
|
5390
|
+
const created = createWorktreeCleanupPlan({
|
|
5391
|
+
repo: paramsWithResolvedCwd.repo?.trim()
|
|
5392
|
+
? path.isAbsolute(paramsWithResolvedCwd.repo) ? paramsWithResolvedCwd.repo : path.resolve(requestCwd, paramsWithResolvedCwd.repo)
|
|
5393
|
+
: requestCwd,
|
|
5394
|
+
...(paramsWithResolvedCwd.handoffPath ? { handoffPath: path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath) } : {}),
|
|
5395
|
+
...(deps.config.worktreeBaseDir ? { worktreeBaseDir: deps.config.worktreeBaseDir } : {}),
|
|
5396
|
+
foregroundRunOwnership: (runId: string) => {
|
|
5397
|
+
if (deps.state.foregroundControls.has(runId)) return "active" as const;
|
|
5398
|
+
const remembered = deps.state.foregroundRuns?.get(runId);
|
|
5399
|
+
if (!remembered || remembered.children.length === 0 || remembered.children.some((child) => child.status === "detached")) return "unknown" as const;
|
|
5400
|
+
return "terminal" as const;
|
|
5401
|
+
},
|
|
5402
|
+
});
|
|
5403
|
+
return { content: [{ type: "text", text: formatWorktreeCleanupPlan(created) }], details: { mode: "management", results: [] } };
|
|
5404
|
+
} catch (error) {
|
|
5405
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5200
5408
|
if (action === "worktree.discard") {
|
|
5201
5409
|
if (deps.allowMutatingManagementActions === false) {
|
|
5202
5410
|
return { content: [{ type: "text", text: "Action 'worktree.discard' is not available from child-safe subagent fanout mode." }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -5224,6 +5432,30 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5224
5432
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
|
|
5225
5433
|
}
|
|
5226
5434
|
}
|
|
5435
|
+
if (action === "lane.status" || action === "lane.recordMerge" || action === "lane.recordSupersession") {
|
|
5436
|
+
if (action !== "lane.status" && deps.allowMutatingManagementActions === false) {
|
|
5437
|
+
return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5438
|
+
}
|
|
5439
|
+
const laneId = paramsWithResolvedCwd.laneId?.trim();
|
|
5440
|
+
if (!laneId) return { content: [{ type: "text", text: `${action} requires laneId.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5441
|
+
const handoffPath = paramsWithResolvedCwd.handoffPath?.trim();
|
|
5442
|
+
if (!handoffPath) return { content: [{ type: "text", text: `${action} requires handoffPath for the existing parallel handoff manifest.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5443
|
+
const manifestPath = path.isAbsolute(handoffPath) ? handoffPath : path.resolve(requestCwd, handoffPath);
|
|
5444
|
+
try {
|
|
5445
|
+
if (action === "lane.status") {
|
|
5446
|
+
let manifest;
|
|
5447
|
+
try { manifest = readParallelHandoffManifest(manifestPath); } catch { manifest = undefined; }
|
|
5448
|
+
if (manifest && manifest.runId !== laneId) throw new Error(`Lane '${laneId}' does not match manifest run '${manifest.runId}'.`);
|
|
5449
|
+
return { content: [{ type: "text", text: formatStoredParallelHandoffCleanup(manifestPath, manifest) }], details: { mode: "management", results: [] } };
|
|
5450
|
+
}
|
|
5451
|
+
const recorded = action === "lane.recordMerge"
|
|
5452
|
+
? recordParallelHandoffMerge({ manifestPath, laneId, merge: paramsWithResolvedCwd.merge })
|
|
5453
|
+
: recordParallelHandoffSupersession({ manifestPath, laneId, supersession: paramsWithResolvedCwd.supersession });
|
|
5454
|
+
return { content: [{ type: "text", text: recorded.text }], details: { mode: "management", results: [], parallelHandoff: recorded.reference } };
|
|
5455
|
+
} catch (error) {
|
|
5456
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5227
5459
|
if ((HERDR_PROJECT_PANE_ACTIONS as readonly string[]).includes(action)) {
|
|
5228
5460
|
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
5229
5461
|
return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -5501,6 +5733,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5501
5733
|
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
|
|
5502
5734
|
}
|
|
5503
5735
|
if (action === "steer") {
|
|
5736
|
+
if (paramsWithResolvedCwd.mode !== undefined && resolveSteerDeliveryMode(paramsWithResolvedCwd.mode) === undefined) {
|
|
5737
|
+
return { content: [{ type: "text", text: "action='steer' mode must be 'steer', 'follow_up', or 'auto'." }], isError: true, details: { mode: "management", results: [] } };
|
|
5738
|
+
}
|
|
5504
5739
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5505
5740
|
const message = (paramsWithResolvedCwd.message ?? paramsWithResolvedCwd.task ?? "").trim();
|
|
5506
5741
|
if (!message) return { content: [{ type: "text", text: "action='steer' requires message." }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -5513,7 +5748,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5513
5748
|
if (directoryStatus?.mode === "workflow") {
|
|
5514
5749
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
|
|
5515
5750
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5516
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
|
|
5751
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
|
|
5517
5752
|
}
|
|
5518
5753
|
if (location.asyncDir) {
|
|
5519
5754
|
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
@@ -5523,7 +5758,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5523
5758
|
state: deps.state,
|
|
5524
5759
|
runId,
|
|
5525
5760
|
message,
|
|
5526
|
-
mode: paramsWithResolvedCwd.mode,
|
|
5761
|
+
mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
|
|
5527
5762
|
index: paramsWithResolvedCwd.index,
|
|
5528
5763
|
kill: deps.kill,
|
|
5529
5764
|
location,
|
|
@@ -5549,18 +5784,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5549
5784
|
const text = error instanceof Error ? error.message : String(error);
|
|
5550
5785
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
5551
5786
|
}
|
|
5552
|
-
if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal }));
|
|
5787
|
+
if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal }));
|
|
5553
5788
|
if (resolved?.kind === "foreground") {
|
|
5554
5789
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, childRunId: resolved.id, asyncDirRoot: DIRS.async });
|
|
5555
5790
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5556
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
|
|
5791
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
|
|
5557
5792
|
}
|
|
5558
5793
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5559
5794
|
const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
|
|
5560
5795
|
if (resolvedStatus?.mode === "workflow") {
|
|
5561
5796
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
|
|
5562
5797
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5563
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
|
|
5798
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
|
|
5564
5799
|
}
|
|
5565
5800
|
if (resolved.location.asyncDir) {
|
|
5566
5801
|
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
@@ -5570,7 +5805,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5570
5805
|
state: deps.state,
|
|
5571
5806
|
runId: resolved.id,
|
|
5572
5807
|
message,
|
|
5573
|
-
mode: paramsWithResolvedCwd.mode,
|
|
5808
|
+
mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
|
|
5574
5809
|
index: paramsWithResolvedCwd.index,
|
|
5575
5810
|
kill: deps.kill,
|
|
5576
5811
|
location: resolved.location,
|
|
@@ -5809,7 +6044,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5809
6044
|
const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
|
|
5810
6045
|
const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
|
|
5811
6046
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
5812
|
-
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
6047
|
+
const discovered = deps.discoverAgents(effectiveCwd, scope, requestParentModel?.provider);
|
|
5813
6048
|
const discoveredAgents = discovered.agents;
|
|
5814
6049
|
const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
|
|
5815
6050
|
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics, unknownAgentDiagnosticContext);
|
|
@@ -5817,8 +6052,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5817
6052
|
effectiveParams = canonicalParams.params!;
|
|
5818
6053
|
const modelScope = discovered.modelScope;
|
|
5819
6054
|
effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
|
|
5820
|
-
const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
|
|
5821
|
-
if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
|
|
5822
6055
|
// An agent-level defaultContext is a preference, unlike an explicit request.
|
|
5823
6056
|
// Prefer fork only when the parent session is persisted and has a current leaf;
|
|
5824
6057
|
// otherwise use fresh immediately instead of launching a guaranteed-to-fail fork.
|
|
@@ -6138,7 +6371,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6138
6371
|
intercomBridge,
|
|
6139
6372
|
nestedRoute,
|
|
6140
6373
|
timeoutMs: foregroundTimeout.timeoutMs,
|
|
6141
|
-
turnBudget: turnBudget.turnBudget,
|
|
6142
6374
|
toolBudget: runToolBudget.toolBudget,
|
|
6143
6375
|
usageBudget: usageBudget.budget,
|
|
6144
6376
|
allowZeroToolBudget,
|
|
@@ -6283,6 +6515,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6283
6515
|
mode: foregroundMode,
|
|
6284
6516
|
state,
|
|
6285
6517
|
agent: agentsForSummary[0],
|
|
6518
|
+
...(details?.results.length === 1 && details.results[0]?.sessionName ? { sessionName: details.results[0].sessionName } : {}),
|
|
6286
6519
|
agents: agentsForSummary,
|
|
6287
6520
|
...(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 } : {}),
|
|
6288
6521
|
...(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 } : {}),
|
|
@@ -6301,6 +6534,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6301
6534
|
: details?.results.length
|
|
6302
6535
|
? { steps: details.results.map((child) => ({
|
|
6303
6536
|
agent: child.agent,
|
|
6537
|
+
...(child.sessionName ? { sessionName: child.sessionName } : {}),
|
|
6304
6538
|
status: child.interrupted || child.detached ? "paused" as const : child.exitCode === 0 ? "complete" as const : "failed" as const,
|
|
6305
6539
|
...(child.model ? { model: child.model } : {}),
|
|
6306
6540
|
...(child.thinking ? { thinking: child.thinking } : {}),
|
|
@@ -6324,10 +6558,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6324
6558
|
}
|
|
6325
6559
|
if (workflowLaunchObserver) {
|
|
6326
6560
|
const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
|
|
6561
|
+
const singleSessionName = hasSingle
|
|
6562
|
+
? deriveChildSessionName({ agent: effectiveParams.agent!, task: effectiveParams.task })
|
|
6563
|
+
: singleTask
|
|
6564
|
+
? deriveChildSessionName({ agent: singleTask.agent, task: singleTask.task })
|
|
6565
|
+
: undefined;
|
|
6327
6566
|
const launch = hasSingle
|
|
6328
|
-
? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model), async: effectiveAsync,
|
|
6567
|
+
? { agent: effectiveParams.agent!, ...(singleSessionName ? { sessionName: singleSessionName } : {}), sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model), async: effectiveAsync, runId: effectiveAsync ? asyncRunId : runId }
|
|
6329
6568
|
: singleTask
|
|
6330
|
-
? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model), async: effectiveAsync,
|
|
6569
|
+
? { agent: singleTask.agent, ...(singleSessionName ? { sessionName: singleSessionName } : {}), sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model), async: effectiveAsync, runId: effectiveAsync ? asyncRunId : runId }
|
|
6331
6570
|
: undefined;
|
|
6332
6571
|
if (launch) {
|
|
6333
6572
|
workflowLaunchObservers.delete(params);
|
|
@@ -6379,15 +6618,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6379
6618
|
): Promise<AgentToolResult<Details>> => {
|
|
6380
6619
|
const normalizedAction = typeof params.action === "string" ? params.action.trim() : params.action;
|
|
6381
6620
|
const requestParams = normalizedAction ? { ...params, action: normalizedAction } : params;
|
|
6382
|
-
if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx);
|
|
6621
|
+
if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx).then(withAggregatedToolUsage);
|
|
6383
6622
|
const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
6384
6623
|
const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
|
|
6385
6624
|
const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
|
|
6386
|
-
if (!runsForeground) return execute(id, requestParams, signal, onUpdate, ctx);
|
|
6625
|
+
if (!runsForeground) return execute(id, requestParams, signal, onUpdate, ctx).then(withAggregatedToolUsage);
|
|
6387
6626
|
if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(requestParams);
|
|
6388
6627
|
deps.state.subagentInProgress = true;
|
|
6389
6628
|
try {
|
|
6390
|
-
return await execute(id, requestParams, signal, onUpdate, ctx);
|
|
6629
|
+
return withAggregatedToolUsage(await execute(id, requestParams, signal, onUpdate, ctx));
|
|
6391
6630
|
} finally {
|
|
6392
6631
|
deps.state.subagentInProgress = false;
|
|
6393
6632
|
}
|
|
@@ -6400,7 +6639,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6400
6639
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
6401
6640
|
ctx: ExtensionContext,
|
|
6402
6641
|
): Promise<AgentToolResult<Details>> => {
|
|
6403
|
-
const normalized = normalizePublicSubagentExecution(params
|
|
6642
|
+
const normalized = normalizePublicSubagentExecution(params);
|
|
6404
6643
|
if (!normalized.ok) {
|
|
6405
6644
|
return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
|
|
6406
6645
|
}
|
|
@@ -6434,7 +6673,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6434
6673
|
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
6435
6674
|
if (workflowPermit) workflowPermitContexts.set(delegatedParams, { root: workflowPermit });
|
|
6436
6675
|
delegatedExecutions.add(delegatedParams);
|
|
6437
|
-
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
6676
|
+
return withAggregatedToolUsage(await execute(id, delegatedParams, signal, onUpdate, ctx));
|
|
6438
6677
|
};
|
|
6439
6678
|
|
|
6440
6679
|
const executeScheduled = (
|