@selesai/code 0.5.19 → 0.5.21

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 (265) hide show
  1. package/dist/extensions/handoff-new.test.ts +8 -9
  2. package/dist/extensions/handoff-new.ts +6 -8
  3. package/dist/extensions/pi-intercom/broker/spawn.test.ts +31 -0
  4. package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
  5. package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
  6. package/dist/extensions/pi-subagents/README.md +602 -220
  7. package/dist/extensions/pi-subagents/banner.png +0 -0
  8. package/dist/extensions/pi-subagents/index.ts +1 -0
  9. package/dist/extensions/pi-subagents/install.mjs +3 -3
  10. package/dist/extensions/pi-subagents/package-lock.json +3405 -0
  11. package/dist/extensions/pi-subagents/package.json +26 -14
  12. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  13. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  14. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
  15. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
  16. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  17. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  18. package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
  20. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
  21. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
  22. package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
  23. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
  24. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  25. package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
  26. package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
  27. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
  28. package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
  29. package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
  30. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
  31. package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
  32. package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
  33. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
  34. package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
  35. package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
  36. package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
  37. package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
  38. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
  39. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
  40. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
  41. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
  42. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
  43. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
  44. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
  45. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
  46. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
  47. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
  48. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
  49. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
  50. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
  51. package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
  52. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
  53. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
  54. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
  55. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
  56. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
  57. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
  58. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
  59. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
  61. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
  62. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
  63. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
  64. package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
  65. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
  66. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
  67. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
  68. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
  69. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
  70. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
  71. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
  72. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
  73. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
  74. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
  75. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
  76. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
  77. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
  78. package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
  79. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
  80. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
  81. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
  84. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
  86. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
  87. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
  88. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
  89. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
  90. package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
  92. package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
  93. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
  94. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
  95. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
  96. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
  97. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
  98. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
  99. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
  100. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
  101. package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
  102. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
  103. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
  104. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
  105. package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
  106. package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
  107. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
  108. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
  109. package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
  110. package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
  111. package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
  112. package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
  113. package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
  114. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
  115. package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
  116. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
  117. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
  118. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
  119. package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
  120. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
  121. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
  122. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
  127. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
  128. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
  129. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
  130. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
  131. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
  132. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
  133. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
  134. package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
  135. package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
  136. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
  137. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
  138. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
  139. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
  140. package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
  141. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
  142. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
  143. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
  144. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
  145. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
  146. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
  147. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
  148. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
  149. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
  150. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
  151. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
  152. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
  153. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
  154. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
  155. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
  156. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
  157. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
  158. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
  159. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
  160. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
  161. package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
  162. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
  163. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
  164. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
  165. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
  166. package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
  167. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
  168. package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
  169. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  170. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  171. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
  172. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
  173. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
  175. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
  176. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
  177. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
  178. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
  179. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
  180. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
  181. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
  182. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
  183. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
  184. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
  185. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
  186. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
  187. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
  188. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
  189. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
  190. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
  191. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
  192. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
  193. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
  194. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
  195. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
  196. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
  197. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
  198. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
  199. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
  200. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
  201. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
  202. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
  203. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
  204. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
  205. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
  206. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
  207. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
  208. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
  209. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
  210. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
  211. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
  212. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
  213. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
  214. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
  215. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
  216. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
  217. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
  218. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
  219. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
  220. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
  221. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
  222. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
  223. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
  224. package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
  225. package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
  226. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
  227. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
  228. package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
  229. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
  230. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
  231. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
  232. package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
  233. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
  234. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
  235. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
  236. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
  237. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
  238. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
  239. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
  240. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
  241. package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
  245. package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
  246. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
  247. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
  248. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
  249. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
  250. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
  251. package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
  252. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
  253. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
  254. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
  255. package/dist/extensions/question/batch.ts +39 -0
  256. package/dist/extensions/question/dialog-adapter.ts +23 -1
  257. package/dist/extensions/question/index.ts +237 -7
  258. package/dist/extensions/question/schemas.ts +28 -15
  259. package/dist/extensions/question/tests/batch.test.ts +26 -0
  260. package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
  261. package/dist/extensions/question/tui-adapter.ts +2 -3
  262. package/dist/extensions/question/types.ts +37 -0
  263. package/dist/extensions/question/ui-protocol.ts +3 -3
  264. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  265. package/package.json +2 -1
@@ -4,9 +4,9 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { describe, it } from "node:test";
6
6
  import {
7
- getSelesaiSpawnCommand,
8
- resolveWindowsSelesaiCliScript,
9
- type SelesaiSpawnDeps,
7
+ getPiSpawnCommand,
8
+ resolvePiCliScript,
9
+ type PiSpawnDeps,
10
10
  } from "../../src/runs/shared/pi-spawn.ts";
11
11
 
12
12
  function makeDeps(input: {
@@ -18,7 +18,7 @@ function makeDeps(input: {
18
18
  packageJsonContent?: string;
19
19
  packageEntry?: string;
20
20
  env?: NodeJS.ProcessEnv;
21
- }): SelesaiSpawnDeps {
21
+ }): PiSpawnDeps {
22
22
  const existing = new Set(input.existing ?? []);
23
23
  const packageJsonPath = input.packageJsonPath;
24
24
  const packageJsonContent = input.packageJsonContent;
@@ -41,10 +41,10 @@ function makeDeps(input: {
41
41
  };
42
42
  }
43
43
 
44
- describe("getSelesaiSpawnCommand", () => {
44
+ describe("getPiSpawnCommand", () => {
45
45
  it("honors explicit SELESAI_SUBAGENT_PI_BINARY override on any platform", () => {
46
46
  const args = ["--mode", "json", "Task: check output"];
47
- const result = getSelesaiSpawnCommand(args, {
47
+ const result = getPiSpawnCommand(args, {
48
48
  platform: "win32",
49
49
  execPath: "/usr/local/bin/node",
50
50
  argv1: "/tmp/pi-entry.mjs",
@@ -61,27 +61,56 @@ describe("getSelesaiSpawnCommand", () => {
61
61
 
62
62
  it("ignores a blank SELESAI_SUBAGENT_PI_BINARY override", () => {
63
63
  const args = ["--mode", "json", "Task: check output"];
64
- const result = getSelesaiSpawnCommand(args, {
64
+ const result = getPiSpawnCommand(args, {
65
65
  platform: "darwin",
66
+ argv1: "/missing/host.js",
67
+ existsSync: () => false,
68
+ resolvePackageJson: () => {
69
+ throw new Error("Pi package unavailable");
70
+ },
66
71
  env: { SELESAI_SUBAGENT_PI_BINARY: " " },
67
72
  });
68
73
  assert.deepEqual(result, { command: "selesai", args });
69
74
  });
70
75
 
71
- it("uses plain pi on non-Windows even when argv1 is a runnable JS file", () => {
72
- const argv1 = "/tmp/pi-entry.mjs";
73
- const deps = makeDeps({
74
- platform: "darwin",
75
- execPath: "/usr/local/bin/node",
76
- argv1,
77
- existing: [argv1],
78
- });
76
+ it("accepts the legacy PI_SUBAGENT_PI_BINARY alias", () => {
79
77
  const args = ["--mode", "json", "Task: check output"];
80
- const result = getSelesaiSpawnCommand(args, deps);
81
- assert.deepEqual(result, { command: "selesai", args });
78
+ assert.deepEqual(getPiSpawnCommand(args, {
79
+ env: { PI_SUBAGENT_PI_BINARY: "/usr/local/bin/legacy-wrapper" },
80
+ }), { command: "/usr/local/bin/legacy-wrapper", args });
82
81
  });
83
82
 
84
- it("uses plain pi on non-Windows even when the CLI script can be resolved from package bin", () => {
83
+ for (const platform of ["darwin", "linux", "win32"] as const) {
84
+ it(`uses node + argv1 on ${platform} when argv1 belongs to the Pi package`, () => {
85
+ const tempDir = fs.mkdtempSync(
86
+ path.join(os.tmpdir(), "pi-spawn-argv-entry-"),
87
+ );
88
+ try {
89
+ const argv1 = path.join(tempDir, "dist", "cli.js");
90
+ fs.mkdirSync(path.dirname(argv1), { recursive: true });
91
+ fs.writeFileSync(argv1, "#!/usr/bin/env node\n");
92
+ fs.writeFileSync(
93
+ path.join(tempDir, "package.json"),
94
+ JSON.stringify({ name: "@selesai/code" }),
95
+ );
96
+ const args = ["--mode", "json", 'Task: review "quotes" & pipes | too'];
97
+ const result = getPiSpawnCommand(args, {
98
+ platform,
99
+ execPath: "/usr/local/bin/node",
100
+ argv1,
101
+ env: {},
102
+ });
103
+ assert.deepEqual(result, {
104
+ command: "/usr/local/bin/node",
105
+ args: [fs.realpathSync(argv1), ...args],
106
+ });
107
+ } finally {
108
+ fs.rmSync(tempDir, { recursive: true, force: true });
109
+ }
110
+ });
111
+ }
112
+
113
+ it("uses node + package bin on POSIX when argv1 is not a verified Pi entry", () => {
85
114
  const packageJsonPath = "/opt/pi/package.json";
86
115
  const cliPath = path.resolve(
87
116
  path.dirname(packageJsonPath),
@@ -92,37 +121,130 @@ describe("getSelesaiSpawnCommand", () => {
92
121
  execPath: "/usr/local/bin/node",
93
122
  argv1: "/opt/pi/subagent-runner.ts",
94
123
  packageJsonPath,
95
- packageJsonContent: JSON.stringify({ bin: { selesai: "dist/cli/index.js" } }),
124
+ packageJsonContent: JSON.stringify({ bin: { pi: "dist/cli/index.js" } }),
96
125
  existing: [packageJsonPath, cliPath],
97
126
  });
98
127
  const args = ["-p", "Task: hello"];
99
- const result = getSelesaiSpawnCommand(args, deps);
100
- assert.deepEqual(result, { command: "selesai", args });
128
+ const result = getPiSpawnCommand(args, deps);
129
+ assert.deepEqual(result, {
130
+ command: "/usr/local/bin/node",
131
+ args: [cliPath, ...args],
132
+ });
101
133
  });
102
134
 
103
- it("falls back to plain pi command on non-Windows when CLI script cannot be resolved", () => {
135
+ it("falls back to plain selesai command on POSIX when CLI script cannot be resolved", () => {
104
136
  const args = ["--mode", "json", "Task: check output"];
105
- const result = getSelesaiSpawnCommand(args, { platform: "darwin", env: {} });
137
+ const result = getPiSpawnCommand(args, {
138
+ platform: "darwin",
139
+ argv1: "/missing/host.js",
140
+ existsSync: () => false,
141
+ resolvePackageJson: () => {
142
+ throw new Error("Pi package unavailable");
143
+ },
144
+ env: {},
145
+ });
106
146
  assert.deepEqual(result, { command: "selesai", args });
107
147
  });
108
148
 
109
- it("uses node + argv1 script on Windows when argv1 is runnable JS", () => {
110
- const argv1 = "/tmp/pi-entry.mjs";
111
- const deps = makeDeps({
112
- platform: "win32",
113
- execPath: "/usr/local/bin/node",
114
- argv1,
115
- existing: [argv1],
116
- });
117
- const args = [
118
- "--mode",
119
- "json",
120
- 'Task: Read C:/dev/file.md and review "quotes" & pipes | too',
121
- ];
122
- const result = getSelesaiSpawnCommand(args, deps);
123
- assert.equal(result.command, "/usr/local/bin/node");
124
- assert.equal(result.args[0], argv1);
125
- assert.equal(result.args[3], args[2]);
149
+ it("ignores embedded host entry points and resolves the Pi package bin on every platform", () => {
150
+ const tempDir = fs.mkdtempSync(
151
+ path.join(os.tmpdir(), "pi-spawn-embedded-host-"),
152
+ );
153
+ try {
154
+ const hostRoot = path.join(tempDir, "pi-web");
155
+ const hostEntry = path.join(hostRoot, "dist", "server.js");
156
+ const hostPackageJson = path.join(hostRoot, "package.json");
157
+ const piRoot = path.join(
158
+ tempDir,
159
+ "node_modules",
160
+ "@earendil-works",
161
+ "pi-coding-agent",
162
+ );
163
+ const piCli = path.join(piRoot, "dist", "cli.js");
164
+ fs.mkdirSync(path.dirname(hostEntry), { recursive: true });
165
+ fs.mkdirSync(path.dirname(piCli), { recursive: true });
166
+ fs.writeFileSync(hostEntry, "export {};\n");
167
+ fs.writeFileSync(
168
+ hostPackageJson,
169
+ JSON.stringify({ name: "@jmfederico/pi-web" }),
170
+ );
171
+ fs.writeFileSync(piCli, "#!/usr/bin/env node\n");
172
+ fs.writeFileSync(
173
+ path.join(piRoot, "package.json"),
174
+ JSON.stringify({
175
+ name: "@selesai/code",
176
+ bin: { pi: "dist/cli.js" },
177
+ }),
178
+ );
179
+
180
+ for (const platform of ["darwin", "linux", "win32"] as const) {
181
+ const result = getPiSpawnCommand(["-p", "Task: hello"], {
182
+ platform,
183
+ execPath: "/usr/local/bin/node",
184
+ argv1: hostEntry,
185
+ resolvePackageJson: () => path.join(piRoot, "package.json"),
186
+ env: {},
187
+ });
188
+ assert.deepEqual(result, {
189
+ command: "/usr/local/bin/node",
190
+ args: [piCli, "-p", "Task: hello"],
191
+ });
192
+ }
193
+
194
+ fs.writeFileSync(hostPackageJson, "{");
195
+ for (const platform of ["darwin", "linux", "win32"] as const) {
196
+ const malformedHostResult = getPiSpawnCommand(["-p", "Task: hello"], {
197
+ platform,
198
+ execPath: "/usr/local/bin/node",
199
+ argv1: hostEntry,
200
+ resolvePackageJson: () => path.join(piRoot, "package.json"),
201
+ env: {},
202
+ });
203
+ assert.deepEqual(malformedHostResult, {
204
+ command: "/usr/local/bin/node",
205
+ args: [piCli, "-p", "Task: hello"],
206
+ });
207
+ }
208
+ } finally {
209
+ fs.rmSync(tempDir, { recursive: true, force: true });
210
+ }
211
+ });
212
+
213
+ it("validates argv1 ownership against its canonical target", () => {
214
+ const tempDir = fs.mkdtempSync(
215
+ path.join(os.tmpdir(), "pi-spawn-canonical-entry-"),
216
+ );
217
+ try {
218
+ const hostRoot = path.join(tempDir, "embedded-host");
219
+ const hostEntry = path.join(hostRoot, "dist", "server.js");
220
+ const piRoot = path.join(tempDir, "node_modules", "@earendil-works", "pi-coding-agent");
221
+ const disguisedEntry = path.join(piRoot, "dist", "cli.js");
222
+ const piCli = path.join(piRoot, "dist", "real-cli.js");
223
+ fs.mkdirSync(path.dirname(hostEntry), { recursive: true });
224
+ fs.mkdirSync(path.dirname(piCli), { recursive: true });
225
+ fs.writeFileSync(hostEntry, "export {};\n");
226
+ fs.writeFileSync(path.join(hostRoot, "package.json"), JSON.stringify({ name: "embedded-host" }));
227
+ fs.writeFileSync(piCli, "#!/usr/bin/env node\n");
228
+ fs.writeFileSync(path.join(piRoot, "package.json"), JSON.stringify({
229
+ name: "@selesai/code",
230
+ bin: { pi: "dist/real-cli.js" },
231
+ }));
232
+
233
+ const result = getPiSpawnCommand(["-p", "Task: hello"], {
234
+ execPath: "/usr/local/bin/node",
235
+ argv1: disguisedEntry,
236
+ existsSync: (filePath) => filePath === disguisedEntry || fs.existsSync(filePath),
237
+ realpathSync: (filePath) => filePath === disguisedEntry ? hostEntry : fs.realpathSync(filePath),
238
+ resolvePackageJson: () => path.join(piRoot, "package.json"),
239
+ env: {},
240
+ });
241
+ assert.deepEqual(result, {
242
+ command: "/usr/local/bin/node",
243
+ args: [piCli, "-p", "Task: hello"],
244
+ });
245
+ } finally {
246
+ fs.rmSync(tempDir, { recursive: true, force: true });
247
+ }
126
248
  });
127
249
 
128
250
  it("resolves CLI script from package bin when argv1 is not runnable JS", () => {
@@ -139,22 +261,22 @@ describe("getSelesaiSpawnCommand", () => {
139
261
  execPath: "/usr/local/bin/node",
140
262
  argv1: "/opt/pi/subagent-runner.ts",
141
263
  packageJsonPath,
142
- packageJsonContent: JSON.stringify({ bin: { selesai: "dist/cli/index.js" } }),
264
+ packageJsonContent: JSON.stringify({ bin: { pi: "dist/cli/index.js" } }),
143
265
  existing: [packageJsonPath, cliPath],
144
266
  });
145
- const result = getSelesaiSpawnCommand(["-p", "Task: hello"], deps);
267
+ const result = getPiSpawnCommand(["-p", "Task: hello"], deps);
146
268
  assert.equal(result.command, "/usr/local/bin/node");
147
269
  assert.equal(result.args[0], cliPath);
148
270
  });
149
271
 
150
- it("falls back to pi when Windows CLI script cannot be resolved", () => {
272
+ it("falls back to selesai when Windows CLI script cannot be resolved", () => {
151
273
  const deps = makeDeps({
152
274
  platform: "win32",
153
275
  argv1: "/opt/pi/subagent-runner.ts",
154
276
  existing: [],
155
277
  });
156
278
  const args = ["-p", "Task: hello"];
157
- const result = getSelesaiSpawnCommand(args, deps);
279
+ const result = getPiSpawnCommand(args, deps);
158
280
  assert.deepEqual(result, { command: "selesai", args });
159
281
  });
160
282
 
@@ -163,12 +285,7 @@ describe("getSelesaiSpawnCommand", () => {
163
285
  path.join(os.tmpdir(), "pi-spawn-package-root-"),
164
286
  );
165
287
  try {
166
- const packageRoot = path.join(
167
- tempDir,
168
- "node_modules",
169
- "@selesai",
170
- "code",
171
- );
288
+ const packageRoot = path.join(tempDir, "node_modules", "@selesai", "code");
172
289
  const entry = path.join(packageRoot, "dist", "index.js");
173
290
  const cliPath = path.join(packageRoot, "dist", "cli", "index.js");
174
291
  fs.mkdirSync(path.dirname(entry), { recursive: true });
@@ -182,10 +299,11 @@ describe("getSelesaiSpawnCommand", () => {
182
299
  bin: { selesai: "dist/cli/index.js" },
183
300
  }),
184
301
  );
185
- const result = getSelesaiSpawnCommand(["-p", "Task: hello"], {
302
+ const result = getPiSpawnCommand(["-p", "Task: hello"], {
186
303
  platform: "win32",
187
304
  execPath: "/usr/local/bin/node",
188
305
  argv1: "/opt/pi/subagent-runner.ts",
306
+ piPackageRoot: packageRoot,
189
307
  resolvePackageEntry: () => entry,
190
308
  env: {},
191
309
  });
@@ -197,7 +315,7 @@ describe("getSelesaiSpawnCommand", () => {
197
315
  });
198
316
  });
199
317
 
200
- describe("getSelesaiSpawnCommand with piPackageRoot", () => {
318
+ describe("getPiSpawnCommand with piPackageRoot", () => {
201
319
  it("resolves CLI script via piPackageRoot when argv1 is not runnable", () => {
202
320
  const packageJsonPath = "/opt/pi/package.json";
203
321
  const cliPath = path.resolve(
@@ -209,17 +327,17 @@ describe("getSelesaiSpawnCommand with piPackageRoot", () => {
209
327
  execPath: "/usr/local/bin/node",
210
328
  argv1: "/opt/pi/subagent-runner.ts",
211
329
  packageJsonPath,
212
- packageJsonContent: JSON.stringify({ bin: { selesai: "dist/cli/index.js" } }),
330
+ packageJsonContent: JSON.stringify({ bin: { pi: "dist/cli/index.js" } }),
213
331
  existing: [packageJsonPath, cliPath],
214
332
  });
215
333
  deps.piPackageRoot = "/opt/pi";
216
- const result = getSelesaiSpawnCommand(["-p", "Task: hello"], deps);
334
+ const result = getPiSpawnCommand(["-p", "Task: hello"], deps);
217
335
  assert.equal(result.command, "/usr/local/bin/node");
218
336
  assert.equal(result.args[0], cliPath);
219
337
  });
220
338
  });
221
339
 
222
- describe("resolveWindowsSelesaiCliScript", () => {
340
+ describe("resolvePiCliScript", () => {
223
341
  it("supports package bin as string", () => {
224
342
  const packageJsonPath = "/opt/pi/package.json";
225
343
  const cliPath = path.resolve(
@@ -233,6 +351,6 @@ describe("resolveWindowsSelesaiCliScript", () => {
233
351
  packageJsonContent: JSON.stringify({ bin: "dist/cli/index.mjs" }),
234
352
  existing: [packageJsonPath, cliPath],
235
353
  });
236
- assert.equal(resolveWindowsSelesaiCliScript(deps), cliPath);
354
+ assert.equal(resolvePiCliScript(deps), cliPath);
237
355
  });
238
356
  });
@@ -0,0 +1,322 @@
1
+ import assert from "node:assert/strict";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { afterEach, beforeEach, describe, it } from "node:test";
6
+ import { registerSubagentCapabilityCeiling, resolveSubagentCapabilityCeiling } from "../../src/api/capability-ceiling.ts";
7
+ import { resolveSubagentLaunchContract, SUBAGENT_LAUNCH_CONTRACT_VERSION } from "../../src/api/preflight.ts";
8
+ import { clearSkillCache } from "../../src/agents/skills.ts";
9
+ import { computeMcpServerHash } from "../../src/runs/shared/mcp-direct-tool-allowlist.ts";
10
+
11
+ let tempDir = "";
12
+ let previousHome: string | undefined;
13
+ let previousUserProfile: string | undefined;
14
+ let previousAgentDir: string | undefined;
15
+
16
+ function writeAgent(filePath: string, body: string): void {
17
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
18
+ fs.writeFileSync(filePath, body, "utf-8");
19
+ }
20
+
21
+ function writeSkill(cwd: string, name: string): void {
22
+ const skillDir = path.join(cwd, ".selesai", "skills", name);
23
+ fs.mkdirSync(skillDir, { recursive: true });
24
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---\ndescription: ${name}\n---\n\nUse ${name}.\n`, "utf-8");
25
+ }
26
+
27
+ function writeJson(filePath: string, value: unknown): void {
28
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
29
+ fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
30
+ }
31
+
32
+ function writeMcpFixture(): void {
33
+ const agentDir = process.env.SELESAI_CODING_AGENT_DIR;
34
+ assert.equal(typeof agentDir, "string");
35
+ const definition = { command: "github-mcp" };
36
+ writeJson(path.join(agentDir, "mcp.json"), { mcpServers: { github: definition } });
37
+ writeJson(path.join(agentDir, "mcp-cache.json"), {
38
+ version: 1,
39
+ servers: {
40
+ github: {
41
+ configHash: computeMcpServerHash(definition),
42
+ cachedAt: Date.now(),
43
+ tools: [{ name: "search_repositories" }, { name: "create_issue" }],
44
+ resources: [],
45
+ },
46
+ },
47
+ });
48
+ }
49
+
50
+ describe("public launch contract preflight", () => {
51
+ beforeEach(() => {
52
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-preflight-"));
53
+ previousHome = process.env.HOME;
54
+ previousUserProfile = process.env.USERPROFILE;
55
+ previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
56
+ const home = path.join(tempDir, "home");
57
+ process.env.HOME = home;
58
+ process.env.USERPROFILE = home;
59
+ process.env.SELESAI_CODING_AGENT_DIR = path.join(home, ".selesai", "agent");
60
+ clearSkillCache();
61
+ });
62
+
63
+ afterEach(() => {
64
+ clearSkillCache();
65
+ if (previousHome === undefined) delete process.env.HOME;
66
+ else process.env.HOME = previousHome;
67
+ if (previousUserProfile === undefined) delete process.env.USERPROFILE;
68
+ else process.env.USERPROFILE = previousUserProfile;
69
+ if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
70
+ else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
71
+ fs.rmSync(tempDir, { recursive: true, force: true });
72
+ });
73
+
74
+ it("resolves an ordinary single-agent contract without creating launch directories", async () => {
75
+ const cwd = path.join(tempDir, "repo");
76
+ fs.mkdirSync(cwd, { recursive: true });
77
+ writeSkill(cwd, "project-skill");
78
+ writeAgent(path.join(cwd, ".selesai", "agents", "builder.md"), `---
79
+ name: builder
80
+ description: Project builder
81
+ tools:
82
+ - read
83
+ - write
84
+ - /tmp/private-tool.ts
85
+ model: test/primary
86
+ fallbackModels:
87
+ - test/fallback
88
+ thinking: high
89
+ skills:
90
+ - project-skill
91
+ output: report.md
92
+ ---
93
+ Project prompt.
94
+ `);
95
+ const sessionRoot = path.join(tempDir, "sessions");
96
+ const handle = registerSubagentCapabilityCeiling({ sessionId: "preflight-session", ceiling: { allowedTools: ["read"], denyExtensions: true }, source: "test" });
97
+ try {
98
+ const ceiling = resolveSubagentCapabilityCeiling("preflight-session");
99
+ const result = await resolveSubagentLaunchContract({
100
+ agent: "builder",
101
+ cwd,
102
+ task: "Inspect the repo",
103
+ runId: "run-123",
104
+ sessionRoot,
105
+ availableModels: [
106
+ { provider: "test", id: "primary", fullId: "test/primary" },
107
+ { provider: "test", id: "fallback", fullId: "test/fallback" },
108
+ ],
109
+ capabilityCeiling: ceiling,
110
+ });
111
+
112
+ assert.equal(result.ok, true);
113
+ assert.equal(result.contract.version, SUBAGENT_LAUNCH_CONTRACT_VERSION);
114
+ assert.equal(result.contract.agent.source, "project");
115
+ assert.equal(result.contract.agent.definitionProjectionVersion, 1);
116
+ assert.match(result.contract.agent.definitionDigest, /^[a-f0-9]{64}$/);
117
+ assert.match(result.contract.launchContractDigest, /^[a-f0-9]{64}$/);
118
+ assert.ok(result.contract.agent.shadowedCandidates.some((candidate) => candidate.name === "builder" && candidate.source === "builtin"));
119
+ assert.equal(result.contract.model, "test/primary:high");
120
+ assert.deepEqual(result.contract.modelCandidates, ["test/primary:high", "test/fallback:high"]);
121
+ assert.equal(result.contract.thinking, "high");
122
+ assert.deepEqual(result.contract.skills.requested, ["project-skill"]);
123
+ assert.equal(result.contract.skills.resolved[0]?.name, "project-skill");
124
+ assert.deepEqual(result.contract.tools.effectiveAllowlist, ["read"]);
125
+ assert.deepEqual(result.contract.tools.capabilityAudit?.removedTools, ["write"]);
126
+ assert.equal(result.contract.tools.capabilityAudit?.removedExtensionCount, 1);
127
+ assert.equal(result.contract.tools.disableAmbientExtensions, true);
128
+ assert.equal(result.contract.roots.sessionFile, path.join(sessionRoot, "run-123", "run-0", "session.jsonl"));
129
+ assert.equal(result.contract.roots.outputPath, path.join(cwd, ".pi-subagents", "artifacts", "outputs", "run-123", "report.md"));
130
+ assert.equal(result.contract.roots.lifecycle?.statusPath.endsWith(path.join("run-123", "status.json")), true);
131
+ assert.equal(result.contract.roots.lifecycle?.eventsPath.endsWith(path.join("run-123", "events.jsonl")), true);
132
+ assert.equal(result.contract.roots.lifecycle?.processTerminalPath.endsWith(path.join("run-123", "process-terminal.json")), true);
133
+ assert.notEqual(result.contract.roots.lifecycle?.asyncDir, result.contract.roots.artifactsDir);
134
+ assert.match(result.contract.digest, /^[a-f0-9]{64}$/);
135
+ const repeated = await resolveSubagentLaunchContract({
136
+ agent: "builder",
137
+ cwd,
138
+ task: "Inspect the repo",
139
+ runId: "run-123",
140
+ sessionRoot,
141
+ availableModels: [
142
+ { provider: "test", id: "primary", fullId: "test/primary" },
143
+ { provider: "test", id: "fallback", fullId: "test/fallback" },
144
+ ],
145
+ capabilityCeiling: ceiling,
146
+ });
147
+ assert.equal(repeated.ok, true);
148
+ assert.equal(repeated.contract.digest, result.contract.digest);
149
+ assert.equal(fs.existsSync(sessionRoot), false);
150
+ assert.equal(fs.existsSync(path.join(cwd, ".pi-subagents")), false);
151
+ } finally {
152
+ handle.dispose();
153
+ }
154
+ });
155
+
156
+ it("changes definition and launch digests when selected agent content changes", async () => {
157
+ const cwd = path.join(tempDir, "repo");
158
+ fs.mkdirSync(cwd, { recursive: true });
159
+ const agentPath = path.join(cwd, ".selesai", "agents", "worker.md");
160
+ writeAgent(agentPath, `---
161
+ name: digest-worker
162
+ description: Digest worker
163
+ tools:
164
+ - read
165
+ ---
166
+ First prompt.
167
+ `);
168
+ const before = await resolveSubagentLaunchContract({ agent: "digest-worker", cwd, runId: "digest-test" });
169
+ assert.equal(before.ok, true);
170
+ writeAgent(agentPath, `---
171
+ name: digest-worker
172
+ description: Digest worker
173
+ tools:
174
+ - read
175
+ ---
176
+ Changed prompt.
177
+ `);
178
+ const after = await resolveSubagentLaunchContract({ agent: "digest-worker", cwd, runId: "digest-test" });
179
+ assert.equal(after.ok, true);
180
+ assert.notEqual(after.contract.agent.definitionDigest, before.contract.agent.definitionDigest);
181
+ assert.notEqual(after.contract.launchContractDigest, before.contract.launchContractDigest);
182
+ assert.notEqual(after.contract.digest, before.contract.digest);
183
+ });
184
+
185
+ it("binds resolved skill content into the launch digest", async () => {
186
+ const cwd = path.join(tempDir, "repo");
187
+ fs.mkdirSync(cwd, { recursive: true });
188
+ writeSkill(cwd, "digest-skill");
189
+ writeAgent(path.join(cwd, ".selesai", "agents", "worker.md"), `---
190
+ name: worker
191
+ description: Project worker
192
+ tools:
193
+ - read
194
+ skills:
195
+ - digest-skill
196
+ ---
197
+ Project prompt.
198
+ `);
199
+ const before = await resolveSubagentLaunchContract({ agent: "worker", cwd, runId: "skill-digest-test" });
200
+ assert.equal(before.ok, true);
201
+ fs.writeFileSync(path.join(cwd, ".selesai", "skills", "digest-skill", "SKILL.md"), "---\ndescription: updated digest-skill\n---\n\nUse digest-skill.\n", "utf-8");
202
+ clearSkillCache();
203
+
204
+ const after = await resolveSubagentLaunchContract({ agent: "worker", cwd, runId: "skill-digest-test" });
205
+ assert.equal(after.ok, true);
206
+ assert.equal(after.contract.agent.definitionDigest, before.contract.agent.definitionDigest);
207
+ assert.notEqual(after.contract.launchContractDigest, before.contract.launchContractDigest);
208
+ assert.notEqual(after.contract.digest, before.contract.digest);
209
+ });
210
+
211
+ it("returns closed failures for missing agents and missing skills", async () => {
212
+ const cwd = path.join(tempDir, "repo");
213
+ fs.mkdirSync(cwd, { recursive: true });
214
+ writeAgent(path.join(cwd, ".selesai", "agents", "worker.md"), `---
215
+ name: worker
216
+ description: Project worker
217
+ skills:
218
+ - missing-skill
219
+ ---
220
+ Project prompt.
221
+ `);
222
+
223
+ const missingAgent = await resolveSubagentLaunchContract({ agent: "missing", cwd });
224
+ assert.deepEqual(missingAgent, { ok: false, code: "missing_agent", message: "Unknown agent: missing", diagnostics: [] });
225
+
226
+ const missingSkill = await resolveSubagentLaunchContract({ agent: "worker", cwd });
227
+ assert.equal(missingSkill.ok, false);
228
+ assert.equal(missingSkill.code, "missing_skill");
229
+ assert.match(missingSkill.message, /missing-skill/);
230
+ });
231
+
232
+ it("fails closed for invalid runtime inputs", async () => {
233
+ const cwd = path.join(tempDir, "repo");
234
+ fs.mkdirSync(cwd, { recursive: true });
235
+ writeAgent(path.join(cwd, ".selesai", "agents", "worker.md"), `---
236
+ name: worker
237
+ description: Project worker
238
+ ---
239
+ Project prompt.
240
+ `);
241
+
242
+ const invalidCwd = await resolveSubagentLaunchContract({ agent: "worker", cwd: path.join(tempDir, "missing") });
243
+ assert.equal(invalidCwd.ok, false);
244
+ assert.equal(invalidCwd.code, "invalid_cwd");
245
+
246
+ const unsupportedMode = await resolveSubagentLaunchContract({ agent: "worker", cwd, context: "bogus" as never });
247
+ assert.equal(unsupportedMode.ok, false);
248
+ assert.equal(unsupportedMode.code, "unsupported_mode");
249
+
250
+ const invalidArtifactDir = await resolveSubagentLaunchContract({ agent: "worker", cwd, artifactDir: "bogus" as never });
251
+ assert.equal(invalidArtifactDir.ok, false);
252
+ assert.equal(invalidArtifactDir.code, "invalid_artifact_dir");
253
+ });
254
+
255
+ it("projects MCP, extension, fanout, structured-output, and fork diagnostics", async () => {
256
+ const cwd = path.join(tempDir, "repo");
257
+ fs.mkdirSync(cwd, { recursive: true });
258
+ writeMcpFixture();
259
+ writeAgent(path.join(cwd, ".selesai", "agents", "fanout.md"), `---
260
+ name: fanout
261
+ description: Project fanout
262
+ tools:
263
+ - read
264
+ - subagent
265
+ - /tmp/tool-ext.ts
266
+ - mcp:github/search_repositories
267
+ extensions:
268
+ - /tmp/config-ext.ts
269
+ subagentOnlyExtensions:
270
+ - /tmp/subagent-only.ts
271
+ defaultContext: fork
272
+ ---
273
+ Project prompt.
274
+ `);
275
+
276
+ const result = await resolveSubagentLaunchContract({
277
+ agent: "fanout",
278
+ cwd,
279
+ outputSchema: { type: "object", additionalProperties: false },
280
+ });
281
+ assert.equal(result.ok, true);
282
+ assert.equal(result.contract.context, "fork");
283
+ assert.ok(result.contract.diagnostics.some((diagnostic) => diagnostic.code === "host_required"));
284
+ assert.deepEqual(result.contract.tools.declaredBuiltin, ["read", "subagent"]);
285
+ assert.equal(result.contract.tools.explicitAllowlist, true);
286
+ assert.equal(result.contract.tools.fanoutAuthorized, true);
287
+ assert.deepEqual(result.contract.tools.internalTools, ["structured_output"]);
288
+ assert.deepEqual(result.contract.tools.effectiveMcpTools, ["github_search_repositories"]);
289
+ assert.deepEqual(result.contract.tools.requiredChildTools, ["read", "subagent", "github_search_repositories", "structured_output"]);
290
+ assert.deepEqual(result.contract.tools.toolExtensionPaths, ["/tmp/tool-ext.ts"]);
291
+ assert.equal(result.contract.tools.disableAmbientExtensions, true);
292
+ assert.ok(result.contract.tools.runtimeExtensions.some((extensionPath) => extensionPath.endsWith("subagent-prompt-runtime.ts")));
293
+ assert.ok(result.contract.tools.runtimeExtensions.some((extensionPath) => extensionPath.endsWith("fanout-child.ts")));
294
+ assert.ok(result.contract.tools.extensionArgs.includes("/tmp/config-ext.ts"));
295
+ assert.ok(result.contract.tools.extensionArgs.includes("/tmp/subagent-only.ts"));
296
+ });
297
+
298
+ it("fails closed when a capability ceiling denies read required for child skills", async () => {
299
+ const cwd = path.join(tempDir, "repo");
300
+ fs.mkdirSync(cwd, { recursive: true });
301
+ writeSkill(cwd, "project-skill");
302
+ writeAgent(path.join(cwd, ".selesai", "agents", "worker.md"), `---
303
+ name: worker
304
+ description: Project worker
305
+ tools:
306
+ - read
307
+ skills:
308
+ - project-skill
309
+ ---
310
+ Project prompt.
311
+ `);
312
+
313
+ const result = await resolveSubagentLaunchContract({
314
+ agent: "worker",
315
+ cwd,
316
+ capabilityCeiling: { version: 1, allowedTools: [], denyExtensions: false, sources: ["test"] },
317
+ });
318
+ assert.equal(result.ok, false);
319
+ assert.equal(result.code, "denied_required_tool");
320
+ assert.match(result.message, /excludes required tool 'read'/);
321
+ });
322
+ });