@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
@@ -63,6 +63,33 @@ describe("slash live state", { skip: !available ? "slash-live-state.ts not impor
63
63
  assert.equal(snapshot.version > 0, true);
64
64
  });
65
65
 
66
+ it("does not assign a parallel child update to another chain placeholder", () => {
67
+ clearSlashSnapshots!();
68
+ const details = buildSlashInitialResult!("req-parallel", {
69
+ chain: [{ parallel: [{ agent: "scout", task: "map" }, { agent: "context-builder", task: "analyze" }] }],
70
+ });
71
+
72
+ applySlashUpdate!("req-parallel", {
73
+ requestId: "req-parallel",
74
+ progress: [{
75
+ index: 1,
76
+ agent: "context-builder",
77
+ status: "running",
78
+ task: "analyze",
79
+ currentTool: "find",
80
+ recentTools: [],
81
+ recentOutput: [],
82
+ toolCount: 38,
83
+ tokens: 1_000,
84
+ durationMs: 1_000,
85
+ }],
86
+ });
87
+
88
+ const results = getSlashRenderableSnapshot!(details).result.details.results;
89
+ assert.equal(results[0]?.progress?.currentTool, undefined);
90
+ assert.equal(results[1]?.progress?.currentTool, "find");
91
+ });
92
+
66
93
  it("creates stable placeholders for a 40-step worker/reviewer chain", () => {
67
94
  clearSlashSnapshots!();
68
95
  const chain = Array.from({ length: 40 }, (_, index) => ({
@@ -3,7 +3,7 @@ import { describe, it } from "node:test";
3
3
  import { tryImport } from "../support/helpers.ts";
4
4
 
5
5
  interface TopLevelAsyncModule {
6
- applyForceTopLevelAsyncOverride<T extends { async?: boolean; clarify?: boolean }>(
6
+ applyForceTopLevelAsyncOverride<T extends { async?: boolean; clarify?: boolean; foregroundOnly?: boolean }>(
7
7
  params: T,
8
8
  depth: number,
9
9
  forceTopLevelAsync: boolean,
@@ -23,6 +23,12 @@ describe("force top-level async helper", { skip: !available ? "pi packages not a
23
23
  assert.equal(next.agent, "worker");
24
24
  });
25
25
 
26
+ it("keeps bridge-owned foreground calls attached", () => {
27
+ const params = { async: false, clarify: false, foregroundOnly: true };
28
+ const next = mod!.applyForceTopLevelAsyncOverride(params, 0, true);
29
+ assert.equal(next, params);
30
+ });
31
+
26
32
  it("leaves nested calls unchanged", () => {
27
33
  const params = { async: false, clarify: true };
28
34
  const next = mod!.applyForceTopLevelAsyncOverride(params, 1, true);
@@ -51,6 +51,8 @@ interface AgentConfig {
51
51
  extensions?: string[];
52
52
  subagentOnlyExtensions?: string[];
53
53
  skills?: string[];
54
+ skillPath?: string[];
55
+ filePath?: string;
54
56
  thinking?: string;
55
57
  systemPromptMode?: string;
56
58
  inheritProjectContext?: boolean;
@@ -59,6 +61,7 @@ interface AgentConfig {
59
61
  output?: string | false;
60
62
  reads?: string[] | false;
61
63
  progress?: boolean;
64
+ toolBudget?: { soft?: number; hard: number; block?: string[] | "*" };
62
65
  mcpDirectTools?: string[];
63
66
  maxSubagentDepth?: number;
64
67
  completionGuard?: boolean;
@@ -153,6 +156,8 @@ export async function tryImport<T>(specifier: string): Promise<T | null> {
153
156
  }
154
157
  }
155
158
 
159
+ let writeCallSeq = 0;
160
+
156
161
  export const events = {
157
162
  assistantMessage(text: string, model = "mock/test-model"): object {
158
163
  return {
@@ -167,6 +172,33 @@ export const events = {
167
172
  };
168
173
  },
169
174
 
175
+ /** Assistant write tool call plus its successful tool result, as one completed write. */
176
+ completedWrite(filePath: string, content: string, model = "mock/test-model"): object[] {
177
+ const id = `write-${++writeCallSeq}`;
178
+ return [
179
+ {
180
+ type: "message_end",
181
+ message: {
182
+ role: "assistant",
183
+ content: [{ type: "toolCall", id, name: "write", arguments: { path: filePath, content } }],
184
+ model,
185
+ stopReason: "toolUse",
186
+ usage: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0, cost: { total: 0.001 } },
187
+ },
188
+ },
189
+ {
190
+ type: "tool_result_end",
191
+ message: {
192
+ role: "toolResult",
193
+ toolCallId: id,
194
+ toolName: "write",
195
+ isError: false,
196
+ content: [{ type: "text", text: `Wrote ${filePath}` }],
197
+ },
198
+ },
199
+ ];
200
+ },
201
+
170
202
  toolStart(toolName: string, args: Record<string, unknown> = {}): object {
171
203
  return { type: "tool_execution_start", toolName, args };
172
204
  },
@@ -167,6 +167,40 @@ function defaultResponse() {
167
167
  return { output: "ok", exitCode: 0 };
168
168
  }
169
169
 
170
+ function writeDeclaredFiles(response) {
171
+ if (!Array.isArray(response.writeFiles)) return;
172
+ for (const file of response.writeFiles) {
173
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") continue;
174
+ const target = path.resolve(process.cwd(), file.path);
175
+ fs.mkdirSync(path.dirname(target), { recursive: true });
176
+ fs.writeFileSync(target, file.content, "utf-8");
177
+ }
178
+ }
179
+
180
+ function writeStructuredOutputCapture(response) {
181
+ if (!Object.prototype.hasOwnProperty.call(response, "structuredOutputCapture")) return;
182
+ const outputPath = process.env.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE;
183
+ if (!outputPath) return;
184
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
185
+ fs.writeFileSync(outputPath, JSON.stringify(response.structuredOutputCapture), "utf-8");
186
+ }
187
+
188
+ function writeToolDiagnostic(response) {
189
+ if (!Array.isArray(response.missingTools) || response.missingTools.length === 0) return;
190
+ const diagnosticPath = process.env.SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH;
191
+ const required = JSON.parse(process.env.SELESAI_SUBAGENT_REQUIRED_TOOLS ?? "[]");
192
+ if (!diagnosticPath || !Array.isArray(required)) return;
193
+ const missing = response.missingTools.filter((name) => typeof name === "string" && required.includes(name));
194
+ const available = required.filter((name) => !missing.includes(name));
195
+ fs.mkdirSync(path.dirname(diagnosticPath), { recursive: true });
196
+ fs.writeFileSync(diagnosticPath, JSON.stringify({
197
+ agent: process.env.SELESAI_SUBAGENT_CHILD_AGENT,
198
+ required,
199
+ available,
200
+ missing,
201
+ }), "utf-8");
202
+ }
203
+
170
204
  function isJsonMode(args) {
171
205
  for (let i = 0; i < args.length; i++) {
172
206
  if (args[i] === "--mode") {
@@ -220,6 +254,15 @@ async function writeJsonlLine(entry) {
220
254
  await writeStdout(`${line}\n`);
221
255
  }
222
256
 
257
+ async function writeRawStdout(entry) {
258
+ if (Array.isArray(entry?.stdoutBase64Chunks)) {
259
+ for (const chunk of entry.stdoutBase64Chunks) {
260
+ if (typeof chunk === "string") await writeStdout(Buffer.from(chunk, "base64"));
261
+ }
262
+ }
263
+ if (typeof entry?.stdoutRaw === "string") await writeStdout(entry.stdoutRaw);
264
+ }
265
+
223
266
  function extractPlainText(entry) {
224
267
  if (!entry || typeof entry !== "object") return "";
225
268
  if (entry.type === "message_end") {
@@ -283,6 +326,7 @@ async function main() {
283
326
  process.on("SIGTERM", () => {});
284
327
  }
285
328
  writeSessionFile(args);
329
+ writeToolDiagnostic(response);
286
330
  fs.writeFileSync(
287
331
  path.join(queueDir, `call-${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}.json`),
288
332
  JSON.stringify({ args, systemPrompts: readSystemPromptRecords(args) }),
@@ -292,6 +336,16 @@ async function main() {
292
336
  if (typeof response.delay === "number" && response.delay > 0) {
293
337
  await new Promise((resolve) => setTimeout(resolve, response.delay));
294
338
  }
339
+ if (typeof response.waitForPath === "string") {
340
+ const deadline = Date.now() + 30_000;
341
+ while (!fs.existsSync(response.waitForPath)) {
342
+ if (Date.now() >= deadline) fail(`Timed out waiting for mock release path: ${response.waitForPath}`);
343
+ await new Promise((resolve) => setTimeout(resolve, 20));
344
+ }
345
+ }
346
+
347
+ writeDeclaredFiles(response);
348
+ writeStructuredOutputCapture(response);
295
349
 
296
350
  if (Array.isArray(response.steps) && response.steps.length > 0) {
297
351
  for (const step of response.steps) {
@@ -299,14 +353,17 @@ async function main() {
299
353
  await new Promise((resolve) => setTimeout(resolve, step.delay));
300
354
  }
301
355
  if (Array.isArray(step?.jsonl) && step.jsonl.length > 0) {
302
- await writeResponseEntries(step.jsonl, jsonMode, args);
356
+ await writeResponseEntries(step.jsonl, jsonMode, args);
303
357
  }
358
+ await writeRawStdout(step);
304
359
  if (typeof step?.stderr === "string" && step.stderr.length > 0) {
305
360
  process.stderr.write(step.stderr);
306
361
  }
307
362
  }
308
363
  } else if (Array.isArray(response.jsonl) && response.jsonl.length > 0) {
309
- await writeResponseEntries(response.jsonl, jsonMode, args);
364
+ await writeResponseEntries(response.jsonl, jsonMode, args);
365
+ } else if (Array.isArray(response.stdoutBase64Chunks) || typeof response.stdoutRaw === "string") {
366
+ await writeRawStdout(response);
310
367
  } else if (Array.isArray(response.echoEnv) && response.echoEnv.length > 0) {
311
368
  const envSnapshot = Object.fromEntries(response.echoEnv.map((key) => [key, process.env[key] ?? null]));
312
369
  const output = withAcceptanceReport(JSON.stringify(envSnapshot), args);
@@ -8,16 +8,26 @@ interface MockPiResponse {
8
8
  stderr?: string;
9
9
  exitCode?: number;
10
10
  delay?: number;
11
+ waitForPath?: string;
11
12
  keepAliveAfterFinalMessageMs?: number;
12
13
  ignoreSigterm?: boolean;
13
14
  jsonl?: unknown[];
15
+ stdoutRaw?: string;
16
+ stdoutBase64Chunks?: string[];
14
17
  steps?: Array<{
15
18
  delay?: number;
16
19
  jsonl?: unknown[];
20
+ stdoutRaw?: string;
21
+ stdoutBase64Chunks?: string[];
17
22
  stderr?: string;
18
23
  }>;
19
24
  echoEnv?: string[];
25
+ missingTools?: string[];
20
26
  matchArgIncludes?: string | string[];
27
+ /** Files the mock child writes to disk before emitting output, standing in for its write-tool side effects. */
28
+ writeFiles?: Array<{ path: string; content: string }>;
29
+ /** Writes the structured-output capture file without emitting a structured_output tool event. */
30
+ structuredOutputCapture?: unknown;
21
31
  }
22
32
 
23
33
  export interface MockPi {
@@ -58,17 +68,27 @@ export function createMockPi(): MockPi {
58
68
  const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-mock-cli-"));
59
69
  const queueDir = path.join(rootDir, "queue");
60
70
  const binDir = path.join(rootDir, "bin");
71
+ const piPackageDir = path.join(rootDir, "pi-package");
72
+ const cliScriptPath = path.join(piPackageDir, "dist", "cli.mjs");
61
73
  ensureDir(queueDir);
62
74
  ensureDir(binDir);
75
+ ensureDir(path.dirname(cliScriptPath));
76
+ fs.copyFileSync(SCRIPT_PATH, cliScriptPath);
77
+ fs.writeFileSync(
78
+ path.join(piPackageDir, "package.json"),
79
+ JSON.stringify({ name: "@selesai/code" }),
80
+ "utf-8",
81
+ );
63
82
 
64
83
  const shellScriptPath = path.join(binDir, "pi");
65
84
  const cmdScriptPath = path.join(binDir, "pi.cmd");
66
- writeExecutable(shellScriptPath, `#!/bin/sh\nexec "${process.execPath}" "${SCRIPT_PATH}" "$@"\n`);
67
- writeExecutable(cmdScriptPath, `@echo off\r\n"${process.execPath}" "${SCRIPT_PATH}" %*\r\n`);
85
+ writeExecutable(shellScriptPath, `#!/bin/sh\nexec "${process.execPath}" "${cliScriptPath}" "$@"\n`);
86
+ writeExecutable(cmdScriptPath, `@echo off\r\n"${process.execPath}" "${cliScriptPath}" %*\r\n`);
68
87
 
69
88
  let installed = false;
70
89
  let nextSequence = 0;
71
90
  let originalPath: string | undefined;
91
+ let originalPiBinary: string | undefined;
72
92
  let originalArgv1: string | undefined;
73
93
  let originalQueueEnv: string | undefined;
74
94
 
@@ -80,25 +100,31 @@ export function createMockPi(): MockPi {
80
100
  if (installed) return;
81
101
  installed = true;
82
102
  originalPath = process.env.PATH;
103
+ originalPiBinary = process.env.SELESAI_SUBAGENT_PI_BINARY;
83
104
  originalQueueEnv = process.env.MOCK_PI_QUEUE_DIR;
84
105
  process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`;
85
- process.env.MOCK_PI_QUEUE_DIR = queueDir;
86
106
  if (process.platform === "win32") {
107
+ delete process.env.SELESAI_SUBAGENT_PI_BINARY;
87
108
  originalArgv1 = process.argv[1];
88
- process.argv[1] = SCRIPT_PATH;
109
+ process.argv[1] = cliScriptPath;
110
+ } else {
111
+ process.env.SELESAI_SUBAGENT_PI_BINARY = shellScriptPath;
89
112
  }
113
+ process.env.MOCK_PI_QUEUE_DIR = queueDir;
90
114
  },
91
115
  uninstall() {
92
116
  if (!installed) return;
93
117
  installed = false;
94
118
  if (originalPath === undefined) delete process.env.PATH;
95
119
  else process.env.PATH = originalPath;
96
- if (originalQueueEnv === undefined) delete process.env.MOCK_PI_QUEUE_DIR;
97
- else process.env.MOCK_PI_QUEUE_DIR = originalQueueEnv;
120
+ if (originalPiBinary === undefined) delete process.env.SELESAI_SUBAGENT_PI_BINARY;
121
+ else process.env.SELESAI_SUBAGENT_PI_BINARY = originalPiBinary;
98
122
  if (process.platform === "win32") {
99
123
  if (originalArgv1 === undefined) delete process.argv[1];
100
124
  else process.argv[1] = originalArgv1;
101
125
  }
126
+ if (originalQueueEnv === undefined) delete process.env.MOCK_PI_QUEUE_DIR;
127
+ else process.env.MOCK_PI_QUEUE_DIR = originalQueueEnv;
102
128
  try {
103
129
  fs.rmSync(rootDir, { recursive: true, force: true });
104
130
  } catch {}
@@ -3,14 +3,11 @@ import { mkdtempSync, rmSync } from "node:fs";
3
3
  import * as fs from "node:fs";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
- import {
7
- fauxAssistantMessage,
8
- fauxText,
9
- registerFauxProvider,
10
- } from "@earendil-works/pi-ai";
6
+ import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
11
7
  import {
12
8
  createAgentSession,
13
9
  DefaultResourceLoader,
10
+ ModelRuntime,
14
11
  SessionManager,
15
12
  SettingsManager,
16
13
  } from "@selesai/code";
@@ -85,19 +82,6 @@ function parseArgs(argv) {
85
82
  return parsed;
86
83
  }
87
84
 
88
- function createModelRegistry(model) {
89
- return {
90
- find: (provider, id) => provider === model.provider && id === model.id ? model : undefined,
91
- getAll: () => [model],
92
- getAvailable: () => [model],
93
- hasConfiguredAuth: () => true,
94
- isUsingOAuth: () => false,
95
- getApiKeyAndHeaders: async () => ({ ok: true, apiKey: "faux", headers: {} }),
96
- registerProvider: () => {},
97
- unregisterProvider: () => {},
98
- };
99
- }
100
-
101
85
  function createSessionManager(parsed, cwd) {
102
86
  if (parsed.sessionFile) {
103
87
  const sessionDir = parsed.sessionDir ?? path.dirname(parsed.sessionFile);
@@ -112,19 +96,36 @@ function createSessionManager(parsed, cwd) {
112
96
  async function main() {
113
97
  const parsed = parseArgs(process.argv.slice(2));
114
98
  const responseText = process.env.SELESAI_SUBAGENTS_E2E_CHILD_TEXT ?? "CHILD_REAL_SESSION_OK";
99
+ const reportChildTools = process.env.SELESAI_SUBAGENTS_E2E_REPORT_CHILD_TOOLS === "1";
115
100
  const cwd = process.cwd();
116
101
  const ownedAgentDir = process.env.SELESAI_CODING_AGENT_DIR
117
102
  ? undefined
118
103
  : mkdtempSync(path.join(os.tmpdir(), "pi-e2e-agent-dir-"));
119
104
  const agentDir = process.env.SELESAI_CODING_AGENT_DIR ?? ownedAgentDir;
120
105
 
121
- const faux = registerFauxProvider({
106
+ const faux = fauxProvider({
122
107
  provider: "faux-e2e-child",
123
108
  models: [{ id: "child", contextWindow: 200_000 }],
124
109
  });
125
- const model = faux.getModel();
110
+ const modelRuntime = await ModelRuntime.create({
111
+ authPath: path.join(agentDir, "auth.json"),
112
+ modelsPath: null,
113
+ allowModelNetwork: false,
114
+ });
115
+ modelRuntime.registerProvider(faux.provider.id, {
116
+ name: faux.provider.name,
117
+ api: faux.api,
118
+ apiKey: "faux",
119
+ streamSimple: faux.provider.streamSimple,
120
+ models: [...faux.models],
121
+ });
122
+ const model = modelRuntime.getModel(faux.provider.id, "child");
123
+ if (!model) throw new Error("faux child model was not registered");
124
+ let session;
126
125
  faux.setResponses([
127
- () => fauxAssistantMessage(fauxText(responseText), { stopReason: "stop" }),
126
+ () => process.env.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE
127
+ ? fauxAssistantMessage(fauxToolCall("structured_output", { value: { marker: "STRUCTURED_OUTPUT_OK" } }), { stopReason: "toolUse" })
128
+ : fauxAssistantMessage(fauxText(reportChildTools ? `ACTIVE_TOOLS:${session?.getActiveToolNames().sort().join(",") ?? ""}` : responseText), { stopReason: "stop" }),
128
129
  ]);
129
130
 
130
131
  const settingsManager = SettingsManager.inMemory({
@@ -146,16 +147,17 @@ async function main() {
146
147
 
147
148
  try {
148
149
  await loader.reload();
149
- const { session } = await createAgentSession({
150
+ const created = await createAgentSession({
150
151
  cwd,
151
152
  agentDir,
152
153
  model,
153
- modelRegistry: createModelRegistry(model),
154
+ modelRuntime,
154
155
  resourceLoader: loader,
155
156
  sessionManager: createSessionManager(parsed, cwd),
156
157
  settingsManager,
157
158
  tools: parsed.tools,
158
159
  });
160
+ session = created.session;
159
161
 
160
162
  session.subscribe((event) => {
161
163
  if (
@@ -173,7 +175,6 @@ async function main() {
173
175
  await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" });
174
176
  session.dispose();
175
177
  } finally {
176
- faux.unregister();
177
178
  if (ownedAgentDir) rmSync(ownedAgentDir, { recursive: true, force: true });
178
179
  }
179
180
  }
@@ -1,23 +1,21 @@
1
1
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { fileURLToPath } from "node:url";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import { type AssistantMessage, type Context, type ToolCall } from "@earendil-works/pi-ai";
5
6
  import {
6
- type AssistantMessage,
7
- type Context,
8
7
  type FauxContentBlock,
9
8
  type FauxResponseStep,
10
9
  fauxAssistantMessage,
11
10
  fauxText,
12
11
  fauxToolCall,
13
- registerFauxProvider,
14
- type ToolCall,
15
- } from "@earendil-works/pi-ai";
12
+ fauxProvider,
13
+ } from "@earendil-works/pi-ai/providers/faux";
16
14
  import {
17
15
  type AgentSession,
18
16
  type AgentSessionEvent,
19
- type ModelRegistry,
20
17
  createAgentSession,
18
+ ModelRuntime,
21
19
  DefaultResourceLoader,
22
20
  SessionManager,
23
21
  SettingsManager,
@@ -34,6 +32,8 @@ export interface RealSessionRunOptions {
34
32
  childText: string;
35
33
  respond: FauxResponder;
36
34
  timeoutMs?: number;
35
+ projectFiles?: Record<string, string>;
36
+ reportChildTools?: boolean;
37
37
  }
38
38
 
39
39
  export interface RealSessionRun {
@@ -98,51 +98,59 @@ function toAssistantMessage(reply: FauxReply): AssistantMessage {
98
98
  return fauxAssistantMessage(content, { stopReason: hasToolCall ? "toolUse" : "stop" });
99
99
  }
100
100
 
101
- function createModelRegistry(model: { provider: string; id: string }) {
102
- return {
103
- find: (provider: string, id: string) => provider === model.provider && id === model.id ? model : undefined,
104
- getAll: () => [model],
105
- getAvailable: () => [model],
106
- hasConfiguredAuth: () => true,
107
- isUsingOAuth: () => false,
108
- getApiKeyAndHeaders: async () => ({ ok: true, apiKey: "faux", headers: {} }),
109
- registerProvider: () => {},
110
- unregisterProvider: () => {},
111
- };
112
- }
113
-
114
- function installChildPiShim(childText: string): () => void {
101
+ function installChildPiShim(childText: string, reportChildTools = false): () => void {
115
102
  const rootDir = mkdtempSync(path.join(os.tmpdir(), "pi-real-session-cli-"));
116
103
  const binDir = path.join(rootDir, "bin");
104
+ const piPackageDir = path.join(rootDir, "pi-package");
105
+ const childCliPath = path.join(piPackageDir, "dist", "cli.mjs");
117
106
  const previousPath = process.env.PATH;
107
+ const previousPiBinary = process.env.SELESAI_SUBAGENT_PI_BINARY;
118
108
  const previousChildText = process.env.SELESAI_SUBAGENTS_E2E_CHILD_TEXT;
109
+ const previousReportTools = process.env.SELESAI_SUBAGENTS_E2E_REPORT_CHILD_TOOLS;
119
110
  const previousArgv1 = process.argv[1];
120
111
 
121
112
  writeFileSync(path.join(rootDir, ".keep"), "");
122
113
  mkdirSync(binDir, { recursive: true });
114
+ mkdirSync(path.dirname(childCliPath), { recursive: true });
115
+ writeFileSync(childCliPath, `import ${JSON.stringify(pathToFileURL(CHILD_CLI_PATH).href)};\n`);
116
+ writeFileSync(
117
+ path.join(piPackageDir, "package.json"),
118
+ JSON.stringify({ name: "@selesai/code" }),
119
+ );
123
120
  writeFileSync(
124
121
  path.join(binDir, "pi"),
125
- `#!/bin/sh\nexec "${process.execPath}" "${CHILD_CLI_PATH}" "$@"\n`,
122
+ `#!/bin/sh\nexec "${process.execPath}" "${childCliPath}" "$@"\n`,
126
123
  { mode: 0o755 },
127
124
  );
128
125
  writeFileSync(
129
126
  path.join(binDir, "pi.cmd"),
130
- `@echo off\r\n"${process.execPath}" "${CHILD_CLI_PATH}" %*\r\n`,
127
+ `@echo off\r\n"${process.execPath}" "${childCliPath}" %*\r\n`,
131
128
  );
132
129
 
133
130
  process.env.PATH = `${binDir}${path.delimiter}${previousPath ?? ""}`;
131
+ if (process.platform === "win32") {
132
+ delete process.env.SELESAI_SUBAGENT_PI_BINARY;
133
+ process.argv[1] = childCliPath;
134
+ } else {
135
+ process.env.SELESAI_SUBAGENT_PI_BINARY = path.join(binDir, "pi");
136
+ }
134
137
  process.env.SELESAI_SUBAGENTS_E2E_CHILD_TEXT = childText;
135
- if (process.platform === "win32") process.argv[1] = CHILD_CLI_PATH;
138
+ if (reportChildTools) process.env.SELESAI_SUBAGENTS_E2E_REPORT_CHILD_TOOLS = "1";
139
+ else delete process.env.SELESAI_SUBAGENTS_E2E_REPORT_CHILD_TOOLS;
136
140
 
137
141
  return () => {
138
142
  if (previousPath === undefined) delete process.env.PATH;
139
143
  else process.env.PATH = previousPath;
140
- if (previousChildText === undefined) delete process.env.SELESAI_SUBAGENTS_E2E_CHILD_TEXT;
141
- else process.env.SELESAI_SUBAGENTS_E2E_CHILD_TEXT = previousChildText;
144
+ if (previousPiBinary === undefined) delete process.env.SELESAI_SUBAGENT_PI_BINARY;
145
+ else process.env.SELESAI_SUBAGENT_PI_BINARY = previousPiBinary;
142
146
  if (process.platform === "win32") {
143
147
  if (previousArgv1 === undefined) delete process.argv[1];
144
148
  else process.argv[1] = previousArgv1;
145
149
  }
150
+ if (previousChildText === undefined) delete process.env.SELESAI_SUBAGENTS_E2E_CHILD_TEXT;
151
+ else process.env.SELESAI_SUBAGENTS_E2E_CHILD_TEXT = previousChildText;
152
+ if (previousReportTools === undefined) delete process.env.SELESAI_SUBAGENTS_E2E_REPORT_CHILD_TOOLS;
153
+ else process.env.SELESAI_SUBAGENTS_E2E_REPORT_CHILD_TOOLS = previousReportTools;
146
154
  rmSync(rootDir, { recursive: true, force: true });
147
155
  };
148
156
  }
@@ -171,11 +179,10 @@ export async function runRealSubagentSession(options: RealSessionRunOptions): Pr
171
179
  ["SELESAI_SUBAGENT_MAX_DEPTH", process.env.SELESAI_SUBAGENT_MAX_DEPTH],
172
180
  ["SELESAI_SUBAGENT_PARENT_SESSION", process.env.SELESAI_SUBAGENT_PARENT_SESSION],
173
181
  ["SELESAI_SUBAGENT_PI_BINARY", process.env.SELESAI_SUBAGENT_PI_BINARY],
174
- ["SELESAI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT", process.env.SELESAI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT],
182
+ ["SELESAI_SUBAGENTS_SELESAI_CODING_AGENT_PACKAGE_ROOT", process.env.SELESAI_SUBAGENTS_SELESAI_CODING_AGENT_PACKAGE_ROOT],
175
183
  ]);
176
- const uninstallChildPi = installChildPiShim(options.childText);
184
+ const uninstallChildPi = installChildPiShim(options.childText, options.reportChildTools);
177
185
  let session: AgentSession | undefined;
178
- let faux: ReturnType<typeof registerFauxProvider> | undefined;
179
186
  let disposed = false;
180
187
 
181
188
  const dispose = async () => {
@@ -187,7 +194,6 @@ export async function runRealSubagentSession(options: RealSessionRunOptions): Pr
187
194
  try {
188
195
  session?.dispose();
189
196
  } catch {}
190
- faux?.unregister();
191
197
  uninstallChildPi();
192
198
  restoreEnv(envSnapshot);
193
199
  try {
@@ -208,14 +214,32 @@ export async function runRealSubagentSession(options: RealSessionRunOptions): Pr
208
214
  delete process.env.SELESAI_SUBAGENT_DEPTH;
209
215
  delete process.env.SELESAI_SUBAGENT_MAX_DEPTH;
210
216
  delete process.env.SELESAI_SUBAGENT_PARENT_SESSION;
211
- delete process.env.SELESAI_SUBAGENT_PI_BINARY;
212
- delete process.env.SELESAI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT;
217
+ delete process.env.SELESAI_SUBAGENTS_SELESAI_CODING_AGENT_PACKAGE_ROOT;
218
+ for (const [relativePath, content] of Object.entries(options.projectFiles ?? {})) {
219
+ const target = path.resolve(cwd, relativePath);
220
+ if (target !== cwd && !target.startsWith(`${cwd}${path.sep}`)) throw new Error(`E2E project file escapes cwd: ${relativePath}`);
221
+ mkdirSync(path.dirname(target), { recursive: true });
222
+ writeFileSync(target, content, "utf-8");
223
+ }
213
224
 
214
- faux = registerFauxProvider({
225
+ const faux = fauxProvider({
215
226
  provider: "faux-e2e-parent",
216
227
  models: [{ id: "parent", contextWindow: 200_000 }],
217
228
  });
218
- const model = faux.getModel();
229
+ const modelRuntime = await ModelRuntime.create({
230
+ authPath: path.join(home, "auth.json"),
231
+ modelsPath: null,
232
+ allowModelNetwork: false,
233
+ });
234
+ modelRuntime.registerProvider(faux.provider.id, {
235
+ name: faux.provider.name,
236
+ api: faux.api,
237
+ apiKey: "faux",
238
+ streamSimple: faux.provider.streamSimple,
239
+ models: [...faux.models],
240
+ });
241
+ const model = modelRuntime.getModel(faux.provider.id, "parent");
242
+ if (!model) throw new Error("faux parent model was not registered");
219
243
  const respond = options.respond;
220
244
  const responseFactory: FauxResponseStep = async (context, _streamOptions, state) => toAssistantMessage(await respond(context, state));
221
245
  faux.setResponses(Array.from({ length: 8 }, () => responseFactory));
@@ -241,7 +265,7 @@ export async function runRealSubagentSession(options: RealSessionRunOptions): Pr
241
265
  cwd,
242
266
  agentDir: home,
243
267
  model,
244
- modelRegistry: createModelRegistry(model) as unknown as ModelRegistry,
268
+ modelRuntime,
245
269
  resourceLoader: loader,
246
270
  sessionManager: SessionManager.create(cwd, path.join(home, "sessions")),
247
271
  settingsManager,
@@ -0,0 +1,42 @@
1
+ import { spawn } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import { acquireSessionLease } from "../../src/runs/shared/session-lease.ts";
4
+
5
+ const [sessionFile, rootDir, releaseSignal, mode = "hold"] = process.argv.slice(2);
6
+ if (!sessionFile || !rootDir) throw new Error("sessionFile and rootDir are required");
7
+
8
+ let lease;
9
+ try {
10
+ lease = acquireSessionLease({
11
+ sessionFile,
12
+ runId: `child-${process.pid}`,
13
+ sourceRunId: "source-child",
14
+ parentSessionId: "parent-child",
15
+ }, { rootDir });
16
+ } catch (error) {
17
+ process.stdout.write(`${JSON.stringify({ acquired: false, error: error instanceof Error ? error.message : String(error) })}\n`);
18
+ process.exit(2);
19
+ }
20
+ if (mode === "abandon-with-writer") {
21
+ lease.updateWriter({ state: "spawning" });
22
+ const writer = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], {
23
+ detached: true,
24
+ stdio: "ignore",
25
+ });
26
+ if (typeof writer.pid !== "number") throw new Error("writer pid is unavailable");
27
+ lease.updateWriter({ state: "running", pid: writer.pid });
28
+ writer.unref();
29
+ process.stdout.write(`${JSON.stringify({ acquired: true, owner: lease.owner, writerPid: writer.pid })}\n`);
30
+ process.exit(0);
31
+ }
32
+
33
+ process.stdout.write(`${JSON.stringify({ acquired: true, owner: lease.owner })}\n`);
34
+ if (mode === "abandon") process.exit(0);
35
+ if (!releaseSignal) throw new Error("releaseSignal is required in hold mode");
36
+
37
+ const timer = setInterval(() => {
38
+ if (!fs.existsSync(releaseSignal)) return;
39
+ clearInterval(timer);
40
+ lease.release();
41
+ process.exit(0);
42
+ }, 20);