@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
@@ -8,7 +8,7 @@
8
8
  // Layout (the framing a revived node sees):
9
9
  // <roadmap file=…>…</roadmap> its evolving plan — the source of truth
10
10
  // <context-dir path=…>…</context-dir> what artifacts exist on disk
11
- // <feed>Awaiting N nodes … digest</feed> who it waits on + unread reports
11
+ // <feed>Awaiting N nodes … digest</feed> live wait state + unread/catch-up feed
12
12
  // <yield-message>…</yield-message> the note its prior self left on yield
13
13
  //
14
14
  // The roadmap (NOT the original spawn prompt) carries the goal on a refresh: its
@@ -19,7 +19,6 @@
19
19
  // yield-message is one-shot (consumed on the next revive).
20
20
  import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, readdirSync, } from 'node:fs';
21
21
  import { join } from 'node:path';
22
- import { homedir } from 'node:os';
23
22
  import { contextDir, reportsDir, getNode, subscriptionsOf, subscribersOf, } from '../canvas/index.js';
24
23
  import { readRoadmap, roadmapPath } from './roadmap.js';
25
24
  import { buildWakeBearings } from './bearings.js';
@@ -132,56 +131,41 @@ export function drainBearings(meta) {
132
131
  // conversation is gone AND the monotonic inbox cursor has already advanced past
133
132
  // everything drained pre-yield, so the revived node loses sight of reports its
134
133
  // subscriptions pushed BEFORE the yield. The bodies are never lost — they persist
135
- // at reports/<ts>-<kind>.md forever — but nothing pointed the revived node at
136
- // them. reportHistoryLines renders those existing on-disk paths so it can catch
137
- // up; PATHS ONLY (the node dereferences what it needs), no body read, no parse.
138
- const REPORT_HISTORY_PER_SOURCE = 5;
139
- const REPORT_HISTORY_TOTAL_CAP = 20;
140
- /** Collapse the home-dir prefix of an absolute path to a leading `~`, so report
141
- * paths render as `~/.crouter/canvas/nodes/<id>/reports/<ts>.md` instead of
142
- * repeating the long absolute home prefix on every catch-up line — cheaper
143
- * context, still dereferenceable by the revived node. A path NOT under the home
144
- * dir (e.g. a CRTR_HOME / project-scope home elsewhere) is returned unchanged,
145
- * so we never mangle a non-home path. */
146
- function tildify(p) {
147
- const home = homedir();
148
- return p.startsWith(home) ? '~' + p.slice(home.length) : p;
134
+ // at reports/<ts>-<kind>.md forever — but a fresh revive needs a bounded index
135
+ // that says history exists and how to expand it, not every path preloaded.
136
+ const REPORT_HISTORY_SOURCE_CAP = 8;
137
+ function reportKind(filename) {
138
+ return filename.match(/^[0-9]{8}T[0-9]{6}-(.+)\.md$/)?.[1] ?? 'report';
149
139
  }
150
- /** Recent report file PATHS (most-recent-first), grouped by publisher, for each
151
- * ACTIVE subscription of `nodeId` — independent of publisher liveness, so a
152
- * finished worker's history still surfaces for catch-up. Skips subscriptions
153
- * whose reports dir is empty/missing. Returns [] when the node has no active
154
- * subscriptions or none have reports yet (caller renders nothing extra). */
155
- function reportHistoryLines(nodeId) {
156
- const lines = [];
157
- let total = 0;
140
+ /** Condensed report-history index for each ACTIVE subscription of `nodeId`
141
+ * independent of publisher liveness, so a finished worker's history still
142
+ * surfaces for catch-up. Each source carries only its count and newest report
143
+ * ref; older refs stay one command away via `canvas history show`. */
144
+ function reportHistorySources(nodeId) {
145
+ const out = [];
158
146
  for (const sub of subscriptionsOf(nodeId).filter((s) => s.active)) {
159
- if (total >= REPORT_HISTORY_TOTAL_CAP)
160
- break;
161
147
  const dir = reportsDir(sub.node_id);
162
148
  if (!existsSync(dir))
163
149
  continue;
164
150
  const files = readdirSync(dir)
165
151
  .filter((f) => f.endsWith('.md'))
166
152
  .sort() // YYYYMMDDTHHmmss-<kind>.md — lexical sort = chronological
167
- .reverse() // most recent first
168
- .slice(0, REPORT_HISTORY_PER_SOURCE);
153
+ .reverse(); // most recent first
169
154
  if (files.length === 0)
170
155
  continue;
171
156
  const pub = getNode(sub.node_id);
172
- lines.push(` ${pub !== null ? `${pub.name} (${sub.node_id})` : sub.node_id}:`);
173
- for (const f of files) {
174
- if (total >= REPORT_HISTORY_TOTAL_CAP)
175
- break;
176
- lines.push(` ${tildify(join(dir, f))}`);
177
- total++;
178
- }
157
+ out.push({
158
+ nodeId: sub.node_id,
159
+ name: pub?.name ?? sub.node_id,
160
+ count: files.length,
161
+ latest: files[0],
162
+ });
179
163
  }
180
- return lines;
164
+ return out.sort((a, b) => b.latest.localeCompare(a.latest));
181
165
  }
182
166
  /** Render the <feed> block PURELY: the live "awaiting" roster (a read), the
183
- * already-drained unread digest (from drainBearings), and the on-disk report
184
- * history of this node's subscriptions (catch-up pointers). No cursor write. */
167
+ * already-drained unread digest (from drainBearings), and a progressive index
168
+ * to on-disk report history. No cursor write. */
185
169
  function feedBlock(nodeId, unreadDigest) {
186
170
  // Awaiting = active subscriptions whose publisher is still live (active|idle).
187
171
  const awaiting = subscriptionsOf(nodeId)
@@ -210,14 +194,16 @@ function feedBlock(nodeId, unreadDigest) {
210
194
  }
211
195
  // Catch-up history. The unread digest above only covers what arrived since the
212
196
  // cursor; on a refresh-revive the cursor has already passed everything drained
213
- // pre-yield, so point the node at the durable report history that persists on
214
- // disk. Renders nothing when the node has no active subscriptions with reports.
215
- const history = reportHistoryLines(nodeId);
197
+ // pre-yield, so preserve the breadcrumb without preloading every old report.
198
+ const history = reportHistorySources(nodeId);
216
199
  if (history.length > 0) {
217
- lines.push('', 'Report history on disk — the nodes you subscribe to keep every push they made; ' +
218
- 'these persist across your context refresh (the unread digest above only covers ' +
219
- 'what arrived since your cursor). Most recent first; dereference any you need. ' +
220
- '`crtr feed read --all` replays the full inbox history, cursor-independent.', ...history);
200
+ const visible = history.slice(0, REPORT_HISTORY_SOURCE_CAP);
201
+ const hidden = history.length - visible.length;
202
+ const total = history.reduce((sum, h) => sum + h.count, 0);
203
+ lines.push('', `Report history on disk (collapsed): ${total} report${total === 1 ? '' : 's'} across ${history.length} subscribed node${history.length === 1 ? '' : 's'}. This is a catch-up index for reports your cursor already passed; read only what matters.`, ...visible.map((h) => {
204
+ const ref = `${h.nodeId}:reports/${h.latest}`;
205
+ return ` - ${h.name} (${h.nodeId}): ${h.count} report${h.count === 1 ? '' : 's'}; latest ${reportKind(h.latest)}: ${ref}`;
206
+ }), ...(hidden > 0 ? [` - … ${hidden} more subscribed node${hidden === 1 ? '' : 's'} with reports hidden`] : []), 'Expand: `crtr canvas history read <ref>` for one listed report; `crtr canvas history show <node-id> --type report` for that node\'s full report list; `crtr feed read --all` for cursor-independent inbox history.');
221
207
  }
222
208
  return `<feed>\n${lines.join('\n')}\n</feed>`;
223
209
  }
@@ -1,4 +1,5 @@
1
1
  import type { NodeMeta, LaunchSpec, Mode, Lifecycle } from '../canvas/index.js';
2
+ import type { ModelProvider, ModelStrength } from '../../types.js';
2
3
  export declare const CANVAS_STOPHOOK_PATH: string;
3
4
  export declare const CANVAS_INBOX_WATCHER_PATH: string;
4
5
  export declare const CANVAS_NAV_PATH: string;
@@ -7,32 +8,46 @@ export declare const CANVAS_GOAL_CAPTURE_PATH: string;
7
8
  export declare const CANVAS_PASSIVE_CONTEXT_PATH: string;
8
9
  export declare const CANVAS_CONTEXT_INTRO_PATH: string;
9
10
  export declare const CANVAS_DOC_SUBSTRATE_PATH: string;
10
- export declare const CANVAS_COMMANDS_PATH: string;
11
- export declare const CANVAS_RESUME_PATH: string;
12
- export declare const CANVAS_VIEW_PATH: string;
13
11
  /** The canvas extensions every node loads, in order: stophook (routing +
14
12
  * telemetry + session-id capture), inbox-watcher (wake), nav (in-editor
15
13
  * graph chrome), recap (the per-node inactivity recap card — Haiku over the
16
14
  * conversation, shown above the manager line after 300s of no message),
17
15
  * goal-capture (persist the first user message as the goal),
18
16
  * passive-context (drain passive backlog as pre-text on the next message),
19
- * context-intro (inject the <crtr-context> bearings block carrying the
20
- * <knowledge> catalog as its own session message, once per brand-new chat),
21
- * doc-substrate (the unified document substrate's two hooks: <preferences> +
22
- * <memory-guidance> at boot, on-read context injection), commands (the /promote slash-command), resume (the /resume-node
23
- * whole-canvas picker → `crtr node focus`), view (the /view popup → `crtr
24
- * view pick` / `crtr view run <name>`).
17
+ * context-intro (inject the <crtr-bearings> block + the <memory kind="knowledge">
18
+ * catalog as its own session message, once per brand-new chat),
19
+ * doc-substrate (the unified document substrate's two hooks: the
20
+ * <memory kind="preference"> block at boot, on-read context injection).
25
21
  * All self-gate on CRTR_NODE_ID. goal-capture precedes passive-context so it
26
22
  * reads the raw user text. */
27
23
  export declare const CANVAS_EXTENSIONS: string[];
28
- /** The named capability tiers a caller picks with `--model`, in descending
29
- * strength. Each maps to a concrete pi model spec. `strong`/`medium`/`light`
30
- * resolve to the latest opus/sonnet/haiku; `ultra` to the frontier model. */
31
- export declare const MODEL_TIERS: Record<string, string>;
32
- /** Resolve a model token to the spec pi gets via `--model`. A named tier
33
- * (ultra/strong/medium/light) maps to its concrete spec; a bare family alias
34
- * (sonnet/opus/haiku) maps to that family's current versioned id; anything
35
- * with a `/` or an unknown name passes through. */
24
+ /** Env var naming the DEFAULT provider for an UNqualified bare strength only.
25
+ * `CRTR_MODEL_PROVIDER=openai` (case-insensitive) makes a bare `strong` resolve
26
+ * on the OpenAI ladder; anything else defaults to Anthropic. A configured
27
+ * defaultProvider in config.json wins over this env. */
28
+ export declare const OPENAI_PROVIDER_ENV = "CRTR_MODEL_PROVIDER";
29
+ export interface EquivalentProviderModel {
30
+ fromProvider: ModelProvider;
31
+ toProvider: ModelProvider;
32
+ strength: ModelStrength;
33
+ model: string;
34
+ }
35
+ /** Map a concrete ladder model back to its provider×strength cell, then return
36
+ * the other configured provider's model at the same strength. `failedProviders`
37
+ * is per-turn state: once a provider has produced a retryable outage error, do
38
+ * not bounce back to it or keep reattempting the same failed provider. */
39
+ export declare function equivalentOtherProviderModel(currentModel: string, failedProviders?: ReadonlySet<ModelProvider>): EquivalentProviderModel | null;
40
+ /** Resolve a persona/caller `model:` token to the concrete `model:thinking`
41
+ * spec pi gets via `--model`, in order:
42
+ * 1. `provider/rest` — apply strength aliases to `rest`; if `provider` is
43
+ * `anthropic`/`openai` AND `rest` is a strength, return that ladder cell
44
+ * (the word `openai` maps to the openai-codex ladder). Otherwise it is a
45
+ * concrete `provider/id` (incl. any `:thinking` suffix) → pass through.
46
+ * 2. Bare strength (incl. aliases normal/weak) → the default-provider ladder
47
+ * cell (config default provider, then CRTR_MODEL_PROVIDER, else anthropic).
48
+ * 3. Bare family alias (opus/sonnet/haiku) → the Anthropic ladder's
49
+ * strong/medium/light, ALWAYS anthropic (ignores the env/config default).
50
+ * 4. Anything else → pass through unchanged. */
36
51
  export declare function normalizeModel(model: string): string;
37
52
  /** Compose a node's full pi launch recipe from its persona. The system prompt
38
53
  * is composed from FOUR inputs: kind×mode (the persona body) plus lifecycle
@@ -49,7 +64,6 @@ export declare function buildLaunchSpec(kind: string, mode: Mode, opts: {
49
64
  }): {
50
65
  launch: LaunchSpec;
51
66
  lifecycle: 'terminal' | 'resident';
52
- skills: string[];
53
67
  };
54
68
  export interface PiInvocation {
55
69
  /** argv after the `pi` binary. */
@@ -6,13 +6,15 @@
6
6
  // (keeps its conversation), or fresh (against the context dir) for a refresh-yield.
7
7
  // The spec is rewritten on every polymorph (base→orchestrator) so a node
8
8
  // always comes back as its *current* self.
9
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
9
+ import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
10
10
  import { dirname, join } from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
+ import { readConfig, configPath as userConfigPath } from '../config.js';
12
13
  import { resolve as resolvePersona } from '../personas/index.js';
13
14
  import { nodeEnv } from './nodes.js';
14
15
  import { editorLabel } from '../canvas/index.js';
15
16
  import { nodeDir } from '../canvas/paths.js';
17
+ import { defaultScopeConfig } from '../../types.js';
16
18
  // ---------------------------------------------------------------------------
17
19
  // The two canvas pi-extensions every node loads. They self-gate on the live
18
20
  // {kind,mode} env, so the worker→orchestrator polymorph flips hook behavior
@@ -34,21 +36,16 @@ export const CANVAS_GOAL_CAPTURE_PATH = resolveExtension('canvas-goal-capture');
34
36
  export const CANVAS_PASSIVE_CONTEXT_PATH = resolveExtension('canvas-passive-context');
35
37
  export const CANVAS_CONTEXT_INTRO_PATH = resolveExtension('canvas-context-intro');
36
38
  export const CANVAS_DOC_SUBSTRATE_PATH = resolveExtension('canvas-doc-substrate');
37
- export const CANVAS_COMMANDS_PATH = resolveExtension('canvas-commands');
38
- export const CANVAS_RESUME_PATH = resolveExtension('canvas-resume');
39
- export const CANVAS_VIEW_PATH = resolveExtension('canvas-view');
40
39
  /** The canvas extensions every node loads, in order: stophook (routing +
41
40
  * telemetry + session-id capture), inbox-watcher (wake), nav (in-editor
42
41
  * graph chrome), recap (the per-node inactivity recap card — Haiku over the
43
42
  * conversation, shown above the manager line after 300s of no message),
44
43
  * goal-capture (persist the first user message as the goal),
45
44
  * passive-context (drain passive backlog as pre-text on the next message),
46
- * context-intro (inject the <crtr-context> bearings block carrying the
47
- * <knowledge> catalog as its own session message, once per brand-new chat),
48
- * doc-substrate (the unified document substrate's two hooks: <preferences> +
49
- * <memory-guidance> at boot, on-read context injection), commands (the /promote slash-command), resume (the /resume-node
50
- * whole-canvas picker → `crtr node focus`), view (the /view popup → `crtr
51
- * view pick` / `crtr view run <name>`).
45
+ * context-intro (inject the <crtr-bearings> block + the <memory kind="knowledge">
46
+ * catalog as its own session message, once per brand-new chat),
47
+ * doc-substrate (the unified document substrate's two hooks: the
48
+ * <memory kind="preference"> block at boot, on-read context injection).
52
49
  * All self-gate on CRTR_NODE_ID. goal-capture precedes passive-context so it
53
50
  * reads the raw user text. */
54
51
  export const CANVAS_EXTENSIONS = [
@@ -60,37 +57,141 @@ export const CANVAS_EXTENSIONS = [
60
57
  CANVAS_PASSIVE_CONTEXT_PATH,
61
58
  CANVAS_CONTEXT_INTRO_PATH,
62
59
  CANVAS_DOC_SUBSTRATE_PATH,
63
- CANVAS_COMMANDS_PATH,
64
- CANVAS_RESUME_PATH,
65
- CANVAS_VIEW_PATH,
66
60
  ];
67
- /** The named capability tiers a caller picks with `--model`, in descending
68
- * strength. Each maps to a concrete pi model spec. `strong`/`medium`/`light`
69
- * resolve to the latest opus/sonnet/haiku; `ultra` to the frontier model. */
70
- export const MODEL_TIERS = {
71
- ultra: 'anthropic/claude-fable-5',
72
- strong: 'anthropic/claude-opus-4-8',
73
- medium: 'anthropic/claude-sonnet-4-6',
74
- light: 'anthropic/claude-haiku-4-5',
75
- };
76
- /** Bare family aliases the same concrete versioned ids as the tiers. These
77
- * MUST be real registry ids (see `pi --list-models`): an unversioned spec like
78
- * `anthropic/sonnet` is NOT in the registry and silently falls back to the SDK
79
- * default (opus), so it can never be the resolution target. */
80
- const BARE_ALIASES = {
81
- opus: 'anthropic/claude-opus-4-8',
82
- sonnet: 'anthropic/claude-sonnet-4-6',
83
- haiku: 'anthropic/claude-haiku-4-5',
61
+ // ---------------------------------------------------------------------------
62
+ // Two-axis model resolution: (provider × strength) concrete `model:thinking`.
63
+ //
64
+ // A persona declares ONE `model:` field valued `provider/strength` (e.g.
65
+ // `openai/strong`, `anthropic/light`). The provider is a fixed per-persona
66
+ // property; the caller/persona thinks only in strength; the concrete model id
67
+ // AND the thinking level both fall out of the per-provider ladder below. The
68
+ // ladder itself and the default provider are user-configurable in config.json;
69
+ // pi accepts the `:thinking` suffix on `--model` (off,minimal,low,medium,high,
70
+ // xhigh confirmed via `pi --help`), so the resolved spec is exactly the
71
+ // string buildPiArgv passes to `--model`; no extra launch plumbing.
72
+ // ---------------------------------------------------------------------------
73
+ /** Cached user config for model resolution. The launch path is hot and reads
74
+ * config synchronously, so cache the merged config and invalidate on mtime/size
75
+ * changes rather than rereading on every normalizeModel/buildLaunchSpec call. */
76
+ let cachedUserConfig = null;
77
+ function readUserConfigCached() {
78
+ const path = userConfigPath('user');
79
+ if (path === null)
80
+ return defaultScopeConfig();
81
+ try {
82
+ const stat = statSync(path);
83
+ if (cachedUserConfig !== null &&
84
+ cachedUserConfig.path === path &&
85
+ cachedUserConfig.mtimeMs === stat.mtimeMs &&
86
+ cachedUserConfig.size === stat.size) {
87
+ return cachedUserConfig.config;
88
+ }
89
+ const config = readConfig('user');
90
+ cachedUserConfig = { path, mtimeMs: stat.mtimeMs, size: stat.size, config };
91
+ return config;
92
+ }
93
+ catch (error) {
94
+ if (error.code === 'ENOENT') {
95
+ cachedUserConfig = null;
96
+ return defaultScopeConfig();
97
+ }
98
+ throw error;
99
+ }
100
+ }
101
+ const DEFAULT_MODEL_LADDERS = defaultScopeConfig().modelLadders;
102
+ function modelLadders() {
103
+ return readUserConfigCached().modelLadders ?? DEFAULT_MODEL_LADDERS;
104
+ }
105
+ function ladderFor(provider, ladders = modelLadders()) {
106
+ return ladders[provider];
107
+ }
108
+ /** Env var naming the DEFAULT provider for an UNqualified bare strength only.
109
+ * `CRTR_MODEL_PROVIDER=openai` (case-insensitive) makes a bare `strong` resolve
110
+ * on the OpenAI ladder; anything else defaults to Anthropic. A configured
111
+ * defaultProvider in config.json wins over this env. */
112
+ export const OPENAI_PROVIDER_ENV = 'CRTR_MODEL_PROVIDER';
113
+ function defaultProvider() {
114
+ const cfgProvider = readUserConfigCached().modelLadders.defaultProvider;
115
+ if (cfgProvider === 'anthropic' || cfgProvider === 'openai')
116
+ return cfgProvider;
117
+ return (process.env[OPENAI_PROVIDER_ENV] ?? '').toLowerCase() === 'openai' ? 'openai' : 'anthropic';
118
+ }
119
+ /** Strength synonyms accepted from personas/callers. */
120
+ const STRENGTH_ALIASES = {
121
+ ultra: 'ultra',
122
+ strong: 'strong',
123
+ medium: 'medium',
124
+ light: 'light',
125
+ normal: 'medium',
126
+ weak: 'light',
84
127
  };
85
- /** Resolve a model token to the spec pi gets via `--model`. A named tier
86
- * (ultra/strong/medium/light) maps to its concrete spec; a bare family alias
87
- * (sonnet/opus/haiku) maps to that family's current versioned id; anything
88
- * with a `/` or an unknown name passes through. */
128
+ const MODEL_PROVIDER_KEYS = ['anthropic', 'openai'];
129
+ const MODEL_STRENGTHS = ['ultra', 'strong', 'medium', 'light'];
130
+ const THINKING_SUFFIXES = new Set(['off', 'minimal', 'low', 'medium', 'high', 'xhigh']);
131
+ function stripThinkingSuffix(spec) {
132
+ const i = spec.lastIndexOf(':');
133
+ if (i <= 0)
134
+ return spec;
135
+ return THINKING_SUFFIXES.has(spec.slice(i + 1)) ? spec.slice(0, i) : spec;
136
+ }
137
+ /** Map a concrete ladder model back to its provider×strength cell, then return
138
+ * the other configured provider's model at the same strength. `failedProviders`
139
+ * is per-turn state: once a provider has produced a retryable outage error, do
140
+ * not bounce back to it or keep reattempting the same failed provider. */
141
+ export function equivalentOtherProviderModel(currentModel, failedProviders = new Set()) {
142
+ const ladders = modelLadders();
143
+ const currentComparable = stripThinkingSuffix(normalizeModel(currentModel));
144
+ for (const fromProvider of MODEL_PROVIDER_KEYS) {
145
+ for (const strength of MODEL_STRENGTHS) {
146
+ if (stripThinkingSuffix(ladders[fromProvider][strength]) !== currentComparable)
147
+ continue;
148
+ if (failedProviders.has(fromProvider))
149
+ return null;
150
+ const toProvider = fromProvider === 'anthropic' ? 'openai' : 'anthropic';
151
+ if (failedProviders.has(toProvider))
152
+ return null;
153
+ return { fromProvider, toProvider, strength, model: ladders[toProvider][strength] };
154
+ }
155
+ }
156
+ return null;
157
+ }
158
+ /** Resolve a persona/caller `model:` token to the concrete `model:thinking`
159
+ * spec pi gets via `--model`, in order:
160
+ * 1. `provider/rest` — apply strength aliases to `rest`; if `provider` is
161
+ * `anthropic`/`openai` AND `rest` is a strength, return that ladder cell
162
+ * (the word `openai` maps to the openai-codex ladder). Otherwise it is a
163
+ * concrete `provider/id` (incl. any `:thinking` suffix) → pass through.
164
+ * 2. Bare strength (incl. aliases normal/weak) → the default-provider ladder
165
+ * cell (config default provider, then CRTR_MODEL_PROVIDER, else anthropic).
166
+ * 3. Bare family alias (opus/sonnet/haiku) → the Anthropic ladder's
167
+ * strong/medium/light, ALWAYS anthropic (ignores the env/config default).
168
+ * 4. Anything else → pass through unchanged. */
89
169
  export function normalizeModel(model) {
90
- if (model in MODEL_TIERS)
91
- return MODEL_TIERS[model];
92
- if (model in BARE_ALIASES)
93
- return BARE_ALIASES[model];
170
+ const ladders = modelLadders();
171
+ // 1. Qualified provider/rest.
172
+ if (model.includes('/')) {
173
+ const slash = model.indexOf('/');
174
+ const provider = model.slice(0, slash);
175
+ const rest = model.slice(slash + 1);
176
+ const strength = STRENGTH_ALIASES[rest];
177
+ if (strength !== undefined && (provider === 'anthropic' || provider === 'openai')) {
178
+ return ladderFor(provider, ladders)[strength];
179
+ }
180
+ // Concrete `provider/id` (optionally `...:thinking`) — pass through.
181
+ return model;
182
+ }
183
+ // 2. Bare strength → default-provider ladder.
184
+ const strength = STRENGTH_ALIASES[model];
185
+ if (strength !== undefined)
186
+ return ladderFor(defaultProvider(), ladders)[strength];
187
+ // 3. Bare family alias → always Anthropic.
188
+ if (model === 'opus')
189
+ return ladders.anthropic.strong;
190
+ if (model === 'sonnet')
191
+ return ladders.anthropic.medium;
192
+ if (model === 'haiku')
193
+ return ladders.anthropic.light;
194
+ // 4. Unknown — pass through.
94
195
  return model;
95
196
  }
96
197
  // ---------------------------------------------------------------------------
@@ -105,10 +206,14 @@ export function normalizeModel(model) {
105
206
  * declared extensions follow. */
106
207
  export function buildLaunchSpec(kind, mode, opts) {
107
208
  const p = resolvePersona(kind, mode, { lifecycle: opts.lifecycle, hasManager: opts.hasManager });
108
- // A caller-supplied override (durable on `meta.model_override`, re-passed on
109
- // every polymorph) wins over the persona's declared default; absent both, the
110
- // model is left unset and the node inherits pi's default.
111
- const chosenModel = opts.model ?? p.model;
209
+ const cfg = readUserConfigCached();
210
+ // Precedence, from strongest to weakest:
211
+ // 1. Caller-supplied override (durable on `meta.model_override`, re-passed on
212
+ // every polymorph).
213
+ // 2. Configured persona strength for this kind.
214
+ // 3. The persona file's own frontmatter model.
215
+ // 4. Unset → pi default.
216
+ const chosenModel = opts.model ?? cfg.personaStrengths[kind] ?? p.model;
112
217
  const launch = {
113
218
  model: chosenModel !== undefined ? normalizeModel(chosenModel) : undefined,
114
219
  tools: p.tools,
@@ -116,7 +221,7 @@ export function buildLaunchSpec(kind, mode, opts) {
116
221
  systemPrompt: p.systemPrompt,
117
222
  env: { ...(opts.extraEnv ?? {}) },
118
223
  };
119
- return { launch, lifecycle: p.lifecycle, skills: p.skills };
224
+ return { launch, lifecycle: p.lifecycle };
120
225
  }
121
226
  // ---------------------------------------------------------------------------
122
227
  // Build the pi argv to launch / revive a node
@@ -0,0 +1,18 @@
1
+ import { type NodeMeta } from '../canvas/index.js';
2
+ /** True when the node's broker engine is reachable: its recorded pid is alive
3
+ * AND its view.sock exists. Both are required — a dead pid with a stale socket,
4
+ * or a live pid mid-boot before it binds the socket, is NOT a live broker. */
5
+ export declare function isBrokerLive(meta: NodeMeta): boolean;
6
+ /** Swap a LIVE node's model over its view.sock. Connects, claims controller
7
+ * (hello + request_control, so an attached viewer never blocks the swap), sends
8
+ * `set_model`, and resolves with the broker-resolved `provider/id` on its ack —
9
+ * read back from the row the broker just persisted (model_changed.model carries
10
+ * only the bare id). Rejects with the broker's message on an `error` frame (e.g.
11
+ * `no model matching '<spec>' in the registry`), on connect failure, or timeout. */
12
+ export declare function setModelLive(nodeId: string, spec: string): Promise<string>;
13
+ /** Persist a model choice into a DORMANT node's durable recipe — mirrors exactly
14
+ * what the broker's persistModelChoice writes on a live switch: `model_override`
15
+ * (so polymorphs preserve it via buildLaunchSpec) and `launch.model` (the recipe
16
+ * buildPiArgv replays on revive). `spec` must already be a concrete `provider/id`
17
+ * (normalizeModel maps tiers/aliases; a bare substring cannot resolve offline). */
18
+ export declare function persistDormantModel(nodeId: string, spec: string): void;
@@ -0,0 +1,95 @@
1
+ // model-swap.ts — the engine behind `crtr node model`: swap a node's model
2
+ // (and thus its provider) WITHOUT attaching a viewer, persisting the choice so
3
+ // it survives revives. Two paths, chosen on broker liveness:
4
+ //
5
+ // LIVE — the node's broker is up (recorded pi_pid alive AND view.sock
6
+ // present): connect to view.sock, claim controller, send `set_model`,
7
+ // await the ack. The broker's broadcastModelChanged already persists
8
+ // the choice via persistModelChoice (model_override + launch.model), so
9
+ // this path NEVER writes the recipe itself — it reads back the resolved
10
+ // provider/id from the freshly-updated row.
11
+ // DORMANT — no live broker: write the durable recipe directly (mirroring exactly
12
+ // what persistModelChoice writes) so the next revive picks it up.
13
+ //
14
+ // This complements the in-viewer `/model` slash command for the headless case.
15
+ import { existsSync } from 'node:fs';
16
+ import { randomUUID } from 'node:crypto';
17
+ import { getNode, updateNode } from '../canvas/index.js';
18
+ import { viewSocketPath } from '../canvas/paths.js';
19
+ import { isPidAlive } from '../canvas/pid.js';
20
+ import { ViewSocketClient } from './view-socket-client.js';
21
+ /** How long to wait for the broker's `set_model` ack before giving up. The
22
+ * switch is local (engine method + a DB write), so this is generous slack. */
23
+ const ACK_TIMEOUT_MS = 15_000;
24
+ /** True when the node's broker engine is reachable: its recorded pid is alive
25
+ * AND its view.sock exists. Both are required — a dead pid with a stale socket,
26
+ * or a live pid mid-boot before it binds the socket, is NOT a live broker. */
27
+ export function isBrokerLive(meta) {
28
+ return isPidAlive(meta.pi_pid) && existsSync(viewSocketPath(meta.node_id));
29
+ }
30
+ /** Swap a LIVE node's model over its view.sock. Connects, claims controller
31
+ * (hello + request_control, so an attached viewer never blocks the swap), sends
32
+ * `set_model`, and resolves with the broker-resolved `provider/id` on its ack —
33
+ * read back from the row the broker just persisted (model_changed.model carries
34
+ * only the bare id). Rejects with the broker's message on an `error` frame (e.g.
35
+ * `no model matching '<spec>' in the registry`), on connect failure, or timeout. */
36
+ export function setModelLive(nodeId, spec) {
37
+ return new Promise((resolve, reject) => {
38
+ const client = new ViewSocketClient(nodeId);
39
+ const clientId = randomUUID();
40
+ let settled = false;
41
+ let resolvedId;
42
+ const timer = setTimeout(() => finish(() => reject(new Error(`timed out after ${ACK_TIMEOUT_MS}ms waiting for the broker to ack the model switch`))), ACK_TIMEOUT_MS);
43
+ if (typeof timer.unref === 'function')
44
+ timer.unref();
45
+ function finish(act) {
46
+ if (settled)
47
+ return;
48
+ settled = true;
49
+ clearTimeout(timer);
50
+ try {
51
+ client.close();
52
+ }
53
+ catch { /* best-effort */ }
54
+ act();
55
+ }
56
+ client.on('error', (err) => finish(() => reject(err)));
57
+ client.on('close', () => finish(() => reject(new Error('broker connection closed before it acked the model switch'))));
58
+ client.on('connect', () => {
59
+ // Claim control unconditionally: hello admits us as controller only if none
60
+ // is held; request_control preempts an attached viewer (idempotent when we
61
+ // already hold it). The broker processes these in order before set_model.
62
+ client.send({ type: 'hello', role: 'controller', client_id: clientId });
63
+ client.send({ type: 'request_control' });
64
+ client.send({ type: 'set_model', model: spec });
65
+ });
66
+ client.on('frame', (frame) => {
67
+ if (frame.type === 'model_changed') {
68
+ resolvedId = frame.model;
69
+ }
70
+ else if (frame.type === 'ack' && frame.for === 'set_model' && frame.ok) {
71
+ // The broker persisted model_override (full provider/id) in
72
+ // broadcastModelChanged BEFORE flushing these frames, so the row is
73
+ // authoritative; fall back to the bare id, then the raw spec.
74
+ const persisted = getNode(nodeId)?.model_override ?? undefined;
75
+ finish(() => resolve(persisted ?? resolvedId ?? spec));
76
+ }
77
+ else if (frame.type === 'error') {
78
+ finish(() => reject(new Error(frame.message)));
79
+ }
80
+ });
81
+ client.connect();
82
+ });
83
+ }
84
+ /** Persist a model choice into a DORMANT node's durable recipe — mirrors exactly
85
+ * what the broker's persistModelChoice writes on a live switch: `model_override`
86
+ * (so polymorphs preserve it via buildLaunchSpec) and `launch.model` (the recipe
87
+ * buildPiArgv replays on revive). `spec` must already be a concrete `provider/id`
88
+ * (normalizeModel maps tiers/aliases; a bare substring cannot resolve offline). */
89
+ export function persistDormantModel(nodeId, spec) {
90
+ const meta = getNode(nodeId);
91
+ if (meta === null)
92
+ return;
93
+ const launch = meta.launch !== undefined ? { ...meta.launch, model: spec } : undefined;
94
+ updateNode(nodeId, { model_override: spec, ...(launch !== undefined ? { launch } : {}) });
95
+ }
@@ -16,3 +16,10 @@ export declare function slugFromPrompt(prompt: string): string;
16
16
  * pi.setSessionName(editorLabel(meta)) so the LIVE editor label updates in the
17
17
  * same session, instead of waiting for the next revive/cycle. */
18
18
  export declare function generateAndPersistName(nodeId: string, prompt: string, onNamed?: (meta: NodeMeta) => void): void;
19
+ /** A filesystem-safe slug for the `.todos/<slug>.md` fallback path: lowercase,
20
+ * [a-z0-9] kept, everything else → single hyphen, clamped to 8 words. */
21
+ export declare function titleSlug(title: string): string;
22
+ /** Ask pi headlessly for a one-line issue title for `note`. Resolves to the
23
+ * generated title, or a local first-line fallback on any failure (non-zero
24
+ * exit, timeout, empty output). Async; never throws. */
25
+ export declare function generateIssueTitle(note: string): Promise<string>;