@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
@@ -25,6 +25,7 @@ import { registerSubagentCapabilityCeiling } from "../../src/api/capability-ceil
25
25
  import { resolveSubagentLaunchContract } from "../../src/api/preflight.ts";
26
26
  import { discoverAgents } from "../../src/agents/agents.ts";
27
27
  import { runSync } from "../../src/runs/foreground/execution.ts";
28
+ import { ACTIVE_ASYNC_CAPACITY_DIR, acquireActiveAsyncCapacity, activeAsyncCapacitySessionKey } from "../../src/runs/background/active-async-capacity.ts";
28
29
 
29
30
  interface LaunchResolvedExtensions {
30
31
  version?: number;
@@ -80,7 +81,7 @@ interface AsyncResultPayload {
80
81
  totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
81
82
  usageBudget?: UsageBudgetState;
82
83
  checkpoint?: { name?: string; status?: string };
83
- results: Array<{ agent?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string }; outputSaveError?: string; metadataSaveError?: string; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
84
+ results: Array<{ agent?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string; transcriptPath?: string }; outputSaveError?: string; metadataSaveError?: string; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
84
85
  outputs?: Record<string, { text?: string; structured?: unknown }>;
85
86
  workflowGraph?: { nodes?: Array<{ kind?: string; label?: string; phase?: string; status?: string; acceptanceStatus?: string; error?: string; outputName?: string; structured?: boolean; children?: Array<{ label?: string; outputName?: string; itemKey?: string; status?: string; acceptanceStatus?: string; error?: string }> }> };
86
87
  parallelHandoff?: { version?: number; path?: string; groupCount?: number; childCount?: number; changedPatches?: number; cleanupState?: string };
@@ -96,6 +97,7 @@ interface AsyncStatusPayload {
96
97
  currentTool?: string;
97
98
  currentPath?: string;
98
99
  state?: string;
100
+ endedAt?: number;
99
101
  launchContractDigest?: string;
100
102
  launchResolvedExtensions?: LaunchResolvedExtensions;
101
103
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions;
@@ -191,7 +193,7 @@ function childWatchdogStatus(runId: string, phase: "idle" | "reviewing" | "autof
191
193
  return {
192
194
  type: CHILD_WATCHDOG_STATUS_EVENT,
193
195
  runId,
194
- agent: "builder",
196
+ agent: "worker",
195
197
  childIndex: 0,
196
198
  stepIndex: 0,
197
199
  seq,
@@ -230,6 +232,7 @@ interface AsyncExecutionModule {
230
232
 
231
233
  interface UtilsModule {
232
234
  readStatus(dir: string): { runId: string; state: string; mode: string } | null;
235
+ pruneStatusCacheForAsyncRoot(root: string, runIds: Iterable<string>): number;
233
236
  }
234
237
 
235
238
  interface TypesModule {
@@ -254,6 +257,7 @@ const isAsyncAvailable = asyncMod?.isAsyncAvailable;
254
257
  const executeAsyncSingle = asyncMod?.executeAsyncSingle;
255
258
  const executeAsyncChain = asyncMod?.executeAsyncChain;
256
259
  const readStatus = utils?.readStatus;
260
+ const pruneStatusCacheForAsyncRoot = utils?.pruneStatusCacheForAsyncRoot;
257
261
  const ASYNC_DIR = typesMod?.ASYNC_DIR;
258
262
  const RESULTS_DIR = typesMod?.RESULTS_DIR;
259
263
  const TEMP_ROOT_DIR = typesMod?.TEMP_ROOT_DIR;
@@ -455,9 +459,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
455
459
 
456
460
  function launchProtocolTest(id: string): void {
457
461
  executeAsyncSingle(id, {
458
- agent: "builder",
462
+ agent: "worker",
459
463
  task: "Exercise child protocol",
460
- agentConfig: makeAgent("builder", { completionGuard: false }),
464
+ agentConfig: makeAgent("worker", { completionGuard: false }),
461
465
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
462
466
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
463
467
  shareEnabled: false,
@@ -483,6 +487,16 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
483
487
  assert.equal(payload.results[0]?.output, "你好 from fragmented async JSON");
484
488
  });
485
489
 
490
+ it("persists terminal status before creating the result artifact", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
491
+ mockPi.onCall({ output: "completed output" });
492
+ const id = `async-terminal-status-${Date.now().toString(36)}`;
493
+ launchProtocolTest(id);
494
+ await waitForAsyncResultFile(id);
495
+ const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
496
+ assert.equal(status.state, "complete");
497
+ assert.equal(status.endedAt !== undefined, true);
498
+ });
499
+
486
500
  it("persists absent output provenance when async lifecycle text is synthetic", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
487
501
  mockPi.onCall({ jsonl: [mockAssistantMessage("", "tool_use")], stderr: "mock child failure", exitCode: 1 });
488
502
  const id = `async-output-absent-${Date.now().toString(36)}`;
@@ -504,12 +518,12 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
504
518
  });
505
519
 
506
520
  it("matches preflight launch digest in equivalent foreground and async execution", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
507
- const agentName = `contract-builder-${Date.now().toString(36)}`;
521
+ const agentName = `contract-worker-${Date.now().toString(36)}`;
508
522
  const task = "Compare the resolved launch inputs.";
509
523
  const turnBudget = { maxTurns: 2, graceTurns: 1 } as const;
510
524
  const agentPath = path.join(tempDir, ".selesai", "agents", `${agentName}.md`);
511
525
  fs.mkdirSync(path.dirname(agentPath), { recursive: true });
512
- fs.writeFileSync(agentPath, `---\nname: ${agentName}\ndescription: Contract comparison builder\n---\n`, "utf-8");
526
+ fs.writeFileSync(agentPath, `---\nname: ${agentName}\ndescription: Contract comparison worker\n---\n`, "utf-8");
513
527
  const discovered = discoverAgents(tempDir).agents.find((agent) => agent.name === agentName);
514
528
  assert.ok(discovered, "expected temporary agent definition to be discovered");
515
529
  const preflight = await resolveSubagentLaunchContract({ agent: agentName, cwd: tempDir, task, turnBudget, runId: "contract-preflight" });
@@ -548,9 +562,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
548
562
  const id = `async-launch-digest-${Date.now().toString(36)}`;
549
563
  const privateExtension = path.join(tempDir, "extensions", "private-extension.ts");
550
564
  const launch = executeAsyncSingle(id, {
551
- agent: "builder",
565
+ agent: "worker",
552
566
  task: "Exercise launch digest reporting",
553
- agentConfig: makeAgent("builder", { completionGuard: false, extensions: [privateExtension] }),
567
+ agentConfig: makeAgent("worker", { completionGuard: false, extensions: [privateExtension] }),
554
568
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
555
569
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
556
570
  shareEnabled: false,
@@ -559,8 +573,10 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
559
573
  acceptance: false,
560
574
  });
561
575
  assert.match(launch.details.launchContractDigest ?? "", /^[a-f0-9]{64}$/);
576
+ const recovery = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "recovery-descriptor.json"), "utf-8")) as { runFanoutBudget?: { rootRunId?: string; limit?: number } };
577
+ assert.deepEqual(recovery.runFanoutBudget && { rootRunId: recovery.runFanoutBudget.rootRunId, limit: recovery.runFanoutBudget.limit }, { rootRunId: id, limit: 64 });
562
578
  const payload = await readAsyncPayload(id);
563
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
579
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete" && candidate.runtimeAcknowledgedExtensions !== undefined);
564
580
  assert.equal(payload.launchContractDigest, launch.details.launchContractDigest);
565
581
  assert.equal(payload.results[0]?.launchContractDigest, launch.details.launchContractDigest);
566
582
  assert.equal(status.launchContractDigest, launch.details.launchContractDigest);
@@ -612,7 +628,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
612
628
 
613
629
  it("foreground checkpoint decisions use current-session control actions", { skip: !createSubagentExecutor ? "executor not available" : undefined }, async () => {
614
630
  mockPi.onCall({ output: "analysis done" });
615
- const executor = makeAsyncExecutor([makeAgent("analyst"), makeAgent("builder")]);
631
+ const executor = makeAsyncExecutor([makeAgent("analyst"), makeAgent("worker")]);
616
632
  const ctx = makeMinimalCtx(tempDir);
617
633
  ctx.sessionManager.getSessionId = () => "session-foreground-checkpoint";
618
634
  const id = `foreground-checkpoint-${Date.now().toString(36)}`;
@@ -621,7 +637,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
621
637
  chain: [
622
638
  { agent: "analyst", task: "Analyze" },
623
639
  { checkpoint: "review", message: "Approve implementation?" },
624
- { agent: "builder", task: "Implement" },
640
+ { agent: "worker", task: "Implement" },
625
641
  ],
626
642
  }, new AbortController().signal, undefined, ctx) as AsyncExecutionResult;
627
643
 
@@ -646,9 +662,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
646
662
  chain: [
647
663
  { agent: "analyst", task: "Analyze" },
648
664
  { checkpoint: "review" },
649
- { agent: "builder", task: "Implement" },
665
+ { agent: "worker", task: "Implement" },
650
666
  ],
651
- agents: [makeAgent("analyst"), makeAgent("builder")],
667
+ agents: [makeAgent("analyst"), makeAgent("worker")],
652
668
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
653
669
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
654
670
  shareEnabled: false,
@@ -717,11 +733,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
717
733
  const sessionFile = path.join(tempDir, "sessions", "parent-session", "session.jsonl");
718
734
  const ctx = makeMinimalCtx(tempDir);
719
735
  ctx.sessionManager.getSessionFile = () => sessionFile;
720
- const executor = makeAsyncExecutor([makeAgent("builder", { completionGuard: false })], { artifactDir: "session" });
736
+ const executor = makeAsyncExecutor([makeAgent("worker", { completionGuard: false })], { artifactDir: "session" });
721
737
 
722
738
  const launch = await executor.execute(
723
739
  "async-session-artifact-dir",
724
- { agent: "builder", task: "Write async session artifacts", async: true, runId: "async-session-artifacts", acceptance: false },
740
+ { agent: "worker", task: "Write async session artifacts", async: true, runId: "async-session-artifacts", acceptance: false },
725
741
  new AbortController().signal,
726
742
  undefined,
727
743
  ctx,
@@ -746,13 +762,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
746
762
  const sessionId = `session-capability-${Date.now().toString(36)}`;
747
763
  const handle = registerSubagentCapabilityCeiling({ sessionId, ceiling: { allowedTools: ["read"], denyExtensions: true }, source: "test" });
748
764
  try {
749
- const executor = makeAsyncExecutor([makeAgent("builder", { tools: ["read", "write"], completionGuard: false })]);
765
+ const executor = makeAsyncExecutor([makeAgent("worker", { tools: ["read", "write"], completionGuard: false })]);
750
766
  const id = `async-capability-${Date.now().toString(36)}`;
751
767
  const ctx = makeMinimalCtx(tempDir);
752
768
  ctx.sessionManager.getSessionId = () => sessionId;
753
769
  const launch = await executor.execute(
754
770
  id,
755
- { agent: "builder", task: "Run with a restricted capability ceiling", async: true, runId: id, acceptance: false, artifacts: true },
771
+ { agent: "worker", task: "Run with a restricted capability ceiling", async: true, runId: id, acceptance: false, artifacts: true },
756
772
  new AbortController().signal,
757
773
  undefined,
758
774
  ctx,
@@ -783,13 +799,51 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
783
799
  }
784
800
  });
785
801
 
802
+ it("redacts async task text from durable artifacts, transcripts, and metadata", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
803
+ mockPi.onCall({ output: "async redaction complete" });
804
+ const id = `async-prompt-redaction-${Date.now().toString(36)}`;
805
+ const sentinel = "ASYNC_RAW_PROMPT_SENTINEL_1021";
806
+ executeAsyncSingle(id, {
807
+ agent: "worker",
808
+ task: `Handle ${sentinel} without persisting it`,
809
+ agentConfig: makeAgent("worker", { completionGuard: false }),
810
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
811
+ artifactConfig: { enabled: true, includeInput: true, includeOutput: true, includeJsonl: false, includeTranscript: true, includeMetadata: true, cleanupDays: 7 },
812
+ artifactsDir: path.join(tempDir, ".pi-subagents", "artifacts"),
813
+ shareEnabled: false,
814
+ maxSubagentDepth: 2,
815
+ acceptance: false,
816
+ });
817
+
818
+ await waitForMockPiCall(mockPi, 0);
819
+ const callFile = fs.readdirSync(mockPi.dir).find((name) => name.endsWith(".json"));
820
+ assert.ok(callFile);
821
+ const call = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as { args?: string[] };
822
+ assert.match(call.args?.join("\n") ?? "", new RegExp(sentinel));
823
+
824
+ const payload = await readAsyncPayload(id);
825
+ const artifactPaths = payload.results[0]?.artifactPaths;
826
+ assert.ok(artifactPaths?.inputPath);
827
+ assert.ok(artifactPaths.metadataPath);
828
+ assert.ok(artifactPaths.transcriptPath);
829
+ const inputText = fs.readFileSync(artifactPaths.inputPath, "utf-8");
830
+ const transcriptText = fs.readFileSync(artifactPaths.transcriptPath, "utf-8");
831
+ const metadataText = fs.readFileSync(artifactPaths.metadataPath, "utf-8");
832
+ assert.doesNotMatch(inputText, new RegExp(sentinel));
833
+ assert.doesNotMatch(transcriptText, new RegExp(sentinel));
834
+ assert.doesNotMatch(metadataText, new RegExp(sentinel));
835
+ assert.match(inputText, /\[prompt redacted\]/);
836
+ assert.match(transcriptText, /\[prompt redacted\]/);
837
+ assert.equal((JSON.parse(metadataText) as { task?: string }).task, "[prompt redacted]");
838
+ });
839
+
786
840
  it("background writes a failure stub to output artifacts when no output was produced", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
787
841
  mockPi.onCall({ output: "", stderr: "model unavailable", exitCode: 1 });
788
842
  const id = `async-empty-failure-artifact-${Date.now().toString(36)}`;
789
843
  executeAsyncSingle(id, {
790
- agent: "builder",
844
+ agent: "worker",
791
845
  task: "Fail before output",
792
- agentConfig: makeAgent("builder", { completionGuard: false }),
846
+ agentConfig: makeAgent("worker", { completionGuard: false }),
793
847
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
794
848
  artifactConfig: { enabled: true, includeInput: true, includeOutput: true, includeJsonl: false, includeMetadata: true, cleanupDays: 7 },
795
849
  artifactsDir: path.join(tempDir, ".pi-subagents", "artifacts"),
@@ -814,9 +868,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
814
868
  const id = `async-artifact-dir-recovery-${Date.now().toString(36)}`;
815
869
  const artifactsDir = path.join(tempDir, ".pi-subagents", "artifacts");
816
870
  executeAsyncSingle(id, {
817
- agent: "builder",
871
+ agent: "worker",
818
872
  task: "Complete after generated artifacts are cleaned",
819
- agentConfig: makeAgent("builder", { completionGuard: false }),
873
+ agentConfig: makeAgent("worker", { completionGuard: false }),
820
874
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
821
875
  artifactConfig: { enabled: true, includeInput: true, includeOutput: true, includeJsonl: false, includeMetadata: true, cleanupDays: 7 },
822
876
  artifactsDir,
@@ -899,9 +953,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
899
953
  mockPi.onCall({ output: "OK" });
900
954
  const id = `async-single-literal-output-ref-${Date.now().toString(36)}`;
901
955
  const result = executeAsyncSingle(id, {
902
- agent: "builder",
956
+ agent: "worker",
903
957
  task,
904
- agentConfig: makeAgent("builder"),
958
+ agentConfig: makeAgent("worker"),
905
959
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
906
960
  artifactConfig: {
907
961
  enabled: false,
@@ -931,9 +985,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
931
985
  mockPi.onCall({ output: "non-node exec async done" });
932
986
  const id = `async-non-node-exec-${Date.now().toString(36)}`;
933
987
  const result = executeAsyncSingle(id, {
934
- agent: "builder",
988
+ agent: "worker",
935
989
  task: "Say non-node exec async done. Do not edit files.",
936
- agentConfig: makeAgent("builder"),
990
+ agentConfig: makeAgent("worker"),
937
991
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
938
992
  artifactConfig: {
939
993
  enabled: false,
@@ -965,9 +1019,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
965
1019
  mockPi.onCall({ output: "stale node exec async done" });
966
1020
  const id = `async-stale-node-exec-${Date.now().toString(36)}`;
967
1021
  const result = executeAsyncSingle(id, {
968
- agent: "builder",
1022
+ agent: "worker",
969
1023
  task: "Say stale node exec async done. Do not edit files.",
970
- agentConfig: makeAgent("builder"),
1024
+ agentConfig: makeAgent("worker"),
971
1025
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
972
1026
  artifactConfig: {
973
1027
  enabled: false,
@@ -1095,7 +1149,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1095
1149
  await waitForMockPiCall(mockPi, 1, 10_000);
1096
1150
  const resultPath = await waitForAsyncResultFile(id, 8_000);
1097
1151
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1098
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
1152
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
1099
1153
  assert.equal(payload.state, "failed");
1100
1154
  assert.equal(payload.success, false);
1101
1155
  assert.equal(payload.exitCode, 1);
@@ -1113,6 +1167,69 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1113
1167
  assert.equal(mockPi.callCount(), 2);
1114
1168
  });
1115
1169
 
1170
+ it("enforces an agent-level timeout on an async serial child without a composite deadline", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
1171
+ mockPi.onCall({ delay: 5_000, output: "too late" });
1172
+ const id = `async-child-timeout-chain-${Date.now().toString(36)}`;
1173
+ executeAsyncChain(id, {
1174
+ chain: [{ agent: "slow", task: "Wait" }],
1175
+ agents: [makeAgent("slow", { defaultTimeoutMs: 150 })],
1176
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1177
+ artifactConfig: {
1178
+ enabled: false,
1179
+ includeInput: false,
1180
+ includeOutput: false,
1181
+ includeJsonl: false,
1182
+ includeMetadata: false,
1183
+ cleanupDays: 7,
1184
+ },
1185
+ shareEnabled: false,
1186
+ maxSubagentDepth: 2,
1187
+ });
1188
+
1189
+ const payload = await readAsyncPayload(id);
1190
+ assert.equal(payload.timeoutMs, undefined, "composite parent must remain unbounded by default");
1191
+ assert.equal(payload.state, "failed");
1192
+ assert.equal(payload.results[0]?.timedOut, true);
1193
+ assert.equal(payload.results[0]?.error, "Subagent timed out after 150ms.");
1194
+ });
1195
+
1196
+ it("enforces child timeouts on async parallel tasks without a composite deadline", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
1197
+ mockPi.onCall({ delay: 5_000, output: "one too late" });
1198
+ mockPi.onCall({ delay: 5_000, output: "two too late" });
1199
+ const id = `async-child-timeout-parallel-${Date.now().toString(36)}`;
1200
+ executeAsyncChain(id, {
1201
+ chain: [{
1202
+ parallel: [
1203
+ { agent: "slow-one", task: "Wait" },
1204
+ { agent: "slow-two", task: "Wait" },
1205
+ ],
1206
+ concurrency: 2,
1207
+ }],
1208
+ resultMode: "parallel",
1209
+ agents: [
1210
+ makeAgent("slow-one", { defaultTimeoutMs: 150 }),
1211
+ makeAgent("slow-two", { defaultTimeoutMs: 200 }),
1212
+ ],
1213
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1214
+ artifactConfig: {
1215
+ enabled: false,
1216
+ includeInput: false,
1217
+ includeOutput: false,
1218
+ includeJsonl: false,
1219
+ includeMetadata: false,
1220
+ cleanupDays: 7,
1221
+ },
1222
+ shareEnabled: false,
1223
+ maxSubagentDepth: 2,
1224
+ });
1225
+
1226
+ const payload = await readAsyncPayload(id);
1227
+ assert.equal(payload.timeoutMs, undefined, "composite parent must remain unbounded by default");
1228
+ assert.equal(payload.state, "failed");
1229
+ assert.deepEqual(payload.results.map((result) => result.timedOut), [true, true]);
1230
+ assert.deepEqual(payload.results.map((result) => result.error), ["Subagent timed out after 150ms.", "Subagent timed out after 200ms."]);
1231
+ });
1232
+
1116
1233
  it("hard-kills async children that ignore timeout SIGTERM", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
1117
1234
  mockPi.onCall({ delay: 60_000, ignoreSigterm: true, output: "too late" });
1118
1235
  const id = `async-timeout-hard-kill-${Date.now().toString(36)}`;
@@ -1140,7 +1257,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1140
1257
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1141
1258
  const elapsedMs = Date.now() - startedAt;
1142
1259
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1143
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
1260
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
1144
1261
  assert.equal(payload.state, "failed");
1145
1262
  assert.equal(payload.timedOut, true);
1146
1263
  assert.equal(payload.results[0]?.timedOut, true);
@@ -1151,15 +1268,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1151
1268
  assert.equal(mockPi.callCount(), 1);
1152
1269
  });
1153
1270
 
1154
- it("cancels async acceptance verification when the run times out", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
1271
+ it("cancels async acceptance verification when the run times out", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
1155
1272
  mockPi.onCall({ output: "implementation complete" });
1156
1273
  const id = `async-timeout-acceptance-${Date.now().toString(36)}`;
1157
1274
  const timeoutMs = 1_000;
1158
1275
  const startedAt = Date.now();
1159
1276
  executeAsyncSingle(id, {
1160
- agent: "builder",
1277
+ agent: "worker",
1161
1278
  task: "Implement with verified acceptance",
1162
- agentConfig: makeAgent("builder"),
1279
+ agentConfig: makeAgent("worker"),
1163
1280
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1164
1281
  artifactConfig: {
1165
1282
  enabled: true,
@@ -1182,7 +1299,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1182
1299
  const resultPath = await waitForAsyncResultFile(id, 5_000);
1183
1300
  const elapsedMs = Date.now() - startedAt;
1184
1301
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1185
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
1302
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
1186
1303
  assert.equal(payload.state, "failed");
1187
1304
  assert.equal(payload.timedOut, true);
1188
1305
  assert.equal(payload.results[0]?.timedOut, true);
@@ -1206,9 +1323,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1206
1323
  });
1207
1324
  const id = `async-turn-budget-soft-${Date.now().toString(36)}`;
1208
1325
  executeAsyncSingle(id, {
1209
- agent: "builder",
1326
+ agent: "worker",
1210
1327
  task: "Use the final grace turn to wrap up.",
1211
- agentConfig: makeAgent("builder"),
1328
+ agentConfig: makeAgent("worker"),
1212
1329
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1213
1330
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1214
1331
  shareEnabled: false,
@@ -1218,7 +1335,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1218
1335
 
1219
1336
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1220
1337
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1221
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
1338
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
1222
1339
  assert.equal(payload.success, true);
1223
1340
  assert.equal(payload.state, "complete");
1224
1341
  assert.equal(payload.turnBudgetExceeded, undefined);
@@ -1245,9 +1362,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1245
1362
  });
1246
1363
  const id = `async-turn-budget-hard-${Date.now().toString(36)}`;
1247
1364
  executeAsyncSingle(id, {
1248
- agent: "builder",
1365
+ agent: "worker",
1249
1366
  task: "Exceed the turn budget.",
1250
- agentConfig: makeAgent("builder"),
1367
+ agentConfig: makeAgent("worker"),
1251
1368
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1252
1369
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1253
1370
  shareEnabled: false,
@@ -1257,7 +1374,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1257
1374
 
1258
1375
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1259
1376
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1260
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
1377
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
1261
1378
  assert.equal(payload.success, true);
1262
1379
  assert.equal(payload.state, "complete");
1263
1380
  assert.equal(payload.exitCode, 0);
@@ -1295,9 +1412,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1295
1412
  ],
1296
1413
  });
1297
1414
  executeAsyncSingle(id, {
1298
- agent: "builder",
1415
+ agent: "worker",
1299
1416
  task: "Finish active tool work before enforcing the hard limit.",
1300
- agentConfig: makeAgent("builder"),
1417
+ agentConfig: makeAgent("worker"),
1301
1418
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1302
1419
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1303
1420
  shareEnabled: false,
@@ -1319,7 +1436,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1319
1436
 
1320
1437
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1321
1438
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1322
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
1439
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
1323
1440
  assert.equal(payload.state, "complete");
1324
1441
  assert.equal(payload.turnBudgetExceeded, undefined);
1325
1442
  assert.equal(payload.turnBudget?.outcome, "wrap-up-requested");
@@ -1345,9 +1462,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1345
1462
  });
1346
1463
  const id = `async-turn-budget-timeout-${Date.now().toString(36)}`;
1347
1464
  executeAsyncSingle(id, {
1348
- agent: "builder",
1465
+ agent: "worker",
1349
1466
  task: "Timeout while hard termination is deferred.",
1350
- agentConfig: makeAgent("builder"),
1467
+ agentConfig: makeAgent("worker"),
1351
1468
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1352
1469
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1353
1470
  shareEnabled: false,
@@ -1361,7 +1478,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1361
1478
  deliverTimeoutRequest({ asyncDir, pid: pending.pid, source: "test" });
1362
1479
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1363
1480
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1364
- const status = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
1481
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
1365
1482
  assert.equal(payload.state, "failed");
1366
1483
  assert.equal(payload.timedOut, true);
1367
1484
  assert.equal(payload.turnBudgetExceeded, undefined);
@@ -1387,9 +1504,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1387
1504
  });
1388
1505
  const id = `async-turn-budget-stop-${Date.now().toString(36)}`;
1389
1506
  executeAsyncSingle(id, {
1390
- agent: "builder",
1507
+ agent: "worker",
1391
1508
  task: "Stop while hard termination is deferred.",
1392
- agentConfig: makeAgent("builder"),
1509
+ agentConfig: makeAgent("worker"),
1393
1510
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1394
1511
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1395
1512
  shareEnabled: false,
@@ -1402,7 +1519,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1402
1519
  deliverStopRequest({ asyncDir, pid: pending.pid, source: "test" });
1403
1520
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1404
1521
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1405
- const status = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
1522
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "stopped");
1406
1523
  assert.equal(payload.state, "stopped");
1407
1524
  assert.equal(payload.stopped, true);
1408
1525
  assert.equal(payload.turnBudgetExceeded, undefined);
@@ -1445,26 +1562,26 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1445
1562
  const wrappedTask = `Fork preamble: ${"execution ".repeat(20)}`;
1446
1563
  const rawGoal = `Caller-facing goal: ${"raw ".repeat(40)}`;
1447
1564
  const singleResult = executeAsyncSingle(singleId, {
1448
- agent: "builder",
1565
+ agent: "worker",
1449
1566
  task: wrappedTask,
1450
1567
  goal: rawGoal,
1451
- agentConfig: makeAgent("builder"),
1568
+ agentConfig: makeAgent("worker"),
1452
1569
  ...commonParams,
1453
1570
  });
1454
- assert.match(singleResult.content[0]?.text ?? "", /Async: builder \[/);
1571
+ assert.match(singleResult.content[0]?.text ?? "", /Async: worker \[/);
1455
1572
  assert.match(singleResult.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
1456
1573
  assert.match(singleResult.content[0]?.text ?? "", /call subagent_wait\(\)/i);
1457
1574
  assert.match(singleResult.content[0]?.text ?? "", /non-interactive run: Pi auto-drains current-session background work at agent_end/);
1458
- assert.equal(startedEvent(singleId).task, wrappedTask.slice(0, 50));
1459
- assert.equal(startedEvent(singleId).goal, rawGoal.slice(0, 120));
1575
+ assert.equal(startedEvent(singleId).task, "[prompt redacted]");
1576
+ assert.equal(startedEvent(singleId).goal, "[prompt redacted]");
1460
1577
  await waitForAsyncResultFile(singleId, 30_000);
1461
1578
 
1462
1579
  mockPi.onCall({ output: "interactive done" });
1463
1580
  const interactiveId = `async-handoff-interactive-${Date.now().toString(36)}`;
1464
1581
  const interactiveResult = executeAsyncSingle(interactiveId, {
1465
- agent: "builder",
1582
+ agent: "worker",
1466
1583
  task: "Interactive handoff",
1467
- agentConfig: makeAgent("builder"),
1584
+ agentConfig: makeAgent("worker"),
1468
1585
  ...commonParams,
1469
1586
  ctx: { ...commonParams.ctx, interactive: true },
1470
1587
  });
@@ -1478,19 +1595,19 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1478
1595
  mockPi.onCall({ output: "parallel two done" });
1479
1596
  const parallelId = `async-handoff-parallel-${Date.now().toString(36)}`;
1480
1597
  const parallelResult = executeAsyncChain(parallelId, {
1481
- chain: [{ parallel: [{ agent: "builder", task: "Do one" }, { agent: "commentator", task: "Do two" }] }],
1598
+ chain: [{ parallel: [{ agent: "worker", task: "Do one" }, { agent: "reviewer", task: "Do two" }] }],
1482
1599
  resultMode: "parallel",
1483
- agents: [makeAgent("builder"), makeAgent("commentator")],
1600
+ agents: [makeAgent("worker"), makeAgent("reviewer")],
1484
1601
  ...commonParams,
1485
1602
  });
1486
1603
  assert.match(parallelResult.content[0]?.text ?? "", /Async parallel:/);
1487
1604
  assert.match(parallelResult.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
1488
1605
  assert.match(parallelResult.content[0]?.text ?? "", /call subagent_wait\(\)/i);
1489
- assert.equal(startedEvent(parallelId).goal, "Do one");
1606
+ assert.equal(startedEvent(parallelId).goal, "[prompt redacted]");
1490
1607
  const parallelResultPath = await waitForAsyncResultFile(parallelId, 10_000);
1491
1608
  const parallelPayload = JSON.parse(fs.readFileSync(parallelResultPath, "utf-8")) as { agent?: string; mode?: string };
1492
1609
  assert.equal(parallelPayload.mode, "parallel");
1493
- assert.equal(parallelPayload.agent, "parallel:builder+commentator");
1610
+ assert.equal(parallelPayload.agent, "parallel:worker+reviewer");
1494
1611
 
1495
1612
  mockPi.onCall({ output: "chain done" });
1496
1613
  const chainId = `async-handoff-chain-${Date.now().toString(36)}`;
@@ -1498,15 +1615,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1498
1615
  const chainChildTask = `Do chained work ${"child ".repeat(15)}`;
1499
1616
  const chainResult = executeAsyncChain(chainId, {
1500
1617
  task: chainGoal,
1501
- chain: [{ agent: "builder", task: chainChildTask }],
1502
- agents: [makeAgent("builder")],
1618
+ chain: [{ agent: "worker", task: chainChildTask }],
1619
+ agents: [makeAgent("worker")],
1503
1620
  ...commonParams,
1504
1621
  });
1505
1622
  assert.match(chainResult.content[0]?.text ?? "", /Async chain:/);
1506
1623
  assert.match(chainResult.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
1507
1624
  const chainEvent = startedEvent(chainId);
1508
- assert.equal(chainEvent.task, chainChildTask.slice(0, 50));
1509
- assert.equal(chainEvent.goal, chainGoal.slice(0, 120));
1625
+ assert.equal(chainEvent.task, "[prompt redacted]");
1626
+ assert.equal(chainEvent.goal, "[prompt redacted]");
1510
1627
  await waitForAsyncResultFile(chainId, 10_000);
1511
1628
  });
1512
1629
 
@@ -1515,8 +1632,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1515
1632
  const id = `async-missing-extension-tool-${Date.now().toString(36)}`;
1516
1633
 
1517
1634
  executeAsyncChain(id, {
1518
- chain: [{ agent: "extension-builder", task: "Use fixture search" }],
1519
- agents: [makeAgent("extension-builder", { tools: ["read", "fixture_search"] })],
1635
+ chain: [{ agent: "extension-worker", task: "Use fixture search" }],
1636
+ agents: [makeAgent("extension-worker", { tools: ["read", "fixture_search"] })],
1520
1637
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1521
1638
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1522
1639
  shareEnabled: false,
@@ -1533,11 +1650,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1533
1650
 
1534
1651
  it("applies agent acceptance roles to inferred async acceptance", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
1535
1652
  mockPi.onCall({ output: "exploration complete" });
1536
- const executor = makeAsyncExecutor([makeAgent("builder", { acceptanceRole: "read-only" })]);
1653
+ const executor = makeAsyncExecutor([makeAgent("worker", { acceptanceRole: "read-only" })]);
1537
1654
 
1538
1655
  const result = await executor.execute(
1539
1656
  "async-agent-acceptance-role",
1540
- { agent: "builder", task: "Explore the authentication flow", async: true, clarify: false },
1657
+ { agent: "worker", task: "Explore the authentication flow", async: true, clarify: false },
1541
1658
  new AbortController().signal,
1542
1659
  undefined,
1543
1660
  makeMinimalCtx(tempDir),
@@ -1551,11 +1668,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1551
1668
 
1552
1669
  it("applies agent acceptance roles to inferred async parallel acceptance", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
1553
1670
  mockPi.onCall({ output: "parallel exploration complete" });
1554
- const executor = makeAsyncExecutor([makeAgent("builder", { acceptanceRole: "read-only" })]);
1671
+ const executor = makeAsyncExecutor([makeAgent("worker", { acceptanceRole: "read-only" })]);
1555
1672
 
1556
1673
  const result = await executor.execute(
1557
1674
  "async-parallel-agent-acceptance-role",
1558
- { tasks: [{ agent: "builder", task: "Explore the authentication flow" }], async: true, clarify: false },
1675
+ { tasks: [{ agent: "worker", task: "Explore the authentication flow" }], async: true, clarify: false },
1559
1676
  new AbortController().signal,
1560
1677
  undefined,
1561
1678
  makeMinimalCtx(tempDir),
@@ -1599,6 +1716,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1599
1716
  });
1600
1717
 
1601
1718
  it("top-level async parallel conversion preserves output, reads, and progress", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
1719
+ fs.writeFileSync(path.join(tempDir, "input.md"), "context");
1602
1720
  mockPi.onCall({ output: "Async top-level report" });
1603
1721
  const executor = createSubagentExecutor!({
1604
1722
  pi: { events: createEventBus(), getSessionName: () => undefined },
@@ -1608,13 +1726,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1608
1726
  tempArtifactsDir: tempDir,
1609
1727
  getSubagentSessionRoot: () => tempDir,
1610
1728
  expandTilde: (p: string) => p,
1611
- discoverAgents: () => ({ agents: [makeAgent("builder", { defaultProgress: true })] }),
1729
+ discoverAgents: () => ({ agents: [makeAgent("worker", { defaultProgress: true })] }),
1612
1730
  });
1613
1731
 
1614
1732
  const result = await executor.execute(
1615
1733
  "async-parallel-fields",
1616
1734
  {
1617
- tasks: [{ agent: "builder", task: "Do async work", output: "async-top-output.md", reads: ["input.md"] }],
1735
+ tasks: [{ agent: "worker", task: "Do async work", output: "async-top-output.md", reads: ["input.md"] }],
1618
1736
  async: true,
1619
1737
  clarify: false,
1620
1738
  },
@@ -1667,11 +1785,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1667
1785
  it(`async top-level parallel isolates inherited output (${outputOverride === true ? "output:true" : "omitted output"})`, { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
1668
1786
  mockPi.onCall({ matchArgIncludes: "Write the first report", output: "first async report" });
1669
1787
  mockPi.onCall({ matchArgIncludes: "Write the second report", output: "second async report" });
1670
- const agent = makeAgent("builder", { output: "context.md", tools: ["read", "grep", "find", "ls"] });
1788
+ const agent = makeAgent("worker", { output: "context.md", tools: ["read", "grep", "find", "ls"] });
1671
1789
  const executor = makeAsyncExecutor([agent]);
1672
1790
  const tasks = [
1673
- { agent: "builder", task: "Write the first report", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
1674
- { agent: "builder", task: "Write the second report", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
1791
+ { agent: "worker", task: "Write the first report", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
1792
+ { agent: "worker", task: "Write the second report", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
1675
1793
  ];
1676
1794
  const launch = await executor.execute(
1677
1795
  `async-inherited-output-${outputOverride === true ? "true" : "omitted"}`,
@@ -1688,8 +1806,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1688
1806
  assert.equal(payload.results[1]?.output?.split("\n\nOutput saved to:")[0], "second async report");
1689
1807
  const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", launch.details?.asyncId as string);
1690
1808
  const authoritativePaths = [
1691
- path.join(outputDir, "parallel-0", "0-builder", "context.md"),
1692
- path.join(outputDir, "parallel-0", "1-builder", "context.md"),
1809
+ path.join(outputDir, "parallel-0", "0-worker", "context.md"),
1810
+ path.join(outputDir, "parallel-0", "1-worker", "context.md"),
1693
1811
  ];
1694
1812
  assert.equal(fs.readFileSync(authoritativePaths[0]!, "utf-8"), "first async report");
1695
1813
  assert.equal(fs.readFileSync(authoritativePaths[1]!, "utf-8"), "second async report");
@@ -1702,8 +1820,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1702
1820
  const taskArgs = calls.map((name) => (JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord).args?.at(-1) ?? "");
1703
1821
  const firstTask = taskArgs.find((task) => task.includes("Write the first report")) ?? "";
1704
1822
  const secondTask = taskArgs.find((task) => task.includes("Write the second report")) ?? "";
1705
- assert.ok(firstTask.includes(path.join("parallel-0", "0-builder", "context.md")));
1706
- assert.ok(secondTask.includes(path.join("parallel-0", "1-builder", "context.md")));
1823
+ assert.ok(firstTask.includes(path.join("parallel-0", "0-worker", "context.md")));
1824
+ assert.ok(secondTask.includes(path.join("parallel-0", "1-worker", "context.md")));
1707
1825
  for (const taskArg of [firstTask, secondTask]) {
1708
1826
  assert.match(taskArg, /Return the complete artifact in your final response\./);
1709
1827
  assert.match(taskArg, /Do not call contact_supervisor merely because no write-capable tool is available\./);
@@ -1713,13 +1831,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1713
1831
  }
1714
1832
 
1715
1833
  it("async top-level parallel rejects duplicate explicit output paths before spawning", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
1716
- const executor = makeAsyncExecutor([makeAgent("builder")]);
1834
+ const executor = makeAsyncExecutor([makeAgent("worker")]);
1717
1835
  const result = await executor.execute(
1718
1836
  "async-duplicate-output",
1719
1837
  {
1720
1838
  tasks: [
1721
- { agent: "builder", task: "Write A", output: "same.md" },
1722
- { agent: "builder", task: "Write B", output: "same.md" },
1839
+ { agent: "worker", task: "Write A", output: "same.md" },
1840
+ { agent: "worker", task: "Write B", output: "same.md" },
1723
1841
  ],
1724
1842
  async: true,
1725
1843
  clarify: false,
@@ -1730,20 +1848,20 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1730
1848
  );
1731
1849
 
1732
1850
  assert.equal(result.isError, true);
1733
- assert.match(result.content[0]?.text ?? "", /Parallel tasks 1 \(builder\) and 2 \(builder\).*same\.md/);
1851
+ assert.match(result.content[0]?.text ?? "", /Parallel tasks 1 \(worker\) and 2 \(worker\).*same\.md/);
1734
1852
  assert.equal(mockPi.callCount(), 0);
1735
1853
  });
1736
1854
 
1737
1855
  it("async top-level parallel preserves distinct explicit output destinations", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
1738
1856
  mockPi.onCall({ matchArgIncludes: "first.md", output: "first explicit report" });
1739
1857
  mockPi.onCall({ matchArgIncludes: "second.md", output: "second explicit report" });
1740
- const executor = makeAsyncExecutor([makeAgent("builder", { output: "context.md" })]);
1858
+ const executor = makeAsyncExecutor([makeAgent("worker", { output: "context.md" })]);
1741
1859
  const launch = await executor.execute(
1742
1860
  "async-distinct-explicit-output",
1743
1861
  {
1744
1862
  tasks: [
1745
- { agent: "builder", task: "Write A", output: "first.md" },
1746
- { agent: "builder", task: "Write B", output: "second.md" },
1863
+ { agent: "worker", task: "Write A", output: "first.md" },
1864
+ { agent: "worker", task: "Write B", output: "second.md" },
1747
1865
  ],
1748
1866
  async: true,
1749
1867
  clarify: false,
@@ -1758,8 +1876,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1758
1876
  const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", launch.details?.asyncId as string);
1759
1877
  assert.equal(fs.readFileSync(path.join(outputDir, "first.md"), "utf-8"), "first explicit report");
1760
1878
  assert.equal(fs.readFileSync(path.join(outputDir, "second.md"), "utf-8"), "second explicit report");
1761
- assert.ok(payload.results[0]?.artifactPaths?.outputPath?.endsWith("_builder_0_output.md"));
1762
- assert.ok(payload.results[1]?.artifactPaths?.outputPath?.endsWith("_builder_1_output.md"));
1879
+ assert.ok(payload.results[0]?.artifactPaths?.outputPath?.endsWith("_worker_0_output.md"));
1880
+ assert.ok(payload.results[1]?.artifactPaths?.outputPath?.endsWith("_worker_1_output.md"));
1763
1881
  });
1764
1882
 
1765
1883
  it("async chain static parallel namespaces inherited default outputs", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
@@ -1767,8 +1885,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1767
1885
  mockPi.onCall({ matchArgIncludes: "Write second", output: "chain second report" });
1768
1886
  const id = `async-chain-parallel-output-${Date.now().toString(36)}`;
1769
1887
  const launch = executeAsyncChain(id, {
1770
- chain: [{ parallel: [{ agent: "builder", task: "Write first" }, { agent: "builder", task: "Write second" }] }],
1771
- agents: [makeAgent("builder", { output: "context.md" })],
1888
+ chain: [{ parallel: [{ agent: "worker", task: "Write first" }, { agent: "worker", task: "Write second" }] }],
1889
+ agents: [makeAgent("worker", { output: "context.md" })],
1772
1890
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-chain-output" },
1773
1891
  artifactConfig: { enabled: true, includeInput: false, includeOutput: true, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1774
1892
  artifactsDir: path.join(tempDir, ".pi-subagents", "artifacts"),
@@ -1781,8 +1899,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1781
1899
  assert.equal(payload.success, true);
1782
1900
  const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", id);
1783
1901
  const authoritativePaths = [
1784
- path.join(outputDir, "parallel-0", "0-builder", "context.md"),
1785
- path.join(outputDir, "parallel-0", "1-builder", "context.md"),
1902
+ path.join(outputDir, "parallel-0", "0-worker", "context.md"),
1903
+ path.join(outputDir, "parallel-0", "1-worker", "context.md"),
1786
1904
  ];
1787
1905
  assert.equal(fs.readFileSync(authoritativePaths[0]!, "utf-8"), "chain first report");
1788
1906
  assert.equal(fs.readFileSync(authoritativePaths[1]!, "utf-8"), "chain second report");
@@ -1793,8 +1911,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1793
1911
  assert.equal(fs.readFileSync(artifactPaths[1], "utf-8"), "chain second report");
1794
1912
  const calls = fs.readdirSync(mockPi.dir).filter((name) => name.startsWith("call-")).sort();
1795
1913
  const taskArgs = calls.map((name) => (JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord).args?.at(-1) ?? "");
1796
- assert.ok(taskArgs.find((task) => task.includes("Write first"))?.includes(path.join("parallel-0", "0-builder", "context.md")));
1797
- assert.ok(taskArgs.find((task) => task.includes("Write second"))?.includes(path.join("parallel-0", "1-builder", "context.md")));
1914
+ assert.ok(taskArgs.find((task) => task.includes("Write first"))?.includes(path.join("parallel-0", "0-worker", "context.md")));
1915
+ assert.ok(taskArgs.find((task) => task.includes("Write second"))?.includes(path.join("parallel-0", "1-worker", "context.md")));
1798
1916
  });
1799
1917
 
1800
1918
  it("async single preserves checked evidence while independent review is pending", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
@@ -1825,14 +1943,14 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1825
1943
  };
1826
1944
  const id = `async-acceptance-${Date.now().toString(36)}`;
1827
1945
  executeAsyncSingle(id, {
1828
- agent: "builder",
1946
+ agent: "worker",
1829
1947
  task: "Implement acceptance-covered fix",
1830
- agentConfig: makeAgent("builder", { completionGuard: false }),
1948
+ agentConfig: makeAgent("worker", { completionGuard: false }),
1831
1949
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-acceptance" },
1832
1950
  artifactConfig,
1833
1951
  shareEnabled: false,
1834
1952
  maxSubagentDepth: 2,
1835
- acceptance: { level: "checked", criteria: ["Patch bug"], review: { agent: "commentator", required: true } },
1953
+ acceptance: { level: "checked", criteria: ["Patch bug"], review: { agent: "reviewer", required: true } },
1836
1954
  });
1837
1955
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1838
1956
  const result = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
@@ -1856,13 +1974,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1856
1974
  tempArtifactsDir: tempDir,
1857
1975
  getSubagentSessionRoot: () => tempDir,
1858
1976
  expandTilde: (p: string) => p,
1859
- discoverAgents: () => ({ agents: [makeAgent("commentator", { defaultProgress: true })] }),
1977
+ discoverAgents: () => ({ agents: [makeAgent("reviewer", { defaultProgress: true })] }),
1860
1978
  });
1861
1979
 
1862
1980
  const result = await executor.execute(
1863
1981
  "async-chain-read-only-progress",
1864
1982
  {
1865
- chain: [{ agent: "commentator" }],
1983
+ chain: [{ agent: "reviewer" }],
1866
1984
  task: "Review-only. Do not edit files. Return findings.",
1867
1985
  async: true,
1868
1986
  clarify: false,
@@ -1934,7 +2052,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1934
2052
  assert.ok(!result.isError);
1935
2053
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1936
2054
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1937
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
2055
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
1938
2056
  assert.deepEqual(payload.results[0]?.structuredOutput, { value: "Alpha structured" });
1939
2057
  assert.deepEqual(payload.outputs?.data?.structured, { value: "Alpha structured" });
1940
2058
  assert.match(readMockPiArgs(mockPi, 1).at(-1) ?? "", /Alpha structured/);
@@ -1952,15 +2070,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1952
2070
  mockPi.onCall({ matchArgIncludes: "Scout API", output: "Scout A async findings" });
1953
2071
  mockPi.onCall({ matchArgIncludes: "Scout UI", output: "Scout B async findings" });
1954
2072
  mockPi.onCall({ matchArgIncludes: "Synthesize:", output: "Async funnel synthesis" });
1955
- mockPi.onCall({ matchArgIncludes: "Review funnel A:", output: "Async commentator A done" });
1956
- mockPi.onCall({ matchArgIncludes: "Review funnel B:", output: "Async commentator B done" });
2073
+ mockPi.onCall({ matchArgIncludes: "Review funnel A:", output: "Async reviewer A done" });
2074
+ mockPi.onCall({ matchArgIncludes: "Review funnel B:", output: "Async reviewer B done" });
1957
2075
  const id = `async-parallel-funnel-fanout-${Date.now().toString(36)}`;
1958
2076
  const result = executeAsyncChain(id, {
1959
2077
  chain: [
1960
2078
  {
1961
2079
  parallel: [
1962
- { agent: "explorer-a", task: "Scout API" },
1963
- { agent: "explorer-b", task: "Scout UI" },
2080
+ { agent: "scout-a", task: "Scout API" },
2081
+ { agent: "scout-b", task: "Scout UI" },
1964
2082
  ],
1965
2083
  concurrency: 2,
1966
2084
  },
@@ -1973,7 +2091,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1973
2091
  concurrency: 2,
1974
2092
  },
1975
2093
  ],
1976
- agents: [makeAgent("explorer-a"), makeAgent("explorer-b"), makeAgent("synthesizer"), makeAgent("review-a"), makeAgent("review-b")],
2094
+ agents: [makeAgent("scout-a"), makeAgent("scout-b"), makeAgent("synthesizer"), makeAgent("review-a"), makeAgent("review-b")],
1977
2095
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-parallel-funnel-fanout" },
1978
2096
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1979
2097
  shareEnabled: false,
@@ -1983,14 +2101,14 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1983
2101
  assert.ok(!result.isError, `should launch: ${JSON.stringify(result.content)}`);
1984
2102
  const resultPath = await waitForAsyncResultFile(id, 10_000);
1985
2103
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1986
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
2104
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
1987
2105
  assert.equal(payload.success, true);
1988
2106
  assert.deepEqual(payload.results.map((entry) => entry.output), [
1989
2107
  "Scout A async findings",
1990
2108
  "Scout B async findings",
1991
2109
  "Async funnel synthesis",
1992
- "Async commentator A done",
1993
- "Async commentator B done",
2110
+ "Async reviewer A done",
2111
+ "Async reviewer B done",
1994
2112
  ]);
1995
2113
  assert.deepEqual(status.steps?.map((step) => step.status), ["complete", "complete", "complete", "complete", "complete"]);
1996
2114
  assert.deepEqual(status.parallelGroups, [
@@ -1998,9 +2116,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1998
2116
  { start: 3, count: 2, stepIndex: 2 },
1999
2117
  ]);
2000
2118
  const funnelTask = readMockPiArgsMatching(mockPi, "Synthesize:").at(-1) ?? "";
2001
- assert.match(funnelTask, /=== Parallel Task 1 \(explorer-a\) ===/);
2119
+ assert.match(funnelTask, /=== Parallel Task 1 \(scout-a\) ===/);
2002
2120
  assert.match(funnelTask, /Scout A async findings/);
2003
- assert.match(funnelTask, /=== Parallel Task 2 \(explorer-b\) ===/);
2121
+ assert.match(funnelTask, /=== Parallel Task 2 \(scout-b\) ===/);
2004
2122
  assert.match(funnelTask, /Scout B async findings/);
2005
2123
  assert.match(readMockPiArgsMatching(mockPi, "Review funnel A:").at(-1) ?? "", /Review funnel A:\nAsync funnel synthesis/);
2006
2124
  assert.match(readMockPiArgsMatching(mockPi, "Review funnel B:").at(-1) ?? "", /Review funnel B:\nAsync funnel synthesis/);
@@ -2023,13 +2141,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2023
2141
  { agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
2024
2142
  {
2025
2143
  expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
2026
- parallel: { agent: "commentator", task: "Review {target.path}", label: "Review {target.path}", outputSchema: { type: "object" } },
2144
+ parallel: { agent: "reviewer", task: "Review {target.path}", label: "Review {target.path}", outputSchema: { type: "object" } },
2027
2145
  collect: { as: "reviews" },
2028
2146
  concurrency: 1,
2029
2147
  },
2030
2148
  { agent: "consumer", task: "Use {outputs.reviews}" },
2031
2149
  ],
2032
- agents: [makeAgent("producer"), makeAgent("commentator"), makeAgent("consumer")],
2150
+ agents: [makeAgent("producer"), makeAgent("reviewer"), makeAgent("consumer")],
2033
2151
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-placeholder" },
2034
2152
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2035
2153
  shareEnabled: false,
@@ -2045,7 +2163,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2045
2163
  if (fs.existsSync(statusPath)) status = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
2046
2164
  else await new Promise((resolve) => setTimeout(resolve, 50));
2047
2165
  }
2048
- assert.deepEqual(status.steps?.map((step) => step.agent), ["producer", "expand:commentator", "consumer"]);
2166
+ assert.deepEqual(status.steps?.map((step) => step.agent), ["producer", "expand:reviewer", "consumer"]);
2049
2167
  assert.equal(status.steps?.[1]?.label, "Review {target.path}");
2050
2168
  assert.equal(status.steps?.[1]?.outputName, "reviews");
2051
2169
  assert.deepEqual(status.parallelGroups, [{ start: 1, count: 1, stepIndex: 1 }]);
@@ -2054,7 +2172,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2054
2172
  const finalStatus = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
2055
2173
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
2056
2174
  assert.equal(payload.success, true);
2057
- assert.deepEqual(finalStatus.steps?.map((step) => step.agent), ["producer", "commentator", "commentator", "consumer"]);
2175
+ assert.deepEqual(finalStatus.steps?.map((step) => step.agent), ["producer", "reviewer", "reviewer", "consumer"]);
2058
2176
  assert.deepEqual(finalStatus.parallelGroups, [{ start: 1, count: 2, stepIndex: 1 }]);
2059
2177
  });
2060
2178
 
@@ -2070,7 +2188,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2070
2188
  {
2071
2189
  expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
2072
2190
  parallel: {
2073
- agent: "commentator",
2191
+ agent: "reviewer",
2074
2192
  task: "Review {target.path}",
2075
2193
  label: "Review {target.path}",
2076
2194
  outputSchema: { type: "object" },
@@ -2080,7 +2198,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2080
2198
  },
2081
2199
  { agent: "consumer", task: "Use {outputs.reviews}" },
2082
2200
  ],
2083
- agents: [makeAgent("producer"), makeAgent("commentator", { output: "context.md" }), makeAgent("consumer")],
2201
+ agents: [makeAgent("producer"), makeAgent("reviewer", { output: "context.md" }), makeAgent("consumer")],
2084
2202
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic" },
2085
2203
  artifactConfig: { enabled: true, includeInput: false, includeOutput: true, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2086
2204
  artifactsDir: path.join(tempDir, ".pi-subagents", "artifacts"),
@@ -2102,8 +2220,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2102
2220
  assert.deepEqual(collected.map((item) => item.structured), [{ ok: "a" }, { ok: "b" }]);
2103
2221
  const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", id);
2104
2222
  const dynamicOutputPaths = [
2105
- path.join(outputDir, "dynamic-1", "0-commentator", "context.md"),
2106
- path.join(outputDir, "dynamic-1", "1-commentator", "context.md"),
2223
+ path.join(outputDir, "dynamic-1", "0-reviewer", "context.md"),
2224
+ path.join(outputDir, "dynamic-1", "1-reviewer", "context.md"),
2107
2225
  ];
2108
2226
  assert.equal(fs.readFileSync(dynamicOutputPaths[0]!, "utf-8"), "review-a");
2109
2227
  assert.equal(fs.readFileSync(dynamicOutputPaths[1]!, "utf-8"), "review-b");
@@ -2130,13 +2248,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2130
2248
  { agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
2131
2249
  {
2132
2250
  expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
2133
- parallel: { agent: "commentator", task: "Review {target.path}", outputSchema: { type: "object" } },
2251
+ parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" } },
2134
2252
  collect: { as: "reviews" },
2135
2253
  concurrency: 1,
2136
2254
  },
2137
2255
  ],
2138
2256
  usageBudget: { tokens: { hard: 200 } },
2139
- agents: [makeAgent("producer"), makeAgent("commentator")],
2257
+ agents: [makeAgent("producer"), makeAgent("reviewer")],
2140
2258
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-budget" },
2141
2259
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2142
2260
  shareEnabled: false,
@@ -2151,7 +2269,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2151
2269
  assert.equal(status.steps?.[1]?.status, "complete");
2152
2270
  assert.equal(status.steps?.[2]?.status, "failed");
2153
2271
  assert.match(status.steps?.[2]?.error ?? "", /Usage budget exhausted/);
2154
- assert.equal(payload.results.find((result) => result.agent === "commentator" && result.skipped)?.skipped, true);
2272
+ assert.equal(payload.results.find((result) => result.agent === "reviewer" && result.skipped)?.skipped, true);
2155
2273
  });
2156
2274
 
2157
2275
  it("rejects a shared explicit output before dynamic fanout children start", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
@@ -2162,12 +2280,12 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2162
2280
  { agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
2163
2281
  {
2164
2282
  expand: { from: { output: "targets", path: "/items" }, item: "target", maxItems: 2 },
2165
- parallel: { agent: "commentator", task: "Review {target.path}", output: "shared.md" },
2283
+ parallel: { agent: "reviewer", task: "Review {target.path}", output: "shared.md" },
2166
2284
  collect: { as: "reviews" },
2167
2285
  concurrency: 2,
2168
2286
  },
2169
2287
  ],
2170
- agents: [makeAgent("producer"), makeAgent("commentator")],
2288
+ agents: [makeAgent("producer"), makeAgent("reviewer")],
2171
2289
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-explicit-output" },
2172
2290
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2173
2291
  shareEnabled: false,
@@ -2196,7 +2314,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2196
2314
  {
2197
2315
  expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 2 },
2198
2316
  parallel: {
2199
- agent: "commentator",
2317
+ agent: "reviewer",
2200
2318
  task: "Review {target.path}",
2201
2319
  label: "Review {target.path}",
2202
2320
  outputSchema: { type: "object" },
@@ -2205,7 +2323,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2205
2323
  concurrency: 1,
2206
2324
  },
2207
2325
  ],
2208
- agents: [makeAgent("producer"), makeAgent("commentator", { model: "anthropic/claude-sonnet-4-5:high", thinking: "high" })],
2326
+ agents: [makeAgent("producer"), makeAgent("reviewer", { model: "anthropic/claude-sonnet-4-5:high", thinking: "high" })],
2209
2327
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic" },
2210
2328
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2211
2329
  shareEnabled: false,
@@ -2330,7 +2448,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2330
2448
  { agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
2331
2449
  {
2332
2450
  expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
2333
- parallel: { agent: "commentator", task: "Review {target.path}", outputSchema: { type: "object" }, acceptance: { level: "checked" } },
2451
+ parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" }, acceptance: { level: "checked" } },
2334
2452
  collect: { as: "reviews" },
2335
2453
  acceptance: {
2336
2454
  level: "verified",
@@ -2338,7 +2456,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2338
2456
  },
2339
2457
  },
2340
2458
  ],
2341
- agents: [makeAgent("producer"), makeAgent("commentator")],
2459
+ agents: [makeAgent("producer"), makeAgent("reviewer")],
2342
2460
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-acceptance-timeout" },
2343
2461
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2344
2462
  shareEnabled: false,
@@ -2349,7 +2467,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2349
2467
  const resultPath = await waitForAsyncResultFile(id, 5_000);
2350
2468
  const elapsedMs = Date.now() - startedAt;
2351
2469
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
2352
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
2470
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
2353
2471
  const dynamicNode = payload.workflowGraph?.nodes?.[1] as { status?: string; error?: string; acceptanceStatus?: string } | undefined;
2354
2472
  assert.equal(payload.state, "failed");
2355
2473
  assert.equal(payload.timedOut, true);
@@ -2373,13 +2491,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2373
2491
  { agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
2374
2492
  {
2375
2493
  expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
2376
- parallel: { agent: "commentator", task: "Review {target.path}", outputSchema: { type: "object" } },
2494
+ parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" } },
2377
2495
  collect: { as: "reviews" },
2378
2496
  concurrency: 1,
2379
2497
  },
2380
2498
  { agent: "consumer", task: "Use {outputs.reviews}" },
2381
2499
  ],
2382
- agents: [makeAgent("producer"), makeAgent("commentator"), makeAgent("consumer")],
2500
+ agents: [makeAgent("producer"), makeAgent("reviewer"), makeAgent("consumer")],
2383
2501
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-targets" },
2384
2502
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2385
2503
  shareEnabled: false,
@@ -2405,11 +2523,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2405
2523
  { agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
2406
2524
  {
2407
2525
  expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 1 },
2408
- parallel: { agent: "commentator", task: "Review {target.path}" },
2526
+ parallel: { agent: "reviewer", task: "Review {target.path}" },
2409
2527
  collect: { as: "reviews" },
2410
2528
  },
2411
2529
  ],
2412
- agents: [makeAgent("producer"), makeAgent("commentator")],
2530
+ agents: [makeAgent("producer"), makeAgent("reviewer")],
2413
2531
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-fail" },
2414
2532
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2415
2533
  shareEnabled: false,
@@ -2419,7 +2537,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2419
2537
  assert.ok(!result.isError);
2420
2538
  const resultPath = await waitForAsyncResultFile(id, 10_000);
2421
2539
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
2422
- const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload & { workflowGraph?: AsyncResultPayload["workflowGraph"]; error?: string };
2540
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed") as AsyncStatusPayload & { workflowGraph?: AsyncResultPayload["workflowGraph"]; error?: string };
2423
2541
  assert.equal(payload.success, false);
2424
2542
  assert.match(payload.results.at(-1)?.error ?? "", /exceeding maxItems 1/);
2425
2543
  assert.equal(payload.workflowGraph?.nodes?.[1]?.status, "failed");
@@ -2438,11 +2556,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2438
2556
  { agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
2439
2557
  {
2440
2558
  expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
2441
- parallel: { agent: "commentator", task: "Review {target.path}", outputSchema: { type: "object" } },
2559
+ parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" } },
2442
2560
  collect: { as: "reviews", outputSchema: { type: "object" } },
2443
2561
  },
2444
2562
  ],
2445
- agents: [makeAgent("producer"), makeAgent("commentator")],
2563
+ agents: [makeAgent("producer"), makeAgent("reviewer")],
2446
2564
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-collect-fail" },
2447
2565
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
2448
2566
  shareEnabled: false,
@@ -2459,7 +2577,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2459
2577
  assert.match(payload.workflowGraph?.nodes?.[1]?.error ?? "", /Collected output validation failed/);
2460
2578
  });
2461
2579
 
2462
- it("top-level async worktree parallel resolves reads against the worktree and output under project artifacts", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : process.platform === "win32" ? "worktree path separators unreliable on Windows CI" : undefined }, async () => {
2580
+ it("top-level async worktree parallel keeps existing reads and writes output under project artifacts", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : process.platform === "win32" ? "worktree path separators unreliable on Windows CI" : undefined }, async () => {
2463
2581
  const repoDir = createRepo("pi-subagent-async-worktree-");
2464
2582
  try {
2465
2583
  mockPi.onCall({ output: "Worktree report" });
@@ -2471,13 +2589,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2471
2589
  tempArtifactsDir: repoDir,
2472
2590
  getSubagentSessionRoot: () => repoDir,
2473
2591
  expandTilde: (p: string) => p,
2474
- discoverAgents: () => ({ agents: [makeAgent("builder")] }),
2592
+ discoverAgents: () => ({ agents: [makeAgent("worker")] }),
2475
2593
  });
2476
2594
 
2477
2595
  const result = await executor.execute(
2478
2596
  "async-parallel-worktree-fields",
2479
2597
  {
2480
- tasks: [{ agent: "builder", task: "Do worktree work", output: "report.md", reads: ["input.md"] }],
2598
+ tasks: [{ agent: "worker", task: "Do worktree work", output: "report.md", reads: ["input.md"] }],
2481
2599
  async: true,
2482
2600
  clarify: false,
2483
2601
  worktree: true,
@@ -2493,11 +2611,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2493
2611
  const worktreeCwd = path.join(os.tmpdir(), `pi-worktree-${asyncId}-s0-0`);
2494
2612
  const args = await waitForMockPiArgs(mockPi, 0);
2495
2613
  const taskArg = args.at(-1) ?? "";
2496
- assert.ok(taskArg.includes(`[Read from: ${path.join(worktreeCwd, "input.md")}]`));
2614
+ assert.match(taskArg, new RegExp(`\\[Read from: ${escapeRegExp(path.join(worktreeCwd, "input.md"))}\\]`));
2497
2615
  assert.ok(taskArg.includes(`Write your findings to exactly this path: ${path.join(repoDir, ".pi-subagents", "artifacts", "outputs", asyncId, "report.md")}`));
2498
2616
  const resultPath = await waitForAsyncResultFile(asyncId, 90_000);
2499
2617
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
2500
- const status = JSON.parse(fs.readFileSync(path.join(result.details!.asyncDir!, "status.json"), "utf-8")) as AsyncStatusPayload;
2618
+ const status = await waitForAsyncState(asyncId, (candidate) => candidate.state === "complete");
2501
2619
  assert.equal(payload.parallelHandoff?.version, 1);
2502
2620
  assert.equal(payload.parallelHandoff?.path, path.join(result.details!.asyncDir!, "handoff.json"));
2503
2621
  assert.deepEqual(status.parallelHandoff, payload.parallelHandoff);
@@ -2509,7 +2627,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2509
2627
  groups: Array<{ children: Array<{ agent: string; status: string; patch: { path: string } }>; cleanup: { state: string } }>;
2510
2628
  };
2511
2629
  assert.equal(handoff.version, 1);
2512
- assert.equal(handoff.groups[0]!.children[0]!.agent, "builder");
2630
+ assert.equal(handoff.groups[0]!.children[0]!.agent, "worker");
2513
2631
  assert.equal(handoff.groups[0]!.children[0]!.status, "completed");
2514
2632
  assert.equal(handoff.groups[0]!.cleanup.state, "complete");
2515
2633
  assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true, "patch artifact should outlive cleanup");
@@ -2518,33 +2636,49 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2518
2636
  }
2519
2637
  });
2520
2638
 
2521
- it("readStatus caches unchanged files and invalidates same-mtime replacements", () => {
2522
- const dir = createTempDir();
2639
+ it("readStatus caches ordered sweeps above 50 files and invalidates same-mtime replacements", () => {
2640
+ const root = createTempDir();
2523
2641
  try {
2524
- const statusPath = path.join(dir, "status.json");
2525
2642
  const fixedTimestamp = new Date(1_700_000_000_000);
2526
- const statusData = {
2527
- runId: "cache-test",
2528
- state: "running",
2529
- mode: "single",
2530
- startedAt: fixedTimestamp.getTime(),
2531
- };
2532
- fs.writeFileSync(statusPath, JSON.stringify(statusData));
2533
- fs.utimesSync(statusPath, fixedTimestamp, fixedTimestamp);
2643
+ const dirs = Array.from({ length: 51 }, (_, index) => {
2644
+ const dir = path.join(root, `run-${index}`);
2645
+ const statusPath = path.join(dir, "status.json");
2646
+ fs.mkdirSync(dir);
2647
+ fs.writeFileSync(statusPath, JSON.stringify({
2648
+ runId: `cache-test-${index}`,
2649
+ state: "running",
2650
+ mode: "single",
2651
+ startedAt: fixedTimestamp.getTime(),
2652
+ }));
2653
+ fs.utimesSync(statusPath, fixedTimestamp, fixedTimestamp);
2654
+ return dir;
2655
+ });
2534
2656
 
2535
- const cached = readStatus(dir);
2536
- assert.ok(cached);
2537
- assert.strictEqual(readStatus(dir), cached);
2657
+ const cached = dirs.map((dir) => readStatus(dir));
2658
+ cached.forEach((status) => assert.ok(status));
2659
+ dirs.forEach((dir, index) => assert.strictEqual(readStatus(dir), cached[index]));
2538
2660
 
2539
- writeAtomicJson(statusPath, { ...statusData, state: "stopped" });
2661
+ const replacedDir = dirs[25]!;
2662
+ const cachedStatus = cached[25];
2663
+ assert.ok(cachedStatus);
2664
+ const statusPath = path.join(replacedDir, "status.json");
2665
+ writeAtomicJson(statusPath, { ...cachedStatus, state: "stopped" });
2540
2666
  fs.utimesSync(statusPath, fixedTimestamp, fixedTimestamp);
2541
2667
  assert.equal(fs.statSync(statusPath).mtimeMs, fixedTimestamp.getTime());
2542
- const replaced = readStatus(dir);
2668
+ const replaced = readStatus(replacedDir);
2543
2669
  assert.ok(replaced);
2544
2670
  assert.equal(replaced.state, "stopped");
2545
- assert.notStrictEqual(replaced, cached);
2671
+ assert.notStrictEqual(replaced, cachedStatus);
2672
+
2673
+ fs.rmSync(statusPath);
2674
+ assert.equal(readStatus(replacedDir), null);
2675
+
2676
+ const removedDir = dirs[50]!;
2677
+ assert.ok(readStatus(removedDir));
2678
+ fs.rmSync(removedDir, { recursive: true, force: true });
2679
+ assert.equal(pruneStatusCacheForAsyncRoot(root, dirs.slice(0, 50).map((dir) => path.basename(dir))), 1);
2546
2680
  } finally {
2547
- removeTempDir(dir);
2681
+ removeTempDir(root);
2548
2682
  }
2549
2683
  });
2550
2684
 
@@ -2578,10 +2712,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2578
2712
  const asyncDir = path.join(ASYNC_DIR, id);
2579
2713
  const resultPath = path.join(RESULTS_DIR, `${id}.json`);
2580
2714
  const run = executeAsyncSingle(id, {
2581
- agent: "builder",
2715
+ agent: "worker",
2582
2716
  task: "Do work",
2583
2717
  acceptance: { level: "none", reason: "descriptor persistence coverage" },
2584
- agentConfig: makeAgent("builder", {
2718
+ turnBudget: { maxTurns: 8, graceTurns: 2 },
2719
+ agentConfig: makeAgent("worker", {
2585
2720
  model: "openai/gpt-5-mini:high",
2586
2721
  fallbackModels: ["anthropic/claude-sonnet-4:low"],
2587
2722
  }),
@@ -2616,12 +2751,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2616
2751
  const descriptorPath = path.join(asyncDir, "recovery-descriptor.json");
2617
2752
  const descriptor = JSON.parse(fs.readFileSync(descriptorPath, "utf-8"));
2618
2753
  assert.equal(descriptor.sourceRunId, id);
2619
- assert.equal(descriptor.agent, "builder");
2754
+ assert.equal(descriptor.agent, "worker");
2620
2755
  assert.equal(descriptor.model, "openai/gpt-5-mini:high");
2621
2756
  assert.deepEqual(descriptor.fallbackModels, ["anthropic/claude-sonnet-4:low"]);
2622
2757
  assert.equal(descriptor.cwd, tempDir);
2623
2758
  assert.equal(descriptor.sessionDir, path.join(sessionRoot, `async-${id}`));
2624
2759
  assert.deepEqual(descriptor.acceptance, { level: "none", reason: "descriptor persistence coverage" });
2760
+ assert.deepEqual(descriptor.initialTurnBudget, { maxTurns: 8, graceTurns: 2 });
2625
2761
  assert.equal(Object.hasOwn(descriptor.acceptance, "explicit"), false);
2626
2762
  assert.equal(Object.hasOwn(descriptor.acceptance, "inferredReason"), false);
2627
2763
  assert.equal(Object.hasOwn(descriptor, "task"), false);
@@ -2635,7 +2771,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2635
2771
  assert.equal(payload.results[0].modelAttempts.length, 2);
2636
2772
  assert.deepEqual(payload.results[0].totalCost, { inputTokens: 110, outputTokens: 55, costUsd: 0.011 });
2637
2773
  assert.deepEqual(payload.totalCost, { inputTokens: 110, outputTokens: 55, costUsd: 0.011 });
2638
- const statusPayload = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
2774
+ const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "complete" && candidate.totalCost !== undefined);
2639
2775
  assert.equal(statusPayload.lifecycleArtifactVersion, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION);
2640
2776
  assert.equal(statusPayload.steps[0]?.model, "anthropic/claude-sonnet-4:low");
2641
2777
  assert.equal(statusPayload.steps[0]?.thinking, "low");
@@ -2657,9 +2793,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2657
2793
  mockPi.onCall({ output: "Recovered asynchronously after startup race" });
2658
2794
  const id = `async-startup-retry-${Date.now().toString(36)}`;
2659
2795
  executeAsyncSingle(id, {
2660
- agent: "builder",
2796
+ agent: "worker",
2661
2797
  task: "Do work",
2662
- agentConfig: makeAgent("builder", {
2798
+ agentConfig: makeAgent("worker", {
2663
2799
  model: "openai/gpt-5-mini:high",
2664
2800
  fallbackModels: ["anthropic/claude-sonnet-4:low"],
2665
2801
  }),
@@ -2706,9 +2842,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2706
2842
  mockPi.onCall({ output: "Recovered after stream failure" });
2707
2843
  const id = `async-fallback-stream-${Date.now().toString(36)}`;
2708
2844
  executeAsyncSingle(id, {
2709
- agent: "builder",
2845
+ agent: "worker",
2710
2846
  task: "Do work",
2711
- agentConfig: makeAgent("builder", {
2847
+ agentConfig: makeAgent("worker", {
2712
2848
  model: "openai/gpt-5-mini:high",
2713
2849
  fallbackModels: ["anthropic/claude-sonnet-4:low"],
2714
2850
  }),
@@ -2747,9 +2883,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2747
2883
  mockPi.onCall({ output: "fallback must not run" });
2748
2884
  const id = `async-fallback-toolfail-${Date.now().toString(36)}`;
2749
2885
  executeAsyncSingle(id, {
2750
- agent: "builder",
2886
+ agent: "worker",
2751
2887
  task: "Do work",
2752
- agentConfig: makeAgent("builder", {
2888
+ agentConfig: makeAgent("worker", {
2753
2889
  model: "openai/gpt-5-mini:high",
2754
2890
  fallbackModels: ["anthropic/claude-sonnet-4:low"],
2755
2891
  }),
@@ -2795,9 +2931,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2795
2931
  mockPi.onCall({ output: "Recovered asynchronously" });
2796
2932
  const id = `async-fallback-thinking-off-${Date.now().toString(36)}`;
2797
2933
  const run = executeAsyncSingle(id, {
2798
- agent: "builder",
2934
+ agent: "worker",
2799
2935
  task: "Do work",
2800
- agentConfig: makeAgent("builder", {
2936
+ agentConfig: makeAgent("worker", {
2801
2937
  model: "openai/gpt-5-mini:high",
2802
2938
  fallbackModels: ["anthropic/claude-sonnet-4:low"],
2803
2939
  thinking: "high",
@@ -2850,9 +2986,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2850
2986
  mockPi.onCall({ output: "Recovered asynchronously from empty output" });
2851
2987
  const id = `async-empty-output-fallback-${Date.now().toString(36)}`;
2852
2988
  executeAsyncSingle(id, {
2853
- agent: "builder",
2989
+ agent: "worker",
2854
2990
  task: "Do work",
2855
- agentConfig: makeAgent("builder", {
2991
+ agentConfig: makeAgent("worker", {
2856
2992
  model: "openai/gpt-5-mini",
2857
2993
  fallbackModels: ["anthropic/claude-sonnet-4"],
2858
2994
  }),
@@ -2900,9 +3036,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2900
3036
  mockPi.onCall({ output: "Recovered asynchronously on fallback" });
2901
3037
  const id = `async-empty-output-after-tool-error-${Date.now().toString(36)}`;
2902
3038
  executeAsyncSingle(id, {
2903
- agent: "builder",
3039
+ agent: "worker",
2904
3040
  task: "Do work",
2905
- agentConfig: makeAgent("builder", {
3041
+ agentConfig: makeAgent("worker", {
2906
3042
  model: "openai/gpt-5-mini",
2907
3043
  fallbackModels: ["anthropic/claude-sonnet-4"],
2908
3044
  }),
@@ -2942,11 +3078,10 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2942
3078
  exitCode: 0,
2943
3079
  });
2944
3080
  const id = `async-zero-exit-provider-error-${Date.now().toString(36)}`;
2945
- const asyncDir = path.join(ASYNC_DIR, id);
2946
3081
  executeAsyncSingle(id, {
2947
- agent: "builder",
3082
+ agent: "worker",
2948
3083
  task: "Do work",
2949
- agentConfig: makeAgent("builder", { model: "openai/gpt-5-mini" }),
3084
+ agentConfig: makeAgent("worker", { model: "openai/gpt-5-mini" }),
2950
3085
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
2951
3086
  artifactConfig: {
2952
3087
  enabled: false,
@@ -2964,8 +3099,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2964
3099
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
2965
3100
  assert.equal(payload.success, false);
2966
3101
  assert.match(payload.results[0]?.error ?? "", /429 quota exceeded/);
2967
- const statusPayload = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
2968
- assert.equal(statusPayload.state, "failed");
3102
+ const statusPayload = await waitForAsyncState(id, (status) => status.state === "failed");
2969
3103
  assert.match(statusPayload.steps?.[0]?.error ?? "", /429 quota exceeded/);
2970
3104
  });
2971
3105
 
@@ -2990,9 +3124,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2990
3124
  const id = `async-recovered-child-error-${Date.now().toString(36)}`;
2991
3125
  const asyncDir = path.join(ASYNC_DIR, id);
2992
3126
  executeAsyncSingle(id, {
2993
- agent: "builder",
3127
+ agent: "worker",
2994
3128
  task: "Do work",
2995
- agentConfig: makeAgent("builder", { model: "openai/gpt-5-mini" }),
3129
+ agentConfig: makeAgent("worker", { model: "openai/gpt-5-mini" }),
2996
3130
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
2997
3131
  artifactConfig: {
2998
3132
  enabled: false,
@@ -3014,7 +3148,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3014
3148
  assert.equal(payload.results[0]?.success, true);
3015
3149
  assert.equal(payload.results[0]?.error, undefined);
3016
3150
  assert.equal(payload.results[0]?.output, "Recovered asynchronously");
3017
- const statusPayload = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
3151
+ const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
3018
3152
  assert.equal(statusPayload.state, "complete");
3019
3153
  assert.equal(statusPayload.steps?.[0]?.status, "complete");
3020
3154
  assert.equal(statusPayload.steps?.[0]?.exitCode, 0);
@@ -3040,9 +3174,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3040
3174
  const id = `async-provider-error-empty-stop-${Date.now().toString(36)}`;
3041
3175
  const asyncDir = path.join(ASYNC_DIR, id);
3042
3176
  executeAsyncSingle(id, {
3043
- agent: "builder",
3177
+ agent: "worker",
3044
3178
  task: "Do work",
3045
- agentConfig: makeAgent("builder", { model: "openai/gpt-5-mini" }),
3179
+ agentConfig: makeAgent("worker", { model: "openai/gpt-5-mini" }),
3046
3180
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3047
3181
  artifactConfig: {
3048
3182
  enabled: false,
@@ -3064,7 +3198,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3064
3198
  assert.equal(payload.results[0]?.success, false);
3065
3199
  assert.match(payload.results[0]?.error ?? "", /provider transport failed/);
3066
3200
  assert.equal(payload.results[0]?.output, "");
3067
- const statusPayload = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
3201
+ const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
3068
3202
  assert.equal(statusPayload.state, "failed");
3069
3203
  assert.equal(statusPayload.steps?.[0]?.status, "failed");
3070
3204
  assert.equal(statusPayload.steps?.[0]?.exitCode, 1);
@@ -3199,9 +3333,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3199
3333
  mockPi.onCall({ output: "async inline report" });
3200
3334
  const id = `async-string-false-output-${Date.now().toString(36)}`;
3201
3335
  const run = executeAsyncSingle(id, {
3202
- agent: "builder",
3336
+ agent: "worker",
3203
3337
  task: "Do work",
3204
- agentConfig: makeAgent("builder", { output: "default-report.md" }),
3338
+ agentConfig: makeAgent("worker", { output: "default-report.md" }),
3205
3339
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3206
3340
  artifactConfig: {
3207
3341
  enabled: false,
@@ -3238,9 +3372,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3238
3372
  const sessionRoot = path.join(tempDir, "sessions");
3239
3373
 
3240
3374
  executeAsyncSingle(id, {
3241
- agent: "builder",
3375
+ agent: "worker",
3242
3376
  task: "Deploy app",
3243
- agentConfig: makeAgent("builder"),
3377
+ agentConfig: makeAgent("worker"),
3244
3378
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3245
3379
  artifactConfig: {
3246
3380
  enabled: false,
@@ -3277,9 +3411,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3277
3411
  const sessionRoot = path.join(tempDir, "sessions");
3278
3412
 
3279
3413
  executeAsyncSingle(id, {
3280
- agent: "builder",
3414
+ agent: "worker",
3281
3415
  task: "Implement the approved fixes",
3282
- agentConfig: makeAgent("builder"),
3416
+ agentConfig: makeAgent("worker"),
3283
3417
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3284
3418
  artifactConfig: {
3285
3419
  enabled: false,
@@ -3312,7 +3446,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3312
3446
  const eventsPath = path.join(ASYNC_DIR, id, "events.jsonl");
3313
3447
  const eventsText = fs.readFileSync(eventsPath, "utf-8");
3314
3448
  assert.match(eventsText, /"reason":"completion_guard"/);
3315
- assert.match(eventsText, /Subagent failed: builder/);
3449
+ assert.match(eventsText, /Subagent failed: worker/);
3316
3450
  assert.doesNotMatch(eventsText, /Status:/);
3317
3451
  assert.doesNotMatch(eventsText, /Interrupt:/);
3318
3452
  });
@@ -3322,9 +3456,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3322
3456
  const id = `async-v1-separate-${Date.now().toString(36)}`;
3323
3457
 
3324
3458
  executeAsyncSingle(id, {
3325
- agent: "builder",
3459
+ agent: "worker",
3326
3460
  task: "Implement the approved fixes",
3327
- agentConfig: makeAgent("builder", { completionGuard: true }),
3461
+ agentConfig: makeAgent("worker", { completionGuard: true }),
3328
3462
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3329
3463
  artifactConfig: {
3330
3464
  enabled: false,
@@ -3343,7 +3477,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3343
3477
 
3344
3478
  const resultPath = await waitForAsyncResultFile(id, 10_000);
3345
3479
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
3346
- const statusPayload = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
3480
+ const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
3347
3481
 
3348
3482
  assert.equal(payload.success, true);
3349
3483
  assert.equal(payload.state, "complete");
@@ -3364,9 +3498,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3364
3498
  const id = `async-single-schema-${Date.now().toString(36)}`;
3365
3499
 
3366
3500
  executeAsyncSingle(id, {
3367
- agent: "builder",
3501
+ agent: "worker",
3368
3502
  task: "Return structured data",
3369
- agentConfig: makeAgent("builder", { completionGuard: false }),
3503
+ agentConfig: makeAgent("worker", { completionGuard: false }),
3370
3504
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3371
3505
  artifactConfig: {
3372
3506
  enabled: false,
@@ -3432,9 +3566,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3432
3566
  const sessionRoot = path.join(tempDir, "sessions");
3433
3567
 
3434
3568
  executeAsyncSingle(id, {
3435
- agent: "builder",
3569
+ agent: "worker",
3436
3570
  task: "Do work",
3437
- agentConfig: makeAgent("builder", { model: "gpt-5-mini" }),
3571
+ agentConfig: makeAgent("worker", { model: "gpt-5-mini" }),
3438
3572
  ctx: {
3439
3573
  pi: { events: { emit() {} } },
3440
3574
  cwd: tempDir,
@@ -3472,6 +3606,55 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3472
3606
  assert.deepEqual(payload.results[0].attemptedModels, ["github-copilot/gpt-5-mini"]);
3473
3607
  });
3474
3608
 
3609
+ it("rejects an over-cap top-level async launch before creating run artifacts", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
3610
+ fs.rmSync(path.join(ACTIVE_ASYNC_CAPACITY_DIR, activeAsyncCapacitySessionKey("session-cap")), { recursive: true, force: true });
3611
+ const state = {
3612
+ baseCwd: tempDir,
3613
+ currentSessionId: "session-cap",
3614
+ asyncJobs: new Map(),
3615
+ fleetJobs: new Map(),
3616
+ foregroundControls: new Map(),
3617
+ lastForegroundControlId: null,
3618
+ };
3619
+ const occupied = acquireActiveAsyncCapacity({
3620
+ sessionId: "session-cap",
3621
+ limit: 1,
3622
+ runId: "held-run",
3623
+ kind: "runner",
3624
+ asyncDir: path.join(tempDir, "held-run"),
3625
+ });
3626
+ assert.ok(occupied);
3627
+ occupied.markStarted("held-runner");
3628
+ const rejectedAsyncDir = path.join(ASYNC_DIR, "cap-rejected");
3629
+ const rejectedResultPath = path.join(RESULTS_DIR, "cap-rejected.json");
3630
+ fs.rmSync(rejectedAsyncDir, { recursive: true, force: true });
3631
+ fs.rmSync(rejectedResultPath, { force: true });
3632
+ const executor = createSubagentExecutor!({
3633
+ pi: { events: createEventBus(), getSessionName: () => undefined },
3634
+ state,
3635
+ config: { maxActiveAsyncRunsPerSession: 1, artifactDir: "project" },
3636
+ asyncByDefault: false,
3637
+ tempArtifactsDir: tempDir,
3638
+ getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
3639
+ expandTilde: (p: string) => p,
3640
+ discoverAgents: () => ({ agents: [makeAgent("worker")] }),
3641
+ });
3642
+ const context = makeMinimalCtx(tempDir);
3643
+ context.sessionManager.getSessionFile = () => null;
3644
+ context.sessionManager.getSessionId = () => "session-cap";
3645
+ const previousDepth = process.env.SELESAI_SUBAGENT_DEPTH;
3646
+ process.env.SELESAI_SUBAGENT_DEPTH = "0";
3647
+ const result = await executor.execute("cap-rejected", { agent: "worker", task: "Must not start", async: true }, new AbortController().signal, undefined, context);
3648
+ if (previousDepth === undefined) delete process.env.SELESAI_SUBAGENT_DEPTH;
3649
+ else process.env.SELESAI_SUBAGENT_DEPTH = previousDepth;
3650
+ assert.equal(result.isError, true);
3651
+ assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /Active async run capacity exhausted: 1\/1 used/);
3652
+ assert.equal(fs.existsSync(rejectedAsyncDir), false);
3653
+ assert.equal(fs.existsSync(rejectedResultPath), false);
3654
+ assert.equal(fs.existsSync(path.join(tempDir, ".pi-subagents")), false);
3655
+ fs.rmSync(path.join(ACTIVE_ASYNC_CAPACITY_DIR, activeAsyncCapacitySessionKey("session-cap")), { recursive: true, force: true });
3656
+ });
3657
+
3475
3658
  it("scheduled executor launches retain the live active session ownership", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
3476
3659
  mockPi.onCall({ output: "Scheduled work completed" });
3477
3660
  const liveCwd = path.join(tempDir, "live-project");
@@ -3494,14 +3677,14 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3494
3677
  tempArtifactsDir: tempDir,
3495
3678
  getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
3496
3679
  expandTilde: (p: string) => p,
3497
- discoverAgents: () => ({ agents: [makeAgent("builder")] }),
3680
+ discoverAgents: () => ({ agents: [makeAgent("worker")] }),
3498
3681
  });
3499
3682
  const retainedCtx = makeMinimalCtx(tempDir);
3500
3683
  retainedCtx.sessionManager.getSessionId = () => "session-a";
3501
3684
  retainedCtx.model = { provider: "deepseek", id: "scheduled-model" };
3502
3685
  const launch = await executor.executeScheduled(
3503
3686
  `scheduled-owner-${Date.now().toString(36)}`,
3504
- { agent: "builder", task: "Run retained project timer", async: true, acceptance: false },
3687
+ { agent: "worker", task: "Run retained project timer", async: true, acceptance: false },
3505
3688
  new AbortController().signal,
3506
3689
  retainedCtx,
3507
3690
  ) as AsyncExecutionResult;
@@ -3514,7 +3697,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3514
3697
  assert.deepEqual(state.subagentSpawns, { sessionId: "session-b", count: 1, configuredLimit: 1, granted: 1, grantHistory: [] });
3515
3698
  const blocked = await executor.executeScheduled(
3516
3699
  `scheduled-owner-blocked-${Date.now().toString(36)}`,
3517
- { agent: "builder", task: "Exceed the retained owner budget", async: true, acceptance: false },
3700
+ { agent: "worker", task: "Exceed the retained owner budget", async: true, acceptance: false },
3518
3701
  new AbortController().signal,
3519
3702
  retainedCtx,
3520
3703
  );
@@ -3544,7 +3727,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3544
3727
  tempArtifactsDir: tempDir,
3545
3728
  getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
3546
3729
  expandTilde: (p: string) => p,
3547
- discoverAgents: () => ({ agents: [makeAgent("builder")] }),
3730
+ discoverAgents: () => ({ agents: [makeAgent("worker")] }),
3548
3731
  });
3549
3732
  const retainedCtx = makeMinimalCtx(tempDir);
3550
3733
  retainedCtx.sessionManager.getSessionId = () => "session-a";
@@ -3584,7 +3767,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3584
3767
  tempArtifactsDir: tempDir,
3585
3768
  getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
3586
3769
  expandTilde: (p: string) => p,
3587
- discoverAgents: () => ({ agents: [makeAgent("builder")] }),
3770
+ discoverAgents: () => ({ agents: [makeAgent("worker")] }),
3588
3771
  });
3589
3772
  const initialCtx = makeMinimalCtx(tempDir);
3590
3773
  initialCtx.sessionManager.getSessionId = () => "session-continued";
@@ -3595,7 +3778,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3595
3778
  continuedCtx.sessionManager.getSessionId = () => "session-continued";
3596
3779
  const launch = await executor.execute(
3597
3780
  "continued-async-child",
3598
- { agent: "builder", task: "Do work", async: true, acceptance: false },
3781
+ { agent: "worker", task: "Do work", async: true, acceptance: false },
3599
3782
  new AbortController().signal,
3600
3783
  undefined,
3601
3784
  continuedCtx,
@@ -3612,7 +3795,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3612
3795
 
3613
3796
  it("foreground chains keep the last parent session model after continuation drops ctx.model", { skip: !createSubagentExecutor ? "executor not available" : undefined }, async () => {
3614
3797
  mockPi.onCall({ output: "Done foreground" });
3615
- const executor = makeAsyncExecutor([makeAgent("builder", { completionGuard: false })]);
3798
+ const executor = makeAsyncExecutor([makeAgent("worker", { completionGuard: false })]);
3616
3799
  const initialCtx = makeMinimalCtx(tempDir);
3617
3800
  initialCtx.sessionManager.getSessionId = () => "session-continued";
3618
3801
  initialCtx.model = { provider: "deepseek", id: "deepseek-v4-flash" };
@@ -3622,7 +3805,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3622
3805
  continuedCtx.sessionManager.getSessionId = () => "session-continued";
3623
3806
  const result = await executor.execute(
3624
3807
  "continued-foreground-chain",
3625
- { chain: [{ agent: "builder", task: "Do work" }], acceptance: false },
3808
+ { chain: [{ agent: "worker", task: "Do work" }], acceptance: false },
3626
3809
  new AbortController().signal,
3627
3810
  undefined,
3628
3811
  continuedCtx,
@@ -3637,9 +3820,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3637
3820
 
3638
3821
  const id = `async-single-parent-model-${Date.now().toString(36)}`;
3639
3822
  executeAsyncSingle(id, {
3640
- agent: "builder",
3823
+ agent: "worker",
3641
3824
  task: "Do work",
3642
- agentConfig: makeAgent("builder"),
3825
+ agentConfig: makeAgent("worker"),
3643
3826
  ctx: {
3644
3827
  pi: { events: { emit() {} } },
3645
3828
  cwd: tempDir,
@@ -3674,8 +3857,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3674
3857
 
3675
3858
  const id = `async-chain-parent-model-${Date.now().toString(36)}`;
3676
3859
  executeAsyncChain(id, {
3677
- chain: [{ agent: "builder", task: "Do work" }],
3678
- agents: [makeAgent("builder")],
3860
+ chain: [{ agent: "worker", task: "Do work" }],
3861
+ agents: [makeAgent("worker")],
3679
3862
  ctx: {
3680
3863
  pi: { events: { emit() {} } },
3681
3864
  cwd: tempDir,
@@ -3710,8 +3893,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3710
3893
 
3711
3894
  const id = `async-chain-empty-model-${Date.now().toString(36)}`;
3712
3895
  executeAsyncChain(id, {
3713
- chain: [{ agent: "builder", task: "Do work", model: "" }],
3714
- agents: [makeAgent("builder", { model: "anthropic/claude-sonnet-4-5", thinking: "high" })],
3896
+ chain: [{ agent: "worker", task: "Do work", model: "" }],
3897
+ agents: [makeAgent("worker", { model: "anthropic/claude-sonnet-4-5", thinking: "high" })],
3715
3898
  ctx: {
3716
3899
  pi: { events: { emit() {} } },
3717
3900
  cwd: tempDir,
@@ -3754,8 +3937,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3754
3937
  for (const [index, requestedModel] of [undefined, "", "inherit"].entries()) {
3755
3938
  const id = `async-chain-no-parent-${index}-${Date.now().toString(36)}`;
3756
3939
  executeAsyncChain(id, {
3757
- chain: [{ agent: "builder", task: "Do work", ...(requestedModel !== undefined ? { model: requestedModel } : {}) }],
3758
- agents: [makeAgent("builder", { model: "openai/gpt-5-mini", thinking: "high" })],
3940
+ chain: [{ agent: "worker", task: "Do work", ...(requestedModel !== undefined ? { model: requestedModel } : {}) }],
3941
+ agents: [makeAgent("worker", { model: "openai/gpt-5-mini", thinking: "high" })],
3759
3942
  ctx: {
3760
3943
  pi: { events: { emit() {} } },
3761
3944
  cwd: tempDir,
@@ -3804,9 +3987,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3804
3987
  try {
3805
3988
  writePackageSkill(taskCwd, "async-task-cwd-skill");
3806
3989
  executeAsyncSingle(id, {
3807
- agent: "builder",
3990
+ agent: "worker",
3808
3991
  task: "Do work",
3809
- agentConfig: makeAgent("builder", { skills: ["async-task-cwd-skill"] }),
3992
+ agentConfig: makeAgent("worker", { skills: ["async-task-cwd-skill"] }),
3810
3993
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3811
3994
  cwd: taskCwd,
3812
3995
  artifactConfig: {
@@ -3842,15 +4025,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3842
4025
  it("injects agent-file-relative local skills into background single child prompts", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
3843
4026
  mockPi.onCall({ output: "Done asynchronously" });
3844
4027
  const id = `async-local-skill-${Date.now().toString(36)}`;
3845
- const agentFile = path.join(tempDir, "agents", "builder", "builder.md");
4028
+ const agentFile = path.join(tempDir, "agents", "worker", "worker.md");
3846
4029
  const skillFile = path.join(path.dirname(agentFile), "skills", "local", "SKILL.md");
3847
4030
  fs.mkdirSync(path.dirname(skillFile), { recursive: true });
3848
4031
  fs.writeFileSync(skillFile, "---\ndescription: async local skill\n---\nLocal skill body\n", "utf-8");
3849
4032
 
3850
4033
  executeAsyncSingle(id, {
3851
- agent: "builder",
4034
+ agent: "worker",
3852
4035
  task: "Do work",
3853
- agentConfig: makeAgent("builder", { filePath: agentFile, skills: ["local"], skillPath: ["./skills"] }),
4036
+ agentConfig: makeAgent("worker", { filePath: agentFile, skills: ["local"], skillPath: ["./skills"] }),
3854
4037
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3855
4038
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
3856
4039
  shareEnabled: false,
@@ -3896,9 +4079,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3896
4079
  it("background single runs report unavailable pi-subagents skill requests", () => {
3897
4080
  const id = `async-pi-subagents-skill-${Date.now().toString(36)}`;
3898
4081
  const result = executeAsyncSingle(id, {
3899
- agent: "builder",
4082
+ agent: "worker",
3900
4083
  task: "Do work",
3901
- agentConfig: makeAgent("builder"),
4084
+ agentConfig: makeAgent("worker"),
3902
4085
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3903
4086
  cwd: tempDir,
3904
4087
  artifactConfig: {
@@ -3922,8 +4105,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3922
4105
  it("background chains report unavailable pi-subagents skill requests", () => {
3923
4106
  const id = `async-chain-pi-subagents-skill-${Date.now().toString(36)}`;
3924
4107
  const result = executeAsyncChain(id, {
3925
- chain: [{ agent: "builder", task: "Do work", skill: ["pi-subagents"] }],
3926
- agents: [makeAgent("builder")],
4108
+ chain: [{ agent: "worker", task: "Do work", skill: ["pi-subagents"] }],
4109
+ agents: [makeAgent("worker")],
3927
4110
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3928
4111
  cwd: tempDir,
3929
4112
  artifactConfig: {
@@ -3954,8 +4137,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
3954
4137
  try {
3955
4138
  writePackageSkill(path.join(chainCwd, "packages", "app"), "async-chain-step-skill");
3956
4139
  executeAsyncChain(id, {
3957
- chain: [{ agent: "builder", task: "Do work", cwd: "packages/app", skill: ["async-chain-step-skill"] }],
3958
- agents: [makeAgent("builder")],
4140
+ chain: [{ agent: "worker", task: "Do work", cwd: "packages/app", skill: ["async-chain-step-skill"] }],
4141
+ agents: [makeAgent("worker")],
3959
4142
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
3960
4143
  cwd: chainCwd,
3961
4144
  artifactConfig: {
@@ -4053,9 +4236,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4053
4236
  fs.mkdirSync(path.join(TEMP_ROOT_DIR, `async-cfg-${id}.json`), { recursive: true });
4054
4237
 
4055
4238
  const result = executeAsyncSingle(id, {
4056
- agent: "builder",
4239
+ agent: "worker",
4057
4240
  task: "Do work",
4058
- agentConfig: makeAgent("builder"),
4241
+ agentConfig: makeAgent("worker"),
4059
4242
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4060
4243
  artifactConfig: {
4061
4244
  enabled: false,
@@ -4080,9 +4263,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4080
4263
  const missingCwd = path.join(tempDir, "missing-cwd");
4081
4264
 
4082
4265
  const singleResult = executeAsyncSingle(id, {
4083
- agent: "builder",
4266
+ agent: "worker",
4084
4267
  task: "Do work",
4085
- agentConfig: makeAgent("builder"),
4268
+ agentConfig: makeAgent("worker"),
4086
4269
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4087
4270
  cwd: missingCwd,
4088
4271
  artifactConfig: {
@@ -4104,8 +4287,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4104
4287
 
4105
4288
  const chainId = `async-missing-cwd-chain-${Date.now().toString(36)}`;
4106
4289
  const chainResult = executeAsyncChain(chainId, {
4107
- chain: [{ agent: "builder", task: "Do work" }],
4108
- agents: [makeAgent("builder")],
4290
+ chain: [{ agent: "worker", task: "Do work" }],
4291
+ agents: [makeAgent("worker")],
4109
4292
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4110
4293
  cwd: missingCwd,
4111
4294
  artifactConfig: {
@@ -4135,9 +4318,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4135
4318
  try {
4136
4319
  const id = `async-spawn-fail-${Date.now().toString(36)}`;
4137
4320
  const result = executeAsyncSingle(id, {
4138
- agent: "builder",
4321
+ agent: "worker",
4139
4322
  task: "Do work",
4140
- agentConfig: makeAgent("builder"),
4323
+ agentConfig: makeAgent("worker"),
4141
4324
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4142
4325
  artifactConfig: {
4143
4326
  enabled: false,
@@ -4172,8 +4355,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4172
4355
  fs.mkdirSync(path.join(TEMP_ROOT_DIR, `async-cfg-${id}.json`), { recursive: true });
4173
4356
 
4174
4357
  const result = executeAsyncChain(id, {
4175
- chain: [{ agent: "builder", task: "Do work" }],
4176
- agents: [makeAgent("builder")],
4358
+ chain: [{ agent: "worker", task: "Do work" }],
4359
+ agents: [makeAgent("worker")],
4177
4360
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4178
4361
  artifactConfig: {
4179
4362
  enabled: false,
@@ -4203,9 +4386,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4203
4386
 
4204
4387
  const start = Date.now();
4205
4388
  executeAsyncSingle(id, {
4206
- agent: "builder",
4389
+ agent: "worker",
4207
4390
  task: "Do work",
4208
- agentConfig: makeAgent("builder"),
4391
+ agentConfig: makeAgent("worker"),
4209
4392
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4210
4393
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4211
4394
  shareEnabled: false,
@@ -4237,9 +4420,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4237
4420
 
4238
4421
  const start = Date.now();
4239
4422
  executeAsyncSingle(id, {
4240
- agent: "builder",
4423
+ agent: "worker",
4241
4424
  task: "Do work",
4242
- agentConfig: makeAgent("builder"),
4425
+ agentConfig: makeAgent("worker"),
4243
4426
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4244
4427
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4245
4428
  shareEnabled: false,
@@ -4269,9 +4452,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4269
4452
 
4270
4453
  const start = Date.now();
4271
4454
  executeAsyncSingle(id, {
4272
- agent: "builder",
4455
+ agent: "worker",
4273
4456
  task: "Do work",
4274
- agentConfig: makeAgent("builder"),
4457
+ agentConfig: makeAgent("worker"),
4275
4458
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4276
4459
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4277
4460
  shareEnabled: false,
@@ -4304,9 +4487,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4304
4487
 
4305
4488
  const start = Date.now();
4306
4489
  executeAsyncSingle(id, {
4307
- agent: "builder",
4490
+ agent: "worker",
4308
4491
  task: "Do work",
4309
- agentConfig: makeAgent("builder"),
4492
+ agentConfig: makeAgent("worker"),
4310
4493
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4311
4494
  artifactConfig: {
4312
4495
  enabled: false,
@@ -4349,9 +4532,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4349
4532
 
4350
4533
  const start = Date.now();
4351
4534
  executeAsyncSingle(id, {
4352
- agent: "explorer",
4535
+ agent: "scout",
4353
4536
  task: "Inspect something",
4354
- agentConfig: makeAgent("explorer"),
4537
+ agentConfig: makeAgent("scout"),
4355
4538
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4356
4539
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4357
4540
  shareEnabled: false,
@@ -4394,9 +4577,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4394
4577
  const resultPath = path.join(RESULTS_DIR, `${id}.json`);
4395
4578
 
4396
4579
  executeAsyncSingle(id, {
4397
- agent: "builder",
4580
+ agent: "worker",
4398
4581
  task: "Do work",
4399
- agentConfig: makeAgent("builder"),
4582
+ agentConfig: makeAgent("worker"),
4400
4583
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4401
4584
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4402
4585
  shareEnabled: false,
@@ -4431,9 +4614,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4431
4614
  const resultPath = path.join(RESULTS_DIR, `${id}.json`);
4432
4615
 
4433
4616
  executeAsyncSingle(id, {
4434
- agent: "explorer",
4617
+ agent: "scout",
4435
4618
  task: "Investigate behavior",
4436
- agentConfig: makeAgent("explorer"),
4619
+ agentConfig: makeAgent("scout"),
4437
4620
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4438
4621
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4439
4622
  shareEnabled: false,
@@ -4499,9 +4682,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4499
4682
  const eventsPath = path.join(asyncDir, "events.jsonl");
4500
4683
  const resultPath = path.join(RESULTS_DIR, `${id}.json`);
4501
4684
  executeAsyncSingle(id, {
4502
- agent: "builder",
4685
+ agent: "worker",
4503
4686
  task: "Run the command",
4504
- agentConfig: makeAgent("builder"),
4687
+ agentConfig: makeAgent("worker"),
4505
4688
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4506
4689
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4507
4690
  shareEnabled: false,
@@ -4541,23 +4724,25 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4541
4724
  });
4542
4725
 
4543
4726
  it("subagent_wait wakes when an async child is waiting on contact_supervisor", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
4727
+ const id = `async-supervisor-attention-${Date.now().toString(36)}`;
4728
+ const replyReleasePath = path.join(tempDir, `${id}.reply`);
4729
+ const finalReleasePath = path.join(tempDir, `${id}.final`);
4544
4730
  mockPi.onCall({
4545
4731
  steps: [
4546
4732
  { jsonl: [events.toolStart("contact_supervisor", { reason: "need_decision", message: "Need a decision" })] },
4547
- { delay: 5_000, jsonl: [events.toolEnd("contact_supervisor"), events.toolResult("contact_supervisor", "**Reply from supervisor:**\nProceed")] },
4548
- { delay: 2_500, jsonl: [events.assistantMessage("Done")] },
4733
+ { waitForPath: replyReleasePath, jsonl: [events.toolEnd("contact_supervisor"), events.toolResult("contact_supervisor", "**Reply from supervisor:**\nProceed")] },
4734
+ { waitForPath: finalReleasePath, jsonl: [events.assistantMessage("Done")] },
4549
4735
  ],
4550
4736
  });
4551
4737
 
4552
- const id = `async-supervisor-attention-${Date.now().toString(36)}`;
4553
4738
  const asyncDir = path.join(ASYNC_DIR, id);
4554
4739
  const eventsPath = path.join(asyncDir, "events.jsonl");
4555
4740
  const resultPath = path.join(RESULTS_DIR, `${id}.json`);
4556
4741
  const statusPath = path.join(asyncDir, "status.json");
4557
4742
  executeAsyncSingle(id, {
4558
- agent: "builder",
4743
+ agent: "worker",
4559
4744
  task: "Ask the supervisor for a blocking decision",
4560
- agentConfig: makeAgent("builder"),
4745
+ agentConfig: makeAgent("worker"),
4561
4746
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4562
4747
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4563
4748
  shareEnabled: false,
@@ -4573,55 +4758,71 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4573
4758
  },
4574
4759
  });
4575
4760
 
4576
- const attentionDeadline = Date.now() + 10_000;
4577
- let statusDuringAttention: AsyncStatusPayload | undefined;
4578
- while (Date.now() < attentionDeadline && !fs.existsSync(resultPath)) {
4579
- if (fs.existsSync(statusPath)) {
4580
- const nextStatus = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
4581
- if (nextStatus.currentTool === "contact_supervisor" && nextStatus.activityState === "needs_attention") {
4582
- statusDuringAttention = nextStatus;
4583
- break;
4761
+ const releaseMockChild = () => {
4762
+ if (!fs.existsSync(replyReleasePath)) fs.writeFileSync(replyReleasePath, "release", "utf-8");
4763
+ if (!fs.existsSync(finalReleasePath)) fs.writeFileSync(finalReleasePath, "release", "utf-8");
4764
+ };
4765
+ const releaseSupervisorReply = () => {
4766
+ if (!fs.existsSync(replyReleasePath)) fs.writeFileSync(replyReleasePath, "release", "utf-8");
4767
+ };
4768
+ try {
4769
+ const attentionDeadline = Date.now() + 10_000;
4770
+ let statusDuringAttention: AsyncStatusPayload | undefined;
4771
+ while (Date.now() < attentionDeadline && !fs.existsSync(resultPath)) {
4772
+ if (fs.existsSync(statusPath)) {
4773
+ const nextStatus = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
4774
+ if (nextStatus.currentTool === "contact_supervisor" && nextStatus.activityState === "needs_attention") {
4775
+ statusDuringAttention = nextStatus;
4776
+ break;
4777
+ }
4584
4778
  }
4779
+ await new Promise((resolve) => setTimeout(resolve, 100));
4585
4780
  }
4586
- await new Promise((resolve) => setTimeout(resolve, 100));
4587
- }
4588
- assert.ok(statusDuringAttention, "expected status.json to expose the blocking supervisor request");
4781
+ assert.ok(statusDuringAttention, "expected status.json to expose the blocking supervisor request");
4589
4782
 
4590
- const waitResult = await waitForSubagents({ id, timeoutMs: 3_500 }, undefined, {
4591
- state: { currentSessionId: "session-1", foregroundRuns: new Map(), asyncJobs: new Map(), cleanupTimers: new Map(), resultFileCoalescer: new Map() },
4592
- pollIntervalMs: 100,
4593
- events: createEventBus(),
4594
- });
4595
- const waitText = waitResult.content[0]?.text ?? "";
4596
- assert.equal(waitResult.isError, undefined);
4597
- assert.match(waitText, /attention required/i);
4598
- assert.match(waitText, new RegExp(id));
4599
- assert.match(waitText, /intercom\(\{ action: "pending" \}\)/);
4600
- assert.equal(fs.existsSync(resultPath), false, "wait should return before the child completes");
4783
+ try {
4784
+ const waitResult = await waitForSubagents({ id, timeoutMs: 3_500 }, undefined, {
4785
+ state: { currentSessionId: "session-1", foregroundRuns: new Map(), asyncJobs: new Map(), cleanupTimers: new Map(), resultFileCoalescer: new Map() },
4786
+ pollIntervalMs: 100,
4787
+ events: createEventBus(),
4788
+ });
4789
+ const waitText = waitResult.content[0]?.text ?? "";
4790
+ assert.equal(waitResult.isError, undefined);
4791
+ assert.match(waitText, /attention required/i);
4792
+ assert.match(waitText, new RegExp(id));
4793
+ assert.match(waitText, /intercom\(\{ action: "pending" \}\)/);
4794
+ assert.equal(fs.existsSync(resultPath), false, "wait should return before the child completes");
4795
+ } finally {
4796
+ releaseSupervisorReply();
4797
+ }
4601
4798
 
4602
- const eventText = fs.existsSync(eventsPath) ? fs.readFileSync(eventsPath, "utf-8") : "";
4603
- assert.match(eventText, /"type":"needs_attention"/);
4604
- assert.match(eventText, /"reason":"supervisor_request"/);
4605
- assert.equal(statusDuringAttention.activityState, "needs_attention");
4606
- assert.equal(statusDuringAttention.steps?.[0]?.activityState, "needs_attention");
4607
- assert.equal(statusDuringAttention.currentTool, "contact_supervisor");
4608
- assert.equal(statusDuringAttention.steps?.[0]?.currentTool, "contact_supervisor");
4609
-
4610
- const clearDeadline = Date.now() + 10_000;
4611
- let statusAfterReply: AsyncStatusPayload | undefined;
4612
- while (Date.now() < clearDeadline && !fs.existsSync(resultPath)) {
4613
- const nextStatus = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
4614
- if (nextStatus.state === "running" && !nextStatus.currentTool && !nextStatus.steps?.[0]?.currentTool) {
4615
- statusAfterReply = nextStatus;
4616
- break;
4799
+ const eventText = fs.existsSync(eventsPath) ? fs.readFileSync(eventsPath, "utf-8") : "";
4800
+ assert.match(eventText, /"type":"needs_attention"/);
4801
+ assert.match(eventText, /"reason":"supervisor_request"/);
4802
+ assert.equal(statusDuringAttention.activityState, "needs_attention");
4803
+ assert.equal(statusDuringAttention.steps?.[0]?.activityState, "needs_attention");
4804
+ assert.equal(statusDuringAttention.currentTool, "contact_supervisor");
4805
+ assert.equal(statusDuringAttention.steps?.[0]?.currentTool, "contact_supervisor");
4806
+
4807
+ const clearDeadline = Date.now() + 10_000;
4808
+ let statusAfterReply: AsyncStatusPayload | undefined;
4809
+ while (Date.now() < clearDeadline && !fs.existsSync(resultPath)) {
4810
+ const nextStatus = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
4811
+ if (nextStatus.state === "running" && !nextStatus.currentTool && !nextStatus.steps?.[0]?.currentTool) {
4812
+ statusAfterReply = nextStatus;
4813
+ break;
4814
+ }
4815
+ await new Promise((resolve) => setTimeout(resolve, 100));
4617
4816
  }
4618
- await new Promise((resolve) => setTimeout(resolve, 100));
4619
- }
4620
- assert.ok(statusAfterReply, "expected the child to keep running after the supervisor reply");
4621
- assert.equal(statusAfterReply.activityState, undefined);
4622
- assert.equal(statusAfterReply.steps?.[0]?.activityState, undefined);
4817
+ assert.ok(statusAfterReply, "expected the child to keep running after the supervisor reply");
4818
+ assert.equal(statusAfterReply.activityState, undefined);
4819
+ assert.equal(statusAfterReply.steps?.[0]?.activityState, undefined);
4623
4820
 
4624
- await waitForAsyncResultFile(id);
4821
+ fs.writeFileSync(finalReleasePath, "release", "utf-8");
4822
+ await waitForAsyncResultFile(id);
4823
+ } finally {
4824
+ releaseMockChild();
4825
+ }
4625
4826
  });
4626
4827
 
4627
4828
  it("background runs escalate repeated mutating tool failures", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
@@ -4640,9 +4841,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4640
4841
  const resultPath = path.join(RESULTS_DIR, `${id}.json`);
4641
4842
 
4642
4843
  executeAsyncSingle(id, {
4643
- agent: "builder",
4844
+ agent: "worker",
4644
4845
  task: "Implement the approved fixes",
4645
- agentConfig: makeAgent("builder"),
4846
+ agentConfig: makeAgent("worker"),
4646
4847
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4647
4848
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4648
4849
  shareEnabled: false,
@@ -4724,9 +4925,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4724
4925
  const sessionRoot = path.join(tempDir, "sessions");
4725
4926
 
4726
4927
  executeAsyncSingle(id, {
4727
- agent: "builder",
4928
+ agent: "worker",
4728
4929
  task: "Stream noisy diagnostics",
4729
- agentConfig: makeAgent("builder"),
4930
+ agentConfig: makeAgent("worker"),
4730
4931
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4731
4932
  artifactConfig: {
4732
4933
  enabled: false,
@@ -4775,9 +4976,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4775
4976
  const sessionRoot = path.join(tempDir, "sessions");
4776
4977
 
4777
4978
  executeAsyncSingle(id, {
4778
- agent: "builder",
4979
+ agent: "worker",
4779
4980
  task: "Stream detailed progress",
4780
- agentConfig: makeAgent("builder"),
4981
+ agentConfig: makeAgent("worker"),
4781
4982
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4782
4983
  artifactConfig: {
4783
4984
  enabled: false,