@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
@@ -0,0 +1,265 @@
1
+ // conversation/region.ts — the broker-backed companion region in a composed review.
2
+ //
3
+ // CompanionRegion implements SurfaceRegion to render a companion conversation
4
+ // within a bounded rectangle. It owns a SurfaceConversation (broker connection),
5
+ // an editor, and a transcript viewport. Geometry is pushed; repaint flows only
6
+ // through onDirty.
7
+ import { getSelectListTheme } from '@earendil-works/pi-coding-agent';
8
+ import { CURSOR_MARKER } from '@earendil-works/pi-tui';
9
+ import { attachPalette, createKeybindingsManager } from '../attach/config.js';
10
+ import { TitledEditor } from '../attach/input/titled-editor.js';
11
+ import { cropAroundCursor, MIN_TRANSCRIPT_ROWS } from '../attach/session/frame.js';
12
+ import { clipLine, visibleWidth } from '../inbox/tui/ansi.js';
13
+ import { SurfaceConversation } from './conversation.js';
14
+ const EDITOR_MAX_FRACTION = 0.5;
15
+ /**
16
+ * A companion conversation region for a composed review. Renders connection
17
+ * state, editor, and transcript in a bounded viewport. The only repaint channel
18
+ * is onDirty; geometry is always pushed.
19
+ */
20
+ export class CompanionRegion {
21
+ editor;
22
+ conversation;
23
+ cols = 0;
24
+ rows = 0;
25
+ focused = false;
26
+ isDirty = true;
27
+ renderCache = {};
28
+ unreadCount = 0;
29
+ connectionState = 'connecting';
30
+ noticeText;
31
+ onDirtyCallback;
32
+ constructor(opts) {
33
+ // Reuse the attach viewer's initialized theme and full keybinding
34
+ // definitions. TitledEditor only needs invalidation plus the terminal handle,
35
+ // so its own repaint requests return through this region's onDirty channel.
36
+ const pal = attachPalette();
37
+ const editorTheme = { borderColor: pal.border, selectList: getSelectListTheme() };
38
+ const keybindings = createKeybindingsManager();
39
+ const editorTui = {
40
+ requestRender: () => this.dirty(),
41
+ terminal: opts.tui.terminal,
42
+ };
43
+ this.editor = new TitledEditor(editorTui, editorTheme, keybindings, { paddingX: 1 });
44
+ this.editor.title = opts.companionNodeId;
45
+ this.editor.onSubmit = (text) => this.submit(text);
46
+ // Conversation owns the broker connection and chat state
47
+ this.conversation = new SurfaceConversation({
48
+ nodeId: opts.companionNodeId,
49
+ tui: opts.tui,
50
+ canvasSource: opts.canvasSource,
51
+ reopenDormantCompanion: opts.reopenDormantCompanion,
52
+ onDirty: () => this.dirty(),
53
+ onConnection: (state) => this.onConnectionChange(state),
54
+ onMessage: () => this.countUnread(),
55
+ notice: (text) => this.showNotice(text),
56
+ });
57
+ }
58
+ setViewport(cols, rows) {
59
+ const cols2 = Math.max(0, Math.floor(cols));
60
+ const rows2 = Math.max(0, Math.floor(rows));
61
+ if (this.cols === cols2 && this.rows === rows2)
62
+ return;
63
+ const wasHidden = this.isHidden();
64
+ this.cols = cols2;
65
+ this.rows = rows2;
66
+ if (wasHidden && !this.isHidden())
67
+ this.unreadCount = 0;
68
+ this.isDirty = true;
69
+ if (this.onDirtyCallback)
70
+ this.onDirtyCallback();
71
+ }
72
+ render() {
73
+ const { cols, rows } = this;
74
+ // Return cached result if geometry hasn't changed
75
+ if (this.renderCache.cols === cols && this.renderCache.rows === rows && !this.isDirty) {
76
+ return this.renderCache.lines ?? [];
77
+ }
78
+ if (cols === 0 || rows === 0) {
79
+ this.renderCache = { cols, rows, lines: [] };
80
+ this.isDirty = false;
81
+ return [];
82
+ }
83
+ const lines = [];
84
+ // 1. State line at the top (1 row)
85
+ lines.push(this.renderStateLine());
86
+ // 2. Editor section
87
+ const availableRows = rows - 1; // -1 for state line
88
+ const editorMaxRows = Math.max(1, Math.floor(availableRows * EDITOR_MAX_FRACTION));
89
+ const transcriptMinRows = MIN_TRANSCRIPT_ROWS;
90
+ // Calculate actual editor allocation
91
+ const editorRows = Math.max(1, Math.min(editorMaxRows, availableRows - transcriptMinRows));
92
+ const transcriptRows = Math.max(0, availableRows - editorRows);
93
+ // Render and crop editor
94
+ const editorLines = this.editor.render(cols);
95
+ const croppedEditor = cropAroundCursor(editorLines, editorRows);
96
+ // Pad or clip to exact allocation
97
+ while (croppedEditor.length < editorRows) {
98
+ croppedEditor.push('');
99
+ }
100
+ if (croppedEditor.length > editorRows) {
101
+ croppedEditor.length = editorRows;
102
+ }
103
+ lines.push(...croppedEditor);
104
+ // 3. Transcript section
105
+ if (transcriptRows > 0) {
106
+ const transcriptLines = this.conversation.viewport.renderSlice(cols, transcriptRows);
107
+ // Pad to exact allocation
108
+ while (transcriptLines.length < transcriptRows) {
109
+ transcriptLines.push('');
110
+ }
111
+ if (transcriptLines.length > transcriptRows) {
112
+ transcriptLines.length = transcriptRows;
113
+ }
114
+ lines.push(...transcriptLines);
115
+ }
116
+ // Ensure we have exactly `rows` lines, each clipped to `cols` display cells
117
+ while (lines.length < rows) {
118
+ lines.push('');
119
+ }
120
+ if (lines.length > rows) {
121
+ lines.length = rows;
122
+ }
123
+ // Fit each line to the exact column width without splitting ANSI or the
124
+ // zero-width cursor marker. An unfocused region must emit no marker.
125
+ const clipped = lines.map((line) => this.fitLine(line, cols));
126
+ if (!this.focused) {
127
+ for (let i = 0; i < clipped.length; i++) {
128
+ clipped[i] = this.stripMarkers(clipped[i]);
129
+ }
130
+ }
131
+ this.renderCache = { cols, rows, lines: clipped };
132
+ this.isDirty = false;
133
+ return clipped;
134
+ }
135
+ handleInput(data) {
136
+ if (this.focused) {
137
+ // TitledEditor's handleInput returns void, so we route all input to it
138
+ // when focused and assume it was consumed
139
+ this.editor.handleInput(data);
140
+ this.dirty();
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+ handleWheel(direction, lines) {
146
+ // Scroll the transcript: negative = toward older, positive = toward newer
147
+ const scrollLines = direction === 'up' ? -lines : lines;
148
+ this.conversation.viewport.scrollBy(scrollLines);
149
+ this.dirty();
150
+ return true;
151
+ }
152
+ setFocused(value) {
153
+ if (this.focused === value && this.editor.focused === value)
154
+ return;
155
+ this.focused = value;
156
+ this.editor.focused = value;
157
+ if (value)
158
+ this.unreadCount = 0;
159
+ this.isDirty = true;
160
+ if (this.onDirtyCallback)
161
+ this.onDirtyCallback();
162
+ }
163
+ summary() {
164
+ const parts = [];
165
+ // A zero viewport is the composition's hidden-region signal in single mode.
166
+ if (this.isHidden() && this.unreadCount > 0)
167
+ parts.push(`${this.unreadCount}✉`);
168
+ // Show connection state
169
+ if (this.connectionState === 'connecting') {
170
+ parts.push('connecting…');
171
+ }
172
+ else if (this.connectionState === 'reconnecting') {
173
+ parts.push('reconnecting…');
174
+ }
175
+ else if (this.connectionState === 'dormant') {
176
+ parts.push('dormant');
177
+ }
178
+ else if (this.connectionState === 'finished') {
179
+ parts.push('finished');
180
+ }
181
+ else if (this.connectionState === 'gone') {
182
+ parts.push('gone');
183
+ }
184
+ return parts.join(' · ');
185
+ }
186
+ onDirty(fn) {
187
+ this.onDirtyCallback = fn;
188
+ }
189
+ close() {
190
+ this.conversation.close();
191
+ }
192
+ /** Open the connection to the companion broker. */
193
+ async open() {
194
+ try {
195
+ await this.conversation.open();
196
+ }
197
+ catch (error) {
198
+ // Already handled by the conversation's notice callback
199
+ }
200
+ }
201
+ /** Submit text to the companion. */
202
+ submit(text) {
203
+ this.editor.setText('');
204
+ this.conversation.submit(text);
205
+ }
206
+ renderStateLine() {
207
+ const parts = [];
208
+ if (this.connectionState === 'connecting') {
209
+ parts.push('connecting…');
210
+ }
211
+ else if (this.connectionState === 'reconnecting') {
212
+ parts.push('reconnecting…');
213
+ }
214
+ else if (this.connectionState === 'dormant') {
215
+ parts.push('dormant');
216
+ }
217
+ else if (this.connectionState === 'finished') {
218
+ parts.push('finished');
219
+ }
220
+ else if (this.connectionState === 'gone') {
221
+ parts.push('gone');
222
+ }
223
+ if (this.noticeText)
224
+ parts.push(this.noticeText);
225
+ return this.fitLine(parts.join(' · '), this.cols);
226
+ }
227
+ fitLine(line, cols) {
228
+ const markerAt = line.indexOf(CURSOR_MARKER);
229
+ const fitted = this.padLine(clipLine(line, cols), cols);
230
+ if (markerAt === -1)
231
+ return fitted;
232
+ // The shared clipper preserves both ANSI and pi-tui APC sequences whole.
233
+ // Keep the zero-width marker only when its cursor cell remains inside this
234
+ // region's rectangle.
235
+ return visibleWidth(line.slice(0, markerAt)) < cols ? fitted : this.stripMarkers(fitted);
236
+ }
237
+ padLine(line, cols) {
238
+ return line + ' '.repeat(Math.max(0, cols - visibleWidth(this.stripMarkers(line))));
239
+ }
240
+ stripMarkers(line) {
241
+ return line.split(CURSOR_MARKER).join('');
242
+ }
243
+ onConnectionChange(state) {
244
+ this.connectionState = state;
245
+ this.isDirty = true;
246
+ if (this.onDirtyCallback)
247
+ this.onDirtyCallback();
248
+ }
249
+ showNotice(text) {
250
+ this.noticeText = text || undefined;
251
+ }
252
+ isHidden() {
253
+ return this.cols === 0 || this.rows === 0;
254
+ }
255
+ /** One arriving message while the composition gives this region no rectangle. */
256
+ countUnread() {
257
+ if (this.isHidden())
258
+ this.unreadCount++;
259
+ }
260
+ dirty() {
261
+ this.isDirty = true;
262
+ if (this.onDirtyCallback)
263
+ this.onDirtyCallback();
264
+ }
265
+ }
@@ -0,0 +1,38 @@
1
+ export type SubmitClassification = {
2
+ kind: 'empty';
3
+ } | {
4
+ kind: 'slash';
5
+ trimmed: string;
6
+ } | {
7
+ kind: 'dormant';
8
+ text: string;
9
+ trimmed: string;
10
+ } | {
11
+ kind: 'bash';
12
+ command: string;
13
+ excludeFromContext: boolean;
14
+ trimmed: string;
15
+ } | {
16
+ kind: 'steer';
17
+ text: string;
18
+ trimmed: string;
19
+ } | {
20
+ kind: 'prompt';
21
+ text: string;
22
+ trimmed: string;
23
+ };
24
+ /**
25
+ * Classify a text submission to determine the appropriate message kind.
26
+ *
27
+ * The branch order is load-bearing and must match the original handleSubmit order exactly:
28
+ * 1. Empty trim → nothing
29
+ * 2. Slash command → handled by the viewer caller
30
+ * 3. Dormant + can wake → dormant wake (if `canWakeDormant`)
31
+ * 4. Bash (! or !!) → bash
32
+ * 5. Streaming ? steer : prompt → prompt or steer
33
+ */
34
+ export declare function classifySubmit(text: string, ctx: {
35
+ isDormant: boolean;
36
+ canWakeDormant: boolean;
37
+ isStreaming: boolean;
38
+ }): SubmitClassification;
@@ -0,0 +1,51 @@
1
+ // conversation/submit.ts — classification of text submission intent.
2
+ //
3
+ // Extracted from InputController.handleSubmit (T-A4): classify user input before
4
+ // deciding whether to dispatch a slash command, wake a dormant node, run bash,
5
+ // or send a prompt/steer frame. The load-bearing branch order is preserved exactly:
6
+ // empty-trim → slash → dormant (isDormant && canWakeDormant) → bash (! / !!) →
7
+ // streaming ? steer : prompt.
8
+ //
9
+ // Caller applies slash dispatch, readOnlySlashRefusal, expandImagePlaceholders,
10
+ // addToHistory, setText(''), and clearPastedImages() — this extraction is pure
11
+ // classification, not control flow.
12
+ /**
13
+ * Classify a text submission to determine the appropriate message kind.
14
+ *
15
+ * The branch order is load-bearing and must match the original handleSubmit order exactly:
16
+ * 1. Empty trim → nothing
17
+ * 2. Slash command → handled by the viewer caller
18
+ * 3. Dormant + can wake → dormant wake (if `canWakeDormant`)
19
+ * 4. Bash (! or !!) → bash
20
+ * 5. Streaming ? steer : prompt → prompt or steer
21
+ */
22
+ export function classifySubmit(text, ctx) {
23
+ const trimmed = text.trim();
24
+ // Branch 1: empty-trim
25
+ if (!trimmed) {
26
+ return { kind: 'empty' };
27
+ }
28
+ // Branch 2: slash. Dispatch and read-only refusal remain caller-owned.
29
+ if (trimmed.startsWith('/')) {
30
+ return { kind: 'slash', trimmed };
31
+ }
32
+ // Branch 3: dormant
33
+ if (ctx.isDormant && ctx.canWakeDormant) {
34
+ return { kind: 'dormant', text, trimmed };
35
+ }
36
+ // Branch 4: bash
37
+ if (trimmed.startsWith('!')) {
38
+ const excludeFromContext = trimmed.startsWith('!!');
39
+ const command = trimmed.slice(excludeFromContext ? 2 : 1).trim();
40
+ if (command === '') {
41
+ // bare `!`/`!!` with no command → empty
42
+ return { kind: 'empty' };
43
+ }
44
+ return { kind: 'bash', command, excludeFromContext, trimmed };
45
+ }
46
+ // Branch 5: steer or prompt
47
+ if (ctx.isStreaming) {
48
+ return { kind: 'steer', text, trimmed };
49
+ }
50
+ return { kind: 'prompt', text, trimmed };
51
+ }
@@ -0,0 +1,247 @@
1
+ import assert from 'node:assert/strict';
2
+ import { createServer } from 'node:http';
3
+ import { existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { claimExists, claimTicket } from '#core/human/claim';
7
+ import { atomicWriteJson, deckPath } from '#core/human/convention';
8
+ import { ticketDir } from '#core/human/root';
9
+ import { cancelTicket, completeDeck, submitDeck, submitReview, takeoverAndCompleteDeck } from '#core/human/tickets';
10
+ import { inboxLayout } from '../layout.js';
11
+ import { InboxController } from '../controller.js';
12
+ import { buildInboxLines } from '../tui.js';
13
+ const key = (part = {}) => ({ ctrl: false, meta: false, upArrow: false, downArrow: false, leftArrow: false, rightArrow: false, wordLeft: false, wordRight: false, home: false, end: false, pageUp: false, pageDown: false, del: false, return: false, newline: false, escape: false, tab: false, backspace: false, ...part });
14
+ function fakeHost() {
15
+ let closed = false;
16
+ const cleanup = [];
17
+ const handle = {
18
+ close() {
19
+ if (closed)
20
+ return;
21
+ closed = true;
22
+ for (const fn of cleanup)
23
+ void fn();
24
+ cleanup.length = 0;
25
+ },
26
+ invalidate() { },
27
+ onClose(fn) {
28
+ if (!closed)
29
+ cleanup.push(fn);
30
+ },
31
+ };
32
+ return {
33
+ host: {
34
+ capabilities: { remote: false, canFocusNode: false },
35
+ mount() { return handle; },
36
+ notice() { },
37
+ suspendForChild() { return () => { }; },
38
+ focusNode: async () => false,
39
+ pickReviewFile: async () => null,
40
+ },
41
+ };
42
+ }
43
+ const tui = {};
44
+ const canvasSource = {};
45
+ const makeController = (options) => {
46
+ const harness = fakeHost();
47
+ const controller = new InboxController({
48
+ host: harness.host,
49
+ tui,
50
+ canvasSource,
51
+ onDirty: () => { },
52
+ onClosed: () => { },
53
+ ...options,
54
+ });
55
+ return controller;
56
+ };
57
+ const item = (id) => ({ dir: `/tickets/${id}`, id, kind: 'deck', title: id, subtitle: `${id} requires your attention.`, interactionCount: 1, source: {}, blockedSince: '2025-01-01T00:00:00.000Z' });
58
+ const tick = () => new Promise((resolve) => setImmediate(resolve));
59
+ function select(controller, dir) {
60
+ const count = controller.snapshot().items.length;
61
+ const matches = () => {
62
+ const selected = controller.snapshot().selectedDir;
63
+ return selected !== undefined && realpathSync(selected) === realpathSync(dir);
64
+ };
65
+ for (let i = 0; i < count && !matches(); i++)
66
+ controller.handleKey('j', key());
67
+ assert.ok(matches(), `expected ${dir} to be selectable`);
68
+ }
69
+ async function waitFor(predicate, message) {
70
+ const deadline = Date.now() + 1_000;
71
+ while (!predicate()) {
72
+ if (Date.now() >= deadline)
73
+ throw new Error(message);
74
+ await new Promise((resolve) => setTimeout(resolve, 10));
75
+ }
76
+ }
77
+ assert.deepEqual(inboxLayout(120, 30), { mode: 'two-column', listWidth: 40, detailWidth: 79, height: 30 });
78
+ assert.equal(inboxLayout(80, 24).mode, 'list');
79
+ assert.equal(inboxLayout(80, 24, 'detail').mode, 'detail');
80
+ assert.equal(inboxLayout(59, 18).mode, 'minimum');
81
+ const crowded = Array.from({ length: 20 }, (_, index) => item(`ticket-${index}`));
82
+ const visibleRows = buildInboxLines(crowded, 40, 10, 8).join('\n');
83
+ assert.ok(visibleRows.includes('ticket-10'), 'list viewport keeps the selected ticket visible');
84
+ assert.ok(visibleRows.includes('↑') && visibleRows.includes('↓'), 'list viewport signals tickets above and below');
85
+ const claimedRows = buildInboxLines([{ ...item('claimed'), subtitle: 'Approve the deployment because production is blocked.', claim: { owner: 'silas', claimedAt: '2025-01-01T00:00:00.000Z', heartbeatAt: '2025-01-01T00:00:00.000Z' } }], 120, 0).join('\n');
86
+ assert.ok(claimedRows.includes('claimed by silas · Approve the deployment because production is blocked.'), 'claimed rows compose claim status with the required subtitle');
87
+ let rows = [item('b'), item('a')];
88
+ const stable = makeController({ cols: 100, rows: 24, scan: () => rows });
89
+ assert.ok(!stable.render().join('\n').includes('unknown source'), 'passive detail omits missing source labels instead of rendering a fallback');
90
+ stable.handleKey('j', key());
91
+ assert.equal(stable.snapshot().selectedDir, '/tickets/a');
92
+ rows = [item('new'), ...rows];
93
+ stable.rescan();
94
+ assert.equal(stable.snapshot().selectedDir, '/tickets/a', 'prepend preserves selected ticket id');
95
+ rows = [item('new'), item('b')];
96
+ stable.rescan();
97
+ assert.equal(stable.snapshot().selectedDir, '/tickets/b', 'removed selection chooses the prior index');
98
+ let mode = 'success';
99
+ async function startDaemon() {
100
+ const server = createServer(async (req, res) => {
101
+ if (!req.url?.endsWith('/resolve')) {
102
+ res.statusCode = 404;
103
+ res.end(JSON.stringify({ error: { code: 'not_found', message: 'not found' } }));
104
+ return;
105
+ }
106
+ if (mode === 'unreachable') {
107
+ res.statusCode = 503;
108
+ res.end(JSON.stringify({ error: { code: 'daemon_unavailable', message: 'offline' } }));
109
+ return;
110
+ }
111
+ const body = await new Promise((resolve) => {
112
+ let text = '';
113
+ req.on('data', (chunk) => { text += chunk; });
114
+ req.on('end', () => resolve(text));
115
+ });
116
+ const nodeId = req.url.split('/').at(-2);
117
+ if (mode === 'already_resolved') {
118
+ res.statusCode = 409;
119
+ res.end(JSON.stringify({ error: { code: 'ticket_already_resolved', message: 'ticket already resolved' } }));
120
+ return;
121
+ }
122
+ const request = JSON.parse(body);
123
+ if (request.responses === undefined)
124
+ throw new Error('expected a deck resolution');
125
+ takeoverAndCompleteDeck(ticketDir(nodeId), request.responses, { host: 'daemon', pid: 1 });
126
+ if (mode === 'delivery_failed') {
127
+ res.statusCode = 500;
128
+ res.end(JSON.stringify({ error: { code: 'delivery_failed', message: 'final report delivery failed' } }));
129
+ return;
130
+ }
131
+ res.end(JSON.stringify({ delivered: true, kind: 'deck' }));
132
+ });
133
+ await new Promise((resolve) => server.listen(join(process.env['CRTR_HOME'], 'crtrd.sock'), resolve));
134
+ return server;
135
+ }
136
+ const temp = mkdtempSync(join(tmpdir(), 'crtr-inbox-controller-'));
137
+ process.env['CRTR_HOME'] = temp;
138
+ const server = await startDaemon();
139
+ const deck = (id) => ({ title: id, source: { blockedSince: new Date().toISOString(), sessionName: 'Release planning', nodeId: 'node-private-123' }, interactions: [{ id: 'answer', title: 'Answer', subtitle: 'Answer requires your attention.', options: [{ id: 'yes', label: 'Yes', shortcut: 'y' }] }] });
140
+ try {
141
+ const first = submitDeck({ dir: ticketDir('first'), deck: deck('first') });
142
+ const active = makeController({ cols: 100, rows: 24 });
143
+ const initialInbox = active.render().join('\n');
144
+ assert.ok(initialInbox.includes('Answer requires your attention.'), 'the selected ticket preview renders its required subtitle');
145
+ assert.ok(initialInbox.includes('1 question'), 'the selected ticket preview renders its question count');
146
+ assert.ok(!initialInbox.includes('Release planning') && !initialInbox.includes('node-private-123'), 'the selected ticket preview omits source metadata');
147
+ active.activate();
148
+ active.handleKey('y', key());
149
+ await waitFor(() => existsSync(join(first.dir, 'response.json')), 'a deck resolution crosses the controller daemon seam');
150
+ assert.equal(existsSync(join(first.dir, 'response.json')), true, 'a deck resolution crosses the controller daemon seam');
151
+ await waitFor(() => active.snapshot().screen === 'list', 'a successful daemon resolution unmounts the ticket');
152
+ assert.equal(active.snapshot().screen, 'list', 'a successful daemon resolution unmounts the ticket');
153
+ active.close();
154
+ const editing = submitDeck({ dir: ticketDir('editing'), deck: { title: 'Editing', interactions: [{ id: 'note', title: 'Notes', subtitle: 'Notes require your attention.', options: [], allowFreetext: true }] } });
155
+ const editingController = makeController({ cols: 100, rows: 24 });
156
+ select(editingController, editing.dir);
157
+ editingController.activate();
158
+ editingController.handleKey('r', key());
159
+ for (const char of 'draft')
160
+ editingController.handleKey(char, key());
161
+ submitDeck({ dir: ticketDir('arrival'), deck: deck('arrival') });
162
+ editingController.rescan();
163
+ assert.equal(realpathSync(editingController.snapshot().selectedDir), realpathSync(editing.dir), 'arrival does not steal active selection');
164
+ assert.equal(editingController.snapshot().inputBuffer, 'draft', 'arrival does not alter freetext input');
165
+ cancelTicket(editing.dir, { reason: 'canceled while editing' });
166
+ editingController.rescan();
167
+ assert.equal(editingController.snapshot().screen, 'list', 'cancellation invalidation returns to list');
168
+ assert.notEqual(editingController.snapshot().selectedDir, editing.dir, 'cancellation moves selection away from the resolved ticket');
169
+ editingController.close();
170
+ const reviewSourcePreview = join(temp, 'review-preview.ts');
171
+ writeFileSync(reviewSourcePreview, 'export const reviewPreview = true;\n');
172
+ const reviewPreview = submitReview({ dir: ticketDir('review-preview'), review: { file: reviewSourcePreview, title: 'Review source', subtitle: 'Review this source before approving the release.', source: {} } });
173
+ const reviewPreviewController = makeController({ cols: 100, rows: 24 });
174
+ select(reviewPreviewController, reviewPreview.dir);
175
+ const renderedReviewPreview = reviewPreviewController.render().join('\n');
176
+ assert.ok(renderedReviewPreview.includes('Review this source before approving the release.'), 'passive review detail renders its authored subtitle');
177
+ assert.ok(renderedReviewPreview.includes('1 file to review'), 'passive review detail identifies the compact review payload');
178
+ assert.ok(!renderedReviewPreview.includes('reviewPreview = true'), 'passive review detail omits the source document');
179
+ reviewPreviewController.close();
180
+ const already = submitDeck({ dir: ticketDir('already'), deck: deck('already') });
181
+ const alreadyController = makeController({ cols: 100, rows: 24 });
182
+ alreadyController.activate();
183
+ cancelTicket(already.dir, { reason: 'resolved elsewhere' });
184
+ mode = 'already_resolved';
185
+ alreadyController.handleKey('y', key());
186
+ await waitFor(() => alreadyController.snapshot().screen === 'list', 'already_resolved unmounts instead of offering a stale resubmit');
187
+ assert.equal(alreadyController.snapshot().screen, 'list', 'already_resolved unmounts instead of offering a stale resubmit');
188
+ assert.equal(claimExists(already.dir), true, 'already_resolved retains the daemon-taken claim');
189
+ alreadyController.close();
190
+ const delivery = submitDeck({ dir: ticketDir('delivery'), deck: deck('delivery') });
191
+ mode = 'delivery_failed';
192
+ const deliveryController = makeController({ cols: 100, rows: 24 });
193
+ deliveryController.activate();
194
+ deliveryController.handleKey('y', key());
195
+ await waitFor(() => deliveryController.snapshot().screen === 'list', 'delivery_failed unmounts because the canonical answer is already recorded');
196
+ assert.equal(deliveryController.snapshot().screen, 'list', 'delivery_failed unmounts because the canonical answer is already recorded');
197
+ assert.equal(existsSync(join(delivery.dir, 'response.json')), true, 'delivery_failed still leaves the canonical response on disk');
198
+ deliveryController.close();
199
+ const retry = submitDeck({ dir: ticketDir('retry'), deck: deck('retry') });
200
+ mode = 'unreachable';
201
+ const retryController = makeController({ cols: 100, rows: 24 });
202
+ retryController.activate();
203
+ retryController.handleKey('y', key());
204
+ await tick();
205
+ assert.equal(retryController.snapshot().screen, 'detail', 'unreachable keeps the mounted answer available for retry');
206
+ assert.equal(existsSync(join(retry.dir, 'response.json')), false, 'unreachable does not manufacture a local completion');
207
+ retryController.close();
208
+ // Notification acknowledgement remains explicit: opening or abandoning a
209
+ // notification does not create a response, while its canonical completion
210
+ // still rejects a missing acknowledgement.
211
+ mode = 'success';
212
+ const notification = submitDeck({ dir: ticketDir('notification'), deck: { title: 'Notice', interactions: [{ id: 'notice', title: 'Notice', subtitle: 'Read the complete notice.', kind: 'notify', body: 'Notification body that must be visible before acknowledgement.', options: [{ id: 'ok', label: 'OK' }] }] } });
213
+ const notificationController = makeController({ cols: 100, rows: 24 });
214
+ select(notificationController, notification.dir);
215
+ const renderedNotificationPreview = notificationController.render().join('\n');
216
+ assert.ok(renderedNotificationPreview.includes('Read the complete notice') && renderedNotificationPreview.includes('1 question'), 'passive deck preview renders only its subtitle and question count');
217
+ assert.ok(!renderedNotificationPreview.includes('Notification body'), 'passive deck preview omits interaction bodies');
218
+ assert.ok(renderedNotificationPreview.includes('Enter') && renderedNotificationPreview.includes('c comment'), 'preview keeps full-ticket controls visible');
219
+ notificationController.activate();
220
+ notificationController.handleKey('q', key());
221
+ assert.equal(existsSync(join(notification.dir, 'response.json')), false, 'leaving a notification never acknowledges it');
222
+ notificationController.close();
223
+ const notificationClaim = claimTicket(notification.dir);
224
+ assert.notEqual(notificationClaim, null);
225
+ assert.throws(() => completeDeck(notification.dir, [], notificationClaim.token), /explicit acknowledgement/, 'canonical completion rejects a notification without acknowledgement');
226
+ assert.throws(() => completeDeck(notification.dir, [{ id: 'notice' }], notificationClaim.token), /explicit acknowledgement/, 'an empty notification response is not acknowledgement');
227
+ assert.equal(completeDeck(notification.dir, [{ id: 'notice', selectedOptionId: 'ok' }], notificationClaim.token).won, true, 'an explicit acknowledgement completes the notification');
228
+ const replacement = submitDeck({ dir: ticketDir('replacement'), deck: { title: 'Replacement', interactions: [
229
+ { id: 'keep', title: 'Keep', subtitle: 'Keep requires your attention.', options: [{ id: 'yes', label: 'Yes', shortcut: 'y' }] },
230
+ { id: 'drop', title: 'Drop', subtitle: 'Drop requires your attention.', options: [{ id: 'no', label: 'No', shortcut: 'n' }] },
231
+ ] } });
232
+ const replacementController = makeController({ cols: 100, rows: 24 });
233
+ select(replacementController, replacement.dir);
234
+ replacementController.activate();
235
+ replacementController.handleKey('y', key());
236
+ atomicWriteJson(deckPath(replacement.dir), { title: 'Replacement', interactions: [{ id: 'keep', title: 'Keep', subtitle: 'Keep requires your attention.', options: [{ id: 'yes', label: 'Yes', shortcut: 'y' }] }] });
237
+ replacementController.reloadSelectedDeck();
238
+ assert.ok(replacementController.render().join('\n').includes('Current: Yes'), 'deck replacement keeps a surviving answer');
239
+ replacementController.close();
240
+ }
241
+ finally {
242
+ server.closeAllConnections();
243
+ await new Promise((resolve) => server.close(() => resolve()));
244
+ rmSync(temp, { recursive: true, force: true });
245
+ delete process.env['CRTR_HOME'];
246
+ }
247
+ console.log('inbox controller tests passed');