@selesai/code 0.8.5 → 0.8.7

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 (231) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/core/agent-session.js +13 -1
  3. package/dist/core/extensions/types.d.ts +2 -0
  4. package/dist/core/provider-composer.js +17 -4
  5. package/dist/core/settings-manager.d.ts +2 -0
  6. package/dist/core/settings-manager.js +3 -0
  7. package/dist/core/system-prompt.js +2 -8
  8. package/dist/core/system-prompt.test.js +6 -0
  9. package/dist/core/tools/index.d.ts +1 -0
  10. package/dist/core/tools/index.js +1 -0
  11. package/dist/core/tools/schema-prune.d.ts +16 -0
  12. package/dist/core/tools/schema-prune.js +39 -0
  13. package/dist/core/tools/schema-prune.test.d.ts +1 -0
  14. package/dist/core/tools/schema-prune.test.js +111 -0
  15. package/dist/defaults/models.json +5 -0
  16. package/dist/defaults/settings.json +1 -0
  17. package/dist/extensions/agent-browser.test.ts +178 -2
  18. package/dist/extensions/agent-browser.ts +2 -0
  19. package/dist/extensions/copy-turn.test.ts +229 -0
  20. package/dist/extensions/copy-turn.ts +11 -0
  21. package/dist/extensions/grep-app/index.test.ts +356 -1
  22. package/dist/extensions/grep-app/index.ts +6 -1
  23. package/dist/extensions/handoff-new.test.ts +351 -161
  24. package/dist/extensions/handoff-new.ts +3 -0
  25. package/dist/extensions/inline-skills.test.ts +93 -0
  26. package/dist/extensions/inline-skills.ts +3 -0
  27. package/dist/extensions/model-prompt-injector/config.json +10 -0
  28. package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
  29. package/dist/extensions/model-prompt-injector/index.ts +151 -0
  30. package/dist/extensions/package.json +1 -0
  31. package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
  32. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
  33. package/dist/extensions/pi-subagents/agents/builder.md +5 -2
  34. package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
  35. package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
  36. package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
  37. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  38. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  39. package/dist/extensions/pi-subagents/docs/configuration.md +78 -11
  40. package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
  41. package/dist/extensions/pi-subagents/docs/missions.md +5 -3
  42. package/dist/extensions/pi-subagents/docs/models.md +1 -1
  43. package/dist/extensions/pi-subagents/docs/observability.md +42 -2
  44. package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
  45. package/dist/extensions/pi-subagents/docs/workflows.md +4 -4
  46. package/dist/extensions/pi-subagents/install.mjs +2 -2
  47. package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
  48. package/dist/extensions/pi-subagents/package.json +1 -1
  49. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
  50. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  51. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +16 -15
  52. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  53. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  54. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  55. package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
  56. package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
  57. package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
  58. package/dist/extensions/pi-subagents/src/extension/config.ts +59 -0
  59. package/dist/extensions/pi-subagents/src/extension/index.ts +146 -23
  60. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
  61. package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
  62. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
  63. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
  64. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
  65. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
  66. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
  67. package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
  68. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
  69. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
  70. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
  71. package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
  72. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
  73. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
  74. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
  75. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +17 -6
  76. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
  77. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
  78. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +11 -3
  79. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
  80. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
  81. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
  82. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +5 -2
  83. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
  84. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +224 -42
  85. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
  86. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
  87. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +35 -26
  88. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +108 -9
  89. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
  90. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
  91. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
  92. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
  93. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
  94. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +2 -1
  95. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
  96. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
  97. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
  98. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -4
  99. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
  100. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +368 -50
  101. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
  102. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
  103. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
  104. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
  105. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
  106. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
  107. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  108. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
  109. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  110. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
  111. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
  112. package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
  113. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
  114. package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
  115. package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
  116. package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +71 -0
  117. package/dist/extensions/pi-subagents/src/shared/types.ts +66 -4
  118. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
  119. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
  120. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
  121. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
  122. package/dist/extensions/pi-subagents/src/tui/fleet.ts +129 -17
  123. package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
  124. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
  125. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
  126. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
  127. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
  128. package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +28 -0
  129. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
  130. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
  131. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
  132. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
  133. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
  134. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
  135. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  136. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
  137. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
  138. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
  139. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +369 -98
  140. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
  141. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
  142. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
  143. package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
  144. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
  145. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
  146. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
  147. package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
  148. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
  149. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  150. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
  151. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
  152. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
  153. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
  154. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
  155. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
  156. package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +103 -0
  157. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
  158. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
  159. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +91 -0
  160. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
  161. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
  162. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
  163. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  164. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
  165. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
  166. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
  167. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
  168. package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
  169. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
  170. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
  171. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
  172. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
  173. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
  174. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
  175. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
  176. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
  177. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
  178. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
  179. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
  180. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
  181. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
  182. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
  183. package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +73 -0
  184. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
  185. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
  186. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
  187. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
  188. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
  189. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
  190. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
  191. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
  192. package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +45 -0
  193. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
  194. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
  195. package/dist/extensions/ponytail/index.js +11 -0
  196. package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
  197. package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
  198. package/dist/extensions/ponytail/test/extension.test.js +274 -140
  199. package/dist/extensions/ponytail/test/helpers.test.js +280 -92
  200. package/dist/extensions/question/index.ts +33 -0
  201. package/dist/extensions/question/question-list.ts +28 -0
  202. package/dist/extensions/question/row-layout.ts +3 -0
  203. package/dist/extensions/question/tests/batch.test.ts +103 -65
  204. package/dist/extensions/question/tests/exp.test.ts +70 -0
  205. package/dist/extensions/question/tests/exp2.test.ts +59 -0
  206. package/dist/extensions/question/tests/exp3.test.ts +47 -0
  207. package/dist/extensions/question/tests/helpers.test.ts +75 -35
  208. package/dist/extensions/question/tests/question-list.test.ts +640 -198
  209. package/dist/extensions/question/tests/row-layout.test.ts +193 -114
  210. package/dist/extensions/question/tests/schemas.test.ts +42 -19
  211. package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
  212. package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
  213. package/dist/extensions/question/tests/wizard.test.ts +771 -50
  214. package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
  215. package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
  216. package/dist/extensions/rtk.test.ts +180 -1
  217. package/dist/extensions/tokenin-onboarding.ts +3 -0
  218. package/dist/extensions/undo.test.ts +720 -0
  219. package/dist/extensions/undo.ts +6 -0
  220. package/dist/extensions/web-agent-onboarding.test.ts +415 -0
  221. package/dist/extensions/workflow/extension.ts +3 -3
  222. package/dist/extensions/workflow/modes.ts +41 -19
  223. package/dist/skills/pi-subagents/SKILL.md +1 -1
  224. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  225. package/dist/skills/pi-subagents/references/execution-controls.md +16 -15
  226. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  227. package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  228. package/docs/plans/workflow-autoloop-reference.md +1 -2
  229. package/docs/plans/workflow-handoff-carryover.md +108 -0
  230. package/docs/settings.md +6 -0
  231. package/package.json +4 -1
@@ -101,6 +101,7 @@ import { stopAsyncRun } from "./async-stop-action.ts";
101
101
  import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
102
102
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
103
103
  import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
104
+ import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
104
105
  import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
105
106
  import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
106
107
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
@@ -108,13 +109,14 @@ import { inspectSubagentStatus } from "../background/run-status.ts";
108
109
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
109
110
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
110
111
  import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
111
- import { updateMission } from "../../missions/store.ts";
112
+ import { MissionNotFoundError, updateMission } from "../../missions/store.ts";
112
113
  import type { MissionWorkflowChildUpdate } from "../../missions/types.ts";
113
114
  import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
114
115
  import { resolveAuthorityDecision } from "../../policy/authority.ts";
115
116
  import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
116
117
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
117
118
  import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
119
+ import { resolveAutoRelaunchDecision, resolveMaxWorkflowAutoRelaunches } from "../../workflows/workflow-auto-relaunch.ts";
118
120
  import { renderWorkflowPrompt } from "../../shared/prompt-resources.ts";
119
121
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
120
122
  import {
@@ -139,6 +141,7 @@ import {
139
141
  type Details,
140
142
  type ExtensionConfig,
141
143
  type ForegroundRunControl,
144
+ type IntercomBridgeConfig,
142
145
  type IntercomEventBus,
143
146
  type JsonSchemaObject,
144
147
  type MaxOutputConfig,
@@ -300,10 +303,14 @@ export interface SubagentParamsLike {
300
303
  concurrency?: number;
301
304
  worktree?: boolean;
302
305
  context?: "fresh" | "fork";
306
+ /** Per-run intercom bridge config. It replaces the global config for this launch only. */
307
+ intercomBridge?: IntercomBridgeConfig;
303
308
  async?: boolean;
304
309
  foregroundOnly?: boolean;
305
310
  timeoutMs?: number;
306
311
  maxRuntimeMs?: number;
312
+ /** Optional hard per-tool-call timeout (ms). Known-fast tools also have a default. */
313
+ toolTimeoutMs?: number;
307
314
  turnBudget?: TurnBudgetConfig;
308
315
  /** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
309
316
  enforceHardTurnLimit?: boolean;
@@ -387,6 +394,8 @@ interface ExecutionContextData {
387
394
  signal: AbortSignal;
388
395
  onUpdate?: (r: AgentToolResult<Details>) => void;
389
396
  agents: AgentConfig[];
397
+ /** Discovered agent definitions before per-run bridge injection. */
398
+ recoveryAgents: AgentConfig[];
390
399
  runId: string;
391
400
  shareEnabled: boolean;
392
401
  sessionRoot: string;
@@ -404,6 +413,8 @@ interface ExecutionContextData {
404
413
  nestedRoute?: NestedRouteInfo;
405
414
  timeoutMs?: number;
406
415
  deadlineAt?: number;
416
+ /** Raw global config.toolTimeoutMs, for per-step resolution in async runners. */
417
+ configToolTimeoutMs?: number;
407
418
  turnBudget?: ResolvedTurnBudget;
408
419
  toolBudget?: ResolvedToolBudget;
409
420
  usageBudget?: UsageBudgetConfig;
@@ -781,7 +792,8 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
781
792
  function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
782
793
  const requested = (params.id ?? params.runId)?.trim();
783
794
  if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
784
- const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId === state.currentSessionId);
795
+ const acceptedSessions = new Set(state.sessionLineage?.length ? state.sessionLineage : [state.currentSessionId]);
796
+ const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId !== undefined && acceptedSessions.has(run.sessionId));
785
797
  const direct = sessionRuns.find((run) => run.runId === requested);
786
798
  const matches = direct ? [direct] : sessionRuns.filter((run) => run.runId.startsWith(requested));
787
799
  if (matches.length === 0) return undefined;
@@ -1511,17 +1523,18 @@ async function resumeAsyncRun(input: {
1511
1523
  const discoveredAgents = discovered.agents;
1512
1524
  const modelScope = discovered.modelScope;
1513
1525
  const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
1526
+ const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
1514
1527
  const intercomBridge = resolveIntercomBridge({
1515
1528
  config: input.deps.config.intercomBridge,
1529
+ override: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
1516
1530
  context: input.params.context,
1517
1531
  orchestratorTarget: sessionName,
1518
1532
  });
1519
1533
  const agents = intercomBridge.active
1520
1534
  ? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
1521
1535
  : discoveredAgents;
1522
- const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
1523
- const discoveredAgentConfig = agents.find((agent) => agent.name === target.agent);
1524
- const agentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
1536
+ const discoveredAgentConfig = discoveredAgents.find((agent) => agent.name === target.agent);
1537
+ const baseAgentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
1525
1538
  name: recoveryDescriptor.agent,
1526
1539
  description: "Persisted async recovery contract",
1527
1540
  systemPrompt: "",
@@ -1531,7 +1544,7 @@ async function resumeAsyncRun(input: {
1531
1544
  source: "project",
1532
1545
  filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
1533
1546
  } : undefined);
1534
- if (!agentConfig) {
1547
+ if (!baseAgentConfig) {
1535
1548
  return {
1536
1549
  content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
1537
1550
  isError: true,
@@ -1672,7 +1685,8 @@ async function resumeAsyncRun(input: {
1672
1685
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1673
1686
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
1674
1687
  }
1675
- const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
1688
+ const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
1689
+ const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
1676
1690
  const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
1677
1691
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
1678
1692
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
@@ -1682,7 +1696,8 @@ async function resumeAsyncRun(input: {
1682
1696
  agent: target.agent,
1683
1697
  task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
1684
1698
  goal: effectiveFollowUp,
1685
- agentConfig: recoveryAgentConfig,
1699
+ agentConfig,
1700
+ recoveryAgentConfig,
1686
1701
  ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
1687
1702
  pi: input.deps.pi,
1688
1703
  cwd: input.requestCwd,
@@ -1717,6 +1732,7 @@ async function resumeAsyncRun(input: {
1717
1732
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1718
1733
  worktreeBaseDir: input.deps.config.worktreeBaseDir,
1719
1734
  controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
1735
+ intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
1720
1736
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1721
1737
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1722
1738
  availableModels,
@@ -1731,7 +1747,7 @@ async function resumeAsyncRun(input: {
1731
1747
  ...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
1732
1748
  ...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
1733
1749
  capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1734
- runFanoutBudget: recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1750
+ runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1735
1751
  parentWorkflowRunId: input.params.workflowParentRunId,
1736
1752
  workflowKey: input.params.workflowKey,
1737
1753
  activeAsyncCapacity,
@@ -2497,16 +2513,19 @@ function resolveStaticLaunchSummary(input: {
2497
2513
  thinkingOverrideForTask: ForkThinkingOverrideForTask;
2498
2514
  }): StaticLaunchSummary {
2499
2515
  const agentConfig = input.agents.find((agent) => agent.name === input.agent);
2500
- const model = resolveEffectiveSubagentModel(
2501
- input.explicitModel,
2502
- agentConfig?.model,
2503
- input.parentModel,
2504
- input.availableModels,
2505
- input.currentProvider,
2506
- input.modelScope === undefined ? {} : { scope: input.modelScope },
2507
- );
2508
- const thinkingOverride = input.thinkingOverrideForTask(input.agent, input.index, model);
2509
- const thinking = resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
2516
+ const externalRunner = agentConfig?.runner?.type === "external-cli";
2517
+ const model = externalRunner
2518
+ ? undefined
2519
+ : resolveEffectiveSubagentModel(
2520
+ input.explicitModel,
2521
+ agentConfig?.model,
2522
+ input.parentModel,
2523
+ input.availableModels,
2524
+ input.currentProvider,
2525
+ input.modelScope === undefined ? {} : { scope: input.modelScope },
2526
+ );
2527
+ const thinkingOverride = externalRunner ? undefined : input.thinkingOverrideForTask(input.agent, input.index, model);
2528
+ const thinking = externalRunner ? undefined : resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
2510
2529
  return {
2511
2530
  agent: input.agent,
2512
2531
  ...(model ? { model } : {}),
@@ -2789,6 +2808,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2789
2808
  toolBudget: data.toolBudget,
2790
2809
  usageBudget: data.usageBudget,
2791
2810
  configToolBudget: data.configToolBudget,
2811
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
2812
+ configToolTimeoutMs: data.configToolTimeoutMs,
2792
2813
  capabilityCeiling: data.capabilityCeiling,
2793
2814
  runFanoutBudget: data.runFanoutBudget,
2794
2815
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2836,6 +2857,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2836
2857
  toolBudget: data.toolBudget,
2837
2858
  usageBudget: data.usageBudget,
2838
2859
  configToolBudget: data.configToolBudget,
2860
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
2861
+ configToolTimeoutMs: data.configToolTimeoutMs,
2839
2862
  capabilityCeiling: data.capabilityCeiling,
2840
2863
  runFanoutBudget: data.runFanoutBudget,
2841
2864
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2862,14 +2885,15 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2862
2885
  const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
2863
2886
  && params.model === undefined
2864
2887
  && (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
2865
- const modelOverride = externalRunnerWithoutExplicitModel
2866
- ? undefined
2888
+ const modelOverride = a.runner?.type === "external-cli"
2889
+ ? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
2867
2890
  : resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
2868
2891
  return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
2869
2892
  agent: params.agent!,
2870
2893
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
2871
2894
  goal: params.task ?? "",
2872
2895
  agentConfig: a,
2896
+ recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
2873
2897
  ctx: asyncCtx,
2874
2898
  availableModels,
2875
2899
  cwd: effectiveCwd,
@@ -2894,6 +2918,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2894
2918
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2895
2919
  worktreeBaseDir: deps.config.worktreeBaseDir,
2896
2920
  controlConfig,
2921
+ intercomBridge: params.intercomBridge,
2897
2922
  controlIntercomTarget,
2898
2923
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
2899
2924
  nestedRoute,
@@ -2905,6 +2930,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2905
2930
  toolBudget: data.toolBudget,
2906
2931
  usageBudget: data.usageBudget,
2907
2932
  configToolBudget: data.configToolBudget,
2933
+ toolTimeoutMs: data.params?.toolTimeoutMs,
2934
+ configToolTimeoutMs: data.configToolTimeoutMs,
2908
2935
  capabilityCeiling: data.capabilityCeiling,
2909
2936
  runFanoutBudget: data.runFanoutBudget,
2910
2937
  parentWorkflowRunId: params.workflowParentRunId,
@@ -2994,6 +3021,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2994
3021
  toolBudget: data.toolBudget,
2995
3022
  usageBudget: data.usageBudget,
2996
3023
  configToolBudget: data.configToolBudget,
3024
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
3025
+ configToolTimeoutMs: data.configToolTimeoutMs,
2997
3026
  permissions: deps.config.permissions,
2998
3027
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2999
3028
  capabilityCeiling: data.capabilityCeiling,
@@ -3061,6 +3090,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3061
3090
  toolBudget: data.toolBudget,
3062
3091
  usageBudget: data.usageBudget,
3063
3092
  configToolBudget: data.configToolBudget,
3093
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
3094
+ configToolTimeoutMs: data.configToolTimeoutMs,
3064
3095
  capabilityCeiling: data.capabilityCeiling,
3065
3096
  runFanoutBudget: data.runFanoutBudget,
3066
3097
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -3148,6 +3179,10 @@ interface ForegroundParallelRunInput {
3148
3179
  worktreeSetup?: WorktreeSetup;
3149
3180
  timeoutMs?: number;
3150
3181
  deadlineAt?: number;
3182
+ /** Per-call per-tool timeout, resolved separately for each task's agent. */
3183
+ toolTimeoutMs?: number;
3184
+ /** Raw global config.toolTimeoutMs, used by each task's resolver. */
3185
+ configToolTimeoutMs?: number;
3151
3186
  turnBudget?: ResolvedTurnBudget;
3152
3187
  usageBudget?: UsageBudgetConfig;
3153
3188
  toolBudgets: (ResolvedToolBudget | undefined)[];
@@ -3239,10 +3274,128 @@ function buildParallelWorktreeTaskCwdError(
3239
3274
  return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
3240
3275
  }
3241
3276
 
3242
- function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
3277
+ function resolveConfiguredSingleRunOutputBaseDir(deps: ExecutorDeps): string | undefined {
3243
3278
  return deps.config.singleRunOutputBaseDir
3244
3279
  ? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
3245
- : path.join(artifactsDir, "outputs", runId);
3280
+ : undefined;
3281
+ }
3282
+
3283
+ function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
3284
+ return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", runId);
3285
+ }
3286
+
3287
+ function resolveWorkflowAggregateOutputPath(
3288
+ output: string | boolean | undefined,
3289
+ ctxCwd: string,
3290
+ workflowCwd: string,
3291
+ outputBaseDir: string,
3292
+ configuredOutputBaseDir: string | undefined,
3293
+ ): string | undefined {
3294
+ if (typeof output === "string" && output && !path.isAbsolute(output) && !configuredOutputBaseDir) return path.resolve(workflowCwd, output);
3295
+ return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
3296
+ }
3297
+
3298
+ function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
3299
+ if (aggregateOutputPath) {
3300
+ const parsed = path.parse(aggregateOutputPath);
3301
+ return path.join(parsed.dir, `${parsed.name}.${workflowKey}${parsed.ext || ".md"}`);
3302
+ }
3303
+ return path.join(artifactsDir, "outputs", workflowRunId, `${workflowKey}.md`);
3304
+ }
3305
+
3306
+ function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string): string | undefined {
3307
+ if (!outputPath) return undefined;
3308
+ try {
3309
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
3310
+ fs.writeFileSync(outputPath, text, "utf-8");
3311
+ return undefined;
3312
+ } catch (error) {
3313
+ return `Output file error: ${outputPath}\n${error instanceof Error ? error.message : String(error)}`;
3314
+ }
3315
+ }
3316
+
3317
+ function appendWorkflowOutputWarning(text: string, warning: string | undefined): string {
3318
+ return warning ? `${text}\n\n${warning}` : text;
3319
+ }
3320
+
3321
+ function resolveWorkflowChildOutputPath(input: {
3322
+ ctxCwd: string;
3323
+ workflowCwd: string;
3324
+ artifactsDir: string;
3325
+ workflowRunId: string;
3326
+ aggregateOutputPath?: string;
3327
+ configuredOutputBaseDir?: string;
3328
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
3329
+ workflowAgentScope?: unknown;
3330
+ key: string;
3331
+ params: Record<string, unknown>;
3332
+ }): string | undefined {
3333
+ const rawOutput = input.params.output;
3334
+ const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
3335
+ const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
3336
+ const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
3337
+ const agents = input.discoverAgents(childCwd, agentScope).agents;
3338
+ const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
3339
+ const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
3340
+ const output = rawOutput === true || rawOutput === "true"
3341
+ ? agentOutput
3342
+ : hasExplicitOutput
3343
+ ? rawOutput
3344
+ : workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key);
3345
+ return resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir);
3346
+ }
3347
+
3348
+ function workflowChildOutputClaims(input: {
3349
+ ctxCwd: string;
3350
+ workflowCwd: string;
3351
+ artifactsDir: string;
3352
+ workflowRunId: string;
3353
+ aggregateOutputPath?: string;
3354
+ configuredOutputBaseDir?: string;
3355
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
3356
+ workflowAgentScope?: unknown;
3357
+ claimedOutputPaths: Map<string, string>;
3358
+ entries: Array<{ key: string; params: Record<string, unknown> }>;
3359
+ }): { error?: string; claims?: Map<string, string> } {
3360
+ const claims = new Map(input.claimedOutputPaths);
3361
+ for (const { key, params } of input.entries) {
3362
+ const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
3363
+ if (!resolved) continue;
3364
+ if (input.aggregateOutputPath && resolved === input.aggregateOutputPath) return { error: `Workflow child '${key}' output resolves to the workflow aggregate output path: ${resolved}. Use a distinct child output path.` };
3365
+ const previous = claims.get(resolved);
3366
+ if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
3367
+ claims.set(resolved, key);
3368
+ }
3369
+ return { claims };
3370
+ }
3371
+
3372
+ function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
3373
+ target.clear();
3374
+ for (const [resolved, key] of claims) target.set(resolved, key);
3375
+ }
3376
+
3377
+ function prepareWorkflowChildLaunchParams(input: {
3378
+ workflowDefaults: SubagentParamsLike;
3379
+ childParams: Record<string, unknown>;
3380
+ parentWorkflowRunId: string;
3381
+ workflowKey: string;
3382
+ ctxCwd: string;
3383
+ workflowCwd: string;
3384
+ artifactsDir: string;
3385
+ aggregateOutputPath?: string;
3386
+ configuredOutputBaseDir?: string;
3387
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
3388
+ workflowAgentScope?: unknown;
3389
+ options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor };
3390
+ }): SubagentParamsLike {
3391
+ let childParams = input.childParams;
3392
+ if (input.childParams.output === undefined && input.childParams.resume === undefined) {
3393
+ childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
3394
+ } else if (input.childParams.resume === undefined) {
3395
+ const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
3396
+ if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
3397
+ }
3398
+ return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
3246
3399
  }
3247
3400
 
3248
3401
  function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
@@ -3406,6 +3559,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
3406
3559
  share: input.shareEnabled || undefined,
3407
3560
  maxOutput: input.maxOutput,
3408
3561
  timeoutMs: input.timeoutMs,
3562
+ toolTimeoutMs: input.toolTimeoutMs,
3563
+ configToolTimeoutMs: input.configToolTimeoutMs,
3409
3564
  turnBudget: input.turnBudget,
3410
3565
  ...(model ? { model } : {}),
3411
3566
  ...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
@@ -3485,6 +3640,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
3485
3640
  acceptanceContext: { mode: "parallel" },
3486
3641
  onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
3487
3642
  timeoutMs: input.timeoutMs,
3643
+ toolTimeoutMs: input.toolTimeoutMs,
3644
+ configToolTimeoutMs: input.configToolTimeoutMs,
3488
3645
  deadlineAt: input.deadlineAt,
3489
3646
  turnBudget: input.turnBudget,
3490
3647
  toolBudget: input.toolBudgets[index],
@@ -3612,7 +3769,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3612
3769
  ...(task.model !== undefined ? { model: task.model } : {}),
3613
3770
  }));
3614
3771
  const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
3615
- resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope }),
3772
+ agentConfigs[i]?.runner?.type === "external-cli"
3773
+ ? undefined
3774
+ : resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope }),
3616
3775
  );
3617
3776
 
3618
3777
  if (params.clarify === true && ctx.hasUI) {
@@ -3647,7 +3806,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3647
3806
  for (let i = 0; i < result.behaviorOverrides.length; i++) {
3648
3807
  const override = result.behaviorOverrides[i];
3649
3808
  if (override?.model !== undefined) {
3650
- modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
3809
+ modelOverrides[i] = agentConfigs[i]?.runner?.type === "external-cli"
3810
+ ? undefined
3811
+ : resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
3651
3812
  behaviorOverrides[i]!.model = override.model;
3652
3813
  }
3653
3814
  if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
@@ -3734,6 +3895,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3734
3895
  usageBudget: data.usageBudget,
3735
3896
  toolBudget: data.toolBudget,
3736
3897
  configToolBudget: data.configToolBudget,
3898
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
3899
+ configToolTimeoutMs: data.configToolTimeoutMs,
3737
3900
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3738
3901
  runFanoutBudget: data.runFanoutBudget,
3739
3902
  }));
@@ -3854,6 +4017,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3854
4017
  worktreeSetup,
3855
4018
  timeoutMs: data.timeoutMs,
3856
4019
  deadlineAt,
4020
+ toolTimeoutMs: data.params?.toolTimeoutMs,
4021
+ configToolTimeoutMs: data.configToolTimeoutMs,
3857
4022
  turnBudget: data.turnBudget,
3858
4023
  usageBudget: data.usageBudget,
3859
4024
  toolBudgets,
@@ -4073,6 +4238,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4073
4238
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
4074
4239
  goal: task,
4075
4240
  agentConfig,
4241
+ recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
4076
4242
  ctx: asyncCtx,
4077
4243
  availableModels,
4078
4244
  cwd: effectiveCwd,
@@ -4097,6 +4263,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4097
4263
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
4098
4264
  worktreeBaseDir: deps.config.worktreeBaseDir,
4099
4265
  controlConfig,
4266
+ intercomBridge: params.intercomBridge,
4100
4267
  controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
4101
4268
  childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
4102
4269
  nestedRoute: data.nestedRoute,
@@ -4246,6 +4413,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4246
4413
  },
4247
4414
  timeoutMs: data.timeoutMs,
4248
4415
  deadlineAt,
4416
+ toolTimeoutMs: params.toolTimeoutMs,
4417
+ configToolTimeoutMs: data.configToolTimeoutMs,
4249
4418
  turnBudget: data.turnBudget,
4250
4419
  enforceHardTurnLimit: params.enforceHardTurnLimit,
4251
4420
  toolBudget: effectiveToolBudget.toolBudget,
@@ -4324,8 +4493,15 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4324
4493
  }
4325
4494
 
4326
4495
  if (r.detached) {
4496
+ const statusRecovery = `subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.`;
4497
+ const blockingRecovery = `subagent_wait({ id: "${runId}" }). Use ${statusRecovery}`;
4498
+ const message = r.detachedReason === "intercom coordination"
4499
+ ? `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first, then wait with ${blockingRecovery}`
4500
+ : r.detachedReason === "user request"
4501
+ ? `Detached at user request: ${params.agent}. The child continues independently. Register a completion wake-up with subagent_wait({ id: "${runId}", nonBlocking: true }), or use ${statusRecovery}`
4502
+ : `Detached before task completion: ${params.agent}. Wait with ${blockingRecovery}`;
4327
4503
  return {
4328
- content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
4504
+ content: [{ type: "text", text: message }],
4329
4505
  details,
4330
4506
  };
4331
4507
  }
@@ -4369,17 +4545,28 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
4369
4545
 
4370
4546
  const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
4371
4547
 
4548
+ /**
4549
+ * Terminal-mission retention can remove a mission while its children still
4550
+ * report. Remember it to prevent later heartbeats from warning repeatedly.
4551
+ */
4552
+ const missionsMissingFromStore = new Set<string>();
4553
+
4372
4554
  function recordMissionWorkflowChild(
4373
4555
  binding: MissionLaunchBinding | undefined,
4374
4556
  workflowRunId: string,
4375
4557
  key: string,
4376
4558
  update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
4377
4559
  ): void {
4378
- if (!binding) return;
4560
+ if (!binding || missionsMissingFromStore.has(binding.missionId)) return;
4379
4561
  const { task: _task, ...durableUpdate } = update;
4380
4562
  try {
4381
4563
  updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
4382
4564
  } catch (error) {
4565
+ if (error instanceof MissionNotFoundError) {
4566
+ missionsMissingFromStore.add(binding.missionId);
4567
+ console.warn(`[pi-subagents] Mission '${binding.missionId}' is no longer in the mission store; stopped recording its workflow children. Terminal-mission retention can prune a mission while its run is still active.`);
4568
+ return;
4569
+ }
4383
4570
  console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
4384
4571
  }
4385
4572
  }
@@ -4449,6 +4636,7 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
4449
4636
  ...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
4450
4637
  output,
4451
4638
  ...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
4639
+ ...(detached ? { detached: true } : {}),
4452
4640
  ...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
4453
4641
  artifactPaths: [...artifactPaths],
4454
4642
  results: result.details.results,
@@ -4469,16 +4657,19 @@ export function prepareWorkflowLaunchParams(
4469
4657
  const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
4470
4658
  const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
4471
4659
  const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
4660
+ const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
4472
4661
  return {
4473
4662
  action: "resume",
4474
4663
  id: childParams.resume.trim(),
4475
4664
  message: typeof childParams.task === "string" ? childParams.task.trim() : "",
4476
4665
  workflowParentRunId: parentWorkflowRunId,
4477
4666
  workflowKey,
4667
+ ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4478
4668
  ...(options.missionDetached ? { mission: false } : {}),
4479
4669
  ...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
4480
4670
  ...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
4481
4671
  ...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
4672
+ ...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
4482
4673
  };
4483
4674
  }
4484
4675
  const launchParams = {
@@ -4665,6 +4856,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4665
4856
  const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
4666
4857
  if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
4667
4858
  const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
4859
+ const workflowArtifactConfig: ArtifactConfig = omitUndefinedProperties({
4860
+ ...DEFAULT_ARTIFACT_CONFIG,
4861
+ enabled: requestParams.artifacts !== false,
4862
+ dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
4863
+ });
4864
+ const workflowArtifactsDir = getArtifactsDir(ctx.sessionManager.getSessionFile() ?? null, workflowCwd, workflowArtifactConfig.dir);
4865
+ if (workflowArtifactConfig.dir === "project" && !warnedArtifactPackageDirs.has(workflowCwd)) {
4866
+ warnedArtifactPackageDirs.add(workflowCwd);
4867
+ const warning = getProjectArtifactPackagingWarning(workflowCwd);
4868
+ if (warning) console.warn(`[pi-subagents] ${warning}`);
4869
+ }
4668
4870
  const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
4669
4871
  const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
4670
4872
  if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
@@ -4743,7 +4945,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4743
4945
  if (workflowRunId) {
4744
4946
  const toolCallId = _id;
4745
4947
  const asyncDir = path.join(DIRS.async, workflowRunId);
4746
- const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
4948
+ const resultPath = resultFilePath(DIRS.results, workflowRunId);
4747
4949
  const statusPath = path.join(asyncDir, "status.json");
4748
4950
  const eventsPath = path.join(asyncDir, "events.jsonl");
4749
4951
  const startedAt = Date.now();
@@ -4782,7 +4984,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4782
4984
  status.lastUpdate = Date.now();
4783
4985
  writeAtomicJson(statusPath, status);
4784
4986
  if (indexedState !== status.state) {
4785
- updateActiveRunIndex(asyncDir, status.state);
4987
+ updateActiveRunIndex(asyncDir, status.state, status.toolCallId);
4786
4988
  indexedState = status.state;
4787
4989
  }
4788
4990
  const job = deps.state.asyncJobs.get(workflowRunId);
@@ -4807,6 +5009,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4807
5009
  job.workflow = status.workflow;
4808
5010
  }
4809
5011
  };
5012
+ const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
5013
+ try {
5014
+ writeAsyncResultFile(resultPath, payload);
5015
+ return true;
5016
+ } catch (error) {
5017
+ const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
5018
+ console.error(message, error);
5019
+ appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
5020
+ return false;
5021
+ }
5022
+ };
4810
5023
  const projectWorkflowActivity = () => {
4811
5024
  const steps = status.steps ?? [];
4812
5025
  const runningSteps = steps.filter((step) => step.status === "running");
@@ -4828,7 +5041,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4828
5041
  status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
4829
5042
  status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
4830
5043
  };
4831
- const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: workflowCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
5044
+ const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
4832
5045
  deps.state.asyncJobs.set(workflowRunId, workflowJob);
4833
5046
  deps.state.fleetJobs ??= new Map();
4834
5047
  deps.state.fleetJobs.set(workflowRunId, workflowJob);
@@ -4838,6 +5051,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4838
5051
  void Promise.resolve().then(async () => {
4839
5052
  const workflowResults: SingleResult[] = [];
4840
5053
  const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = workflowRequest;
5054
+ const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5055
+ const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5056
+ const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId), configuredOutputBaseDir);
5057
+ const claimedOutputPaths = new Map<string, string>();
4841
5058
  const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
4842
5059
  let projectedTraceLength = 0;
4843
5060
  let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
@@ -4857,12 +5074,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4857
5074
  if (entry.operation !== "run") continue;
4858
5075
  const existing = workflowSteps.get(entry.key);
4859
5076
  if (entry.state === "reused" && existing) continue;
4860
- const mapped = entry.state === "started" || entry.state === "reused" ? "running" : entry.state === "completed" ? "completed" : "failed";
5077
+ const mapped = entry.state === "started" || entry.state === "reused"
5078
+ ? "running"
5079
+ : entry.state === "completed"
5080
+ ? "completed"
5081
+ : entry.state === "stopped"
5082
+ ? "stopped"
5083
+ : entry.state === "detached"
5084
+ ? "paused"
5085
+ : "failed";
4861
5086
  if (existing) {
4862
5087
  existing.status = mapped;
4863
5088
  if (entry.agent) existing.agent = entry.agent;
4864
5089
  if (entry.runId) existing.runId = entry.runId;
4865
5090
  if (entry.state === "failed" && !entry.runId && existing.async === undefined) existing.async = false;
5091
+ if (entry.state === "detached") existing.activityState = "needs_attention";
5092
+ else if (existing.status !== "running") delete existing.activityState;
5093
+ if (entry.state === "stopped") existing.stopped = true;
5094
+ else delete existing.stopped;
4866
5095
  if (entry.error === undefined) delete existing.error;
4867
5096
  else existing.error = entry.error;
4868
5097
  if (entry.durationMs === undefined) delete existing.durationMs;
@@ -4877,6 +5106,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4877
5106
  startedAt: Date.now(),
4878
5107
  ...(entry.runId ? { runId: entry.runId } : {}),
4879
5108
  ...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
5109
+ ...(entry.state === "detached" ? { activityState: "needs_attention" as const } : {}),
5110
+ ...(entry.state === "stopped" ? { stopped: true } : {}),
4880
5111
  };
4881
5112
  status.steps?.push(step);
4882
5113
  workflowSteps.set(entry.key, step);
@@ -4889,15 +5120,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4889
5120
  appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
4890
5121
  };
4891
5122
  try {
4892
- const workflow = await runWorkflowScript({
4893
- script: workflowScript,
5123
+ const autoRelaunchCap = resolveMaxWorkflowAutoRelaunches(deps.config.maxWorkflowAutoRelaunches);
5124
+ let relaunched = 0;
5125
+ let capReached = false;
5126
+ let workflow: Awaited<ReturnType<typeof runWorkflowScript>> | undefined;
5127
+ while (true) {
5128
+ if (relaunched > 0) {
5129
+ workflowFanoutBudget = createRunFanoutBudget(`${workflowRunId}#${relaunched + 1}`, workflowFanoutBudget.limit);
5130
+ status = { ...status, workflow: { trace: [], emits: [], console: [] } };
5131
+ }
5132
+ workflow = await runWorkflowScript({
5133
+ script: workflowScript,
4894
5134
  timeoutMs: timeout,
4895
5135
  signal: controller.signal,
4896
5136
  prompts: workflowPrompts,
4897
5137
  ...(workflowState ? { state: workflowState } : {}),
4898
5138
  onTrace: updateTrace,
4899
5139
  admit: (calls) => {
5140
+ const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
5141
+ if (outputClaims.error) throw new Error(outputClaims.error);
4900
5142
  status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5143
+ if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
4901
5144
  persist();
4902
5145
  },
4903
5146
  onEmit: (emits) => {
@@ -4921,7 +5164,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4921
5164
  });
4922
5165
  const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
4923
5166
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
4924
- { ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
5167
+ { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
4925
5168
  missionBinding,
4926
5169
  deps.asyncByDefault,
4927
5170
  );
@@ -4939,7 +5182,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4939
5182
  return execute(randomUUID(), childRequest, workflowSignal, (update) => {
4940
5183
  const progress = update.details.progress?.[0];
4941
5184
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
4942
- if (!progress || !step) return;
5185
+ if (!progress || !step || step.stopped) return;
4943
5186
  step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
4944
5187
  step.activityState = progress.activityState;
4945
5188
  step.lastActivityAt = progress.lastActivityAt;
@@ -4988,21 +5231,57 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4988
5231
  },
4989
5232
  status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
4990
5233
  });
5234
+ const decision = resolveAutoRelaunchDecision(workflow?.value, relaunched, autoRelaunchCap);
5235
+ if (!decision.relaunch) {
5236
+ capReached = decision.capped;
5237
+ break;
5238
+ }
5239
+ relaunched += 1;
5240
+ status = { ...status, workflowRelaunchCount: relaunched };
5241
+ persist();
5242
+ appendWorkflowEvent({ type: "subagent.workflow.relaunched", relaunched });
5243
+ }
5244
+ if (!workflow) throw new Error("Workflow script produced no result.");
4991
5245
  const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
4992
5246
  const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
4993
- const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
5247
+ const relaunchNote = relaunched > 0 ? `Auto-relaunched ${relaunched} time(s) with a fresh fan-out budget. ` : "";
5248
+ const summary = `${relaunchNote}Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
4994
5249
  const workflowUsage = sumResultsUsage(workflowResults);
4995
- 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) };
4996
- writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, 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.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4997
- persist();
4998
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
5250
+ if (capReached) {
5251
+ const relaunchCapError = `Workflow auto-relaunch cap reached after ${relaunched} relaunch(es); the goal is not clean yet. The progress file is current; raise config.maxWorkflowAutoRelaunches or relaunch manually.`;
5252
+ status = compactOptional<AsyncStatus>({ ...status, state: "failed", error: relaunchCapError, endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console } });
5253
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, relaunchCapError);
5254
+ const resultSummary = appendWorkflowOutputWarning(relaunchCapError, outputWarning);
5255
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: "failed", summary: resultSummary, error: relaunchCapError, 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.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
5256
+ persist();
5257
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: "failed", error: status.error });
5258
+ } else {
5259
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary);
5260
+ const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5261
+ 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) };
5262
+ 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.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
5263
+ persist();
5264
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
5265
+ }
4999
5266
  } catch (error) {
5000
5267
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
5001
5268
  const stopped = controller.signal.aborted;
5002
- status = compactOptional<AsyncStatus>({ ...status, state: stopped ? "stopped" : "failed", stopped: stopped || undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
5003
- writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, 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.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
5269
+ const detachedChildKeys = new Set(partial.children.filter((child) => child.detached).map((child) => child.key));
5270
+ const hasRealFailedChild = partial.children.some((child) => !child.ok && !child.detached);
5271
+ const pauseForDetached = !stopped && error instanceof WorkflowScriptError && error.errorKind === "detached-child" && detachedChildKeys.size > 0 && !hasRealFailedChild;
5272
+ const state = stopped ? "stopped" : pauseForDetached ? "paused" : "failed";
5273
+ for (const step of status.steps ?? []) {
5274
+ if (step.workflowKey && detachedChildKeys.has(step.workflowKey)) {
5275
+ step.status = "paused";
5276
+ step.activityState = "needs_attention";
5277
+ }
5278
+ }
5279
+ 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 } });
5280
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed."));
5281
+ const resultSummary = appendWorkflowOutputWarning(status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed."), outputWarning);
5282
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: resultSummary, error: 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.detached ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
5004
5283
  persist();
5005
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
5284
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error, ...(status.activityState ? { activityState: status.activityState } : {}) });
5006
5285
  } finally {
5007
5286
  deps.state.workflowControllers?.delete(workflowRunId);
5008
5287
  deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
@@ -5015,6 +5294,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5015
5294
  }, workflowFanoutBudget));
5016
5295
  }
5017
5296
  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;
5297
+ const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5298
+ const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5299
+ const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id), configuredOutputBaseDir);
5300
+ const claimedOutputPaths = new Map<string, string>();
5018
5301
  const workflowResults: SingleResult[] = [];
5019
5302
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
5020
5303
  const sendWorkflowProgress = () => {
@@ -5033,7 +5316,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5033
5316
  sendWorkflowProgress();
5034
5317
  },
5035
5318
  admit: (calls) => {
5319
+ const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
5320
+ if (outputClaims.error) throw new Error(outputClaims.error);
5036
5321
  claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5322
+ if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
5037
5323
  },
5038
5324
  onEmit: (emits) => {
5039
5325
  liveWorkflow = { ...liveWorkflow, emits };
@@ -5054,7 +5340,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5054
5340
  });
5055
5341
  const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
5056
5342
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
5057
- { ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
5343
+ { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
5058
5344
  missionBinding,
5059
5345
  deps.asyncByDefault,
5060
5346
  );
@@ -5095,8 +5381,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5095
5381
  if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
5096
5382
  if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
5097
5383
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
5384
+ const workflowText = sections.join("\n\n");
5385
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
5386
+ const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5098
5387
  return attachWorkflowMission(withRunFanoutBudget({
5099
- content: [{ type: "text", text: sections.join("\n\n") }],
5388
+ content: [{ type: "text", text: displayText }],
5100
5389
  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 }, chatProgress }),
5101
5390
  }, workflowFanoutBudget));
5102
5391
  } catch (error) {
@@ -5107,8 +5396,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5107
5396
  if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
5108
5397
  if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
5109
5398
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
5399
+ const workflowText = sections.join("\n\n");
5400
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
5401
+ const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5110
5402
  return attachWorkflowMission(withRunFanoutBudget({
5111
- content: [{ type: "text", text: sections.join("\n\n") }],
5403
+ content: [{ type: "text", text: displayText }],
5112
5404
  isError: true,
5113
5405
  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 }, chatProgress }),
5114
5406
  }, workflowFanoutBudget));
@@ -5326,7 +5618,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5326
5618
  }
5327
5619
  if (action === "children.list") {
5328
5620
  deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5329
- const children = listRetainedChildren(DIRS.async, deps.state.currentSessionId);
5621
+ const children = listRetainedChildren(DIRS.async, deps.state.sessionLineage?.length ? deps.state.sessionLineage : [deps.state.currentSessionId]);
5330
5622
  return {
5331
5623
  content: [{ type: "text", text: formatRetainedChildren(children) }],
5332
5624
  details: { mode: "management", results: [] },
@@ -5372,11 +5664,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5372
5664
  details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
5373
5665
  };
5374
5666
  }
5375
- if (action === "status") {
5667
+ if (action === "status" || action === "debug.run") {
5376
5668
  if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5377
5669
  const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
5378
5670
  const hasDirectoryTarget = Boolean(paramsWithResolvedCwd.dir);
5379
- const targetLabel = formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
5671
+ const targetLabel = action === "debug.run" ? "Debug run" : formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
5380
5672
  const withBudget = (result: AgentToolResult<Details>) => {
5381
5673
  const budgeted = withSpawnBudgetStatus(result, deps.state, deps.config, deps.state.currentSessionId);
5382
5674
  return {
@@ -5388,6 +5680,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5388
5680
  };
5389
5681
  const nestedScope = nestedResolutionScopeForExecutor(deps);
5390
5682
  const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
5683
+ if (action === "debug.run") {
5684
+ if (!targetRunId && !hasDirectoryTarget) {
5685
+ return withBudget({ content: [{ type: "text", text: "action='debug.run' requires id, runId, or dir." }], isError: true, details: { mode: "management", results: [] } });
5686
+ }
5687
+ if (paramsWithResolvedCwd.view) {
5688
+ return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
5689
+ }
5690
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5691
+ }
5391
5692
  if (paramsWithResolvedCwd.view === "fleet") {
5392
5693
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5393
5694
  }
@@ -5777,6 +6078,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5777
6078
  const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
5778
6079
  const intercomBridge = resolveIntercomBridge({
5779
6080
  config: deps.config.intercomBridge,
6081
+ override: effectiveParams.intercomBridge,
5780
6082
  context: effectiveParams.context ?? (contextPolicy.usesFork ? "fork" : undefined),
5781
6083
  orchestratorTarget: sessionName,
5782
6084
  });
@@ -5825,6 +6127,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5825
6127
  const parentModel = requestParentModel;
5826
6128
  prepareForkThinking = (agentName, index, modelOverride) => {
5827
6129
  const agentConfig = agents.find((agent) => agent.name === agentName);
6130
+ if (agentConfig?.runner?.type === "external-cli") {
6131
+ forkThinkingRequirements.set(index, true);
6132
+ return;
6133
+ }
5828
6134
  const primaryModel = resolveEffectiveSubagentModel(
5829
6135
  modelOverride,
5830
6136
  agentConfig?.model,
@@ -6039,6 +6345,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6039
6345
  signal,
6040
6346
  onUpdate: onUpdateWithContext,
6041
6347
  agents,
6348
+ recoveryAgents: discoveredAgents,
6042
6349
  runId,
6043
6350
  shareEnabled,
6044
6351
  sessionRoot,
@@ -6060,6 +6367,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6060
6367
  usageBudget: usageBudget.budget,
6061
6368
  allowZeroToolBudget,
6062
6369
  configToolBudget: configToolBudget.toolBudget,
6370
+ configToolTimeoutMs: deps.config.toolTimeoutMs,
6063
6371
  contextPolicy,
6064
6372
  modelScope,
6065
6373
  parentModel: requestParentModel,
@@ -6229,7 +6537,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6229
6537
  };
6230
6538
 
6231
6539
  let nestedForegroundStarted = false;
6540
+ let asyncLaunchFailed = false;
6232
6541
  try {
6542
+ if (effectiveAsync) {
6543
+ deps.state.liveAsyncSessionRoots ??= new Map();
6544
+ deps.state.liveAsyncSessionRoots.set(asyncRunId, sessionRoot);
6545
+ }
6233
6546
  if (workflowLaunchObserver) {
6234
6547
  const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
6235
6548
  const launch = hasSingle
@@ -6243,7 +6556,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6243
6556
  }
6244
6557
  }
6245
6558
  const asyncResult = runAsyncPath(execData, deps);
6246
- if (asyncResult) return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
6559
+ if (asyncResult) {
6560
+ asyncLaunchFailed = asyncResult.isError === true;
6561
+ return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
6562
+ }
6247
6563
  if (foregroundControl) {
6248
6564
  writeNestedForegroundEvent("subagent.nested.started");
6249
6565
  nestedForegroundStarted = true;
@@ -6265,10 +6581,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6265
6581
  return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
6266
6582
  }
6267
6583
  } catch (error) {
6584
+ asyncLaunchFailed = effectiveAsync;
6268
6585
  const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
6269
6586
  if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
6270
6587
  return attachMission(errorResult);
6271
6588
  } finally {
6589
+ if (effectiveAsync && (asyncLaunchFailed || (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt))) deps.state.liveAsyncSessionRoots?.delete(asyncRunId);
6272
6590
  if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
6273
6591
  if (foregroundControl) {
6274
6592
  settleForegroundSchedulingOwner(foregroundControl);