@crouton-kit/crouter 0.3.32 → 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 (440) 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 +2 -2
  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 +31 -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/inbox.d.ts +15 -0
  230. package/dist/core/feed/inbox.js +40 -14
  231. package/dist/core/frontmatter.d.ts +1 -11
  232. package/dist/core/frontmatter.js +1 -55
  233. package/dist/core/hearth/config.d.ts +2 -0
  234. package/dist/core/hearth/config.js +75 -0
  235. package/dist/core/hearth/guest-env.d.ts +2 -0
  236. package/dist/core/hearth/guest-env.js +11 -0
  237. package/dist/core/hearth/index.d.ts +5 -0
  238. package/dist/core/hearth/index.js +5 -0
  239. package/dist/core/hearth/provider.d.ts +21 -0
  240. package/dist/core/hearth/provider.js +10 -0
  241. package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +10 -0
  242. package/dist/core/hearth/providers/blaxel-bootstrap.js +39 -0
  243. package/dist/core/hearth/providers/blaxel-home.d.ts +11 -0
  244. package/dist/core/hearth/providers/blaxel-home.js +126 -0
  245. package/dist/core/hearth/providers/blaxel.d.ts +26 -0
  246. package/dist/core/hearth/providers/blaxel.js +208 -0
  247. package/dist/core/hearth/providers/types.d.ts +72 -0
  248. package/dist/core/hearth/providers/types.js +1 -0
  249. package/dist/core/hearth/registry.d.ts +15 -0
  250. package/dist/core/hearth/registry.js +179 -0
  251. package/dist/core/hearth/types.d.ts +121 -0
  252. package/dist/core/hearth/types.js +1 -0
  253. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +1 -0
  254. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
  255. package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
  256. package/dist/core/host-exports/builtins.js +68 -0
  257. package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
  258. package/dist/core/{skill-sync → host-exports}/export.js +37 -14
  259. package/dist/core/io.js +1 -1
  260. package/dist/core/log.d.ts +9 -0
  261. package/dist/core/log.js +113 -0
  262. package/dist/core/memory-resolver.d.ts +8 -8
  263. package/dist/core/memory-resolver.js +15 -28
  264. package/dist/core/personas/index.d.ts +4 -4
  265. package/dist/core/personas/index.js +2 -2
  266. package/dist/core/personas/loader.d.ts +51 -0
  267. package/dist/core/personas/loader.js +54 -50
  268. package/dist/core/personas/resolve.d.ts +43 -27
  269. package/dist/core/personas/resolve.js +336 -94
  270. package/dist/core/runtime/auth-reload.d.ts +7 -0
  271. package/dist/core/runtime/auth-reload.js +69 -0
  272. package/dist/core/runtime/bearings.d.ts +35 -15
  273. package/dist/core/runtime/bearings.js +305 -44
  274. package/dist/core/runtime/branded-host.d.ts +4 -2
  275. package/dist/core/runtime/branded-host.js +66 -4
  276. package/dist/core/runtime/broker-protocol.d.ts +38 -3
  277. package/dist/core/runtime/broker.js +343 -18
  278. package/dist/core/runtime/close.js +37 -29
  279. package/dist/core/runtime/connectivity.d.ts +12 -0
  280. package/dist/core/runtime/connectivity.js +73 -0
  281. package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
  282. package/dist/core/runtime/fault-recovery-nudge.js +2 -0
  283. package/dist/core/runtime/fault-recovery.d.ts +23 -0
  284. package/dist/core/runtime/fault-recovery.js +74 -0
  285. package/dist/core/runtime/fault.d.ts +25 -0
  286. package/dist/core/runtime/fault.js +176 -0
  287. package/dist/core/runtime/front-door.d.ts +1 -1
  288. package/dist/core/runtime/front-door.js +2 -2
  289. package/dist/core/runtime/host.d.ts +10 -0
  290. package/dist/core/runtime/host.js +71 -9
  291. package/dist/core/runtime/kickoff.js +31 -45
  292. package/dist/core/runtime/launch.d.ts +32 -18
  293. package/dist/core/runtime/launch.js +148 -43
  294. package/dist/core/runtime/model-swap.d.ts +18 -0
  295. package/dist/core/runtime/model-swap.js +95 -0
  296. package/dist/core/runtime/naming.d.ts +7 -0
  297. package/dist/core/runtime/naming.js +61 -9
  298. package/dist/core/runtime/nodes.js +6 -1
  299. package/dist/core/runtime/persona.js +16 -17
  300. package/dist/core/runtime/placement.d.ts +36 -31
  301. package/dist/core/runtime/placement.js +149 -64
  302. package/dist/core/runtime/promote.d.ts +2 -0
  303. package/dist/core/runtime/promote.js +23 -3
  304. package/dist/core/runtime/recycle.js +6 -4
  305. package/dist/core/runtime/reset.d.ts +5 -2
  306. package/dist/core/runtime/reset.js +2 -2
  307. package/dist/core/runtime/revive.d.ts +4 -0
  308. package/dist/core/runtime/revive.js +27 -9
  309. package/dist/core/runtime/roadmap.d.ts +1 -1
  310. package/dist/core/runtime/roadmap.js +1 -1
  311. package/dist/core/runtime/spawn.d.ts +2 -2
  312. package/dist/core/runtime/spawn.js +30 -6
  313. package/dist/core/runtime/stop-guard.js +6 -1
  314. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  315. package/dist/core/runtime/tmux-chrome.js +1 -1
  316. package/dist/core/runtime/tmux.d.ts +29 -6
  317. package/dist/core/runtime/tmux.js +134 -80
  318. package/dist/core/runtime/view-socket-client.d.ts +46 -0
  319. package/dist/core/runtime/view-socket-client.js +203 -0
  320. package/dist/core/substrate/index.d.ts +1 -1
  321. package/dist/core/substrate/index.js +1 -1
  322. package/dist/core/substrate/on-read.js +16 -4
  323. package/dist/core/substrate/render.d.ts +10 -13
  324. package/dist/core/substrate/render.js +56 -51
  325. package/dist/core/tui/host.js +83 -18
  326. package/dist/core/view/__tests__/transport-cache.test.d.ts +1 -0
  327. package/dist/core/view/__tests__/transport-cache.test.js +62 -0
  328. package/dist/core/view/contract.d.ts +20 -0
  329. package/dist/core/view/stream-local.d.ts +3 -0
  330. package/dist/core/view/stream-local.js +184 -0
  331. package/dist/core/view/transport-cache.d.ts +8 -0
  332. package/dist/core/view/transport-cache.js +38 -0
  333. package/dist/core/view/transport-local.js +4 -0
  334. package/dist/core/view/transport.d.ts +7 -1
  335. package/dist/daemon/crtrd.d.ts +3 -19
  336. package/dist/daemon/crtrd.js +139 -178
  337. package/dist/daemon/manage.d.ts +18 -1
  338. package/dist/daemon/manage.js +54 -9
  339. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
  340. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
  341. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
  342. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
  343. package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
  344. package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
  345. package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
  346. package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
  347. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
  348. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
  349. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
  350. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
  351. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
  352. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
  353. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +1 -0
  354. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +455 -0
  355. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
  356. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
  357. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
  358. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
  359. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
  360. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
  361. package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
  362. package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
  363. package/dist/hearth/wake-proxy/auth.d.ts +22 -0
  364. package/dist/hearth/wake-proxy/auth.js +128 -0
  365. package/dist/hearth/wake-proxy/config.d.ts +2 -0
  366. package/dist/hearth/wake-proxy/config.js +151 -0
  367. package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
  368. package/dist/hearth/wake-proxy/guest-source.js +130 -0
  369. package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
  370. package/dist/hearth/wake-proxy/hearth-status.js +267 -0
  371. package/dist/hearth/wake-proxy/home.d.ts +67 -0
  372. package/dist/hearth/wake-proxy/home.js +297 -0
  373. package/dist/hearth/wake-proxy/main.d.ts +1 -0
  374. package/dist/hearth/wake-proxy/main.js +134 -0
  375. package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
  376. package/dist/hearth/wake-proxy/model-auth.js +345 -0
  377. package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
  378. package/dist/hearth/wake-proxy/proxy.js +716 -0
  379. package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
  380. package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
  381. package/dist/hearth/wake-proxy/redact.d.ts +1 -0
  382. package/dist/hearth/wake-proxy/redact.js +17 -0
  383. package/dist/hearth/wake-proxy/server.d.ts +11 -0
  384. package/dist/hearth/wake-proxy/server.js +142 -0
  385. package/dist/hearth/wake-proxy/state.d.ts +18 -0
  386. package/dist/hearth/wake-proxy/state.js +342 -0
  387. package/dist/hearth/wake-proxy/types.d.ts +76 -0
  388. package/dist/hearth/wake-proxy/types.js +1 -0
  389. package/dist/index.js +3 -2
  390. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
  391. package/dist/pi-extensions/canvas-context-intro.js +7 -7
  392. package/dist/pi-extensions/canvas-doc-substrate.js +19 -22
  393. package/dist/pi-extensions/canvas-nav.js +15 -8
  394. package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
  395. package/dist/pi-extensions/canvas-stophook.js +78 -11
  396. package/dist/types.d.ts +39 -20
  397. package/dist/types.js +38 -13
  398. package/dist/web/runtime.js +141 -42
  399. package/dist/web/transport-http.js +7 -5
  400. package/dist/web/transport-stream.d.ts +3 -0
  401. package/dist/web/transport-stream.js +204 -0
  402. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  403. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  404. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  405. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  406. package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
  407. package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
  408. package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
  409. package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
  410. package/dist/web-client/assets/index-D36PNBj4.js +80 -0
  411. package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
  412. package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
  413. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  414. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  415. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  416. package/dist/web-client/index.html +3 -2
  417. package/package.json +32 -10
  418. package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
  419. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
  420. package/dist/core/skill-sync/builtins.js +0 -112
  421. package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
  422. package/dist/core/skill-sync/claude-plugins.js +0 -71
  423. package/dist/core/skill-sync/engine.d.ts +0 -42
  424. package/dist/core/skill-sync/engine.js +0 -633
  425. package/dist/core/skill-sync/manifest.d.ts +0 -64
  426. package/dist/core/skill-sync/manifest.js +0 -181
  427. package/dist/core/skill-sync/profile.d.ts +0 -76
  428. package/dist/core/skill-sync/profile.js +0 -173
  429. package/dist/core/skill-sync/snapshot.d.ts +0 -57
  430. package/dist/core/skill-sync/snapshot.js +0 -120
  431. package/dist/pi-extensions/canvas-commands.d.ts +0 -37
  432. package/dist/pi-extensions/canvas-commands.js +0 -113
  433. package/dist/pi-extensions/canvas-resume.d.ts +0 -21
  434. package/dist/pi-extensions/canvas-resume.js +0 -83
  435. package/dist/pi-extensions/canvas-view.d.ts +0 -21
  436. package/dist/pi-extensions/canvas-view.js +0 -76
  437. package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
  438. package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
  439. /package/dist/{core/__tests__/error-stall-recycle.test.d.ts → clients/attach/__tests__/autocomplete-and-bash-mode.test.d.ts} +0 -0
  440. /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → clients/attach/__tests__/bash-bang-routing.test.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ import type { AgentMessage } from '@earendil-works/pi-agent-core';
1
2
  /** Test seam (M-1 regression): a FRESH-start kickoff prompt carrying this token
2
3
  * makes the fake engine THROW inside bindExtensions BEFORE it fires
3
4
  * session_start — the exact failure mode that records no pid and no session, so
@@ -64,10 +65,23 @@ interface ResourceLoader {
64
65
  runtime: ExtRuntime;
65
66
  };
66
67
  }
68
+ interface TreeEntry {
69
+ type: 'message';
70
+ id: string;
71
+ parentId: string | null;
72
+ timestamp: string;
73
+ message: AgentMessage;
74
+ }
75
+ interface SessionTreeNode {
76
+ entry: TreeEntry;
77
+ children: SessionTreeNode[];
78
+ }
67
79
  declare class FakeSession {
68
80
  private readonly sm;
69
81
  private readonly loader;
70
82
  private readonly dir;
83
+ private readonly tree;
84
+ private promptFailuresRemaining;
71
85
  private extensions;
72
86
  private readonly injected;
73
87
  private uiContext;
@@ -78,14 +92,25 @@ declare class FakeSession {
78
92
  private timer;
79
93
  private readonly listeners;
80
94
  private readonly messageLog;
95
+ private turnEndLeafId;
81
96
  constructor(sm: SessionManager, loader: ResourceLoader | undefined);
82
- get messages(): unknown[];
97
+ get messages(): AgentMessage[];
83
98
  get sessionManager(): {
84
99
  buildSessionContext: () => {
85
- messages: unknown[];
100
+ messages: AgentMessage[];
86
101
  thinkingLevel: string;
87
102
  model: null;
88
103
  };
104
+ getLeafId: () => string | null;
105
+ getEntry: (id: string) => TreeEntry | undefined;
106
+ getTree: () => SessionTreeNode[];
107
+ getUserMessagesForForking: () => Array<{
108
+ entryId: string;
109
+ text: string;
110
+ timestamp?: string;
111
+ }>;
112
+ getSessionId: () => string;
113
+ getSessionFile: () => string;
89
114
  };
90
115
  get sessionId(): string;
91
116
  get sessionFile(): string | undefined;
@@ -98,6 +123,7 @@ declare class FakeSession {
98
123
  }): Promise<void>;
99
124
  get isStreaming(): boolean;
100
125
  getSessionStats(): Record<string, unknown>;
126
+ private syncTreeSnapshot;
101
127
  setSessionName(_name: string): void;
102
128
  prompt(text: string, _options?: unknown): Promise<void>;
103
129
  steer(_text: string): Promise<void>;
@@ -106,8 +132,8 @@ declare class FakeSession {
106
132
  /** In-place tree rewind (broker `navigate_tree`). Mirrors the real SDK's
107
133
  * contract: the session file is unchanged, the live history is truncated,
108
134
  * and the navigated-to user message's text comes back as `editorText`. The
109
- * fake's "rewind" drops the last accrued turn so a re-welcome snapshot is
110
- * observably shorter (the navigate-tree-rewelcome regression lock). */
135
+ * fake's "rewind" re-anchors the active branch without deleting the abandoned
136
+ * tail so the welcome snapshot can prove the one-nudge property. */
111
137
  navigateTree(targetId: string, _options?: unknown): Promise<{
112
138
  editorText?: string;
113
139
  cancelled: boolean;
@@ -140,10 +140,86 @@ function assistantMessage(text, pad = 0) {
140
140
  function textDelta(partial, delta) {
141
141
  return { type: 'text_delta', contentIndex: 0, delta, partial };
142
142
  }
143
+ class SessionTree {
144
+ entries = new Map();
145
+ children = new Map();
146
+ leafId = null;
147
+ append(message) {
148
+ const entry = {
149
+ type: 'message',
150
+ id: randomUUID().slice(0, 8),
151
+ parentId: this.leafId,
152
+ timestamp: new Date().toISOString(),
153
+ message,
154
+ };
155
+ this.entries.set(entry.id, entry);
156
+ const siblings = this.children.get(entry.parentId) ?? [];
157
+ siblings.push(entry.id);
158
+ this.children.set(entry.parentId, siblings);
159
+ this.leafId = entry.id;
160
+ return entry;
161
+ }
162
+ navigateTree(targetId) {
163
+ const entry = this.entries.get(targetId);
164
+ if (entry === undefined)
165
+ throw new Error(`[fake-engine] navigateTree: unknown entry ${targetId}`);
166
+ this.leafId = targetId;
167
+ return { cancelled: false, editorText: this.textOf(entry.message) };
168
+ }
169
+ getLeafId() {
170
+ return this.leafId;
171
+ }
172
+ getEntry(id) {
173
+ return this.entries.get(id);
174
+ }
175
+ getTree() {
176
+ return (this.children.get(null) ?? []).map((id) => this.node(id)).filter((node) => node !== null);
177
+ }
178
+ getUserMessagesForForking() {
179
+ return [...this.entries.values()]
180
+ .filter((entry) => entry.message.role === 'user')
181
+ .map((entry) => ({ entryId: entry.id, text: this.textOf(entry.message), timestamp: entry.timestamp }));
182
+ }
183
+ buildSessionContext() {
184
+ return { messages: this.branchMessages(), thinkingLevel: 'off', model: null };
185
+ }
186
+ branchMessages() {
187
+ const ids = [];
188
+ let cur = this.leafId;
189
+ while (cur !== null) {
190
+ const entry = this.entries.get(cur);
191
+ if (entry === undefined)
192
+ break;
193
+ ids.push(cur);
194
+ cur = entry.parentId;
195
+ }
196
+ ids.reverse();
197
+ return ids.map((id) => this.entries.get(id)?.message).filter((m) => m !== undefined);
198
+ }
199
+ node(id) {
200
+ const entry = this.entries.get(id);
201
+ if (entry === undefined)
202
+ return null;
203
+ return {
204
+ entry,
205
+ children: (this.children.get(id) ?? []).map((child) => this.node(child)).filter((node) => node !== null),
206
+ };
207
+ }
208
+ textOf(message) {
209
+ const content = Array.isArray(message.content) ? message.content : [];
210
+ for (const part of content) {
211
+ if (typeof part?.text === 'string' && part.text !== '')
212
+ return part.text;
213
+ }
214
+ return '';
215
+ }
216
+ }
143
217
  class FakeSession {
144
218
  sm;
145
219
  loader;
146
220
  dir;
221
+ tree = new SessionTree();
222
+ promptFailuresRemaining;
147
223
  extensions = [];
148
224
  injected = [];
149
225
  uiContext;
@@ -156,25 +232,35 @@ class FakeSession {
156
232
  // accumulating message history the catch-up snapshot serializes (G3).
157
233
  listeners = new Set();
158
234
  messageLog = [];
235
+ turnEndLeafId = null;
159
236
  constructor(sm, loader) {
160
237
  this.sm = sm;
161
238
  this.loader = loader;
162
239
  this.dir = nodeDirFromEnv();
240
+ const configured = Number(process.env['CRTR_FAKE_ENGINE_PROMPT_FAILS'] ?? '0');
241
+ this.promptFailuresRemaining = Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 0;
242
+ this.syncTreeSnapshot();
163
243
  }
164
244
  // --- broker-read getters. buildSnapshot (on a client hello) reads messages/
165
245
  // stats/model: T8 clients attach and assert on welcome.snapshot, so messages
166
246
  // returns the accrued history; stats/model stay cheap stubs. ---------------
167
247
  get messages() {
168
- return this.messageLog;
248
+ return this.tree.buildSessionContext().messages;
169
249
  }
170
250
  // The broker's catch-up snapshot reconstructs history from the persisted
171
251
  // session tree (sessionManager.buildSessionContext) — the single source of
172
252
  // truth, since `session.messages` can be sliced by pi's retry/overflow/
173
253
  // compaction recovery. Mirror that surface so the fake's snapshot still serves
174
- // the accrued history (G2/G3). The fake's persisted analog IS messageLog.
254
+ // the accrued history (G2/G3). The fake's persisted analog is the active branch.
175
255
  get sessionManager() {
176
256
  return {
177
- buildSessionContext: () => ({ messages: this.messageLog, thinkingLevel: 'off', model: null }),
257
+ buildSessionContext: () => this.tree.buildSessionContext(),
258
+ getLeafId: () => this.tree.getLeafId(),
259
+ getEntry: (id) => this.tree.getEntry(id),
260
+ getTree: () => this.tree.getTree(),
261
+ getUserMessagesForForking: () => this.tree.getUserMessagesForForking(),
262
+ getSessionId: () => this.sm.getSessionId(),
263
+ getSessionFile: () => this.sm.getSessionFile(),
178
264
  };
179
265
  }
180
266
  get sessionId() {
@@ -200,19 +286,33 @@ class FakeSession {
200
286
  getSessionStats() {
201
287
  return { messages: 0, tokens: 0 };
202
288
  }
289
+ syncTreeSnapshot() {
290
+ try {
291
+ writeFileSync(join(this.dir, 'fake-pi.tree.json'), JSON.stringify({
292
+ leafId: this.tree.getLeafId(),
293
+ activeBranch: this.tree.buildSessionContext().messages,
294
+ tree: this.tree.getTree(),
295
+ userMessagesForForking: this.tree.getUserMessagesForForking(),
296
+ }, null, 2));
297
+ }
298
+ catch {
299
+ /* best effort */
300
+ }
301
+ }
203
302
  // --- naming is cosmetic (broker calls it in a try/catch) ---------------------
204
303
  setSessionName(_name) {
205
304
  /* no-op recording stub */
206
305
  }
207
306
  // --- controller-drive stubs (no controller connects in these tests) ----------
208
307
  prompt(text, _options) {
209
- // The broker calls this for BOTH the fresh-start kickoff AND a controller's
210
- // `prompt` frame (G1/G4). Either way emit a realistic streaming turn on the
211
- // subscribe channel; the broker fans it out to every attached viewer VERBATIM.
212
- // This is the SUBSCRIBE channel only — it does NOT run the canvas stophook
213
- // (that fires via the separate extension-handler channel in dispatch()/fire()),
214
- // so it never tears the broker down. The broker fire-and-forgets the promise.
215
- return this.emitTurn(text);
308
+ // The broker calls this for the controller-less retry path. Emit the turn
309
+ // on the broker's subscribe channel and also run the real extension hooks so
310
+ // the canvas stophook records the fault marker and the broker can schedule
311
+ // the next retry.
312
+ const fail = this.promptFailuresRemaining > 0;
313
+ if (fail)
314
+ this.promptFailuresRemaining--;
315
+ return this.emitTurn(text, { fail, runHooks: true });
216
316
  }
217
317
  steer(_text) {
218
318
  return Promise.resolve();
@@ -226,11 +326,12 @@ class FakeSession {
226
326
  /** In-place tree rewind (broker `navigate_tree`). Mirrors the real SDK's
227
327
  * contract: the session file is unchanged, the live history is truncated,
228
328
  * and the navigated-to user message's text comes back as `editorText`. The
229
- * fake's "rewind" drops the last accrued turn so a re-welcome snapshot is
230
- * observably shorter (the navigate-tree-rewelcome regression lock). */
329
+ * fake's "rewind" re-anchors the active branch without deleting the abandoned
330
+ * tail so the welcome snapshot can prove the one-nudge property. */
231
331
  navigateTree(targetId, _options) {
232
- this.messageLog.pop();
233
- return Promise.resolve({ cancelled: false, editorText: `rewound-to:${targetId}` });
332
+ const result = this.tree.navigateTree(targetId);
333
+ this.syncTreeSnapshot();
334
+ return Promise.resolve(result);
234
335
  }
235
336
  // --- the single engine event stream the broker fans out (broker.ts subscribes
236
337
  // here and broadcasts each event VERBATIM to every attached viewer) --------
@@ -266,6 +367,7 @@ class FakeSession {
266
367
  const updates = opts.updates ?? 3;
267
368
  const pad = opts.padBytes ?? 0;
268
369
  const withTool = opts.tool ?? true;
370
+ const fail = opts.fail === true;
269
371
  const small = assistantMessage(text);
270
372
  // A normal turn yields with setImmediate (fast). A sized flood (G8, pad>0) is
271
373
  // PACED with a small real delay so a fast (reading) viewer always drains each
@@ -274,6 +376,9 @@ class FakeSession {
274
376
  // pace the burst outruns the reader's event loop and sheds the fast viewer too.
275
377
  const yieldTick = () => pad > 0 ? new Promise((r) => setTimeout(r, 3)) : new Promise((r) => setImmediate(r));
276
378
  this.streaming = true;
379
+ if (opts.runHooks === true) {
380
+ await this.fire('agent_start', {}, this.buildCtx());
381
+ }
277
382
  this.emit({ type: 'agent_start' });
278
383
  this.emit({ type: 'turn_start' });
279
384
  this.emit({ type: 'message_start', message: small });
@@ -337,14 +442,39 @@ class FakeSession {
337
442
  }
338
443
  if (this.disposed)
339
444
  return;
340
- const finalMsg = assistantMessage(text);
445
+ const userMsg = {
446
+ role: 'user',
447
+ content: [{ type: 'text', text }],
448
+ timestamp: Date.now(),
449
+ };
450
+ this.tree.append(userMsg);
451
+ this.syncTreeSnapshot();
452
+ const finalMsg = fail
453
+ ? { ...assistantMessage(text), stopReason: 'error', errorMessage: 'Connection error.' }
454
+ : assistantMessage(text);
341
455
  this.emit({ type: 'message_end', message: finalMsg });
342
456
  this.emit({ type: 'turn_end', message: finalMsg, toolResults: [] });
343
457
  this.messageLog.push(finalMsg);
458
+ const anchorBeforeFinal = this.tree.getLeafId();
459
+ this.turnEndLeafId = anchorBeforeFinal;
460
+ this.tree.append(finalMsg);
461
+ this.syncTreeSnapshot();
462
+ if (opts.runHooks === true) {
463
+ const hookCtx = this.buildCtx();
464
+ await this.fire('turn_end', { message: finalMsg, toolResults: [] }, hookCtx);
465
+ const hookMessages = fail
466
+ ? [...this.tree.buildSessionContext().messages, finalMsg]
467
+ : this.tree.buildSessionContext().messages.slice();
468
+ await this.fire('agent_end', { messages: hookMessages, willRetry: false }, hookCtx);
469
+ }
470
+ this.turnEndLeafId = null;
344
471
  this.streaming = false;
345
472
  // agent_end on the SUBSCRIBE channel is a relayed frame ONLY (willRetry=false);
346
473
  // it never runs the stophook, so the broker stays alive (G2/G3/G4/G8).
347
- this.emit({ type: 'agent_end', messages: this.messageLog.slice(), willRetry: false });
474
+ const agentEndMessages = fail
475
+ ? [...this.tree.buildSessionContext().messages, finalMsg]
476
+ : this.tree.buildSessionContext().messages.slice();
477
+ this.emit({ type: 'agent_end', messages: agentEndMessages, willRetry: false });
348
478
  }
349
479
  dispose() {
350
480
  this.disposed = true;
@@ -470,6 +600,7 @@ class FakeSession {
470
600
  sessionManager: {
471
601
  getSessionId: () => this.sm.getSessionId(),
472
602
  getSessionFile: () => this.sm.getSessionFile(),
603
+ getLeafId: () => this.turnEndLeafId ?? this.tree.getLeafId(),
473
604
  },
474
605
  getContextUsage: () => ({ tokens: 1000 }),
475
606
  // Wired to the broker's shutdownHandler: the stophook calls ctx.shutdown()
@@ -557,7 +688,15 @@ class FakeSession {
557
688
  this.streaming = false;
558
689
  await this.fire('agent_end', {
559
690
  messages: [
560
- { role: 'assistant', stopReason: cmd.reason ?? 'stop', content: [{ type: 'text', text: cmd.text ?? '' }] },
691
+ {
692
+ role: 'assistant',
693
+ stopReason: cmd.reason ?? 'stop',
694
+ // A real engine attaches errorMessage on a stopReason:'error'
695
+ // turn; the stophook classifies it for the active-fault marker.
696
+ // Forward it so a test can drive a CONNECTION stall faithfully.
697
+ ...(cmd.errorMessage === undefined ? {} : { errorMessage: cmd.errorMessage }),
698
+ content: [{ type: 'text', text: cmd.text ?? '' }],
699
+ },
561
700
  ],
562
701
  }, ctx);
563
702
  break;
@@ -255,7 +255,16 @@ async function dispatch(cmd) {
255
255
  streaming = false;
256
256
  await fire('agent_end', {
257
257
  messages: [
258
- { role: 'assistant', stopReason: cmd.reason ?? 'stop', content: [{ type: 'text', text: cmd.text ?? '' }] },
258
+ {
259
+ role: 'assistant',
260
+ stopReason: cmd.reason ?? 'stop',
261
+ // A real engine attaches errorMessage on a stopReason:'error' turn;
262
+ // the stophook classifies it (connection/rate-limit/…) for the
263
+ // active-fault marker. Forward it so a test can drive a CONNECTION
264
+ // stall faithfully.
265
+ ...(cmd.errorMessage === undefined ? {} : { errorMessage: cmd.errorMessage }),
266
+ content: [{ type: 'text', text: cmd.text ?? '' }],
267
+ },
259
268
  ],
260
269
  }, ctx);
261
270
  ack(id, { ok: true });
@@ -1,6 +1,6 @@
1
1
  // Run with: node --import tsx/esm --test src/core/__tests__/human-new-window-regression.test.ts
2
2
  //
3
- // BUG REGRESSION: `crtr human ask|approve|review|notify` opened NO pane at all.
3
+ // BUG REGRESSION: `crtr human ask|review|notify` opened NO pane at all.
4
4
  //
5
5
  // spawnAndDetach's 'new-window' placement passed a tmux PANE id straight to
6
6
  // `new-window -t` (`new-window -d -a -t %<pane>`). tmux REJECTS a pane id for
@@ -76,7 +76,7 @@ export interface Harness {
76
76
  spawnHeadlessChild(parentId: string, task: string, o?: SpawnOpts): Promise<string>;
77
77
  spawnHeadlessChildNoBoot(parentId: string, task: string, o?: SpawnOpts): Promise<string>;
78
78
  turn(nodeId: string, text?: string): Promise<void>;
79
- stop(nodeId: string, reason?: 'stop' | 'length' | 'aborted' | 'error'): Promise<void>;
79
+ stop(nodeId: string, reason?: 'stop' | 'length' | 'aborted' | 'error', errorMessage?: string): Promise<void>;
80
80
  finish(nodeId: string, finalText: string): Promise<void>;
81
81
  tick(now?: number): Promise<void>;
82
82
  awaitBoot(nodeId: string, o?: {
@@ -389,9 +389,9 @@ export async function createHarness(opts = {}) {
389
389
  sendCmd(nodeId, { cmd: 'turn', id: `turn-${Date.now()}`, text });
390
390
  await awaitAgentEnd(nodeId, base, `turn agent_end for ${nodeId}`);
391
391
  },
392
- async stop(nodeId, reason = 'stop') {
392
+ async stop(nodeId, reason = 'stop', errorMessage) {
393
393
  const base = eventCount(nodeId, 'agent_end');
394
- sendCmd(nodeId, { cmd: 'stop', id: `stop-${Date.now()}`, reason });
394
+ sendCmd(nodeId, { cmd: 'stop', id: `stop-${Date.now()}`, reason, errorMessage });
395
395
  await awaitAgentEnd(nodeId, base, `stop agent_end for ${nodeId}`);
396
396
  },
397
397
  async finish(nodeId, finalText) {
@@ -22,7 +22,7 @@ import { closeDb } from '../canvas/db.js';
22
22
  import { readInboxSince } from '../feed/inbox.js';
23
23
  import { interactionDir, interactionsRoot } from '../artifact.js';
24
24
  import { atomicWriteJson, deckPath } from '@crouton-kit/humanloop';
25
- import { finalizeResolvedInteraction } from '../../commands/human/queue.js';
25
+ import { finalizeResolvedInteraction, acquireInteractionClaim } from '../../commands/human/queue.js';
26
26
  let home;
27
27
  // A unique cwd so interactionDir (keyed off the REAL homedir + mangled cwd, NOT
28
28
  // CRTR_HOME) lands in an isolated, cleanable subtree.
@@ -96,6 +96,43 @@ test('a canceled-on-disk response reaps the bridge without delivering a result',
96
96
  const blob = JSON.stringify(readInboxSince('A'));
97
97
  assert.match(blob, /no answer is coming/);
98
98
  });
99
+ test('the _run held-claim path delivers, and finalize without the claim self-conflicts', async () => {
100
+ // BUG REGRESSION (_run ask self-claim): the detached `_run` ask worker holds
101
+ // the exclusive resolve.lock across the blocking ask(), then must deliver the
102
+ // answer. If it calls finalizeResolvedInteraction WITHOUT passing its held
103
+ // claim, finalize re-acquires the same lock, sees this very process's live
104
+ // lock (EEXIST, owner.pid == self, pidAlive true) -> 'claimed' -> returns
105
+ // false, and the human's answer never reaches the asker. Passing the held
106
+ // claim lets finalize run under the worker's own lock.
107
+ createNode(node('A', null));
108
+ createNode(node('B', 'A'));
109
+ subscribe('A', 'B');
110
+ // Set up the deck + run.json but NOT the answer yet, then hold the claim
111
+ // exactly as the _run worker does (no opts) BEFORE ask() resolves the deck.
112
+ const idir = interactionDir('B', workCwd);
113
+ mkdirSync(idir, { recursive: true });
114
+ atomicWriteJson(deckPath(idir), { interactions: [] });
115
+ atomicWriteJson(join(idir, 'run.json'), { mode: 'ask', job_id: 'B' });
116
+ const claim = acquireInteractionClaim(idir, 'B');
117
+ assert.equal(typeof claim === 'object', true, 'claim must be acquired');
118
+ if (typeof claim !== 'object')
119
+ return;
120
+ // ask() then writes the human's answer to disk while the claim is held.
121
+ atomicWriteJson(join(idir, 'response.json'), {
122
+ responses: [{ id: 'q', selectedOptionId: 'resync', freetext: 'go ahead' }],
123
+ completedAt: '2026-06-10T22:27:07.000Z',
124
+ });
125
+ // Without the held claim, finalize from the SAME pid self-conflicts on the
126
+ // live lock and must NOT deliver (this is the stranding regression).
127
+ assert.equal(await finalizeResolvedInteraction('B'), false, 'self-held lock must block claimless finalize');
128
+ assert.equal(getNode('B')?.status, 'active', 'bridge must still be live after the self-conflicting call');
129
+ // Passing the held claim lets finalize run under the worker's own lock and
130
+ // deliver the answer back to the asker, then reap the bridge.
131
+ assert.equal(await finalizeResolvedInteraction('B', claim), true, 'held claim must deliver');
132
+ assert.equal(getNode('B')?.status, 'done', 'bridge reaped after held-claim deliver');
133
+ assert.match(JSON.stringify(readInboxSince('A')), /resync/, 'asker must receive the human selection');
134
+ claim.release();
135
+ });
99
136
  test('an unresolved (no response.json) live bridge is left untouched', async () => {
100
137
  createNode(node('A', null));
101
138
  createNode(node('B', 'A'));
@@ -1,6 +1,6 @@
1
1
  // Run with: node --import tsx/esm --test src/core/__tests__/human-surface-target.test.ts
2
2
  //
3
- // BUG REGRESSION: `crtr human ask|approve|review|notify` surfaced its humanloop
3
+ // BUG REGRESSION: `crtr human ask|review|notify` surfaced its humanloop
4
4
  // TUI in the backstage `crtr` session (the asking node's own pane) — a session
5
5
  // the user never watches — because spawnAndDetach was called with no `-t`
6
6
  // target. The fix routes the TUI to the HIGHEST FOCUSED node of the asking
@@ -8,7 +8,7 @@
8
8
  import { test, before, after, beforeEach } from 'node:test';
9
9
  import assert from 'node:assert/strict';
10
10
  import { mkdtempSync, rmSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
11
- import { homedir } from 'node:os';
11
+ import { tmpdir } from 'node:os';
12
12
  import { join } from 'node:path';
13
13
  import { createNode, subscribe } from '../canvas/canvas.js';
14
14
  import { reportsDir } from '../canvas/paths.js';
@@ -29,9 +29,7 @@ function node(id) {
29
29
  };
30
30
  }
31
31
  before(() => {
32
- // Under homedir (not tmpdir) so report paths fall under the home prefix and
33
- // exercise the tildify() collapse in reportHistoryLines.
34
- home = mkdtempSync(join(homedir(), '.crtr-kickoff-'));
32
+ home = mkdtempSync(join(tmpdir(), 'crtr-kickoff-'));
35
33
  process.env['CRTR_HOME'] = home;
36
34
  });
37
35
  beforeEach(() => {
@@ -76,10 +74,10 @@ test('the feed block frames awaiting workers as alive + auto-waking, so a fresh
76
74
  test('a fresh revive is pointed at its subscriptions\' on-disk report history (catch-up bug)', () => {
77
75
  // Regression for the fresh-revive catch-up bug: on a refresh-yield
78
76
  // (resume:false) the old conversation is gone AND the inbox cursor has already
79
- // advanced past everything drained pre-yield, so the revived node loses sight
80
- // of reports its subscriptions pushed BEFORE the yield. The bodies persist
81
- // forever at reports/<ts>-<kind>.md; the kickoff must point the node at those
82
- // existing paths so it can catch up. Asserts the paths are rendered.
77
+ // advanced past everything drained pre-yield, so the revived node needs a
78
+ // durable catch-up breadcrumb for reports its subscriptions pushed BEFORE the
79
+ // yield. The bodies persist forever at reports/<ts>-<kind>.md; the kickoff
80
+ // surfaces a collapsed index with the newest ref and expansion commands.
83
81
  const parent = createNode(node('p1'));
84
82
  const worker = createNode(node('w1'));
85
83
  subscribe(parent.node_id, worker.node_id);
@@ -91,17 +89,18 @@ test('a fresh revive is pointed at its subscriptions\' on-disk report history (c
91
89
  writeFileSync(older, '---\n---\nearly progress', 'utf8');
92
90
  writeFileSync(newer, '---\n---\nthe result', 'utf8');
93
91
  const msg = buildReviveKickoff(parent, drainBearings(parent));
94
- // Both existing report PATHS surface (most recent first), so the revived node
95
- // can dereference the history its advanced cursor would otherwise hide. They
96
- // render home-relative with a leading ~ (the absolute home prefix is wasted
97
- // context tokens; ~ is still dereferenceable by the revived node).
98
- const olderRel = '~' + older.slice(homedir().length);
99
- const newerRel = '~' + newer.slice(homedir().length);
100
- assert.ok(msg.includes(olderRel), 'older report path surfaced, home-relative (~)');
101
- assert.ok(msg.includes(newerRel), 'newer report path surfaced, home-relative (~)');
102
- assert.ok(!msg.includes(older), 'absolute home prefix not rendered');
103
- assert.ok(msg.indexOf(newerRel) < msg.indexOf(olderRel), 'most recent report listed first');
104
- // ...and the hint that the full inbox history is replayable cursor-independently.
92
+ // The feed block stays condensed: it reports that history exists and surfaces
93
+ // the newest report ref for immediate dereference, while older refs stay one
94
+ // command away instead of being preloaded into the revive prompt.
95
+ const olderRef = `${worker.node_id}:reports/20260608T120000-update.md`;
96
+ const newerRef = `${worker.node_id}:reports/20260608T130000-final.md`;
97
+ assert.ok(/Report history on disk \(collapsed\): 2 reports across 1 subscribed node/.test(msg));
98
+ assert.ok(msg.includes(`latest final: ${newerRef}`), 'newest report ref surfaced');
99
+ assert.ok(!msg.includes(olderRef), 'older report ref omitted from collapsed kickoff');
100
+ assert.ok(!msg.includes(older), 'absolute report paths not rendered');
101
+ assert.ok(!msg.includes(newer), 'absolute report paths not rendered');
102
+ assert.ok(/canvas history read <ref>/.test(msg), 'points at one-report expansion');
103
+ assert.ok(/canvas history show <node-id> --type report/.test(msg), 'points at full per-node expansion');
105
104
  assert.ok(/feed read --all/.test(msg), 'points at the cursor-independent full-history replay');
106
105
  });
107
106
  test('buildReviveKickoff is pure — building twice eats nothing', () => {
@@ -26,7 +26,7 @@ import assert from 'node:assert/strict';
26
26
  import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
27
27
  import { tmpdir } from 'node:os';
28
28
  import { join } from 'node:path';
29
- import { resolveMemoryDoc } from '../memory-resolver.js';
29
+ import { listAllMemoryDocs, resolveMemoryDoc } from '../memory-resolver.js';
30
30
  import { resetScopeCache } from '../scope.js';
31
31
  // Fixture: a temp PROJECT scope (a `.crouter/` dir discovered by walking up from
32
32
  // cwd) holding both a native memory/ tree and one enabled fixture plugin whose
@@ -55,6 +55,9 @@ before(() => {
55
55
  writeFileSync(join(pluginMem, 'widget.md'), doc('plugin-widget'));
56
56
  writeFileSync(join(pluginMem, 'area', 'zone.md'), doc('plugin-zone'));
57
57
  writeFileSync(join(pluginMem, 'shared.md'), doc('plugin-shared'));
58
+ writeFileSync(join(pluginMem, 'SKILL.md'), doc('legacy-plugin-skill'));
59
+ mkdirSync(join(memDir, 'legacy'), { recursive: true });
60
+ writeFileSync(join(memDir, 'legacy', 'SKILL.md'), doc('legacy-native-skill'));
58
61
  // Plugin-root INDEX.md: a BARE plugin name must resolve this (mirrors the
59
62
  // native bare-dir-name -> INDEX.md contract for the plugin mount root).
60
63
  writeFileSync(join(pluginMem, 'INDEX.md'), doc('plugin-root-index'));
@@ -106,3 +109,11 @@ test('native doc wins over a plugin doc of the same canonical name', () => {
106
109
  assert.ok(d.path.endsWith(join('memory', PLUGIN, 'shared.md')));
107
110
  assert.ok(!d.path.includes(join('plugins', PLUGIN)));
108
111
  });
112
+ test('legacy SKILL.md bundles are not memory docs', () => {
113
+ const names = listAllMemoryDocs('project').map((d) => d.name);
114
+ assert.ok(!names.includes('legacy'));
115
+ assert.ok(!names.includes(`${PLUGIN}/SKILL`));
116
+ assert.throws(() => resolveMemoryDoc('legacy'), /memory document not found/);
117
+ assert.throws(() => resolveMemoryDoc('legacy/SKILL'), /memory document not found/);
118
+ assert.throws(() => resolveMemoryDoc(`${PLUGIN}/SKILL`), /memory document not found/);
119
+ });
@@ -219,20 +219,55 @@ test('v4 is idempotent on re-open', () => {
219
219
  assert.equal(userVersion(db2), MIGRATIONS.length);
220
220
  assert.equal(edgeCount(db2), 1);
221
221
  });
222
- test('migration is idempotent on re-open and on re-run', () => {
223
- // First open migrates v0 -> latest.
222
+ function buildV4DbWithLaterColumns() {
223
+ ensureHome();
224
+ const raw = new DatabaseSync(canvasDbPath());
225
+ raw.exec(`
226
+ CREATE TABLE nodes (
227
+ node_id TEXT PRIMARY KEY,
228
+ name TEXT NOT NULL,
229
+ kind TEXT NOT NULL,
230
+ mode TEXT NOT NULL DEFAULT 'base',
231
+ lifecycle TEXT NOT NULL DEFAULT 'terminal',
232
+ status TEXT NOT NULL DEFAULT 'active',
233
+ cwd TEXT NOT NULL,
234
+ parent TEXT,
235
+ created TEXT NOT NULL,
236
+ intent TEXT,
237
+ pi_pid INTEGER,
238
+ window TEXT,
239
+ tmux_session TEXT,
240
+ pane TEXT,
241
+ host_kind TEXT
242
+ );
243
+ CREATE TABLE edges (
244
+ type TEXT NOT NULL,
245
+ from_id TEXT NOT NULL,
246
+ to_id TEXT NOT NULL,
247
+ active INTEGER NOT NULL DEFAULT 1,
248
+ created TEXT NOT NULL,
249
+ PRIMARY KEY (type, from_id, to_id)
250
+ );
251
+ PRAGMA user_version = 4;
252
+ `);
253
+ raw
254
+ .prepare('INSERT INTO nodes (node_id, name, kind, status, cwd, created, pane, host_kind) VALUES (?,?,?,?,?,?,?,?)')
255
+ .run('stale', 'Stale', 'general', 'idle', '/tmp/work', '2020-01-01T00:00:00Z', '%9', 'broker');
256
+ return raw;
257
+ }
258
+ test('bug #99: a stale v4 db with later columns already present migrates cleanly', () => {
259
+ const raw = buildV4DbWithLaterColumns();
260
+ assert.equal(userVersion(raw), 4);
261
+ assert.equal(nodeColumns(raw).includes('pane'), true, 'precondition: pane already exists even though user_version is stale');
262
+ raw.close();
224
263
  const db = openDb();
225
264
  assert.equal(userVersion(db), MIGRATIONS.length);
226
- // Re-running migrate() directly is a no-op (the gate skips applied steps).
227
- // If the gate were broken, the second ALTER ADD COLUMN would throw.
228
- assert.doesNotThrow(() => migrate(db));
229
- assert.equal(userVersion(db), MIGRATIONS.length);
230
- // Re-opening from disk is likewise a no-op — no duplicate-column error.
231
- closeDb();
232
- const db2 = openDb();
233
- assert.equal(userVersion(db2), MIGRATIONS.length);
234
- const cols = nodeColumns(db2);
235
- for (const c of RUNTIME_COLUMNS) {
236
- assert.ok(cols.includes(c), `expected nodes.${c} to persist`);
237
- }
265
+ const cols = nodeColumns(db);
266
+ assert.ok(cols.includes('pane'), 'pane survives the migration');
267
+ assert.ok(cols.includes('host_kind'), 'host_kind survives the migration');
268
+ const row = db
269
+ .prepare('SELECT pane, host_kind FROM nodes WHERE node_id = ?')
270
+ .get('stale');
271
+ assert.equal(row.pane, '%9');
272
+ assert.equal(row.host_kind, 'broker');
238
273
  });
@@ -0,0 +1 @@
1
+ export {};