@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,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-intercom",
3
- "version": "0.6.0",
3
+ "version": "0.9.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -8,13 +8,16 @@
8
8
  "index.ts",
9
9
  "types.ts",
10
10
  "config.ts",
11
+ "cwd.ts",
12
+ "extension-api.ts",
13
+ "format-context.ts",
11
14
  "reply-tracker.ts",
12
15
  "broker/**/*.ts",
13
16
  "ui/**/*.ts",
14
17
  "skills/**/*"
15
18
  ],
16
19
  "scripts": {
17
- "test": "tsx --test broker/paths.test.ts broker/spawn.test.ts reply-tracker.test.ts intercom.integration.test.ts test/inline-message.test.ts"
20
+ "test": "tsx --test broker/client.test.ts broker/extension.test.ts broker/framing.test.ts broker/paths.test.ts broker/runtime-claim.test.ts broker/spawn.test.ts config.test.ts cwd.test.ts format-context.test.ts reply-tracker.test.ts intercom.integration.test.ts test/inline-message.test.ts test/overlay-width.test.ts"
18
21
  },
19
22
  "keywords": [
20
23
  "pi-package"
@@ -28,11 +31,18 @@
28
31
  ]
29
32
  },
30
33
  "peerDependencies": {
34
+ "@earendil-works/pi-ai": "*",
31
35
  "@selesai/code": "*",
32
- "@earendil-works/pi-tui": "*"
36
+ "@earendil-works/pi-tui": "*",
37
+ "typebox": "*"
33
38
  },
34
39
  "dependencies": {
35
- "tsx": "^4.20.0",
36
- "typebox": "^1.1.24"
40
+ "tsx": "^4.20.0"
41
+ },
42
+ "devDependencies": {
43
+ "@earendil-works/pi-ai": "*",
44
+ "@selesai/code": "*",
45
+ "@earendil-works/pi-tui": "*",
46
+ "typebox": "*"
37
47
  }
38
48
  }
@@ -0,0 +1,134 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { getAskTimeoutMs } from "./config.ts";
4
+ import { ReplyTracker } from "./reply-tracker.ts";
5
+ import type { Message, SessionInfo } from "./types.ts";
6
+
7
+ function createSession(id: string, name: string): SessionInfo {
8
+ return {
9
+ id,
10
+ name,
11
+ cwd: "/tmp/project",
12
+ model: "test-model",
13
+ pid: 1,
14
+ startedAt: 1,
15
+ lastActivity: 1,
16
+ };
17
+ }
18
+
19
+ function createMessage(id: string, text: string, expectsReply = true): Message {
20
+ return {
21
+ id,
22
+ timestamp: 1,
23
+ expectsReply,
24
+ content: { text },
25
+ };
26
+ }
27
+
28
+ test("reply resolves from current triggered message context", () => {
29
+ const tracker = new ReplyTracker();
30
+ const from = createSession("planner-id", "planner");
31
+ const message = createMessage("ask-1", "Need a decision");
32
+
33
+ const context = tracker.recordIncomingMessage(from, message, 1000);
34
+ tracker.queueTurnContext(context);
35
+ tracker.beginTurn(1001);
36
+
37
+ assert.equal(tracker.resolveReplyTarget({}, 1002).message.id, "ask-1");
38
+ assert.equal(tracker.resolveReplyTarget({}, 1002).from.id, "planner-id");
39
+ });
40
+
41
+ test("reply resolves from single pending ask without current turn context", () => {
42
+ const tracker = new ReplyTracker();
43
+ tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "Need a decision"), 1000);
44
+
45
+ assert.equal(tracker.resolveReplyTarget({}, 1001).message.id, "ask-1");
46
+ });
47
+
48
+ test("reply with to resolves matching pending ask", () => {
49
+ const tracker = new ReplyTracker();
50
+ tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "First"), 1000);
51
+ tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Second"), 1001);
52
+
53
+ assert.equal(tracker.resolveReplyTarget({ to: "reviewer" }, 1002).message.id, "ask-2");
54
+ assert.equal(tracker.resolveReplyTarget({ to: "planner-id" }, 1002).message.id, "ask-1");
55
+ });
56
+
57
+ test("explicit to overrides the current turn context", () => {
58
+ const tracker = new ReplyTracker();
59
+ const current = tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "First"), 1000);
60
+ tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Second"), 1001);
61
+ tracker.queueTurnContext(current);
62
+ tracker.beginTurn(1002);
63
+
64
+ assert.equal(tracker.resolveReplyTarget({ to: "reviewer" }, 1003).message.id, "ask-2");
65
+ assert.throws(() => tracker.resolveReplyTarget({ to: "missing" }, 1003), /No pending ask from/);
66
+ });
67
+
68
+ test("replyTo resolves the exact pending ask", () => {
69
+ const tracker = new ReplyTracker();
70
+ tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "First"), 1000);
71
+ tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Second"), 1001);
72
+
73
+ assert.equal(tracker.resolveReplyTarget({ replyTo: "ask-2" }, 1002).from.id, "reviewer-id");
74
+ assert.throws(() => tracker.resolveReplyTarget({ to: "planner", replyTo: "ask-2" }, 1002), /is not from/);
75
+ });
76
+
77
+ test("reply errors when no context and no pending asks", () => {
78
+ const tracker = new ReplyTracker();
79
+
80
+ assert.throws(() => tracker.resolveReplyTarget({}, 1000), /No active intercom context to reply to/);
81
+ });
82
+
83
+ test("reply errors when multiple pending asks and no to", () => {
84
+ const tracker = new ReplyTracker();
85
+ tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "First"), 1000);
86
+ tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Second"), 1001);
87
+
88
+ assert.throws(() => tracker.resolveReplyTarget({}, 1002), /Multiple pending asks β€” specify `to`/);
89
+ });
90
+
91
+ test("reply removes pending ask after successful reply", () => {
92
+ const tracker = new ReplyTracker();
93
+ tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "Need a decision"), 1000);
94
+
95
+ tracker.markReplied("ask-1");
96
+
97
+ assert.deepEqual(tracker.listPending(1001), []);
98
+ });
99
+
100
+ test("ask timeout can be configured from environment", () => {
101
+ const previous = process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
102
+ process.env.PI_INTERCOM_ASK_TIMEOUT_MS = "42";
103
+ try {
104
+ assert.equal(getAskTimeoutMs(), 42);
105
+ assert.throws(() => {
106
+ process.env.PI_INTERCOM_ASK_TIMEOUT_MS = "0";
107
+ getAskTimeoutMs();
108
+ }, /positive integer/);
109
+ } finally {
110
+ if (previous === undefined) delete process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
111
+ else process.env.PI_INTERCOM_ASK_TIMEOUT_MS = previous;
112
+ }
113
+ });
114
+
115
+ test("pending asks can be explicitly dismissed without removing retryable failures", () => {
116
+ const tracker = new ReplyTracker();
117
+ tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "Need a decision"), 1000);
118
+ tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Retryable"), 1001);
119
+
120
+ tracker.dismissPendingAsk("ask-1");
121
+
122
+ assert.deepEqual(tracker.listPending(1002).map((context) => context.message.id), ["ask-2"]);
123
+ });
124
+
125
+ test("dismissing a pending ask removes queued turn context", () => {
126
+ const tracker = new ReplyTracker();
127
+ const context = tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "Need a decision"), 1000);
128
+ tracker.queueTurnContext(context);
129
+
130
+ tracker.dismissPendingAsk("ask-1");
131
+ tracker.beginTurn(1001);
132
+
133
+ assert.throws(() => tracker.resolveReplyTarget({}, 1002), /No active intercom context to reply to/);
134
+ });
@@ -1,3 +1,4 @@
1
+ import { getAskTimeoutMs } from "./config.ts";
1
2
  import type { Message, SessionInfo } from "./types.ts";
2
3
 
3
4
  export interface IntercomContext {
@@ -19,7 +20,7 @@ export class ReplyTracker {
19
20
  private readonly pendingTurnContexts: IntercomContext[] = [];
20
21
  private currentTurnContext: IntercomContext | null = null;
21
22
 
22
- constructor(private readonly askTimeoutMs = 10 * 60 * 1000) {}
23
+ constructor(private readonly askTimeoutMs = getAskTimeoutMs()) {}
23
24
 
24
25
  recordIncomingMessage(from: SessionInfo, message: Message, receivedAt = Date.now()): IntercomContext {
25
26
  const context = { from, message, receivedAt };
@@ -48,31 +49,39 @@ export class ReplyTracker {
48
49
  this.currentTurnContext = null;
49
50
  }
50
51
 
51
- resolveReplyTarget(options: { to?: string }, now = Date.now()): IntercomContext {
52
+ resolveReplyTarget(options: { to?: string; replyTo?: string }, now = Date.now()): IntercomContext {
52
53
  this.pruneExpired(now);
53
54
 
54
- if (this.currentTurnContext) {
55
- return this.currentTurnContext;
55
+ if (options.replyTo) {
56
+ const target = this.pendingAsks.get(options.replyTo);
57
+ if (!target) {
58
+ throw new Error(`No pending ask with message ID "${options.replyTo}"`);
59
+ }
60
+ if (options.to && !matchesPendingSender(target, options.to)) {
61
+ throw new Error(`Pending ask "${options.replyTo}" is not from "${options.to}"`);
62
+ }
63
+ return target;
56
64
  }
57
65
 
58
66
  const pending = Array.from(this.pendingAsks.values());
59
- if (pending.length === 1) {
60
- return pending[0]!;
61
- }
62
-
63
67
  if (options.to) {
64
68
  const matches = pending.filter((context) => matchesPendingSender(context, options.to!));
65
69
  if (matches.length === 1) {
66
70
  return matches[0]!;
67
71
  }
68
72
  if (matches.length > 1) {
69
- throw new Error(`Multiple pending asks from \"${options.to}\" β€” use the sender session ID instead.`);
70
- }
71
- if (pending.length > 1) {
72
- throw new Error(`No pending ask from \"${options.to}\"`);
73
+ throw new Error(`Multiple pending asks from "${options.to}" β€” use the sender session ID instead.`);
73
74
  }
75
+ throw new Error(`No pending ask from "${options.to}"`);
76
+ }
77
+
78
+ if (this.currentTurnContext) {
79
+ return this.currentTurnContext;
74
80
  }
75
81
 
82
+ if (pending.length === 1) {
83
+ return pending[0]!;
84
+ }
76
85
  if (pending.length === 0) {
77
86
  throw new Error("No active intercom context to reply to");
78
87
  }
@@ -81,7 +90,16 @@ export class ReplyTracker {
81
90
  }
82
91
 
83
92
  markReplied(replyTo: string): void {
93
+ this.dismissPendingAsk(replyTo);
94
+ }
95
+
96
+ dismissPendingAsk(replyTo: string): void {
84
97
  this.pendingAsks.delete(replyTo);
98
+ for (let index = this.pendingTurnContexts.length - 1; index >= 0; index -= 1) {
99
+ if (this.pendingTurnContexts[index]?.message.id === replyTo) {
100
+ this.pendingTurnContexts.splice(index, 1);
101
+ }
102
+ }
85
103
  if (this.currentTurnContext?.message.id === replyTo) {
86
104
  this.currentTurnContext = null;
87
105
  }
@@ -95,7 +113,7 @@ export class ReplyTracker {
95
113
  private pruneExpired(now: number): void {
96
114
  for (const [messageId, context] of this.pendingAsks) {
97
115
  if (now - context.receivedAt > this.askTimeoutMs) {
98
- this.pendingAsks.delete(messageId);
116
+ this.dismissPendingAsk(messageId);
99
117
  }
100
118
  }
101
119
  }
@@ -2,15 +2,15 @@
2
2
  name: pi-intercom
3
3
  description: |
4
4
  Streamline session-to-session coordination with pi-intercom. Send messages,
5
- delegate tasks, and coordinate work across multiple pi sessions on the same
5
+ delegate tasks, and coordinate work across multiple Selesai sessions on the same
6
6
  machine. Use for planner-worker workflows, cross-session context sharing,
7
7
  and real-time collaboration between sessions.
8
8
  ---
9
9
 
10
- # Pi Intercom Skill
10
+ # Selesai Intercom Skill
11
11
 
12
- Use this skill when you need to coordinate work across multiple pi sessions
13
- running on the same machine. Pi-intercom enables direct 1:1 messaging between
12
+ Use this skill when you need to coordinate work across multiple Selesai sessions
13
+ running on the same machine. pi-intercom enables direct 1:1 messaging between
14
14
  sessions for delegation, context sharing, and collaborative workflows.
15
15
 
16
16
  When you are supervising `pi-subagents`, delegated child agents can escalate to
@@ -156,8 +156,8 @@ This works because `reply` resolves the correct sender and message ID automatica
156
156
 
157
157
  | Type | What it means | How to respond |
158
158
  |------|---------------|----------------|
159
- | `need_decision` | Subagent is blocked and waiting for your answer. Has a 10-minute timeout. | Reply promptly with a clear decision. If you need more context, ask follow-up questions via `reply`. |
160
- | `interview_request` | Subagent needs multiple structured answers in one blocking exchange. Has a 10-minute timeout. | Reply with plain JSON or a fenced `json` block using the provided `{ "responses": [...] }` shape. |
159
+ | `need_decision` | Subagent is blocked and waiting for your answer. Uses the shared ask timeout: 10 minutes by default, configurable with `PI_INTERCOM_ASK_TIMEOUT_MS`. | Reply promptly with a clear decision. If you need more context, ask follow-up questions via `reply`. |
160
+ | `interview_request` | Subagent needs multiple structured answers in one blocking exchange. Uses the shared ask timeout: 10 minutes by default, configurable with `PI_INTERCOM_ASK_TIMEOUT_MS`. | Reply with plain JSON or a fenced `json` block using the provided `{ "responses": [...] }` shape. |
161
161
  | `progress_update` | Subagent is sharing meaningful progress or a plan-changing discovery. Not blocking. | Read and acknowledge. No reply required unless you want to redirect. |
162
162
 
163
163
  **When a subagent asks:**
@@ -197,7 +197,7 @@ it as a `contact_supervisor` escalation.
197
197
  | Action | Behavior | Use When |
198
198
  |--------|----------|----------|
199
199
  | `send` | Fire-and-forget | You don't need a response |
200
- | `ask` | Blocks until reply (10 min timeout) | You need an answer to continue |
200
+ | `ask` | Blocks until reply (10 min default, configurable with `PI_INTERCOM_ASK_TIMEOUT_MS`) | You need an answer to continue |
201
201
  | `reply` | Responds to the active or pending inbound ask | You were asked something and need to answer naturally |
202
202
  | `pending` | Lists unresolved inbound asks | You need to see who is waiting before replying |
203
203
  | `list` | Returns all sessions with live status | You need to discover targets or choose an idle peer |
@@ -301,7 +301,7 @@ If neither `cmux` nor `tmux` is available, skip this path and use normal `interc
301
301
 
302
302
  ### `ask` Limitations
303
303
 
304
- - **10-minute timeout**: If no reply comes within 10 minutes, the ask fails
304
+ - **Configurable timeout**: If no reply arrives before the shared ask timeout, the ask fails. The default is 10 minutes; set `PI_INTERCOM_ASK_TIMEOUT_MS` to a positive millisecond value to change it.
305
305
  - **One at a time**: Cannot have multiple pending asks from the same session
306
306
  - **Cannot self-target**: A session cannot ask itself
307
307
 
@@ -402,11 +402,13 @@ if (!result.delivered) {
402
402
  await intercom({ action: "list" });
403
403
  }
404
404
  ```
405
+ Replies to recently disconnected named senders can be queued by the broker and delivered if that sender reconnects with the same name. New sends still need a known live or recently disconnected target.
405
406
 
406
- **Ask timeout (after 10 minutes)**
407
+ **Ask timeout**
407
408
  ```typescript
408
409
  // The ask will reject with a timeout error
409
- // Design your workflow so answers come within 10 minutes
410
+ // Default: 10 minutes
411
+ // Override: set PI_INTERCOM_ASK_TIMEOUT_MS to a positive millisecond value
410
412
  // For longer tasks, use send + follow-up ask pattern
411
413
  ```
412
414
 
@@ -490,7 +492,7 @@ intercom({ action: "send", to: "planner", message: "Task-3 complete. All done."
490
492
  ### Long-Running Task with Checkpoints
491
493
 
492
494
  ```typescript
493
- // For tasks that might exceed 10 minutes, use send + periodic asks
495
+ // For tasks that might exceed the ask timeout, use send + periodic asks
494
496
 
495
497
  // 1. Initial send with full context
496
498
  intercom({
@@ -0,0 +1,184 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { stripVTControlCharacters } from "node:util";
4
+
5
+ import { visibleWidth } from "@earendil-works/pi-tui";
6
+ import { InlineMessageComponent } from "../ui/inline-message.ts";
7
+ import type { Message, SessionInfo } from "../types.ts";
8
+
9
+ const theme = {
10
+ fg(_name: string, text: string): string {
11
+ return text;
12
+ },
13
+ };
14
+
15
+ const from: SessionInfo = {
16
+ id: "session-12345678",
17
+ name: "sender",
18
+ cwd: "/tmp/project",
19
+ model: "model",
20
+ pid: 1,
21
+ startedAt: 0,
22
+ lastActivity: 0,
23
+ };
24
+
25
+ const message: Message = {
26
+ id: "message-1",
27
+ timestamp: 0,
28
+ content: {
29
+ text: "This is a long message that should use the available terminal width instead of a narrow fixed card.",
30
+ },
31
+ };
32
+
33
+ test("inline intercom messages render at the available terminal width", () => {
34
+ const component = new InlineMessageComponent(from, message, theme as any);
35
+
36
+ const lines = component.render(120);
37
+
38
+ assert.ok(lines.length > 0);
39
+ for (const line of lines) assert.equal(visibleWidth(line), 120);
40
+ });
41
+
42
+ test("expanded inline intercom messages show the full body without collapse controls", () => {
43
+ const component = new InlineMessageComponent(from, message, theme as any, "intercom({ action: \"reply\", message: \"...\" })");
44
+
45
+ const rendered = component.render(100).join("\n");
46
+
47
+ assert.match(rendered, /available terminal width/);
48
+ assert.match(rendered, /narrow fixed/);
49
+ assert.match(rendered, /card/);
50
+ assert.match(rendered, /To reply: intercom/);
51
+ assert.doesNotMatch(rendered, /Ctrl\+O/);
52
+ });
53
+
54
+ test("collapsed inline intercom messages keep preview, reply hint, and expand key visible", () => {
55
+ const component = new InlineMessageComponent(
56
+ from,
57
+ {
58
+ ...message,
59
+ content: {
60
+ text: "Alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu. This tail should only appear when expanded because the collapsed preview is intentionally brief.",
61
+ attachments: [{ type: "snippet", name: "note.txt", content: "important details" }],
62
+ },
63
+ },
64
+ theme as any,
65
+ "intercom({ action: \"reply\", message: \"...\" })",
66
+ undefined,
67
+ true,
68
+ );
69
+
70
+ const lines = component.render(120);
71
+ const rendered = lines.join("\n");
72
+
73
+ assert.equal(lines.length, 4);
74
+ for (const line of lines) assert.equal(visibleWidth(line), 120);
75
+ assert.match(rendered, /Alpha beta gamma/);
76
+ assert.doesNotMatch(rendered, /intentionally brief/);
77
+ assert.match(rendered, /To reply: intercom/);
78
+ assert.match(rendered, /Ctrl\+O/);
79
+ assert.match(rendered, /1 attachment/);
80
+ });
81
+
82
+ const roleCodes = {
83
+ accent: 31,
84
+ toolTitle: 32,
85
+ text: 33,
86
+ muted: 34,
87
+ dim: 35,
88
+ } as const;
89
+
90
+ function styledTheme(calls: string[] = []) {
91
+ return {
92
+ fg(name: keyof typeof roleCodes, text: string): string {
93
+ calls.push(name);
94
+ return `\u001b[${roleCodes[name]}m${text}\u001b[0m`;
95
+ },
96
+ };
97
+ }
98
+
99
+ test("inline message colors follow the tool-title, text, muted-border, and dim-metadata hierarchy", () => {
100
+ const calls: string[] = [];
101
+ const component = new InlineMessageComponent(
102
+ from,
103
+ {
104
+ ...message,
105
+ replyTo: "parent-message-id",
106
+ content: {
107
+ text: "Body copy",
108
+ attachments: [{ type: "snippet", name: "note.txt", content: "details" }],
109
+ },
110
+ },
111
+ styledTheme(calls) as any,
112
+ "intercom reply",
113
+ );
114
+
115
+ const lines = component.render(72);
116
+ const rendered = lines.join("\n");
117
+
118
+ assert.match(lines[0], /^\u001b\[34mβ•­\u001b\[0m\u001b\[32m πŸ“¨ From:/);
119
+ assert.match(rendered, /\u001b\[34mβ”‚\u001b\[0m\u001b\[33mBody copy\u001b\[0m/);
120
+ assert.match(rendered, /\u001b\[35m ↩ To reply: intercom reply\u001b\[0m/);
121
+ assert.match(rendered, /\u001b\[35m πŸ“Ž note\.txt\u001b\[0m/);
122
+ assert.match(rendered, /\u001b\[35m ↳ Reply to parent-m\u001b\[0m/);
123
+ assert.match(lines.at(-1)!, /^\u001b\[34m╰─+β•―\u001b\[0m$/);
124
+ assert.ok(calls.includes("toolTitle"));
125
+ assert.ok(calls.includes("text"));
126
+ assert.ok(calls.includes("muted"));
127
+ assert.ok(calls.includes("dim"));
128
+ assert.ok(!calls.includes("accent"));
129
+ });
130
+
131
+ test("collapsed inline messages preserve the same hierarchy without accent", () => {
132
+ const calls: string[] = [];
133
+ const component = new InlineMessageComponent(from, message, styledTheme(calls) as any, "intercom reply", undefined, true);
134
+
135
+ const rendered = component.render(72).join("\n");
136
+
137
+ assert.match(rendered, /\u001b\[32m πŸ“¨ From:/);
138
+ assert.match(rendered, /\u001b\[33mThis is a long message/);
139
+ assert.match(rendered, /\u001b\[35m ↩ To reply:/);
140
+ assert.ok(!calls.includes("accent"));
141
+ });
142
+
143
+ test("semantic styling remains ANSI- and Unicode-width safe", () => {
144
+ const unicodeFrom = { ...from, name: "ι€δΏ‘θ€…πŸ›°οΈ", cwd: "/tmp/θ¨ˆη”»" };
145
+ const unicodeMessage = {
146
+ ...message,
147
+ content: { text: "\u001b[36mθ‰²δ»˜γζœ¬ζ–‡\u001b[0m with emoji πŸ§ͺ and a long Unicode tail θ¨ˆη”»θ¨ˆη”»θ¨ˆη”»" },
148
+ };
149
+ const component = new InlineMessageComponent(unicodeFrom, unicodeMessage, styledTheme() as any, "θΏ”δΏ‘ πŸ“¨");
150
+
151
+ for (const width of [18, 31, 52]) {
152
+ const lines = component.render(width);
153
+ assert.ok(lines.length > 0);
154
+ for (const line of lines) {
155
+ assert.equal(visibleWidth(line), width, `${width}: ${stripVTControlCharacters(line)}`);
156
+ }
157
+ }
158
+ });
159
+
160
+ test("inline messages pick up mutable theme proxy changes on rerender", () => {
161
+ const palette: Record<string, number> = { ...roleCodes };
162
+ const mutableTheme = new Proxy(
163
+ {},
164
+ {
165
+ get(_target, property) {
166
+ if (property !== "fg") return undefined;
167
+ return (name: string, text: string) => `\u001b[${palette[name]}m${text}\u001b[0m`;
168
+ },
169
+ },
170
+ );
171
+ const component = new InlineMessageComponent(from, message, mutableTheme as any);
172
+
173
+ const before = component.render(72).join("\n");
174
+ palette.toolTitle = 96;
175
+ palette.text = 97;
176
+ palette.muted = 90;
177
+ const after = component.render(72).join("\n");
178
+
179
+ assert.match(before, /\u001b\[32m πŸ“¨ From:/);
180
+ assert.match(after, /\u001b\[96m πŸ“¨ From:/);
181
+ assert.match(after, /\u001b\[97mThis is a long message/);
182
+ assert.match(after, /\u001b\[90mβ•­/);
183
+ assert.notEqual(before, after);
184
+ });
@@ -0,0 +1,66 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { visibleWidth } from "@earendil-works/pi-tui";
5
+ import { ComposeOverlay } from "../ui/compose.ts";
6
+ import { SessionListOverlay } from "../ui/session-list.ts";
7
+ import type { SessionInfo } from "../types.ts";
8
+
9
+ const theme = {
10
+ fg(_name: string, text: string): string {
11
+ return text;
12
+ },
13
+ bold(text: string): string {
14
+ return text;
15
+ },
16
+ };
17
+
18
+ const keybindings = {
19
+ matches(): boolean {
20
+ return false;
21
+ },
22
+ getKeys(id: string): string[] {
23
+ return id.includes("confirm") ? ["enter"] : ["escape", "ctrl+c"];
24
+ },
25
+ };
26
+
27
+ const session: SessionInfo = {
28
+ id: "session-12345678",
29
+ name: "subagent-chat-019ecaf6",
30
+ cwd: "/Users/envvar/.config/ghostty",
31
+ model: "bsy-deepseek-v4-pro",
32
+ pid: 1,
33
+ startedAt: 0,
34
+ lastActivity: 0,
35
+ };
36
+
37
+ function assertLineWidths(label: string, lines: string[], expectedWidth: number): void {
38
+ assert.ok(lines.length > 0, `${label} should render lines`);
39
+ for (const [index, line] of lines.entries()) {
40
+ assert.equal(visibleWidth(line), expectedWidth, `${label} line ${index} should match overlay width`);
41
+ }
42
+ }
43
+
44
+ test("compose overlay renders lines at the declared overlay width", () => {
45
+ const overlay = new ComposeOverlay(
46
+ { requestRender() {} } as any,
47
+ theme as any,
48
+ keybindings as any,
49
+ session,
50
+ "subagent-chat-019ecaf6",
51
+ { send: async () => ({ delivered: true, id: "message-1" }) } as any,
52
+ () => {},
53
+ );
54
+
55
+ for (const width of [1, 2, 20, 40, 72]) {
56
+ assertLineWidths("compose overlay", overlay.render(width), width);
57
+ }
58
+ });
59
+
60
+ test("session list overlay renders lines at the declared overlay width", () => {
61
+ const overlay = new SessionListOverlay(theme as any, keybindings as any, session, [session], () => {});
62
+
63
+ for (const width of [1, 2, 20, 50, 88]) {
64
+ assertLineWidths("session list overlay", overlay.render(width), width);
65
+ }
66
+ });