pi-subagents 0.55.0 → 0.57.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 (97) hide show
  1. package/CHANGELOG.md +81 -3
  2. package/agents/claude-code-writer.md +15 -0
  3. package/agents/claude-code.md +15 -0
  4. package/agents/codex-exec-writer.md +15 -0
  5. package/agents/codex-exec.md +15 -0
  6. package/agents/cursor-agent-writer.md +14 -0
  7. package/agents/cursor-agent.md +14 -0
  8. package/docs/agents.md +111 -15
  9. package/docs/configuration.md +37 -0
  10. package/docs/extension-api.md +2 -2
  11. package/docs/models.md +6 -0
  12. package/docs/observability.md +8 -7
  13. package/docs/tool-reference.md +11 -1
  14. package/docs/workflows.md +20 -0
  15. package/package.json +1 -1
  16. package/skills/pi-subagents/references/execution-controls.md +1 -1
  17. package/src/agents/agent-management.ts +13 -5
  18. package/src/agents/agent-refinements.ts +4 -4
  19. package/src/agents/agent-serializer.ts +2 -0
  20. package/src/agents/agents.ts +242 -49
  21. package/src/agents/builtin-names.ts +6 -0
  22. package/src/agents/runtime-agent-registry.ts +8 -2
  23. package/src/api/preflight.ts +22 -3
  24. package/src/extension/config.ts +70 -0
  25. package/src/extension/doctor.ts +3 -3
  26. package/src/extension/index.ts +18 -4
  27. package/src/extension/public-execution.ts +29 -13
  28. package/src/extension/rpc.ts +20 -5
  29. package/src/extension/schemas.ts +9 -3
  30. package/src/extension/tool-description.ts +11 -9
  31. package/src/inspectors/herdr/actions.ts +2 -1
  32. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  33. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  34. package/src/integrations/herdr-status.ts +46 -3
  35. package/src/runs/background/active-async-capacity.ts +77 -10
  36. package/src/runs/background/async-execution.ts +148 -27
  37. package/src/runs/background/async-job-tracker.ts +5 -0
  38. package/src/runs/background/async-resume.ts +3 -0
  39. package/src/runs/background/async-retention.ts +20 -3
  40. package/src/runs/background/async-status.ts +5 -0
  41. package/src/runs/background/chain-root-attachment.ts +15 -1
  42. package/src/runs/background/fleet-view.ts +16 -10
  43. package/src/runs/background/inspect-rpc.ts +8 -8
  44. package/src/runs/background/result-files.ts +27 -14
  45. package/src/runs/background/result-watcher.ts +9 -5
  46. package/src/runs/background/run-status.ts +33 -6
  47. package/src/runs/background/scheduled-runs.ts +7 -1
  48. package/src/runs/background/subagent-runner.ts +254 -49
  49. package/src/runs/background/wait-completions.ts +4 -0
  50. package/src/runs/foreground/execution.ts +107 -16
  51. package/src/runs/foreground/foreground-control.ts +6 -0
  52. package/src/runs/foreground/foreground-history.ts +25 -3
  53. package/src/runs/foreground/subagent-executor.ts +324 -84
  54. package/src/runs/foreground/workflow-detach-reconcile.ts +31 -7
  55. package/src/runs/shared/acceptance.ts +10 -5
  56. package/src/runs/shared/agent-contract.ts +1 -1
  57. package/src/runs/shared/child-protocol.ts +21 -7
  58. package/src/runs/shared/claude-code-adapter.ts +129 -0
  59. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  60. package/src/runs/shared/completion-guard.ts +3 -2
  61. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  62. package/src/runs/shared/dynamic-fanout.ts +1 -1
  63. package/src/runs/shared/extension-bindings.ts +78 -0
  64. package/src/runs/shared/external-cli-contract.ts +167 -0
  65. package/src/runs/shared/external-cli-preflight.ts +122 -0
  66. package/src/runs/shared/external-cli-runner.ts +349 -54
  67. package/src/runs/shared/fast-mode-extension.ts +10 -0
  68. package/src/runs/shared/model-exclusions.ts +59 -8
  69. package/src/runs/shared/model-fallback.ts +25 -8
  70. package/src/runs/shared/mutation-evidence.ts +150 -0
  71. package/src/runs/shared/nested-events.ts +3 -1
  72. package/src/runs/shared/nested-render.ts +2 -2
  73. package/src/runs/shared/parallel-utils.ts +3 -0
  74. package/src/runs/shared/pi-args.ts +47 -0
  75. package/src/runs/shared/process-signal.ts +13 -0
  76. package/src/runs/shared/run-history.ts +21 -1
  77. package/src/runs/shared/structured-output.ts +18 -4
  78. package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
  79. package/src/shared/fork-context.ts +21 -0
  80. package/src/shared/formatters.ts +7 -1
  81. package/src/shared/launch-contract.ts +6 -0
  82. package/src/shared/pruned-fork.ts +450 -0
  83. package/src/shared/session-file-trust.ts +19 -0
  84. package/src/shared/session-tokens.ts +14 -3
  85. package/src/shared/settings.ts +16 -3
  86. package/src/shared/shortcuts.ts +17 -0
  87. package/src/shared/types.ts +166 -9
  88. package/src/shared/workflow-child-permit.ts +116 -0
  89. package/src/slash/delegation-adapters.ts +0 -1
  90. package/src/slash/slash-commands.ts +8 -6
  91. package/src/tui/fleet-status.ts +27 -10
  92. package/src/tui/fleet-transcript.ts +11 -5
  93. package/src/tui/fleet.ts +28 -13
  94. package/src/tui/render.ts +47 -21
  95. package/src/workflows/scripted-workflow.ts +298 -30
  96. package/src/workflows/workflow-child-summary.ts +117 -0
  97. package/src/workflows/workflow-receipt.ts +119 -4
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
5
5
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
6
- import { findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope } from "../../agents/agents.ts";
6
+ import { discoverAgents, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
7
7
  import { getArtifactsDir, getChainRunsDir, getProjectArtifactPackagingWarning, getProjectSubagentsDir } from "../../shared/artifacts.ts";
8
8
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
9
9
  import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
@@ -56,12 +56,13 @@ import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skill
56
56
  import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
57
57
  import { updateActiveRunIndex } from "../background/active-run-index.ts";
58
58
  import { steeringReceipt } from "../background/steering.ts";
59
- import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
59
+ import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
60
60
  import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
61
61
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
62
62
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
63
63
  import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
64
64
  import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
65
+ import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
65
66
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
66
67
  import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
67
68
  import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
@@ -73,8 +74,9 @@ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
73
74
  import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
74
75
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
75
76
  import { isAgentContractV1 } from "../shared/agent-contract.ts";
77
+ import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
76
78
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
77
- import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
79
+ import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
78
80
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
79
81
  import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
80
82
  import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
@@ -88,7 +90,7 @@ import {
88
90
  resolveSubagentResultStatus,
89
91
  stripDetailsOutputsForIntercomReceipt,
90
92
  } from "../../intercom/result-intercom.ts";
91
- import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
93
+ import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, readAsyncRecoveryDescriptor, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
92
94
  import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
93
95
  import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
94
96
  import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
@@ -112,6 +114,7 @@ import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/c
112
114
  import { inspectSubagentStatus } from "../background/run-status.ts";
113
115
  import { getExternalJobProvider } from "../../api/external-job-provider.ts";
114
116
  import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
117
+ import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
115
118
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
116
119
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
117
120
  import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
@@ -121,9 +124,11 @@ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
121
124
  import { resolveAuthorityDecision } from "../../policy/authority.ts";
122
125
  import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
123
126
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
124
- import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
127
+ import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
125
128
  import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
129
+ import { workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
126
130
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
131
+ import { claimWorkflowChildPermit, validateWorkflowChildPermitRoot, type WorkflowChildPermit, type WorkflowChildPermitContext } from "../../shared/workflow-child-permit.ts";
127
132
  import {
128
133
  cleanupWorktrees,
129
134
  createWorktrees,
@@ -145,6 +150,7 @@ import {
145
150
  type ControlEvent,
146
151
  type Details,
147
152
  type ExtensionConfig,
153
+ type ForegroundResumeChild,
148
154
  type ForegroundRunControl,
149
155
  type IntercomBridgeConfig,
150
156
  type IntercomEventBus,
@@ -152,10 +158,12 @@ import {
152
158
  type MaxOutputConfig,
153
159
  type NestedRouteInfo,
154
160
  type NestedRunSummary,
161
+ type OutputMode,
155
162
  type ResolvedControlConfig,
156
163
  type ResolvedTurnBudget,
157
164
  type ResolvedToolBudget,
158
165
  type RunFanoutBudgetDescriptor,
166
+ type SteeringRecoveryDescriptor,
159
167
  type SingleResult,
160
168
  type SubagentChildStatusEvent,
161
169
  type ToolBudgetConfig,
@@ -264,6 +272,7 @@ interface TaskParam {
264
272
  reads?: string[] | boolean;
265
273
  progress?: boolean;
266
274
  model?: string;
275
+ fast?: boolean;
267
276
  skill?: string | string[] | boolean;
268
277
  outputSchema?: JsonSchemaObject;
269
278
  acceptance?: AcceptanceInput;
@@ -288,12 +297,14 @@ export interface SubagentParamsLike {
288
297
  type?: string;
289
298
  agent?: string;
290
299
  task?: string;
300
+ extensionBindings?: ExtensionBindings;
291
301
  /** Retained async child run id. Valid only on workflow runs.run items. */
292
302
  resume?: string;
293
303
  message?: string;
294
304
  steeringRecovery?: boolean;
295
305
  mode?: SteerDeliveryMode;
296
306
  workflowScript?: string;
307
+ workflowScriptPath?: string;
297
308
  chatProgress?: "auto" | "off" | "live-card";
298
309
  isolation?: "none" | "worktree";
299
310
  step?: ChainStep;
@@ -340,6 +351,7 @@ export interface SubagentParamsLike {
340
351
  artifacts?: boolean;
341
352
  includeProgress?: boolean;
342
353
  model?: string;
354
+ fast?: boolean;
343
355
  thinking?: string | false;
344
356
  scope?: string;
345
357
  target?: string;
@@ -392,7 +404,7 @@ interface ExecutorDeps {
392
404
  tempArtifactsDir: string;
393
405
  getSubagentSessionRoot: (parentSessionFile: string | null) => string;
394
406
  expandTilde: (p: string) => string;
395
- discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"] };
407
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
396
408
  allowMutatingManagementActions?: boolean;
397
409
  activateSupervisorTransport?: () => void;
398
410
  refreshResultDelivery?: () => void;
@@ -400,6 +412,7 @@ interface ExecutorDeps {
400
412
  }
401
413
 
402
414
  type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
415
+ type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => Promise<void>;
403
416
  type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
404
417
 
405
418
  interface ExecutionContextData {
@@ -409,6 +422,8 @@ interface ExecutionContextData {
409
422
  signal: AbortSignal;
410
423
  onUpdate?: (r: AgentToolResult<Details>) => void;
411
424
  agents: AgentConfig[];
425
+ /** Exact filesystem discovery provenance retained through defensive execution backstops. */
426
+ unknownAgentDiagnosticContext: UnknownAgentDiagnosticContext;
412
427
  /** Discovered agent definitions before per-run bridge injection. */
413
428
  recoveryAgents: AgentConfig[];
414
429
  runId: string;
@@ -444,12 +459,27 @@ interface ExecutionContextData {
444
459
  runFanoutBudget: RunFanoutBudgetDescriptor;
445
460
  topLevelAsyncCapacityEligible: boolean;
446
461
  activeAsyncCapacity?: ActiveAsyncCapacityHandle;
462
+ workflowChildPermitLaunch?: WorkflowChildPermitContext;
447
463
  }
448
464
 
449
465
  function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
450
466
  return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
451
467
  }
452
468
 
469
+ function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string): { params?: SubagentParamsLike; error?: string } {
470
+ if (params.workflowScriptPath === undefined) return { params };
471
+ const scriptPath = path.resolve(resolveRequestedCwd(runtimeCwd, params.cwd), params.workflowScriptPath);
472
+ let workflowScript: string;
473
+ try {
474
+ workflowScript = fs.readFileSync(scriptPath, "utf8");
475
+ } catch (error) {
476
+ return { error: `Failed to read workflowScriptPath '${scriptPath}': ${error instanceof Error ? error.message : String(error)}` };
477
+ }
478
+ if (!workflowScript.trim()) return { error: `workflowScriptPath file '${scriptPath}' is empty.` };
479
+ const { workflowScriptPath: _workflowScriptPath, ...rest } = params;
480
+ return { params: { ...rest, workflowScript } };
481
+ }
482
+
453
483
  function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
454
484
  const control = state.foregroundControls.get(runId);
455
485
  if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
@@ -538,6 +568,7 @@ function withSpawnBudgetStatus(
538
568
  const activeAsyncCapacity = sessionId
539
569
  ? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
540
570
  liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
571
+ abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(config.capacity?.abandonedSlotReleaseAfterMs),
541
572
  })
542
573
  : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
543
574
  state.activeAsyncCapacity = activeAsyncCapacity;
@@ -640,11 +671,13 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
640
671
  ...(progress?.currentPath ? { currentPath: progress.currentPath } : {}),
641
672
  ...(progress?.turnCount !== undefined ? { turnCount: progress.turnCount } : {}),
642
673
  ...(progress?.tokens !== undefined ? { tokens: progress.tokens } : {}),
674
+ ...(progress?.window !== undefined ? { window: progress.window } : {}),
675
+ ...(progress?.windowPeak !== undefined ? { windowPeak: progress.windowPeak } : {}),
643
676
  ...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
644
677
  };
645
678
  }
646
679
 
647
- function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
680
+ function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
648
681
  state.foregroundRuns ??= new Map();
649
682
  const previous = state.foregroundRuns.get(input.runId);
650
683
  const updatedAt = Date.now();
@@ -655,6 +688,13 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
655
688
  ...(input.sessionId ? { sessionId: input.sessionId } : {}),
656
689
  updatedAt,
657
690
  children: input.results.map((result, index) => {
691
+ const resumeContract = omitUndefinedProperties({
692
+ outputSchema: input.params.outputSchema,
693
+ agentContract: input.params.agentContract,
694
+ acceptance: input.params.acceptance,
695
+ output: input.effectiveOutput,
696
+ outputMode: input.effectiveOutputMode,
697
+ });
658
698
  const child = {
659
699
  agent: result.agent,
660
700
  index,
@@ -685,7 +725,9 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
685
725
  ...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
686
726
  ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
687
727
  ...(result.acceptance ? { acceptance: result.acceptance } : {}),
728
+ ...(Object.keys(resumeContract).length ? { resumeContract } : {}),
688
729
  ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
730
+ ...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
689
731
  ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
690
732
  ...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
691
733
  ...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
@@ -804,7 +846,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
804
846
  });
805
847
  }
806
848
 
807
- function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { exactOnly?: boolean } = {}): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
849
+ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { exactOnly?: boolean } = {}): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; resumeContract?: ForegroundResumeChild["resumeContract"]; extensionBindings?: ExtensionBindings; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
808
850
  const requested = (params.id ?? params.runId)?.trim();
809
851
  if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
810
852
  const direct = state.foregroundRuns.get(requested);
@@ -835,6 +877,8 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
835
877
  ...(child.model ? { model: child.model } : {}),
836
878
  ...(child.thinking ? { thinking: child.thinking } : {}),
837
879
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
880
+ ...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
881
+ ...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings)!.value } : {}),
838
882
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
839
883
  };
840
884
  }
@@ -854,6 +898,7 @@ type NestedResumeSourceTarget = {
854
898
  thinking?: AgentConfig["thinking"];
855
899
  launchContractDigest?: string;
856
900
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
901
+ recoveryDescriptor?: SteeringRecoveryDescriptor;
857
902
  };
858
903
  type ResumeSourceTarget = AsyncResumeSourceTarget | ForegroundResumeSourceTarget | NestedResumeSourceTarget;
859
904
 
@@ -1231,6 +1276,7 @@ function appendStepToAsyncChain(input: {
1231
1276
  agents,
1232
1277
  ctx: asyncCtx,
1233
1278
  availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
1279
+ unknownAgentDiagnosticContext: diagnosticContextFromDiscovery(discoveredForAppend, input.requestCwd, scope),
1234
1280
  cwd: status.cwd ?? input.requestCwd,
1235
1281
  chainSkills,
1236
1282
  dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
@@ -1339,6 +1385,14 @@ function validateNestedSessionFile(run: NestedRunSummary, trustedSessionRoots: s
1339
1385
  return realSessionFile;
1340
1386
  }
1341
1387
 
1388
+ export function readNestedRecoveryDescriptor(asyncDir: string | undefined, runId: string, agent: string): SteeringRecoveryDescriptor | undefined {
1389
+ const recoveryDescriptor = readAsyncRecoveryDescriptor(asyncDir);
1390
+ if (!recoveryDescriptor) return undefined;
1391
+ if (recoveryDescriptor.sourceRunId !== runId) throw new Error(`Nested run '${runId}' has a recovery descriptor for a different source run.`);
1392
+ if (recoveryDescriptor.agent !== agent) throw new Error(`Nested run '${runId}' has a recovery descriptor for a different agent.`);
1393
+ return recoveryDescriptor;
1394
+ }
1395
+
1342
1396
  function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "nested" }, trustedSessionRoots: string[]): NestedResumeSourceTarget {
1343
1397
  const run = match.match.run;
1344
1398
  if (run.state === "running" || run.state === "queued") throw new Error(`Nested run '${run.id}' is live; route the follow-up to the owner process instead.`);
@@ -1347,6 +1401,7 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
1347
1401
  if (!agent) throw new Error(`Could not determine child agent for nested run '${run.id}'.`);
1348
1402
  const state = run.state === "complete" || run.state === "failed" || run.state === "paused" ? run.state : "failed";
1349
1403
  const asyncDir = resolveNestedAsyncDir(match.match.rootRunId, run);
1404
+ const recoveryDescriptor = readNestedRecoveryDescriptor(asyncDir, run.id, agent);
1350
1405
  return compactOptional<NestedResumeSourceTarget>({
1351
1406
  kind: "revive",
1352
1407
  source: "nested",
@@ -1357,6 +1412,7 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
1357
1412
  cwd: asyncDir ? path.dirname(asyncDir) : undefined,
1358
1413
  sessionFile: validateNestedSessionFile(run, trustedSessionRoots),
1359
1414
  ...(run.capabilityCeiling ? { capabilityCeiling: run.capabilityCeiling } : {}),
1415
+ ...(recoveryDescriptor ? { recoveryDescriptor } : {}),
1360
1416
  });
1361
1417
  }
1362
1418
 
@@ -1496,9 +1552,14 @@ function externalJobFollowUpStarted(input: { sourceRunId: string; runId: string;
1496
1552
  function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
1497
1553
  const status = readStatus(asyncDir);
1498
1554
  if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
1499
- const message = action === "steer"
1500
- ? "External runners do not accept live steer messages."
1501
- : "External runners do not persist Pi sessions and cannot be resumed.";
1555
+ const externalCli = normalizeExternalCliRunnerStatus(status.steps.find((step) => step.runner?.type === "external-cli")?.runner);
1556
+ const message = externalCli
1557
+ ? action === "steer"
1558
+ ? `External adapter '${externalCli.adapter.id}' does not support runs.steer: ${externalCli.unsupportedReasons.steer}`
1559
+ : `External adapter '${externalCli.adapter.id}' cannot resume: ${externalCli.nonResumableReason}`
1560
+ : action === "steer"
1561
+ ? "External runners do not accept live steer messages."
1562
+ : "External runners do not persist Pi sessions and cannot be resumed.";
1502
1563
  return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
1503
1564
  }
1504
1565
 
@@ -1554,7 +1615,7 @@ async function resumeExternalJobFollowUp(input: {
1554
1615
  runId,
1555
1616
  kind: "runner",
1556
1617
  asyncDir,
1557
- }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
1618
+ }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
1558
1619
  } catch (error) {
1559
1620
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1560
1621
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
@@ -1711,6 +1772,7 @@ async function resumeAsyncRun(input: {
1711
1772
  const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
1712
1773
  const discovered = input.deps.discoverAgents(effectiveCwd, scope);
1713
1774
  const discoveredAgents = discovered.agents;
1775
+ const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
1714
1776
  const modelScope = discovered.modelScope;
1715
1777
  const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
1716
1778
  const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
@@ -1738,13 +1800,17 @@ async function resumeAsyncRun(input: {
1738
1800
  } : undefined);
1739
1801
  if (!baseAgentConfig) {
1740
1802
  return {
1741
- content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
1803
+ content: [{ type: "text", text: formatUnknownAgentError(target.agent, unknownAgentDiagnosticContext, "Unknown agent for resume") }],
1742
1804
  isError: true,
1743
1805
  details: { mode: "management", results: [] },
1744
1806
  };
1745
1807
  }
1746
1808
  if (target.source === "async" && target.runner?.type === "external-cli") {
1747
- return { content: [{ type: "text", text: "External runners do not persist Pi sessions and cannot be resumed." }], isError: true, details: { mode: "management", results: [] } };
1809
+ const runner = normalizeExternalCliRunnerStatus(target.runner);
1810
+ const message = runner
1811
+ ? `External adapter '${runner.adapter.id}' cannot resume: ${runner.nonResumableReason}`
1812
+ : "External runners do not persist Pi sessions and cannot be resumed.";
1813
+ return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
1748
1814
  }
1749
1815
  if (target.source === "async" && target.runner?.type === "external-job") {
1750
1816
  if (attachChain) return { content: [{ type: "text", text: "External-job follow-up does not support chain attachment. Use action='resume' with message instead." }], isError: true, details: { mode: "management", results: [] } };
@@ -1789,7 +1855,7 @@ async function resumeAsyncRun(input: {
1789
1855
  runId,
1790
1856
  kind: "runner",
1791
1857
  asyncDir: path.join(DIRS.async, runId),
1792
- }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
1858
+ }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
1793
1859
  } catch (error) {
1794
1860
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
1795
1861
  throw error;
@@ -1815,6 +1881,7 @@ async function resumeAsyncRun(input: {
1815
1881
  label: `Attached ${target.runId}`,
1816
1882
  },
1817
1883
  agents,
1884
+ unknownAgentDiagnosticContext,
1818
1885
  ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
1819
1886
  pi: input.deps.pi,
1820
1887
  cwd: input.requestCwd,
@@ -1835,6 +1902,7 @@ async function resumeAsyncRun(input: {
1835
1902
  sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
1836
1903
  chainSkills: normalized === false ? [] : (normalized ?? []),
1837
1904
  agentContract: input.params.agentContract,
1905
+ fast: input.params.fast,
1838
1906
  dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
1839
1907
  maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1840
1908
  waitToolEnabled: input.deps.waitToolEnabled,
@@ -1886,20 +1954,23 @@ async function resumeAsyncRun(input: {
1886
1954
  sourceRunId: target.runId,
1887
1955
  runId,
1888
1956
  asyncDir: path.join(DIRS.async, runId),
1889
- })
1957
+ }, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) })
1890
1958
  : acquireActiveAsyncCapacity({
1891
1959
  sessionId: input.deps.state.currentSessionId!,
1892
1960
  limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
1893
1961
  runId,
1894
1962
  kind: "runner",
1895
1963
  asyncDir: path.join(DIRS.async, runId),
1896
- });
1964
+ }, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) });
1897
1965
  } catch (error) {
1898
1966
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1899
1967
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
1900
1968
  }
1901
1969
  const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
1902
1970
  const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
1971
+ const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
1972
+ const outputSchema = input.params.outputSchema ?? foregroundContract?.outputSchema ?? recoveryDescriptor?.structuredOutputSchema;
1973
+ const agentContract = input.params.agentContract ?? foregroundContract?.agentContract ?? recoveryDescriptor?.agentContract;
1903
1974
  const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
1904
1975
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
1905
1976
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
@@ -1938,9 +2009,11 @@ async function resumeAsyncRun(input: {
1938
2009
  },
1939
2010
  context: recoveryContext,
1940
2011
  modelOverride: recoveryDescriptor?.model ?? target.model,
2012
+ fast: recoveryDescriptor?.fast,
1941
2013
  modelOverrideFromParent: recoveryDescriptor?.modelOverrideFromParent,
1942
2014
  thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
1943
2015
  thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
2016
+ extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
1944
2017
  outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
1945
2018
  maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1946
2019
  waitToolEnabled: input.deps.waitToolEnabled,
@@ -1953,12 +2026,12 @@ async function resumeAsyncRun(input: {
1953
2026
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1954
2027
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1955
2028
  availableModels,
1956
- output: typeof input.params.output === "string" ? input.params.output : recoveryDescriptor?.outputPath,
1957
- outputMode: input.params.outputMode ?? recoveryDescriptor?.outputMode,
1958
- ...(recoveryDescriptor?.agentContract ? { agentContract: recoveryDescriptor.agentContract } : {}),
1959
- ...(recoveryDescriptor?.structuredOutputSchema ? { structuredOutputSchema: recoveryDescriptor.structuredOutputSchema } : {}),
2029
+ output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
2030
+ outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
2031
+ ...(agentContract ? { agentContract } : {}),
2032
+ ...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
1960
2033
  ...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
1961
- ...(recoveryDescriptor?.acceptance !== undefined && input.params.acceptance === undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
2034
+ ...(input.params.acceptance !== undefined ? { acceptance: input.params.acceptance } : foregroundContract?.acceptance !== undefined ? { acceptance: foregroundContract.acceptance } : recoveryDescriptor?.acceptance !== undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
1962
2035
  ...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
1963
2036
  ...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
1964
2037
  ...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
@@ -2037,9 +2110,13 @@ async function resumeAsyncRun(input: {
2037
2110
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
2038
2111
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
2039
2112
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
2113
+ ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
2114
+ ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
2115
+ ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
2116
+ ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
2040
2117
  };
2041
2118
  return {
2042
- content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
2119
+ content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` : completed.error || completed.output || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
2043
2120
  ...(completed.success ? {} : { isError: true }),
2044
2121
  details: {
2045
2122
  ...result.details,
@@ -2204,25 +2281,43 @@ async function maybeBuildForegroundIntercomReceipt(input: {
2204
2281
  };
2205
2282
  }
2206
2283
 
2207
- function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { name?: string; error?: string } {
2284
+ function diagnosticContextFromDiscovery(
2285
+ discovered: { agents: AgentConfig[]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] },
2286
+ cwd: string,
2287
+ scope: AgentScope,
2288
+ ): UnknownAgentDiagnosticContext {
2289
+ if (discovered.cwd && discovered.scope && discovered.directories) return unknownAgentDiagnosticContext({ ...discovered, cwd: discovered.cwd, scope: discovered.scope, directories: discovered.directories });
2290
+ return unknownAgentDiagnosticContext(discoverAgents(path.resolve(cwd), scope));
2291
+ }
2292
+
2293
+ function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { name?: string; error?: string } {
2208
2294
  const resolved = resolveAgentName(name, agents);
2209
2295
  const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
2210
2296
  const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
2211
2297
  if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
2212
2298
  if (resolved.error) return { error: resolved.error };
2213
- if (!resolved.agent) return { error: `Unknown agent: ${name}` };
2299
+ if (!resolved.agent) return { error: formatUnknownAgentError(name, context) };
2214
2300
  return { name: resolved.agent.name };
2215
2301
  }
2216
2302
 
2217
- function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { params?: SubagentParamsLike; error?: string } {
2303
+ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { params?: SubagentParamsLike; error?: string } {
2218
2304
  const resolve = (name: string, location?: string): { name?: string; error?: string } => {
2219
- const result = canonicalizeAgentName(name, agents, diagnostics);
2305
+ const result = canonicalizeAgentName(name, agents, diagnostics, context);
2220
2306
  return result.error && location ? { error: `${result.error} (${location})` } : result;
2221
2307
  };
2222
2308
  if (params.agent) {
2223
2309
  const result = resolve(params.agent);
2224
2310
  if (result.error) return { error: result.error };
2225
2311
  params = omitUndefinedProperties({ ...params, agent: result.name });
2312
+ const agent = agents.find((candidate) => candidate.name === result.name);
2313
+ if (params.extensionBindings !== undefined && (agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job")) return { error: `extensionBindings is not supported for runner.type='${agent.runner.type}'.` };
2314
+ }
2315
+ if (params.extensionBindings !== undefined) {
2316
+ try {
2317
+ params = { ...params, extensionBindings: normalizeExtensionBindings(params.extensionBindings)!.value };
2318
+ } catch (error) {
2319
+ return { error: error instanceof Error ? error.message : String(error) };
2320
+ }
2226
2321
  }
2227
2322
  if (params.tasks) {
2228
2323
  const tasks: TaskParam[] = [];
@@ -2275,6 +2370,7 @@ function validateExecutionInput(
2275
2370
  hasTasks: boolean,
2276
2371
  hasSingle: boolean,
2277
2372
  allowClarifyTaskPrompt: boolean,
2373
+ context: UnknownAgentDiagnosticContext,
2278
2374
  ): AgentToolResult<Details> | null {
2279
2375
  if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
2280
2376
  return {
@@ -2300,7 +2396,7 @@ function validateExecutionInput(
2300
2396
 
2301
2397
  if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
2302
2398
  return {
2303
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
2399
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent, context) }],
2304
2400
  isError: true,
2305
2401
  details: { mode: "single" as const, results: [] },
2306
2402
  };
@@ -2311,7 +2407,7 @@ function validateExecutionInput(
2311
2407
  const task = params.tasks[i]!;
2312
2408
  if (!agents.find((agent) => agent.name === task.agent)) {
2313
2409
  return {
2314
- content: [{ type: "text", text: `Unknown agent: ${task.agent} (task ${i + 1})` }],
2410
+ content: [{ type: "text", text: `${formatUnknownAgentError(task.agent, context)} (task ${i + 1})` }],
2315
2411
  isError: true,
2316
2412
  details: { mode: "parallel" as const, results: [] },
2317
2413
  };
@@ -2356,7 +2452,7 @@ function validateExecutionInput(
2356
2452
  for (const agentName of stepAgents) {
2357
2453
  if (!agents.find((a) => a.name === agentName)) {
2358
2454
  return {
2359
- content: [{ type: "text", text: `Unknown agent: ${agentName} (step ${i + 1})` }],
2455
+ content: [{ type: "text", text: `${formatUnknownAgentError(agentName, context)} (step ${i + 1})` }],
2360
2456
  isError: true,
2361
2457
  details: { mode: "chain" as const, results: [] },
2362
2458
  };
@@ -2947,21 +3043,21 @@ function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultCo
2947
3043
  });
2948
3044
  }
2949
3045
 
2950
- function preflightForkSessionsForStaticTasks(
3046
+ async function preflightForkSessionsForStaticTasks(
2951
3047
  params: SubagentParamsLike,
2952
3048
  contextPolicy: AgentDefaultContextPolicy,
2953
- sessionFileForTask: ForkSessionFileForTask,
3049
+ prepareSessionForTask: PrepareForkSessionForTask,
2954
3050
  dynamicFanoutMaxItems?: number,
2955
- ): void {
3051
+ ): Promise<void> {
2956
3052
  if (!contextPolicy.usesFork) return;
2957
3053
  if (params.agent) {
2958
- if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0, params.model);
3054
+ if (shouldForkAgent(contextPolicy, params.agent)) await prepareSessionForTask(params.agent, 0, params.model);
2959
3055
  return;
2960
3056
  }
2961
3057
  if (params.tasks) {
2962
- params.tasks.forEach((task, index) => {
2963
- if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index, task.model);
2964
- });
3058
+ for (const [index, task] of params.tasks.entries()) {
3059
+ if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, index, task.model);
3060
+ }
2965
3061
  return;
2966
3062
  }
2967
3063
  if (!params.chain?.length) return;
@@ -2969,7 +3065,7 @@ function preflightForkSessionsForStaticTasks(
2969
3065
  for (const step of params.chain) {
2970
3066
  if (isParallelStep(step)) {
2971
3067
  for (const task of step.parallel) {
2972
- if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex, task.model);
3068
+ if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, flatIndex, task.model);
2973
3069
  flatIndex++;
2974
3070
  }
2975
3071
  continue;
@@ -2977,13 +3073,13 @@ function preflightForkSessionsForStaticTasks(
2977
3073
  if (isDynamicParallelStep(step)) {
2978
3074
  const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
2979
3075
  if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
2980
- for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
3076
+ for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) await prepareSessionForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
2981
3077
  }
2982
3078
  flatIndex += maxItems;
2983
3079
  continue;
2984
3080
  }
2985
3081
  const sequential = step as SequentialStep;
2986
- if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex, sequential.model);
3082
+ if (shouldForkAgent(contextPolicy, sequential.agent)) await prepareSessionForTask(sequential.agent, flatIndex, sequential.model);
2987
3083
  flatIndex++;
2988
3084
  }
2989
3085
  }
@@ -3037,9 +3133,13 @@ async function waitForWorkflowAsyncSingleResult(
3037
3133
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
3038
3134
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
3039
3135
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
3136
+ ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
3137
+ ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
3138
+ ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
3139
+ ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
3040
3140
  });
3041
3141
  return {
3042
- content: [{ type: "text", text: completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` }],
3142
+ content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` : completed.error || completed.output || `Async workflow child ${options.runId} completed without output.` }],
3043
3143
  ...(completed.success ? {} : { isError: true }),
3044
3144
  details: {
3045
3145
  ...launchResult.details,
@@ -3067,6 +3167,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3067
3167
  intercomBridge,
3068
3168
  nestedRoute,
3069
3169
  contextPolicy,
3170
+ unknownAgentDiagnosticContext,
3070
3171
  } = data;
3071
3172
  const hasChain = (params.chain?.length ?? 0) > 0;
3072
3173
  const hasTasks = (params.tasks?.length ?? 0) > 0;
@@ -3105,7 +3206,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3105
3206
  const a = agents.find((x) => x.name === params.agent);
3106
3207
  if (!a) {
3107
3208
  return {
3108
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
3209
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent!, unknownAgentDiagnosticContext) }],
3109
3210
  isError: true,
3110
3211
  details: { mode: "single" as const, results: [] },
3111
3212
  };
@@ -3119,6 +3220,9 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3119
3220
  const externalRunnerWithoutExplicitModel = (a.runner?.type === "external-cli" || a.runner?.type === "external-job")
3120
3221
  && params.model === undefined
3121
3222
  && (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
3223
+ if ((a.runner?.type === "external-cli" || a.runner?.type === "external-job") && (params.fast ?? a.fast) === true) {
3224
+ return buildRequestedModeError(params, `Agent '${a.name}' uses runner.type='${a.runner.type}' and does not support fast mode.`);
3225
+ }
3122
3226
  const modelScopes = resolveModelScopesForAgent(data.modelScope, a.name, parentModel);
3123
3227
  const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
3124
3228
  ? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
@@ -3147,6 +3251,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3147
3251
  ...(params.reads !== undefined ? { reads: params.reads } : {}),
3148
3252
  outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
3149
3253
  modelOverride,
3254
+ fast: params.fast,
3150
3255
  modelOverrideFromParent,
3151
3256
  thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3152
3257
  thinkingCeiling: a.maxThinking,
@@ -3163,6 +3268,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3163
3268
  nestedRoute,
3164
3269
  agentContract: params.agentContract,
3165
3270
  structuredOutputSchema: params.outputSchema,
3271
+ extensionBindings: params.extensionBindings,
3166
3272
  acceptance: params.acceptance,
3167
3273
  timeoutMs: data.timeoutMs,
3168
3274
  turnBudget: data.turnBudget,
@@ -3466,7 +3572,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3466
3572
  const agentConfig = agents.find((a) => a.name === params.agent);
3467
3573
  if (!agentConfig) {
3468
3574
  return {
3469
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
3575
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent!, data.unknownAgentDiagnosticContext) }],
3470
3576
  isError: true,
3471
3577
  details: { mode: "single", results: [] },
3472
3578
  };
@@ -3540,7 +3646,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3540
3646
  return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
3541
3647
  }
3542
3648
  const structuredRuntime = params.outputSchema
3543
- ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined)
3649
+ ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { captureAcceptanceReport: params.acceptance !== false })
3544
3650
  : undefined;
3545
3651
  // Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
3546
3652
  // absolute paths pass through; relative paths resolve against the child cwd.
@@ -3599,6 +3705,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3599
3705
  llmIntentArbiter: createTaskMutationArbiter(ctx),
3600
3706
  ...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
3601
3707
  context: data.contextPolicy.contextForAgent(params.agent!),
3708
+ unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
3602
3709
  runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
3603
3710
  cwd: singleCwd,
3604
3711
  signal,
@@ -3624,9 +3731,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3624
3731
  nestedRoute: foregroundControl?.nestedRoute,
3625
3732
  index: 0,
3626
3733
  modelOverride,
3734
+ fast: params.fast,
3627
3735
  modelOverrideFromParent,
3628
3736
  thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3629
3737
  thinkingCeiling: agentConfig.maxThinking,
3738
+ extensionBindings: params.extensionBindings,
3630
3739
  availableModels,
3631
3740
  preferredModelProvider: currentProvider,
3632
3741
  modelScope: modelScopes,
@@ -3635,6 +3744,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3635
3744
  agentContract: params.agentContract,
3636
3745
  acceptance: params.acceptance,
3637
3746
  acceptanceContext: { mode: "single" },
3747
+ workflowChildPermitLaunch: data.workflowChildPermitLaunch,
3638
3748
  onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
3639
3749
  onDetachReady: (detach) => {
3640
3750
  detachForeground = detach;
@@ -3668,7 +3778,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3668
3778
  }
3669
3779
  }
3670
3780
  }
3671
- recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
3781
+ recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0, result);
3672
3782
  },
3673
3783
  timeoutMs: data.timeoutMs,
3674
3784
  deadlineAt,
@@ -3693,7 +3803,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3693
3803
  }
3694
3804
  }
3695
3805
  if (!r.detached) {
3696
- recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
3806
+ recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
3697
3807
  }
3698
3808
 
3699
3809
  let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
@@ -3739,7 +3849,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3739
3849
  usageBudget: usageBudgetState(data.usageBudget, totalCost),
3740
3850
  ...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
3741
3851
  }));
3742
- rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results });
3852
+ rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
3743
3853
 
3744
3854
  const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
3745
3855
  if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
@@ -3893,9 +4003,14 @@ function workflowChildResult(
3893
4003
  const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
3894
4004
  ? result.details.results[0].finalOutput
3895
4005
  : receiptOutput;
4006
+ const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
4007
+ const failureError = childError && receiptOutput && receiptOutput !== childError
4008
+ ? `${childError}\n\n${receiptOutput}`
4009
+ : childError || receiptOutput || output || "Child run failed.";
3896
4010
  const detached = result.details.results.some((child) => child.detached);
4011
+ const interrupted = result.details.results.some((child) => child.interrupted);
3897
4012
  const stopped = result.details.results.some((child) => child.stopped);
3898
- const ok = result.isError !== true && !detached && !stopped;
4013
+ const ok = result.isError !== true && !detached && !interrupted && !stopped;
3899
4014
  const artifactPaths = new Set<string>();
3900
4015
  if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
3901
4016
  if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
@@ -3927,19 +4042,28 @@ function workflowChildResult(
3927
4042
  const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
3928
4043
  const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
3929
4044
  ?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
4045
+ const externalResult = result.details.results.length === 1 && result.details.results[0]?.runner?.type === "external-cli" ? result.details.results[0] : undefined;
4046
+ const externalStatus = result.details.asyncDir ? readStatus(result.details.asyncDir) : undefined;
4047
+ const externalStatusStep = externalStatus?.steps?.length === 1 && externalStatus.steps[0]?.runner?.type === "external-cli" ? externalStatus.steps[0] : undefined;
4048
+ const externalRunner = normalizeExternalCliRunnerStatus(externalResult?.runner ?? externalStatusStep?.runner);
4049
+ const externalProcess = externalResult?.externalProcess ?? externalStatusStep?.externalProcess;
4050
+ const externalAdapter = externalRunner ? externalCliReceiptMetadata({ runner: externalRunner, externalProcess, outputReference }) : undefined;
4051
+ if (externalAdapter) resumability = { state: "not-resumable", reason: externalAdapter.nonResumableReason };
3930
4052
  return {
3931
4053
  key,
3932
4054
  ok,
3933
4055
  ...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
3934
4056
  ...(runId ? { runId } : {}),
3935
4057
  output,
3936
- ...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
4058
+ ...(!ok ? { error: failureError } : {}),
3937
4059
  ...(detached ? { detached: true } : {}),
4060
+ ...(interrupted ? { interrupted: true } : {}),
3938
4061
  ...(stopped ? { stopped: true } : {}),
3939
4062
  ...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
3940
4063
  ...(requestedContext ? { requestedContext } : {}),
3941
4064
  ...(resolvedContext ? { resolvedContext } : {}),
3942
4065
  ...(outputReference ? { outputReference } : {}),
4066
+ ...(externalAdapter ? { externalAdapter } : {}),
3943
4067
  resumability,
3944
4068
  continuation: { runIds: continuationRunIds },
3945
4069
  artifactPaths: [...artifactPaths],
@@ -3985,8 +4109,18 @@ function terminalWorkflowReceipt(
3985
4109
  workflowRunId: string,
3986
4110
  state: WorkflowReceiptState,
3987
4111
  children: WorkflowScriptChildResult[],
4112
+ workflowChildren?: WorkflowReceipt["workflowChildren"],
3988
4113
  ): WorkflowReceipt {
3989
- return buildWorkflowReceipt({ workflowRunId, state, children });
4114
+ return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren });
4115
+ }
4116
+
4117
+ function workflowFailureMessage(error: unknown, workflowRunId: string, children: WorkflowScriptChildResult[]): string {
4118
+ const text = error instanceof Error ? error.message : String(error);
4119
+ const validationPrefix = "workflowScript validation failed before child launch; no children launched.";
4120
+ if (children.length === 0 && text.includes(validationPrefix)) {
4121
+ return `Workflow '${workflowRunId}' validation failed before child launch; no children launched.${text.slice(text.indexOf(validationPrefix) + validationPrefix.length)}`;
4122
+ }
4123
+ return text;
3990
4124
  }
3991
4125
 
3992
4126
  export async function steerWorkflowChildByKey(input: {
@@ -4029,6 +4163,8 @@ export async function steerWorkflowChildByKey(input: {
4029
4163
  return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
4030
4164
  }
4031
4165
  if (childStatus) {
4166
+ const unsupported = externalRunnerControlError(asyncDir, "steer");
4167
+ if (unsupported) return workflowSteerReceipt(input.key, unsupported);
4032
4168
  const result = await steerAsyncRun({
4033
4169
  state: input.state,
4034
4170
  runId: childRunId,
@@ -4071,6 +4207,9 @@ export function prepareWorkflowLaunchParams(
4071
4207
  ? undefined
4072
4208
  : Math.max(1, options.parentDeadlineAt - Date.now());
4073
4209
  if (typeof childParams.resume === "string") {
4210
+ if (childParams.extensionBindings !== undefined || workflowDefaults.extensionBindings !== undefined) {
4211
+ throw new Error("extensionBindings is not supported with retained resume; resume uses the original retained child binding.");
4212
+ }
4074
4213
  if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
4075
4214
  throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
4076
4215
  }
@@ -4079,6 +4218,15 @@ export function prepareWorkflowLaunchParams(
4079
4218
  const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
4080
4219
  const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
4081
4220
  const worktree = childParams.worktree ?? workflowDefaults.worktree;
4221
+ const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
4222
+ if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
4223
+ const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
4224
+ if (agentContract !== undefined && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
4225
+ const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
4226
+ const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
4227
+ if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
4228
+ const outputMode = Object.hasOwn(childParams, "outputMode") ? childParams.outputMode : workflowDefaults.outputMode;
4229
+ if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("outputMode must be 'inline' or 'file-only'.");
4082
4230
  return {
4083
4231
  action: "resume",
4084
4232
  id: childParams.resume.trim(),
@@ -4087,6 +4235,11 @@ export function prepareWorkflowLaunchParams(
4087
4235
  workflowParentRunId: parentWorkflowRunId,
4088
4236
  workflowKey,
4089
4237
  ...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
4238
+ ...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
4239
+ ...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
4240
+ ...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
4241
+ ...(output !== undefined ? { output: output as string | boolean } : {}),
4242
+ ...(outputMode !== undefined ? { outputMode: outputMode as OutputMode } : {}),
4090
4243
  ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4091
4244
  ...(options.missionDetached ? { mission: false } : {}),
4092
4245
  ...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
@@ -4109,6 +4262,7 @@ export function prepareWorkflowLaunchParams(
4109
4262
  ...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
4110
4263
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
4111
4264
  } as SubagentParamsLike;
4265
+ if (launchParams.extensionBindings !== undefined) launchParams.extensionBindings = normalizeExtensionBindings(launchParams.extensionBindings)!.value;
4112
4266
  const normalizedGate = normalizeGateParams(launchParams);
4113
4267
  if (!normalizedGate.ok) throw new Error(normalizedGate.error);
4114
4268
  return normalizedGate.params;
@@ -4143,11 +4297,12 @@ function workflowChatProgressUpdate(
4143
4297
  runId: string,
4144
4298
  chatProgress: WorkflowChatProgressProjection,
4145
4299
  workflow: NonNullable<Details["workflow"]>,
4300
+ workflowChildren?: Details["workflowChildren"],
4146
4301
  ): AgentToolResult<Details> | undefined {
4147
4302
  if (chatProgress.mode !== "live-card") return undefined;
4148
4303
  return {
4149
4304
  content: [{ type: "text", text: "Workflow running." }],
4150
- details: { mode: "workflow", runId, results: [], workflow, chatProgress },
4305
+ details: { mode: "workflow", runId, results: [], workflow, ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
4151
4306
  };
4152
4307
  }
4153
4308
 
@@ -4158,10 +4313,12 @@ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string
4158
4313
  grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
4159
4314
  }
4160
4315
  : undefined;
4316
+ const ownerParentModel = source.currentSessionId === ownerSessionId ? source.lastParentModel : undefined;
4161
4317
  return {
4162
4318
  ...source,
4163
4319
  baseCwd: ctx.cwd,
4164
4320
  currentSessionId: ownerSessionId,
4321
+ lastParentModel: ownerParentModel,
4165
4322
  parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
4166
4323
  subagentInProgress: false,
4167
4324
  ...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
@@ -4222,6 +4379,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4222
4379
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4223
4380
  const delegatedZeroToolBudgets = new WeakSet<object>();
4224
4381
  const delegatedExecutions = new WeakSet<object>();
4382
+ const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
4225
4383
  const warnedArtifactPackageDirs = new Set<string>();
4226
4384
  const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
4227
4385
  const execute = async (
@@ -4231,11 +4389,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4231
4389
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
4232
4390
  ctx: ExtensionContext,
4233
4391
  preserveActiveSession = false,
4392
+ parentModelOverride?: ParentModel | null,
4234
4393
  ): Promise<AgentToolResult<Details>> => {
4394
+ if (params.action?.trim() === "validate") {
4395
+ const validation = validateWorkflowScript(params.workflowScript ?? "");
4396
+ return {
4397
+ content: [{ type: "text", text: JSON.stringify(validation) }],
4398
+ ...(validation.ok ? {} : { isError: true }),
4399
+ details: { mode: "management", results: [] },
4400
+ };
4401
+ }
4235
4402
  const workflowLaunchObserver = workflowLaunchObservers.get(params);
4236
4403
  const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
4237
4404
  const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
4238
4405
  const delegatedExecution = delegatedExecutions.has(params);
4406
+ const workflowPermitContext = workflowPermitContexts.get(params);
4407
+ const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
4408
+ const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
4239
4409
  if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
4240
4410
  deps.state.foregroundRuns ??= new Map();
4241
4411
  deps.state.foregroundControls ??= new Map();
@@ -4244,7 +4414,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4244
4414
  if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
4245
4415
  const requestParams = normalizedGate.params;
4246
4416
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4417
+ if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
4247
4418
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4419
+ if (delegatedWorkflowPermit) {
4420
+ const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
4421
+ if (permitError) return buildRequestedModeError(requestParams, permitError);
4422
+ if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
4423
+ }
4424
+ const workflowParentModel = parentModelOverride !== undefined
4425
+ ? parentModelOverride
4426
+ : (() => {
4427
+ const currentParentModel = normalizeParentModel(ctx.model);
4428
+ return (preserveActiveSession
4429
+ ? currentParentModel
4430
+ : rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
4431
+ })();
4432
+ if (requestParams.extensionBindings !== undefined) {
4433
+ try {
4434
+ requestParams.extensionBindings = normalizeExtensionBindings(requestParams.extensionBindings)!.value;
4435
+ } catch (error) {
4436
+ return buildRequestedModeError(requestParams, error instanceof Error ? error.message : String(error));
4437
+ }
4438
+ }
4248
4439
  const parentCwd = ctx.cwd;
4249
4440
  const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? resolveConfigDefaultTimeoutMs(deps.config.timeoutMs) ?? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
4250
4441
  const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
@@ -4289,7 +4480,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4289
4480
  runId: workflowRunId,
4290
4481
  kind: "workflow",
4291
4482
  asyncDir: path.join(DIRS.async, workflowRunId),
4292
- }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
4483
+ }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
4293
4484
  } catch (error) {
4294
4485
  if (error instanceof ActiveAsyncCapacityError) {
4295
4486
  deps.state.activeAsyncCapacity = error.snapshot;
@@ -4363,7 +4554,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4363
4554
  deps.state.workflowChildStops ??= new Map();
4364
4555
  deps.state.workflowControllers.set(workflowRunId, controller);
4365
4556
  workflowCapacity?.markWorkflowStarted();
4366
- if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
4557
+ if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
4367
4558
  let status: AsyncStatus = {
4368
4559
  runId: workflowRunId,
4369
4560
  toolCallId,
@@ -4380,6 +4571,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4380
4571
  pid: process.pid,
4381
4572
  steps: [],
4382
4573
  workflow: { trace: [], emits: [], console: [] },
4574
+ workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
4383
4575
  runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
4384
4576
  };
4385
4577
  const appendWorkflowEvent = (event: Record<string, unknown>) => {
@@ -4428,6 +4620,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4428
4620
  if (persistClosed) return;
4429
4621
  const liveJob = deps.state.asyncJobs.get(workflowRunId);
4430
4622
  if (liveJob && (liveJob.status === "complete" || liveJob.status === "failed") && status.state !== "complete" && status.state !== "failed") return;
4623
+ const workflowState = status.state === "complete" ? "completed" : status.state === "failed" || status.state === "rejected" ? "failed" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "running";
4624
+ status.workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: workflowState !== "running", trace: status.workflow?.trace, steps: status.steps });
4431
4625
  status.lastUpdate = Date.now();
4432
4626
  if (!initialPersistenceComplete) {
4433
4627
  writeAtomicJson(statusPath, status);
@@ -4471,6 +4665,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4471
4665
  delete liveJob.agents;
4472
4666
  }
4473
4667
  liveJob.workflow = status.workflow;
4668
+ liveJob.workflowChildren = status.workflowChildren;
4474
4669
  }
4475
4670
  };
4476
4671
  const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
@@ -4505,7 +4700,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4505
4700
  status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
4506
4701
  status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
4507
4702
  };
4508
- const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
4703
+ const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow, workflowChildren: status.workflowChildren };
4509
4704
  deps.state.asyncJobs.set(workflowRunId, workflowJob);
4510
4705
  deps.state.fleetJobs ??= new Map();
4511
4706
  deps.state.fleetJobs.set(workflowRunId, workflowJob);
@@ -4692,7 +4887,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4692
4887
  status: step.status,
4693
4888
  heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
4694
4889
  });
4695
- }, ctx, preserveActiveSession);
4890
+ }, ctx, preserveActiveSession, workflowParentModel);
4696
4891
  });
4697
4892
  workflowResults.push(...result.details.results);
4698
4893
  for (const childResult of result.details.results) {
@@ -4722,7 +4917,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4722
4917
  }
4723
4918
  return child;
4724
4919
  },
4725
- status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
4920
+ status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
4726
4921
  resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
4727
4922
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
4728
4923
  });
@@ -4732,15 +4927,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4732
4927
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
4733
4928
  const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
4734
4929
  const workflowUsage = sumResultsUsage(workflowResults);
4735
- status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
4736
- const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children);
4930
+ const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
4931
+ status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
4932
+ const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren);
4737
4933
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
4738
4934
  try {
4739
4935
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
4740
4936
  } catch (receiptError) {
4741
4937
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
4742
4938
  }
4743
- if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.stopped ? { stopped: 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;
4939
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.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;
4744
4940
  persist();
4745
4941
  persistClosed = true;
4746
4942
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
@@ -4762,7 +4958,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4762
4958
  delete step.activityState;
4763
4959
  }
4764
4960
  }
4765
- status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
4961
+ const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
4962
+ const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
4963
+ status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, workflowChildren });
4766
4964
  if (pauseForDetached) {
4767
4965
  const promoted = promotePausedWorkflowIfSettled(status);
4768
4966
  if (promoted) status = promoted;
@@ -4773,14 +4971,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4773
4971
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
4774
4972
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
4775
4973
  const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
4776
- const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children);
4974
+ const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren);
4777
4975
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
4778
4976
  try {
4779
4977
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
4780
4978
  } catch (receiptError) {
4781
4979
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
4782
4980
  }
4783
- 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, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.stopped ? { stopped: 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;
4981
+ 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, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.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;
4784
4982
  persist();
4785
4983
  persistClosed = true;
4786
4984
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
@@ -4794,7 +4992,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4794
4992
  });
4795
4993
  return attachWorkflowMission(withRunFanoutBudget({
4796
4994
  content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
4797
- details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
4995
+ details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
4798
4996
  }, workflowFanoutBudget));
4799
4997
  }
4800
4998
  const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
@@ -4807,20 +5005,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4807
5005
  const workflowResults: SingleResult[] = [];
4808
5006
  const workflowChildRunIds = new Map<string, string>();
4809
5007
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
5008
+ let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
4810
5009
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
4811
5010
  const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
4812
5011
  const sendWorkflowProgress = () => {
4813
- const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
5012
+ const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren);
4814
5013
  if (update) onUpdate?.(update);
4815
5014
  };
4816
5015
  try {
4817
5016
  const workflow = await runWorkflowScript({
4818
5017
  script: requestParams.workflowScript,
5018
+ ...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
4819
5019
  timeoutMs: timeout,
4820
5020
  signal,
4821
5021
  ...(workflowState ? { state: workflowState } : {}),
4822
5022
  onTrace: (trace) => {
4823
5023
  liveWorkflow = { ...liveWorkflow, trace };
5024
+ liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false, trace });
4824
5025
  sendWorkflowProgress();
4825
5026
  },
4826
5027
  admit: (calls) => {
@@ -4835,6 +5036,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4835
5036
  sendWorkflowProgress();
4836
5037
  },
4837
5038
  launch: async (key, childParams, workflowSignal, admission) => {
5039
+ if (delegatedWorkflowPermit && admission.batch) throw new Error("Workflow child permit does not support runs.all.");
5040
+ if (delegatedWorkflowPermit && childParams.resume !== undefined) throw new Error("Workflow child permit does not support retained resume.");
5041
+ if (delegatedWorkflowPermit && childParams.async === true) throw new Error("Workflow child permit supports one foreground child only.");
4838
5042
  if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."), childParams, deps.state);
4839
5043
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4840
5044
  if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
@@ -4853,6 +5057,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4853
5057
  missionBinding,
4854
5058
  deps.asyncByDefault,
4855
5059
  );
5060
+ if (delegatedWorkflowPermit) {
5061
+ if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
5062
+ const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
5063
+ const childAgent = typeof childRequest.agent === "string"
5064
+ ? resolveAgentName(childRequest.agent, deps.discoverAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
5065
+ : undefined;
5066
+ if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
5067
+ workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: _id, childKey: key } });
5068
+ }
4856
5069
  workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
4857
5070
  status: "running",
4858
5071
  agent: launch.agent,
@@ -4866,7 +5079,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4866
5079
  status: progressStatus,
4867
5080
  heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
4868
5081
  });
4869
- }, ctx, preserveActiveSession);
5082
+ }, ctx, preserveActiveSession, workflowParentModel);
4870
5083
  });
4871
5084
  workflowResults.push(...result.details.results);
4872
5085
  for (const childResult of result.details.results) {
@@ -4887,11 +5100,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4887
5100
  });
4888
5101
  return child;
4889
5102
  },
4890
- status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
5103
+ status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
4891
5104
  resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
4892
5105
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
4893
5106
  });
4894
- const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children);
5107
+ const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
5108
+ const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren);
4895
5109
  const traceLines = workflow.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
4896
5110
  const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
4897
5111
  if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
@@ -4902,11 +5116,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4902
5116
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
4903
5117
  return attachWorkflowMission(withRunFanoutBudget({
4904
5118
  content: [{ type: "text", text: displayText }],
4905
- details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console, receipt }, chatProgress }),
5119
+ details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console, receipt }, chatProgress }),
4906
5120
  }, workflowFanoutBudget));
4907
5121
  } catch (error) {
4908
5122
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
4909
- const text = error instanceof Error ? error.message : String(error);
5123
+ const text = workflowFailureMessage(error, _id, partial.children);
4910
5124
  const traceLines = partial.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
4911
5125
  const sections = [`Workflow failed: ${text}`];
4912
5126
  if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
@@ -4915,11 +5129,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4915
5129
  const workflowText = sections.join("\n\n");
4916
5130
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
4917
5131
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
4918
- const receipt = terminalWorkflowReceipt(_id, "failed", partial.children);
5132
+ const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
5133
+ const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren);
4919
5134
  return attachWorkflowMission(withRunFanoutBudget({
4920
5135
  content: [{ type: "text", text: displayText }],
4921
5136
  isError: true,
4922
- details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console, receipt }, chatProgress }),
5137
+ details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console, receipt }, chatProgress }),
4923
5138
  }, workflowFanoutBudget));
4924
5139
  }
4925
5140
  }
@@ -4933,9 +5148,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4933
5148
  try {
4934
5149
  requestSessionId = resolveCurrentSessionId(ctx.sessionManager);
4935
5150
  requestPiSessionId = ctx.sessionManager.getSessionId() ?? undefined;
4936
- requestParentModel = preserveActiveSession
4937
- ? normalizeParentModel(ctx.model)
4938
- : rememberParentModel(deps.state, requestSessionId, ctx.model);
5151
+ requestParentModel = parentModelOverride !== undefined
5152
+ ? parentModelOverride ?? undefined
5153
+ : preserveActiveSession
5154
+ ? normalizeParentModel(ctx.model)
5155
+ : rememberParentModel(deps.state, requestSessionId, ctx.model);
4939
5156
  } catch (error) {
4940
5157
  if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
4941
5158
  requestParentModel = normalizeParentModel(ctx.model);
@@ -5160,7 +5377,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5160
5377
  }
5161
5378
  const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
5162
5379
  const activeAsyncCapacity = currentSessionId
5163
- ? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
5380
+ ? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })
5164
5381
  : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
5165
5382
  deps.state.activeAsyncCapacity = activeAsyncCapacity;
5166
5383
  return {
@@ -5205,10 +5422,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5205
5422
  if (paramsWithResolvedCwd.view) {
5206
5423
  return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
5207
5424
  }
5208
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5425
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5209
5426
  }
5210
5427
  if (paramsWithResolvedCwd.view === "fleet") {
5211
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5428
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5212
5429
  }
5213
5430
  if (targetRunId) {
5214
5431
  try {
@@ -5239,7 +5456,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5239
5456
  });
5240
5457
  }
5241
5458
  }
5242
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5459
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5243
5460
  }
5244
5461
  if (action === "resume") {
5245
5462
  return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
@@ -5565,7 +5782,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5565
5782
  const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
5566
5783
  const discovered = deps.discoverAgents(effectiveCwd, scope);
5567
5784
  const discoveredAgents = discovered.agents;
5568
- const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics);
5785
+ const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
5786
+ const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics, unknownAgentDiagnosticContext);
5569
5787
  if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
5570
5788
  effectiveParams = canonicalParams.params!;
5571
5789
  const modelScope = discovered.modelScope;
@@ -5614,6 +5832,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5614
5832
  hasTasks,
5615
5833
  hasSingle,
5616
5834
  allowClarifyTaskPrompt,
5835
+ unknownAgentDiagnosticContext,
5617
5836
  );
5618
5837
  if (validationError) return validationError;
5619
5838
 
@@ -5628,6 +5847,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5628
5847
  if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
5629
5848
 
5630
5849
  let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
5850
+ let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
5631
5851
  let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
5632
5852
  let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
5633
5853
  const forkThinkingRequirements = new Map<number, boolean>();
@@ -5663,9 +5883,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5663
5883
  || candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
5664
5884
  );
5665
5885
  };
5886
+ const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
5887
+ ? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
5888
+ : undefined;
5666
5889
  const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
5667
5890
  forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
5891
+ ...(pruneSession ? { pruneSession } : {}),
5668
5892
  });
5893
+ prepareForkSessionForIndex = forkContextResolver.prepareSessionForIndex;
5669
5894
  forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
5670
5895
  forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
5671
5896
  } catch (error) {
@@ -5709,8 +5934,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5709
5934
  runId: asyncRunId,
5710
5935
  kind: "runner",
5711
5936
  asyncDir: path.join(DIRS.async, asyncRunId),
5712
- }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
5713
- deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
5937
+ }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
5938
+ deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
5714
5939
  } catch (error) {
5715
5940
  if (error instanceof ActiveAsyncCapacityError) {
5716
5941
  deps.state.activeAsyncCapacity = error.snapshot;
@@ -5773,6 +5998,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5773
5998
  prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
5774
5999
  return forkSessionFileForIndex(idx);
5775
6000
  };
6001
+ const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6002
+ if (!shouldForkAgent(contextPolicy, agentName)) return;
6003
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6004
+ await prepareForkSessionForIndex(idx);
6005
+ };
5776
6006
  const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
5777
6007
  if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
5778
6008
  prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
@@ -5785,8 +6015,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5785
6015
  const childSessionFileForIndex = (idx?: number) =>
5786
6016
  path.join(sessionDirForIndex(idx), "session.jsonl");
5787
6017
  try {
5788
- if (!(effectiveParams.clarify === true && ctx.hasUI)) {
5789
- preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
6018
+ if (!(effectiveParams.clarify === true && ctx.hasUI) || deps.config.forkContext?.mode === "pruned") {
6019
+ await preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, prepareForkSessionForTask, deps.config.chain?.dynamicFanout?.maxItems);
5790
6020
  }
5791
6021
  } catch (error) {
5792
6022
  activeAsyncCapacity?.rollback();
@@ -5859,6 +6089,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5859
6089
  signal,
5860
6090
  onUpdate: onUpdateWithContext,
5861
6091
  agents,
6092
+ unknownAgentDiagnosticContext,
5862
6093
  recoveryAgents: discoveredAgents,
5863
6094
  runId,
5864
6095
  shareEnabled,
@@ -5891,6 +6122,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5891
6122
  runFanoutBudget,
5892
6123
  topLevelAsyncCapacityEligible,
5893
6124
  activeAsyncCapacity,
6125
+ workflowChildPermitLaunch,
5894
6126
  });
5895
6127
 
5896
6128
  const foregroundDescription = selectedAgentNames.length === 1
@@ -6141,7 +6373,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6141
6373
  if (!normalized.ok) {
6142
6374
  return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
6143
6375
  }
6144
- return executeWithSingleDispatchGuard(id, normalized.params, signal, onUpdate, ctx);
6376
+ const loaded = loadWorkflowScriptPath(normalized.params, ctx.cwd);
6377
+ if (loaded.error) {
6378
+ return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: normalized.params.action ? "management" : "workflow", results: [] } });
6379
+ }
6380
+ return executeWithSingleDispatchGuard(id, loaded.params!, signal, onUpdate, ctx);
6145
6381
  };
6146
6382
 
6147
6383
  const executeDelegated = async (
@@ -6155,13 +6391,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6155
6391
  const privateParams = delegatedParams as SubagentParamsLike & {
6156
6392
  delegatedThinkingOverride?: AgentConfig["thinking"];
6157
6393
  delegatedAllowZeroToolBudget?: true;
6394
+ delegatedWorkflowPermit?: WorkflowChildPermit;
6158
6395
  };
6159
6396
  const thinkingOverride = privateParams.delegatedThinkingOverride;
6160
6397
  const allowZeroToolBudget = privateParams.delegatedAllowZeroToolBudget === true;
6398
+ const workflowPermit = privateParams.delegatedWorkflowPermit;
6161
6399
  delete privateParams.delegatedThinkingOverride;
6162
6400
  delete privateParams.delegatedAllowZeroToolBudget;
6401
+ delete privateParams.delegatedWorkflowPermit;
6163
6402
  if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
6164
6403
  if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
6404
+ if (workflowPermit) workflowPermitContexts.set(delegatedParams, { root: workflowPermit });
6165
6405
  delegatedExecutions.add(delegatedParams);
6166
6406
  return execute(id, delegatedParams, signal, onUpdate, ctx);
6167
6407
  };