@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
@@ -7,14 +7,15 @@
7
7
  // UI unless you switch to it. Bring one forefront with select-window (within a
8
8
  // root) or switch-client + select-window (across roots). done/dead nodes close
9
9
  // their window; reviving opens a fresh one.
10
- import { spawnSync } from 'node:child_process';
11
- import { readFileSync, rmSync } from 'node:fs';
12
- import { homedir } from 'node:os';
10
+ import { execFile, spawnSync } from 'node:child_process';
11
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
12
+ import { homedir, tmpdir } from 'node:os';
13
13
  import { join } from 'node:path';
14
- import { ATTACH_CONTROL_BINDINGS, BINDING_CATALOG, encodeAttachControlInput, formatGesture, resolveUserKeybindings, } from '../keybindings/index.js';
14
+ import { ATTACH_CONTROL_BINDINGS, BINDING_CATALOG, BINDING_IDS, encodeAttachControlInput, formatGesture, isAttachPaneBinding, resolveUserKeybindingSettings, } from '../keybindings/index.js';
15
+ import { normalizeTmuxPassthrough } from '../config.js';
16
+ import { withExclusiveDirectoryLock } from '../human/convention.js';
15
17
  import { bundledPiCommandForShell } from './pi-cli.js';
16
18
  import { surfaceTmuxStyleArgs } from './surface-bg.js';
17
- import { inboxToggleTmuxCommand } from '@crouton-kit/humanloop';
18
19
  // ---------------------------------------------------------------------------
19
20
  // Shell quoting + tmux invocation
20
21
  // ---------------------------------------------------------------------------
@@ -30,6 +31,9 @@ function tmux(args) {
30
31
  stderr: (r.stderr ?? '').trim(),
31
32
  };
32
33
  }
34
+ function tmuxOnSocket(socket, args) {
35
+ return socket === undefined || socket === '' ? tmux(args) : tmux(['-S', socket, ...args]);
36
+ }
33
37
  export function inTmux() {
34
38
  return process.env['TMUX'] !== undefined && process.env['TMUX'] !== '';
35
39
  }
@@ -311,8 +315,7 @@ export function listLivePanes() {
311
315
  return new Set(r.stdout.split('\n').filter((p) => p !== ''));
312
316
  }
313
317
  /** The working directory of a pane (`display-message -p -t <pane>
314
- * '#{pane_current_path}'`). Used to preserve a view monitor's cwd across a
315
- * view-cycle respawn so project-scoped views still resolve. Null if tmux fails. */
318
+ * '#{pane_current_path}'`). Null if tmux fails. */
316
319
  export function paneCurrentPath(pane) {
317
320
  const r = tmux(['display-message', '-p', '-t', pane, '#{pane_current_path}']);
318
321
  return r.ok && r.stdout !== '' ? r.stdout : null;
@@ -329,8 +332,6 @@ export function displayMessage(message, pane, ms = 4000) {
329
332
  return tmux(args).ok;
330
333
  }
331
334
  /** Set a PANE-scoped tmux option (`tmux set-option -p -t <pane> <name> <value>`).
332
- * Used to tag a pane with the view id it currently hosts (`@crtr_view`) so the
333
- * view-nav cycle can read it back and switch to the next/prev view in place.
334
335
  * Best-effort; never throws. */
335
336
  export function setPaneOption(pane, name, value) {
336
337
  return tmux(['set-option', '-p', '-t', pane, name, value]).ok;
@@ -342,6 +343,121 @@ export function getPaneOption(pane, name) {
342
343
  const r = tmux(['show-options', '-p', '-t', pane, '-q', '-v', name]);
343
344
  return r.ok ? r.stdout : undefined;
344
345
  }
346
+ /** Pane option recording when an attach viewer was last started or focused.
347
+ * `liveViewerPanes` uses it to select the most recently used viewer. */
348
+ export const VIEWER_STAMP_OPTION = '@crtr_viewer_at';
349
+ let lastViewerStampMs = 0;
350
+ let viewerStampQueue = Promise.resolve();
351
+ /** Stamp a viewer pane without blocking the input path that observed focus.
352
+ * Stamps are monotonically increasing and their tmux writes are serialized, so
353
+ * two rapid focus events cannot leave an older pane with the newer stamp. */
354
+ export function stampViewerPaneAsync(pane) {
355
+ if (pane === '')
356
+ return;
357
+ const stampMs = Math.max(Date.now(), lastViewerStampMs + 1);
358
+ lastViewerStampMs = stampMs;
359
+ viewerStampQueue = viewerStampQueue.then(() => new Promise((resolve) => {
360
+ try {
361
+ execFile('tmux', ['set-option', '-p', '-t', pane, VIEWER_STAMP_OPTION, String(stampMs)], () => resolve());
362
+ }
363
+ catch {
364
+ resolve();
365
+ }
366
+ }));
367
+ }
368
+ function escapeRegex(text) {
369
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
370
+ }
371
+ function looksLikeViewerCommand(command, nodeId) {
372
+ const unquoted = command.replace(/^"|"$/g, '');
373
+ return new RegExp(`(?:^|\\s)surface\\s+attach\\s+to\\s+${escapeRegex(nodeId)}(?:\\s|$)`).test(unquoted);
374
+ }
375
+ function readProcessTree() {
376
+ const result = spawnSync('ps', ['-ax', '-o', 'pid=,ppid=,command='], { encoding: 'utf8' });
377
+ if (result.status !== 0 || typeof result.stdout !== 'string')
378
+ return null;
379
+ const childrenOf = new Map();
380
+ const commandOf = new Map();
381
+ for (const line of result.stdout.split('\n')) {
382
+ const match = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
383
+ if (match === null)
384
+ continue;
385
+ const pid = Number(match[1]);
386
+ const parentPid = Number(match[2]);
387
+ commandOf.set(pid, match[3]);
388
+ const children = childrenOf.get(parentPid) ?? [];
389
+ children.push(pid);
390
+ childrenOf.set(parentPid, children);
391
+ }
392
+ return { childrenOf, commandOf };
393
+ }
394
+ function processTreeHasViewer(tree, rootPid, nodeId) {
395
+ const pending = [rootPid];
396
+ while (pending.length > 0) {
397
+ const pid = pending.pop();
398
+ if (looksLikeViewerCommand(tree.commandOf.get(pid) ?? '', nodeId))
399
+ return true;
400
+ const children = tree.childrenOf.get(pid);
401
+ if (children !== undefined)
402
+ pending.push(...children);
403
+ }
404
+ return false;
405
+ }
406
+ /** List viewer panes tagged by a running local attach viewer. Pane tags can
407
+ * survive a killed process, so every tag must match a viewer for the same node.
408
+ * Shell-hosted viewers are recognized through their live child process. Unstamped
409
+ * valid viewers remain eligible at stamp zero while an older viewer rolls forward. */
410
+ export function liveViewerPanes(socket) {
411
+ const listed = tmuxOnSocket(socket, [
412
+ 'list-panes', '-a', '-F',
413
+ '#{pane_id}\t#{session_name}\t#{@crtr_node}\t#{@crtr_viewer_at}',
414
+ ]);
415
+ if (!listed.ok || listed.stdout === '')
416
+ return [];
417
+ const viewers = [];
418
+ let processTree;
419
+ for (const line of listed.stdout.split('\n')) {
420
+ const [pane, session, nodeId, stamp] = line.split('\t');
421
+ if (pane === undefined || pane === '' || session === undefined || session === '' || nodeId === undefined || nodeId === '')
422
+ continue;
423
+ const command = tmuxOnSocket(socket, [
424
+ 'display-message', '-p', '-t', pane,
425
+ '#{pane_current_command}\t#{pane_start_command}\t#{pane_pid}',
426
+ ]);
427
+ if (!command.ok)
428
+ continue;
429
+ const [currentCommand = '', startCommand = '', pid] = command.stdout.split('\t');
430
+ const directViewer = looksLikeViewerCommand(currentCommand, nodeId) || looksLikeViewerCommand(startCommand, nodeId);
431
+ if (!directViewer) {
432
+ processTree ??= readProcessTree();
433
+ const rootPid = Number.parseInt(pid, 10);
434
+ if (processTree === null || !Number.isInteger(rootPid) || rootPid <= 0 || !processTreeHasViewer(processTree, rootPid, nodeId))
435
+ continue;
436
+ }
437
+ const parsedStamp = Number.parseInt(stamp ?? '', 10);
438
+ viewers.push({
439
+ pane,
440
+ session,
441
+ nodeId,
442
+ stampMs: Number.isSafeInteger(parsedStamp) && parsedStamp >= 0 ? parsedStamp : 0,
443
+ });
444
+ }
445
+ return viewers;
446
+ }
447
+ /** Deliver one private attach-control sequence literally into a viewer pane.
448
+ * `-l` bypasses tmux key-table lookup, so delivery cannot re-trigger a root
449
+ * binding that initiated it. */
450
+ export function sendAttachControl(socket, pane, actionId) {
451
+ return tmuxOnSocket(socket, ['send-keys', '-l', '-t', pane, encodeAttachControlInput(actionId)]).ok;
452
+ }
453
+ /** Show a tmux status-line message on a specific client when one is known. */
454
+ export function displayClientMessage(socket, client, text) {
455
+ const args = ['display-message'];
456
+ if (client !== undefined && client !== '')
457
+ args.push('-c', client);
458
+ args.push(text);
459
+ return tmuxOnSocket(socket, args).ok;
460
+ }
345
461
  /** The `respawn-pane -k` argv for `opts`. `-k` kills the pane's current process
346
462
  * (e.g. a yielding pi) and re-execs `command` in the SAME pane, preserving its
347
463
  * `%id` (§1.5 F3: a frozen focus pane resumes in place, no new window). The
@@ -472,12 +588,12 @@ const TMUX_BINDING_MANIFEST = '@crtr_keybindings_manifest';
472
588
  const ROOT_TABLE = 'root';
473
589
  const LEAF_MENU_ACTIONS = {
474
590
  'crtr.tmux.menu.promote': { description: 'promote to orchestrator', action: { kind: 'keys', keys: '/promote' } },
475
- 'crtr.tmux.menu.resume': { description: 'resume node', action: { kind: 'keys', keys: '/resume-node' } },
591
+ 'crtr.tmux.menu.resume': { description: 'find node', action: { kind: 'keys', keys: '/resume-node' } },
476
592
  'crtr.tmux.menu.fork': { description: 'fork into idle root', action: { kind: 'direct', run: 'node fork --pane {pane}' } },
477
- 'crtr.tmux.menu.demote': { description: 'demote to terminal', action: { kind: 'command', run: 'node lifecycle demote --pane {pane}' } },
478
- 'crtr.tmux.menu.detach': { description: 'detach to background', action: { kind: 'command', run: 'node lifecycle demote --pane {pane} --detach' } },
593
+ 'crtr.tmux.menu.demote': { description: 'demote (finish when done)', action: { kind: 'command', run: 'node lifecycle demote --pane {pane}' } },
594
+ 'crtr.tmux.menu.detach': { description: 'demote + close viewer', action: { kind: 'command', run: 'node lifecycle demote --pane {pane} --detach' } },
479
595
  'crtr.tmux.menu.close-subtree': { description: 'close agent + subtree', action: { kind: 'command', run: 'node lifecycle close --pane {pane}' } },
480
- 'crtr.tmux.menu.background-bash': { description: 'background bash command', action: { kind: 'direct', run: 'node bash background --pane {pane}' } },
596
+ 'crtr.tmux.menu.background-bash': { description: 'background running bash', action: { kind: 'direct', run: 'node bash background --pane {pane}' } },
481
597
  // M-S-r, not M-R: with extended-keys csi-u tmux encodes M-R as alt+uppercase-R
482
598
  // WITHOUT the shift modifier bit, which pi-tui's `alt+shift+r` matcher rejects;
483
599
  // M-S-r carries shift+alt and lands as the same chord the keyboard produces.
@@ -486,10 +602,11 @@ const LEAF_MENU_ACTIONS = {
486
602
  'crtr.tmux.menu.fold-tools': { description: 'fold finished tool calls', action: { kind: 'keys', keys: '/fold-tools' } },
487
603
  'crtr.tmux.menu.context': { description: 'context + reports', action: { kind: 'keys', keys: '/context' } },
488
604
  'crtr.tmux.menu.providers': { description: 'settings', action: { kind: 'popup', run: 'sys setup' } },
489
- 'crtr.tmux.menu.graph': { description: 'graph view', action: { kind: 'keys', keys: '/graph' } },
490
- 'crtr.tmux.menu.focus-manager': { description: 'focus manager', action: { kind: 'command', run: 'surface node focus {manager}' } },
605
+ 'crtr.tmux.menu.graph': { description: 'canvas graph', action: { kind: 'keys', keys: '/graph' } },
606
+ 'crtr.tmux.menu.focus-manager': { description: 'go to manager', action: { kind: 'command', run: 'surface node focus {manager}' } },
491
607
  'crtr.tmux.menu.copy-id': { description: 'copy node id', action: { kind: 'command', run: 'surface node id --pane {pane}' } },
492
608
  'crtr.tmux.menu.reload-viewer': { description: 'reload viewer', action: { kind: 'direct', run: 'surface reload --pane {pane}' } },
609
+ 'crtr.tmux.menu.recap': { description: 'generate recap', action: { kind: 'keys', keys: '/recap' } },
493
610
  'crtr.tmux.menu.pin': { description: 'pin conversation', action: { kind: 'direct', run: 'surface node pin --pane {pane}' } },
494
611
  'crtr.tmux.menu.issue.idea': { description: 'idea', action: { kind: 'popup', run: 'canvas issue new --type idea' } },
495
612
  'crtr.tmux.menu.issue.bug': { description: 'bug', action: { kind: 'popup', run: 'canvas issue new --type bug' } },
@@ -601,10 +718,17 @@ const TMUX_NAMED_KEYS = {
601
718
  space: 'Space', up: 'Up', down: 'Down', left: 'Left', right: 'Right',
602
719
  pageup: 'PPage', pagedown: 'NPage', home: 'Home', end: 'End',
603
720
  };
604
- function tmuxKey(stroke) {
721
+ function normalizedStrokeParts(stroke) {
722
+ if (stroke === '+')
723
+ return { key: '+', modifiers: [] };
724
+ if (stroke.endsWith('++'))
725
+ return { key: '+', modifiers: stroke.slice(0, -2).split('+') };
605
726
  const parts = stroke.split('+');
606
- let key = parts.pop();
607
- const modifiers = new Set(parts);
727
+ return { key: parts.pop(), modifiers: parts };
728
+ }
729
+ function tmuxKey(stroke) {
730
+ let { key, modifiers: modifierParts } = normalizedStrokeParts(stroke);
731
+ const modifiers = new Set(modifierParts);
608
732
  if (modifiers.has('shift') && key.length === 1 && /[a-z]/.test(key)) {
609
733
  // tmux names a shifted printable with its uppercase character even when an
610
734
  // extended-key terminal also preserves Shift as a modifier: Alt+Shift+F is
@@ -622,9 +746,8 @@ function tmuxKey(stroke) {
622
746
  return `${prefix}${key}`;
623
747
  }
624
748
  function menuSelector(stroke) {
625
- const parts = stroke.split('+');
626
- const key = parts.at(-1);
627
- return parts.includes('shift') && /[a-z]/.test(key) ? key.toUpperCase() : key === 'space' ? ' ' : key;
749
+ const { key, modifiers } = normalizedStrokeParts(stroke);
750
+ return modifiers.includes('shift') && /[a-z]/.test(key) ? key.toUpperCase() : key === 'space' ? ' ' : key;
628
751
  }
629
752
  function boundMenuItems(ids, entries, bindings) {
630
753
  const items = [];
@@ -659,8 +782,8 @@ function resolvedMenuActions(bindings) {
659
782
  const attachItems = boundMenuItems(ATTACH_CONTROL_BINDINGS.map(({ menuId }) => menuId), entries, bindings);
660
783
  if (attachItems.length > 0) {
661
784
  entries.set('crtr.tmux.menu.attach', {
662
- description: 'attach ›',
663
- action: { kind: 'submenu', title: ' attach ', items: attachItems },
785
+ description: 'attach viewer ›',
786
+ action: { kind: 'submenu', title: ' attach viewer ', items: attachItems },
664
787
  });
665
788
  }
666
789
  const showItems = boundMenuItems([
@@ -668,8 +791,8 @@ function resolvedMenuActions(bindings) {
668
791
  'crtr.tmux.menu.context',
669
792
  ], entries, bindings);
670
793
  entries.set('crtr.tmux.menu.show', {
671
- description: 'show ›',
672
- action: { kind: 'submenu', title: ' show node ', items: showItems },
794
+ description: 'inspect ›',
795
+ action: { kind: 'submenu', title: ' inspect node ', items: showItems },
673
796
  });
674
797
  // How this viewer PAINTS the transcript, as opposed to what it shows. Each item
675
798
  // is a toggle the viewer applies to its own render state (a `/`-command sent to
@@ -677,8 +800,8 @@ function resolvedMenuActions(bindings) {
677
800
  // reads as the action and the viewer answers with a notice.
678
801
  const viewSettingsItems = boundMenuItems(['crtr.tmux.menu.fold-tools'], entries, bindings);
679
802
  entries.set('crtr.tmux.menu.view-settings', {
680
- description: 'view settings ›',
681
- action: { kind: 'submenu', title: ' view settings ', items: viewSettingsItems },
803
+ description: 'display ›',
804
+ action: { kind: 'submenu', title: ' display ', items: viewSettingsItems },
682
805
  });
683
806
  const issueItems = boundMenuItems([
684
807
  'crtr.tmux.menu.issue.idea',
@@ -716,6 +839,7 @@ function menuItems(bindings, entries) {
716
839
  'crtr.tmux.menu.pin',
717
840
  'crtr.tmux.menu.copy-id',
718
841
  'crtr.tmux.menu.reload-viewer',
842
+ 'crtr.tmux.menu.recap',
719
843
  ]));
720
844
  addGroup('Lifecycle', boundItems([
721
845
  'crtr.tmux.menu.fork',
@@ -743,37 +867,194 @@ function menuItems(bindings, entries) {
743
867
  }
744
868
  return items;
745
869
  }
870
+ /** Read one token from tmux's sourceable `list-keys` output. Key names that
871
+ * contain punctuation are quoted, so splitting on whitespace would make their
872
+ * displayed token differ from the key name accepted by bind/unbind commands. */
873
+ function readTmuxToken(line, from) {
874
+ let index = from;
875
+ while (index < line.length && /\s/.test(line[index]))
876
+ index += 1;
877
+ if (index >= line.length)
878
+ return null;
879
+ let quote = null;
880
+ let value = '';
881
+ while (index < line.length) {
882
+ const char = line[index];
883
+ if (quote === null && /\s/.test(char))
884
+ break;
885
+ if (quote === null && (char === "'" || char === '"')) {
886
+ quote = char;
887
+ index += 1;
888
+ continue;
889
+ }
890
+ if (quote === char) {
891
+ quote = null;
892
+ index += 1;
893
+ continue;
894
+ }
895
+ if (char === '\\' && quote !== "'" && index + 1 < line.length) {
896
+ value += line[index + 1];
897
+ index += 2;
898
+ continue;
899
+ }
900
+ value += char;
901
+ index += 1;
902
+ }
903
+ return { value, end: index };
904
+ }
905
+ function parseTmuxBinding(line) {
906
+ const commandName = readTmuxToken(line, 0);
907
+ if (commandName?.value !== 'bind-key')
908
+ return null;
909
+ let cursor = commandName.end;
910
+ let repeat = false;
911
+ while (true) {
912
+ const token = readTmuxToken(line, cursor);
913
+ if (token === null)
914
+ return null;
915
+ cursor = token.end;
916
+ if (token.value === '-r') {
917
+ repeat = true;
918
+ continue;
919
+ }
920
+ if (token.value !== '-T')
921
+ continue;
922
+ const table = readTmuxToken(line, cursor);
923
+ if (table === null)
924
+ return null;
925
+ const key = readTmuxToken(line, table.end);
926
+ if (key === null)
927
+ return null;
928
+ return {
929
+ table: table.value,
930
+ key: key.value,
931
+ command: line.slice(key.end).trimStart(),
932
+ repeat,
933
+ };
934
+ }
935
+ }
746
936
  function bindingAt(table, key) {
747
937
  const result = tmux(['list-keys', '-T', table]);
748
938
  if (!result.ok)
749
939
  return null;
750
940
  return result.stdout.split('\n').find((line) => {
751
- const match = line.match(/^bind-key\s+(?:-r\s+)?-T\s+\S+\s+(\S+)\s+/);
752
- return match?.[1] === key;
941
+ const parsed = parseTmuxBinding(line);
942
+ return parsed?.table === table && parsed.key === key;
753
943
  }) ?? null;
754
944
  }
755
- function sequenceTable(prefix) {
756
- return `crtr-${Buffer.from(prefix).toString('hex')}`;
945
+ /** Effective crouter gestures that tmux's root table currently consumes before
946
+ * pane input can see them. One list-keys snapshot serves the whole query. */
947
+ export function occupiedTmuxRootGestures(gestures) {
948
+ const result = tmux(['list-keys', '-T', ROOT_TABLE]);
949
+ if (!result.ok)
950
+ return [];
951
+ const occupied = new Set(result.stdout.split('\n').flatMap((line) => {
952
+ const parsed = parseTmuxBinding(line);
953
+ return parsed?.table === ROOT_TABLE ? [parsed.key] : [];
954
+ }));
955
+ return gestures.filter((gesture) => !gesture.includes(' > ') && occupied.has(tmuxKey(gesture)));
956
+ }
957
+ function isInstalledPair(value) {
958
+ return typeof value === 'object' && value !== null
959
+ && typeof value.table === 'string'
960
+ && typeof value.key === 'string';
961
+ }
962
+ function isWrappedRootBinding(value) {
963
+ if (typeof value !== 'object' || value === null)
964
+ return false;
965
+ const wrapper = value;
966
+ return typeof wrapper.bindingId === 'string'
967
+ && BINDING_IDS.has(wrapper.bindingId)
968
+ && typeof wrapper.gesture === 'string'
969
+ && typeof wrapper.key === 'string'
970
+ && (typeof wrapper.original === 'string' || wrapper.original === null)
971
+ && typeof wrapper.wrapper === 'string'
972
+ && (wrapper.state === 'owned' || wrapper.state === 'relinquished');
757
973
  }
758
974
  function readManifest() {
759
975
  const option = tmux(['show-options', '-s', '-q', TMUX_BINDING_MANIFEST]);
760
976
  if (!option.ok)
761
977
  return null;
762
978
  if (option.stdout === '')
763
- return { pairs: [], existed: false };
979
+ return { pairs: [], wrappers: [] };
764
980
  const result = tmux(['show-options', '-s', '-qv', TMUX_BINDING_MANIFEST]);
765
981
  if (!result.ok)
766
982
  return null;
767
983
  try {
768
984
  const value = JSON.parse(result.stdout);
769
- if (!Array.isArray(value) || value.some((pair) => typeof pair !== 'object' || pair === null || typeof pair.table !== 'string' || typeof pair.key !== 'string'))
985
+ // Before passthrough wrappers, the manifest was the pair array itself.
986
+ if (Array.isArray(value))
987
+ return value.every(isInstalledPair) ? { pairs: value, wrappers: [] } : null;
988
+ if (typeof value !== 'object' || value === null)
989
+ return null;
990
+ const manifest = value;
991
+ if (!Array.isArray(manifest.pairs) || !manifest.pairs.every(isInstalledPair)
992
+ || !Array.isArray(manifest.wrappers) || !manifest.wrappers.every(isWrappedRootBinding))
770
993
  return null;
771
- return { pairs: value, existed: true };
994
+ return { pairs: manifest.pairs, wrappers: manifest.wrappers };
772
995
  }
773
996
  catch {
774
997
  return null;
775
998
  }
776
999
  }
1000
+ function writeManifest(manifest) {
1001
+ return tmux(['set-option', '-s', TMUX_BINDING_MANIFEST, JSON.stringify(manifest)]);
1002
+ }
1003
+ /** Tmux parses semicolons once more even when argv already isolated them. */
1004
+ function tmuxKeyArgument(key) {
1005
+ return key.replace(/\\/g, '\\\\').replace(/;/g, '\\;');
1006
+ }
1007
+ function tmuxCommandToken(value) {
1008
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/;/g, '\\;')}"`;
1009
+ }
1010
+ /** A top-level separator is printed as `\;` in a bind-key line, but the same
1011
+ * sequence is literal after the command tail becomes one if-shell branch. */
1012
+ function bindingCommandForBranch(command) {
1013
+ let quote = null;
1014
+ let out = '';
1015
+ for (let index = 0; index < command.length; index += 1) {
1016
+ const char = command[index];
1017
+ if (quote === null && (char === "'" || char === '"')) {
1018
+ quote = char;
1019
+ out += char;
1020
+ continue;
1021
+ }
1022
+ if (quote === char) {
1023
+ quote = null;
1024
+ out += char;
1025
+ continue;
1026
+ }
1027
+ if (char === '\\' && index + 1 < command.length) {
1028
+ const next = command[index + 1];
1029
+ if (quote === null && next === ';') {
1030
+ out += ';';
1031
+ }
1032
+ else {
1033
+ out += char + next;
1034
+ }
1035
+ index += 1;
1036
+ continue;
1037
+ }
1038
+ out += char;
1039
+ }
1040
+ return out;
1041
+ }
1042
+ function sourceBindingLine(line) {
1043
+ const dir = mkdtempSync(join(tmpdir(), 'crtr-tmux-binding-'));
1044
+ const file = join(dir, 'binding.conf');
1045
+ try {
1046
+ writeFileSync(file, `${line}\n`, 'utf8');
1047
+ return tmux(['source-file', file]);
1048
+ }
1049
+ finally {
1050
+ rmSync(dir, { recursive: true, force: true });
1051
+ }
1052
+ }
1053
+ function restoreWrappedBinding(wrapper) {
1054
+ return wrapper.original === null
1055
+ ? tmux(['unbind-key', '-T', ROOT_TABLE, tmuxKeyArgument(wrapper.key)])
1056
+ : sourceBindingLine(wrapper.original);
1057
+ }
777
1058
  // Crouter's own pre-manifest root bindings. Reconciliation always adopts an
778
1059
  // untracked binding matching these crouter-specific markers, even when a partial
779
1060
  // ownership manifest already exists. Older installs could leave M-c outside a
@@ -838,18 +1119,49 @@ function removeLegacyInboxBindings(manifestPairs) {
838
1119
  }
839
1120
  catch { /* best-effort */ }
840
1121
  }
1122
+ // --quiet keeps the background `run-shell -b` binding from printing its result JSON, which
1123
+ // tmux would otherwise surface as a view-mode overlay on the active pane. The popup opening
1124
+ // is the feedback; a genuine failure still prints through the CLI toggle action. This deliberately
1125
+ // invokes bare `crtr` from PATH: it is installed once and must resolve to the selected generation,
1126
+ // not the sealed generation that installed it. The neighboring `crtr surface node cycle` builders
1127
+ // share that property.
1128
+ export function inboxToggleTmuxCommand() {
1129
+ return ['run-shell', '-b', 'crtr human inbox toggle --quiet --tmux-socket "#{socket_path}" --tmux-client "#{client_name}" --target-pane "#{pane_id}"'];
1130
+ }
841
1131
  function actionCommand(id) {
842
1132
  switch (id) {
843
1133
  case 'crtr.tmux.node.next': return ['run-shell', `crtr surface node cycle --dir next --pane '#{pane_id}' >/dev/null 2>&1`];
844
1134
  case 'crtr.tmux.node.previous': return ['run-shell', `crtr surface node cycle --dir prev --pane '#{pane_id}' >/dev/null 2>&1`];
845
1135
  case 'crtr.tmux.inbox.toggle': return inboxToggleTmuxCommand();
846
- case 'crtr.tmux.view.next': return ['run-shell', `crtr surface view cycle --dir next --pane '#{pane_id}' >/dev/null 2>&1`];
847
- case 'crtr.tmux.view.previous': return ['run-shell', `crtr surface view cycle --dir prev --pane '#{pane_id}' >/dev/null 2>&1`];
848
1136
  default: throw new Error(`no tmux command for ${id}`);
849
1137
  }
850
1138
  }
851
- /** Reconcile the tmux server with one immutable crouter binding snapshot. */
852
- export function installTmuxBindings(bindings = resolveUserKeybindings()) {
1139
+ /** A directory beside the server socket serializes every process driving that
1140
+ * exact tmux server. Outside tmux, ask the default server for its socket first;
1141
+ * an unavailable server uses one per-user fallback lock for the error path. */
1142
+ function tmuxBindingLockPath() {
1143
+ const envSocket = process.env['TMUX']?.split(',')[0]?.trim();
1144
+ if (envSocket !== undefined && envSocket !== '')
1145
+ return `${envSocket}.crtr-keybindings.lock`;
1146
+ const result = tmux(['display-message', '-p', '#{socket_path}']);
1147
+ if (result.ok && result.stdout !== '')
1148
+ return `${result.stdout}.crtr-keybindings.lock`;
1149
+ const user = typeof process.getuid === 'function' ? process.getuid() : 'user';
1150
+ return join(tmpdir(), `crtr-tmux-keybindings-${user}.lock`);
1151
+ }
1152
+ /** Reconcile the tmux server with one immutable crouter settings snapshot.
1153
+ * Passing snapshots is hermetic; the no-argument front door reads both user
1154
+ * settings from the same config read. The server lock covers the entire
1155
+ * manifest read, restore, install, fingerprint, and write transition. */
1156
+ export function installTmuxBindings(bindingSnapshot, passthroughSnapshot = []) {
1157
+ return withExclusiveDirectoryLock(tmuxBindingLockPath(), () => reconcileTmuxBindings(bindingSnapshot, passthroughSnapshot));
1158
+ }
1159
+ function reconcileTmuxBindings(bindingSnapshot, passthroughSnapshot = []) {
1160
+ const userSettings = bindingSnapshot === undefined ? resolveUserKeybindingSettings() : undefined;
1161
+ const bindings = bindingSnapshot ?? userSettings.bindings;
1162
+ const passthrough = bindingSnapshot === undefined
1163
+ ? userSettings.tmuxPassthrough
1164
+ : normalizeTmuxPassthrough(passthroughSnapshot);
853
1165
  const diagnostics = [];
854
1166
  const installed = [];
855
1167
  const manifest = readManifest();
@@ -857,15 +1169,61 @@ export function installTmuxBindings(bindings = resolveUserKeybindings()) {
857
1169
  diagnostics.push({ code: 'tmux-error', message: `tmux is unavailable or ${TMUX_BINDING_MANIFEST} is malformed` });
858
1170
  return { ok: false, installed, diagnostics };
859
1171
  }
1172
+ const desiredWrappers = new Map();
1173
+ for (const bindingId of passthrough) {
1174
+ const definition = BINDING_CATALOG.find((candidate) => candidate.id === bindingId);
1175
+ if (definition === undefined || !isAttachPaneBinding(definition))
1176
+ continue;
1177
+ for (const gesture of bindings.gestures(bindingId)) {
1178
+ if (gesture.includes(' > '))
1179
+ continue;
1180
+ const key = tmuxKey(gesture);
1181
+ if (!desiredWrappers.has(key))
1182
+ desiredWrappers.set(key, { bindingId, gesture });
1183
+ }
1184
+ }
1185
+ // Restore every wrapper crouter still owns before the ordinary binding sweep.
1186
+ // A live line that differs from the fingerprint is a user's later rebind: it
1187
+ // is protected from this and every later reconciliation until they toggle the
1188
+ // passthrough preference off and on again.
1189
+ const protectedRootKeys = new Set();
1190
+ const wrappers = [];
1191
+ for (const prior of manifest.wrappers) {
1192
+ const desired = desiredWrappers.get(prior.key);
1193
+ const live = bindingAt(ROOT_TABLE, prior.key);
1194
+ if (prior.state === 'relinquished' || live !== prior.wrapper) {
1195
+ protectedRootKeys.add(prior.key);
1196
+ if (desired !== undefined) {
1197
+ wrappers.push({ ...prior, ...desired, state: 'relinquished' });
1198
+ }
1199
+ continue;
1200
+ }
1201
+ const restored = restoreWrappedBinding(prior);
1202
+ if (!restored.ok) {
1203
+ protectedRootKeys.add(prior.key);
1204
+ wrappers.push(prior);
1205
+ diagnostics.push({
1206
+ code: 'tmux-error',
1207
+ bindingId: prior.bindingId,
1208
+ gesture: prior.gesture,
1209
+ message: `could not restore tmux binding ${ROOT_TABLE}:${prior.key}: ${restored.stderr}`,
1210
+ });
1211
+ }
1212
+ }
860
1213
  adoptLegacyBindings();
861
1214
  removeLegacyInboxBindings(manifest.pairs);
862
1215
  for (const pair of manifest.pairs) {
863
- const result = tmux(['unbind-key', '-T', pair.table, pair.key]);
1216
+ if (pair.table === ROOT_TABLE && protectedRootKeys.has(pair.key))
1217
+ continue;
1218
+ const result = tmux(['unbind-key', '-T', pair.table, tmuxKeyArgument(pair.key)]);
864
1219
  if (!result.ok && bindingAt(pair.table, pair.key) !== null) {
865
1220
  diagnostics.push({ code: 'tmux-error', message: `could not remove crouter binding ${pair.table}:${pair.key}: ${result.stderr}` });
866
1221
  }
867
1222
  }
868
1223
  if (diagnostics.some((diagnostic) => diagnostic.code === 'tmux-error')) {
1224
+ const save = writeManifest({ pairs: manifest.pairs, wrappers });
1225
+ if (!save.ok)
1226
+ diagnostics.push({ code: 'tmux-error', message: `could not save crouter tmux ownership manifest: ${save.stderr}` });
869
1227
  return { ok: false, installed, diagnostics };
870
1228
  }
871
1229
  const install = (table, key, command, id, gesture) => {
@@ -873,7 +1231,7 @@ export function installTmuxBindings(bindings = resolveUserKeybindings()) {
873
1231
  diagnostics.push({ code: 'occupied', bindingId: id, gesture, message: `${table}:${key} is already bound by tmux; ${id} was not installed` });
874
1232
  return false;
875
1233
  }
876
- const result = tmux(['bind-key', '-T', table, key, ...command]);
1234
+ const result = tmux(['bind-key', '-T', table, tmuxKeyArgument(key), ...command]);
877
1235
  if (!result.ok) {
878
1236
  diagnostics.push({ code: 'tmux-error', bindingId: id, gesture, message: `could not install ${id}: ${result.stderr}` });
879
1237
  return false;
@@ -906,32 +1264,51 @@ export function installTmuxBindings(bindings = resolveUserKeybindings()) {
906
1264
  for (const gesture of bindings.gestures(id))
907
1265
  install(ROOT_TABLE, tmuxKey(gesture), actionCommand(id), id, gesture);
908
1266
  }
909
- const sequences = new Map();
910
- for (const id of ['crtr.tmux.view.next', 'crtr.tmux.view.previous']) {
911
- for (const gesture of bindings.gestures(id)) {
912
- const [first, second] = gesture.split(' > ');
913
- const prefix = tmuxKey(first);
914
- const entries = sequences.get(prefix) ?? [];
915
- entries.push({ id, gesture, second: tmuxKey(second) });
916
- sequences.set(prefix, entries);
1267
+ // Wrap only occupied roots. Unoccupied gestures already reach the pane and do
1268
+ // not need crouter ownership. Re-read the installed line as the fingerprint;
1269
+ // tmux canonicalizes quoting and braces in ways hand serialization cannot.
1270
+ const newlyWrapped = [];
1271
+ for (const [key, desired] of desiredWrappers) {
1272
+ if (protectedRootKeys.has(key))
1273
+ continue;
1274
+ const original = bindingAt(ROOT_TABLE, key);
1275
+ if (original === null)
1276
+ continue;
1277
+ const parsed = parseTmuxBinding(original);
1278
+ if (parsed === null || parsed.command === '') {
1279
+ diagnostics.push({ code: 'tmux-error', bindingId: desired.bindingId, gesture: desired.gesture, message: `could not parse occupied tmux binding ${ROOT_TABLE}:${key}` });
1280
+ continue;
917
1281
  }
918
- }
919
- for (const [prefix, entries] of sequences) {
920
- if (bindingAt(ROOT_TABLE, prefix) !== null) {
921
- for (const entry of entries)
922
- diagnostics.push({ code: 'occupied', bindingId: entry.id, gesture: entry.gesture, message: `${ROOT_TABLE}:${prefix} is already bound by tmux; ${entry.id} was not installed` });
1282
+ const result = tmux([
1283
+ 'bind-key',
1284
+ ...(parsed.repeat ? ['-r'] : []),
1285
+ '-T', ROOT_TABLE, tmuxKeyArgument(key),
1286
+ 'if-shell', '-F', '#{!=:#{@crtr_attach},}',
1287
+ `send-keys ${tmuxCommandToken(key)}`,
1288
+ bindingCommandForBranch(parsed.command),
1289
+ ]);
1290
+ if (!result.ok) {
1291
+ diagnostics.push({ code: 'tmux-error', bindingId: desired.bindingId, gesture: desired.gesture, message: `could not wrap ${ROOT_TABLE}:${key}: ${result.stderr}` });
1292
+ continue;
1293
+ }
1294
+ const wrapper = bindingAt(ROOT_TABLE, key);
1295
+ if (wrapper === null || !wrapper.includes('@crtr_attach')) {
1296
+ diagnostics.push({ code: 'tmux-error', bindingId: desired.bindingId, gesture: desired.gesture, message: `could not fingerprint wrapped tmux binding ${ROOT_TABLE}:${key}` });
923
1297
  continue;
924
1298
  }
925
- const table = sequenceTable(prefix);
926
- let children = 0;
927
- for (const entry of entries)
928
- if (install(table, entry.second, actionCommand(entry.id), entry.id, entry.gesture))
929
- children++;
930
- if (children > 0)
931
- install(ROOT_TABLE, prefix, ['switch-client', '-T', table], entries[0].id, entries[0].gesture);
1299
+ const record = { ...desired, key, original, wrapper, state: 'owned' };
1300
+ wrappers.push(record);
1301
+ newlyWrapped.push(record);
932
1302
  }
933
- const save = tmux(['set-option', '-s', TMUX_BINDING_MANIFEST, JSON.stringify(installed)]);
934
- if (!save.ok)
1303
+ const save = writeManifest({ pairs: installed, wrappers });
1304
+ if (!save.ok) {
935
1305
  diagnostics.push({ code: 'tmux-error', message: `could not save crouter tmux ownership manifest: ${save.stderr}` });
1306
+ // A wrapper without restoration data is unsafe. Put each newly wrapped key
1307
+ // back immediately when manifest persistence fails.
1308
+ for (const wrapper of newlyWrapped) {
1309
+ if (bindingAt(ROOT_TABLE, wrapper.key) === wrapper.wrapper)
1310
+ restoreWrappedBinding(wrapper);
1311
+ }
1312
+ }
936
1313
  return { ok: diagnostics.every((diagnostic) => diagnostic.code !== 'tmux-error'), installed, diagnostics };
937
1314
  }