@north-light/crouter 0.3.197 → 0.3.199

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 (339) hide show
  1. package/dist/api/client.d.ts +19 -19
  2. package/dist/api/client.js +29 -37
  3. package/dist/api/dto/broker-ops.d.ts +3 -1
  4. package/dist/api/dto/broker.d.ts +13 -2
  5. package/dist/api/dto/broker.js +4 -4
  6. package/dist/api/dto/human.d.ts +6 -5
  7. package/dist/api/dto/inbox.d.ts +126 -33
  8. package/dist/api/dto/nodes.d.ts +8 -2
  9. package/dist/api/dto/profiles.d.ts +23 -0
  10. package/dist/api/dto/profiles.js +2 -2
  11. package/dist/api/dto/worktree.d.ts +3 -5
  12. package/dist/api/routes.d.ts +3 -5
  13. package/dist/api/routes.js +4 -6
  14. package/dist/builtin-memory/00-runtime-base.md +8 -8
  15. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  16. package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
  18. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
  20. package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
  21. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
  23. package/dist/builtin-memory/insights/capture.md +2 -2
  24. package/dist/builtin-memory/insights/init.md +3 -3
  25. package/dist/builtin-memory/internal/INDEX.md +1 -1
  26. package/dist/builtin-memory/internal/memory-loading.md +1 -1
  27. package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -3
  28. package/dist/builtin-memory/internal/plugins.md +59 -2
  29. package/dist/builtin-memory/internal/storage-tiers.md +3 -1
  30. package/dist/builtin-memory/spec/roadmap.md +2 -2
  31. package/dist/builtin-memory/testing.md +1 -1
  32. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
  33. package/dist/clients/attach/chrome/bash-jobs.js +3 -3
  34. package/dist/clients/attach/render/chat-view.d.ts +29 -9
  35. package/dist/clients/attach/render/chat-view.js +64 -35
  36. package/dist/clients/attach/render/markdown-source.js +0 -6
  37. package/dist/clients/attach/render/page-block.d.ts +34 -0
  38. package/dist/clients/attach/render/page-block.js +226 -0
  39. package/dist/clients/attach/render/tool-calls.js +16 -2
  40. package/dist/clients/attach/session/whip.d.ts +2 -0
  41. package/dist/clients/attach/session/whip.js +2 -0
  42. package/dist/clients/attach/viewer.js +1419 -982
  43. package/dist/clients/conversation/projection.js +18 -3
  44. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
  45. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -14
  46. package/dist/clients/inbox/controller.js +10 -16
  47. package/dist/clients/inbox/page-adapter.d.ts +3 -4
  48. package/dist/clients/inbox/page-adapter.js +3 -4
  49. package/dist/clients/inbox/resolve.d.ts +1 -1
  50. package/dist/clients/inbox/resolve.js +3 -3
  51. package/dist/clients/inbox/review/document-surface.js +3 -1
  52. package/dist/clients/inbox/review/state.d.ts +2 -1
  53. package/dist/clients/inbox/review/state.js +5 -16
  54. package/dist/clients/inbox/tui/input.js +37 -19
  55. package/dist/clients/inbox/tui/panel.js +5 -31
  56. package/dist/clients/inbox/tui/render.d.ts +11 -0
  57. package/dist/clients/inbox/tui/render.js +101 -36
  58. package/dist/clients/inbox/tui/slots.d.ts +4 -4
  59. package/dist/clients/inbox/tui/slots.js +19 -44
  60. package/dist/clients/inbox/tui/types.d.ts +6 -10
  61. package/dist/clients/inbox/tui.js +2 -2
  62. package/dist/commands/__tests__/human.test.js +118 -24
  63. package/dist/commands/__tests__/node-message.test.js +14 -0
  64. package/dist/commands/attention.js +4 -4
  65. package/dist/commands/canvas-browse.js +1 -1
  66. package/dist/commands/canvas-snapshot.js +1 -1
  67. package/dist/commands/canvas.js +1 -1
  68. package/dist/commands/dashboard.js +1 -1
  69. package/dist/commands/human/{doc.d.ts → components.d.ts} +1 -1
  70. package/dist/commands/human/components.js +77 -0
  71. package/dist/commands/human/feedback.d.ts +2 -0
  72. package/dist/commands/human/feedback.js +108 -0
  73. package/dist/commands/human/prompts.d.ts +17 -8
  74. package/dist/commands/human/prompts.js +217 -226
  75. package/dist/commands/human/queue.js +57 -88
  76. package/dist/commands/human/shared.d.ts +4 -15
  77. package/dist/commands/human/shared.js +19 -61
  78. package/dist/commands/human.js +32 -19
  79. package/dist/commands/memory/origin.js +2 -2
  80. package/dist/commands/node/bash.js +4 -4
  81. package/dist/commands/node/inspect.js +3 -3
  82. package/dist/commands/node/lifecycle.js +1 -1
  83. package/dist/commands/node/message.js +2 -2
  84. package/dist/commands/node-worktree.js +8 -8
  85. package/dist/commands/pkg/browse.js +1 -1
  86. package/dist/commands/pkg/plugin-manage.js +129 -10
  87. package/dist/commands/profile/delete.js +41 -15
  88. package/dist/commands/profile.js +1 -1
  89. package/dist/commands/push.js +1 -1
  90. package/dist/commands/surface/node/focus.js +29 -14
  91. package/dist/commands/surface-edit.js +3 -3
  92. package/dist/commands/surface-inbox.d.ts +2 -0
  93. package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
  94. package/dist/commands/surface.js +2 -1
  95. package/dist/commands/sys/doctor.js +60 -3
  96. package/dist/commands/sys/feedback.js +79 -25
  97. package/dist/commands/sys/settings.js +1 -1
  98. package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
  99. package/dist/core/__tests__/daemon-boot.test.js +7 -4
  100. package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
  101. package/dist/core/__tests__/extension-abort.test.js +5 -2
  102. package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
  103. package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
  104. package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
  105. package/dist/core/__tests__/human-deliver.test.js +41 -45
  106. package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
  107. package/dist/core/__tests__/plugin-page-components.test.js +157 -0
  108. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
  109. package/dist/core/__tests__/seam/dormancy-release.test.js +103 -4
  110. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
  111. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -16
  112. package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
  113. package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
  114. package/dist/core/__tests__/serial/worktree.test.js +24 -198
  115. package/dist/core/__tests__/session-cycles.test.js +22 -0
  116. package/dist/core/__tests__/stop-guard.test.js +4 -4
  117. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
  118. package/dist/core/bash-jobs.d.ts +6 -0
  119. package/dist/core/bash-jobs.js +10 -0
  120. package/dist/core/canvas/__tests__/attention.test.js +8 -5
  121. package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
  122. package/dist/core/canvas/attention.js +8 -6
  123. package/dist/core/canvas/browse/app.d.ts +1 -1
  124. package/dist/core/canvas/browse/app.js +21 -3
  125. package/dist/core/canvas/browse/model.d.ts +8 -2
  126. package/dist/core/canvas/browse/model.js +11 -4
  127. package/dist/core/canvas/browse/render.d.ts +4 -0
  128. package/dist/core/canvas/browse/render.js +5 -1
  129. package/dist/core/canvas/canvas.d.ts +33 -0
  130. package/dist/core/canvas/canvas.js +92 -0
  131. package/dist/core/canvas/crons.d.ts +9 -0
  132. package/dist/core/canvas/crons.js +51 -0
  133. package/dist/core/canvas/node-order.d.ts +22 -1
  134. package/dist/core/canvas/node-order.js +26 -1
  135. package/dist/core/canvas/render-source.d.ts +5 -0
  136. package/dist/core/canvas/render-source.js +1 -0
  137. package/dist/core/canvas/types.d.ts +9 -3
  138. package/dist/core/command-plugins/bundle.d.ts +6 -1
  139. package/dist/core/command-plugins/bundle.js +26 -13
  140. package/dist/core/command.js +8 -12
  141. package/dist/core/config.d.ts +47 -1
  142. package/dist/core/config.js +138 -4
  143. package/dist/core/help.d.ts +3 -0
  144. package/dist/core/help.js +1 -1
  145. package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
  146. package/dist/core/human/__tests__/page-catalog.test.js +20 -0
  147. package/dist/core/human/__tests__/page-tickets.test.js +68 -34
  148. package/dist/core/human/__tests__/page.test.js +60 -119
  149. package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -16
  150. package/dist/core/human/answer-text.d.ts +3 -0
  151. package/dist/core/human/answer-text.js +98 -0
  152. package/dist/core/human/answer.d.ts +53 -0
  153. package/dist/core/human/answer.js +119 -0
  154. package/dist/core/human/component-docs.d.ts +18 -0
  155. package/dist/core/human/component-docs.js +434 -0
  156. package/dist/core/human/convention.d.ts +17 -3
  157. package/dist/core/human/convention.js +36 -5
  158. package/dist/core/human/feedback-companion.d.ts +20 -0
  159. package/dist/core/human/feedback-companion.js +98 -0
  160. package/dist/core/human/feedback.d.ts +65 -0
  161. package/dist/core/human/feedback.js +116 -0
  162. package/dist/core/human/page-catalog.d.ts +56 -5
  163. package/dist/core/human/page-catalog.js +151 -22
  164. package/dist/core/human/page-errors.d.ts +4 -0
  165. package/dist/core/human/page-errors.js +7 -0
  166. package/dist/core/human/page-eval.d.ts +20 -0
  167. package/dist/core/human/page-eval.js +123 -0
  168. package/dist/core/human/page-schema.d.ts +48 -116
  169. package/dist/core/human/page-schema.js +100 -72
  170. package/dist/core/human/page-synth.d.ts +2 -4
  171. package/dist/core/human/page-synth.js +20 -7
  172. package/dist/core/human/page.d.ts +13 -21
  173. package/dist/core/human/page.js +147 -391
  174. package/dist/core/human/review-schema.d.ts +1 -0
  175. package/dist/core/human/review-schema.js +1 -1
  176. package/dist/core/human/root.d.ts +6 -2
  177. package/dist/core/human/root.js +12 -4
  178. package/dist/core/human/scan.d.ts +3 -3
  179. package/dist/core/human/scan.js +21 -21
  180. package/dist/core/human/summary.d.ts +2 -6
  181. package/dist/core/human/summary.js +3 -59
  182. package/dist/core/human/tickets.d.ts +22 -15
  183. package/dist/core/human/tickets.js +90 -28
  184. package/dist/core/human/types.d.ts +14 -2
  185. package/dist/core/keybindings/catalog.d.ts +2 -2
  186. package/dist/core/keybindings/catalog.js +1 -0
  187. package/dist/core/preview-registry.js +8 -10
  188. package/dist/core/profiles/default-binding.d.ts +4 -0
  189. package/dist/core/profiles/default-binding.js +24 -0
  190. package/dist/core/profiles/deletion-reservation.d.ts +8 -0
  191. package/dist/core/profiles/deletion-reservation.js +38 -0
  192. package/dist/core/profiles/manifest.d.ts +3 -0
  193. package/dist/core/profiles/manifest.js +12 -0
  194. package/dist/core/profiles/select.js +2 -2
  195. package/dist/core/review/realize.js +12 -4
  196. package/dist/core/review/store.d.ts +6 -0
  197. package/dist/core/review/store.js +31 -0
  198. package/dist/core/runtime/bearings.d.ts +0 -4
  199. package/dist/core/runtime/bearings.js +4 -62
  200. package/dist/core/runtime/bin-contributions.d.ts +44 -0
  201. package/dist/core/runtime/bin-contributions.js +186 -0
  202. package/dist/core/runtime/boot-root.js +6 -1
  203. package/dist/core/runtime/broker/client-registry.d.ts +4 -0
  204. package/dist/core/runtime/broker/client-registry.js +13 -1
  205. package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
  206. package/dist/core/runtime/broker/extension-abort.js +4 -2
  207. package/dist/core/runtime/broker/fault-retry.js +4 -0
  208. package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
  209. package/dist/core/runtime/broker/frame-dispatch.js +5 -6
  210. package/dist/core/runtime/broker/read-ops.d.ts +1 -1
  211. package/dist/core/runtime/broker/read-ops.js +2 -2
  212. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  213. package/dist/core/runtime/broker/rebind.js +6 -1
  214. package/dist/core/runtime/broker-extension-render.js +19 -0
  215. package/dist/core/runtime/broker-protocol.d.ts +14 -0
  216. package/dist/core/runtime/broker.d.ts +1 -1
  217. package/dist/core/runtime/broker.js +25 -12
  218. package/dist/core/runtime/busy.d.ts +4 -3
  219. package/dist/core/runtime/busy.js +4 -3
  220. package/dist/core/runtime/front-door-env.d.ts +5 -0
  221. package/dist/core/runtime/front-door-env.js +16 -0
  222. package/dist/core/runtime/front-door.d.ts +1 -5
  223. package/dist/core/runtime/front-door.js +2 -5
  224. package/dist/core/runtime/host.d.ts +1 -1
  225. package/dist/core/runtime/invocation.d.ts +6 -0
  226. package/dist/core/runtime/invocation.js +10 -0
  227. package/dist/core/runtime/kickoff.js +34 -0
  228. package/dist/core/runtime/launch.d.ts +2 -6
  229. package/dist/core/runtime/node-read.js +4 -1
  230. package/dist/core/runtime/nodes.js +2 -0
  231. package/dist/core/runtime/promote.d.ts +1 -0
  232. package/dist/core/runtime/promote.js +3 -1
  233. package/dist/core/runtime/session-cycles.d.ts +6 -0
  234. package/dist/core/runtime/session-cycles.js +15 -1
  235. package/dist/core/runtime/session-visibility.d.ts +17 -8
  236. package/dist/core/runtime/session-visibility.js +19 -10
  237. package/dist/core/runtime/spawn-env.d.ts +9 -1
  238. package/dist/core/runtime/spawn-env.js +62 -2
  239. package/dist/core/runtime/spawn.js +4 -4
  240. package/dist/core/runtime/stop-guard.d.ts +2 -2
  241. package/dist/core/runtime/stop-guard.js +2 -2
  242. package/dist/core/runtime/stop-signals.d.ts +1 -1
  243. package/dist/core/runtime/stop-signals.js +2 -2
  244. package/dist/core/runtime/tmux-bindings.js +1 -1
  245. package/dist/core/session-model/session-state.d.ts +4 -3
  246. package/dist/core/session-model/session-state.js +14 -1
  247. package/dist/core/user-settings.d.ts +36 -2
  248. package/dist/core/user-settings.js +56 -14
  249. package/dist/core/worktree.d.ts +15 -45
  250. package/dist/core/worktree.js +59 -261
  251. package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
  252. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
  253. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  254. package/dist/daemon/api/handlers/broker-ops.js +9 -5
  255. package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
  256. package/dist/daemon/api/handlers/feedback-comments.js +207 -0
  257. package/dist/daemon/api/handlers/human.js +24 -20
  258. package/dist/daemon/api/handlers/inbox.d.ts +10 -0
  259. package/dist/daemon/api/handlers/inbox.js +48 -109
  260. package/dist/daemon/api/handlers/profiles.js +11 -29
  261. package/dist/daemon/api/map.d.ts +6 -1
  262. package/dist/daemon/api/map.js +23 -0
  263. package/dist/daemon/api/server.js +3 -1
  264. package/dist/daemon/companion-retire.d.ts +2 -0
  265. package/dist/daemon/companion-retire.js +33 -0
  266. package/dist/daemon/cron-run.js +29 -17
  267. package/dist/daemon/crtrd.js +1 -1
  268. package/dist/daemon/fleet.d.ts +22 -10
  269. package/dist/daemon/fleet.js +43 -24
  270. package/dist/daemon/human/finish.d.ts +12 -6
  271. package/dist/daemon/human/finish.js +134 -54
  272. package/dist/daemon/human/sweep.js +9 -3
  273. package/dist/daemon/profile-delete.d.ts +7 -0
  274. package/dist/daemon/profile-delete.js +306 -0
  275. package/dist/daemon/reconcilers/broker-supervision.js +14 -9
  276. package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
  277. package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
  278. package/dist/daemon/reconcilers/live-obligation.js +30 -2
  279. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
  280. package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
  281. package/dist/daemon/review/deliver.js +3 -3
  282. package/dist/daemon/review/finish.d.ts +0 -2
  283. package/dist/daemon/review/finish.js +1 -29
  284. package/dist/daemon/review/sweep.js +12 -1
  285. package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
  286. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
  287. package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
  288. package/dist/pi-extensions/canvas-bash-valve.js +31 -13
  289. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  290. package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
  291. package/dist/pi-extensions/canvas-review-boundary.js +18 -9
  292. package/dist/pi-extensions/canvas-stophook.js +4 -4
  293. package/dist/shared/generated-context.js +2 -2
  294. package/dist/types.d.ts +61 -5
  295. package/dist/types.js +8 -2
  296. package/package.json +4 -4
  297. package/runtime.lock.json +5 -33
  298. package/scripts/install-runtime.mjs +19 -24
  299. package/dist/builtin-memory/init.md +0 -38
  300. package/dist/builtin-memory/plan.md +0 -19
  301. package/dist/builtin-memory/spec.md +0 -19
  302. package/dist/clients/attach/render/html-markdown.d.ts +0 -13
  303. package/dist/clients/attach/render/html-markdown.js +0 -206
  304. package/dist/commands/human/doc.js +0 -144
  305. package/dist/commands/human/inbox.d.ts +0 -2
  306. package/dist/core/human/__tests__/page-render.test.js +0 -49
  307. package/dist/core/human/markdown-html.d.ts +0 -8
  308. package/dist/core/human/markdown-html.js +0 -581
  309. package/dist/core/human/page-render.d.ts +0 -3
  310. package/dist/core/human/page-render.js +0 -131
  311. package/dist/pages/bundle.css +0 -1
  312. package/dist/pages/bundle.js +0 -1347
  313. package/dist/pages/comments.d.ts +0 -43
  314. package/dist/pages/comments.js +0 -125
  315. package/dist/pages/elements/cards.d.ts +0 -17
  316. package/dist/pages/elements/cards.js +0 -802
  317. package/dist/pages/elements/chart.js +0 -648
  318. package/dist/pages/elements/options.d.ts +0 -21
  319. package/dist/pages/elements/options.js +0 -629
  320. package/dist/pages/elements/pages.d.ts +0 -24
  321. package/dist/pages/elements/pages.js +0 -355
  322. package/dist/pages/elements/slot.d.ts +0 -21
  323. package/dist/pages/elements/slot.js +0 -105
  324. package/dist/pages/elements/table.d.ts +0 -19
  325. package/dist/pages/elements/table.js +0 -935
  326. package/dist/pages/elements/text.d.ts +0 -25
  327. package/dist/pages/elements/text.js +0 -968
  328. package/dist/pages/entry.d.ts +0 -8
  329. package/dist/pages/entry.js +0 -8
  330. package/dist/pages/host.d.ts +0 -158
  331. package/dist/pages/host.js +0 -182
  332. package/dist/pages/register.d.ts +0 -2
  333. package/dist/pages/register.js +0 -3
  334. package/dist/pages/slot-config.d.ts +0 -36
  335. package/dist/pages/slot-config.js +0 -50
  336. package/dist/pages/types.d.ts +0 -98
  337. package/dist/pages/types.js +0 -23
  338. /package/dist/{core/human/__tests__/page-render.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
  339. /package/dist/{pages/elements/chart.d.ts → core/__tests__/plugin-page-components.test.d.ts} +0 -0
@@ -38,21 +38,26 @@ function syntheticBoundary(timestamp) {
38
38
  /**
39
39
  * Project snapshot messages to the visible window for a review companion.
40
40
  *
41
+ * A companion passes its binding's review id so the slice starts at ITS OWN
42
+ * boundary marker: a node forked from another companion inherits that
43
+ * companion's earlier marker, and slicing at the first marker of any review
44
+ * would reveal the origin companion's transcript.
45
+ *
41
46
  * Contract (in evaluation order):
42
- * 1. Marker present → slice from the **first** occurrence (marker included) to
43
- * the end. Everything before the marker is inherited context, hidden from
44
- * the person.
45
- * 2. Not a review companion → return input unchanged (same reference). Ordinary
46
- * nodes are not filtered.
47
- * 3. No marker and review companion → a leading compaction summary is replaced
47
+ * 1. This binding's marker present (any marker when the node has no binding) →
48
+ * slice from the **first** occurrence (marker included) to the end.
49
+ * Everything before the marker is inherited context, hidden from the person.
50
+ * 2. Not a companion (`boundaryReviewId` undefined) → return input unchanged
51
+ * (same reference). Ordinary nodes are not filtered.
52
+ * 3. Bound but no matching marker → a leading compaction summary is replaced
48
53
  * with a synthetic boundary while its retained tail remains visible. Every
49
54
  * other markerless shape fails closed with the synthetic boundary alone.
50
55
  */
51
56
  export function visibleMessages(messages, opts) {
52
- const markerIndex = messages.findIndex((message) => isReviewBoundary(message));
57
+ const markerIndex = messages.findIndex((message) => isReviewBoundary(message, opts.boundaryReviewId));
53
58
  if (markerIndex !== -1)
54
59
  return messages.slice(markerIndex);
55
- if (!opts.reviewCompanion)
60
+ if (opts.boundaryReviewId === undefined)
56
61
  return messages;
57
62
  const [first, ...retainedTail] = messages;
58
63
  if (first?.role === 'compactionSummary') {
@@ -68,14 +73,18 @@ export function visibleMessages(messages, opts) {
68
73
  * boundary is crossed, its complete descendant subtree remains navigable. An
69
74
  * absent boundary produces an empty tree, so picker previews never disclose an
70
75
  * inherited entry before a mutation guard can reject its selection.
76
+ *
77
+ * `boundaryReviewId` selects only this binding's marker as a root, so a
78
+ * companion forked from another companion never navigates into the inherited
79
+ * subtree rooted at the origin's earlier marker.
71
80
  */
72
- export function boundaryRootedTree(tree) {
81
+ export function boundaryRootedTree(tree, boundaryReviewId) {
73
82
  const retainSubtree = (node) => ({
74
83
  ...node,
75
84
  children: node.children.map(retainSubtree),
76
85
  });
77
86
  const project = (node) => {
78
- if (isReviewBoundary(node.entry))
87
+ if (isReviewBoundary(node.entry, boundaryReviewId))
79
88
  return [retainSubtree(node)];
80
89
  return node.children.flatMap(project);
81
90
  };
@@ -1,4 +1,12 @@
1
- import type { PiInvocation } from './launch.js';
1
+ import type { PiInvocation } from './invocation.js';
2
+ /** The unshimmed PATH admitted from the host. Broker and cron shells resolve
3
+ * crouter-owned executables from this value, while commands those shells run
4
+ * still receive the shim-prepended PATH. */
5
+ export declare const ADMITTED_HOST_PATH_ENV = "CRTR_ADMITTED_HOST_PATH";
6
+ /** Resolve a regular executable against a shell PATH without executing it.
7
+ * The result is absolute, or null when no PATH entry supplies the command. */
8
+ export declare function resolvePathExecutable(name: string, env: NodeJS.ProcessEnv, cwd: string): string | null;
9
+ export declare function resolveAdmittedHostCommand(name: string, env: NodeJS.ProcessEnv, cwd: string): string;
2
10
  /** Sources A–C: the operational base shared by the broker boundary (this
3
11
  * module's `buildBrokerEnv`) and cron executor — the single shared definition
4
12
  * of "non-secret host state the runtime exposes to any spawned shell." Does not include
@@ -15,6 +15,13 @@
15
15
  // `spawnEnv.allow` entry. `inv.env` (D) is crtr-constructed and trusted, so
16
16
  // it overlays unconditionally, layered after E.
17
17
  //
18
+ // One name IS rewritten rather than merely admitted: `PATH`. Bare-binary
19
+ // contributions (`bin` in a plugin manifest or a scope `config.json`) are
20
+ // materialized into a crouter-owned shim directory by
21
+ // `core/runtime/bin-contributions.ts`, and that directory is PREPENDED here so
22
+ // a declared bare command wins its name in every node's bash. The admitted
23
+ // host PATH stays intact behind it.
24
+ //
18
25
  // `GIT_*` is deliberately absent from the allowlist: the whole namespace is an
19
26
  // auth/config-injection surface (`GIT_CONFIG_KEY_*`/`GIT_CONFIG_VALUE_*`,
20
27
  // `GIT_ASKPASS`, `GIT_SSH_COMMAND`), not a non-secret operational family, and
@@ -22,9 +29,12 @@
22
29
  // `~/.gitconfig`, reached through the allowlisted `HOME`; SSH remotes via the
23
30
  // allowlisted `SSH_AUTH_SOCK`). A deliberate `GIT_*` need goes through
24
31
  // `spawnEnv.allow`.
32
+ import { statSync } from 'node:fs';
33
+ import { delimiter, resolve } from 'node:path';
25
34
  import { readMergedLaunchConfig } from '../config.js';
26
35
  import { readProfileEnvVars } from '../profiles/env-store.js';
27
- import { FRONT_DOOR_ENV } from './front-door.js';
36
+ import { binShimDirFor } from './bin-contributions.js';
37
+ import { FRONT_DOOR_ENV } from './front-door-env.js';
28
38
  /** A1 — literal env-var names copied verbatim (no value inspection): a login
29
39
  * shell plus dev toolchain needs exactly these, and none of them can carry a
30
40
  * credential. */
@@ -54,6 +64,39 @@ const OPERATIONAL_LITERAL_NAMES = new Set([
54
64
  * file-backed in `auth.json`, never an env value). `GIT_` is NOT here — see
55
65
  * module comment. */
56
66
  const OPERATIONAL_PREFIXES = ['LC_', 'XDG_', 'PI_'];
67
+ /** The unshimmed PATH admitted from the host. Broker and cron shells resolve
68
+ * crouter-owned executables from this value, while commands those shells run
69
+ * still receive the shim-prepended PATH. */
70
+ export const ADMITTED_HOST_PATH_ENV = 'CRTR_ADMITTED_HOST_PATH';
71
+ /** Resolve a regular executable against a shell PATH without executing it.
72
+ * The result is absolute, or null when no PATH entry supplies the command. */
73
+ export function resolvePathExecutable(name, env, cwd) {
74
+ const pathValue = env['PATH'];
75
+ if (pathValue === undefined || pathValue === '')
76
+ return null;
77
+ for (const directory of pathValue.split(delimiter)) {
78
+ const candidate = resolve(cwd, directory === '' ? '.' : directory, name);
79
+ try {
80
+ const stat = statSync(candidate);
81
+ if (stat.isFile() && (stat.mode & 0o111) !== 0)
82
+ return candidate;
83
+ }
84
+ catch {
85
+ // Keep searching exactly as PATH resolution does.
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+ export function resolveAdmittedHostCommand(name, env, cwd) {
91
+ const pathValue = env[ADMITTED_HOST_PATH_ENV] ?? env['PATH'];
92
+ if (pathValue === undefined || pathValue === '') {
93
+ throw new Error(`cannot resolve crouter-owned ${name}: the admitted host PATH is empty`);
94
+ }
95
+ const command = resolvePathExecutable(name, { PATH: pathValue }, cwd);
96
+ if (command !== null)
97
+ return command;
98
+ throw new Error(`cannot resolve crouter-owned ${name} from the admitted host PATH`);
99
+ }
57
100
  /** A2 — proxy var names. Admitted only when the value's URL authority carries
58
101
  * no userinfo component (no `user@` / `user:pass@`) — the one value-inspected
59
102
  * entry in the whole builder, because a proxy var is the single operational
@@ -151,6 +194,21 @@ export function buildOperationalEnvBase(opts) {
151
194
  out[key] = value;
152
195
  }
153
196
  }
197
+ // Preserve the unshimmed admitted host PATH across broker and cron children.
198
+ // A nested `crtr` launched from an agent inherits this constructed value,
199
+ // rather than mistaking its shim-prepended PATH for the host path.
200
+ const admittedHostPath = host[ADMITTED_HOST_PATH_ENV] ?? out['PATH'];
201
+ if (admittedHostPath !== undefined && admittedHostPath !== '')
202
+ out[ADMITTED_HOST_PATH_ENV] = admittedHostPath;
203
+ // Bare-binary contributions (`core/runtime/bin-contributions.ts`): the shim
204
+ // dir holding one symlink per declared bare command, PREPENDED so a
205
+ // contribution wins its name, with the host PATH admitted above left intact
206
+ // behind it. Resolved from the TARGET's own cwd/profile like source C.
207
+ const shimDir = binShimDirFor(opts.targetCwd, opts.targetProfileId);
208
+ if (shimDir !== null) {
209
+ const hostPath = out['PATH'];
210
+ out['PATH'] = hostPath === undefined || hostPath === '' ? shimDir : `${shimDir}:${hostPath}`;
211
+ }
154
212
  return out;
155
213
  }
156
214
  /** The one authoritative broker child env: sources A–C (the operational
@@ -170,10 +228,12 @@ export function buildOperationalEnvBase(opts) {
170
228
  export function buildBrokerEnv(inv) {
171
229
  const targetCwd = inv.env['CRTR_NODE_CWD'] ?? process.cwd();
172
230
  const targetProfileId = inv.env['CRTR_PROFILE_ID'] || null;
231
+ const operational = buildOperationalEnvBase({ targetCwd, targetProfileId });
173
232
  return {
174
- ...buildOperationalEnvBase({ targetCwd, targetProfileId }),
233
+ ...operational,
175
234
  ...readProfileEnvVars(targetProfileId),
176
235
  ...inv.env,
236
+ [ADMITTED_HOST_PATH_ENV]: operational[ADMITTED_HOST_PATH_ENV],
177
237
  [FRONT_DOOR_ENV]: '1',
178
238
  };
179
239
  }
@@ -401,15 +401,15 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
401
401
  // have bound view.sock yet. Hand the attach pane CRTR_ATTACH_WAIT_MS so
402
402
  // it self-polls the socket until it accepts instead of connecting once,
403
403
  // finding a cold socket, and closing the pane instantly — the exact
404
- // mechanism bootRoot uses. Mark this as the initial auto-opened child
405
- // viewer too, so whip_mode can greet it without firing on ordinary
406
- // focus/attach paths. No pre-wait in this CLI subprocess.
404
+ // mechanism bootRoot uses. A child with a kickoff prompt marks its
405
+ // initial viewer for whip_mode; ordinary focus/attach paths stay
406
+ // silent. No pre-wait in this CLI subprocess.
407
407
  const viewer = openViewerWindow(meta.node_id, parentViewer.session ?? '', {
408
408
  name: fullName(meta),
409
409
  cwd: opts.cwd,
410
410
  besidePane: parentViewer.pane,
411
411
  waitForBroker: true,
412
- initialWhip: true,
412
+ initialWhip: hasKickoff,
413
413
  });
414
414
  if (viewer !== null && viewer.pane !== null && viewer.pane !== '') {
415
415
  session = viewer.session;
@@ -2,8 +2,8 @@ export { STALL_REPROMPT } from '../../shared/generated-context.js';
2
2
  export interface StopSignals {
3
3
  /** Did the node call `push --final` (finish) this turn? */
4
4
  pushedFinal: boolean;
5
- /** Did the node call `crtr ask` (escalate to the human) this turn? */
6
- askedHuman: boolean;
5
+ /** Did the node call `crtr ask` (escalate to the user) this turn? */
6
+ requestedHumanReply: boolean;
7
7
  }
8
8
  export type StopAction = {
9
9
  action: 'allow';
@@ -37,7 +37,7 @@ function formatInvalidStructuredOutputReprompt(error) {
37
37
  `1. Remove the invalid file (the output-schema.json in your node directory has been corrupted)\n` +
38
38
  `2. Fix the corruption if you know the original request schema\n\n` +
39
39
  `After resolving the file, retry the submission. ` +
40
- `If this is unexpected, escalate to the human using \`crtr human ask\`.`);
40
+ `If this is unexpected, escalate to the user using \`crtr human send\`.`);
41
41
  }
42
42
  /** Decide what to do when a node stops. crtrd supplies every current-canvas
43
43
  * fact; the broker contributes only whether its Pi-owned background job registry
@@ -45,7 +45,7 @@ function formatInvalidStructuredOutputReprompt(error) {
45
45
  export function evaluateStop(nodeId, signals, backgroundJobsRunning = activeBackgroundBashJobs(contextDir(nodeId)).length > 0) {
46
46
  if (signals.pushedFinal)
47
47
  return { action: 'allow', reason: 'finished' };
48
- if (signals.askedHuman)
48
+ if (signals.requestedHumanReply)
49
49
  return { action: 'allow', reason: 'escalated' };
50
50
  // A pending structured-output request overrides every stored-state dormancy
51
51
  // path: a post-final one-off follow-up still owes `submit` before it may stop.
@@ -1,6 +1,6 @@
1
1
  export interface StopSignals {
2
2
  pushedFinal: boolean;
3
- askedHuman: boolean;
3
+ requestedHumanReply: boolean;
4
4
  }
5
5
  export type StopSignal = keyof StopSignals;
6
6
  /** Record a successful command outcome for this broker process. The stophook
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { jobDir } from '../canvas/paths.js';
4
- const SIGNALS = ['pushedFinal', 'askedHuman'];
4
+ const SIGNALS = ['pushedFinal', 'requestedHumanReply'];
5
5
  function signalPath(nodeId, signal) {
6
6
  return join(jobDir(nodeId), `.${signal}`);
7
7
  }
@@ -24,7 +24,7 @@ export function markStopSignal(nodeId, signal) {
24
24
  /** Consume command outcomes from this broker's just-settled turn. Markers from
25
25
  * an earlier crashed broker are discarded rather than bleeding into its successor. */
26
26
  export function consumeStopSignals(nodeId) {
27
- const signals = { pushedFinal: false, askedHuman: false };
27
+ const signals = { pushedFinal: false, requestedHumanReply: false };
28
28
  for (const signal of SIGNALS) {
29
29
  const path = signalPath(nodeId, signal);
30
30
  if (!existsSync(path))
@@ -522,7 +522,7 @@ function restoreWrappedBinding(wrapper) {
522
522
  // not the sealed generation that installed it. The neighboring `crtr surface node cycle` builders
523
523
  // share that property.
524
524
  export function inboxToggleTmuxCommand() {
525
- return ['run-shell', '-b', 'crtr human inbox toggle --quiet --tmux-socket "#{socket_path}" --tmux-client "#{client_name}" --target-pane "#{pane_id}"'];
525
+ return ['run-shell', '-b', 'crtr surface inbox --quiet --tmux-socket "#{socket_path}" --tmux-client "#{client_name}" --target-pane "#{pane_id}"'];
526
526
  }
527
527
  function actionCommand(id) {
528
528
  switch (id) {
@@ -24,9 +24,10 @@ export interface SessionState {
24
24
  /** Live context occupancy in prompt tokens: seeded from the snapshot's
25
25
  * contextUsage, then tracked off each assistant `message_end`. */
26
26
  contextTokens: number | undefined;
27
- /** Queued steering + follow-up messages, from `queue_update`. A fresh broker
28
- * with an empty queue emits no `queue_update`, so every welcome resets this
29
- * rather than leaving a dead broker's rows on screen. */
27
+ /** Queued steering + follow-up messages: seeded from `welcome.snapshot.queued`
28
+ * and tracked off `queue_update`. Every welcome REPLACES this — the attaching
29
+ * broker's own queue is the whole truth, so a dead broker's rows never survive
30
+ * and a client attaching mid-queue still sees what is waiting. */
30
31
  queued: Array<{
31
32
  text: string;
32
33
  }>;
@@ -48,7 +48,7 @@ export function applySnapshot(frame) {
48
48
  agentDir: frame.agentDir,
49
49
  engine: snapshot.state,
50
50
  contextTokens: contextUsage?.tokens ?? undefined,
51
- queued: [],
51
+ queued: snapshotQueued(snapshot),
52
52
  display: {
53
53
  statuses: { ...display.statuses },
54
54
  widgets: Object.fromEntries(Object.entries(display.widgets).map(([k, w]) => [k, { lines: [...w.lines], placement: w.placement }])),
@@ -140,6 +140,19 @@ export function reduce(state, frame) {
140
140
  return state;
141
141
  }
142
142
  }
143
+ /** The queue a `welcome` reports, composed exactly as `queue_update` composes
144
+ * it. Absent on a snapshot from a broker generation older than the field, and
145
+ * on an offline node (no engine, so no queue) — both mean nothing is waiting. */
146
+ function snapshotQueued(snapshot) {
147
+ const queued = snapshot.queued;
148
+ if (typeof queued !== 'object' || queued === null)
149
+ return [];
150
+ const { steering, followUp } = queued;
151
+ return [...texts(steering), ...texts(followUp)].map((text) => ({ text }));
152
+ }
153
+ function texts(value) {
154
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
155
+ }
143
156
  /** Apply a partial patch to pi's state, preserving reference identity when the
144
157
  * patch changes nothing (and when no welcome has seeded `engine` yet). */
145
158
  function patchEngine(state, patch) {
@@ -147,10 +147,40 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
147
147
  setFromString: (config: ScopeConfig, raw: string) => string | null;
148
148
  };
149
149
  };
150
+ page_surface: {
151
+ surface: "settings";
152
+ setting: {
153
+ key: "page_surface";
154
+ label: string;
155
+ description: string;
156
+ valueConstraint: string;
157
+ applies: string;
158
+ group: "display";
159
+ order: number;
160
+ format: (config: ScopeConfig) => "on" | "off";
161
+ cycle: (config: ScopeConfig) => void;
162
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
163
+ };
164
+ };
150
165
  whip_messages: {
151
166
  surface: "specialized";
152
167
  owner: string;
153
168
  };
169
+ whip_message_mode: {
170
+ surface: "settings";
171
+ setting: {
172
+ key: "whip_message_mode";
173
+ label: string;
174
+ description: string;
175
+ valueConstraint: string;
176
+ applies: string;
177
+ group: "display";
178
+ order: number;
179
+ format: (config: ScopeConfig) => "random" | "go faster";
180
+ cycle: (config: ScopeConfig, delta: number) => void;
181
+ setFromString: (config: ScopeConfig, raw: string) => string | null;
182
+ };
183
+ };
154
184
  mouse_mode_default: {
155
185
  surface: "settings";
156
186
  setting: {
@@ -196,10 +226,10 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
196
226
  setFromString: (config: ScopeConfig, raw: string) => string | null;
197
227
  };
198
228
  };
199
- html_pages: {
229
+ bash_tool_purpose: {
200
230
  surface: "settings";
201
231
  setting: {
202
- key: "html_pages";
232
+ key: "bash_tool_purpose";
203
233
  label: string;
204
234
  description: string;
205
235
  valueConstraint: string;
@@ -292,6 +322,10 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
292
322
  surface: "specialized";
293
323
  owner: string;
294
324
  };
325
+ bin: {
326
+ surface: "specialized";
327
+ owner: string;
328
+ };
295
329
  };
296
330
  /** The exhaustive top-level key list used by config persistence and help. */
297
331
  export declare const SCOPE_CONFIG_KEYS: (keyof ScopeConfig)[];
@@ -1,4 +1,4 @@
1
- import { CONDENSED_HISTORY_MODES, MOUSE_MODE_DEFAULTS } from '../types.js';
1
+ import { CONDENSED_HISTORY_MODES, MOUSE_MODE_DEFAULTS, WHIP_MESSAGE_MODES } from '../types.js';
2
2
  function cycleChoice(choices, current, delta) {
3
3
  const index = choices.indexOf(current);
4
4
  const start = index >= 0 ? index : 0;
@@ -158,7 +158,7 @@ export const CONFIG_SETTINGS_COVERAGE = {
158
158
  valueConstraint: 'true|false',
159
159
  applies: 'Applies on the next turn end.',
160
160
  group: 'display',
161
- order: 40,
161
+ order: 41,
162
162
  format: (config) => config.completion_bell ? 'on' : 'off',
163
163
  cycle: (config) => { config.completion_bell = !config.completion_bell; },
164
164
  setFromString: (config, raw) => {
@@ -176,7 +176,7 @@ export const CONFIG_SETTINGS_COVERAGE = {
176
176
  setting: {
177
177
  key: 'whip_mode',
178
178
  label: 'Whip mode',
179
- description: 'Play the whip animation when you send a prompt and when an automatically opened child viewer starts.',
179
+ description: 'Play the whip animation when you send a prompt and when a viewer opens for an agent with an initial prompt.',
180
180
  valueConstraint: 'true|false',
181
181
  applies: 'Applies on the next attach.',
182
182
  group: 'display',
@@ -192,7 +192,48 @@ export const CONFIG_SETTINGS_COVERAGE = {
192
192
  },
193
193
  },
194
194
  },
195
+ page_surface: {
196
+ surface: 'settings',
197
+ setting: {
198
+ key: 'page_surface',
199
+ label: 'Page human surface',
200
+ description: 'Prompt agents to use JSX pages by default for structured or interactive content shown to you.',
201
+ valueConstraint: 'true|false',
202
+ applies: 'Applies when human command help is assembled.',
203
+ group: 'display',
204
+ order: 37,
205
+ format: (config) => config.page_surface ? 'on' : 'off',
206
+ cycle: (config) => { config.page_surface = !config.page_surface; },
207
+ setFromString: (config, raw) => {
208
+ const value = booleanFromString(raw, 'page_surface');
209
+ if (typeof value === 'string')
210
+ return value;
211
+ config.page_surface = value;
212
+ return null;
213
+ },
214
+ },
215
+ },
195
216
  whip_messages: { surface: 'specialized', owner: 'sys config set whip_messages' },
217
+ whip_message_mode: {
218
+ surface: 'settings',
219
+ setting: {
220
+ key: 'whip_message_mode',
221
+ label: 'Whip message',
222
+ description: 'What the whip action sends: a random line from whip_messages, or the fixed “Go faster”.',
223
+ valueConstraint: WHIP_MESSAGE_MODES.join('|'),
224
+ applies: 'Applies on the next attach.',
225
+ group: 'display',
226
+ order: 40,
227
+ format: (config) => config.whip_message_mode === 'go-faster' ? 'go faster' : 'random',
228
+ cycle: (config, delta) => { config.whip_message_mode = cycleChoice(WHIP_MESSAGE_MODES, config.whip_message_mode, delta); },
229
+ setFromString: (config, raw) => {
230
+ if (!WHIP_MESSAGE_MODES.includes(raw))
231
+ return `whip_message_mode must be one of: ${WHIP_MESSAGE_MODES.join('|')}`;
232
+ config.whip_message_mode = raw;
233
+ return null;
234
+ },
235
+ },
236
+ },
196
237
  mouse_mode_default: {
197
238
  surface: 'settings',
198
239
  setting: {
@@ -254,23 +295,23 @@ export const CONFIG_SETTINGS_COVERAGE = {
254
295
  },
255
296
  },
256
297
  },
257
- html_pages: {
298
+ bash_tool_purpose: {
258
299
  surface: 'settings',
259
300
  setting: {
260
- key: 'html_pages',
261
- label: 'HTML pages',
262
- description: 'Teach agent help the HTML page-authoring dialect. Off keeps page authoring Markdown-only.',
301
+ key: 'bash_tool_purpose',
302
+ label: 'Bash purpose field',
303
+ description: 'Expose the optional purpose label to agents in the bash tool schema. Off removes the field without disabling the bash valve.',
263
304
  valueConstraint: 'true|false',
264
- applies: 'Applies on the next help request.',
305
+ applies: 'Applies on the next node session.',
265
306
  group: 'display',
266
- order: 70,
267
- format: (config) => config.html_pages ? 'on' : 'off',
268
- cycle: (config) => { config.html_pages = !config.html_pages; },
307
+ order: 25,
308
+ format: (config) => config.bash_tool_purpose ? 'on' : 'off',
309
+ cycle: (config) => { config.bash_tool_purpose = !config.bash_tool_purpose; },
269
310
  setFromString: (config, raw) => {
270
- const value = booleanFromString(raw, 'html_pages');
311
+ const value = booleanFromString(raw, 'bash_tool_purpose');
271
312
  if (typeof value === 'string')
272
313
  return value;
273
- config.html_pages = value;
314
+ config.bash_tool_purpose = value;
274
315
  return null;
275
316
  },
276
317
  },
@@ -344,9 +385,10 @@ export const CONFIG_SETTINGS_COVERAGE = {
344
385
  modelRoutes: { surface: 'specialized', owner: 'Models route declarations' },
345
386
  modelRouting: { surface: 'specialized', owner: 'Models route declarations' },
346
387
  kinds: { surface: 'specialized', owner: 'Models tab and kind registry' },
347
- page_components: { surface: 'specialized', owner: 'Page component catalog: product-registered page slot kinds' },
388
+ page_components: { surface: 'specialized', owner: 'Page component catalog: product-registered page component kinds' },
348
389
  remoteCanvas: { surface: 'specialized', owner: 'canvas config commands' },
349
390
  spawnEnv: { surface: 'specialized', owner: 'spawn environment allow-list' },
391
+ bin: { surface: 'specialized', owner: 'bare-binary contributions on every node PATH' },
350
392
  };
351
393
  /** The exhaustive top-level key list used by config persistence and help. */
352
394
  export const SCOPE_CONFIG_KEYS = Object.keys(CONFIG_SETTINGS_COVERAGE);
@@ -1,4 +1,3 @@
1
- import { type GitResult, type GitSyncOptions } from './git.js';
2
1
  import { type NodeMeta, type ManagedWorktree } from './canvas/index.js';
3
2
  export interface WorktreeCommandResult {
4
3
  ok: boolean;
@@ -23,32 +22,24 @@ export interface CloseManagedWorktreeResult {
23
22
  branch: string;
24
23
  worktree_path: string;
25
24
  landed_sha: string;
26
- /** False when the landing happened but the worktree checkout could not be
27
- * removed automatically afterward — the close still counts as done (state is
28
- * closed, push-final is unblocked); `worktree_remove_error` carries the
29
- * manual cleanup note. */
25
+ /** Always false for a live caller: cleanup is deliberately pending until
26
+ * the caller exits and the reported manual command can safely remove it. */
30
27
  worktree_removed: boolean;
31
28
  worktree_remove_error?: string;
32
- /** False when the landing happened but the local branch could not be deleted
33
- * afterward — same non-fatal treatment as worktree_removed. */
29
+ /** Always false while the pending-cleanup checkout has this branch checked out. */
34
30
  branch_deleted: boolean;
35
31
  branch_delete_error?: string;
36
32
  }
37
33
  export declare function closeManagedWorktree(nodeId: string): CloseManagedWorktreeResult;
38
- type WorktreeCleanupRunner = (args: string[], cwd: string, options: GitSyncOptions) => GitResult;
39
- /** Test seam for the bounded post-land cleanup regression. */
40
- export declare function setWorktreeCleanupRunnerForTest(runner?: WorktreeCleanupRunner): void;
41
34
  /** Recovery guidance for a vanished checkout whose refs do not prove that its
42
35
  * work is delivered. This deliberately never recommends `worktree close`:
43
36
  * that command cannot operate on the absent checkout. */
44
37
  export declare function missingWorktreeRecoveryHint(wt: ManagedWorktree): string;
45
38
  interface CleanEmptyProof {
46
- /** The branch's HEAD SHA at the moment this was proven clean-and-contained
47
- * — the exact value the destructive delete must be conditioned on. */
39
+ /** The branch's HEAD SHA at the moment this was proven clean and contained
40
+ * in the current base. */
48
41
  branchSha: string;
49
- /** The base branch tip the proof was made against; re-resolved and
50
- * re-checked immediately before the destructive delete, since the proof
51
- * and the delete are two different points in time. */
42
+ /** The current base tip, retained for race diagnostics. */
52
43
  baseSha: string;
53
44
  }
54
45
  /** Fix B's shared "nothing to land" test — the SAME clean-worktree definition
@@ -58,20 +49,11 @@ interface CleanEmptyProof {
58
49
  * be a git-ancestor of (or equal to) the CURRENT tip of its recorded base
59
50
  * branch — i.e. landing it would be a pure no-op. Any ambiguity (missing
60
51
  * path, mid-rebase, wrong branch, an unresolvable base, or a real ancestry
61
- * divergence) returns null: this function only ever proves "safe to drop"
52
+ * divergence) returns null: this function only ever proves a no-op land
62
53
  * when a live git check establishes it, never on a heuristic guess, so real
63
- * unlanded work always falls through to the existing explicit-close guard.
64
- * This proof is NOT itself the safety boundary — it only decides whether
65
- * auto-drop is even attempted. `autoDropCleanManagedWorktreeLocked` below
66
- * re-proves containment against the LIVE base immediately before the
67
- * destructive delete, refuses outright if the checkout cannot be removed,
68
- * and conditions the delete itself on BOTH `branchSha` and that same live
69
- * base SHA via `bestEffortDeleteBranchWithBaseProof`'s atomic multi-ref
70
- * transaction — those checks, not this one, are what close the TOCTOU gap.
71
- * Exported (alongside
72
- * `autoDropCleanManagedWorktreeLocked`) purely so regression tests can
73
- * inject a race in the window between proving and cleanup; production code
74
- * must always go through `autoDropCleanManagedWorktree`. */
54
+ * unlanded work always falls through to the explicit-close guard. The locked
55
+ * path re-checks this predicate before recording deferred cleanup; production
56
+ * code must always go through `autoDropCleanManagedWorktree`. */
75
57
  export declare function proveCleanEmptyManagedWorktree(wt: ManagedWorktree): CleanEmptyProof | null;
76
58
  export interface AutoDroppedWorktree {
77
59
  node_id: string;
@@ -82,22 +64,10 @@ export interface AutoDroppedWorktree {
82
64
  branch_deleted: boolean;
83
65
  branch_delete_error?: string;
84
66
  }
85
- /** Exported (see `proveCleanEmptyManagedWorktree`'s doc) so regression tests
86
- * can call it directly with a proof captured before injecting a race.
87
- * Returns null — persisting NO state change — when the immediate base
88
- * re-verification, the checkout removal, or the atomic base+branch delete
89
- * refuses: the caller (`autoDropCleanManagedWorktree`, and `push final`
90
- * above it) then falls through to the existing `open_managed_worktree`
91
- * guard exactly as if this worktree had never looked droppable. In every
92
- * refusal case the branch, and every commit reachable from it, is
93
- * unconditionally preserved — that is the actual safety property this
94
- * function guarantees. A checkout-removal refusal in particular must be
95
- * treated as a full refusal, not a partial success: if the checkout cannot
96
- * be removed (e.g. a file reappeared after the clean predicate ran), the
97
- * branch is still checked out somewhere, so this function must never reach
98
- * the ref-deletion transaction or mutate managed-worktree state — both the
99
- * branch and the live checkout stay fully recoverable and the caller's
100
- * `open_managed_worktree` guard fires. */
67
+ /** Re-check a clean auto-drop before closing its record. `push final` runs
68
+ * inside the owner broker, so it has the same destructive-cwd boundary as
69
+ * explicit close: removal and branch deletion are deferred until that owner
70
+ * has exited. */
101
71
  export declare function autoDropCleanManagedWorktreeLocked(nodeId: string, wt: ManagedWorktree, proof: CleanEmptyProof): AutoDroppedWorktree | null;
102
72
  /** `push final`'s escape hatch: a managed worktree that has
103
73
  * nothing to land is auto-dropped instead of blocking on an explicit
@@ -106,7 +76,7 @@ export declare function autoDropCleanManagedWorktreeLocked(nodeId: string, wt: M
106
76
  * that never had anything to close in the first place (a read-only
107
77
  * review node). Returns null and changes nothing when the worktree is not
108
78
  * PROVABLY empty-and-clean by `proveCleanEmptyManagedWorktree`, OR when a
109
- * concurrent mutation invalidates that proof before the destructive delete
79
+ * concurrent mutation invalidates that proof before the record is closed
110
80
  * (see `autoDropCleanManagedWorktreeLocked`); the caller's existing
111
81
  * `open_managed_worktree` guard then still applies unchanged — real
112
82
  * commits, real uncommitted changes, and a lost race all require the