pi-subagents 0.65.1 → 0.67.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 +123 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/researcher.md +23 -13
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +20 -3
- package/docs/configuration.md +25 -5
- package/docs/extension-api.md +124 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +59 -2
- package/docs/observability.md +46 -6
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +20 -10
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +40 -19
- package/inspector-runner.mjs +2 -2
- package/package.json +2 -1
- package/prompts/parallel-review.md +1 -1
- package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
- package/skills/pi-subagents/SKILL.md +14 -0
- package/skills/pi-subagents/references/execution-controls.md +20 -5
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +94 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +14 -0
- package/src/agents/builtin-names.ts +1 -0
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +76 -45
- package/src/api/shared-types.ts +3 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +58 -8
- package/src/extension/public-execution.ts +4 -3
- package/src/extension/rpc.ts +8 -21
- package/src/extension/schemas.ts +71 -80
- package/src/extension/tool-description.ts +29 -81
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +104 -67
- package/src/runs/background/active-async-capacity.ts +22 -18
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +44 -30
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +117 -13
- package/src/runs/background/owned-process-tree.ts +35 -8
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +121 -36
- package/src/runs/background/run-status.ts +78 -5
- package/src/runs/background/runner-aliases.ts +28 -9
- package/src/runs/background/runner-child-launch.ts +88 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +458 -239
- package/src/runs/background/subagent-wait.ts +54 -8
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +37 -7
- package/src/runs/foreground/execution.ts +145 -56
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +584 -297
- package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
- package/src/runs/shared/acceptance.ts +7 -4
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +51 -47
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +167 -3
- package/src/runs/shared/child-launch.ts +28 -13
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +3 -1
- package/src/runs/shared/child-session.ts +75 -8
- package/src/runs/shared/child-tool-plan.ts +124 -5
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +6 -3
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +21 -11
- package/src/runs/shared/model-fallback.ts +12 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +7 -1
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +366 -208
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +65 -2
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/types.ts +85 -61
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -1
- package/src/slash/subagents-admin.ts +11 -4
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +27 -19
- package/src/tui/render.ts +172 -33
- package/src/watchdog/child-status.ts +8 -0
- package/src/watchdog/model-selection.ts +20 -0
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +1 -0
- package/src/watchdog/register-main.ts +31 -27
- package/src/watchdog/review.ts +132 -67
- package/src/watchdog/runtime.ts +82 -20
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +9 -3
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +4 -0
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +275 -17
- package/src/workflows/workflow-checklist.ts +13 -17
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -29,6 +29,7 @@ import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/
|
|
|
29
29
|
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
30
30
|
import { applyWatchdogLaunchRules } from "../../watchdog/rules.ts";
|
|
31
31
|
import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type ModelOrigin, type ParentModel } from "../shared/model-fallback.ts";
|
|
32
|
+
import { getHostBuiltinToolNames } from "../shared/child-tool-plan.ts";
|
|
32
33
|
import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
|
|
33
34
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
34
35
|
import { recordRun } from "../shared/run-history.ts";
|
|
@@ -52,8 +53,8 @@ import { isScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
|
52
53
|
import { encodeIndexSegment } from "../background/index-segment.ts";
|
|
53
54
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
54
55
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
55
|
-
import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
56
|
-
import { canPreferFork, createForkContextResolver,
|
|
56
|
+
import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
57
|
+
import { canPreferFork, createForkContextResolver, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
|
|
57
58
|
import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
|
|
58
59
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
59
60
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
@@ -65,13 +66,13 @@ import { retainLiveForegroundNestedRoute } from "../../integrations/pi-web-sessi
|
|
|
65
66
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
66
67
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
67
68
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
68
|
-
import {
|
|
69
|
+
import { isAgentContract } from "../shared/agent-contract.ts";
|
|
69
70
|
import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
70
71
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
71
72
|
import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
72
73
|
import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.ts";
|
|
73
74
|
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
74
|
-
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup,
|
|
75
|
+
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.ts";
|
|
75
76
|
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
76
77
|
import {
|
|
77
78
|
attachNestedChildrenToResultChildren,
|
|
@@ -82,12 +83,13 @@ import {
|
|
|
82
83
|
stripDetailsOutputsForIntercomReceipt,
|
|
83
84
|
} from "../../intercom/result-intercom.ts";
|
|
84
85
|
import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, readAsyncRecoveryDescriptor, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
85
|
-
import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
86
|
-
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
86
|
+
import { closeSteerInbox, consumeSteerRequests, deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, watchAsyncControlInbox, type SteerDeliveryMode, type SteerRequest } from "../background/control-channel.ts";
|
|
87
|
+
import { createSteeringStatus, recordSteeringRequest, steeringStatus, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
87
88
|
import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
|
|
88
89
|
import {
|
|
89
90
|
resolveWorkflowForegroundSteeringTarget,
|
|
90
91
|
steerWorkflowForegroundTarget,
|
|
92
|
+
steerWorkflowRun,
|
|
91
93
|
} from "./workflow-foreground-steering.ts";
|
|
92
94
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
93
95
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
@@ -111,14 +113,16 @@ import { MissionNotFoundError, updateMission } from "../../missions/store.ts";
|
|
|
111
113
|
import type { MissionWorkflowChildUpdate } from "../../missions/types.ts";
|
|
112
114
|
import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
113
115
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
114
|
-
import {
|
|
116
|
+
import { handleInspectorAction, INSPECTOR_ACTIONS } from "../../inspectors/actions.ts";
|
|
117
|
+
import { createBuiltinInspectorPlugins } from "../../inspectors/plugins.ts";
|
|
115
118
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
116
|
-
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowLanePlan, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowScriptTraceEntry, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
119
|
+
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowChildSettledNotification, type WorkflowLanePlan, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowScriptTraceEntry, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
120
|
+
import { formatIncrementalChildCompletion, scheduledCompletionTriggersTurn } from "../background/notify.ts";
|
|
117
121
|
import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
|
|
118
|
-
import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
|
|
122
|
+
import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
|
|
119
123
|
import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
|
|
120
124
|
import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
|
|
121
|
-
import { parseWorkflowChildSummary, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
125
|
+
import { parseWorkflowChildSummary, workflowChildProgress, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
122
126
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
123
127
|
import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.ts";
|
|
124
128
|
import {
|
|
@@ -135,6 +139,9 @@ import { resolveWorkflowResource } from "../../workflows/workflow-resources.ts";
|
|
|
135
139
|
import {
|
|
136
140
|
cleanupWorktrees,
|
|
137
141
|
createWorktrees,
|
|
142
|
+
preflightWorktreeSource,
|
|
143
|
+
withWorktreeTransaction,
|
|
144
|
+
type WorktreeSetupProgress,
|
|
138
145
|
diffWorktrees,
|
|
139
146
|
formatWorktreeDiffSummary,
|
|
140
147
|
type WorktreeSetup,
|
|
@@ -157,7 +164,7 @@ import {
|
|
|
157
164
|
type ForegroundSteerInput,
|
|
158
165
|
type ForegroundSteerOutcome,
|
|
159
166
|
type ForegroundRunControl,
|
|
160
|
-
type
|
|
167
|
+
type HostStepNode,
|
|
161
168
|
type IntercomBridgeConfig,
|
|
162
169
|
type IntercomEventBus,
|
|
163
170
|
type JsonSchemaObject,
|
|
@@ -174,7 +181,7 @@ import {
|
|
|
174
181
|
type ToolBudgetConfig,
|
|
175
182
|
type Usage,
|
|
176
183
|
type UsageBudgetConfig,
|
|
177
|
-
type
|
|
184
|
+
type WorkflowResourceProvenance,
|
|
178
185
|
type WorkflowGraphNode,
|
|
179
186
|
type WorkflowGraphSnapshot,
|
|
180
187
|
type WorkflowNodeStatus,
|
|
@@ -194,6 +201,7 @@ import {
|
|
|
194
201
|
resolveMaxSubagentSpawnsPerRun,
|
|
195
202
|
wrapForkTask,
|
|
196
203
|
type ScheduleOrigin,
|
|
204
|
+
type SteeringTargetState,
|
|
197
205
|
} from "../../shared/types.ts";
|
|
198
206
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
199
207
|
|
|
@@ -326,7 +334,7 @@ export interface SubagentParamsLike {
|
|
|
326
334
|
workflowScriptPath?: string;
|
|
327
335
|
globalConcurrencyLimit?: number;
|
|
328
336
|
maxSubagentSpawnsPerRun?: number;
|
|
329
|
-
preflight?: import("../../shared/types.ts").
|
|
337
|
+
preflight?: import("../../shared/types.ts").WorkflowPreflight;
|
|
330
338
|
chatProgress?: "auto" | "off" | "live-card";
|
|
331
339
|
isolation?: "none" | "worktree";
|
|
332
340
|
step?: ChainStep;
|
|
@@ -400,6 +408,7 @@ export interface SubagentParamsLike {
|
|
|
400
408
|
at?: string;
|
|
401
409
|
every?: string;
|
|
402
410
|
sessionOnly?: boolean;
|
|
411
|
+
quiet?: boolean;
|
|
403
412
|
on?: string | number;
|
|
404
413
|
timezone?: string;
|
|
405
414
|
overlap?: "skip";
|
|
@@ -437,8 +446,10 @@ interface ExecutorDeps {
|
|
|
437
446
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
438
447
|
expandTilde: (p: string) => string;
|
|
439
448
|
discoverAgents: (cwd: string, scope: AgentScope, preferredModelProvider?: string) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
|
|
449
|
+
onAgentsChanged?: () => void;
|
|
440
450
|
allowMutatingManagementActions?: boolean;
|
|
441
451
|
activateSupervisorTransport?: () => void;
|
|
452
|
+
findPendingAsks?: Parameters<typeof steerAsyncRun>[0]["findPendingAsks"];
|
|
442
453
|
refreshResultDelivery?: () => void;
|
|
443
454
|
trackRetainedNestedRoute?: (rootRunId: string) => void;
|
|
444
455
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
@@ -460,7 +471,7 @@ function inheritedRunFanoutBudget(deps: Pick<ExecutorDeps, "childRuntime">): Run
|
|
|
460
471
|
|
|
461
472
|
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => string | undefined;
|
|
462
473
|
type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => Promise<void>;
|
|
463
|
-
type
|
|
474
|
+
type ThinkingOverrideForTask = () => AgentConfig["thinking"] | undefined;
|
|
464
475
|
|
|
465
476
|
interface ExecutionContextData {
|
|
466
477
|
params: SubagentParamsLike;
|
|
@@ -480,7 +491,7 @@ interface ExecutionContextData {
|
|
|
480
491
|
sessionDirForIndex: (idx?: number) => string;
|
|
481
492
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
482
493
|
sessionFileForTask: ForkSessionFileForTask;
|
|
483
|
-
thinkingOverrideForTask:
|
|
494
|
+
thinkingOverrideForTask: ThinkingOverrideForTask;
|
|
484
495
|
artifactConfig: ArtifactConfig;
|
|
485
496
|
artifactsDir: string;
|
|
486
497
|
backgroundRequestedWhileClarifying: boolean;
|
|
@@ -497,6 +508,8 @@ interface ExecutionContextData {
|
|
|
497
508
|
configToolTimeoutMs?: number;
|
|
498
509
|
toolBudget?: ResolvedToolBudget;
|
|
499
510
|
usageBudget?: UsageBudgetConfig;
|
|
511
|
+
/** Parent workflow owns this allowance; presence vetoes unverifiable child continuation. */
|
|
512
|
+
inheritedUsageBudget?: UsageBudgetConfig;
|
|
500
513
|
allowZeroToolBudget?: boolean;
|
|
501
514
|
configToolBudget?: ResolvedToolBudget;
|
|
502
515
|
contextPolicy: AgentDefaultContextPolicy;
|
|
@@ -1742,6 +1755,28 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1742
1755
|
return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId: result.details.asyncId ?? runId, asyncDir: result.details.asyncDir ?? asyncDir, interactive: input.ctx.hasUI });
|
|
1743
1756
|
}
|
|
1744
1757
|
|
|
1758
|
+
function resolveRequestedResumeTarget(params: SubagentParamsLike, deps: ExecutorDeps, parentSessionFile: string | null): ResumeSourceTarget | { kind: "live-nested"; target: ResolvedSubagentRunId & { kind: "nested" } } {
|
|
1759
|
+
const requestedId = params.id ?? params.runId;
|
|
1760
|
+
let resolved: ResolvedSubagentRunId | undefined;
|
|
1761
|
+
try {
|
|
1762
|
+
resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
const message = error instanceof Error ? error.message : "";
|
|
1765
|
+
const asyncMatches = message.match(/async:/g)?.length ?? 0;
|
|
1766
|
+
if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
|
|
1767
|
+
}
|
|
1768
|
+
if (resolved?.kind === "nested") {
|
|
1769
|
+
if (params.chain?.length) throw new Error("Attaching a running subagent as a chain root is currently available for top-level async runs only.");
|
|
1770
|
+
if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") return { kind: "live-nested", target: resolved };
|
|
1771
|
+
const trustedSessionRoots = [
|
|
1772
|
+
...(deps.config.defaultSessionDir ? [path.resolve(deps.expandTilde(deps.config.defaultSessionDir))] : []),
|
|
1773
|
+
...(parentSessionFile ? [deps.getSubagentSessionRoot(parentSessionFile)] : []),
|
|
1774
|
+
];
|
|
1775
|
+
return resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
1776
|
+
}
|
|
1777
|
+
return resolveResumeTarget(params, deps.state, { asyncRequireSessionFile: false });
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1745
1780
|
async function resumeAsyncRun(input: {
|
|
1746
1781
|
params: SubagentParamsLike;
|
|
1747
1782
|
requestCwd: string;
|
|
@@ -1780,34 +1815,9 @@ async function resumeAsyncRun(input: {
|
|
|
1780
1815
|
let target: ResumeSourceTarget;
|
|
1781
1816
|
const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
|
|
1782
1817
|
try {
|
|
1783
|
-
const
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) })) : undefined;
|
|
1787
|
-
} catch (error) {
|
|
1788
|
-
const message = error instanceof Error ? error.message : "";
|
|
1789
|
-
const asyncMatches = message.match(/async:/g)?.length ?? 0;
|
|
1790
|
-
if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
|
|
1791
|
-
}
|
|
1792
|
-
if (resolved?.kind === "nested") {
|
|
1793
|
-
if (attachChain) {
|
|
1794
|
-
return {
|
|
1795
|
-
content: [{ type: "text", text: "Attaching a running subagent as a chain root is currently available for top-level async runs only." }],
|
|
1796
|
-
isError: true,
|
|
1797
|
-
details: { mode: "management", results: [] },
|
|
1798
|
-
};
|
|
1799
|
-
}
|
|
1800
|
-
if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") {
|
|
1801
|
-
return resumeLiveNestedRun({ target: resolved, message: followUp });
|
|
1802
|
-
}
|
|
1803
|
-
const trustedSessionRoots = [
|
|
1804
|
-
...(input.deps.config.defaultSessionDir ? [path.resolve(input.deps.expandTilde(input.deps.config.defaultSessionDir))] : []),
|
|
1805
|
-
...(parentSessionFile ? [input.deps.getSubagentSessionRoot(parentSessionFile)] : []),
|
|
1806
|
-
];
|
|
1807
|
-
target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
1808
|
-
} else {
|
|
1809
|
-
target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: false });
|
|
1810
|
-
}
|
|
1818
|
+
const resolved = resolveRequestedResumeTarget(input.params, input.deps, parentSessionFile);
|
|
1819
|
+
if (resolved.kind === "live-nested") return resumeLiveNestedRun({ target: resolved.target, message: followUp });
|
|
1820
|
+
target = resolved;
|
|
1811
1821
|
} catch (error) {
|
|
1812
1822
|
const message = error instanceof Error ? error.message : String(error);
|
|
1813
1823
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -2118,6 +2128,7 @@ async function resumeAsyncRun(input: {
|
|
|
2118
2128
|
availableModels,
|
|
2119
2129
|
output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
|
|
2120
2130
|
outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
|
|
2131
|
+
outputClaimPath: input.params.workflowOutputClaimPath,
|
|
2121
2132
|
...(agentContract ? { agentContract } : {}),
|
|
2122
2133
|
...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
|
|
2123
2134
|
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
@@ -2194,6 +2205,7 @@ async function resumeAsyncRun(input: {
|
|
|
2194
2205
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
2195
2206
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
2196
2207
|
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
2208
|
+
...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
|
|
2197
2209
|
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
2198
2210
|
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
2199
2211
|
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
@@ -2904,19 +2916,6 @@ function withAggregatedToolUsage(result: AgentToolResult<Details>): AgentToolRes
|
|
|
2904
2916
|
: result;
|
|
2905
2917
|
}
|
|
2906
2918
|
|
|
2907
|
-
function withForkThinkingNotes(
|
|
2908
|
-
result: AgentToolResult<Details>,
|
|
2909
|
-
downgrades: Map<number, string>,
|
|
2910
|
-
): AgentToolResult<Details> {
|
|
2911
|
-
if (downgrades.size === 0) return result;
|
|
2912
|
-
const children = [...downgrades.entries()]
|
|
2913
|
-
.sort(([a], [b]) => a - b)
|
|
2914
|
-
.map(([index, agent]) => `${agent} (child ${index})`)
|
|
2915
|
-
.join(", ");
|
|
2916
|
-
const note = `Note: fork context forced thinking off for ${children}. The forked transcript contained signed Anthropic thinking blocks that were sanitized, and Anthropic children cannot resume such a transcript with thinking enabled. Use context: "fresh" when an Anthropic child needs thinking.`;
|
|
2917
|
-
return { ...result, content: [...result.content, { type: "text", text: note }] };
|
|
2918
|
-
}
|
|
2919
|
-
|
|
2920
2919
|
function toExecutionErrorResult(params: SubagentParamsLike, error: unknown, contextSummary?: ContextSummary): AgentToolResult<Details> {
|
|
2921
2920
|
const message = error instanceof Error ? error.message : String(error);
|
|
2922
2921
|
return withResolvedContext(
|
|
@@ -2940,7 +2939,7 @@ function resolveStaticLaunchSummary(input: {
|
|
|
2940
2939
|
availableModels: ModelInfo[];
|
|
2941
2940
|
currentProvider?: string;
|
|
2942
2941
|
modelScope?: ModelScopeConfig;
|
|
2943
|
-
thinkingOverrideForTask:
|
|
2942
|
+
thinkingOverrideForTask: ThinkingOverrideForTask;
|
|
2944
2943
|
}): StaticLaunchSummary {
|
|
2945
2944
|
const agentConfig = input.agents.find((agent) => agent.name === input.agent);
|
|
2946
2945
|
const externalRunner = agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job";
|
|
@@ -2955,7 +2954,7 @@ function resolveStaticLaunchSummary(input: {
|
|
|
2955
2954
|
agentConfig?.modelProvider ?? input.currentProvider,
|
|
2956
2955
|
modelScopes.length === 0 ? {} : { scope: modelScopes },
|
|
2957
2956
|
);
|
|
2958
|
-
const thinkingOverride = externalRunner ? undefined : input.thinkingOverrideForTask(
|
|
2957
|
+
const thinkingOverride = externalRunner ? undefined : input.thinkingOverrideForTask();
|
|
2959
2958
|
const thinking = externalRunner ? undefined : resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
|
|
2960
2959
|
return {
|
|
2961
2960
|
agent: input.agent,
|
|
@@ -2971,7 +2970,7 @@ function collectStaticLaunchSummaries(input: {
|
|
|
2971
2970
|
availableModels: ModelInfo[];
|
|
2972
2971
|
currentProvider?: string;
|
|
2973
2972
|
modelScope?: ModelScopeConfig;
|
|
2974
|
-
thinkingOverrideForTask:
|
|
2973
|
+
thinkingOverrideForTask: ThinkingOverrideForTask;
|
|
2975
2974
|
dynamicFanoutMaxItems?: number;
|
|
2976
2975
|
}): StaticLaunchSummary[] {
|
|
2977
2976
|
const summary = (agent: string, index: number, explicitModel?: string) => resolveStaticLaunchSummary({
|
|
@@ -3169,6 +3168,7 @@ async function waitForWorkflowAsyncSingleResult(
|
|
|
3169
3168
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
3170
3169
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
3171
3170
|
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
3171
|
+
...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
|
|
3172
3172
|
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
3173
3173
|
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
3174
3174
|
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
@@ -3303,7 +3303,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3303
3303
|
fast: params.fast,
|
|
3304
3304
|
modelOverrideFromParent,
|
|
3305
3305
|
modelOrigin,
|
|
3306
|
-
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(
|
|
3306
|
+
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(),
|
|
3307
3307
|
thinkingCeiling: a.maxThinking,
|
|
3308
3308
|
maxSubagentDepth,
|
|
3309
3309
|
waitToolEnabled: deps.waitToolEnabled,
|
|
@@ -3343,8 +3343,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3343
3343
|
return null;
|
|
3344
3344
|
}
|
|
3345
3345
|
|
|
3346
|
-
function createSingleWorktreeSetup(
|
|
3347
|
-
enabled: boolean | undefined,
|
|
3346
|
+
async function createSingleWorktreeSetup(
|
|
3348
3347
|
cwd: string,
|
|
3349
3348
|
runId: string,
|
|
3350
3349
|
agent: string,
|
|
@@ -3356,12 +3355,13 @@ function createSingleWorktreeSetup(
|
|
|
3356
3355
|
branchPrefix: ExtensionConfig["worktreeBranchPrefix"],
|
|
3357
3356
|
label?: string,
|
|
3358
3357
|
task?: string,
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3358
|
+
onProgress?: (snapshot: WorktreeSetupProgress) => void,
|
|
3359
|
+
signal?: AbortSignal,
|
|
3360
|
+
deadlineAt?: number,
|
|
3361
|
+
): Promise<{ setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> }> {
|
|
3362
3362
|
try {
|
|
3363
3363
|
return {
|
|
3364
|
-
setup: createWorktrees(cwd, runId, 1, omitUndefinedProperties({
|
|
3364
|
+
setup: await createWorktrees(cwd, runId, 1, omitUndefinedProperties({
|
|
3365
3365
|
agents: [agent],
|
|
3366
3366
|
setupHook: setupHook
|
|
3367
3367
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
@@ -3372,7 +3372,9 @@ function createSingleWorktreeSetup(
|
|
|
3372
3372
|
branchPrefix,
|
|
3373
3373
|
labels: [label],
|
|
3374
3374
|
tasks: [task],
|
|
3375
|
-
|
|
3375
|
+
onProgress,
|
|
3376
|
+
signal,
|
|
3377
|
+
deadlineAt,
|
|
3376
3378
|
})),
|
|
3377
3379
|
};
|
|
3378
3380
|
} catch (error) {
|
|
@@ -3398,15 +3400,6 @@ function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string,
|
|
|
3398
3400
|
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
|
|
3399
3401
|
}
|
|
3400
3402
|
|
|
3401
|
-
function resolveWorkflowAggregateOutputPath(
|
|
3402
|
-
output: string | boolean | undefined,
|
|
3403
|
-
ctxCwd: string,
|
|
3404
|
-
workflowCwd: string,
|
|
3405
|
-
outputBaseDir: string,
|
|
3406
|
-
): string | undefined {
|
|
3407
|
-
return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
3403
|
function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
|
|
3411
3404
|
if (aggregateOutputPath) {
|
|
3412
3405
|
const parsed = path.parse(aggregateOutputPath);
|
|
@@ -3468,7 +3461,9 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3468
3461
|
key: string;
|
|
3469
3462
|
params: Record<string, unknown>;
|
|
3470
3463
|
}): { path?: string; inherited: boolean } {
|
|
3471
|
-
|
|
3464
|
+
const rawOutput = input.params.output;
|
|
3465
|
+
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3466
|
+
if (typeof input.params.resume === "string" && (!hasExplicitOutput || rawOutput === true || rawOutput === "true")) {
|
|
3472
3467
|
if (!input.state) return { path: undefined, inherited: false };
|
|
3473
3468
|
const index = input.params.index;
|
|
3474
3469
|
const target = resolveResumeTarget({
|
|
@@ -3477,15 +3472,16 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3477
3472
|
}, input.state);
|
|
3478
3473
|
return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
|
|
3479
3474
|
}
|
|
3480
|
-
const rawOutput = input.params.output;
|
|
3481
|
-
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3482
3475
|
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3476
|
+
let agentOutput: string | undefined;
|
|
3477
|
+
if (rawOutput === true || rawOutput === "true" || (!hasExplicitOutput && !input.aggregateOutputPath)) {
|
|
3478
|
+
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
3479
|
+
const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3480
|
+
const agent = typeof input.params.agent === "string"
|
|
3481
|
+
? resolveAgentName(input.params.agent, discoveredAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent
|
|
3482
|
+
: undefined;
|
|
3483
|
+
agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3484
|
+
}
|
|
3489
3485
|
const output = rawOutput === true || rawOutput === "true"
|
|
3490
3486
|
? agentOutput
|
|
3491
3487
|
: hasExplicitOutput
|
|
@@ -3574,7 +3570,7 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3574
3570
|
childParams = { ...input.childParams, output: input.outputOverride };
|
|
3575
3571
|
} else if (usesDefaultOutput && input.aggregateOutputPath !== undefined) {
|
|
3576
3572
|
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3577
|
-
} else if (input.childParams.resume === undefined) {
|
|
3573
|
+
} else if (input.childParams.resume === undefined || input.childParams.output !== undefined) {
|
|
3578
3574
|
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 });
|
|
3579
3575
|
if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
|
|
3580
3576
|
}
|
|
@@ -3588,7 +3584,7 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3588
3584
|
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired, outputClaimPath: input.outputClaimPath });
|
|
3589
3585
|
}
|
|
3590
3586
|
|
|
3591
|
-
function finalizeSingleWorktreeHandoff(input: {
|
|
3587
|
+
async function finalizeSingleWorktreeHandoff(input: {
|
|
3592
3588
|
worktreeSetup: WorktreeSetup;
|
|
3593
3589
|
artifactsDir: string;
|
|
3594
3590
|
runId: string;
|
|
@@ -3597,55 +3593,78 @@ function finalizeSingleWorktreeHandoff(input: {
|
|
|
3597
3593
|
result: SingleResult;
|
|
3598
3594
|
workflowKey?: string;
|
|
3599
3595
|
lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
|
|
3600
|
-
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
3601
|
-
|
|
3602
|
-
const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
|
|
3603
|
-
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3604
|
-
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3605
|
-
const handoff = {
|
|
3606
|
-
manifestPath,
|
|
3607
|
-
runId: input.runId,
|
|
3608
|
-
mode: "single" as const,
|
|
3609
|
-
source: "foreground" as const,
|
|
3610
|
-
cwd: input.cwd,
|
|
3611
|
-
stepIndex: 0,
|
|
3612
|
-
flatStartIndex: 0,
|
|
3613
|
-
setup: input.worktreeSetup,
|
|
3614
|
-
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,
|
|
3615
|
-
diffs,
|
|
3616
|
-
results: [{
|
|
3617
|
-
agent: input.result.agent,
|
|
3618
|
-
...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
|
|
3619
|
-
...(input.workflowKey ? { runId: input.runId } : {}),
|
|
3620
|
-
...(input.lane ? { lane: input.lane } : {}),
|
|
3621
|
-
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3622
|
-
exitCode: input.result.exitCode,
|
|
3623
|
-
interrupted: input.result.interrupted,
|
|
3624
|
-
detached: input.result.detached,
|
|
3625
|
-
state: input.result.stopped ? "stopped" : undefined,
|
|
3626
|
-
processSignal: input.result.processSignal,
|
|
3627
|
-
timedOut: input.result.timedOut,
|
|
3628
|
-
stopped: input.result.stopped,
|
|
3629
|
-
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
3630
|
-
})),
|
|
3631
|
-
summary: resultSummaryForIntercom(input.result),
|
|
3632
|
-
...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
|
|
3633
|
-
...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
|
|
3634
|
-
...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
|
|
3635
|
-
...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
|
|
3636
|
-
}],
|
|
3637
|
-
};
|
|
3596
|
+
}): Promise<{ suffix: string; reference?: NonNullable<Details["parallelHandoff"]> }> {
|
|
3597
|
+
let admitted = false;
|
|
3638
3598
|
try {
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3599
|
+
return await withWorktreeTransaction(() => {
|
|
3600
|
+
admitted = true;
|
|
3601
|
+
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
3602
|
+
const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
|
|
3603
|
+
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3604
|
+
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3605
|
+
const handoff = {
|
|
3606
|
+
manifestPath,
|
|
3607
|
+
runId: input.runId,
|
|
3608
|
+
mode: "single" as const,
|
|
3609
|
+
source: "foreground" as const,
|
|
3610
|
+
cwd: input.cwd,
|
|
3611
|
+
stepIndex: 0,
|
|
3612
|
+
flatStartIndex: 0,
|
|
3613
|
+
setup: input.worktreeSetup,
|
|
3614
|
+
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,
|
|
3615
|
+
diffs,
|
|
3616
|
+
results: [{
|
|
3617
|
+
agent: input.result.agent,
|
|
3618
|
+
...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
|
|
3619
|
+
...(input.workflowKey ? { runId: input.runId } : {}),
|
|
3620
|
+
...(input.lane ? { lane: input.lane } : {}),
|
|
3621
|
+
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3622
|
+
exitCode: input.result.exitCode,
|
|
3623
|
+
interrupted: input.result.interrupted,
|
|
3624
|
+
detached: input.result.detached,
|
|
3625
|
+
state: input.result.stopped ? "stopped" : undefined,
|
|
3626
|
+
processSignal: input.result.processSignal,
|
|
3627
|
+
timedOut: input.result.timedOut,
|
|
3628
|
+
stopped: input.result.stopped,
|
|
3629
|
+
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
3630
|
+
})),
|
|
3631
|
+
summary: resultSummaryForIntercom(input.result),
|
|
3632
|
+
...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
|
|
3633
|
+
...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
|
|
3634
|
+
...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
|
|
3635
|
+
...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
|
|
3636
|
+
}],
|
|
3637
|
+
};
|
|
3638
|
+
try {
|
|
3639
|
+
writeParallelHandoffGroup(handoff);
|
|
3640
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
|
|
3641
|
+
const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
3642
|
+
return {
|
|
3643
|
+
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
3644
|
+
reference,
|
|
3645
|
+
};
|
|
3646
|
+
} catch (error) {
|
|
3647
|
+
return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
|
|
3648
|
+
}
|
|
3649
|
+
});
|
|
3646
3650
|
} catch (error) {
|
|
3647
|
-
|
|
3648
|
-
|
|
3651
|
+
if (admitted) throw error;
|
|
3652
|
+
const reference = retainSingleWorktreeHandoff(input, error);
|
|
3653
|
+
return { suffix: `${formatParallelHandoffError(error)}\n\n${formatParallelHandoffReference(reference)}`, reference };
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
function retainSingleWorktreeHandoff(input: { worktreeSetup: WorktreeSetup; artifactsDir: string; runId: string; cwd: string }, error: unknown): NonNullable<Details["parallelHandoff"]> {
|
|
3658
|
+
const reason = `Worktree finalization retained; manual reconciliation required: ${error instanceof Error ? error.message : String(error)}`;
|
|
3659
|
+
return writeParallelHandoffGroup({
|
|
3660
|
+
manifestPath: parallelHandoffPath(input.artifactsDir, input.runId), runId: input.runId,
|
|
3661
|
+
mode: "single", source: "foreground", cwd: input.cwd, stepIndex: 0, flatStartIndex: 0,
|
|
3662
|
+
setup: input.worktreeSetup, diffs: [], results: [],
|
|
3663
|
+
cleanup: { state: "partial", pruned: false, errors: [reason], tasks: input.worktreeSetup.worktrees.map((worktree) => ({
|
|
3664
|
+
index: worktree.index, path: worktree.path, branch: worktree.branch, provider: worktree.provider, naming: worktree.naming,
|
|
3665
|
+
worktreeRemoved: false, branchRemoved: false, preserved: true, reason,
|
|
3666
|
+
})) },
|
|
3667
|
+
});
|
|
3649
3668
|
}
|
|
3650
3669
|
|
|
3651
3670
|
async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
@@ -3726,8 +3745,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3726
3745
|
|
|
3727
3746
|
const sourceCwd = effectiveCwd;
|
|
3728
3747
|
let pendingHandoff: Details["parallelHandoff"];
|
|
3729
|
-
const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
|
|
3730
|
-
params.worktree,
|
|
3748
|
+
const { setup: worktreeSetup, errorResult: worktreeSetupError } = params.worktree ? await createSingleWorktreeSetup(
|
|
3731
3749
|
sourceCwd,
|
|
3732
3750
|
runId,
|
|
3733
3751
|
params.agent!,
|
|
@@ -3739,8 +3757,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3739
3757
|
deps.config.worktreeBranchPrefix,
|
|
3740
3758
|
params.lane?.key ?? params.workflowKey,
|
|
3741
3759
|
task,
|
|
3742
|
-
(
|
|
3743
|
-
pendingHandoff =
|
|
3760
|
+
(progress) => {
|
|
3761
|
+
pendingHandoff = writeWorktreeSetupHandoff({
|
|
3744
3762
|
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3745
3763
|
runId,
|
|
3746
3764
|
mode: "single",
|
|
@@ -3748,13 +3766,42 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3748
3766
|
cwd: sourceCwd,
|
|
3749
3767
|
stepIndex: 0,
|
|
3750
3768
|
flatStartIndex: 0,
|
|
3751
|
-
|
|
3769
|
+
progress,
|
|
3752
3770
|
laneBindings: params.workflowKey || lane ? [{ index: 0, taskIndex: 0, ...(params.workflowKey ? { workflowKey: params.workflowKey, runId } : {}), ...(lane ? { lane } : {}) }] : undefined,
|
|
3753
3771
|
});
|
|
3754
3772
|
},
|
|
3755
|
-
|
|
3756
|
-
|
|
3773
|
+
signal,
|
|
3774
|
+
data.deadlineAt,
|
|
3775
|
+
) : {};
|
|
3776
|
+
if (worktreeSetupError) {
|
|
3777
|
+
if (pendingHandoff) {
|
|
3778
|
+
worktreeSetupError.details!.parallelHandoff = pendingHandoff;
|
|
3779
|
+
worktreeSetupError.content.push({ type: "text", text: formatParallelHandoffReference(pendingHandoff) });
|
|
3780
|
+
}
|
|
3781
|
+
return worktreeSetupError;
|
|
3782
|
+
}
|
|
3757
3783
|
const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
|
|
3784
|
+
const cleanupSingleWorktree = async () => {
|
|
3785
|
+
if (!worktreeSetup) return;
|
|
3786
|
+
let admitted = false;
|
|
3787
|
+
try { await withWorktreeTransaction(() => {
|
|
3788
|
+
if (data.deadlineAt !== undefined && Date.now() >= data.deadlineAt) throw new Error("Run deadline expired before unlaunched worktree cleanup");
|
|
3789
|
+
admitted = true;
|
|
3790
|
+
const cleanup = cleanupWorktrees(worktreeSetup);
|
|
3791
|
+
pendingHandoff = writeParallelHandoffGroup({
|
|
3792
|
+
manifestPath: parallelHandoffPath(artifactsDir, runId), runId, mode: "single", source: "foreground",
|
|
3793
|
+
cwd: sourceCwd, stepIndex: 0, flatStartIndex: 0, setup: worktreeSetup, diffs: [], results: [], cleanup,
|
|
3794
|
+
});
|
|
3795
|
+
}); }
|
|
3796
|
+
catch (error) {
|
|
3797
|
+
if (admitted) throw error;
|
|
3798
|
+
pendingHandoff = retainSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd }, error);
|
|
3799
|
+
}
|
|
3800
|
+
};
|
|
3801
|
+
if (worktreeSetup && (signal?.aborted || (data.deadlineAt !== undefined && Date.now() >= data.deadlineAt))) {
|
|
3802
|
+
await cleanupSingleWorktree();
|
|
3803
|
+
return { content: [{ type: "text", text: "Subagent setup canceled before child launch." }], isError: true, details: { mode: "single", results: [], parallelHandoff: pendingHandoff } };
|
|
3804
|
+
}
|
|
3758
3805
|
|
|
3759
3806
|
const authoredTask = task;
|
|
3760
3807
|
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
@@ -3764,8 +3811,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3764
3811
|
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
|
|
3765
3812
|
const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
|
|
3766
3813
|
if (validationError) {
|
|
3767
|
-
|
|
3768
|
-
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
3814
|
+
await cleanupSingleWorktree();
|
|
3815
|
+
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [], parallelHandoff: pendingHandoff } };
|
|
3769
3816
|
}
|
|
3770
3817
|
const structuredRuntime = params.outputSchema
|
|
3771
3818
|
? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
|
|
@@ -3791,7 +3838,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3791
3838
|
let childSessionControls: ForegroundChildSessionControls | undefined;
|
|
3792
3839
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3793
3840
|
if (foregroundControl) {
|
|
3794
|
-
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(
|
|
3841
|
+
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask());
|
|
3795
3842
|
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
3796
3843
|
index: 0,
|
|
3797
3844
|
agent: params.agent!,
|
|
@@ -3823,6 +3870,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3823
3870
|
}
|
|
3824
3871
|
},
|
|
3825
3872
|
}));
|
|
3873
|
+
// Capture the owned mailbox before a child can start and finish between polling ticks.
|
|
3874
|
+
if (deps.childRuntime?.fanoutChild) deps.activateSupervisorTransport?.();
|
|
3826
3875
|
}
|
|
3827
3876
|
|
|
3828
3877
|
const modelResponseAliases = deps.config.modelResponseAliases === undefined ? undefined : structuredClone(deps.config.modelResponseAliases);
|
|
@@ -3843,8 +3892,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3843
3892
|
const launched = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3844
3893
|
permissions: deps.config.permissions,
|
|
3845
3894
|
runtimeSnapshotHost: deps.pi,
|
|
3895
|
+
hostAvailableBuiltins: getHostBuiltinToolNames(deps.pi),
|
|
3846
3896
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3847
|
-
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3897
|
+
llmIntentArbiter: createTaskMutationArbiter({ model: ctx.model, modelRegistry: ctx.modelRegistry, sessionId: ctx.sessionManager.getSessionId() }),
|
|
3848
3898
|
childRuntime: deps.childRuntime,
|
|
3849
3899
|
onChildSession: (controls) => { childSessionControls = controls; },
|
|
3850
3900
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
@@ -3881,7 +3931,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3881
3931
|
fast: params.fast,
|
|
3882
3932
|
modelOverrideFromParent,
|
|
3883
3933
|
modelOrigin,
|
|
3884
|
-
thinkingOverride: thinkingOverrideForTask(
|
|
3934
|
+
thinkingOverride: thinkingOverrideForTask(),
|
|
3885
3935
|
thinkingCeiling: agentConfig.maxThinking,
|
|
3886
3936
|
extensionBindings: params.extensionBindings,
|
|
3887
3937
|
availableModels,
|
|
@@ -3898,14 +3948,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3898
3948
|
onDetachReady: (detach) => {
|
|
3899
3949
|
detachForeground = detach;
|
|
3900
3950
|
},
|
|
3901
|
-
onDetachedExit: (result) => {
|
|
3951
|
+
onDetachedExit: async (result) => {
|
|
3902
3952
|
if (resolveDetachedWorkflowChild) {
|
|
3903
3953
|
resolveDetachedWorkflowChild(result);
|
|
3904
3954
|
return;
|
|
3905
3955
|
}
|
|
3906
3956
|
try {
|
|
3907
3957
|
if (worktreeSetup) {
|
|
3908
|
-
finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result, workflowKey: params.workflowKey, lane });
|
|
3958
|
+
await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result, workflowKey: params.workflowKey, lane });
|
|
3909
3959
|
}
|
|
3910
3960
|
try {
|
|
3911
3961
|
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
|
|
@@ -3941,12 +3991,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3941
3991
|
toolTimeoutMs: params.toolTimeoutMs,
|
|
3942
3992
|
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3943
3993
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3994
|
+
usageBudget: data.usageBudget ?? data.inheritedUsageBudget,
|
|
3944
3995
|
capabilityCeiling: data.capabilityCeiling,
|
|
3945
3996
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3946
3997
|
}));
|
|
3947
3998
|
r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
|
|
3948
3999
|
} catch (error) {
|
|
3949
|
-
|
|
4000
|
+
await cleanupSingleWorktree();
|
|
3950
4001
|
throw error;
|
|
3951
4002
|
} finally {
|
|
3952
4003
|
// An attached runSync rejection still owns its child and structured runtime.
|
|
@@ -3961,11 +4012,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3961
4012
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
|
|
3962
4013
|
}
|
|
3963
4014
|
|
|
3964
|
-
let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff
|
|
4015
|
+
let worktreeHandoff: Awaited<ReturnType<typeof finalizeSingleWorktreeHandoff>> | undefined;
|
|
3965
4016
|
if (worktreeSetup) {
|
|
3966
4017
|
worktreeHandoff = r.detached
|
|
3967
4018
|
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3968
|
-
: finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r, workflowKey: params.workflowKey, lane });
|
|
4019
|
+
: await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r, workflowKey: params.workflowKey, lane });
|
|
3969
4020
|
}
|
|
3970
4021
|
|
|
3971
4022
|
if (r.progress) allProgress.push(r.progress);
|
|
@@ -4079,6 +4130,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4079
4130
|
}
|
|
4080
4131
|
|
|
4081
4132
|
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionName?: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
4133
|
+
const workflowOwnedUsageBudgets = new WeakMap<object, UsageBudgetConfig>();
|
|
4082
4134
|
|
|
4083
4135
|
/**
|
|
4084
4136
|
* Terminal-mission retention can remove a mission while its children still
|
|
@@ -4204,8 +4256,6 @@ function workflowChildResult(
|
|
|
4204
4256
|
}
|
|
4205
4257
|
const requestedContext = childParams.context === "fresh" || childParams.context === "fork" ? childParams.context : undefined;
|
|
4206
4258
|
const resolvedContext = result.details.context ?? (resolvedContexts.length === 1 ? resolvedContexts[0] : resolvedContexts.length > 1 ? "mixed" : undefined);
|
|
4207
|
-
const resumeSourceRunId = typeof childParams.resume === "string" && childParams.resume.trim() ? childParams.resume.trim() : undefined;
|
|
4208
|
-
const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
|
|
4209
4259
|
const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
|
|
4210
4260
|
?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
|
|
4211
4261
|
const outputPathMapping = typeof childParams.task === "string" ? outputPathMappingFromTask(childParams.task, outputReference) : undefined;
|
|
@@ -4238,7 +4288,7 @@ function workflowChildResult(
|
|
|
4238
4288
|
...(outputPathMapping ? { outputPathMapping } : {}),
|
|
4239
4289
|
...(externalAdapter ? { externalAdapter } : {}),
|
|
4240
4290
|
resumability,
|
|
4241
|
-
continuation: { runIds:
|
|
4291
|
+
continuation: { runIds: runId ? [runId] : [] },
|
|
4242
4292
|
artifactPaths: [...artifactPaths],
|
|
4243
4293
|
results: result.details.results,
|
|
4244
4294
|
};
|
|
@@ -4351,10 +4401,33 @@ function missingWorkflowReceiptResumeHint(reference: WorkflowReceiptResumeRefere
|
|
|
4351
4401
|
}
|
|
4352
4402
|
}
|
|
4353
4403
|
|
|
4354
|
-
function
|
|
4355
|
-
reference: WorkflowReceiptResumeReference,
|
|
4356
|
-
|
|
4357
|
-
|
|
4404
|
+
function resolveWorkflowResume(
|
|
4405
|
+
reference: WorkflowReceiptResumeReference | string,
|
|
4406
|
+
deps: ExecutorDeps,
|
|
4407
|
+
parentSessionFile: string | null,
|
|
4408
|
+
index?: number,
|
|
4409
|
+
): string | { runId: string; runIds: string[] } {
|
|
4410
|
+
const state = deps.state;
|
|
4411
|
+
if (typeof reference === "string") {
|
|
4412
|
+
const target = resolveRequestedResumeTarget({ id: reference.trim(), ...(index !== undefined ? { index } : {}) }, deps, parentSessionFile);
|
|
4413
|
+
// Live routing stays with action=resume and does not claim retained lineage.
|
|
4414
|
+
if (target.kind !== "revive") return reference.trim();
|
|
4415
|
+
const runId = target.runId;
|
|
4416
|
+
const status = target.source === "async" && target.asyncDir ? readStatus(target.asyncDir) : undefined;
|
|
4417
|
+
if (status?.runId === runId && status.parentWorkflowRunId && status.workflowKey) {
|
|
4418
|
+
// Only follow the validated child's recorded owner; never search other workflows.
|
|
4419
|
+
const receiptPath = workflowReceiptPath(DIRS.async, status.parentWorkflowRunId);
|
|
4420
|
+
try {
|
|
4421
|
+
fs.statSync(receiptPath);
|
|
4422
|
+
} catch (error) {
|
|
4423
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return { runId, runIds: [runId] };
|
|
4424
|
+
throw error;
|
|
4425
|
+
}
|
|
4426
|
+
const entry = readWorkflowReceipt(DIRS.async, status.parentWorkflowRunId).entries[status.workflowKey];
|
|
4427
|
+
if (entry?.latestRunId === runId) return { runId, runIds: entry.continuation.runIds };
|
|
4428
|
+
}
|
|
4429
|
+
return { runId, runIds: [runId] };
|
|
4430
|
+
}
|
|
4358
4431
|
try {
|
|
4359
4432
|
const entry = resolveWorkflowReceiptResumeEntry({
|
|
4360
4433
|
reference,
|
|
@@ -4364,9 +4437,7 @@ function resolveKeyedWorkflowResume(
|
|
|
4364
4437
|
if (target.kind !== "revive") throw new Error(`Workflow receipt child '${reference.key}' latest run '${runId}' is still running.`);
|
|
4365
4438
|
},
|
|
4366
4439
|
});
|
|
4367
|
-
|
|
4368
|
-
if (!runId) throw new Error(`Workflow receipt child '${reference.key}' has no retained run id.`);
|
|
4369
|
-
return { runId, runIds: entry.continuation.runIds };
|
|
4440
|
+
return { runId: entry.latestRunId, runIds: entry.continuation.runIds };
|
|
4370
4441
|
} catch (error) {
|
|
4371
4442
|
const workflowRunId = reference.workflowRunId.trim();
|
|
4372
4443
|
if (isMissingWorkflowReceiptDiagnostic(error, workflowRunId)) {
|
|
@@ -4469,6 +4540,35 @@ export async function steerWorkflowChildByKey(input: {
|
|
|
4469
4540
|
}
|
|
4470
4541
|
}
|
|
4471
4542
|
|
|
4543
|
+
export async function preflightWorkflowWorktrees(input: {
|
|
4544
|
+
workflowDefaults: SubagentParamsLike;
|
|
4545
|
+
defaultWorktree?: boolean;
|
|
4546
|
+
calls: Array<{ key: string; params: Record<string, unknown> }>;
|
|
4547
|
+
ctxCwd: string;
|
|
4548
|
+
signal: AbortSignal;
|
|
4549
|
+
deadlineAt?: number;
|
|
4550
|
+
}): Promise<void> {
|
|
4551
|
+
const sources = new Map<string, string[]>();
|
|
4552
|
+
for (const { key, params } of input.calls) {
|
|
4553
|
+
// The workflow validates retained IDs/receipt references before admission; resolution follows it.
|
|
4554
|
+
if (params.resume !== undefined) continue;
|
|
4555
|
+
const effective = prepareWorkflowLaunchParams(input.workflowDefaults, params, "preflight", key);
|
|
4556
|
+
if ((effective.worktree ?? input.defaultWorktree) !== true) continue;
|
|
4557
|
+
// Match recursive execute's resolution, including relative default/child cwd.
|
|
4558
|
+
const cwd = resolveRequestedCwd(input.ctxCwd, effective.cwd);
|
|
4559
|
+
const keys = sources.get(cwd) ?? [];
|
|
4560
|
+
keys.push(key);
|
|
4561
|
+
sources.set(cwd, keys);
|
|
4562
|
+
}
|
|
4563
|
+
for (const [cwd, keys] of sources) {
|
|
4564
|
+
try { await preflightWorktreeSource(cwd, { signal: input.signal, deadlineAt: input.deadlineAt }); }
|
|
4565
|
+
catch (error) {
|
|
4566
|
+
throw new Error(`Worktree admission failed for ${keys.map((key) => `'${key}'`).join(", ")} at ${cwd}: ${error instanceof Error ? error.message : String(error)} Select the correct cwd or arrange an operator-approved commit/stash.`, { cause: error });
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
input.signal.throwIfAborted();
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4472
4572
|
export function prepareWorkflowLaunchParams(
|
|
4473
4573
|
workflowDefaults: SubagentParamsLike,
|
|
4474
4574
|
childParams: Record<string, unknown>,
|
|
@@ -4505,7 +4605,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4505
4605
|
const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
|
|
4506
4606
|
if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
|
|
4507
4607
|
const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
|
|
4508
|
-
if (agentContract !== undefined && !
|
|
4608
|
+
if (agentContract !== undefined && !isAgentContract(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
|
|
4509
4609
|
const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
|
|
4510
4610
|
const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
|
|
4511
4611
|
if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
|
|
@@ -4519,6 +4619,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4519
4619
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4520
4620
|
workflowParentRunId: parentWorkflowRunId,
|
|
4521
4621
|
workflowKey,
|
|
4622
|
+
...(options.outputClaimPath ? { workflowOutputClaimPath: options.outputClaimPath } : {}),
|
|
4522
4623
|
...(lane ? { lane } : {}),
|
|
4523
4624
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4524
4625
|
...(baseRef !== undefined ? { baseRef: baseRef as string } : {}),
|
|
@@ -4681,14 +4782,14 @@ function applyWorkflowLaneTrace(graph: WorkflowGraphSnapshot, trace: WorkflowScr
|
|
|
4681
4782
|
else delete graph.currentNodeId;
|
|
4682
4783
|
}
|
|
4683
4784
|
|
|
4684
|
-
function
|
|
4785
|
+
function workflowProgressUpdate(
|
|
4685
4786
|
runId: string,
|
|
4686
4787
|
chatProgress: WorkflowChatProgressProjection,
|
|
4687
4788
|
workflow: NonNullable<Details["workflow"]>,
|
|
4688
4789
|
workflowChildren?: Details["workflowChildren"],
|
|
4689
|
-
preflight?: import("../../shared/types.ts").
|
|
4690
|
-
): AgentToolResult<Details>
|
|
4691
|
-
|
|
4790
|
+
preflight?: import("../../shared/types.ts").WorkflowPreflight,
|
|
4791
|
+
): AgentToolResult<Details> {
|
|
4792
|
+
// chatProgress controls the TUI projection, not transport-level progress.
|
|
4692
4793
|
return {
|
|
4693
4794
|
content: [{ type: "text", text: "Workflow running." }],
|
|
4694
4795
|
details: { mode: "workflow", runId, results: [], workflow, ...(preflight ? { preflight } : {}), ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
|
|
@@ -4764,6 +4865,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4764
4865
|
signal: AbortSignal,
|
|
4765
4866
|
ctx: ExtensionContext,
|
|
4766
4867
|
) => Promise<AgentToolResult<Details>>;
|
|
4868
|
+
/** Scheduled state visible to the current runtime supervisor owner only. */
|
|
4869
|
+
getCurrentSupervisorOwnerStates: () => Iterable<SubagentState>;
|
|
4767
4870
|
} {
|
|
4768
4871
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4769
4872
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
@@ -4772,7 +4875,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4772
4875
|
const workflowResourcePermits = new WeakMap<object, WorkflowResourcePermit>();
|
|
4773
4876
|
const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
|
|
4774
4877
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4775
|
-
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4878
|
+
const scheduledOwnerExecutors = new Map<string | null, Map<string, { state: SubagentState; executor: ReturnType<typeof createSubagentExecutor> }>>();
|
|
4776
4879
|
const execute = async (
|
|
4777
4880
|
_id: string,
|
|
4778
4881
|
params: SubagentParamsLike,
|
|
@@ -4783,6 +4886,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4783
4886
|
parentModelOverride?: ParentModel | null,
|
|
4784
4887
|
): Promise<AgentToolResult<Details>> => {
|
|
4785
4888
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4889
|
+
const inheritedUsageBudget = workflowOwnedUsageBudgets.get(params);
|
|
4786
4890
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4787
4891
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4788
4892
|
const delegatedExecution = delegatedExecutions.has(params);
|
|
@@ -4800,7 +4904,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4800
4904
|
let requestParams = normalizedGate.params;
|
|
4801
4905
|
const capacityOverrideError = validateWorkflowCapacityOverrides(requestParams);
|
|
4802
4906
|
if (capacityOverrideError) return buildRequestedModeError(requestParams, capacityOverrideError);
|
|
4803
|
-
let workflowPreflight: import("../../shared/types.ts").
|
|
4907
|
+
let workflowPreflight: import("../../shared/types.ts").WorkflowPreflight | undefined;
|
|
4804
4908
|
try {
|
|
4805
4909
|
if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
|
|
4806
4910
|
throw new Error("preflight requires workflowScript or workflowScriptPath.");
|
|
@@ -4810,7 +4914,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4810
4914
|
} catch (error) {
|
|
4811
4915
|
const message = error instanceof Error ? error.message : String(error);
|
|
4812
4916
|
if (requestParams.action?.trim() === "validate") {
|
|
4813
|
-
const validation = validateWorkflowScript(requestParams.workflowScript ?? ""
|
|
4917
|
+
const validation = validateWorkflowScript(requestParams.workflowScript ?? "", {
|
|
4918
|
+
maxSubagentSpawnsPerRun: requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
|
|
4919
|
+
});
|
|
4814
4920
|
const invalidValidation = { ...validation, ok: false, errors: [...validation.errors, { message }] };
|
|
4815
4921
|
return {
|
|
4816
4922
|
content: [{ type: "text", text: JSON.stringify(invalidValidation) }],
|
|
@@ -4821,7 +4927,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4821
4927
|
return buildRequestedModeError(requestParams, message);
|
|
4822
4928
|
}
|
|
4823
4929
|
if (requestParams.action?.trim() === "validate") {
|
|
4824
|
-
const validation = validateWorkflowScript(requestParams.workflowScript ?? ""
|
|
4930
|
+
const validation = validateWorkflowScript(requestParams.workflowScript ?? "", {
|
|
4931
|
+
maxSubagentSpawnsPerRun: requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
|
|
4932
|
+
});
|
|
4825
4933
|
const payload = workflowPreflight ? { ...validation, preflight: workflowPreflight } : validation;
|
|
4826
4934
|
return {
|
|
4827
4935
|
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
@@ -4831,7 +4939,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4831
4939
|
}
|
|
4832
4940
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4833
4941
|
if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
|
|
4834
|
-
let workflowResource: { permit: WorkflowResourcePermit; provenance:
|
|
4942
|
+
let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenance; authority: WorkflowResourceAuthority } | undefined;
|
|
4835
4943
|
if (workflowResourcePermit) {
|
|
4836
4944
|
if (typeof requestParams.workflowScript !== "string") return buildRequestedModeError(requestParams, "Resolved workflow resource is missing its workflow script.");
|
|
4837
4945
|
const consumed = consumeWorkflowResourcePermit(workflowResourcePermit, requestParams.workflowScript);
|
|
@@ -4839,6 +4947,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4839
4947
|
workflowResource = { permit: workflowResourcePermit, ...consumed };
|
|
4840
4948
|
}
|
|
4841
4949
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4950
|
+
const spawnBudgetValidation = validateWorkflowScript(requestParams.workflowScript, {
|
|
4951
|
+
maxSubagentSpawnsPerRun: requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
|
|
4952
|
+
});
|
|
4953
|
+
const spawnBudgetErrors = spawnBudgetValidation.errors.filter((error) => error.kind === "spawn-budget");
|
|
4954
|
+
if (spawnBudgetErrors.length > 0) {
|
|
4955
|
+
return buildRequestedModeError(requestParams, `workflowScript validation failed before child launch; no children launched. ${spawnBudgetErrors.map((error) => error.message).join(" ")}`);
|
|
4956
|
+
}
|
|
4957
|
+
for (const warning of spawnBudgetValidation.warnings ?? []) console.warn(`[pi-subagents] ${warning.message}`);
|
|
4958
|
+
const acceptanceErrors = validateAcceptanceInput(requestParams.acceptance);
|
|
4959
|
+
if (acceptanceErrors.length > 0) return buildRequestedModeError(requestParams, acceptanceErrors.join(" "));
|
|
4842
4960
|
const foregroundWorkflowRunId = encodeIndexSegment(_id);
|
|
4843
4961
|
if (delegatedWorkflowPermit) {
|
|
4844
4962
|
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, foregroundWorkflowRunId);
|
|
@@ -5016,18 +5134,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5016
5134
|
onSuccess: (_filePath, payload) => { indexedState = (payload as { state: AsyncStatus["state"] }).state; },
|
|
5017
5135
|
onError: (error, filePath) => console.error(`Failed to update async workflow index '${filePath}':`, error),
|
|
5018
5136
|
});
|
|
5019
|
-
const queueActiveRunIndex = (): void => {
|
|
5020
|
-
const state =
|
|
5137
|
+
const queueActiveRunIndex = (snapshot: AsyncStatus = status): void => {
|
|
5138
|
+
const state = snapshot.state;
|
|
5021
5139
|
if (indexedState === state && indexPersistence.pendingCount() === 0) return;
|
|
5022
|
-
indexPersistence.write(asyncDir, { state, toolCallId:
|
|
5140
|
+
indexPersistence.write(asyncDir, { state, toolCallId: snapshot.toolCallId }, (_filePath, payload) => {
|
|
5023
5141
|
const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
|
|
5024
5142
|
updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
|
|
5025
5143
|
});
|
|
5026
5144
|
};
|
|
5145
|
+
let pendingResultPublication: Promise<boolean> | undefined;
|
|
5146
|
+
let settleResultPublication: ((published: boolean) => void) | undefined;
|
|
5147
|
+
const reportResultWriteFailure = (error: unknown): void => {
|
|
5148
|
+
const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
5149
|
+
console.error(message, error);
|
|
5150
|
+
appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
|
|
5151
|
+
};
|
|
5027
5152
|
const runPersistence = createCapacityResilientJsonWriter({
|
|
5028
5153
|
keepAlive: true,
|
|
5029
|
-
onSuccess: (filePath) => {
|
|
5030
|
-
|
|
5154
|
+
onSuccess: (filePath, payload) => {
|
|
5155
|
+
if (filePath === statusPath) queueActiveRunIndex(payload as AsyncStatus);
|
|
5156
|
+
if (filePath === resultPath) settleResultPublication?.(true);
|
|
5157
|
+
},
|
|
5158
|
+
onError: (error, filePath) => {
|
|
5159
|
+
if (filePath === resultPath) { reportResultWriteFailure(error); settleResultPublication?.(false); }
|
|
5160
|
+
else console.error(`Failed to persist async workflow state '${filePath}':`, error);
|
|
5161
|
+
},
|
|
5031
5162
|
write: (filePath, payload) => filePath === resultPath
|
|
5032
5163
|
? writeAsyncResultFile(filePath, payload as Record<string, unknown>)
|
|
5033
5164
|
: writeAtomicJson(filePath, payload),
|
|
@@ -5055,7 +5186,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5055
5186
|
queueActiveRunIndex();
|
|
5056
5187
|
} else if (options.tolerateStatusWriteFailure) {
|
|
5057
5188
|
try {
|
|
5058
|
-
runPersistence.write(statusPath, status);
|
|
5189
|
+
runPersistence.write(statusPath, { ...status });
|
|
5059
5190
|
statusPersistenceDegraded = false;
|
|
5060
5191
|
} catch (error) {
|
|
5061
5192
|
const message = `Failed to persist async workflow state ${statusPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -5070,7 +5201,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5070
5201
|
}
|
|
5071
5202
|
}
|
|
5072
5203
|
} else {
|
|
5073
|
-
runPersistence.write(statusPath, status);
|
|
5204
|
+
runPersistence.write(statusPath, { ...status });
|
|
5074
5205
|
}
|
|
5075
5206
|
if (liveJob) {
|
|
5076
5207
|
liveJob.status = status.state;
|
|
@@ -5098,12 +5229,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5098
5229
|
};
|
|
5099
5230
|
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
5100
5231
|
try {
|
|
5232
|
+
let resultPublished = false;
|
|
5233
|
+
settleResultPublication = (published) => { resultPublished = published; };
|
|
5234
|
+
pendingResultPublication = undefined;
|
|
5101
5235
|
runPersistence.write(resultPath, payload);
|
|
5236
|
+
// Only capacity deferral yields; ordinary publication remains synchronous.
|
|
5237
|
+
if (!resultPublished) pendingResultPublication = new Promise<boolean>((resolve) => { settleResultPublication = resolve; });
|
|
5102
5238
|
return true;
|
|
5103
5239
|
} catch (error) {
|
|
5104
|
-
|
|
5105
|
-
console.error(message, error);
|
|
5106
|
-
appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
|
|
5240
|
+
reportResultWriteFailure(error);
|
|
5107
5241
|
return false;
|
|
5108
5242
|
}
|
|
5109
5243
|
};
|
|
@@ -5143,7 +5277,148 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5143
5277
|
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
5144
5278
|
}
|
|
5145
5279
|
appendWorkflowEvent({ type: "subagent.workflow.started" });
|
|
5280
|
+
// The runner does not execute workflow scripts: this active owner must consume its own
|
|
5281
|
+
// durable steer inbox, deliver through in-process child controls, and persist receipts.
|
|
5282
|
+
const emitWorkflowSteerEvent = (type: string, requestId: string, index?: number, extra: Record<string, unknown> = {}): void => {
|
|
5283
|
+
appendWorkflowEvent({ type, requestId, ...(index !== undefined ? { index } : {}), ...extra });
|
|
5284
|
+
};
|
|
5285
|
+
const recordWorkflowSteerTargets = (request: SteerRequest, targets: Array<{ index: number; state: SteeringTargetState; reason?: string }>): void => {
|
|
5286
|
+
status.steering ??= createSteeringStatus();
|
|
5287
|
+
recordSteeringRequest(steeringStatus(status), {
|
|
5288
|
+
id: request.id,
|
|
5289
|
+
requestedAt: request.ts,
|
|
5290
|
+
...(request.source ? { source: request.source } : {}),
|
|
5291
|
+
message: request.message,
|
|
5292
|
+
targets,
|
|
5293
|
+
});
|
|
5294
|
+
};
|
|
5295
|
+
const explicitWorkflowSteerIndexes = (request: SteerRequest): number[] | undefined => request.targetIndex !== undefined ? [request.targetIndex] : request.targetIndexes;
|
|
5296
|
+
const failWorkflowSteer = (request: SteerRequest, indexes: number[], reason: string): void => {
|
|
5297
|
+
const targets = indexes.map((index) => ({ index, state: "failed" as const, reason }));
|
|
5298
|
+
recordWorkflowSteerTargets(request, targets);
|
|
5299
|
+
emitWorkflowSteerEvent("subagent.steer.requested", request.id, undefined, { targets });
|
|
5300
|
+
for (const index of indexes) emitWorkflowSteerEvent("subagent.steer.failed", request.id, index, { reason });
|
|
5301
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5302
|
+
};
|
|
5303
|
+
const pendingWorkflowSteers = new Set<(state: "delivered" | "queued" | "failed", reason?: string) => void>();
|
|
5304
|
+
const deliverWorkflowSteerRequest = (request: SteerRequest): void => {
|
|
5305
|
+
let indexes = explicitWorkflowSteerIndexes(request);
|
|
5306
|
+
if (status.state !== "running") {
|
|
5307
|
+
failWorkflowSteer(request, indexes ?? [0], `run became ${status.state} before steering request was consumed`);
|
|
5308
|
+
return;
|
|
5309
|
+
}
|
|
5310
|
+
const liveControls = [...deps.state.foregroundControls.values()].filter((candidate) => candidate.parentWorkflowRunId === workflowRunId && (candidate.activeChildren?.size ?? 0) > 0);
|
|
5311
|
+
if (indexes === undefined) {
|
|
5312
|
+
if (liveControls.length !== 1) {
|
|
5313
|
+
failWorkflowSteer(request, [0], liveControls.length > 1
|
|
5314
|
+
? `workflow has ${liveControls.length} live children; steer a child run id instead`
|
|
5315
|
+
: "workflow child is not live in this process");
|
|
5316
|
+
return;
|
|
5317
|
+
}
|
|
5318
|
+
const index = status.steps?.findIndex((step) => !!step.workflowKey && step.workflowKey === liveControls[0]!.workflowKey) ?? -1;
|
|
5319
|
+
if (index < 0) {
|
|
5320
|
+
failWorkflowSteer(request, [0], "live workflow child has no projected step");
|
|
5321
|
+
return;
|
|
5322
|
+
}
|
|
5323
|
+
indexes = [index];
|
|
5324
|
+
}
|
|
5325
|
+
// Normalize and record the whole set once: recordSteeringRequest is idempotent by id.
|
|
5326
|
+
// Explicit indexes resolve only by their projected key, never by a live sibling.
|
|
5327
|
+
const resolved = indexes.map((index) => {
|
|
5328
|
+
const step = status.steps?.[index];
|
|
5329
|
+
const control = step?.workflowKey ? liveControls.find((candidate) => candidate.workflowKey === step.workflowKey) : undefined;
|
|
5330
|
+
return { index, control, reason: !step ? "child index out of range" : !control ? "workflow child is not live in this process" : undefined };
|
|
5331
|
+
});
|
|
5332
|
+
const targets = resolved.map(({ index, reason }) => ({ index, state: reason ? "failed" as const : "routed" as const, ...(reason ? { reason } : {}) }));
|
|
5333
|
+
recordWorkflowSteerTargets(request, targets);
|
|
5334
|
+
emitWorkflowSteerEvent("subagent.steer.requested", request.id, undefined, { targets });
|
|
5335
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5336
|
+
for (const { index, control, reason } of resolved) {
|
|
5337
|
+
if (!control) {
|
|
5338
|
+
emitWorkflowSteerEvent("subagent.steer.failed", request.id, index, { reason });
|
|
5339
|
+
continue;
|
|
5340
|
+
}
|
|
5341
|
+
emitWorkflowSteerEvent("subagent.steer.routed", request.id, index);
|
|
5342
|
+
const applyWorkflowSteerDelivery = (state: "delivered" | "queued" | "failed", reason?: string): void => {
|
|
5343
|
+
// Shutdown settles unresolved receipts synchronously; late SDK callbacks cannot rewrite them.
|
|
5344
|
+
if (!pendingWorkflowSteers.has(applyWorkflowSteerDelivery)) return;
|
|
5345
|
+
// Queuing is not confirmation of delivery: retain ownership until terminal settlement.
|
|
5346
|
+
if (state !== "queued") pendingWorkflowSteers.delete(applyWorkflowSteerDelivery);
|
|
5347
|
+
const now = Date.now();
|
|
5348
|
+
const steering = steeringStatus(status);
|
|
5349
|
+
// This callback owns a pending target, even after bounded display history
|
|
5350
|
+
// evicts its request. Account independently; queued still represents one pending target.
|
|
5351
|
+
if (state !== "queued") steering.pending = Math.max(0, steering.pending - 1);
|
|
5352
|
+
if (state === "delivered") {
|
|
5353
|
+
steering.delivered++;
|
|
5354
|
+
steering.lastDeliveredAt = now;
|
|
5355
|
+
} else if (state === "failed") steering.failed++;
|
|
5356
|
+
const receipt = steering.recent.find((candidate) => candidate.id === request.id)?.targets.find((target) => target.index === index);
|
|
5357
|
+
if (receipt) {
|
|
5358
|
+
receipt.state = state;
|
|
5359
|
+
if (state === "delivered") receipt.deliveredAt = now;
|
|
5360
|
+
if (state === "failed") receipt.failedAt = now;
|
|
5361
|
+
if (reason) receipt.reason = reason;
|
|
5362
|
+
}
|
|
5363
|
+
if (state === "failed") {
|
|
5364
|
+
const failedStep = status.steps?.[index];
|
|
5365
|
+
if (failedStep) failedStep.activityState = "needs_attention";
|
|
5366
|
+
status.activityState = "needs_attention";
|
|
5367
|
+
}
|
|
5368
|
+
emitWorkflowSteerEvent(`subagent.steer.${state}`, request.id, index, state === "failed" ? { reason } : { deliveryStatus: state, message: request.message });
|
|
5369
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5370
|
+
};
|
|
5371
|
+
pendingWorkflowSteers.add(applyWorkflowSteerDelivery);
|
|
5372
|
+
void steerWorkflowForegroundTarget({
|
|
5373
|
+
target: { control, workflowRunId, sourceRunId: workflowRunId },
|
|
5374
|
+
message: request.message,
|
|
5375
|
+
...(request.mode ? { mode: request.mode } : {}),
|
|
5376
|
+
}).then(
|
|
5377
|
+
(result) => {
|
|
5378
|
+
const target = result.details.steering?.targets[0];
|
|
5379
|
+
applyWorkflowSteerDelivery(target?.state === "delivered" ? "delivered" : target?.state === "queued" ? "queued" : "failed", target?.reason ?? (target ? undefined : "steering produced no target receipt"));
|
|
5380
|
+
},
|
|
5381
|
+
(error) => applyWorkflowSteerDelivery("failed", error instanceof Error ? error.message : String(error)),
|
|
5382
|
+
);
|
|
5383
|
+
}
|
|
5384
|
+
};
|
|
5385
|
+
let disposeWorkflowSteerInbox: (() => void) | undefined;
|
|
5386
|
+
try {
|
|
5387
|
+
disposeWorkflowSteerInbox = watchAsyncControlInbox(asyncDir, {
|
|
5388
|
+
onSteer: deliverWorkflowSteerRequest,
|
|
5389
|
+
onError: (error, phase, request) => {
|
|
5390
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
5391
|
+
appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase, error: reason });
|
|
5392
|
+
if (request) failWorkflowSteer(request, explicitWorkflowSteerIndexes(request) ?? [0], `steer consumer failed: ${reason}`);
|
|
5393
|
+
},
|
|
5394
|
+
});
|
|
5395
|
+
} catch (error) {
|
|
5396
|
+
// Steering is an optional control surface, so a watcher that cannot install must not fail the run.
|
|
5397
|
+
appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", error: error instanceof Error ? error.message : String(error) });
|
|
5398
|
+
}
|
|
5399
|
+
deps.activateSupervisorTransport?.();
|
|
5146
5400
|
const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
|
|
5401
|
+
let workflowSteerInboxClosed = false;
|
|
5402
|
+
const settleWorkflowSteerInbox = (outcome = status.state): void => {
|
|
5403
|
+
if (workflowSteerInboxClosed) return;
|
|
5404
|
+
workflowSteerInboxClosed = true;
|
|
5405
|
+
disposeWorkflowSteerInbox?.();
|
|
5406
|
+
// Consumed deliveries are not files anymore. Fail unresolved SDK operations without awaiting them.
|
|
5407
|
+
for (const settle of pendingWorkflowSteers) settle("failed", `run became ${outcome} before steering delivery settled; delivery unconfirmed`);
|
|
5408
|
+
try {
|
|
5409
|
+
closeSteerInbox(asyncDir, outcome);
|
|
5410
|
+
// Separately drain requests that never reached the consumer.
|
|
5411
|
+
const undelivered = consumeSteerRequests(asyncDir, undefined, (error) => {
|
|
5412
|
+
appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase: "drain", error: error instanceof Error ? error.message : String(error) });
|
|
5413
|
+
});
|
|
5414
|
+
for (const request of undelivered) failWorkflowSteer(request, explicitWorkflowSteerIndexes(request) ?? [0], `run became ${outcome} before steering request was consumed`);
|
|
5415
|
+
} catch (error) {
|
|
5416
|
+
appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase: "close", error: error instanceof Error ? error.message : String(error) });
|
|
5417
|
+
}
|
|
5418
|
+
// Commit receipts while run status is still nonterminal. Result/index publication
|
|
5419
|
+
// can fail next; teardown must not publish the subsequently computed terminal status.
|
|
5420
|
+
persist();
|
|
5421
|
+
};
|
|
5147
5422
|
void Promise.resolve().then(async () => {
|
|
5148
5423
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5149
5424
|
const workflowResults: SingleResult[] = [];
|
|
@@ -5151,7 +5426,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5151
5426
|
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, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = workflowRequest;
|
|
5152
5427
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5153
5428
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5154
|
-
const workflowAggregateOutputPath =
|
|
5429
|
+
const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
|
|
5155
5430
|
const claimedOutputPaths = new Map<string, string>();
|
|
5156
5431
|
const childOutputOverrides = new Map<string, string>();
|
|
5157
5432
|
const childOutputClaimPaths = new Map<string, string>();
|
|
@@ -5273,6 +5548,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5273
5548
|
try {
|
|
5274
5549
|
const workflow = await runWorkflowScript({
|
|
5275
5550
|
script: workflowScript,
|
|
5551
|
+
workflowRunId,
|
|
5276
5552
|
globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
|
|
5277
5553
|
timeoutMs: timeout,
|
|
5278
5554
|
signal: controller.signal,
|
|
@@ -5288,6 +5564,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5288
5564
|
},
|
|
5289
5565
|
...(workflowState ? { state: workflowState } : {}),
|
|
5290
5566
|
onTrace: updateTrace,
|
|
5567
|
+
onChildSettled: (notification) => {
|
|
5568
|
+
appendWorkflowEvent({
|
|
5569
|
+
type: "subagent.workflow.child_settled",
|
|
5570
|
+
childKey: notification.childKey,
|
|
5571
|
+
...(notification.childRunId ? { childRunId: notification.childRunId } : {}),
|
|
5572
|
+
outcome: notification.outcome,
|
|
5573
|
+
workflowRunning: notification.workflowRunning,
|
|
5574
|
+
});
|
|
5575
|
+
try {
|
|
5576
|
+
deps.pi.sendMessage(
|
|
5577
|
+
{
|
|
5578
|
+
customType: "subagent-incremental-child-notify",
|
|
5579
|
+
content: formatIncrementalChildCompletion(notification),
|
|
5580
|
+
display: notification.outcome !== "completed",
|
|
5581
|
+
},
|
|
5582
|
+
{ triggerTurn: scheduledCompletionTriggersTurn(requestParams.scheduleOrigin, notification.outcome) },
|
|
5583
|
+
);
|
|
5584
|
+
} catch (sendError) {
|
|
5585
|
+
console.error(`Failed to send incremental child completion notification for '${notification.childKey}':`, sendError);
|
|
5586
|
+
}
|
|
5587
|
+
deps.refreshResultDelivery?.();
|
|
5588
|
+
},
|
|
5291
5589
|
onLanePlan: (lanes) => {
|
|
5292
5590
|
status.workflowGraph = buildWorkflowLaneGraph(workflowRunId, lanes, status.workflowGraph);
|
|
5293
5591
|
applyWorkflowLaneTrace(status.workflowGraph, status.workflow?.trace ?? []);
|
|
@@ -5300,7 +5598,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5300
5598
|
persist({ tolerateStatusWriteFailure: true });
|
|
5301
5599
|
} });
|
|
5302
5600
|
},
|
|
5303
|
-
admit: (calls) => {
|
|
5601
|
+
admit: async (calls, admissionSignal) => {
|
|
5602
|
+
await preflightWorkflowWorktrees({ workflowDefaults: workflowChildDefaults, defaultWorktree: deps.config.worktree, calls, ctxCwd: parentCwd, signal: admissionSignal, deadlineAt: workflowDeadlineAt });
|
|
5304
5603
|
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 });
|
|
5305
5604
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5306
5605
|
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
@@ -5336,6 +5635,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5336
5635
|
deps.asyncByDefault,
|
|
5337
5636
|
);
|
|
5338
5637
|
preparedChildParams = childRequest;
|
|
5638
|
+
if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
|
|
5339
5639
|
workflowLaunchObservers.set(childRequest, (launch) => {
|
|
5340
5640
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
5341
5641
|
if (step) {
|
|
@@ -5406,7 +5706,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5406
5706
|
return child;
|
|
5407
5707
|
},
|
|
5408
5708
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
5409
|
-
resolveResume: (reference) =>
|
|
5709
|
+
resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
|
|
5410
5710
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5411
5711
|
});
|
|
5412
5712
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
@@ -5417,16 +5717,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5417
5717
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
5418
5718
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
5419
5719
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
5720
|
+
settleWorkflowSteerInbox("complete");
|
|
5420
5721
|
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
|
|
5421
5722
|
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
5422
5723
|
const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
|
|
5724
|
+
delete status.workflowReceiptPath;
|
|
5423
5725
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
5424
5726
|
try {
|
|
5425
5727
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
5728
|
+
status.workflowReceiptPath = workflowReceipt.path;
|
|
5426
5729
|
} catch (receiptError) {
|
|
5427
5730
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
5428
5731
|
}
|
|
5429
5732
|
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;
|
|
5733
|
+
if (pendingResultPublication && !await pendingResultPublication) return;
|
|
5430
5734
|
persist();
|
|
5431
5735
|
deps.refreshResultDelivery?.();
|
|
5432
5736
|
persistClosed = true;
|
|
@@ -5450,10 +5754,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5450
5754
|
}
|
|
5451
5755
|
}
|
|
5452
5756
|
const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
|
|
5757
|
+
settleWorkflowSteerInbox(state);
|
|
5453
5758
|
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
|
|
5454
5759
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
|
|
5455
5760
|
const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
|
|
5456
|
-
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, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
|
|
5761
|
+
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached || status.activityState === "needs_attention" ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
|
|
5457
5762
|
if (pauseForDetached) {
|
|
5458
5763
|
const promoted = promotePausedWorkflowIfSettled(status);
|
|
5459
5764
|
if (promoted) status = promoted;
|
|
@@ -5466,18 +5771,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5466
5771
|
const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
|
|
5467
5772
|
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5468
5773
|
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
|
|
5774
|
+
delete status.workflowReceiptPath;
|
|
5469
5775
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
5470
5776
|
try {
|
|
5471
5777
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
5778
|
+
status.workflowReceiptPath = workflowReceipt.path;
|
|
5472
5779
|
} catch (receiptError) {
|
|
5473
5780
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
5474
5781
|
}
|
|
5475
5782
|
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;
|
|
5783
|
+
if (pendingResultPublication && !await pendingResultPublication) return;
|
|
5476
5784
|
persist();
|
|
5477
5785
|
deps.refreshResultDelivery?.();
|
|
5478
5786
|
persistClosed = true;
|
|
5479
5787
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
5480
5788
|
} finally {
|
|
5789
|
+
// Idempotent cleanup only: a failed result/index write must not authorize terminal status.
|
|
5790
|
+
try {
|
|
5791
|
+
settleWorkflowSteerInbox();
|
|
5792
|
+
} catch (error) {
|
|
5793
|
+
console.error(`Failed to close async workflow steer inbox '${asyncDir}':`, error);
|
|
5794
|
+
}
|
|
5481
5795
|
persistClosed = true;
|
|
5482
5796
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5483
5797
|
deps.state.workflowChildStops?.delete(workflowRunId);
|
|
@@ -5493,7 +5807,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5493
5807
|
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, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = requestParams;
|
|
5494
5808
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5495
5809
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5496
|
-
const workflowAggregateOutputPath =
|
|
5810
|
+
const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
|
|
5497
5811
|
const claimedOutputPaths = new Map<string, string>();
|
|
5498
5812
|
const childOutputOverrides = new Map<string, string>();
|
|
5499
5813
|
const childOutputClaimPaths = new Map<string, string>();
|
|
@@ -5513,18 +5827,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5513
5827
|
: workflowResource
|
|
5514
5828
|
? workflowResource.authority.host ? runHostCommand : undefined
|
|
5515
5829
|
: publicExecution ? undefined : runHostCommand;
|
|
5516
|
-
const workflowHostSteps = new Map<string,
|
|
5830
|
+
const workflowHostSteps = new Map<string, HostStepNode>();
|
|
5517
5831
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
|
|
5518
|
-
|
|
5832
|
+
const childProgress = new Map<string, ReturnType<typeof workflowChildProgress>>();
|
|
5833
|
+
let activityTimer: ReturnType<typeof setTimeout> | undefined;
|
|
5834
|
+
let lastProgressAt = 0;
|
|
5835
|
+
let workflowProgressClosed = false;
|
|
5519
5836
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5520
5837
|
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
5521
5838
|
const sendWorkflowProgress = () => {
|
|
5522
|
-
|
|
5523
|
-
if (
|
|
5839
|
+
if (workflowProgressClosed) return;
|
|
5840
|
+
if (activityTimer) clearTimeout(activityTimer);
|
|
5841
|
+
activityTimer = undefined;
|
|
5842
|
+
lastProgressAt = Date.now();
|
|
5843
|
+
const liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false, trace: liveWorkflow.trace, progress: childProgress });
|
|
5844
|
+
onUpdate?.(workflowProgressUpdate(foregroundWorkflowRunId, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight));
|
|
5524
5845
|
};
|
|
5525
5846
|
try {
|
|
5526
5847
|
const workflow = await runWorkflowScript({
|
|
5527
5848
|
script: requestParams.workflowScript,
|
|
5849
|
+
workflowRunId: foregroundWorkflowRunId,
|
|
5528
5850
|
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, foregroundWorkflowRunId, key) } } : {}),
|
|
5529
5851
|
globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
|
|
5530
5852
|
timeoutMs: timeout,
|
|
@@ -5538,10 +5860,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5538
5860
|
const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
|
|
5539
5861
|
const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
|
|
5540
5862
|
liveWorkflow = { ...liveWorkflow, trace: projectedTrace, ...(preflightWarnings.length ? { preflightWarnings } : {}) };
|
|
5541
|
-
|
|
5863
|
+
for (const entry of trace) {
|
|
5864
|
+
if (entry.operation === "run" && ["completed", "failed", "stopped", "detached"].includes(entry.state)) childProgress.delete(entry.key);
|
|
5865
|
+
}
|
|
5542
5866
|
sendWorkflowProgress();
|
|
5543
5867
|
},
|
|
5544
|
-
admit: (calls) => {
|
|
5868
|
+
admit: async (calls, admissionSignal) => {
|
|
5869
|
+
await preflightWorkflowWorktrees({ workflowDefaults: workflowChildDefaults, defaultWorktree: deps.config.worktree, calls, ctxCwd: ctx.cwd, signal: admissionSignal, deadlineAt: workflowDeadlineAt });
|
|
5545
5870
|
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: foregroundWorkflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
5546
5871
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5547
5872
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
@@ -5577,6 +5902,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5577
5902
|
deps.asyncByDefault,
|
|
5578
5903
|
);
|
|
5579
5904
|
preparedChildParams = childRequest;
|
|
5905
|
+
if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
|
|
5580
5906
|
if (delegatedWorkflowPermit) {
|
|
5581
5907
|
if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
|
|
5582
5908
|
const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
|
|
@@ -5594,6 +5920,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5594
5920
|
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
5595
5921
|
const progress = update.details.progress?.[0];
|
|
5596
5922
|
if (!progress) return;
|
|
5923
|
+
if (onUpdate && !workflowProgressClosed) {
|
|
5924
|
+
if (progress.status === "running") childProgress.set(key, workflowChildProgress(progress));
|
|
5925
|
+
else childProgress.delete(key);
|
|
5926
|
+
if (!activityTimer) {
|
|
5927
|
+
const delay = 100 - (Date.now() - lastProgressAt);
|
|
5928
|
+
if (delay <= 0) sendWorkflowProgress();
|
|
5929
|
+
else activityTimer = setTimeout(sendWorkflowProgress, delay);
|
|
5930
|
+
}
|
|
5931
|
+
}
|
|
5597
5932
|
const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
5598
5933
|
recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5599
5934
|
status: progressStatus,
|
|
@@ -5621,7 +5956,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5621
5956
|
return child;
|
|
5622
5957
|
},
|
|
5623
5958
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
5624
|
-
resolveResume: (reference) =>
|
|
5959
|
+
resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
|
|
5625
5960
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: foregroundWorkflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5626
5961
|
});
|
|
5627
5962
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
@@ -5674,6 +6009,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5674
6009
|
isError: true,
|
|
5675
6010
|
details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(partial.children), ...(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, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5676
6011
|
}, workflowFanoutBudget));
|
|
6012
|
+
} finally {
|
|
6013
|
+
workflowProgressClosed = true;
|
|
6014
|
+
if (activityTimer) clearTimeout(activityTimer);
|
|
6015
|
+
childProgress.clear();
|
|
5677
6016
|
}
|
|
5678
6017
|
}
|
|
5679
6018
|
const directParams = requestParams;
|
|
@@ -5744,10 +6083,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5744
6083
|
}
|
|
5745
6084
|
if (!confirmed) return { content: [{ type: "text", text: "Worktree discard canceled; preserved worktrees were not changed." }], details: { mode: "management", results: [] } };
|
|
5746
6085
|
try {
|
|
5747
|
-
const
|
|
5748
|
-
|
|
6086
|
+
const manifestPath = path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath);
|
|
6087
|
+
const discarded = await withWorktreeTransaction(() => discardPreservedWorktrees(
|
|
6088
|
+
manifestPath,
|
|
5749
6089
|
{ kind: decision === "confirm" ? "confirmed" : "policy", ...(deps.config.authorityPolicy ? { policy: deps.config.authorityPolicy } : {}) },
|
|
5750
|
-
);
|
|
6090
|
+
));
|
|
5751
6091
|
return { content: [{ type: "text", text: discarded.text }], details: { mode: "management", results: [] } };
|
|
5752
6092
|
} catch (error) {
|
|
5753
6093
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -5784,16 +6124,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5784
6124
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5785
6125
|
return handleHerdrProjectPaneAction(action as (typeof HERDR_PROJECT_PANE_ACTIONS)[number], paramsWithResolvedCwd, { cwd: requestCwd, state: deps.state, signal });
|
|
5786
6126
|
}
|
|
5787
|
-
if ((
|
|
6127
|
+
if ((INSPECTOR_ACTIONS as readonly string[]).includes(action)) {
|
|
5788
6128
|
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
5789
6129
|
return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5790
6130
|
}
|
|
5791
6131
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5792
|
-
return
|
|
6132
|
+
return handleInspectorAction(action as (typeof INSPECTOR_ACTIONS)[number], paramsWithResolvedCwd, {
|
|
5793
6133
|
state: deps.state,
|
|
5794
6134
|
cwd: requestCwd,
|
|
5795
6135
|
...(deps.config.missions ? { missions: deps.config.missions } : {}),
|
|
5796
6136
|
...(deps.config.authorityPolicy ? { authorityPolicy: deps.config.authorityPolicy } : {}),
|
|
6137
|
+
plugins: createBuiltinInspectorPlugins(),
|
|
5797
6138
|
signal,
|
|
5798
6139
|
});
|
|
5799
6140
|
}
|
|
@@ -6016,7 +6357,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6016
6357
|
}
|
|
6017
6358
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
6018
6359
|
}
|
|
6019
|
-
if (paramsWithResolvedCwd.view === "fleet") {
|
|
6360
|
+
if (paramsWithResolvedCwd.view === "fleet" || paramsWithResolvedCwd.view === "transcript") {
|
|
6020
6361
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
6021
6362
|
}
|
|
6022
6363
|
if (targetRunId) {
|
|
@@ -6025,12 +6366,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6025
6366
|
if (resolved?.kind === "foreground") {
|
|
6026
6367
|
const foreground = getForegroundControl(deps.state, resolved.id);
|
|
6027
6368
|
if (foreground) {
|
|
6028
|
-
if (paramsWithResolvedCwd.view === "transcript") {
|
|
6029
|
-
return withBudget({
|
|
6030
|
-
content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Persisted session transcript becomes inspectable after the foreground run completes when sessions are enabled." }],
|
|
6031
|
-
details: { mode: "management", results: [] },
|
|
6032
|
-
});
|
|
6033
|
-
}
|
|
6034
6369
|
return withBudget(foregroundStatusResult(foreground));
|
|
6035
6370
|
}
|
|
6036
6371
|
}
|
|
@@ -6040,13 +6375,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6040
6375
|
}
|
|
6041
6376
|
} else if (!hasDirectoryTarget) {
|
|
6042
6377
|
const foreground = getForegroundControl(deps.state, undefined);
|
|
6043
|
-
if (foreground
|
|
6044
|
-
if (foreground && paramsWithResolvedCwd.view === "transcript") {
|
|
6045
|
-
return withBudget({
|
|
6046
|
-
content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Pass an async run id to inspect a background transcript." }],
|
|
6047
|
-
details: { mode: "management", results: [] },
|
|
6048
|
-
});
|
|
6049
|
-
}
|
|
6378
|
+
if (foreground) return withBudget(foregroundStatusResult(foreground));
|
|
6050
6379
|
}
|
|
6051
6380
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
6052
6381
|
}
|
|
@@ -6067,9 +6396,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6067
6396
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
6068
6397
|
const directoryStatus = location.asyncDir ? readStatus(location.asyncDir) : null;
|
|
6069
6398
|
if (directoryStatus?.mode === "workflow") {
|
|
6070
|
-
|
|
6071
|
-
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6072
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
6399
|
+
return steerWorkflowRun({ state: deps.state, runId, asyncDir: location.asyncDir!, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
|
|
6073
6400
|
}
|
|
6074
6401
|
if (location.asyncDir) {
|
|
6075
6402
|
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
@@ -6077,6 +6404,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6077
6404
|
}
|
|
6078
6405
|
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
6079
6406
|
state: deps.state,
|
|
6407
|
+
findPendingAsks: deps.findPendingAsks,
|
|
6080
6408
|
runId,
|
|
6081
6409
|
message,
|
|
6082
6410
|
mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
|
|
@@ -6114,9 +6442,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6114
6442
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6115
6443
|
const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
|
|
6116
6444
|
if (resolvedStatus?.mode === "workflow") {
|
|
6117
|
-
|
|
6118
|
-
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6119
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
6445
|
+
return steerWorkflowRun({ state: deps.state, runId: resolved.id, asyncDir: resolved.location.asyncDir!, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
|
|
6120
6446
|
}
|
|
6121
6447
|
if (resolved.location.asyncDir) {
|
|
6122
6448
|
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
@@ -6124,6 +6450,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6124
6450
|
}
|
|
6125
6451
|
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
6126
6452
|
state: deps.state,
|
|
6453
|
+
findPendingAsks: deps.findPendingAsks,
|
|
6127
6454
|
runId: resolved.id,
|
|
6128
6455
|
message,
|
|
6129
6456
|
mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
|
|
@@ -6325,6 +6652,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6325
6652
|
config: deps.config,
|
|
6326
6653
|
currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
|
|
6327
6654
|
runtimeAgentOwner: deps.pi,
|
|
6655
|
+
onAgentsChanged: deps.onAgentsChanged,
|
|
6328
6656
|
});
|
|
6329
6657
|
}
|
|
6330
6658
|
|
|
@@ -6437,65 +6765,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6437
6765
|
|
|
6438
6766
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
6439
6767
|
let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
const forkThinkingDowngrades = new Map<number, string>();
|
|
6768
|
+
// Forked children keep their requested thinking level. Signed Anthropic thinking
|
|
6769
|
+
// blocks are stripped from the inherited transcript by the resolver (they are bound
|
|
6770
|
+
// to the parent session), which is not a reason to disable the child's own reasoning.
|
|
6444
6771
|
try {
|
|
6445
|
-
const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
|
|
6446
|
-
const parentModel = requestParentModel;
|
|
6447
|
-
prepareForkThinking = (agentName, index, modelOverride, modelOverrideFromParent, storedOrigin) => {
|
|
6448
|
-
const agentConfig = agents.find((agent) => agent.name === agentName);
|
|
6449
|
-
if (agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job") {
|
|
6450
|
-
forkThinkingRequirements.set(index, true);
|
|
6451
|
-
return;
|
|
6452
|
-
}
|
|
6453
|
-
const effectiveStoredOrigin = storedOrigin === "configured" && modelOverride === undefined && agentConfig?.model === undefined
|
|
6454
|
-
? undefined
|
|
6455
|
-
: storedOrigin;
|
|
6456
|
-
const origin = resolveModelOrigin({
|
|
6457
|
-
fromParent: modelOverrideFromParent,
|
|
6458
|
-
storedOrigin: effectiveStoredOrigin,
|
|
6459
|
-
explicitModel: modelOverrideFromParent || effectiveStoredOrigin === "configured" ? undefined : modelOverride,
|
|
6460
|
-
agentModel: agentConfig?.model,
|
|
6461
|
-
parentModel,
|
|
6462
|
-
});
|
|
6463
|
-
const primaryModel = modelOverrideFromParent
|
|
6464
|
-
? modelOverride
|
|
6465
|
-
: resolveEffectiveSubagentModel(
|
|
6466
|
-
effectiveStoredOrigin === "configured" ? undefined : modelOverride,
|
|
6467
|
-
agentConfig?.model,
|
|
6468
|
-
parentModel,
|
|
6469
|
-
forkAvailableModels,
|
|
6470
|
-
agentConfig?.modelProvider ?? parentModel?.provider,
|
|
6471
|
-
{ source: origin === "explicit" ? "explicit" : "inherited" },
|
|
6472
|
-
);
|
|
6473
|
-
const candidates = buildModelCandidates(
|
|
6474
|
-
primaryModel,
|
|
6475
|
-
agentConfig?.fallbackModels,
|
|
6476
|
-
forkAvailableModels,
|
|
6477
|
-
agentConfig?.modelProvider ?? parentModel?.provider,
|
|
6478
|
-
{
|
|
6479
|
-
primaryModelFromParent: origin === "inherited",
|
|
6480
|
-
origin,
|
|
6481
|
-
},
|
|
6482
|
-
);
|
|
6483
|
-
forkThinkingRequirements.set(
|
|
6484
|
-
index,
|
|
6485
|
-
candidates.length === 0
|
|
6486
|
-
|| candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
|
|
6487
|
-
);
|
|
6488
|
-
};
|
|
6489
6772
|
const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
|
|
6490
6773
|
? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
|
|
6491
6774
|
: undefined;
|
|
6492
|
-
const forkContextResolver = createForkContextResolver(
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6775
|
+
const forkContextResolver = createForkContextResolver(
|
|
6776
|
+
ctx.sessionManager,
|
|
6777
|
+
contextPolicy.usesFork ? "fork" : undefined,
|
|
6778
|
+
pruneSession ? { pruneSession } : {},
|
|
6779
|
+
);
|
|
6496
6780
|
prepareForkSessionForIndex = forkContextResolver.prepareSessionForIndex;
|
|
6497
6781
|
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
6498
|
-
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
6499
6782
|
} catch (error) {
|
|
6500
6783
|
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
6501
6784
|
}
|
|
@@ -6598,23 +6881,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6598
6881
|
}
|
|
6599
6882
|
const sessionDirForIndex = (idx?: number) =>
|
|
6600
6883
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
6601
|
-
const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0
|
|
6884
|
+
const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0) => {
|
|
6602
6885
|
if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
|
|
6603
|
-
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
|
|
6604
6886
|
return forkSessionFileForIndex(idx);
|
|
6605
6887
|
};
|
|
6606
|
-
const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0
|
|
6888
|
+
const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0) => {
|
|
6607
6889
|
if (!shouldForkAgent(contextPolicy, agentName)) return;
|
|
6608
|
-
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
|
|
6609
6890
|
await prepareForkSessionForIndex(idx);
|
|
6610
6891
|
};
|
|
6611
|
-
const
|
|
6612
|
-
if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
|
|
6613
|
-
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
|
|
6614
|
-
const override = forkThinkingOverrideForIndex(idx);
|
|
6615
|
-
if (override === "off") forkThinkingDowngrades.set(idx, agentName);
|
|
6616
|
-
return override ?? delegatedThinkingOverride;
|
|
6617
|
-
};
|
|
6892
|
+
const thinkingOverrideForTask: ThinkingOverrideForTask = () => delegatedThinkingOverride;
|
|
6618
6893
|
const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin) =>
|
|
6619
6894
|
forkSessionFileForTask(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
6620
6895
|
const childSessionFileForIndex = (idx?: number) =>
|
|
@@ -6703,7 +6978,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6703
6978
|
sessionDirForIndex,
|
|
6704
6979
|
sessionFileForIndex: childSessionFileForIndex,
|
|
6705
6980
|
sessionFileForTask: childSessionFileForTask,
|
|
6706
|
-
thinkingOverrideForTask
|
|
6981
|
+
thinkingOverrideForTask,
|
|
6707
6982
|
artifactConfig,
|
|
6708
6983
|
artifactsDir,
|
|
6709
6984
|
backgroundRequestedWhileClarifying,
|
|
@@ -6716,6 +6991,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6716
6991
|
timeoutMs: foregroundTimeout.timeoutMs,
|
|
6717
6992
|
toolBudget: runToolBudget.toolBudget,
|
|
6718
6993
|
usageBudget: usageBudget.budget,
|
|
6994
|
+
inheritedUsageBudget,
|
|
6719
6995
|
allowZeroToolBudget,
|
|
6720
6996
|
configToolBudget: configToolBudget.toolBudget,
|
|
6721
6997
|
configToolTimeoutMs: deps.config.toolTimeoutMs,
|
|
@@ -6816,7 +7092,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6816
7092
|
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
6817
7093
|
currentProvider: requestParentModel?.provider,
|
|
6818
7094
|
modelScope,
|
|
6819
|
-
thinkingOverrideForTask
|
|
7095
|
+
thinkingOverrideForTask,
|
|
6820
7096
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
6821
7097
|
});
|
|
6822
7098
|
} catch (error) {
|
|
@@ -6907,7 +7183,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6907
7183
|
const asyncResult = await runAsyncPath(execData, deps);
|
|
6908
7184
|
if (asyncResult) {
|
|
6909
7185
|
asyncLaunchFailed = asyncResult.isError === true;
|
|
6910
|
-
return attachMission(withRunFanoutBudget(withResolvedContext(
|
|
7186
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(asyncResult, contextPolicy.contextSummary), runFanoutBudget));
|
|
6911
7187
|
}
|
|
6912
7188
|
if (foregroundControl) {
|
|
6913
7189
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
@@ -6917,11 +7193,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6917
7193
|
if (hasSingle) {
|
|
6918
7194
|
const result = await runSinglePath(execData, deps);
|
|
6919
7195
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
6920
|
-
return attachMission(withRunFanoutBudget(withResolvedContext(
|
|
7196
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(result, contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
6921
7197
|
}
|
|
6922
7198
|
} catch (error) {
|
|
6923
7199
|
asyncLaunchFailed = effectiveAsync;
|
|
6924
|
-
const errorResult =
|
|
7200
|
+
const errorResult = toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
6925
7201
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
6926
7202
|
return attachMission(errorResult);
|
|
6927
7203
|
} finally {
|
|
@@ -6976,7 +7252,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6976
7252
|
}
|
|
6977
7253
|
let publicParams = normalized.params as SubagentParamsLike;
|
|
6978
7254
|
if (publicParams.workflow !== undefined) {
|
|
6979
|
-
const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args);
|
|
7255
|
+
const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
|
|
6980
7256
|
if (!resolved.ok) return Promise.resolve({ content: [{ type: "text", text: resolved.error }], isError: true, details: { mode: "workflow", results: [] } });
|
|
6981
7257
|
const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
|
|
6982
7258
|
publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
|
|
@@ -7023,16 +7299,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
7023
7299
|
ctx: ExtensionContext,
|
|
7024
7300
|
) => {
|
|
7025
7301
|
const ownerSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7302
|
+
const runtimeOwnerId = ctx.sessionManager.getSessionId() || null;
|
|
7303
|
+
let ownerExecutors = scheduledOwnerExecutors.get(runtimeOwnerId);
|
|
7304
|
+
if (!ownerExecutors) {
|
|
7305
|
+
ownerExecutors = new Map();
|
|
7306
|
+
scheduledOwnerExecutors.set(runtimeOwnerId, ownerExecutors);
|
|
7307
|
+
}
|
|
7308
|
+
let owner = ownerExecutors.get(ownerSessionId);
|
|
7309
|
+
if (!owner) {
|
|
7310
|
+
const state = createScheduledOwnerState(deps.state, ownerSessionId, ctx);
|
|
7311
|
+
owner = { state, executor: createSubagentExecutor({ ...deps, state }) };
|
|
7312
|
+
ownerExecutors.set(ownerSessionId, owner);
|
|
7033
7313
|
}
|
|
7034
|
-
return
|
|
7314
|
+
return owner.executor.executePublic(id, params, signal, undefined, ctx);
|
|
7035
7315
|
};
|
|
7036
7316
|
|
|
7037
|
-
|
|
7317
|
+
function* getCurrentSupervisorOwnerStates(): Iterable<SubagentState> {
|
|
7318
|
+
const ownerId = deps.state.supervisorOwnerSessionId;
|
|
7319
|
+
if (!ownerId) return;
|
|
7320
|
+
// File transitions remain separate entries; other runtime owners are never scanned.
|
|
7321
|
+
for (const owner of scheduledOwnerExecutors.get(ownerId)?.values() ?? []) yield owner.state;
|
|
7322
|
+
}
|
|
7323
|
+
|
|
7324
|
+
return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled, getCurrentSupervisorOwnerStates };
|
|
7038
7325
|
}
|