@selesai/code 0.13.15 → 0.13.18

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 (473) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +2 -14
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.js +3 -9
  5. package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
  6. package/dist/bun/sandbox-env-setup.d.ts +1 -0
  7. package/dist/bun/sandbox-env-setup.js +3 -0
  8. package/dist/cli/auth-command.d.ts +23 -0
  9. package/dist/cli/auth-command.js +102 -0
  10. package/dist/cli/config-selector.js +2 -1
  11. package/dist/cli/setup.d.ts +1 -0
  12. package/dist/cli/setup.js +11 -0
  13. package/dist/cli.js +2 -14
  14. package/dist/config.d.ts +3 -2
  15. package/dist/config.js +9 -4
  16. package/dist/core/agent-session-runtime.js +15 -7
  17. package/dist/core/agent-session.d.ts +2 -6
  18. package/dist/core/agent-session.js +15 -4
  19. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  20. package/dist/core/compaction/branch-summarization.js +2 -1
  21. package/dist/core/extensions/loader.js +3 -0
  22. package/dist/core/extensions/types.d.ts +16 -2
  23. package/dist/core/http-dispatcher.js +2 -0
  24. package/dist/core/keybindings.d.ts +5 -0
  25. package/dist/core/keybindings.js +4 -0
  26. package/dist/core/messages.d.ts +1 -1
  27. package/dist/core/model-config.d.ts +15 -0
  28. package/dist/core/model-config.js +3 -0
  29. package/dist/core/model-runtime.d.ts +1 -0
  30. package/dist/core/model-runtime.js +5 -2
  31. package/dist/core/provider-composer.js +8 -2
  32. package/dist/core/session-export.d.ts +3 -0
  33. package/dist/core/session-export.js +31 -0
  34. package/dist/core/session-manager.d.ts +3 -2
  35. package/dist/core/session-manager.js +40 -13
  36. package/dist/core/skills.d.ts +1 -1
  37. package/dist/core/skills.js +4 -2
  38. package/dist/core/system-prompt.js +10 -10
  39. package/dist/core/tools/bash.js +3 -117
  40. package/dist/core/tools/edit.d.ts +1 -12
  41. package/dist/core/tools/edit.js +5 -163
  42. package/dist/core/tools/find.js +4 -55
  43. package/dist/core/tools/grep.js +4 -60
  44. package/dist/core/tools/ls.js +4 -49
  45. package/dist/core/tools/read.d.ts +4 -0
  46. package/dist/core/tools/read.js +10 -122
  47. package/dist/core/tools/renderers/bash.d.ts +11 -0
  48. package/dist/core/tools/renderers/bash.js +129 -0
  49. package/dist/core/tools/renderers/edit.d.ts +22 -0
  50. package/dist/core/tools/renderers/edit.js +166 -0
  51. package/dist/core/tools/renderers/find.d.ts +9 -0
  52. package/dist/core/tools/renderers/find.js +63 -0
  53. package/dist/core/tools/renderers/grep.d.ts +9 -0
  54. package/dist/core/tools/renderers/grep.js +68 -0
  55. package/dist/core/tools/renderers/index.d.ts +27 -0
  56. package/dist/core/tools/renderers/index.js +46 -0
  57. package/dist/core/tools/renderers/ls.d.ts +9 -0
  58. package/dist/core/tools/renderers/ls.js +57 -0
  59. package/dist/core/tools/renderers/read.d.ts +10 -0
  60. package/dist/core/tools/renderers/read.js +128 -0
  61. package/dist/core/tools/renderers/write.d.ts +9 -0
  62. package/dist/core/tools/renderers/write.js +152 -0
  63. package/dist/core/tools/write.js +5 -146
  64. package/dist/extensions/capability-gateway/index.ts +12 -1
  65. package/dist/extensions/capability-gateway/integration.test.ts +23 -0
  66. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
  67. package/dist/extensions/pi-hermes-memory/README.md +16 -12
  68. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
  69. package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
  70. package/dist/extensions/pi-hermes-memory/package.json +4 -4
  71. package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
  72. package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
  73. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
  74. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
  75. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
  76. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
  77. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
  78. package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
  79. package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
  80. package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
  83. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
  84. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
  85. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
  86. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
  87. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
  88. package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
  89. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
  90. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
  91. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
  92. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
  93. package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
  94. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
  95. package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
  96. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
  97. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
  98. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
  99. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
  100. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
  101. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
  102. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
  103. package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
  104. package/dist/extensions/pi-intercom/README.md +16 -2
  105. package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
  106. package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
  107. package/dist/extensions/pi-intercom/index.ts +1 -1
  108. package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
  109. package/dist/extensions/pi-intercom/package.json +1 -1
  110. package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
  111. package/dist/extensions/pi-intercom/project-agent.ts +1 -6
  112. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
  113. package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
  114. package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
  115. package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
  116. package/dist/extensions/pi-subagents/README.md +3 -3
  117. package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
  118. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
  119. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  120. package/dist/extensions/pi-subagents/docs/agents.md +32 -16
  121. package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
  122. package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
  123. package/dist/extensions/pi-subagents/docs/missions.md +2 -0
  124. package/dist/extensions/pi-subagents/docs/models.md +58 -1
  125. package/dist/extensions/pi-subagents/docs/observability.md +50 -12
  126. package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
  127. package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
  128. package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
  129. package/dist/extensions/pi-subagents/package-lock.json +747 -177
  130. package/dist/extensions/pi-subagents/package.json +6 -4
  131. package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
  132. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
  133. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  137. package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
  138. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
  139. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  140. package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
  141. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
  142. package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  143. package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
  144. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
  145. package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
  146. package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
  147. package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
  148. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
  149. package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
  150. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
  151. package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
  152. package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
  153. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
  154. package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
  155. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
  156. package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
  157. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
  158. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
  159. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
  160. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
  161. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
  164. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
  165. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
  166. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
  167. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
  168. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
  169. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
  170. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
  171. package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
  172. package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
  173. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
  174. package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
  175. package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
  176. package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
  177. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
  178. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
  179. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
  180. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
  181. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
  182. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
  183. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
  184. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
  185. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
  186. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
  187. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
  188. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  189. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
  190. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
  192. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
  193. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
  194. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
  195. package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
  196. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
  197. package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
  198. package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
  199. package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
  200. package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
  201. package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
  202. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
  203. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
  204. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
  205. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
  206. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
  207. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  208. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
  209. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
  210. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
  211. package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
  212. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
  213. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
  214. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
  215. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
  216. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
  217. package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
  218. package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
  219. package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
  220. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
  221. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
  222. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
  223. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
  224. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
  225. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
  226. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
  227. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
  228. package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
  229. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
  230. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
  231. package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
  232. package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
  233. package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
  234. package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
  235. package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
  236. package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
  237. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
  238. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
  239. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
  240. package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
  241. package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
  242. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
  243. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
  244. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
  245. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
  246. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
  247. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
  248. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
  249. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
  250. package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
  251. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
  253. package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
  254. package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
  255. package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
  256. package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
  257. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
  258. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
  259. package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
  260. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
  261. package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
  262. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
  263. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
  264. package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
  265. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
  266. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
  267. package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
  268. package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
  269. package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
  270. package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
  271. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
  272. package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
  273. package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
  274. package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
  275. package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
  276. package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
  277. package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
  278. package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
  279. package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
  280. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
  281. package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
  282. package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
  283. package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
  284. package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
  285. package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
  286. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
  287. package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
  288. package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
  289. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
  290. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
  291. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
  292. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
  293. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
  294. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
  295. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
  296. package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
  297. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
  298. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
  299. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
  300. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
  301. package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
  302. package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
  303. package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
  304. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
  305. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
  306. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
  307. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
  308. package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
  309. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
  310. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
  311. package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
  312. package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
  313. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
  314. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
  315. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
  316. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
  317. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
  318. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
  319. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
  320. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
  321. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
  322. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
  323. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
  324. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
  325. package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
  326. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
  327. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
  328. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
  329. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
  330. package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
  331. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
  332. package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
  333. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
  334. package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
  335. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
  336. package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
  337. package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
  338. package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
  339. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
  340. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
  341. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
  342. package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
  343. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
  344. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
  345. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
  346. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
  347. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
  348. package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
  349. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
  350. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
  351. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
  352. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
  353. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
  354. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
  355. package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
  356. package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
  357. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
  358. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
  359. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
  360. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
  361. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
  362. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
  363. package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
  364. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
  365. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
  366. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
  367. package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
  368. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
  369. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
  370. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
  371. package/dist/extensions/pi-web-agent/src/backends/config.ts +1 -0
  372. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +5 -3
  373. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +7 -3
  374. package/dist/extensions/pi-web-agent/src/types.ts +1 -1
  375. package/dist/extensions/pi-zentui/README.md +17 -0
  376. package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
  377. package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
  378. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
  379. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
  380. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
  381. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
  382. package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
  383. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
  384. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
  385. package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
  386. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
  387. package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
  388. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
  389. package/dist/extensions/pi-zentui/package-lock.json +277 -517
  390. package/dist/extensions/pi-zentui/package.json +7 -4
  391. package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
  392. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
  393. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
  394. package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
  395. package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
  396. package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
  397. package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
  398. package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
  399. package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
  400. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
  401. package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
  402. package/dist/index.d.ts +1 -1
  403. package/dist/main.js +4 -1
  404. package/dist/modes/interactive/chat-viewport.d.ts +19 -0
  405. package/dist/modes/interactive/chat-viewport.js +27 -0
  406. package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
  407. package/dist/modes/interactive/components/assistant-message.js +30 -13
  408. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  409. package/dist/modes/interactive/components/custom-editor.js +39 -1
  410. package/dist/modes/interactive/components/first-time-setup.js +1 -1
  411. package/dist/modes/interactive/components/index.d.ts +1 -1
  412. package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
  413. package/dist/modes/interactive/components/markdown-transform.js +18 -0
  414. package/dist/modes/interactive/components/model-selector.js +19 -17
  415. package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
  416. package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
  417. package/dist/modes/interactive/components/settings-selector.js +128 -20
  418. package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
  419. package/dist/modes/interactive/components/settings-submenu.js +164 -0
  420. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  421. package/dist/modes/interactive/components/status-indicator.js +10 -3
  422. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  423. package/dist/modes/interactive/components/thinking-selector.js +75 -18
  424. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  425. package/dist/modes/interactive/components/tool-execution.js +35 -33
  426. package/dist/modes/interactive/components/trust-selector.js +2 -2
  427. package/dist/modes/interactive/interactive-mode.d.ts +13 -15
  428. package/dist/modes/interactive/interactive-mode.js +144 -80
  429. package/dist/modes/interactive/session-share.d.ts +15 -0
  430. package/dist/modes/interactive/session-share.js +183 -0
  431. package/dist/modes/interactive/theme/dark.json +2 -1
  432. package/dist/modes/interactive/theme/light.json +2 -1
  433. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  434. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  435. package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
  436. package/dist/modes/interactive/theme/theme-json.js +129 -0
  437. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  438. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  439. package/dist/modes/interactive/theme/theme.js +18 -119
  440. package/dist/modes/interactive/tui-renderer.d.ts +20 -0
  441. package/dist/modes/interactive/tui-renderer.js +65 -0
  442. package/dist/utils/exif-orientation.js +2 -3
  443. package/dist/utils/management-http.d.ts +24 -0
  444. package/dist/utils/management-http.js +53 -0
  445. package/dist/utils/text.d.ts +7 -0
  446. package/dist/utils/text.js +8 -0
  447. package/dist/utils/tools-manager.d.ts +1 -0
  448. package/dist/utils/tools-manager.js +43 -21
  449. package/docs/docs.json +0 -4
  450. package/package.json +4 -4
  451. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
  452. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
  453. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
  454. package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  455. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
  456. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
  457. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
  458. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
  459. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
  460. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
  461. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
  462. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
  463. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
  464. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
  465. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
  466. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
  467. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
  468. package/dist/skills/implanger/SKILL.md +0 -70
  469. package/dist/skills/output/SKILL.md +0 -49
  470. package/dist/skills/planger/SKILL.md +0 -167
  471. package/dist/skills/workflow/SKILL.md +0 -95
  472. package/docs/workflows.md +0 -18
  473. /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
@@ -2,34 +2,18 @@ import assert from "node:assert/strict";
2
2
  import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
- import { afterEach, describe, it } from "node:test";
6
- import { consumeSteerAcks, writeSteerRequestToDir } from "../../src/runs/background/control-channel.ts";
7
- import {
8
- SUBAGENT_CHILD_AGENT_ENV,
9
- SUBAGENT_CHILD_INDEX_ENV,
10
- SUBAGENT_FANOUT_CHILD_ENV,
11
- SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV,
12
- SUBAGENT_ORCHESTRATOR_TARGET_ENV,
13
- SUBAGENT_RUN_ID_ENV,
14
- SUBAGENT_STEER_ACK_DIR_ENV,
15
- SUBAGENT_STEER_CAPABILITY_ENV,
16
- SUBAGENT_STEER_INBOX_ENV,
17
- SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV,
18
- } from "../../src/runs/shared/pi-args.ts";
19
- import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV } from "../../src/runs/shared/runtime-acknowledged-extensions.ts";
20
- import { clearStructuredOutputCaptures, STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "../../src/runs/shared/structured-output.ts";
21
- import { TOOL_BUDGET_ENV } from "../../src/runs/shared/tool-budget.ts";
5
+ import { describe, it } from "node:test";
6
+ import { RUNTIME_EXTENSION_ACK_EVENT } from "../../src/runs/shared/runtime-acknowledged-extensions.ts";
7
+ import { clearStructuredOutputCaptures } from "../../src/runs/shared/structured-output.ts";
22
8
  import { getAgentDir } from "../../src/shared/utils.ts";
23
- import { PERMISSION_POLICY_ENV } from "../../src/runs/shared/permissions.ts";
24
- import { CHILD_TOOL_DIAGNOSTIC_PATH_ENV, formatChildToolDiagnostic, MCP_DIRECT_CHILD_TOOLS_ENV, readChildToolDiagnostic, REQUIRED_CHILD_TOOLS_ENV } from "../../src/runs/shared/tool-availability.ts";
25
- import { CHILD_WATCHDOG_CONFIG_ENV } from "../../src/watchdog/child-status.ts";
9
+ import { formatChildToolDiagnostic, type ChildToolDiagnostic } from "../../src/runs/shared/tool-availability.ts";
10
+ import type { ChildRuntimeConfig } from "../../src/runs/shared/child-runtime-config.ts";
11
+ import type { ChildWatchdogConfig } from "../../src/watchdog/child-status.ts";
26
12
  import { SUBAGENT_WATCHDOG_WARNING_TYPE } from "../../src/watchdog/types.ts";
27
13
  import registerSubagentPromptRuntime, {
28
14
  CHILD_FANOUT_BOUNDARY_INSTRUCTIONS,
29
15
  CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS,
30
- SUBAGENT_INTERCOM_SESSION_NAME_ENV,
31
16
  registerPermissionGate,
32
- registerSteeringInbox,
33
17
  rewriteSubagentPrompt,
34
18
  stripGlobalContext,
35
19
  stripInheritedSkills,
@@ -38,34 +22,34 @@ import registerSubagentPromptRuntime, {
38
22
  stripSubagentOrchestrationSkill,
39
23
  } from "../../src/runs/shared/subagent-prompt-runtime.ts";
40
24
 
41
- const envSnapshot = {
42
- SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT: process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT,
43
- SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT: process.env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT,
44
- SELESAI_SUBAGENT_INHERIT_SKILLS: process.env.SELESAI_SUBAGENT_INHERIT_SKILLS,
45
- SELESAI_SUBAGENT_INTERCOM_SESSION_NAME: process.env.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME,
46
- SELESAI_SUBAGENT_FANOUT_CHILD: process.env.SELESAI_SUBAGENT_FANOUT_CHILD,
47
- SELESAI_SUBAGENT_STEER_INBOX: process.env.SELESAI_SUBAGENT_STEER_INBOX,
48
- SELESAI_SUBAGENT_STEER_CAPABILITY: process.env.SELESAI_SUBAGENT_STEER_CAPABILITY,
49
- SELESAI_SUBAGENT_STEER_ACK_DIR: process.env.SELESAI_SUBAGENT_STEER_ACK_DIR,
50
- SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE: process.env.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE,
51
- SELESAI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA: process.env.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA,
52
- SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE: process.env.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE,
53
- SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED: process.env.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED,
54
- SELESAI_SUBAGENT_RUNTIME_ACKNOWLEDGED_EXTENSIONS: process.env.SELESAI_SUBAGENT_RUNTIME_ACKNOWLEDGED_EXTENSIONS,
55
- SELESAI_SUBAGENT_TOOL_BUDGET: process.env.SELESAI_SUBAGENT_TOOL_BUDGET,
56
- SELESAI_SUBAGENT_PERMISSION_POLICY: process.env.SELESAI_SUBAGENT_PERMISSION_POLICY,
57
- SELESAI_SUBAGENT_REQUIRED_TOOLS: process.env.SELESAI_SUBAGENT_REQUIRED_TOOLS,
58
- SELESAI_SUBAGENT_MCP_DIRECT_TOOLS: process.env.SELESAI_SUBAGENT_MCP_DIRECT_TOOLS,
59
- SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH: process.env.SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH,
60
- SELESAI_SUBAGENT_ORCHESTRATOR_TARGET: process.env.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET,
61
- SELESAI_SUBAGENT_ORCHESTRATOR_SESSION_ID: process.env.SELESAI_SUBAGENT_ORCHESTRATOR_SESSION_ID,
62
- SELESAI_SUBAGENT_SUPERVISOR_CHANNEL_DIR: process.env.SELESAI_SUBAGENT_SUPERVISOR_CHANNEL_DIR,
63
- SELESAI_SUBAGENT_RUN_ID: process.env.SELESAI_SUBAGENT_RUN_ID,
64
- SELESAI_SUBAGENT_CHILD_AGENT: process.env.SELESAI_SUBAGENT_CHILD_AGENT,
65
- SELESAI_SUBAGENT_CHILD_INDEX: process.env.SELESAI_SUBAGENT_CHILD_INDEX,
66
- SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG: process.env.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG,
67
-
68
- };
25
+ function childConfig(overrides: Partial<ChildRuntimeConfig> = {}): ChildRuntimeConfig {
26
+ return { fanoutChild: false, depth: 1, waitTool: { enabled: true }, fast: false, ...overrides };
27
+ }
28
+
29
+ function supervisorConfig(overrides: Partial<ChildRuntimeConfig> = {}): ChildRuntimeConfig {
30
+ return childConfig({
31
+ orchestratorTarget: "subagent-chat-parent",
32
+ orchestratorSessionId: "session-parent",
33
+ supervisorChannelDir: path.join(os.tmpdir(), "subagent-supervisor-runtime-test"),
34
+ runId: "run-123",
35
+ agent: "worker",
36
+ childIndex: 0,
37
+ ...overrides,
38
+ });
39
+ }
40
+
41
+ const watchdogConfig: ChildWatchdogConfig = {
42
+ enabled: true,
43
+ runId: "run-1",
44
+ agent: "worker",
45
+ childIndex: 0,
46
+ watchdogTailTimeoutMs: 1000,
47
+ agentEndTimeoutMs: 500,
48
+ maxWarnings: null,
49
+ lsp: { enabled: false, timeoutMs: 3000, maxFiles: 20, maxDiagnostics: 50 },
50
+ stalemateRepeats: 2,
51
+ cadence: { everyNTools: null },
52
+ } as ChildWatchdogConfig;
69
53
 
70
54
  const SKILLS_SECTION = "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>safe-bash</name>\n <description>desc</description>\n <location>/tmp/SKILL.md</location>\n </skill>\n <skill>\n <name>pi-subagents</name>\n <description>delegate to subagents</description>\n <location>/tmp/pi-subagents/SKILL.md</location>\n </skill>\n</available_skills>";
71
55
 
@@ -86,79 +70,18 @@ const PROMPT_WITH_EXPLICIT_SKILL = [
86
70
 
87
71
  const CONFIGURED_SKILLS_SECTION = "\n\nThe following configured skills are available to this subagent.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>configured-skill</name>\n <description>explicit agent skill</description>\n <location>/tmp/configured-skill/SKILL.md</location>\n </skill>\n</available_skills>";
88
72
 
89
- afterEach(() => {
90
- if (envSnapshot.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT === undefined) delete process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT;
91
- else process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT = envSnapshot.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT;
92
- if (envSnapshot.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT === undefined) delete process.env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT;
93
- else process.env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT = envSnapshot.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT;
94
- if (envSnapshot.SELESAI_SUBAGENT_INHERIT_SKILLS === undefined) delete process.env.SELESAI_SUBAGENT_INHERIT_SKILLS;
95
- else process.env.SELESAI_SUBAGENT_INHERIT_SKILLS = envSnapshot.SELESAI_SUBAGENT_INHERIT_SKILLS;
96
- if (envSnapshot.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME === undefined) delete process.env.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME;
97
- else process.env.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME = envSnapshot.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME;
98
- if (envSnapshot.SELESAI_SUBAGENT_FANOUT_CHILD === undefined) delete process.env.SELESAI_SUBAGENT_FANOUT_CHILD;
99
- else process.env.SELESAI_SUBAGENT_FANOUT_CHILD = envSnapshot.SELESAI_SUBAGENT_FANOUT_CHILD;
100
- if (envSnapshot.SELESAI_SUBAGENT_STEER_INBOX === undefined) delete process.env[SUBAGENT_STEER_INBOX_ENV];
101
- else process.env[SUBAGENT_STEER_INBOX_ENV] = envSnapshot.SELESAI_SUBAGENT_STEER_INBOX;
102
- if (envSnapshot.SELESAI_SUBAGENT_STEER_CAPABILITY === undefined) delete process.env[SUBAGENT_STEER_CAPABILITY_ENV];
103
- else process.env[SUBAGENT_STEER_CAPABILITY_ENV] = envSnapshot.SELESAI_SUBAGENT_STEER_CAPABILITY;
104
- if (envSnapshot.SELESAI_SUBAGENT_STEER_ACK_DIR === undefined) delete process.env[SUBAGENT_STEER_ACK_DIR_ENV];
105
- else process.env[SUBAGENT_STEER_ACK_DIR_ENV] = envSnapshot.SELESAI_SUBAGENT_STEER_ACK_DIR;
106
- if (envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE === undefined) delete process.env[STRUCTURED_OUTPUT_CAPTURE_ENV];
107
- else process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE;
108
- if (envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA === undefined) delete process.env[STRUCTURED_OUTPUT_SCHEMA_ENV];
109
- else process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA;
110
- if (envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE === undefined) delete process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV];
111
- else process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE;
112
- if (envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED === undefined) delete process.env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV];
113
- else process.env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] = envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED;
114
- if (envSnapshot.SELESAI_SUBAGENT_RUNTIME_ACKNOWLEDGED_EXTENSIONS === undefined) delete process.env[RUNTIME_EXTENSION_ACK_PATH_ENV];
115
- else process.env[RUNTIME_EXTENSION_ACK_PATH_ENV] = envSnapshot.SELESAI_SUBAGENT_RUNTIME_ACKNOWLEDGED_EXTENSIONS;
116
- if (envSnapshot.SELESAI_SUBAGENT_TOOL_BUDGET === undefined) delete process.env[TOOL_BUDGET_ENV];
117
- else process.env[TOOL_BUDGET_ENV] = envSnapshot.SELESAI_SUBAGENT_TOOL_BUDGET;
118
- if (envSnapshot.SELESAI_SUBAGENT_PERMISSION_POLICY === undefined) delete process.env[PERMISSION_POLICY_ENV];
119
- else process.env[PERMISSION_POLICY_ENV] = envSnapshot.SELESAI_SUBAGENT_PERMISSION_POLICY;
120
- if (envSnapshot.SELESAI_SUBAGENT_REQUIRED_TOOLS === undefined) delete process.env[REQUIRED_CHILD_TOOLS_ENV];
121
- else process.env[REQUIRED_CHILD_TOOLS_ENV] = envSnapshot.SELESAI_SUBAGENT_REQUIRED_TOOLS;
122
- if (envSnapshot.SELESAI_SUBAGENT_MCP_DIRECT_TOOLS === undefined) delete process.env[MCP_DIRECT_CHILD_TOOLS_ENV];
123
- else process.env[MCP_DIRECT_CHILD_TOOLS_ENV] = envSnapshot.SELESAI_SUBAGENT_MCP_DIRECT_TOOLS;
124
- if (envSnapshot.SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH === undefined) delete process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV];
125
- else process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = envSnapshot.SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH;
126
- if (envSnapshot.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET === undefined) delete process.env[SUBAGENT_ORCHESTRATOR_TARGET_ENV];
127
- else process.env[SUBAGENT_ORCHESTRATOR_TARGET_ENV] = envSnapshot.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET;
128
- if (envSnapshot.SELESAI_SUBAGENT_ORCHESTRATOR_SESSION_ID === undefined) delete process.env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV];
129
- else process.env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV] = envSnapshot.SELESAI_SUBAGENT_ORCHESTRATOR_SESSION_ID;
130
- if (envSnapshot.SELESAI_SUBAGENT_SUPERVISOR_CHANNEL_DIR === undefined) delete process.env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV];
131
- else process.env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV] = envSnapshot.SELESAI_SUBAGENT_SUPERVISOR_CHANNEL_DIR;
132
- if (envSnapshot.SELESAI_SUBAGENT_RUN_ID === undefined) delete process.env[SUBAGENT_RUN_ID_ENV];
133
- else process.env[SUBAGENT_RUN_ID_ENV] = envSnapshot.SELESAI_SUBAGENT_RUN_ID;
134
- if (envSnapshot.SELESAI_SUBAGENT_CHILD_AGENT === undefined) delete process.env[SUBAGENT_CHILD_AGENT_ENV];
135
- else process.env[SUBAGENT_CHILD_AGENT_ENV] = envSnapshot.SELESAI_SUBAGENT_CHILD_AGENT;
136
- if (envSnapshot.SELESAI_SUBAGENT_CHILD_INDEX === undefined) delete process.env[SUBAGENT_CHILD_INDEX_ENV];
137
- else process.env[SUBAGENT_CHILD_INDEX_ENV] = envSnapshot.SELESAI_SUBAGENT_CHILD_INDEX;
138
- if (envSnapshot.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG === undefined) delete process.env[CHILD_WATCHDOG_CONFIG_ENV];
139
- else process.env[CHILD_WATCHDOG_CONFIG_ENV] = envSnapshot.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG;
140
-
141
- });
142
-
143
- function setSupervisorEnv(): void {
144
- process.env[SUBAGENT_ORCHESTRATOR_TARGET_ENV] = "subagent-chat-parent";
145
- process.env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV] = "session-parent";
146
- process.env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV] = path.join(os.tmpdir(), "subagent-supervisor-runtime-test");
147
- process.env[SUBAGENT_RUN_ID_ENV] = "run-123";
148
- process.env[SUBAGENT_CHILD_AGENT_ENV] = "worker";
149
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
150
- }
151
-
152
73
  describe("subagent prompt runtime", () => {
74
+ it("ignores an unconfigured path-based load", () => {
75
+ assert.doesNotThrow(() => registerSubagentPromptRuntime({} as never));
76
+ });
77
+
153
78
  it("registers no permission hook by default and routes ask only to the watchdog arbiter", async () => {
154
79
  const handlers: Array<(event: { toolName?: string; input?: unknown }, ctx?: unknown) => unknown> = [];
155
80
  const pi = { on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx?: unknown) => unknown) { if (event === "tool_call") handlers.push(handler); } };
156
- delete process.env[PERMISSION_POLICY_ENV];
157
- registerPermissionGate(pi as never);
81
+ registerPermissionGate(pi as never, undefined, undefined);
158
82
  assert.equal(handlers.length, 0);
159
83
 
160
- process.env[PERMISSION_POLICY_ENV] = JSON.stringify({ write: "deny" });
161
- registerPermissionGate(pi as never);
84
+ registerPermissionGate(pi as never, { rules: { write: "deny" } }, undefined);
162
85
  assert.equal(handlers.length, 1);
163
86
  assert.equal(await handlers[0]!({ toolName: "bash", input: { command: "rm -rf /" } }), undefined);
164
87
  assert.equal(await handlers[0]!({ toolName: "contact_supervisor", input: {} }), undefined);
@@ -167,10 +90,9 @@ describe("subagent prompt runtime", () => {
167
90
  reason: "Blocked by pi-subagents permission rule: 'write' is denied.",
168
91
  });
169
92
 
170
- process.env[PERMISSION_POLICY_ENV] = JSON.stringify({ write: "ask" });
171
93
  const askHandlers: Array<(event: { toolName?: string; input?: unknown }, ctx: unknown) => unknown> = [];
172
94
  const requests: Array<{ toolName: string; args: unknown }> = [];
173
- registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: unknown) => unknown) { if (event === "tool_call") askHandlers.push(handler); } } as never, async (request) => {
95
+ registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: unknown) => unknown) { if (event === "tool_call") askHandlers.push(handler); } } as never, { rules: { write: "ask" } }, undefined, async (request) => {
174
96
  requests.push({ toolName: request.toolName, args: request.args });
175
97
  return { approved: true, reason: "approved by watchdog", source: "watchdog" };
176
98
  });
@@ -179,19 +101,10 @@ describe("subagent prompt runtime", () => {
179
101
  });
180
102
 
181
103
  it("fails closed when an ask permission decision stalls", async () => {
182
- try {
183
- process.env[PERMISSION_POLICY_ENV] = JSON.stringify({ write: "ask" });
184
- process.env[CHILD_WATCHDOG_CONFIG_ENV] = JSON.stringify({
185
- enabled: true,
186
- watchdogTailTimeoutMs: 1_000,
187
- agentEndTimeoutMs: 5,
188
- maxWarnings: null,
189
- lsp: { enabled: false, timeoutMs: 100, maxFiles: 1, maxDiagnostics: 1 },
190
- stalemateRepeats: 2,
191
- cadence: { everyNTools: null },
192
- });
104
+ {
105
+ const stallingWatchdog = { ...watchdogConfig, agentEndTimeoutMs: 5, lsp: { enabled: false, timeoutMs: 100, maxFiles: 1, maxDiagnostics: 1 } } as ChildWatchdogConfig;
193
106
  const handlers: Array<(event: { toolName?: string; input?: unknown }, ctx: { signal?: AbortSignal }) => unknown> = [];
194
- registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: { signal?: AbortSignal }) => unknown) { if (event === "tool_call") handlers.push(handler); } } as never, async () => new Promise(() => undefined));
107
+ registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: { signal?: AbortSignal }) => unknown) { if (event === "tool_call") handlers.push(handler); } } as never, { rules: { write: "ask" } }, stallingWatchdog, async () => new Promise(() => undefined));
195
108
 
196
109
  const result = await Promise.race([
197
110
  handlers[0]!({ toolName: "write", input: { path: "out.txt" } }, { signal: undefined }),
@@ -203,18 +116,11 @@ describe("subagent prompt runtime", () => {
203
116
  block: true,
204
117
  reason: "Blocked by pi-subagents permission rule: Watchdog permission arbiter failed closed: Watchdog permission decision timed out after 5ms.",
205
118
  });
206
- } finally {
207
- if (envSnapshot.SELESAI_SUBAGENT_PERMISSION_POLICY === undefined) delete process.env[PERMISSION_POLICY_ENV];
208
- else process.env[PERMISSION_POLICY_ENV] = envSnapshot.SELESAI_SUBAGENT_PERMISSION_POLICY;
209
- if (envSnapshot.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG === undefined) delete process.env[CHILD_WATCHDOG_CONFIG_ENV];
210
- else process.env[CHILD_WATCHDOG_CONFIG_ENV] = envSnapshot.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG;
211
119
  }
212
120
  });
213
121
  it("collects runtime extension acknowledgements until terminal serialization", () => {
214
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-runtime-ack-"));
215
- try {
216
- const outputPath = path.join(dir, "acks.json");
217
- process.env[RUNTIME_EXTENSION_ACK_PATH_ENV] = outputPath;
122
+ {
123
+ const acknowledged: string[][] = [];
218
124
  const runtimeHandlers = new Map<string, Array<(payload?: unknown) => unknown>>();
219
125
  const extensionHandlers = new Map<string, Array<(payload?: unknown) => unknown>>();
220
126
  const pushHandler = (target: Map<string, Array<(payload?: unknown) => unknown>>, event: string, handler: (payload?: unknown) => unknown): void => {
@@ -227,7 +133,7 @@ describe("subagent prompt runtime", () => {
227
133
  registerSubagentPromptRuntime({
228
134
  events: { on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(extensionHandlers, event, handler); } },
229
135
  on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(runtimeHandlers, event, handler); },
230
- } as never);
136
+ } as never, childConfig({ runtimeAcknowledgements: (ids) => acknowledged.push(ids) }));
231
137
 
232
138
  emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" });
233
139
  emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" });
@@ -235,21 +141,13 @@ describe("subagent prompt runtime", () => {
235
141
  runtimeHandlers.get("agent_end")?.[0]?.({});
236
142
  emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "late" });
237
143
 
238
- assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, "utf-8")), {
239
- version: 1,
240
- source: "child-runtime",
241
- ids: ["ext.one"],
242
- omitted: 0,
243
- });
244
- } finally {
245
- fs.rmSync(dir, { recursive: true, force: true });
144
+ assert.deepEqual(acknowledged, [["ext.one", "ext.one"]]);
246
145
  }
247
146
  });
248
147
 
249
148
  it("nudges after the tool budget soft limit and blocks configured tools after hard", () => {
250
149
  const handlers = new Map<string, (payload: { toolName?: string }) => unknown>();
251
150
  const sent: string[] = [];
252
- process.env[TOOL_BUDGET_ENV] = JSON.stringify({ soft: 2, hard: 2, block: ["read"] });
253
151
 
254
152
  registerSubagentPromptRuntime({
255
153
  on(event: string, handler: (payload: { toolName?: string }) => unknown) {
@@ -258,7 +156,7 @@ describe("subagent prompt runtime", () => {
258
156
  sendUserMessage(content: string) {
259
157
  sent.push(content);
260
158
  },
261
- } as { on(event: string, handler: (payload: { toolName?: string }) => unknown): void; sendUserMessage(content: string): void });
159
+ } as { on(event: string, handler: (payload: { toolName?: string }) => unknown): void; sendUserMessage(content: string): void }, childConfig({ toolBudget: { soft: 2, hard: 2, block: ["read"] } }));
262
160
 
263
161
  const toolCall = handlers.get("tool_call");
264
162
  assert.ok(toolCall, "tool_call handler should be registered");
@@ -273,436 +171,15 @@ describe("subagent prompt runtime", () => {
273
171
  assert.equal(toolCall({ toolName: "write" }), undefined);
274
172
  });
275
173
 
276
- it("registers the native canonical steering inbox path", () => {
277
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-watch-runtime-"));
278
- try {
279
- const inbox = path.join(dir, "steer");
280
- const nativeInbox = path.join(dir, "native-steer");
281
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
282
- const handlers = new Map<string, (payload?: unknown) => unknown>();
283
- let watchedDir: fs.PathLike | undefined;
284
- const intervalDelays: number[] = [];
285
- const fakeWatcher = { on() { return fakeWatcher; }, close() {} } as fs.FSWatcher;
286
-
287
- registerSteeringInbox({
288
- on(event: string, handler: (payload?: unknown) => unknown) {
289
- handlers.set(event, handler);
290
- },
291
- sendUserMessage() {},
292
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; sendUserMessage(): void }, {
293
- platform: "linux",
294
- nativeRealpath(target) {
295
- assert.equal(target, inbox);
296
- return nativeInbox;
297
- },
298
- watch: ((target: fs.PathLike) => {
299
- watchedDir = target;
300
- return fakeWatcher;
301
- }) as typeof fs.watch,
302
- timers: {
303
- setInterval: ((_handler: Parameters<typeof setInterval>[0], delay?: number) => {
304
- intervalDelays.push(delay ?? 0);
305
- return { unref() {} };
306
- }) as typeof setInterval,
307
- clearInterval: (() => {}) as typeof clearInterval,
308
- },
309
- });
310
-
311
- handlers.get("session_start")?.({});
312
- assert.equal(watchedDir, nativeInbox);
313
- assert.deepEqual(intervalDelays, [5000]);
314
- handlers.get("session_shutdown")?.({});
315
- } finally {
316
- fs.rmSync(dir, { recursive: true, force: true });
317
- }
318
- });
319
-
320
- it("uses polling without native steering watchers on Darwin", () => {
321
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-darwin-runtime-"));
322
- try {
323
- const inbox = path.join(dir, "steer");
324
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
325
- const handlers = new Map<string, (payload?: unknown) => unknown>();
326
- const intervalDelays: number[] = [];
327
- let watchCalls = 0;
328
-
329
- registerSteeringInbox({
330
- on(event: string, handler: (payload?: unknown) => unknown) {
331
- handlers.set(event, handler);
332
- },
333
- sendUserMessage() {},
334
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; sendUserMessage(): void }, {
335
- platform: "darwin",
336
- watch: (() => { watchCalls += 1; throw new Error("Darwin must not use fs.watch."); }) as typeof fs.watch,
337
- timers: {
338
- setInterval: ((_handler: Parameters<typeof setInterval>[0], delay?: number) => {
339
- intervalDelays.push(delay ?? 0);
340
- return { unref() {} };
341
- }) as typeof setInterval,
342
- clearInterval: (() => {}) as typeof clearInterval,
343
- },
344
- });
345
-
346
- handlers.get("session_start")?.({});
347
- assert.equal(watchCalls, 0);
348
- assert.deepEqual(intervalDelays, [250]);
349
- handlers.get("session_shutdown")?.({});
350
- } finally {
351
- fs.rmSync(dir, { recursive: true, force: true });
352
- }
353
- });
354
-
355
- it("delivers steering inbox requests as mid-run user messages", () => {
356
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-runtime-"));
357
- try {
358
- const inbox = path.join(dir, "steer");
359
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
360
- const handlers = new Map<string, (payload?: unknown) => unknown>();
361
- const sent: Array<{ content: string; options: { deliverAs: string } }> = [];
362
-
363
- registerSubagentPromptRuntime({
364
- on(event: string, handler: (payload?: unknown) => unknown) {
365
- handlers.set(event, handler);
366
- },
367
- sendUserMessage(content: string, options: { deliverAs: string }) {
368
- sent.push({ content, options });
369
- },
370
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; sendUserMessage(content: string, options: { deliverAs: string }): void });
371
-
372
- writeSteerRequestToDir(inbox, { type: "steer", id: "steer-1", ts: 1, message: "Focus on tests." });
373
- handlers.get("message_start")?.({});
374
- handlers.get("session_shutdown")?.({});
375
-
376
- assert.equal(sent.length, 1);
377
- assert.equal(sent[0]?.options.deliverAs, "steer");
378
- assert.match(sent[0]?.content ?? "", /Mid-run steering/);
379
- assert.match(sent[0]?.content ?? "", /Focus on tests\./);
380
- assert.deepEqual(fs.readdirSync(inbox).filter((entry) => entry.endsWith(".json")), []);
381
- } finally {
382
- fs.rmSync(dir, { recursive: true, force: true });
383
- }
384
- });
385
-
386
- it("queues follow-ups and acknowledges delivery at the next turn boundary", () => {
387
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-follow-up-runtime-"));
388
- try {
389
- const inbox = path.join(dir, "inbox");
390
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
391
- process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
392
- process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
393
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
394
- const handlers = new Map<string, (payload?: unknown) => unknown>();
395
- const sent: Array<{ content: string; deliverAs?: string }> = [];
396
- registerSteeringInbox({
397
- on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
398
- sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
399
- } as never);
400
- handlers.get("session_start")?.({});
401
- handlers.get("agent_start")?.({});
402
- handlers.get("turn_start")?.({});
403
- writeSteerRequestToDir(inbox, { type: "steer", id: "follow", ts: 1, message: "Check docs.", mode: "follow_up" });
404
- handlers.get("message_start")?.({});
405
- assert.equal(sent[0]?.deliverAs, "followUp");
406
- handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[0]?.content });
407
- assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
408
- handlers.get("turn_end")?.({});
409
- handlers.get("turn_start")?.({});
410
- const delivered = consumeSteerAcks(dir)[0];
411
- assert.equal(delivered?.state, "delivered");
412
- assert.equal(delivered?.deliveryStatus, "delivered");
413
-
414
- writeSteerRequestToDir(inbox, { type: "steer", id: "auto-mid", ts: 2, message: "Mid-turn auto.", mode: "auto" });
415
- handlers.get("message_start")?.({});
416
- assert.equal(sent[1]?.deliverAs, "followUp");
417
- handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[1]?.content });
418
- assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
419
- handlers.get("turn_end")?.({});
420
- handlers.get("turn_start")?.({});
421
- assert.equal(consumeSteerAcks(dir)[0]?.state, "delivered");
422
-
423
- handlers.get("turn_end")?.({});
424
- writeSteerRequestToDir(inbox, { type: "steer", id: "auto-idle", ts: 3, message: "Between-turn auto.", mode: "auto" });
425
- handlers.get("message_start")?.({});
426
- assert.equal(sent[2]?.deliverAs, "steer");
427
- handlers.get("input")?.({ source: "extension", streamingBehavior: "steer", text: sent[2]?.content });
428
- assert.equal(consumeSteerAcks(dir)[0]?.deliveryStatus, "delivered");
429
-
430
- writeSteerRequestToDir(inbox, { type: "steer", id: "undelivered", ts: 4, message: "Never reached.", mode: "follow_up" });
431
- handlers.get("message_start")?.({});
432
- handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[3]?.content });
433
- assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
434
- handlers.get("session_shutdown")?.({});
435
- const failed = consumeSteerAcks(dir)[0];
436
- assert.equal(failed?.state, "failed");
437
- assert.match(failed?.message ?? "", /ended before queued follow-up/);
438
- } finally {
439
- fs.rmSync(dir, { recursive: true, force: true });
440
- }
441
- });
442
-
443
- it("queues auto steering after agent_end until agent_settled", () => {
444
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-settled-steering-runtime-"));
445
- try {
446
- const inbox = path.join(dir, "inbox");
447
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
448
- process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
449
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
450
- const handlers = new Map<string, (payload?: unknown) => unknown>();
451
- const sent: Array<{ content: string; deliverAs?: string }> = [];
452
- registerSteeringInbox({
453
- on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
454
- sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
455
- } as never);
456
- handlers.get("session_start")?.({});
457
- handlers.get("agent_start")?.({});
458
- handlers.get("turn_start")?.({});
459
- handlers.get("turn_end")?.({});
460
- handlers.get("agent_end")?.({ willRetry: false });
461
-
462
- writeSteerRequestToDir(inbox, { type: "steer", id: "settling-auto", ts: 1, message: "Wait for settled.", mode: "auto" });
463
- handlers.get("message_start")?.({});
464
- assert.equal(sent[0]?.deliverAs, "followUp");
465
- handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[0]?.content });
466
- assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
467
-
468
- handlers.get("agent_settled")?.({});
469
- writeSteerRequestToDir(inbox, { type: "steer", id: "settled-auto", ts: 2, message: "Now idle.", mode: "auto" });
470
- handlers.get("message_start")?.({});
471
- assert.equal(sent[1]?.deliverAs, undefined);
472
- handlers.get("input")?.({ source: "extension", text: sent[1]?.content });
473
- assert.equal(consumeSteerAcks(dir)[0]?.deliveryStatus, "delivered");
474
- handlers.get("session_shutdown")?.({});
475
- } finally {
476
- fs.rmSync(dir, { recursive: true, force: true });
477
- }
478
- });
479
-
480
- it("keeps auto steering queued while agent_end will retry", () => {
481
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-retry-steering-runtime-"));
482
- try {
483
- const inbox = path.join(dir, "inbox");
484
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
485
- process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
486
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
487
- const handlers = new Map<string, (payload?: unknown) => unknown>();
488
- const sent: Array<{ content: string; deliverAs?: string }> = [];
489
- registerSteeringInbox({
490
- on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
491
- sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
492
- } as never);
493
- handlers.get("session_start")?.({});
494
- handlers.get("agent_start")?.({});
495
- handlers.get("turn_start")?.({});
496
- handlers.get("turn_end")?.({});
497
- handlers.get("agent_end")?.({ willRetry: true });
498
-
499
- writeSteerRequestToDir(inbox, { type: "steer", id: "retry-auto", ts: 1, message: "Keep this guidance.", mode: "auto" });
500
- handlers.get("message_start")?.({});
501
- assert.equal(sent[0]?.deliverAs, "followUp");
502
- handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[0]?.content });
503
- assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
504
-
505
- handlers.get("turn_start")?.({});
506
- const delivered = consumeSteerAcks(dir)[0];
507
- assert.equal(delivered?.requestId, "retry-auto");
508
- assert.equal(delivered?.deliveryStatus, "delivered");
509
- handlers.get("session_shutdown")?.({});
510
- } finally {
511
- fs.rmSync(dir, { recursive: true, force: true });
512
- }
513
- });
514
-
515
- it("falls back to idle delivery for runtimes without agent_settled", async () => {
516
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-legacy-steering-runtime-"));
517
- try {
518
- const inbox = path.join(dir, "inbox");
519
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
520
- const handlers = new Map<string, (payload?: unknown) => unknown>();
521
- const sent: Array<{ content: string; deliverAs?: string }> = [];
522
- registerSteeringInbox({
523
- on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
524
- sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
525
- } as never, { legacySettleFallbackMs: 5 });
526
- handlers.get("session_start")?.({});
527
- handlers.get("agent_start")?.({});
528
- handlers.get("turn_start")?.({});
529
- handlers.get("turn_end")?.({});
530
- handlers.get("agent_end")?.({ willRetry: false });
531
- await new Promise((resolve) => setTimeout(resolve, 20));
532
-
533
- writeSteerRequestToDir(inbox, { type: "steer", id: "legacy-auto", ts: 1, message: "Legacy idle.", mode: "auto" });
534
- handlers.get("message_start")?.({});
535
- assert.equal(sent[0]?.deliverAs, undefined);
536
- handlers.get("session_shutdown")?.({});
537
- } finally {
538
- fs.rmSync(dir, { recursive: true, force: true });
539
- }
540
- });
541
-
542
- it("does not acknowledge sendUserMessage until the correlated Pi input event arrives", () => {
543
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-ack-runtime-"));
544
- try {
545
- const inbox = path.join(dir, "inbox");
546
- const capability = path.join(dir, "capability.json");
547
- const ackDir = path.join(dir, "control", "steer-acks", "0");
548
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
549
- process.env[SUBAGENT_STEER_CAPABILITY_ENV] = capability;
550
- process.env[SUBAGENT_STEER_ACK_DIR_ENV] = ackDir;
551
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
552
- const handlers = new Map<string, (payload?: unknown) => unknown>();
553
- const sent: string[] = [];
554
- registerSteeringInbox({
555
- on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
556
- sendUserMessage(content: string) { sent.push(content); },
557
- } as never);
558
- handlers.get("session_start")?.({});
559
- writeSteerRequestToDir(inbox, { type: "steer", id: "first", ts: 1, message: "Focus on tests." });
560
- handlers.get("message_start")?.({});
561
- assert.equal(sent.length, 1);
562
- assert.deepEqual(fs.existsSync(ackDir) ? fs.readdirSync(ackDir) : [], []);
563
- handlers.get("input")?.({ source: "extension", streamingBehavior: "steer", text: sent[0] });
564
- const acks = consumeSteerAcks(dir);
565
- assert.equal(acks.length, 1);
566
- assert.equal(acks[0]?.requestId, "first");
567
- assert.equal(acks[0]?.state, "delivered");
568
- assert.equal(acks[0]?.message, "Pi accepted the correlated steering input.");
569
- handlers.get("session_shutdown")?.({});
570
- } finally {
571
- fs.rmSync(dir, { recursive: true, force: true });
572
- }
573
- });
574
-
575
- it("retries pending correlation once as a follow-up after compaction", () => {
576
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-compaction-runtime-"));
577
- try {
578
- const inbox = path.join(dir, "inbox");
579
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
580
- process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
581
- process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
582
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
583
- const handlers = new Map<string, (payload?: unknown) => unknown>();
584
- const sent: Array<{ content: string; deliverAs?: string }> = [];
585
- registerSteeringInbox({
586
- on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
587
- sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
588
- } as never);
589
- handlers.get("session_start")?.({});
590
- writeSteerRequestToDir(inbox, { type: "steer", id: "compact", ts: 1, message: "Keep this guidance." });
591
- handlers.get("message_start")?.({});
592
- assert.equal(sent.length, 1);
593
- assert.deepEqual(consumeSteerAcks(dir), []);
594
-
595
- handlers.get("session_compact")?.({ reason: "manual" });
596
- assert.equal(sent.length, 2);
597
- assert.equal(sent[1]?.deliverAs, "followUp");
598
- handlers.get("input")?.({ source: "extension", text: sent[1]?.content });
599
- assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
600
- handlers.get("turn_start")?.({});
601
- assert.equal(consumeSteerAcks(dir)[0]?.state, "delivered");
602
- handlers.get("session_compact")?.({ reason: "manual" });
603
- assert.equal(sent.length, 2);
604
- handlers.get("session_shutdown")?.({});
605
- } finally {
606
- fs.rmSync(dir, { recursive: true, force: true });
607
- }
608
- });
609
-
610
- it("fails pending correlation when the session shuts down", () => {
611
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-pending-shutdown-runtime-"));
612
- try {
613
- const inbox = path.join(dir, "inbox");
614
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
615
- process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
616
- process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
617
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
618
- const handlers = new Map<string, (payload?: unknown) => unknown>();
619
- registerSteeringInbox({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, sendUserMessage() {} } as never);
620
- handlers.get("session_start")?.({});
621
- writeSteerRequestToDir(inbox, { type: "steer", id: "pending", ts: 1, message: "Unconfirmed guidance." });
622
- handlers.get("message_start")?.({});
623
- handlers.get("session_shutdown")?.({});
624
- const ack = consumeSteerAcks(dir)[0];
625
- assert.equal(ack?.requestId, "pending");
626
- assert.equal(ack?.state, "failed");
627
- assert.match(ack?.message ?? "", /before Pi confirmed steering input delivery/);
628
- } finally {
629
- fs.rmSync(dir, { recursive: true, force: true });
630
- }
631
- });
632
-
633
- it("correlates duplicate guidance FIFO without a visible marker", () => {
634
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-fifo-runtime-"));
635
- try {
636
- const inbox = path.join(dir, "inbox");
637
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
638
- process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
639
- process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
640
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
641
- const handlers = new Map<string, (payload?: unknown) => unknown>();
642
- const sent: string[] = [];
643
- registerSteeringInbox({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, sendUserMessage(content: string) { sent.push(content); } } as never);
644
- handlers.get("session_start")?.({});
645
- writeSteerRequestToDir(inbox, { type: "steer", id: "one", ts: 1, message: "same guidance" });
646
- writeSteerRequestToDir(inbox, { type: "steer", id: "two", ts: 2, message: "same guidance" });
647
- handlers.get("message_start")?.({});
648
- handlers.get("input")?.({ source: "extension", streamingBehavior: "steer", text: sent[0] });
649
- handlers.get("input")?.({ source: "extension", streamingBehavior: "steer", text: sent[1] });
650
- assert.deepEqual(consumeSteerAcks(dir).map((ack) => ack.requestId), ["one", "two"]);
651
- handlers.get("session_shutdown")?.({});
652
- } finally {
653
- fs.rmSync(dir, { recursive: true, force: true });
654
- }
655
- });
656
-
657
- it("publishes an unsupported capability and failed acknowledgments without sendUserMessage", () => {
658
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-unsupported-runtime-"));
659
- try {
660
- const inbox = path.join(dir, "inbox");
661
- process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
662
- process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
663
- process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
664
- process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
665
- const handlers = new Map<string, (payload?: unknown) => unknown>();
666
- registerSteeringInbox({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); } } as never);
667
- handlers.get("session_start")?.({});
668
- assert.equal(JSON.parse(fs.readFileSync(path.join(dir, "capability.json"), "utf-8")).supported, false);
669
- writeSteerRequestToDir(inbox, { type: "steer", id: "unsupported", ts: 1, message: "guidance" });
670
- handlers.get("message_start")?.({});
671
- assert.equal(consumeSteerAcks(dir)[0]?.state, "failed");
672
- handlers.get("session_shutdown")?.({});
673
- } finally {
674
- fs.rmSync(dir, { recursive: true, force: true });
675
- }
676
- });
677
-
678
- it("registers child watchdog lifecycle handlers only when enabled by env", () => {
679
- delete process.env[CHILD_WATCHDOG_CONFIG_ENV];
680
- // Clear the ack capture env explicitly: when this test suite itself runs inside a
681
- // pi-subagents child, the runner sets it and an extra agent_end handler registers.
682
- delete process.env[RUNTIME_EXTENSION_ACK_PATH_ENV];
683
- delete process.env[SUBAGENT_STEER_INBOX_ENV];
684
- delete process.env[SUBAGENT_STEER_CAPABILITY_ENV];
685
- delete process.env[SUBAGENT_STEER_ACK_DIR_ENV];
174
+ it("registers child watchdog lifecycle handlers only when the config enables them", () => {
686
175
  const handlersWithout = new Map<string, unknown[]>();
687
176
  registerSubagentPromptRuntime({
688
177
  on(event: string, handler: unknown) {
689
178
  handlersWithout.set(event, [...(handlersWithout.get(event) ?? []), handler]);
690
179
  },
691
- } as { on(event: string, handler: unknown): void });
180
+ } as { on(event: string, handler: unknown): void }, childConfig());
692
181
  assert.equal(handlersWithout.get("agent_end")?.length ?? 0, 1, "headless auto-drain is always registered");
693
182
 
694
- process.env[CHILD_WATCHDOG_CONFIG_ENV] = JSON.stringify({
695
- enabled: true,
696
- runId: "run-1",
697
- agent: "worker",
698
- childIndex: 0,
699
- watchdogTailTimeoutMs: 1000,
700
- agentEndTimeoutMs: 500,
701
- maxWarnings: null,
702
- lsp: { enabled: false, timeoutMs: 3000, maxFiles: 20, maxDiagnostics: 50 },
703
- stalemateRepeats: 2,
704
- cadence: { everyNTools: null },
705
- });
706
183
  const handlersWith = new Map<string, unknown[]>();
707
184
  registerSubagentPromptRuntime({
708
185
  on(event: string, handler: unknown) {
@@ -712,21 +189,16 @@ describe("subagent prompt runtime", () => {
712
189
  return "off";
713
190
  },
714
191
  sendMessage() {},
715
- } as { on(event: string, handler: unknown): void; getThinkingLevel(): string; sendMessage(): void });
192
+ } as { on(event: string, handler: unknown): void; getThinkingLevel(): string; sendMessage(): void }, childConfig({ childWatchdog: watchdogConfig, watchdogStatus: () => {} }));
716
193
 
717
194
  assert.ok((handlersWith.get("before_agent_start")?.length ?? 0) >= 2);
718
195
  assert.ok((handlersWith.get("turn_end")?.length ?? 0) >= 1);
719
196
  assert.ok((handlersWith.get("agent_end")?.length ?? 0) >= 2, "watchdog and auto-drain both observe agent_end");
720
197
  });
721
198
 
722
- it("registered structured_output tool accepts valid schema output and writes the capture file", async () => {
723
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-structured-runtime-"));
724
- try {
725
- const schemaPath = path.join(dir, "schema.json");
726
- const outputPath = path.join(dir, "output.json");
727
- fs.writeFileSync(schemaPath, JSON.stringify({ type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } }), "utf-8");
728
- process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = schemaPath;
729
- process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = outputPath;
199
+ it("registered structured_output tool accepts valid schema output and captures it", async () => {
200
+ {
201
+ const captured: unknown[] = [];
730
202
  let execute: ((_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }>) | undefined;
731
203
  let parameters: unknown;
732
204
 
@@ -738,7 +210,9 @@ describe("subagent prompt runtime", () => {
738
210
  }
739
211
  },
740
212
  on() {},
741
- } as { registerTool(tool: { name: string; parameters: unknown; execute: (_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }> }): void; on(): void });
213
+ } as { registerTool(tool: { name: string; parameters: unknown; execute: (_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }> }): void; on(): void }, childConfig({
214
+ structuredOutput: { schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } }, capture: (value) => captured.push(value) },
215
+ }));
742
216
 
743
217
  assert.ok(execute, "structured_output tool should be registered");
744
218
  assert.deepEqual(parameters, {
@@ -749,23 +223,13 @@ describe("subagent prompt runtime", () => {
749
223
  });
750
224
  const result = await execute("tool-1", { value: { ok: true } });
751
225
  assert.equal(result.terminate, true);
752
- assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, "utf-8")), { ok: true });
753
- } finally {
754
- fs.rmSync(dir, { recursive: true, force: true });
226
+ assert.deepEqual(captured, [{ ok: true }]);
755
227
  }
756
228
  });
757
229
 
758
230
  it("requires and validates acceptanceReport when structured capture is required", async () => {
759
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-structured-acceptance-"));
760
- try {
761
- const schemaPath = path.join(dir, "schema.json");
762
- const outputPath = path.join(dir, "output.json");
763
- const acceptancePath = path.join(dir, "acceptance.json");
764
- fs.writeFileSync(schemaPath, JSON.stringify({ type: "object" }), "utf-8");
765
- process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = schemaPath;
766
- process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = outputPath;
767
- process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = acceptancePath;
768
- process.env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] = "1";
231
+ {
232
+ const captured: Array<{ value: unknown; acceptanceReport: unknown }> = [];
769
233
  let execute: ((_id: string, params: { value: unknown; acceptanceReport?: unknown }) => Promise<unknown>) | undefined;
770
234
  let parameters: { required?: string[] } | undefined;
771
235
 
@@ -777,29 +241,21 @@ describe("subagent prompt runtime", () => {
777
241
  }
778
242
  },
779
243
  on() {},
780
- } as { registerTool(tool: { name: string; parameters: unknown; execute: typeof execute }): void; on(): void });
244
+ } as { registerTool(tool: { name: string; parameters: unknown; execute: typeof execute }): void; on(): void }, childConfig({
245
+ structuredOutput: { schema: { type: "object" }, acceptanceReport: "required", capture: (value, acceptanceReport) => captured.push({ value, acceptanceReport }) },
246
+ }));
781
247
 
782
248
  assert.deepEqual(parameters?.required, ["value", "acceptanceReport"]);
783
249
  await assert.rejects(execute!("missing", { value: {} }), /Missing acceptanceReport/);
784
250
  await assert.rejects(execute!("empty", { value: {}, acceptanceReport: {} }), /expected at least one acceptance report field/);
785
251
  await execute!("valid", { value: {}, acceptanceReport: { manualNotes: "validated evidence" } });
786
- assert.deepEqual(JSON.parse(fs.readFileSync(acceptancePath, "utf-8")), { manualNotes: "validated evidence" });
787
- } finally {
788
- fs.rmSync(dir, { recursive: true, force: true });
252
+ assert.deepEqual(captured, [{ value: {}, acceptanceReport: { manualNotes: "validated evidence" } }]);
789
253
  }
790
254
  });
791
255
 
792
- it("clears stale optional acceptance reports when structured output omits them", async () => {
793
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-structured-acceptance-stale-"));
794
- try {
795
- const schemaPath = path.join(dir, "schema.json");
796
- const outputPath = path.join(dir, "output.json");
797
- const acceptancePath = path.join(dir, "acceptance.json");
798
- fs.writeFileSync(schemaPath, JSON.stringify({ type: "object" }), "utf-8");
799
- fs.writeFileSync(acceptancePath, JSON.stringify({ manualNotes: "stale" }), "utf-8");
800
- process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = schemaPath;
801
- process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = outputPath;
802
- process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = acceptancePath;
256
+ it("captures an omitted optional acceptance report as undefined", async () => {
257
+ {
258
+ const captured: Array<{ value: unknown; acceptanceReport: unknown }> = [];
803
259
  let execute: ((_id: string, params: { value: unknown }) => Promise<unknown>) | undefined;
804
260
 
805
261
  registerSubagentPromptRuntime({
@@ -807,13 +263,12 @@ describe("subagent prompt runtime", () => {
807
263
  if (tool.name === "structured_output") execute = tool.execute;
808
264
  },
809
265
  on() {},
810
- } as { registerTool(tool: { name: string; execute: typeof execute }): void; on(): void });
266
+ } as { registerTool(tool: { name: string; execute: typeof execute }): void; on(): void }, childConfig({
267
+ structuredOutput: { schema: { type: "object" }, acceptanceReport: "optional", capture: (value, acceptanceReport) => captured.push({ value, acceptanceReport }) },
268
+ }));
811
269
 
812
270
  await execute!("without-report", { value: {} });
813
- assert.equal(fs.existsSync(acceptancePath), false);
814
- assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, "utf-8")), {});
815
- } finally {
816
- fs.rmSync(dir, { recursive: true, force: true });
271
+ assert.deepEqual(captured, [{ value: {}, acceptanceReport: undefined }]);
817
272
  }
818
273
  });
819
274
 
@@ -836,11 +291,8 @@ describe("subagent prompt runtime", () => {
836
291
  });
837
292
 
838
293
  it("scopes local structured_output schema refs under the value parameter", () => {
839
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-structured-refs-"));
840
- try {
841
- const schemaPath = path.join(dir, "schema.json");
842
- const outputPath = path.join(dir, "output.json");
843
- fs.writeFileSync(schemaPath, JSON.stringify({
294
+ {
295
+ const schema = {
844
296
  $defs: { item: { type: "string" } },
845
297
  type: "object",
846
298
  properties: {
@@ -850,9 +302,7 @@ describe("subagent prompt runtime", () => {
850
302
  properties: { label: { $ref: "#/$defs/item" } },
851
303
  },
852
304
  },
853
- }), "utf-8");
854
- process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = schemaPath;
855
- process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = outputPath;
305
+ };
856
306
  let parameters = {} as { properties?: { value?: { properties?: { name?: { $ref?: string }; nested?: { properties?: { label?: { $ref?: string } } } } } } };
857
307
 
858
308
  registerSubagentPromptRuntime({
@@ -860,12 +310,10 @@ describe("subagent prompt runtime", () => {
860
310
  if (tool.name === "structured_output") parameters = tool.parameters as typeof parameters;
861
311
  },
862
312
  on() {},
863
- } as { registerTool(tool: { name: string; parameters: unknown }): void; on(): void });
313
+ } as { registerTool(tool: { name: string; parameters: unknown }): void; on(): void }, childConfig({ structuredOutput: { schema, capture: () => {} } }));
864
314
 
865
315
  assert.equal(parameters.properties?.value?.properties?.name?.$ref, "#/properties/value/$defs/item");
866
316
  assert.equal(parameters.properties?.value?.properties?.nested?.properties?.label?.$ref, "#/properties/value/$defs/item");
867
- } finally {
868
- fs.rmSync(dir, { recursive: true, force: true });
869
317
  }
870
318
  });
871
319
 
@@ -1187,13 +635,10 @@ describe("subagent prompt runtime", () => {
1187
635
  const subagentResult = { role: "toolResult", toolName: "subagent", content: "OK" };
1188
636
  const subagentCall = { role: "assistant", content: [{ type: "toolCall", name: "subagent", input: { agent: "delegate" } }] };
1189
637
  const instruction = { role: "custom", customType: "subagent-orchestration-instructions", content: "Subagent orchestration is enabled." };
1190
- process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
1191
-
1192
- assert.deepEqual(stripParentOnlySubagentMessages([user, subagentCall, subagentResult, instruction]), [user, subagentCall, subagentResult]);
638
+ assert.deepEqual(stripParentOnlySubagentMessages([user, subagentCall, subagentResult, instruction], { preserveFanoutToolHistory: true }), [user, subagentCall, subagentResult]);
1193
639
  });
1194
640
 
1195
641
  it("defers native supervisor registration until runtime events and respects installed pi-intercom tools", async () => {
1196
- setSupervisorEnv();
1197
642
  const handlers = new Map<string, (payload?: unknown) => unknown>();
1198
643
  const registered: string[] = [];
1199
644
 
@@ -1205,7 +650,7 @@ describe("subagent prompt runtime", () => {
1205
650
  registerTool(tool: { name: string }) {
1206
651
  registered.push(tool.name);
1207
652
  },
1208
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void });
653
+ } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig());
1209
654
 
1210
655
  assert.deepEqual(registered, ["bg_wait", "subagent_wait"]);
1211
656
  handlers.get("session_start")?.({});
@@ -1214,15 +659,10 @@ describe("subagent prompt runtime", () => {
1214
659
  });
1215
660
 
1216
661
  it("does not satisfy strict allowlists with native generic intercom", () => {
1217
- setSupervisorEnv();
1218
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-intercom-diagnostic-"));
1219
- try {
1220
- const diagnosticPath = path.join(dir, "tools.json");
662
+ {
663
+ const diagnostics: Array<ChildToolDiagnostic | undefined> = [];
1221
664
  const handlers = new Map<string, (payload?: unknown) => unknown>();
1222
665
  const registered: string[] = [];
1223
- process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"]);
1224
- process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
1225
- process.env[SUBAGENT_CHILD_AGENT_ENV] = "scout";
1226
666
 
1227
667
  registerSubagentPromptRuntime({
1228
668
  on(event: string, handler: (payload?: unknown) => unknown) {
@@ -1232,30 +672,28 @@ describe("subagent prompt runtime", () => {
1232
672
  registerTool(tool: { name: string }) {
1233
673
  registered.push(tool.name);
1234
674
  },
1235
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void });
675
+ } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig({
676
+ agent: "scout",
677
+ requiredTools: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"],
678
+ toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
679
+ }));
1236
680
 
1237
681
  handlers.get("session_start")?.({});
1238
682
  assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]);
1239
683
  assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: read, grep, find, ls, bash, edit, write, intercom/);
1240
- assert.deepEqual(readChildToolDiagnostic(diagnosticPath), {
684
+ assert.deepEqual(diagnostics, [{
1241
685
  agent: "scout",
1242
686
  required: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"],
1243
687
  available: ["bg_wait", "subagent_wait", "contact_supervisor"],
1244
688
  missing: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"],
1245
- });
1246
- } finally {
1247
- fs.rmSync(dir, { recursive: true, force: true });
689
+ }]);
1248
690
  }
1249
691
  });
1250
692
 
1251
693
  it("records missing core write tools from the actual child registry", () => {
1252
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-core-tool-diagnostic-"));
1253
- try {
1254
- const diagnosticPath = path.join(dir, "tools.json");
694
+ {
695
+ const diagnostics: Array<ChildToolDiagnostic | undefined> = [];
1255
696
  const handlers = new Map<string, (payload?: unknown) => unknown>();
1256
- process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "grep", "find", "ls", "bash", "edit", "write"]);
1257
- process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
1258
- process.env[SUBAGENT_CHILD_AGENT_ENV] = "worker";
1259
697
 
1260
698
  registerSubagentPromptRuntime({
1261
699
  on(event: string, handler: (payload?: unknown) => unknown) {
@@ -1263,22 +701,23 @@ describe("subagent prompt runtime", () => {
1263
701
  },
1264
702
  getAllTools: () => ["read", "grep", "find", "ls", "contact_supervisor"].map((name) => ({ name })),
1265
703
  registerTool() {},
1266
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void });
704
+ } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({
705
+ agent: "worker",
706
+ requiredTools: ["read", "grep", "find", "ls", "bash", "edit", "write"],
707
+ toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
708
+ }));
1267
709
 
1268
710
  assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: bash, edit, write/);
1269
- assert.deepEqual(readChildToolDiagnostic(diagnosticPath), {
711
+ assert.deepEqual(diagnostics, [{
1270
712
  agent: "worker",
1271
713
  required: ["read", "grep", "find", "ls", "bash", "edit", "write"],
1272
714
  available: ["read", "grep", "find", "ls", "contact_supervisor"],
1273
715
  missing: ["bash", "edit", "write"],
1274
- });
1275
- } finally {
1276
- fs.rmSync(dir, { recursive: true, force: true });
716
+ }]);
1277
717
  }
1278
718
  });
1279
719
 
1280
720
  it("keeps installed pi-intercom while filling only a missing child contact_supervisor tool", async () => {
1281
- setSupervisorEnv();
1282
721
  const handlers = new Map<string, (payload?: unknown) => unknown>();
1283
722
  const registered: string[] = [];
1284
723
 
@@ -1290,7 +729,7 @@ describe("subagent prompt runtime", () => {
1290
729
  registerTool(tool: { name: string }) {
1291
730
  registered.push(tool.name);
1292
731
  },
1293
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void });
732
+ } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig());
1294
733
 
1295
734
  handlers.get("session_start")?.({});
1296
735
  await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT });
@@ -1299,13 +738,10 @@ describe("subagent prompt runtime", () => {
1299
738
  });
1300
739
 
1301
740
  it("registers only native supervisor tools at runtime when pi-intercom is absent", async () => {
1302
- setSupervisorEnv();
1303
- const previousRequiredTools = process.env[REQUIRED_CHILD_TOOLS_ENV];
1304
- delete process.env[REQUIRED_CHILD_TOOLS_ENV];
1305
741
  const handlers = new Map<string, (payload?: unknown) => unknown>();
1306
742
  const registered: string[] = [];
1307
743
 
1308
- try {
744
+ {
1309
745
  registerSubagentPromptRuntime({
1310
746
  on(event: string, handler: (payload?: unknown) => unknown) {
1311
747
  handlers.set(event, handler);
@@ -1314,28 +750,21 @@ describe("subagent prompt runtime", () => {
1314
750
  registerTool(tool: { name: string }) {
1315
751
  registered.push(tool.name);
1316
752
  },
1317
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void });
753
+ } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig());
1318
754
 
1319
755
  handlers.get("session_start")?.({});
1320
756
  assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]);
1321
757
 
1322
758
  await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT });
1323
759
  assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]);
1324
- } finally {
1325
- if (previousRequiredTools === undefined) delete process.env[REQUIRED_CHILD_TOOLS_ENV];
1326
- else process.env[REQUIRED_CHILD_TOOLS_ENV] = previousRequiredTools;
1327
760
  }
1328
761
  });
1329
762
 
1330
763
  it("records requested tools missing from the child registry after startup hooks settle", async () => {
1331
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-tool-diagnostic-"));
1332
- try {
1333
- const diagnosticPath = path.join(dir, "tools.json");
764
+ {
765
+ const diagnostics: Array<ChildToolDiagnostic | undefined> = [];
1334
766
  const handlers = new Map<string, (payload?: unknown) => unknown>();
1335
767
  const available = ["read"];
1336
- process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "fixture_search"]);
1337
- process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
1338
- process.env[SUBAGENT_CHILD_AGENT_ENV] = "extension-worker";
1339
768
 
1340
769
  registerSubagentPromptRuntime({
1341
770
  on(event: string, handler: (payload?: unknown) => unknown) {
@@ -1343,67 +772,34 @@ describe("subagent prompt runtime", () => {
1343
772
  },
1344
773
  getAllTools: () => available.map((name) => ({ name })),
1345
774
  registerTool() {},
1346
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void });
775
+ } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({
776
+ agent: "extension-worker",
777
+ requiredTools: ["read", "fixture_search"],
778
+ toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
779
+ }));
1347
780
 
1348
781
  const promptRewrite = await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT }) as { systemPrompt?: string } | undefined;
1349
- assert.equal(fs.existsSync(diagnosticPath), false);
782
+ assert.deepEqual(diagnostics, []);
1350
783
  assert.doesNotMatch(promptRewrite?.systemPrompt ?? "", /requested unavailable child tools/);
1351
784
 
1352
785
  assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: fixture_search/);
1353
- assert.deepEqual(readChildToolDiagnostic(diagnosticPath), {
786
+ assert.deepEqual(diagnostics, [{
1354
787
  agent: "extension-worker",
1355
788
  required: ["read", "fixture_search"],
1356
789
  available: ["read"],
1357
790
  missing: ["fixture_search"],
1358
- });
791
+ }]);
1359
792
 
1360
793
  available.push("fixture_search");
1361
794
  handlers.get("agent_start")?.({});
1362
- assert.equal(fs.existsSync(diagnosticPath), false);
1363
- } finally {
1364
- fs.rmSync(dir, { recursive: true, force: true });
1365
- }
1366
- });
1367
-
1368
- it("ignores malformed inherited MCP metadata before strict availability diagnostics", () => {
1369
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-stale-mcp-tool-diagnostic-"));
1370
- try {
1371
- const diagnosticPath = path.join(dir, "tools.json");
1372
- const handlers = new Map<string, (payload?: unknown) => unknown>();
1373
- process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "fixture_search"]);
1374
- process.env[MCP_DIRECT_CHILD_TOOLS_ENV] = "not-json";
1375
- process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
1376
- process.env[SUBAGENT_CHILD_AGENT_ENV] = "worker";
1377
-
1378
- registerSubagentPromptRuntime({
1379
- on(event: string, handler: (payload?: unknown) => unknown) {
1380
- handlers.set(event, handler);
1381
- },
1382
- getAllTools: () => [{ name: "read" }],
1383
- registerTool() {},
1384
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void });
1385
-
1386
- assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: fixture_search/);
1387
- assert.deepEqual(readChildToolDiagnostic(diagnosticPath), {
1388
- agent: "worker",
1389
- required: ["read", "fixture_search"],
1390
- available: ["read"],
1391
- missing: ["fixture_search"],
1392
- });
1393
- } finally {
1394
- fs.rmSync(dir, { recursive: true, force: true });
795
+ assert.equal(diagnostics.at(-1), undefined);
1395
796
  }
1396
797
  });
1397
798
 
1398
799
  it("classifies missing resolved MCP direct tools without softening strict diagnostics", () => {
1399
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-mcp-tool-diagnostic-"));
1400
- try {
1401
- const diagnosticPath = path.join(dir, "tools.json");
800
+ {
801
+ const diagnostics: Array<ChildToolDiagnostic | undefined> = [];
1402
802
  const handlers = new Map<string, (payload?: unknown) => unknown>();
1403
- process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "rust_symbols_workspace_symbols", "fixture_search"]);
1404
- process.env[MCP_DIRECT_CHILD_TOOLS_ENV] = JSON.stringify(["rust_symbols_workspace_symbols"]);
1405
- process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
1406
- process.env[SUBAGENT_CHILD_AGENT_ENV] = "worker";
1407
803
 
1408
804
  registerSubagentPromptRuntime({
1409
805
  on(event: string, handler: (payload?: unknown) => unknown) {
@@ -1411,10 +807,15 @@ describe("subagent prompt runtime", () => {
1411
807
  },
1412
808
  getAllTools: () => [{ name: "read" }],
1413
809
  registerTool() {},
1414
- } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void });
810
+ } as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({
811
+ agent: "worker",
812
+ requiredTools: ["read", "rust_symbols_workspace_symbols", "fixture_search"],
813
+ mcpDirectTools: ["rust_symbols_workspace_symbols"],
814
+ toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
815
+ }));
1415
816
 
1416
817
  assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: rust_symbols_workspace_symbols, fixture_search/);
1417
- const diagnostic = readChildToolDiagnostic(diagnosticPath);
818
+ const diagnostic = diagnostics[0];
1418
819
  assert.deepEqual(diagnostic, {
1419
820
  agent: "worker",
1420
821
  required: ["read", "rust_symbols_workspace_symbols", "fixture_search"],
@@ -1424,16 +825,12 @@ describe("subagent prompt runtime", () => {
1424
825
  });
1425
826
  assert.match(formatChildToolDiagnostic(diagnostic!), /host\/pi-mcp-adapter registration problem/);
1426
827
  assert.match(formatChildToolDiagnostic(diagnostic!), /fixture_search/);
1427
- assert.equal(fs.existsSync(diagnosticPath), true);
1428
- } finally {
1429
- fs.rmSync(dir, { recursive: true, force: true });
1430
828
  }
1431
829
  });
1432
830
 
1433
- it("sets the child intercom session name from env during agent startup", async () => {
831
+ it("sets the child intercom session name from the config during agent startup", async () => {
1434
832
  let sessionName: string | undefined;
1435
833
  let beforeAgentStart: ((event: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) | undefined;
1436
- process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV] = "subagent-worker-78f659a3";
1437
834
 
1438
835
  registerSubagentPromptRuntime({
1439
836
  on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) {
@@ -1443,7 +840,7 @@ describe("subagent prompt runtime", () => {
1443
840
  setSessionName(name: string) {
1444
841
  sessionName = name;
1445
842
  },
1446
- } as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }>; setSessionName(name: string): void });
843
+ } as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }>; setSessionName(name: string): void }, childConfig({ intercomSessionName: "subagent-worker-78f659a3", sessionName: "worker: display name" }));
1447
844
 
1448
845
  await beforeAgentStart?.({ systemPrompt: BASE_PROMPT });
1449
846
 
@@ -1457,11 +854,9 @@ describe("subagent prompt runtime", () => {
1457
854
  if (event === "before_agent_start") beforeAgentStart = handler;
1458
855
  },
1459
856
  getAllTools: () => [{ name: "intercom" }, { name: "contact_supervisor" }],
1460
- } as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> });
857
+ } as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> }, childConfig({ inheritProjectContext: false, inheritGlobalContext: true, inheritSkills: false }));
1461
858
 
1462
859
  assert.ok(beforeAgentStart, "expected before_agent_start handler");
1463
- process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT = "0";
1464
- process.env.SELESAI_SUBAGENT_INHERIT_SKILLS = "0";
1465
860
 
1466
861
  const rewritten = await beforeAgentStart?.({ systemPrompt: BASE_PROMPT });
1467
862
  assert.ok(rewritten);
@@ -1470,18 +865,14 @@ describe("subagent prompt runtime", () => {
1470
865
  assert.ok(rewritten.systemPrompt.includes("Current date: 2026-04-16"));
1471
866
  });
1472
867
 
1473
- it("uses the fanout boundary through before_agent_start when fanout env is set", async () => {
868
+ it("uses the fanout boundary through before_agent_start for a fanout child", async () => {
1474
869
  let beforeAgentStart: ((event: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) | undefined;
1475
870
  registerSubagentPromptRuntime({
1476
871
  on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) {
1477
872
  if (event === "before_agent_start") beforeAgentStart = handler;
1478
873
  },
1479
874
  getAllTools: () => [{ name: "intercom" }, { name: "contact_supervisor" }],
1480
- } as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> });
1481
-
1482
- process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT = "1";
1483
- process.env.SELESAI_SUBAGENT_INHERIT_SKILLS = "1";
1484
- process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
875
+ } as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> }, childConfig({ fanoutChild: true, inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true }));
1485
876
 
1486
877
  const rewritten = await beforeAgentStart?.({ systemPrompt: BASE_PROMPT });
1487
878
  assert.ok(rewritten);
@@ -1494,7 +885,7 @@ describe("subagent prompt runtime", () => {
1494
885
  on(event: string, handler: (payload: { messages: unknown[] }) => { messages: unknown[] } | undefined) {
1495
886
  if (event === "context") contextHandler = handler;
1496
887
  },
1497
- } as { on(event: string, handler: (payload: { messages: unknown[] }) => { messages: unknown[] } | undefined): void });
888
+ } as { on(event: string, handler: (payload: { messages: unknown[] }) => { messages: unknown[] } | undefined): void }, childConfig());
1498
889
 
1499
890
  const priorParentTurn = { role: "user", content: "Earlier we said planner → worker → reviewers → worker." };
1500
891
  const currentTask = { role: "user", content: "Now implement only the assigned fix." };
@@ -1517,7 +908,7 @@ describe("subagent prompt runtime", () => {
1517
908
  on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) {
1518
909
  if (event === "context") contextHandler = handler;
1519
910
  },
1520
- } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void });
911
+ } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig());
1521
912
 
1522
913
  const toolCallId = "call_N7iYNRPXLl9czpXh3bDyMpIL|fc_0e76718634eca88f016a76fdc89aec81919763fa7858f67a0d";
1523
914
  const messages = [
@@ -1542,7 +933,7 @@ describe("subagent prompt runtime", () => {
1542
933
  on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) {
1543
934
  if (event === "context") contextHandler = handler;
1544
935
  },
1545
- } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void });
936
+ } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig());
1546
937
 
1547
938
  const toolCallId = "call_7XJjvAJfk07117JO8LgBCZjY|fc_0e92b09b28010bac016a756e9e79cc8197b01825a5dc3d9eaa";
1548
939
  const messages = [
@@ -1560,7 +951,7 @@ describe("subagent prompt runtime", () => {
1560
951
  on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) {
1561
952
  if (event === "context") contextHandler = handler;
1562
953
  },
1563
- } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void });
954
+ } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig());
1564
955
 
1565
956
  const toolCallId = "call_7XJjvAJfk07117JO8LgBCZjY\nfc_0e92b09b28010bac016a756e9e79cc8197b01825a5dc3d9eaa";
1566
957
  const messages = [
@@ -1580,7 +971,7 @@ describe("subagent prompt runtime", () => {
1580
971
  on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) {
1581
972
  if (event === "context") contextHandler = handler;
1582
973
  },
1583
- } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void });
974
+ } as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig());
1584
975
 
1585
976
  const messages = [
1586
977
  { role: "user", content: "Task" },
@@ -1590,4 +981,4 @@ describe("subagent prompt runtime", () => {
1590
981
 
1591
982
  assert.equal(contextHandler?.({ messages }, {}), undefined);
1592
983
  });
1593
- });
984
+ });