@north-light/crouter 0.3.170 → 0.3.172

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 (670) hide show
  1. package/dist/api/client.d.ts +37 -11
  2. package/dist/api/client.js +84 -15
  3. package/dist/api/dto/human.d.ts +53 -39
  4. package/dist/api/dto/human.js +2 -3
  5. package/dist/api/dto/inbox.d.ts +0 -1
  6. package/dist/api/dto/nodes.d.ts +12 -0
  7. package/dist/api/dto/review-comments.d.ts +137 -0
  8. package/dist/api/dto/review-comments.js +5 -0
  9. package/dist/api/dto/reviews.d.ts +109 -0
  10. package/dist/api/dto/reviews.js +5 -0
  11. package/dist/api/index.d.ts +2 -0
  12. package/dist/api/index.js +2 -0
  13. package/dist/api/routes.d.ts +17 -4
  14. package/dist/api/routes.js +21 -5
  15. package/dist/build-root.d.ts +1 -16
  16. package/dist/build-root.js +1 -25
  17. package/dist/builtin-memory/00-runtime-base.md +7 -5
  18. package/dist/builtin-memory/04-base-worker.md +1 -1
  19. package/dist/builtin-memory/04-orchestration-kernel.md +7 -7
  20. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  21. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -1
  23. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -1
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -1
  26. package/dist/builtin-memory/05-kinds/review/00-base.md +1 -1
  27. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +20 -0
  28. package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -7
  29. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -3
  30. package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -3
  31. package/dist/builtin-memory/design.md +2 -2
  32. package/dist/builtin-memory/insights/capture.md +50 -0
  33. package/dist/builtin-memory/insights/init.md +45 -0
  34. package/dist/builtin-memory/insights/listen.md +9 -0
  35. package/dist/builtin-memory/internal/agent-shaping.md +2 -2
  36. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  37. package/dist/builtin-memory/internal/plugins.md +12 -11
  38. package/dist/builtin-memory/internal/storage-tiers.md +2 -2
  39. package/dist/builtin-memory/{planning.md → plan/roadmap.md} +3 -3
  40. package/dist/builtin-memory/plan.md +19 -0
  41. package/dist/builtin-memory/spec/guide.md +38 -0
  42. package/dist/builtin-memory/spec/requirements.md +31 -0
  43. package/dist/builtin-memory/spec/roadmap.md +35 -0
  44. package/dist/builtin-memory/spec.md +10 -86
  45. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +3 -4
  46. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +49 -5
  47. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +56 -0
  48. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +920 -0
  49. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +221 -156
  50. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +4 -5
  51. package/dist/cli.js +2 -6
  52. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +7 -35
  53. package/dist/clients/attach/__tests__/attach-keybindings.test.js +32 -16
  54. package/dist/clients/attach/__tests__/context-message.test.js +142 -6
  55. package/dist/clients/attach/__tests__/crtr-output.test.js +29 -45
  56. package/dist/clients/attach/__tests__/diagram.test.js +1 -1
  57. package/dist/clients/attach/__tests__/file-review-focus.test.js +48 -0
  58. package/dist/clients/attach/__tests__/group-activity.test.js +266 -0
  59. package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +32 -0
  60. package/dist/clients/attach/__tests__/session-identity-refresh.test.js +32 -0
  61. package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -2
  62. package/dist/clients/attach/chrome/header.d.ts +10 -0
  63. package/dist/clients/attach/chrome/header.js +30 -0
  64. package/dist/clients/attach/chrome/recap.d.ts +7 -0
  65. package/dist/clients/attach/chrome/recap.js +19 -0
  66. package/dist/clients/attach/chrome/status-line.d.ts +3 -0
  67. package/dist/clients/attach/chrome/status-line.js +5 -0
  68. package/dist/clients/attach/chrome/widgets.js +11 -21
  69. package/dist/clients/attach/command.js +11 -7
  70. package/dist/clients/attach/config.d.ts +4 -0
  71. package/dist/clients/attach/config.js +3 -0
  72. package/dist/clients/attach/input/controller.d.ts +19 -0
  73. package/dist/clients/attach/input/controller.js +62 -28
  74. package/dist/clients/attach/input/titled-editor.d.ts +0 -4
  75. package/dist/clients/attach/input/titled-editor.js +2 -10
  76. package/dist/clients/attach/overlays/file-review.d.ts +9 -5
  77. package/dist/clients/attach/overlays/file-review.js +9 -70
  78. package/dist/clients/attach/overlays/help.d.ts +33 -0
  79. package/dist/clients/attach/overlays/help.js +204 -0
  80. package/dist/clients/attach/render/activity-slot.d.ts +4 -0
  81. package/dist/clients/attach/render/activity-slot.js +13 -0
  82. package/dist/clients/attach/render/chat-view.d.ts +117 -12
  83. package/dist/clients/attach/render/chat-view.js +604 -135
  84. package/dist/clients/attach/render/context-message.d.ts +2 -1
  85. package/dist/clients/attach/render/context-message.js +17 -13
  86. package/dist/clients/attach/render/crtr-output.js +1 -3
  87. package/dist/clients/attach/render/diagram.js +2 -2
  88. package/dist/clients/attach/render/edit-diff.d.ts +4 -1
  89. package/dist/clients/attach/render/edit-diff.js +7 -11
  90. package/dist/clients/attach/render/group-activity.d.ts +32 -0
  91. package/dist/clients/attach/render/group-activity.js +206 -0
  92. package/dist/clients/attach/render/group-recap.d.ts +10 -0
  93. package/dist/clients/attach/render/group-recap.js +93 -0
  94. package/dist/clients/attach/render/html-markdown.d.ts +17 -0
  95. package/dist/clients/attach/render/html-markdown.js +228 -0
  96. package/dist/clients/attach/render/measured-container.d.ts +96 -0
  97. package/dist/clients/attach/render/measured-container.js +322 -0
  98. package/dist/clients/attach/render/scroll-animation.d.ts +8 -0
  99. package/dist/clients/attach/render/scroll-animation.js +80 -0
  100. package/dist/clients/attach/render/tool-calls.js +6 -6
  101. package/dist/clients/attach/render/transcript-copy.d.ts +9 -0
  102. package/dist/clients/attach/render/transcript-copy.js +81 -0
  103. package/dist/clients/attach/render/viewport.d.ts +29 -0
  104. package/dist/clients/attach/render/viewport.js +159 -0
  105. package/dist/clients/attach/session/bindings.d.ts +10 -7
  106. package/dist/clients/attach/session/bindings.js +23 -8
  107. package/dist/clients/attach/session/connection.js +4 -5
  108. package/dist/clients/attach/session/context.d.ts +6 -4
  109. package/dist/clients/attach/session/frame.d.ts +34 -0
  110. package/dist/clients/attach/session/frame.js +176 -0
  111. package/dist/clients/attach/session/frames.d.ts +4 -1
  112. package/dist/clients/attach/session/frames.js +14 -1
  113. package/dist/clients/attach/session/identity.js +4 -3
  114. package/dist/clients/attach/session/input-wiring.d.ts +14 -4
  115. package/dist/clients/attach/session/input-wiring.js +15 -13
  116. package/dist/clients/attach/session/invariant.d.ts +9 -0
  117. package/dist/clients/attach/session/invariant.js +15 -0
  118. package/dist/clients/attach/session/keys.d.ts +10 -2
  119. package/dist/clients/attach/session/keys.js +13 -5
  120. package/dist/clients/attach/session/layout.d.ts +10 -8
  121. package/dist/clients/attach/session/layout.js +33 -34
  122. package/dist/clients/attach/session/mouse.d.ts +18 -0
  123. package/dist/clients/attach/session/mouse.js +64 -0
  124. package/dist/clients/attach/session/pane-tag.d.ts +3 -0
  125. package/dist/clients/attach/session/pane-tag.js +51 -8
  126. package/dist/clients/attach/session/reconnect.d.ts +3 -0
  127. package/dist/clients/attach/session/reconnect.js +42 -5
  128. package/dist/clients/attach/session/surface-frame.d.ts +20 -0
  129. package/dist/clients/attach/session/surface-frame.js +73 -0
  130. package/dist/clients/attach/session/surface-host.d.ts +64 -0
  131. package/dist/clients/attach/session/surface-host.js +255 -0
  132. package/dist/clients/attach/session/whip.d.ts +17 -0
  133. package/dist/clients/attach/session/whip.js +334 -0
  134. package/dist/clients/attach/slash/dispatch.d.ts +13 -1
  135. package/dist/clients/attach/slash/dispatch.js +51 -29
  136. package/dist/clients/attach/viewer.js +907 -819
  137. package/dist/clients/conversation/conversation.d.ts +65 -0
  138. package/dist/clients/conversation/conversation.js +318 -0
  139. package/dist/clients/conversation/frames.d.ts +30 -0
  140. package/dist/clients/conversation/frames.js +52 -0
  141. package/dist/clients/conversation/region.d.ts +51 -0
  142. package/dist/clients/conversation/region.js +265 -0
  143. package/dist/clients/conversation/submit.d.ts +38 -0
  144. package/dist/clients/conversation/submit.js +51 -0
  145. package/dist/clients/inbox/__tests__/inbox-controller.test.js +247 -0
  146. package/dist/clients/inbox/__tests__/mount-panel.test.js +776 -0
  147. package/dist/clients/inbox/controller.d.ts +79 -0
  148. package/dist/clients/inbox/controller.js +448 -0
  149. package/dist/clients/inbox/deck-adapter.d.ts +29 -0
  150. package/dist/clients/inbox/deck-adapter.js +62 -0
  151. package/dist/clients/inbox/layout.d.ts +8 -0
  152. package/dist/clients/inbox/layout.js +9 -0
  153. package/dist/clients/inbox/resolve.d.ts +10 -0
  154. package/dist/clients/inbox/resolve.js +34 -0
  155. package/dist/clients/inbox/review/__tests__/editor-roundtrip.test.js +29 -0
  156. package/dist/clients/inbox/review/__tests__/remap.test.js +196 -0
  157. package/dist/clients/inbox/review/__tests__/review-core.test.js +341 -0
  158. package/dist/clients/inbox/review/anchors.d.ts +41 -0
  159. package/dist/clients/inbox/review/anchors.js +89 -0
  160. package/dist/clients/inbox/review/columns.d.ts +22 -0
  161. package/dist/clients/inbox/review/columns.js +40 -0
  162. package/dist/clients/inbox/review/comments-client.d.ts +66 -0
  163. package/dist/clients/inbox/review/comments-client.js +98 -0
  164. package/dist/clients/inbox/review/composition.d.ts +60 -0
  165. package/dist/clients/inbox/review/composition.js +278 -0
  166. package/dist/clients/inbox/review/document-region.d.ts +120 -0
  167. package/dist/clients/inbox/review/document-region.js +902 -0
  168. package/dist/clients/inbox/review/frame.d.ts +45 -0
  169. package/dist/clients/inbox/review/frame.js +215 -0
  170. package/dist/clients/inbox/review/keys.d.ts +67 -0
  171. package/dist/clients/inbox/review/keys.js +130 -0
  172. package/dist/clients/inbox/review/launch.d.ts +10 -0
  173. package/dist/clients/inbox/review/launch.js +129 -0
  174. package/dist/clients/inbox/review/live-source.d.ts +24 -0
  175. package/dist/clients/inbox/review/live-source.js +160 -0
  176. package/dist/clients/inbox/review/nvim.d.ts +13 -0
  177. package/dist/clients/inbox/review/nvim.js +42 -0
  178. package/dist/clients/inbox/review/remap.d.ts +51 -0
  179. package/dist/clients/inbox/review/remap.js +157 -0
  180. package/dist/clients/inbox/review/review-client.d.ts +30 -0
  181. package/dist/clients/inbox/review/review-client.js +107 -0
  182. package/dist/clients/inbox/review/roundtrip.d.ts +5 -0
  183. package/dist/clients/inbox/review/roundtrip.js +36 -0
  184. package/dist/clients/inbox/review/state.d.ts +143 -0
  185. package/dist/clients/inbox/review/state.js +282 -0
  186. package/dist/clients/inbox/review-adapter.d.ts +29 -0
  187. package/dist/clients/inbox/review-adapter.js +92 -0
  188. package/dist/clients/inbox/review-visibility.d.ts +5 -0
  189. package/dist/clients/inbox/review-visibility.js +19 -0
  190. package/dist/clients/inbox/surface.d.ts +19 -0
  191. package/dist/clients/inbox/surface.js +128 -0
  192. package/dist/clients/inbox/tui/ansi.d.ts +66 -0
  193. package/dist/clients/inbox/tui/ansi.js +317 -0
  194. package/dist/clients/inbox/tui/clipboard.d.ts +2 -0
  195. package/dist/clients/inbox/tui/clipboard.js +26 -0
  196. package/dist/clients/inbox/tui/input.d.ts +7 -0
  197. package/dist/clients/inbox/tui/input.js +653 -0
  198. package/dist/clients/inbox/tui/keys.d.ts +28 -0
  199. package/dist/clients/inbox/tui/keys.js +111 -0
  200. package/dist/clients/inbox/tui/panel.d.ts +2 -0
  201. package/dist/clients/inbox/tui/panel.js +217 -0
  202. package/dist/clients/inbox/tui/render.d.ts +37 -0
  203. package/dist/clients/inbox/tui/render.js +476 -0
  204. package/dist/clients/inbox/tui/types.d.ts +57 -0
  205. package/dist/clients/inbox/tui/types.js +1 -0
  206. package/dist/clients/inbox/tui.d.ts +6 -0
  207. package/dist/clients/inbox/tui.js +74 -0
  208. package/dist/clients/surfaces/host.d.ts +66 -0
  209. package/dist/clients/surfaces/host.js +1 -0
  210. package/dist/clients/surfaces/region.d.ts +16 -0
  211. package/dist/clients/surfaces/region.js +1 -0
  212. package/dist/clients/web/__tests__/push-engine.test.js +14 -158
  213. package/dist/clients/web/dev-server.d.ts +3 -3
  214. package/dist/clients/web/dev-server.js +9 -23
  215. package/dist/clients/web/events.js +4 -6
  216. package/dist/clients/web/push-engine.d.ts +2 -66
  217. package/dist/clients/web/push-engine.js +30 -161
  218. package/dist/clients/web/push-registry.d.ts +0 -4
  219. package/dist/clients/web/push-registry.js +2 -7
  220. package/dist/clients/web/server.js +3 -35
  221. package/dist/clients/web/web-client/shared/protocol.d.ts +0 -108
  222. package/dist/clients/web/web-cmd.js +2 -2
  223. package/dist/commands/__tests__/human.test.js +27 -15
  224. package/dist/commands/api-client.js +3 -3
  225. package/dist/commands/canvas-browse.js +1 -1
  226. package/dist/commands/canvas-issue.js +1 -1
  227. package/dist/commands/canvas-use.js +1 -1
  228. package/dist/commands/human/doc.d.ts +2 -0
  229. package/dist/commands/human/doc.js +98 -0
  230. package/dist/commands/human/inbox.d.ts +2 -0
  231. package/dist/commands/human/inbox.js +101 -0
  232. package/dist/commands/human/prompts.d.ts +2 -3
  233. package/dist/commands/human/prompts.js +27 -180
  234. package/dist/commands/human/queue.d.ts +1 -5
  235. package/dist/commands/human/queue.js +54 -216
  236. package/dist/commands/human/review.d.ts +2 -0
  237. package/dist/commands/human/review.js +298 -0
  238. package/dist/commands/human/shared.d.ts +2 -69
  239. package/dist/commands/human/shared.js +1 -181
  240. package/dist/commands/human.js +15 -20
  241. package/dist/commands/memory/__tests__/lint.test.d.ts +1 -0
  242. package/dist/commands/memory/__tests__/lint.test.js +15 -0
  243. package/dist/commands/memory/delete.js +2 -2
  244. package/dist/commands/memory/lint.d.ts +1 -0
  245. package/dist/commands/memory/lint.js +21 -3
  246. package/dist/commands/memory/read.js +4 -4
  247. package/dist/commands/node.js +8 -7
  248. package/dist/commands/pkg/plugin-manage.js +143 -68
  249. package/dist/commands/surface-inspect.js +2 -2
  250. package/dist/commands/surface.js +6 -12
  251. package/dist/commands/sys/__tests__/setup-core.test.js +9 -5
  252. package/dist/commands/sys/__tests__/setup-front-door.test.js +7 -42
  253. package/dist/commands/sys/config.js +29 -3
  254. package/dist/commands/sys/feedback.js +1 -1
  255. package/dist/commands/sys/setup-core.d.ts +12 -11
  256. package/dist/commands/sys/setup-core.js +55 -46
  257. package/dist/commands/sys/setup-wizard.d.ts +4 -1
  258. package/dist/commands/sys/setup-wizard.js +50 -9
  259. package/dist/commands/sys/sync-deps.js +1 -1
  260. package/dist/commands/sys.js +4 -6
  261. package/dist/core/__tests__/broker-sdk-wiring.test.js +6 -1
  262. package/dist/core/__tests__/broker-snapshot-history.test.js +9 -1
  263. package/dist/core/__tests__/dead-node-policy-table.test.js +3 -4
  264. package/dist/core/__tests__/fixtures/fake-engine.d.ts +8 -5
  265. package/dist/core/__tests__/fixtures/fake-engine.js +28 -2
  266. package/dist/core/__tests__/full/broker-provider-retry.test.js +87 -2
  267. package/dist/core/__tests__/full/broker-whip-interrupt.test.d.ts +1 -0
  268. package/dist/core/__tests__/full/broker-whip-interrupt.test.js +39 -0
  269. package/dist/core/__tests__/full/detach-focus.test.js +1 -1
  270. package/dist/core/__tests__/human-deliver-e2e.test.js +64 -64
  271. package/dist/core/__tests__/human-deliver.test.js +71 -345
  272. package/dist/core/__tests__/inline-memory-refs.test.js +2 -12
  273. package/dist/core/__tests__/memory-resolver-precedence.test.js +12 -15
  274. package/dist/core/__tests__/migration.test.js +10 -0
  275. package/dist/core/__tests__/model-pin-durability.test.js +14 -0
  276. package/dist/core/__tests__/phase4-review-migration.test.d.ts +1 -0
  277. package/dist/core/__tests__/phase4-review-migration.test.js +100 -0
  278. package/dist/core/__tests__/phase4-review-store.test.d.ts +1 -0
  279. package/dist/core/__tests__/phase4-review-store.test.js +143 -0
  280. package/dist/core/__tests__/remote-canvas-target.test.js +2 -2
  281. package/dist/core/__tests__/revive.test.js +22 -53
  282. package/dist/core/__tests__/session-cycles.test.js +48 -18
  283. package/dist/core/__tests__/spawn-no-kickoff.test.js +8 -12
  284. package/dist/core/__tests__/stop-guard.test.js +16 -1
  285. package/dist/core/__tests__/tmux-surface.test.js +21 -25
  286. package/dist/core/asset-root.d.ts +7 -0
  287. package/dist/core/asset-root.js +18 -0
  288. package/dist/core/broker-client/client.d.ts +0 -7
  289. package/dist/core/broker-client/client.js +4 -10
  290. package/dist/core/broker-client/transport-relay.d.ts +3 -4
  291. package/dist/core/broker-client/transport-relay.js +3 -4
  292. package/dist/core/canvas/__tests__/attention.test.js +1 -1
  293. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  294. package/dist/core/canvas/__tests__/remote-transport.test.d.ts +1 -0
  295. package/dist/core/{view/__tests__/transport-remote.test.js → canvas/__tests__/remote-transport.test.js} +2 -2
  296. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  297. package/dist/core/canvas/attention.js +4 -4
  298. package/dist/core/canvas/canvas.d.ts +6 -11
  299. package/dist/core/canvas/canvas.js +11 -33
  300. package/dist/core/canvas/db.js +207 -38
  301. package/dist/core/canvas/extensions.d.ts +1 -1
  302. package/dist/core/canvas/extensions.js +8 -1
  303. package/dist/core/canvas/index.d.ts +0 -1
  304. package/dist/core/canvas/index.js +0 -1
  305. package/dist/core/canvas/nav-model.d.ts +1 -1
  306. package/dist/core/canvas/nav-model.js +1 -1
  307. package/dist/core/canvas/paths.d.ts +5 -0
  308. package/dist/core/canvas/paths.js +16 -0
  309. package/dist/core/canvas/remote-canvas-source.js +2 -2
  310. package/dist/core/canvas/remote-transport.d.ts +7 -0
  311. package/dist/core/{view/transport-remote.js → canvas/remote-transport.js} +3 -4
  312. package/dist/core/canvas/source.js +1 -1
  313. package/dist/core/canvas/status-glyph.js +4 -12
  314. package/dist/core/canvas/types.d.ts +10 -35
  315. package/dist/core/clipboard-text.d.ts +8 -0
  316. package/dist/core/clipboard-text.js +18 -0
  317. package/dist/core/command-manifests/registry.d.ts +1 -3
  318. package/dist/core/command-plugins/bundle.d.ts +29 -0
  319. package/dist/core/command-plugins/bundle.js +198 -0
  320. package/dist/core/command-plugins/discovery.d.ts +0 -8
  321. package/dist/core/command-plugins/discovery.js +2 -39
  322. package/dist/core/command-plugins/transport/http-fetch.d.ts +4 -30
  323. package/dist/core/command-plugins/transport/http-fetch.js +17 -95
  324. package/dist/core/command.d.ts +1 -13
  325. package/dist/core/command.js +1 -16
  326. package/dist/core/config.d.ts +4 -0
  327. package/dist/core/config.js +28 -3
  328. package/dist/core/document-lines.d.ts +4 -0
  329. package/dist/core/document-lines.js +6 -0
  330. package/dist/core/feed/feed.d.ts +2 -0
  331. package/dist/core/feed/feed.js +11 -7
  332. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +1 -0
  333. package/dist/core/human/__tests__/inbox-cancel-worker.js +7 -0
  334. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +1 -0
  335. package/dist/core/human/__tests__/inbox-claim-worker.js +17 -0
  336. package/dist/core/human/__tests__/inbox-core.test.d.ts +1 -0
  337. package/dist/core/human/__tests__/inbox-core.test.js +144 -0
  338. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +1 -0
  339. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +8 -0
  340. package/dist/core/human/__tests__/visible-paths.test.d.ts +1 -0
  341. package/dist/{clients/attach → core/human}/__tests__/visible-paths.test.js +9 -0
  342. package/dist/core/human/claim.d.ts +23 -0
  343. package/dist/core/human/claim.js +67 -0
  344. package/dist/core/human/convention.d.ts +30 -0
  345. package/dist/core/human/convention.js +202 -0
  346. package/dist/core/human/deck-factories.d.ts +8 -0
  347. package/dist/core/human/deck-factories.js +15 -0
  348. package/dist/core/human/deck-schema.d.ts +70 -0
  349. package/dist/core/human/deck-schema.js +92 -0
  350. package/dist/core/human/root.d.ts +7 -0
  351. package/dist/core/human/root.js +37 -0
  352. package/dist/core/human/scan.d.ts +3 -0
  353. package/dist/core/human/scan.js +76 -0
  354. package/dist/core/human/summary.d.ts +8 -0
  355. package/dist/core/human/summary.js +44 -0
  356. package/dist/core/human/tickets.d.ts +49 -0
  357. package/dist/core/human/tickets.js +190 -0
  358. package/dist/core/human/types.d.ts +136 -0
  359. package/dist/core/human/types.js +5 -0
  360. package/dist/{clients/attach → core/human}/visible-paths.d.ts +1 -1
  361. package/dist/{clients/attach → core/human}/visible-paths.js +3 -2
  362. package/dist/core/inspector/chrome.d.ts +6 -0
  363. package/dist/core/inspector/chrome.js +14 -0
  364. package/dist/core/inspector/contract.d.ts +105 -0
  365. package/dist/core/inspector/contract.js +1 -0
  366. package/dist/core/inspector/core.d.ts +4 -5
  367. package/dist/core/inspector/core.js +3 -5
  368. package/dist/core/inspector/host.d.ts +46 -0
  369. package/dist/core/{tui → inspector}/host.js +42 -208
  370. package/dist/core/inspector/text.d.ts +2 -2
  371. package/dist/core/inspector/transport.d.ts +6 -0
  372. package/dist/core/inspector/transport.js +36 -0
  373. package/dist/core/inspector/tui.d.ts +2 -2
  374. package/dist/core/inspector/tui.js +15 -15
  375. package/dist/core/keybindings/__tests__/inbox-affordance.test.js +11 -11
  376. package/dist/core/keybindings/__tests__/resolve.test.js +10 -19
  377. package/dist/core/keybindings/attach-control.d.ts +9 -3
  378. package/dist/core/keybindings/attach-control.js +3 -1
  379. package/dist/core/keybindings/catalog.d.ts +6 -5
  380. package/dist/core/keybindings/catalog.js +61 -133
  381. package/dist/core/keybindings/inbox.d.ts +5 -6
  382. package/dist/core/keybindings/inbox.js +9 -10
  383. package/dist/core/keybindings/index.d.ts +3 -3
  384. package/dist/core/keybindings/index.js +3 -3
  385. package/dist/core/keybindings/persistence.d.ts +5 -1
  386. package/dist/core/keybindings/persistence.js +24 -6
  387. package/dist/core/keybindings/resolve.d.ts +6 -0
  388. package/dist/core/keybindings/resolve.js +10 -3
  389. package/dist/core/memory/inline-ref-grammar.js +2 -3
  390. package/dist/core/memory-resolver.d.ts +27 -29
  391. package/dist/core/memory-resolver.js +45 -39
  392. package/dist/core/model-routes.d.ts +33 -0
  393. package/dist/core/model-routes.js +42 -3
  394. package/dist/core/preview-registry.js +17 -55
  395. package/dist/core/profiles/select.js +3 -3
  396. package/dist/core/{view/remote-canvas-target.d.ts → remote-canvas-target.d.ts} +2 -2
  397. package/dist/core/{view/remote-canvas-target.js → remote-canvas-target.js} +4 -4
  398. package/dist/core/review/__tests__/capture-origin.test.d.ts +1 -0
  399. package/dist/core/review/__tests__/capture-origin.test.js +139 -0
  400. package/dist/core/review/__tests__/comments.test.d.ts +1 -0
  401. package/dist/core/review/__tests__/comments.test.js +98 -0
  402. package/dist/core/review/__tests__/project.test.d.ts +1 -0
  403. package/dist/core/review/__tests__/project.test.js +71 -0
  404. package/dist/core/review/__tests__/remap.test.d.ts +1 -0
  405. package/dist/core/review/__tests__/remap.test.js +159 -0
  406. package/dist/core/review/__tests__/stage-identity.test.d.ts +1 -0
  407. package/dist/core/review/__tests__/stage-identity.test.js +140 -0
  408. package/dist/core/review/birth.d.ts +7 -0
  409. package/dist/core/review/birth.js +23 -0
  410. package/dist/core/review/comments.d.ts +69 -0
  411. package/dist/core/review/comments.js +295 -0
  412. package/dist/core/review/companion.d.ts +35 -0
  413. package/dist/core/review/companion.js +205 -0
  414. package/dist/core/review/document.d.ts +27 -0
  415. package/dist/core/review/document.js +138 -0
  416. package/dist/core/review/project.d.ts +13 -0
  417. package/dist/core/review/project.js +58 -0
  418. package/dist/core/review/realize.d.ts +19 -0
  419. package/dist/core/review/realize.js +217 -0
  420. package/dist/core/review/remap.d.ts +24 -0
  421. package/dist/core/review/remap.js +156 -0
  422. package/dist/core/review/signal.d.ts +5 -0
  423. package/dist/core/review/signal.js +31 -0
  424. package/dist/core/review/stage.d.ts +26 -0
  425. package/dist/core/review/stage.js +184 -0
  426. package/dist/core/review/store.d.ts +46 -0
  427. package/dist/core/review/store.js +163 -0
  428. package/dist/core/review/ticket-filter.d.ts +17 -0
  429. package/dist/core/review/ticket-filter.js +32 -0
  430. package/dist/core/review/types.d.ts +130 -0
  431. package/dist/core/review/types.js +23 -0
  432. package/dist/core/runtime/__tests__/session-visibility.test.d.ts +1 -0
  433. package/dist/core/runtime/__tests__/session-visibility.test.js +119 -0
  434. package/dist/core/runtime/bearings.d.ts +5 -17
  435. package/dist/core/runtime/bearings.js +88 -71
  436. package/dist/core/runtime/broker-protocol.d.ts +31 -8
  437. package/dist/core/runtime/broker.d.ts +15 -1
  438. package/dist/core/runtime/broker.js +573 -141
  439. package/dist/core/runtime/canvas-extensions.d.ts +4 -5
  440. package/dist/core/runtime/canvas-extensions.js +5 -5
  441. package/dist/core/runtime/front-door.js +4 -4
  442. package/dist/core/runtime/headless-pi.d.ts +24 -0
  443. package/dist/core/runtime/headless-pi.js +152 -0
  444. package/dist/core/runtime/kickoff.d.ts +1 -5
  445. package/dist/core/runtime/kickoff.js +1 -5
  446. package/dist/core/runtime/model-registry.d.ts +3 -0
  447. package/dist/core/runtime/model-registry.js +13 -0
  448. package/dist/core/runtime/naming-persist.d.ts +4 -0
  449. package/dist/core/runtime/naming-persist.js +27 -1
  450. package/dist/core/runtime/naming.d.ts +17 -12
  451. package/dist/core/runtime/naming.js +91 -140
  452. package/dist/core/runtime/node-read.js +19 -10
  453. package/dist/core/runtime/nodes.d.ts +9 -14
  454. package/dist/core/runtime/nodes.js +3 -53
  455. package/dist/core/runtime/package-health.d.ts +6 -21
  456. package/dist/core/runtime/package-health.js +12 -123
  457. package/dist/core/runtime/placement-tmux.d.ts +2 -1
  458. package/dist/core/runtime/placement-tmux.js +6 -4
  459. package/dist/core/runtime/placement.d.ts +1 -0
  460. package/dist/core/runtime/placement.js +4 -8
  461. package/dist/core/runtime/promote.js +3 -0
  462. package/dist/core/runtime/recap.d.ts +2 -7
  463. package/dist/core/runtime/recap.js +35 -69
  464. package/dist/core/runtime/revive.js +34 -77
  465. package/dist/core/runtime/session-cycles.d.ts +4 -0
  466. package/dist/core/runtime/session-cycles.js +14 -1
  467. package/dist/core/runtime/session-visibility.d.ts +37 -0
  468. package/dist/core/runtime/session-visibility.js +83 -0
  469. package/dist/core/runtime/spawn.js +10 -22
  470. package/dist/core/runtime/stop-guard.d.ts +1 -1
  471. package/dist/core/runtime/stop-guard.js +18 -13
  472. package/dist/core/runtime/surface-bg.js +1 -1
  473. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  474. package/dist/core/runtime/tmux-chrome.js +1 -1
  475. package/dist/core/runtime/tmux.d.ts +35 -7
  476. package/dist/core/runtime/tmux.js +439 -62
  477. package/dist/core/runtime/tool-group-summary.d.ts +19 -0
  478. package/dist/core/runtime/tool-group-summary.js +88 -0
  479. package/dist/core/scope.d.ts +2 -7
  480. package/dist/core/scope.js +9 -35
  481. package/dist/core/substrate/schema.d.ts +2 -2
  482. package/dist/core/termrender/code-doc.d.ts +12 -0
  483. package/dist/core/termrender/code-doc.js +92 -0
  484. package/dist/core/termrender/display.d.ts +12 -0
  485. package/dist/core/termrender/display.js +19 -0
  486. package/dist/core/termrender/termrender.d.ts +73 -0
  487. package/dist/core/termrender/termrender.js +795 -0
  488. package/dist/core/termrender/version.d.ts +1 -0
  489. package/dist/core/termrender/version.js +1 -0
  490. package/dist/core/tui/terminal.js +1 -2
  491. package/dist/core/user-settings.d.ts +71 -3
  492. package/dist/core/user-settings.js +83 -1
  493. package/dist/daemon/api/__tests__/inbox.test.js +44 -133
  494. package/dist/daemon/api/handlers/human.d.ts +0 -10
  495. package/dist/daemon/api/handlers/human.js +104 -702
  496. package/dist/daemon/api/handlers/inbox.js +113 -142
  497. package/dist/daemon/api/handlers/review-comments.d.ts +2 -0
  498. package/dist/daemon/api/handlers/review-comments.js +500 -0
  499. package/dist/daemon/api/handlers/reviews.d.ts +2 -0
  500. package/dist/daemon/api/handlers/reviews.js +287 -0
  501. package/dist/daemon/api/map.d.ts +22 -7
  502. package/dist/daemon/api/map.js +159 -11
  503. package/dist/daemon/api/server.js +4 -0
  504. package/dist/daemon/cron-run.js +6 -9
  505. package/dist/daemon/crtrd.js +17 -33
  506. package/dist/daemon/fleet.d.ts +1 -8
  507. package/dist/daemon/fleet.js +4 -30
  508. package/dist/daemon/human/finish.d.ts +14 -0
  509. package/dist/daemon/human/finish.js +115 -0
  510. package/dist/daemon/human/sweep.d.ts +6 -0
  511. package/dist/daemon/human/sweep.js +49 -0
  512. package/dist/daemon/messaging/node-message.d.ts +18 -0
  513. package/dist/daemon/messaging/node-message.js +70 -0
  514. package/dist/daemon/review/comment-notify.d.ts +26 -0
  515. package/dist/daemon/review/comment-notify.js +107 -0
  516. package/dist/daemon/review/deliver.d.ts +7 -0
  517. package/dist/daemon/review/deliver.js +56 -0
  518. package/dist/daemon/review/finish.d.ts +22 -0
  519. package/dist/daemon/review/finish.js +254 -0
  520. package/dist/daemon/review/sweep.d.ts +11 -0
  521. package/dist/daemon/review/sweep.js +120 -0
  522. package/dist/index.d.ts +2 -0
  523. package/dist/index.js +2 -2
  524. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +42 -64
  525. package/dist/pi-extensions/__tests__/canvas-recap.test.d.ts +1 -0
  526. package/dist/pi-extensions/__tests__/canvas-recap.test.js +159 -0
  527. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +16 -38
  528. package/dist/pi-extensions/__tests__/canvas-structured-output.test.d.ts +1 -0
  529. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +63 -0
  530. package/dist/pi-extensions/canvas-context-intro.d.ts +8 -11
  531. package/dist/pi-extensions/canvas-context-intro.js +50 -104
  532. package/dist/pi-extensions/canvas-recap.d.ts +15 -2
  533. package/dist/pi-extensions/canvas-recap.js +192 -69
  534. package/dist/pi-extensions/canvas-review-boundary.d.ts +59 -0
  535. package/dist/pi-extensions/canvas-review-boundary.js +81 -0
  536. package/dist/pi-extensions/canvas-stophook.d.ts +3 -3
  537. package/dist/pi-extensions/canvas-stophook.js +78 -128
  538. package/dist/pi-extensions/canvas-structured-output.js +52 -8
  539. package/dist/shared/generated-context.d.ts +29 -4
  540. package/dist/shared/generated-context.js +122 -7
  541. package/dist/shared/tool-groups.d.ts +23 -0
  542. package/dist/shared/tool-groups.js +60 -0
  543. package/dist/shared/working-activity.d.ts +8 -0
  544. package/dist/shared/working-activity.js +26 -0
  545. package/dist/types.d.ts +36 -2
  546. package/dist/types.js +51 -0
  547. package/dist/web-client/assets/index-CCPaLeW7.css +2 -0
  548. package/dist/web-client/assets/index-kyOWsnZK.js +86 -0
  549. package/dist/web-client/index.html +2 -2
  550. package/dist/web-client/sw.js +1 -1
  551. package/docs/compat/hearth-crtr-v4.md +2 -2
  552. package/docs/compat/hearth-crtr-v5.md +4 -2
  553. package/docs/public-api.md +3 -3
  554. package/package.json +21 -14
  555. package/runtime.lock.json +4661 -1453
  556. package/scripts/postinstall.mjs +15 -2
  557. package/dist/builtin-pi-packages/pi-mode-switch/README.md +0 -36
  558. package/dist/builtin-pi-packages/pi-mode-switch/bin/mode +0 -36
  559. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +0 -449
  560. package/dist/builtin-pi-packages/pi-mode-switch/package.json +0 -14
  561. package/dist/builtin-pi-packages/pi-mode-switch/tsconfig.json +0 -9
  562. package/dist/builtin-views/_lib/states.mjs +0 -161
  563. package/dist/builtin-views/canvas/core.mjs +0 -657
  564. package/dist/builtin-views/canvas/text.mjs +0 -58
  565. package/dist/builtin-views/canvas/tui.mjs +0 -168
  566. package/dist/builtin-views/canvas/web.jsx +0 -121
  567. package/dist/builtin-views/chat/core.mjs +0 -684
  568. package/dist/builtin-views/chat/text.mjs +0 -101
  569. package/dist/builtin-views/chat/tui.mjs +0 -360
  570. package/dist/builtin-views/chat/web.jsx +0 -362
  571. package/dist/builtin-views/git-pr/core.mjs +0 -673
  572. package/dist/builtin-views/git-pr/text.mjs +0 -84
  573. package/dist/builtin-views/git-pr/tui.mjs +0 -301
  574. package/dist/builtin-views/git-pr/web.jsx +0 -216
  575. package/dist/builtin-views/inbox/_lib/render.mjs +0 -175
  576. package/dist/builtin-views/inbox/core.mjs +0 -1273
  577. package/dist/builtin-views/inbox/text.mjs +0 -73
  578. package/dist/builtin-views/inbox/tui.mjs +0 -314
  579. package/dist/builtin-views/inbox/web.jsx +0 -188
  580. package/dist/builtin-views/linkedin/core.mjs +0 -906
  581. package/dist/builtin-views/linkedin/text.mjs +0 -69
  582. package/dist/builtin-views/linkedin/tui.mjs +0 -413
  583. package/dist/builtin-views/linkedin/web.jsx +0 -206
  584. package/dist/builtin-views/prompt-review/core.mjs +0 -735
  585. package/dist/builtin-views/prompt-review/text.mjs +0 -15
  586. package/dist/builtin-views/prompt-review/tui.mjs +0 -196
  587. package/dist/builtin-views/prompt-review/web.jsx +0 -484
  588. package/dist/builtin-views/workspace-sidebar/__tests__/core.test.js +0 -50
  589. package/dist/builtin-views/workspace-sidebar/__tests__/core.test.ts +0 -53
  590. package/dist/builtin-views/workspace-sidebar/core.mjs +0 -670
  591. package/dist/builtin-views/workspace-sidebar/text.mjs +0 -53
  592. package/dist/builtin-views/workspace-sidebar/tui.mjs +0 -141
  593. package/dist/builtin-views/workspace-sidebar/web.jsx +0 -109
  594. package/dist/clients/attach/session/mode.d.ts +0 -25
  595. package/dist/clients/attach/session/mode.js +0 -97
  596. package/dist/commands/sys/promptstudio.d.ts +0 -2
  597. package/dist/commands/sys/promptstudio.js +0 -65
  598. package/dist/commands/view-cycle.d.ts +0 -2
  599. package/dist/commands/view-cycle.js +0 -130
  600. package/dist/commands/view-list.d.ts +0 -2
  601. package/dist/commands/view-list.js +0 -66
  602. package/dist/commands/view-new.d.ts +0 -2
  603. package/dist/commands/view-new.js +0 -74
  604. package/dist/commands/view-pick.d.ts +0 -6
  605. package/dist/commands/view-pick.js +0 -129
  606. package/dist/commands/view-run.d.ts +0 -2
  607. package/dist/commands/view-run.js +0 -214
  608. package/dist/commands/view.d.ts +0 -2
  609. package/dist/commands/view.js +0 -31
  610. package/dist/commands/workspace.d.ts +0 -2
  611. package/dist/commands/workspace.js +0 -165
  612. package/dist/core/__tests__/chat-view-reconnect.test.js +0 -105
  613. package/dist/core/__tests__/full/consult-startup-failure.test.js +0 -124
  614. package/dist/core/canvas/__tests__/human-work-outbox.test.js +0 -123
  615. package/dist/core/canvas/human-work-outbox.d.ts +0 -73
  616. package/dist/core/canvas/human-work-outbox.js +0 -261
  617. package/dist/core/command-plugins/store.d.ts +0 -16
  618. package/dist/core/command-plugins/store.js +0 -64
  619. package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +0 -40
  620. package/dist/core/runtime/fault-recovery-nudge.d.ts +0 -3
  621. package/dist/core/runtime/fault-recovery-nudge.js +0 -3
  622. package/dist/core/tui/__tests__/host-keybindings.test.js +0 -113
  623. package/dist/core/tui/host.d.ts +0 -66
  624. package/dist/core/view/__tests__/transport-cache.test.js +0 -62
  625. package/dist/core/view/bridge.d.ts +0 -10
  626. package/dist/core/view/bridge.js +0 -31
  627. package/dist/core/view/chrome.d.ts +0 -9
  628. package/dist/core/view/chrome.js +0 -22
  629. package/dist/core/view/contract.d.ts +0 -205
  630. package/dist/core/view/contract.js +0 -23
  631. package/dist/core/view/loader.d.ts +0 -31
  632. package/dist/core/view/loader.js +0 -188
  633. package/dist/core/view/stream-local.d.ts +0 -3
  634. package/dist/core/view/stream-local.js +0 -231
  635. package/dist/core/view/transport-cache.d.ts +0 -8
  636. package/dist/core/view/transport-cache.js +0 -38
  637. package/dist/core/view/transport-local.d.ts +0 -7
  638. package/dist/core/view/transport-local.js +0 -83
  639. package/dist/core/view/transport-remote.d.ts +0 -2
  640. package/dist/core/view/transport.d.ts +0 -10
  641. package/dist/core/view/transport.js +0 -15
  642. package/dist/pi-extensions/naming-tool.d.ts +0 -24
  643. package/dist/pi-extensions/naming-tool.js +0 -67
  644. package/dist/prompts/view.d.ts +0 -7
  645. package/dist/prompts/view.js +0 -172
  646. package/dist/web/ViewChrome.d.ts +0 -7
  647. package/dist/web/ViewChrome.js +0 -28
  648. package/dist/web/ViewPane.d.ts +0 -39
  649. package/dist/web/ViewPane.js +0 -48
  650. package/dist/web/index.d.ts +0 -8
  651. package/dist/web/index.js +0 -22
  652. package/dist/web/runtime.d.ts +0 -39
  653. package/dist/web/runtime.js +0 -232
  654. package/dist/web/states.d.ts +0 -24
  655. package/dist/web/states.js +0 -24
  656. package/dist/web/transport-http.d.ts +0 -5
  657. package/dist/web/transport-http.js +0 -34
  658. package/dist/web/transport-stream.d.ts +0 -3
  659. package/dist/web/transport-stream.js +0 -204
  660. package/dist/web-client/assets/index-U_NZ66VE.js +0 -79
  661. package/dist/web-client/assets/index-aaeu4adv.css +0 -2
  662. /package/dist/{builtin-views/workspace-sidebar/__tests__/core.test.d.ts → clients/attach/__tests__/file-review-focus.test.d.ts} +0 -0
  663. /package/dist/clients/attach/__tests__/{visible-paths.test.d.ts → group-activity.test.d.ts} +0 -0
  664. /package/dist/{core/__tests__/chat-view-reconnect.test.d.ts → clients/attach/__tests__/input-controller-extension-command.test.d.ts} +0 -0
  665. /package/dist/{core/__tests__/full/consult-startup-failure.test.d.ts → clients/attach/__tests__/session-identity-refresh.test.d.ts} +0 -0
  666. /package/dist/{core/canvas/__tests__/human-work-outbox.test.d.ts → clients/inbox/__tests__/inbox-controller.test.d.ts} +0 -0
  667. /package/dist/{core/keybindings/__tests__/bespoke-consumers.test.d.ts → clients/inbox/__tests__/mount-panel.test.d.ts} +0 -0
  668. /package/dist/{core/tui/__tests__/host-keybindings.test.d.ts → clients/inbox/review/__tests__/editor-roundtrip.test.d.ts} +0 -0
  669. /package/dist/{core/view/__tests__/transport-cache.test.d.ts → clients/inbox/review/__tests__/remap.test.d.ts} +0 -0
  670. /package/dist/{core/view/__tests__/transport-remote.test.d.ts → clients/inbox/review/__tests__/review-core.test.d.ts} +0 -0
@@ -1,49 +1,27 @@
1
1
  // Run with: node --import tsx/esm --test src/core/__tests__/human-deliver.test.ts
2
2
  //
3
- // Unit C1 — crouter as a humanloop producer + completion bridge. These tests
4
- // pin the two crouter boundaries the hard cut leaves behind:
5
- // 1. ask/review ENQUEUE only — they create a bridge node, write a minimal
6
- // run.json, submit to humanloop, and return immediately. No pane/pid.
7
- // 2. `_deliver` is the registered completion handler — it turns a humanloop
8
- // completion event into the existing `pushFinal` delivery (deck/review) or
9
- // a quiet bridge finalization (canceled), idempotently.
10
- //
11
- // Everything crosses the real humanloop package boundary: fixtures are the
12
- // canonical `response.json` union that humanloop's strict `readTicketResult`
13
- // decodes. Tickets live under a root REGISTERED to owner 'crouter' (the
14
- // delivery contract), isolated via a temp XDG_STATE_HOME + HOME + CRTR_HOME.
3
+ // Crouter creates bridge tickets, while crtrd owns terminal ticket delivery.
15
4
  import { test, describe, before, after, beforeEach, afterEach } from 'node:test';
16
5
  import assert from 'node:assert/strict';
17
- import { mkdtempSync, mkdirSync, writeFileSync, realpathSync, readFileSync, existsSync, symlinkSync } from 'node:fs';
6
+ import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
18
7
  import { tmpdir } from 'node:os';
19
- import { dirname, join, resolve, basename } from 'node:path';
8
+ import { join, resolve } from 'node:path';
20
9
  import { Readable } from 'node:stream';
21
- import { registerInboxRoot, submitDeck, validateDeck, claimTicket, completeDeck, cancelTicket, readTicketResult, atomicWriteJson, } from '@crouton-kit/humanloop';
22
- import { spawnNode } from '../runtime/nodes.js';
10
+ import { createApiServer } from '../../daemon/api/server.js';
11
+ import { cancelHumanTicket, deliverTerminalResult, resolveDeckTicket } from '../../daemon/human/finish.js';
12
+ import { humanAsk, humanNotify } from '../../commands/human/prompts.js';
13
+ import { runCli } from '../command.js';
23
14
  import { getNode, listNodes, closeDb } from '../canvas/index.js';
24
15
  import { readInboxSince } from '../feed/inbox.js';
25
- import { humanAsk, humanReview, humanNotify } from '../../commands/human/prompts.js';
26
- import { runCli } from '../command.js';
27
16
  import { resolveRef } from '../canvas/history.js';
28
- import { humanDeliver } from '../../commands/human/queue.js';
29
- import { createApiServer } from '../../daemon/api/server.js';
30
- import { trustedCrouterVisualHandler } from '../../daemon/api/handlers/human.js';
31
17
  import { apiSocketPath, nodeDir } from '../canvas/paths.js';
18
+ import { spawnNode } from '../runtime/nodes.js';
32
19
  import { resolveRoot } from '../../build-root.js';
33
- // ---------------------------------------------------------------------------
34
- // Isolated environment
35
- // ---------------------------------------------------------------------------
36
- let home; // CRTR_HOME (canvas db) + HOME (os.homedir → interactionsRoot)
37
- let stateHome; // XDG_STATE_HOME (humanloop root registry)
38
- let root; // canonical registered interaction root, owner 'crouter'
20
+ import { validateDeck } from '../human/deck-schema.js';
21
+ import { ticketDir } from '../human/root.js';
22
+ import { readTicketResult, submitDeck } from '../human/tickets.js';
23
+ let home;
39
24
  let priorHome;
40
- // Since the crtrd-API re-plumb the producer + completion leaves are thin CLI
41
- // forwarders: `ask`/`review` create their bridge node through
42
- // `cliClient().createHumanBridge`, and `_deliver` forwards the completion event
43
- // to crtrd, which owns the pushFinal/finalize delivery. Stand up a real
44
- // in-process `createApiServer()` bound to the isolated CRTR_HOME's socket so the
45
- // full leaf → client → server → handler path runs against the genuine seam —
46
- // never an autostarted subprocess daemon, never a mock client.
47
25
  let server;
48
26
  async function waitForSocket(path) {
49
27
  for (let i = 0; i < 200; i++) {
@@ -53,13 +31,6 @@ async function waitForSocket(path) {
53
31
  }
54
32
  throw new Error(`api socket never bound at ${path}`);
55
33
  }
56
- const ISO = '2026-07-10T12:00:00.000Z';
57
- /** A benign direct-exec handler so humanloop's completion dispatch during the
58
- * submit/cancel race is a fast no-op — the crouter delivery is exercised by
59
- * calling `_deliver` in-process, not through this subprocess. */
60
- function benignHandler() {
61
- return { command: process.execPath, args: ['-e', ''] };
62
- }
63
34
  async function withPipedStdin(body, fn) {
64
35
  const original = process.stdin;
65
36
  const stdin = Readable.from([Buffer.from(body)]);
@@ -82,38 +53,22 @@ async function withMutedStdout(fn) {
82
53
  process.stdout.write = originalWrite;
83
54
  }
84
55
  }
85
- /** Register `dir` (created fresh) as an owner-'crouter' root and return its
86
- * canonical path. */
87
- function registerRoot(dir) {
88
- return registerInboxRoot({ root: dir, owner: 'crouter', handler: benignHandler() }).root;
89
- }
90
- /** Create a subscribed parent + a terminal human bridge child; return both ids.
91
- * The parent is auto-subscribed to the bridge at spawn, so a `pushFinal` from
92
- * the bridge fans into the parent's inbox — the sole answer route. */
93
56
  function bridgeUnder() {
94
57
  const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
95
58
  const bridge = spawnNode({ kind: 'human', parent, cwd: home, name: 'human-ask', lifecycle: 'terminal' }).node_id;
96
59
  return { parent, bridge };
97
60
  }
98
- /** Materialize an interaction dir under the registered root with a run.json. */
99
- function seedTicket(id, mode, jobId) {
100
- const dir = join(root, id);
101
- mkdirSync(dir, { recursive: true });
102
- atomicWriteJson(join(dir, 'run.json'), { mode, ...(jobId !== undefined ? { job_id: jobId } : {}) });
103
- return dir;
104
- }
105
- function completionEvent(dir, kind) {
106
- return {
107
- schema: 'humanloop.completion/v1',
108
- root,
61
+ function submitBridgeDeck(bridge) {
62
+ const dir = ticketDir(bridge);
63
+ submitDeck({
109
64
  dir,
110
- ticketId: dir.split('/').pop(),
111
- kind,
112
- outcome: kind === 'canceled' ? 'canceled' : 'resolved',
113
- responsePath: join(dir, 'response.json'),
114
- };
65
+ deck: validateDeck({
66
+ title: 'Ship it?',
67
+ interactions: [{ id: 'question', title: 'Ship it?', subtitle: 'Decide whether to ship now so the release can proceed.', options: [], allowFreetext: true }],
68
+ }),
69
+ });
70
+ return dir;
115
71
  }
116
- /** Read the report body a subscriber inbox pointer references. */
117
72
  function reportBody(ref) {
118
73
  const resolved = resolveRef(ref);
119
74
  if (resolved === null || resolved.source !== 'report')
@@ -126,18 +81,15 @@ before(() => {
126
81
  beforeEach(async () => {
127
82
  closeDb();
128
83
  home = mkdtempSync(join(tmpdir(), 'crtr-deliver-home-'));
129
- stateHome = mkdtempSync(join(tmpdir(), 'crtr-deliver-state-'));
130
- const rootDir = mkdtempSync(join(tmpdir(), 'crtr-deliver-root-'));
131
84
  process.env['CRTR_HOME'] = home;
132
85
  process.env['HOME'] = home;
133
- process.env['XDG_STATE_HOME'] = stateHome;
134
86
  delete process.env['CRTR_NODE_ID'];
135
- root = registerRoot(realpathSync(rootDir));
136
87
  server = createApiServer();
137
88
  await waitForSocket(apiSocketPath());
138
89
  });
139
90
  afterEach(async () => {
140
91
  await server?.close();
92
+ rmSync(home, { recursive: true, force: true });
141
93
  });
142
94
  after(() => {
143
95
  closeDb();
@@ -146,314 +98,88 @@ after(() => {
146
98
  else
147
99
  delete process.env['HOME'];
148
100
  delete process.env['CRTR_HOME'];
149
- delete process.env['XDG_STATE_HOME'];
150
101
  delete process.env['CRTR_NODE_ID'];
151
102
  });
152
- // ---------------------------------------------------------------------------
153
- // (a) ask/review enqueue immediately — no pane/pid; run.json captures fork provenance
154
- // ---------------------------------------------------------------------------
155
- test('Visual handler trusts sealed sibling runtime generations across a daemon roll', () => {
156
- const currentGeneration = 'a'.repeat(64);
157
- const retainedGeneration = 'b'.repeat(64);
158
- const current = {
159
- command: '/home/.crouter/host/crouter',
160
- args: [`/home/.crouter/runtime/generations/${currentGeneration}/dist/cli.js`, 'human', '_visual'],
161
- };
162
- const retained = {
163
- command: '/opt/bin/node',
164
- args: [`/home/.crouter/runtime/generations/${retainedGeneration}/dist/cli.js`, 'human', '_visual'],
165
- };
166
- assert.equal(trustedCrouterVisualHandler(retained, current), true);
167
- assert.equal(trustedCrouterVisualHandler({ ...retained, command: '/tmp/python' }, current), false);
168
- assert.equal(trustedCrouterVisualHandler({ ...retained, args: ['/tmp/cli.js', 'human', '_visual'] }, current), false);
169
- assert.equal(trustedCrouterVisualHandler({ ...retained, args: [retained.args[0], 'human', '_deliver'] }, current), false);
170
- });
171
103
  describe('enqueue is producer-only', () => {
172
104
  test('ask requires and preserves an explicit title instead of deriving one from the body', async () => {
173
105
  const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
174
106
  process.env['CRTR_NODE_ID'] = parent;
175
107
  await assert.rejects(() => humanAsk.run({ question: 'This first line must never become the title.' }), /require a non-empty --title/);
176
- const r = (await humanAsk.run({ title: 'Release decision', subtitle: 'Decide whether to release now so the launch can proceed.', question: 'This first line must never become the title.' }));
177
- assert.equal(typeof r['job_id'], 'string');
178
- assert.equal(typeof r['dir'], 'string');
179
- assert.equal(typeof r['follow_up'], 'string');
180
- for (const forbidden of ['pane_id', 'render_pane_id', 'pid', 'worker_pid']) {
181
- assert.ok(!(forbidden in r), `ask result must not expose ${forbidden}`);
182
- }
183
- const deck = JSON.parse(readFileSync(join(r['dir'], 'deck.json'), 'utf8'));
108
+ const result = (await humanAsk.run({ title: 'Release decision', subtitle: 'Decide whether to release now so the launch can proceed.', question: 'This first line must never become the title.' }));
109
+ assert.equal(typeof result['job_id'], 'string');
110
+ assert.equal(typeof result['dir'], 'string');
111
+ for (const forbidden of ['pane_id', 'render_pane_id', 'pid', 'worker_pid'])
112
+ assert.ok(!(forbidden in result), `ask result must not expose ${forbidden}`);
113
+ const deck = JSON.parse(readFileSync(join(result['dir'], 'deck.json'), 'utf8'));
184
114
  assert.equal(deck.title, 'Release decision');
185
115
  assert.equal(deck.interactions[0]?.title, 'Release decision');
186
116
  assert.equal(deck.interactions[0]?.subtitle, 'Decide whether to release now so the launch can proceed.');
187
117
  assert.equal(deck.interactions[0]?.body, 'This first line must never become the title.');
188
118
  assert.equal(deck.interactions[0]?.kind, 'decision');
189
- const structured = (await humanAsk.run({
190
- deck: {
191
- title: 'Structured decision',
192
- interactions: [
193
- { id: 'default', title: 'Default', subtitle: 'Choose the default so the release can proceed.', options: [] },
194
- { id: 'context', kind: 'context', title: 'Context', subtitle: 'Read this context so the decision is informed.', options: [] },
195
- ],
196
- },
197
- }));
198
- const structuredDeck = JSON.parse(readFileSync(join(structured['dir'], 'deck.json'), 'utf8'));
199
- assert.deepEqual(structuredDeck.interactions.map((interaction) => interaction.kind), ['decision', 'context']);
200
- const deckDir = mkdtempSync(join(tmpdir(), 'crtr-context-file-deck-'));
201
- assert.notEqual(resolve(process.cwd()), deckDir, 'the CLI caller cwd must differ from the deck directory');
202
- const deckPath = join(deckDir, 'deck.json');
203
- writeFileSync(join(deckDir, 'body.md'), 'The body came from beside the deck JSON.');
204
- writeFileSync(deckPath, JSON.stringify({
205
- title: 'Context file body',
206
- interactions: [{
207
- id: 'decision',
208
- title: 'Use the deck',
209
- subtitle: 'Use the deck body so the person can decide with its context.',
210
- bodyPath: 'body.md',
211
- options: [{ id: 'yes', label: 'Yes' }],
212
- }],
213
- }));
214
- const nodesBeforeContextFile = new Set(listNodes().map((node) => node.node_id));
119
+ const deckDirectory = mkdtempSync(join(tmpdir(), 'crtr-context-file-deck-'));
120
+ assert.notEqual(resolve(process.cwd()), deckDirectory, 'the CLI caller cwd must differ from the deck directory');
121
+ const deckPath = join(deckDirectory, 'deck.json');
122
+ writeFileSync(join(deckDirectory, 'body.md'), 'The body came from beside the deck JSON.');
123
+ writeFileSync(deckPath, JSON.stringify({ title: 'Context file body', interactions: [{ id: 'decision', title: 'Use the deck', subtitle: 'Use the deck body so the person can decide with its context.', bodyPath: 'body.md', options: [{ id: 'yes', label: 'Yes' }] }] }));
124
+ const nodesBefore = new Set(listNodes().map((node) => node.node_id));
215
125
  await withMutedStdout(() => withPipedStdin('', async () => {
216
126
  await runCli(await resolveRoot('human'), ['node', 'crtr', 'human', 'ask', '--context-file', deckPath]);
217
127
  }));
218
- const contextFileBridge = listNodes().find((node) => !nodesBeforeContextFile.has(node.node_id));
219
- assert.ok(contextFileBridge, 'the real CLI invocation created a human bridge');
220
- const contextFileDeck = JSON.parse(readFileSync(join(nodeDir(contextFileBridge.node_id), 'deck.json'), 'utf8'));
221
- assert.equal(contextFileDeck.interactions[0]?.body, 'The body came from beside the deck JSON.');
222
- assert.equal(contextFileDeck.interactions[0]?.bodyPath, undefined);
223
- await assert.rejects(() => humanAsk.run({
224
- deck: {
225
- title: 'Absolute path',
226
- interactions: [{
227
- id: 'decision',
228
- title: 'Reject absolute path',
229
- subtitle: 'Reject the invalid path so the documented deck boundary remains intact.',
230
- bodyPath: join(deckDir, 'body.md'),
231
- options: [{ id: 'yes', label: 'Yes' }],
232
- }],
233
- },
234
- }, { providedParams: new Set(['deck']), contextFilePaths: new Map([['deck', deckPath]]) }), /absolute paths are unsupported/);
235
- const rc = JSON.parse(readFileSync(join(r['dir'], 'run.json'), 'utf8'));
236
- assert.equal(rc['mode'], 'ask');
237
- assert.equal(rc['job_id'], r['job_id']);
238
- assert.deepEqual(rc['conversation_fork'] && {
239
- status: rc['conversation_fork']['status'],
240
- reason: rc['conversation_fork']['reason'],
241
- }, { status: 'unavailable', reason: 'broker_unavailable' });
242
- const bridge = getNode(r['job_id']);
243
- assert.equal(bridge?.kind, 'human');
244
- assert.equal(bridge?.parent, parent);
245
- });
246
- test('review returns immediately and writes run.json = {mode:review, job_id}', async () => {
247
- const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
248
- process.env['CRTR_NODE_ID'] = parent;
249
- const md = join(home, 'plan.md');
250
- writeFileSync(md, '# Plan\n\nline\n');
251
- const r = (await humanReview.run({ file: md, subtitle: 'Review this plan so implementation can begin safely.' }));
252
- assert.equal(typeof r['job_id'], 'string');
253
- assert.ok(!('pane_id' in r) && !('render_pane_id' in r), 'no pane fields');
254
- const rc = JSON.parse(readFileSync(join(r['dir'], 'run.json'), 'utf8'));
255
- assert.deepEqual(rc, { mode: 'review', job_id: r['job_id'] });
128
+ const bridge = listNodes().find((node) => !nodesBefore.has(node.node_id));
129
+ assert.ok(bridge, 'the real CLI invocation created a human bridge');
130
+ const contextDeck = JSON.parse(readFileSync(join(nodeDir(bridge.node_id), 'deck.json'), 'utf8'));
131
+ assert.equal(contextDeck.interactions[0]?.body, 'The body came from beside the deck JSON.');
132
+ assert.equal(contextDeck.interactions[0]?.bodyPath, undefined);
256
133
  });
257
134
  });
258
- // ---------------------------------------------------------------------------
259
- // (b) deck _deliver: pushFinal exactly once, parent inbox gets the answer
260
- // ---------------------------------------------------------------------------
261
- describe('_deliver: deck', () => {
262
- test('pushFinals the readable answer once and wakes the subscribed parent', async () => {
135
+ describe('terminal ticket delivery', () => {
136
+ test('resolveDeckTicket pushFinals the readable answer once and wakes the subscribed parent', async () => {
263
137
  const { parent, bridge } = bridgeUnder();
264
- const dir = seedTicket(bridge, 'ask', bridge);
265
- const deckResult = {
266
- schema: 'humanloop.response/v2',
267
- kind: 'deck',
268
- responses: [{ id: 'question', freetext: 'yes, ship' }],
269
- summary: 'Ship it?: yes, ship',
270
- completedAt: ISO,
271
- };
272
- writeFileSync(join(dir, 'response.json'), JSON.stringify(deckResult));
273
- const out = (await humanDeliver.run({ body: JSON.stringify(completionEvent(dir, 'deck')) }));
274
- assert.equal(out.delivered, true);
275
- // Bridge finalized.
138
+ const dir = submitBridgeDeck(bridge);
139
+ const result = await resolveDeckTicket(bridge, [{ id: 'question', freetext: 'yes, ship' }]);
140
+ assert.equal(result.kind, 'deck');
276
141
  closeDb();
277
142
  assert.equal(getNode(bridge)?.status, 'done');
278
- // Parent inbox has exactly one final pointer carrying the human's words —
279
- // the summary, not a serialized envelope — plus the metadata path.
280
- const finals = readInboxSince(parent).filter((e) => e.from === bridge && e.kind === 'final');
143
+ const finals = readInboxSince(parent).filter((entry) => entry.from === bridge && entry.kind === 'final');
281
144
  assert.equal(finals.length, 1, 'exactly one pushFinal reached the parent');
282
145
  const body = reportBody(finals[0].ref);
283
- assert.equal(body.split('\n')[0], deckResult.summary, 'the answer leads the report');
284
- assert.ok(body.includes(join(dir, 'response.json')), 'the canonical response path is one path away');
285
- assert.ok(!body.includes('humanloop.response/v2'), 'no serialized envelope is inlined');
146
+ assert.equal(body.split('\n')[0], 'Ship it?: yes, ship');
147
+ assert.ok(body.includes(join(dir, 'response.json')));
148
+ assert.ok(!body.includes('humanloop.response/v2'));
286
149
  });
287
- // (c) replay idempotent
288
- test('a second _deliver is idempotent — no second push, still delivered', async () => {
150
+ test('a replay against a terminal bridge is a no-op', async () => {
289
151
  const { parent, bridge } = bridgeUnder();
290
- const dir = seedTicket(bridge, 'ask', bridge);
291
- writeFileSync(join(dir, 'response.json'), JSON.stringify({ schema: 'humanloop.response/v2', kind: 'deck', responses: [{ id: 'q', freetext: 'x' }], summary: 'q: x', completedAt: ISO }));
292
- const ev = JSON.stringify(completionEvent(dir, 'deck'));
293
- await humanDeliver.run({ body: ev });
294
- const replay = (await humanDeliver.run({ body: ev }));
295
- assert.equal(replay.delivered, true);
152
+ submitBridgeDeck(bridge);
153
+ await resolveDeckTicket(bridge, [{ id: 'question', freetext: 'yes' }]);
154
+ assert.equal(await deliverTerminalResult(bridge), true);
296
155
  closeDb();
297
- const finals = readInboxSince(parent).filter((e) => e.from === bridge && e.kind === 'final');
156
+ const finals = readInboxSince(parent).filter((entry) => entry.from === bridge && entry.kind === 'final');
298
157
  assert.equal(finals.length, 1, 'replay after a terminal bridge pushes nothing further');
299
158
  });
300
- });
301
- // ---------------------------------------------------------------------------
302
- // (d) review _deliver: delivered == readable anchored comments + record path
303
- // ---------------------------------------------------------------------------
304
- describe('_deliver: review', () => {
305
- test('pushes readable anchored comments plus the feedback-record path', async () => {
306
- const { parent, bridge } = bridgeUnder();
307
- const dir = seedTicket(bridge, 'review', bridge);
308
- const md = realpathSync(home) + '/spec.md';
309
- writeFileSync(md, '# spec\n');
310
- const feedback = {
311
- file: md,
312
- submitted: true,
313
- approved: false,
314
- comments: [{ id: 'c1', line: 1, endLine: 1, lineText: '# spec', comment: 'tighten', createdAt: ISO }],
315
- submittedAt: ISO,
316
- savedAt: ISO,
317
- };
318
- writeFileSync(join(dir, 'response.json'), JSON.stringify({ schema: 'humanloop.review-response/v1', kind: 'review', result: feedback, completedAt: ISO }));
319
- // The output projection humanloop writes: the bare FeedbackResult.
320
- const projection = join(dir, 'feedback.json');
321
- writeFileSync(projection, JSON.stringify(feedback));
322
- const out = (await humanDeliver.run({ body: JSON.stringify(completionEvent(dir, 'review')) }));
323
- assert.equal(out.delivered, true);
324
- closeDb();
325
- const finals = readInboxSince(parent).filter((e) => e.from === bridge && e.kind === 'final');
326
- assert.equal(finals.length, 1);
327
- const delivered = reportBody(finals[0].ref);
328
- assert.ok(delivered.includes('1 comment on ' + md), 'the comment count and file lead the report');
329
- assert.ok(delivered.includes('tighten'), 'the human\'s comment text is inline');
330
- assert.ok(delivered.includes(join(dir, 'response.json')), 'the full feedback record is one path away');
331
- assert.ok(!delivered.includes('"savedAt"'), 'no serialized FeedbackResult is inlined');
332
- assert.deepEqual(JSON.parse(readFileSync(projection, 'utf8')), feedback, 'the output projection file is untouched');
333
- });
334
- });
335
- // ---------------------------------------------------------------------------
336
- // (e) canceled _deliver: bridge finalized, deferred note skips the actor
337
- // ---------------------------------------------------------------------------
338
- describe('_deliver: canceled', () => {
339
- test('finalizes the bridge and sends a deferred no-answer note to non-actor subscribers only', async () => {
340
- // Two subscribers: the actor (who canceled) and another waiter.
159
+ test('cancelHumanTicket finishes the bridge and notifies non-actor subscribers', async () => {
341
160
  const actor = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'actor' }).node_id;
342
161
  const bridge = spawnNode({ kind: 'human', parent: actor, cwd: home, name: 'human-ask', lifecycle: 'terminal' }).node_id;
343
162
  const other = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'other' }).node_id;
344
- // `other` also subscribes to the bridge.
345
163
  const { subscribe } = await import('../canvas/canvas.js');
346
164
  subscribe(other, bridge, true);
347
- const dir = seedTicket(bridge, 'ask', bridge);
348
- writeFileSync(join(dir, 'response.json'), JSON.stringify({ schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: ISO, reason: 'stale', actor }));
349
- const out = (await humanDeliver.run({ body: JSON.stringify(completionEvent(dir, 'canceled')) }));
350
- assert.equal(out.delivered, true);
351
- closeDb();
352
- assert.equal(getNode(bridge)?.status, 'done', 'canceled bridge finalized');
353
- // Actor (who canceled) gets no note; the other waiter does.
354
- assert.equal(readInboxSince(actor).length, 0, 'the cancelling actor is not messaged');
355
- const otherNotes = readInboxSince(other);
356
- assert.equal(otherNotes.length, 1, 'the other waiter gets one deferred note');
357
- assert.equal(otherNotes[0].tier, 'deferred');
358
- assert.match(otherNotes[0].label, /canceled/);
359
- });
360
- });
361
- // ---------------------------------------------------------------------------
362
- // (e2) REGRESSION (Unit X DEFECT F) — a completion event whose root/dir carry an
363
- // UNRESOLVED symlinked path component still delivers.
364
- //
365
- // Humanloop's registry stores every root realpath-canonicalized, but its
366
- // `dispatchCompletion` only `resolve()`s the dir into the event (not realpath),
367
- // and crouter's own `human cancel` hands `cancelTicket` a CRTR_HOME-derived dir
368
- // that is likewise not realpath'd. On any host whose interaction path has a
369
- // symlinked component (macOS `/tmp`→`/private/tmp`, `/var`→`/private/var`) the
370
- // event thus carries `/tmp/…` while the registry holds `/private/tmp/…`. Before
371
- // the fix, `_deliver` compared those with a plain `resolve()` and spuriously
372
- // threw `interaction dir is not a direct child of the event root` (exit 2) →
373
- // humanloop wrote delivery-error.json and NO delivery.json, and the cancel
374
- // completion was never acknowledged. The deck-SUBMIT path dodged this only
375
- // because the controller's dir already comes from scanning the realpath'd root.
376
- // `_deliver` now realpath-canonicalizes both sides, so any caller's event
377
- // delivers regardless of symlinked components.
378
- // ---------------------------------------------------------------------------
379
- describe('_deliver: symlinked event paths (DEFECT F regression)', () => {
380
- test('delivers a canceled completion whose root/dir carry an unresolved symlink', async () => {
381
- // A symlink whose target is the registered root's PARENT, so `symRoot`
382
- // realpath-resolves to the registered (realpath'd) `root` but is a distinct
383
- // string with an unresolved symlink component — exactly humanloop's event shape.
384
- const linkParent = mkdtempSync(join(tmpdir(), 'crtr-deliver-link-'));
385
- const link = join(linkParent, 'lnk');
386
- symlinkSync(dirname(root), link);
387
- const symRoot = join(link, basename(root));
388
- assert.notEqual(symRoot, root, 'symRoot is a distinct string');
389
- assert.equal(realpathSync(symRoot), root, 'but realpath-resolves to the registered root');
390
- const actor = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'actor' }).node_id;
391
- const bridge = spawnNode({ kind: 'human', parent: actor, cwd: home, name: 'human-ask', lifecycle: 'terminal' }).node_id;
392
- const dir = seedTicket(bridge, 'ask', bridge); // real (realpath'd) dir on disk
393
- writeFileSync(join(dir, 'response.json'), JSON.stringify({ schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: ISO, actor }));
394
- // The event carries the SYMLINKED root/dir/responsePath (non-realpath), the
395
- // way dispatchCompletion + `human cancel` build it on a symlinked host.
396
- const symDir = join(symRoot, bridge);
397
- const event = {
398
- schema: 'humanloop.completion/v1',
399
- root: symRoot,
400
- dir: symDir,
401
- ticketId: bridge,
402
- kind: 'canceled',
403
- outcome: 'canceled',
404
- responsePath: join(symDir, 'response.json'),
405
- };
406
- const out = (await humanDeliver.run({ body: JSON.stringify(event) }));
407
- assert.equal(out.delivered, true, 'the cancel completion delivers (no exit-2 rejection)');
408
- closeDb();
409
- assert.equal(getNode(bridge)?.status, 'done', 'the bridge is finalized exactly once');
410
- assert.equal(readInboxSince(actor).length, 0, 'the cancelling actor is not messaged');
411
- });
412
- });
413
- // ---------------------------------------------------------------------------
414
- // (f) submit/cancel race via humanloop primitives → one outcome; crouter converges
415
- // ---------------------------------------------------------------------------
416
- describe('submit/cancel race', () => {
417
- test('one canonical outcome wins and _deliver converges the bridge to a single terminal state', async () => {
418
- const { bridge } = bridgeUnder();
419
- const dir = join(root, bridge);
420
- mkdirSync(dir, { recursive: true });
421
- atomicWriteJson(join(dir, 'run.json'), { mode: 'ask', job_id: bridge });
422
- const deck = validateDeck({ title: 'Go?', interactions: [{ id: 'q', title: 'Go?', subtitle: 'Decide whether to proceed so the work can continue.', options: [], allowFreetext: true }] });
423
- submitDeck({ root, id: bridge, deck });
424
- const claim = claimTicket(dir);
425
- assert.ok(claim !== null, 'claimed the fresh ticket');
426
- // Race an answer against a cancellation on the same exclusive publish.
427
- const [completed, canceled] = await Promise.allSettled([
428
- completeDeck(dir, [{ id: 'q', freetext: 'go' }], claim.token),
429
- cancelTicket(dir, { reason: 'changed mind', actor: 'someone' }),
430
- ]);
431
- // Exactly one canonical final marker exists on disk.
432
- const result = readTicketResult(dir);
433
- assert.ok(result !== null, 'a single canonical response.json won');
434
- assert.ok(result.kind === 'deck' || result.kind === 'canceled');
435
- // Whatever won, at most one side reports a win.
436
- const wonDeck = completed.status === 'fulfilled' && completed.value.won === true;
437
- const wonCancel = canceled.status === 'fulfilled' && canceled.value.status === 'canceled';
438
- assert.ok(!(wonDeck && wonCancel), 'submit and cancel cannot both win');
439
- // Feed the winning outcome to crouter; the bridge reaches exactly one terminal state.
440
- const out = (await humanDeliver.run({ body: JSON.stringify(completionEvent(dir, result.kind)) }));
441
- assert.equal(out.delivered, true);
165
+ submitBridgeDeck(bridge);
166
+ await cancelHumanTicket(bridge, { reason: 'stale', actor });
442
167
  closeDb();
443
- assert.equal(getNode(bridge)?.status, 'done', 'bridge converged to one terminal outcome');
168
+ assert.equal(getNode(bridge)?.status, 'done');
169
+ assert.equal(readInboxSince(actor).length, 0, 'the canceling actor is not messaged');
170
+ const notes = readInboxSince(other);
171
+ assert.equal(notes.length, 1, 'the other waiter gets one deferred note');
172
+ assert.equal(notes[0].tier, 'deferred');
173
+ assert.match(notes[0].label, /canceled/);
444
174
  });
445
175
  });
446
- // ---------------------------------------------------------------------------
447
- // (g) notify creates no node
448
- // ---------------------------------------------------------------------------
449
- describe('notify', () => {
450
- test('enqueues a durable deck and creates no canvas node', async () => {
451
- const before = listNodes().length;
452
- const r = (await humanNotify.run({ title: 'Build done', subtitle: 'The build passed, so the change is ready to review.' }));
453
- assert.equal(r['queued'], true);
454
- assert.equal(typeof r['dir'], 'string');
455
- assert.ok(existsSync(join(r['dir'], 'deck.json')), 'notify wrote a durable deck');
456
- closeDb();
457
- assert.equal(listNodes().length, before, 'notify created no node');
458
- });
176
+ test('notify enqueues a durable deck and creates no canvas node', async () => {
177
+ const before = listNodes().length;
178
+ const result = (await humanNotify.run({ title: 'Build done', subtitle: 'The build passed, so the change is ready to review.' }));
179
+ assert.equal(result['queued'], true);
180
+ assert.equal(typeof result['dir'], 'string');
181
+ assert.ok(existsSync(join(result['dir'], 'deck.json')));
182
+ assert.ok(readTicketResult(result['dir']) === null);
183
+ closeDb();
184
+ assert.equal(listNodes().length, before);
459
185
  });
@@ -3,8 +3,8 @@
3
3
  // Load-bearing shared-contract regression for the inline memory-reference
4
4
  // token grammar (spec §4.1): `src/core/memory/inline-ref-grammar.ts` is the
5
5
  // SINGLE implementation the broker, terminal attach viewer, and web composer
6
- // all consume. This file (Wave C1) covers the grammar/caret/browser-barrel
7
- // cases. Wave C4 extends it (below, in its own `describe`) with the
6
+ // all consume. This file (Wave C1) covers the grammar/caret cases. Wave C4
7
+ // extends it (below, in its own `describe`) with the
8
8
  // corpus/inventory/guidance cases: `buildRefInventory`'s precedence dedup,
9
9
  // both-kinds/gated/slash:false referenceability, INDEX naming, and
10
10
  // metadata-only output, plus `buildGuidance`'s exact-match resolution,
@@ -113,16 +113,6 @@ test('findRefTokenAtCaret: caret at start of a leading token', () => {
113
113
  const caret = findRefTokenAtCaret(text, 1); // right after the slash
114
114
  assert.deepEqual(caret, { start: 0, end: 4, surfacePrefix: '/' });
115
115
  });
116
- test('browser barrel (src/web/index.ts) re-exports the identical grammar', async () => {
117
- const barrel = await import('../../web/index.js');
118
- assert.equal(barrel.findRefTokens, findRefTokens, 'must be the same function, not a mirrored copy');
119
- assert.equal(barrel.formatRefToken, formatRefToken);
120
- assert.equal(barrel.findRefTokenAtCaret, findRefTokenAtCaret);
121
- // And it behaves identically when driven through the barrel.
122
- const [tok] = barrel.findRefTokens('read /dev before starting');
123
- assert.equal(tok.candidate, 'dev');
124
- assert.equal(barrel.formatRefToken('taste/foo'), '/taste:foo');
125
- });
126
116
  // ---------------------------------------------------------------------------
127
117
  // Wave C4 — buildGuidance: pure over (text, names), no filesystem/corpus
128
118
  // involved. See the fixture-backed buildRefInventory suite below for the
@@ -1,11 +1,10 @@
1
- // Regression matrix for `resolveMemoryDoc`'s precedence rule: scopes/sources
2
- // are visited NEAREST-FIRST, and the first source with ANY hit — by identity,
3
- // direct physical path, or bare-leaf fallback — wins outright. A farther
4
- // source's match, even an EXACT identity match, must never be consulted once
5
- // a nearer source has answered by any means. This locks in the fix for the
6
- // regression where a global identity-first scan let a farther-scope doc named
7
- // exactly `AGENTS` shadow a nearer worktree's own `AGENTS.md` (whose explicit
8
- // frontmatter `name` had since diverged from the physical filename).
1
+ // Regression matrix for `resolveMemoryDoc`'s precedence rule: exact identity
2
+ // and direct physical-path matches are resolved NEAREST-FIRST across the whole
3
+ // scope stack before bare-leaf fallback is considered. This prevents a nested
4
+ // doc's coincidental leaf from hiding a farther canonical identity while still
5
+ // ensuring a nearer worktree's own physical `AGENTS.md` beats a farther doc
6
+ // named exactly `AGENTS`, even when the nearer file's explicit frontmatter
7
+ // identity has diverged from its physical filename.
9
8
  //
10
9
  // Fixture shape: two nested project scopes on disk —
11
10
  // root/.crouter/memory/... (farther project scope)
@@ -149,19 +148,17 @@ test('--kind skips a nearer wrong-kind (frontmatter-less, kind-defaulted) doc an
149
148
  process.chdir(prevCwd);
150
149
  }
151
150
  });
152
- test('two docs sharing a path-derived leaf name across scopes: nearest wins even over a farther EXACT identity match', () => {
151
+ test('an exact identity match beats a nearer document reached only by leaf fallback', () => {
153
152
  process.chdir(child);
154
153
  resetScopeCache();
155
154
  try {
156
155
  // Nearest scope's doc lives at "sub/shared.md" (derived name "sub/shared",
157
156
  // leaf "shared"); the farther scope's doc lives at top-level "shared.md"
158
- // (derived name "shared", an EXACT identity match for the bare query).
159
- // Precedence-as-outer-loop means the nearer source's mere leaf-fallback
160
- // hit must still beat the farther source's exact identity hit.
157
+ // (derived name "shared", an exact identity match for the bare query).
161
158
  const doc = resolveMemoryDoc('shared');
162
- assert.equal(doc.path, join(child, '.crouter', 'memory', 'sub', 'shared.md'));
163
- assert.equal(doc.name, 'sub/shared');
164
- assert.match(doc.body, /CHILD SUB SHARED BODY/);
159
+ assert.equal(doc.path, join(root, '.crouter', 'memory', 'shared.md'));
160
+ assert.equal(doc.name, 'shared');
161
+ assert.match(doc.body, /ROOT SHARED BODY/);
165
162
  }
166
163
  finally {
167
164
  process.chdir(prevCwd);
@@ -18,6 +18,12 @@ const PRE_TOOL_GUIDE_CANVAS_EXTENSION_NAMES = [
18
18
  'canvas-goal-capture', 'canvas-passive-context', 'canvas-context-intro',
19
19
  'canvas-doc-substrate', 'canvas-structured-output', 'canvas-bash-valve',
20
20
  ];
21
+ const REVIEW_BOUNDARY_CANVAS_EXTENSION_NAMES = [
22
+ 'canvas-inbox-watcher', 'canvas-stophook', 'canvas-recap',
23
+ 'canvas-goal-capture', 'canvas-passive-context', 'canvas-review-boundary',
24
+ 'canvas-context-intro', 'canvas-tool-guide', 'canvas-doc-substrate',
25
+ 'canvas-structured-output', 'canvas-bash-valve',
26
+ ];
21
27
  let home;
22
28
  async function waitForPath(path) {
23
29
  for (;;) {
@@ -74,6 +80,10 @@ test('opening a fresh home migrates to the latest user_version', () => {
74
80
  const db = openDb();
75
81
  assert.equal(userVersion(db), MIGRATIONS.length);
76
82
  });
83
+ test('a persisted review-boundary canvas prefix is not treated as custom extensions', () => {
84
+ const persisted = REVIEW_BOUNDARY_CANVAS_EXTENSION_NAMES.map((name) => `/old-generation/pi-extensions/${name}.js`);
85
+ assert.deepEqual(customExtensionPaths(persisted), []);
86
+ });
77
87
  test('a v14 canvas normalizes historical canvas paths while retaining custom launch extras', () => {
78
88
  const metaPath = join(home, 'nodes', 'saved', 'meta.json');
79
89
  mkdirSync(join(home, 'nodes', 'saved'), { recursive: true });