@crouton-kit/crouter 0.3.31 → 0.3.33

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 (443) hide show
  1. package/README.md +8 -0
  2. package/dist/build-root.js +1 -0
  3. package/dist/builtin-memory/crouter-development/marketplaces.md +2 -2
  4. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
  5. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
  6. package/dist/builtin-memory/crouter-development/personas.md +3 -4
  7. package/dist/builtin-memory/crouter-development/plugins.md +18 -18
  8. package/dist/builtin-memory/design.md +4 -1
  9. package/dist/builtin-memory/development.md +4 -1
  10. package/dist/builtin-memory/internal/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/nodes-and-canvas.md +6 -6
  12. package/dist/builtin-memory/internal/storage-tiers.md +5 -5
  13. package/dist/builtin-memory/planning.md +4 -1
  14. package/dist/builtin-memory/product.md +80 -0
  15. package/dist/builtin-memory/spec.md +4 -1
  16. package/dist/builtin-personas/advisor/PERSONA.md +10 -0
  17. package/dist/builtin-personas/design/PERSONA.md +2 -2
  18. package/dist/builtin-personas/design/orchestrator.md +3 -3
  19. package/dist/builtin-personas/developer/PERSONA.md +1 -1
  20. package/dist/builtin-personas/developer/orchestrator.md +2 -2
  21. package/dist/builtin-personas/explore/PERSONA.md +3 -3
  22. package/dist/builtin-personas/explore/orchestrator.md +4 -2
  23. package/dist/builtin-personas/general/PERSONA.md +1 -1
  24. package/dist/builtin-personas/general/orchestrator.md +1 -1
  25. package/dist/builtin-personas/orchestration-kernel.md +7 -14
  26. package/dist/builtin-personas/plan/PERSONA.md +1 -1
  27. package/dist/builtin-personas/plan/orchestrator.md +2 -2
  28. package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
  29. package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
  30. package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
  31. package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
  32. package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
  33. package/dist/builtin-personas/product/PERSONA.md +18 -0
  34. package/dist/builtin-personas/product/orchestrator.md +14 -0
  35. package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
  36. package/dist/builtin-personas/review/PERSONA.md +1 -1
  37. package/dist/builtin-personas/review/orchestrator.md +1 -1
  38. package/dist/builtin-personas/runtime-base.md +5 -0
  39. package/dist/builtin-personas/spec/PERSONA.md +1 -1
  40. package/dist/builtin-personas/spec/orchestrator.md +2 -2
  41. package/dist/builtin-personas/spec/requirements/PERSONA.md +1 -1
  42. package/dist/builtin-views/canvas/core.mjs +82 -1
  43. package/dist/builtin-views/canvas/tui.mjs +9 -5
  44. package/dist/builtin-views/canvas/web.jsx +3 -2
  45. package/dist/builtin-views/chat/core.mjs +725 -0
  46. package/dist/builtin-views/chat/text.mjs +101 -0
  47. package/dist/builtin-views/chat/tui.mjs +368 -0
  48. package/dist/builtin-views/chat/web.jsx +367 -0
  49. package/dist/builtin-views/prompt-review/core.mjs +863 -0
  50. package/dist/builtin-views/prompt-review/text.mjs +15 -0
  51. package/dist/builtin-views/prompt-review/tui.mjs +196 -0
  52. package/dist/builtin-views/prompt-review/web.jsx +484 -0
  53. package/dist/builtin-views/settings/core.mjs +397 -0
  54. package/dist/builtin-views/settings/text.mjs +40 -0
  55. package/dist/builtin-views/settings/tui.mjs +95 -0
  56. package/dist/builtin-views/settings/web.jsx +167 -0
  57. package/dist/cli.js +16 -3
  58. package/dist/clients/attach/__tests__/autocomplete-and-bash-mode.test.js +96 -0
  59. package/dist/clients/attach/__tests__/bash-bang-routing.test.js +58 -0
  60. package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.d.ts +1 -0
  61. package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.js +56 -0
  62. package/dist/clients/attach/__tests__/titled-editor.test.js +22 -1
  63. package/dist/clients/attach/attach-cmd.d.ts +18 -0
  64. package/dist/clients/attach/attach-cmd.js +1723 -795
  65. package/dist/clients/attach/canvas-panels.js +2 -2
  66. package/dist/clients/attach/chat-view.d.ts +10 -0
  67. package/dist/clients/attach/chat-view.js +23 -0
  68. package/dist/clients/attach/config-load.d.ts +8 -4
  69. package/dist/clients/attach/config-load.js +2 -0
  70. package/dist/clients/attach/extension-dialogs.d.ts +2 -3
  71. package/dist/clients/attach/extension-dialogs.js +7 -9
  72. package/dist/clients/attach/graph-overlay.js +3 -2
  73. package/dist/clients/attach/input-controller.d.ts +4 -3
  74. package/dist/clients/attach/input-controller.js +24 -3
  75. package/dist/clients/attach/slash-commands.d.ts +7 -7
  76. package/dist/clients/attach/slash-commands.js +38 -14
  77. package/dist/clients/attach/titled-editor.js +18 -14
  78. package/dist/clients/attach/view-socket.d.ts +2 -1
  79. package/dist/clients/attach/view-socket.js +27 -8
  80. package/dist/clients/web/__tests__/source-cache.test.d.ts +1 -0
  81. package/dist/clients/web/__tests__/source-cache.test.js +32 -0
  82. package/dist/clients/web/dev-server.js +1 -0
  83. package/dist/clients/web/events.js +9 -11
  84. package/dist/clients/web/server.d.ts +4 -3
  85. package/dist/clients/web/server.js +138 -31
  86. package/dist/clients/web/source-cache.d.ts +10 -0
  87. package/dist/clients/web/source-cache.js +57 -0
  88. package/dist/clients/web/web-cmd.js +28 -9
  89. package/dist/commands/__tests__/human.test.js +30 -28
  90. package/dist/commands/canvas-history/search.js +1 -1
  91. package/dist/commands/canvas-history.js +5 -8
  92. package/dist/commands/canvas-issue.d.ts +2 -0
  93. package/dist/commands/canvas-issue.js +148 -0
  94. package/dist/commands/canvas-prune.js +19 -0
  95. package/dist/commands/canvas-rebuild-index.d.ts +2 -0
  96. package/dist/commands/canvas-rebuild-index.js +57 -0
  97. package/dist/commands/canvas-snapshot.d.ts +2 -0
  98. package/dist/commands/canvas-snapshot.js +48 -0
  99. package/dist/commands/canvas-tmux-spread.d.ts +2 -0
  100. package/dist/commands/canvas-tmux-spread.js +188 -0
  101. package/dist/commands/canvas.d.ts +1 -0
  102. package/dist/commands/canvas.js +16 -1
  103. package/dist/commands/chord.js +143 -48
  104. package/dist/commands/daemon.js +3 -3
  105. package/dist/commands/human/prompts.d.ts +0 -1
  106. package/dist/commands/human/prompts.js +39 -54
  107. package/dist/commands/human/queue.d.ts +12 -1
  108. package/dist/commands/human/queue.js +468 -73
  109. package/dist/commands/human/shared.d.ts +3 -4
  110. package/dist/commands/human/shared.js +3 -3
  111. package/dist/commands/human.js +11 -10
  112. package/dist/commands/memory/find.js +3 -2
  113. package/dist/commands/memory/list.js +4 -3
  114. package/dist/commands/memory/read.js +3 -3
  115. package/dist/commands/memory/shared.d.ts +1 -4
  116. package/dist/commands/memory/shared.js +6 -9
  117. package/dist/commands/memory/write.js +3 -3
  118. package/dist/commands/memory.js +4 -7
  119. package/dist/commands/node-context.d.ts +2 -0
  120. package/dist/commands/node-context.js +172 -0
  121. package/dist/commands/node-snapshot.d.ts +2 -0
  122. package/dist/commands/node-snapshot.js +123 -0
  123. package/dist/commands/node.js +228 -52
  124. package/dist/commands/pkg/plugin-inspect.js +6 -6
  125. package/dist/commands/pkg/plugin-manage.js +1 -1
  126. package/dist/commands/pkg/plugin.js +2 -2
  127. package/dist/commands/pkg.js +2 -2
  128. package/dist/commands/push.d.ts +2 -0
  129. package/dist/commands/push.js +79 -11
  130. package/dist/commands/revive.js +74 -1
  131. package/dist/commands/search/answer.d.ts +1 -0
  132. package/dist/commands/search/answer.js +50 -0
  133. package/dist/commands/search/contents.d.ts +1 -0
  134. package/dist/commands/search/contents.js +96 -0
  135. package/dist/commands/search/exa.d.ts +53 -0
  136. package/dist/commands/search/exa.js +155 -0
  137. package/dist/commands/search/puremd.d.ts +11 -0
  138. package/dist/commands/search/puremd.js +53 -0
  139. package/dist/commands/search/web.d.ts +1 -0
  140. package/dist/commands/search/web.js +65 -0
  141. package/dist/commands/search.d.ts +2 -0
  142. package/dist/commands/search.js +24 -0
  143. package/dist/commands/sys/__tests__/sync-import.test.d.ts +1 -0
  144. package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
  145. package/dist/commands/sys/config.js +59 -10
  146. package/dist/commands/sys/doctor.js +56 -4
  147. package/dist/commands/sys/prompt-review.d.ts +1 -0
  148. package/dist/commands/sys/prompt-review.js +178 -0
  149. package/dist/commands/sys/promptstudio.d.ts +2 -0
  150. package/dist/commands/sys/promptstudio.js +65 -0
  151. package/dist/commands/sys/settings.d.ts +2 -0
  152. package/dist/commands/sys/settings.js +16 -0
  153. package/dist/commands/sys/sync.js +311 -159
  154. package/dist/commands/sys/sysprompt.d.ts +1 -0
  155. package/dist/commands/sys/sysprompt.js +86 -0
  156. package/dist/commands/sys.js +9 -5
  157. package/dist/commands/view-new.js +2 -2
  158. package/dist/core/__tests__/artifact-paths.test.d.ts +1 -0
  159. package/dist/core/__tests__/artifact-paths.test.js +44 -0
  160. package/dist/core/__tests__/broker-preflight.test.d.ts +1 -0
  161. package/dist/core/__tests__/broker-preflight.test.js +85 -0
  162. package/dist/core/__tests__/broker-sdk-wiring.test.js +15 -0
  163. package/dist/core/__tests__/canvas.test.js +37 -1
  164. package/dist/core/__tests__/chat-view-reconnect.test.d.ts +1 -0
  165. package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
  166. package/dist/core/__tests__/child-death-wake.test.js +11 -2
  167. package/dist/core/__tests__/close.test.js +39 -1
  168. package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +1 -0
  169. package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
  170. package/dist/core/__tests__/context-intro.test.js +48 -14
  171. package/dist/core/__tests__/daemon-boot.test.js +182 -9
  172. package/dist/core/__tests__/editor-label.test.d.ts +1 -0
  173. package/dist/core/__tests__/editor-label.test.js +26 -0
  174. package/dist/core/__tests__/fault-marker.test.d.ts +1 -0
  175. package/dist/core/__tests__/fault-marker.test.js +112 -0
  176. package/dist/core/__tests__/fault-retry-rewind.test.d.ts +1 -0
  177. package/dist/core/__tests__/fault-retry-rewind.test.js +123 -0
  178. package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
  179. package/dist/core/__tests__/fixtures/fake-engine.js +156 -17
  180. package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -1
  181. package/dist/core/__tests__/full/human-new-window-regression.test.js +1 -1
  182. package/dist/core/__tests__/helpers/harness.d.ts +1 -1
  183. package/dist/core/__tests__/helpers/harness.js +9 -3
  184. package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
  185. package/dist/core/__tests__/human-surface-target.test.js +1 -1
  186. package/dist/core/__tests__/kickoff.test.js +18 -19
  187. package/dist/core/__tests__/memory-resolver.test.js +12 -1
  188. package/dist/core/__tests__/migration.test.js +49 -14
  189. package/dist/core/__tests__/model-ladders.test.d.ts +1 -0
  190. package/dist/core/__tests__/model-ladders.test.js +160 -0
  191. package/dist/core/__tests__/node-env.test.d.ts +1 -0
  192. package/dist/core/__tests__/node-env.test.js +26 -0
  193. package/dist/core/__tests__/push-final-guard.test.js +7 -1
  194. package/dist/core/__tests__/relaunch-root.test.js +9 -9
  195. package/dist/core/__tests__/revive.test.js +36 -6
  196. package/dist/core/__tests__/spawn-root.test.js +24 -1
  197. package/dist/core/__tests__/stop-guard.test.js +10 -0
  198. package/dist/core/__tests__/stranded-relaunch.test.d.ts +1 -0
  199. package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
  200. package/dist/core/__tests__/tmux-surface.test.js +5 -1
  201. package/dist/core/__tests__/unknown-path.test.js +19 -19
  202. package/dist/core/__tests__/yield-ensures-daemon.test.d.ts +1 -0
  203. package/dist/core/__tests__/yield-ensures-daemon.test.js +54 -0
  204. package/dist/core/artifact.d.ts +2 -33
  205. package/dist/core/artifact.js +27 -87
  206. package/dist/core/bootstrap.js +2 -0
  207. package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
  208. package/dist/core/canvas/browse/app.js +87 -17
  209. package/dist/core/canvas/browse/model.d.ts +10 -1
  210. package/dist/core/canvas/browse/model.js +37 -1
  211. package/dist/core/canvas/browse/render.js +35 -33
  212. package/dist/core/canvas/canvas.d.ts +7 -0
  213. package/dist/core/canvas/canvas.js +54 -5
  214. package/dist/core/canvas/db.js +35 -8
  215. package/dist/core/canvas/labels.d.ts +6 -8
  216. package/dist/core/canvas/labels.js +8 -11
  217. package/dist/core/canvas/nav-model.d.ts +5 -4
  218. package/dist/core/canvas/nav-model.js +31 -17
  219. package/dist/core/canvas/paths.d.ts +18 -1
  220. package/dist/core/canvas/paths.js +37 -2
  221. package/dist/core/canvas/render.d.ts +25 -0
  222. package/dist/core/canvas/render.js +96 -13
  223. package/dist/core/canvas/status-glyph.d.ts +35 -0
  224. package/dist/core/canvas/status-glyph.js +104 -0
  225. package/dist/core/canvas/types.d.ts +2 -2
  226. package/dist/core/config.js +114 -16
  227. package/dist/core/fault-classifier.d.ts +41 -0
  228. package/dist/core/fault-classifier.js +110 -0
  229. package/dist/core/feed/__tests__/empty-node-guard.test.d.ts +1 -0
  230. package/dist/core/feed/__tests__/empty-node-guard.test.js +45 -0
  231. package/dist/core/feed/feed.js +8 -0
  232. package/dist/core/feed/inbox.d.ts +15 -0
  233. package/dist/core/feed/inbox.js +40 -14
  234. package/dist/core/frontmatter.d.ts +1 -11
  235. package/dist/core/frontmatter.js +1 -55
  236. package/dist/core/hearth/config.d.ts +2 -0
  237. package/dist/core/hearth/config.js +75 -0
  238. package/dist/core/hearth/guest-env.d.ts +2 -0
  239. package/dist/core/hearth/guest-env.js +11 -0
  240. package/dist/core/hearth/index.d.ts +5 -0
  241. package/dist/core/hearth/index.js +5 -0
  242. package/dist/core/hearth/provider.d.ts +21 -0
  243. package/dist/core/hearth/provider.js +10 -0
  244. package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +10 -0
  245. package/dist/core/hearth/providers/blaxel-bootstrap.js +39 -0
  246. package/dist/core/hearth/providers/blaxel-home.d.ts +11 -0
  247. package/dist/core/hearth/providers/blaxel-home.js +126 -0
  248. package/dist/core/hearth/providers/blaxel.d.ts +26 -0
  249. package/dist/core/hearth/providers/blaxel.js +208 -0
  250. package/dist/core/hearth/providers/types.d.ts +72 -0
  251. package/dist/core/hearth/providers/types.js +1 -0
  252. package/dist/core/hearth/registry.d.ts +15 -0
  253. package/dist/core/hearth/registry.js +179 -0
  254. package/dist/core/hearth/types.d.ts +121 -0
  255. package/dist/core/hearth/types.js +1 -0
  256. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +1 -0
  257. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
  258. package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
  259. package/dist/core/host-exports/builtins.js +68 -0
  260. package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
  261. package/dist/core/{skill-sync → host-exports}/export.js +37 -14
  262. package/dist/core/io.js +1 -1
  263. package/dist/core/log.d.ts +9 -0
  264. package/dist/core/log.js +113 -0
  265. package/dist/core/memory-resolver.d.ts +8 -8
  266. package/dist/core/memory-resolver.js +15 -28
  267. package/dist/core/personas/index.d.ts +4 -4
  268. package/dist/core/personas/index.js +2 -2
  269. package/dist/core/personas/loader.d.ts +51 -0
  270. package/dist/core/personas/loader.js +54 -50
  271. package/dist/core/personas/resolve.d.ts +43 -27
  272. package/dist/core/personas/resolve.js +336 -94
  273. package/dist/core/runtime/auth-reload.d.ts +7 -0
  274. package/dist/core/runtime/auth-reload.js +69 -0
  275. package/dist/core/runtime/bearings.d.ts +35 -15
  276. package/dist/core/runtime/bearings.js +305 -44
  277. package/dist/core/runtime/branded-host.d.ts +4 -2
  278. package/dist/core/runtime/branded-host.js +66 -4
  279. package/dist/core/runtime/broker-protocol.d.ts +38 -3
  280. package/dist/core/runtime/broker.js +343 -18
  281. package/dist/core/runtime/close.js +37 -29
  282. package/dist/core/runtime/connectivity.d.ts +12 -0
  283. package/dist/core/runtime/connectivity.js +73 -0
  284. package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
  285. package/dist/core/runtime/fault-recovery-nudge.js +2 -0
  286. package/dist/core/runtime/fault-recovery.d.ts +23 -0
  287. package/dist/core/runtime/fault-recovery.js +74 -0
  288. package/dist/core/runtime/fault.d.ts +25 -0
  289. package/dist/core/runtime/fault.js +176 -0
  290. package/dist/core/runtime/front-door.d.ts +1 -1
  291. package/dist/core/runtime/front-door.js +2 -2
  292. package/dist/core/runtime/host.d.ts +10 -0
  293. package/dist/core/runtime/host.js +71 -9
  294. package/dist/core/runtime/kickoff.js +31 -45
  295. package/dist/core/runtime/launch.d.ts +32 -18
  296. package/dist/core/runtime/launch.js +148 -43
  297. package/dist/core/runtime/model-swap.d.ts +18 -0
  298. package/dist/core/runtime/model-swap.js +95 -0
  299. package/dist/core/runtime/naming.d.ts +7 -0
  300. package/dist/core/runtime/naming.js +61 -9
  301. package/dist/core/runtime/nodes.js +6 -1
  302. package/dist/core/runtime/persona.js +16 -17
  303. package/dist/core/runtime/placement.d.ts +36 -31
  304. package/dist/core/runtime/placement.js +149 -64
  305. package/dist/core/runtime/promote.d.ts +2 -0
  306. package/dist/core/runtime/promote.js +23 -3
  307. package/dist/core/runtime/recycle.js +6 -4
  308. package/dist/core/runtime/reset.d.ts +5 -2
  309. package/dist/core/runtime/reset.js +2 -2
  310. package/dist/core/runtime/revive.d.ts +4 -0
  311. package/dist/core/runtime/revive.js +27 -9
  312. package/dist/core/runtime/roadmap.d.ts +1 -1
  313. package/dist/core/runtime/roadmap.js +1 -1
  314. package/dist/core/runtime/spawn.d.ts +2 -2
  315. package/dist/core/runtime/spawn.js +30 -6
  316. package/dist/core/runtime/stop-guard.js +6 -1
  317. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  318. package/dist/core/runtime/tmux-chrome.js +1 -1
  319. package/dist/core/runtime/tmux.d.ts +29 -6
  320. package/dist/core/runtime/tmux.js +134 -80
  321. package/dist/core/runtime/view-socket-client.d.ts +46 -0
  322. package/dist/core/runtime/view-socket-client.js +203 -0
  323. package/dist/core/substrate/index.d.ts +1 -1
  324. package/dist/core/substrate/index.js +1 -1
  325. package/dist/core/substrate/on-read.js +16 -4
  326. package/dist/core/substrate/render.d.ts +10 -13
  327. package/dist/core/substrate/render.js +56 -51
  328. package/dist/core/tui/host.js +83 -18
  329. package/dist/core/view/__tests__/transport-cache.test.d.ts +1 -0
  330. package/dist/core/view/__tests__/transport-cache.test.js +62 -0
  331. package/dist/core/view/contract.d.ts +20 -0
  332. package/dist/core/view/stream-local.d.ts +3 -0
  333. package/dist/core/view/stream-local.js +184 -0
  334. package/dist/core/view/transport-cache.d.ts +8 -0
  335. package/dist/core/view/transport-cache.js +38 -0
  336. package/dist/core/view/transport-local.js +4 -0
  337. package/dist/core/view/transport.d.ts +7 -1
  338. package/dist/daemon/crtrd.d.ts +3 -19
  339. package/dist/daemon/crtrd.js +139 -178
  340. package/dist/daemon/manage.d.ts +18 -1
  341. package/dist/daemon/manage.js +54 -9
  342. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
  343. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
  344. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
  345. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
  346. package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
  347. package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
  348. package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
  349. package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
  350. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
  351. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
  352. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
  353. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
  354. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
  355. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
  356. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +1 -0
  357. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +455 -0
  358. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
  359. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
  360. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
  361. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
  362. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
  363. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
  364. package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
  365. package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
  366. package/dist/hearth/wake-proxy/auth.d.ts +22 -0
  367. package/dist/hearth/wake-proxy/auth.js +128 -0
  368. package/dist/hearth/wake-proxy/config.d.ts +2 -0
  369. package/dist/hearth/wake-proxy/config.js +151 -0
  370. package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
  371. package/dist/hearth/wake-proxy/guest-source.js +130 -0
  372. package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
  373. package/dist/hearth/wake-proxy/hearth-status.js +267 -0
  374. package/dist/hearth/wake-proxy/home.d.ts +67 -0
  375. package/dist/hearth/wake-proxy/home.js +297 -0
  376. package/dist/hearth/wake-proxy/main.d.ts +1 -0
  377. package/dist/hearth/wake-proxy/main.js +134 -0
  378. package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
  379. package/dist/hearth/wake-proxy/model-auth.js +345 -0
  380. package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
  381. package/dist/hearth/wake-proxy/proxy.js +716 -0
  382. package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
  383. package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
  384. package/dist/hearth/wake-proxy/redact.d.ts +1 -0
  385. package/dist/hearth/wake-proxy/redact.js +17 -0
  386. package/dist/hearth/wake-proxy/server.d.ts +11 -0
  387. package/dist/hearth/wake-proxy/server.js +142 -0
  388. package/dist/hearth/wake-proxy/state.d.ts +18 -0
  389. package/dist/hearth/wake-proxy/state.js +342 -0
  390. package/dist/hearth/wake-proxy/types.d.ts +76 -0
  391. package/dist/hearth/wake-proxy/types.js +1 -0
  392. package/dist/index.js +3 -2
  393. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
  394. package/dist/pi-extensions/canvas-context-intro.js +7 -7
  395. package/dist/pi-extensions/canvas-doc-substrate.js +19 -22
  396. package/dist/pi-extensions/canvas-nav.js +15 -8
  397. package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
  398. package/dist/pi-extensions/canvas-stophook.js +78 -11
  399. package/dist/types.d.ts +39 -20
  400. package/dist/types.js +38 -13
  401. package/dist/web/runtime.js +141 -42
  402. package/dist/web/transport-http.js +7 -5
  403. package/dist/web/transport-stream.d.ts +3 -0
  404. package/dist/web/transport-stream.js +204 -0
  405. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  406. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  407. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  408. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  409. package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
  410. package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
  411. package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
  412. package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
  413. package/dist/web-client/assets/index-D36PNBj4.js +80 -0
  414. package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
  415. package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
  416. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  417. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  418. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  419. package/dist/web-client/index.html +3 -2
  420. package/package.json +32 -10
  421. package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
  422. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
  423. package/dist/core/skill-sync/builtins.js +0 -112
  424. package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
  425. package/dist/core/skill-sync/claude-plugins.js +0 -71
  426. package/dist/core/skill-sync/engine.d.ts +0 -42
  427. package/dist/core/skill-sync/engine.js +0 -633
  428. package/dist/core/skill-sync/manifest.d.ts +0 -64
  429. package/dist/core/skill-sync/manifest.js +0 -181
  430. package/dist/core/skill-sync/profile.d.ts +0 -76
  431. package/dist/core/skill-sync/profile.js +0 -173
  432. package/dist/core/skill-sync/snapshot.d.ts +0 -57
  433. package/dist/core/skill-sync/snapshot.js +0 -120
  434. package/dist/pi-extensions/canvas-commands.d.ts +0 -37
  435. package/dist/pi-extensions/canvas-commands.js +0 -113
  436. package/dist/pi-extensions/canvas-resume.d.ts +0 -21
  437. package/dist/pi-extensions/canvas-resume.js +0 -83
  438. package/dist/pi-extensions/canvas-view.d.ts +0 -21
  439. package/dist/pi-extensions/canvas-view.js +0 -76
  440. package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
  441. package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
  442. /package/dist/{core/__tests__/error-stall-recycle.test.d.ts → clients/attach/__tests__/autocomplete-and-bash-mode.test.d.ts} +0 -0
  443. /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → clients/attach/__tests__/bash-bang-routing.test.d.ts} +0 -0
@@ -0,0 +1,167 @@
1
+ // @ts-check
2
+ /**
3
+ * Model ladders settings — web presenter for the `settings` view.
4
+ *
5
+ * Mouse-first: enum fields (default provider, persona strength) are click-to-pick
6
+ * chips that save immediately; free-text fields (matrix model ids, new persona)
7
+ * open an inline editor prefilled with the current value, with Save/Cancel.
8
+ *
9
+ * @module settings/web
10
+ */
11
+
12
+ import { Loading, NotReady, ErrorState } from '@crouton-kit/crouter/web';
13
+
14
+ /** @typedef {import('./core.mjs').SettingsState} SettingsState */
15
+
16
+ const STRENGTHS = ['ultra', 'strong', 'medium', 'light'];
17
+ const PROVIDERS = ['anthropic', 'openai'];
18
+
19
+ /** Inline editor for a free-text field (matrix model id, new persona pair). */
20
+ /** @param {{ state: SettingsState, dispatch: (intent:string, payload?:unknown)=>void }} props */
21
+ function Editor({ state, dispatch }) {
22
+ if (!state.edit) return null;
23
+ return (
24
+ <div className="rounded border border-cyan-300 bg-cyan-50 p-3">
25
+ <div className="mb-2 text-sm text-slate-600">Editing <span className="font-semibold text-slate-900">{state.edit.label}</span></div>
26
+ <div className="flex items-center gap-2">
27
+ <input
28
+ autoFocus
29
+ value={state.edit.draft}
30
+ placeholder={state.edit.kind === 'personaNew' ? 'kind strength' : 'model id'}
31
+ onChange={(e) => dispatch('setDraft', e.target.value)}
32
+ onKeyDown={(e) => {
33
+ if (e.key === 'Enter') { e.preventDefault(); dispatch('submitEdit'); }
34
+ else if (e.key === 'Escape') { e.preventDefault(); dispatch('cancelEdit'); }
35
+ }}
36
+ className="flex-1 rounded border border-slate-300 bg-white px-3 py-2 font-mono text-sm outline-none focus:border-cyan-500"
37
+ />
38
+ <button type="button" onClick={() => dispatch('submitEdit')} className="rounded bg-cyan-600 px-3 py-2 text-sm font-semibold text-white hover:bg-cyan-700">Save</button>
39
+ <button type="button" onClick={() => dispatch('cancelEdit')} className="rounded border border-slate-300 bg-white px-3 py-2 text-sm text-slate-600 hover:bg-slate-100">Cancel</button>
40
+ </div>
41
+ {state.edit.kind === 'personaNew' ? <div className="mt-2 text-xs text-slate-500">Format: <code>kind strength</code> or <code>kind=strength</code> (strength = ultra·strong·medium·light).</div> : null}
42
+ </div>
43
+ );
44
+ }
45
+
46
+ /** A small segmented control of fixed choices that saves on click. */
47
+ /** @param {{ choices: string[], value: string, onPick: (c:string)=>void }} props */
48
+ function Segmented({ choices, value, onPick }) {
49
+ return (
50
+ <div className="inline-flex overflow-hidden rounded border border-slate-300">
51
+ {choices.map((c) => (
52
+ <button
53
+ key={c}
54
+ type="button"
55
+ onClick={() => onPick(c)}
56
+ className={`px-2.5 py-1 text-sm capitalize ${c === value ? 'bg-cyan-600 text-white' : 'bg-white text-slate-600 hover:bg-slate-100'} ${c !== choices[0] ? 'border-l border-slate-300' : ''}`}
57
+ >
58
+ {c}
59
+ </button>
60
+ ))}
61
+ </div>
62
+ );
63
+ }
64
+
65
+ /** True when the editor is currently targeting this matrix cell. */
66
+ /** @param {SettingsState} state @param {string} strength @param {string} provider */
67
+ function isEditingCell(state, strength, provider) {
68
+ return !!state.edit && state.edit.kind === 'matrixCell' && state.edit.strength === strength && state.edit.provider === provider;
69
+ }
70
+
71
+ /** @param {import('../../core/view/contract.js').ViewProps<SettingsState>} props */
72
+ export default function Settings({ state, dispatch }) {
73
+ if (state.sourceError && state.modelLadders === null) {
74
+ const d = state.sourceError.display;
75
+ const Comp = d.level === 'error' ? ErrorState : NotReady;
76
+ return <Comp headline={d.headline} explanation={d.explanation} nextStep={d.nextStep || undefined} onRetry={() => dispatch('refresh')} />;
77
+ }
78
+ if (state.modelLadders === null) {
79
+ return <Loading label="Loading model ladders…" />;
80
+ }
81
+
82
+ const ladders = state.modelLadders;
83
+ const personas = Object.keys(state.personaStrengths).sort((a, b) => a.localeCompare(b));
84
+ const editingPersonaNew = !!state.edit && state.edit.kind === 'personaNew';
85
+
86
+ return (
87
+ <div className="h-full overflow-auto text-sm">
88
+ <div className="space-y-5 p-4">
89
+ <div>
90
+ <h1 className="text-base font-semibold">Model ladders</h1>
91
+ <p className="text-xs text-slate-500">Click a model id to edit it; click a provider or strength to switch it. Changes save immediately.</p>
92
+ </div>
93
+
94
+ {/* Default provider — enum, click to pick. */}
95
+ <div className="flex items-center gap-3">
96
+ <span className="font-semibold">Default provider</span>
97
+ <Segmented choices={PROVIDERS} value={ladders.defaultProvider ?? 'anthropic'} onPick={(p) => dispatch('applyValue', { kind: 'defaultProvider', value: p })} />
98
+ </div>
99
+
100
+ {/* Provider × strength matrix — each model id click-to-edit. */}
101
+ <div className="space-y-3">
102
+ {STRENGTHS.map((strength) => (
103
+ <div key={strength}>
104
+ <div className="mb-1 font-semibold capitalize">{strength}</div>
105
+ <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
106
+ {PROVIDERS.map((provider) => {
107
+ const editing = isEditingCell(state, strength, provider);
108
+ if (editing) {
109
+ return (
110
+ <div key={provider} className="rounded border border-cyan-300 bg-cyan-50 p-2">
111
+ <div className="mb-1 text-xs uppercase tracking-wide text-slate-400">{provider}</div>
112
+ <div className="flex items-center gap-2">
113
+ <input
114
+ autoFocus
115
+ value={state.edit.draft}
116
+ onChange={(e) => dispatch('setDraft', e.target.value)}
117
+ onKeyDown={(e) => {
118
+ if (e.key === 'Enter') { e.preventDefault(); dispatch('submitEdit'); }
119
+ else if (e.key === 'Escape') { e.preventDefault(); dispatch('cancelEdit'); }
120
+ }}
121
+ className="flex-1 rounded border border-slate-300 bg-white px-2 py-1 font-mono text-sm outline-none focus:border-cyan-500"
122
+ />
123
+ <button type="button" onClick={() => dispatch('submitEdit')} className="rounded bg-cyan-600 px-2 py-1 text-xs font-semibold text-white hover:bg-cyan-700">Save</button>
124
+ <button type="button" onClick={() => dispatch('cancelEdit')} className="rounded border border-slate-300 bg-white px-2 py-1 text-xs text-slate-600 hover:bg-slate-100">Cancel</button>
125
+ </div>
126
+ </div>
127
+ );
128
+ }
129
+ const v = ladders[provider]?.[strength] ?? '—';
130
+ return (
131
+ <button
132
+ key={provider}
133
+ type="button"
134
+ title="Click to edit"
135
+ onClick={() => dispatch('beginEditFor', { kind: 'matrix', strength, provider })}
136
+ className="rounded border border-slate-200 bg-white px-2 py-1 text-left hover:border-cyan-400 hover:bg-cyan-50"
137
+ >
138
+ <div className="text-xs uppercase tracking-wide text-slate-400">{provider}</div>
139
+ <div className="truncate font-mono text-sm text-slate-800">{v}</div>
140
+ </button>
141
+ );
142
+ })}
143
+ </div>
144
+ </div>
145
+ ))}
146
+ </div>
147
+
148
+ {/* Persona strengths — enum per persona, click to pick. */}
149
+ <div className="space-y-2">
150
+ <div className="font-semibold">Persona strengths</div>
151
+ {personas.length === 0 ? <div className="text-xs text-slate-400">None set yet.</div> : null}
152
+ {personas.map((persona) => (
153
+ <div key={persona} className="flex items-center gap-3">
154
+ <span className="w-40 truncate font-mono text-slate-800">{persona}</span>
155
+ <Segmented choices={STRENGTHS} value={state.personaStrengths[persona] ?? 'strong'} onPick={(s) => dispatch('applyValue', { kind: 'personaStrength', persona, value: s })} />
156
+ </div>
157
+ ))}
158
+ {editingPersonaNew ? (
159
+ <Editor state={state} dispatch={dispatch} />
160
+ ) : (
161
+ <button type="button" onClick={() => dispatch('beginEditFor', { kind: 'addPersona' })} className="rounded border border-dashed border-emerald-400 px-3 py-1.5 text-sm font-semibold text-emerald-700 hover:bg-emerald-50">+ add persona strength</button>
162
+ )}
163
+ </div>
164
+ </div>
165
+ </div>
166
+ );
167
+ }
package/dist/cli.js CHANGED
@@ -1,10 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import { enableCompileCache } from 'node:module';
2
3
  import { runCli } from './core/command.js';
3
4
  import { resolveRoot } from './build-root.js';
4
5
  import { maybeBootRoot } from './core/runtime/front-door.js';
5
6
  import { maybeAutoUpdate } from './core/auto-update.js';
6
7
  import { ensureOfficialMarketplace, ensureProjectScope } from './core/bootstrap.js';
7
- import { provisionExports } from './core/skill-sync/export.js';
8
+ import { provisionExports } from './core/host-exports/export.js';
9
+ // V8 compile-cache (Node 22+): persist compiled bytecode across runs so a cold
10
+ // `crtr` re-spawn skips recompiling its module graph. Biggest payoff is the
11
+ // heavy attach-viewer bundle that main() dynamic-imports via resolveRoot — this
12
+ // runs first, so the cache is active before that import() compiles. Measured
13
+ // neutral on the tiny leaf path (feed/node), a clear win on attach. Best-effort:
14
+ // older Node lacks the API.
15
+ try {
16
+ enableCompileCache();
17
+ }
18
+ catch { /* pre-22 Node: no compile cache */ }
8
19
  async function main() {
9
20
  // Lazy command tree: load only the subtree this invocation dispatches into.
10
21
  // resolveRoot returns the full tree only for help/version/bare/unknown, where
@@ -12,14 +23,16 @@ async function main() {
12
23
  // hot leaf-dispatch path loads one subtree, keeping the other 11 (and their
13
24
  // heavy deps — the attach TUI, web/vite) off cold-start.
14
25
  const root = await resolveRoot(process.argv[2]);
26
+ // Prune legacy generated host artifacts before the front door can boot pi,
27
+ // otherwise stale Agent Skills can leak into the prompt for bare `crtr`.
28
+ provisionExports(root, process.argv);
15
29
  // The front door: bare `crtr` (or `crtr [dir] ["prompt"]`) boots a resident
16
30
  // root node and execs pi in this terminal. Recognized subcommands fall through
17
31
  // to the normal dispatcher. Must run before anything that assumes a subcommand.
18
- if (maybeBootRoot(root, process.argv)) {
32
+ if (await maybeBootRoot(root, process.argv)) {
19
33
  // bootRoot exec'd pi inline and exited; unreachable.
20
34
  }
21
35
  ensureOfficialMarketplace(process.argv);
22
- provisionExports(root);
23
36
  ensureProjectScope(process.argv);
24
37
  maybeAutoUpdate(process.argv);
25
38
  await runCli(root, process.argv);
@@ -0,0 +1,96 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { chmodSync, mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { CombinedAutocompleteProvider } from '@earendil-works/pi-tui';
7
+ import { buildAttachAutocompleteProvider, deriveBashMode, resolveEditorFrameStyles } from '../attach-cmd.js';
8
+ function writeFakeFd(binDir) {
9
+ const fdBin = join(binDir, 'fd');
10
+ writeFileSync(fdBin, `#!/usr/bin/env node
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+ if (process.argv.includes('--version')) {
14
+ process.stdout.write('fd 0.0.0\\n');
15
+ process.exit(0);
16
+ }
17
+ const args = process.argv.slice(2);
18
+ const baseDirIndex = args.indexOf('--base-directory');
19
+ const baseDir = baseDirIndex >= 0 ? args[baseDirIndex + 1] : process.cwd();
20
+ const maxResultsIndex = args.indexOf('--max-results');
21
+ const maxResults = maxResultsIndex >= 0 ? Number(args[maxResultsIndex + 1]) : Infinity;
22
+ const fullPath = args.includes('--full-path');
23
+ let query = '';
24
+ for (let i = 0; i < args.length; i += 1) {
25
+ const arg = args[i];
26
+ if (arg === '--base-directory' || arg === '--max-results' || arg === '--type' || arg === '--exclude') {
27
+ i += 1;
28
+ continue;
29
+ }
30
+ if (arg === '--follow' || arg === '--hidden' || arg === '--full-path') continue;
31
+ if (!arg.startsWith('--')) query = arg;
32
+ }
33
+ const results = [];
34
+ function walk(dir, rel = '') {
35
+ const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
36
+ for (const entry of entries) {
37
+ if (entry.name === '.git') continue;
38
+ const nextRel = rel ? rel + '/' + entry.name : entry.name;
39
+ const full = path.join(dir, entry.name);
40
+ if (!query || nextRel.includes(query) || entry.name.includes(query)) {
41
+ results.push((fullPath ? nextRel : nextRel) + (entry.isDirectory() ? '/' : ''));
42
+ if (results.length >= maxResults) return true;
43
+ }
44
+ if (entry.isDirectory() && walk(full, nextRel)) return true;
45
+ }
46
+ return false;
47
+ }
48
+ walk(baseDir);
49
+ process.stdout.write(results.join('\\n'));
50
+ `, 'utf8');
51
+ chmodSync(fdBin, 0o755);
52
+ return fdBin;
53
+ }
54
+ test('#6 attach threads the resolved fd path into @-mention file completion', async () => {
55
+ const base = mkdtempSync(join(tmpdir(), 'crtr-at-'));
56
+ const binDir = mkdtempSync(join(tmpdir(), 'crtr-fd-'));
57
+ mkdirSync(join(base, 'nested'));
58
+ writeFileSync(join(base, 'alpha.txt'), 'a');
59
+ writeFileSync(join(base, 'nested', 'beta.md'), 'b');
60
+ const fdPath = writeFakeFd(binDir);
61
+ let seen;
62
+ const provider = buildAttachAutocompleteProvider(base, fdPath, [], (items, cwd, passedFdPath) => {
63
+ seen = { cwd, fdPath: passedFdPath, items };
64
+ return new CombinedAutocompleteProvider(items, cwd, passedFdPath);
65
+ });
66
+ assert.equal(seen?.cwd, base);
67
+ assert.equal(seen?.fdPath, fdPath);
68
+ assert.ok((seen?.items.length ?? 0) > 0);
69
+ const result = await provider.getSuggestions(['@'], 0, 1, {
70
+ force: false,
71
+ signal: new AbortController().signal,
72
+ });
73
+ assert.ok(result, 'expected @ suggestions from the file picker');
74
+ assert.equal(result?.prefix, '@');
75
+ assert.ok(result?.items.some((item) => item.label === 'alpha.txt' || item.label === 'nested/'));
76
+ });
77
+ test('attach-cmd derives bash mode from the leading cue and switches frame colors', () => {
78
+ assert.equal(deriveBashMode('plain text'), 0);
79
+ assert.equal(deriveBashMode(' ! echo hi'), 1);
80
+ assert.equal(deriveBashMode('\t!! echo hi'), 2);
81
+ const pal = {
82
+ bashMode: (s) => `green:${s}`,
83
+ bashModeAlt: (s) => `bright:${s}`,
84
+ border: (s) => `border:${s}`,
85
+ };
86
+ const off = resolveEditorFrameStyles(0, undefined, pal);
87
+ assert.equal(off.titleStyle('x'), '\x1b[7m x \x1b[27m');
88
+ assert.notEqual(off.borderColor, pal.bashMode);
89
+ assert.notEqual(off.borderColor, pal.bashModeAlt);
90
+ const single = resolveEditorFrameStyles(1, undefined, pal);
91
+ assert.equal(single.borderColor, pal.bashMode);
92
+ assert.equal(single.titleStyle('x'), '\x1b[42m\x1b[97m\x1b[1m x \x1b[0m');
93
+ const double = resolveEditorFrameStyles(2, undefined, pal);
94
+ assert.equal(double.borderColor, pal.bashModeAlt);
95
+ assert.equal(double.titleStyle('x'), '\x1b[102m\x1b[30m\x1b[1m x \x1b[0m');
96
+ });
@@ -0,0 +1,58 @@
1
+ // Regression: typing `!cmd` in the attach viewer was sent to the engine as a
2
+ // normal PROMPT (the LLM saw the literal `!cmd` and took a turn) instead of
3
+ // running bash and injecting the output into context with no agent turn — pi's
4
+ // native `!`/`!!` semantics. The fix routes a leading `!` in InputController to a
5
+ // `bash` drive frame (and `!!` to one with excludeFromContext). These tests lock
6
+ // the routing decision (the user-visible crux) so it can't silently revert.
7
+ import { test } from 'node:test';
8
+ import assert from 'node:assert/strict';
9
+ import { mkdtempSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ import { TUI, ProcessTerminal } from '@earendil-works/pi-tui';
13
+ import { TitledEditor } from '../titled-editor.js';
14
+ import { InputController } from '../input-controller.js';
15
+ import { createKeybindingsManager } from '../config-load.js';
16
+ const ENTER = '\r';
17
+ function build() {
18
+ const agentDir = mkdtempSync(join(tmpdir(), 'crtr-bang-'));
19
+ const km = createKeybindingsManager(agentDir);
20
+ const tui = new TUI(new ProcessTerminal());
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ const editor = new TitledEditor(tui, { borderColor: (s) => s, selectList: {} }, km, {
23
+ paddingX: 1,
24
+ });
25
+ const frames = [];
26
+ const hooks = {
27
+ onCommand: (f) => frames.push(f),
28
+ onDialogResponse: () => { },
29
+ };
30
+ // eslint-disable-next-line no-new -- wiring the editor.onSubmit handler is the point
31
+ new InputController(tui, editor, km, hooks);
32
+ return { editor, frames };
33
+ }
34
+ test('`!cmd` routes to a bash frame, not a prompt', () => {
35
+ const { editor, frames } = build();
36
+ editor.setText('!ls -la');
37
+ editor.handleInput(ENTER);
38
+ assert.deepEqual(frames, [{ type: 'bash', command: 'ls -la', excludeFromContext: false }]);
39
+ assert.equal(editor.getText(), '', 'editor clears after a bash submit');
40
+ });
41
+ test('`!!cmd` routes to a bash frame with excludeFromContext', () => {
42
+ const { editor, frames } = build();
43
+ editor.setText('!!echo secret');
44
+ editor.handleInput(ENTER);
45
+ assert.deepEqual(frames, [{ type: 'bash', command: 'echo secret', excludeFromContext: true }]);
46
+ });
47
+ test('a bare `!` with no command is ignored (no frame, text kept)', () => {
48
+ const { editor, frames } = build();
49
+ editor.setText('! ');
50
+ editor.handleInput(ENTER);
51
+ assert.equal(frames.length, 0, 'a bare `!` must not emit a bash frame');
52
+ });
53
+ test('ordinary text still routes to a prompt frame (no regression)', () => {
54
+ const { editor, frames } = build();
55
+ editor.setText('hello world');
56
+ editor.handleInput(ENTER);
57
+ assert.deepEqual(frames, [{ type: 'prompt', text: 'hello world' }]);
58
+ });
@@ -0,0 +1,56 @@
1
+ // Regression: after the attach viewer was re-bundled (commit d72c0de), Alt+Enter
2
+ // silently stopped inserting a newline. Root cause was NOT in the editor logic —
3
+ // editor-newline.test.ts still passed because it runs UNBUNDLED (tsx), where
4
+ // config-load and pi-coding-agent's CustomEditor each load their own pi-tui from
5
+ // disk and `mirrorKeybindingsToEditor` reaches the editor's disk copy.
6
+ //
7
+ // In the BUNDLE, esbuild inlined TWO distinct pi-tui module instances — the
8
+ // top-level `node_modules/@earendil-works/pi-tui` (imported by config-load) and
9
+ // pi-coding-agent's nested `.../pi-coding-agent/node_modules/@earendil-works/pi-tui`
10
+ // (imported by CustomEditor). `setKeybindings` ran against the first global; the
11
+ // editor read `getKeybindings` from the second. The Alt+Enter newLine binding was
12
+ // invisible to the editor and the key fell through — swallowed. The runtime disk
13
+ // mirror can't help: it targets a third (on-disk) instance, not either bundled one.
14
+ //
15
+ // Fix: the build:attach script aliases @earendil-works/pi-tui to one absolute
16
+ // path so esbuild collapses both imports to a SINGLE bundled instance, and
17
+ // setKeybindings reaches the editor directly. This test bundles attach-cmd.ts
18
+ // the way the build script does and asserts exactly one pi-tui copy is inlined.
19
+ import { test } from 'node:test';
20
+ import assert from 'node:assert/strict';
21
+ import { readFileSync } from 'node:fs';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { dirname, join } from 'node:path';
24
+ import { build } from 'esbuild';
25
+ const here = dirname(fileURLToPath(import.meta.url));
26
+ const repoRoot = join(here, '..', '..', '..', '..'); // src/clients/attach/__tests__ → package root
27
+ const entry = join(repoRoot, 'src/clients/attach/attach-cmd.ts');
28
+ const piTui = join(repoRoot, 'node_modules/@earendil-works/pi-tui');
29
+ /** Count distinct pi-tui package directories esbuild inlines into the bundle.
30
+ * Unminified output keeps each module's source path as a `// <path>` comment. */
31
+ function piTuiCopies(code) {
32
+ const copies = new Set();
33
+ for (const m of code.matchAll(/node_modules\/(?:\.pnpm\/)?@earendil-works\/(?:pi-coding-agent\/node_modules\/@earendil-works\/)?pi-tui/g)) {
34
+ copies.add(m[0]);
35
+ }
36
+ return copies;
37
+ }
38
+ test('build:attach aliases pi-tui to one path (sanity: package.json carries the alias)', () => {
39
+ const pkg = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8'));
40
+ assert.match(pkg.scripts['build:attach'], /--alias:@earendil-works\/pi-tui=/, 'build:attach must alias pi-tui so the bundle inlines a single instance');
41
+ });
42
+ test('attach bundle inlines exactly ONE pi-tui instance (editor + config-load share the keybindings global)', async () => {
43
+ const result = await build({
44
+ entryPoints: [entry],
45
+ bundle: true,
46
+ write: false,
47
+ format: 'esm',
48
+ platform: 'node',
49
+ target: 'node22',
50
+ alias: { '@earendil-works/pi-tui': piTui },
51
+ logLevel: 'silent',
52
+ });
53
+ const code = result.outputFiles[0].text;
54
+ const copies = piTuiCopies(code);
55
+ assert.equal(copies.size, 1, `bundle must inline exactly one pi-tui (two = the Alt+Enter regression). Found: ${[...copies].join(', ')}`);
56
+ });
@@ -10,7 +10,7 @@
10
10
  import { test } from 'node:test';
11
11
  import assert from 'node:assert/strict';
12
12
  import { visibleWidth } from '@earendil-works/pi-tui';
13
- import { composeTopBorder } from '../titled-editor.js';
13
+ import { composeTopBorder, thinkingBorderColor, thinkingTitleStyle } from '../titled-editor.js';
14
14
  const titleStyle = (s) => `\x1b[7m ${s} \x1b[27m`;
15
15
  const borderColor = (s) => `\x1b[38;2;128;128;128m${s}\x1b[39m`;
16
16
  const compose = (width, title, info) => composeTopBorder(width, title, info, titleStyle, borderColor);
@@ -37,3 +37,24 @@ test('info chip survives intact when there is room for it', () => {
37
37
  assert.ok(line.includes(info), 'fitting info must not be truncated or dropped');
38
38
  assert.equal(visibleWidth(line), 80);
39
39
  });
40
+ test('thinking levels use an evenly spaced blue→orange scale', () => {
41
+ const cases = [
42
+ ['minimal', '38;2;68;120;228', '48;2;68;120;228'],
43
+ ['low', '38;2;108;128;188', '48;2;108;128;188'],
44
+ ['medium', '38;2;148;136;148', '48;2;148;136;148'],
45
+ ['high', '38;2;188;144;108', '48;2;188;144;108'],
46
+ ['xhigh', '38;2;228;152;68', '48;2;228;152;68'],
47
+ ];
48
+ const borderRgb = new Set();
49
+ const titleRgb = new Set();
50
+ for (const [level, border, title] of cases) {
51
+ const borderColor = thinkingBorderColor(level, (s) => s);
52
+ const titleStyle = thinkingTitleStyle(level, (s) => s);
53
+ assert.match(composeTopBorder(24, 'sess', '', (s) => s, borderColor), new RegExp(`\\x1b\\[${border}m`), `${level} border should use ${border}`);
54
+ assert.match(titleStyle('sess'), new RegExp(`\\x1b\\[${title}m`), `${level} title should use ${title}`);
55
+ borderRgb.add(border);
56
+ titleRgb.add(title);
57
+ }
58
+ assert.equal(borderRgb.size, cases.length, 'every thinking level needs its own border step');
59
+ assert.equal(titleRgb.size, cases.length, 'every thinking level needs its own title step');
60
+ });
@@ -1,2 +1,20 @@
1
+ import { CombinedAutocompleteProvider } from '@earendil-works/pi-tui';
1
2
  import type { BranchDef } from '../../core/command.js';
3
+ import type { BrokerSnapshot } from '../../core/runtime/broker-protocol.js';
4
+ import { type AttachPalette } from './config-load.js';
5
+ /** Derive the shell-mode cue from the editor contents. Leading whitespace is
6
+ * ignored so pasted commands and indented text still toggle correctly. */
7
+ export declare function deriveBashMode(text: string): 0 | 1 | 2;
8
+ /** Resolve the editor border + title-chip styles for the current shell cue. */
9
+ export declare function resolveEditorFrameStyles(bashMode: 0 | 1 | 2, thinkingLevel: BrokerSnapshot['state']['thinkingLevel'] | undefined, pal: Pick<AttachPalette, 'bashMode' | 'bashModeAlt' | 'border'>): {
10
+ borderColor: (s: string) => string;
11
+ titleStyle: (s: string) => string;
12
+ };
13
+ export declare function buildAttachAutocompleteProvider(cwd: string, fdPath: string | null, commands?: ReadonlyArray<{
14
+ name: string;
15
+ description?: string;
16
+ }>, createProvider?: (items: ReadonlyArray<{
17
+ name: string;
18
+ description?: string;
19
+ }>, cwd: string, fdPath: string | null) => CombinedAutocompleteProvider): CombinedAutocompleteProvider;
2
20
  export declare function registerAttach(): BranchDef;