pi-subagents 0.56.0 → 0.58.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 (108) hide show
  1. package/CHANGELOG.md +92 -0
  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 +124 -21
  9. package/docs/configuration.md +37 -0
  10. package/docs/extension-api.md +41 -2
  11. package/docs/models.md +3 -3
  12. package/docs/observability.md +8 -7
  13. package/docs/tool-reference.md +14 -3
  14. package/docs/workflows.md +44 -0
  15. package/package.json +1 -1
  16. package/skills/pi-subagents/SKILL.md +2 -0
  17. package/skills/pi-subagents/references/execution-controls.md +2 -2
  18. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
  19. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  20. package/src/agents/agent-management.ts +36 -5
  21. package/src/agents/agent-refinements.ts +4 -4
  22. package/src/agents/agent-serializer.ts +5 -0
  23. package/src/agents/agents.ts +257 -51
  24. package/src/agents/builtin-names.ts +6 -0
  25. package/src/agents/runtime-agent-events.ts +70 -0
  26. package/src/agents/runtime-agent-registry.ts +18 -4
  27. package/src/api/agents.ts +10 -5
  28. package/src/api/preflight.ts +28 -3
  29. package/src/extension/config.ts +70 -0
  30. package/src/extension/doctor.ts +3 -3
  31. package/src/extension/index.ts +33 -8
  32. package/src/extension/public-execution.ts +29 -13
  33. package/src/extension/rpc.ts +55 -19
  34. package/src/extension/schemas.ts +6 -5
  35. package/src/extension/tool-description.ts +18 -12
  36. package/src/inspectors/herdr/actions.ts +2 -1
  37. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  38. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  39. package/src/integrations/herdr-status.ts +51 -3
  40. package/src/runs/background/active-async-capacity.ts +77 -10
  41. package/src/runs/background/async-execution.ts +127 -19
  42. package/src/runs/background/async-job-tracker.ts +5 -0
  43. package/src/runs/background/async-resume.ts +6 -2
  44. package/src/runs/background/async-retention.ts +20 -3
  45. package/src/runs/background/async-status.ts +7 -0
  46. package/src/runs/background/chain-append.ts +2 -0
  47. package/src/runs/background/chain-root-attachment.ts +15 -1
  48. package/src/runs/background/fleet-view.ts +16 -10
  49. package/src/runs/background/inspect-rpc.ts +8 -8
  50. package/src/runs/background/notify.ts +26 -3
  51. package/src/runs/background/result-delivery-ownership.ts +45 -0
  52. package/src/runs/background/result-files.ts +27 -14
  53. package/src/runs/background/result-watcher.ts +36 -15
  54. package/src/runs/background/run-status.ts +33 -6
  55. package/src/runs/background/scheduled-runs.ts +7 -1
  56. package/src/runs/background/subagent-runner.ts +239 -56
  57. package/src/runs/background/wait-completions.ts +4 -0
  58. package/src/runs/foreground/execution.ts +92 -11
  59. package/src/runs/foreground/foreground-control.ts +6 -0
  60. package/src/runs/foreground/foreground-history.ts +22 -1
  61. package/src/runs/foreground/subagent-executor.ts +322 -102
  62. package/src/runs/foreground/workflow-detach-reconcile.ts +144 -18
  63. package/src/runs/shared/child-protocol.ts +21 -7
  64. package/src/runs/shared/claude-code-adapter.ts +129 -0
  65. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  66. package/src/runs/shared/completion-guard.ts +4 -3
  67. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  68. package/src/runs/shared/dynamic-fanout.ts +3 -3
  69. package/src/runs/shared/external-cli-contract.ts +167 -0
  70. package/src/runs/shared/external-cli-preflight.ts +122 -0
  71. package/src/runs/shared/external-cli-runner.ts +348 -55
  72. package/src/runs/shared/fast-mode-extension.ts +5 -5
  73. package/src/runs/shared/launch-cwd.ts +16 -0
  74. package/src/runs/shared/long-running-guard.ts +2 -1
  75. package/src/runs/shared/mcp-config-sources.ts +386 -0
  76. package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
  77. package/src/runs/shared/model-exclusions.ts +69 -7
  78. package/src/runs/shared/model-fallback.ts +39 -5
  79. package/src/runs/shared/mutation-evidence.ts +7 -2
  80. package/src/runs/shared/nested-events.ts +3 -1
  81. package/src/runs/shared/nested-render.ts +2 -2
  82. package/src/runs/shared/parallel-utils.ts +8 -1
  83. package/src/runs/shared/pi-args.ts +61 -6
  84. package/src/runs/shared/process-signal.ts +13 -0
  85. package/src/runs/shared/run-history.ts +21 -1
  86. package/src/runs/shared/single-output.ts +17 -0
  87. package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
  88. package/src/shared/fork-context.ts +21 -0
  89. package/src/shared/formatters.ts +13 -1
  90. package/src/shared/launch-contract.ts +4 -0
  91. package/src/shared/pruned-fork.ts +450 -0
  92. package/src/shared/session-file-trust.ts +19 -0
  93. package/src/shared/session-tokens.ts +14 -3
  94. package/src/shared/settings.ts +10 -2
  95. package/src/shared/shortcuts.ts +17 -0
  96. package/src/shared/types.ts +160 -10
  97. package/src/shared/utils.ts +6 -29
  98. package/src/shared/workflow-child-permit.ts +116 -0
  99. package/src/slash/delegation-adapters.ts +0 -1
  100. package/src/slash/slash-commands.ts +8 -6
  101. package/src/slash/subagents-admin.ts +3 -0
  102. package/src/tui/fleet-status.ts +27 -10
  103. package/src/tui/fleet-transcript.ts +11 -5
  104. package/src/tui/fleet.ts +28 -13
  105. package/src/tui/render.ts +55 -21
  106. package/src/workflows/scripted-workflow.ts +299 -31
  107. package/src/workflows/workflow-child-summary.ts +117 -0
  108. package/src/workflows/workflow-receipt.ts +155 -5
@@ -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";
@@ -74,8 +75,8 @@ import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig
74
75
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
75
76
  import { isAgentContractV1 } from "../shared/agent-contract.ts";
76
77
  import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
77
- import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
78
- import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
78
+ import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
79
+ import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
79
80
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
80
81
  import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
81
82
  import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
@@ -113,6 +114,7 @@ import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/c
113
114
  import { inspectSubagentStatus } from "../background/run-status.ts";
114
115
  import { getExternalJobProvider } from "../../api/external-job-provider.ts";
115
116
  import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
117
+ import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
116
118
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
117
119
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
118
120
  import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
@@ -122,9 +124,11 @@ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
122
124
  import { resolveAuthorityDecision } from "../../policy/authority.ts";
123
125
  import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
124
126
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
125
- 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";
126
128
  import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
129
+ import { workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
127
130
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
131
+ import { claimWorkflowChildPermit, validateWorkflowChildPermitRoot, type WorkflowChildPermit, type WorkflowChildPermitContext } from "../../shared/workflow-child-permit.ts";
128
132
  import {
129
133
  cleanupWorktrees,
130
134
  createWorktrees,
@@ -146,6 +150,7 @@ import {
146
150
  type ControlEvent,
147
151
  type Details,
148
152
  type ExtensionConfig,
153
+ type ForegroundResumeChild,
149
154
  type ForegroundRunControl,
150
155
  type IntercomBridgeConfig,
151
156
  type IntercomEventBus,
@@ -153,6 +158,7 @@ import {
153
158
  type MaxOutputConfig,
154
159
  type NestedRouteInfo,
155
160
  type NestedRunSummary,
161
+ type OutputMode,
156
162
  type ResolvedControlConfig,
157
163
  type ResolvedTurnBudget,
158
164
  type ResolvedToolBudget,
@@ -163,6 +169,7 @@ import {
163
169
  type ToolBudgetConfig,
164
170
  type TurnBudgetConfig,
165
171
  type UsageBudgetConfig,
172
+ type WorkflowTerminalOutcome,
166
173
  type SubagentRunMode,
167
174
  type SubagentState,
168
175
  DIRS,
@@ -298,6 +305,7 @@ export interface SubagentParamsLike {
298
305
  steeringRecovery?: boolean;
299
306
  mode?: SteerDeliveryMode;
300
307
  workflowScript?: string;
308
+ workflowScriptPath?: string;
301
309
  chatProgress?: "auto" | "off" | "live-card";
302
310
  isolation?: "none" | "worktree";
303
311
  step?: ChainStep;
@@ -397,7 +405,7 @@ interface ExecutorDeps {
397
405
  tempArtifactsDir: string;
398
406
  getSubagentSessionRoot: (parentSessionFile: string | null) => string;
399
407
  expandTilde: (p: string) => string;
400
- discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"] };
408
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
401
409
  allowMutatingManagementActions?: boolean;
402
410
  activateSupervisorTransport?: () => void;
403
411
  refreshResultDelivery?: () => void;
@@ -405,15 +413,19 @@ interface ExecutorDeps {
405
413
  }
406
414
 
407
415
  type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
416
+ type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => Promise<void>;
408
417
  type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
409
418
 
410
419
  interface ExecutionContextData {
411
420
  params: SubagentParamsLike;
412
421
  effectiveCwd: string;
422
+ requestedCwd?: string;
413
423
  ctx: ExtensionContext;
414
424
  signal: AbortSignal;
415
425
  onUpdate?: (r: AgentToolResult<Details>) => void;
416
426
  agents: AgentConfig[];
427
+ /** Exact filesystem discovery provenance retained through defensive execution backstops. */
428
+ unknownAgentDiagnosticContext: UnknownAgentDiagnosticContext;
417
429
  /** Discovered agent definitions before per-run bridge injection. */
418
430
  recoveryAgents: AgentConfig[];
419
431
  runId: string;
@@ -449,12 +461,27 @@ interface ExecutionContextData {
449
461
  runFanoutBudget: RunFanoutBudgetDescriptor;
450
462
  topLevelAsyncCapacityEligible: boolean;
451
463
  activeAsyncCapacity?: ActiveAsyncCapacityHandle;
464
+ workflowChildPermitLaunch?: WorkflowChildPermitContext;
452
465
  }
453
466
 
454
467
  function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
455
468
  return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
456
469
  }
457
470
 
471
+ function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string): { params?: SubagentParamsLike; error?: string } {
472
+ if (params.workflowScriptPath === undefined) return { params };
473
+ const scriptPath = path.resolve(resolveRequestedCwd(runtimeCwd, params.cwd), params.workflowScriptPath);
474
+ let workflowScript: string;
475
+ try {
476
+ workflowScript = fs.readFileSync(scriptPath, "utf8");
477
+ } catch (error) {
478
+ return { error: `Failed to read workflowScriptPath '${scriptPath}': ${error instanceof Error ? error.message : String(error)}` };
479
+ }
480
+ if (!workflowScript.trim()) return { error: `workflowScriptPath file '${scriptPath}' is empty.` };
481
+ const { workflowScriptPath: _workflowScriptPath, ...rest } = params;
482
+ return { params: { ...rest, workflowScript } };
483
+ }
484
+
458
485
  function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
459
486
  const control = state.foregroundControls.get(runId);
460
487
  if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
@@ -543,6 +570,7 @@ function withSpawnBudgetStatus(
543
570
  const activeAsyncCapacity = sessionId
544
571
  ? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
545
572
  liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
573
+ abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(config.capacity?.abandonedSlotReleaseAfterMs),
546
574
  })
547
575
  : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
548
576
  state.activeAsyncCapacity = activeAsyncCapacity;
@@ -645,11 +673,13 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
645
673
  ...(progress?.currentPath ? { currentPath: progress.currentPath } : {}),
646
674
  ...(progress?.turnCount !== undefined ? { turnCount: progress.turnCount } : {}),
647
675
  ...(progress?.tokens !== undefined ? { tokens: progress.tokens } : {}),
676
+ ...(progress?.window !== undefined ? { window: progress.window } : {}),
677
+ ...(progress?.windowPeak !== undefined ? { windowPeak: progress.windowPeak } : {}),
648
678
  ...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
649
679
  };
650
680
  }
651
681
 
652
- function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; extensionBindings?: ExtensionBindings }): void {
682
+ 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 {
653
683
  state.foregroundRuns ??= new Map();
654
684
  const previous = state.foregroundRuns.get(input.runId);
655
685
  const updatedAt = Date.now();
@@ -660,6 +690,13 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
660
690
  ...(input.sessionId ? { sessionId: input.sessionId } : {}),
661
691
  updatedAt,
662
692
  children: input.results.map((result, index) => {
693
+ const resumeContract = omitUndefinedProperties({
694
+ outputSchema: input.params.outputSchema,
695
+ agentContract: input.params.agentContract,
696
+ acceptance: input.params.acceptance,
697
+ output: input.effectiveOutput,
698
+ outputMode: input.effectiveOutputMode,
699
+ });
663
700
  const child = {
664
701
  agent: result.agent,
665
702
  index,
@@ -690,6 +727,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
690
727
  ...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
691
728
  ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
692
729
  ...(result.acceptance ? { acceptance: result.acceptance } : {}),
730
+ ...(Object.keys(resumeContract).length ? { resumeContract } : {}),
693
731
  ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
694
732
  ...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
695
733
  ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
@@ -810,7 +848,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
810
848
  });
811
849
  }
812
850
 
813
- 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; extensionBindings?: ExtensionBindings; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
851
+ 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 {
814
852
  const requested = (params.id ?? params.runId)?.trim();
815
853
  if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
816
854
  const direct = state.foregroundRuns.get(requested);
@@ -841,6 +879,7 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
841
879
  ...(child.model ? { model: child.model } : {}),
842
880
  ...(child.thinking ? { thinking: child.thinking } : {}),
843
881
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
882
+ ...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
844
883
  ...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings)!.value } : {}),
845
884
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
846
885
  };
@@ -1239,6 +1278,7 @@ function appendStepToAsyncChain(input: {
1239
1278
  agents,
1240
1279
  ctx: asyncCtx,
1241
1280
  availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
1281
+ unknownAgentDiagnosticContext: diagnosticContextFromDiscovery(discoveredForAppend, input.requestCwd, scope),
1242
1282
  cwd: status.cwd ?? input.requestCwd,
1243
1283
  chainSkills,
1244
1284
  dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
@@ -1514,9 +1554,14 @@ function externalJobFollowUpStarted(input: { sourceRunId: string; runId: string;
1514
1554
  function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
1515
1555
  const status = readStatus(asyncDir);
1516
1556
  if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
1517
- const message = action === "steer"
1518
- ? "External runners do not accept live steer messages."
1519
- : "External runners do not persist Pi sessions and cannot be resumed.";
1557
+ const externalCli = normalizeExternalCliRunnerStatus(status.steps.find((step) => step.runner?.type === "external-cli")?.runner);
1558
+ const message = externalCli
1559
+ ? action === "steer"
1560
+ ? `External adapter '${externalCli.adapter.id}' does not support runs.steer: ${externalCli.unsupportedReasons.steer}`
1561
+ : `External adapter '${externalCli.adapter.id}' cannot resume: ${externalCli.nonResumableReason}`
1562
+ : action === "steer"
1563
+ ? "External runners do not accept live steer messages."
1564
+ : "External runners do not persist Pi sessions and cannot be resumed.";
1520
1565
  return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
1521
1566
  }
1522
1567
 
@@ -1572,7 +1617,7 @@ async function resumeExternalJobFollowUp(input: {
1572
1617
  runId,
1573
1618
  kind: "runner",
1574
1619
  asyncDir,
1575
- }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
1620
+ }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
1576
1621
  } catch (error) {
1577
1622
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1578
1623
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
@@ -1729,6 +1774,7 @@ async function resumeAsyncRun(input: {
1729
1774
  const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
1730
1775
  const discovered = input.deps.discoverAgents(effectiveCwd, scope);
1731
1776
  const discoveredAgents = discovered.agents;
1777
+ const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
1732
1778
  const modelScope = discovered.modelScope;
1733
1779
  const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
1734
1780
  const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
@@ -1749,6 +1795,7 @@ async function resumeAsyncRun(input: {
1749
1795
  systemPrompt: "",
1750
1796
  systemPromptMode: recoveryDescriptor.systemPromptMode,
1751
1797
  inheritProjectContext: recoveryDescriptor.inheritProjectContext,
1798
+ inheritGlobalContext: recoveryDescriptor.inheritGlobalContext,
1752
1799
  inheritSkills: recoveryDescriptor.inheritSkills,
1753
1800
  source: "project",
1754
1801
  filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
@@ -1756,13 +1803,17 @@ async function resumeAsyncRun(input: {
1756
1803
  } : undefined);
1757
1804
  if (!baseAgentConfig) {
1758
1805
  return {
1759
- content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
1806
+ content: [{ type: "text", text: formatUnknownAgentError(target.agent, unknownAgentDiagnosticContext, "Unknown agent for resume") }],
1760
1807
  isError: true,
1761
1808
  details: { mode: "management", results: [] },
1762
1809
  };
1763
1810
  }
1764
1811
  if (target.source === "async" && target.runner?.type === "external-cli") {
1765
- return { content: [{ type: "text", text: "External runners do not persist Pi sessions and cannot be resumed." }], isError: true, details: { mode: "management", results: [] } };
1812
+ const runner = normalizeExternalCliRunnerStatus(target.runner);
1813
+ const message = runner
1814
+ ? `External adapter '${runner.adapter.id}' cannot resume: ${runner.nonResumableReason}`
1815
+ : "External runners do not persist Pi sessions and cannot be resumed.";
1816
+ return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
1766
1817
  }
1767
1818
  if (target.source === "async" && target.runner?.type === "external-job") {
1768
1819
  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: [] } };
@@ -1807,7 +1858,7 @@ async function resumeAsyncRun(input: {
1807
1858
  runId,
1808
1859
  kind: "runner",
1809
1860
  asyncDir: path.join(DIRS.async, runId),
1810
- }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
1861
+ }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
1811
1862
  } catch (error) {
1812
1863
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
1813
1864
  throw error;
@@ -1833,6 +1884,7 @@ async function resumeAsyncRun(input: {
1833
1884
  label: `Attached ${target.runId}`,
1834
1885
  },
1835
1886
  agents,
1887
+ unknownAgentDiagnosticContext,
1836
1888
  ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
1837
1889
  pi: input.deps.pi,
1838
1890
  cwd: input.requestCwd,
@@ -1905,20 +1957,23 @@ async function resumeAsyncRun(input: {
1905
1957
  sourceRunId: target.runId,
1906
1958
  runId,
1907
1959
  asyncDir: path.join(DIRS.async, runId),
1908
- })
1960
+ }, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) })
1909
1961
  : acquireActiveAsyncCapacity({
1910
1962
  sessionId: input.deps.state.currentSessionId!,
1911
1963
  limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
1912
1964
  runId,
1913
1965
  kind: "runner",
1914
1966
  asyncDir: path.join(DIRS.async, runId),
1915
- });
1967
+ }, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) });
1916
1968
  } catch (error) {
1917
1969
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1918
1970
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
1919
1971
  }
1920
1972
  const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
1921
1973
  const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
1974
+ const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
1975
+ const outputSchema = input.params.outputSchema ?? foregroundContract?.outputSchema ?? recoveryDescriptor?.structuredOutputSchema;
1976
+ const agentContract = input.params.agentContract ?? foregroundContract?.agentContract ?? recoveryDescriptor?.agentContract;
1922
1977
  const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
1923
1978
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
1924
1979
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
@@ -1974,12 +2029,12 @@ async function resumeAsyncRun(input: {
1974
2029
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1975
2030
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1976
2031
  availableModels,
1977
- output: typeof input.params.output === "string" ? input.params.output : recoveryDescriptor?.outputPath,
1978
- outputMode: input.params.outputMode ?? recoveryDescriptor?.outputMode,
1979
- ...(recoveryDescriptor?.agentContract ? { agentContract: recoveryDescriptor.agentContract } : {}),
1980
- ...(recoveryDescriptor?.structuredOutputSchema ? { structuredOutputSchema: recoveryDescriptor.structuredOutputSchema } : {}),
2032
+ output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
2033
+ outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
2034
+ ...(agentContract ? { agentContract } : {}),
2035
+ ...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
1981
2036
  ...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
1982
- ...(recoveryDescriptor?.acceptance !== undefined && input.params.acceptance === undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
2037
+ ...(input.params.acceptance !== undefined ? { acceptance: input.params.acceptance } : foregroundContract?.acceptance !== undefined ? { acceptance: foregroundContract.acceptance } : recoveryDescriptor?.acceptance !== undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
1983
2038
  ...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
1984
2039
  ...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
1985
2040
  ...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
@@ -2058,9 +2113,13 @@ async function resumeAsyncRun(input: {
2058
2113
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
2059
2114
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
2060
2115
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
2116
+ ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
2117
+ ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
2118
+ ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
2119
+ ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
2061
2120
  };
2062
2121
  return {
2063
- content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
2122
+ 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.` }],
2064
2123
  ...(completed.success ? {} : { isError: true }),
2065
2124
  details: {
2066
2125
  ...result.details,
@@ -2225,19 +2284,28 @@ async function maybeBuildForegroundIntercomReceipt(input: {
2225
2284
  };
2226
2285
  }
2227
2286
 
2228
- function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { name?: string; error?: string } {
2287
+ function diagnosticContextFromDiscovery(
2288
+ discovered: { agents: AgentConfig[]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] },
2289
+ cwd: string,
2290
+ scope: AgentScope,
2291
+ ): UnknownAgentDiagnosticContext {
2292
+ if (discovered.cwd && discovered.scope && discovered.directories) return unknownAgentDiagnosticContext({ ...discovered, cwd: discovered.cwd, scope: discovered.scope, directories: discovered.directories });
2293
+ return unknownAgentDiagnosticContext(discoverAgents(path.resolve(cwd), scope));
2294
+ }
2295
+
2296
+ function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { name?: string; error?: string } {
2229
2297
  const resolved = resolveAgentName(name, agents);
2230
2298
  const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
2231
2299
  const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
2232
2300
  if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
2233
2301
  if (resolved.error) return { error: resolved.error };
2234
- if (!resolved.agent) return { error: `Unknown agent: ${name}` };
2302
+ if (!resolved.agent) return { error: formatUnknownAgentError(name, context) };
2235
2303
  return { name: resolved.agent.name };
2236
2304
  }
2237
2305
 
2238
- function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { params?: SubagentParamsLike; error?: string } {
2306
+ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { params?: SubagentParamsLike; error?: string } {
2239
2307
  const resolve = (name: string, location?: string): { name?: string; error?: string } => {
2240
- const result = canonicalizeAgentName(name, agents, diagnostics);
2308
+ const result = canonicalizeAgentName(name, agents, diagnostics, context);
2241
2309
  return result.error && location ? { error: `${result.error} (${location})` } : result;
2242
2310
  };
2243
2311
  if (params.agent) {
@@ -2305,6 +2373,7 @@ function validateExecutionInput(
2305
2373
  hasTasks: boolean,
2306
2374
  hasSingle: boolean,
2307
2375
  allowClarifyTaskPrompt: boolean,
2376
+ context: UnknownAgentDiagnosticContext,
2308
2377
  ): AgentToolResult<Details> | null {
2309
2378
  if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
2310
2379
  return {
@@ -2330,7 +2399,7 @@ function validateExecutionInput(
2330
2399
 
2331
2400
  if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
2332
2401
  return {
2333
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
2402
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent, context) }],
2334
2403
  isError: true,
2335
2404
  details: { mode: "single" as const, results: [] },
2336
2405
  };
@@ -2341,7 +2410,7 @@ function validateExecutionInput(
2341
2410
  const task = params.tasks[i]!;
2342
2411
  if (!agents.find((agent) => agent.name === task.agent)) {
2343
2412
  return {
2344
- content: [{ type: "text", text: `Unknown agent: ${task.agent} (task ${i + 1})` }],
2413
+ content: [{ type: "text", text: `${formatUnknownAgentError(task.agent, context)} (task ${i + 1})` }],
2345
2414
  isError: true,
2346
2415
  details: { mode: "parallel" as const, results: [] },
2347
2416
  };
@@ -2386,7 +2455,7 @@ function validateExecutionInput(
2386
2455
  for (const agentName of stepAgents) {
2387
2456
  if (!agents.find((a) => a.name === agentName)) {
2388
2457
  return {
2389
- content: [{ type: "text", text: `Unknown agent: ${agentName} (step ${i + 1})` }],
2458
+ content: [{ type: "text", text: `${formatUnknownAgentError(agentName, context)} (step ${i + 1})` }],
2390
2459
  isError: true,
2391
2460
  details: { mode: "chain" as const, results: [] },
2392
2461
  };
@@ -2977,21 +3046,21 @@ function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultCo
2977
3046
  });
2978
3047
  }
2979
3048
 
2980
- function preflightForkSessionsForStaticTasks(
3049
+ async function preflightForkSessionsForStaticTasks(
2981
3050
  params: SubagentParamsLike,
2982
3051
  contextPolicy: AgentDefaultContextPolicy,
2983
- sessionFileForTask: ForkSessionFileForTask,
3052
+ prepareSessionForTask: PrepareForkSessionForTask,
2984
3053
  dynamicFanoutMaxItems?: number,
2985
- ): void {
3054
+ ): Promise<void> {
2986
3055
  if (!contextPolicy.usesFork) return;
2987
3056
  if (params.agent) {
2988
- if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0, params.model);
3057
+ if (shouldForkAgent(contextPolicy, params.agent)) await prepareSessionForTask(params.agent, 0, params.model);
2989
3058
  return;
2990
3059
  }
2991
3060
  if (params.tasks) {
2992
- params.tasks.forEach((task, index) => {
2993
- if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index, task.model);
2994
- });
3061
+ for (const [index, task] of params.tasks.entries()) {
3062
+ if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, index, task.model);
3063
+ }
2995
3064
  return;
2996
3065
  }
2997
3066
  if (!params.chain?.length) return;
@@ -2999,7 +3068,7 @@ function preflightForkSessionsForStaticTasks(
2999
3068
  for (const step of params.chain) {
3000
3069
  if (isParallelStep(step)) {
3001
3070
  for (const task of step.parallel) {
3002
- if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex, task.model);
3071
+ if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, flatIndex, task.model);
3003
3072
  flatIndex++;
3004
3073
  }
3005
3074
  continue;
@@ -3007,13 +3076,13 @@ function preflightForkSessionsForStaticTasks(
3007
3076
  if (isDynamicParallelStep(step)) {
3008
3077
  const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
3009
3078
  if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
3010
- for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
3079
+ for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) await prepareSessionForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
3011
3080
  }
3012
3081
  flatIndex += maxItems;
3013
3082
  continue;
3014
3083
  }
3015
3084
  const sequential = step as SequentialStep;
3016
- if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex, sequential.model);
3085
+ if (shouldForkAgent(contextPolicy, sequential.agent)) await prepareSessionForTask(sequential.agent, flatIndex, sequential.model);
3017
3086
  flatIndex++;
3018
3087
  }
3019
3088
  }
@@ -3067,9 +3136,13 @@ async function waitForWorkflowAsyncSingleResult(
3067
3136
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
3068
3137
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
3069
3138
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
3139
+ ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
3140
+ ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
3141
+ ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
3142
+ ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
3070
3143
  });
3071
3144
  return {
3072
- content: [{ type: "text", text: completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` }],
3145
+ 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.` }],
3073
3146
  ...(completed.success ? {} : { isError: true }),
3074
3147
  details: {
3075
3148
  ...launchResult.details,
@@ -3097,6 +3170,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3097
3170
  intercomBridge,
3098
3171
  nestedRoute,
3099
3172
  contextPolicy,
3173
+ unknownAgentDiagnosticContext,
3100
3174
  } = data;
3101
3175
  const hasChain = (params.chain?.length ?? 0) > 0;
3102
3176
  const hasTasks = (params.tasks?.length ?? 0) > 0;
@@ -3135,7 +3209,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3135
3209
  const a = agents.find((x) => x.name === params.agent);
3136
3210
  if (!a) {
3137
3211
  return {
3138
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
3212
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent!, unknownAgentDiagnosticContext) }],
3139
3213
  isError: true,
3140
3214
  details: { mode: "single" as const, results: [] },
3141
3215
  };
@@ -3166,6 +3240,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3166
3240
  ctx: asyncCtx,
3167
3241
  availableModels,
3168
3242
  cwd: effectiveCwd,
3243
+ requestedCwd: data.requestedCwd,
3169
3244
  maxOutput: params.maxOutput,
3170
3245
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3171
3246
  artifactConfig,
@@ -3501,7 +3576,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3501
3576
  const agentConfig = agents.find((a) => a.name === params.agent);
3502
3577
  if (!agentConfig) {
3503
3578
  return {
3504
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
3579
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent!, data.unknownAgentDiagnosticContext) }],
3505
3580
  isError: true,
3506
3581
  details: { mode: "single", results: [] },
3507
3582
  };
@@ -3630,12 +3705,15 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3630
3705
  try {
3631
3706
  r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
3632
3707
  permissions: deps.config.permissions,
3708
+ runtimeSnapshotHost: deps.pi,
3633
3709
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
3634
3710
  llmIntentArbiter: createTaskMutationArbiter(ctx),
3635
3711
  ...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
3636
3712
  context: data.contextPolicy.contextForAgent(params.agent!),
3713
+ unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
3637
3714
  runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
3638
3715
  cwd: singleCwd,
3716
+ requestedCwd: data.requestedCwd,
3639
3717
  signal,
3640
3718
  interruptSignal: interruptController.signal,
3641
3719
  allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
@@ -3672,12 +3750,16 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3672
3750
  agentContract: params.agentContract,
3673
3751
  acceptance: params.acceptance,
3674
3752
  acceptanceContext: { mode: "single" },
3753
+ workflowChildPermitLaunch: data.workflowChildPermitLaunch,
3675
3754
  onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
3676
3755
  onDetachReady: (detach) => {
3677
3756
  detachForeground = detach;
3678
3757
  },
3679
3758
  onDetachedExit: (result) => {
3680
3759
  try {
3760
+ if (worktreeSetup) {
3761
+ finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result });
3762
+ }
3681
3763
  try {
3682
3764
  updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
3683
3765
  } catch {
@@ -3705,7 +3787,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3705
3787
  }
3706
3788
  }
3707
3789
  }
3708
- recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
3790
+ recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0, result);
3709
3791
  },
3710
3792
  timeoutMs: data.timeoutMs,
3711
3793
  deadlineAt,
@@ -3730,7 +3812,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3730
3812
  }
3731
3813
  }
3732
3814
  if (!r.detached) {
3733
- recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
3815
+ recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
3734
3816
  }
3735
3817
 
3736
3818
  let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
@@ -3776,7 +3858,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3776
3858
  usageBudget: usageBudgetState(data.usageBudget, totalCost),
3777
3859
  ...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
3778
3860
  }));
3779
- rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, extensionBindings: params.extensionBindings });
3861
+ rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
3780
3862
 
3781
3863
  const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
3782
3864
  if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
@@ -3925,14 +4007,25 @@ function workflowChildResult(
3925
4007
  result: AgentToolResult<Details>,
3926
4008
  childParams: Record<string, unknown> = {},
3927
4009
  resumeState?: SubagentState,
4010
+ forcedTerminalOutcome?: WorkflowTerminalOutcome,
3928
4011
  ): WorkflowScriptChildResult {
3929
4012
  const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
3930
4013
  const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
3931
4014
  ? result.details.results[0].finalOutput
3932
4015
  : receiptOutput;
4016
+ const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
4017
+ const failureError = childError && receiptOutput && receiptOutput !== childError
4018
+ ? `${childError}\n\n${receiptOutput}`
4019
+ : childError || receiptOutput || output || "Child run failed.";
3933
4020
  const detached = result.details.results.some((child) => child.detached);
3934
4021
  const interrupted = result.details.results.some((child) => child.interrupted);
3935
4022
  const stopped = result.details.results.some((child) => child.stopped);
4023
+ const terminalOutcome = forcedTerminalOutcome
4024
+ ?? (result.details.results.some((child) => child.timedOut)
4025
+ ? { state: "partial" as const, reason: "timeout" as const }
4026
+ : result.details.usageBudget?.exhausted || result.details.results.some((child) => child.turnBudgetExceeded || child.toolBudgetBlocked)
4027
+ ? { state: "partial" as const, reason: "budget_exhausted" as const }
4028
+ : undefined);
3936
4029
  const ok = result.isError !== true && !detached && !interrupted && !stopped;
3937
4030
  const artifactPaths = new Set<string>();
3938
4031
  if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
@@ -3965,13 +4058,22 @@ function workflowChildResult(
3965
4058
  const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
3966
4059
  const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
3967
4060
  ?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
4061
+ const outputPathMapping = typeof childParams.task === "string" ? outputPathMappingFromTask(childParams.task, outputReference) : undefined;
4062
+ const externalResult = result.details.results.length === 1 && result.details.results[0]?.runner?.type === "external-cli" ? result.details.results[0] : undefined;
4063
+ const externalStatus = result.details.asyncDir ? readStatus(result.details.asyncDir) : undefined;
4064
+ const externalStatusStep = externalStatus?.steps?.length === 1 && externalStatus.steps[0]?.runner?.type === "external-cli" ? externalStatus.steps[0] : undefined;
4065
+ const externalRunner = normalizeExternalCliRunnerStatus(externalResult?.runner ?? externalStatusStep?.runner);
4066
+ const externalProcess = externalResult?.externalProcess ?? externalStatusStep?.externalProcess;
4067
+ const externalAdapter = externalRunner ? externalCliReceiptMetadata({ runner: externalRunner, externalProcess, outputReference }) : undefined;
4068
+ if (externalAdapter) resumability = { state: "not-resumable", reason: externalAdapter.nonResumableReason };
3968
4069
  return {
3969
4070
  key,
3970
4071
  ok,
4072
+ ...(terminalOutcome ? { terminalOutcome } : {}),
3971
4073
  ...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
3972
4074
  ...(runId ? { runId } : {}),
3973
4075
  output,
3974
- ...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
4076
+ ...(!ok ? { error: failureError } : {}),
3975
4077
  ...(detached ? { detached: true } : {}),
3976
4078
  ...(interrupted ? { interrupted: true } : {}),
3977
4079
  ...(stopped ? { stopped: true } : {}),
@@ -3979,6 +4081,8 @@ function workflowChildResult(
3979
4081
  ...(requestedContext ? { requestedContext } : {}),
3980
4082
  ...(resolvedContext ? { resolvedContext } : {}),
3981
4083
  ...(outputReference ? { outputReference } : {}),
4084
+ ...(outputPathMapping ? { outputPathMapping } : {}),
4085
+ ...(externalAdapter ? { externalAdapter } : {}),
3982
4086
  resumability,
3983
4087
  continuation: { runIds: continuationRunIds },
3984
4088
  artifactPaths: [...artifactPaths],
@@ -3986,6 +4090,13 @@ function workflowChildResult(
3986
4090
  };
3987
4091
  }
3988
4092
 
4093
+ function workflowOutputPathMappingSummary(children: WorkflowScriptChildResult[]): string {
4094
+ const mappings = children.flatMap((child) => child.outputPathMapping
4095
+ ? [`'${child.key}': requested ${child.outputPathMapping.requestedPath} -> saved ${child.outputPathMapping.savedPath}`]
4096
+ : []);
4097
+ return mappings.length > 0 ? ` Output path mappings: ${mappings.join("; ")}.` : "";
4098
+ }
4099
+
3989
4100
  function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): WorkflowSteerResult {
3990
4101
  const steering = result.details.steering;
3991
4102
  const error = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n") || undefined;
@@ -4024,8 +4135,24 @@ function terminalWorkflowReceipt(
4024
4135
  workflowRunId: string,
4025
4136
  state: WorkflowReceiptState,
4026
4137
  children: WorkflowScriptChildResult[],
4138
+ workflowChildren?: WorkflowReceipt["workflowChildren"],
4139
+ terminalOutcome?: WorkflowTerminalOutcome,
4027
4140
  ): WorkflowReceipt {
4028
- return buildWorkflowReceipt({ workflowRunId, state, children });
4141
+ return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome });
4142
+ }
4143
+
4144
+ function workflowFailureTerminalOutcome(error: unknown, _children: WorkflowScriptChildResult[], usageBudget: ReturnType<typeof usageBudgetState>): WorkflowTerminalOutcome | undefined {
4145
+ if (usageBudget?.exhausted) return { state: "partial", reason: "budget_exhausted" };
4146
+ return error instanceof WorkflowScriptError && error.errorKind === "timeout" ? { state: "partial", reason: "timeout" } : undefined;
4147
+ }
4148
+
4149
+ function workflowFailureMessage(error: unknown, workflowRunId: string, children: WorkflowScriptChildResult[]): string {
4150
+ const text = error instanceof Error ? error.message : String(error);
4151
+ const validationPrefix = "workflowScript validation failed before child launch; no children launched.";
4152
+ if (children.length === 0 && text.includes(validationPrefix)) {
4153
+ return `Workflow '${workflowRunId}' validation failed before child launch; no children launched.${text.slice(text.indexOf(validationPrefix) + validationPrefix.length)}`;
4154
+ }
4155
+ return text;
4029
4156
  }
4030
4157
 
4031
4158
  export async function steerWorkflowChildByKey(input: {
@@ -4068,6 +4195,8 @@ export async function steerWorkflowChildByKey(input: {
4068
4195
  return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
4069
4196
  }
4070
4197
  if (childStatus) {
4198
+ const unsupported = externalRunnerControlError(asyncDir, "steer");
4199
+ if (unsupported) return workflowSteerReceipt(input.key, unsupported);
4071
4200
  const result = await steerAsyncRun({
4072
4201
  state: input.state,
4073
4202
  runId: childRunId,
@@ -4121,6 +4250,15 @@ export function prepareWorkflowLaunchParams(
4121
4250
  const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
4122
4251
  const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
4123
4252
  const worktree = childParams.worktree ?? workflowDefaults.worktree;
4253
+ const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
4254
+ if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
4255
+ const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
4256
+ if (agentContract !== undefined && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
4257
+ const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
4258
+ const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
4259
+ if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
4260
+ const outputMode = Object.hasOwn(childParams, "outputMode") ? childParams.outputMode : workflowDefaults.outputMode;
4261
+ if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("outputMode must be 'inline' or 'file-only'.");
4124
4262
  return {
4125
4263
  action: "resume",
4126
4264
  id: childParams.resume.trim(),
@@ -4129,6 +4267,11 @@ export function prepareWorkflowLaunchParams(
4129
4267
  workflowParentRunId: parentWorkflowRunId,
4130
4268
  workflowKey,
4131
4269
  ...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
4270
+ ...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
4271
+ ...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
4272
+ ...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
4273
+ ...(output !== undefined ? { output: output as string | boolean } : {}),
4274
+ ...(outputMode !== undefined ? { outputMode: outputMode as OutputMode } : {}),
4132
4275
  ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4133
4276
  ...(options.missionDetached ? { mission: false } : {}),
4134
4277
  ...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
@@ -4186,11 +4329,12 @@ function workflowChatProgressUpdate(
4186
4329
  runId: string,
4187
4330
  chatProgress: WorkflowChatProgressProjection,
4188
4331
  workflow: NonNullable<Details["workflow"]>,
4332
+ workflowChildren?: Details["workflowChildren"],
4189
4333
  ): AgentToolResult<Details> | undefined {
4190
4334
  if (chatProgress.mode !== "live-card") return undefined;
4191
4335
  return {
4192
4336
  content: [{ type: "text", text: "Workflow running." }],
4193
- details: { mode: "workflow", runId, results: [], workflow, chatProgress },
4337
+ details: { mode: "workflow", runId, results: [], workflow, ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
4194
4338
  };
4195
4339
  }
4196
4340
 
@@ -4201,10 +4345,12 @@ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string
4201
4345
  grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
4202
4346
  }
4203
4347
  : undefined;
4348
+ const ownerParentModel = source.currentSessionId === ownerSessionId ? source.lastParentModel : undefined;
4204
4349
  return {
4205
4350
  ...source,
4206
4351
  baseCwd: ctx.cwd,
4207
4352
  currentSessionId: ownerSessionId,
4353
+ lastParentModel: ownerParentModel,
4208
4354
  parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
4209
4355
  subagentInProgress: false,
4210
4356
  ...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
@@ -4265,6 +4411,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4265
4411
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4266
4412
  const delegatedZeroToolBudgets = new WeakSet<object>();
4267
4413
  const delegatedExecutions = new WeakSet<object>();
4414
+ const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
4268
4415
  const warnedArtifactPackageDirs = new Set<string>();
4269
4416
  const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
4270
4417
  const execute = async (
@@ -4274,11 +4421,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4274
4421
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
4275
4422
  ctx: ExtensionContext,
4276
4423
  preserveActiveSession = false,
4424
+ parentModelOverride?: ParentModel | null,
4277
4425
  ): Promise<AgentToolResult<Details>> => {
4426
+ if (params.action?.trim() === "validate") {
4427
+ const validation = validateWorkflowScript(params.workflowScript ?? "");
4428
+ return {
4429
+ content: [{ type: "text", text: JSON.stringify(validation) }],
4430
+ ...(validation.ok ? {} : { isError: true }),
4431
+ details: { mode: "management", results: [] },
4432
+ };
4433
+ }
4278
4434
  const workflowLaunchObserver = workflowLaunchObservers.get(params);
4279
4435
  const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
4280
4436
  const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
4281
4437
  const delegatedExecution = delegatedExecutions.has(params);
4438
+ const workflowPermitContext = workflowPermitContexts.get(params);
4439
+ const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
4440
+ const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
4282
4441
  if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
4283
4442
  deps.state.foregroundRuns ??= new Map();
4284
4443
  deps.state.foregroundControls ??= new Map();
@@ -4289,6 +4448,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4289
4448
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4290
4449
  if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
4291
4450
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4451
+ if (delegatedWorkflowPermit) {
4452
+ const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
4453
+ if (permitError) return buildRequestedModeError(requestParams, permitError);
4454
+ if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
4455
+ }
4456
+ const workflowParentModel = parentModelOverride !== undefined
4457
+ ? parentModelOverride
4458
+ : (() => {
4459
+ const currentParentModel = normalizeParentModel(ctx.model);
4460
+ return (preserveActiveSession
4461
+ ? currentParentModel
4462
+ : rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
4463
+ })();
4292
4464
  if (requestParams.extensionBindings !== undefined) {
4293
4465
  try {
4294
4466
  requestParams.extensionBindings = normalizeExtensionBindings(requestParams.extensionBindings)!.value;
@@ -4340,7 +4512,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4340
4512
  runId: workflowRunId,
4341
4513
  kind: "workflow",
4342
4514
  asyncDir: path.join(DIRS.async, workflowRunId),
4343
- }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
4515
+ }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
4344
4516
  } catch (error) {
4345
4517
  if (error instanceof ActiveAsyncCapacityError) {
4346
4518
  deps.state.activeAsyncCapacity = error.snapshot;
@@ -4414,7 +4586,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4414
4586
  deps.state.workflowChildStops ??= new Map();
4415
4587
  deps.state.workflowControllers.set(workflowRunId, controller);
4416
4588
  workflowCapacity?.markWorkflowStarted();
4417
- if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
4589
+ if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
4418
4590
  let status: AsyncStatus = {
4419
4591
  runId: workflowRunId,
4420
4592
  toolCallId,
@@ -4431,6 +4603,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4431
4603
  pid: process.pid,
4432
4604
  steps: [],
4433
4605
  workflow: { trace: [], emits: [], console: [] },
4606
+ workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
4434
4607
  runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
4435
4608
  };
4436
4609
  const appendWorkflowEvent = (event: Record<string, unknown>) => {
@@ -4479,6 +4652,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4479
4652
  if (persistClosed) return;
4480
4653
  const liveJob = deps.state.asyncJobs.get(workflowRunId);
4481
4654
  if (liveJob && (liveJob.status === "complete" || liveJob.status === "failed") && status.state !== "complete" && status.state !== "failed") return;
4655
+ const workflowState = status.state === "complete" ? "completed" : status.state === "failed" || status.state === "rejected" ? "failed" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "running";
4656
+ status.workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: workflowState !== "running", trace: status.workflow?.trace, steps: status.steps });
4482
4657
  status.lastUpdate = Date.now();
4483
4658
  if (!initialPersistenceComplete) {
4484
4659
  writeAtomicJson(statusPath, status);
@@ -4522,6 +4697,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4522
4697
  delete liveJob.agents;
4523
4698
  }
4524
4699
  liveJob.workflow = status.workflow;
4700
+ liveJob.workflowChildren = status.workflowChildren;
4525
4701
  }
4526
4702
  };
4527
4703
  const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
@@ -4556,7 +4732,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4556
4732
  status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
4557
4733
  status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
4558
4734
  };
4559
- 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 };
4735
+ 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 };
4560
4736
  deps.state.asyncJobs.set(workflowRunId, workflowJob);
4561
4737
  deps.state.fleetJobs ??= new Map();
4562
4738
  deps.state.fleetJobs.set(workflowRunId, workflowJob);
@@ -4647,7 +4823,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4647
4823
  version: 1,
4648
4824
  childId: entry.key,
4649
4825
  status: "stopped",
4650
- reason: "user",
4826
+ reason: "subagent-action",
4651
4827
  source: "async",
4652
4828
  stepIndex: status.steps?.indexOf(projectedStep),
4653
4829
  agent: projectedStep.agent,
@@ -4692,7 +4868,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4692
4868
  launch: async (key, childParams, workflowSignal, admission) => {
4693
4869
  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);
4694
4870
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4695
- if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
4871
+ if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
4696
4872
  const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
4697
4873
  const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
4698
4874
  recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
@@ -4743,7 +4919,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4743
4919
  status: step.status,
4744
4920
  heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
4745
4921
  });
4746
- }, ctx, preserveActiveSession);
4922
+ }, ctx, preserveActiveSession, workflowParentModel);
4747
4923
  });
4748
4924
  workflowResults.push(...result.details.results);
4749
4925
  for (const childResult of result.details.results) {
@@ -4773,25 +4949,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4773
4949
  }
4774
4950
  return child;
4775
4951
  },
4776
- status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
4952
+ status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
4777
4953
  resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
4778
4954
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
4779
4955
  });
4780
4956
  const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
4781
4957
  const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
4782
- const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
4958
+ const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).${workflowOutputPathMappingSummary(workflow.children)}`;
4783
4959
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
4784
4960
  const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
4785
4961
  const workflowUsage = sumResultsUsage(workflowResults);
4786
- 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) };
4787
- const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children);
4962
+ const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
4963
+ 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) };
4964
+ const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren);
4788
4965
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
4789
4966
  try {
4790
4967
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
4791
4968
  } catch (receiptError) {
4792
4969
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
4793
4970
  }
4794
- 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.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;
4971
+ 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.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
4795
4972
  persist();
4796
4973
  persistClosed = true;
4797
4974
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
@@ -4813,28 +4990,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4813
4990
  delete step.activityState;
4814
4991
  }
4815
4992
  }
4816
- 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 } });
4993
+ const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
4994
+ const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
4995
+ 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 });
4817
4996
  if (pauseForDetached) {
4818
4997
  const promoted = promotePausedWorkflowIfSettled(status);
4819
4998
  if (promoted) status = promoted;
4820
4999
  }
4821
- const terminalSummary = status.state === "complete"
5000
+ const terminalSummary = `${status.state === "complete"
4822
5001
  ? "Workflow completed after detached child finished."
4823
- : status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.");
5002
+ : status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${workflowOutputPathMappingSummary(partial.children)}`;
4824
5003
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
4825
5004
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
4826
5005
  const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
4827
- const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children);
5006
+ const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5007
+ const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome);
4828
5008
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
4829
5009
  try {
4830
5010
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
4831
5011
  } catch (receiptError) {
4832
5012
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
4833
5013
  }
4834
- 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.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;
5014
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
4835
5015
  persist();
4836
5016
  persistClosed = true;
4837
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
5017
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
4838
5018
  } finally {
4839
5019
  persistClosed = true;
4840
5020
  deps.state.workflowControllers?.delete(workflowRunId);
@@ -4845,7 +5025,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4845
5025
  });
4846
5026
  return attachWorkflowMission(withRunFanoutBudget({
4847
5027
  content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
4848
- details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
5028
+ details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
4849
5029
  }, workflowFanoutBudget));
4850
5030
  }
4851
5031
  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;
@@ -4858,20 +5038,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4858
5038
  const workflowResults: SingleResult[] = [];
4859
5039
  const workflowChildRunIds = new Map<string, string>();
4860
5040
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
5041
+ let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
4861
5042
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
4862
5043
  const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
4863
5044
  const sendWorkflowProgress = () => {
4864
- const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
5045
+ const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren);
4865
5046
  if (update) onUpdate?.(update);
4866
5047
  };
4867
5048
  try {
4868
5049
  const workflow = await runWorkflowScript({
4869
5050
  script: requestParams.workflowScript,
5051
+ ...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
4870
5052
  timeoutMs: timeout,
4871
5053
  signal,
4872
5054
  ...(workflowState ? { state: workflowState } : {}),
4873
5055
  onTrace: (trace) => {
4874
5056
  liveWorkflow = { ...liveWorkflow, trace };
5057
+ liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false, trace });
4875
5058
  sendWorkflowProgress();
4876
5059
  },
4877
5060
  admit: (calls) => {
@@ -4886,9 +5069,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4886
5069
  sendWorkflowProgress();
4887
5070
  },
4888
5071
  launch: async (key, childParams, workflowSignal, admission) => {
5072
+ if (delegatedWorkflowPermit && admission.batch) throw new Error("Workflow child permit does not support runs.all.");
5073
+ if (delegatedWorkflowPermit && childParams.resume !== undefined) throw new Error("Workflow child permit does not support retained resume.");
5074
+ if (delegatedWorkflowPermit && childParams.async === true) throw new Error("Workflow child permit supports one foreground child only.");
4889
5075
  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);
4890
5076
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4891
- if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
5077
+ if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
4892
5078
  const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
4893
5079
  const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
4894
5080
  recordMissionWorkflowChild(missionBinding, _id, key, {
@@ -4904,6 +5090,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4904
5090
  missionBinding,
4905
5091
  deps.asyncByDefault,
4906
5092
  );
5093
+ if (delegatedWorkflowPermit) {
5094
+ if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
5095
+ const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
5096
+ const childAgent = typeof childRequest.agent === "string"
5097
+ ? resolveAgentName(childRequest.agent, deps.discoverAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
5098
+ : undefined;
5099
+ if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
5100
+ workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: _id, childKey: key } });
5101
+ }
4907
5102
  workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
4908
5103
  status: "running",
4909
5104
  agent: launch.agent,
@@ -4917,7 +5112,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4917
5112
  status: progressStatus,
4918
5113
  heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
4919
5114
  });
4920
- }, ctx, preserveActiveSession);
5115
+ }, ctx, preserveActiveSession, workflowParentModel);
4921
5116
  });
4922
5117
  workflowResults.push(...result.details.results);
4923
5118
  for (const childResult of result.details.results) {
@@ -4938,43 +5133,51 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4938
5133
  });
4939
5134
  return child;
4940
5135
  },
4941
- status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
5136
+ status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
4942
5137
  resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
4943
5138
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
4944
5139
  });
4945
- const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children);
5140
+ const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
5141
+ const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren);
4946
5142
  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}` : ""}`);
4947
5143
  const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
4948
5144
  if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
4949
5145
  if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
4950
5146
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
5147
+ const outputMappings = workflowOutputPathMappingSummary(workflow.children).trim();
5148
+ if (outputMappings) sections.push(outputMappings);
4951
5149
  const workflowText = sections.join("\n\n");
4952
5150
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
4953
5151
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
4954
5152
  return attachWorkflowMission(withRunFanoutBudget({
4955
5153
  content: [{ type: "text", text: displayText }],
4956
- 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 }),
5154
+ 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 }),
4957
5155
  }, workflowFanoutBudget));
4958
5156
  } catch (error) {
4959
5157
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
4960
- const text = error instanceof Error ? error.message : String(error);
5158
+ const text = workflowFailureMessage(error, _id, partial.children);
4961
5159
  const traceLines = partial.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
4962
5160
  const sections = [`Workflow failed: ${text}`];
4963
5161
  if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
4964
5162
  if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
4965
5163
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
5164
+ const outputMappings = workflowOutputPathMappingSummary(partial.children).trim();
5165
+ if (outputMappings) sections.push(outputMappings);
4966
5166
  const workflowText = sections.join("\n\n");
4967
5167
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
4968
5168
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
4969
- const receipt = terminalWorkflowReceipt(_id, "failed", partial.children);
5169
+ const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
5170
+ const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5171
+ const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome);
4970
5172
  return attachWorkflowMission(withRunFanoutBudget({
4971
5173
  content: [{ type: "text", text: displayText }],
4972
5174
  isError: true,
4973
- 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 }),
5175
+ 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 }),
4974
5176
  }, workflowFanoutBudget));
4975
5177
  }
4976
5178
  }
4977
5179
  const directParams = requestParams;
5180
+ const requestedCwd = directParams.cwd;
4978
5181
  const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
4979
5182
  const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
4980
5183
  const action = paramsWithResolvedCwd.action;
@@ -4984,9 +5187,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4984
5187
  try {
4985
5188
  requestSessionId = resolveCurrentSessionId(ctx.sessionManager);
4986
5189
  requestPiSessionId = ctx.sessionManager.getSessionId() ?? undefined;
4987
- requestParentModel = preserveActiveSession
4988
- ? normalizeParentModel(ctx.model)
4989
- : rememberParentModel(deps.state, requestSessionId, ctx.model);
5190
+ requestParentModel = parentModelOverride !== undefined
5191
+ ? parentModelOverride ?? undefined
5192
+ : preserveActiveSession
5193
+ ? normalizeParentModel(ctx.model)
5194
+ : rememberParentModel(deps.state, requestSessionId, ctx.model);
4990
5195
  } catch (error) {
4991
5196
  if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
4992
5197
  requestParentModel = normalizeParentModel(ctx.model);
@@ -5211,7 +5416,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5211
5416
  }
5212
5417
  const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
5213
5418
  const activeAsyncCapacity = currentSessionId
5214
- ? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
5419
+ ? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })
5215
5420
  : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
5216
5421
  deps.state.activeAsyncCapacity = activeAsyncCapacity;
5217
5422
  return {
@@ -5256,10 +5461,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5256
5461
  if (paramsWithResolvedCwd.view) {
5257
5462
  return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
5258
5463
  }
5259
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5464
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5260
5465
  }
5261
5466
  if (paramsWithResolvedCwd.view === "fleet") {
5262
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5467
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5263
5468
  }
5264
5469
  if (targetRunId) {
5265
5470
  try {
@@ -5290,7 +5495,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5290
5495
  });
5291
5496
  }
5292
5497
  }
5293
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5498
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5294
5499
  }
5295
5500
  if (action === "resume") {
5296
5501
  return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
@@ -5446,7 +5651,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5446
5651
  if (!isStoppableAsyncStatusStep(resolution.child.step)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in async run '${targetRunId}' is ${resolution.child.step.status}; stop only supports pending or running children.` }], isError: true, details: { mode: "management", results: [] } };
5447
5652
  const stopChild = deps.state.workflowChildStops?.get(targetRunId!);
5448
5653
  if (!stopChild) return { content: [{ type: "text", text: `Workflow ${targetRunId} child stop is unavailable in this extension runtime.` }], isError: true, details: { mode: "management", results: [] } };
5449
- if (!stopChild(resolution.child.id)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
5654
+ if (!stopChild(resolution.child.id, `Workflow child '${resolution.child.id}' stopped.`)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
5450
5655
  try {
5451
5656
  fs.appendFileSync(path.join(asyncJob.asyncDir, "events.jsonl"), `${JSON.stringify({
5452
5657
  type: "subagent.child-status",
@@ -5455,7 +5660,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5455
5660
  childId: resolution.child.id,
5456
5661
  status: "stopping",
5457
5662
  ts: Date.now(),
5458
- reason: "user",
5663
+ reason: "subagent-action",
5459
5664
  source: "async",
5460
5665
  stepIndex: resolution.child.index,
5461
5666
  agent: resolution.child.step.agent,
@@ -5469,17 +5674,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5469
5674
  }
5470
5675
  return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
5471
5676
  }
5472
- workflowController.abort(new Error("Workflow stopped by user."));
5677
+ workflowController.abort(new Error("Workflow stopped."));
5473
5678
  return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
5474
5679
  }
5475
5680
  let resolved: ResolvedSubagentRunId | undefined;
5476
5681
  if (paramsWithResolvedCwd.dir) {
5477
5682
  try {
5478
5683
  const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
5479
- const existingStatus = readStatus(location.asyncDir ?? "");
5480
- if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
5481
- return { content: [{ type: "text", text: `Workflow ${existingStatus.runId} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
5482
- }
5483
5684
  const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location, paramsWithResolvedCwd.childId);
5484
5685
  return stopResult ?? { content: [{ type: "text", text: `No running or queued async run was found for '${targetRunId ?? paramsWithResolvedCwd.dir}'.` }], isError: true, details: { mode: "management", results: [] } };
5485
5686
  } catch (error) {
@@ -5496,12 +5697,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5496
5697
  }
5497
5698
  if (resolved?.kind === "nested") return { content: [{ type: "text", text: "action='stop' supports current-session top-level async runs only." }], isError: true, details: { mode: "management", results: [] } };
5498
5699
  if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='stop' supports async runs only. Use action='interrupt' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
5499
- if (resolved?.kind === "async") {
5500
- const existingStatus = readStatus(resolved.location.asyncDir ?? "");
5501
- if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
5502
- return { content: [{ type: "text", text: `Workflow ${resolved.id} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
5503
- }
5504
- }
5505
5700
  const stopResult = stopAsyncRun(
5506
5701
  deps.state,
5507
5702
  resolved?.kind === "async" ? resolved.id : targetRunId,
@@ -5616,7 +5811,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5616
5811
  const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
5617
5812
  const discovered = deps.discoverAgents(effectiveCwd, scope);
5618
5813
  const discoveredAgents = discovered.agents;
5619
- const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics);
5814
+ const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
5815
+ const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics, unknownAgentDiagnosticContext);
5620
5816
  if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
5621
5817
  effectiveParams = canonicalParams.params!;
5622
5818
  const modelScope = discovered.modelScope;
@@ -5665,6 +5861,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5665
5861
  hasTasks,
5666
5862
  hasSingle,
5667
5863
  allowClarifyTaskPrompt,
5864
+ unknownAgentDiagnosticContext,
5668
5865
  );
5669
5866
  if (validationError) return validationError;
5670
5867
 
@@ -5679,6 +5876,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5679
5876
  if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
5680
5877
 
5681
5878
  let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
5879
+ let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
5682
5880
  let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
5683
5881
  let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
5684
5882
  const forkThinkingRequirements = new Map<number, boolean>();
@@ -5700,6 +5898,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5700
5898
  parentModel,
5701
5899
  forkAvailableModels,
5702
5900
  agentConfig?.modelProvider ?? parentModel?.provider,
5901
+ { source: "inherited" },
5703
5902
  );
5704
5903
  const candidates = buildModelCandidates(
5705
5904
  primaryModel,
@@ -5714,9 +5913,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5714
5913
  || candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
5715
5914
  );
5716
5915
  };
5916
+ const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
5917
+ ? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
5918
+ : undefined;
5717
5919
  const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
5718
5920
  forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
5921
+ ...(pruneSession ? { pruneSession } : {}),
5719
5922
  });
5923
+ prepareForkSessionForIndex = forkContextResolver.prepareSessionForIndex;
5720
5924
  forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
5721
5925
  forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
5722
5926
  } catch (error) {
@@ -5760,8 +5964,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5760
5964
  runId: asyncRunId,
5761
5965
  kind: "runner",
5762
5966
  asyncDir: path.join(DIRS.async, asyncRunId),
5763
- }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
5764
- deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
5967
+ }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
5968
+ deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
5765
5969
  } catch (error) {
5766
5970
  if (error instanceof ActiveAsyncCapacityError) {
5767
5971
  deps.state.activeAsyncCapacity = error.snapshot;
@@ -5824,6 +6028,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5824
6028
  prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
5825
6029
  return forkSessionFileForIndex(idx);
5826
6030
  };
6031
+ const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6032
+ if (!shouldForkAgent(contextPolicy, agentName)) return;
6033
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6034
+ await prepareForkSessionForIndex(idx);
6035
+ };
5827
6036
  const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
5828
6037
  if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
5829
6038
  prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
@@ -5836,8 +6045,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5836
6045
  const childSessionFileForIndex = (idx?: number) =>
5837
6046
  path.join(sessionDirForIndex(idx), "session.jsonl");
5838
6047
  try {
5839
- if (!(effectiveParams.clarify === true && ctx.hasUI)) {
5840
- preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
6048
+ if (!(effectiveParams.clarify === true && ctx.hasUI) || deps.config.forkContext?.mode === "pruned") {
6049
+ await preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, prepareForkSessionForTask, deps.config.chain?.dynamicFanout?.maxItems);
5841
6050
  }
5842
6051
  } catch (error) {
5843
6052
  activeAsyncCapacity?.rollback();
@@ -5906,10 +6115,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5906
6115
  const execData: ExecutionContextData = omitUndefinedProperties({
5907
6116
  params: effectiveParams,
5908
6117
  effectiveCwd,
6118
+ requestedCwd,
5909
6119
  ctx,
5910
6120
  signal,
5911
6121
  onUpdate: onUpdateWithContext,
5912
6122
  agents,
6123
+ unknownAgentDiagnosticContext,
5913
6124
  recoveryAgents: discoveredAgents,
5914
6125
  runId,
5915
6126
  shareEnabled,
@@ -5942,6 +6153,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5942
6153
  runFanoutBudget,
5943
6154
  topLevelAsyncCapacityEligible,
5944
6155
  activeAsyncCapacity,
6156
+ workflowChildPermitLaunch,
5945
6157
  });
5946
6158
 
5947
6159
  const foregroundDescription = selectedAgentNames.length === 1
@@ -6192,7 +6404,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6192
6404
  if (!normalized.ok) {
6193
6405
  return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
6194
6406
  }
6195
- return executeWithSingleDispatchGuard(id, normalized.params, signal, onUpdate, ctx);
6407
+ const loaded = loadWorkflowScriptPath(normalized.params, ctx.cwd);
6408
+ if (loaded.error) {
6409
+ return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: normalized.params.action ? "management" : "workflow", results: [] } });
6410
+ }
6411
+ return executeWithSingleDispatchGuard(id, loaded.params!, signal, onUpdate, ctx);
6196
6412
  };
6197
6413
 
6198
6414
  const executeDelegated = async (
@@ -6206,13 +6422,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6206
6422
  const privateParams = delegatedParams as SubagentParamsLike & {
6207
6423
  delegatedThinkingOverride?: AgentConfig["thinking"];
6208
6424
  delegatedAllowZeroToolBudget?: true;
6425
+ delegatedWorkflowPermit?: WorkflowChildPermit;
6209
6426
  };
6210
6427
  const thinkingOverride = privateParams.delegatedThinkingOverride;
6211
6428
  const allowZeroToolBudget = privateParams.delegatedAllowZeroToolBudget === true;
6429
+ const workflowPermit = privateParams.delegatedWorkflowPermit;
6212
6430
  delete privateParams.delegatedThinkingOverride;
6213
6431
  delete privateParams.delegatedAllowZeroToolBudget;
6432
+ delete privateParams.delegatedWorkflowPermit;
6214
6433
  if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
6215
6434
  if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
6435
+ if (workflowPermit) workflowPermitContexts.set(delegatedParams, { root: workflowPermit });
6216
6436
  delegatedExecutions.add(delegatedParams);
6217
6437
  return execute(id, delegatedParams, signal, onUpdate, ctx);
6218
6438
  };