pi-subagents 0.65.1 → 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 (85) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +1 -1
  3. package/agents/researcher.md +23 -13
  4. package/docs/agents.md +16 -2
  5. package/docs/configuration.md +18 -0
  6. package/docs/extension-api.md +91 -0
  7. package/docs/models.md +58 -1
  8. package/docs/observability.md +42 -2
  9. package/docs/tool-reference.md +3 -3
  10. package/docs/workflows.md +14 -7
  11. package/package.json +2 -1
  12. package/skills/pi-subagents/references/execution-controls.md +14 -1
  13. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  14. package/src/agents/advertised-agent-prompt.ts +63 -0
  15. package/src/agents/agent-management.ts +14 -1
  16. package/src/agents/agent-serializer.ts +2 -0
  17. package/src/agents/agents.ts +8 -0
  18. package/src/api/shared-types.ts +1 -1
  19. package/src/api/workflow-resources.ts +6 -0
  20. package/src/extension/index.ts +40 -2
  21. package/src/extension/public-execution.ts +0 -1
  22. package/src/extension/rpc.ts +4 -21
  23. package/src/extension/schemas.ts +8 -6
  24. package/src/extension/tool-description.ts +6 -5
  25. package/src/intercom/native-supervisor-channel.ts +82 -54
  26. package/src/runs/background/active-async-capacity.ts +18 -18
  27. package/src/runs/background/async-job-tracker.ts +35 -3
  28. package/src/runs/background/async-status-snapshot.ts +10 -12
  29. package/src/runs/background/async-status.ts +17 -9
  30. package/src/runs/background/auto-drain.ts +40 -29
  31. package/src/runs/background/chain-root-attachment.ts +8 -0
  32. package/src/runs/background/control-channel.ts +78 -44
  33. package/src/runs/background/notify.ts +86 -12
  34. package/src/runs/background/owned-process-tree.ts +6 -6
  35. package/src/runs/background/process-terminal.ts +23 -23
  36. package/src/runs/background/run-child-session.ts +60 -32
  37. package/src/runs/background/run-status.ts +75 -5
  38. package/src/runs/background/runner-aliases.ts +18 -7
  39. package/src/runs/background/runner-child-launch.ts +86 -0
  40. package/src/runs/background/stale-run-reconciler.ts +3 -1
  41. package/src/runs/background/subagent-runner.ts +412 -209
  42. package/src/runs/background/subagent-wait.ts +3 -0
  43. package/src/runs/background/wait-completions.ts +4 -0
  44. package/src/runs/foreground/async-steering-action.ts +19 -0
  45. package/src/runs/foreground/execution.ts +101 -25
  46. package/src/runs/foreground/subagent-executor.ts +474 -197
  47. package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  48. package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
  49. package/src/runs/shared/acceptance.ts +2 -2
  50. package/src/runs/shared/agent-contract.ts +1 -1
  51. package/src/runs/shared/async-status-projection.ts +47 -47
  52. package/src/runs/shared/child-hooks.ts +151 -2
  53. package/src/runs/shared/child-launch.ts +18 -13
  54. package/src/runs/shared/child-session.ts +42 -6
  55. package/src/runs/shared/child-tool-plan.ts +2 -2
  56. package/src/runs/shared/completion-evidence.ts +2 -2
  57. package/src/runs/shared/completion-guard.ts +1 -0
  58. package/src/runs/shared/host-step-status.ts +11 -11
  59. package/src/runs/shared/llm-intent-arbiter.ts +10 -9
  60. package/src/runs/shared/model-fallback.ts +10 -6
  61. package/src/runs/shared/nested-events.ts +5 -5
  62. package/src/runs/shared/orca-progress-tabs.ts +6 -0
  63. package/src/runs/shared/parallel-handoff.ts +57 -12
  64. package/src/runs/shared/parallel-utils.ts +2 -2
  65. package/src/runs/shared/readonly-drain-observation.ts +42 -0
  66. package/src/runs/shared/readonly-model-continuation.ts +69 -0
  67. package/src/runs/shared/readonly-session-evidence.ts +307 -0
  68. package/src/runs/shared/run-fanout-budget.ts +8 -8
  69. package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
  70. package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
  71. package/src/runs/shared/worktree-setup-command.ts +190 -0
  72. package/src/runs/shared/worktree.ts +332 -204
  73. package/src/shared/types.ts +81 -60
  74. package/src/shared/utils.ts +7 -2
  75. package/src/shared/workflow-child-permit.ts +18 -13
  76. package/src/tui/fleet.ts +11 -5
  77. package/src/tui/render.ts +23 -5
  78. package/src/workflows/chat-progress.ts +3 -3
  79. package/src/workflows/scripted-workflow.ts +38 -10
  80. package/src/workflows/workflow-checklist.ts +11 -15
  81. package/src/workflows/workflow-child-summary.ts +57 -8
  82. package/src/workflows/workflow-preflight.ts +19 -19
  83. package/src/workflows/workflow-receipt.ts +3 -3
  84. package/src/workflows/workflow-resources.ts +96 -21
  85. package/src/workflows/workflow-settlement.ts +3 -0
@@ -52,7 +52,7 @@ import { isScheduledRunAction } from "../background/scheduled-runs.ts";
52
52
  import { encodeIndexSegment } from "../background/index-segment.ts";
53
53
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
54
54
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
55
- import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateExecutionAcceptance } from "../shared/acceptance.ts";
55
+ import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
56
56
  import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
57
57
  import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
58
58
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
@@ -65,13 +65,13 @@ import { retainLiveForegroundNestedRoute } from "../../integrations/pi-web-sessi
65
65
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
66
66
  import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
67
67
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
68
- import { isAgentContractV1 } from "../shared/agent-contract.ts";
68
+ import { isAgentContract } from "../shared/agent-contract.ts";
69
69
  import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
70
70
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
71
71
  import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
72
72
  import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.ts";
73
73
  import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
74
- import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
74
+ import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.ts";
75
75
  import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
76
76
  import {
77
77
  attachNestedChildrenToResultChildren,
@@ -82,12 +82,13 @@ import {
82
82
  stripDetailsOutputsForIntercomReceipt,
83
83
  } from "../../intercom/result-intercom.ts";
84
84
  import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, readAsyncRecoveryDescriptor, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
85
- import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
86
- import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
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";
87
87
  import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
88
88
  import {
89
89
  resolveWorkflowForegroundSteeringTarget,
90
90
  steerWorkflowForegroundTarget,
91
+ steerWorkflowRun,
91
92
  } from "./workflow-foreground-steering.ts";
92
93
  import { stopAsyncRun } from "./async-stop-action.ts";
93
94
  import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
@@ -115,10 +116,10 @@ import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspe
115
116
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
116
117
  import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowLanePlan, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowScriptTraceEntry, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
117
118
  import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
118
- import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
119
+ import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
119
120
  import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
120
121
  import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
121
- import { parseWorkflowChildSummary, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
122
+ import { parseWorkflowChildSummary, workflowChildProgress, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
122
123
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
123
124
  import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.ts";
124
125
  import {
@@ -135,6 +136,8 @@ import { resolveWorkflowResource } from "../../workflows/workflow-resources.ts";
135
136
  import {
136
137
  cleanupWorktrees,
137
138
  createWorktrees,
139
+ withWorktreeTransaction,
140
+ type WorktreeSetupProgress,
138
141
  diffWorktrees,
139
142
  formatWorktreeDiffSummary,
140
143
  type WorktreeSetup,
@@ -157,7 +160,7 @@ import {
157
160
  type ForegroundSteerInput,
158
161
  type ForegroundSteerOutcome,
159
162
  type ForegroundRunControl,
160
- type HostStepNodeV1,
163
+ type HostStepNode,
161
164
  type IntercomBridgeConfig,
162
165
  type IntercomEventBus,
163
166
  type JsonSchemaObject,
@@ -174,7 +177,7 @@ import {
174
177
  type ToolBudgetConfig,
175
178
  type Usage,
176
179
  type UsageBudgetConfig,
177
- type WorkflowResourceProvenanceV1,
180
+ type WorkflowResourceProvenance,
178
181
  type WorkflowGraphNode,
179
182
  type WorkflowGraphSnapshot,
180
183
  type WorkflowNodeStatus,
@@ -194,6 +197,7 @@ import {
194
197
  resolveMaxSubagentSpawnsPerRun,
195
198
  wrapForkTask,
196
199
  type ScheduleOrigin,
200
+ type SteeringTargetState,
197
201
  } from "../../shared/types.ts";
198
202
  import { deriveChildSessionName } from "../../shared/child-session-name.ts";
199
203
 
@@ -326,7 +330,7 @@ export interface SubagentParamsLike {
326
330
  workflowScriptPath?: string;
327
331
  globalConcurrencyLimit?: number;
328
332
  maxSubagentSpawnsPerRun?: number;
329
- preflight?: import("../../shared/types.ts").WorkflowPreflightV1;
333
+ preflight?: import("../../shared/types.ts").WorkflowPreflight;
330
334
  chatProgress?: "auto" | "off" | "live-card";
331
335
  isolation?: "none" | "worktree";
332
336
  step?: ChainStep;
@@ -437,8 +441,10 @@ interface ExecutorDeps {
437
441
  getSubagentSessionRoot: (parentSessionFile: string | null) => string;
438
442
  expandTilde: (p: string) => string;
439
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;
440
445
  allowMutatingManagementActions?: boolean;
441
446
  activateSupervisorTransport?: () => void;
447
+ findPendingAsks?: Parameters<typeof steerAsyncRun>[0]["findPendingAsks"];
442
448
  refreshResultDelivery?: () => void;
443
449
  trackRetainedNestedRoute?: (rootRunId: string) => void;
444
450
  kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
@@ -497,6 +503,8 @@ interface ExecutionContextData {
497
503
  configToolTimeoutMs?: number;
498
504
  toolBudget?: ResolvedToolBudget;
499
505
  usageBudget?: UsageBudgetConfig;
506
+ /** Parent workflow owns this allowance; presence vetoes unverifiable child continuation. */
507
+ inheritedUsageBudget?: UsageBudgetConfig;
500
508
  allowZeroToolBudget?: boolean;
501
509
  configToolBudget?: ResolvedToolBudget;
502
510
  contextPolicy: AgentDefaultContextPolicy;
@@ -1742,6 +1750,28 @@ async function resumeExternalJobFollowUp(input: {
1742
1750
  return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId: result.details.asyncId ?? runId, asyncDir: result.details.asyncDir ?? asyncDir, interactive: input.ctx.hasUI });
1743
1751
  }
1744
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
+
1745
1775
  async function resumeAsyncRun(input: {
1746
1776
  params: SubagentParamsLike;
1747
1777
  requestCwd: string;
@@ -1780,34 +1810,9 @@ async function resumeAsyncRun(input: {
1780
1810
  let target: ResumeSourceTarget;
1781
1811
  const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
1782
1812
  try {
1783
- const requestedId = input.params.id ?? input.params.runId;
1784
- let resolved: ResolvedSubagentRunId | undefined;
1785
- try {
1786
- resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) })) : undefined;
1787
- } catch (error) {
1788
- const message = error instanceof Error ? error.message : "";
1789
- const asyncMatches = message.match(/async:/g)?.length ?? 0;
1790
- if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
1791
- }
1792
- if (resolved?.kind === "nested") {
1793
- if (attachChain) {
1794
- return {
1795
- content: [{ type: "text", text: "Attaching a running subagent as a chain root is currently available for top-level async runs only." }],
1796
- isError: true,
1797
- details: { mode: "management", results: [] },
1798
- };
1799
- }
1800
- if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") {
1801
- return resumeLiveNestedRun({ target: resolved, message: followUp });
1802
- }
1803
- const trustedSessionRoots = [
1804
- ...(input.deps.config.defaultSessionDir ? [path.resolve(input.deps.expandTilde(input.deps.config.defaultSessionDir))] : []),
1805
- ...(parentSessionFile ? [input.deps.getSubagentSessionRoot(parentSessionFile)] : []),
1806
- ];
1807
- target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
1808
- } else {
1809
- target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: false });
1810
- }
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;
1811
1816
  } catch (error) {
1812
1817
  const message = error instanceof Error ? error.message : String(error);
1813
1818
  return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
@@ -2118,6 +2123,7 @@ async function resumeAsyncRun(input: {
2118
2123
  availableModels,
2119
2124
  output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
2120
2125
  outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
2126
+ outputClaimPath: input.params.workflowOutputClaimPath,
2121
2127
  ...(agentContract ? { agentContract } : {}),
2122
2128
  ...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
2123
2129
  ...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
@@ -2194,6 +2200,7 @@ async function resumeAsyncRun(input: {
2194
2200
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
2195
2201
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
2196
2202
  ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
2203
+ ...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
2197
2204
  ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
2198
2205
  ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
2199
2206
  ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
@@ -3169,6 +3176,7 @@ async function waitForWorkflowAsyncSingleResult(
3169
3176
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
3170
3177
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
3171
3178
  ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
3179
+ ...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
3172
3180
  ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
3173
3181
  ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
3174
3182
  ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
@@ -3343,8 +3351,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3343
3351
  return null;
3344
3352
  }
3345
3353
 
3346
- function createSingleWorktreeSetup(
3347
- enabled: boolean | undefined,
3354
+ async function createSingleWorktreeSetup(
3348
3355
  cwd: string,
3349
3356
  runId: string,
3350
3357
  agent: string,
@@ -3356,12 +3363,13 @@ function createSingleWorktreeSetup(
3356
3363
  branchPrefix: ExtensionConfig["worktreeBranchPrefix"],
3357
3364
  label?: string,
3358
3365
  task?: string,
3359
- beforeCreate?: (setup: WorktreeSetup) => void,
3360
- ): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
3361
- if (!enabled) return {};
3366
+ onProgress?: (snapshot: WorktreeSetupProgress) => void,
3367
+ signal?: AbortSignal,
3368
+ deadlineAt?: number,
3369
+ ): Promise<{ setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> }> {
3362
3370
  try {
3363
3371
  return {
3364
- setup: createWorktrees(cwd, runId, 1, omitUndefinedProperties({
3372
+ setup: await createWorktrees(cwd, runId, 1, omitUndefinedProperties({
3365
3373
  agents: [agent],
3366
3374
  setupHook: setupHook
3367
3375
  ? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
@@ -3372,7 +3380,9 @@ function createSingleWorktreeSetup(
3372
3380
  branchPrefix,
3373
3381
  labels: [label],
3374
3382
  tasks: [task],
3375
- beforeCreate,
3383
+ onProgress,
3384
+ signal,
3385
+ deadlineAt,
3376
3386
  })),
3377
3387
  };
3378
3388
  } catch (error) {
@@ -3398,15 +3408,6 @@ function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string,
3398
3408
  return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
3399
3409
  }
3400
3410
 
3401
- function resolveWorkflowAggregateOutputPath(
3402
- output: string | boolean | undefined,
3403
- ctxCwd: string,
3404
- workflowCwd: string,
3405
- outputBaseDir: string,
3406
- ): string | undefined {
3407
- return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
3408
- }
3409
-
3410
3411
  function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
3411
3412
  if (aggregateOutputPath) {
3412
3413
  const parsed = path.parse(aggregateOutputPath);
@@ -3468,7 +3469,9 @@ function resolveWorkflowChildOutputPath(input: {
3468
3469
  key: string;
3469
3470
  params: Record<string, unknown>;
3470
3471
  }): { path?: string; inherited: boolean } {
3471
- 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")) {
3472
3475
  if (!input.state) return { path: undefined, inherited: false };
3473
3476
  const index = input.params.index;
3474
3477
  const target = resolveResumeTarget({
@@ -3477,15 +3480,16 @@ function resolveWorkflowChildOutputPath(input: {
3477
3480
  }, input.state);
3478
3481
  return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
3479
3482
  }
3480
- const rawOutput = input.params.output;
3481
- const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
3482
3483
  const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
3483
- const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
3484
- const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
3485
- const agent = typeof input.params.agent === "string"
3486
- ? resolveAgentName(input.params.agent, discoveredAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent
3487
- : undefined;
3488
- const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
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
+ }
3489
3493
  const output = rawOutput === true || rawOutput === "true"
3490
3494
  ? agentOutput
3491
3495
  : hasExplicitOutput
@@ -3574,7 +3578,7 @@ function prepareWorkflowChildLaunchParams(input: {
3574
3578
  childParams = { ...input.childParams, output: input.outputOverride };
3575
3579
  } else if (usesDefaultOutput && input.aggregateOutputPath !== undefined) {
3576
3580
  childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
3577
- } else if (input.childParams.resume === undefined) {
3581
+ } else if (input.childParams.resume === undefined || input.childParams.output !== undefined) {
3578
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 });
3579
3583
  if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
3580
3584
  }
@@ -3588,7 +3592,7 @@ function prepareWorkflowChildLaunchParams(input: {
3588
3592
  return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired, outputClaimPath: input.outputClaimPath });
3589
3593
  }
3590
3594
 
3591
- function finalizeSingleWorktreeHandoff(input: {
3595
+ async function finalizeSingleWorktreeHandoff(input: {
3592
3596
  worktreeSetup: WorktreeSetup;
3593
3597
  artifactsDir: string;
3594
3598
  runId: string;
@@ -3597,55 +3601,78 @@ function finalizeSingleWorktreeHandoff(input: {
3597
3601
  result: SingleResult;
3598
3602
  workflowKey?: string;
3599
3603
  lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
3600
- }): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
3601
- const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
3602
- const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
3603
- const diffSummary = formatWorktreeDiffSummary(diffs);
3604
- const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
3605
- const handoff = {
3606
- manifestPath,
3607
- runId: input.runId,
3608
- mode: "single" as const,
3609
- source: "foreground" as const,
3610
- cwd: input.cwd,
3611
- stepIndex: 0,
3612
- flatStartIndex: 0,
3613
- setup: input.worktreeSetup,
3614
- laneBindings: input.workflowKey || input.lane ? [{ index: 0, taskIndex: 0, ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}), runId: input.workflowKey ? input.runId : undefined, ...(input.lane ? { lane: input.lane } : {}) }] : undefined,
3615
- diffs,
3616
- results: [{
3617
- agent: input.result.agent,
3618
- ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
3619
- ...(input.workflowKey ? { runId: input.runId } : {}),
3620
- ...(input.lane ? { lane: input.lane } : {}),
3621
- status: resolveSubagentResultStatus(omitUndefinedProperties({
3622
- exitCode: input.result.exitCode,
3623
- interrupted: input.result.interrupted,
3624
- detached: input.result.detached,
3625
- state: input.result.stopped ? "stopped" : undefined,
3626
- processSignal: input.result.processSignal,
3627
- timedOut: input.result.timedOut,
3628
- stopped: input.result.stopped,
3629
- turnBudgetExceeded: input.result.turnBudgetExceeded,
3630
- })),
3631
- summary: resultSummaryForIntercom(input.result),
3632
- ...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
3633
- ...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
3634
- ...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
3635
- ...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
3636
- }],
3637
- };
3604
+ }): Promise<{ suffix: string; reference?: NonNullable<Details["parallelHandoff"]> }> {
3605
+ let admitted = false;
3638
3606
  try {
3639
- writeParallelHandoffGroup(handoff);
3640
- const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
3641
- const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
3642
- return {
3643
- suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
3644
- reference,
3645
- };
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
+ });
3646
3658
  } catch (error) {
3647
- return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
3648
- }
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
+ });
3649
3676
  }
3650
3677
 
3651
3678
  async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
@@ -3726,8 +3753,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3726
3753
 
3727
3754
  const sourceCwd = effectiveCwd;
3728
3755
  let pendingHandoff: Details["parallelHandoff"];
3729
- const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
3730
- params.worktree,
3756
+ const { setup: worktreeSetup, errorResult: worktreeSetupError } = params.worktree ? await createSingleWorktreeSetup(
3731
3757
  sourceCwd,
3732
3758
  runId,
3733
3759
  params.agent!,
@@ -3739,8 +3765,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3739
3765
  deps.config.worktreeBranchPrefix,
3740
3766
  params.lane?.key ?? params.workflowKey,
3741
3767
  task,
3742
- (plannedSetup) => {
3743
- pendingHandoff = writePendingParallelHandoff({
3768
+ (progress) => {
3769
+ pendingHandoff = writeWorktreeSetupHandoff({
3744
3770
  manifestPath: parallelHandoffPath(artifactsDir, runId),
3745
3771
  runId,
3746
3772
  mode: "single",
@@ -3748,13 +3774,42 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3748
3774
  cwd: sourceCwd,
3749
3775
  stepIndex: 0,
3750
3776
  flatStartIndex: 0,
3751
- setup: plannedSetup,
3777
+ progress,
3752
3778
  laneBindings: params.workflowKey || lane ? [{ index: 0, taskIndex: 0, ...(params.workflowKey ? { workflowKey: params.workflowKey, runId } : {}), ...(lane ? { lane } : {}) }] : undefined,
3753
3779
  });
3754
3780
  },
3755
- );
3756
- 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
+ }
3757
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
+ }
3758
3813
 
3759
3814
  const authoredTask = task;
3760
3815
  if (shouldForkAgent(contextPolicy, params.agent!)) {
@@ -3764,8 +3819,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3764
3819
  const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
3765
3820
  const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
3766
3821
  if (validationError) {
3767
- if (worktreeSetup) cleanupWorktrees(worktreeSetup);
3768
- 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 } };
3769
3824
  }
3770
3825
  const structuredRuntime = params.outputSchema
3771
3826
  ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
@@ -3898,14 +3953,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3898
3953
  onDetachReady: (detach) => {
3899
3954
  detachForeground = detach;
3900
3955
  },
3901
- onDetachedExit: (result) => {
3956
+ onDetachedExit: async (result) => {
3902
3957
  if (resolveDetachedWorkflowChild) {
3903
3958
  resolveDetachedWorkflowChild(result);
3904
3959
  return;
3905
3960
  }
3906
3961
  try {
3907
3962
  if (worktreeSetup) {
3908
- 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 });
3909
3964
  }
3910
3965
  try {
3911
3966
  updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
@@ -3941,12 +3996,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3941
3996
  toolTimeoutMs: params.toolTimeoutMs,
3942
3997
  configToolTimeoutMs: data.configToolTimeoutMs,
3943
3998
  toolBudget: effectiveToolBudget.toolBudget,
3999
+ usageBudget: data.usageBudget ?? data.inheritedUsageBudget,
3944
4000
  capabilityCeiling: data.capabilityCeiling,
3945
4001
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3946
4002
  }));
3947
4003
  r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
3948
4004
  } catch (error) {
3949
- if (worktreeSetup) cleanupWorktrees(worktreeSetup);
4005
+ await cleanupSingleWorktree();
3950
4006
  throw error;
3951
4007
  } finally {
3952
4008
  // An attached runSync rejection still owns its child and structured runtime.
@@ -3961,11 +4017,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3961
4017
  recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
3962
4018
  }
3963
4019
 
3964
- let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
4020
+ let worktreeHandoff: Awaited<ReturnType<typeof finalizeSingleWorktreeHandoff>> | undefined;
3965
4021
  if (worktreeSetup) {
3966
4022
  worktreeHandoff = r.detached
3967
4023
  ? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
3968
- : 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 });
3969
4025
  }
3970
4026
 
3971
4027
  if (r.progress) allProgress.push(r.progress);
@@ -4079,6 +4135,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
4079
4135
  }
4080
4136
 
4081
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>();
4082
4139
 
4083
4140
  /**
4084
4141
  * Terminal-mission retention can remove a mission while its children still
@@ -4204,8 +4261,6 @@ function workflowChildResult(
4204
4261
  }
4205
4262
  const requestedContext = childParams.context === "fresh" || childParams.context === "fork" ? childParams.context : undefined;
4206
4263
  const resolvedContext = result.details.context ?? (resolvedContexts.length === 1 ? resolvedContexts[0] : resolvedContexts.length > 1 ? "mixed" : undefined);
4207
- const resumeSourceRunId = typeof childParams.resume === "string" && childParams.resume.trim() ? childParams.resume.trim() : undefined;
4208
- const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
4209
4264
  const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
4210
4265
  ?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
4211
4266
  const outputPathMapping = typeof childParams.task === "string" ? outputPathMappingFromTask(childParams.task, outputReference) : undefined;
@@ -4238,7 +4293,7 @@ function workflowChildResult(
4238
4293
  ...(outputPathMapping ? { outputPathMapping } : {}),
4239
4294
  ...(externalAdapter ? { externalAdapter } : {}),
4240
4295
  resumability,
4241
- continuation: { runIds: continuationRunIds },
4296
+ continuation: { runIds: runId ? [runId] : [] },
4242
4297
  artifactPaths: [...artifactPaths],
4243
4298
  results: result.details.results,
4244
4299
  };
@@ -4351,10 +4406,33 @@ function missingWorkflowReceiptResumeHint(reference: WorkflowReceiptResumeRefere
4351
4406
  }
4352
4407
  }
4353
4408
 
4354
- function resolveKeyedWorkflowResume(
4355
- reference: WorkflowReceiptResumeReference,
4356
- state: SubagentState,
4357
- ): { 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
+ }
4358
4436
  try {
4359
4437
  const entry = resolveWorkflowReceiptResumeEntry({
4360
4438
  reference,
@@ -4364,9 +4442,7 @@ function resolveKeyedWorkflowResume(
4364
4442
  if (target.kind !== "revive") throw new Error(`Workflow receipt child '${reference.key}' latest run '${runId}' is still running.`);
4365
4443
  },
4366
4444
  });
4367
- const runId = entry.latestRunId;
4368
- if (!runId) throw new Error(`Workflow receipt child '${reference.key}' has no retained run id.`);
4369
- return { runId, runIds: entry.continuation.runIds };
4445
+ return { runId: entry.latestRunId, runIds: entry.continuation.runIds };
4370
4446
  } catch (error) {
4371
4447
  const workflowRunId = reference.workflowRunId.trim();
4372
4448
  if (isMissingWorkflowReceiptDiagnostic(error, workflowRunId)) {
@@ -4505,7 +4581,7 @@ export function prepareWorkflowLaunchParams(
4505
4581
  const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
4506
4582
  if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
4507
4583
  const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
4508
- if (agentContract !== undefined && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
4584
+ if (agentContract !== undefined && !isAgentContract(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
4509
4585
  const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
4510
4586
  const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
4511
4587
  if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
@@ -4519,6 +4595,7 @@ export function prepareWorkflowLaunchParams(
4519
4595
  message: typeof childParams.task === "string" ? childParams.task.trim() : "",
4520
4596
  workflowParentRunId: parentWorkflowRunId,
4521
4597
  workflowKey,
4598
+ ...(options.outputClaimPath ? { workflowOutputClaimPath: options.outputClaimPath } : {}),
4522
4599
  ...(lane ? { lane } : {}),
4523
4600
  ...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
4524
4601
  ...(baseRef !== undefined ? { baseRef: baseRef as string } : {}),
@@ -4681,14 +4758,14 @@ function applyWorkflowLaneTrace(graph: WorkflowGraphSnapshot, trace: WorkflowScr
4681
4758
  else delete graph.currentNodeId;
4682
4759
  }
4683
4760
 
4684
- function workflowChatProgressUpdate(
4761
+ function workflowProgressUpdate(
4685
4762
  runId: string,
4686
4763
  chatProgress: WorkflowChatProgressProjection,
4687
4764
  workflow: NonNullable<Details["workflow"]>,
4688
4765
  workflowChildren?: Details["workflowChildren"],
4689
- preflight?: import("../../shared/types.ts").WorkflowPreflightV1,
4690
- ): AgentToolResult<Details> | undefined {
4691
- 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.
4692
4769
  return {
4693
4770
  content: [{ type: "text", text: "Workflow running." }],
4694
4771
  details: { mode: "workflow", runId, results: [], workflow, ...(preflight ? { preflight } : {}), ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
@@ -4764,6 +4841,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4764
4841
  signal: AbortSignal,
4765
4842
  ctx: ExtensionContext,
4766
4843
  ) => Promise<AgentToolResult<Details>>;
4844
+ /** Scheduled state visible to the current runtime supervisor owner only. */
4845
+ getCurrentSupervisorOwnerStates: () => Iterable<SubagentState>;
4767
4846
  } {
4768
4847
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4769
4848
  const delegatedZeroToolBudgets = new WeakSet<object>();
@@ -4772,7 +4851,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4772
4851
  const workflowResourcePermits = new WeakMap<object, WorkflowResourcePermit>();
4773
4852
  const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
4774
4853
  const warnedArtifactPackageDirs = new Set<string>();
4775
- const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
4854
+ const scheduledOwnerExecutors = new Map<string | null, Map<string, { state: SubagentState; executor: ReturnType<typeof createSubagentExecutor> }>>();
4776
4855
  const execute = async (
4777
4856
  _id: string,
4778
4857
  params: SubagentParamsLike,
@@ -4783,6 +4862,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4783
4862
  parentModelOverride?: ParentModel | null,
4784
4863
  ): Promise<AgentToolResult<Details>> => {
4785
4864
  const workflowLaunchObserver = workflowLaunchObservers.get(params);
4865
+ const inheritedUsageBudget = workflowOwnedUsageBudgets.get(params);
4786
4866
  const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
4787
4867
  const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
4788
4868
  const delegatedExecution = delegatedExecutions.has(params);
@@ -4800,7 +4880,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4800
4880
  let requestParams = normalizedGate.params;
4801
4881
  const capacityOverrideError = validateWorkflowCapacityOverrides(requestParams);
4802
4882
  if (capacityOverrideError) return buildRequestedModeError(requestParams, capacityOverrideError);
4803
- let workflowPreflight: import("../../shared/types.ts").WorkflowPreflightV1 | undefined;
4883
+ let workflowPreflight: import("../../shared/types.ts").WorkflowPreflight | undefined;
4804
4884
  try {
4805
4885
  if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
4806
4886
  throw new Error("preflight requires workflowScript or workflowScriptPath.");
@@ -4831,7 +4911,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4831
4911
  }
4832
4912
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4833
4913
  if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
4834
- let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenanceV1; authority: WorkflowResourceAuthority } | undefined;
4914
+ let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenance; authority: WorkflowResourceAuthority } | undefined;
4835
4915
  if (workflowResourcePermit) {
4836
4916
  if (typeof requestParams.workflowScript !== "string") return buildRequestedModeError(requestParams, "Resolved workflow resource is missing its workflow script.");
4837
4917
  const consumed = consumeWorkflowResourcePermit(workflowResourcePermit, requestParams.workflowScript);
@@ -4839,6 +4919,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4839
4919
  workflowResource = { permit: workflowResourcePermit, ...consumed };
4840
4920
  }
4841
4921
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4922
+ const acceptanceErrors = validateAcceptanceInput(requestParams.acceptance);
4923
+ if (acceptanceErrors.length > 0) return buildRequestedModeError(requestParams, acceptanceErrors.join(" "));
4842
4924
  const foregroundWorkflowRunId = encodeIndexSegment(_id);
4843
4925
  if (delegatedWorkflowPermit) {
4844
4926
  const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, foregroundWorkflowRunId);
@@ -5016,18 +5098,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5016
5098
  onSuccess: (_filePath, payload) => { indexedState = (payload as { state: AsyncStatus["state"] }).state; },
5017
5099
  onError: (error, filePath) => console.error(`Failed to update async workflow index '${filePath}':`, error),
5018
5100
  });
5019
- const queueActiveRunIndex = (): void => {
5020
- const state = status.state;
5101
+ const queueActiveRunIndex = (snapshot: AsyncStatus = status): void => {
5102
+ const state = snapshot.state;
5021
5103
  if (indexedState === state && indexPersistence.pendingCount() === 0) return;
5022
- indexPersistence.write(asyncDir, { state, toolCallId: status.toolCallId }, (_filePath, payload) => {
5104
+ indexPersistence.write(asyncDir, { state, toolCallId: snapshot.toolCallId }, (_filePath, payload) => {
5023
5105
  const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
5024
5106
  updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
5025
5107
  });
5026
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
+ };
5027
5116
  const runPersistence = createCapacityResilientJsonWriter({
5028
5117
  keepAlive: true,
5029
- onSuccess: (filePath) => { if (filePath === statusPath) queueActiveRunIndex(); },
5030
- 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
+ },
5031
5126
  write: (filePath, payload) => filePath === resultPath
5032
5127
  ? writeAsyncResultFile(filePath, payload as Record<string, unknown>)
5033
5128
  : writeAtomicJson(filePath, payload),
@@ -5055,7 +5150,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5055
5150
  queueActiveRunIndex();
5056
5151
  } else if (options.tolerateStatusWriteFailure) {
5057
5152
  try {
5058
- runPersistence.write(statusPath, status);
5153
+ runPersistence.write(statusPath, { ...status });
5059
5154
  statusPersistenceDegraded = false;
5060
5155
  } catch (error) {
5061
5156
  const message = `Failed to persist async workflow state ${statusPath}: ${error instanceof Error ? error.message : String(error)}`;
@@ -5070,7 +5165,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5070
5165
  }
5071
5166
  }
5072
5167
  } else {
5073
- runPersistence.write(statusPath, status);
5168
+ runPersistence.write(statusPath, { ...status });
5074
5169
  }
5075
5170
  if (liveJob) {
5076
5171
  liveJob.status = status.state;
@@ -5098,12 +5193,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5098
5193
  };
5099
5194
  const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
5100
5195
  try {
5196
+ let resultPublished = false;
5197
+ settleResultPublication = (published) => { resultPublished = published; };
5198
+ pendingResultPublication = undefined;
5101
5199
  runPersistence.write(resultPath, payload);
5200
+ // Only capacity deferral yields; ordinary publication remains synchronous.
5201
+ if (!resultPublished) pendingResultPublication = new Promise<boolean>((resolve) => { settleResultPublication = resolve; });
5102
5202
  return true;
5103
5203
  } catch (error) {
5104
- const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
5105
- console.error(message, error);
5106
- appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
5204
+ reportResultWriteFailure(error);
5107
5205
  return false;
5108
5206
  }
5109
5207
  };
@@ -5143,7 +5241,148 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5143
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: [] } };
5144
5242
  }
5145
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?.();
5146
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
+ };
5147
5386
  void Promise.resolve().then(async () => {
5148
5387
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
5149
5388
  const workflowResults: SingleResult[] = [];
@@ -5151,7 +5390,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5151
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;
5152
5391
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5153
5392
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5154
- const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
5393
+ const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
5155
5394
  const claimedOutputPaths = new Map<string, string>();
5156
5395
  const childOutputOverrides = new Map<string, string>();
5157
5396
  const childOutputClaimPaths = new Map<string, string>();
@@ -5336,6 +5575,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5336
5575
  deps.asyncByDefault,
5337
5576
  );
5338
5577
  preparedChildParams = childRequest;
5578
+ if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
5339
5579
  workflowLaunchObservers.set(childRequest, (launch) => {
5340
5580
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
5341
5581
  if (step) {
@@ -5406,7 +5646,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5406
5646
  return child;
5407
5647
  },
5408
5648
  status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
5409
- resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
5649
+ resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
5410
5650
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
5411
5651
  });
5412
5652
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
@@ -5417,16 +5657,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5417
5657
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
5418
5658
  const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5419
5659
  const workflowUsage = sumResultsUsage(workflowResults);
5660
+ settleWorkflowSteerInbox("complete");
5420
5661
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
5421
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) };
5422
5663
  const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5664
+ delete status.workflowReceiptPath;
5423
5665
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5424
5666
  try {
5425
5667
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
5668
+ status.workflowReceiptPath = workflowReceipt.path;
5426
5669
  } catch (receiptError) {
5427
5670
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
5428
5671
  }
5429
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;
5430
5674
  persist();
5431
5675
  deps.refreshResultDelivery?.();
5432
5676
  persistClosed = true;
@@ -5450,10 +5694,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5450
5694
  }
5451
5695
  }
5452
5696
  const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
5697
+ settleWorkflowSteerInbox(state);
5453
5698
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
5454
5699
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5455
5700
  const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5456
- status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
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 });
5457
5702
  if (pauseForDetached) {
5458
5703
  const promoted = promotePausedWorkflowIfSettled(status);
5459
5704
  if (promoted) status = promoted;
@@ -5466,18 +5711,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5466
5711
  const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
5467
5712
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5468
5713
  const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5714
+ delete status.workflowReceiptPath;
5469
5715
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5470
5716
  try {
5471
5717
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
5718
+ status.workflowReceiptPath = workflowReceipt.path;
5472
5719
  } catch (receiptError) {
5473
5720
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
5474
5721
  }
5475
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;
5476
5724
  persist();
5477
5725
  deps.refreshResultDelivery?.();
5478
5726
  persistClosed = true;
5479
5727
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
5480
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
+ }
5481
5735
  persistClosed = true;
5482
5736
  deps.state.workflowControllers?.delete(workflowRunId);
5483
5737
  deps.state.workflowChildStops?.delete(workflowRunId);
@@ -5493,7 +5747,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5493
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;
5494
5748
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5495
5749
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5496
- const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
5750
+ const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
5497
5751
  const claimedOutputPaths = new Map<string, string>();
5498
5752
  const childOutputOverrides = new Map<string, string>();
5499
5753
  const childOutputClaimPaths = new Map<string, string>();
@@ -5513,14 +5767,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5513
5767
  : workflowResource
5514
5768
  ? workflowResource.authority.host ? runHostCommand : undefined
5515
5769
  : publicExecution ? undefined : runHostCommand;
5516
- const workflowHostSteps = new Map<string, HostStepNodeV1>();
5770
+ const workflowHostSteps = new Map<string, HostStepNode>();
5517
5771
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
5518
- let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, 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;
5519
5776
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
5520
5777
  const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
5521
5778
  const sendWorkflowProgress = () => {
5522
- const update = workflowChatProgressUpdate(foregroundWorkflowRunId, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight);
5523
- 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));
5524
5785
  };
5525
5786
  try {
5526
5787
  const workflow = await runWorkflowScript({
@@ -5538,7 +5799,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5538
5799
  const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
5539
5800
  const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
5540
5801
  liveWorkflow = { ...liveWorkflow, trace: projectedTrace, ...(preflightWarnings.length ? { preflightWarnings } : {}) };
5541
- liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, 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
+ }
5542
5805
  sendWorkflowProgress();
5543
5806
  },
5544
5807
  admit: (calls) => {
@@ -5577,6 +5840,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5577
5840
  deps.asyncByDefault,
5578
5841
  );
5579
5842
  preparedChildParams = childRequest;
5843
+ if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
5580
5844
  if (delegatedWorkflowPermit) {
5581
5845
  if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
5582
5846
  const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
@@ -5594,6 +5858,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5594
5858
  return execute(randomUUID(), childRequest, workflowSignal, (update) => {
5595
5859
  const progress = update.details.progress?.[0];
5596
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
+ }
5597
5870
  const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
5598
5871
  recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
5599
5872
  status: progressStatus,
@@ -5621,7 +5894,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5621
5894
  return child;
5622
5895
  },
5623
5896
  status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
5624
- resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
5897
+ resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
5625
5898
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: foregroundWorkflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
5626
5899
  });
5627
5900
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
@@ -5674,6 +5947,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5674
5947
  isError: true,
5675
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 }),
5676
5949
  }, workflowFanoutBudget));
5950
+ } finally {
5951
+ workflowProgressClosed = true;
5952
+ if (activityTimer) clearTimeout(activityTimer);
5953
+ childProgress.clear();
5677
5954
  }
5678
5955
  }
5679
5956
  const directParams = requestParams;
@@ -5744,10 +6021,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5744
6021
  }
5745
6022
  if (!confirmed) return { content: [{ type: "text", text: "Worktree discard canceled; preserved worktrees were not changed." }], details: { mode: "management", results: [] } };
5746
6023
  try {
5747
- const discarded = discardPreservedWorktrees(
5748
- 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,
5749
6027
  { kind: decision === "confirm" ? "confirmed" : "policy", ...(deps.config.authorityPolicy ? { policy: deps.config.authorityPolicy } : {}) },
5750
- );
6028
+ ));
5751
6029
  return { content: [{ type: "text", text: discarded.text }], details: { mode: "management", results: [] } };
5752
6030
  } catch (error) {
5753
6031
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
@@ -6016,7 +6294,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6016
6294
  }
6017
6295
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
6018
6296
  }
6019
- if (paramsWithResolvedCwd.view === "fleet") {
6297
+ if (paramsWithResolvedCwd.view === "fleet" || paramsWithResolvedCwd.view === "transcript") {
6020
6298
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
6021
6299
  }
6022
6300
  if (targetRunId) {
@@ -6025,12 +6303,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6025
6303
  if (resolved?.kind === "foreground") {
6026
6304
  const foreground = getForegroundControl(deps.state, resolved.id);
6027
6305
  if (foreground) {
6028
- if (paramsWithResolvedCwd.view === "transcript") {
6029
- return withBudget({
6030
- content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Persisted session transcript becomes inspectable after the foreground run completes when sessions are enabled." }],
6031
- details: { mode: "management", results: [] },
6032
- });
6033
- }
6034
6306
  return withBudget(foregroundStatusResult(foreground));
6035
6307
  }
6036
6308
  }
@@ -6040,13 +6312,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6040
6312
  }
6041
6313
  } else if (!hasDirectoryTarget) {
6042
6314
  const foreground = getForegroundControl(deps.state, undefined);
6043
- if (foreground && paramsWithResolvedCwd.view !== "transcript") return withBudget(foregroundStatusResult(foreground));
6044
- if (foreground && paramsWithResolvedCwd.view === "transcript") {
6045
- return withBudget({
6046
- content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Pass an async run id to inspect a background transcript." }],
6047
- details: { mode: "management", results: [] },
6048
- });
6049
- }
6315
+ if (foreground) return withBudget(foregroundStatusResult(foreground));
6050
6316
  }
6051
6317
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
6052
6318
  }
@@ -6067,9 +6333,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6067
6333
  const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
6068
6334
  const directoryStatus = location.asyncDir ? readStatus(location.asyncDir) : null;
6069
6335
  if (directoryStatus?.mode === "workflow") {
6070
- const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
6071
- if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
6072
- return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
6336
+ return steerWorkflowRun({ state: deps.state, runId, asyncDir: location.asyncDir!, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
6073
6337
  }
6074
6338
  if (location.asyncDir) {
6075
6339
  const unsupported = externalRunnerControlError(location.asyncDir, "steer");
@@ -6077,6 +6341,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6077
6341
  }
6078
6342
  return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
6079
6343
  state: deps.state,
6344
+ findPendingAsks: deps.findPendingAsks,
6080
6345
  runId,
6081
6346
  message,
6082
6347
  mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
@@ -6114,9 +6379,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6114
6379
  if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
6115
6380
  const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
6116
6381
  if (resolvedStatus?.mode === "workflow") {
6117
- const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
6118
- if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
6119
- return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
6382
+ return steerWorkflowRun({ state: deps.state, runId: resolved.id, asyncDir: resolved.location.asyncDir!, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
6120
6383
  }
6121
6384
  if (resolved.location.asyncDir) {
6122
6385
  const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
@@ -6124,6 +6387,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6124
6387
  }
6125
6388
  return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
6126
6389
  state: deps.state,
6390
+ findPendingAsks: deps.findPendingAsks,
6127
6391
  runId: resolved.id,
6128
6392
  message,
6129
6393
  mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
@@ -6325,6 +6589,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6325
6589
  config: deps.config,
6326
6590
  currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
6327
6591
  runtimeAgentOwner: deps.pi,
6592
+ onAgentsChanged: deps.onAgentsChanged,
6328
6593
  });
6329
6594
  }
6330
6595
 
@@ -6716,6 +6981,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6716
6981
  timeoutMs: foregroundTimeout.timeoutMs,
6717
6982
  toolBudget: runToolBudget.toolBudget,
6718
6983
  usageBudget: usageBudget.budget,
6984
+ inheritedUsageBudget,
6719
6985
  allowZeroToolBudget,
6720
6986
  configToolBudget: configToolBudget.toolBudget,
6721
6987
  configToolTimeoutMs: deps.config.toolTimeoutMs,
@@ -6976,7 +7242,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6976
7242
  }
6977
7243
  let publicParams = normalized.params as SubagentParamsLike;
6978
7244
  if (publicParams.workflow !== undefined) {
6979
- const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args);
7245
+ const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
6980
7246
  if (!resolved.ok) return Promise.resolve({ content: [{ type: "text", text: resolved.error }], isError: true, details: { mode: "workflow", results: [] } });
6981
7247
  const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
6982
7248
  publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
@@ -7023,16 +7289,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
7023
7289
  ctx: ExtensionContext,
7024
7290
  ) => {
7025
7291
  const ownerSessionId = resolveCurrentSessionId(ctx.sessionManager);
7026
- let ownerExecutor = scheduledOwnerExecutors.get(ownerSessionId);
7027
- if (!ownerExecutor) {
7028
- ownerExecutor = createSubagentExecutor({
7029
- ...deps,
7030
- state: createScheduledOwnerState(deps.state, ownerSessionId, ctx),
7031
- });
7032
- scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
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);
7033
7297
  }
7034
- 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);
7035
7305
  };
7036
7306
 
7037
- 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 };
7038
7315
  }