@selesai/code 0.5.27 → 0.5.29

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 (310) hide show
  1. package/dist/cli/args.d.ts.map +1 -1
  2. package/dist/cli/args.js +9 -1
  3. package/dist/cli/args.js.map +1 -1
  4. package/dist/cli/config-selector.d.ts.map +1 -1
  5. package/dist/cli/config-selector.js +1 -1
  6. package/dist/cli/config-selector.js.map +1 -1
  7. package/dist/cli/credential-print.d.ts +23 -0
  8. package/dist/cli/credential-print.d.ts.map +1 -0
  9. package/dist/cli/credential-print.js +117 -0
  10. package/dist/cli/credential-print.js.map +1 -0
  11. package/dist/cli/startup-ui.d.ts.map +1 -1
  12. package/dist/cli/startup-ui.js +1 -1
  13. package/dist/cli/startup-ui.js.map +1 -1
  14. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  15. package/dist/core/agent-session-runtime.js +3 -0
  16. package/dist/core/agent-session-runtime.js.map +1 -1
  17. package/dist/core/agent-session.d.ts +12 -1
  18. package/dist/core/agent-session.d.ts.map +1 -1
  19. package/dist/core/agent-session.js +20 -14
  20. package/dist/core/agent-session.js.map +1 -1
  21. package/dist/core/compaction/compaction.d.ts.map +1 -1
  22. package/dist/core/compaction/compaction.js +11 -3
  23. package/dist/core/compaction/compaction.js.map +1 -1
  24. package/dist/core/extensions/runner.d.ts +1 -0
  25. package/dist/core/extensions/runner.d.ts.map +1 -1
  26. package/dist/core/extensions/runner.js +11 -0
  27. package/dist/core/extensions/runner.js.map +1 -1
  28. package/dist/core/extensions/types.d.ts +14 -1
  29. package/dist/core/extensions/types.d.ts.map +1 -1
  30. package/dist/core/extensions/types.js.map +1 -1
  31. package/dist/core/footer-data-provider.d.ts +10 -0
  32. package/dist/core/footer-data-provider.d.ts.map +1 -1
  33. package/dist/core/footer-data-provider.js +1 -1
  34. package/dist/core/footer-data-provider.js.map +1 -1
  35. package/dist/core/llama/provider.d.ts.map +1 -1
  36. package/dist/core/llama/provider.js +8 -3
  37. package/dist/core/llama/provider.js.map +1 -1
  38. package/dist/core/model-config.d.ts +30 -0
  39. package/dist/core/model-config.d.ts.map +1 -1
  40. package/dist/core/model-config.js +6 -0
  41. package/dist/core/model-config.js.map +1 -1
  42. package/dist/core/model-registry.d.ts.map +1 -1
  43. package/dist/core/model-registry.js +2 -2
  44. package/dist/core/model-registry.js.map +1 -1
  45. package/dist/core/model-resolver.d.ts +1 -0
  46. package/dist/core/model-resolver.d.ts.map +1 -1
  47. package/dist/core/model-resolver.js +20 -3
  48. package/dist/core/model-resolver.js.map +1 -1
  49. package/dist/core/model-runtime.d.ts +2 -0
  50. package/dist/core/model-runtime.d.ts.map +1 -1
  51. package/dist/core/model-runtime.js +5 -4
  52. package/dist/core/model-runtime.js.map +1 -1
  53. package/dist/core/package-manager.d.ts.map +1 -1
  54. package/dist/core/package-manager.js +13 -6
  55. package/dist/core/package-manager.js.map +1 -1
  56. package/dist/core/remote-catalog-provider.d.ts +1 -1
  57. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  58. package/dist/core/remote-catalog-provider.js +24 -11
  59. package/dist/core/remote-catalog-provider.js.map +1 -1
  60. package/dist/core/resource-loader.d.ts +15 -0
  61. package/dist/core/resource-loader.d.ts.map +1 -1
  62. package/dist/core/resource-loader.js +66 -9
  63. package/dist/core/resource-loader.js.map +1 -1
  64. package/dist/core/settings-manager.d.ts +1 -1
  65. package/dist/core/settings-manager.d.ts.map +1 -1
  66. package/dist/core/settings-manager.js.map +1 -1
  67. package/dist/core/system-prompt.d.ts.map +1 -1
  68. package/dist/core/system-prompt.js +1 -1
  69. package/dist/core/system-prompt.js.map +1 -1
  70. package/dist/core/tools/bash.d.ts +2 -0
  71. package/dist/core/tools/bash.d.ts.map +1 -1
  72. package/dist/core/tools/bash.js +34 -5
  73. package/dist/core/tools/bash.js.map +1 -1
  74. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  75. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  76. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  77. package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
  78. package/dist/extensions/pi-intercom/README.md +102 -39
  79. package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
  80. package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
  81. package/dist/extensions/pi-intercom/broker/client.ts +315 -12
  82. package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
  83. package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
  84. package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
  85. package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
  86. package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
  87. package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
  88. package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
  89. package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
  90. package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
  91. package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
  92. package/dist/extensions/pi-intercom/config.test.ts +93 -0
  93. package/dist/extensions/pi-intercom/config.ts +55 -6
  94. package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
  95. package/dist/extensions/pi-intercom/cwd.ts +31 -0
  96. package/dist/extensions/pi-intercom/extension-api.ts +44 -0
  97. package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
  98. package/dist/extensions/pi-intercom/format-context.ts +32 -0
  99. package/dist/extensions/pi-intercom/index.ts +742 -145
  100. package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
  101. package/dist/extensions/pi-intercom/package.json +15 -5
  102. package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
  103. package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
  104. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
  105. package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
  106. package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
  107. package/dist/extensions/pi-intercom/types.ts +94 -4
  108. package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
  109. package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
  110. package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
  111. package/dist/extensions/pi-subagents/CHANGELOG.md +85 -0
  112. package/dist/extensions/pi-subagents/LICENSE +21 -0
  113. package/dist/extensions/pi-subagents/README.md +146 -52
  114. package/dist/extensions/pi-subagents/agents/architect.md +1 -0
  115. package/dist/extensions/pi-subagents/agents/builder.md +1 -0
  116. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  117. package/dist/extensions/pi-subagents/package.json +3 -3
  118. package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
  119. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +21 -989
  120. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
  121. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +430 -0
  122. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
  123. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +282 -0
  124. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +71 -30
  125. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
  126. package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
  127. package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
  128. package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
  129. package/dist/extensions/pi-subagents/src/api/preflight.ts +16 -12
  130. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
  131. package/dist/extensions/pi-subagents/src/extension/index.ts +17 -7
  132. package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
  133. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -6
  134. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +8 -7
  135. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
  136. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
  137. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +63 -16
  138. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
  139. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
  140. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  141. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
  142. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
  143. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +29 -4
  144. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +29 -6
  145. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
  146. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +372 -28
  147. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
  148. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
  149. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +143 -43
  150. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +527 -247
  151. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
  152. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +426 -123
  153. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +15 -7
  154. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
  155. package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
  156. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  157. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
  158. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
  159. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
  160. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
  161. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
  162. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
  163. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
  164. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
  165. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
  166. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
  167. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +1 -1
  168. package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
  169. package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
  170. package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
  171. package/dist/extensions/pi-subagents/src/shared/types.ts +155 -10
  172. package/dist/extensions/pi-subagents/src/shared/utils.ts +73 -4
  173. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +5 -0
  174. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
  175. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
  176. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
  177. package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
  178. package/dist/extensions/pi-subagents/src/tui/render.ts +27 -28
  179. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
  180. package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
  181. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
  182. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
  183. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
  184. package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
  185. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  186. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +348 -7
  187. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
  188. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
  189. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
  190. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +26 -1
  191. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
  192. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +3 -3
  193. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +213 -13
  194. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +34 -0
  195. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
  196. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +118 -12
  197. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +850 -7
  198. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
  199. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
  200. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
  201. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
  202. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
  203. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +128 -0
  204. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
  205. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
  206. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
  207. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +102 -0
  208. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
  209. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
  210. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
  211. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
  212. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
  213. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
  214. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +4 -2
  215. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
  216. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
  217. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
  218. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
  219. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +11 -11
  220. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
  221. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
  222. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
  223. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +10 -2
  224. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
  225. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
  226. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
  227. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +46 -0
  228. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
  229. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
  230. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
  231. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
  232. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
  233. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +34 -2
  234. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  235. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
  236. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
  237. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
  238. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
  239. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +3 -0
  240. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +1 -1
  241. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
  245. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
  246. package/dist/extensions/question/index.ts +14 -1
  247. package/dist/extensions/question/tests/wizard.test.ts +11 -8
  248. package/dist/main.d.ts.map +1 -1
  249. package/dist/main.js +43 -0
  250. package/dist/main.js.map +1 -1
  251. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  252. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  253. package/dist/modes/interactive/components/custom-message.js +10 -2
  254. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  255. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  256. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  257. package/dist/modes/interactive/components/extension-editor.js +16 -46
  258. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  259. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  260. package/dist/modes/interactive/components/model-selector.js +4 -1
  261. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  262. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  263. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  264. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  265. package/dist/modes/interactive/external-editor.d.ts +12 -0
  266. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  267. package/dist/modes/interactive/external-editor.js +37 -0
  268. package/dist/modes/interactive/external-editor.js.map +1 -0
  269. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  270. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  271. package/dist/modes/interactive/interactive-mode.js +72 -64
  272. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  273. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  274. package/dist/modes/rpc/rpc-mode.js +14 -0
  275. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  276. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  277. package/dist/utils/clipboard.d.ts.map +1 -1
  278. package/dist/utils/clipboard.js +19 -8
  279. package/dist/utils/clipboard.js.map +1 -1
  280. package/dist/utils/version-check.d.ts.map +1 -1
  281. package/dist/utils/version-check.js +1 -1
  282. package/dist/utils/version-check.js.map +1 -1
  283. package/docs/compaction.md +1 -1
  284. package/docs/custom-provider.md +14 -5
  285. package/docs/environment-variables.md +86 -0
  286. package/docs/extensions.md +15 -5
  287. package/docs/index.md +1 -0
  288. package/docs/models.md +9 -2
  289. package/docs/providers.md +21 -2
  290. package/docs/rpc.md +23 -6
  291. package/docs/session-format.md +2 -0
  292. package/docs/settings.md +1 -1
  293. package/docs/usage.md +0 -13
  294. package/examples/extensions/custom-compaction.ts +5 -2
  295. package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
  296. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  297. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  298. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  299. package/examples/extensions/gondolin/package-lock.json +2 -2
  300. package/examples/extensions/gondolin/package.json +1 -1
  301. package/examples/extensions/handoff.ts +11 -3
  302. package/examples/extensions/message-renderer.ts +3 -3
  303. package/examples/extensions/sandbox/package-lock.json +2 -2
  304. package/examples/extensions/sandbox/package.json +1 -1
  305. package/examples/extensions/summarize.ts +5 -2
  306. package/examples/extensions/with-deps/package-lock.json +2 -2
  307. package/examples/extensions/with-deps/package.json +1 -1
  308. package/examples/sdk/12-full-control.ts +3 -1
  309. package/package.json +15 -6
  310. package/dist/extensions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -1,15 +1,25 @@
1
1
  import { spawn } from "child_process";
2
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
3
- import { createRequire } from "module";
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
4
3
  import { join, dirname } from "path";
5
4
  import { fileURLToPath } from "url";
5
+ import { createRequire } from "module";
6
6
  import net from "net";
7
- import { getBrokerSocketPath, getIntercomDir } from "./paths.js";
8
-
9
- const require = createRequire(import.meta.url);
10
- const INTERCOM_DIR = getIntercomDir();
7
+ import { randomUUID } from "crypto";
8
+ import { createMessageReader, writeMessage } from "./framing.ts";
9
+ import {
10
+ ensureIntercomRuntimeDir,
11
+ getAgentDirPath,
12
+ getBrokerConnectTarget,
13
+ getIntercomDirPath,
14
+ INTERCOM_PROTOCOL_NAME,
15
+ INTERCOM_PROTOCOL_VERSION,
16
+ INTERCOM_RUNTIME_FILE_MODE,
17
+ restrictIntercomRuntimeFile,
18
+ type BrokerConnectTarget,
19
+ } from "./paths.ts";
20
+
21
+ const INTERCOM_DIR = getIntercomDirPath();
11
22
  const EXTENSION_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");
12
- const BROKER_SOCKET = getBrokerSocketPath();
13
23
  const BROKER_PID = join(INTERCOM_DIR, "broker.pid");
14
24
  const BROKER_SPAWN_LOCK = join(INTERCOM_DIR, "broker.spawn.lock");
15
25
 
@@ -31,8 +41,19 @@ function sleep(ms: number): Promise<void> {
31
41
  return new Promise(resolve => setTimeout(resolve, ms));
32
42
  }
33
43
 
34
- export function getTsxCliPath(): string {
35
- return require.resolve("tsx/cli");
44
+ export function getTsxCliPath(extensionDir: string = EXTENSION_DIR): string {
45
+ // Resolve tsx via Node's module resolution so it works regardless of whether
46
+ // tsx is bundled under extensionDir/node_modules or hoisted to a workspace
47
+ // root by npm. We resolve the tsx package main entry (its "exports" field
48
+ // does not expose ./dist/cli.mjs as a subpath) and then locate cli.mjs next
49
+ // to it. Falls back to the legacy relative path if resolution fails.
50
+ try {
51
+ const requireFromExtension = createRequire(import.meta.url);
52
+ const tsxMain = requireFromExtension.resolve("tsx");
53
+ return join(dirname(tsxMain), "cli.mjs");
54
+ } catch {
55
+ return join(extensionDir, "node_modules", "tsx", "dist", "cli.mjs");
56
+ }
36
57
  }
37
58
 
38
59
  function quoteWindowsArg(value: string): string {
@@ -50,6 +71,13 @@ function usesDefaultBrokerCommand(brokerCommand: string, brokerArgs: string[]):
50
71
  && brokerArgs[1] === "tsx";
51
72
  }
52
73
 
74
+ function getNodeCommand(nodePath: string): string {
75
+ const executableName = nodePath.split(/[\\/]/).pop();
76
+ return executableName && /^node(?:js)?(?:\.exe)?$/i.test(executableName)
77
+ ? nodePath
78
+ : "node";
79
+ }
80
+
53
81
  export function getWindowsBrokerCommandLine(
54
82
  brokerPath: string,
55
83
  extensionDir: string = EXTENSION_DIR,
@@ -58,7 +86,7 @@ export function getWindowsBrokerCommandLine(
58
86
  brokerArgs: string[] = ["--no-install", "tsx"],
59
87
  ): string {
60
88
  if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
61
- return [quoteWindowsArg(nodePath), quoteWindowsArg(getTsxCliPath()), quoteWindowsArg(brokerPath)].join(" ");
89
+ return [quoteWindowsArg(getNodeCommand(nodePath)), quoteWindowsArg(getTsxCliPath(extensionDir)), quoteWindowsArg(brokerPath)].join(" ");
62
90
  }
63
91
 
64
92
  return [quoteWindowsArg(brokerCommand), ...brokerArgs.map(quoteWindowsArg), quoteWindowsArg(brokerPath)].join(" ");
@@ -73,12 +101,27 @@ export function getWindowsHiddenLauncherScript(commandLine: string): string {
73
101
  ].join("\r\n");
74
102
  }
75
103
 
104
+ export function isBrokerHealthOkMessage(message: unknown, requestId: string): boolean {
105
+ if (typeof message !== "object" || message === null || !("type" in message)) {
106
+ return false;
107
+ }
108
+ const response = message as Record<string, unknown>;
109
+ return response.type === "health_ok"
110
+ && response.requestId === requestId
111
+ && response.protocol === INTERCOM_PROTOCOL_NAME
112
+ && response.version === INTERCOM_PROTOCOL_VERSION;
113
+ }
114
+
76
115
  function writeWindowsHiddenLauncher(
77
116
  commandLine: string,
78
117
  launcherPath: string = getWindowsHiddenLauncherPath(),
79
118
  ): string {
80
- mkdirSync(dirname(launcherPath), { recursive: true });
81
- writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine), "utf-8");
119
+ ensureIntercomRuntimeDir(dirname(launcherPath));
120
+ writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine), {
121
+ encoding: "utf-8",
122
+ mode: INTERCOM_RUNTIME_FILE_MODE,
123
+ });
124
+ restrictIntercomRuntimeFile(launcherPath);
82
125
  return launcherPath;
83
126
  }
84
127
 
@@ -102,6 +145,14 @@ export function getBrokerLaunchSpec(
102
145
  };
103
146
  }
104
147
 
148
+ if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
149
+ return {
150
+ kind: "direct",
151
+ command: getNodeCommand(nodePath),
152
+ args: [getTsxCliPath(extensionDir), brokerPath],
153
+ };
154
+ }
155
+
105
156
  return {
106
157
  kind: "direct",
107
158
  command: brokerCommand,
@@ -109,7 +160,10 @@ export function getBrokerLaunchSpec(
109
160
  };
110
161
  }
111
162
 
112
- export function getBrokerSpawnOptions(extensionDir: string = EXTENSION_DIR): {
163
+ export function getBrokerSpawnOptions(
164
+ extensionDir: string = EXTENSION_DIR,
165
+ env: NodeJS.ProcessEnv = process.env,
166
+ ): {
113
167
  detached: true;
114
168
  stdio: "ignore";
115
169
  cwd: string;
@@ -120,7 +174,7 @@ export function getBrokerSpawnOptions(extensionDir: string = EXTENSION_DIR): {
120
174
  detached: true,
121
175
  stdio: "ignore",
122
176
  cwd: extensionDir,
123
- env: { ...process.env, NODE_NO_WARNINGS: "1" },
177
+ env: { ...env, SELESAI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
124
178
  windowsHide: true,
125
179
  };
126
180
  }
@@ -130,7 +184,7 @@ function toError(error: unknown): Error {
130
184
  }
131
185
 
132
186
  export async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: string[]): Promise<void> {
133
- mkdirSync(INTERCOM_DIR, { recursive: true });
187
+ ensureIntercomRuntimeDir(INTERCOM_DIR);
134
188
 
135
189
  if (await isBrokerRunning()) {
136
190
  return;
@@ -211,29 +265,57 @@ async function isBrokerRunning(): Promise<boolean> {
211
265
  }
212
266
  }
213
267
 
268
+ function connectToBrokerTarget(target: BrokerConnectTarget): net.Socket {
269
+ return typeof target === "string"
270
+ ? net.connect(target)
271
+ : net.connect({ host: target.host, port: target.port });
272
+ }
273
+
214
274
  function checkSocketConnectable(): Promise<boolean> {
215
275
  return new Promise((resolve) => {
216
- const socket = net.connect(BROKER_SOCKET);
276
+ let target: BrokerConnectTarget;
277
+ try {
278
+ target = getBrokerConnectTarget();
279
+ } catch {
280
+ resolve(false);
281
+ return;
282
+ }
283
+
284
+ const socket = connectToBrokerTarget(target);
285
+ const requestId = randomUUID();
286
+ const expectedStateId = typeof target === "string" ? undefined : target.stateId;
287
+ let settled = false;
217
288
  const finish = (isConnected: boolean) => {
289
+ if (settled) {
290
+ return;
291
+ }
292
+ settled = true;
218
293
  clearTimeout(timeout);
219
294
  socket.off("connect", onConnect);
220
295
  socket.off("error", onError);
296
+ socket.off("data", reader);
297
+ socket.destroy();
221
298
  resolve(isConnected);
222
299
  };
223
300
  const onConnect = () => {
224
- socket.end();
225
- finish(true);
226
- };
227
- const onError = () => {
228
- socket.destroy();
229
- finish(false);
301
+ try {
302
+ writeMessage(socket, {
303
+ type: "health",
304
+ requestId,
305
+ ...(expectedStateId ? { stateId: expectedStateId } : {}),
306
+ });
307
+ } catch {
308
+ finish(false);
309
+ }
230
310
  };
311
+ const onError = () => finish(false);
312
+ const reader = createMessageReader((message) => {
313
+ finish(isBrokerHealthOkMessage(message, requestId));
314
+ }, () => finish(false));
231
315
  socket.on("connect", onConnect);
232
316
  socket.on("error", onError);
233
- const timeout = setTimeout(() => {
234
- socket.destroy();
235
- finish(false);
236
- }, 1000);
317
+ socket.on("data", reader);
318
+ const timeout = setTimeout(() => finish(false), 1000);
237
319
  });
238
320
  }
239
321
 
@@ -241,7 +323,11 @@ function acquireSpawnLock(): boolean {
241
323
  const maxRetries = 5;
242
324
  for (let attempt = 0; attempt < maxRetries; attempt++) {
243
325
  try {
244
- writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\n${Date.now()}\n`, { flag: "wx" });
326
+ writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\n${Date.now()}\n`, {
327
+ flag: "wx",
328
+ mode: INTERCOM_RUNTIME_FILE_MODE,
329
+ });
330
+ restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);
245
331
  return true;
246
332
  } catch (error) {
247
333
  if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== "EEXIST") {
@@ -0,0 +1,93 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { getConfigPath, loadConfig } from "./config.ts";
7
+
8
+ async function withAgentDir<T>(agentDir: string, fn: () => T | Promise<T>): Promise<T> {
9
+ const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
10
+ process.env.SELESAI_CODING_AGENT_DIR = agentDir;
11
+ try {
12
+ return await fn();
13
+ } finally {
14
+ if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
15
+ else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
16
+ }
17
+ }
18
+
19
+ test("getConfigPath uses the centralized intercom runtime directory", () => {
20
+ assert.equal(getConfigPath("/tmp/pi-agent/intercom"), join("/tmp/pi-agent", "intercom", "config.json"));
21
+ });
22
+
23
+ test("loadConfig reads config below SELESAI_CODING_AGENT_DIR", async () => {
24
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
25
+
26
+ try {
27
+ const intercomDir = join(root, "intercom");
28
+ mkdirSync(intercomDir, { recursive: true });
29
+ writeFileSync(join(intercomDir, "config.json"), JSON.stringify({ status: "platform-test" }));
30
+
31
+ await withAgentDir(root, () => {
32
+ assert.equal(loadConfig().status, "platform-test");
33
+ });
34
+ } finally {
35
+ rmSync(root, { recursive: true, force: true });
36
+ }
37
+ });
38
+
39
+ test("loadConfig defaults inboundTrigger to current auto-trigger behavior", async () => {
40
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
41
+ try {
42
+ await withAgentDir(root, () => {
43
+ assert.equal(loadConfig().inboundTrigger, "always");
44
+ });
45
+ } finally {
46
+ rmSync(root, { recursive: true, force: true });
47
+ }
48
+ });
49
+
50
+ test("loadConfig accepts inboundTrigger replies policy", async () => {
51
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
52
+ try {
53
+ mkdirSync(join(root, "intercom"), { recursive: true });
54
+ writeFileSync(join(root, "intercom", "config.json"), JSON.stringify({ inboundTrigger: "replies" }));
55
+ await withAgentDir(root, () => {
56
+ assert.equal(loadConfig().inboundTrigger, "replies");
57
+ });
58
+ } finally {
59
+ rmSync(root, { recursive: true, force: true });
60
+ }
61
+ });
62
+
63
+ test("loadConfig accepts a restart-stable intercom id", async () => {
64
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
65
+ try {
66
+ mkdirSync(join(root, "intercom"), { recursive: true });
67
+ writeFileSync(join(root, "intercom", "config.json"), JSON.stringify({ stableId: " pinned-worker " }));
68
+ await withAgentDir(root, () => {
69
+ assert.equal(loadConfig().stableId, "pinned-worker");
70
+ });
71
+ } finally {
72
+ rmSync(root, { recursive: true, force: true });
73
+ }
74
+ });
75
+
76
+ test("loadConfig rejects invalid inboundTrigger values by failing closed", async () => {
77
+ const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
78
+ try {
79
+ mkdirSync(join(root, "intercom"), { recursive: true });
80
+ writeFileSync(join(root, "intercom", "config.json"), JSON.stringify({ inboundTrigger: "prompt" }));
81
+ const previousError = console.error;
82
+ console.error = () => undefined;
83
+ try {
84
+ await withAgentDir(root, () => {
85
+ assert.equal(loadConfig().inboundTrigger, "never");
86
+ });
87
+ } finally {
88
+ console.error = previousError;
89
+ }
90
+ } finally {
91
+ rmSync(root, { recursive: true, force: true });
92
+ }
93
+ });
@@ -1,6 +1,23 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
2
  import { join } from "path";
3
- import { getIntercomDir } from "./broker/paths.js";
3
+ import { getIntercomDirPath } from "./broker/paths.ts";
4
+
5
+ export const DEFAULT_ASK_TIMEOUT_MS = 10 * 60 * 1000;
6
+
7
+ export function getAskTimeoutMs(): number {
8
+ const raw = process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
9
+ if (raw === undefined || raw.trim() === "") {
10
+ return DEFAULT_ASK_TIMEOUT_MS;
11
+ }
12
+
13
+ const value = Number(raw);
14
+ if (!Number.isSafeInteger(value) || value <= 0) {
15
+ throw new Error("PI_INTERCOM_ASK_TIMEOUT_MS must be a positive integer number of milliseconds");
16
+ }
17
+ return value;
18
+ }
19
+
20
+ export type InboundTriggerPolicy = "always" | "replies" | "never";
4
21
 
5
22
  export interface IntercomConfig {
6
23
  /** Broker command used to spawn the broker process (e.g. "npx" or "bun") */
@@ -12,8 +29,14 @@ export interface IntercomConfig {
12
29
  /** Require confirmation before non-reply sends from interactive sessions */
13
30
  confirmSend: boolean;
14
31
 
32
+ /** Controls whether inbound broker messages may automatically trigger a model turn */
33
+ inboundTrigger: InboundTriggerPolicy;
34
+
15
35
  /** Optional custom status suffix shown after automatic lifecycle status */
16
36
  status?: string;
37
+
38
+ /** Optional stable intercom session ID for restart-stable addressing */
39
+ stableId?: string;
17
40
 
18
41
  /** Enable/disable intercom (default: true) */
19
42
  enabled: boolean;
@@ -22,23 +45,27 @@ export interface IntercomConfig {
22
45
  replyHint: boolean;
23
46
  }
24
47
 
25
- const CONFIG_PATH = join(getIntercomDir(), "config.json");
48
+ export function getConfigPath(intercomDir: string = getIntercomDirPath()): string {
49
+ return join(intercomDir, "config.json");
50
+ }
26
51
 
27
52
  const defaults: IntercomConfig = {
28
53
  brokerCommand: "npx",
29
54
  brokerArgs: ["--no-install", "tsx"],
30
55
  confirmSend: false,
56
+ inboundTrigger: "always",
31
57
  enabled: true,
32
58
  replyHint: true,
33
59
  };
34
60
 
35
61
  export function loadConfig(): IntercomConfig {
36
- if (!existsSync(CONFIG_PATH)) {
62
+ const configPath = getConfigPath();
63
+ if (!existsSync(configPath)) {
37
64
  return { ...defaults };
38
65
  }
39
66
 
40
67
  try {
41
- const raw = readFileSync(CONFIG_PATH, "utf-8");
68
+ const raw = readFileSync(configPath, "utf-8");
42
69
  const parsed: unknown = JSON.parse(raw);
43
70
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
44
71
  throw new Error("Config must be a JSON object");
@@ -86,6 +113,17 @@ export function loadConfig(): IntercomConfig {
86
113
  config.enabled = parsedConfig.enabled;
87
114
  }
88
115
 
116
+ if (Object.hasOwn(parsedConfig, "inboundTrigger")) {
117
+ if (
118
+ parsedConfig.inboundTrigger !== "always"
119
+ && parsedConfig.inboundTrigger !== "replies"
120
+ && parsedConfig.inboundTrigger !== "never"
121
+ ) {
122
+ throw new Error(`"inboundTrigger" must be "always", "replies", or "never"`);
123
+ }
124
+ config.inboundTrigger = parsedConfig.inboundTrigger;
125
+ }
126
+
89
127
  if (Object.hasOwn(parsedConfig, "replyHint")) {
90
128
  if (typeof parsedConfig.replyHint !== "boolean") {
91
129
  throw new Error(`"replyHint" must be a boolean`);
@@ -100,9 +138,20 @@ export function loadConfig(): IntercomConfig {
100
138
  config.status = parsedConfig.status;
101
139
  }
102
140
 
141
+ if (Object.hasOwn(parsedConfig, "stableId")) {
142
+ if (typeof parsedConfig.stableId !== "string") {
143
+ throw new Error(`"stableId" must be a string`);
144
+ }
145
+ const stableId = parsedConfig.stableId.trim();
146
+ if (!stableId) {
147
+ throw new Error(`"stableId" must not be empty`);
148
+ }
149
+ config.stableId = stableId;
150
+ }
151
+
103
152
  return config;
104
153
  } catch (error) {
105
- console.error(`Failed to load intercom config at ${CONFIG_PATH}:`, error);
106
- return { ...defaults };
154
+ console.error(`Failed to load intercom config at ${configPath}:`, error);
155
+ return { ...defaults, inboundTrigger: "never" };
107
156
  }
108
157
  }
@@ -0,0 +1,40 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync, symlinkSync, mkdirSync, realpathSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { normalizeCwd, sameCwd } from "./cwd.ts";
7
+
8
+ test("sameCwd collapses a trailing slash", () => {
9
+ assert.equal(sameCwd("/usr/local", "/usr/local/"), true);
10
+ });
11
+
12
+ test("sameCwd collapses lexical '.' and '..' segments", () => {
13
+ assert.equal(sameCwd("/usr/local/../local/./bin", "/usr/local/bin"), true);
14
+ });
15
+
16
+ test("sameCwd treats genuinely different directories as different", () => {
17
+ assert.equal(sameCwd("/usr/local", "/usr/lib"), false);
18
+ });
19
+
20
+ test("sameCwd collapses a symlink to its real target", () => {
21
+ const base = mkdtempSync(join(tmpdir(), "cwd-symlink-"));
22
+ try {
23
+ const real = join(base, "real");
24
+ const link = join(base, "link");
25
+ mkdirSync(real);
26
+ symlinkSync(real, link);
27
+ // Reached via the symlink vs its canonical path — must compare equal.
28
+ assert.equal(sameCwd(link, real), true);
29
+ // And normalizeCwd resolves the link to the real path.
30
+ assert.equal(normalizeCwd(link), realpathSync(real));
31
+ } finally {
32
+ rmSync(base, { recursive: true, force: true });
33
+ }
34
+ });
35
+
36
+ test("normalizeCwd falls back to the resolved path for a nonexistent dir", () => {
37
+ // realpathSync throws for a missing dir; normalizeCwd must still collapse the
38
+ // lexical variants rather than throw.
39
+ assert.equal(normalizeCwd("/no/such/dir/../dir"), "/no/such/dir");
40
+ });
@@ -0,0 +1,31 @@
1
+ import { resolve } from "node:path";
2
+ import { realpathSync } from "node:fs";
3
+
4
+ // Normalize a cwd for same-directory comparison. A raw string match ("a === b")
5
+ // hides genuine same-directory peers when two cwd strings differ only by a
6
+ // trailing slash, a "."/".." segment, or a symlink (e.g. macOS /tmp <->
7
+ // /private/tmp). resolve() collapses the lexical variants; realpathSync()
8
+ // collapses symlinks (best-effort: falls back to the resolved path if the
9
+ // directory no longer exists). Memoized — the set of distinct cwd strings is
10
+ // small and stable, so repeat comparisons are free after warmup.
11
+ const normalizeCache = new Map<string, string>();
12
+
13
+ export function normalizeCwd(cwd: string): string {
14
+ const cached = normalizeCache.get(cwd);
15
+ if (cached !== undefined) {
16
+ return cached;
17
+ }
18
+ const resolved = resolve(cwd);
19
+ let normalized: string;
20
+ try {
21
+ normalized = realpathSync(resolved);
22
+ } catch {
23
+ normalized = resolved;
24
+ }
25
+ normalizeCache.set(cwd, normalized);
26
+ return normalized;
27
+ }
28
+
29
+ export function sameCwd(a: string, b: string): boolean {
30
+ return normalizeCwd(a) === normalizeCwd(b);
31
+ }
@@ -0,0 +1,44 @@
1
+ import type { SessionInfo } from "./types.ts";
2
+
3
+ export const INTERCOM_EXTENSION_REGISTER_EVENT = "intercom:extension-register";
4
+ export const INTERCOM_EXTENSION_REGISTRY_READY_EVENT = "intercom:extension-registry-ready";
5
+
6
+ export interface IntercomExtensionOwner {
7
+ sessionId: string;
8
+ epoch: string;
9
+ }
10
+
11
+ export interface IntercomExtensionState {
12
+ revision: number;
13
+ payload: unknown;
14
+ }
15
+
16
+ export type IntercomExtensionEvent =
17
+ | { type: "connection"; connected: boolean; supported: boolean }
18
+ | { type: "owner"; owner?: IntercomExtensionOwner }
19
+ | { type: "message"; fromSessionId: string; owner?: IntercomExtensionOwner; payload: unknown }
20
+ | { type: "state"; state: IntercomExtensionState }
21
+ | { type: "state_result"; committed: boolean; revision: number; reason?: string }
22
+ | { type: "session_joined"; session: SessionInfo }
23
+ | { type: "session_left"; sessionId: string }
24
+ | { type: "presence_update"; session: SessionInfo };
25
+
26
+ export interface IntercomExtensionChannel {
27
+ readonly namespace: string;
28
+ snapshot(): {
29
+ connected: boolean;
30
+ supported: boolean;
31
+ owner?: IntercomExtensionOwner;
32
+ state?: IntercomExtensionState;
33
+ };
34
+ publish(payload: unknown, options?: { audience?: "owner" | "capable"; ownerOnly?: boolean }): void;
35
+ commitState(payload: unknown, expectedRevision?: number): void;
36
+ listSessions(): Promise<SessionInfo[]>;
37
+ }
38
+
39
+ export interface IntercomExtensionRegistration {
40
+ namespace: string;
41
+ ownerEligible: boolean;
42
+ onEvent(event: IntercomExtensionEvent): void;
43
+ onReady(channel: IntercomExtensionChannel): void;
44
+ }
@@ -0,0 +1,31 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { formatTokenCount, formatContextUsage } from "./format-context.ts";
4
+ import type { SessionInfo } from "./types.ts";
5
+
6
+ const base: SessionInfo = { id: "s1", cwd: "/w", model: "m", pid: 1, startedAt: 0, lastActivity: 0 };
7
+
8
+ test("formatTokenCount compacts thousands", () => {
9
+ assert.equal(formatTokenCount(0), "0");
10
+ assert.equal(formatTokenCount(999), "999");
11
+ assert.equal(formatTokenCount(1432), "1.4k");
12
+ assert.equal(formatTokenCount(144000), "144k");
13
+ assert.equal(formatTokenCount(200000), "200k");
14
+ });
15
+
16
+ test("formatContextUsage renders percent + token detail when all known", () => {
17
+ assert.equal(
18
+ formatContextUsage({ ...base, contextPct: 72, contextTokens: 144000, contextWindow: 200000 }),
19
+ " · 72% ctx (144k/200k)",
20
+ );
21
+ });
22
+
23
+ test("formatContextUsage shows percent only when token counts are absent", () => {
24
+ assert.equal(formatContextUsage({ ...base, contextPct: 30 }), " · 30% ctx");
25
+ });
26
+
27
+ test("formatContextUsage renders nothing when percent is unknown (never a stale %)", () => {
28
+ assert.equal(formatContextUsage(base), "");
29
+ // Tokens present but no percent (e.g. right after a compaction) still renders nothing.
30
+ assert.equal(formatContextUsage({ ...base, contextTokens: 100, contextWindow: 200 }), "");
31
+ });
@@ -0,0 +1,32 @@
1
+ import type { SessionInfo } from "./types.ts";
2
+
3
+ // Compact token count for display: 1432 -> "1.4k", 144000 -> "144k". Keeps list
4
+ // rows short while staying legible.
5
+ export function formatTokenCount(tokens: number): string {
6
+ if (tokens < 1000) {
7
+ return String(Math.max(0, Math.round(tokens)));
8
+ }
9
+ const k = tokens / 1000;
10
+ const value = k >= 100 ? String(Math.round(k)) : k.toFixed(1).replace(/\.0$/, "");
11
+ return `${value}k`;
12
+ }
13
+
14
+ // Render a session's context-window usage to sit beside its model, e.g.
15
+ // " · 72% ctx (144k/200k)". The token detail is appended only when both counts
16
+ // are known. Unknown percent (e.g. right after a compaction, before the next
17
+ // assistant response) renders nothing, so a frozen value is never shown as a
18
+ // stale percentage.
19
+ export function formatContextUsage(session: SessionInfo): string {
20
+ if (typeof session.contextPct !== "number") {
21
+ return "";
22
+ }
23
+ let out = ` · ${session.contextPct}% ctx`;
24
+ if (
25
+ typeof session.contextTokens === "number"
26
+ && typeof session.contextWindow === "number"
27
+ && session.contextWindow > 0
28
+ ) {
29
+ out += ` (${formatTokenCount(session.contextTokens)}/${formatTokenCount(session.contextWindow)})`;
30
+ }
31
+ return out;
32
+ }