@selesai/code 0.8.2 → 0.8.4

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 (528) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +4 -2
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.js +18 -0
  5. package/dist/cli/config-selector.js +2 -2
  6. package/dist/cli/startup-ui.d.ts +1 -1
  7. package/dist/cli/startup-ui.js +2 -2
  8. package/dist/core/keybindings.d.ts +44 -4
  9. package/dist/core/llama/provider.js +22 -7
  10. package/dist/core/model-resolver.js +2 -0
  11. package/dist/core/model-runtime.d.ts +4 -2
  12. package/dist/core/model-runtime.js +23 -2
  13. package/dist/core/remote-catalog-provider.js +72 -67
  14. package/dist/core/settings-manager.d.ts +8 -0
  15. package/dist/core/settings-manager.js +18 -0
  16. package/dist/extensions/handoff-new.ts +1 -1
  17. package/dist/extensions/inline-skills.test.ts +1 -0
  18. package/dist/extensions/inline-skills.ts +1 -1
  19. package/dist/extensions/pi-intercom/CHANGELOG.md +25 -3
  20. package/dist/extensions/pi-intercom/README.md +34 -15
  21. package/dist/extensions/pi-intercom/broker/broker.ts +38 -141
  22. package/dist/extensions/pi-intercom/broker/client-liveness.test.ts +173 -0
  23. package/dist/extensions/pi-intercom/broker/client.ts +78 -150
  24. package/dist/extensions/pi-intercom/broker/extension-state.ts +18 -7
  25. package/dist/extensions/pi-intercom/broker/framing.ts +1 -1
  26. package/dist/extensions/pi-intercom/broker/protocol.ts +182 -0
  27. package/dist/extensions/pi-intercom/broker/spawn.test.ts +106 -7
  28. package/dist/extensions/pi-intercom/broker/spawn.ts +37 -11
  29. package/dist/extensions/pi-intercom/config.test.ts +8 -10
  30. package/dist/extensions/pi-intercom/config.ts +3 -3
  31. package/dist/extensions/pi-intercom/index.ts +218 -147
  32. package/dist/extensions/pi-intercom/intercom.integration.test.ts +891 -103
  33. package/dist/extensions/pi-intercom/package.json +3 -2
  34. package/dist/extensions/pi-intercom/project-agent.test.ts +145 -0
  35. package/dist/extensions/pi-intercom/project-agent.ts +324 -0
  36. package/dist/extensions/pi-intercom/reply-tracker.test.ts +86 -1
  37. package/dist/extensions/pi-intercom/reply-tracker.ts +41 -9
  38. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +49 -9
  39. package/dist/extensions/pi-intercom/test/inline-message.test.ts +9 -9
  40. package/dist/extensions/pi-intercom/types.ts +3 -1
  41. package/dist/extensions/pi-intercom/ui/inline-message.ts +7 -7
  42. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +2 -2
  43. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +15 -1
  44. package/dist/extensions/pi-powerline-footer/README.md +20 -37
  45. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +40 -25
  46. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +149 -18
  47. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -0
  48. package/dist/extensions/pi-powerline-footer/editor-performance.ts +99 -0
  49. package/dist/extensions/pi-powerline-footer/git-status.ts +15 -0
  50. package/dist/extensions/pi-powerline-footer/index.ts +394 -586
  51. package/dist/extensions/pi-powerline-footer/package.json +5 -6
  52. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -18
  53. package/dist/extensions/pi-powerline-footer/queue/store.ts +8 -56
  54. package/dist/extensions/pi-powerline-footer/queue/types.ts +1 -2
  55. package/dist/extensions/pi-powerline-footer/segments.ts +0 -3
  56. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +231 -1
  57. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +5 -19
  58. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +43 -0
  59. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +2 -2
  60. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +64 -0
  61. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +1 -1
  62. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +86 -19
  63. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +19 -149
  64. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +6 -0
  65. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +5 -5
  66. package/dist/extensions/pi-powerline-footer/tsconfig.json +1 -1
  67. package/dist/extensions/pi-powerline-footer/types.ts +1 -2
  68. package/dist/extensions/pi-subagents/CHANGELOG.md +202 -14
  69. package/dist/extensions/pi-subagents/README.md +65 -1614
  70. package/dist/extensions/pi-subagents/agents/builder.md +0 -1
  71. package/dist/extensions/pi-subagents/agents/delegate.md +14 -0
  72. package/dist/extensions/pi-subagents/agents/oracle.md +74 -0
  73. package/dist/extensions/pi-subagents/agents/reviewer.md +78 -0
  74. package/dist/extensions/pi-subagents/agents/scout.md +50 -0
  75. package/dist/extensions/pi-subagents/agents/worker.md +58 -0
  76. package/dist/extensions/pi-subagents/docs/configuration.md +328 -0
  77. package/dist/extensions/pi-subagents/docs/extension-api.md +308 -0
  78. package/dist/extensions/pi-subagents/docs/missions.md +119 -0
  79. package/dist/extensions/pi-subagents/docs/models.md +192 -0
  80. package/dist/extensions/pi-subagents/docs/observability.md +174 -0
  81. package/dist/extensions/pi-subagents/docs/tool-reference.md +343 -0
  82. package/dist/extensions/pi-subagents/docs/watchdog.md +176 -0
  83. package/dist/extensions/pi-subagents/docs/workflows.md +163 -0
  84. package/dist/extensions/pi-subagents/inspector-runner.mjs +11 -0
  85. package/dist/extensions/pi-subagents/package-lock.json +152 -194
  86. package/dist/extensions/pi-subagents/package.json +9 -10
  87. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  88. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  89. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  90. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  91. package/dist/extensions/pi-subagents/prompts/review-loop.md +12 -12
  92. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -17
  93. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +77 -72
  94. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +177 -69
  95. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +28 -8
  96. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +88 -66
  97. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +3 -3
  98. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +624 -0
  99. package/dist/extensions/pi-subagents/src/agents/agents.ts +29 -16
  100. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +7 -3
  101. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  102. package/dist/extensions/pi-subagents/src/agents/skills.ts +2 -9
  103. package/dist/extensions/pi-subagents/src/api/delegation.ts +1 -2
  104. package/dist/extensions/pi-subagents/src/api/project-panes.ts +30 -0
  105. package/dist/extensions/pi-subagents/src/extension/config.ts +56 -8
  106. package/dist/extensions/pi-subagents/src/extension/control-notices.ts +2 -2
  107. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +51 -26
  108. package/dist/extensions/pi-subagents/src/extension/index.ts +119 -50
  109. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +72 -0
  110. package/dist/extensions/pi-subagents/src/extension/rpc.ts +15 -15
  111. package/dist/extensions/pi-subagents/src/extension/schemas.ts +58 -21
  112. package/dist/extensions/pi-subagents/src/extension/subagent-guide.ts +39 -0
  113. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +47 -25
  114. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +3 -3
  115. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +460 -64
  116. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +102 -4
  117. package/dist/extensions/pi-subagents/src/missions/actions.ts +77 -16
  118. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +162 -0
  119. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +65 -14
  120. package/dist/extensions/pi-subagents/src/missions/store.ts +147 -5
  121. package/dist/extensions/pi-subagents/src/missions/types.ts +58 -3
  122. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +256 -0
  123. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -8
  124. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +42 -0
  125. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +41 -7
  126. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +294 -134
  127. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +19 -3
  128. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +39 -6
  129. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +267 -0
  130. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +145 -60
  131. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +114 -6
  132. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +33 -0
  133. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +68 -0
  134. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +71 -0
  135. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +21 -2
  136. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +42 -14
  137. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +12 -24
  138. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +7 -5
  139. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +152 -68
  140. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -4
  141. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +146 -0
  142. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +21 -5
  143. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +85 -0
  144. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +43 -18
  145. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +20 -19
  146. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +3 -0
  147. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +55 -26
  148. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +137 -0
  149. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +798 -112
  150. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +187 -0
  151. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +165 -22
  152. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  153. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
  154. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +9 -5
  155. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +12 -2
  156. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +1 -0
  157. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +465 -116
  158. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +10 -10
  159. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +189 -32
  160. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +2 -2
  161. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +3 -3
  162. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +128 -3
  163. package/dist/extensions/pi-subagents/src/shared/display-text.ts +100 -0
  164. package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +9 -0
  165. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +13 -0
  166. package/dist/extensions/pi-subagents/src/shared/formatters.ts +4 -6
  167. package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +51 -0
  168. package/dist/extensions/pi-subagents/src/shared/settings.ts +41 -11
  169. package/dist/extensions/pi-subagents/src/shared/types.ts +100 -3
  170. package/dist/extensions/pi-subagents/src/shared/utf8.ts +11 -0
  171. package/dist/extensions/pi-subagents/src/shared/utils.ts +43 -33
  172. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -14
  173. package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +1 -1
  174. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +14 -12
  175. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +176 -21
  176. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +12 -6
  177. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +52 -18
  178. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +36 -5
  179. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +30 -16
  180. package/dist/extensions/pi-subagents/src/tui/fleet.ts +151 -31
  181. package/dist/extensions/pi-subagents/src/tui/render.ts +201 -68
  182. package/dist/extensions/pi-subagents/src/types/pi-runtime-compat.d.ts +0 -6
  183. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +5 -4
  184. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +0 -1
  185. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +313 -25
  186. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +15 -15
  187. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
  188. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
  189. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
  190. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
  191. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
  192. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
  193. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
  194. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
  195. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
  196. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
  197. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
  198. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
  199. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
  200. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
  201. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
  202. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
  203. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
  204. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
  205. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
  206. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
  207. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
  208. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
  209. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
  210. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
  211. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
  212. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
  213. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
  214. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
  215. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
  216. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
  217. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
  218. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
  219. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
  220. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
  221. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
  222. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
  223. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
  224. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
  225. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
  226. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
  227. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
  228. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
  229. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
  230. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
  231. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
  232. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
  233. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
  234. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
  235. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
  236. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
  237. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
  238. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
  239. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
  240. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
  241. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
  242. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
  243. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
  244. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
  245. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
  246. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
  247. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
  248. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
  249. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
  250. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
  251. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
  252. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
  253. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
  254. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
  255. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
  256. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
  257. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
  258. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
  259. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
  260. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
  261. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
  262. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
  263. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
  264. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
  265. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
  266. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
  267. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  268. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
  269. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
  270. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
  271. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
  272. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
  273. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
  274. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
  275. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
  276. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
  277. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
  278. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
  279. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
  280. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
  281. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
  282. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
  283. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
  284. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
  285. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  286. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  287. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  288. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
  289. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  290. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  291. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  292. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
  293. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  294. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
  295. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  296. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  297. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
  298. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
  299. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
  300. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  301. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
  302. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
  303. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
  304. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
  305. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
  306. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
  307. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  308. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  309. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
  310. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
  311. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  312. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  313. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  314. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  315. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  316. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
  317. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
  318. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  319. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  320. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
  321. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  322. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
  323. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
  324. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
  325. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
  326. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
  327. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
  328. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
  329. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
  330. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
  331. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
  332. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
  333. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
  334. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
  335. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
  336. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
  337. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
  338. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
  339. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
  340. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
  341. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
  342. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
  343. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
  344. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
  345. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
  346. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
  347. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
  348. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
  349. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
  350. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
  351. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
  352. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
  353. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
  354. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
  355. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
  356. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
  357. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
  358. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
  359. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
  360. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
  361. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
  362. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +84 -0
  363. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/package.json +14 -0
  364. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +15 -15
  365. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +411 -300
  366. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +309 -123
  367. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +99 -56
  368. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +62 -16
  369. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +159 -108
  370. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +1 -1
  371. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +4 -2
  372. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +56 -56
  373. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +114 -45
  374. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +57 -19
  375. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +106 -106
  376. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +416 -106
  377. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +287 -17
  378. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +809 -87
  379. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +107 -20
  380. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +9 -9
  381. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +26 -6
  382. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +2 -2
  383. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  384. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +1 -1
  385. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +0 -8
  386. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +163 -58
  387. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  388. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  389. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +334 -248
  390. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +52 -52
  391. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +26 -26
  392. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +99 -99
  393. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +160 -0
  394. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +9 -9
  395. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +43 -1
  396. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +36 -10
  397. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +387 -30
  398. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +9 -9
  399. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +2 -2
  400. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +94 -40
  401. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +5 -3
  402. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +7 -7
  403. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +19 -19
  404. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +19 -19
  405. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +9 -9
  406. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +20 -20
  407. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +9 -9
  408. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +35 -5
  409. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +2 -2
  410. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +52 -52
  411. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +187 -0
  412. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +125 -71
  413. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +18 -3
  414. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +12 -12
  415. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +17 -23
  416. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  417. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +25 -25
  418. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +3 -3
  419. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +15 -0
  420. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +62 -44
  421. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +329 -37
  422. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +4 -4
  423. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +37 -0
  424. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +69 -0
  425. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +79 -2
  426. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +102 -49
  427. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +98 -32
  428. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +5 -5
  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 +55 -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 +28 -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/package-manifest.test.ts +16 -2
  440. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +7 -7
  441. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +10 -10
  442. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +3 -3
  443. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +26 -17
  444. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +405 -0
  445. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +357 -92
  446. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +22 -6
  447. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +6 -6
  448. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +43 -43
  449. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +13 -13
  450. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +4 -4
  451. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +19 -19
  452. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +243 -0
  453. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +68 -168
  454. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +58 -15
  455. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +40 -0
  456. package/dist/extensions/pi-subagents/test/unit/reads-resolution.test.ts +66 -0
  457. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +47 -28
  458. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +12 -12
  459. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +57 -0
  460. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +107 -27
  461. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +1 -1
  462. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +56 -50
  463. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +69 -28
  464. package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
  465. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +59 -44
  466. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +424 -49
  467. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +23 -1
  468. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +32 -5
  469. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +13 -13
  470. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +120 -91
  471. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +2 -2
  472. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +85 -0
  473. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +35 -35
  474. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +24 -0
  475. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +312 -22
  476. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +150 -15
  477. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -26
  478. package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +73 -0
  479. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +76 -25
  480. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +5 -5
  481. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +55 -6
  482. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +13 -13
  483. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +1 -1
  484. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +1 -1
  485. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +8 -8
  486. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +6 -6
  487. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +138 -11
  488. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +8 -8
  489. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +113 -0
  490. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +7 -7
  491. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +6 -6
  492. package/dist/extensions/workflow/extension.ts +21 -19
  493. package/dist/extensions/workflow/modes.ts +65 -0
  494. package/dist/index.d.ts +1 -1
  495. package/dist/main.js +1 -0
  496. package/dist/modes/interactive/components/settings-selector.d.ts +6 -2
  497. package/dist/modes/interactive/components/settings-selector.js +20 -0
  498. package/dist/modes/interactive/interactive-mode.d.ts +28 -0
  499. package/dist/modes/interactive/interactive-mode.js +214 -28
  500. package/dist/modes/interactive/theme/dark.json +1 -0
  501. package/dist/modes/interactive/theme/light.json +1 -0
  502. package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
  503. package/dist/modes/interactive/theme/theme-controller.js +10 -1
  504. package/dist/modes/interactive/theme/theme.d.ts +2 -2
  505. package/dist/modes/interactive/theme/theme.js +24 -6
  506. package/dist/skills/pi-subagents/SKILL.md +20 -15
  507. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +77 -83
  508. package/dist/skills/pi-subagents/references/execution-controls.md +215 -106
  509. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +29 -9
  510. package/dist/skills/pi-subagents/references/prompting-and-roles.md +58 -43
  511. package/docs/keybindings.md +35 -7
  512. package/docs/plans/workflow-autoloop-reference.md +192 -0
  513. package/docs/plans/workflow-subagent-consolidation-planb.md +374 -0
  514. package/docs/plans/workflow-subagent-scripted-merge.md +406 -0
  515. package/docs/settings.md +16 -14
  516. package/docs/usage.md +5 -0
  517. package/package.json +5 -5
  518. package/dist/extensions/pi-powerline-footer/fixed-editor/cluster.ts +0 -113
  519. package/dist/extensions/pi-powerline-footer/fixed-editor/terminal-split.ts +0 -1080
  520. package/dist/extensions/pi-powerline-footer/tests/fixed-editor.test.ts +0 -1452
  521. package/dist/extensions/workflow/adapter.ts +0 -949
  522. package/dist/extensions/workflow/modes/loop.ts +0 -74
  523. package/dist/extensions/workflow/modes/prototype.ts +0 -170
  524. package/dist/extensions/workflow/modes/quicktype.ts +0 -150
  525. package/dist/extensions/workflow/modes/task.ts +0 -105
  526. package/dist/extensions/workflow/run-state.ts +0 -124
  527. package/dist/extensions/workflow/state-machine.ts +0 -484
  528. package/dist/extensions/workflow/validators.ts +0 -42
@@ -27,6 +27,7 @@ import {
27
27
  tryImport,
28
28
  } from "../support/helpers.ts";
29
29
  import registerSubagentExtension from "../../src/extension/index.ts";
30
+ import { discoverAgents } from "../../src/agents/agents.ts";
30
31
  import {
31
32
  SUBAGENT_DELEGATION_REQUEST_EVENT,
32
33
  SUBAGENT_DELEGATION_RESPONSE_EVENT,
@@ -35,7 +36,8 @@ import {
35
36
  type SubagentDelegationResponse,
36
37
  type SubagentDelegationStarted,
37
38
  } from "../../src/api/delegation.ts";
38
- import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, type SubagentState } from "../../src/shared/types.ts";
39
+ import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, type AsyncStatus, type SubagentState } from "../../src/shared/types.ts";
40
+ import { ACTIVE_RUN_INDEX_DIR } from "../../src/runs/background/active-run-index.ts";
39
41
  import { CHILD_WATCHDOG_STATUS_EVENT } from "../../src/watchdog/child-status.ts";
40
42
  import { WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/wait-config.ts";
41
43
  import { TOOL_BUDGET_ENV, TOOL_BUDGET_ZERO_AUTH_ENV } from "../../src/runs/shared/tool-budget.ts";
@@ -43,6 +45,9 @@ import { MainWatchdogRuntime } from "../../src/watchdog/runtime.ts";
43
45
  import { MAX_CHILD_PENDING_LINE_BYTES, MAX_CHILD_STDERR_BYTES } from "../../src/runs/shared/child-protocol.ts";
44
46
  import {
45
47
  SUBAGENT_FANOUT_CHILD_ENV,
48
+ SUBAGENT_STEER_ACK_DIR_ENV,
49
+ SUBAGENT_STEER_CAPABILITY_ENV,
50
+ SUBAGENT_STEER_INBOX_ENV,
46
51
  SUBAGENT_PARENT_CHILD_INDEX_ENV,
47
52
  SUBAGENT_PARENT_CONTROL_INBOX_ENV,
48
53
  SUBAGENT_PARENT_DEPTH_ENV,
@@ -142,6 +147,7 @@ interface RunSyncResult {
142
147
 
143
148
  interface MockPiCallRecord {
144
149
  args?: string[];
150
+ cwd?: string;
145
151
  systemPrompts?: Array<{ mode?: string; path?: string; text?: string; error?: string }>;
146
152
  }
147
153
 
@@ -235,6 +241,7 @@ interface ExecutorToolResult {
235
241
  isError?: boolean;
236
242
  details?: {
237
243
  totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
244
+ controlEvents?: Array<{ type?: string }>;
238
245
  asyncId?: string;
239
246
  timeoutMs?: number;
240
247
  turnBudget?: { maxTurns: number; graceTurns: number };
@@ -300,7 +307,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
300
307
  removeTempDir(tempDir);
301
308
  });
302
309
 
303
- function readCall(): { args: string[]; systemPrompts: NonNullable<MockPiCallRecord["systemPrompts"]> } {
310
+ function readCall(): { args: string[]; cwd?: string; systemPrompts: NonNullable<MockPiCallRecord["systemPrompts"]> } {
304
311
  const callFile = fs.readdirSync(mockPi.dir)
305
312
  .filter((name) => name.startsWith("call-") && name.endsWith(".json"))
306
313
  .sort()
@@ -308,7 +315,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
308
315
  assert.ok(callFile, "expected a recorded mock pi call");
309
316
  const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
310
317
  assert.ok(Array.isArray(payload.args), "expected recorded args");
311
- return { args: payload.args, systemPrompts: payload.systemPrompts ?? [] };
318
+ return { args: payload.args, cwd: payload.cwd, systemPrompts: payload.systemPrompts ?? [] };
312
319
  }
313
320
 
314
321
  function readCallArgs(): string[] {
@@ -323,9 +330,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
323
330
  allowMutatingManagementActions = true,
324
331
  initialAsyncJobs: SubagentState["asyncJobs"] = new Map(),
325
332
  workflowControllers?: Map<string, AbortController>,
333
+ handleScheduledRunAction?: Parameters<typeof createSubagentExecutor>[0]["handleScheduledRunAction"],
334
+ piEvents = createEventBus(),
326
335
  ) {
327
336
  return createSubagentExecutor!({
328
- pi: { events: createEventBus(), getSessionName: () => undefined },
337
+ pi: { events: piEvents, getSessionName: () => undefined },
329
338
  state: {
330
339
  baseCwd: tempDir,
331
340
  currentSessionId: initialSpawnState?.sessionId ?? null,
@@ -342,6 +351,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
342
351
  expandTilde: (value: string) => value,
343
352
  discoverAgents: () => ({ agents }),
344
353
  allowMutatingManagementActions,
354
+ ...(handleScheduledRunAction ? { handleScheduledRunAction } : {}),
345
355
  });
346
356
  }
347
357
 
@@ -361,11 +371,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
361
371
  assert.equal(output, "Hello from mock agent");
362
372
  });
363
373
 
364
- it("treats action='single' with execution fields as single execution", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
365
- mockPi.onCall({ output: "single alias finished" });
374
+ it("rejects action='single' with execution fields", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
366
375
  const executor = makeExecutor([makeAgent("echo")]);
367
376
 
368
- const result = await executor.execute(
377
+ const result = await executor.executePublic(
369
378
  "single-alias",
370
379
  { action: "single", agent: "echo", task: "Run through alias" },
371
380
  new AbortController().signal,
@@ -373,18 +382,46 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
373
382
  makeMinimalCtx(tempDir),
374
383
  );
375
384
 
385
+ assert.equal(result.isError, true);
386
+ assert.match(result.content[0]?.text ?? "", /is not a management action/);
387
+ assert.equal(mockPi.callCount(), 0);
388
+ });
389
+
390
+ it("allows schedule.create to carry the required workflowScript target", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
391
+ let forwarded;
392
+ const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, async (params) => {
393
+ forwarded = params;
394
+ return { content: [{ type: "text", text: "created" }], details: { mode: "management", results: [] } };
395
+ });
396
+
397
+ const result = await executor.execute(
398
+ "schedule-create",
399
+ { action: "schedule.create", id: "nightly", every: "1h", workflowScript: "return runs.run('main', { agent: 'echo' })" },
400
+ new AbortController().signal,
401
+ undefined,
402
+ makeMinimalCtx(tempDir),
403
+ );
404
+
376
405
  assert.equal(result.isError, undefined);
377
- assert.match(result.content[0]?.text ?? "", /single alias finished/);
406
+ assert.equal(result.content[0]?.text, "created");
407
+ assert.equal(forwarded?.workflowScript, "return runs.run('main', { agent: 'echo' })");
378
408
  });
379
409
 
380
- it("starts workflow scripts asynchronously by default and persists live status", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
381
- mockPi.onCall({ output: "async workflow child" });
382
- const executor = makeExecutor([makeAgent("echo")]);
383
- const runId = `scripted-workflow-async-${Date.now()}`;
410
+ it("starts workflow scripts asynchronously with a portable internal run id", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
411
+ mockPi.onCall({ echoEnv: [SUBAGENT_STEER_INBOX_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_ACK_DIR_ENV] });
412
+ const asyncJobs: SubagentState["asyncJobs"] = new Map();
413
+ const executor = makeExecutor([makeAgent("echo", { aliases: ["helper"] })], { missions: { globalIndex: false } }, false, undefined, true, asyncJobs);
414
+ const workflowCwd = path.join(tempDir, "workflow-cwd");
415
+ fs.mkdirSync(workflowCwd);
416
+ const toolCallId = "call_demo|fc_demo";
384
417
 
385
418
  const result = await executor.execute(
386
- runId,
387
- { workflowScript: `emit("starting"); await runs.run("work", { agent: "echo", task: "Async work" }); return { answer: 42 };` },
419
+ toolCallId,
420
+ {
421
+ cwd: workflowCwd,
422
+ workflowScript: `emit("starting"); await runs.run("work", { agent: "helper", task: "Async work" }); return { answer: 42 };`,
423
+ mission: { summary: "Review the active backlog", labels: ["github-backlog", "review"] },
424
+ },
388
425
  new AbortController().signal,
389
426
  undefined,
390
427
  makeMinimalCtx(tempDir),
@@ -392,25 +429,141 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
392
429
 
393
430
  assert.equal(result.isError, undefined);
394
431
  assert.equal(result.details.mode, "workflow");
395
- assert.equal(result.details.asyncId, runId);
432
+ assert.equal(result.details.toolCallId, toolCallId);
433
+ assert.ok(result.details.asyncId);
434
+ const workflowRunId = result.details.asyncId;
435
+ assert.equal(result.details.runId, workflowRunId);
436
+ assert.notEqual(workflowRunId, toolCallId);
437
+ assert.match(workflowRunId, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
438
+ assert.equal(path.basename(result.details.asyncDir!), workflowRunId);
439
+ assert.equal(asyncJobs.has(workflowRunId), true);
440
+ assert.equal(asyncJobs.get(workflowRunId)?.cwd, workflowCwd);
441
+ assert.equal(asyncJobs.has(toolCallId), false);
442
+ assert.equal(fs.existsSync(path.join(DIRS.async, toolCallId)), false);
396
443
  assert.match(result.content[0]?.text ?? "", /Async workflow/);
397
444
  const statusPath = path.join(result.details.asyncDir!, "status.json");
398
- let status: { state?: string; workflow?: { value?: unknown; emits?: unknown[]; trace?: Array<{ key?: string; state?: string }> } } = {};
445
+ let status: { runId?: string; toolCallId?: string; cwd?: string; state?: string; steps?: Array<{ agent?: string; label?: string; workflowKey?: string; parentWorkflowRunId?: string }>; workflow?: { value?: unknown; emits?: unknown[]; trace?: Array<{ key?: string; agent?: string; state?: string }> } } = {};
399
446
  for (let attempt = 0; attempt < 100; attempt++) {
400
447
  status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
401
448
  if (status.state === "complete" || status.state === "failed") break;
402
449
  await new Promise((resolve) => setTimeout(resolve, 20));
403
450
  }
404
451
  assert.equal(status.state, "complete");
452
+ assert.equal(status.runId, workflowRunId);
453
+ assert.equal(status.toolCallId, toolCallId);
454
+ assert.equal(status.cwd, workflowCwd);
455
+ assert.equal(status.steps?.length, 1);
456
+ assert.deepEqual(status.steps?.map(({ agent, label, workflowKey }) => ({ agent, label, workflowKey })), [
457
+ { agent: "echo", label: "work", workflowKey: "work" },
458
+ ]);
459
+ assert.ok(status.steps?.every((step) => step.parentWorkflowRunId === workflowRunId));
405
460
  assert.deepEqual(status.workflow?.value, { answer: 42 });
406
461
  assert.deepEqual(status.workflow?.emits, ["starting"]);
407
- assert.ok(status.workflow?.trace?.some((entry) => entry.key === "work" && entry.state === "completed"));
408
- const persistedResult = JSON.parse(fs.readFileSync(path.join(DIRS.results, `${runId}.json`), "utf-8")) as { agent?: string; summary?: string; workflow?: { value?: unknown } };
462
+ assert.equal(mockPi.callCount(), 1);
463
+ assert.ok(status.workflow?.trace?.some((entry) => entry.key === "work" && entry.agent === "echo" && entry.state === "completed"));
464
+ const traceEvents = fs.readFileSync(path.join(result.details.asyncDir!, "events.jsonl"), "utf-8")
465
+ .trim()
466
+ .split("\n")
467
+ .map((line) => JSON.parse(line) as { type?: string; trace?: Array<{ key?: string; state?: string }> })
468
+ .filter((event) => event.type === "subagent.workflow.trace");
469
+ assert.equal(traceEvents.length, 2);
470
+ assert.deepEqual(traceEvents[0]?.trace?.map(({ key, state }) => ({ key, state })), [{ key: "work", state: "started" }]);
471
+ assert.deepEqual(traceEvents[1]?.trace?.map(({ key, state }) => ({ key, state })), [
472
+ { key: "work", state: "started" },
473
+ { key: "work", state: "completed" },
474
+ ]);
475
+ const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
476
+ const persistedResult = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { id?: string; runId?: string; toolCallId?: string; agent?: string; cwd?: string; summary?: string; workflow?: { value?: unknown }; results?: Array<{ agent?: string; workflowKey?: string; runId?: string; output?: string }> };
477
+ assert.equal(persistedResult.id, workflowRunId);
478
+ assert.equal(persistedResult.runId, workflowRunId);
479
+ assert.equal(persistedResult.toolCallId, toolCallId);
409
480
  assert.equal(persistedResult.agent, "workflow");
481
+ assert.equal(persistedResult.cwd, workflowCwd);
482
+ assert.deepEqual(persistedResult.results?.map(({ agent, workflowKey }) => ({ agent, workflowKey })), [
483
+ { agent: "echo", workflowKey: "work" },
484
+ ]);
485
+ const steeringEnv = JSON.parse(persistedResult.results?.[0]?.output ?? "null") as Record<string, string | null>;
486
+ assert.match(steeringEnv[SUBAGENT_STEER_INBOX_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-targets[/\\]0$/);
487
+ assert.match(steeringEnv[SUBAGENT_STEER_CAPABILITY_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-capabilities[/\\]0\.json$/);
488
+ assert.match(steeringEnv[SUBAGENT_STEER_ACK_DIR_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-acks[/\\]0$/);
489
+ assert.equal(fs.existsSync(path.join(result.details.asyncDir!, "control", "workflow-foreground", persistedResult.results?.[0]?.runId ?? "missing")), false);
410
490
  assert.match(persistedResult.summary ?? "", /Return: \{\n "answer": 42\n\}/);
411
491
  assert.deepEqual(persistedResult.workflow?.value, { answer: 42 });
492
+ assert.equal(fs.existsSync(path.join(DIRS.results, `${toolCallId}.json`)), false);
412
493
  fs.rmSync(result.details.asyncDir!, { recursive: true, force: true });
413
- fs.rmSync(path.join(DIRS.results, `${runId}.json`), { force: true });
494
+ fs.rmSync(resultPath, { force: true });
495
+ });
496
+
497
+ it("projects live child activity into async workflow status", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
498
+ mockPi.onCall({
499
+ steps: [
500
+ { jsonl: [events.toolStart("read", { path: "src/example.ts" })] },
501
+ { delay: 2_500, jsonl: [events.toolEnd("read"), events.toolResult("read", "contents")] },
502
+ { jsonl: [events.assistantMessage("Done")] },
503
+ ],
504
+ });
505
+ const asyncJobs: SubagentState["asyncJobs"] = new Map();
506
+ const executor = makeExecutor([makeAgent("echo")], {
507
+ control: {
508
+ enabled: true,
509
+ needsAttentionAfterMs: 100,
510
+ activeNoticeAfterMs: 100,
511
+ activeNoticeAfterTurns: 999_999,
512
+ activeNoticeAfterTokens: 999_999,
513
+ notifyOn: ["active_long_running", "needs_attention"],
514
+ notifyChannels: ["event"],
515
+ },
516
+ }, false, undefined, true, asyncJobs);
517
+
518
+ const result = await executor.execute(
519
+ "workflow-live-activity",
520
+ { workflowScript: `return runs.run("main", { agent: "echo", task: "Inspect the file" });` },
521
+ new AbortController().signal,
522
+ undefined,
523
+ makeMinimalCtx(tempDir),
524
+ );
525
+ const { asyncId: workflowRunId, asyncDir } = result.details;
526
+ assert.ok(workflowRunId);
527
+ assert.ok(asyncDir);
528
+ const statusPath = path.join(asyncDir, "status.json");
529
+ const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
530
+ const activeMarkerPath = path.join(DIRS.async, ACTIVE_RUN_INDEX_DIR, workflowRunId);
531
+ assert.equal(fs.existsSync(activeMarkerPath), true);
532
+ let liveStatus: AsyncStatus | undefined;
533
+ const activityDeadline = Date.now() + 5_000;
534
+ while (Date.now() < activityDeadline && !fs.existsSync(resultPath)) {
535
+ const candidate = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatus;
536
+ if (candidate.activityState === "active_long_running" && candidate.steps?.[0]?.currentTool === "read") {
537
+ liveStatus = candidate;
538
+ break;
539
+ }
540
+ await new Promise((resolve) => setTimeout(resolve, 50));
541
+ }
542
+
543
+ assert.ok(liveStatus, "expected workflow status to expose live child activity");
544
+ assert.equal(liveStatus.activityState, "active_long_running");
545
+ assert.equal(typeof liveStatus.lastActivityAt, "number");
546
+ assert.equal(liveStatus.currentTool, "read");
547
+ assert.match(liveStatus.currentPath ?? "", /src[/\\]example\.ts$/);
548
+ assert.equal(liveStatus.toolCount, 1);
549
+ assert.equal(liveStatus.steps?.[0]?.status, "running");
550
+ assert.equal(liveStatus.steps?.[0]?.agent, "echo");
551
+ assert.match(liveStatus.steps?.[0]?.sessionFile ?? "", /session\.jsonl$/);
552
+ assert.equal(fs.existsSync(liveStatus.steps?.[0]?.sessionFile ?? ""), true);
553
+ assert.equal(liveStatus.steps?.[0]?.activityState, "active_long_running");
554
+ assert.equal(typeof liveStatus.steps?.[0]?.lastActivityAt, "number");
555
+ assert.equal(liveStatus.steps?.[0]?.toolCount, 1);
556
+ assert.equal(asyncJobs.get(workflowRunId)?.activityState, "active_long_running");
557
+ assert.equal(asyncJobs.get(workflowRunId)?.steps?.[0]?.currentTool, "read");
558
+
559
+ const completionDeadline = Date.now() + 5_000;
560
+ while (!fs.existsSync(resultPath)) {
561
+ if (Date.now() > completionDeadline) assert.fail("Timed out waiting for async workflow completion");
562
+ await new Promise((resolve) => setTimeout(resolve, 50));
563
+ }
564
+ assert.equal(fs.existsSync(activeMarkerPath), false);
565
+ fs.rmSync(asyncDir, { recursive: true, force: true });
566
+ fs.rmSync(resultPath, { force: true });
414
567
  });
415
568
 
416
569
  it("rejects an invalid async workflow usage budget before creating run state", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
@@ -449,8 +602,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
449
602
  );
450
603
 
451
604
  assert.equal(started.isError, undefined);
452
- assert.equal(started.details.asyncId, runId);
453
- const resultPath = path.join(DIRS.results, `${runId}.json`);
605
+ assert.ok(started.details.asyncId);
606
+ assert.notEqual(started.details.asyncId, runId);
607
+ const resultPath = path.join(DIRS.results, `${started.details.asyncId}.json`);
454
608
  let persisted: { state?: string; summary?: string; results?: Array<{ success?: boolean; output?: string }> } = {};
455
609
  for (let attempt = 0; attempt < 100; attempt++) {
456
610
  if (fs.existsSync(resultPath)) persisted = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
@@ -467,6 +621,60 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
467
621
  fs.rmSync(resultPath, { force: true });
468
622
  });
469
623
 
624
+ it("keeps ordinary async workflow child results in the watcher-owned path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
625
+ mockPi.onCall({ output: "async child done" });
626
+ const executor = makeExecutor([makeAgent("echo")]);
627
+ const result = await executor.execute(
628
+ `scripted-workflow-async-child-${Date.now()}`,
629
+ { workflowScript: `return await runs.run("background", { agent: "echo", task: "Async child", async: true });`, async: false },
630
+ new AbortController().signal,
631
+ undefined,
632
+ makeMinimalCtx(tempDir),
633
+ );
634
+ const childRunId = (result.details.workflow?.value as { runId?: string } | undefined)?.runId;
635
+ assert.ok(childRunId);
636
+ const childDir = path.join(DIRS.async, childRunId);
637
+ const childResultPath = path.join(DIRS.results, `${childRunId}.json`);
638
+ for (let attempt = 0; attempt < 200 && !fs.existsSync(childResultPath); attempt++) {
639
+ await new Promise((resolve) => setTimeout(resolve, 20));
640
+ }
641
+ assert.equal(fs.existsSync(childResultPath), true);
642
+ assert.equal(fs.existsSync(path.join(childDir, "workflow-result.json")), false);
643
+ fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
644
+ fs.rmSync(childResultPath, { force: true });
645
+ });
646
+
647
+ it("applies an agent deadline to a workflow-launched async child", { skip: !createSubagentExecutor ? "executor not importable" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
648
+ mockPi.onCall({ delay: 5_000, output: "too late" });
649
+ const executor = makeExecutor([makeAgent("slow", { defaultTimeoutMs: 150 })]);
650
+ const result = await executor.execute(
651
+ `scripted-workflow-async-child-timeout-${Date.now()}`,
652
+ {
653
+ workflowScript: `return await runs.run("background", { agent: "slow", task: "Wait", async: true });`,
654
+ async: false,
655
+ },
656
+ new AbortController().signal,
657
+ undefined,
658
+ makeMinimalCtx(tempDir),
659
+ );
660
+ const childRunId = (result.details.workflow?.value as { runId?: string } | undefined)?.runId;
661
+ assert.ok(childRunId, JSON.stringify(result.details.workflow?.value ?? result.content));
662
+ const childDir = path.join(DIRS.async, childRunId);
663
+ const childResultPath = path.join(DIRS.results, `${childRunId}.json`);
664
+ let persisted: { timeoutMs?: number; state?: string; results?: Array<{ timedOut?: boolean; error?: string }> } = {};
665
+ for (let attempt = 0; attempt < 200; attempt++) {
666
+ if (fs.existsSync(childResultPath)) persisted = JSON.parse(fs.readFileSync(childResultPath, "utf-8"));
667
+ if (persisted.state === "failed") break;
668
+ await new Promise((resolve) => setTimeout(resolve, 20));
669
+ }
670
+ assert.equal(persisted.timeoutMs, 150);
671
+ assert.equal(persisted.state, "failed");
672
+ assert.deepEqual(persisted.results?.map((entry) => entry.timedOut), [true]);
673
+ assert.deepEqual(persisted.results?.map((entry) => entry.error), ["Subagent timed out after 150ms."]);
674
+ fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
675
+ fs.rmSync(childResultPath, { force: true });
676
+ });
677
+
470
678
  it("persists workflow parent metadata in an async worktree child status and result", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
471
679
  execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
472
680
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
@@ -476,14 +684,16 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
476
684
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
477
685
  mockPi.onCall({ output: "async child done" });
478
686
  const executor = makeExecutor([makeAgent("echo")]);
479
- const workflowRunId = `scripted-workflow-parent-${Date.now()}`;
687
+ const toolCallId = `scripted-workflow-parent-${Date.now()}`;
480
688
  const started = await executor.execute(
481
- workflowRunId,
689
+ toolCallId,
482
690
  { workflowScript: `const child = await runs.run("background", { agent: "echo", task: "Async child", async: true, worktree: true }); return child.runId;` },
483
691
  new AbortController().signal,
484
692
  undefined,
485
693
  makeMinimalCtx(tempDir),
486
694
  );
695
+ assert.ok(started.details.asyncId);
696
+ const workflowRunId = started.details.asyncId;
487
697
  const workflowResultPath = path.join(DIRS.results, `${workflowRunId}.json`);
488
698
  let childRunId: string | undefined;
489
699
  for (let attempt = 0; attempt < 150; attempt++) {
@@ -494,6 +704,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
494
704
  await new Promise((resolve) => setTimeout(resolve, 20));
495
705
  }
496
706
  assert.ok(childRunId);
707
+ const workflowStatus = JSON.parse(fs.readFileSync(path.join(started.details.asyncDir!, "status.json"), "utf-8")) as AsyncStatus;
708
+ const workflowStepSessionFile = workflowStatus.steps?.[0]?.sessionFile ?? "";
709
+ assert.equal(workflowStatus.steps?.[0]?.agent, "echo");
710
+ assert.match(workflowStepSessionFile, /session\.jsonl$/);
497
711
  const childDir = path.join(DIRS.async, childRunId);
498
712
  const childStatusPath = path.join(childDir, "status.json");
499
713
  let childStatus: { state?: string; mode?: string; parentWorkflowRunId?: string; workflowKey?: string; parallelHandoff?: { path?: string } } = {};
@@ -513,6 +727,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
513
727
  const childResult = JSON.parse(fs.readFileSync(childResultPath, "utf-8")) as { parentWorkflowRunId?: string; workflowKey?: string };
514
728
  assert.equal(childResult.parentWorkflowRunId, workflowRunId);
515
729
  assert.equal(childResult.workflowKey, "background");
730
+ assert.equal(fs.existsSync(workflowStepSessionFile), true);
516
731
  fs.rmSync(started.details.asyncDir!, { recursive: true, force: true });
517
732
  fs.rmSync(workflowResultPath, { force: true });
518
733
  fs.rmSync(childDir, { recursive: true, force: true });
@@ -575,19 +790,20 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
575
790
  fs.rmSync(asyncDir, { recursive: true, force: true });
576
791
  });
577
792
 
578
- it("routes workflow script children through ordinary foreground execution", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
793
+ it("routes workflow children through one automatic mission", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
579
794
  mockPi.onCall({ output: "scanned auth" });
580
795
  mockPi.onCall({ output: "reviewed auth" });
581
- const executor = makeExecutor([makeAgent("echo")]);
796
+ const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndex: false } });
582
797
 
583
798
  const result = await executor.execute(
584
799
  "scripted-workflow",
585
800
  {
586
801
  async: false,
587
802
  workflowScript: `
803
+ const stateType = typeof state;
588
804
  const scan = await runs.run("scan", { agent: "echo", task: "Scan auth" });
589
805
  const review = await runs.run("review", { agent: "echo", task: "Review: " + scan.output });
590
- return review.output;
806
+ return { output: review.output, stateType };
591
807
  `,
592
808
  },
593
809
  new AbortController().signal,
@@ -599,9 +815,88 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
599
815
  assert.match(result.content[0]?.text ?? "", /reviewed auth/);
600
816
  assert.equal(result.details.mode, "workflow");
601
817
  assert.equal(result.details.results.length, 2);
818
+ assert.equal(result.details.workflow?.value && (result.details.workflow.value as { stateType?: unknown }).stateType, "object");
819
+ assert.ok(result.details.missionId);
820
+ const missionDir = path.join(tempDir, ".pi-subagents", "missions");
821
+ const missionFiles = fs.readdirSync(missionDir).filter((entry) => entry.endsWith(".json"));
822
+ assert.equal(missionFiles.length, 1);
823
+ const mission = JSON.parse(fs.readFileSync(path.join(missionDir, missionFiles[0]!), "utf-8")) as { objective?: string };
824
+ assert.equal(mission.objective, "Scan auth");
602
825
  assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state === "completed").map((entry) => entry.key), ["scan", "review"]);
603
826
  });
604
827
 
828
+ it("keeps workflow children mission-detached when automatic mission persistence fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
829
+ mockPi.onCall({ output: "scanned auth" });
830
+ mockPi.onCall({ output: "reviewed auth" });
831
+ const blockedIndex = path.join(tempDir, "blocked-mission-index");
832
+ fs.writeFileSync(blockedIndex, "not a directory", "utf-8");
833
+ const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndexDir: blockedIndex } });
834
+
835
+ const result = await executor.execute(
836
+ "scripted-workflow-mission-warning",
837
+ {
838
+ async: false,
839
+ workflowScript: `
840
+ const scan = await runs.run("scan", { agent: "echo", task: "Scan auth" });
841
+ const review = await runs.run("review", { agent: "echo", task: "Review: " + scan.output });
842
+ return review.output;
843
+ `,
844
+ },
845
+ new AbortController().signal,
846
+ undefined,
847
+ makeMinimalCtx(tempDir),
848
+ );
849
+
850
+ assert.equal(result.isError, undefined);
851
+ assert.equal(result.details.missionId, undefined);
852
+ assert.match(result.details.missionWarning ?? "", /Mission tracking unavailable/);
853
+ assert.equal(result.details.results.length, 2);
854
+ const missionDir = path.join(tempDir, ".pi-subagents", "missions");
855
+ const missionFiles = fs.existsSync(missionDir) ? fs.readdirSync(missionDir).filter((entry) => entry.endsWith(".json")) : [];
856
+ assert.equal(missionFiles.length, 1);
857
+ });
858
+
859
+ it("shares durable workflow state across a mission and omits it for mission:false", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
860
+ const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndex: false } });
861
+ const first = await executor.execute(
862
+ "mission-state-first",
863
+ {
864
+ async: false,
865
+ mission: { title: "Stateful workflow" },
866
+ workflowScript: `await state.set("review.stage", { count: 1 }); return await state.get("review.stage");`,
867
+ },
868
+ new AbortController().signal,
869
+ undefined,
870
+ makeMinimalCtx(tempDir),
871
+ );
872
+ assert.equal(first.isError, undefined, first.content[0]?.text ?? "first workflow failed");
873
+ assert.ok(first.details.missionId);
874
+ assert.deepEqual(first.details.workflow?.value, { count: 1 });
875
+ const statePath = path.join(tempDir, ".pi-subagents", "missions", first.details.missionId, "state.json");
876
+ assert.equal(fs.existsSync(statePath), true);
877
+
878
+ const second = await executor.execute(
879
+ "mission-state-second",
880
+ { async: false, missionId: first.details.missionId, workflowScript: `return await state.get("review.stage");` },
881
+ new AbortController().signal,
882
+ undefined,
883
+ makeMinimalCtx(tempDir),
884
+ );
885
+ assert.equal(second.isError, undefined, second.content[0]?.text ?? "second workflow failed");
886
+ assert.deepEqual(second.details.workflow?.value, { count: 1 });
887
+
888
+ const ephemeral = await executor.execute(
889
+ "mission-state-off",
890
+ { async: false, mission: false, workflowScript: `return typeof state;` },
891
+ new AbortController().signal,
892
+ undefined,
893
+ makeMinimalCtx(tempDir),
894
+ );
895
+ assert.equal(ephemeral.isError, undefined, ephemeral.content[0]?.text ?? "ephemeral workflow failed");
896
+ assert.equal(ephemeral.details.workflow?.value, "undefined");
897
+ assert.equal(ephemeral.details.missionId, undefined);
898
+ });
899
+
605
900
  it("runs a direct single child in a managed worktree", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
606
901
  execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
607
902
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
@@ -610,11 +905,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
610
905
  execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
611
906
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
612
907
  mockPi.onCall({ output: "isolated feature", writeFiles: [{ path: "feature.txt", content: "feature\n" }] });
613
- const executor = makeExecutor([makeAgent("builder")]);
908
+ const executor = makeExecutor([makeAgent("worker")]);
614
909
 
615
910
  const result = await executor.execute(
616
911
  "direct-worktree",
617
- { async: false, agent: "builder", task: "Implement feature", worktree: true },
912
+ { async: false, agent: "worker", task: "Implement feature", worktree: true },
618
913
  new AbortController().signal,
619
914
  undefined,
620
915
  makeMinimalCtx(tempDir),
@@ -635,6 +930,57 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
635
930
 
636
931
  });
637
932
 
933
+ it("aligns a forked workflow child session with its managed worktree cwd", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
934
+ execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
935
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
936
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
937
+ fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
938
+ execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
939
+ execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
940
+
941
+ const parentSessionFile = path.join(mockPi.dir, "parent-session.jsonl");
942
+ const childSessionFile = path.join(mockPi.dir, "forked-child-session.jsonl");
943
+ fs.writeFileSync(parentSessionFile, `${JSON.stringify({ type: "session", version: 3, id: "parent", cwd: tempDir })}\n`, "utf-8");
944
+ const ctx = makeMinimalCtx(tempDir);
945
+ Object.assign(ctx.sessionManager, {
946
+ getSessionFile: () => parentSessionFile,
947
+ getLeafId: () => "parent-leaf",
948
+ openSession: () => ({
949
+ createBranchedSession: () => {
950
+ fs.writeFileSync(childSessionFile, `${JSON.stringify({ type: "session", version: 3, id: "child", cwd: tempDir })}\n`, "utf-8");
951
+ return childSessionFile;
952
+ },
953
+ }),
954
+ });
955
+ mockPi.onCall({ output: "isolated fork child" });
956
+ const executor = makeExecutor([makeAgent("worker", { defaultContext: "fork" })]);
957
+
958
+ const result = await executor.execute(
959
+ "forked-worktree-workflow",
960
+ { async: false, workflowScript: `return runs.run("isolated", { agent: "worker", task: "Work in isolation", worktree: true });` },
961
+ new AbortController().signal,
962
+ undefined,
963
+ ctx,
964
+ );
965
+
966
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
967
+ const workflowValue = result.details.workflow?.value as { artifactPaths?: string[] } | undefined;
968
+ const handoffPath = workflowValue?.artifactPaths?.find((candidate) => candidate.endsWith(".json") && candidate.includes("handoffs"));
969
+ assert.ok(handoffPath, JSON.stringify(workflowValue));
970
+ const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
971
+ groups: Array<{ cleanup: { tasks: Array<{ path: string }> } }>;
972
+ };
973
+ const managedWorktreeCwd = handoff.groups[0]?.cleanup.tasks[0]?.path;
974
+ assert.ok(managedWorktreeCwd);
975
+ const callCwd = readCall().cwd;
976
+ assert.ok(callCwd);
977
+ assert.notEqual(path.resolve(callCwd), path.resolve(tempDir));
978
+ assert.equal(path.basename(callCwd), path.basename(managedWorktreeCwd));
979
+ const sessionHeader = JSON.parse(fs.readFileSync(childSessionFile, "utf-8").split("\n", 1)[0]!) as { cwd?: string };
980
+ assert.ok(sessionHeader.cwd);
981
+ assert.equal(path.basename(sessionHeader.cwd), path.basename(callCwd));
982
+ });
983
+
638
984
  it("lets runs.all siblings settle when one child fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
639
985
  mockPi.onCall({ exitCode: 1, stderr: "first child failed" });
640
986
  mockPi.onCall({ output: "second child completed" });
@@ -669,6 +1015,88 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
669
1015
  assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state !== "started").map(({ state }) => state).sort(), ["completed", "failed"]);
670
1016
  });
671
1017
 
1018
+ it("runs a direct child gate as host-verified acceptance", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1019
+ const markerFile = "direct-gate.txt";
1020
+ const markerPath = path.join(tempDir, markerFile);
1021
+ mockPi.onCall({ output: [
1022
+ "done",
1023
+ "```acceptance-report",
1024
+ JSON.stringify({
1025
+ criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "implemented" }],
1026
+ changedFiles: ["src/file.ts"],
1027
+ testsAddedOrUpdated: ["test/file.test.ts"],
1028
+ commandsRun: [{ command: "npm test", result: "passed", summary: "passed" }],
1029
+ validationOutput: ["tests passed"],
1030
+ residualRisks: [],
1031
+ noStagedFiles: true,
1032
+ }),
1033
+ "```",
1034
+ ].join("\n") });
1035
+ const executor = makeExecutor([makeAgent("echo")]);
1036
+
1037
+ const result = await executor.execute(
1038
+ "direct-gate",
1039
+ { async: false, agent: "echo", task: "Validate the result without edits", gate: `${process.execPath} -e "require('node:fs').writeFileSync('${markerFile}','verified')"` },
1040
+ new AbortController().signal,
1041
+ undefined,
1042
+ makeMinimalCtx(tempDir),
1043
+ );
1044
+
1045
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "direct gate failed");
1046
+ assert.equal(fs.readFileSync(markerPath, "utf-8"), "verified");
1047
+ assert.equal(result.details.results[0]?.acceptance?.status, "verified");
1048
+ assert.equal(result.details.results[0]?.acceptance?.verifyRuns[0]?.id, "gate");
1049
+ });
1050
+
1051
+ it("lets runs.all siblings settle when one verified gate fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1052
+ const acceptedReport = [
1053
+ "done",
1054
+ "```acceptance-report",
1055
+ JSON.stringify({
1056
+ criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "implemented" }],
1057
+ changedFiles: ["src/file.ts"],
1058
+ testsAddedOrUpdated: ["test/file.test.ts"],
1059
+ commandsRun: [{ command: "npm test", result: "passed", summary: "passed" }],
1060
+ validationOutput: ["tests passed"],
1061
+ residualRisks: [],
1062
+ noStagedFiles: true,
1063
+ }),
1064
+ "```",
1065
+ ].join("\n");
1066
+ mockPi.onCall({ output: acceptedReport });
1067
+ mockPi.onCall({ output: acceptedReport });
1068
+ const executor = makeExecutor([makeAgent("echo")]);
1069
+
1070
+ const result = await executor.execute(
1071
+ "scripted-workflow-gates",
1072
+ {
1073
+ async: false,
1074
+ workflowScript: `
1075
+ const children = await runs.all([
1076
+ { key: "fails-gate", agent: "echo", task: "First task", gate: ${JSON.stringify(`${process.execPath} -e "process.exit(7)"`)} },
1077
+ { key: "passes-gate", agent: "echo", task: "Second task", gate: ${JSON.stringify(`${process.execPath} -e "process.exit(0)"`)} }
1078
+ ]);
1079
+ return children.map(({ key, ok }) => ({ key, ok }));
1080
+ `,
1081
+ },
1082
+ new AbortController().signal,
1083
+ undefined,
1084
+ makeMinimalCtx(tempDir),
1085
+ );
1086
+
1087
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1088
+ assert.equal(mockPi.callCount(), 2);
1089
+ assert.deepEqual(result.details.workflow?.value, [
1090
+ { key: "fails-gate", ok: false },
1091
+ { key: "passes-gate", ok: true },
1092
+ ]);
1093
+ const [failed, passed] = result.details.results;
1094
+ assert.equal(failed?.acceptance?.status, "rejected");
1095
+ assert.equal(failed?.acceptance?.verifyRuns[0]?.status, "failed");
1096
+ assert.equal(passed?.acceptance?.status, "verified");
1097
+ assert.equal(passed?.acceptance?.verifyRuns[0]?.status, "passed");
1098
+ });
1099
+
672
1100
  it("gives parallel workflow children separate managed worktrees and durable handoffs", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
673
1101
  execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
674
1102
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
@@ -678,7 +1106,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
678
1106
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
679
1107
  mockPi.onCall({ output: "feature a", writeFiles: [{ path: "feature-a.txt", content: "a\n" }] });
680
1108
  mockPi.onCall({ output: "feature b", writeFiles: [{ path: "feature-b.txt", content: "b\n" }] });
681
- const executor = makeExecutor([makeAgent("builder")]);
1109
+ const executor = makeExecutor([makeAgent("worker")]);
682
1110
 
683
1111
  const result = await executor.execute(
684
1112
  "scripted-workflow-worktrees",
@@ -686,8 +1114,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
686
1114
  async: false,
687
1115
  workflowScript: `
688
1116
  const children = await runs.all([
689
- { key: "feature-a", agent: "builder", task: "Implement A", worktree: true },
690
- { key: "feature-b", agent: "builder", task: "Implement B", worktree: true }
1117
+ { key: "feature-a", agent: "worker", task: "Implement A", worktree: true },
1118
+ { key: "feature-b", agent: "worker", task: "Implement B", worktree: true }
691
1119
  ]);
692
1120
  return children.map(({ key, artifactPaths }) => ({ key, artifactPaths }));
693
1121
  `,
@@ -726,6 +1154,87 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
726
1154
  assert.match(result.content[0]?.text ?? "", /handoffs/);
727
1155
  });
728
1156
 
1157
+ it("preserves a workflow worktree when its child detaches for supervisor coordination", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
1158
+ execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
1159
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
1160
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
1161
+ fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
1162
+ execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
1163
+ execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
1164
+ mockPi.onCall({
1165
+ steps: [
1166
+ { jsonl: [events.toolStart("contact_supervisor", { reason: "need_decision", message: "Need a decision" })] },
1167
+ { delay: 500, jsonl: [events.assistantMessage("done after coordination")] },
1168
+ ],
1169
+ });
1170
+ const piEvents = createEventBus();
1171
+ const executor = makeExecutor(
1172
+ [makeAgent("worker", { systemPrompt: "Intercom orchestration channel:" })],
1173
+ {},
1174
+ false,
1175
+ undefined,
1176
+ true,
1177
+ new Map(),
1178
+ undefined,
1179
+ undefined,
1180
+ piEvents,
1181
+ );
1182
+ let detachAccepted = false;
1183
+ piEvents.on(INTERCOM_DETACH_RESPONSE_EVENT, (payload) => {
1184
+ if ((payload as { requestId?: unknown }).requestId === "workflow-worktree-detach") {
1185
+ detachAccepted ||= (payload as { accepted?: unknown }).accepted === true;
1186
+ }
1187
+ });
1188
+ const detachTimer = setInterval(() => {
1189
+ if (!detachAccepted) piEvents.emit(INTERCOM_DETACH_REQUEST_EVENT, { requestId: "workflow-worktree-detach" });
1190
+ }, 10);
1191
+ detachTimer.unref();
1192
+
1193
+ const result = await executor.execute(
1194
+ "scripted-workflow-detached-worktree",
1195
+ {
1196
+ async: false,
1197
+ workflowScript: `
1198
+ const children = await runs.all([
1199
+ { key: "detaches", agent: "worker", task: "Ask then continue", worktree: true }
1200
+ ]);
1201
+ return children.map(({ key, ok, artifactPaths }) => ({ key, ok, artifactPaths }));
1202
+ `,
1203
+ },
1204
+ new AbortController().signal,
1205
+ undefined,
1206
+ makeMinimalCtx(tempDir),
1207
+ );
1208
+ clearInterval(detachTimer);
1209
+
1210
+ assert.equal(detachAccepted, true);
1211
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1212
+ assert.match(result.content[0]?.text ?? "", /run detaches: failed/);
1213
+ const workflowValue = result.details.workflow?.value as Array<{ ok: boolean; artifactPaths: string[] }>;
1214
+ assert.equal(workflowValue[0]?.ok, false);
1215
+ const handoffPath = workflowValue[0]?.artifactPaths.find((candidate) => candidate.endsWith(".json"));
1216
+ assert.ok(handoffPath, result.content[0]?.text ?? "missing pending handoff");
1217
+ const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
1218
+ groups: Array<{
1219
+ cleanup: { state: string; tasks: Array<{ path: string; branch: string; preserved: boolean; worktreeRemoved: boolean; branchRemoved: boolean }> };
1220
+ }>;
1221
+ };
1222
+ const cleanup = handoff.groups[0]?.cleanup;
1223
+ assert.equal(cleanup?.state, "partial");
1224
+ assert.equal(cleanup?.tasks[0]?.preserved, true);
1225
+ assert.equal(cleanup?.tasks[0]?.worktreeRemoved, false);
1226
+ assert.equal(cleanup?.tasks[0]?.branchRemoved, false);
1227
+ const worktreePath = cleanup?.tasks[0]?.path;
1228
+ const branch = cleanup?.tasks[0]?.branch;
1229
+ assert.ok(worktreePath);
1230
+ assert.ok(branch);
1231
+ assert.equal(fs.existsSync(worktreePath), true, "live detached worktree must remain present");
1232
+
1233
+ await new Promise((resolve) => setTimeout(resolve, 750));
1234
+ execFileSync("git", ["worktree", "remove", "--force", worktreePath], { cwd: tempDir });
1235
+ execFileSync("git", ["branch", "-D", branch], { cwd: tempDir, stdio: "ignore" });
1236
+ });
1237
+
729
1238
  it("inherits workflow-level worktree isolation and allows a child opt-out", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
730
1239
  execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
731
1240
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
@@ -735,7 +1244,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
735
1244
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
736
1245
  mockPi.onCall({ output: "isolated", writeFiles: [{ path: "isolated.txt", content: "isolated\n" }] });
737
1246
  mockPi.onCall({ output: "shared", writeFiles: [{ path: "shared.txt", content: "shared\n" }] });
738
- const executor = makeExecutor([makeAgent("builder")]);
1247
+ const executor = makeExecutor([makeAgent("worker")]);
739
1248
 
740
1249
  const result = await executor.execute(
741
1250
  "scripted-workflow-worktree-default",
@@ -743,8 +1252,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
743
1252
  async: false,
744
1253
  worktree: true,
745
1254
  workflowScript: `
746
- const isolated = await runs.run("isolated", { agent: "builder", task: "Isolated" });
747
- const shared = await runs.run("shared", { agent: "builder", task: "Shared", worktree: false });
1255
+ const isolated = await runs.run("isolated", { agent: "worker", task: "Isolated" });
1256
+ const shared = await runs.run("shared", { agent: "worker", task: "Shared", worktree: false });
748
1257
  return { isolated: isolated.artifactPaths, shared: shared.artifactPaths };
749
1258
  `,
750
1259
  },
@@ -762,6 +1271,79 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
762
1271
  assert.equal(fs.existsSync(handoffPaths[0]!), true);
763
1272
  });
764
1273
 
1274
+ it("supports dynamic parallel phases followed by sequential worktree children", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
1275
+ execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
1276
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
1277
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
1278
+ fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
1279
+ execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
1280
+ execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
1281
+ mockPi.onCall({ output: "api built", writeFiles: [{ path: "api.txt", content: "api\n" }] });
1282
+ mockPi.onCall({ output: "ui built", writeFiles: [{ path: "ui.txt", content: "ui\n" }] });
1283
+ mockPi.onCall({ output: "joined", writeFiles: [{ path: "joined.txt", content: "joined\n" }] });
1284
+ mockPi.onCall({ output: "shared", writeFiles: [{ path: "shared.txt", content: "shared\n" }] });
1285
+ const executor = makeExecutor([makeAgent("worker")]);
1286
+
1287
+ const result = await executor.execute(
1288
+ "scripted-workflow-dynamic-worktree-phases",
1289
+ {
1290
+ async: false,
1291
+ worktree: true,
1292
+ workflowScript: `
1293
+ const targets = ["api", "ui"];
1294
+ const built = await runs.all(targets.map((target) => ({
1295
+ key: "build-" + target,
1296
+ agent: "worker",
1297
+ task: "Build " + target
1298
+ })));
1299
+ const joined = await runs.run("join", { agent: "worker", task: built.map((child) => child.key).join(",") });
1300
+ const shared = await runs.run("shared", { agent: "worker", task: joined.key, worktree: false });
1301
+ return {
1302
+ built: built.map((child) => ({ key: child.key, artifactPaths: child.artifactPaths })),
1303
+ joined: { key: joined.key, artifactPaths: joined.artifactPaths },
1304
+ shared: shared.key
1305
+ };
1306
+ `,
1307
+ },
1308
+ new AbortController().signal,
1309
+ undefined,
1310
+ makeMinimalCtx(tempDir),
1311
+ );
1312
+
1313
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1314
+ assert.equal(mockPi.callCount(), 4, result.content[0]?.text ?? "workflow produced no output");
1315
+ assert.equal(fs.existsSync(path.join(tempDir, "api.txt")), false);
1316
+ assert.equal(fs.existsSync(path.join(tempDir, "ui.txt")), false);
1317
+ assert.equal(fs.existsSync(path.join(tempDir, "joined.txt")), false);
1318
+ assert.equal(fs.readFileSync(path.join(tempDir, "shared.txt"), "utf-8"), "shared\n");
1319
+
1320
+ const output = result.content[0]?.text ?? "";
1321
+ assert.match(output, /build-api/);
1322
+ assert.match(output, /build-ui/);
1323
+ assert.match(output, /join/);
1324
+ assert.match(output, /shared/);
1325
+ const handoffPaths = [...output.matchAll(/"([^"\n]*\/handoffs\/[^"\n]+\.json)"/g)].map((match) => match[1]!);
1326
+ assert.equal(handoffPaths.length, 3, output);
1327
+ const worktreePaths = new Set<string>();
1328
+ for (const handoffPath of handoffPaths) {
1329
+ const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
1330
+ groups: Array<{
1331
+ children: Array<{ patch: { changed: boolean; path: string } }>;
1332
+ cleanup: { state: string; tasks: Array<{ path: string; worktreeRemoved: boolean; branchRemoved: boolean }> };
1333
+ }>;
1334
+ };
1335
+ assert.equal(handoff.groups.length, 1);
1336
+ assert.equal(handoff.groups[0]?.children[0]?.patch.changed, true);
1337
+ assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true);
1338
+ assert.equal(handoff.groups[0]?.cleanup.state, "complete");
1339
+ assert.equal(handoff.groups[0]?.cleanup.tasks[0]?.worktreeRemoved, true);
1340
+ assert.equal(handoff.groups[0]?.cleanup.tasks[0]?.branchRemoved, true);
1341
+ worktreePaths.add(handoff.groups[0]!.cleanup.tasks[0]!.path);
1342
+ }
1343
+ assert.equal(worktreePaths.size, 3);
1344
+ for (const worktreePath of worktreePaths) assert.equal(fs.existsSync(worktreePath), false);
1345
+ });
1346
+
765
1347
  it("applies a workflow usage budget across scripted child launches", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
766
1348
  mockPi.onCall({ output: "first result" });
767
1349
  const executor = makeExecutor([makeAgent("echo")]);
@@ -884,11 +1466,27 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
884
1466
  );
885
1467
 
886
1468
  assert.equal(result.isError, true);
887
- assert.match(result.content[0]?.text ?? "", /(?:verified.*object form|verify.*at least one command)/i);
1469
+ assert.match(result.content[0]?.text ?? "", /(?:verified.*object form|verify.*at least one runtime command)/i);
888
1470
  }
889
1471
  assert.equal(mockPi.callCount(), 0);
890
1472
  });
891
1473
 
1474
+ it("rejects invalid verified async chain acceptance before spawning", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1475
+ const executor = makeExecutor([makeAgent("echo")]);
1476
+
1477
+ const result = await executor.execute(
1478
+ "invalid-verified-async-chain-acceptance",
1479
+ { chain: [{ agent: "echo", task: "Do work", acceptance: { level: "verified", verify: [] } }], async: true },
1480
+ new AbortController().signal,
1481
+ undefined,
1482
+ makeMinimalCtx(tempDir),
1483
+ );
1484
+
1485
+ assert.equal(result.isError, true);
1486
+ assert.match(result.content[0]?.text ?? "", /verify.*at least one runtime command/i);
1487
+ assert.equal(mockPi.callCount(), 0);
1488
+ });
1489
+
892
1490
  it("rejects unknown action strings at runtime", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
893
1491
  const executor = makeExecutor([makeAgent("echo")]);
894
1492
 
@@ -1053,7 +1651,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1053
1651
  requestId: "registered-a",
1054
1652
  ownerRunId: "owner-delegation",
1055
1653
  nodeId: "node-a",
1056
- agent: "builder",
1654
+ agent: "worker",
1057
1655
  task: "Return literal JSON-looking text",
1058
1656
  context: "fresh",
1059
1657
  cwd: tempDir,
@@ -1065,7 +1663,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1065
1663
  requestId: "registered-b",
1066
1664
  ownerRunId: "owner-delegation",
1067
1665
  nodeId: "node-b",
1068
- agent: "commentator",
1666
+ agent: "reviewer",
1069
1667
  task: "Run the second logical node",
1070
1668
  context: "fresh",
1071
1669
  cwd: tempDir,
@@ -1187,7 +1785,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1187
1785
 
1188
1786
  const status = await executor.execute("status", { action: "status" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1189
1787
 
1190
- assert.match(status.content[0]?.text ?? "", /^Spawn budget: 3\/5 used, 2 remaining/);
1788
+ assert.match(status.content[0]?.text ?? "", /^Status target: active runs\nSpawn budget: 3\/5 used, 2 remaining/);
1191
1789
  assert.deepEqual(status.details?.spawnBudget, {
1192
1790
  used: 3,
1193
1791
  configuredLimit: 4,
@@ -1409,6 +2007,52 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1409
2007
  assert.deepEqual(result.details?.totalCost, { inputTokens: 100, outputTokens: 50, costUsd: 0.001 });
1410
2008
  });
1411
2009
 
2010
+ it("ignores stale foreground control notification contexts after reload", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2011
+ mockPi.onCall({
2012
+ jsonl: [
2013
+ events.assistantMessage("first update"),
2014
+ events.assistantMessage("second update"),
2015
+ ],
2016
+ });
2017
+ const state: SubagentState = {
2018
+ baseCwd: tempDir,
2019
+ currentSessionId: null,
2020
+ asyncJobs: new Map(),
2021
+ foregroundRuns: new Map(),
2022
+ foregroundControls: new Map(),
2023
+ lastForegroundControlId: null,
2024
+ };
2025
+ const staleEvents = {
2026
+ on: createEventBus().on,
2027
+ emit() { throw new Error("This extension ctx is stale after session replacement or reload."); },
2028
+ };
2029
+ const updates: ExecutorToolResult[] = [];
2030
+ const executor = createSubagentExecutor!({
2031
+ pi: { events: staleEvents, getSessionName: () => undefined },
2032
+ state,
2033
+ config: { control: { enabled: true, activeNoticeAfterTurns: 2, activeNoticeAfterMs: 999_999, activeNoticeAfterTokens: 999_999, notifyOn: ["active_long_running"], notifyChannels: ["event"] } },
2034
+ asyncByDefault: false,
2035
+ tempArtifactsDir: tempDir,
2036
+ getSubagentSessionRoot: () => path.join(tempDir, ".pi-subagents", "sessions"),
2037
+ expandTilde: (value: string) => value,
2038
+ discoverAgents: () => ({ agents: [makeAgent("echo")] }),
2039
+ allowMutatingManagementActions: true,
2040
+ });
2041
+
2042
+ const result = await executor.execute(
2043
+ "stale-control-context",
2044
+ { agent: "echo", task: "Investigate behavior", async: false },
2045
+ new AbortController().signal,
2046
+ (update: ExecutorToolResult) => updates.push(update),
2047
+ makeMinimalCtx(tempDir),
2048
+ );
2049
+
2050
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "foreground run failed");
2051
+ assert.equal(result.details.results[0]?.exitCode, 0);
2052
+ const controlEvents = updates.flatMap((update) => update.details?.controlEvents ?? []);
2053
+ assert.equal(controlEvents[0]?.type, "active_long_running");
2054
+ });
2055
+
1412
2056
  it("emits resolved model and thinking for nested foreground starts", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1413
2057
  mockPi.onCall({ output: "nested result" });
1414
2058
  const route = createNestedRoute("root-nested-model");
@@ -1502,10 +2146,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1502
2146
 
1503
2147
  it("fails implementation runs that complete without mutation attempts", async () => {
1504
2148
  mockPi.onCall({ output: "Validation:\nlet rawFilename = params.filename.trim();" });
1505
- const agents = [makeAgent("builder")];
2149
+ const agents = [makeAgent("worker")];
1506
2150
  const controlEvents: Array<{ message: string }> = [];
1507
2151
 
1508
- const result = await runSync(tempDir, agents, "builder", "Implement the approved file changes", {
2152
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
1509
2153
  runId: "guard-run",
1510
2154
  onControlEvent: (event: { message: string }) => controlEvents.push(event),
1511
2155
  });
@@ -1515,18 +2159,18 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1515
2159
  assert.equal(result.finalOutput, "Validation:\nlet rawFilename = params.filename.trim();");
1516
2160
  assert.equal(result.progress.status, "failed");
1517
2161
  assert.deepEqual(controlEvents.map((event) => event.message), [
1518
- "builder completed without making edits for an implementation task",
2162
+ "worker completed without making edits for an implementation task",
1519
2163
  ]);
1520
2164
  assert.deepEqual(result.controlEvents?.map((event) => event.message), [
1521
- "builder completed without making edits for an implementation task",
2165
+ "worker completed without making edits for an implementation task",
1522
2166
  ]);
1523
2167
  });
1524
2168
 
1525
2169
  it("agent contract v1 reports omitted acceptance separately without injecting a prompt", async () => {
1526
2170
  mockPi.onCall({ output: "Plan only" });
1527
- const agents = [makeAgent("builder", { tools: ["read", "write"] })];
2171
+ const agents = [makeAgent("worker", { tools: ["read", "write"] })];
1528
2172
 
1529
- const result = await runSync(tempDir, agents, "builder", "Implement the approved file changes", {
2173
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
1530
2174
  runId: "v1-no-acceptance",
1531
2175
  agentContract: { version: 1 },
1532
2176
  });
@@ -1543,9 +2187,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1543
2187
 
1544
2188
  it("agent contract v1 keeps acceptance rejection out of execution status", async () => {
1545
2189
  mockPi.onCall({ output: "Done\n```acceptance-report\n{\"criteriaSatisfied\":[{\"id\":\"criterion-1\",\"status\":\"not-satisfied\",\"evidence\":\"no proof\"}]}\n```" });
1546
- const agents = [makeAgent("builder", { tools: ["read"], completionGuard: false })];
2190
+ const agents = [makeAgent("worker", { tools: ["read"], completionGuard: false })];
1547
2191
 
1548
- const result = await runSync(tempDir, agents, "builder", "Summarize the fix", {
2192
+ const result = await runSync(tempDir, agents, "worker", "Summarize the fix", {
1549
2193
  runId: "v1-acceptance-reject",
1550
2194
  agentContract: { version: 1 },
1551
2195
  acceptance: { level: "checked", criteria: ["Return required proof"] },
@@ -1561,9 +2205,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1561
2205
 
1562
2206
  it("agent contract v1 records explicit completion guard as an effect", async () => {
1563
2207
  mockPi.onCall({ output: "Plan only" });
1564
- const agents = [makeAgent("builder", { tools: ["read", "write"], completionGuard: true })];
2208
+ const agents = [makeAgent("worker", { tools: ["read", "write"], completionGuard: true })];
1565
2209
 
1566
- const result = await runSync(tempDir, agents, "builder", "Implement the approved file changes", {
2210
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
1567
2211
  runId: "v1-completion-effect",
1568
2212
  agentContract: { version: 1 },
1569
2213
  });
@@ -1601,6 +2245,36 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1601
2245
  if (child?.artifactPaths?.outputPath) assert.match(fs.readFileSync(child.artifactPaths.outputPath, "utf-8"), /"note": "captured"/);
1602
2246
  });
1603
2247
 
2248
+ it("accepts recovered tool errors before valid structured output but rejects later errors", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2249
+ const recoveredError = { type: "tool_result_end", message: { role: "toolResult", toolName: "read", isError: true, content: [{ type: "text", text: "EISDIR" }] } };
2250
+ const structuredEvents = [
2251
+ { type: "tool_execution_start", toolName: "structured_output", args: { value: { ok: true } } },
2252
+ { type: "tool_result_end", message: { role: "toolResult", toolName: "structured_output", content: [{ type: "text", text: "Structured output captured." }] } },
2253
+ { type: "tool_execution_end", toolName: "structured_output" },
2254
+ ];
2255
+ mockPi.onCall({
2256
+ stdoutRaw: [recoveredError, ...structuredEvents].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
2257
+ structuredOutputCapture: { ok: true },
2258
+ });
2259
+ const executor = makeExecutor([makeAgent("echo")]);
2260
+ const params = { agent: "echo", task: "Return structured data", outputSchema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } }, acceptance: false } as const;
2261
+
2262
+ const recovered = await executor.execute("single-schema-recovered-error", params, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
2263
+
2264
+ assert.equal(recovered.isError, undefined);
2265
+ assert.deepEqual(recovered.details?.results?.[0]?.structuredOutput, { ok: true });
2266
+
2267
+ mockPi.reset();
2268
+ mockPi.onCall({
2269
+ stdoutRaw: [...structuredEvents, recoveredError].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
2270
+ structuredOutputCapture: { ok: true },
2271
+ });
2272
+ const terminal = await executor.execute("single-schema-terminal-error", params, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
2273
+
2274
+ assert.equal(terminal.isError, true);
2275
+ assert.match(terminal.details?.results?.[0]?.error ?? "", /read failed/);
2276
+ });
2277
+
1604
2278
  it("rejects structured output capture files that were not produced by the structured_output tool", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1605
2279
  mockPi.onCall({ output: "spoofed", structuredOutputCapture: { ok: true } });
1606
2280
  const executor = makeExecutor([makeAgent("echo")]);
@@ -1682,11 +2356,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1682
2356
 
1683
2357
  it("returns captured output when the foreground executor fails an implementation run", async () => {
1684
2358
  mockPi.onCall({ output: "Oracle review:\n- finding one\n- finding two" });
1685
- const executor = makeExecutor([makeAgent("commentator")]);
2359
+ const executor = makeExecutor([makeAgent("oracle")]);
1686
2360
 
1687
2361
  const result = await executor.execute(
1688
2362
  "failed-single-output",
1689
- { agent: "commentator", task: "Implement the approved file changes" },
2363
+ { agent: "oracle", task: "Implement the approved file changes" },
1690
2364
  new AbortController().signal,
1691
2365
  undefined,
1692
2366
  makeMinimalCtx(tempDir),
@@ -1701,9 +2375,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1701
2375
 
1702
2376
  it("fails future-tense implementation summaries when no mutation attempt occurred", async () => {
1703
2377
  mockPi.onCall({ output: "I’ll do that now and report back after implementing." });
1704
- const agents = [makeAgent("builder")];
2378
+ const agents = [makeAgent("worker")];
1705
2379
 
1706
- const result = await runSync(tempDir, agents, "builder", "Implement the approved fixes", {
2380
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved fixes", {
1707
2381
  runId: "guard-future-tense",
1708
2382
  });
1709
2383
 
@@ -1761,9 +2435,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1761
2435
  events.assistantMessage("Applied edit"),
1762
2436
  ],
1763
2437
  });
1764
- const agents = [makeAgent("builder")];
2438
+ const agents = [makeAgent("worker")];
1765
2439
 
1766
- const result = await runSync(tempDir, agents, "builder", "Implement the approved file changes", {
2440
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
1767
2441
  runId: "guard-success",
1768
2442
  });
1769
2443
 
@@ -1774,12 +2448,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1774
2448
 
1775
2449
  it("resolves explicit agent aliases to canonical execution names", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1776
2450
  mockPi.onCall({ output: "Implemented" });
1777
- const executor = makeExecutor([makeAgent("builder", { aliases: ["developer"], completionGuard: false })]);
2451
+ const executor = makeExecutor([makeAgent("worker", { aliases: ["developer"], completionGuard: false })]);
1778
2452
 
1779
2453
  const result = await executor.execute("single", { agent: "developer", task: "Implement" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1780
2454
 
1781
2455
  assert.equal(result.isError, undefined);
1782
- assert.equal(result.details?.results[0]?.agent, "builder");
2456
+ assert.equal(result.details?.results[0]?.agent, "worker");
1783
2457
  assert.match(result.content[0]?.text ?? "", /Implemented/);
1784
2458
  });
1785
2459
 
@@ -1832,10 +2506,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1832
2506
  events.assistantMessage("I need to retry the same edit."),
1833
2507
  ],
1834
2508
  });
1835
- const agents = [makeAgent("builder")];
2509
+ const agents = [makeAgent("worker")];
1836
2510
  const controlEvents: NonNullable<RunSyncResult["controlEvents"]> = [];
1837
2511
 
1838
- const result = await runSync(tempDir, agents, "builder", "Implement the approved fixes", {
2512
+ const result = await runSync(tempDir, agents, "worker", "Implement the approved fixes", {
1839
2513
  runId: "run-failures",
1840
2514
  controlConfig: { enabled: true, failedToolAttemptsBeforeAttention: 3, notifyOn: ["active_long_running", "needs_attention"] },
1841
2515
  onControlEvent: (event: NonNullable<RunSyncResult["controlEvents"]>[number]) => controlEvents.push(event),
@@ -1884,12 +2558,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1884
2558
  it("retries a zero-activity startup exit on the same model", async () => {
1885
2559
  mockPi.onCall({ exitCode: 1 });
1886
2560
  mockPi.onCall({ output: "Recovered after startup race" });
1887
- const agents = [makeAgent("builder", {
2561
+ const agents = [makeAgent("worker", {
1888
2562
  model: "openai/gpt-5-mini",
1889
2563
  fallbackModels: ["anthropic/claude-sonnet-4"],
1890
2564
  })];
1891
2565
 
1892
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2566
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1893
2567
  runId: "startup-retry-sync",
1894
2568
  acceptance: false,
1895
2569
  });
@@ -1907,12 +2581,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1907
2581
  it("does not retry a non-zero exit after tool activity", async () => {
1908
2582
  mockPi.onCall({ jsonl: [events.toolStart("read", { path: "package.json" })], exitCode: 1 });
1909
2583
  mockPi.onCall({ output: "must not run" });
1910
- const agents = [makeAgent("builder", {
2584
+ const agents = [makeAgent("worker", {
1911
2585
  model: "openai/gpt-5-mini",
1912
2586
  fallbackModels: ["anthropic/claude-sonnet-4"],
1913
2587
  })];
1914
2588
 
1915
- const result = await runSync(tempDir, agents, "builder", "Read a file", {
2589
+ const result = await runSync(tempDir, agents, "worker", "Read a file", {
1916
2590
  runId: "startup-no-retry-after-tool",
1917
2591
  acceptance: false,
1918
2592
  });
@@ -1925,9 +2599,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1925
2599
  it("does not retry non-SIGKILL signaled child exits", { skip: process.platform === "win32" ? "POSIX child signal reporting is unavailable on Windows" : false }, async () => {
1926
2600
  mockPi.onCall({ signal: "SIGTERM" });
1927
2601
  mockPi.onCall({ output: "must not run" });
1928
- const agents = [makeAgent("builder", { model: "openai/gpt-5-mini" })];
2602
+ const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
1929
2603
 
1930
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2604
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1931
2605
  runId: "startup-no-retry-after-signal",
1932
2606
  acceptance: false,
1933
2607
  });
@@ -1941,9 +2615,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1941
2615
 
1942
2616
  it("prefers signal termination errors over stderr tails", { skip: process.platform === "win32" ? "POSIX child signal reporting is unavailable on Windows" : false }, async () => {
1943
2617
  mockPi.onCall({ stderr: "INFO benign startup line\n", signal: "SIGTERM" });
1944
- const agents = [makeAgent("builder", { model: "openai/gpt-5-mini" })];
2618
+ const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
1945
2619
 
1946
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2620
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1947
2621
  runId: "signal-error-over-stderr",
1948
2622
  acceptance: false,
1949
2623
  });
@@ -1957,9 +2631,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1957
2631
  it("does not retry a child exit with raw stdout diagnostics", async () => {
1958
2632
  mockPi.onCall({ stdoutRaw: "configuration failed before protocol startup\n", exitCode: 1 });
1959
2633
  mockPi.onCall({ output: "must not run" });
1960
- const agents = [makeAgent("builder", { model: "openai/gpt-5-mini" })];
2634
+ const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
1961
2635
 
1962
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2636
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1963
2637
  runId: "startup-no-retry-after-stdout-diagnostic",
1964
2638
  acceptance: false,
1965
2639
  });
@@ -1972,12 +2646,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1972
2646
 
1973
2647
  it("reports an actionable error after startup retries are exhausted", async () => {
1974
2648
  mockPi.onCall({ exitCode: 1 });
1975
- const agents = [makeAgent("builder", {
2649
+ const agents = [makeAgent("worker", {
1976
2650
  model: "openai/gpt-5-mini",
1977
2651
  fallbackModels: ["anthropic/claude-sonnet-4"],
1978
2652
  })];
1979
2653
 
1980
- const result = await runSync(tempDir, agents, "builder", "Do work", {
2654
+ const result = await runSync(tempDir, agents, "worker", "Do work", {
1981
2655
  runId: "startup-retry-exhausted-sync",
1982
2656
  acceptance: false,
1983
2657
  });
@@ -2487,6 +3161,32 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2487
3161
  assert.ok(result.progress.durationMs > 0, "should track duration");
2488
3162
  });
2489
3163
 
3164
+ it("streams progress while a foreground child has not emitted output", async () => {
3165
+ const updates: Array<{ text: string; durationMs: number | undefined }> = [];
3166
+ const releasePath = path.join(tempDir, "release-foreground-progress");
3167
+ mockPi.onCall({ output: "Done", waitForPath: releasePath });
3168
+
3169
+ const runPromise = runSync(tempDir, makeAgentConfigs(["echo"]), "echo", "Task", {
3170
+ onUpdate: (update: { content: Array<{ type: string; text?: string }>; details?: { progress?: ProgressSummary[] } }) => {
3171
+ updates.push({
3172
+ text: update.content[0]?.text ?? "",
3173
+ durationMs: update.details?.progress?.[0]?.durationMs,
3174
+ });
3175
+ },
3176
+ });
3177
+ const deadline = Date.now() + 5_000;
3178
+ while (updates.filter((update) => update.text === "(running...)").length < 2 && Date.now() < deadline) {
3179
+ await new Promise((resolve) => setTimeout(resolve, 50));
3180
+ }
3181
+ fs.writeFileSync(releasePath, "release", "utf-8");
3182
+ const result = await runPromise;
3183
+
3184
+ const runningUpdates = updates.filter((update) => update.text === "(running...)");
3185
+ assert.equal(result.exitCode, 0);
3186
+ assert.ok(runningUpdates.length >= 2, "expected an initial update and a heartbeat before child output");
3187
+ assert.ok((runningUpdates.at(-1)?.durationMs ?? 0) > (runningUpdates[0]?.durationMs ?? 0), "expected heartbeat duration to advance");
3188
+ });
3189
+
2490
3190
  it("tracks live activity updates and exposes artifact paths while running", async () => {
2491
3191
  const updates: Array<{ details?: { results?: Array<{ artifactPaths?: ArtifactPaths }>; progress?: ProgressSummary[] } }> = [];
2492
3192
  mockPi.onCall({
@@ -2563,9 +3263,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2563
3263
  events.assistantMessage("Found 2 files: file1.txt and file2.txt"),
2564
3264
  ],
2565
3265
  });
2566
- const agents = makeAgentConfigs(["explorer"]);
3266
+ const agents = makeAgentConfigs(["scout"]);
2567
3267
 
2568
- const result = await runSync(tempDir, agents, "explorer", "List files", {});
3268
+ const result = await runSync(tempDir, agents, "scout", "List files", {});
2569
3269
 
2570
3270
  assert.equal(result.exitCode, 0);
2571
3271
  const output = getFinalOutput(result.messages);
@@ -2592,13 +3292,13 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2592
3292
 
2593
3293
  it("injects an agent-file-relative local skill into the foreground child prompt", async () => {
2594
3294
  mockPi.onCall({ output: "Done" });
2595
- const agentFile = path.join(tempDir, "agents", "nested", "builder.md");
3295
+ const agentFile = path.join(tempDir, "agents", "nested", "worker.md");
2596
3296
  const skillFile = path.join(path.dirname(agentFile), "skills", "local", "SKILL.md");
2597
3297
  fs.mkdirSync(path.dirname(skillFile), { recursive: true });
2598
3298
  fs.writeFileSync(skillFile, "---\ndescription: local skill description\n---\nLocal skill body\n", "utf-8");
2599
- const agents = [makeAgent("builder", { filePath: agentFile, skills: ["local"], skillPath: ["./skills"] })];
3299
+ const agents = [makeAgent("worker", { filePath: agentFile, skills: ["local"], skillPath: ["./skills"] })];
2600
3300
 
2601
- const result = await runSync(tempDir, agents, "builder", "Task", {});
3301
+ const result = await runSync(tempDir, agents, "worker", "Task", {});
2602
3302
 
2603
3303
  assert.equal(result.exitCode, 0);
2604
3304
  assert.deepEqual(result.skills, ["local"]);
@@ -2622,9 +3322,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2622
3322
  });
2623
3323
 
2624
3324
  it("fails foreground runs on explicit unavailable pi-subagents skill requests without spawning", async () => {
2625
- const agents = [makeAgent("builder")];
3325
+ const agents = [makeAgent("worker")];
2626
3326
 
2627
- const result = await runSync(tempDir, agents, "builder", "Task", { skills: ["pi-subagents"] });
3327
+ const result = await runSync(tempDir, agents, "worker", "Task", { skills: ["pi-subagents"] });
2628
3328
 
2629
3329
  assert.equal(result.exitCode, 1);
2630
3330
  assert.equal(result.error, "Skills not found: pi-subagents");
@@ -2632,9 +3332,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2632
3332
  });
2633
3333
 
2634
3334
  it("fails foreground runs when an agent default requests pi-subagents skill", async () => {
2635
- const agents = [makeAgent("builder", { skills: ["pi-subagents"] })];
3335
+ const agents = [makeAgent("worker", { skills: ["pi-subagents"] })];
2636
3336
 
2637
- const result = await runSync(tempDir, agents, "builder", "Task", {});
3337
+ const result = await runSync(tempDir, agents, "worker", "Task", {});
2638
3338
 
2639
3339
  assert.equal(result.exitCode, 1);
2640
3340
  assert.equal(result.error, "Skills not found: pi-subagents");
@@ -2806,6 +3506,28 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2806
3506
  assert.equal(fs.readFileSync(outputPath, "utf-8"), "fresh assistant output");
2807
3507
  });
2808
3508
 
3509
+ it("top-level reviewer runs do not inherit bundled chain artifact reads", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
3510
+ fs.writeFileSync(path.join(tempDir, "plan.md"), "chain plan");
3511
+ fs.writeFileSync(path.join(tempDir, "progress.md"), "chain progress");
3512
+ mockPi.onCall({ output: "Review done" });
3513
+ const reviewer = discoverAgents(tempDir, "project").agents.find((agent) => agent.name === "reviewer");
3514
+ assert.ok(reviewer, "expected bundled reviewer");
3515
+ assert.equal(reviewer.defaultReads, undefined);
3516
+ const executor = makeExecutor([reviewer]);
3517
+
3518
+ await executor.execute(
3519
+ "single-reviewer-without-chain-artifacts",
3520
+ { agent: "reviewer", task: "Review the supplied files." },
3521
+ new AbortController().signal,
3522
+ undefined,
3523
+ makeMinimalCtx(tempDir),
3524
+ );
3525
+
3526
+ const taskArg = readCallArgs().at(-1) ?? "";
3527
+ assert.doesNotMatch(taskArg, /\[Read from:/);
3528
+ assert.doesNotMatch(taskArg, /plan\.md|progress\.md/);
3529
+ });
3530
+
2809
3531
  it("routes foreground single relative outputs to the run output artifact directory by default", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2810
3532
  mockPi.onCall({ output: "default report" });
2811
3533
  const executor = makeExecutor([makeAgent("researcher", { output: "context.md" })]);
@@ -3310,8 +4032,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3310
4032
  });
3311
4033
 
3312
4034
  mockPi.onCall({ echoEnv: envKeys });
3313
- const nonFanoutAgents = [makeAgent("builder", { tools: ["read"] })];
3314
- const nonFanout = await runSync(tempDir, nonFanoutAgents, "builder", "Task", { runId: "non-fanout-run" });
4035
+ const nonFanoutAgents = [makeAgent("worker", { tools: ["read"] })];
4036
+ const nonFanout = await runSync(tempDir, nonFanoutAgents, "worker", "Task", { runId: "non-fanout-run" });
3315
4037
  assert.equal(nonFanout.exitCode, 0);
3316
4038
  assert.deepEqual(JSON.parse(nonFanout.finalOutput ?? "{}"), {
3317
4039
  SELESAI_SUBAGENT_FANOUT_CHILD: "0",
@@ -3357,9 +4079,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3357
4079
 
3358
4080
  it("fails with actionable diagnostics when a requested extension tool is not loaded", async () => {
3359
4081
  mockPi.onCall({ output: "Model incorrectly claimed success", missingTools: ["fixture_search"] });
3360
- const agents = [makeAgent("extension-builder", { tools: ["read", "fixture_search"], fallbackModels: ["mock/fallback-model"] })];
4082
+ const agents = [makeAgent("extension-worker", { tools: ["read", "fixture_search"], fallbackModels: ["mock/fallback-model"] })];
3361
4083
 
3362
- const result = await runSync(tempDir, agents, "extension-builder", "Use fixture search", { runId: "missing-extension-tool" });
4084
+ const result = await runSync(tempDir, agents, "extension-worker", "Use fixture search", { runId: "missing-extension-tool" });
3363
4085
 
3364
4086
  assert.equal(result.exitCode, 1);
3365
4087
  assert.match(result.error ?? "", /requested unavailable child tools: fixture_search/);
@@ -3585,9 +4307,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3585
4307
  mockAssistantMessage("final wrapped output", "stop"),
3586
4308
  ],
3587
4309
  });
3588
- const agents = makeAgentConfigs(["builder"]);
4310
+ const agents = makeAgentConfigs(["worker"]);
3589
4311
 
3590
- const result = await runSync(tempDir, agents, "builder", "Use the final grace turn to wrap up.", {
4312
+ const result = await runSync(tempDir, agents, "worker", "Use the final grace turn to wrap up.", {
3591
4313
  turnBudget: { maxTurns: 1, graceTurns: 1 },
3592
4314
  runId: "foreground-turn-budget-soft",
3593
4315
  });
@@ -3620,7 +4342,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3620
4342
  },
3621
4343
  ],
3622
4344
  });
3623
- const agents = makeAgentConfigs(["builder"]);
4345
+ const agents = makeAgentConfigs(["worker"]);
3624
4346
  const snapshots: Array<{
3625
4347
  turnBudget?: { outcome?: string; terminationDeferredAtTurn?: number };
3626
4348
  turnBudgetExceeded?: boolean;
@@ -3629,7 +4351,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3629
4351
  status?: string;
3630
4352
  }> = [];
3631
4353
 
3632
- const result = await runSync(tempDir, agents, "builder", "Finish active tool work before enforcing the hard limit.", {
4354
+ const result = await runSync(tempDir, agents, "worker", "Finish active tool work before enforcing the hard limit.", {
3633
4355
  turnBudget: { maxTurns: 1, graceTurns: 0 },
3634
4356
  runId: "foreground-turn-budget-deferred",
3635
4357
  onUpdate(update: { details?: { results?: Array<{ turnBudget?: { outcome?: string; terminationDeferredAtTurn?: number }; turnBudgetExceeded?: boolean; error?: string }>; progress?: Array<{ currentTool?: string; status?: string }> } }) {