pi-subagents 0.70.1 → 0.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +4 -2
  3. package/agents/worker.md +2 -2
  4. package/docs/agents.md +1 -1
  5. package/docs/configuration.md +18 -2
  6. package/docs/extension-api.md +67 -3
  7. package/docs/models.md +1 -1
  8. package/docs/observability.md +17 -13
  9. package/docs/tool-reference.md +14 -10
  10. package/docs/workflows.md +3 -1
  11. package/package.json +11 -4
  12. package/prompts/council.md +6 -3
  13. package/skills/pi-subagents/references/constraints-and-recipes.md +5 -2
  14. package/skills/pi-subagents/references/execution-controls.md +3 -3
  15. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  16. package/src/agents/agent-management.d.ts +2 -1
  17. package/src/agents/agent-management.d.ts.map +1 -1
  18. package/src/agents/agent-management.js +32 -29
  19. package/src/agents/agent-management.js.map +1 -1
  20. package/src/agents/agents.d.ts +7 -3
  21. package/src/agents/agents.d.ts.map +1 -1
  22. package/src/agents/agents.js +16 -15
  23. package/src/agents/agents.js.map +1 -1
  24. package/src/agents/global-npm-root.d.ts +7 -0
  25. package/src/agents/global-npm-root.d.ts.map +1 -0
  26. package/src/agents/global-npm-root.js +58 -0
  27. package/src/agents/global-npm-root.js.map +1 -0
  28. package/src/agents/proactive-skills.d.ts +1 -0
  29. package/src/agents/proactive-skills.d.ts.map +1 -1
  30. package/src/agents/proactive-skills.js +3 -1
  31. package/src/agents/proactive-skills.js.map +1 -1
  32. package/src/agents/skills.d.ts +2 -0
  33. package/src/agents/skills.d.ts.map +1 -1
  34. package/src/agents/skills.js +41 -8
  35. package/src/agents/skills.js.map +1 -1
  36. package/src/api/delegation.d.ts +14 -0
  37. package/src/api/delegation.d.ts.map +1 -1
  38. package/src/api/inspectors.d.ts +21 -0
  39. package/src/api/inspectors.d.ts.map +1 -0
  40. package/src/api/inspectors.js +12 -0
  41. package/src/api/inspectors.js.map +1 -0
  42. package/src/extension/fanout-child.d.ts.map +1 -1
  43. package/src/extension/fanout-child.js +9 -0
  44. package/src/extension/fanout-child.js.map +1 -1
  45. package/src/extension/index.d.ts.map +1 -1
  46. package/src/extension/index.js +76 -29
  47. package/src/extension/index.js.map +1 -1
  48. package/src/extension/rpc.d.ts +1 -1
  49. package/src/extension/rpc.d.ts.map +1 -1
  50. package/src/extension/rpc.js +11 -1
  51. package/src/extension/rpc.js.map +1 -1
  52. package/src/extension/schemas.js +5 -5
  53. package/src/extension/schemas.js.map +1 -1
  54. package/src/extension/subagent-guide.d.ts +1 -1
  55. package/src/extension/subagent-guide.d.ts.map +1 -1
  56. package/src/extension/subagent-guide.js +11 -4
  57. package/src/extension/subagent-guide.js.map +1 -1
  58. package/src/extension/tool-activation.d.ts +9 -0
  59. package/src/extension/tool-activation.d.ts.map +1 -0
  60. package/src/extension/tool-activation.js +182 -0
  61. package/src/extension/tool-activation.js.map +1 -0
  62. package/src/extension/tool-description.d.ts +4 -4
  63. package/src/extension/tool-description.d.ts.map +1 -1
  64. package/src/extension/tool-description.js +1 -1
  65. package/src/inspectors/plugins.d.ts +7 -2
  66. package/src/inspectors/plugins.d.ts.map +1 -1
  67. package/src/inspectors/plugins.js +78 -3
  68. package/src/inspectors/plugins.js.map +1 -1
  69. package/src/intercom/intercom-bridge.d.ts.map +1 -1
  70. package/src/intercom/intercom-bridge.js +2 -0
  71. package/src/intercom/intercom-bridge.js.map +1 -1
  72. package/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  73. package/src/intercom/native-supervisor-channel.js +3 -1
  74. package/src/intercom/native-supervisor-channel.js.map +1 -1
  75. package/src/runs/background/active-async-capacity.d.ts.map +1 -1
  76. package/src/runs/background/active-async-capacity.js +4 -33
  77. package/src/runs/background/active-async-capacity.js.map +1 -1
  78. package/src/runs/background/async-execution.d.ts.map +1 -1
  79. package/src/runs/background/async-execution.js +7 -33
  80. package/src/runs/background/async-execution.js.map +1 -1
  81. package/src/runs/background/async-job-tracker.d.ts.map +1 -1
  82. package/src/runs/background/async-job-tracker.js +26 -39
  83. package/src/runs/background/async-job-tracker.js.map +1 -1
  84. package/src/runs/background/async-resume.d.ts +1 -1
  85. package/src/runs/background/async-resume.d.ts.map +1 -1
  86. package/src/runs/background/async-resume.js +7 -2
  87. package/src/runs/background/async-resume.js.map +1 -1
  88. package/src/runs/background/async-status.d.ts +1 -0
  89. package/src/runs/background/async-status.d.ts.map +1 -1
  90. package/src/runs/background/async-status.js +1 -0
  91. package/src/runs/background/async-status.js.map +1 -1
  92. package/src/runs/background/binary-bootstrap.d.ts.map +1 -1
  93. package/src/runs/background/binary-bootstrap.js +34 -6
  94. package/src/runs/background/binary-bootstrap.js.map +1 -1
  95. package/src/runs/background/fleet-view.d.ts.map +1 -1
  96. package/src/runs/background/fleet-view.js +26 -7
  97. package/src/runs/background/fleet-view.js.map +1 -1
  98. package/src/runs/background/notify.d.ts +11 -0
  99. package/src/runs/background/notify.d.ts.map +1 -1
  100. package/src/runs/background/notify.js +73 -3
  101. package/src/runs/background/notify.js.map +1 -1
  102. package/src/runs/background/pid-namespace.d.ts +3 -0
  103. package/src/runs/background/pid-namespace.d.ts.map +1 -0
  104. package/src/runs/background/pid-namespace.js +20 -0
  105. package/src/runs/background/pid-namespace.js.map +1 -0
  106. package/src/runs/background/process-terminal-candidate.d.ts +18 -0
  107. package/src/runs/background/process-terminal-candidate.d.ts.map +1 -0
  108. package/src/runs/background/process-terminal-candidate.js +97 -0
  109. package/src/runs/background/process-terminal-candidate.js.map +1 -0
  110. package/src/runs/background/process-terminal.d.ts +3 -15
  111. package/src/runs/background/process-terminal.d.ts.map +1 -1
  112. package/src/runs/background/process-terminal.js +19 -96
  113. package/src/runs/background/process-terminal.js.map +1 -1
  114. package/src/runs/background/retained-children.d.ts.map +1 -1
  115. package/src/runs/background/retained-children.js +3 -2
  116. package/src/runs/background/retained-children.js.map +1 -1
  117. package/src/runs/background/run-child-session.d.ts +2 -0
  118. package/src/runs/background/run-child-session.d.ts.map +1 -1
  119. package/src/runs/background/run-child-session.js +2 -0
  120. package/src/runs/background/run-child-session.js.map +1 -1
  121. package/src/runs/background/run-status.d.ts.map +1 -1
  122. package/src/runs/background/run-status.js +5 -1
  123. package/src/runs/background/run-status.js.map +1 -1
  124. package/src/runs/background/runner-aliases.d.ts.map +1 -1
  125. package/src/runs/background/runner-aliases.js +2 -1
  126. package/src/runs/background/runner-aliases.js.map +1 -1
  127. package/src/runs/background/runner-startup-failure.d.ts +14 -0
  128. package/src/runs/background/runner-startup-failure.d.ts.map +1 -0
  129. package/src/runs/background/runner-startup-failure.js +47 -0
  130. package/src/runs/background/runner-startup-failure.js.map +1 -0
  131. package/src/runs/background/stale-run-reconciler.d.ts +1 -0
  132. package/src/runs/background/stale-run-reconciler.d.ts.map +1 -1
  133. package/src/runs/background/stale-run-reconciler.js +18 -3
  134. package/src/runs/background/stale-run-reconciler.js.map +1 -1
  135. package/src/runs/background/subagent-runner-bootstrap.d.ts +14 -0
  136. package/src/runs/background/subagent-runner-bootstrap.d.ts.map +1 -0
  137. package/src/runs/background/subagent-runner-bootstrap.js +277 -0
  138. package/src/runs/background/subagent-runner-bootstrap.js.map +1 -0
  139. package/src/runs/background/subagent-runner.d.ts +4 -2
  140. package/src/runs/background/subagent-runner.d.ts.map +1 -1
  141. package/src/runs/background/subagent-runner.js +58 -180
  142. package/src/runs/background/subagent-runner.js.map +1 -1
  143. package/src/runs/background/workflow-terminal-proof.d.ts +18 -0
  144. package/src/runs/background/workflow-terminal-proof.d.ts.map +1 -0
  145. package/src/runs/background/workflow-terminal-proof.js +68 -0
  146. package/src/runs/background/workflow-terminal-proof.js.map +1 -0
  147. package/src/runs/foreground/execution.d.ts.map +1 -1
  148. package/src/runs/foreground/execution.js +43 -14
  149. package/src/runs/foreground/execution.js.map +1 -1
  150. package/src/runs/foreground/prompt-audit.d.ts.map +1 -1
  151. package/src/runs/foreground/prompt-audit.js +2 -6
  152. package/src/runs/foreground/prompt-audit.js.map +1 -1
  153. package/src/runs/foreground/subagent-executor.d.ts +2 -1
  154. package/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  155. package/src/runs/foreground/subagent-executor.js +228 -24
  156. package/src/runs/foreground/subagent-executor.js.map +1 -1
  157. package/src/runs/shared/child-launch.d.ts.map +1 -1
  158. package/src/runs/shared/child-launch.js +3 -0
  159. package/src/runs/shared/child-launch.js.map +1 -1
  160. package/src/runs/shared/child-runtime-config.d.ts +4 -0
  161. package/src/runs/shared/child-runtime-config.d.ts.map +1 -1
  162. package/src/runs/shared/child-runtime-config.js.map +1 -1
  163. package/src/runs/shared/child-session.d.ts +1 -0
  164. package/src/runs/shared/child-session.d.ts.map +1 -1
  165. package/src/runs/shared/child-session.js +1 -0
  166. package/src/runs/shared/child-session.js.map +1 -1
  167. package/src/runs/shared/child-tool-plan.d.ts.map +1 -1
  168. package/src/runs/shared/child-tool-plan.js +4 -6
  169. package/src/runs/shared/child-tool-plan.js.map +1 -1
  170. package/src/runs/shared/external-cli-runner.d.ts.map +1 -1
  171. package/src/runs/shared/external-cli-runner.js +3 -1
  172. package/src/runs/shared/external-cli-runner.js.map +1 -1
  173. package/src/runs/shared/external-job-bridge.d.ts +7 -0
  174. package/src/runs/shared/external-job-bridge.d.ts.map +1 -1
  175. package/src/runs/shared/external-job-bridge.js +63 -0
  176. package/src/runs/shared/external-job-bridge.js.map +1 -1
  177. package/src/runs/shared/git-environment.d.ts +3 -0
  178. package/src/runs/shared/git-environment.d.ts.map +1 -0
  179. package/src/runs/shared/git-environment.js +28 -0
  180. package/src/runs/shared/git-environment.js.map +1 -0
  181. package/src/runs/shared/mcp-config-sources.d.ts +3 -0
  182. package/src/runs/shared/mcp-config-sources.d.ts.map +1 -1
  183. package/src/runs/shared/mcp-config-sources.js.map +1 -1
  184. package/src/runs/shared/mcp-direct-tool-allowlist.js +1 -1
  185. package/src/runs/shared/mcp-direct-tool-allowlist.js.map +1 -1
  186. package/src/runs/shared/mcp-direct-tool-grant.d.ts +3 -1
  187. package/src/runs/shared/mcp-direct-tool-grant.d.ts.map +1 -1
  188. package/src/runs/shared/mcp-direct-tool-grant.js +19 -5
  189. package/src/runs/shared/mcp-direct-tool-grant.js.map +1 -1
  190. package/src/runs/shared/model-resolution.d.ts.map +1 -1
  191. package/src/runs/shared/model-resolution.js +7 -1
  192. package/src/runs/shared/model-resolution.js.map +1 -1
  193. package/src/runs/shared/pi-spawn.d.ts +10 -1
  194. package/src/runs/shared/pi-spawn.d.ts.map +1 -1
  195. package/src/runs/shared/pi-spawn.js +86 -7
  196. package/src/runs/shared/pi-spawn.js.map +1 -1
  197. package/src/runs/shared/single-output.js +1 -1
  198. package/src/runs/shared/single-output.js.map +1 -1
  199. package/src/runs/shared/structured-output.d.ts +7 -0
  200. package/src/runs/shared/structured-output.d.ts.map +1 -1
  201. package/src/runs/shared/structured-output.js +74 -0
  202. package/src/runs/shared/structured-output.js.map +1 -1
  203. package/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  204. package/src/runs/shared/subagent-prompt-runtime.js +23 -7
  205. package/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  206. package/src/runs/shared/tool-availability.js +2 -2
  207. package/src/runs/shared/tool-availability.js.map +1 -1
  208. package/src/runs/shared/worktree.d.ts.map +1 -1
  209. package/src/runs/shared/worktree.js +9 -3
  210. package/src/runs/shared/worktree.js.map +1 -1
  211. package/src/shared/child-session-name.d.ts.map +1 -1
  212. package/src/shared/child-session-name.js +4 -2
  213. package/src/shared/child-session-name.js.map +1 -1
  214. package/src/shared/fork-context.d.ts +4 -0
  215. package/src/shared/fork-context.d.ts.map +1 -1
  216. package/src/shared/fork-context.js +9 -4
  217. package/src/shared/fork-context.js.map +1 -1
  218. package/src/shared/pruned-fork.d.ts.map +1 -1
  219. package/src/shared/pruned-fork.js +68 -56
  220. package/src/shared/pruned-fork.js.map +1 -1
  221. package/src/shared/types.d.ts +31 -3
  222. package/src/shared/types.d.ts.map +1 -1
  223. package/src/shared/types.js +2 -0
  224. package/src/shared/types.js.map +1 -1
  225. package/src/slash/delegation-adapters.d.ts +7 -1
  226. package/src/slash/delegation-adapters.d.ts.map +1 -1
  227. package/src/slash/delegation-adapters.js +17 -0
  228. package/src/slash/delegation-adapters.js.map +1 -1
  229. package/src/slash/prompt-template-bridge.d.ts.map +1 -1
  230. package/src/slash/prompt-template-bridge.js +14 -1
  231. package/src/slash/prompt-template-bridge.js.map +1 -1
  232. package/src/slash/prompt-workflows.d.ts.map +1 -1
  233. package/src/slash/prompt-workflows.js +16 -2
  234. package/src/slash/prompt-workflows.js.map +1 -1
  235. package/src/slash/slash-commands.d.ts.map +1 -1
  236. package/src/slash/slash-commands.js +7 -253
  237. package/src/slash/slash-commands.js.map +1 -1
  238. package/src/slash/subagent-cost.d.ts +30 -0
  239. package/src/slash/subagent-cost.d.ts.map +1 -0
  240. package/src/slash/subagent-cost.js +307 -0
  241. package/src/slash/subagent-cost.js.map +1 -0
  242. package/src/tui/fleet-status.d.ts +2 -0
  243. package/src/tui/fleet-status.d.ts.map +1 -1
  244. package/src/tui/fleet-status.js +25 -2
  245. package/src/tui/fleet-status.js.map +1 -1
  246. package/src/tui/fleet.d.ts +1 -1
  247. package/src/tui/fleet.d.ts.map +1 -1
  248. package/src/tui/fleet.js +5 -1
  249. package/src/tui/fleet.js.map +1 -1
  250. package/src/tui/render.d.ts.map +1 -1
  251. package/src/tui/render.js +8 -4
  252. package/src/tui/render.js.map +1 -1
  253. package/src/watchdog/diff-tool.d.ts +2 -1
  254. package/src/watchdog/diff-tool.d.ts.map +1 -1
  255. package/src/watchdog/diff-tool.js +9 -3
  256. package/src/watchdog/diff-tool.js.map +1 -1
  257. package/src/watchdog/permission-arbiter.d.ts.map +1 -1
  258. package/src/watchdog/permission-arbiter.js +2 -7
  259. package/src/watchdog/permission-arbiter.js.map +1 -1
  260. package/src/watchdog/register-child.d.ts +3 -1
  261. package/src/watchdog/register-child.d.ts.map +1 -1
  262. package/src/watchdog/register-child.js +12 -3
  263. package/src/watchdog/register-child.js.map +1 -1
  264. package/src/watchdog/review.d.ts +1 -1
  265. package/src/watchdog/review.d.ts.map +1 -1
  266. package/src/watchdog/review.js +2 -7
  267. package/src/watchdog/review.js.map +1 -1
  268. package/src/watchdog/runtime.d.ts +1 -1
  269. package/src/watchdog/runtime.d.ts.map +1 -1
  270. package/src/watchdog/runtime.js +2 -1
  271. package/src/watchdog/runtime.js.map +1 -1
  272. package/src/watchdog/turn-delta.d.ts +5 -1
  273. package/src/watchdog/turn-delta.d.ts.map +1 -1
  274. package/src/watchdog/turn-delta.js +9 -5
  275. package/src/watchdog/turn-delta.js.map +1 -1
@@ -40,6 +40,7 @@ import { canPreferFork, createForkContextResolver, resolveSubagentLaunchContext
40
40
  import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.js";
41
41
  import { resolveCurrentSessionId } from "../../shared/session-identity.js";
42
42
  import { currentCompletionOwnerId } from "../../shared/completion-owner.js";
43
+ import { SUBAGENT_ASYNC_COMPLETE_EVENT } from "../../shared/types.js";
43
44
  import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.js";
44
45
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.js";
45
46
  import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.js";
@@ -47,13 +48,13 @@ import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudg
47
48
  import { retainLiveForegroundNestedRoute } from "../../integrations/pi-web-session-liveness.js";
48
49
  import { validateToolBudgetConfig } from "../shared/tool-budget.js";
49
50
  import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.js";
50
- import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling } from "../shared/capability-ceiling.js";
51
+ import { assertAgentAllowedByCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling } from "../shared/capability-ceiling.js";
51
52
  import { isAgentContract } from "../shared/agent-contract.js";
52
53
  import { normalizeExtensionBindings } from "../shared/extension-bindings.js";
53
54
  import { resolveRequiredChildExtensions } from "../../shared/required-child-extensions.js";
54
55
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.js";
55
56
  import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.js";
56
- import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.js";
57
+ import { compactForegroundDetails, getSingleResultOutput, PROMPT_REDACTED, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.js";
57
58
  import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.js";
58
59
  import { summarizeContextModes } from "../shared/context-mode.js";
59
60
  import { attachNestedChildrenToResultChildren, buildSubagentResultIntercomPayload, deliverSubagentResultIntercomEvent, formatSubagentResultReceipt, resolveSubagentResultStatus, stripDetailsOutputsForIntercomReceipt, } from "../../intercom/result-intercom.js";
@@ -84,7 +85,7 @@ import { MissionNotFoundError, updateMission } from "../../missions/store.js";
84
85
  import { createMissionWorkflowState } from "../../missions/workflow-state.js";
85
86
  import { resolveAuthorityDecision } from "../../policy/authority.js";
86
87
  import { handleInspectorAction, INSPECTOR_ACTIONS } from "../../inspectors/actions.js";
87
- import { createBuiltinInspectorPlugins } from "../../inspectors/plugins.js";
88
+ import { getInspectorPlugins } from "../../inspectors/plugins.js";
88
89
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.js";
89
90
  import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError } from "../../workflows/scripted-workflow.js";
90
91
  import { formatIncrementalChildCompletion, incrementalChildCompletionTriggersTurn } from "../background/notify.js";
@@ -100,7 +101,7 @@ import { deepFreezeWorkflowArgs, normalizeWorkflowArgs, resolveWorkflowResource
100
101
  import { stableJsonDigest } from "../../shared/launch-contract.js";
101
102
  import { cleanupWorktrees, createWorktrees, preflightWorktreeSource, withWorktreeTransaction, diffWorktrees, formatWorktreeDiffSummary, } from "../shared/worktree.js";
102
103
  import { createWorktreeCleanupPlan, formatWorktreeCleanupPlan } from "../shared/worktree-cleanup-plan.js";
103
- import { DIRS, DEFAULT_ARTIFACT_CONFIG, DEFAULT_FORK_PREAMBLE, SUBAGENT_ACTIONS, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, checkSubagentDepth, resolveChildMaxSubagentDepth, resolveCurrentMaxSubagentDepth, resolveMaxSubagentSpawnsPerRun, wrapForkTask, } from "../../shared/types.js";
104
+ import { DIRS, DEFAULT_ARTIFACT_CONFIG, DEFAULT_FORK_PREAMBLE, SUBAGENT_ACTIONS, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, checkSubagentDepth, resolveChildMaxSubagentDepth, resolveCurrentMaxSubagentDepth, resolveMaxSubagentSpawnsPerRun, wrapForkTask, } from "../../shared/types.js";
104
105
  import { deriveChildSessionName } from "../../shared/child-session-name.js";
105
106
  const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "worktree.cleanup", "lane.recordMerge", "lane.recordSupersession", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
106
107
  const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "schedule.delete"]);
@@ -1159,6 +1160,57 @@ function resolveNestedResumeTarget(match, trustedSessionRoots) {
1159
1160
  ...(recoveryDescriptor ? { recoveryDescriptor } : {}),
1160
1161
  });
1161
1162
  }
1163
+ function resolveNestedExternalJobResumeTarget(match, params, deps) {
1164
+ const run = match.match.run;
1165
+ if (run.state === "stopped")
1166
+ return undefined;
1167
+ const asyncDir = resolveNestedAsyncDir(match.match.rootRunId, run);
1168
+ if (!asyncDir)
1169
+ return undefined;
1170
+ const status = readNestedRunStatus(asyncDir, run);
1171
+ const steps = status?.steps ?? [];
1172
+ const step = steps[params.index ?? 0];
1173
+ if (!step) {
1174
+ // An index outside an external-job run gets the async resolver's index error instead of a Pi revive.
1175
+ if (params.index === undefined || !steps.some((candidate) => candidate.runner?.type === "external-job"))
1176
+ return undefined;
1177
+ }
1178
+ else if (!step.runner) {
1179
+ // Pi steps record no runner, and neither does a launch status before the runner starts.
1180
+ // A session file means a Pi run; otherwise the agent definition says what the step runs.
1181
+ if (nestedRunSessionFile(run))
1182
+ return undefined;
1183
+ const agent = deps.discoverAgents(status?.cwd ?? deps.state.baseCwd, resolveExecutionAgentScope(params.agentScope)).agents.find((candidate) => candidate.name === step.agent);
1184
+ if (agent?.runner?.type === "external-job")
1185
+ throw new Error(externalJobWithoutMetadataMessage(run.id));
1186
+ return undefined;
1187
+ }
1188
+ else if (step.runner.type !== "external-job") {
1189
+ return undefined;
1190
+ }
1191
+ // External-job runs have no Pi session, so resume them as a provider follow-up from their own run directory.
1192
+ const target = resolveAsyncResumeTarget(compactOptional({ dir: asyncDir, index: params.index }), nestedRunScope(match.match.rootRunId), { requireSessionFile: false });
1193
+ if (target.kind === "live")
1194
+ throw new Error(externalJobStillRunningMessage(target.runId));
1195
+ return { source: "async", ...target };
1196
+ }
1197
+ function readNestedRunStatus(asyncDir, run) {
1198
+ try {
1199
+ return readStatus(asyncDir);
1200
+ }
1201
+ catch (error) {
1202
+ // A nested Pi run revives from its session file, so its unreadable status file must not block it.
1203
+ if (nestedRunSessionFile(run))
1204
+ return null;
1205
+ throw error;
1206
+ }
1207
+ }
1208
+ function externalJobStillRunningMessage(runId) {
1209
+ return `External-job run '${runId}' is still running. Wait for completion, then use subagent({ action: "resume", id: "${runId}", message: "..." }).`;
1210
+ }
1211
+ function externalJobWithoutMetadataMessage(runId) {
1212
+ return `External-job run '${runId}' has no persisted provider metadata. Cannot follow up without the parent provider job id.`;
1213
+ }
1162
1214
  async function waitForNestedControlResult(target, requestId, ignoredFiles, timeoutMs = 1_000) {
1163
1215
  const deadline = Date.now() + timeoutMs;
1164
1216
  while (Date.now() < deadline) {
@@ -1319,14 +1371,14 @@ function externalRunnerControlError(asyncDir, action) {
1319
1371
  }
1320
1372
  async function resumeExternalJobFollowUp(input) {
1321
1373
  if (input.target.kind === "live" || input.target.state === "running" || input.target.state === "queued") {
1322
- return { content: [{ type: "text", text: `External-job run '${input.target.runId}' is still running. Wait for completion, then use subagent({ action: "resume", id: "${input.target.runId}", message: "..." }).` }], isError: true, details: { mode: "management", results: [] } };
1374
+ return { content: [{ type: "text", text: externalJobStillRunningMessage(input.target.runId) }], isError: true, details: { mode: "management", results: [] } };
1323
1375
  }
1324
1376
  const runner = input.target.runner;
1325
1377
  const externalJob = input.target.externalJob;
1326
1378
  if (runner?.type !== "external-job")
1327
1379
  return { content: [{ type: "text", text: "Internal error: external-job follow-up was requested for a non-external-job runner." }], isError: true, details: { mode: "management", results: [] } };
1328
1380
  if (!externalJob)
1329
- return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has no persisted provider metadata. Cannot follow up without the parent provider job id.` }], isError: true, details: { mode: "management", results: [] } };
1381
+ return { content: [{ type: "text", text: externalJobWithoutMetadataMessage(input.target.runId) }], isError: true, details: { mode: "management", results: [] } };
1330
1382
  if (externalJob.provider !== runner.provider)
1331
1383
  return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has mismatched provider metadata. Refusing to follow up.` }], isError: true, details: { mode: "management", results: [] } };
1332
1384
  if (!externalJobOptionsEqual(externalJob.options, runner.options))
@@ -1342,11 +1394,14 @@ async function resumeExternalJobFollowUp(input) {
1342
1394
  const requestDigest = externalJobFollowUpRequestDigest({ provider: runner.provider, parentProviderJobId: externalJob.providerJobId, promptDigest, options: runner.options });
1343
1395
  const requestId = externalJobFollowUpRequestId(requestDigest);
1344
1396
  const runId = externalJobFollowUpRunId(requestDigest);
1345
- const asyncDir = path.join(DIRS.async, runId);
1397
+ // A child launches the follow-up under its nested route, so look for an earlier one where it would be.
1398
+ const nestedRoute = inheritedNestedRoute(input.deps);
1399
+ const runRoots = nestedRoute ? nestedRunScope(nestedRoute.rootRunId) : { asyncDirRoot: DIRS.async, resultsDir: DIRS.results };
1400
+ const asyncDir = path.join(runRoots.asyncDirRoot, runId);
1346
1401
  const currentSessionId = input.deps.state.currentSessionId;
1347
1402
  if (!currentSessionId)
1348
1403
  return { content: [{ type: "text", text: "External-job follow-up requires an active parent session." }], isError: true, details: { mode: "management", results: [] } };
1349
- if (fs.existsSync(asyncDir) || fs.existsSync(resultFilePath(DIRS.results, runId))) {
1404
+ if (fs.existsSync(asyncDir) || fs.existsSync(resultFilePath(runRoots.resultsDir, runId))) {
1350
1405
  return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId, asyncDir, duplicate: true, interactive: input.ctx.hasUI });
1351
1406
  }
1352
1407
  const depthState = checkSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime);
@@ -1442,6 +1497,9 @@ function resolveRequestedResumeTarget(params, deps, parentSessionFile) {
1442
1497
  throw new Error("Attaching a running subagent as a chain root is currently available for top-level async runs only.");
1443
1498
  if (resolved.match.run.state === "running" || resolved.match.run.state === "queued")
1444
1499
  return { kind: "live-nested", target: resolved };
1500
+ const externalJobTarget = resolveNestedExternalJobResumeTarget(resolved, params, deps);
1501
+ if (externalJobTarget)
1502
+ return externalJobTarget;
1445
1503
  const trustedSessionRoots = [
1446
1504
  ...(deps.config.defaultSessionDir ? [path.resolve(deps.expandTilde(deps.config.defaultSessionDir))] : []),
1447
1505
  ...(parentSessionFile ? [deps.getSubagentSessionRoot(parentSessionFile)] : []),
@@ -1516,7 +1574,7 @@ async function resumeAsyncRun(input) {
1516
1574
  const discoveredAgents = discovered.agents;
1517
1575
  const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
1518
1576
  const modelScope = discovered.modelScope;
1519
- const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
1577
+ const sessionName = resolveIntercomSessionTarget(input.deps.childRuntime?.intercomSessionName ?? input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
1520
1578
  const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
1521
1579
  const recoveryContext = recoveryDescriptor?.context ?? (input.params.context === "profile" ? undefined : input.params.context);
1522
1580
  const intercomBridge = resolveIntercomBridge({
@@ -1558,7 +1616,7 @@ async function resumeAsyncRun(input) {
1558
1616
  if (target.source === "async" && target.runner?.type === "external-job") {
1559
1617
  if (attachChain)
1560
1618
  return { content: [{ type: "text", text: "External-job follow-up does not support chain attachment. Use action='resume' with message instead." }], isError: true, details: { mode: "management", results: [] } };
1561
- return resumeExternalJobFollowUp({
1619
+ const resumed = await resumeExternalJobFollowUp({
1562
1620
  target,
1563
1621
  followUp,
1564
1622
  baseAgentConfig,
@@ -1572,6 +1630,10 @@ async function resumeAsyncRun(input) {
1572
1630
  parentSessionFile,
1573
1631
  absoluteDeadlineAt: input.absoluteDeadlineAt,
1574
1632
  });
1633
+ const resumedRunId = resumed.details.runId ?? resumed.details.asyncId;
1634
+ if (!resumed.isError && resumedRunId)
1635
+ input.onLaunch?.({ agent: target.agent, async: true, runId: resumedRunId });
1636
+ return resumed;
1575
1637
  }
1576
1638
  if (attachChain) {
1577
1639
  if (target.source !== "async") {
@@ -1679,6 +1741,7 @@ async function resumeAsyncRun(input) {
1679
1741
  return result;
1680
1742
  }
1681
1743
  const attachedId = result.details.asyncId ?? runId;
1744
+ input.onLaunch?.({ agent: target.agent, ...(target.sessionFile ? { sessionFile: target.sessionFile } : {}), async: true, runId: attachedId });
1682
1745
  const lines = [
1683
1746
  `Attached async subagent ${target.runId} as the first step of a new chain.`,
1684
1747
  `Chain run: ${attachedId}`,
@@ -1695,11 +1758,14 @@ async function resumeAsyncRun(input) {
1695
1758
  if (!revivalSessionFile) {
1696
1759
  return { content: [{ type: "text", text: `Async run '${target.runId}' child ${target.index} does not have a persisted session file to resume from.` }], isError: true, details: { mode: "management", results: [] } };
1697
1760
  }
1761
+ if (input.params.baseRef !== undefined && "managedWorktree" in target && target.managedWorktree === true) {
1762
+ return { content: [{ type: "text", text: "Cannot resume with baseRef: retained managed-worktree children continue in their existing worktree. Start a new worktree run from that base ref instead." }], isError: true, details: { mode: "management", results: [] } };
1763
+ }
1698
1764
  if (target.source === "async" && asyncReviveRequiresRecoveryDescriptor(target)) {
1699
1765
  return { content: [{ type: "text", text: `Async child '${target.runId}' is missing its required run fan-out recovery identity. Start a new run instead.` }], isError: true, details: { mode: "management", results: [] } };
1700
1766
  }
1701
- if (input.params.baseRef !== undefined && "managedWorktree" in target && target.managedWorktree === true) {
1702
- return { content: [{ type: "text", text: "Cannot resume with baseRef: retained managed-worktree children continue in their existing worktree. Start a new worktree run from that base ref instead." }], isError: true, details: { mode: "management", results: [] } };
1767
+ if (target.source === "nested" && !target.recoveryDescriptor) {
1768
+ return { content: [{ type: "text", text: `Nested child '${target.runId}' is missing its required recovery identity. Start a new run instead.` }], isError: true, details: { mode: "management", results: [] } };
1703
1769
  }
1704
1770
  const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
1705
1771
  const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
@@ -1821,15 +1887,19 @@ async function resumeAsyncRun(input) {
1821
1887
  ...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
1822
1888
  ...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
1823
1889
  ...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
1824
- capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1890
+ // Recovery descriptors, remembered foreground runs, and current workflow roots
1891
+ // preserve parent authority before the selected agent's descendant ceiling.
1892
+ capabilityCeiling: intersectSubagentCapabilityCeilings(recoveryDescriptor?.capabilityCeiling ?? (target.source === "foreground" || (target.source === "async" && target.mode === "workflow") ? target.capabilityCeiling : undefined), resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1825
1893
  runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1826
1894
  parentWorkflowRunId: input.params.workflowParentRunId,
1827
1895
  workflowKey: input.params.workflowKey,
1828
1896
  activeAsyncCapacity,
1829
1897
  }));
1898
+ const notifyLaunch = () => input.onLaunch?.({ agent: target.agent, sessionFile: revivalSessionFile, async: true, runId });
1830
1899
  if (result.isError) {
1831
1900
  const startedStatus = readStatus(revivalAsyncDir);
1832
1901
  if (input.params.workflowParentRunId !== undefined && startedStatus?.runId === runId && startedStatus.processTerminal?.runnerProcessInstanceId) {
1902
+ notifyLaunch();
1833
1903
  return {
1834
1904
  ...result,
1835
1905
  details: {
@@ -1844,6 +1914,7 @@ async function resumeAsyncRun(input) {
1844
1914
  activeAsyncCapacity?.rollback();
1845
1915
  return result;
1846
1916
  }
1917
+ notifyLaunch();
1847
1918
  for (const brief of queuedBriefs)
1848
1919
  fs.rmSync(brief.path, { force: true });
1849
1920
  if (queuedBriefs.length > 0 && sourceAsyncDir) {
@@ -1872,6 +1943,7 @@ async function resumeAsyncRun(input) {
1872
1943
  }
1873
1944
  if (completed.importedPublication)
1874
1945
  removeWorkflowAwaitedResult(asyncDir, resultPath, revivedId, completed.importedPublication);
1946
+ emitWorkflowAwaitedChildComplete(input.deps.pi, input.deps.state, revivedId, asyncDir, input.params.workflowParentRunId, completed);
1875
1947
  const usage = importedAsyncRootUsage(completed);
1876
1948
  const childResult = {
1877
1949
  index: 0,
@@ -2147,11 +2219,15 @@ function projectEffectiveAcceptanceSchemas(params, agents) {
2147
2219
  }
2148
2220
  function validateExecutionInput(params, agents, hasChain, hasTasks, hasSingle, allowClarifyTaskPrompt, context) {
2149
2221
  if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
2222
+ const agentList = agents.map((a) => a.name).join(", ") || "none";
2223
+ const noMode = !hasChain && !hasTasks && !hasSingle;
2150
2224
  return {
2151
2225
  content: [
2152
2226
  {
2153
2227
  type: "text",
2154
- text: `Provide exactly one mode. Agents: ${agents.map((a) => a.name).join(", ") || "none"}`,
2228
+ text: noMode && typeof params.workflowKey === "string"
2229
+ ? `Workflow child '${params.workflowKey}' has no agent. Pass { key, agent, task }. Agents: ${agentList}`
2230
+ : `Provide exactly one mode. Agents: ${agentList}`,
2155
2231
  },
2156
2232
  ],
2157
2233
  isError: true,
@@ -2787,6 +2863,30 @@ function removeWorkflowAwaitedResult(asyncDir, resultPath, runId, publication) {
2787
2863
  }
2788
2864
  removeResultIndex(asyncDir, publication.sessionId, runId, publication.toolCallId);
2789
2865
  }
2866
+ function emitWorkflowAwaitedChildComplete(pi, state, runId, asyncDir, parentWorkflowRunId, completed) {
2867
+ let status;
2868
+ try {
2869
+ status = readStatus(asyncDir);
2870
+ }
2871
+ catch (error) {
2872
+ console.error(`Failed to read awaited workflow child status for ${runId}; emitting completion with fallback metadata:`, error);
2873
+ }
2874
+ pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
2875
+ id: runId,
2876
+ runId,
2877
+ sessionId: status?.sessionId ?? completed.importedPublication?.sessionId ?? state.currentSessionId,
2878
+ completionOwnerId: status?.completionOwnerId ?? state.completionOwnerId ?? currentCompletionOwnerId(),
2879
+ asyncDir,
2880
+ agent: completed.agent,
2881
+ mode: "single",
2882
+ state: completed.stopped ? "stopped" : completed.success ? "complete" : "failed",
2883
+ success: completed.success,
2884
+ timestamp: Date.now(),
2885
+ triggerTurn: false,
2886
+ awaitedByWorkflow: true,
2887
+ parentWorkflowRunId,
2888
+ });
2889
+ }
2790
2890
  async function waitForWorkflowAsyncSingleResult(params, launchResult, options) {
2791
2891
  if (params.workflowAwaitAsync !== true || !launchResult.details.asyncDir)
2792
2892
  return launchResult;
@@ -2809,6 +2909,7 @@ async function waitForWorkflowAsyncSingleResult(params, launchResult, options) {
2809
2909
  }
2810
2910
  if (completed.importedPublication)
2811
2911
  removeWorkflowAwaitedResult(asyncDir, resultPath, options.runId, completed.importedPublication);
2912
+ emitWorkflowAwaitedChildComplete(options.pi, options.state, options.runId, asyncDir, params.workflowParentRunId, completed);
2812
2913
  const usage = importedAsyncRootUsage(completed);
2813
2914
  const childResult = omitUndefinedProperties({
2814
2915
  index: 0,
@@ -2983,7 +3084,7 @@ async function runAsyncPath(data, deps) {
2983
3084
  lane: params.lane,
2984
3085
  workflowAwaitAsync: params.workflowAwaitAsync,
2985
3086
  }));
2986
- return waitForWorkflowAsyncSingleResult(params, asyncResult, { runId: id, task: params.task ?? "", signal: data.signal, state: deps.state, kill: deps.kill });
3087
+ return waitForWorkflowAsyncSingleResult(params, asyncResult, { runId: id, task: params.task ?? "", signal: data.signal, state: deps.state, pi: deps.pi, kill: deps.kill });
2987
3088
  }
2988
3089
  return null;
2989
3090
  }
@@ -4662,6 +4763,7 @@ export function createSubagentExecutor(deps) {
4662
4763
  ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}),
4663
4764
  ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}),
4664
4765
  pid: process.pid,
4766
+ ...(workflowCapabilityCeiling ? { admissionCapabilityCeiling: workflowCapabilityCeiling } : {}),
4665
4767
  steps: [],
4666
4768
  ...(workflowPreflight ? { preflight: workflowPreflight } : {}),
4667
4769
  workflow: { trace: [], emits: [], console: [], ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}) },
@@ -4730,11 +4832,15 @@ export function createSubagentExecutor(deps) {
4730
4832
  deps.refreshResultDelivery?.();
4731
4833
  return false;
4732
4834
  };
4835
+ const pendingWorkflowChildAnnouncements = new Map();
4836
+ let announcePersistedWorkflowChildren = () => { };
4733
4837
  const runPersistence = createCapacityResilientJsonWriter({
4734
4838
  keepAlive: true,
4735
4839
  onSuccess: (filePath, payload) => {
4736
- if (filePath === statusPath)
4840
+ if (filePath === statusPath) {
4841
+ announcePersistedWorkflowChildren();
4737
4842
  queueActiveRunIndex(payload);
4843
+ }
4738
4844
  if (filePath === resultPath)
4739
4845
  settleResultPublication?.(true);
4740
4846
  },
@@ -4775,7 +4881,8 @@ export function createSubagentExecutor(deps) {
4775
4881
  else if (options.tolerateStatusWriteFailure) {
4776
4882
  try {
4777
4883
  runPersistence.write(statusPath, { ...status });
4778
- statusPersistenceDegraded = false;
4884
+ if (runPersistence.pendingCount() === 0)
4885
+ statusPersistenceDegraded = false;
4779
4886
  }
4780
4887
  catch (error) {
4781
4888
  const message = `Failed to persist async workflow state ${statusPath}: ${error instanceof Error ? error.message : String(error)}`;
@@ -4871,6 +4978,25 @@ export function createSubagentExecutor(deps) {
4871
4978
  return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
4872
4979
  }
4873
4980
  appendWorkflowEvent({ type: "subagent.workflow.started", ...(workflowArgsDigest ? { argsDigest: workflowArgsDigest } : {}) });
4981
+ try {
4982
+ deps.pi.events.emit(SUBAGENT_ASYNC_STARTED_EVENT, {
4983
+ lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
4984
+ id: workflowRunId,
4985
+ asyncDir,
4986
+ cwd: workflowCwd,
4987
+ ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}),
4988
+ pid: process.pid,
4989
+ sessionId: currentSessionId ?? undefined,
4990
+ completionOwnerId,
4991
+ mode: "workflow",
4992
+ agent: "workflow",
4993
+ goal: derivedObjective.trim() ? PROMPT_REDACTED : undefined,
4994
+ ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}),
4995
+ });
4996
+ }
4997
+ catch (error) {
4998
+ console.error("Failed to emit async workflow start event:", error);
4999
+ }
4874
5000
  // The runner does not execute workflow scripts: this active owner must consume its own
4875
5001
  // durable steer inbox, deliver through in-process child controls, and persist receipts.
4876
5002
  const emitWorkflowSteerEvent = (type, requestId, index, extra = {}) => {
@@ -5039,6 +5165,44 @@ export function createSubagentExecutor(deps) {
5039
5165
  const childOutputClaimPaths = new Map();
5040
5166
  const producedChildOutputPaths = new Set();
5041
5167
  const workflowSteps = new Map();
5168
+ const announcedWorkflowChildren = new Set();
5169
+ const announceWorkflowChild = (key, step) => {
5170
+ if (announcedWorkflowChildren.has(key))
5171
+ return;
5172
+ announcedWorkflowChildren.add(key);
5173
+ const stepIndex = status.steps?.indexOf(step);
5174
+ const childEvent = {
5175
+ type: "subagent.child-status",
5176
+ version: 1,
5177
+ runId: workflowRunId,
5178
+ childId: key,
5179
+ status: "started",
5180
+ ts: Date.now(),
5181
+ source: "async",
5182
+ asyncDir,
5183
+ ...(stepIndex !== undefined ? { stepIndex } : {}),
5184
+ agent: step.agent,
5185
+ ...(step.runId ? { childRunId: step.runId } : {}),
5186
+ workflowKey: key,
5187
+ ...(step.phase ? { phase: step.phase } : {}),
5188
+ ...(step.label ? { label: step.label } : {}),
5189
+ };
5190
+ appendWorkflowEvent({ ...childEvent });
5191
+ try {
5192
+ deps.pi.events.emit(SUBAGENT_CHILD_STATUS_EVENT, childEvent);
5193
+ }
5194
+ catch (error) {
5195
+ console.error("Failed to emit workflow child status event:", error);
5196
+ }
5197
+ };
5198
+ announcePersistedWorkflowChildren = () => {
5199
+ for (const [key, step] of pendingWorkflowChildAnnouncements) {
5200
+ if (!step.runId)
5201
+ continue;
5202
+ announceWorkflowChild(key, step);
5203
+ pendingWorkflowChildAnnouncements.delete(key);
5204
+ }
5205
+ };
5042
5206
  const runHostCommand = workflowHostCommandRunner({
5043
5207
  workflowCwd,
5044
5208
  artifactsDir: workflowArtifactsDir,
@@ -5280,6 +5444,8 @@ export function createSubagentExecutor(deps) {
5280
5444
  step.runId = launch.runId;
5281
5445
  if (childRequest.lane)
5282
5446
  step.lane = childRequest.lane;
5447
+ if (step.runId)
5448
+ pendingWorkflowChildAnnouncements.set(key, step);
5283
5449
  persist({ tolerateStatusWriteFailure: true });
5284
5450
  }
5285
5451
  recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
@@ -5376,7 +5542,7 @@ export function createSubagentExecutor(deps) {
5376
5542
  catch (receiptError) {
5377
5543
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
5378
5544
  }
5379
- if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflowResultChildren(workflow.children, status, false), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt }))
5545
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflowResultChildren(workflow.children, status, false), workflow: status.workflow, ...(workflowCapabilityCeiling ? { admissionCapabilityCeiling: workflowCapabilityCeiling } : {}), ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt }))
5380
5546
  return;
5381
5547
  if (pendingResultPublication && !await pendingResultPublication)
5382
5548
  return;
@@ -5433,7 +5599,7 @@ export function createSubagentExecutor(deps) {
5433
5599
  catch (receiptError) {
5434
5600
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
5435
5601
  }
5436
- if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: workflowResultChildren(partial.children, status, true), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt }))
5602
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: workflowResultChildren(partial.children, status, true), workflow: status.workflow, ...(workflowCapabilityCeiling ? { admissionCapabilityCeiling: workflowCapabilityCeiling } : {}), ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt }))
5437
5603
  return;
5438
5604
  if (pendingResultPublication && !await pendingResultPublication)
5439
5605
  return;
@@ -5880,7 +6046,7 @@ export function createSubagentExecutor(deps) {
5880
6046
  cwd: requestCwd,
5881
6047
  ...(deps.config.missions ? { missions: deps.config.missions } : {}),
5882
6048
  ...(deps.config.authorityPolicy ? { authorityPolicy: deps.config.authorityPolicy } : {}),
5883
- plugins: createBuiltinInspectorPlugins(),
6049
+ plugins: getInspectorPlugins(deps.pi),
5884
6050
  signal,
5885
6051
  });
5886
6052
  }
@@ -6035,7 +6201,7 @@ export function createSubagentExecutor(deps) {
6035
6201
  }
6036
6202
  let orchestratorTarget;
6037
6203
  try {
6038
- orchestratorTarget = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
6204
+ orchestratorTarget = resolveIntercomSessionTarget(deps.childRuntime?.intercomSessionName ?? deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
6039
6205
  }
6040
6206
  catch (error) {
6041
6207
  if (!sessionError)
@@ -6117,7 +6283,18 @@ export function createSubagentExecutor(deps) {
6117
6283
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
6118
6284
  }
6119
6285
  if (action === "resume") {
6120
- return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
6286
+ return resumeAsyncRun(omitUndefinedProperties({
6287
+ params: paramsWithResolvedCwd,
6288
+ requestCwd,
6289
+ ctx,
6290
+ deps,
6291
+ parentModel: requestParentModel,
6292
+ signal,
6293
+ ...(workflowLaunchObserver ? { onLaunch: (launch) => {
6294
+ workflowLaunchObservers.delete(params);
6295
+ workflowLaunchObserver(launch);
6296
+ } } : {}),
6297
+ }));
6121
6298
  }
6122
6299
  if (action === "steer") {
6123
6300
  if (paramsWithResolvedCwd.mode !== undefined && resolveSteerDeliveryMode(paramsWithResolvedCwd.mode) === undefined) {
@@ -6401,6 +6578,7 @@ export function createSubagentExecutor(deps) {
6401
6578
  cwd: requestCwd,
6402
6579
  config: deps.config,
6403
6580
  currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
6581
+ discoverAgentsAll: deps.discoverAgentsAll,
6404
6582
  runtimeAgentOwner: deps.pi,
6405
6583
  onAgentsChanged: deps.onAgentsChanged,
6406
6584
  });
@@ -6468,7 +6646,7 @@ export function createSubagentExecutor(deps) {
6468
6646
  return buildRequestedModeError(effectiveParams, contextPolicyResult.error);
6469
6647
  const contextPolicy = contextPolicyResult;
6470
6648
  effectiveParams = contextPolicy.params;
6471
- const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
6649
+ const sessionName = resolveIntercomSessionTarget(deps.childRuntime?.intercomSessionName ?? deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
6472
6650
  const intercomBridge = resolveIntercomBridge({
6473
6651
  config: deps.config.intercomBridge,
6474
6652
  override: effectiveParams.intercomBridge,
@@ -6498,10 +6676,29 @@ export function createSubagentExecutor(deps) {
6498
6676
  return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
6499
6677
  let forkSessionFileForIndex = () => undefined;
6500
6678
  let prepareForkSessionForIndex = async () => { };
6679
+ // Check before pruner/session preparation, then recheck immediately before each fork
6680
+ // because the session capability ceiling can tighten while preparation awaits.
6681
+ const launchCapabilityCeiling = () => intersectSubagentCapabilityCeilings(intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestSessionId)), deps.childRuntime?.capabilityCeiling);
6682
+ const assertLaunchableAgentsAllowedBeforeFork = () => {
6683
+ if (!contextPolicy.usesFork)
6684
+ return;
6685
+ const ceiling = launchCapabilityCeiling();
6686
+ const launchable = hasSingle
6687
+ ? [effectiveParams.agent]
6688
+ : hasTasks
6689
+ ? (effectiveParams.tasks ?? []).map((task) => task.agent)
6690
+ : (effectiveParams.chain ?? []).flatMap((step) => isDynamicParallelStep(step)
6691
+ && (step.expand.maxItems ?? deps.config.chain?.dynamicFanout?.maxItems ?? 0) === 0
6692
+ ? []
6693
+ : getStepAgents(step));
6694
+ for (const agent of launchable)
6695
+ assertAgentAllowedByCapabilityCeiling(agent, ceiling);
6696
+ };
6501
6697
  // Forked children keep their requested thinking level. Signed Anthropic thinking
6502
6698
  // blocks are stripped from the inherited transcript by the resolver (they are bound
6503
6699
  // to the parent session), which is not a reason to disable the child's own reasoning.
6504
6700
  try {
6701
+ assertLaunchableAgentsAllowedBeforeFork();
6505
6702
  const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
6506
6703
  ? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
6507
6704
  : undefined;
@@ -6630,8 +6827,15 @@ export function createSubagentExecutor(deps) {
6630
6827
  const childSessionFileForTask = (agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin) => forkSessionFileForTask(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
6631
6828
  const childSessionFileForIndex = (idx) => path.join(sessionDirForIndex(idx), "session.jsonl");
6632
6829
  try {
6830
+ // The ceiling can change while the pruned writer is built; clarify skips preflight but
6831
+ // still branches the parent session at launch.
6832
+ assertLaunchableAgentsAllowedBeforeFork();
6633
6833
  if (!(effectiveParams.clarify === true && ctx.hasUI) || deps.config.forkContext?.mode === "pruned") {
6634
- await preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, prepareForkSessionForTask, deps.config.chain?.dynamicFanout?.maxItems);
6834
+ const prepareAllowedForkSession = async (agent, ...rest) => {
6835
+ assertAgentAllowedByCapabilityCeiling(agent, launchCapabilityCeiling());
6836
+ await prepareForkSessionForTask(agent, ...rest);
6837
+ };
6838
+ await preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, prepareAllowedForkSession, deps.config.chain?.dynamicFanout?.maxItems);
6635
6839
  }
6636
6840
  }
6637
6841
  catch (error) {