@selesai/code 0.8.6 → 0.8.8

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 (234) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/dist/cli/args.d.ts +1 -0
  3. package/dist/cli/args.js +11 -0
  4. package/dist/cli/args.test.d.ts +1 -0
  5. package/dist/cli/args.test.js +24 -0
  6. package/dist/core/agent-session.d.ts +4 -1
  7. package/dist/core/agent-session.js +18 -7
  8. package/dist/core/experimental.d.ts +4 -0
  9. package/dist/core/experimental.js +4 -0
  10. package/dist/core/extensions/types.d.ts +6 -0
  11. package/dist/core/keybindings.d.ts +24 -0
  12. package/dist/core/model-config.d.ts +5 -0
  13. package/dist/core/model-config.js +1 -0
  14. package/dist/core/project-trust.js +2 -2
  15. package/dist/core/provider-composer.js +17 -4
  16. package/dist/core/sdk.d.ts +5 -3
  17. package/dist/core/sdk.js +2 -1
  18. package/dist/core/session-manager.js +2 -2
  19. package/dist/core/settings-manager.d.ts +8 -0
  20. package/dist/core/settings-manager.js +15 -0
  21. package/dist/core/system-prompt.js +3 -9
  22. package/dist/core/system-prompt.test.js +6 -0
  23. package/dist/core/tools/bash.js +2 -0
  24. package/dist/core/tools/edit.js +2 -0
  25. package/dist/core/tools/find.js +1 -1
  26. package/dist/core/tools/grep.js +1 -1
  27. package/dist/core/tools/index.d.ts +1 -0
  28. package/dist/core/tools/index.js +1 -0
  29. package/dist/core/tools/read.js +2 -0
  30. package/dist/core/tools/schema-prune.d.ts +16 -0
  31. package/dist/core/tools/schema-prune.js +39 -0
  32. package/dist/core/tools/schema-prune.test.d.ts +1 -0
  33. package/dist/core/tools/schema-prune.test.js +111 -0
  34. package/dist/core/tools/write.js +2 -0
  35. package/dist/defaults/settings.json +1 -0
  36. package/dist/extensions/model-prompt-injector/config.json +10 -0
  37. package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
  38. package/dist/extensions/model-prompt-injector/index.ts +151 -0
  39. package/dist/extensions/package.json +1 -0
  40. package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
  41. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
  42. package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
  43. package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
  44. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  45. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  46. package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
  47. package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
  48. package/dist/extensions/pi-subagents/docs/missions.md +5 -3
  49. package/dist/extensions/pi-subagents/docs/models.md +1 -1
  50. package/dist/extensions/pi-subagents/docs/observability.md +42 -2
  51. package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
  52. package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
  53. package/dist/extensions/pi-subagents/install.mjs +2 -2
  54. package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
  55. package/dist/extensions/pi-subagents/package.json +1 -1
  56. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
  57. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  58. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
  59. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  60. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  61. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  62. package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
  63. package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
  64. package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
  65. package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
  66. package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
  67. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
  68. package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
  69. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
  70. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
  71. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
  72. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
  73. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
  74. package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
  75. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
  76. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
  77. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
  78. package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
  79. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
  80. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
  81. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
  82. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
  83. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
  84. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
  85. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
  86. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
  87. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
  88. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
  89. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
  90. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +187 -41
  91. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
  92. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
  93. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
  94. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
  95. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
  96. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
  97. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
  98. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
  99. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
  100. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
  101. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
  102. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
  103. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
  104. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
  105. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
  106. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
  107. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
  108. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
  109. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
  110. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
  111. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  112. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
  113. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  114. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
  115. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
  116. package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
  117. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
  118. package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
  119. package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
  120. package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
  121. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
  122. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
  127. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
  128. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
  129. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
  130. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
  131. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
  132. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
  133. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
  134. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
  135. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
  136. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
  137. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  138. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
  139. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
  140. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
  141. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +395 -98
  142. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
  143. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
  144. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
  145. package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
  146. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
  147. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
  148. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
  149. package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
  150. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
  151. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  152. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
  153. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
  154. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
  155. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
  156. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
  157. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
  158. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
  159. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
  160. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
  161. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
  162. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
  163. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
  164. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  165. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
  166. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
  167. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
  168. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
  169. package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
  170. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
  171. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
  172. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
  173. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
  175. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
  176. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
  177. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
  178. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
  179. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
  180. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
  181. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
  182. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
  183. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
  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-chat-progress.test.ts +94 -1
  193. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
  194. package/dist/extensions/question/index.ts +21 -6
  195. package/dist/extensions/question/question-list.ts +7 -1
  196. package/dist/extensions/question/row-layout.ts +3 -0
  197. package/dist/extensions/question/tests/exp.test.ts +70 -0
  198. package/dist/extensions/question/tests/exp2.test.ts +59 -0
  199. package/dist/extensions/question/tests/exp3.test.ts +47 -0
  200. package/dist/extensions/question/tests/helpers.test.ts +7 -0
  201. package/dist/extensions/question/tests/question-list.test.ts +140 -0
  202. package/dist/extensions/question/tests/row-layout.test.ts +13 -0
  203. package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
  204. package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
  205. package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
  206. package/dist/extensions/tokenin-onboarding.ts +1 -1
  207. package/dist/index.d.ts +1 -1
  208. package/dist/main.js +4 -0
  209. package/dist/modes/interactive/components/model-selector.js +2 -1
  210. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  211. package/dist/modes/interactive/components/settings-selector.js +10 -0
  212. package/dist/modes/interactive/components/tool-execution.js +10 -1
  213. package/dist/modes/interactive/interactive-mode.d.ts +8 -2
  214. package/dist/modes/interactive/interactive-mode.js +81 -25
  215. package/dist/modes/interactive/model-catalog-refresh.d.ts +6 -0
  216. package/dist/modes/interactive/model-catalog-refresh.js +33 -0
  217. package/dist/modes/interactive/theme/dark.json +2 -0
  218. package/dist/modes/interactive/theme/light.json +2 -0
  219. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  220. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  221. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  222. package/dist/modes/interactive/theme/theme.d.ts +5 -3
  223. package/dist/modes/interactive/theme/theme.js +12 -2
  224. package/dist/skills/pi-subagents/SKILL.md +1 -1
  225. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  226. package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
  227. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  228. package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  229. package/dist/utils/abort.d.ts +4 -0
  230. package/dist/utils/abort.js +47 -0
  231. package/dist/utils/tools-manager.d.ts +5 -1
  232. package/dist/utils/tools-manager.js +12 -21
  233. package/docs/settings.md +6 -0
  234. package/package.json +6 -6
@@ -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,7 +109,7 @@ 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";
@@ -140,6 +141,7 @@ import {
140
141
  type Details,
141
142
  type ExtensionConfig,
142
143
  type ForegroundRunControl,
144
+ type IntercomBridgeConfig,
143
145
  type IntercomEventBus,
144
146
  type JsonSchemaObject,
145
147
  type MaxOutputConfig,
@@ -301,10 +303,14 @@ export interface SubagentParamsLike {
301
303
  concurrency?: number;
302
304
  worktree?: boolean;
303
305
  context?: "fresh" | "fork";
306
+ /** Per-run intercom bridge config. It replaces the global config for this launch only. */
307
+ intercomBridge?: IntercomBridgeConfig;
304
308
  async?: boolean;
305
309
  foregroundOnly?: boolean;
306
310
  timeoutMs?: number;
307
311
  maxRuntimeMs?: number;
312
+ /** Optional hard per-tool-call timeout (ms). Known-fast tools also have a default. */
313
+ toolTimeoutMs?: number;
308
314
  turnBudget?: TurnBudgetConfig;
309
315
  /** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
310
316
  enforceHardTurnLimit?: boolean;
@@ -388,6 +394,8 @@ interface ExecutionContextData {
388
394
  signal: AbortSignal;
389
395
  onUpdate?: (r: AgentToolResult<Details>) => void;
390
396
  agents: AgentConfig[];
397
+ /** Discovered agent definitions before per-run bridge injection. */
398
+ recoveryAgents: AgentConfig[];
391
399
  runId: string;
392
400
  shareEnabled: boolean;
393
401
  sessionRoot: string;
@@ -405,6 +413,8 @@ interface ExecutionContextData {
405
413
  nestedRoute?: NestedRouteInfo;
406
414
  timeoutMs?: number;
407
415
  deadlineAt?: number;
416
+ /** Raw global config.toolTimeoutMs, for per-step resolution in async runners. */
417
+ configToolTimeoutMs?: number;
408
418
  turnBudget?: ResolvedTurnBudget;
409
419
  toolBudget?: ResolvedToolBudget;
410
420
  usageBudget?: UsageBudgetConfig;
@@ -1513,17 +1523,18 @@ async function resumeAsyncRun(input: {
1513
1523
  const discoveredAgents = discovered.agents;
1514
1524
  const modelScope = discovered.modelScope;
1515
1525
  const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
1526
+ const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
1516
1527
  const intercomBridge = resolveIntercomBridge({
1517
1528
  config: input.deps.config.intercomBridge,
1529
+ override: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
1518
1530
  context: input.params.context,
1519
1531
  orchestratorTarget: sessionName,
1520
1532
  });
1521
1533
  const agents = intercomBridge.active
1522
1534
  ? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
1523
1535
  : discoveredAgents;
1524
- const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
1525
- const discoveredAgentConfig = agents.find((agent) => agent.name === target.agent);
1526
- const agentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
1536
+ const discoveredAgentConfig = discoveredAgents.find((agent) => agent.name === target.agent);
1537
+ const baseAgentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
1527
1538
  name: recoveryDescriptor.agent,
1528
1539
  description: "Persisted async recovery contract",
1529
1540
  systemPrompt: "",
@@ -1533,7 +1544,7 @@ async function resumeAsyncRun(input: {
1533
1544
  source: "project",
1534
1545
  filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
1535
1546
  } : undefined);
1536
- if (!agentConfig) {
1547
+ if (!baseAgentConfig) {
1537
1548
  return {
1538
1549
  content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
1539
1550
  isError: true,
@@ -1674,7 +1685,8 @@ async function resumeAsyncRun(input: {
1674
1685
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1675
1686
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
1676
1687
  }
1677
- const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
1688
+ const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
1689
+ const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
1678
1690
  const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
1679
1691
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
1680
1692
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
@@ -1684,7 +1696,8 @@ async function resumeAsyncRun(input: {
1684
1696
  agent: target.agent,
1685
1697
  task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
1686
1698
  goal: effectiveFollowUp,
1687
- agentConfig: recoveryAgentConfig,
1699
+ agentConfig,
1700
+ recoveryAgentConfig,
1688
1701
  ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
1689
1702
  pi: input.deps.pi,
1690
1703
  cwd: input.requestCwd,
@@ -1719,6 +1732,7 @@ async function resumeAsyncRun(input: {
1719
1732
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1720
1733
  worktreeBaseDir: input.deps.config.worktreeBaseDir,
1721
1734
  controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
1735
+ intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
1722
1736
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1723
1737
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1724
1738
  availableModels,
@@ -1733,7 +1747,7 @@ async function resumeAsyncRun(input: {
1733
1747
  ...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
1734
1748
  ...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
1735
1749
  capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1736
- runFanoutBudget: recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1750
+ runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1737
1751
  parentWorkflowRunId: input.params.workflowParentRunId,
1738
1752
  workflowKey: input.params.workflowKey,
1739
1753
  activeAsyncCapacity,
@@ -2499,16 +2513,19 @@ function resolveStaticLaunchSummary(input: {
2499
2513
  thinkingOverrideForTask: ForkThinkingOverrideForTask;
2500
2514
  }): StaticLaunchSummary {
2501
2515
  const agentConfig = input.agents.find((agent) => agent.name === input.agent);
2502
- const model = resolveEffectiveSubagentModel(
2503
- input.explicitModel,
2504
- agentConfig?.model,
2505
- input.parentModel,
2506
- input.availableModels,
2507
- input.currentProvider,
2508
- input.modelScope === undefined ? {} : { scope: input.modelScope },
2509
- );
2510
- const thinkingOverride = input.thinkingOverrideForTask(input.agent, input.index, model);
2511
- 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);
2512
2529
  return {
2513
2530
  agent: input.agent,
2514
2531
  ...(model ? { model } : {}),
@@ -2791,6 +2808,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2791
2808
  toolBudget: data.toolBudget,
2792
2809
  usageBudget: data.usageBudget,
2793
2810
  configToolBudget: data.configToolBudget,
2811
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
2812
+ configToolTimeoutMs: data.configToolTimeoutMs,
2794
2813
  capabilityCeiling: data.capabilityCeiling,
2795
2814
  runFanoutBudget: data.runFanoutBudget,
2796
2815
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2838,6 +2857,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2838
2857
  toolBudget: data.toolBudget,
2839
2858
  usageBudget: data.usageBudget,
2840
2859
  configToolBudget: data.configToolBudget,
2860
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
2861
+ configToolTimeoutMs: data.configToolTimeoutMs,
2841
2862
  capabilityCeiling: data.capabilityCeiling,
2842
2863
  runFanoutBudget: data.runFanoutBudget,
2843
2864
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2864,14 +2885,15 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2864
2885
  const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
2865
2886
  && params.model === undefined
2866
2887
  && (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
2867
- const modelOverride = externalRunnerWithoutExplicitModel
2868
- ? undefined
2888
+ const modelOverride = a.runner?.type === "external-cli"
2889
+ ? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
2869
2890
  : resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
2870
2891
  return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
2871
2892
  agent: params.agent!,
2872
2893
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
2873
2894
  goal: params.task ?? "",
2874
2895
  agentConfig: a,
2896
+ recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
2875
2897
  ctx: asyncCtx,
2876
2898
  availableModels,
2877
2899
  cwd: effectiveCwd,
@@ -2896,6 +2918,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2896
2918
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2897
2919
  worktreeBaseDir: deps.config.worktreeBaseDir,
2898
2920
  controlConfig,
2921
+ intercomBridge: params.intercomBridge,
2899
2922
  controlIntercomTarget,
2900
2923
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
2901
2924
  nestedRoute,
@@ -2907,6 +2930,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2907
2930
  toolBudget: data.toolBudget,
2908
2931
  usageBudget: data.usageBudget,
2909
2932
  configToolBudget: data.configToolBudget,
2933
+ toolTimeoutMs: data.params?.toolTimeoutMs,
2934
+ configToolTimeoutMs: data.configToolTimeoutMs,
2910
2935
  capabilityCeiling: data.capabilityCeiling,
2911
2936
  runFanoutBudget: data.runFanoutBudget,
2912
2937
  parentWorkflowRunId: params.workflowParentRunId,
@@ -2996,6 +3021,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2996
3021
  toolBudget: data.toolBudget,
2997
3022
  usageBudget: data.usageBudget,
2998
3023
  configToolBudget: data.configToolBudget,
3024
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
3025
+ configToolTimeoutMs: data.configToolTimeoutMs,
2999
3026
  permissions: deps.config.permissions,
3000
3027
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3001
3028
  capabilityCeiling: data.capabilityCeiling,
@@ -3063,6 +3090,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3063
3090
  toolBudget: data.toolBudget,
3064
3091
  usageBudget: data.usageBudget,
3065
3092
  configToolBudget: data.configToolBudget,
3093
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
3094
+ configToolTimeoutMs: data.configToolTimeoutMs,
3066
3095
  capabilityCeiling: data.capabilityCeiling,
3067
3096
  runFanoutBudget: data.runFanoutBudget,
3068
3097
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -3150,6 +3179,10 @@ interface ForegroundParallelRunInput {
3150
3179
  worktreeSetup?: WorktreeSetup;
3151
3180
  timeoutMs?: number;
3152
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;
3153
3186
  turnBudget?: ResolvedTurnBudget;
3154
3187
  usageBudget?: UsageBudgetConfig;
3155
3188
  toolBudgets: (ResolvedToolBudget | undefined)[];
@@ -3241,10 +3274,128 @@ function buildParallelWorktreeTaskCwdError(
3241
3274
  return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
3242
3275
  }
3243
3276
 
3244
- function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
3277
+ function resolveConfiguredSingleRunOutputBaseDir(deps: ExecutorDeps): string | undefined {
3245
3278
  return deps.config.singleRunOutputBaseDir
3246
3279
  ? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
3247
- : 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);
3248
3399
  }
3249
3400
 
3250
3401
  function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
@@ -3408,6 +3559,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
3408
3559
  share: input.shareEnabled || undefined,
3409
3560
  maxOutput: input.maxOutput,
3410
3561
  timeoutMs: input.timeoutMs,
3562
+ toolTimeoutMs: input.toolTimeoutMs,
3563
+ configToolTimeoutMs: input.configToolTimeoutMs,
3411
3564
  turnBudget: input.turnBudget,
3412
3565
  ...(model ? { model } : {}),
3413
3566
  ...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
@@ -3487,6 +3640,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
3487
3640
  acceptanceContext: { mode: "parallel" },
3488
3641
  onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
3489
3642
  timeoutMs: input.timeoutMs,
3643
+ toolTimeoutMs: input.toolTimeoutMs,
3644
+ configToolTimeoutMs: input.configToolTimeoutMs,
3490
3645
  deadlineAt: input.deadlineAt,
3491
3646
  turnBudget: input.turnBudget,
3492
3647
  toolBudget: input.toolBudgets[index],
@@ -3614,7 +3769,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3614
3769
  ...(task.model !== undefined ? { model: task.model } : {}),
3615
3770
  }));
3616
3771
  const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
3617
- 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 }),
3618
3775
  );
3619
3776
 
3620
3777
  if (params.clarify === true && ctx.hasUI) {
@@ -3649,7 +3806,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3649
3806
  for (let i = 0; i < result.behaviorOverrides.length; i++) {
3650
3807
  const override = result.behaviorOverrides[i];
3651
3808
  if (override?.model !== undefined) {
3652
- 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 });
3653
3812
  behaviorOverrides[i]!.model = override.model;
3654
3813
  }
3655
3814
  if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
@@ -3736,6 +3895,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3736
3895
  usageBudget: data.usageBudget,
3737
3896
  toolBudget: data.toolBudget,
3738
3897
  configToolBudget: data.configToolBudget,
3898
+ callToolTimeoutMs: data.params?.toolTimeoutMs,
3899
+ configToolTimeoutMs: data.configToolTimeoutMs,
3739
3900
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3740
3901
  runFanoutBudget: data.runFanoutBudget,
3741
3902
  }));
@@ -3856,6 +4017,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3856
4017
  worktreeSetup,
3857
4018
  timeoutMs: data.timeoutMs,
3858
4019
  deadlineAt,
4020
+ toolTimeoutMs: data.params?.toolTimeoutMs,
4021
+ configToolTimeoutMs: data.configToolTimeoutMs,
3859
4022
  turnBudget: data.turnBudget,
3860
4023
  usageBudget: data.usageBudget,
3861
4024
  toolBudgets,
@@ -4075,6 +4238,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4075
4238
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
4076
4239
  goal: task,
4077
4240
  agentConfig,
4241
+ recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
4078
4242
  ctx: asyncCtx,
4079
4243
  availableModels,
4080
4244
  cwd: effectiveCwd,
@@ -4099,6 +4263,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4099
4263
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
4100
4264
  worktreeBaseDir: deps.config.worktreeBaseDir,
4101
4265
  controlConfig,
4266
+ intercomBridge: params.intercomBridge,
4102
4267
  controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
4103
4268
  childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
4104
4269
  nestedRoute: data.nestedRoute,
@@ -4248,6 +4413,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4248
4413
  },
4249
4414
  timeoutMs: data.timeoutMs,
4250
4415
  deadlineAt,
4416
+ toolTimeoutMs: params.toolTimeoutMs,
4417
+ configToolTimeoutMs: data.configToolTimeoutMs,
4251
4418
  turnBudget: data.turnBudget,
4252
4419
  enforceHardTurnLimit: params.enforceHardTurnLimit,
4253
4420
  toolBudget: effectiveToolBudget.toolBudget,
@@ -4326,8 +4493,15 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4326
4493
  }
4327
4494
 
4328
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}`;
4329
4503
  return {
4330
- 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 }],
4331
4505
  details,
4332
4506
  };
4333
4507
  }
@@ -4371,17 +4545,28 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
4371
4545
 
4372
4546
  const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
4373
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
+
4374
4554
  function recordMissionWorkflowChild(
4375
4555
  binding: MissionLaunchBinding | undefined,
4376
4556
  workflowRunId: string,
4377
4557
  key: string,
4378
4558
  update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
4379
4559
  ): void {
4380
- if (!binding) return;
4560
+ if (!binding || missionsMissingFromStore.has(binding.missionId)) return;
4381
4561
  const { task: _task, ...durableUpdate } = update;
4382
4562
  try {
4383
4563
  updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
4384
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
+ }
4385
4570
  console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
4386
4571
  }
4387
4572
  }
@@ -4451,6 +4636,7 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
4451
4636
  ...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
4452
4637
  output,
4453
4638
  ...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
4639
+ ...(detached ? { detached: true } : {}),
4454
4640
  ...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
4455
4641
  artifactPaths: [...artifactPaths],
4456
4642
  results: result.details.results,
@@ -4471,16 +4657,19 @@ export function prepareWorkflowLaunchParams(
4471
4657
  const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
4472
4658
  const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
4473
4659
  const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
4660
+ const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
4474
4661
  return {
4475
4662
  action: "resume",
4476
4663
  id: childParams.resume.trim(),
4477
4664
  message: typeof childParams.task === "string" ? childParams.task.trim() : "",
4478
4665
  workflowParentRunId: parentWorkflowRunId,
4479
4666
  workflowKey,
4667
+ ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4480
4668
  ...(options.missionDetached ? { mission: false } : {}),
4481
4669
  ...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
4482
4670
  ...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
4483
4671
  ...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
4672
+ ...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
4484
4673
  };
4485
4674
  }
4486
4675
  const launchParams = {
@@ -4667,6 +4856,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4667
4856
  const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
4668
4857
  if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
4669
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
+ }
4670
4870
  const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
4671
4871
  const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
4672
4872
  if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
@@ -4745,7 +4945,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4745
4945
  if (workflowRunId) {
4746
4946
  const toolCallId = _id;
4747
4947
  const asyncDir = path.join(DIRS.async, workflowRunId);
4748
- const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
4948
+ const resultPath = resultFilePath(DIRS.results, workflowRunId);
4749
4949
  const statusPath = path.join(asyncDir, "status.json");
4750
4950
  const eventsPath = path.join(asyncDir, "events.jsonl");
4751
4951
  const startedAt = Date.now();
@@ -4784,7 +4984,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4784
4984
  status.lastUpdate = Date.now();
4785
4985
  writeAtomicJson(statusPath, status);
4786
4986
  if (indexedState !== status.state) {
4787
- updateActiveRunIndex(asyncDir, status.state);
4987
+ updateActiveRunIndex(asyncDir, status.state, status.toolCallId);
4788
4988
  indexedState = status.state;
4789
4989
  }
4790
4990
  const job = deps.state.asyncJobs.get(workflowRunId);
@@ -4809,6 +5009,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4809
5009
  job.workflow = status.workflow;
4810
5010
  }
4811
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
+ };
4812
5023
  const projectWorkflowActivity = () => {
4813
5024
  const steps = status.steps ?? [];
4814
5025
  const runningSteps = steps.filter((step) => step.status === "running");
@@ -4830,7 +5041,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4830
5041
  status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
4831
5042
  status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
4832
5043
  };
4833
- 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 };
4834
5045
  deps.state.asyncJobs.set(workflowRunId, workflowJob);
4835
5046
  deps.state.fleetJobs ??= new Map();
4836
5047
  deps.state.fleetJobs.set(workflowRunId, workflowJob);
@@ -4840,6 +5051,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4840
5051
  void Promise.resolve().then(async () => {
4841
5052
  const workflowResults: SingleResult[] = [];
4842
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>();
4843
5058
  const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
4844
5059
  let projectedTraceLength = 0;
4845
5060
  let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
@@ -4859,12 +5074,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4859
5074
  if (entry.operation !== "run") continue;
4860
5075
  const existing = workflowSteps.get(entry.key);
4861
5076
  if (entry.state === "reused" && existing) continue;
4862
- 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";
4863
5086
  if (existing) {
4864
5087
  existing.status = mapped;
4865
5088
  if (entry.agent) existing.agent = entry.agent;
4866
5089
  if (entry.runId) existing.runId = entry.runId;
4867
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;
4868
5095
  if (entry.error === undefined) delete existing.error;
4869
5096
  else existing.error = entry.error;
4870
5097
  if (entry.durationMs === undefined) delete existing.durationMs;
@@ -4879,6 +5106,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4879
5106
  startedAt: Date.now(),
4880
5107
  ...(entry.runId ? { runId: entry.runId } : {}),
4881
5108
  ...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
5109
+ ...(entry.state === "detached" ? { activityState: "needs_attention" as const } : {}),
5110
+ ...(entry.state === "stopped" ? { stopped: true } : {}),
4882
5111
  };
4883
5112
  status.steps?.push(step);
4884
5113
  workflowSteps.set(entry.key, step);
@@ -4908,7 +5137,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4908
5137
  ...(workflowState ? { state: workflowState } : {}),
4909
5138
  onTrace: updateTrace,
4910
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);
4911
5142
  status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5143
+ if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
4912
5144
  persist();
4913
5145
  },
4914
5146
  onEmit: (emits) => {
@@ -4932,7 +5164,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4932
5164
  });
4933
5165
  const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
4934
5166
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
4935
- { ...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 },
4936
5168
  missionBinding,
4937
5169
  deps.asyncByDefault,
4938
5170
  );
@@ -4950,7 +5182,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4950
5182
  return execute(randomUUID(), childRequest, workflowSignal, (update) => {
4951
5183
  const progress = update.details.progress?.[0];
4952
5184
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
4953
- if (!progress || !step) return;
5185
+ if (!progress || !step || step.stopped) return;
4954
5186
  step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
4955
5187
  step.activityState = progress.activityState;
4956
5188
  step.lastActivityAt = progress.lastActivityAt;
@@ -5016,23 +5248,40 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5016
5248
  const summary = `${relaunchNote}Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
5017
5249
  const workflowUsage = sumResultsUsage(workflowResults);
5018
5250
  if (capReached) {
5019
- status = compactOptional<AsyncStatus>({ ...status, state: "failed", error: `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.`, endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console } });
5020
- writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: "failed", summary: status.error, error: status.error, 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 });
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;
5021
5256
  persist();
5022
5257
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: "failed", error: status.error });
5023
5258
  } else {
5259
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary);
5260
+ const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5024
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) };
5025
- 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 });
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;
5026
5263
  persist();
5027
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
5264
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
5028
5265
  }
5029
5266
  } catch (error) {
5030
5267
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
5031
5268
  const stopped = controller.signal.aborted;
5032
- 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 } });
5033
- 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;
5034
5283
  persist();
5035
- 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 } : {}) });
5036
5285
  } finally {
5037
5286
  deps.state.workflowControllers?.delete(workflowRunId);
5038
5287
  deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
@@ -5045,6 +5294,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5045
5294
  }, workflowFanoutBudget));
5046
5295
  }
5047
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>();
5048
5301
  const workflowResults: SingleResult[] = [];
5049
5302
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
5050
5303
  const sendWorkflowProgress = () => {
@@ -5063,7 +5316,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5063
5316
  sendWorkflowProgress();
5064
5317
  },
5065
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);
5066
5321
  claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5322
+ if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
5067
5323
  },
5068
5324
  onEmit: (emits) => {
5069
5325
  liveWorkflow = { ...liveWorkflow, emits };
@@ -5084,7 +5340,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5084
5340
  });
5085
5341
  const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
5086
5342
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
5087
- { ...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 },
5088
5344
  missionBinding,
5089
5345
  deps.asyncByDefault,
5090
5346
  );
@@ -5125,8 +5381,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5125
5381
  if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
5126
5382
  if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
5127
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);
5128
5387
  return attachWorkflowMission(withRunFanoutBudget({
5129
- content: [{ type: "text", text: sections.join("\n\n") }],
5388
+ content: [{ type: "text", text: displayText }],
5130
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 }),
5131
5390
  }, workflowFanoutBudget));
5132
5391
  } catch (error) {
@@ -5137,8 +5396,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5137
5396
  if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
5138
5397
  if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
5139
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);
5140
5402
  return attachWorkflowMission(withRunFanoutBudget({
5141
- content: [{ type: "text", text: sections.join("\n\n") }],
5403
+ content: [{ type: "text", text: displayText }],
5142
5404
  isError: true,
5143
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 }),
5144
5406
  }, workflowFanoutBudget));
@@ -5402,11 +5664,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5402
5664
  details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
5403
5665
  };
5404
5666
  }
5405
- if (action === "status") {
5667
+ if (action === "status" || action === "debug.run") {
5406
5668
  if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5407
5669
  const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
5408
5670
  const hasDirectoryTarget = Boolean(paramsWithResolvedCwd.dir);
5409
- const targetLabel = formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
5671
+ const targetLabel = action === "debug.run" ? "Debug run" : formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
5410
5672
  const withBudget = (result: AgentToolResult<Details>) => {
5411
5673
  const budgeted = withSpawnBudgetStatus(result, deps.state, deps.config, deps.state.currentSessionId);
5412
5674
  return {
@@ -5418,6 +5680,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5418
5680
  };
5419
5681
  const nestedScope = nestedResolutionScopeForExecutor(deps);
5420
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
+ }
5421
5692
  if (paramsWithResolvedCwd.view === "fleet") {
5422
5693
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5423
5694
  }
@@ -5807,6 +6078,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5807
6078
  const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
5808
6079
  const intercomBridge = resolveIntercomBridge({
5809
6080
  config: deps.config.intercomBridge,
6081
+ override: effectiveParams.intercomBridge,
5810
6082
  context: effectiveParams.context ?? (contextPolicy.usesFork ? "fork" : undefined),
5811
6083
  orchestratorTarget: sessionName,
5812
6084
  });
@@ -5855,6 +6127,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5855
6127
  const parentModel = requestParentModel;
5856
6128
  prepareForkThinking = (agentName, index, modelOverride) => {
5857
6129
  const agentConfig = agents.find((agent) => agent.name === agentName);
6130
+ if (agentConfig?.runner?.type === "external-cli") {
6131
+ forkThinkingRequirements.set(index, true);
6132
+ return;
6133
+ }
5858
6134
  const primaryModel = resolveEffectiveSubagentModel(
5859
6135
  modelOverride,
5860
6136
  agentConfig?.model,
@@ -6069,6 +6345,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6069
6345
  signal,
6070
6346
  onUpdate: onUpdateWithContext,
6071
6347
  agents,
6348
+ recoveryAgents: discoveredAgents,
6072
6349
  runId,
6073
6350
  shareEnabled,
6074
6351
  sessionRoot,
@@ -6090,6 +6367,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6090
6367
  usageBudget: usageBudget.budget,
6091
6368
  allowZeroToolBudget,
6092
6369
  configToolBudget: configToolBudget.toolBudget,
6370
+ configToolTimeoutMs: deps.config.toolTimeoutMs,
6093
6371
  contextPolicy,
6094
6372
  modelScope,
6095
6373
  parentModel: requestParentModel,
@@ -6259,7 +6537,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6259
6537
  };
6260
6538
 
6261
6539
  let nestedForegroundStarted = false;
6540
+ let asyncLaunchFailed = false;
6262
6541
  try {
6542
+ if (effectiveAsync) {
6543
+ deps.state.liveAsyncSessionRoots ??= new Map();
6544
+ deps.state.liveAsyncSessionRoots.set(asyncRunId, sessionRoot);
6545
+ }
6263
6546
  if (workflowLaunchObserver) {
6264
6547
  const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
6265
6548
  const launch = hasSingle
@@ -6273,7 +6556,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6273
6556
  }
6274
6557
  }
6275
6558
  const asyncResult = runAsyncPath(execData, deps);
6276
- 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
+ }
6277
6563
  if (foregroundControl) {
6278
6564
  writeNestedForegroundEvent("subagent.nested.started");
6279
6565
  nestedForegroundStarted = true;
@@ -6295,10 +6581,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6295
6581
  return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
6296
6582
  }
6297
6583
  } catch (error) {
6584
+ asyncLaunchFailed = effectiveAsync;
6298
6585
  const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
6299
6586
  if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
6300
6587
  return attachMission(errorResult);
6301
6588
  } finally {
6589
+ if (effectiveAsync && (asyncLaunchFailed || (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt))) deps.state.liveAsyncSessionRoots?.delete(asyncRunId);
6302
6590
  if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
6303
6591
  if (foregroundControl) {
6304
6592
  settleForegroundSchedulingOwner(foregroundControl);