@selesai/code 0.8.3 → 0.8.5

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 (518) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +7 -1
  3. package/dist/core/slash-commands.js +1 -0
  4. package/dist/defaults/models.json +41 -55
  5. package/dist/defaults/settings.json +7 -8
  6. package/dist/extensions/handoff-new.ts +1 -1
  7. package/dist/extensions/inline-skills.test.ts +1 -0
  8. package/dist/extensions/inline-skills.ts +1 -1
  9. package/dist/extensions/pi-intercom/CHANGELOG.md +25 -3
  10. package/dist/extensions/pi-intercom/README.md +34 -15
  11. package/dist/extensions/pi-intercom/broker/broker.ts +38 -141
  12. package/dist/extensions/pi-intercom/broker/client-liveness.test.ts +173 -0
  13. package/dist/extensions/pi-intercom/broker/client.ts +78 -150
  14. package/dist/extensions/pi-intercom/broker/extension-state.ts +18 -7
  15. package/dist/extensions/pi-intercom/broker/framing.ts +1 -1
  16. package/dist/extensions/pi-intercom/broker/protocol.ts +182 -0
  17. package/dist/extensions/pi-intercom/broker/spawn.test.ts +106 -7
  18. package/dist/extensions/pi-intercom/broker/spawn.ts +37 -11
  19. package/dist/extensions/pi-intercom/config.test.ts +8 -10
  20. package/dist/extensions/pi-intercom/config.ts +3 -3
  21. package/dist/extensions/pi-intercom/index.ts +218 -147
  22. package/dist/extensions/pi-intercom/intercom.integration.test.ts +891 -103
  23. package/dist/extensions/pi-intercom/package.json +3 -2
  24. package/dist/extensions/pi-intercom/project-agent.test.ts +145 -0
  25. package/dist/extensions/pi-intercom/project-agent.ts +324 -0
  26. package/dist/extensions/pi-intercom/reply-tracker.test.ts +86 -1
  27. package/dist/extensions/pi-intercom/reply-tracker.ts +41 -9
  28. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +49 -9
  29. package/dist/extensions/pi-intercom/test/inline-message.test.ts +9 -9
  30. package/dist/extensions/pi-intercom/types.ts +3 -1
  31. package/dist/extensions/pi-intercom/ui/inline-message.ts +7 -7
  32. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +2 -2
  33. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +14 -1
  34. package/dist/extensions/pi-powerline-footer/README.md +20 -37
  35. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +40 -25
  36. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +149 -18
  37. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -0
  38. package/dist/extensions/pi-powerline-footer/editor-performance.ts +99 -0
  39. package/dist/extensions/pi-powerline-footer/git-status.ts +15 -0
  40. package/dist/extensions/pi-powerline-footer/index.ts +394 -317
  41. package/dist/extensions/pi-powerline-footer/package.json +1 -1
  42. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -12
  43. package/dist/extensions/pi-powerline-footer/queue/store.ts +8 -56
  44. package/dist/extensions/pi-powerline-footer/queue/types.ts +1 -2
  45. package/dist/extensions/pi-powerline-footer/segments.ts +0 -3
  46. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +231 -1
  47. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +5 -19
  48. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +43 -0
  49. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +64 -0
  50. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +116 -0
  51. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +19 -149
  52. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +6 -0
  53. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +5 -5
  54. package/dist/extensions/pi-powerline-footer/types.ts +1 -2
  55. package/dist/extensions/pi-subagents/CHANGELOG.md +225 -14
  56. package/dist/extensions/pi-subagents/README.md +66 -1613
  57. package/dist/extensions/pi-subagents/agents/builder.md +0 -1
  58. package/dist/extensions/pi-subagents/agents/delegate.md +14 -0
  59. package/dist/extensions/pi-subagents/agents/oracle.md +74 -0
  60. package/dist/extensions/pi-subagents/agents/reviewer.md +78 -0
  61. package/dist/extensions/pi-subagents/agents/scout.md +50 -0
  62. package/dist/extensions/pi-subagents/agents/worker.md +58 -0
  63. package/dist/extensions/pi-subagents/docs/configuration.md +372 -0
  64. package/dist/extensions/pi-subagents/docs/extension-api.md +308 -0
  65. package/dist/extensions/pi-subagents/docs/missions.md +119 -0
  66. package/dist/extensions/pi-subagents/docs/models.md +192 -0
  67. package/dist/extensions/pi-subagents/docs/observability.md +174 -0
  68. package/dist/extensions/pi-subagents/docs/tool-reference.md +343 -0
  69. package/dist/extensions/pi-subagents/docs/watchdog.md +176 -0
  70. package/dist/extensions/pi-subagents/docs/workflows.md +163 -0
  71. package/dist/extensions/pi-subagents/inspector-runner.mjs +11 -0
  72. package/dist/extensions/pi-subagents/package-lock.json +152 -194
  73. package/dist/extensions/pi-subagents/package.json +9 -10
  74. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  75. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  76. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  77. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  78. package/dist/extensions/pi-subagents/prompts/review-loop.md +12 -12
  79. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -17
  80. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +77 -72
  81. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +177 -69
  82. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +28 -8
  83. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +88 -66
  84. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +3 -3
  85. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +624 -0
  86. package/dist/extensions/pi-subagents/src/agents/agents.ts +38 -19
  87. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +7 -3
  88. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  89. package/dist/extensions/pi-subagents/src/agents/skills.ts +2 -9
  90. package/dist/extensions/pi-subagents/src/api/delegation.ts +1 -2
  91. package/dist/extensions/pi-subagents/src/api/project-panes.ts +30 -0
  92. package/dist/extensions/pi-subagents/src/extension/config.ts +62 -8
  93. package/dist/extensions/pi-subagents/src/extension/control-notices.ts +2 -2
  94. package/dist/extensions/pi-subagents/src/extension/doctor.ts +40 -0
  95. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +51 -26
  96. package/dist/extensions/pi-subagents/src/extension/index.ts +140 -50
  97. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +77 -0
  98. package/dist/extensions/pi-subagents/src/extension/rpc.ts +18 -24
  99. package/dist/extensions/pi-subagents/src/extension/schemas.ts +58 -21
  100. package/dist/extensions/pi-subagents/src/extension/subagent-guide.ts +39 -0
  101. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +47 -25
  102. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +3 -3
  103. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +460 -64
  104. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -1
  105. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +102 -4
  106. package/dist/extensions/pi-subagents/src/missions/actions.ts +77 -16
  107. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +162 -0
  108. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +65 -17
  109. package/dist/extensions/pi-subagents/src/missions/store.ts +151 -9
  110. package/dist/extensions/pi-subagents/src/missions/types.ts +58 -3
  111. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +260 -0
  112. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -8
  113. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +374 -0
  114. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +46 -0
  115. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +158 -36
  116. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +294 -134
  117. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +26 -4
  118. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +45 -6
  119. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +33 -15
  120. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +267 -0
  121. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +145 -60
  122. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +104 -0
  123. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +17 -3
  124. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +114 -6
  125. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +33 -0
  126. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +68 -0
  127. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +71 -0
  128. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +26 -3
  129. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +42 -14
  130. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +12 -24
  131. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +7 -5
  132. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +233 -101
  133. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -4
  134. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +146 -0
  135. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +21 -5
  136. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +85 -0
  137. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +43 -18
  138. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +20 -19
  139. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +40 -2
  140. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +188 -46
  141. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +12 -0
  142. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +137 -0
  143. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +171 -0
  144. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1365 -252
  145. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +187 -0
  146. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +178 -26
  147. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  148. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +286 -0
  149. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
  150. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +32 -5
  151. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +12 -2
  152. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -0
  153. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +509 -117
  154. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +10 -10
  155. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +280 -0
  156. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +4 -2
  157. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +189 -32
  158. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -5
  159. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +20 -8
  160. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +128 -3
  161. package/dist/extensions/pi-subagents/src/shared/display-text.ts +100 -0
  162. package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +13 -0
  164. package/dist/extensions/pi-subagents/src/shared/formatters.ts +4 -6
  165. package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +51 -0
  166. package/dist/extensions/pi-subagents/src/shared/settings.ts +41 -11
  167. package/dist/extensions/pi-subagents/src/shared/types.ts +192 -3
  168. package/dist/extensions/pi-subagents/src/shared/utf8.ts +11 -0
  169. package/dist/extensions/pi-subagents/src/shared/utils.ts +46 -34
  170. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -14
  171. package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +1 -1
  172. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +14 -12
  173. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +176 -21
  174. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +12 -6
  175. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +52 -18
  176. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +36 -5
  177. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +37 -21
  178. package/dist/extensions/pi-subagents/src/tui/fleet.ts +375 -42
  179. package/dist/extensions/pi-subagents/src/tui/render.ts +201 -68
  180. package/dist/extensions/pi-subagents/src/types/pi-runtime-compat.d.ts +0 -6
  181. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +5 -4
  182. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +0 -1
  183. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +332 -27
  184. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +15 -15
  185. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
  186. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
  187. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
  188. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
  189. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
  190. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
  191. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
  192. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
  193. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
  194. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
  195. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
  196. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
  197. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
  198. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
  199. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
  200. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
  201. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
  202. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
  203. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
  204. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
  205. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
  206. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
  207. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
  208. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
  209. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
  210. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
  211. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
  212. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
  213. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
  214. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
  215. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
  216. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
  217. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
  218. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
  219. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
  220. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
  221. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
  222. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
  223. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
  224. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
  225. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
  226. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
  227. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
  228. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
  229. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
  230. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
  231. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
  232. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
  233. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
  234. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
  235. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
  236. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
  237. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
  238. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
  239. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
  240. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
  241. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
  242. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
  243. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
  244. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
  245. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
  246. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
  247. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
  248. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
  249. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
  250. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
  251. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
  253. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
  254. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
  255. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
  256. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
  257. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
  258. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
  259. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
  260. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
  261. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
  262. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
  263. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
  264. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
  265. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  266. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
  267. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
  268. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
  269. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
  270. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
  271. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
  272. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
  273. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
  274. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
  275. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
  276. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
  277. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
  278. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
  279. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
  280. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
  281. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
  282. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
  283. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  284. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  285. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  286. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
  287. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  288. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  289. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  290. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
  291. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  292. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
  293. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  294. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  295. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
  296. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
  297. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
  298. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  299. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
  300. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
  301. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
  302. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
  303. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
  304. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
  305. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  306. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  307. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
  308. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
  309. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  310. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  311. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  312. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  313. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  314. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
  315. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
  316. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  317. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  318. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
  319. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  320. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
  321. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
  322. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
  323. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
  324. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
  325. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
  326. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
  327. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
  328. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
  329. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
  330. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
  331. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
  332. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
  333. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
  334. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
  335. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
  336. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
  337. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
  338. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
  339. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
  340. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
  341. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
  342. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
  343. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
  344. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
  345. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
  346. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
  347. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
  348. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
  349. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
  350. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
  351. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
  352. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
  353. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
  354. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
  355. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
  356. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
  357. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
  358. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
  359. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
  360. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +84 -0
  361. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/package.json +14 -0
  362. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +102 -15
  363. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +507 -306
  364. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +309 -123
  365. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +124 -58
  366. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +62 -16
  367. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +167 -113
  368. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +1 -1
  369. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +4 -2
  370. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +139 -58
  371. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +324 -59
  372. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +98 -22
  373. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +106 -106
  374. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +416 -106
  375. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +287 -17
  376. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1122 -96
  377. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +107 -20
  378. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +9 -9
  379. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +26 -6
  380. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +2 -2
  381. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  382. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +1 -1
  383. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +0 -8
  384. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +194 -59
  385. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +317 -0
  386. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  387. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  388. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +334 -248
  389. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +52 -52
  390. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +26 -26
  391. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +118 -99
  392. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +160 -0
  393. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +9 -9
  394. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +43 -1
  395. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +36 -10
  396. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +387 -30
  397. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +9 -9
  398. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +18 -3
  399. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +99 -40
  400. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +5 -3
  401. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +7 -7
  402. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +19 -19
  403. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +94 -23
  404. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +9 -9
  405. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +20 -20
  406. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +9 -9
  407. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +35 -5
  408. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +2 -2
  409. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +70 -52
  410. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +187 -0
  411. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +125 -71
  412. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +18 -3
  413. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +12 -12
  414. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +17 -23
  415. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +30 -2
  416. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +25 -25
  417. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +3 -3
  418. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +15 -0
  419. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +91 -47
  420. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +468 -38
  421. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +14 -4
  422. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +37 -0
  423. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +69 -0
  424. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +79 -2
  425. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +102 -49
  426. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +98 -32
  427. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +7 -6
  428. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +171 -0
  429. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +116 -0
  430. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +81 -7
  431. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +289 -13
  432. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +42 -1
  433. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +2 -2
  434. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +11 -2
  435. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +84 -10
  436. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +122 -8
  437. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +8 -8
  438. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +24 -24
  439. package/dist/extensions/pi-subagents/test/unit/owned-process-tree.test.ts +69 -0
  440. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +16 -2
  441. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +7 -7
  442. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +10 -10
  443. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +3 -3
  444. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +26 -17
  445. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +405 -0
  446. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +440 -92
  447. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +24 -8
  448. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +6 -6
  449. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +43 -43
  450. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +13 -13
  451. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +59 -6
  452. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +19 -19
  453. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +243 -0
  454. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +68 -168
  455. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +58 -15
  456. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +51 -0
  457. package/dist/extensions/pi-subagents/test/unit/reads-resolution.test.ts +66 -0
  458. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +47 -28
  459. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +12 -12
  460. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +57 -0
  461. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +111 -31
  462. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +121 -0
  463. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +1 -1
  464. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +65 -50
  465. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +69 -28
  466. package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
  467. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +60 -44
  468. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +424 -49
  469. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +28 -0
  470. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +23 -1
  471. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +32 -5
  472. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +13 -13
  473. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +130 -92
  474. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +2 -2
  475. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +85 -0
  476. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +35 -35
  477. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +24 -0
  478. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +312 -22
  479. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +150 -15
  480. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +65 -26
  481. package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +138 -0
  482. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +76 -25
  483. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +5 -5
  484. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +55 -6
  485. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +13 -13
  486. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +1 -1
  487. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +1 -1
  488. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +8 -8
  489. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +6 -6
  490. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +138 -11
  491. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +8 -8
  492. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +113 -0
  493. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +68 -7
  494. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +6 -6
  495. package/dist/extensions/workflow/extension.ts +21 -19
  496. package/dist/extensions/workflow/modes.ts +65 -0
  497. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  498. package/dist/modes/interactive/interactive-mode.js +48 -1
  499. package/dist/skills/pi-subagents/SKILL.md +20 -15
  500. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +77 -83
  501. package/dist/skills/pi-subagents/references/execution-controls.md +215 -106
  502. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +29 -9
  503. package/dist/skills/pi-subagents/references/prompting-and-roles.md +58 -43
  504. package/docs/plans/workflow-autoloop-reference.md +192 -0
  505. package/docs/plans/workflow-subagent-consolidation-planb.md +374 -0
  506. package/docs/plans/workflow-subagent-scripted-merge.md +406 -0
  507. package/docs/quickstart.md +27 -43
  508. package/docs/settings.md +4 -0
  509. package/docs/usage.md +1 -0
  510. package/package.json +2 -1
  511. package/dist/extensions/workflow/adapter.ts +0 -949
  512. package/dist/extensions/workflow/modes/loop.ts +0 -74
  513. package/dist/extensions/workflow/modes/prototype.ts +0 -170
  514. package/dist/extensions/workflow/modes/quicktype.ts +0 -150
  515. package/dist/extensions/workflow/modes/task.ts +0 -105
  516. package/dist/extensions/workflow/run-state.ts +0 -124
  517. package/dist/extensions/workflow/state-machine.ts +0 -484
  518. package/dist/extensions/workflow/validators.ts +0 -42
@@ -27,6 +27,7 @@ import {
27
27
  tryImport,
28
28
  } from "../support/helpers.ts";
29
29
  import registerSubagentExtension from "../../src/extension/index.ts";
30
+ import { discoverAgents } from "../../src/agents/agents.ts";
30
31
  import {
31
32
  SUBAGENT_DELEGATION_REQUEST_EVENT,
32
33
  SUBAGENT_DELEGATION_RESPONSE_EVENT,
@@ -35,14 +36,19 @@ import {
35
36
  type SubagentDelegationResponse,
36
37
  type SubagentDelegationStarted,
37
38
  } from "../../src/api/delegation.ts";
38
- import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, type SubagentState } from "../../src/shared/types.ts";
39
+ import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, type AsyncStatus, type SubagentState } from "../../src/shared/types.ts";
40
+ import { ACTIVE_RUN_INDEX_DIR } from "../../src/runs/background/active-run-index.ts";
39
41
  import { CHILD_WATCHDOG_STATUS_EVENT } from "../../src/watchdog/child-status.ts";
40
42
  import { WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/wait-config.ts";
41
43
  import { TOOL_BUDGET_ENV, TOOL_BUDGET_ZERO_AUTH_ENV } from "../../src/runs/shared/tool-budget.ts";
44
+ import { createRunFanoutBudget, encodeRunFanoutBudgetDescriptor, RUN_FANOUT_BUDGET_ENV } from "../../src/runs/shared/run-fanout-budget.ts";
42
45
  import { MainWatchdogRuntime } from "../../src/watchdog/runtime.ts";
43
46
  import { MAX_CHILD_PENDING_LINE_BYTES, MAX_CHILD_STDERR_BYTES } from "../../src/runs/shared/child-protocol.ts";
44
47
  import {
45
48
  SUBAGENT_FANOUT_CHILD_ENV,
49
+ SUBAGENT_STEER_ACK_DIR_ENV,
50
+ SUBAGENT_STEER_CAPABILITY_ENV,
51
+ SUBAGENT_STEER_INBOX_ENV,
46
52
  SUBAGENT_PARENT_CHILD_INDEX_ENV,
47
53
  SUBAGENT_PARENT_CONTROL_INBOX_ENV,
48
54
  SUBAGENT_PARENT_DEPTH_ENV,
@@ -61,6 +67,7 @@ interface ProgressSummary {
61
67
  agent: string;
62
68
  index: number;
63
69
  status: string;
70
+ task?: string;
64
71
  activityState?: string;
65
72
  lastActivityAt?: number;
66
73
  currentTool?: string;
@@ -74,6 +81,7 @@ interface ProgressSummary {
74
81
  }
75
82
 
76
83
  interface ArtifactPaths {
84
+ inputPath?: string;
77
85
  outputPath: string;
78
86
  transcriptPath?: string;
79
87
  metadataPath?: string;
@@ -98,6 +106,7 @@ interface RuntimeAcknowledgedExtensions {
98
106
  interface RunSyncResult {
99
107
  exitCode: number;
100
108
  agent: string;
109
+ task?: string;
101
110
  messages: unknown[];
102
111
  error?: string;
103
112
  protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number };
@@ -142,6 +151,7 @@ interface RunSyncResult {
142
151
 
143
152
  interface MockPiCallRecord {
144
153
  args?: string[];
154
+ cwd?: string;
145
155
  systemPrompts?: Array<{ mode?: string; path?: string; text?: string; error?: string }>;
146
156
  }
147
157
 
@@ -235,6 +245,7 @@ interface ExecutorToolResult {
235
245
  isError?: boolean;
236
246
  details?: {
237
247
  totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
248
+ controlEvents?: Array<{ type?: string }>;
238
249
  asyncId?: string;
239
250
  timeoutMs?: number;
240
251
  turnBudget?: { maxTurns: number; graceTurns: number };
@@ -300,7 +311,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
300
311
  removeTempDir(tempDir);
301
312
  });
302
313
 
303
- function readCall(): { args: string[]; systemPrompts: NonNullable<MockPiCallRecord["systemPrompts"]> } {
314
+ function readCall(): { args: string[]; cwd?: string; systemPrompts: NonNullable<MockPiCallRecord["systemPrompts"]> } {
304
315
  const callFile = fs.readdirSync(mockPi.dir)
305
316
  .filter((name) => name.startsWith("call-") && name.endsWith(".json"))
306
317
  .sort()
@@ -308,13 +319,20 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
308
319
  assert.ok(callFile, "expected a recorded mock pi call");
309
320
  const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
310
321
  assert.ok(Array.isArray(payload.args), "expected recorded args");
311
- return { args: payload.args, systemPrompts: payload.systemPrompts ?? [] };
322
+ return { args: payload.args, cwd: payload.cwd, systemPrompts: payload.systemPrompts ?? [] };
312
323
  }
313
324
 
314
325
  function readCallArgs(): string[] {
315
326
  return readCall().args;
316
327
  }
317
328
 
329
+ function readAllCallArgs(): string[][] {
330
+ return fs.readdirSync(mockPi.dir)
331
+ .filter((name) => name.startsWith("call-") && name.endsWith(".json"))
332
+ .sort()
333
+ .map((name) => (JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord).args);
334
+ }
335
+
318
336
  function makeExecutor(
319
337
  agents = [makeAgent("echo")],
320
338
  config: Record<string, unknown> = {},
@@ -323,9 +341,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
323
341
  allowMutatingManagementActions = true,
324
342
  initialAsyncJobs: SubagentState["asyncJobs"] = new Map(),
325
343
  workflowControllers?: Map<string, AbortController>,
344
+ handleScheduledRunAction?: Parameters<typeof createSubagentExecutor>[0]["handleScheduledRunAction"],
345
+ piEvents = createEventBus(),
326
346
  ) {
327
347
  return createSubagentExecutor!({
328
- pi: { events: createEventBus(), getSessionName: () => undefined },
348
+ pi: { events: piEvents, getSessionName: () => undefined },
329
349
  state: {
330
350
  baseCwd: tempDir,
331
351
  currentSessionId: initialSpawnState?.sessionId ?? null,
@@ -342,6 +362,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
342
362
  expandTilde: (value: string) => value,
343
363
  discoverAgents: () => ({ agents }),
344
364
  allowMutatingManagementActions,
365
+ ...(handleScheduledRunAction ? { handleScheduledRunAction } : {}),
345
366
  });
346
367
  }
347
368
 
@@ -361,11 +382,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
361
382
  assert.equal(output, "Hello from mock agent");
362
383
  });
363
384
 
364
- it("treats action='single' with execution fields as single execution", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
365
- mockPi.onCall({ output: "single alias finished" });
385
+ it("rejects action='single' with execution fields", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
366
386
  const executor = makeExecutor([makeAgent("echo")]);
367
387
 
368
- const result = await executor.execute(
388
+ const result = await executor.executePublic(
369
389
  "single-alias",
370
390
  { action: "single", agent: "echo", task: "Run through alias" },
371
391
  new AbortController().signal,
@@ -373,18 +393,59 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
373
393
  makeMinimalCtx(tempDir),
374
394
  );
375
395
 
376
- assert.equal(result.isError, undefined);
377
- assert.match(result.content[0]?.text ?? "", /single alias finished/);
396
+ assert.equal(result.isError, true);
397
+ assert.match(result.content[0]?.text ?? "", /is not a management action/);
398
+ assert.equal(mockPi.callCount(), 0);
378
399
  });
379
400
 
380
- it("starts workflow scripts asynchronously by default and persists live status", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
381
- mockPi.onCall({ output: "async workflow child" });
401
+ it("rejects internal fan-out fields from public workflows", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
382
402
  const executor = makeExecutor([makeAgent("echo")]);
383
- const runId = `scripted-workflow-async-${Date.now()}`;
403
+ for (const params of [
404
+ { workflowScript: `return runs.run("main", { agent: "echo", task: "work" })`, runFanoutBudget: { version: 1 } },
405
+ { workflowScript: `return runs.run("main", { agent: "echo", task: "work" })`, runFanoutAdmitted: true },
406
+ ] as const) {
407
+ const result = await executor.executePublic("private-fanout", params, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
408
+ assert.equal(result.isError, true);
409
+ assert.match(result.content[0]?.text ?? "", /does not accept internal run fan-out fields/);
410
+ }
411
+ assert.equal(mockPi.callCount(), 0);
412
+ });
413
+
414
+ it("allows schedule.create to carry the required workflowScript target", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
415
+ let forwarded;
416
+ const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, async (params) => {
417
+ forwarded = params;
418
+ return { content: [{ type: "text", text: "created" }], details: { mode: "management", results: [] } };
419
+ });
384
420
 
385
421
  const result = await executor.execute(
386
- runId,
387
- { workflowScript: `emit("starting"); await runs.run("work", { agent: "echo", task: "Async work" }); return { answer: 42 };` },
422
+ "schedule-create",
423
+ { action: "schedule.create", id: "nightly", every: "1h", workflowScript: "return runs.run('main', { agent: 'echo' })" },
424
+ new AbortController().signal,
425
+ undefined,
426
+ makeMinimalCtx(tempDir),
427
+ );
428
+
429
+ assert.equal(result.isError, undefined);
430
+ assert.equal(result.content[0]?.text, "created");
431
+ assert.equal(forwarded?.workflowScript, "return runs.run('main', { agent: 'echo' })");
432
+ });
433
+
434
+ it("starts workflow scripts asynchronously with a portable internal run id", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
435
+ mockPi.onCall({ echoEnv: [SUBAGENT_STEER_INBOX_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_ACK_DIR_ENV] });
436
+ const asyncJobs: SubagentState["asyncJobs"] = new Map();
437
+ const executor = makeExecutor([makeAgent("echo", { aliases: ["helper"] })], { missions: { globalIndex: false } }, false, undefined, true, asyncJobs);
438
+ const workflowCwd = path.join(tempDir, "workflow-cwd");
439
+ fs.mkdirSync(workflowCwd);
440
+ const toolCallId = "call_demo|fc_demo";
441
+
442
+ const result = await executor.execute(
443
+ toolCallId,
444
+ {
445
+ cwd: workflowCwd,
446
+ workflowScript: `emit("starting"); await runs.run("work", { agent: "helper", task: "Async work" }); return { answer: 42 };`,
447
+ mission: { summary: "Review the active backlog", labels: ["github-backlog", "review"] },
448
+ },
388
449
  new AbortController().signal,
389
450
  undefined,
390
451
  makeMinimalCtx(tempDir),
@@ -392,25 +453,240 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
392
453
 
393
454
  assert.equal(result.isError, undefined);
394
455
  assert.equal(result.details.mode, "workflow");
395
- assert.equal(result.details.asyncId, runId);
456
+ assert.equal(result.details.toolCallId, toolCallId);
457
+ assert.ok(result.details.asyncId);
458
+ const workflowRunId = result.details.asyncId;
459
+ assert.equal(result.details.runId, workflowRunId);
460
+ assert.notEqual(workflowRunId, toolCallId);
461
+ assert.match(workflowRunId, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
462
+ assert.equal(path.basename(result.details.asyncDir!), workflowRunId);
463
+ assert.equal(asyncJobs.has(workflowRunId), true);
464
+ assert.equal(asyncJobs.get(workflowRunId)?.cwd, workflowCwd);
465
+ assert.equal(asyncJobs.has(toolCallId), false);
466
+ assert.equal(fs.existsSync(path.join(DIRS.async, toolCallId)), false);
396
467
  assert.match(result.content[0]?.text ?? "", /Async workflow/);
397
468
  const statusPath = path.join(result.details.asyncDir!, "status.json");
398
- let status: { state?: string; workflow?: { value?: unknown; emits?: unknown[]; trace?: Array<{ key?: string; state?: string }> } } = {};
469
+ let status: { runId?: string; toolCallId?: string; cwd?: string; state?: string; steps?: Array<{ agent?: string; label?: string; workflowKey?: string; parentWorkflowRunId?: string }>; workflow?: { value?: unknown; emits?: unknown[]; trace?: Array<{ key?: string; agent?: string; state?: string }> } } = {};
399
470
  for (let attempt = 0; attempt < 100; attempt++) {
400
471
  status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
401
472
  if (status.state === "complete" || status.state === "failed") break;
402
473
  await new Promise((resolve) => setTimeout(resolve, 20));
403
474
  }
404
475
  assert.equal(status.state, "complete");
476
+ assert.equal(status.runId, workflowRunId);
477
+ assert.equal(status.toolCallId, toolCallId);
478
+ assert.equal(status.cwd, workflowCwd);
479
+ assert.equal(status.steps?.length, 1);
480
+ assert.deepEqual(status.steps?.map(({ agent, label, workflowKey }) => ({ agent, label, workflowKey })), [
481
+ { agent: "echo", label: "work", workflowKey: "work" },
482
+ ]);
483
+ assert.ok(status.steps?.every((step) => step.parentWorkflowRunId === workflowRunId));
405
484
  assert.deepEqual(status.workflow?.value, { answer: 42 });
406
485
  assert.deepEqual(status.workflow?.emits, ["starting"]);
407
- assert.ok(status.workflow?.trace?.some((entry) => entry.key === "work" && entry.state === "completed"));
408
- const persistedResult = JSON.parse(fs.readFileSync(path.join(DIRS.results, `${runId}.json`), "utf-8")) as { agent?: string; summary?: string; workflow?: { value?: unknown } };
486
+ assert.equal(mockPi.callCount(), 1);
487
+ assert.ok(status.workflow?.trace?.some((entry) => entry.key === "work" && entry.agent === "echo" && entry.state === "completed"));
488
+ const traceEvents = fs.readFileSync(path.join(result.details.asyncDir!, "events.jsonl"), "utf-8")
489
+ .trim()
490
+ .split("\n")
491
+ .map((line) => JSON.parse(line) as { type?: string; trace?: Array<{ key?: string; state?: string }> })
492
+ .filter((event) => event.type === "subagent.workflow.trace");
493
+ assert.equal(traceEvents.length, 2);
494
+ assert.deepEqual(traceEvents[0]?.trace?.map(({ key, state }) => ({ key, state })), [{ key: "work", state: "started" }]);
495
+ assert.deepEqual(traceEvents[1]?.trace?.map(({ key, state }) => ({ key, state })), [
496
+ { key: "work", state: "started" },
497
+ { key: "work", state: "completed" },
498
+ ]);
499
+ const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
500
+ const persistedResult = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { id?: string; runId?: string; toolCallId?: string; agent?: string; cwd?: string; summary?: string; workflow?: { value?: unknown }; results?: Array<{ agent?: string; workflowKey?: string; runId?: string; output?: string }> };
501
+ assert.equal(persistedResult.id, workflowRunId);
502
+ assert.equal(persistedResult.runId, workflowRunId);
503
+ assert.equal(persistedResult.toolCallId, toolCallId);
409
504
  assert.equal(persistedResult.agent, "workflow");
505
+ assert.equal(persistedResult.cwd, workflowCwd);
506
+ assert.deepEqual(persistedResult.results?.map(({ agent, workflowKey }) => ({ agent, workflowKey })), [
507
+ { agent: "echo", workflowKey: "work" },
508
+ ]);
509
+ const steeringEnv = JSON.parse(persistedResult.results?.[0]?.output ?? "null") as Record<string, string | null>;
510
+ assert.match(steeringEnv[SUBAGENT_STEER_INBOX_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-targets[/\\]0$/);
511
+ assert.match(steeringEnv[SUBAGENT_STEER_CAPABILITY_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-capabilities[/\\]0\.json$/);
512
+ assert.match(steeringEnv[SUBAGENT_STEER_ACK_DIR_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-acks[/\\]0$/);
513
+ assert.equal(fs.existsSync(path.join(result.details.asyncDir!, "control", "workflow-foreground", persistedResult.results?.[0]?.runId ?? "missing")), false);
410
514
  assert.match(persistedResult.summary ?? "", /Return: \{\n "answer": 42\n\}/);
411
515
  assert.deepEqual(persistedResult.workflow?.value, { answer: 42 });
516
+ assert.equal(fs.existsSync(path.join(DIRS.results, `${toolCallId}.json`)), false);
412
517
  fs.rmSync(result.details.asyncDir!, { recursive: true, force: true });
413
- fs.rmSync(path.join(DIRS.results, `${runId}.json`), { force: true });
518
+ fs.rmSync(resultPath, { force: true });
519
+ });
520
+
521
+ it("runs an external CLI workflow child with subagents.defaultModel configured", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
522
+ const markerPath = path.join(tempDir, "external-started");
523
+ const executor = makeExecutor([
524
+ makeAgent("external", {
525
+ runner: { type: "external-cli", command: process.execPath, args: ["-e", `require("node:fs").writeFileSync(${JSON.stringify(markerPath)}, "started"); process.stdout.write("external result")`] },
526
+ model: "mock/default-model",
527
+ modelSource: { type: "subagents.defaultModel", scope: "user", path: "/settings.json", model: "mock/default-model" },
528
+ }),
529
+ ]);
530
+ const ctx = { ...makeMinimalCtx(tempDir), model: { provider: "mock", id: "parent-model" } };
531
+ const started = await executor.execute(
532
+ `external-workflow-${Date.now()}`,
533
+ { workflowScript: `return await runs.run("external", { agent: "external", task: "Run external", async: true });` },
534
+ new AbortController().signal,
535
+ undefined,
536
+ ctx,
537
+ );
538
+
539
+ assert.equal(started.isError, undefined);
540
+ assert.ok(started.details.asyncId);
541
+ const workflowResultPath = path.join(DIRS.results, `${started.details.asyncId}.json`);
542
+ let workflowResult: { state?: string; results?: Array<{ output?: string; runId?: string }> } = {};
543
+ for (let attempt = 0; attempt < 100; attempt++) {
544
+ if (fs.existsSync(workflowResultPath)) workflowResult = JSON.parse(fs.readFileSync(workflowResultPath, "utf-8"));
545
+ if (workflowResult.state === "complete" || workflowResult.state === "failed") break;
546
+ await new Promise((resolve) => setTimeout(resolve, 20));
547
+ }
548
+ assert.equal(workflowResult.state, "complete");
549
+ assert.match(workflowResult.results?.[0]?.output ?? "", /Async: external/);
550
+ for (let attempt = 0; attempt < 100 && !fs.existsSync(markerPath); attempt++) {
551
+ await new Promise((resolve) => setTimeout(resolve, 20));
552
+ }
553
+ assert.equal(fs.readFileSync(markerPath, "utf-8"), "started");
554
+ assert.equal(mockPi.callCount(), 0);
555
+
556
+ fs.rmSync(started.details.asyncDir!, { recursive: true, force: true });
557
+ fs.rmSync(workflowResultPath, { force: true });
558
+ });
559
+
560
+ it("rejects explicit model overrides for external CLI agents", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
561
+ const executor = makeExecutor([
562
+ makeAgent("external", {
563
+ runner: { type: "external-cli", command: process.execPath, args: ["-e", "process.stdout.write('unreachable')"] },
564
+ }),
565
+ ]);
566
+ const result = await executor.execute(
567
+ "external-explicit-model",
568
+ { agent: "external", task: "Run external", async: true, model: "mock/override" },
569
+ new AbortController().signal,
570
+ undefined,
571
+ makeMinimalCtx(tempDir),
572
+ );
573
+
574
+ assert.equal(result.isError, true);
575
+ assert.match(result.content[0]?.text ?? "", /does not support: model override/);
576
+ assert.equal(mockPi.callCount(), 0);
577
+ });
578
+
579
+ it("rejects external CLI agent models that differ from inherited subagents.defaultModel", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
580
+ const executor = makeExecutor([
581
+ makeAgent("external", {
582
+ runner: { type: "external-cli", command: process.execPath, args: ["-e", "process.stdout.write('unreachable')"] },
583
+ model: "mock/override-model",
584
+ modelSource: { type: "subagents.defaultModel", scope: "user", path: "/settings.json", model: "mock/default-model" },
585
+ }),
586
+ ]);
587
+ const result = await executor.execute(
588
+ "external-agent-override-model",
589
+ { agent: "external", task: "Run external", async: true },
590
+ new AbortController().signal,
591
+ undefined,
592
+ makeMinimalCtx(tempDir),
593
+ );
594
+
595
+ assert.equal(result.isError, true);
596
+ assert.match(result.content[0]?.text ?? "", /does not support: model override/);
597
+ assert.equal(mockPi.callCount(), 0);
598
+ });
599
+
600
+ it("rejects external CLI agent models that equal inherited subagents.defaultModel without provenance", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
601
+ const executor = makeExecutor([
602
+ makeAgent("external", {
603
+ runner: { type: "external-cli", command: process.execPath, args: ["-e", "process.stdout.write('unreachable')"] },
604
+ model: "mock/default-model",
605
+ }),
606
+ ]);
607
+ const result = await executor.execute(
608
+ "external-agent-same-value-override-model",
609
+ { agent: "external", task: "Run external", async: true },
610
+ new AbortController().signal,
611
+ undefined,
612
+ makeMinimalCtx(tempDir),
613
+ );
614
+
615
+ assert.equal(result.isError, true);
616
+ assert.match(result.content[0]?.text ?? "", /does not support: model override/);
617
+ assert.equal(mockPi.callCount(), 0);
618
+ });
619
+
620
+ it("projects live child activity into async workflow status", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
621
+ mockPi.onCall({
622
+ steps: [
623
+ { jsonl: [events.toolStart("read", { path: "src/example.ts" })] },
624
+ { delay: 2_500, jsonl: [events.toolEnd("read"), events.toolResult("read", "contents")] },
625
+ { jsonl: [events.assistantMessage("Done")] },
626
+ ],
627
+ });
628
+ const asyncJobs: SubagentState["asyncJobs"] = new Map();
629
+ const executor = makeExecutor([makeAgent("echo")], {
630
+ control: {
631
+ enabled: true,
632
+ needsAttentionAfterMs: 100,
633
+ activeNoticeAfterMs: 100,
634
+ activeNoticeAfterTurns: 999_999,
635
+ activeNoticeAfterTokens: 999_999,
636
+ notifyOn: ["active_long_running", "needs_attention"],
637
+ notifyChannels: ["event"],
638
+ },
639
+ }, false, undefined, true, asyncJobs);
640
+
641
+ const result = await executor.execute(
642
+ "workflow-live-activity",
643
+ { workflowScript: `return runs.run("main", { agent: "echo", task: "Inspect the file" });` },
644
+ new AbortController().signal,
645
+ undefined,
646
+ makeMinimalCtx(tempDir),
647
+ );
648
+ const { asyncId: workflowRunId, asyncDir } = result.details;
649
+ assert.ok(workflowRunId);
650
+ assert.ok(asyncDir);
651
+ const statusPath = path.join(asyncDir, "status.json");
652
+ const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
653
+ const activeMarkerPath = path.join(DIRS.async, ACTIVE_RUN_INDEX_DIR, workflowRunId);
654
+ assert.equal(fs.existsSync(activeMarkerPath), true);
655
+ let liveStatus: AsyncStatus | undefined;
656
+ const activityDeadline = Date.now() + 5_000;
657
+ while (Date.now() < activityDeadline && !fs.existsSync(resultPath)) {
658
+ const candidate = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatus;
659
+ if (candidate.activityState === "active_long_running" && candidate.steps?.[0]?.currentTool === "read") {
660
+ liveStatus = candidate;
661
+ break;
662
+ }
663
+ await new Promise((resolve) => setTimeout(resolve, 50));
664
+ }
665
+
666
+ assert.ok(liveStatus, "expected workflow status to expose live child activity");
667
+ assert.equal(liveStatus.activityState, "active_long_running");
668
+ assert.equal(typeof liveStatus.lastActivityAt, "number");
669
+ assert.equal(liveStatus.currentTool, "read");
670
+ assert.match(liveStatus.currentPath ?? "", /src[/\\]example\.ts$/);
671
+ assert.equal(liveStatus.toolCount, 1);
672
+ assert.equal(liveStatus.steps?.[0]?.status, "running");
673
+ assert.equal(liveStatus.steps?.[0]?.agent, "echo");
674
+ assert.match(liveStatus.steps?.[0]?.sessionFile ?? "", /session\.jsonl$/);
675
+ assert.equal(fs.existsSync(liveStatus.steps?.[0]?.sessionFile ?? ""), true);
676
+ assert.equal(liveStatus.steps?.[0]?.activityState, "active_long_running");
677
+ assert.equal(typeof liveStatus.steps?.[0]?.lastActivityAt, "number");
678
+ assert.equal(liveStatus.steps?.[0]?.toolCount, 1);
679
+ assert.equal(asyncJobs.get(workflowRunId)?.activityState, "active_long_running");
680
+ assert.equal(asyncJobs.get(workflowRunId)?.steps?.[0]?.currentTool, "read");
681
+
682
+ const completionDeadline = Date.now() + 5_000;
683
+ while (!fs.existsSync(resultPath)) {
684
+ if (Date.now() > completionDeadline) assert.fail("Timed out waiting for async workflow completion");
685
+ await new Promise((resolve) => setTimeout(resolve, 50));
686
+ }
687
+ assert.equal(fs.existsSync(activeMarkerPath), false);
688
+ fs.rmSync(asyncDir, { recursive: true, force: true });
689
+ fs.rmSync(resultPath, { force: true });
414
690
  });
415
691
 
416
692
  it("rejects an invalid async workflow usage budget before creating run state", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
@@ -449,8 +725,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
449
725
  );
450
726
 
451
727
  assert.equal(started.isError, undefined);
452
- assert.equal(started.details.asyncId, runId);
453
- const resultPath = path.join(DIRS.results, `${runId}.json`);
728
+ assert.ok(started.details.asyncId);
729
+ assert.notEqual(started.details.asyncId, runId);
730
+ const resultPath = path.join(DIRS.results, `${started.details.asyncId}.json`);
454
731
  let persisted: { state?: string; summary?: string; results?: Array<{ success?: boolean; output?: string }> } = {};
455
732
  for (let attempt = 0; attempt < 100; attempt++) {
456
733
  if (fs.existsSync(resultPath)) persisted = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
@@ -467,6 +744,60 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
467
744
  fs.rmSync(resultPath, { force: true });
468
745
  });
469
746
 
747
+ it("keeps ordinary async workflow child results in the watcher-owned path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
748
+ mockPi.onCall({ output: "async child done" });
749
+ const executor = makeExecutor([makeAgent("echo")]);
750
+ const result = await executor.execute(
751
+ `scripted-workflow-async-child-${Date.now()}`,
752
+ { workflowScript: `return await runs.run("background", { agent: "echo", task: "Async child", async: true });`, async: false },
753
+ new AbortController().signal,
754
+ undefined,
755
+ makeMinimalCtx(tempDir),
756
+ );
757
+ const childRunId = (result.details.workflow?.value as { runId?: string } | undefined)?.runId;
758
+ assert.ok(childRunId);
759
+ const childDir = path.join(DIRS.async, childRunId);
760
+ const childResultPath = path.join(DIRS.results, `${childRunId}.json`);
761
+ for (let attempt = 0; attempt < 200 && !fs.existsSync(childResultPath); attempt++) {
762
+ await new Promise((resolve) => setTimeout(resolve, 20));
763
+ }
764
+ assert.equal(fs.existsSync(childResultPath), true);
765
+ assert.equal(fs.existsSync(path.join(childDir, "workflow-result.json")), false);
766
+ fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
767
+ fs.rmSync(childResultPath, { force: true });
768
+ });
769
+
770
+ it("applies an agent deadline to a workflow-launched async child", { skip: !createSubagentExecutor ? "executor not importable" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
771
+ mockPi.onCall({ delay: 5_000, output: "too late" });
772
+ const executor = makeExecutor([makeAgent("slow", { defaultTimeoutMs: 150 })]);
773
+ const result = await executor.execute(
774
+ `scripted-workflow-async-child-timeout-${Date.now()}`,
775
+ {
776
+ workflowScript: `return await runs.run("background", { agent: "slow", task: "Wait", async: true });`,
777
+ async: false,
778
+ },
779
+ new AbortController().signal,
780
+ undefined,
781
+ makeMinimalCtx(tempDir),
782
+ );
783
+ const childRunId = (result.details.workflow?.value as { runId?: string } | undefined)?.runId;
784
+ assert.ok(childRunId, JSON.stringify(result.details.workflow?.value ?? result.content));
785
+ const childDir = path.join(DIRS.async, childRunId);
786
+ const childResultPath = path.join(DIRS.results, `${childRunId}.json`);
787
+ let persisted: { timeoutMs?: number; state?: string; results?: Array<{ timedOut?: boolean; error?: string }> } = {};
788
+ for (let attempt = 0; attempt < 200; attempt++) {
789
+ if (fs.existsSync(childResultPath)) persisted = JSON.parse(fs.readFileSync(childResultPath, "utf-8"));
790
+ if (persisted.state === "failed") break;
791
+ await new Promise((resolve) => setTimeout(resolve, 20));
792
+ }
793
+ assert.equal(persisted.timeoutMs, 150);
794
+ assert.equal(persisted.state, "failed");
795
+ assert.deepEqual(persisted.results?.map((entry) => entry.timedOut), [true]);
796
+ assert.deepEqual(persisted.results?.map((entry) => entry.error), ["Subagent timed out after 150ms."]);
797
+ fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
798
+ fs.rmSync(childResultPath, { force: true });
799
+ });
800
+
470
801
  it("persists workflow parent metadata in an async worktree child status and result", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
471
802
  execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
472
803
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
@@ -476,14 +807,16 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
476
807
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
477
808
  mockPi.onCall({ output: "async child done" });
478
809
  const executor = makeExecutor([makeAgent("echo")]);
479
- const workflowRunId = `scripted-workflow-parent-${Date.now()}`;
810
+ const toolCallId = `scripted-workflow-parent-${Date.now()}`;
480
811
  const started = await executor.execute(
481
- workflowRunId,
812
+ toolCallId,
482
813
  { workflowScript: `const child = await runs.run("background", { agent: "echo", task: "Async child", async: true, worktree: true }); return child.runId;` },
483
814
  new AbortController().signal,
484
815
  undefined,
485
816
  makeMinimalCtx(tempDir),
486
817
  );
818
+ assert.ok(started.details.asyncId);
819
+ const workflowRunId = started.details.asyncId;
487
820
  const workflowResultPath = path.join(DIRS.results, `${workflowRunId}.json`);
488
821
  let childRunId: string | undefined;
489
822
  for (let attempt = 0; attempt < 150; attempt++) {
@@ -494,6 +827,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
494
827
  await new Promise((resolve) => setTimeout(resolve, 20));
495
828
  }
496
829
  assert.ok(childRunId);
830
+ const workflowStatus = JSON.parse(fs.readFileSync(path.join(started.details.asyncDir!, "status.json"), "utf-8")) as AsyncStatus;
831
+ const workflowStepSessionFile = workflowStatus.steps?.[0]?.sessionFile ?? "";
832
+ assert.equal(workflowStatus.steps?.[0]?.agent, "echo");
833
+ assert.match(workflowStepSessionFile, /session\.jsonl$/);
497
834
  const childDir = path.join(DIRS.async, childRunId);
498
835
  const childStatusPath = path.join(childDir, "status.json");
499
836
  let childStatus: { state?: string; mode?: string; parentWorkflowRunId?: string; workflowKey?: string; parallelHandoff?: { path?: string } } = {};
@@ -513,9 +850,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
513
850
  const childResult = JSON.parse(fs.readFileSync(childResultPath, "utf-8")) as { parentWorkflowRunId?: string; workflowKey?: string };
514
851
  assert.equal(childResult.parentWorkflowRunId, workflowRunId);
515
852
  assert.equal(childResult.workflowKey, "background");
516
- fs.rmSync(started.details.asyncDir!, { recursive: true, force: true });
853
+ assert.equal(fs.existsSync(workflowStepSessionFile), true);
854
+ fs.rmSync(started.details.asyncDir!, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
517
855
  fs.rmSync(workflowResultPath, { force: true });
518
- fs.rmSync(childDir, { recursive: true, force: true });
856
+ fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
519
857
  fs.rmSync(path.join(DIRS.results, `${childRunId}.json`), { force: true });
520
858
  });
521
859
 
@@ -575,19 +913,20 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
575
913
  fs.rmSync(asyncDir, { recursive: true, force: true });
576
914
  });
577
915
 
578
- it("routes workflow script children through ordinary foreground execution", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
916
+ it("routes workflow children through one automatic mission", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
579
917
  mockPi.onCall({ output: "scanned auth" });
580
918
  mockPi.onCall({ output: "reviewed auth" });
581
- const executor = makeExecutor([makeAgent("echo")]);
919
+ const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndex: false } });
582
920
 
583
921
  const result = await executor.execute(
584
922
  "scripted-workflow",
585
923
  {
586
924
  async: false,
587
925
  workflowScript: `
926
+ const stateType = typeof state;
588
927
  const scan = await runs.run("scan", { agent: "echo", task: "Scan auth" });
589
928
  const review = await runs.run("review", { agent: "echo", task: "Review: " + scan.output });
590
- return review.output;
929
+ return { output: review.output, stateType };
591
930
  `,
592
931
  },
593
932
  new AbortController().signal,
@@ -599,9 +938,88 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
599
938
  assert.match(result.content[0]?.text ?? "", /reviewed auth/);
600
939
  assert.equal(result.details.mode, "workflow");
601
940
  assert.equal(result.details.results.length, 2);
941
+ assert.equal(result.details.workflow?.value && (result.details.workflow.value as { stateType?: unknown }).stateType, "object");
942
+ assert.ok(result.details.missionId);
943
+ const missionDir = path.join(tempDir, ".pi-subagents", "missions");
944
+ const missionFiles = fs.readdirSync(missionDir).filter((entry) => entry.endsWith(".json"));
945
+ assert.equal(missionFiles.length, 1);
946
+ const mission = JSON.parse(fs.readFileSync(path.join(missionDir, missionFiles[0]!), "utf-8")) as { objective?: string };
947
+ assert.equal(mission.objective, utils.PROMPT_REDACTED);
602
948
  assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state === "completed").map((entry) => entry.key), ["scan", "review"]);
603
949
  });
604
950
 
951
+ it("keeps workflow children mission-detached when automatic mission persistence fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
952
+ mockPi.onCall({ output: "scanned auth" });
953
+ mockPi.onCall({ output: "reviewed auth" });
954
+ const blockedIndex = path.join(tempDir, "blocked-mission-index");
955
+ fs.writeFileSync(blockedIndex, "not a directory", "utf-8");
956
+ const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndexDir: blockedIndex } });
957
+
958
+ const result = await executor.execute(
959
+ "scripted-workflow-mission-warning",
960
+ {
961
+ async: false,
962
+ workflowScript: `
963
+ const scan = await runs.run("scan", { agent: "echo", task: "Scan auth" });
964
+ const review = await runs.run("review", { agent: "echo", task: "Review: " + scan.output });
965
+ return review.output;
966
+ `,
967
+ },
968
+ new AbortController().signal,
969
+ undefined,
970
+ makeMinimalCtx(tempDir),
971
+ );
972
+
973
+ assert.equal(result.isError, undefined);
974
+ assert.equal(result.details.missionId, undefined);
975
+ assert.match(result.details.missionWarning ?? "", /Mission tracking unavailable/);
976
+ assert.equal(result.details.results.length, 2);
977
+ const missionDir = path.join(tempDir, ".pi-subagents", "missions");
978
+ const missionFiles = fs.existsSync(missionDir) ? fs.readdirSync(missionDir).filter((entry) => entry.endsWith(".json")) : [];
979
+ assert.equal(missionFiles.length, 1);
980
+ });
981
+
982
+ it("shares durable workflow state across a mission and omits it for mission:false", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
983
+ const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndex: false } });
984
+ const first = await executor.execute(
985
+ "mission-state-first",
986
+ {
987
+ async: false,
988
+ mission: { title: "Stateful workflow" },
989
+ workflowScript: `await state.set("review.stage", { count: 1 }); return await state.get("review.stage");`,
990
+ },
991
+ new AbortController().signal,
992
+ undefined,
993
+ makeMinimalCtx(tempDir),
994
+ );
995
+ assert.equal(first.isError, undefined, first.content[0]?.text ?? "first workflow failed");
996
+ assert.ok(first.details.missionId);
997
+ assert.deepEqual(first.details.workflow?.value, { count: 1 });
998
+ const statePath = path.join(tempDir, ".pi-subagents", "missions", first.details.missionId, "state.json");
999
+ assert.equal(fs.existsSync(statePath), true);
1000
+
1001
+ const second = await executor.execute(
1002
+ "mission-state-second",
1003
+ { async: false, missionId: first.details.missionId, workflowScript: `return await state.get("review.stage");` },
1004
+ new AbortController().signal,
1005
+ undefined,
1006
+ makeMinimalCtx(tempDir),
1007
+ );
1008
+ assert.equal(second.isError, undefined, second.content[0]?.text ?? "second workflow failed");
1009
+ assert.deepEqual(second.details.workflow?.value, { count: 1 });
1010
+
1011
+ const ephemeral = await executor.execute(
1012
+ "mission-state-off",
1013
+ { async: false, mission: false, workflowScript: `return typeof state;` },
1014
+ new AbortController().signal,
1015
+ undefined,
1016
+ makeMinimalCtx(tempDir),
1017
+ );
1018
+ assert.equal(ephemeral.isError, undefined, ephemeral.content[0]?.text ?? "ephemeral workflow failed");
1019
+ assert.equal(ephemeral.details.workflow?.value, "undefined");
1020
+ assert.equal(ephemeral.details.missionId, undefined);
1021
+ });
1022
+
605
1023
  it("runs a direct single child in a managed worktree", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
606
1024
  execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
607
1025
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
@@ -610,11 +1028,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
610
1028
  execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
611
1029
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
612
1030
  mockPi.onCall({ output: "isolated feature", writeFiles: [{ path: "feature.txt", content: "feature\n" }] });
613
- const executor = makeExecutor([makeAgent("builder")]);
1031
+ const executor = makeExecutor([makeAgent("worker")]);
614
1032
 
615
1033
  const result = await executor.execute(
616
1034
  "direct-worktree",
617
- { async: false, agent: "builder", task: "Implement feature", worktree: true },
1035
+ { async: false, agent: "worker", task: "Implement feature", worktree: true },
618
1036
  new AbortController().signal,
619
1037
  undefined,
620
1038
  makeMinimalCtx(tempDir),
@@ -635,6 +1053,57 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
635
1053
 
636
1054
  });
637
1055
 
1056
+ it("aligns a forked workflow child session with its managed worktree cwd", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1057
+ execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
1058
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
1059
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
1060
+ fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
1061
+ execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
1062
+ execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
1063
+
1064
+ const parentSessionFile = path.join(mockPi.dir, "parent-session.jsonl");
1065
+ const childSessionFile = path.join(mockPi.dir, "forked-child-session.jsonl");
1066
+ fs.writeFileSync(parentSessionFile, `${JSON.stringify({ type: "session", version: 3, id: "parent", cwd: tempDir })}\n`, "utf-8");
1067
+ const ctx = makeMinimalCtx(tempDir);
1068
+ Object.assign(ctx.sessionManager, {
1069
+ getSessionFile: () => parentSessionFile,
1070
+ getLeafId: () => "parent-leaf",
1071
+ openSession: () => ({
1072
+ createBranchedSession: () => {
1073
+ fs.writeFileSync(childSessionFile, `${JSON.stringify({ type: "session", version: 3, id: "child", cwd: tempDir })}\n`, "utf-8");
1074
+ return childSessionFile;
1075
+ },
1076
+ }),
1077
+ });
1078
+ mockPi.onCall({ output: "isolated fork child" });
1079
+ const executor = makeExecutor([makeAgent("worker", { defaultContext: "fork" })]);
1080
+
1081
+ const result = await executor.execute(
1082
+ "forked-worktree-workflow",
1083
+ { async: false, workflowScript: `return runs.run("isolated", { agent: "worker", task: "Work in isolation", worktree: true });` },
1084
+ new AbortController().signal,
1085
+ undefined,
1086
+ ctx,
1087
+ );
1088
+
1089
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1090
+ const workflowValue = result.details.workflow?.value as { artifactPaths?: string[] } | undefined;
1091
+ const handoffPath = workflowValue?.artifactPaths?.find((candidate) => candidate.endsWith(".json") && candidate.includes("handoffs"));
1092
+ assert.ok(handoffPath, JSON.stringify(workflowValue));
1093
+ const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
1094
+ groups: Array<{ cleanup: { tasks: Array<{ path: string }> } }>;
1095
+ };
1096
+ const managedWorktreeCwd = handoff.groups[0]?.cleanup.tasks[0]?.path;
1097
+ assert.ok(managedWorktreeCwd);
1098
+ const callCwd = readCall().cwd;
1099
+ assert.ok(callCwd);
1100
+ assert.notEqual(path.resolve(callCwd), path.resolve(tempDir));
1101
+ assert.equal(path.basename(callCwd), path.basename(managedWorktreeCwd));
1102
+ const sessionHeader = JSON.parse(fs.readFileSync(childSessionFile, "utf-8").split("\n", 1)[0]!) as { cwd?: string };
1103
+ assert.ok(sessionHeader.cwd);
1104
+ assert.equal(path.basename(sessionHeader.cwd), path.basename(callCwd));
1105
+ });
1106
+
638
1107
  it("lets runs.all siblings settle when one child fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
639
1108
  mockPi.onCall({ exitCode: 1, stderr: "first child failed" });
640
1109
  mockPi.onCall({ output: "second child completed" });
@@ -669,6 +1138,112 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
669
1138
  assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state !== "started").map(({ state }) => state).sort(), ["completed", "failed"]);
670
1139
  });
671
1140
 
1141
+ it("rejects an over-limit runs.all batch before launching any workflow child", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1142
+ const executor = makeExecutor([makeAgent("echo")], { maxSubagentSpawnsPerRun: 1 });
1143
+
1144
+ const result = await executor.execute(
1145
+ "scripted-workflow-fanout-limit",
1146
+ {
1147
+ async: false,
1148
+ workflowScript: `return await runs.all([
1149
+ { key: "first", agent: "echo", task: "First task" },
1150
+ { key: "second", agent: "echo", task: "Second task" }
1151
+ ]);`,
1152
+ },
1153
+ new AbortController().signal,
1154
+ undefined,
1155
+ makeMinimalCtx(tempDir),
1156
+ );
1157
+
1158
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1159
+ assert.equal(mockPi.callCount(), 0);
1160
+ const children = result.details.workflow?.value as Array<{ ok: boolean; error?: string }>;
1161
+ assert.deepEqual(children.map(({ ok }) => ok), [false, false]);
1162
+ for (const child of children) assert.match(child.error ?? "", /workflow\[second\].*0\/1 used; 2 requested, 1 remaining/);
1163
+ });
1164
+
1165
+ it("runs a direct child gate as host-verified acceptance", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1166
+ const markerFile = "direct-gate.txt";
1167
+ const markerPath = path.join(tempDir, markerFile);
1168
+ mockPi.onCall({ output: [
1169
+ "done",
1170
+ "```acceptance-report",
1171
+ JSON.stringify({
1172
+ criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "implemented" }],
1173
+ changedFiles: ["src/file.ts"],
1174
+ testsAddedOrUpdated: ["test/file.test.ts"],
1175
+ commandsRun: [{ command: "npm test", result: "passed", summary: "passed" }],
1176
+ validationOutput: ["tests passed"],
1177
+ residualRisks: [],
1178
+ noStagedFiles: true,
1179
+ }),
1180
+ "```",
1181
+ ].join("\n") });
1182
+ const executor = makeExecutor([makeAgent("echo")]);
1183
+
1184
+ const result = await executor.execute(
1185
+ "direct-gate",
1186
+ { async: false, agent: "echo", task: "Validate the result without edits", gate: `${process.execPath} -e "require('node:fs').writeFileSync('${markerFile}','verified')"` },
1187
+ new AbortController().signal,
1188
+ undefined,
1189
+ makeMinimalCtx(tempDir),
1190
+ );
1191
+
1192
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "direct gate failed");
1193
+ assert.equal(fs.readFileSync(markerPath, "utf-8"), "verified");
1194
+ assert.equal(result.details.results[0]?.acceptance?.status, "verified");
1195
+ assert.equal(result.details.results[0]?.acceptance?.verifyRuns[0]?.id, "gate");
1196
+ });
1197
+
1198
+ it("lets runs.all siblings settle when one verified gate fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1199
+ const acceptedReport = [
1200
+ "done",
1201
+ "```acceptance-report",
1202
+ JSON.stringify({
1203
+ criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "implemented" }],
1204
+ changedFiles: ["src/file.ts"],
1205
+ testsAddedOrUpdated: ["test/file.test.ts"],
1206
+ commandsRun: [{ command: "npm test", result: "passed", summary: "passed" }],
1207
+ validationOutput: ["tests passed"],
1208
+ residualRisks: [],
1209
+ noStagedFiles: true,
1210
+ }),
1211
+ "```",
1212
+ ].join("\n");
1213
+ mockPi.onCall({ output: acceptedReport });
1214
+ mockPi.onCall({ output: acceptedReport });
1215
+ const executor = makeExecutor([makeAgent("echo")]);
1216
+
1217
+ const result = await executor.execute(
1218
+ "scripted-workflow-gates",
1219
+ {
1220
+ async: false,
1221
+ workflowScript: `
1222
+ const children = await runs.all([
1223
+ { key: "fails-gate", agent: "echo", task: "First task", gate: ${JSON.stringify(`${process.execPath} -e "process.exit(7)"`)} },
1224
+ { key: "passes-gate", agent: "echo", task: "Second task", gate: ${JSON.stringify(`${process.execPath} -e "process.exit(0)"`)} }
1225
+ ]);
1226
+ return children.map(({ key, ok }) => ({ key, ok }));
1227
+ `,
1228
+ },
1229
+ new AbortController().signal,
1230
+ undefined,
1231
+ makeMinimalCtx(tempDir),
1232
+ );
1233
+
1234
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1235
+ assert.equal(mockPi.callCount(), 2);
1236
+ assert.deepEqual(result.details.workflow?.value, [
1237
+ { key: "fails-gate", ok: false },
1238
+ { key: "passes-gate", ok: true },
1239
+ ]);
1240
+ const [failed, passed] = result.details.results;
1241
+ assert.equal(failed?.acceptance?.status, "rejected");
1242
+ assert.equal(failed?.acceptance?.verifyRuns[0]?.status, "failed");
1243
+ assert.equal(passed?.acceptance?.status, "verified");
1244
+ assert.equal(passed?.acceptance?.verifyRuns[0]?.status, "passed");
1245
+ });
1246
+
672
1247
  it("gives parallel workflow children separate managed worktrees and durable handoffs", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
673
1248
  execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
674
1249
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
@@ -678,7 +1253,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
678
1253
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
679
1254
  mockPi.onCall({ output: "feature a", writeFiles: [{ path: "feature-a.txt", content: "a\n" }] });
680
1255
  mockPi.onCall({ output: "feature b", writeFiles: [{ path: "feature-b.txt", content: "b\n" }] });
681
- const executor = makeExecutor([makeAgent("builder")]);
1256
+ const executor = makeExecutor([makeAgent("worker")]);
682
1257
 
683
1258
  const result = await executor.execute(
684
1259
  "scripted-workflow-worktrees",
@@ -686,8 +1261,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
686
1261
  async: false,
687
1262
  workflowScript: `
688
1263
  const children = await runs.all([
689
- { key: "feature-a", agent: "builder", task: "Implement A", worktree: true },
690
- { key: "feature-b", agent: "builder", task: "Implement B", worktree: true }
1264
+ { key: "feature-a", agent: "worker", task: "Implement A", worktree: true },
1265
+ { key: "feature-b", agent: "worker", task: "Implement B", worktree: true }
691
1266
  ]);
692
1267
  return children.map(({ key, artifactPaths }) => ({ key, artifactPaths }));
693
1268
  `,
@@ -726,6 +1301,87 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
726
1301
  assert.match(result.content[0]?.text ?? "", /handoffs/);
727
1302
  });
728
1303
 
1304
+ it("preserves a workflow worktree when its child detaches for supervisor coordination", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
1305
+ execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
1306
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
1307
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
1308
+ fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
1309
+ execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
1310
+ execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
1311
+ mockPi.onCall({
1312
+ steps: [
1313
+ { jsonl: [events.toolStart("contact_supervisor", { reason: "need_decision", message: "Need a decision" })] },
1314
+ { delay: 500, jsonl: [events.assistantMessage("done after coordination")] },
1315
+ ],
1316
+ });
1317
+ const piEvents = createEventBus();
1318
+ const executor = makeExecutor(
1319
+ [makeAgent("worker", { systemPrompt: "Intercom orchestration channel:" })],
1320
+ {},
1321
+ false,
1322
+ undefined,
1323
+ true,
1324
+ new Map(),
1325
+ undefined,
1326
+ undefined,
1327
+ piEvents,
1328
+ );
1329
+ let detachAccepted = false;
1330
+ piEvents.on(INTERCOM_DETACH_RESPONSE_EVENT, (payload) => {
1331
+ if ((payload as { requestId?: unknown }).requestId === "workflow-worktree-detach") {
1332
+ detachAccepted ||= (payload as { accepted?: unknown }).accepted === true;
1333
+ }
1334
+ });
1335
+ const detachTimer = setInterval(() => {
1336
+ if (!detachAccepted) piEvents.emit(INTERCOM_DETACH_REQUEST_EVENT, { requestId: "workflow-worktree-detach" });
1337
+ }, 10);
1338
+ detachTimer.unref();
1339
+
1340
+ const result = await executor.execute(
1341
+ "scripted-workflow-detached-worktree",
1342
+ {
1343
+ async: false,
1344
+ workflowScript: `
1345
+ const children = await runs.all([
1346
+ { key: "detaches", agent: "worker", task: "Ask then continue", worktree: true }
1347
+ ]);
1348
+ return children.map(({ key, ok, artifactPaths }) => ({ key, ok, artifactPaths }));
1349
+ `,
1350
+ },
1351
+ new AbortController().signal,
1352
+ undefined,
1353
+ makeMinimalCtx(tempDir),
1354
+ );
1355
+ clearInterval(detachTimer);
1356
+
1357
+ assert.equal(detachAccepted, true);
1358
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1359
+ assert.match(result.content[0]?.text ?? "", /run detaches: failed/);
1360
+ const workflowValue = result.details.workflow?.value as Array<{ ok: boolean; artifactPaths: string[] }>;
1361
+ assert.equal(workflowValue[0]?.ok, false);
1362
+ const handoffPath = workflowValue[0]?.artifactPaths.find((candidate) => candidate.endsWith(".json"));
1363
+ assert.ok(handoffPath, result.content[0]?.text ?? "missing pending handoff");
1364
+ const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
1365
+ groups: Array<{
1366
+ cleanup: { state: string; tasks: Array<{ path: string; branch: string; preserved: boolean; worktreeRemoved: boolean; branchRemoved: boolean }> };
1367
+ }>;
1368
+ };
1369
+ const cleanup = handoff.groups[0]?.cleanup;
1370
+ assert.equal(cleanup?.state, "partial");
1371
+ assert.equal(cleanup?.tasks[0]?.preserved, true);
1372
+ assert.equal(cleanup?.tasks[0]?.worktreeRemoved, false);
1373
+ assert.equal(cleanup?.tasks[0]?.branchRemoved, false);
1374
+ const worktreePath = cleanup?.tasks[0]?.path;
1375
+ const branch = cleanup?.tasks[0]?.branch;
1376
+ assert.ok(worktreePath);
1377
+ assert.ok(branch);
1378
+ assert.equal(fs.existsSync(worktreePath), true, "live detached worktree must remain present");
1379
+
1380
+ await new Promise((resolve) => setTimeout(resolve, 750));
1381
+ execFileSync("git", ["worktree", "remove", "--force", worktreePath], { cwd: tempDir });
1382
+ execFileSync("git", ["branch", "-D", branch], { cwd: tempDir, stdio: "ignore" });
1383
+ });
1384
+
729
1385
  it("inherits workflow-level worktree isolation and allows a child opt-out", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
730
1386
  execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
731
1387
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
@@ -735,7 +1391,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
735
1391
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
736
1392
  mockPi.onCall({ output: "isolated", writeFiles: [{ path: "isolated.txt", content: "isolated\n" }] });
737
1393
  mockPi.onCall({ output: "shared", writeFiles: [{ path: "shared.txt", content: "shared\n" }] });
738
- const executor = makeExecutor([makeAgent("builder")]);
1394
+ const executor = makeExecutor([makeAgent("worker")]);
739
1395
 
740
1396
  const result = await executor.execute(
741
1397
  "scripted-workflow-worktree-default",
@@ -743,8 +1399,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
743
1399
  async: false,
744
1400
  worktree: true,
745
1401
  workflowScript: `
746
- const isolated = await runs.run("isolated", { agent: "builder", task: "Isolated" });
747
- const shared = await runs.run("shared", { agent: "builder", task: "Shared", worktree: false });
1402
+ const isolated = await runs.run("isolated", { agent: "worker", task: "Isolated" });
1403
+ const shared = await runs.run("shared", { agent: "worker", task: "Shared", worktree: false });
748
1404
  return { isolated: isolated.artifactPaths, shared: shared.artifactPaths };
749
1405
  `,
750
1406
  },
@@ -762,6 +1418,79 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
762
1418
  assert.equal(fs.existsSync(handoffPaths[0]!), true);
763
1419
  });
764
1420
 
1421
+ it("supports dynamic parallel phases followed by sequential worktree children", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
1422
+ execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
1423
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
1424
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
1425
+ fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
1426
+ execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
1427
+ execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
1428
+ mockPi.onCall({ output: "api built", writeFiles: [{ path: "api.txt", content: "api\n" }] });
1429
+ mockPi.onCall({ output: "ui built", writeFiles: [{ path: "ui.txt", content: "ui\n" }] });
1430
+ mockPi.onCall({ output: "joined", writeFiles: [{ path: "joined.txt", content: "joined\n" }] });
1431
+ mockPi.onCall({ output: "shared", writeFiles: [{ path: "shared.txt", content: "shared\n" }] });
1432
+ const executor = makeExecutor([makeAgent("worker")]);
1433
+
1434
+ const result = await executor.execute(
1435
+ "scripted-workflow-dynamic-worktree-phases",
1436
+ {
1437
+ async: false,
1438
+ worktree: true,
1439
+ workflowScript: `
1440
+ const targets = ["api", "ui"];
1441
+ const built = await runs.all(targets.map((target) => ({
1442
+ key: "build-" + target,
1443
+ agent: "worker",
1444
+ task: "Build " + target
1445
+ })));
1446
+ const joined = await runs.run("join", { agent: "worker", task: built.map((child) => child.key).join(",") });
1447
+ const shared = await runs.run("shared", { agent: "worker", task: joined.key, worktree: false });
1448
+ return {
1449
+ built: built.map((child) => ({ key: child.key, artifactPaths: child.artifactPaths })),
1450
+ joined: { key: joined.key, artifactPaths: joined.artifactPaths },
1451
+ shared: shared.key
1452
+ };
1453
+ `,
1454
+ },
1455
+ new AbortController().signal,
1456
+ undefined,
1457
+ makeMinimalCtx(tempDir),
1458
+ );
1459
+
1460
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1461
+ assert.equal(mockPi.callCount(), 4, result.content[0]?.text ?? "workflow produced no output");
1462
+ assert.equal(fs.existsSync(path.join(tempDir, "api.txt")), false);
1463
+ assert.equal(fs.existsSync(path.join(tempDir, "ui.txt")), false);
1464
+ assert.equal(fs.existsSync(path.join(tempDir, "joined.txt")), false);
1465
+ assert.equal(fs.readFileSync(path.join(tempDir, "shared.txt"), "utf-8"), "shared\n");
1466
+
1467
+ const output = result.content[0]?.text ?? "";
1468
+ assert.match(output, /build-api/);
1469
+ assert.match(output, /build-ui/);
1470
+ assert.match(output, /join/);
1471
+ assert.match(output, /shared/);
1472
+ const handoffPaths = [...output.matchAll(/"([^"\n]*\/handoffs\/[^"\n]+\.json)"/g)].map((match) => match[1]!);
1473
+ assert.equal(handoffPaths.length, 3, output);
1474
+ const worktreePaths = new Set<string>();
1475
+ for (const handoffPath of handoffPaths) {
1476
+ const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
1477
+ groups: Array<{
1478
+ children: Array<{ patch: { changed: boolean; path: string } }>;
1479
+ cleanup: { state: string; tasks: Array<{ path: string; worktreeRemoved: boolean; branchRemoved: boolean }> };
1480
+ }>;
1481
+ };
1482
+ assert.equal(handoff.groups.length, 1);
1483
+ assert.equal(handoff.groups[0]?.children[0]?.patch.changed, true);
1484
+ assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true);
1485
+ assert.equal(handoff.groups[0]?.cleanup.state, "complete");
1486
+ assert.equal(handoff.groups[0]?.cleanup.tasks[0]?.worktreeRemoved, true);
1487
+ assert.equal(handoff.groups[0]?.cleanup.tasks[0]?.branchRemoved, true);
1488
+ worktreePaths.add(handoff.groups[0]!.cleanup.tasks[0]!.path);
1489
+ }
1490
+ assert.equal(worktreePaths.size, 3);
1491
+ for (const worktreePath of worktreePaths) assert.equal(fs.existsSync(worktreePath), false);
1492
+ });
1493
+
765
1494
  it("applies a workflow usage budget across scripted child launches", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
766
1495
  mockPi.onCall({ output: "first result" });
767
1496
  const executor = makeExecutor([makeAgent("echo")]);
@@ -884,11 +1613,27 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
884
1613
  );
885
1614
 
886
1615
  assert.equal(result.isError, true);
887
- assert.match(result.content[0]?.text ?? "", /(?:verified.*object form|verify.*at least one command)/i);
1616
+ assert.match(result.content[0]?.text ?? "", /(?:verified.*object form|verify.*at least one runtime command)/i);
888
1617
  }
889
1618
  assert.equal(mockPi.callCount(), 0);
890
1619
  });
891
1620
 
1621
+ it("rejects invalid verified async chain acceptance before spawning", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1622
+ const executor = makeExecutor([makeAgent("echo")]);
1623
+
1624
+ const result = await executor.execute(
1625
+ "invalid-verified-async-chain-acceptance",
1626
+ { chain: [{ agent: "echo", task: "Do work", acceptance: { level: "verified", verify: [] } }], async: true },
1627
+ new AbortController().signal,
1628
+ undefined,
1629
+ makeMinimalCtx(tempDir),
1630
+ );
1631
+
1632
+ assert.equal(result.isError, true);
1633
+ assert.match(result.content[0]?.text ?? "", /verify.*at least one runtime command/i);
1634
+ assert.equal(mockPi.callCount(), 0);
1635
+ });
1636
+
892
1637
  it("rejects unknown action strings at runtime", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
893
1638
  const executor = makeExecutor([makeAgent("echo")]);
894
1639
 
@@ -1053,7 +1798,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1053
1798
  requestId: "registered-a",
1054
1799
  ownerRunId: "owner-delegation",
1055
1800
  nodeId: "node-a",
1056
- agent: "builder",
1801
+ agent: "worker",
1057
1802
  task: "Return literal JSON-looking text",
1058
1803
  context: "fresh",
1059
1804
  cwd: tempDir,
@@ -1065,7 +1810,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1065
1810
  requestId: "registered-b",
1066
1811
  ownerRunId: "owner-delegation",
1067
1812
  nodeId: "node-b",
1068
- agent: "commentator",
1813
+ agent: "reviewer",
1069
1814
  task: "Run the second logical node",
1070
1815
  context: "fresh",
1071
1816
  cwd: tempDir,
@@ -1181,13 +1926,53 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1181
1926
  assert.equal(mockPi.callCount(), 1);
1182
1927
  });
1183
1928
 
1929
+ it("qualifies inherited nested claims with the generated nested run id", async () => {
1930
+ mockPi.onCall({ output: "nested completed" });
1931
+ const descriptor = createRunFanoutBudget("root-run", 2);
1932
+ const previous = process.env[RUN_FANOUT_BUDGET_ENV];
1933
+ try {
1934
+ process.env[RUN_FANOUT_BUDGET_ENV] = encodeRunFanoutBudgetDescriptor({ ...descriptor, parentPath: "tasks[0]" });
1935
+ const executor = makeExecutor([makeAgent("echo")]);
1936
+ const result = await executor.execute("nested", { agent: "echo", task: "Nested work" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1937
+
1938
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "nested run failed");
1939
+ const claims = fs.readdirSync(path.join(descriptor.directory, "claims"));
1940
+ assert.equal(claims.length, 1);
1941
+ const claim = JSON.parse(fs.readFileSync(path.join(descriptor.directory, "claims", claims[0]!), "utf-8")) as { path: string };
1942
+ assert.match(claim.path, /^tasks\[0\]\/[a-f0-9]{8}\/single$/);
1943
+ } finally {
1944
+ if (previous === undefined) delete process.env[RUN_FANOUT_BUDGET_ENV];
1945
+ else process.env[RUN_FANOUT_BUDGET_ENV] = previous;
1946
+ fs.rmSync(descriptor.directory, { recursive: true, force: true });
1947
+ }
1948
+ });
1949
+
1950
+ it("rejects an over-limit static run fan-out before creating session artifacts", async () => {
1951
+ const sessionDir = path.join(tempDir, "run-fanout-preflight");
1952
+ const executor = makeExecutor([makeAgent("echo"), makeAgent("second")], { maxSubagentSpawnsPerRun: 1 });
1953
+ const result = await executor.execute(
1954
+ "run-fanout-preflight",
1955
+ { tasks: [{ agent: "echo", task: "First" }, { agent: "second", task: "Second" }], sessionDir },
1956
+ new AbortController().signal,
1957
+ undefined,
1958
+ makeMinimalCtx(tempDir),
1959
+ );
1960
+
1961
+ assert.equal(result.isError, true);
1962
+ assert.match(result.content[0]?.text ?? "", /Run fan-out limit reached at tasks\[1\] \(0\/1 used; 2 requested, 1 remaining\)/);
1963
+ assert.deepEqual(result.details.runFanoutBudget, { used: 0, limit: 1, remaining: 1 });
1964
+ assert.equal(result.details.runFanoutRejection?.path, "tasks[1]");
1965
+ assert.equal(fs.existsSync(sessionDir), false);
1966
+ assert.equal(mockPi.callCount(), 0);
1967
+ });
1968
+
1184
1969
  it("reports structured spawn-budget usage through status", async () => {
1185
1970
  const spawnState = { sessionId: "session-123", count: 3, configuredLimit: 4, granted: 1, grantHistory: [] };
1186
1971
  const executor = makeExecutor([makeAgent("echo")], { maxSubagentSpawnsPerSession: 4 }, false, spawnState);
1187
1972
 
1188
1973
  const status = await executor.execute("status", { action: "status" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1189
1974
 
1190
- assert.match(status.content[0]?.text ?? "", /^Spawn budget: 3\/5 used, 2 remaining/);
1975
+ assert.match(status.content[0]?.text ?? "", /^Status target: active runs\nSpawn budget: 3\/5 used, 2 remaining/);
1191
1976
  assert.deepEqual(status.details?.spawnBudget, {
1192
1977
  used: 3,
1193
1978
  configuredLimit: 4,
@@ -1409,6 +2194,52 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1409
2194
  assert.deepEqual(result.details?.totalCost, { inputTokens: 100, outputTokens: 50, costUsd: 0.001 });
1410
2195
  });
1411
2196
 
2197
+ it("ignores stale foreground control notification contexts after reload", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2198
+ mockPi.onCall({
2199
+ jsonl: [
2200
+ events.assistantMessage("first update"),
2201
+ events.assistantMessage("second update"),
2202
+ ],
2203
+ });
2204
+ const state: SubagentState = {
2205
+ baseCwd: tempDir,
2206
+ currentSessionId: null,
2207
+ asyncJobs: new Map(),
2208
+ foregroundRuns: new Map(),
2209
+ foregroundControls: new Map(),
2210
+ lastForegroundControlId: null,
2211
+ };
2212
+ const staleEvents = {
2213
+ on: createEventBus().on,
2214
+ emit() { throw new Error("This extension ctx is stale after session replacement or reload."); },
2215
+ };
2216
+ const updates: ExecutorToolResult[] = [];
2217
+ const executor = createSubagentExecutor!({
2218
+ pi: { events: staleEvents, getSessionName: () => undefined },
2219
+ state,
2220
+ config: { control: { enabled: true, activeNoticeAfterTurns: 2, activeNoticeAfterMs: 999_999, activeNoticeAfterTokens: 999_999, notifyOn: ["active_long_running"], notifyChannels: ["event"] } },
2221
+ asyncByDefault: false,
2222
+ tempArtifactsDir: tempDir,
2223
+ getSubagentSessionRoot: () => path.join(tempDir, ".pi-subagents", "sessions"),
2224
+ expandTilde: (value: string) => value,
2225
+ discoverAgents: () => ({ agents: [makeAgent("echo")] }),
2226
+ allowMutatingManagementActions: true,
2227
+ });
2228
+
2229
+ const result = await executor.execute(
2230
+ "stale-control-context",
2231
+ { agent: "echo", task: "Investigate behavior", async: false },
2232
+ new AbortController().signal,
2233
+ (update: ExecutorToolResult) => updates.push(update),
2234
+ makeMinimalCtx(tempDir),
2235
+ );
2236
+
2237
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "foreground run failed");
2238
+ assert.equal(result.details.results[0]?.exitCode, 0);
2239
+ const controlEvents = updates.flatMap((update) => update.details?.controlEvents ?? []);
2240
+ assert.equal(controlEvents[0]?.type, "active_long_running");
2241
+ });
2242
+
1412
2243
  it("emits resolved model and thinking for nested foreground starts", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1413
2244
  mockPi.onCall({ output: "nested result" });
1414
2245
  const route = createNestedRoute("root-nested-model");
@@ -1502,10 +2333,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1502
2333
 
1503
2334
  it("fails implementation runs that complete without mutation attempts", async () => {
1504
2335
  mockPi.onCall({ output: "Validation:\nlet rawFilename = params.filename.trim();" });
1505
- const agents = [makeAgent("builder")];
2336
+ const agents = [makeAgent("worker")];
1506
2337
  const controlEvents: Array<{ message: string }> = [];
1507
2338
 
1508
- const result = await runSync(tempDir, agents, "builder", "Implement the approved file changes", {
2339
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
1509
2340
  runId: "guard-run",
1510
2341
  onControlEvent: (event: { message: string }) => controlEvents.push(event),
1511
2342
  });
@@ -1515,18 +2346,18 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1515
2346
  assert.equal(result.finalOutput, "Validation:\nlet rawFilename = params.filename.trim();");
1516
2347
  assert.equal(result.progress.status, "failed");
1517
2348
  assert.deepEqual(controlEvents.map((event) => event.message), [
1518
- "builder completed without making edits for an implementation task",
2349
+ "worker completed without making edits for an implementation task",
1519
2350
  ]);
1520
2351
  assert.deepEqual(result.controlEvents?.map((event) => event.message), [
1521
- "builder completed without making edits for an implementation task",
2352
+ "worker completed without making edits for an implementation task",
1522
2353
  ]);
1523
2354
  });
1524
2355
 
1525
2356
  it("agent contract v1 reports omitted acceptance separately without injecting a prompt", async () => {
1526
2357
  mockPi.onCall({ output: "Plan only" });
1527
- const agents = [makeAgent("builder", { tools: ["read", "write"] })];
2358
+ const agents = [makeAgent("worker", { tools: ["read", "write"] })];
1528
2359
 
1529
- const result = await runSync(tempDir, agents, "builder", "Implement the approved file changes", {
2360
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
1530
2361
  runId: "v1-no-acceptance",
1531
2362
  agentContract: { version: 1 },
1532
2363
  });
@@ -1543,9 +2374,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1543
2374
 
1544
2375
  it("agent contract v1 keeps acceptance rejection out of execution status", async () => {
1545
2376
  mockPi.onCall({ output: "Done\n```acceptance-report\n{\"criteriaSatisfied\":[{\"id\":\"criterion-1\",\"status\":\"not-satisfied\",\"evidence\":\"no proof\"}]}\n```" });
1546
- const agents = [makeAgent("builder", { tools: ["read"], completionGuard: false })];
2377
+ const agents = [makeAgent("worker", { tools: ["read"], completionGuard: false })];
1547
2378
 
1548
- const result = await runSync(tempDir, agents, "builder", "Summarize the fix", {
2379
+ const result = await runSync(tempDir, agents, "worker", "Summarize the fix", {
1549
2380
  runId: "v1-acceptance-reject",
1550
2381
  agentContract: { version: 1 },
1551
2382
  acceptance: { level: "checked", criteria: ["Return required proof"] },
@@ -1561,9 +2392,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1561
2392
 
1562
2393
  it("agent contract v1 records explicit completion guard as an effect", async () => {
1563
2394
  mockPi.onCall({ output: "Plan only" });
1564
- const agents = [makeAgent("builder", { tools: ["read", "write"], completionGuard: true })];
2395
+ const agents = [makeAgent("worker", { tools: ["read", "write"], completionGuard: true })];
1565
2396
 
1566
- const result = await runSync(tempDir, agents, "builder", "Implement the approved file changes", {
2397
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
1567
2398
  runId: "v1-completion-effect",
1568
2399
  agentContract: { version: 1 },
1569
2400
  });
@@ -1601,6 +2432,36 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1601
2432
  if (child?.artifactPaths?.outputPath) assert.match(fs.readFileSync(child.artifactPaths.outputPath, "utf-8"), /"note": "captured"/);
1602
2433
  });
1603
2434
 
2435
+ it("accepts recovered tool errors before valid structured output but rejects later errors", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2436
+ const recoveredError = { type: "tool_result_end", message: { role: "toolResult", toolName: "read", isError: true, content: [{ type: "text", text: "EISDIR" }] } };
2437
+ const structuredEvents = [
2438
+ { type: "tool_execution_start", toolName: "structured_output", args: { value: { ok: true } } },
2439
+ { type: "tool_result_end", message: { role: "toolResult", toolName: "structured_output", content: [{ type: "text", text: "Structured output captured." }] } },
2440
+ { type: "tool_execution_end", toolName: "structured_output" },
2441
+ ];
2442
+ mockPi.onCall({
2443
+ stdoutRaw: [recoveredError, ...structuredEvents].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
2444
+ structuredOutputCapture: { ok: true },
2445
+ });
2446
+ const executor = makeExecutor([makeAgent("echo")]);
2447
+ const params = { agent: "echo", task: "Return structured data", outputSchema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } }, acceptance: false } as const;
2448
+
2449
+ const recovered = await executor.execute("single-schema-recovered-error", params, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
2450
+
2451
+ assert.equal(recovered.isError, undefined);
2452
+ assert.deepEqual(recovered.details?.results?.[0]?.structuredOutput, { ok: true });
2453
+
2454
+ mockPi.reset();
2455
+ mockPi.onCall({
2456
+ stdoutRaw: [...structuredEvents, recoveredError].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
2457
+ structuredOutputCapture: { ok: true },
2458
+ });
2459
+ const terminal = await executor.execute("single-schema-terminal-error", params, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
2460
+
2461
+ assert.equal(terminal.isError, true);
2462
+ assert.match(terminal.details?.results?.[0]?.error ?? "", /read failed/);
2463
+ });
2464
+
1604
2465
  it("rejects structured output capture files that were not produced by the structured_output tool", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1605
2466
  mockPi.onCall({ output: "spoofed", structuredOutputCapture: { ok: true } });
1606
2467
  const executor = makeExecutor([makeAgent("echo")]);
@@ -1682,11 +2543,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1682
2543
 
1683
2544
  it("returns captured output when the foreground executor fails an implementation run", async () => {
1684
2545
  mockPi.onCall({ output: "Oracle review:\n- finding one\n- finding two" });
1685
- const executor = makeExecutor([makeAgent("commentator")]);
2546
+ const executor = makeExecutor([makeAgent("oracle")]);
1686
2547
 
1687
2548
  const result = await executor.execute(
1688
2549
  "failed-single-output",
1689
- { agent: "commentator", task: "Implement the approved file changes" },
2550
+ { agent: "oracle", task: "Implement the approved file changes" },
1690
2551
  new AbortController().signal,
1691
2552
  undefined,
1692
2553
  makeMinimalCtx(tempDir),
@@ -1701,9 +2562,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1701
2562
 
1702
2563
  it("fails future-tense implementation summaries when no mutation attempt occurred", async () => {
1703
2564
  mockPi.onCall({ output: "I’ll do that now and report back after implementing." });
1704
- const agents = [makeAgent("builder")];
2565
+ const agents = [makeAgent("worker")];
1705
2566
 
1706
- const result = await runSync(tempDir, agents, "builder", "Implement the approved fixes", {
2567
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved fixes", {
1707
2568
  runId: "guard-future-tense",
1708
2569
  });
1709
2570
 
@@ -1761,9 +2622,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1761
2622
  events.assistantMessage("Applied edit"),
1762
2623
  ],
1763
2624
  });
1764
- const agents = [makeAgent("builder")];
2625
+ const agents = [makeAgent("worker")];
1765
2626
 
1766
- const result = await runSync(tempDir, agents, "builder", "Implement the approved file changes", {
2627
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
1767
2628
  runId: "guard-success",
1768
2629
  });
1769
2630
 
@@ -1774,21 +2635,24 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1774
2635
 
1775
2636
  it("resolves explicit agent aliases to canonical execution names", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1776
2637
  mockPi.onCall({ output: "Implemented" });
1777
- const executor = makeExecutor([makeAgent("builder", { aliases: ["developer"], completionGuard: false })]);
2638
+ const executor = makeExecutor([makeAgent("worker", { aliases: ["developer"], completionGuard: false })]);
1778
2639
 
1779
2640
  const result = await executor.execute("single", { agent: "developer", task: "Implement" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1780
2641
 
1781
2642
  assert.equal(result.isError, undefined);
1782
- assert.equal(result.details?.results[0]?.agent, "builder");
2643
+ assert.equal(result.details?.results[0]?.agent, "worker");
1783
2644
  assert.match(result.content[0]?.text ?? "", /Implemented/);
1784
2645
  });
1785
2646
 
1786
- it("returns error for unknown agent", async () => {
2647
+ it("returns error for unknown agent without retaining the prompt", async () => {
1787
2648
  const agents = makeAgentConfigs(["echo"]);
1788
- const result = await runSync(tempDir, agents, "nonexistent", "Do something", {});
2649
+ const sentinel = "PROMPT_AUDIT_SENTINEL_UNKNOWN";
2650
+ const result = await runSync(tempDir, agents, "nonexistent", sentinel, {});
1789
2651
 
1790
2652
  assert.equal(result.exitCode, 1);
1791
2653
  assert.ok(result.error?.includes("Unknown agent"));
2654
+ assert.equal(result.task, "[prompt redacted]");
2655
+ assert.doesNotMatch(JSON.stringify(result), new RegExp(sentinel));
1792
2656
  });
1793
2657
 
1794
2658
 
@@ -1832,10 +2696,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1832
2696
  events.assistantMessage("I need to retry the same edit."),
1833
2697
  ],
1834
2698
  });
1835
- const agents = [makeAgent("builder")];
2699
+ const agents = [makeAgent("worker")];
1836
2700
  const controlEvents: NonNullable<RunSyncResult["controlEvents"]> = [];
1837
2701
 
1838
- const result = await runSync(tempDir, agents, "builder", "Implement the approved fixes", {
2702
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved fixes", {
1839
2703
  runId: "run-failures",
1840
2704
  controlConfig: { enabled: true, failedToolAttemptsBeforeAttention: 3, notifyOn: ["active_long_running", "needs_attention"] },
1841
2705
  onControlEvent: (event: NonNullable<RunSyncResult["controlEvents"]>[number]) => controlEvents.push(event),
@@ -1884,12 +2748,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1884
2748
  it("retries a zero-activity startup exit on the same model", async () => {
1885
2749
  mockPi.onCall({ exitCode: 1 });
1886
2750
  mockPi.onCall({ output: "Recovered after startup race" });
1887
- const agents = [makeAgent("builder", {
2751
+ const agents = [makeAgent("worker", {
1888
2752
  model: "openai/gpt-5-mini",
1889
2753
  fallbackModels: ["anthropic/claude-sonnet-4"],
1890
2754
  })];
1891
2755
 
1892
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2756
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1893
2757
  runId: "startup-retry-sync",
1894
2758
  acceptance: false,
1895
2759
  });
@@ -1904,15 +2768,38 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1904
2768
  assert.equal(mockPi.callCount(), 2);
1905
2769
  });
1906
2770
 
2771
+ it("escalates to file task delivery after a zero-activity SIGKILL startup exit", { skip: process.platform === "win32" ? "POSIX child signal reporting is unavailable on Windows" : false }, async () => {
2772
+ mockPi.onCall({ signal: "SIGKILL" });
2773
+ mockPi.onCall({ output: "Recovered via file delivery" });
2774
+ const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
2775
+
2776
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
2777
+ runId: "startup-sigkill-file-delivery-sync",
2778
+ acceptance: false,
2779
+ });
2780
+
2781
+ assert.equal(result.exitCode, 0);
2782
+ assert.equal(result.finalOutput, "Recovered via file delivery");
2783
+ assert.equal(mockPi.callCount(), 2);
2784
+ const [firstArgs, retryArgs] = readAllCallArgs();
2785
+ assert.ok(firstArgs?.includes("Task: Do work"), "first attempt should deliver the task inline");
2786
+ const retryTaskArg = retryArgs?.at(-1) ?? "";
2787
+ assert.ok(
2788
+ retryTaskArg.startsWith("@") && retryTaskArg.endsWith("task.md"),
2789
+ `retry should reference a task file instead of inline argv, got: ${retryTaskArg}`,
2790
+ );
2791
+ assert.match(result.progress.recentOutput.join("\n"), /\[startup-retry\].*file task delivery/i);
2792
+ });
2793
+
1907
2794
  it("does not retry a non-zero exit after tool activity", async () => {
1908
2795
  mockPi.onCall({ jsonl: [events.toolStart("read", { path: "package.json" })], exitCode: 1 });
1909
2796
  mockPi.onCall({ output: "must not run" });
1910
- const agents = [makeAgent("builder", {
2797
+ const agents = [makeAgent("worker", {
1911
2798
  model: "openai/gpt-5-mini",
1912
2799
  fallbackModels: ["anthropic/claude-sonnet-4"],
1913
2800
  })];
1914
2801
 
1915
- const result = await runSync(tempDir, agents, "builder", "Read a file", {
2802
+ const result = await runSync(tempDir, agents, "worker", "Read a file", {
1916
2803
  runId: "startup-no-retry-after-tool",
1917
2804
  acceptance: false,
1918
2805
  });
@@ -1925,9 +2812,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1925
2812
  it("does not retry non-SIGKILL signaled child exits", { skip: process.platform === "win32" ? "POSIX child signal reporting is unavailable on Windows" : false }, async () => {
1926
2813
  mockPi.onCall({ signal: "SIGTERM" });
1927
2814
  mockPi.onCall({ output: "must not run" });
1928
- const agents = [makeAgent("builder", { model: "openai/gpt-5-mini" })];
2815
+ const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
1929
2816
 
1930
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2817
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1931
2818
  runId: "startup-no-retry-after-signal",
1932
2819
  acceptance: false,
1933
2820
  });
@@ -1941,9 +2828,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1941
2828
 
1942
2829
  it("prefers signal termination errors over stderr tails", { skip: process.platform === "win32" ? "POSIX child signal reporting is unavailable on Windows" : false }, async () => {
1943
2830
  mockPi.onCall({ stderr: "INFO benign startup line\n", signal: "SIGTERM" });
1944
- const agents = [makeAgent("builder", { model: "openai/gpt-5-mini" })];
2831
+ const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
1945
2832
 
1946
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2833
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1947
2834
  runId: "signal-error-over-stderr",
1948
2835
  acceptance: false,
1949
2836
  });
@@ -1957,9 +2844,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1957
2844
  it("does not retry a child exit with raw stdout diagnostics", async () => {
1958
2845
  mockPi.onCall({ stdoutRaw: "configuration failed before protocol startup\n", exitCode: 1 });
1959
2846
  mockPi.onCall({ output: "must not run" });
1960
- const agents = [makeAgent("builder", { model: "openai/gpt-5-mini" })];
2847
+ const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
1961
2848
 
1962
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2849
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1963
2850
  runId: "startup-no-retry-after-stdout-diagnostic",
1964
2851
  acceptance: false,
1965
2852
  });
@@ -1972,12 +2859,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1972
2859
 
1973
2860
  it("reports an actionable error after startup retries are exhausted", async () => {
1974
2861
  mockPi.onCall({ exitCode: 1 });
1975
- const agents = [makeAgent("builder", {
2862
+ const agents = [makeAgent("worker", {
1976
2863
  model: "openai/gpt-5-mini",
1977
2864
  fallbackModels: ["anthropic/claude-sonnet-4"],
1978
2865
  })];
1979
2866
 
1980
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2867
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1981
2868
  runId: "startup-retry-exhausted-sync",
1982
2869
  acceptance: false,
1983
2870
  });
@@ -2307,7 +3194,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2307
3194
 
2308
3195
  assert.equal(result.exitCode, 1);
2309
3196
  assert.match(result.error ?? "", /429 quota exceeded/);
2310
- assert.deepEqual(result.modelAttempts?.map((attempt) => attempt.success), [false]);
3197
+ // No fallback model configured: the last candidate is retried on the same
3198
+ // model up to MODEL_RETRY_DELAYS_MS.length times before giving up.
3199
+ assert.deepEqual(result.modelAttempts?.map((attempt) => attempt.success), [false, false, false, false]);
2311
3200
  });
2312
3201
 
2313
3202
  it("treats recovered child tool errors as successful foreground runs", async () => {
@@ -2415,7 +3304,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2415
3304
  });
2416
3305
 
2417
3306
  assert.equal(result.exitCode, 1);
2418
- assert.deepEqual(result.modelAttempts?.map((attempt) => attempt.success), [false, false]);
3307
+ // Primary fails, fallback fails, then the last (fallback) candidate is
3308
+ // retried on the same model up to MODEL_RETRY_DELAYS_MS.length times.
3309
+ assert.deepEqual(result.modelAttempts?.map((attempt) => attempt.success), [false, false, false, false, false]);
2419
3310
  assert.match(result.error ?? "", /429 quota exceeded/);
2420
3311
  });
2421
3312
 
@@ -2487,6 +3378,32 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2487
3378
  assert.ok(result.progress.durationMs > 0, "should track duration");
2488
3379
  });
2489
3380
 
3381
+ it("streams progress while a foreground child has not emitted output", async () => {
3382
+ const updates: Array<{ text: string; durationMs: number | undefined }> = [];
3383
+ const releasePath = path.join(tempDir, "release-foreground-progress");
3384
+ mockPi.onCall({ output: "Done", waitForPath: releasePath });
3385
+
3386
+ const runPromise = runSync(tempDir, makeAgentConfigs(["echo"]), "echo", "Task", {
3387
+ onUpdate: (update: { content: Array<{ type: string; text?: string }>; details?: { progress?: ProgressSummary[] } }) => {
3388
+ updates.push({
3389
+ text: update.content[0]?.text ?? "",
3390
+ durationMs: update.details?.progress?.[0]?.durationMs,
3391
+ });
3392
+ },
3393
+ });
3394
+ const deadline = Date.now() + 5_000;
3395
+ while (updates.filter((update) => update.text === "(running...)").length < 2 && Date.now() < deadline) {
3396
+ await new Promise((resolve) => setTimeout(resolve, 50));
3397
+ }
3398
+ fs.writeFileSync(releasePath, "release", "utf-8");
3399
+ const result = await runPromise;
3400
+
3401
+ const runningUpdates = updates.filter((update) => update.text === "(running...)");
3402
+ assert.equal(result.exitCode, 0);
3403
+ assert.ok(runningUpdates.length >= 2, "expected an initial update and a heartbeat before child output");
3404
+ assert.ok((runningUpdates.at(-1)?.durationMs ?? 0) > (runningUpdates[0]?.durationMs ?? 0), "expected heartbeat duration to advance");
3405
+ });
3406
+
2490
3407
  it("tracks live activity updates and exposes artifact paths while running", async () => {
2491
3408
  const updates: Array<{ details?: { results?: Array<{ artifactPaths?: ArtifactPaths }>; progress?: ProgressSummary[] } }> = [];
2492
3409
  mockPi.onCall({
@@ -2563,9 +3480,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2563
3480
  events.assistantMessage("Found 2 files: file1.txt and file2.txt"),
2564
3481
  ],
2565
3482
  });
2566
- const agents = makeAgentConfigs(["explorer"]);
3483
+ const agents = makeAgentConfigs(["scout"]);
2567
3484
 
2568
- const result = await runSync(tempDir, agents, "explorer", "List files", {});
3485
+ const result = await runSync(tempDir, agents, "scout", "List files", {});
2569
3486
 
2570
3487
  assert.equal(result.exitCode, 0);
2571
3488
  const output = getFinalOutput(result.messages);
@@ -2592,13 +3509,13 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2592
3509
 
2593
3510
  it("injects an agent-file-relative local skill into the foreground child prompt", async () => {
2594
3511
  mockPi.onCall({ output: "Done" });
2595
- const agentFile = path.join(tempDir, "agents", "nested", "builder.md");
3512
+ const agentFile = path.join(tempDir, "agents", "nested", "worker.md");
2596
3513
  const skillFile = path.join(path.dirname(agentFile), "skills", "local", "SKILL.md");
2597
3514
  fs.mkdirSync(path.dirname(skillFile), { recursive: true });
2598
3515
  fs.writeFileSync(skillFile, "---\ndescription: local skill description\n---\nLocal skill body\n", "utf-8");
2599
- const agents = [makeAgent("builder", { filePath: agentFile, skills: ["local"], skillPath: ["./skills"] })];
3516
+ const agents = [makeAgent("worker", { filePath: agentFile, skills: ["local"], skillPath: ["./skills"] })];
2600
3517
 
2601
- const result = await runSync(tempDir, agents, "builder", "Task", {});
3518
+ const result = await runSync(tempDir, agents, "worker", "Task", {});
2602
3519
 
2603
3520
  assert.equal(result.exitCode, 0);
2604
3521
  assert.deepEqual(result.skills, ["local"]);
@@ -2622,9 +3539,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2622
3539
  });
2623
3540
 
2624
3541
  it("fails foreground runs on explicit unavailable pi-subagents skill requests without spawning", async () => {
2625
- const agents = [makeAgent("builder")];
3542
+ const agents = [makeAgent("worker")];
2626
3543
 
2627
- const result = await runSync(tempDir, agents, "builder", "Task", { skills: ["pi-subagents"] });
3544
+ const result = await runSync(tempDir, agents, "worker", "Task", { skills: ["pi-subagents"] });
2628
3545
 
2629
3546
  assert.equal(result.exitCode, 1);
2630
3547
  assert.equal(result.error, "Skills not found: pi-subagents");
@@ -2632,16 +3549,16 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2632
3549
  });
2633
3550
 
2634
3551
  it("fails foreground runs when an agent default requests pi-subagents skill", async () => {
2635
- const agents = [makeAgent("builder", { skills: ["pi-subagents"] })];
3552
+ const agents = [makeAgent("worker", { skills: ["pi-subagents"] })];
2636
3553
 
2637
- const result = await runSync(tempDir, agents, "builder", "Task", {});
3554
+ const result = await runSync(tempDir, agents, "worker", "Task", {});
2638
3555
 
2639
3556
  assert.equal(result.exitCode, 1);
2640
3557
  assert.equal(result.error, "Skills not found: pi-subagents");
2641
3558
  assert.equal(mockPi.callCount(), 0);
2642
3559
  });
2643
3560
 
2644
- it("writes artifacts when configured", async () => {
3561
+ it("writes artifacts without retaining the effective prompt", async () => {
2645
3562
  mockPi.onCall({
2646
3563
  output: "Result text",
2647
3564
  runtimeAcknowledgedExtensions: { version: 1, source: "child-runtime", ids: ["ext.ok"], omitted: 0 },
@@ -2649,8 +3566,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2649
3566
  const privateExtension = path.join(tempDir, "extensions", "private-extension.ts");
2650
3567
  const agents = [makeAgent("echo", { extensions: [privateExtension] })];
2651
3568
  const artifactsDir = path.join(tempDir, "artifacts");
3569
+ const sentinel = "PROMPT_AUDIT_SENTINEL_1021";
2652
3570
 
2653
- const result = await runSync(tempDir, agents, "echo", "Task", {
3571
+ const result = await runSync(tempDir, agents, "echo", sentinel, {
2654
3572
  runId: "test-run",
2655
3573
  artifactsDir,
2656
3574
  artifactConfig: { enabled: true, includeInput: true, includeOutput: true, includeMetadata: true },
@@ -2658,16 +3576,27 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2658
3576
 
2659
3577
  assert.equal(result.exitCode, 0);
2660
3578
  assert.ok(result.artifactPaths, "should have artifact paths");
3579
+ assert.ok(result.artifactPaths.inputPath, "should have a redacted input artifact");
3580
+ assert.doesNotMatch(fs.readFileSync(result.artifactPaths.inputPath, "utf-8"), new RegExp(sentinel));
3581
+ assert.match(fs.readFileSync(result.artifactPaths.inputPath, "utf-8"), /live Prompt Audit only/);
2661
3582
  assert.ok(result.transcriptPath, "should expose transcript path on the result");
2662
3583
  assert.equal(result.transcriptPath, result.artifactPaths.transcriptPath);
2663
3584
  assert.ok(fs.existsSync(result.transcriptPath), "transcript should be written");
2664
3585
  const transcript = fs.readFileSync(result.transcriptPath, "utf-8").trim().split("\n").map((line) => JSON.parse(line) as { recordType?: string; source?: string; text?: string });
2665
3586
  assert.equal(transcript[0]?.recordType, "message");
2666
3587
  assert.equal(transcript[0]?.source, "foreground");
3588
+ assert.match(transcript[0]?.text ?? "", /live Prompt Audit only/);
3589
+ assert.doesNotMatch(fs.readFileSync(result.transcriptPath, "utf-8"), new RegExp(sentinel));
2667
3590
  assert.match(transcript.at(-1)?.text ?? "", /^Result text/);
2668
3591
  assert.equal(result.transcriptError, undefined);
2669
3592
  assert.ok(fs.existsSync(artifactsDir), "artifacts dir should exist");
2670
- const metadata = JSON.parse(fs.readFileSync(result.artifactPaths.metadataPath, "utf-8")) as { launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions };
3593
+ const metadataText = fs.readFileSync(result.artifactPaths.metadataPath, "utf-8");
3594
+ const metadata = JSON.parse(metadataText) as { task?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions };
3595
+ assert.doesNotMatch(metadataText, new RegExp(sentinel));
3596
+ assert.equal(metadata.task, "[prompt redacted]");
3597
+ assert.equal(result.task, "[prompt redacted]");
3598
+ assert.equal(result.progress.task, "[prompt redacted]");
3599
+ assert.match(readCallArgs().join("\n"), new RegExp(sentinel));
2671
3600
  assert.equal(metadata.launchContractDigest, result.launchContractDigest);
2672
3601
  assert.equal(result.launchResolvedExtensions?.source, "launch-resolved");
2673
3602
  assert.equal(result.launchResolvedExtensions?.disableAmbientExtensions, true);
@@ -2806,6 +3735,28 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2806
3735
  assert.equal(fs.readFileSync(outputPath, "utf-8"), "fresh assistant output");
2807
3736
  });
2808
3737
 
3738
+ it("top-level reviewer runs do not inherit bundled chain artifact reads", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
3739
+ fs.writeFileSync(path.join(tempDir, "plan.md"), "chain plan");
3740
+ fs.writeFileSync(path.join(tempDir, "progress.md"), "chain progress");
3741
+ mockPi.onCall({ output: "Review done" });
3742
+ const reviewer = discoverAgents(tempDir, "project").agents.find((agent) => agent.name === "reviewer");
3743
+ assert.ok(reviewer, "expected bundled reviewer");
3744
+ assert.equal(reviewer.defaultReads, undefined);
3745
+ const executor = makeExecutor([reviewer]);
3746
+
3747
+ await executor.execute(
3748
+ "single-reviewer-without-chain-artifacts",
3749
+ { agent: "reviewer", task: "Review the supplied files." },
3750
+ new AbortController().signal,
3751
+ undefined,
3752
+ makeMinimalCtx(tempDir),
3753
+ );
3754
+
3755
+ const taskArg = readCallArgs().at(-1) ?? "";
3756
+ assert.doesNotMatch(taskArg, /\[Read from:/);
3757
+ assert.doesNotMatch(taskArg, /plan\.md|progress\.md/);
3758
+ });
3759
+
2809
3760
  it("routes foreground single relative outputs to the run output artifact directory by default", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2810
3761
  mockPi.onCall({ output: "default report" });
2811
3762
  const executor = makeExecutor([makeAgent("researcher", { output: "context.md" })]);
@@ -3034,6 +3985,81 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3034
3985
  assert.equal(agentResult.details?.timeoutMs, 4_000);
3035
3986
  });
3036
3987
 
3988
+ it("threads the global config timeout default from deps.config, without overriding explicit or agent values", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
3989
+ const NINETY_MIN = 90 * 60 * 1000;
3990
+ mockPi.onCall({ output: "config default" });
3991
+ mockPi.onCall({ output: "explicit over config" });
3992
+ mockPi.onCall({ output: "agent over config" });
3993
+ mockPi.onCall({ output: "invalid config ignored" });
3994
+
3995
+ // A global config.timeoutMs replaces the built-in 30-minute foreground backstop.
3996
+ const configExecutor = makeExecutor([makeAgent("echo")], { timeoutMs: NINETY_MIN });
3997
+ const configResult = await configExecutor.execute(
3998
+ "config-timeout-default",
3999
+ { agent: "echo", task: "Task", async: false },
4000
+ new AbortController().signal,
4001
+ undefined,
4002
+ makeMinimalCtx(tempDir),
4003
+ );
4004
+ assert.equal(configResult.details?.timeoutMs, NINETY_MIN);
4005
+
4006
+ // An explicit call value still wins over the global config default.
4007
+ const explicitResult = await configExecutor.execute(
4008
+ "config-timeout-explicit",
4009
+ { agent: "echo", task: "Task", async: false, timeoutMs: 2_000 },
4010
+ new AbortController().signal,
4011
+ undefined,
4012
+ makeMinimalCtx(tempDir),
4013
+ );
4014
+ assert.equal(explicitResult.details?.timeoutMs, 2_000);
4015
+
4016
+ // An agent frontmatter default still wins over the global config default (single launches).
4017
+ const agentResult = await makeExecutor([makeAgent("echo", { defaultTimeoutMs: 4_000 })], { timeoutMs: NINETY_MIN }).execute(
4018
+ "config-timeout-agent-default",
4019
+ { agent: "echo", task: "Task", async: false },
4020
+ new AbortController().signal,
4021
+ undefined,
4022
+ makeMinimalCtx(tempDir),
4023
+ );
4024
+ assert.equal(agentResult.details?.timeoutMs, 4_000);
4025
+
4026
+ // An invalid config value is ignored -> falls back to the built-in 30-minute default.
4027
+ const invalidResult = await makeExecutor([makeAgent("echo")], { timeoutMs: -1 }).execute(
4028
+ "config-timeout-invalid",
4029
+ { agent: "echo", task: "Task", async: false },
4030
+ new AbortController().signal,
4031
+ undefined,
4032
+ makeMinimalCtx(tempDir),
4033
+ );
4034
+ assert.equal(invalidResult.details?.timeoutMs, executorMod?.DEFAULT_FOREGROUND_TIMEOUT_MS);
4035
+ });
4036
+
4037
+ it("applies the global config timeout default to foreground workflow scripts", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4038
+ mockPi.onCall({ delay: 5_000, output: "too late" });
4039
+ mockPi.onCall({ delay: 5_000, output: "too late" });
4040
+ const executor = makeExecutor([makeAgent("echo")], { timeoutMs: 250 });
4041
+
4042
+ const configResult = await executor.execute(
4043
+ "workflow-config-timeout-default",
4044
+ { async: false, workflowScript: `return await runs.run("slow", { agent: "echo", task: "Wait" });` },
4045
+ new AbortController().signal,
4046
+ undefined,
4047
+ makeMinimalCtx(tempDir),
4048
+ );
4049
+ assert.equal(configResult.isError, true);
4050
+ assert.match(configResult.content[0]?.text ?? "", /Workflow script timed out after 250ms/);
4051
+
4052
+ const explicitResult = await executor.execute(
4053
+ "workflow-config-timeout-explicit",
4054
+ { async: false, timeoutMs: 150, workflowScript: `return await runs.run("slow", { agent: "echo", task: "Wait" });` },
4055
+ new AbortController().signal,
4056
+ undefined,
4057
+ makeMinimalCtx(tempDir),
4058
+ );
4059
+ assert.equal(explicitResult.isError, true);
4060
+ assert.match(explicitResult.content[0]?.text ?? "", /Workflow script timed out after 150ms/);
4061
+ });
4062
+
3037
4063
  it("runs omitted async launches in the background when the global default is enabled", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
3038
4064
  const executor = makeExecutor([makeAgent("echo")], {}, true);
3039
4065
 
@@ -3310,8 +4336,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3310
4336
  });
3311
4337
 
3312
4338
  mockPi.onCall({ echoEnv: envKeys });
3313
- const nonFanoutAgents = [makeAgent("builder", { tools: ["read"] })];
3314
- const nonFanout = await runSync(tempDir, nonFanoutAgents, "builder", "Task", { runId: "non-fanout-run" });
4339
+ const nonFanoutAgents = [makeAgent("worker", { tools: ["read"] })];
4340
+ const nonFanout = await runSync(tempDir, nonFanoutAgents, "worker", "Task", { runId: "non-fanout-run" });
3315
4341
  assert.equal(nonFanout.exitCode, 0);
3316
4342
  assert.deepEqual(JSON.parse(nonFanout.finalOutput ?? "{}"), {
3317
4343
  SELESAI_SUBAGENT_FANOUT_CHILD: "0",
@@ -3357,9 +4383,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3357
4383
 
3358
4384
  it("fails with actionable diagnostics when a requested extension tool is not loaded", async () => {
3359
4385
  mockPi.onCall({ output: "Model incorrectly claimed success", missingTools: ["fixture_search"] });
3360
- const agents = [makeAgent("extension-builder", { tools: ["read", "fixture_search"], fallbackModels: ["mock/fallback-model"] })];
4386
+ const agents = [makeAgent("extension-worker", { tools: ["read", "fixture_search"], fallbackModels: ["mock/fallback-model"] })];
3361
4387
 
3362
- const result = await runSync(tempDir, agents, "extension-builder", "Use fixture search", { runId: "missing-extension-tool" });
4388
+ const result = await runSync(tempDir, agents, "extension-worker", "Use fixture search", { runId: "missing-extension-tool" });
3363
4389
 
3364
4390
  assert.equal(result.exitCode, 1);
3365
4391
  assert.match(result.error ?? "", /requested unavailable child tools: fixture_search/);
@@ -3585,9 +4611,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3585
4611
  mockAssistantMessage("final wrapped output", "stop"),
3586
4612
  ],
3587
4613
  });
3588
- const agents = makeAgentConfigs(["builder"]);
4614
+ const agents = makeAgentConfigs(["worker"]);
3589
4615
 
3590
- const result = await runSync(tempDir, agents, "builder", "Use the final grace turn to wrap up.", {
4616
+ const result = await runSync(tempDir, agents, "worker", "Use the final grace turn to wrap up.", {
3591
4617
  turnBudget: { maxTurns: 1, graceTurns: 1 },
3592
4618
  runId: "foreground-turn-budget-soft",
3593
4619
  });
@@ -3620,7 +4646,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3620
4646
  },
3621
4647
  ],
3622
4648
  });
3623
- const agents = makeAgentConfigs(["builder"]);
4649
+ const agents = makeAgentConfigs(["worker"]);
3624
4650
  const snapshots: Array<{
3625
4651
  turnBudget?: { outcome?: string; terminationDeferredAtTurn?: number };
3626
4652
  turnBudgetExceeded?: boolean;
@@ -3629,7 +4655,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3629
4655
  status?: string;
3630
4656
  }> = [];
3631
4657
 
3632
- const result = await runSync(tempDir, agents, "builder", "Finish active tool work before enforcing the hard limit.", {
4658
+ const result = await runSync(tempDir, agents, "worker", "Finish active tool work before enforcing the hard limit.", {
3633
4659
  turnBudget: { maxTurns: 1, graceTurns: 0 },
3634
4660
  runId: "foreground-turn-budget-deferred",
3635
4661
  onUpdate(update: { details?: { results?: Array<{ turnBudget?: { outcome?: string; terminationDeferredAtTurn?: number }; turnBudgetExceeded?: boolean; error?: string }>; progress?: Array<{ currentTool?: string; status?: string }> } }) {