pi-subagents 0.65.0 → 0.66.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 (104) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +1 -1
  3. package/agents/researcher.md +23 -13
  4. package/docs/agents.md +17 -3
  5. package/docs/configuration.md +34 -0
  6. package/docs/extension-api.md +94 -0
  7. package/docs/models.md +58 -1
  8. package/docs/observability.md +42 -2
  9. package/docs/tool-reference.md +11 -5
  10. package/docs/workflows.md +22 -7
  11. package/package.json +4 -1
  12. package/runner-server-preload.mjs +13 -0
  13. package/skills/pi-subagents/SKILL.md +2 -1
  14. package/skills/pi-subagents/references/execution-controls.md +19 -2
  15. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  16. package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  17. package/src/agents/advertised-agent-prompt.ts +63 -0
  18. package/src/agents/agent-management.ts +14 -1
  19. package/src/agents/agent-serializer.ts +2 -0
  20. package/src/agents/agents.ts +8 -0
  21. package/src/api/preflight.ts +5 -1
  22. package/src/api/shared-types.ts +1 -1
  23. package/src/api/workflow-resources.ts +6 -0
  24. package/src/extension/config.ts +4 -2
  25. package/src/extension/index.ts +71 -4
  26. package/src/extension/public-execution.ts +0 -1
  27. package/src/extension/rpc.ts +4 -21
  28. package/src/extension/schemas.ts +9 -7
  29. package/src/extension/tool-description.ts +10 -5
  30. package/src/integrations/pi-web-session-liveness.ts +73 -0
  31. package/src/intercom/native-supervisor-channel.ts +102 -88
  32. package/src/intercom/supervisor-ui.ts +3 -2
  33. package/src/missions/workflow-state.ts +37 -16
  34. package/src/runs/background/active-async-capacity.ts +18 -18
  35. package/src/runs/background/async-execution.ts +8 -1
  36. package/src/runs/background/async-job-tracker.ts +35 -3
  37. package/src/runs/background/async-resume.ts +3 -1
  38. package/src/runs/background/async-retention.ts +9 -0
  39. package/src/runs/background/async-status-snapshot.ts +10 -12
  40. package/src/runs/background/async-status.ts +17 -9
  41. package/src/runs/background/auto-drain.ts +40 -29
  42. package/src/runs/background/chain-root-attachment.ts +8 -0
  43. package/src/runs/background/control-channel.ts +78 -44
  44. package/src/runs/background/notify.ts +88 -12
  45. package/src/runs/background/owned-process-tree.ts +6 -6
  46. package/src/runs/background/process-terminal.ts +23 -23
  47. package/src/runs/background/retained-nested-route-tracker.ts +96 -0
  48. package/src/runs/background/run-child-session.ts +62 -33
  49. package/src/runs/background/run-status.ts +75 -5
  50. package/src/runs/background/runner-aliases.ts +46 -8
  51. package/src/runs/background/runner-child-launch.ts +86 -0
  52. package/src/runs/background/stale-run-reconciler.ts +3 -1
  53. package/src/runs/background/subagent-runner.ts +430 -208
  54. package/src/runs/background/subagent-wait.ts +3 -0
  55. package/src/runs/background/wait-completions.ts +4 -0
  56. package/src/runs/foreground/async-steering-action.ts +19 -0
  57. package/src/runs/foreground/execution.ts +116 -26
  58. package/src/runs/foreground/foreground-history.ts +3 -1
  59. package/src/runs/foreground/prompt-audit.ts +9 -5
  60. package/src/runs/foreground/subagent-executor.ts +531 -227
  61. package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  62. package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
  63. package/src/runs/shared/acceptance.ts +16 -3
  64. package/src/runs/shared/agent-contract.ts +1 -1
  65. package/src/runs/shared/async-status-projection.ts +47 -47
  66. package/src/runs/shared/child-hooks.ts +151 -2
  67. package/src/runs/shared/child-launch.ts +18 -13
  68. package/src/runs/shared/child-session.ts +55 -24
  69. package/src/runs/shared/child-tool-plan.ts +2 -2
  70. package/src/runs/shared/completion-evidence.ts +2 -2
  71. package/src/runs/shared/completion-guard.ts +1 -0
  72. package/src/runs/shared/host-step-status.ts +11 -11
  73. package/src/runs/shared/llm-intent-arbiter.ts +30 -20
  74. package/src/runs/shared/model-exclusions.ts +2 -1
  75. package/src/runs/shared/model-fallback.ts +41 -8
  76. package/src/runs/shared/nested-events.ts +8 -8
  77. package/src/runs/shared/orca-progress-tabs.ts +6 -0
  78. package/src/runs/shared/parallel-handoff.ts +57 -12
  79. package/src/runs/shared/parallel-utils.ts +3 -2
  80. package/src/runs/shared/readonly-drain-observation.ts +42 -0
  81. package/src/runs/shared/readonly-model-continuation.ts +69 -0
  82. package/src/runs/shared/readonly-session-evidence.ts +307 -0
  83. package/src/runs/shared/run-fanout-budget.ts +8 -8
  84. package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
  85. package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
  86. package/src/runs/shared/worktree-cleanup-plan.ts +6 -3
  87. package/src/runs/shared/worktree-setup-command.ts +190 -0
  88. package/src/runs/shared/worktree.ts +403 -210
  89. package/src/shared/model-response-aliases.ts +13 -0
  90. package/src/shared/types.ts +89 -60
  91. package/src/shared/utils.ts +10 -2
  92. package/src/shared/watch-strategy.ts +2 -0
  93. package/src/shared/workflow-child-permit.ts +18 -13
  94. package/src/tui/fleet-status.ts +1 -1
  95. package/src/tui/fleet.ts +11 -5
  96. package/src/tui/render.ts +44 -15
  97. package/src/workflows/chat-progress.ts +3 -3
  98. package/src/workflows/scripted-workflow.ts +70 -16
  99. package/src/workflows/workflow-checklist.ts +15 -18
  100. package/src/workflows/workflow-child-summary.ts +57 -8
  101. package/src/workflows/workflow-preflight.ts +19 -19
  102. package/src/workflows/workflow-receipt.ts +3 -3
  103. package/src/workflows/workflow-resources.ts +96 -21
  104. package/src/workflows/workflow-settlement.ts +3 -0
@@ -49,9 +49,10 @@ import { updateActiveRunIndex } from "../background/active-run-index.ts";
49
49
  import { steeringReceipt } from "../background/steering.ts";
50
50
  import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
51
51
  import { isScheduledRunAction } from "../background/scheduled-runs.ts";
52
+ import { encodeIndexSegment } from "../background/index-segment.ts";
52
53
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
53
54
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
54
- import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateExecutionAcceptance } from "../shared/acceptance.ts";
55
+ import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
55
56
  import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
56
57
  import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
57
58
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
@@ -60,16 +61,17 @@ import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBrid
60
61
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
61
62
  import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
62
63
  import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
64
+ import { retainLiveForegroundNestedRoute } from "../../integrations/pi-web-session-liveness.ts";
63
65
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
64
66
  import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
65
67
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
66
- import { isAgentContractV1 } from "../shared/agent-contract.ts";
68
+ import { isAgentContract } from "../shared/agent-contract.ts";
67
69
  import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
68
70
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
69
71
  import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
70
72
  import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.ts";
71
73
  import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
72
- import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
74
+ import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.ts";
73
75
  import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
74
76
  import {
75
77
  attachNestedChildrenToResultChildren,
@@ -80,12 +82,13 @@ import {
80
82
  stripDetailsOutputsForIntercomReceipt,
81
83
  } from "../../intercom/result-intercom.ts";
82
84
  import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, readAsyncRecoveryDescriptor, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
83
- import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
84
- import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
85
+ import { closeSteerInbox, consumeSteerRequests, deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, watchAsyncControlInbox, type SteerDeliveryMode, type SteerRequest } from "../background/control-channel.ts";
86
+ import { createSteeringStatus, recordSteeringRequest, steeringStatus, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
85
87
  import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
86
88
  import {
87
89
  resolveWorkflowForegroundSteeringTarget,
88
90
  steerWorkflowForegroundTarget,
91
+ steerWorkflowRun,
89
92
  } from "./workflow-foreground-steering.ts";
90
93
  import { stopAsyncRun } from "./async-stop-action.ts";
91
94
  import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
@@ -113,10 +116,10 @@ import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspe
113
116
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
114
117
  import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowLanePlan, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowScriptTraceEntry, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
115
118
  import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
116
- import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
119
+ import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
117
120
  import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
118
121
  import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
119
- import { parseWorkflowChildSummary, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
122
+ import { parseWorkflowChildSummary, workflowChildProgress, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
120
123
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
121
124
  import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.ts";
122
125
  import {
@@ -133,6 +136,8 @@ import { resolveWorkflowResource } from "../../workflows/workflow-resources.ts";
133
136
  import {
134
137
  cleanupWorktrees,
135
138
  createWorktrees,
139
+ withWorktreeTransaction,
140
+ type WorktreeSetupProgress,
136
141
  diffWorktrees,
137
142
  formatWorktreeDiffSummary,
138
143
  type WorktreeSetup,
@@ -155,7 +160,7 @@ import {
155
160
  type ForegroundSteerInput,
156
161
  type ForegroundSteerOutcome,
157
162
  type ForegroundRunControl,
158
- type HostStepNodeV1,
163
+ type HostStepNode,
159
164
  type IntercomBridgeConfig,
160
165
  type IntercomEventBus,
161
166
  type JsonSchemaObject,
@@ -172,7 +177,7 @@ import {
172
177
  type ToolBudgetConfig,
173
178
  type Usage,
174
179
  type UsageBudgetConfig,
175
- type WorkflowResourceProvenanceV1,
180
+ type WorkflowResourceProvenance,
176
181
  type WorkflowGraphNode,
177
182
  type WorkflowGraphSnapshot,
178
183
  type WorkflowNodeStatus,
@@ -192,6 +197,7 @@ import {
192
197
  resolveMaxSubagentSpawnsPerRun,
193
198
  wrapForkTask,
194
199
  type ScheduleOrigin,
200
+ type SteeringTargetState,
195
201
  } from "../../shared/types.ts";
196
202
  import { deriveChildSessionName } from "../../shared/child-session-name.ts";
197
203
 
@@ -324,7 +330,7 @@ export interface SubagentParamsLike {
324
330
  workflowScriptPath?: string;
325
331
  globalConcurrencyLimit?: number;
326
332
  maxSubagentSpawnsPerRun?: number;
327
- preflight?: import("../../shared/types.ts").WorkflowPreflightV1;
333
+ preflight?: import("../../shared/types.ts").WorkflowPreflight;
328
334
  chatProgress?: "auto" | "off" | "live-card";
329
335
  isolation?: "none" | "worktree";
330
336
  step?: ChainStep;
@@ -435,9 +441,12 @@ interface ExecutorDeps {
435
441
  getSubagentSessionRoot: (parentSessionFile: string | null) => string;
436
442
  expandTilde: (p: string) => string;
437
443
  discoverAgents: (cwd: string, scope: AgentScope, preferredModelProvider?: string) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
444
+ onAgentsChanged?: () => void;
438
445
  allowMutatingManagementActions?: boolean;
439
446
  activateSupervisorTransport?: () => void;
447
+ findPendingAsks?: Parameters<typeof steerAsyncRun>[0]["findPendingAsks"];
440
448
  refreshResultDelivery?: () => void;
449
+ trackRetainedNestedRoute?: (rootRunId: string) => void;
441
450
  kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
442
451
  /** Set when this executor runs inside a child session; carries the runtime settings the host passes instead of environment variables. */
443
452
  childRuntime?: ChildRuntimeConfig;
@@ -494,6 +503,8 @@ interface ExecutionContextData {
494
503
  configToolTimeoutMs?: number;
495
504
  toolBudget?: ResolvedToolBudget;
496
505
  usageBudget?: UsageBudgetConfig;
506
+ /** Parent workflow owns this allowance; presence vetoes unverifiable child continuation. */
507
+ inheritedUsageBudget?: UsageBudgetConfig;
497
508
  allowZeroToolBudget?: boolean;
498
509
  configToolBudget?: ResolvedToolBudget;
499
510
  contextPolicy: AgentDefaultContextPolicy;
@@ -526,9 +537,16 @@ function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string):
526
537
  return { params: { ...rest, workflowScript } };
527
538
  }
528
539
 
529
- function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
540
+ export function removeForegroundControlIfIdle(state: SubagentState, runId: string, trackRetainedNestedRoute?: (rootRunId: string) => void): boolean {
530
541
  const control = state.foregroundControls.get(runId);
531
542
  if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
543
+ if (control?.nestedRoute && trackRetainedNestedRoute) {
544
+ try {
545
+ if (retainLiveForegroundNestedRoute(state, control.nestedRoute)) trackRetainedNestedRoute(runId);
546
+ } catch (error) {
547
+ console.error(`Failed to retain live nested descendants for foreground run '${runId}':`, error);
548
+ }
549
+ }
532
550
  state.foregroundControls.delete(runId);
533
551
  if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
534
552
  return true;
@@ -723,7 +741,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
723
741
  };
724
742
  }
725
743
 
726
- function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
744
+ function rememberForegroundRun(state: SubagentState, input: { modelResponseAliases?: Record<string, string[]>; runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
727
745
  state.foregroundRuns ??= new Map();
728
746
  const previous = state.foregroundRuns.get(input.runId);
729
747
  const updatedAt = Date.now();
@@ -735,6 +753,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
735
753
  updatedAt,
736
754
  children: input.results.map((result, index) => {
737
755
  const resumeContract = omitUndefinedProperties({
756
+ modelResponseAliases: input.modelResponseAliases,
738
757
  outputSchema: input.params.outputSchema,
739
758
  agentContract: input.params.agentContract,
740
759
  acceptance: input.params.acceptance,
@@ -1313,6 +1332,7 @@ function appendStepToAsyncChain(input: {
1313
1332
  currentModelProvider: parentModel?.provider,
1314
1333
  currentModel: parentModel,
1315
1334
  modelScope: discoveredForAppend.modelScope,
1335
+ modelResponseAliases: input.deps.config.modelResponseAliases,
1316
1336
  interactive: input.ctx.hasUI,
1317
1337
  permissions: input.deps.config.permissions,
1318
1338
  childRuntime: input.deps.childRuntime,
@@ -1694,6 +1714,7 @@ async function resumeExternalJobFollowUp(input: {
1694
1714
  currentModelProvider: parentModel?.provider,
1695
1715
  currentModel: parentModel,
1696
1716
  modelScope: input.modelScope,
1717
+ modelResponseAliases: input.deps.config.modelResponseAliases,
1697
1718
  interactive: input.ctx.hasUI,
1698
1719
  permissions: input.deps.config.permissions,
1699
1720
  childRuntime: input.deps.childRuntime,
@@ -1729,6 +1750,28 @@ async function resumeExternalJobFollowUp(input: {
1729
1750
  return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId: result.details.asyncId ?? runId, asyncDir: result.details.asyncDir ?? asyncDir, interactive: input.ctx.hasUI });
1730
1751
  }
1731
1752
 
1753
+ function resolveRequestedResumeTarget(params: SubagentParamsLike, deps: ExecutorDeps, parentSessionFile: string | null): ResumeSourceTarget | { kind: "live-nested"; target: ResolvedSubagentRunId & { kind: "nested" } } {
1754
+ const requestedId = params.id ?? params.runId;
1755
+ let resolved: ResolvedSubagentRunId | undefined;
1756
+ try {
1757
+ resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
1758
+ } catch (error) {
1759
+ const message = error instanceof Error ? error.message : "";
1760
+ const asyncMatches = message.match(/async:/g)?.length ?? 0;
1761
+ if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
1762
+ }
1763
+ if (resolved?.kind === "nested") {
1764
+ if (params.chain?.length) throw new Error("Attaching a running subagent as a chain root is currently available for top-level async runs only.");
1765
+ if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") return { kind: "live-nested", target: resolved };
1766
+ const trustedSessionRoots = [
1767
+ ...(deps.config.defaultSessionDir ? [path.resolve(deps.expandTilde(deps.config.defaultSessionDir))] : []),
1768
+ ...(parentSessionFile ? [deps.getSubagentSessionRoot(parentSessionFile)] : []),
1769
+ ];
1770
+ return resolveNestedResumeTarget(resolved, trustedSessionRoots);
1771
+ }
1772
+ return resolveResumeTarget(params, deps.state, { asyncRequireSessionFile: false });
1773
+ }
1774
+
1732
1775
  async function resumeAsyncRun(input: {
1733
1776
  params: SubagentParamsLike;
1734
1777
  requestCwd: string;
@@ -1767,34 +1810,9 @@ async function resumeAsyncRun(input: {
1767
1810
  let target: ResumeSourceTarget;
1768
1811
  const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
1769
1812
  try {
1770
- const requestedId = input.params.id ?? input.params.runId;
1771
- let resolved: ResolvedSubagentRunId | undefined;
1772
- try {
1773
- resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) })) : undefined;
1774
- } catch (error) {
1775
- const message = error instanceof Error ? error.message : "";
1776
- const asyncMatches = message.match(/async:/g)?.length ?? 0;
1777
- if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
1778
- }
1779
- if (resolved?.kind === "nested") {
1780
- if (attachChain) {
1781
- return {
1782
- content: [{ type: "text", text: "Attaching a running subagent as a chain root is currently available for top-level async runs only." }],
1783
- isError: true,
1784
- details: { mode: "management", results: [] },
1785
- };
1786
- }
1787
- if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") {
1788
- return resumeLiveNestedRun({ target: resolved, message: followUp });
1789
- }
1790
- const trustedSessionRoots = [
1791
- ...(input.deps.config.defaultSessionDir ? [path.resolve(input.deps.expandTilde(input.deps.config.defaultSessionDir))] : []),
1792
- ...(parentSessionFile ? [input.deps.getSubagentSessionRoot(parentSessionFile)] : []),
1793
- ];
1794
- target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
1795
- } else {
1796
- target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: false });
1797
- }
1813
+ const resolved = resolveRequestedResumeTarget(input.params, input.deps, parentSessionFile);
1814
+ if (resolved.kind === "live-nested") return resumeLiveNestedRun({ target: resolved.target, message: followUp });
1815
+ target = resolved;
1798
1816
  } catch (error) {
1799
1817
  const message = error instanceof Error ? error.message : String(error);
1800
1818
  return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
@@ -1947,6 +1965,7 @@ async function resumeAsyncRun(input: {
1947
1965
  currentModelProvider: parentModel?.provider,
1948
1966
  currentModel: parentModel,
1949
1967
  modelScope,
1968
+ modelResponseAliases: input.deps.config.modelResponseAliases,
1950
1969
  interactive: input.ctx.hasUI,
1951
1970
  permissions: input.deps.config.permissions,
1952
1971
  childRuntime: input.deps.childRuntime,
@@ -2055,6 +2074,8 @@ async function resumeAsyncRun(input: {
2055
2074
  currentModelProvider: parentModel?.provider,
2056
2075
  currentModel: parentModel,
2057
2076
  modelScope,
2077
+ // Absence in the retained contract is meaningful; never acquire current aliases.
2078
+ modelResponseAliases: recoveryDescriptor ? recoveryDescriptor.modelResponseAliases : foregroundContract?.modelResponseAliases,
2058
2079
  interactive: input.ctx.hasUI,
2059
2080
  permissions: input.deps.config.permissions,
2060
2081
  childRuntime: input.deps.childRuntime,
@@ -2102,6 +2123,7 @@ async function resumeAsyncRun(input: {
2102
2123
  availableModels,
2103
2124
  output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
2104
2125
  outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
2126
+ outputClaimPath: input.params.workflowOutputClaimPath,
2105
2127
  ...(agentContract ? { agentContract } : {}),
2106
2128
  ...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
2107
2129
  ...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
@@ -2178,6 +2200,7 @@ async function resumeAsyncRun(input: {
2178
2200
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
2179
2201
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
2180
2202
  ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
2203
+ ...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
2181
2204
  ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
2182
2205
  ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
2183
2206
  ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
@@ -3153,6 +3176,7 @@ async function waitForWorkflowAsyncSingleResult(
3153
3176
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
3154
3177
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
3155
3178
  ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
3179
+ ...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
3156
3180
  ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
3157
3181
  ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
3158
3182
  ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
@@ -3210,6 +3234,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3210
3234
  currentModelProvider: parentModel?.provider,
3211
3235
  currentModel: parentModel,
3212
3236
  modelScope: data.modelScope,
3237
+ modelResponseAliases: deps.config.modelResponseAliases,
3213
3238
  interactive: ctx.hasUI,
3214
3239
  permissions: deps.config.permissions,
3215
3240
  childRuntime: deps.childRuntime,
@@ -3326,8 +3351,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3326
3351
  return null;
3327
3352
  }
3328
3353
 
3329
- function createSingleWorktreeSetup(
3330
- enabled: boolean | undefined,
3354
+ async function createSingleWorktreeSetup(
3331
3355
  cwd: string,
3332
3356
  runId: string,
3333
3357
  agent: string,
@@ -3339,12 +3363,13 @@ function createSingleWorktreeSetup(
3339
3363
  branchPrefix: ExtensionConfig["worktreeBranchPrefix"],
3340
3364
  label?: string,
3341
3365
  task?: string,
3342
- beforeCreate?: (setup: WorktreeSetup) => void,
3343
- ): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
3344
- if (!enabled) return {};
3366
+ onProgress?: (snapshot: WorktreeSetupProgress) => void,
3367
+ signal?: AbortSignal,
3368
+ deadlineAt?: number,
3369
+ ): Promise<{ setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> }> {
3345
3370
  try {
3346
3371
  return {
3347
- setup: createWorktrees(cwd, runId, 1, omitUndefinedProperties({
3372
+ setup: await createWorktrees(cwd, runId, 1, omitUndefinedProperties({
3348
3373
  agents: [agent],
3349
3374
  setupHook: setupHook
3350
3375
  ? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
@@ -3355,7 +3380,9 @@ function createSingleWorktreeSetup(
3355
3380
  branchPrefix,
3356
3381
  labels: [label],
3357
3382
  tasks: [task],
3358
- beforeCreate,
3383
+ onProgress,
3384
+ signal,
3385
+ deadlineAt,
3359
3386
  })),
3360
3387
  };
3361
3388
  } catch (error) {
@@ -3381,15 +3408,6 @@ function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string,
3381
3408
  return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
3382
3409
  }
3383
3410
 
3384
- function resolveWorkflowAggregateOutputPath(
3385
- output: string | boolean | undefined,
3386
- ctxCwd: string,
3387
- workflowCwd: string,
3388
- outputBaseDir: string,
3389
- ): string | undefined {
3390
- return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
3391
- }
3392
-
3393
3411
  function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
3394
3412
  if (aggregateOutputPath) {
3395
3413
  const parsed = path.parse(aggregateOutputPath);
@@ -3451,7 +3469,9 @@ function resolveWorkflowChildOutputPath(input: {
3451
3469
  key: string;
3452
3470
  params: Record<string, unknown>;
3453
3471
  }): { path?: string; inherited: boolean } {
3454
- if (typeof input.params.resume === "string") {
3472
+ const rawOutput = input.params.output;
3473
+ const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
3474
+ if (typeof input.params.resume === "string" && (!hasExplicitOutput || rawOutput === true || rawOutput === "true")) {
3455
3475
  if (!input.state) return { path: undefined, inherited: false };
3456
3476
  const index = input.params.index;
3457
3477
  const target = resolveResumeTarget({
@@ -3460,15 +3480,16 @@ function resolveWorkflowChildOutputPath(input: {
3460
3480
  }, input.state);
3461
3481
  return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
3462
3482
  }
3463
- const rawOutput = input.params.output;
3464
- const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
3465
3483
  const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
3466
- const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
3467
- const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
3468
- const agent = typeof input.params.agent === "string"
3469
- ? resolveAgentName(input.params.agent, discoveredAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent
3470
- : undefined;
3471
- const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
3484
+ let agentOutput: string | undefined;
3485
+ if (rawOutput === true || rawOutput === "true" || (!hasExplicitOutput && !input.aggregateOutputPath)) {
3486
+ const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
3487
+ const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
3488
+ const agent = typeof input.params.agent === "string"
3489
+ ? resolveAgentName(input.params.agent, discoveredAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent
3490
+ : undefined;
3491
+ agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
3492
+ }
3472
3493
  const output = rawOutput === true || rawOutput === "true"
3473
3494
  ? agentOutput
3474
3495
  : hasExplicitOutput
@@ -3557,7 +3578,7 @@ function prepareWorkflowChildLaunchParams(input: {
3557
3578
  childParams = { ...input.childParams, output: input.outputOverride };
3558
3579
  } else if (usesDefaultOutput && input.aggregateOutputPath !== undefined) {
3559
3580
  childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
3560
- } else if (input.childParams.resume === undefined) {
3581
+ } else if (input.childParams.resume === undefined || input.childParams.output !== undefined) {
3561
3582
  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 });
3562
3583
  if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
3563
3584
  }
@@ -3571,7 +3592,7 @@ function prepareWorkflowChildLaunchParams(input: {
3571
3592
  return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired, outputClaimPath: input.outputClaimPath });
3572
3593
  }
3573
3594
 
3574
- function finalizeSingleWorktreeHandoff(input: {
3595
+ async function finalizeSingleWorktreeHandoff(input: {
3575
3596
  worktreeSetup: WorktreeSetup;
3576
3597
  artifactsDir: string;
3577
3598
  runId: string;
@@ -3580,55 +3601,78 @@ function finalizeSingleWorktreeHandoff(input: {
3580
3601
  result: SingleResult;
3581
3602
  workflowKey?: string;
3582
3603
  lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
3583
- }): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
3584
- const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
3585
- const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
3586
- const diffSummary = formatWorktreeDiffSummary(diffs);
3587
- const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
3588
- const handoff = {
3589
- manifestPath,
3590
- runId: input.runId,
3591
- mode: "single" as const,
3592
- source: "foreground" as const,
3593
- cwd: input.cwd,
3594
- stepIndex: 0,
3595
- flatStartIndex: 0,
3596
- setup: input.worktreeSetup,
3597
- 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,
3598
- diffs,
3599
- results: [{
3600
- agent: input.result.agent,
3601
- ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
3602
- ...(input.workflowKey ? { runId: input.runId } : {}),
3603
- ...(input.lane ? { lane: input.lane } : {}),
3604
- status: resolveSubagentResultStatus(omitUndefinedProperties({
3605
- exitCode: input.result.exitCode,
3606
- interrupted: input.result.interrupted,
3607
- detached: input.result.detached,
3608
- state: input.result.stopped ? "stopped" : undefined,
3609
- processSignal: input.result.processSignal,
3610
- timedOut: input.result.timedOut,
3611
- stopped: input.result.stopped,
3612
- turnBudgetExceeded: input.result.turnBudgetExceeded,
3613
- })),
3614
- summary: resultSummaryForIntercom(input.result),
3615
- ...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
3616
- ...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
3617
- ...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
3618
- ...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
3619
- }],
3620
- };
3604
+ }): Promise<{ suffix: string; reference?: NonNullable<Details["parallelHandoff"]> }> {
3605
+ let admitted = false;
3621
3606
  try {
3622
- writeParallelHandoffGroup(handoff);
3623
- const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
3624
- const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
3625
- return {
3626
- suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
3627
- reference,
3628
- };
3607
+ return await withWorktreeTransaction(() => {
3608
+ admitted = true;
3609
+ const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
3610
+ const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
3611
+ const diffSummary = formatWorktreeDiffSummary(diffs);
3612
+ const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
3613
+ const handoff = {
3614
+ manifestPath,
3615
+ runId: input.runId,
3616
+ mode: "single" as const,
3617
+ source: "foreground" as const,
3618
+ cwd: input.cwd,
3619
+ stepIndex: 0,
3620
+ flatStartIndex: 0,
3621
+ setup: input.worktreeSetup,
3622
+ 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,
3623
+ diffs,
3624
+ results: [{
3625
+ agent: input.result.agent,
3626
+ ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
3627
+ ...(input.workflowKey ? { runId: input.runId } : {}),
3628
+ ...(input.lane ? { lane: input.lane } : {}),
3629
+ status: resolveSubagentResultStatus(omitUndefinedProperties({
3630
+ exitCode: input.result.exitCode,
3631
+ interrupted: input.result.interrupted,
3632
+ detached: input.result.detached,
3633
+ state: input.result.stopped ? "stopped" : undefined,
3634
+ processSignal: input.result.processSignal,
3635
+ timedOut: input.result.timedOut,
3636
+ stopped: input.result.stopped,
3637
+ turnBudgetExceeded: input.result.turnBudgetExceeded,
3638
+ })),
3639
+ summary: resultSummaryForIntercom(input.result),
3640
+ ...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
3641
+ ...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
3642
+ ...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
3643
+ ...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
3644
+ }],
3645
+ };
3646
+ try {
3647
+ writeParallelHandoffGroup(handoff);
3648
+ const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
3649
+ const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
3650
+ return {
3651
+ suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
3652
+ reference,
3653
+ };
3654
+ } catch (error) {
3655
+ return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
3656
+ }
3657
+ });
3629
3658
  } catch (error) {
3630
- return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
3631
- }
3659
+ if (admitted) throw error;
3660
+ const reference = retainSingleWorktreeHandoff(input, error);
3661
+ return { suffix: `${formatParallelHandoffError(error)}\n\n${formatParallelHandoffReference(reference)}`, reference };
3662
+ }
3663
+ }
3664
+
3665
+ function retainSingleWorktreeHandoff(input: { worktreeSetup: WorktreeSetup; artifactsDir: string; runId: string; cwd: string }, error: unknown): NonNullable<Details["parallelHandoff"]> {
3666
+ const reason = `Worktree finalization retained; manual reconciliation required: ${error instanceof Error ? error.message : String(error)}`;
3667
+ return writeParallelHandoffGroup({
3668
+ manifestPath: parallelHandoffPath(input.artifactsDir, input.runId), runId: input.runId,
3669
+ mode: "single", source: "foreground", cwd: input.cwd, stepIndex: 0, flatStartIndex: 0,
3670
+ setup: input.worktreeSetup, diffs: [], results: [],
3671
+ cleanup: { state: "partial", pruned: false, errors: [reason], tasks: input.worktreeSetup.worktrees.map((worktree) => ({
3672
+ index: worktree.index, path: worktree.path, branch: worktree.branch, provider: worktree.provider, naming: worktree.naming,
3673
+ worktreeRemoved: false, branchRemoved: false, preserved: true, reason,
3674
+ })) },
3675
+ });
3632
3676
  }
3633
3677
 
3634
3678
  async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
@@ -3709,8 +3753,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3709
3753
 
3710
3754
  const sourceCwd = effectiveCwd;
3711
3755
  let pendingHandoff: Details["parallelHandoff"];
3712
- const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
3713
- params.worktree,
3756
+ const { setup: worktreeSetup, errorResult: worktreeSetupError } = params.worktree ? await createSingleWorktreeSetup(
3714
3757
  sourceCwd,
3715
3758
  runId,
3716
3759
  params.agent!,
@@ -3722,8 +3765,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3722
3765
  deps.config.worktreeBranchPrefix,
3723
3766
  params.lane?.key ?? params.workflowKey,
3724
3767
  task,
3725
- (plannedSetup) => {
3726
- pendingHandoff = writePendingParallelHandoff({
3768
+ (progress) => {
3769
+ pendingHandoff = writeWorktreeSetupHandoff({
3727
3770
  manifestPath: parallelHandoffPath(artifactsDir, runId),
3728
3771
  runId,
3729
3772
  mode: "single",
@@ -3731,13 +3774,42 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3731
3774
  cwd: sourceCwd,
3732
3775
  stepIndex: 0,
3733
3776
  flatStartIndex: 0,
3734
- setup: plannedSetup,
3777
+ progress,
3735
3778
  laneBindings: params.workflowKey || lane ? [{ index: 0, taskIndex: 0, ...(params.workflowKey ? { workflowKey: params.workflowKey, runId } : {}), ...(lane ? { lane } : {}) }] : undefined,
3736
3779
  });
3737
3780
  },
3738
- );
3739
- if (worktreeSetupError) return worktreeSetupError;
3781
+ signal,
3782
+ data.deadlineAt,
3783
+ ) : {};
3784
+ if (worktreeSetupError) {
3785
+ if (pendingHandoff) {
3786
+ worktreeSetupError.details!.parallelHandoff = pendingHandoff;
3787
+ worktreeSetupError.content.push({ type: "text", text: formatParallelHandoffReference(pendingHandoff) });
3788
+ }
3789
+ return worktreeSetupError;
3790
+ }
3740
3791
  const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
3792
+ const cleanupSingleWorktree = async () => {
3793
+ if (!worktreeSetup) return;
3794
+ let admitted = false;
3795
+ try { await withWorktreeTransaction(() => {
3796
+ if (data.deadlineAt !== undefined && Date.now() >= data.deadlineAt) throw new Error("Run deadline expired before unlaunched worktree cleanup");
3797
+ admitted = true;
3798
+ const cleanup = cleanupWorktrees(worktreeSetup);
3799
+ pendingHandoff = writeParallelHandoffGroup({
3800
+ manifestPath: parallelHandoffPath(artifactsDir, runId), runId, mode: "single", source: "foreground",
3801
+ cwd: sourceCwd, stepIndex: 0, flatStartIndex: 0, setup: worktreeSetup, diffs: [], results: [], cleanup,
3802
+ });
3803
+ }); }
3804
+ catch (error) {
3805
+ if (admitted) throw error;
3806
+ pendingHandoff = retainSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd }, error);
3807
+ }
3808
+ };
3809
+ if (worktreeSetup && (signal?.aborted || (data.deadlineAt !== undefined && Date.now() >= data.deadlineAt))) {
3810
+ await cleanupSingleWorktree();
3811
+ return { content: [{ type: "text", text: "Subagent setup canceled before child launch." }], isError: true, details: { mode: "single", results: [], parallelHandoff: pendingHandoff } };
3812
+ }
3741
3813
 
3742
3814
  const authoredTask = task;
3743
3815
  if (shouldForkAgent(contextPolicy, params.agent!)) {
@@ -3747,8 +3819,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3747
3819
  const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
3748
3820
  const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
3749
3821
  if (validationError) {
3750
- if (worktreeSetup) cleanupWorktrees(worktreeSetup);
3751
- return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
3822
+ await cleanupSingleWorktree();
3823
+ return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [], parallelHandoff: pendingHandoff } };
3752
3824
  }
3753
3825
  const structuredRuntime = params.outputSchema
3754
3826
  ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
@@ -3808,6 +3880,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3808
3880
  }));
3809
3881
  }
3810
3882
 
3883
+ const modelResponseAliases = deps.config.modelResponseAliases === undefined ? undefined : structuredClone(deps.config.modelResponseAliases);
3811
3884
  const forwardSingleUpdate = onUpdate
3812
3885
  ? (update: AgentToolResult<Details>) => {
3813
3886
  if (foregroundControl) updateForegroundChild(foregroundControl, 0, update.details?.progress?.[0]);
@@ -3867,6 +3940,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3867
3940
  thinkingCeiling: agentConfig.maxThinking,
3868
3941
  extensionBindings: params.extensionBindings,
3869
3942
  availableModels,
3943
+ modelResponseAliases,
3870
3944
  preferredModelProvider: currentProvider,
3871
3945
  modelScope: modelScopes,
3872
3946
  skills: effectiveSkills,
@@ -3879,14 +3953,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3879
3953
  onDetachReady: (detach) => {
3880
3954
  detachForeground = detach;
3881
3955
  },
3882
- onDetachedExit: (result) => {
3956
+ onDetachedExit: async (result) => {
3883
3957
  if (resolveDetachedWorkflowChild) {
3884
3958
  resolveDetachedWorkflowChild(result);
3885
3959
  return;
3886
3960
  }
3887
3961
  try {
3888
3962
  if (worktreeSetup) {
3889
- finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result, workflowKey: params.workflowKey, lane });
3963
+ await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result, workflowKey: params.workflowKey, lane });
3890
3964
  }
3891
3965
  try {
3892
3966
  updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
@@ -3911,7 +3985,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3911
3985
  try {
3912
3986
  if (foregroundControl) finishForegroundChild(foregroundControl, 0);
3913
3987
  } finally {
3914
- removeForegroundControlIfIdle(deps.state, runId);
3988
+ removeForegroundControlIfIdle(deps.state, runId, deps.trackRetainedNestedRoute);
3915
3989
  }
3916
3990
  }
3917
3991
  }
@@ -3922,12 +3996,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3922
3996
  toolTimeoutMs: params.toolTimeoutMs,
3923
3997
  configToolTimeoutMs: data.configToolTimeoutMs,
3924
3998
  toolBudget: effectiveToolBudget.toolBudget,
3999
+ usageBudget: data.usageBudget ?? data.inheritedUsageBudget,
3925
4000
  capabilityCeiling: data.capabilityCeiling,
3926
4001
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3927
4002
  }));
3928
4003
  r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
3929
4004
  } catch (error) {
3930
- if (worktreeSetup) cleanupWorktrees(worktreeSetup);
4005
+ await cleanupSingleWorktree();
3931
4006
  throw error;
3932
4007
  } finally {
3933
4008
  // An attached runSync rejection still owns its child and structured runtime.
@@ -3942,11 +4017,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3942
4017
  recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
3943
4018
  }
3944
4019
 
3945
- let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
4020
+ let worktreeHandoff: Awaited<ReturnType<typeof finalizeSingleWorktreeHandoff>> | undefined;
3946
4021
  if (worktreeSetup) {
3947
4022
  worktreeHandoff = r.detached
3948
4023
  ? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
3949
- : finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r, workflowKey: params.workflowKey, lane });
4024
+ : await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r, workflowKey: params.workflowKey, lane });
3950
4025
  }
3951
4026
 
3952
4027
  if (r.progress) allProgress.push(r.progress);
@@ -3984,7 +4059,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3984
4059
  usageBudget: usageBudgetState(data.usageBudget, totalCost),
3985
4060
  ...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
3986
4061
  }));
3987
- rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
4062
+ rememberForegroundRun(deps.state, { modelResponseAliases, runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
3988
4063
 
3989
4064
  const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
3990
4065
  if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
@@ -4060,6 +4135,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
4060
4135
  }
4061
4136
 
4062
4137
  const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionName?: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
4138
+ const workflowOwnedUsageBudgets = new WeakMap<object, UsageBudgetConfig>();
4063
4139
 
4064
4140
  /**
4065
4141
  * Terminal-mission retention can remove a mission while its children still
@@ -4140,9 +4216,13 @@ function workflowChildResult(
4140
4216
  ? result.details.results[0].finalOutput
4141
4217
  : receiptOutput;
4142
4218
  const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
4143
- const failureError = childError && receiptOutput && receiptOutput !== childError
4144
- ? `${childError}\n\n${receiptOutput}`
4219
+ const failureErrorBase = childError && receiptOutput
4220
+ ? receiptOutput.includes(childError) ? receiptOutput : `${childError}\n\n${receiptOutput}`
4145
4221
  : childError || receiptOutput || output || "Child run failed.";
4222
+ const savedOutputEvidence = [...new Set(result.details.results.map((child) => child.savedOutputPath).filter((value): value is string => Boolean(value)))]
4223
+ .filter((savedOutputPath) => !failureErrorBase.includes(savedOutputPath))
4224
+ .map((savedOutputPath) => `Saved output: ${savedOutputPath}`);
4225
+ const failureError = [failureErrorBase, ...savedOutputEvidence].join("\n");
4146
4226
  const detached = result.details.results.some((child) => child.detached);
4147
4227
  const interrupted = result.details.results.some((child) => child.interrupted);
4148
4228
  const stopped = result.details.results.some((child) => child.stopped);
@@ -4181,8 +4261,6 @@ function workflowChildResult(
4181
4261
  }
4182
4262
  const requestedContext = childParams.context === "fresh" || childParams.context === "fork" ? childParams.context : undefined;
4183
4263
  const resolvedContext = result.details.context ?? (resolvedContexts.length === 1 ? resolvedContexts[0] : resolvedContexts.length > 1 ? "mixed" : undefined);
4184
- const resumeSourceRunId = typeof childParams.resume === "string" && childParams.resume.trim() ? childParams.resume.trim() : undefined;
4185
- const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
4186
4264
  const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
4187
4265
  ?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
4188
4266
  const outputPathMapping = typeof childParams.task === "string" ? outputPathMappingFromTask(childParams.task, outputReference) : undefined;
@@ -4215,7 +4293,7 @@ function workflowChildResult(
4215
4293
  ...(outputPathMapping ? { outputPathMapping } : {}),
4216
4294
  ...(externalAdapter ? { externalAdapter } : {}),
4217
4295
  resumability,
4218
- continuation: { runIds: continuationRunIds },
4296
+ continuation: { runIds: runId ? [runId] : [] },
4219
4297
  artifactPaths: [...artifactPaths],
4220
4298
  results: result.details.results,
4221
4299
  };
@@ -4328,10 +4406,33 @@ function missingWorkflowReceiptResumeHint(reference: WorkflowReceiptResumeRefere
4328
4406
  }
4329
4407
  }
4330
4408
 
4331
- function resolveKeyedWorkflowResume(
4332
- reference: WorkflowReceiptResumeReference,
4333
- state: SubagentState,
4334
- ): { runId: string; runIds: string[] } {
4409
+ function resolveWorkflowResume(
4410
+ reference: WorkflowReceiptResumeReference | string,
4411
+ deps: ExecutorDeps,
4412
+ parentSessionFile: string | null,
4413
+ index?: number,
4414
+ ): string | { runId: string; runIds: string[] } {
4415
+ const state = deps.state;
4416
+ if (typeof reference === "string") {
4417
+ const target = resolveRequestedResumeTarget({ id: reference.trim(), ...(index !== undefined ? { index } : {}) }, deps, parentSessionFile);
4418
+ // Live routing stays with action=resume and does not claim retained lineage.
4419
+ if (target.kind !== "revive") return reference.trim();
4420
+ const runId = target.runId;
4421
+ const status = target.source === "async" && target.asyncDir ? readStatus(target.asyncDir) : undefined;
4422
+ if (status?.runId === runId && status.parentWorkflowRunId && status.workflowKey) {
4423
+ // Only follow the validated child's recorded owner; never search other workflows.
4424
+ const receiptPath = workflowReceiptPath(DIRS.async, status.parentWorkflowRunId);
4425
+ try {
4426
+ fs.statSync(receiptPath);
4427
+ } catch (error) {
4428
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return { runId, runIds: [runId] };
4429
+ throw error;
4430
+ }
4431
+ const entry = readWorkflowReceipt(DIRS.async, status.parentWorkflowRunId).entries[status.workflowKey];
4432
+ if (entry?.latestRunId === runId) return { runId, runIds: entry.continuation.runIds };
4433
+ }
4434
+ return { runId, runIds: [runId] };
4435
+ }
4335
4436
  try {
4336
4437
  const entry = resolveWorkflowReceiptResumeEntry({
4337
4438
  reference,
@@ -4341,9 +4442,7 @@ function resolveKeyedWorkflowResume(
4341
4442
  if (target.kind !== "revive") throw new Error(`Workflow receipt child '${reference.key}' latest run '${runId}' is still running.`);
4342
4443
  },
4343
4444
  });
4344
- const runId = entry.latestRunId;
4345
- if (!runId) throw new Error(`Workflow receipt child '${reference.key}' has no retained run id.`);
4346
- return { runId, runIds: entry.continuation.runIds };
4445
+ return { runId: entry.latestRunId, runIds: entry.continuation.runIds };
4347
4446
  } catch (error) {
4348
4447
  const workflowRunId = reference.workflowRunId.trim();
4349
4448
  if (isMissingWorkflowReceiptDiagnostic(error, workflowRunId)) {
@@ -4482,7 +4581,7 @@ export function prepareWorkflowLaunchParams(
4482
4581
  const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
4483
4582
  if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
4484
4583
  const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
4485
- if (agentContract !== undefined && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
4584
+ if (agentContract !== undefined && !isAgentContract(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
4486
4585
  const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
4487
4586
  const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
4488
4587
  if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
@@ -4496,6 +4595,7 @@ export function prepareWorkflowLaunchParams(
4496
4595
  message: typeof childParams.task === "string" ? childParams.task.trim() : "",
4497
4596
  workflowParentRunId: parentWorkflowRunId,
4498
4597
  workflowKey,
4598
+ ...(options.outputClaimPath ? { workflowOutputClaimPath: options.outputClaimPath } : {}),
4499
4599
  ...(lane ? { lane } : {}),
4500
4600
  ...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
4501
4601
  ...(baseRef !== undefined ? { baseRef: baseRef as string } : {}),
@@ -4514,12 +4614,13 @@ export function prepareWorkflowLaunchParams(
4514
4614
  };
4515
4615
  }
4516
4616
  const control = mergeWorkflowControlOverrides(workflowDefaults.control, childParams.control as ControlConfig | undefined);
4617
+ const asyncOmitted = childParams.async === undefined && workflowDefaults.async === undefined;
4517
4618
  const launchParams = {
4518
4619
  ...workflowDefaults,
4519
- async: options.externalAsyncRequired === true && childParams.async === undefined && workflowDefaults.async === undefined ? true : false,
4620
+ ...(options.externalAsyncRequired === true && asyncOmitted ? { async: true } : {}),
4520
4621
  ...childParams,
4521
4622
  ...(control !== undefined ? { control } : {}),
4522
- ...(options.externalAsyncRequired === true && childParams.async === undefined && workflowDefaults.async === undefined ? { workflowAwaitAsync: true } : {}),
4623
+ ...(asyncOmitted ? { workflowAwaitAsync: true } : {}),
4523
4624
  ...(options.missionDetached ? { mission: false } : {}),
4524
4625
  workflowParentRunId: parentWorkflowRunId,
4525
4626
  workflowKey,
@@ -4657,14 +4758,14 @@ function applyWorkflowLaneTrace(graph: WorkflowGraphSnapshot, trace: WorkflowScr
4657
4758
  else delete graph.currentNodeId;
4658
4759
  }
4659
4760
 
4660
- function workflowChatProgressUpdate(
4761
+ function workflowProgressUpdate(
4661
4762
  runId: string,
4662
4763
  chatProgress: WorkflowChatProgressProjection,
4663
4764
  workflow: NonNullable<Details["workflow"]>,
4664
4765
  workflowChildren?: Details["workflowChildren"],
4665
- preflight?: import("../../shared/types.ts").WorkflowPreflightV1,
4666
- ): AgentToolResult<Details> | undefined {
4667
- if (chatProgress.mode !== "live-card") return undefined;
4766
+ preflight?: import("../../shared/types.ts").WorkflowPreflight,
4767
+ ): AgentToolResult<Details> {
4768
+ // chatProgress controls the TUI projection, not transport-level progress.
4668
4769
  return {
4669
4770
  content: [{ type: "text", text: "Workflow running." }],
4670
4771
  details: { mode: "workflow", runId, results: [], workflow, ...(preflight ? { preflight } : {}), ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
@@ -4740,6 +4841,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4740
4841
  signal: AbortSignal,
4741
4842
  ctx: ExtensionContext,
4742
4843
  ) => Promise<AgentToolResult<Details>>;
4844
+ /** Scheduled state visible to the current runtime supervisor owner only. */
4845
+ getCurrentSupervisorOwnerStates: () => Iterable<SubagentState>;
4743
4846
  } {
4744
4847
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4745
4848
  const delegatedZeroToolBudgets = new WeakSet<object>();
@@ -4748,7 +4851,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4748
4851
  const workflowResourcePermits = new WeakMap<object, WorkflowResourcePermit>();
4749
4852
  const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
4750
4853
  const warnedArtifactPackageDirs = new Set<string>();
4751
- const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
4854
+ const scheduledOwnerExecutors = new Map<string | null, Map<string, { state: SubagentState; executor: ReturnType<typeof createSubagentExecutor> }>>();
4752
4855
  const execute = async (
4753
4856
  _id: string,
4754
4857
  params: SubagentParamsLike,
@@ -4759,6 +4862,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4759
4862
  parentModelOverride?: ParentModel | null,
4760
4863
  ): Promise<AgentToolResult<Details>> => {
4761
4864
  const workflowLaunchObserver = workflowLaunchObservers.get(params);
4865
+ const inheritedUsageBudget = workflowOwnedUsageBudgets.get(params);
4762
4866
  const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
4763
4867
  const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
4764
4868
  const delegatedExecution = delegatedExecutions.has(params);
@@ -4776,7 +4880,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4776
4880
  let requestParams = normalizedGate.params;
4777
4881
  const capacityOverrideError = validateWorkflowCapacityOverrides(requestParams);
4778
4882
  if (capacityOverrideError) return buildRequestedModeError(requestParams, capacityOverrideError);
4779
- let workflowPreflight: import("../../shared/types.ts").WorkflowPreflightV1 | undefined;
4883
+ let workflowPreflight: import("../../shared/types.ts").WorkflowPreflight | undefined;
4780
4884
  try {
4781
4885
  if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
4782
4886
  throw new Error("preflight requires workflowScript or workflowScriptPath.");
@@ -4807,7 +4911,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4807
4911
  }
4808
4912
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4809
4913
  if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
4810
- let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenanceV1; authority: WorkflowResourceAuthority } | undefined;
4914
+ let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenance; authority: WorkflowResourceAuthority } | undefined;
4811
4915
  if (workflowResourcePermit) {
4812
4916
  if (typeof requestParams.workflowScript !== "string") return buildRequestedModeError(requestParams, "Resolved workflow resource is missing its workflow script.");
4813
4917
  const consumed = consumeWorkflowResourcePermit(workflowResourcePermit, requestParams.workflowScript);
@@ -4815,8 +4919,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4815
4919
  workflowResource = { permit: workflowResourcePermit, ...consumed };
4816
4920
  }
4817
4921
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4922
+ const acceptanceErrors = validateAcceptanceInput(requestParams.acceptance);
4923
+ if (acceptanceErrors.length > 0) return buildRequestedModeError(requestParams, acceptanceErrors.join(" "));
4924
+ const foregroundWorkflowRunId = encodeIndexSegment(_id);
4818
4925
  if (delegatedWorkflowPermit) {
4819
- const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
4926
+ const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, foregroundWorkflowRunId);
4820
4927
  if (permitError) return buildRequestedModeError(requestParams, permitError);
4821
4928
  if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
4822
4929
  }
@@ -4991,18 +5098,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4991
5098
  onSuccess: (_filePath, payload) => { indexedState = (payload as { state: AsyncStatus["state"] }).state; },
4992
5099
  onError: (error, filePath) => console.error(`Failed to update async workflow index '${filePath}':`, error),
4993
5100
  });
4994
- const queueActiveRunIndex = (): void => {
4995
- const state = status.state;
5101
+ const queueActiveRunIndex = (snapshot: AsyncStatus = status): void => {
5102
+ const state = snapshot.state;
4996
5103
  if (indexedState === state && indexPersistence.pendingCount() === 0) return;
4997
- indexPersistence.write(asyncDir, { state, toolCallId: status.toolCallId }, (_filePath, payload) => {
5104
+ indexPersistence.write(asyncDir, { state, toolCallId: snapshot.toolCallId }, (_filePath, payload) => {
4998
5105
  const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
4999
5106
  updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
5000
5107
  });
5001
5108
  };
5109
+ let pendingResultPublication: Promise<boolean> | undefined;
5110
+ let settleResultPublication: ((published: boolean) => void) | undefined;
5111
+ const reportResultWriteFailure = (error: unknown): void => {
5112
+ const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
5113
+ console.error(message, error);
5114
+ appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
5115
+ };
5002
5116
  const runPersistence = createCapacityResilientJsonWriter({
5003
5117
  keepAlive: true,
5004
- onSuccess: (filePath) => { if (filePath === statusPath) queueActiveRunIndex(); },
5005
- onError: (error, filePath) => console.error(`Failed to persist async workflow state '${filePath}':`, error),
5118
+ onSuccess: (filePath, payload) => {
5119
+ if (filePath === statusPath) queueActiveRunIndex(payload as AsyncStatus);
5120
+ if (filePath === resultPath) settleResultPublication?.(true);
5121
+ },
5122
+ onError: (error, filePath) => {
5123
+ if (filePath === resultPath) { reportResultWriteFailure(error); settleResultPublication?.(false); }
5124
+ else console.error(`Failed to persist async workflow state '${filePath}':`, error);
5125
+ },
5006
5126
  write: (filePath, payload) => filePath === resultPath
5007
5127
  ? writeAsyncResultFile(filePath, payload as Record<string, unknown>)
5008
5128
  : writeAtomicJson(filePath, payload),
@@ -5030,7 +5150,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5030
5150
  queueActiveRunIndex();
5031
5151
  } else if (options.tolerateStatusWriteFailure) {
5032
5152
  try {
5033
- runPersistence.write(statusPath, status);
5153
+ runPersistence.write(statusPath, { ...status });
5034
5154
  statusPersistenceDegraded = false;
5035
5155
  } catch (error) {
5036
5156
  const message = `Failed to persist async workflow state ${statusPath}: ${error instanceof Error ? error.message : String(error)}`;
@@ -5045,7 +5165,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5045
5165
  }
5046
5166
  }
5047
5167
  } else {
5048
- runPersistence.write(statusPath, status);
5168
+ runPersistence.write(statusPath, { ...status });
5049
5169
  }
5050
5170
  if (liveJob) {
5051
5171
  liveJob.status = status.state;
@@ -5073,12 +5193,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5073
5193
  };
5074
5194
  const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
5075
5195
  try {
5196
+ let resultPublished = false;
5197
+ settleResultPublication = (published) => { resultPublished = published; };
5198
+ pendingResultPublication = undefined;
5076
5199
  runPersistence.write(resultPath, payload);
5200
+ // Only capacity deferral yields; ordinary publication remains synchronous.
5201
+ if (!resultPublished) pendingResultPublication = new Promise<boolean>((resolve) => { settleResultPublication = resolve; });
5077
5202
  return true;
5078
5203
  } catch (error) {
5079
- const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
5080
- console.error(message, error);
5081
- appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
5204
+ reportResultWriteFailure(error);
5082
5205
  return false;
5083
5206
  }
5084
5207
  };
@@ -5118,7 +5241,148 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5118
5241
  return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
5119
5242
  }
5120
5243
  appendWorkflowEvent({ type: "subagent.workflow.started" });
5244
+ // The runner does not execute workflow scripts: this active owner must consume its own
5245
+ // durable steer inbox, deliver through in-process child controls, and persist receipts.
5246
+ const emitWorkflowSteerEvent = (type: string, requestId: string, index?: number, extra: Record<string, unknown> = {}): void => {
5247
+ appendWorkflowEvent({ type, requestId, ...(index !== undefined ? { index } : {}), ...extra });
5248
+ };
5249
+ const recordWorkflowSteerTargets = (request: SteerRequest, targets: Array<{ index: number; state: SteeringTargetState; reason?: string }>): void => {
5250
+ status.steering ??= createSteeringStatus();
5251
+ recordSteeringRequest(steeringStatus(status), {
5252
+ id: request.id,
5253
+ requestedAt: request.ts,
5254
+ ...(request.source ? { source: request.source } : {}),
5255
+ message: request.message,
5256
+ targets,
5257
+ });
5258
+ };
5259
+ const explicitWorkflowSteerIndexes = (request: SteerRequest): number[] | undefined => request.targetIndex !== undefined ? [request.targetIndex] : request.targetIndexes;
5260
+ const failWorkflowSteer = (request: SteerRequest, indexes: number[], reason: string): void => {
5261
+ const targets = indexes.map((index) => ({ index, state: "failed" as const, reason }));
5262
+ recordWorkflowSteerTargets(request, targets);
5263
+ emitWorkflowSteerEvent("subagent.steer.requested", request.id, undefined, { targets });
5264
+ for (const index of indexes) emitWorkflowSteerEvent("subagent.steer.failed", request.id, index, { reason });
5265
+ persist({ tolerateStatusWriteFailure: true });
5266
+ };
5267
+ const pendingWorkflowSteers = new Set<(state: "delivered" | "queued" | "failed", reason?: string) => void>();
5268
+ const deliverWorkflowSteerRequest = (request: SteerRequest): void => {
5269
+ let indexes = explicitWorkflowSteerIndexes(request);
5270
+ if (status.state !== "running") {
5271
+ failWorkflowSteer(request, indexes ?? [0], `run became ${status.state} before steering request was consumed`);
5272
+ return;
5273
+ }
5274
+ const liveControls = [...deps.state.foregroundControls.values()].filter((candidate) => candidate.parentWorkflowRunId === workflowRunId && (candidate.activeChildren?.size ?? 0) > 0);
5275
+ if (indexes === undefined) {
5276
+ if (liveControls.length !== 1) {
5277
+ failWorkflowSteer(request, [0], liveControls.length > 1
5278
+ ? `workflow has ${liveControls.length} live children; steer a child run id instead`
5279
+ : "workflow child is not live in this process");
5280
+ return;
5281
+ }
5282
+ const index = status.steps?.findIndex((step) => !!step.workflowKey && step.workflowKey === liveControls[0]!.workflowKey) ?? -1;
5283
+ if (index < 0) {
5284
+ failWorkflowSteer(request, [0], "live workflow child has no projected step");
5285
+ return;
5286
+ }
5287
+ indexes = [index];
5288
+ }
5289
+ // Normalize and record the whole set once: recordSteeringRequest is idempotent by id.
5290
+ // Explicit indexes resolve only by their projected key, never by a live sibling.
5291
+ const resolved = indexes.map((index) => {
5292
+ const step = status.steps?.[index];
5293
+ const control = step?.workflowKey ? liveControls.find((candidate) => candidate.workflowKey === step.workflowKey) : undefined;
5294
+ return { index, control, reason: !step ? "child index out of range" : !control ? "workflow child is not live in this process" : undefined };
5295
+ });
5296
+ const targets = resolved.map(({ index, reason }) => ({ index, state: reason ? "failed" as const : "routed" as const, ...(reason ? { reason } : {}) }));
5297
+ recordWorkflowSteerTargets(request, targets);
5298
+ emitWorkflowSteerEvent("subagent.steer.requested", request.id, undefined, { targets });
5299
+ persist({ tolerateStatusWriteFailure: true });
5300
+ for (const { index, control, reason } of resolved) {
5301
+ if (!control) {
5302
+ emitWorkflowSteerEvent("subagent.steer.failed", request.id, index, { reason });
5303
+ continue;
5304
+ }
5305
+ emitWorkflowSteerEvent("subagent.steer.routed", request.id, index);
5306
+ const applyWorkflowSteerDelivery = (state: "delivered" | "queued" | "failed", reason?: string): void => {
5307
+ // Shutdown settles unresolved receipts synchronously; late SDK callbacks cannot rewrite them.
5308
+ if (!pendingWorkflowSteers.has(applyWorkflowSteerDelivery)) return;
5309
+ // Queuing is not confirmation of delivery: retain ownership until terminal settlement.
5310
+ if (state !== "queued") pendingWorkflowSteers.delete(applyWorkflowSteerDelivery);
5311
+ const now = Date.now();
5312
+ const steering = steeringStatus(status);
5313
+ // This callback owns a pending target, even after bounded display history
5314
+ // evicts its request. Account independently; queued still represents one pending target.
5315
+ if (state !== "queued") steering.pending = Math.max(0, steering.pending - 1);
5316
+ if (state === "delivered") {
5317
+ steering.delivered++;
5318
+ steering.lastDeliveredAt = now;
5319
+ } else if (state === "failed") steering.failed++;
5320
+ const receipt = steering.recent.find((candidate) => candidate.id === request.id)?.targets.find((target) => target.index === index);
5321
+ if (receipt) {
5322
+ receipt.state = state;
5323
+ if (state === "delivered") receipt.deliveredAt = now;
5324
+ if (state === "failed") receipt.failedAt = now;
5325
+ if (reason) receipt.reason = reason;
5326
+ }
5327
+ if (state === "failed") {
5328
+ const failedStep = status.steps?.[index];
5329
+ if (failedStep) failedStep.activityState = "needs_attention";
5330
+ status.activityState = "needs_attention";
5331
+ }
5332
+ emitWorkflowSteerEvent(`subagent.steer.${state}`, request.id, index, state === "failed" ? { reason } : { deliveryStatus: state, message: request.message });
5333
+ persist({ tolerateStatusWriteFailure: true });
5334
+ };
5335
+ pendingWorkflowSteers.add(applyWorkflowSteerDelivery);
5336
+ void steerWorkflowForegroundTarget({
5337
+ target: { control, workflowRunId, sourceRunId: workflowRunId },
5338
+ message: request.message,
5339
+ ...(request.mode ? { mode: request.mode } : {}),
5340
+ }).then(
5341
+ (result) => {
5342
+ const target = result.details.steering?.targets[0];
5343
+ applyWorkflowSteerDelivery(target?.state === "delivered" ? "delivered" : target?.state === "queued" ? "queued" : "failed", target?.reason ?? (target ? undefined : "steering produced no target receipt"));
5344
+ },
5345
+ (error) => applyWorkflowSteerDelivery("failed", error instanceof Error ? error.message : String(error)),
5346
+ );
5347
+ }
5348
+ };
5349
+ let disposeWorkflowSteerInbox: (() => void) | undefined;
5350
+ try {
5351
+ disposeWorkflowSteerInbox = watchAsyncControlInbox(asyncDir, {
5352
+ onSteer: deliverWorkflowSteerRequest,
5353
+ onError: (error, phase, request) => {
5354
+ const reason = error instanceof Error ? error.message : String(error);
5355
+ appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase, error: reason });
5356
+ if (request) failWorkflowSteer(request, explicitWorkflowSteerIndexes(request) ?? [0], `steer consumer failed: ${reason}`);
5357
+ },
5358
+ });
5359
+ } catch (error) {
5360
+ // Steering is an optional control surface, so a watcher that cannot install must not fail the run.
5361
+ appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", error: error instanceof Error ? error.message : String(error) });
5362
+ }
5363
+ deps.activateSupervisorTransport?.();
5121
5364
  const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
5365
+ let workflowSteerInboxClosed = false;
5366
+ const settleWorkflowSteerInbox = (outcome = status.state): void => {
5367
+ if (workflowSteerInboxClosed) return;
5368
+ workflowSteerInboxClosed = true;
5369
+ disposeWorkflowSteerInbox?.();
5370
+ // Consumed deliveries are not files anymore. Fail unresolved SDK operations without awaiting them.
5371
+ for (const settle of pendingWorkflowSteers) settle("failed", `run became ${outcome} before steering delivery settled; delivery unconfirmed`);
5372
+ try {
5373
+ closeSteerInbox(asyncDir, outcome);
5374
+ // Separately drain requests that never reached the consumer.
5375
+ const undelivered = consumeSteerRequests(asyncDir, undefined, (error) => {
5376
+ appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase: "drain", error: error instanceof Error ? error.message : String(error) });
5377
+ });
5378
+ for (const request of undelivered) failWorkflowSteer(request, explicitWorkflowSteerIndexes(request) ?? [0], `run became ${outcome} before steering request was consumed`);
5379
+ } catch (error) {
5380
+ appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase: "close", error: error instanceof Error ? error.message : String(error) });
5381
+ }
5382
+ // Commit receipts while run status is still nonterminal. Result/index publication
5383
+ // can fail next; teardown must not publish the subsequently computed terminal status.
5384
+ persist();
5385
+ };
5122
5386
  void Promise.resolve().then(async () => {
5123
5387
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
5124
5388
  const workflowResults: SingleResult[] = [];
@@ -5126,7 +5390,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5126
5390
  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;
5127
5391
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5128
5392
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5129
- const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
5393
+ const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
5130
5394
  const claimedOutputPaths = new Map<string, string>();
5131
5395
  const childOutputOverrides = new Map<string, string>();
5132
5396
  const childOutputClaimPaths = new Map<string, string>();
@@ -5311,6 +5575,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5311
5575
  deps.asyncByDefault,
5312
5576
  );
5313
5577
  preparedChildParams = childRequest;
5578
+ if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
5314
5579
  workflowLaunchObservers.set(childRequest, (launch) => {
5315
5580
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
5316
5581
  if (step) {
@@ -5359,7 +5624,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5359
5624
  if (child.runId) workflowChildRunIds.set(key, child.runId);
5360
5625
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
5361
5626
  if (step) {
5362
- step.async = Boolean(result.details.asyncId || result.details.asyncDir);
5627
+ step.async = step.async === true || Boolean(result.details.asyncId || result.details.asyncDir);
5363
5628
  if (child.runId) step.runId = child.runId;
5364
5629
  if (child.lane) step.lane = child.lane;
5365
5630
  }
@@ -5381,7 +5646,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5381
5646
  return child;
5382
5647
  },
5383
5648
  status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
5384
- resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
5649
+ resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
5385
5650
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
5386
5651
  });
5387
5652
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
@@ -5392,16 +5657,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5392
5657
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
5393
5658
  const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5394
5659
  const workflowUsage = sumResultsUsage(workflowResults);
5660
+ settleWorkflowSteerInbox("complete");
5395
5661
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
5396
5662
  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) };
5397
5663
  const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5664
+ delete status.workflowReceiptPath;
5398
5665
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5399
5666
  try {
5400
5667
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
5668
+ status.workflowReceiptPath = workflowReceipt.path;
5401
5669
  } catch (receiptError) {
5402
5670
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
5403
5671
  }
5404
5672
  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;
5673
+ if (pendingResultPublication && !await pendingResultPublication) return;
5405
5674
  persist();
5406
5675
  deps.refreshResultDelivery?.();
5407
5676
  persistClosed = true;
@@ -5425,10 +5694,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5425
5694
  }
5426
5695
  }
5427
5696
  const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
5697
+ settleWorkflowSteerInbox(state);
5428
5698
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
5429
5699
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5430
5700
  const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5431
- 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 });
5701
+ 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 });
5432
5702
  if (pauseForDetached) {
5433
5703
  const promoted = promotePausedWorkflowIfSettled(status);
5434
5704
  if (promoted) status = promoted;
@@ -5441,18 +5711,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5441
5711
  const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
5442
5712
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5443
5713
  const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5714
+ delete status.workflowReceiptPath;
5444
5715
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5445
5716
  try {
5446
5717
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
5718
+ status.workflowReceiptPath = workflowReceipt.path;
5447
5719
  } catch (receiptError) {
5448
5720
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
5449
5721
  }
5450
5722
  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;
5723
+ if (pendingResultPublication && !await pendingResultPublication) return;
5451
5724
  persist();
5452
5725
  deps.refreshResultDelivery?.();
5453
5726
  persistClosed = true;
5454
5727
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
5455
5728
  } finally {
5729
+ // Idempotent cleanup only: a failed result/index write must not authorize terminal status.
5730
+ try {
5731
+ settleWorkflowSteerInbox();
5732
+ } catch (error) {
5733
+ console.error(`Failed to close async workflow steer inbox '${asyncDir}':`, error);
5734
+ }
5456
5735
  persistClosed = true;
5457
5736
  deps.state.workflowControllers?.delete(workflowRunId);
5458
5737
  deps.state.workflowChildStops?.delete(workflowRunId);
@@ -5468,7 +5747,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5468
5747
  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;
5469
5748
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5470
5749
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5471
- const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id));
5750
+ const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
5472
5751
  const claimedOutputPaths = new Map<string, string>();
5473
5752
  const childOutputOverrides = new Map<string, string>();
5474
5753
  const childOutputClaimPaths = new Map<string, string>();
@@ -5478,7 +5757,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5478
5757
  const runHostCommand = workflowHostCommandRunner({
5479
5758
  workflowCwd,
5480
5759
  artifactsDir: workflowArtifactsDir,
5481
- workflowRunId: _id,
5760
+ workflowRunId: foregroundWorkflowRunId,
5482
5761
  claimedOutputPaths,
5483
5762
  producedOutputPaths: producedChildOutputPaths,
5484
5763
  ...(workflowResource ? { authorize: (key, params) => authorizeWorkflowResourceHost(workflowResource!.permit, key, params.command) } : {}),
@@ -5488,19 +5767,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5488
5767
  : workflowResource
5489
5768
  ? workflowResource.authority.host ? runHostCommand : undefined
5490
5769
  : publicExecution ? undefined : runHostCommand;
5491
- const workflowHostSteps = new Map<string, HostStepNodeV1>();
5770
+ const workflowHostSteps = new Map<string, HostStepNode>();
5492
5771
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
5493
- let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
5772
+ const childProgress = new Map<string, ReturnType<typeof workflowChildProgress>>();
5773
+ let activityTimer: ReturnType<typeof setTimeout> | undefined;
5774
+ let lastProgressAt = 0;
5775
+ let workflowProgressClosed = false;
5494
5776
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
5495
5777
  const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
5496
5778
  const sendWorkflowProgress = () => {
5497
- const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight);
5498
- if (update) onUpdate?.(update);
5779
+ if (workflowProgressClosed) return;
5780
+ if (activityTimer) clearTimeout(activityTimer);
5781
+ activityTimer = undefined;
5782
+ lastProgressAt = Date.now();
5783
+ const liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false, trace: liveWorkflow.trace, progress: childProgress });
5784
+ onUpdate?.(workflowProgressUpdate(foregroundWorkflowRunId, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight));
5499
5785
  };
5500
5786
  try {
5501
5787
  const workflow = await runWorkflowScript({
5502
5788
  script: requestParams.workflowScript,
5503
- ...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
5789
+ ...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, foregroundWorkflowRunId, key) } } : {}),
5504
5790
  globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
5505
5791
  timeoutMs: timeout,
5506
5792
  signal,
@@ -5513,11 +5799,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5513
5799
  const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
5514
5800
  const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
5515
5801
  liveWorkflow = { ...liveWorkflow, trace: projectedTrace, ...(preflightWarnings.length ? { preflightWarnings } : {}) };
5516
- liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false, trace });
5802
+ for (const entry of trace) {
5803
+ if (entry.operation === "run" && ["completed", "failed", "stopped", "detached"].includes(entry.state)) childProgress.delete(entry.key);
5804
+ }
5517
5805
  sendWorkflowProgress();
5518
5806
  },
5519
5807
  admit: (calls) => {
5520
- const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
5808
+ 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 });
5521
5809
  if (outputClaims.error) throw new Error(outputClaims.error);
5522
5810
  claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5523
5811
  if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
@@ -5537,7 +5825,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5537
5825
  if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
5538
5826
  const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
5539
5827
  const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
5540
- recordMissionWorkflowChild(missionBinding, _id, key, {
5828
+ recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
5541
5829
  status: "running",
5542
5830
  ...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
5543
5831
  ...(childLabel ? { label: childLabel } : {}),
@@ -5545,13 +5833,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5545
5833
  heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
5546
5834
  });
5547
5835
  let preparedChildParams: SubagentParamsLike | undefined;
5548
- const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
5836
+ const result = await runMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, childPhase, () => {
5549
5837
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
5550
- { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), outputClaimPath: childOutputClaimPaths.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
5838
+ { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams: delegatedWorkflowPermit ? { ...childParams, async: false } : childParams, parentWorkflowRunId: foregroundWorkflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), outputClaimPath: childOutputClaimPaths.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
5551
5839
  missionBinding,
5552
5840
  deps.asyncByDefault,
5553
5841
  );
5554
5842
  preparedChildParams = childRequest;
5843
+ if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
5555
5844
  if (delegatedWorkflowPermit) {
5556
5845
  if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
5557
5846
  const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
@@ -5559,9 +5848,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5559
5848
  ? resolveAgentName(childRequest.agent, discoverWorkflowAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
5560
5849
  : undefined;
5561
5850
  if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
5562
- workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: _id, childKey: key } });
5851
+ workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: foregroundWorkflowRunId, childKey: key } });
5563
5852
  }
5564
- workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
5853
+ workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
5565
5854
  status: "running",
5566
5855
  agent: launch.agent,
5567
5856
  ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}),
@@ -5569,8 +5858,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5569
5858
  return execute(randomUUID(), childRequest, workflowSignal, (update) => {
5570
5859
  const progress = update.details.progress?.[0];
5571
5860
  if (!progress) return;
5861
+ if (onUpdate && !workflowProgressClosed) {
5862
+ if (progress.status === "running") childProgress.set(key, workflowChildProgress(progress));
5863
+ else childProgress.delete(key);
5864
+ if (!activityTimer) {
5865
+ const delay = 100 - (Date.now() - lastProgressAt);
5866
+ if (delay <= 0) sendWorkflowProgress();
5867
+ else activityTimer = setTimeout(sendWorkflowProgress, delay);
5868
+ }
5869
+ }
5572
5870
  const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
5573
- recordMissionWorkflowChild(missionBinding, _id, key, {
5871
+ recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
5574
5872
  status: progressStatus,
5575
5873
  heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
5576
5874
  });
@@ -5584,7 +5882,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5584
5882
  const child = workflowChildResult(key, result, preparedChildParams ?? childParams, deps.state);
5585
5883
  if (child.runId) workflowChildRunIds.set(key, child.runId);
5586
5884
  const childStatus = missionWorkflowChildStatus(result);
5587
- recordMissionWorkflowChild(missionBinding, _id, key, {
5885
+ recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
5588
5886
  status: childStatus,
5589
5887
  ...(child.runId ? { runId: child.runId } : {}),
5590
5888
  ...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
@@ -5596,13 +5894,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5596
5894
  return child;
5597
5895
  },
5598
5896
  status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
5599
- resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
5600
- steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
5897
+ resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
5898
+ steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: foregroundWorkflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
5601
5899
  });
5602
5900
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
5603
5901
  const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
5604
- const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
5605
- const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance);
5902
+ const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
5903
+ const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance);
5606
5904
  const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
5607
5905
  const sections = [
5608
5906
  ...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
@@ -5620,11 +5918,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5620
5918
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5621
5919
  return attachWorkflowMission(withRunFanoutBudget({
5622
5920
  content: [{ type: "text", text: displayText }],
5623
- details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflowDetailsResults(workflow.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
5921
+ details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(workflow.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
5624
5922
  }, workflowFanoutBudget));
5625
5923
  } catch (error) {
5626
5924
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
5627
- const text = workflowFailureMessage(error, _id, partial.children);
5925
+ const text = workflowFailureMessage(error, foregroundWorkflowRunId, partial.children);
5628
5926
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5629
5927
  const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5630
5928
  const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
@@ -5641,14 +5939,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5641
5939
  const workflowText = sections.join("\n\n");
5642
5940
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
5643
5941
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5644
- const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
5942
+ const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
5645
5943
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5646
- const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance);
5944
+ const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance);
5647
5945
  return attachWorkflowMission(withRunFanoutBudget({
5648
5946
  content: [{ type: "text", text: displayText }],
5649
5947
  isError: true,
5650
- details: compactOptional<Details>({ mode: "workflow", runId: _id, 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 }),
5948
+ 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 }),
5651
5949
  }, workflowFanoutBudget));
5950
+ } finally {
5951
+ workflowProgressClosed = true;
5952
+ if (activityTimer) clearTimeout(activityTimer);
5953
+ childProgress.clear();
5652
5954
  }
5653
5955
  }
5654
5956
  const directParams = requestParams;
@@ -5719,10 +6021,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5719
6021
  }
5720
6022
  if (!confirmed) return { content: [{ type: "text", text: "Worktree discard canceled; preserved worktrees were not changed." }], details: { mode: "management", results: [] } };
5721
6023
  try {
5722
- const discarded = discardPreservedWorktrees(
5723
- path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath),
6024
+ const manifestPath = path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath);
6025
+ const discarded = await withWorktreeTransaction(() => discardPreservedWorktrees(
6026
+ manifestPath,
5724
6027
  { kind: decision === "confirm" ? "confirmed" : "policy", ...(deps.config.authorityPolicy ? { policy: deps.config.authorityPolicy } : {}) },
5725
- );
6028
+ ));
5726
6029
  return { content: [{ type: "text", text: discarded.text }], details: { mode: "management", results: [] } };
5727
6030
  } catch (error) {
5728
6031
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
@@ -5991,7 +6294,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5991
6294
  }
5992
6295
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5993
6296
  }
5994
- if (paramsWithResolvedCwd.view === "fleet") {
6297
+ if (paramsWithResolvedCwd.view === "fleet" || paramsWithResolvedCwd.view === "transcript") {
5995
6298
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5996
6299
  }
5997
6300
  if (targetRunId) {
@@ -6000,12 +6303,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6000
6303
  if (resolved?.kind === "foreground") {
6001
6304
  const foreground = getForegroundControl(deps.state, resolved.id);
6002
6305
  if (foreground) {
6003
- if (paramsWithResolvedCwd.view === "transcript") {
6004
- return withBudget({
6005
- 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." }],
6006
- details: { mode: "management", results: [] },
6007
- });
6008
- }
6009
6306
  return withBudget(foregroundStatusResult(foreground));
6010
6307
  }
6011
6308
  }
@@ -6015,13 +6312,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6015
6312
  }
6016
6313
  } else if (!hasDirectoryTarget) {
6017
6314
  const foreground = getForegroundControl(deps.state, undefined);
6018
- if (foreground && paramsWithResolvedCwd.view !== "transcript") return withBudget(foregroundStatusResult(foreground));
6019
- if (foreground && paramsWithResolvedCwd.view === "transcript") {
6020
- return withBudget({
6021
- 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." }],
6022
- details: { mode: "management", results: [] },
6023
- });
6024
- }
6315
+ if (foreground) return withBudget(foregroundStatusResult(foreground));
6025
6316
  }
6026
6317
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
6027
6318
  }
@@ -6042,9 +6333,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6042
6333
  const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
6043
6334
  const directoryStatus = location.asyncDir ? readStatus(location.asyncDir) : null;
6044
6335
  if (directoryStatus?.mode === "workflow") {
6045
- const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
6046
- if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
6047
- return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
6336
+ return steerWorkflowRun({ state: deps.state, runId, asyncDir: location.asyncDir!, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
6048
6337
  }
6049
6338
  if (location.asyncDir) {
6050
6339
  const unsupported = externalRunnerControlError(location.asyncDir, "steer");
@@ -6052,6 +6341,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6052
6341
  }
6053
6342
  return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
6054
6343
  state: deps.state,
6344
+ findPendingAsks: deps.findPendingAsks,
6055
6345
  runId,
6056
6346
  message,
6057
6347
  mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
@@ -6089,9 +6379,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6089
6379
  if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
6090
6380
  const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
6091
6381
  if (resolvedStatus?.mode === "workflow") {
6092
- const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
6093
- if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
6094
- return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
6382
+ return steerWorkflowRun({ state: deps.state, runId: resolved.id, asyncDir: resolved.location.asyncDir!, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
6095
6383
  }
6096
6384
  if (resolved.location.asyncDir) {
6097
6385
  const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
@@ -6099,6 +6387,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6099
6387
  }
6100
6388
  return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
6101
6389
  state: deps.state,
6390
+ findPendingAsks: deps.findPendingAsks,
6102
6391
  runId: resolved.id,
6103
6392
  message,
6104
6393
  mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
@@ -6300,6 +6589,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6300
6589
  config: deps.config,
6301
6590
  currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
6302
6591
  runtimeAgentOwner: deps.pi,
6592
+ onAgentsChanged: deps.onAgentsChanged,
6303
6593
  });
6304
6594
  }
6305
6595
 
@@ -6551,7 +6841,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6551
6841
 
6552
6842
  let sessionRoot: string;
6553
6843
  if (effectiveParams.sessionDir) {
6554
- sessionRoot = path.resolve(deps.expandTilde(effectiveParams.sessionDir));
6844
+ // An explicit sessionDir is a root keyed by this launch's run id so
6845
+ // concurrent children resolve distinct per-child session files.
6846
+ sessionRoot = path.join(path.resolve(deps.expandTilde(effectiveParams.sessionDir)), runId);
6555
6847
  } else {
6556
6848
  const baseSessionRoot = deps.config.defaultSessionDir
6557
6849
  ? path.resolve(deps.expandTilde(deps.config.defaultSessionDir))
@@ -6689,6 +6981,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6689
6981
  timeoutMs: foregroundTimeout.timeoutMs,
6690
6982
  toolBudget: runToolBudget.toolBudget,
6691
6983
  usageBudget: usageBudget.budget,
6984
+ inheritedUsageBudget,
6692
6985
  allowZeroToolBudget,
6693
6986
  configToolBudget: configToolBudget.toolBudget,
6694
6987
  configToolTimeoutMs: deps.config.toolTimeoutMs,
@@ -6902,7 +7195,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6902
7195
  if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
6903
7196
  if (foregroundControl) {
6904
7197
  settleForegroundSchedulingOwner(foregroundControl);
6905
- removeForegroundControlIfIdle(deps.state, runId);
7198
+ removeForegroundControlIfIdle(deps.state, runId, deps.trackRetainedNestedRoute);
6906
7199
  }
6907
7200
  }
6908
7201
 
@@ -6949,7 +7242,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6949
7242
  }
6950
7243
  let publicParams = normalized.params as SubagentParamsLike;
6951
7244
  if (publicParams.workflow !== undefined) {
6952
- const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args);
7245
+ const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
6953
7246
  if (!resolved.ok) return Promise.resolve({ content: [{ type: "text", text: resolved.error }], isError: true, details: { mode: "workflow", results: [] } });
6954
7247
  const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
6955
7248
  publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
@@ -6996,16 +7289,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6996
7289
  ctx: ExtensionContext,
6997
7290
  ) => {
6998
7291
  const ownerSessionId = resolveCurrentSessionId(ctx.sessionManager);
6999
- let ownerExecutor = scheduledOwnerExecutors.get(ownerSessionId);
7000
- if (!ownerExecutor) {
7001
- ownerExecutor = createSubagentExecutor({
7002
- ...deps,
7003
- state: createScheduledOwnerState(deps.state, ownerSessionId, ctx),
7004
- });
7005
- scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
7292
+ const runtimeOwnerId = ctx.sessionManager.getSessionId() || null;
7293
+ let ownerExecutors = scheduledOwnerExecutors.get(runtimeOwnerId);
7294
+ if (!ownerExecutors) {
7295
+ ownerExecutors = new Map();
7296
+ scheduledOwnerExecutors.set(runtimeOwnerId, ownerExecutors);
7006
7297
  }
7007
- return ownerExecutor.executePublic(id, params, signal, undefined, ctx);
7298
+ let owner = ownerExecutors.get(ownerSessionId);
7299
+ if (!owner) {
7300
+ const state = createScheduledOwnerState(deps.state, ownerSessionId, ctx);
7301
+ owner = { state, executor: createSubagentExecutor({ ...deps, state }) };
7302
+ ownerExecutors.set(ownerSessionId, owner);
7303
+ }
7304
+ return owner.executor.executePublic(id, params, signal, undefined, ctx);
7008
7305
  };
7009
7306
 
7010
- return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled };
7307
+ function* getCurrentSupervisorOwnerStates(): Iterable<SubagentState> {
7308
+ const ownerId = deps.state.supervisorOwnerSessionId;
7309
+ if (!ownerId) return;
7310
+ // File transitions remain separate entries; other runtime owners are never scanned.
7311
+ for (const owner of scheduledOwnerExecutors.get(ownerId)?.values() ?? []) yield owner.state;
7312
+ }
7313
+
7314
+ return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled, getCurrentSupervisorOwnerStates };
7011
7315
  }