@selesai/code 0.5.28 → 0.6.1

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 (341) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +9 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.d.ts.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/cli/credential-print.d.ts +23 -0
  10. package/dist/cli/credential-print.d.ts.map +1 -0
  11. package/dist/cli/credential-print.js +117 -0
  12. package/dist/cli/credential-print.js.map +1 -0
  13. package/dist/cli/startup-ui.d.ts.map +1 -1
  14. package/dist/cli/startup-ui.js +1 -1
  15. package/dist/cli/startup-ui.js.map +1 -1
  16. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  17. package/dist/core/agent-session-runtime.js +3 -0
  18. package/dist/core/agent-session-runtime.js.map +1 -1
  19. package/dist/core/agent-session.d.ts +12 -1
  20. package/dist/core/agent-session.d.ts.map +1 -1
  21. package/dist/core/agent-session.js +20 -14
  22. package/dist/core/agent-session.js.map +1 -1
  23. package/dist/core/compaction/compaction.d.ts.map +1 -1
  24. package/dist/core/compaction/compaction.js +11 -3
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/extensions/runner.d.ts +1 -0
  27. package/dist/core/extensions/runner.d.ts.map +1 -1
  28. package/dist/core/extensions/runner.js +11 -0
  29. package/dist/core/extensions/runner.js.map +1 -1
  30. package/dist/core/extensions/types.d.ts +14 -1
  31. package/dist/core/extensions/types.d.ts.map +1 -1
  32. package/dist/core/extensions/types.js.map +1 -1
  33. package/dist/core/footer-data-provider.d.ts +10 -0
  34. package/dist/core/footer-data-provider.d.ts.map +1 -1
  35. package/dist/core/footer-data-provider.js +1 -1
  36. package/dist/core/footer-data-provider.js.map +1 -1
  37. package/dist/core/llama/provider.d.ts.map +1 -1
  38. package/dist/core/llama/provider.js +8 -3
  39. package/dist/core/llama/provider.js.map +1 -1
  40. package/dist/core/model-config.d.ts +30 -0
  41. package/dist/core/model-config.d.ts.map +1 -1
  42. package/dist/core/model-config.js +6 -0
  43. package/dist/core/model-config.js.map +1 -1
  44. package/dist/core/model-registry.d.ts.map +1 -1
  45. package/dist/core/model-registry.js +2 -2
  46. package/dist/core/model-registry.js.map +1 -1
  47. package/dist/core/model-resolver.d.ts +1 -0
  48. package/dist/core/model-resolver.d.ts.map +1 -1
  49. package/dist/core/model-resolver.js +20 -3
  50. package/dist/core/model-resolver.js.map +1 -1
  51. package/dist/core/model-runtime.d.ts +2 -0
  52. package/dist/core/model-runtime.d.ts.map +1 -1
  53. package/dist/core/model-runtime.js +5 -4
  54. package/dist/core/model-runtime.js.map +1 -1
  55. package/dist/core/package-manager.d.ts.map +1 -1
  56. package/dist/core/package-manager.js +13 -6
  57. package/dist/core/package-manager.js.map +1 -1
  58. package/dist/core/remote-catalog-provider.d.ts +1 -1
  59. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  60. package/dist/core/remote-catalog-provider.js +24 -11
  61. package/dist/core/remote-catalog-provider.js.map +1 -1
  62. package/dist/core/resource-loader.d.ts +15 -0
  63. package/dist/core/resource-loader.d.ts.map +1 -1
  64. package/dist/core/resource-loader.js +66 -9
  65. package/dist/core/resource-loader.js.map +1 -1
  66. package/dist/core/settings-manager.d.ts +1 -1
  67. package/dist/core/settings-manager.d.ts.map +1 -1
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/system-prompt.d.ts.map +1 -1
  70. package/dist/core/system-prompt.js +19 -1
  71. package/dist/core/system-prompt.js.map +1 -1
  72. package/dist/core/system-prompt.test.d.ts +2 -0
  73. package/dist/core/system-prompt.test.d.ts.map +1 -0
  74. package/dist/core/system-prompt.test.js +89 -0
  75. package/dist/core/system-prompt.test.js.map +1 -0
  76. package/dist/core/tools/bash.d.ts +2 -0
  77. package/dist/core/tools/bash.d.ts.map +1 -1
  78. package/dist/core/tools/bash.js +34 -5
  79. package/dist/core/tools/bash.js.map +1 -1
  80. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  81. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  82. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  83. package/dist/defaults/models.json +13 -45
  84. package/dist/defaults/settings.json +1 -2
  85. package/dist/extensions/copy-turn.test.ts +131 -0
  86. package/dist/extensions/copy-turn.ts +6 -1
  87. package/dist/extensions/package.json +0 -1
  88. package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
  89. package/dist/extensions/pi-intercom/README.md +102 -39
  90. package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
  91. package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
  92. package/dist/extensions/pi-intercom/broker/client.ts +315 -12
  93. package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
  94. package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
  95. package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
  96. package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
  97. package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
  98. package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
  99. package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
  100. package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
  101. package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
  102. package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
  103. package/dist/extensions/pi-intercom/config.test.ts +93 -0
  104. package/dist/extensions/pi-intercom/config.ts +55 -6
  105. package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
  106. package/dist/extensions/pi-intercom/cwd.ts +31 -0
  107. package/dist/extensions/pi-intercom/extension-api.ts +44 -0
  108. package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
  109. package/dist/extensions/pi-intercom/format-context.ts +32 -0
  110. package/dist/extensions/pi-intercom/index.ts +742 -145
  111. package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
  112. package/dist/extensions/pi-intercom/package.json +15 -5
  113. package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
  114. package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
  115. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
  116. package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
  117. package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
  118. package/dist/extensions/pi-intercom/types.ts +94 -4
  119. package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
  120. package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
  121. package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
  122. package/dist/extensions/pi-subagents/CHANGELOG.md +88 -0
  123. package/dist/extensions/pi-subagents/LICENSE +21 -0
  124. package/dist/extensions/pi-subagents/README.md +158 -69
  125. package/dist/extensions/pi-subagents/agents/architect.md +5 -4
  126. package/dist/extensions/pi-subagents/agents/builder.md +6 -4
  127. package/dist/extensions/pi-subagents/agents/commentator.md +3 -2
  128. package/dist/extensions/pi-subagents/agents/explorer.md +3 -2
  129. package/dist/extensions/pi-subagents/agents/recapper.md +3 -2
  130. package/dist/extensions/pi-subagents/agents/researcher.md +4 -3
  131. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  132. package/dist/extensions/pi-subagents/package.json +3 -3
  133. package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -988
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +257 -0
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +431 -0
  137. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
  138. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +281 -0
  139. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +125 -37
  140. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
  141. package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
  142. package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
  143. package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +125 -0
  144. package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
  145. package/dist/extensions/pi-subagents/src/api/preflight.ts +17 -13
  146. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
  147. package/dist/extensions/pi-subagents/src/extension/index.ts +22 -8
  148. package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
  149. package/dist/extensions/pi-subagents/src/extension/schemas.ts +33 -8
  150. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +32 -14
  151. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
  152. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
  153. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +86 -21
  154. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
  155. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
  156. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  157. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
  158. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
  159. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +56 -5
  160. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +92 -11
  161. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +388 -29
  163. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
  164. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
  165. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +214 -60
  166. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +546 -252
  167. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
  168. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +553 -154
  169. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +19 -13
  170. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
  171. package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
  172. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  173. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
  174. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
  175. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
  176. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
  177. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
  178. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
  179. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
  180. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
  181. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +63 -9
  182. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
  183. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
  184. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +22 -1
  185. package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
  186. package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
  187. package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
  188. package/dist/extensions/pi-subagents/src/shared/types.ts +196 -12
  189. package/dist/extensions/pi-subagents/src/shared/utils.ts +102 -5
  190. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +10 -1
  191. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
  192. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
  193. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
  194. package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
  195. package/dist/extensions/pi-subagents/src/tui/render.ts +55 -34
  196. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
  197. package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
  198. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
  199. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
  200. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
  201. package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
  202. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +113 -8
  203. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +419 -47
  204. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
  205. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
  206. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
  207. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +62 -22
  208. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
  209. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +8 -6
  210. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +231 -19
  211. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +48 -7
  212. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
  213. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +199 -17
  214. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +898 -16
  215. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
  216. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
  217. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
  218. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +18 -2
  219. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
  220. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
  221. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
  222. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +198 -6
  223. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +161 -1
  224. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
  225. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
  226. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
  227. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +63 -0
  228. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +136 -0
  229. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
  230. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
  231. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
  232. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
  233. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
  234. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
  235. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +28 -2
  236. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
  237. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
  238. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
  239. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
  240. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +17 -12
  241. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
  242. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
  243. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
  244. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +39 -2
  245. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
  246. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
  247. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
  248. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +48 -0
  249. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
  250. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
  251. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
  252. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
  253. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
  254. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +46 -2
  255. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +91 -1
  256. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  257. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
  258. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
  259. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
  260. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
  261. package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +213 -0
  262. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -1
  263. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +61 -10
  264. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
  265. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
  266. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
  267. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
  268. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
  269. package/dist/main.d.ts.map +1 -1
  270. package/dist/main.js +43 -0
  271. package/dist/main.js.map +1 -1
  272. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  273. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  274. package/dist/modes/interactive/components/custom-message.js +10 -2
  275. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  276. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  277. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  278. package/dist/modes/interactive/components/extension-editor.js +16 -46
  279. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  280. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/model-selector.js +4 -1
  282. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  283. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  284. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  285. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  286. package/dist/modes/interactive/external-editor.d.ts +12 -0
  287. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  288. package/dist/modes/interactive/external-editor.js +37 -0
  289. package/dist/modes/interactive/external-editor.js.map +1 -0
  290. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  291. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  292. package/dist/modes/interactive/interactive-mode.js +72 -64
  293. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  294. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  295. package/dist/modes/rpc/rpc-mode.js +14 -0
  296. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  297. package/dist/skills/ponytail/SKILL.md +1 -3
  298. package/dist/utils/clipboard.d.ts.map +1 -1
  299. package/dist/utils/clipboard.js +19 -8
  300. package/dist/utils/clipboard.js.map +1 -1
  301. package/dist/utils/version-check.d.ts.map +1 -1
  302. package/dist/utils/version-check.js +1 -1
  303. package/dist/utils/version-check.js.map +1 -1
  304. package/docs/compaction.md +1 -1
  305. package/docs/custom-provider.md +14 -5
  306. package/docs/environment-variables.md +86 -0
  307. package/docs/extensions.md +15 -5
  308. package/docs/index.md +1 -0
  309. package/docs/models.md +9 -2
  310. package/docs/plans/subagent-delegation/phase-0-correctness.md +265 -0
  311. package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +486 -0
  312. package/docs/plans/subagent-delegation/phase-2-context-controls.md +282 -0
  313. package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +362 -0
  314. package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +381 -0
  315. package/docs/providers.md +21 -2
  316. package/docs/rpc.md +23 -6
  317. package/docs/session-format.md +2 -0
  318. package/docs/settings.md +1 -1
  319. package/docs/usage.md +0 -13
  320. package/examples/extensions/custom-compaction.ts +5 -2
  321. package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
  322. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  323. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  324. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  325. package/examples/extensions/gondolin/package-lock.json +2 -2
  326. package/examples/extensions/gondolin/package.json +1 -1
  327. package/examples/extensions/handoff.ts +11 -3
  328. package/examples/extensions/message-renderer.ts +3 -3
  329. package/examples/extensions/sandbox/package-lock.json +2 -2
  330. package/examples/extensions/sandbox/package.json +1 -1
  331. package/examples/extensions/summarize.ts +5 -2
  332. package/examples/extensions/with-deps/package-lock.json +2 -2
  333. package/examples/extensions/with-deps/package.json +1 -1
  334. package/examples/sdk/12-full-control.ts +3 -1
  335. package/package.json +16 -7
  336. package/dist/extensions/caveman/caveman-instructions.cjs +0 -11
  337. package/dist/extensions/caveman/index.js +0 -118
  338. package/dist/extensions/caveman/package.json +0 -8
  339. package/dist/extensions/caveman/test/extension.test.js +0 -203
  340. package/dist/extensions/caveman/test/helpers.test.js +0 -58
  341. package/dist/skills/caveman/SKILL.md +0 -50
@@ -1,15 +1,18 @@
1
1
  import type { Socket } from "net";
2
2
 
3
+ export const MAX_FRAME_BYTES = 1024 * 1024;
4
+
3
5
  /**
4
6
  * Write a length-prefixed message to a socket.
5
7
  * Format: 4-byte big-endian length + JSON payload
6
8
  */
7
9
  export function writeMessage(socket: Socket, msg: unknown): void {
8
10
  const json = JSON.stringify(msg);
9
- const payload = Buffer.from(json, "utf-8");
10
- const header = Buffer.alloc(4);
11
- header.writeUInt32BE(payload.length, 0);
12
- socket.write(Buffer.concat([header, payload]));
11
+ const payloadLength = Buffer.byteLength(json, "utf-8");
12
+ const frame = Buffer.allocUnsafe(4 + payloadLength);
13
+ frame.writeUInt32BE(payloadLength, 0);
14
+ frame.write(json, 4, payloadLength, "utf-8");
15
+ socket.write(frame);
13
16
  }
14
17
 
15
18
  /**
@@ -20,36 +23,91 @@ export function writeMessage(socket: Socket, msg: unknown): void {
20
23
  export function createMessageReader(
21
24
  onMessage: (msg: unknown) => void,
22
25
  onError: (error: Error) => void,
26
+ maxFrameBytes = MAX_FRAME_BYTES,
23
27
  ) {
24
- let buffer = Buffer.alloc(0);
28
+ const header = Buffer.allocUnsafe(4);
29
+ let headerBytes = 0;
30
+ let payload: Buffer | null = null;
31
+ let payloadBytes = 0;
32
+ let payloadLength = 0;
33
+
34
+ function reportMessage(framePayload: Buffer): boolean {
35
+ let msg: unknown;
36
+ try {
37
+ msg = JSON.parse(framePayload.toString("utf-8"));
38
+ } catch (error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error }));
41
+ return false;
42
+ }
43
+
44
+ try {
45
+ onMessage(msg);
46
+ return true;
47
+ } catch (error) {
48
+ const message = error instanceof Error ? error.message : String(error);
49
+ onError(new Error(`Failed to handle intercom message: ${message}`, { cause: error }));
50
+ return false;
51
+ }
52
+ }
25
53
 
26
54
  return (data: Buffer) => {
27
- buffer = Buffer.concat([buffer, data]);
55
+ let offset = 0;
28
56
 
29
- while (buffer.length >= 4) {
30
- const length = buffer.readUInt32BE(0);
31
-
32
- if (buffer.length < 4 + length) {
33
- break;
57
+ while (offset < data.length) {
58
+ if (headerBytes < 4) {
59
+ const bytes = Math.min(4 - headerBytes, data.length - offset);
60
+ data.copy(header, headerBytes, offset, offset + bytes);
61
+ headerBytes += bytes;
62
+ offset += bytes;
63
+ if (headerBytes < 4) {
64
+ return;
65
+ }
66
+
67
+ payloadLength = header.readUInt32BE(0);
68
+ if (payloadLength > maxFrameBytes) {
69
+ headerBytes = 0;
70
+ onError(new Error(`Intercom frame length ${payloadLength} exceeds maximum ${maxFrameBytes} bytes`));
71
+ return;
72
+ }
73
+ }
74
+
75
+ // Fast path: the whole payload is already in this chunk, so parse it
76
+ // in place without copying into a reassembly buffer.
77
+ if (payloadBytes === 0 && data.length - offset >= payloadLength) {
78
+ const framePayload = data.subarray(offset, offset + payloadLength);
79
+ offset += payloadLength;
80
+ headerBytes = 0;
81
+ payload = null;
82
+ payloadLength = 0;
83
+ if (!reportMessage(framePayload)) {
84
+ return;
85
+ }
86
+ continue;
34
87
  }
35
88
 
36
- const payload = buffer.subarray(4, 4 + length);
37
- buffer = buffer.subarray(4 + length);
89
+ // A buffer allocated for a previous frame (e.g. when a chunk ended
90
+ // exactly on a header boundary) must not be reused for a frame of a
91
+ // different size, so the reassembly buffer is size-checked, not just
92
+ // presence-checked.
93
+ if (payload === null || payload.length !== payloadLength) {
94
+ payload = Buffer.allocUnsafe(payloadLength);
95
+ }
96
+ const bytes = Math.min(payloadLength - payloadBytes, data.length - offset);
97
+ data.copy(payload, payloadBytes, offset, offset + bytes);
98
+ payloadBytes += bytes;
99
+ offset += bytes;
38
100
 
39
- let msg: unknown;
40
- try {
41
- msg = JSON.parse(payload.toString("utf-8"));
42
- } catch (error) {
43
- const message = error instanceof Error ? error.message : String(error);
44
- onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error }));
101
+ if (payloadBytes < payloadLength) {
45
102
  return;
46
103
  }
47
104
 
48
- try {
49
- onMessage(msg);
50
- } catch (error) {
51
- const message = error instanceof Error ? error.message : String(error);
52
- onError(new Error(`Failed to handle intercom message: ${message}`, { cause: error }));
105
+ const framePayload = payload;
106
+ headerBytes = 0;
107
+ payload = null;
108
+ payloadBytes = 0;
109
+ payloadLength = 0;
110
+ if (!reportMessage(framePayload)) {
53
111
  return;
54
112
  }
55
113
  }
@@ -0,0 +1,153 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { chmodSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { getAgentDir } from "@selesai/code";
7
+ import {
8
+ ensureIntercomRuntimeDir,
9
+ getAgentDirPath,
10
+ getBrokerConnectTarget,
11
+ getBrokerListenTarget,
12
+ getBrokerPortFilePath,
13
+ getBrokerSocketPath,
14
+ getIntercomDirPath,
15
+ INTERCOM_DIR_MODE,
16
+ INTERCOM_RUNTIME_FILE_MODE,
17
+ INTERCOM_TCP_HOST,
18
+ restrictIntercomRuntimeFile,
19
+ shouldUseWindowsTcpTransport,
20
+ } from "./paths.ts";
21
+
22
+ test("getAgentDirPath defaults to Selesai's configured agent directory", () => {
23
+ assert.equal(getAgentDirPath({}), getAgentDir());
24
+ });
25
+
26
+ test("getAgentDirPath honors SELESAI_CODING_AGENT_DIR", () => {
27
+ assert.equal(getAgentDirPath({ SELESAI_CODING_AGENT_DIR: "/tmp/selesai-agent" }), "/tmp/selesai-agent");
28
+ });
29
+
30
+ test("getAgentDirPath resolves relative SELESAI_CODING_AGENT_DIR values from the caller cwd", () => {
31
+ const cwd = join(tmpdir(), "workspace", "project");
32
+ assert.equal(
33
+ getAgentDirPath({ SELESAI_CODING_AGENT_DIR: "relative-agent" }, cwd),
34
+ join(cwd, "relative-agent"),
35
+ );
36
+ });
37
+
38
+ test("getIntercomDirPath points at the intercom runtime directory under the agent dir", () => {
39
+ assert.equal(getIntercomDirPath("/tmp/selesai-agent"), join("/tmp/selesai-agent", "intercom"));
40
+ });
41
+
42
+ test("getBrokerSocketPath uses named pipe on Windows", () => {
43
+ const pipePath = getBrokerSocketPath("win32", "C:/Users/rcroh/.selesai/agent");
44
+ assert.match(pipePath, /^\\\\\.\\pipe\\pi-intercom-/);
45
+ assert.doesNotMatch(pipePath, /broker\.sock$/);
46
+ });
47
+
48
+ test("getBrokerSocketPath uses broker.sock under SELESAI_CODING_AGENT_DIR on non-Windows", () => {
49
+ const socketPath = getBrokerSocketPath("linux", "/tmp/selesai-agent");
50
+ assert.equal(socketPath, join("/tmp/selesai-agent", "intercom", "broker.sock"));
51
+ });
52
+
53
+ test("Windows TCP transport is opt-in", () => {
54
+ assert.equal(shouldUseWindowsTcpTransport("win32", {}), false);
55
+ assert.equal(shouldUseWindowsTcpTransport("win32", { PI_INTERCOM_TRANSPORT: "tcp" }), true);
56
+ assert.equal(shouldUseWindowsTcpTransport("win32", { PI_INTERCOM_TCP: "1" }), true);
57
+ assert.equal(shouldUseWindowsTcpTransport("linux", { PI_INTERCOM_TRANSPORT: "tcp" }), false);
58
+ });
59
+
60
+ test("getBrokerListenTarget uses dynamic localhost TCP only when opted in on Windows", () => {
61
+ assert.deepEqual(getBrokerListenTarget("win32", { PI_INTERCOM_TRANSPORT: "tcp" }), {
62
+ transport: "tcp",
63
+ host: INTERCOM_TCP_HOST,
64
+ port: 0,
65
+ });
66
+ assert.equal(getBrokerListenTarget("win32", {}), getBrokerSocketPath("win32", getAgentDirPath({})));
67
+ });
68
+
69
+ test("getBrokerConnectTarget reads opt-in Windows TCP endpoint from intercom state", () => {
70
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
71
+ const intercomDir = join(root, "intercom");
72
+
73
+ try {
74
+ ensureIntercomRuntimeDir(intercomDir, "win32");
75
+ writeFileSync(getBrokerPortFilePath(intercomDir), JSON.stringify({
76
+ transport: "tcp",
77
+ host: "127.0.0.1",
78
+ port: 41234,
79
+ stateId: "state-1",
80
+ }));
81
+ assert.deepEqual(getBrokerConnectTarget("win32", { PI_INTERCOM_TRANSPORT: "tcp" }, intercomDir), {
82
+ transport: "tcp",
83
+ host: "127.0.0.1",
84
+ port: 41234,
85
+ stateId: "state-1",
86
+ });
87
+ } finally {
88
+ rmSync(root, { recursive: true, force: true });
89
+ }
90
+ });
91
+
92
+ test("getBrokerConnectTarget rejects non-local TCP endpoint hosts", () => {
93
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
94
+ const intercomDir = join(root, "intercom");
95
+
96
+ try {
97
+ ensureIntercomRuntimeDir(intercomDir, "win32");
98
+ writeFileSync(getBrokerPortFilePath(intercomDir), JSON.stringify({
99
+ transport: "tcp",
100
+ host: "10.0.0.5",
101
+ port: 41234,
102
+ stateId: "state-1",
103
+ }));
104
+ assert.throws(
105
+ () => getBrokerConnectTarget("win32", { PI_INTERCOM_TRANSPORT: "tcp" }, intercomDir),
106
+ /Invalid intercom TCP endpoint/,
107
+ );
108
+ } finally {
109
+ rmSync(root, { recursive: true, force: true });
110
+ }
111
+ });
112
+
113
+ test("ensureIntercomRuntimeDir creates and repairs restrictive Unix directory permissions", { skip: process.platform === "win32" }, () => {
114
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
115
+ const intercomDir = join(root, "intercom");
116
+
117
+ try {
118
+ ensureIntercomRuntimeDir(intercomDir, "linux");
119
+ assert.equal(statSync(intercomDir).mode & 0o777, INTERCOM_DIR_MODE);
120
+
121
+ chmodSync(intercomDir, 0o755);
122
+ ensureIntercomRuntimeDir(intercomDir, "linux");
123
+ assert.equal(statSync(intercomDir).mode & 0o777, INTERCOM_DIR_MODE);
124
+ } finally {
125
+ rmSync(root, { recursive: true, force: true });
126
+ }
127
+ });
128
+
129
+ test("restrictIntercomRuntimeFile applies restrictive Unix file permissions", { skip: process.platform === "win32" }, () => {
130
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
131
+ const filePath = join(root, "broker.pid");
132
+
133
+ try {
134
+ writeFileSync(filePath, "123", { mode: 0o644 });
135
+ restrictIntercomRuntimeFile(filePath, "linux");
136
+ assert.equal(statSync(filePath).mode & 0o777, INTERCOM_RUNTIME_FILE_MODE);
137
+ } finally {
138
+ rmSync(root, { recursive: true, force: true });
139
+ }
140
+ });
141
+
142
+ test("runtime permission helpers skip chmod on Windows paths", () => {
143
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
144
+ const filePath = join(root, "broker.pid");
145
+
146
+ try {
147
+ ensureIntercomRuntimeDir(root, "win32");
148
+ writeFileSync(filePath, "123");
149
+ assert.doesNotThrow(() => restrictIntercomRuntimeFile(filePath, "win32"));
150
+ } finally {
151
+ rmSync(root, { recursive: true, force: true });
152
+ }
153
+ });
@@ -1,6 +1,22 @@
1
- import { join } from "path";
1
+ import { chmodSync, mkdirSync, readFileSync } from "fs";
2
+ import { isAbsolute, join, resolve } from "path";
2
3
  import { getAgentDir } from "@selesai/code";
3
4
 
5
+ export const INTERCOM_DIR_MODE = 0o700;
6
+ export const INTERCOM_RUNTIME_FILE_MODE = 0o600;
7
+ export const INTERCOM_TCP_HOST = "127.0.0.1";
8
+ export const INTERCOM_PROTOCOL_NAME = "pi-intercom";
9
+ export const INTERCOM_PROTOCOL_VERSION = 1;
10
+
11
+ export interface BrokerTcpEndpoint {
12
+ transport: "tcp";
13
+ host: string;
14
+ port: number;
15
+ stateId?: string;
16
+ }
17
+
18
+ export type BrokerConnectTarget = string | BrokerTcpEndpoint;
19
+
4
20
  function sanitizePipeSegment(value: string): string {
5
21
  return value
6
22
  .replace(/[^a-zA-Z0-9]+/g, "-")
@@ -8,18 +24,111 @@ function sanitizePipeSegment(value: string): string {
8
24
  .toLowerCase() || "default";
9
25
  }
10
26
 
11
- /** selesai config dir (~/.selesai/agent on the selesai fork, ~/.pi/agent upstream). */
12
- export function getIntercomDir(): string {
13
- return join(getAgentDir(), "intercom");
27
+ export function getAgentDirPath(
28
+ env: NodeJS.ProcessEnv = process.env,
29
+ cwd: string = process.cwd(),
30
+ ): string {
31
+ const configured = env.SELESAI_CODING_AGENT_DIR?.trim();
32
+ if (!configured) {
33
+ return getAgentDir();
34
+ }
35
+
36
+ return isAbsolute(configured) ? configured : resolve(cwd, configured);
37
+ }
38
+
39
+ export function getIntercomDirPath(agentDir: string = getAgentDirPath()): string {
40
+ return join(agentDir, "intercom");
41
+ }
42
+
43
+ export function shouldUseWindowsTcpTransport(
44
+ platform: NodeJS.Platform = process.platform,
45
+ env: NodeJS.ProcessEnv = process.env,
46
+ ): boolean {
47
+ if (platform !== "win32") {
48
+ return false;
49
+ }
50
+
51
+ const transport = env.PI_INTERCOM_TRANSPORT?.trim().toLowerCase();
52
+ if (transport === "tcp") {
53
+ return true;
54
+ }
55
+
56
+ const legacyOptIn = env.PI_INTERCOM_TCP?.trim().toLowerCase();
57
+ return legacyOptIn === "1" || legacyOptIn === "true";
58
+ }
59
+
60
+ export function getBrokerPortFilePath(intercomDir: string = getIntercomDirPath()): string {
61
+ return join(intercomDir, "broker.port.json");
14
62
  }
15
63
 
16
64
  export function getBrokerSocketPath(
17
65
  platform: NodeJS.Platform = process.platform,
18
- intercomDir: string = getIntercomDir(),
66
+ agentDir: string = getAgentDirPath(),
19
67
  ): string {
20
68
  if (platform === "win32") {
21
- return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(intercomDir)}`;
69
+ return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(agentDir)}`;
22
70
  }
23
71
 
24
- return join(intercomDir, "broker.sock");
25
- }
72
+ return join(getIntercomDirPath(agentDir), "broker.sock");
73
+ }
74
+
75
+ export function getBrokerConnectTarget(
76
+ platform: NodeJS.Platform = process.platform,
77
+ env: NodeJS.ProcessEnv = process.env,
78
+ intercomDir: string = getIntercomDirPath(getAgentDirPath(env)),
79
+ ): BrokerConnectTarget {
80
+ if (shouldUseWindowsTcpTransport(platform, env)) {
81
+ const endpointFile = getBrokerPortFilePath(intercomDir);
82
+ const raw = readFileSync(endpointFile, "utf-8");
83
+ const parsed: unknown = JSON.parse(raw);
84
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
85
+ throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}: expected a JSON object`);
86
+ }
87
+ const endpoint = parsed as Record<string, unknown>;
88
+ if (
89
+ endpoint.transport !== "tcp"
90
+ || endpoint.host !== INTERCOM_TCP_HOST
91
+ || typeof endpoint.port !== "number"
92
+ || !Number.isSafeInteger(endpoint.port)
93
+ || endpoint.port <= 0
94
+ || endpoint.port > 65535
95
+ || typeof endpoint.stateId !== "string"
96
+ || endpoint.stateId.length === 0
97
+ ) {
98
+ throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}`);
99
+ }
100
+ return { transport: "tcp", host: endpoint.host, port: endpoint.port, stateId: endpoint.stateId };
101
+ }
102
+
103
+ return getBrokerSocketPath(platform, getAgentDirPath(env));
104
+ }
105
+
106
+ export function getBrokerListenTarget(
107
+ platform: NodeJS.Platform = process.platform,
108
+ env: NodeJS.ProcessEnv = process.env,
109
+ ): BrokerConnectTarget {
110
+ if (shouldUseWindowsTcpTransport(platform, env)) {
111
+ return { transport: "tcp", host: INTERCOM_TCP_HOST, port: 0 };
112
+ }
113
+
114
+ return getBrokerSocketPath(platform, getAgentDirPath(env));
115
+ }
116
+
117
+ export function ensureIntercomRuntimeDir(
118
+ intercomDir: string = getIntercomDirPath(),
119
+ platform: NodeJS.Platform = process.platform,
120
+ ): void {
121
+ mkdirSync(intercomDir, { recursive: true, mode: INTERCOM_DIR_MODE });
122
+ if (platform !== "win32") {
123
+ chmodSync(intercomDir, INTERCOM_DIR_MODE);
124
+ }
125
+ }
126
+
127
+ export function restrictIntercomRuntimeFile(
128
+ filePath: string,
129
+ platform: NodeJS.Platform = process.platform,
130
+ ): void {
131
+ if (platform !== "win32") {
132
+ chmodSync(filePath, INTERCOM_RUNTIME_FILE_MODE);
133
+ }
134
+ }
@@ -0,0 +1,34 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import path from "node:path";
4
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { assertNoLiveBroker } from "./runtime-claim.ts";
7
+
8
+ test("broker startup refuses to replace a live broker PID", () => {
9
+ const directory = mkdtempSync(path.join(tmpdir(), "pi-intercom-runtime-"));
10
+ const pidPath = path.join(directory, "broker.pid");
11
+ try {
12
+ writeFileSync(pidPath, `${process.pid}\n`);
13
+ assert.throws(
14
+ () => assertNoLiveBroker(pidPath),
15
+ new RegExp(`Refusing to replace live intercom broker process ${process.pid}`),
16
+ );
17
+ } finally {
18
+ rmSync(directory, { recursive: true, force: true });
19
+ }
20
+ });
21
+
22
+ test("broker startup tolerates absent, invalid, and stale PID files", () => {
23
+ const directory = mkdtempSync(path.join(tmpdir(), "pi-intercom-runtime-"));
24
+ const pidPath = path.join(directory, "broker.pid");
25
+ try {
26
+ assert.doesNotThrow(() => assertNoLiveBroker(pidPath));
27
+ writeFileSync(pidPath, "invalid\n");
28
+ assert.doesNotThrow(() => assertNoLiveBroker(pidPath));
29
+ writeFileSync(pidPath, "2147483647\n");
30
+ assert.doesNotThrow(() => assertNoLiveBroker(pidPath));
31
+ } finally {
32
+ rmSync(directory, { recursive: true, force: true });
33
+ }
34
+ });
@@ -0,0 +1,21 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+
3
+ export function assertNoLiveBroker(pidPath: string): void {
4
+ if (!existsSync(pidPath)) return;
5
+
6
+ let pid: number;
7
+ try {
8
+ pid = Number.parseInt(readFileSync(pidPath, "utf8").trim(), 10);
9
+ } catch {
10
+ return;
11
+ }
12
+ if (!Number.isSafeInteger(pid) || pid <= 0) return;
13
+
14
+ try {
15
+ process.kill(pid, 0);
16
+ } catch (error) {
17
+ if ((error as NodeJS.ErrnoException).code === "ESRCH") return;
18
+ throw error;
19
+ }
20
+ throw new Error(`Refusing to replace live intercom broker process ${pid}`);
21
+ }
@@ -1,31 +1,168 @@
1
- import { createRequire } from "module";
2
- import { describe, expect, it } from "vitest";
3
- import { getBrokerLaunchSpec, getWindowsBrokerCommandLine } from "./spawn.ts";
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import path from "node:path";
4
+ import { existsSync, mkdtempSync, rmSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import {
7
+ getBrokerLaunchSpec,
8
+ getBrokerSpawnOptions,
9
+ getTsxCliPath,
10
+ getWindowsHiddenLauncherScript,
11
+ getWindowsBrokerCommandLine,
12
+ getWindowsHiddenLauncherPath,
13
+ isBrokerHealthOkMessage,
14
+ } from "./spawn.ts";
4
15
 
5
- const require = createRequire(import.meta.url);
6
- const brokerPath = "C:\\broker dir\\broker.ts";
7
- const extensionDir = "C:\\published-package\\dist\\extensions\\pi-intercom";
8
- const nodePath = "C:\\Program Files\\nodejs\\node.exe";
16
+ test("getTsxCliPath resolves tsx cli via module resolution", () => {
17
+ const cliPath = getTsxCliPath("C:/repo");
18
+ // getTsxCliPath resolves the tsx package main entry and locates cli.mjs next
19
+ // to it, so the path reflects the real install location (bundled under
20
+ // extensionDir or hoisted by npm) rather than a hardcoded relative path.
21
+ assert.equal(path.basename(cliPath), "cli.mjs");
22
+ assert.equal(path.basename(path.dirname(cliPath)), "dist");
23
+ assert.equal(path.basename(path.dirname(path.dirname(cliPath))), "tsx");
24
+ });
25
+
26
+ test("getWindowsHiddenLauncherPath points at the broker launcher script", () => {
27
+ const launcherPath = getWindowsHiddenLauncherPath("C:/tmp/intercom");
28
+ assert.equal(launcherPath, path.join("C:/tmp/intercom", "broker-launch.vbs"));
29
+ });
30
+
31
+ test("getWindowsBrokerCommandLine wraps node, resolved tsx cli, and broker path", () => {
32
+ const commandLine = getWindowsBrokerCommandLine(
33
+ "C:/repo/broker.ts",
34
+ "C:/repo",
35
+ "C:/Program Files/nodejs/node.exe",
36
+ );
37
+ const expectedTsxPath = getTsxCliPath("C:/repo");
38
+ assert.equal(
39
+ commandLine,
40
+ `"C:/Program Files/nodejs/node.exe" "${expectedTsxPath}" "C:/repo/broker.ts"`,
41
+ );
42
+ });
43
+
44
+ test("getWindowsHiddenLauncherScript runs the broker command without showing a console", () => {
45
+ const script = getWindowsHiddenLauncherScript('"C:/Program Files/nodejs/node.exe" "C:/repo/node_modules/tsx/dist/cli.mjs" "C:/repo/broker.ts"');
46
+ assert.match(script, /WshShell\.Run/);
47
+ assert.match(script, /, 0, False/);
48
+ });
9
49
 
10
- describe("intercom broker launch", () => {
11
- it("resolves default Windows tsx launcher from installed dependencies", () => {
12
- const tsxCliPath = require.resolve("tsx/cli");
50
+ test("getBrokerLaunchSpec uses wscript launcher on Windows without writing files", () => {
51
+ const intercomDir = mkdtempSync(path.join(tmpdir(), "pi-intercom-"));
13
52
 
14
- expect(getWindowsBrokerCommandLine(brokerPath, extensionDir, nodePath)).toBe(
15
- `"${nodePath}" "${tsxCliPath}" "${brokerPath}"`,
53
+ try {
54
+ const spec = getBrokerLaunchSpec(
55
+ "C:/repo/broker.ts",
56
+ "npx",
57
+ ["--no-install", "tsx"],
58
+ "C:/repo",
59
+ "win32",
60
+ intercomDir,
61
+ "C:/Program Files/nodejs/node.exe",
16
62
  );
17
- expect(getWindowsBrokerCommandLine(brokerPath, extensionDir, nodePath)).not.toContain(extensionDir);
18
- });
63
+ assert.equal(spec.command, "wscript.exe");
64
+ assert.deepEqual(spec.args, [path.join(intercomDir, "broker-launch.vbs")]);
65
+ assert.equal(spec.kind, "windows-launcher");
66
+ const expectedTsxPath = getTsxCliPath("C:/repo");
67
+ assert.equal(spec.launcherCommandLine, `"C:/Program Files/nodejs/node.exe" "${expectedTsxPath}" "C:/repo/broker.ts"`);
68
+ assert.equal(existsSync(path.join(intercomDir, "broker-launch.vbs")), false);
69
+ } finally {
70
+ rmSync(intercomDir, { recursive: true, force: true });
71
+ }
72
+ });
73
+
74
+ test("getBrokerLaunchSpec falls back to PATH node for a standalone Selesai executable on Windows", () => {
75
+ const intercomDir = mkdtempSync(path.join(tmpdir(), "pi-intercom-"));
19
76
 
20
- it("preserves custom broker commands on Windows and other platforms", () => {
21
- expect(getWindowsBrokerCommandLine(brokerPath, extensionDir, nodePath, "broker-command", ["--port", "1234"])).toBe(
22
- `"broker-command" "--port" "1234" "${brokerPath}"`,
77
+ try {
78
+ const spec = getBrokerLaunchSpec(
79
+ "C:/repo/broker.ts",
80
+ "npx",
81
+ ["--no-install", "tsx"],
82
+ "C:/repo",
83
+ "win32",
84
+ intercomDir,
85
+ "C:/Program Files/Selesai/selesai.exe",
23
86
  );
87
+ assert.equal(spec.kind, "windows-launcher");
88
+ assert.equal(spec.launcherCommandLine, `"node" "${getTsxCliPath("C:/repo")}" "C:/repo/broker.ts"`);
89
+ } finally {
90
+ rmSync(intercomDir, { recursive: true, force: true });
91
+ }
92
+ });
93
+
94
+ test("getBrokerLaunchSpec uses custom broker command on Windows", () => {
95
+ const intercomDir = mkdtempSync(path.join(tmpdir(), "pi-intercom-"));
96
+
97
+ try {
98
+ const spec = getBrokerLaunchSpec("C:/repo/broker.ts", "bun", ["--smol"], "C:/repo", "win32", intercomDir, "C:/Program Files/nodejs/node.exe");
99
+ assert.equal(spec.command, "wscript.exe");
100
+ assert.equal(spec.kind, "windows-launcher");
101
+ assert.equal(spec.launcherCommandLine, `"bun" "--smol" "C:/repo/broker.ts"`);
102
+ } finally {
103
+ rmSync(intercomDir, { recursive: true, force: true });
104
+ }
105
+ });
106
+
107
+ test("getBrokerLaunchSpec uses node + resolved tsx for the default non-Windows launch", () => {
108
+ const spec = getBrokerLaunchSpec("C:/repo/broker.ts", "npx", ["--no-install", "tsx"], "C:/repo", "linux", "/tmp/intercom", "/usr/bin/node");
109
+ assert.equal(spec.command, "/usr/bin/node");
110
+ assert.deepEqual(spec.args, [
111
+ getTsxCliPath("C:/repo"),
112
+ "C:/repo/broker.ts",
113
+ ]);
114
+ assert.equal(spec.kind, "direct");
115
+ });
116
+
117
+ test("getBrokerLaunchSpec falls back to PATH node for a standalone Selesai executable on non-Windows", () => {
118
+ const spec = getBrokerLaunchSpec(
119
+ "/repo/broker.ts",
120
+ "npx",
121
+ ["--no-install", "tsx"],
122
+ "/repo",
123
+ "darwin",
124
+ "/tmp/intercom",
125
+ "/Applications/Selesai.app/Contents/MacOS/selesai",
126
+ );
127
+ assert.equal(spec.command, "node");
128
+ assert.deepEqual(spec.args, [
129
+ getTsxCliPath("/repo"),
130
+ "/repo/broker.ts",
131
+ ]);
132
+ assert.equal(spec.kind, "direct");
133
+ });
134
+
135
+ test("getBrokerLaunchSpec uses custom broker command on non-Windows", () => {
136
+ const spec = getBrokerLaunchSpec("/repo/broker.ts", "bun", [], "/repo", "linux", "/tmp/intercom", "/usr/bin/node");
137
+ assert.equal(spec.command, "bun");
138
+ assert.deepEqual(spec.args, ["/repo/broker.ts"]);
139
+ assert.equal(spec.kind, "direct");
140
+ });
141
+
142
+ test("getBrokerSpawnOptions hides the broker console window on Windows", () => {
143
+ const options = getBrokerSpawnOptions("C:/repo");
144
+ assert.equal(options.windowsHide, true);
145
+ assert.equal(options.detached, true);
146
+ assert.equal(options.stdio, "ignore");
147
+ assert.equal(options.cwd, "C:/repo");
148
+ });
149
+
150
+ test("getBrokerSpawnOptions keeps portable defaults on non-Windows platforms", () => {
151
+ const options = getBrokerSpawnOptions("/repo");
152
+ assert.equal(options.windowsHide, true);
153
+ assert.equal(options.detached, true);
154
+ assert.equal(options.stdio, "ignore");
155
+ assert.equal(options.cwd, "/repo");
156
+ });
157
+
158
+ test("getBrokerSpawnOptions passes an absolute SELESAI_CODING_AGENT_DIR to the broker", () => {
159
+ const options = getBrokerSpawnOptions("/repo", { SELESAI_CODING_AGENT_DIR: "relative-agent" });
160
+ assert.equal(options.env.SELESAI_CODING_AGENT_DIR, path.resolve("relative-agent"));
161
+ });
24
162
 
25
- expect(getBrokerLaunchSpec(brokerPath, "broker-command", ["--port", "1234"], extensionDir, "linux")).toEqual({
26
- kind: "direct",
27
- command: "broker-command",
28
- args: ["--port", "1234", brokerPath],
29
- });
30
- });
163
+ test("isBrokerHealthOkMessage requires the intercom protocol marker", () => {
164
+ assert.equal(isBrokerHealthOkMessage({ type: "health_ok", requestId: "req-1", protocol: "pi-intercom", version: 1 }, "req-1"), true);
165
+ assert.equal(isBrokerHealthOkMessage({ type: "health_ok", requestId: "req-1" }, "req-1"), false);
166
+ assert.equal(isBrokerHealthOkMessage({ type: "health_ok", requestId: "req-2", protocol: "pi-intercom", version: 1 }, "req-1"), false);
167
+ assert.equal(isBrokerHealthOkMessage("ok", "req-1"), false);
31
168
  });