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.
Files changed (148) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/researcher.md +23 -13
  5. package/agents/reviewer.md +3 -2
  6. package/docs/agents.md +20 -3
  7. package/docs/configuration.md +25 -5
  8. package/docs/extension-api.md +124 -18
  9. package/docs/missions.md +8 -0
  10. package/docs/models.md +59 -2
  11. package/docs/observability.md +46 -6
  12. package/docs/standalone-background.md +49 -0
  13. package/docs/tool-reference.md +20 -10
  14. package/docs/watchdog.md +35 -4
  15. package/docs/workflows.md +40 -19
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +2 -1
  18. package/prompts/parallel-review.md +1 -1
  19. package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
  20. package/skills/pi-subagents/SKILL.md +14 -0
  21. package/skills/pi-subagents/references/execution-controls.md +20 -5
  22. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  23. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  24. package/src/agents/advertised-agent-prompt.ts +94 -0
  25. package/src/agents/agent-management.ts +14 -1
  26. package/src/agents/agent-serializer.ts +2 -0
  27. package/src/agents/agents.ts +14 -0
  28. package/src/agents/builtin-names.ts +1 -0
  29. package/src/api/delegation.ts +4 -0
  30. package/src/api/preflight.ts +76 -45
  31. package/src/api/shared-types.ts +3 -1
  32. package/src/api/workflow-resources.ts +6 -0
  33. package/src/extension/fanout-child.ts +63 -4
  34. package/src/extension/index.ts +58 -8
  35. package/src/extension/public-execution.ts +4 -3
  36. package/src/extension/rpc.ts +8 -21
  37. package/src/extension/schemas.ts +71 -80
  38. package/src/extension/tool-description.ts +29 -81
  39. package/src/inspectors/actions.ts +148 -0
  40. package/src/inspectors/ghostty/actions.ts +74 -0
  41. package/src/inspectors/ghostty/plugin.ts +17 -0
  42. package/src/inspectors/herdr/actions.ts +99 -179
  43. package/src/inspectors/herdr/plugin.ts +20 -0
  44. package/src/inspectors/herdr/project-panes.ts +1 -1
  45. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  46. package/src/inspectors/plugins.ts +8 -0
  47. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  48. package/src/inspectors/types.ts +51 -0
  49. package/src/intercom/intercom-bridge.ts +50 -8
  50. package/src/intercom/native-supervisor-channel.ts +104 -67
  51. package/src/runs/background/active-async-capacity.ts +22 -18
  52. package/src/runs/background/async-execution.ts +45 -56
  53. package/src/runs/background/async-job-tracker.ts +35 -3
  54. package/src/runs/background/async-resume.ts +5 -9
  55. package/src/runs/background/async-status-snapshot.ts +10 -12
  56. package/src/runs/background/async-status.ts +17 -9
  57. package/src/runs/background/auto-drain.ts +44 -30
  58. package/src/runs/background/binary-bootstrap.ts +33 -0
  59. package/src/runs/background/chain-root-attachment.ts +8 -0
  60. package/src/runs/background/control-channel.ts +78 -44
  61. package/src/runs/background/fleet-view.ts +30 -2
  62. package/src/runs/background/notify.ts +117 -13
  63. package/src/runs/background/owned-process-tree.ts +35 -8
  64. package/src/runs/background/process-terminal.ts +23 -23
  65. package/src/runs/background/run-child-session.ts +121 -36
  66. package/src/runs/background/run-status.ts +78 -5
  67. package/src/runs/background/runner-aliases.ts +28 -9
  68. package/src/runs/background/runner-child-launch.ts +88 -0
  69. package/src/runs/background/runner-child-sessions.ts +5 -4
  70. package/src/runs/background/scheduled-runs.ts +40 -13
  71. package/src/runs/background/stale-run-reconciler.ts +3 -1
  72. package/src/runs/background/steering.ts +20 -2
  73. package/src/runs/background/subagent-runner.ts +458 -239
  74. package/src/runs/background/subagent-wait.ts +54 -8
  75. package/src/runs/background/wait-completions.ts +4 -0
  76. package/src/runs/background/wait-tool.ts +1 -1
  77. package/src/runs/foreground/async-steering-action.ts +37 -7
  78. package/src/runs/foreground/execution.ts +145 -56
  79. package/src/runs/foreground/prompt-audit.ts +3 -1
  80. package/src/runs/foreground/subagent-executor.ts +584 -297
  81. package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
  82. package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
  83. package/src/runs/shared/acceptance.ts +7 -4
  84. package/src/runs/shared/agent-contract.ts +1 -1
  85. package/src/runs/shared/async-status-projection.ts +51 -47
  86. package/src/runs/shared/capability-ceiling.ts +2 -0
  87. package/src/runs/shared/child-hooks.ts +167 -3
  88. package/src/runs/shared/child-launch.ts +28 -13
  89. package/src/runs/shared/child-lifecycle.ts +6 -3
  90. package/src/runs/shared/child-runtime-config.ts +3 -1
  91. package/src/runs/shared/child-session.ts +75 -8
  92. package/src/runs/shared/child-tool-plan.ts +124 -5
  93. package/src/runs/shared/completion-evidence.ts +2 -2
  94. package/src/runs/shared/completion-guard.ts +6 -3
  95. package/src/runs/shared/effective-system-prompt.ts +33 -0
  96. package/src/runs/shared/external-cli-runner.ts +9 -7
  97. package/src/runs/shared/host-step-status.ts +11 -11
  98. package/src/runs/shared/llm-intent-arbiter.ts +21 -11
  99. package/src/runs/shared/model-fallback.ts +12 -6
  100. package/src/runs/shared/nested-events.ts +5 -5
  101. package/src/runs/shared/orca-progress-tabs.ts +7 -1
  102. package/src/runs/shared/parallel-handoff.ts +57 -12
  103. package/src/runs/shared/parallel-utils.ts +2 -2
  104. package/src/runs/shared/pi-spawn.ts +10 -0
  105. package/src/runs/shared/readonly-drain-observation.ts +42 -0
  106. package/src/runs/shared/readonly-model-continuation.ts +69 -0
  107. package/src/runs/shared/readonly-session-evidence.ts +307 -0
  108. package/src/runs/shared/run-fanout-budget.ts +8 -8
  109. package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
  110. package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
  111. package/src/runs/shared/task-intent.ts +46 -13
  112. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  113. package/src/runs/shared/worktree-setup-command.ts +190 -0
  114. package/src/runs/shared/worktree.ts +366 -208
  115. package/src/shared/fork-context.ts +15 -72
  116. package/src/shared/launch-contract.ts +65 -2
  117. package/src/shared/opencode-session-headers.ts +30 -0
  118. package/src/shared/types.ts +85 -61
  119. package/src/shared/utils.ts +7 -2
  120. package/src/shared/workflow-child-permit.ts +18 -13
  121. package/src/slash/delegation-adapters.ts +3 -1
  122. package/src/slash/delegation-request.ts +14 -0
  123. package/src/slash/slash-commands.ts +2 -1
  124. package/src/slash/subagents-admin.ts +11 -4
  125. package/src/tui/fleet-status.ts +164 -19
  126. package/src/tui/fleet.ts +27 -19
  127. package/src/tui/render.ts +172 -33
  128. package/src/watchdog/child-status.ts +8 -0
  129. package/src/watchdog/model-selection.ts +20 -0
  130. package/src/watchdog/permission-arbiter.ts +3 -1
  131. package/src/watchdog/register-child.ts +1 -0
  132. package/src/watchdog/register-main.ts +31 -27
  133. package/src/watchdog/review.ts +132 -67
  134. package/src/watchdog/runtime.ts +82 -20
  135. package/src/watchdog/scope.ts +1 -1
  136. package/src/watchdog/settings.ts +9 -3
  137. package/src/watchdog/tool-actions.ts +13 -12
  138. package/src/watchdog/turn-delta.ts +23 -0
  139. package/src/watchdog/types.ts +4 -0
  140. package/src/workflows/chat-progress.ts +3 -3
  141. package/src/workflows/scripted-workflow.ts +275 -17
  142. package/src/workflows/workflow-checklist.ts +13 -17
  143. package/src/workflows/workflow-child-summary.ts +57 -8
  144. package/src/workflows/workflow-preflight.ts +19 -19
  145. package/src/workflows/workflow-receipt.ts +3 -3
  146. package/src/workflows/workflow-resources.ts +96 -21
  147. package/src/workflows/workflow-settlement.ts +3 -0
  148. /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, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
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 { isAgentContractV1 } from "../shared/agent-contract.ts";
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, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
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 { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
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 HostStepNodeV1,
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 WorkflowResourceProvenanceV1,
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").WorkflowPreflightV1;
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 ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => AgentConfig["thinking"] | undefined;
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: ForkThinkingOverrideForTask;
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 requestedId = input.params.id ?? input.params.runId;
1784
- let resolved: ResolvedSubagentRunId | undefined;
1785
- try {
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: ForkThinkingOverrideForTask;
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(input.agent, input.index, model);
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: ForkThinkingOverrideForTask;
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(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
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
- beforeCreate?: (setup: WorktreeSetup) => void,
3360
- ): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
3361
- if (!enabled) return {};
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
- beforeCreate,
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
- if (typeof input.params.resume === "string") {
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
- const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
3484
- const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
3485
- const agent = typeof input.params.agent === "string"
3486
- ? resolveAgentName(input.params.agent, discoveredAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent
3487
- : undefined;
3488
- const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
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
- 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
- };
3596
+ }): Promise<{ suffix: string; reference?: NonNullable<Details["parallelHandoff"]> }> {
3597
+ let admitted = false;
3638
3598
  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
- };
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
- return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
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
- (plannedSetup) => {
3743
- pendingHandoff = writePendingParallelHandoff({
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
- setup: plannedSetup,
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
- if (worktreeSetupError) return worktreeSetupError;
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
- if (worktreeSetup) cleanupWorktrees(worktreeSetup);
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(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin));
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(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
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
- if (worktreeSetup) cleanupWorktrees(worktreeSetup);
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> | undefined;
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: continuationRunIds },
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 resolveKeyedWorkflowResume(
4355
- reference: WorkflowReceiptResumeReference,
4356
- state: SubagentState,
4357
- ): { runId: string; runIds: string[] } {
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
- const runId = entry.latestRunId;
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 && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
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 workflowChatProgressUpdate(
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").WorkflowPreflightV1,
4690
- ): AgentToolResult<Details> | undefined {
4691
- if (chatProgress.mode !== "live-card") return undefined;
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").WorkflowPreflightV1 | undefined;
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: WorkflowResourceProvenanceV1; authority: WorkflowResourceAuthority } | undefined;
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 = status.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: status.toolCallId }, (_filePath, payload) => {
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) => { if (filePath === statusPath) queueActiveRunIndex(); },
5030
- onError: (error, filePath) => console.error(`Failed to persist async workflow state '${filePath}':`, error),
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
- const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
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 = resolveWorkflowAggregateOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
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) => resolveKeyedWorkflowResume(reference, deps.state),
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 = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
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, HostStepNodeV1>();
5830
+ const workflowHostSteps = new Map<string, HostStepNode>();
5517
5831
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
5518
- let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false });
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
- const update = workflowChatProgressUpdate(foregroundWorkflowRunId, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight);
5523
- if (update) onUpdate?.(update);
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
- liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false, trace });
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) => resolveKeyedWorkflowResume(reference, deps.state),
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 discarded = discardPreservedWorktrees(
5748
- path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath),
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 ((HERDR_INSPECTOR_ACTIONS as readonly string[]).includes(action)) {
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 handleHerdrInspectorAction(action as (typeof HERDR_INSPECTOR_ACTIONS)[number], paramsWithResolvedCwd, {
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 && paramsWithResolvedCwd.view !== "transcript") return withBudget(foregroundStatusResult(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
- const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
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
- const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
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
- let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
6441
- let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean, _modelOrigin?: ModelOrigin): void => {};
6442
- const forkThinkingRequirements = new Map<number, boolean>();
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(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
6493
- forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
6494
- ...(pruneSession ? { pruneSession } : {}),
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, modelOverride, modelOverrideFromParent, modelOrigin) => {
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, modelOverride, modelOverrideFromParent, modelOrigin) => {
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 forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent, modelOrigin) => {
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: forkThinkingOverrideForTask,
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: forkThinkingOverrideForTask,
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(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
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(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
7196
+ return attachMission(withRunFanoutBudget(withResolvedContext(result, contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
6921
7197
  }
6922
7198
  } catch (error) {
6923
7199
  asyncLaunchFailed = effectiveAsync;
6924
- const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
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
- let ownerExecutor = scheduledOwnerExecutors.get(ownerSessionId);
7027
- if (!ownerExecutor) {
7028
- ownerExecutor = createSubagentExecutor({
7029
- ...deps,
7030
- state: createScheduledOwnerState(deps.state, ownerSessionId, ctx),
7031
- });
7032
- scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
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 ownerExecutor.executePublic(id, params, signal, undefined, ctx);
7314
+ return owner.executor.executePublic(id, params, signal, undefined, ctx);
7035
7315
  };
7036
7316
 
7037
- return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled };
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
  }