@north-light/crouter 0.3.233 → 0.3.235

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 (411) hide show
  1. package/dist/api/client.js +16 -15
  2. package/dist/api/dto/nodes.d.ts +11 -0
  3. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +1 -1
  4. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +14 -17
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -11
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +8 -8
  7. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +10 -11
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +2 -2
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +3 -3
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -7
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +8 -8
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +6 -6
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +3 -3
  14. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +1 -1
  15. package/dist/clients/attach/chrome/ticket-panel.js +15 -2
  16. package/dist/clients/attach/overlays/graph.js +0 -3
  17. package/dist/clients/attach/overlays/mcp.js +1 -2
  18. package/dist/clients/attach/render/chat-view.js +0 -12
  19. package/dist/clients/attach/render/crtr-output.js +0 -2
  20. package/dist/clients/attach/render/tool-calls.js +0 -6
  21. package/dist/clients/attach/slash/dispatch.js +8 -11
  22. package/dist/clients/attach/viewer.js +535 -537
  23. package/dist/clients/conversation/projection.js +0 -2
  24. package/dist/clients/inbox/controller.js +22 -3
  25. package/dist/clients/inbox/review/companion-pane.js +0 -4
  26. package/dist/clients/inbox/review-adapter.js +0 -7
  27. package/dist/clients/inbox/tui/slots.d.ts +3 -3
  28. package/dist/clients/inbox/tui/types.d.ts +2 -2
  29. package/dist/commands/api-client.js +0 -8
  30. package/dist/commands/attention.js +1 -9
  31. package/dist/commands/canvas-browse.js +2 -1
  32. package/dist/commands/canvas-config.js +0 -10
  33. package/dist/commands/canvas-history.js +2 -1
  34. package/dist/commands/cron.js +10 -23
  35. package/dist/commands/dashboard.js +0 -2
  36. package/dist/commands/human/feedback.js +2 -1
  37. package/dist/commands/human/prompts.js +2 -5
  38. package/dist/commands/human/queue.js +2 -7
  39. package/dist/commands/human/request.js +0 -14
  40. package/dist/commands/human/review.js +2 -1
  41. package/dist/commands/human.js +1 -1
  42. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
  43. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.d.ts +1 -0
  44. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +96 -0
  45. package/dist/commands/memory/delete.js +1 -1
  46. package/dist/commands/memory/edit.js +6 -3
  47. package/dist/commands/memory/find.js +2 -2
  48. package/dist/commands/memory/lint.js +2 -1
  49. package/dist/commands/memory/list.d.ts +4 -0
  50. package/dist/commands/memory/list.js +20 -14
  51. package/dist/commands/memory/move.js +1 -1
  52. package/dist/commands/memory/read.d.ts +2 -7
  53. package/dist/commands/memory/read.js +5 -16
  54. package/dist/commands/memory/shared.d.ts +1 -0
  55. package/dist/commands/memory/shared.js +17 -8
  56. package/dist/commands/memory/write.js +8 -4
  57. package/dist/commands/node/bash.js +2 -1
  58. package/dist/commands/node/create.js +2 -1
  59. package/dist/commands/node/inspect.js +11 -10
  60. package/dist/commands/node/lifecycle.js +5 -13
  61. package/dist/commands/node/message.js +11 -6
  62. package/dist/commands/node/subscription.js +7 -2
  63. package/dist/commands/node/wait.js +9 -4
  64. package/dist/commands/node-context.js +2 -1
  65. package/dist/commands/node-lifecycle-revive.js +0 -2
  66. package/dist/commands/node-worktree.js +2 -1
  67. package/dist/commands/pkg/browse/actions.d.ts +1 -1
  68. package/dist/commands/pkg/browse/actions.js +4 -4
  69. package/dist/commands/pkg/browse/catalog.d.ts +1 -1
  70. package/dist/commands/pkg/browse/catalog.js +15 -15
  71. package/dist/commands/pkg/browse/command-view.d.ts +1 -1
  72. package/dist/commands/pkg/browse/command-view.js +3 -3
  73. package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
  74. package/dist/commands/pkg/browse/detail-panel.js +8 -8
  75. package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
  76. package/dist/commands/pkg/browse/doc-view.js +3 -3
  77. package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
  78. package/dist/commands/pkg/browse/list-panel.js +3 -3
  79. package/dist/commands/pkg/browse/model.d.ts +3 -3
  80. package/dist/commands/pkg/browse/shell.d.ts +2 -2
  81. package/dist/commands/pkg/browse/shell.js +13 -13
  82. package/dist/commands/pkg/plugin-manage.js +3 -2
  83. package/dist/commands/pkg/shared.js +0 -2
  84. package/dist/commands/pkg.js +1 -0
  85. package/dist/commands/profile/delete.js +2 -1
  86. package/dist/commands/profile/rename.js +1 -1
  87. package/dist/commands/push.js +2 -3
  88. package/dist/commands/revive.js +0 -2
  89. package/dist/commands/surface/node/placement.js +2 -1
  90. package/dist/commands/surface-edit.js +2 -1
  91. package/dist/commands/sys/config.js +1 -4
  92. package/dist/commands/sys/daemon.js +0 -10
  93. package/dist/commands/sys/doctor.js +3 -2
  94. package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
  95. package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
  96. package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
  97. package/dist/commands/sys/panels/dependencies-panel.js +2 -2
  98. package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
  99. package/dist/commands/sys/panels/keybindings-panel.js +5 -5
  100. package/dist/commands/sys/panels/models-panel.d.ts +4 -4
  101. package/dist/commands/sys/panels/models-panel.js +11 -10
  102. package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
  103. package/dist/commands/sys/panels/plugins-panel.js +2 -2
  104. package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
  105. package/dist/commands/sys/panels/prefs-panel.js +4 -4
  106. package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
  107. package/dist/commands/sys/panels/profiles-panel.js +14 -13
  108. package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
  109. package/dist/commands/sys/panels/provider-panel.js +8 -8
  110. package/dist/commands/sys/settings-shell.d.ts +3 -3
  111. package/dist/commands/sys/settings-shell.js +15 -15
  112. package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
  113. package/dist/commands/sys/setup-settings-runner.js +5 -5
  114. package/dist/commands/sys/setup-wizard.d.ts +4 -4
  115. package/dist/commands/sys/setup-wizard.js +8 -8
  116. package/dist/commands/sys/support/prepare.js +4 -4
  117. package/dist/commands/sys/support/submit.js +2 -2
  118. package/dist/commands/sys/sync-deps.js +0 -2
  119. package/dist/commands/sys/sync-project-guidance.js +2 -1
  120. package/dist/commands/sys/sysprompt.js +2 -1
  121. package/dist/commands/sys.js +1 -0
  122. package/dist/core/__tests__/boot.test.js +0 -8
  123. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
  124. package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
  125. package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
  126. package/dist/core/__tests__/canvas.test.js +1 -1
  127. package/dist/core/__tests__/child-death-wake.test.js +0 -2
  128. package/dist/core/__tests__/cli-help-contract.test.d.ts +1 -0
  129. package/dist/core/__tests__/cli-help-contract.test.js +150 -0
  130. package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
  131. package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
  132. package/dist/core/__tests__/daemon-wedge.test.js +0 -4
  133. package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
  134. package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
  135. package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
  136. package/dist/core/__tests__/focuses.test.js +0 -6
  137. package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
  138. package/dist/core/__tests__/fs-utils.test.js +50 -0
  139. package/dist/core/__tests__/helpers/harness.js +1 -1
  140. package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
  141. package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
  142. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
  143. package/dist/core/__tests__/integration/command-plugins.test.js +21 -24
  144. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
  145. package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
  146. package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
  147. package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
  148. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
  149. package/dist/core/__tests__/integration/revive.test.js +0 -12
  150. package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
  151. package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
  152. package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
  153. package/dist/core/__tests__/lifecycle.test.js +0 -18
  154. package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
  155. package/dist/core/__tests__/migration.test.js +2 -2
  156. package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
  157. package/dist/core/__tests__/pid-identity-match.test.js +1 -1
  158. package/dist/core/__tests__/plugin-kinds.test.js +1 -1
  159. package/dist/core/__tests__/plugin-page-components.test.js +1 -1
  160. package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
  161. package/dist/core/__tests__/respawn-throttle.test.js +0 -4
  162. package/dist/core/__tests__/revive-all.test.js +5 -5
  163. package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
  164. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
  165. package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
  166. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
  167. package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
  168. package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
  169. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +11 -6
  170. package/dist/core/activity-signal.d.ts +7 -0
  171. package/dist/core/activity-signal.js +38 -0
  172. package/dist/core/auto-update.js +2 -1
  173. package/dist/core/bootstrap.js +2 -1
  174. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  175. package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
  176. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  177. package/dist/core/canvas/browse/app.js +3 -2
  178. package/dist/core/canvas/browse/model.d.ts +1 -1
  179. package/dist/core/canvas/browse/model.js +0 -4
  180. package/dist/core/canvas/canvas.d.ts +22 -0
  181. package/dist/core/canvas/canvas.js +61 -19
  182. package/dist/core/canvas/crons.js +0 -6
  183. package/dist/core/canvas/db.d.ts +14 -20
  184. package/dist/core/canvas/db.js +30 -1332
  185. package/dist/core/canvas/focuses.js +0 -4
  186. package/dist/core/canvas/history.js +2 -1
  187. package/dist/core/canvas/meta-file.js +18 -27
  188. package/dist/core/canvas/migrations.d.ts +20 -0
  189. package/dist/core/canvas/migrations.js +1339 -0
  190. package/dist/core/canvas/nav-model.js +1 -11
  191. package/dist/core/canvas/nav-render.js +0 -8
  192. package/dist/core/canvas/remote-canvas-source.d.ts +8 -0
  193. package/dist/core/canvas/remote-canvas-source.js +9 -3
  194. package/dist/core/canvas/render-source.js +31 -13
  195. package/dist/core/command-manifests/manifest.js +0 -10
  196. package/dist/core/command-manifests/registry.d.ts +2 -0
  197. package/dist/core/command-manifests/schema.js +0 -4
  198. package/dist/core/command-plugins/compose.js +4 -2
  199. package/dist/core/command-plugins/discovery.d.ts +1 -0
  200. package/dist/core/command-plugins/discovery.js +1 -1
  201. package/dist/core/command-plugins/transport/http-invoke.js +0 -14
  202. package/dist/core/command.js +2 -6
  203. package/dist/core/config.js +2 -1
  204. package/dist/core/fault-classifier.js +8 -0
  205. package/dist/core/feed/inbox.d.ts +10 -0
  206. package/dist/core/feed/inbox.js +18 -12
  207. package/dist/core/fs-utils.d.ts +9 -2
  208. package/dist/core/fs-utils.js +7 -15
  209. package/dist/core/help.js +0 -8
  210. package/dist/core/host-exports/export.d.ts +2 -2
  211. package/dist/core/host-exports/export.js +9 -12
  212. package/dist/core/human/answer.d.ts +2 -2
  213. package/dist/core/human/convention.d.ts +2 -5
  214. package/dist/core/human/convention.js +3 -13
  215. package/dist/core/human/page-schema.d.ts +2 -2
  216. package/dist/core/human/page-schema.js +2 -2
  217. package/dist/core/human/root.js +6 -23
  218. package/dist/core/human/scan.js +24 -7
  219. package/dist/core/human/tickets.d.ts +4 -1
  220. package/dist/core/human/tickets.js +20 -6
  221. package/dist/core/io.js +0 -12
  222. package/dist/core/memory/identity.js +0 -4
  223. package/dist/core/memory/inline-ref-inventory.js +3 -2
  224. package/dist/core/memory/lint.js +3 -6
  225. package/dist/core/memory-resolver.d.ts +5 -0
  226. package/dist/core/memory-resolver.js +14 -12
  227. package/dist/core/pagination.d.ts +2 -0
  228. package/dist/core/pagination.js +2 -3
  229. package/dist/core/preview-registry.d.ts +14 -0
  230. package/dist/core/preview-registry.js +2 -4
  231. package/dist/core/preview-result-path.js +2 -1
  232. package/dist/core/profiles/manifest.d.ts +37 -5
  233. package/dist/core/profiles/manifest.js +44 -24
  234. package/dist/core/profiles/select.js +0 -4
  235. package/dist/core/review/signal.js +4 -23
  236. package/dist/core/review/ticket-filter.d.ts +3 -3
  237. package/dist/core/review/ticket-filter.js +4 -9
  238. package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
  239. package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
  240. package/dist/core/runtime/bearings-render.d.ts +56 -0
  241. package/dist/core/runtime/bearings-render.js +241 -0
  242. package/dist/core/runtime/bearings.d.ts +2 -17
  243. package/dist/core/runtime/bearings.js +12 -198
  244. package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
  245. package/dist/core/runtime/broker/auth-reload.js +149 -0
  246. package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
  247. package/dist/core/runtime/broker/engine-drive.js +257 -0
  248. package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
  249. package/dist/core/runtime/broker/engine-routing.js +211 -0
  250. package/dist/core/runtime/broker/event-projection.js +3 -2
  251. package/dist/core/runtime/broker/fault-retry.js +11 -4
  252. package/dist/core/runtime/broker/frame-client.d.ts +38 -0
  253. package/dist/core/runtime/broker/frame-client.js +129 -0
  254. package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
  255. package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
  256. package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
  257. package/dist/core/runtime/broker/frame-engine-control.js +231 -0
  258. package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
  259. package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
  260. package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
  261. package/dist/core/runtime/broker/frame-model-query.js +69 -0
  262. package/dist/core/runtime/broker/frame-read.d.ts +19 -0
  263. package/dist/core/runtime/broker/frame-read.js +116 -0
  264. package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
  265. package/dist/core/runtime/broker/frame-replies.js +64 -0
  266. package/dist/core/runtime/broker/frame-session.d.ts +34 -0
  267. package/dist/core/runtime/broker/frame-session.js +203 -0
  268. package/dist/core/runtime/broker/rebind.js +50 -0
  269. package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
  270. package/dist/core/runtime/broker/turn-ignition.js +74 -0
  271. package/dist/core/runtime/broker-extension-render.js +9 -85
  272. package/dist/core/runtime/broker-protocol.js +0 -4
  273. package/dist/core/runtime/broker-sdk.js +2 -1
  274. package/dist/core/runtime/broker.d.ts +1 -1
  275. package/dist/core/runtime/broker.js +5 -23
  276. package/dist/core/runtime/fault.js +3 -2
  277. package/dist/core/runtime/headless-pi.js +3 -2
  278. package/dist/core/runtime/host.js +2 -3
  279. package/dist/core/runtime/kickoff.js +0 -4
  280. package/dist/core/runtime/launch-target.js +2 -1
  281. package/dist/core/runtime/launch.d.ts +2 -2
  282. package/dist/core/runtime/launch.js +20 -10
  283. package/dist/core/runtime/lifecycle.js +17 -26
  284. package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
  285. package/dist/core/runtime/naming.js +0 -2
  286. package/dist/core/runtime/nodes.js +2 -1
  287. package/dist/core/runtime/persona.js +0 -6
  288. package/dist/core/runtime/placement-tmux.js +2 -5
  289. package/dist/core/runtime/placement.js +4 -11
  290. package/dist/core/runtime/recap.js +4 -3
  291. package/dist/core/runtime/reopen.js +4 -7
  292. package/dist/core/runtime/reset.js +0 -4
  293. package/dist/core/runtime/resume-root.js +0 -2
  294. package/dist/core/runtime/revive.js +17 -13
  295. package/dist/core/runtime/session-list-cache.js +2 -2
  296. package/dist/core/runtime/shell-expansion.js +0 -2
  297. package/dist/core/runtime/spawn.js +0 -2
  298. package/dist/core/runtime/stop-guard.js +28 -16
  299. package/dist/core/runtime/stream-watchdog.d.ts +0 -1
  300. package/dist/core/runtime/stream-watchdog.js +13 -18
  301. package/dist/core/runtime/tmux-bindings.js +0 -2
  302. package/dist/core/runtime/tmux-driver.d.ts +12 -0
  303. package/dist/core/runtime/tmux-driver.js +18 -13
  304. package/dist/core/runtime/warm-pool.js +0 -4
  305. package/dist/core/scope.js +4 -3
  306. package/dist/core/spawn.js +2 -3
  307. package/dist/core/subscription-state.js +10 -17
  308. package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +3 -3
  309. package/dist/core/substrate/frontmatter-validation.d.ts +1 -0
  310. package/dist/core/substrate/frontmatter-validation.js +1 -1
  311. package/dist/core/substrate/render.js +0 -12
  312. package/dist/core/substrate/schema.js +0 -12
  313. package/dist/core/substrate/surface-match.js +0 -2
  314. package/dist/core/termrender/display.js +2 -1
  315. package/dist/core/tui/page-host.d.ts +3 -3
  316. package/dist/core/tui/page-host.js +4 -4
  317. package/dist/core/tui/panel.js +3 -3
  318. package/dist/core/wake.js +0 -12
  319. package/dist/core/worktree.js +0 -2
  320. package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
  321. package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
  322. package/dist/daemon/api/__tests__/error-seam.test.js +27 -0
  323. package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
  324. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
  325. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
  326. package/dist/daemon/api/handlers/attach.js +2 -1
  327. package/dist/daemon/api/handlers/broker-ops.js +3 -6
  328. package/dist/daemon/api/handlers/canvas.js +12 -18
  329. package/dist/daemon/api/handlers/feedback-comments.js +0 -8
  330. package/dist/daemon/api/handlers/health.js +3 -2
  331. package/dist/daemon/api/handlers/human-requests.js +0 -10
  332. package/dist/daemon/api/handlers/human.js +1 -5
  333. package/dist/daemon/api/handlers/inbox.js +0 -18
  334. package/dist/daemon/api/handlers/memory.js +1 -1
  335. package/dist/daemon/api/handlers/messages.js +16 -20
  336. package/dist/daemon/api/handlers/nodes.js +23 -22
  337. package/dist/daemon/api/handlers/reports.js +1 -1
  338. package/dist/daemon/api/handlers/reviews.js +1 -6
  339. package/dist/daemon/api/handlers/worktree.js +6 -22
  340. package/dist/daemon/api/map.d.ts +4 -2
  341. package/dist/daemon/api/map.js +22 -8
  342. package/dist/daemon/api/router.js +0 -4
  343. package/dist/daemon/api/server.js +2 -5
  344. package/dist/daemon/boot-migrations.d.ts +14 -0
  345. package/dist/daemon/boot-migrations.js +31 -0
  346. package/dist/daemon/cron/armed-context.d.ts +21 -0
  347. package/dist/daemon/cron/armed-context.js +100 -0
  348. package/dist/daemon/cron/capture.d.ts +20 -0
  349. package/dist/daemon/cron/capture.js +45 -0
  350. package/dist/daemon/cron/disposition.d.ts +19 -0
  351. package/dist/daemon/cron/disposition.js +52 -0
  352. package/dist/daemon/cron/escalation.d.ts +12 -0
  353. package/dist/daemon/cron/escalation.js +124 -0
  354. package/dist/daemon/cron/held.d.ts +4 -0
  355. package/dist/daemon/cron/held.js +20 -0
  356. package/dist/daemon/cron/lease-recovery.d.ts +3 -0
  357. package/dist/daemon/cron/lease-recovery.js +119 -0
  358. package/dist/daemon/cron/live-runs.d.ts +11 -0
  359. package/dist/daemon/cron/live-runs.js +23 -0
  360. package/dist/daemon/cron/passes.d.ts +11 -0
  361. package/dist/daemon/cron/passes.js +78 -0
  362. package/dist/daemon/cron/settle.d.ts +28 -0
  363. package/dist/daemon/cron/settle.js +37 -0
  364. package/dist/daemon/cron/sinks.d.ts +2 -0
  365. package/dist/daemon/cron/sinks.js +100 -0
  366. package/dist/daemon/cron-run.d.ts +5 -41
  367. package/dist/daemon/cron-run.js +193 -882
  368. package/dist/daemon/crtrd-cli.js +2 -1
  369. package/dist/daemon/crtrd.js +22 -22
  370. package/dist/daemon/fleet.js +0 -4
  371. package/dist/daemon/human/deliver-action.js +2 -1
  372. package/dist/daemon/human/sweep.js +15 -4
  373. package/dist/daemon/manage.d.ts +2 -2
  374. package/dist/daemon/manage.js +4 -13
  375. package/dist/daemon/pidfile.js +2 -2
  376. package/dist/daemon/reconcilers/broker-supervision.js +27 -30
  377. package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
  378. package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
  379. package/dist/daemon/reconcilers/live-obligation.js +2 -2
  380. package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
  381. package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
  382. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
  383. package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
  384. package/dist/daemon/review/companion.js +2 -2
  385. package/dist/daemon/review/sweep.js +8 -3
  386. package/dist/migrations/001-surfaces-frontmatter.js +0 -6
  387. package/dist/migrations/corpus.js +2 -5
  388. package/dist/migrations/registry.js +15 -2
  389. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
  390. package/dist/pi-extensions/broker-local.js +2 -1
  391. package/dist/pi-extensions/canvas-bash-valve.js +7 -20
  392. package/dist/pi-extensions/canvas-context-intro.js +2 -7
  393. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  394. package/dist/pi-extensions/canvas-goal-capture.js +2 -1
  395. package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
  396. package/dist/pi-extensions/canvas-passive-context.js +2 -5
  397. package/dist/pi-extensions/canvas-preview-result.js +2 -1
  398. package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
  399. package/dist/pi-extensions/canvas-recap.js +3 -16
  400. package/dist/pi-extensions/canvas-review-boundary.js +2 -5
  401. package/dist/pi-extensions/canvas-stophook.js +2 -26
  402. package/dist/pi-extensions/canvas-structured-output.js +2 -1
  403. package/dist/pi-extensions/canvas-tool-guide.js +2 -1
  404. package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
  405. package/dist/shared/__tests__/env-boundary.test.js +93 -0
  406. package/dist/shared/env.d.ts +68 -0
  407. package/dist/shared/env.js +171 -0
  408. package/package.json +3 -1
  409. package/runtime.lock.json +2 -2
  410. package/dist/core/canvas/render.d.ts +0 -47
  411. package/dist/core/canvas/render.js +0 -245
@@ -140,9 +140,7 @@ function bootCorpus() {
140
140
  }
141
141
  return { docs, sources };
142
142
  }
143
- // ---------------------------------------------------------------------------
144
143
  // Exposure registration
145
- // ---------------------------------------------------------------------------
146
144
  function rendersAtBoot(doc) {
147
145
  return isVisible(doc.bootRung) && (doc.bootRung !== 'content' || doc.body.trim() !== '');
148
146
  }
@@ -156,9 +154,7 @@ function recordRendered(target, docs) {
156
154
  registerDocumentExposure(target, d.path, d.body, d.bootRung);
157
155
  }
158
156
  }
159
- // ---------------------------------------------------------------------------
160
157
  // Step 2 — display ordering + grouped render.
161
- // ---------------------------------------------------------------------------
162
158
  /** Content-prose ordering rank: general → specific. builtin=0, user=1,
163
159
  * profile=2, project ranks start at 3 and climb by root nearness — the
164
160
  * OUTERMOST ancestor `.crouter/` root ranks lowest (renders first), the
@@ -412,9 +408,7 @@ function buildTree(docs, rootLabel) {
412
408
  renderChildren(root, '', lines);
413
409
  return lines.join('\n');
414
410
  }
415
- // ---------------------------------------------------------------------------
416
411
  // Block prose — the intro legend + the update directive for each kind.
417
- // ---------------------------------------------------------------------------
418
412
  const READ_LEGEND = 'Each %s below shows either its full content (indented beneath its name), a ' +
419
413
  '`# read when:` line telling you when to read the full document, or simply its name.';
420
414
  const PREFERENCES_INTRO = 'Preferences are how the user wants you to behave — standing directives and corrections. ' +
@@ -487,9 +481,7 @@ export function buildSubPersonaMenu(kind) {
487
481
  'dragging unrelated context into it.\n\n' +
488
482
  lines.join('\n'));
489
483
  }
490
- // ---------------------------------------------------------------------------
491
484
  // 1. Preferences — protocol prose + `<memory kind="preference">` (system prompt).
492
- // ---------------------------------------------------------------------------
493
485
  /** The system-prompt substrate splice, TWO parts in one string.
494
486
  *
495
487
  * 1. The operating protocol: shipped (builtin/plugin) content-rung bodies as
@@ -548,9 +540,7 @@ function buildPreferencesBlock(subject, target) {
548
540
  export function renderPreferencesForSubject(subject, target) {
549
541
  return buildPreferencesBlock(subject, target);
550
542
  }
551
- // ---------------------------------------------------------------------------
552
543
  // 2. Knowledge — `<memory kind="knowledge">` (the first-message bearings).
553
- // ---------------------------------------------------------------------------
554
544
  /** STEP 1 for the knowledge block, shared by the boot render and the
555
545
  * config-change delta: the resolver's knowledge winners ∪ the node's own store
556
546
  * (any kind), first-wins deduped resolver-first so a same-named node doc
@@ -589,9 +579,7 @@ export function renderKnowledgeForSubject(subject, nodeId, target) {
589
579
  // Surfaced INTO this node: swap literal `$CRTR_CONTEXT_DIR` for the real path.
590
580
  return interpolateNodePaths(`<memory kind="knowledge">\n${out}\n</memory>`, nodeId);
591
581
  }
592
- // ---------------------------------------------------------------------------
593
582
  // 3. The config-change delta — the memory a node's NEW configuration exposes.
594
- // ---------------------------------------------------------------------------
595
583
  const DELTA_PROTOCOL_INTRO = 'Your node configuration changed. This operating protocol is in force for you now:';
596
584
  const DELTA_PREFERENCES_INTRO = 'Your new configuration puts these preferences in force — standing directives and corrections ' +
597
585
  'from the user. To read one, run `crtr memory read <name>`. ' +
@@ -9,21 +9,17 @@
9
9
  // render, and `crtr memory lint` owns strict authoring-time enforcement.
10
10
  import { PROFILE_PROJECT_MEMORY_VALUES } from '../../api/dto/profiles.js';
11
11
  import { surfaceEntryGatePasses } from './gate.js';
12
- // ---------------------------------------------------------------------------
13
12
  // Kinds — the two semantic kinds (design §3): `knowledge` (consult — procedural
14
13
  // playbooks + factual references merged) vs `preference` (behave — standing
15
14
  // directives). `kind` is a load-bearing binary, not a content taxonomy.
16
- // ---------------------------------------------------------------------------
17
15
  export const KINDS = ['knowledge', 'preference'];
18
16
  /** Is `v` one of the two valid document kinds? */
19
17
  export function isDocKind(v) {
20
18
  return typeof v === 'string' && KINDS.includes(v);
21
19
  }
22
- // ---------------------------------------------------------------------------
23
20
  // The 4-rung visibility ladder (design §4). A strict, monotone ordering where
24
21
  // each rung is a superset of disclosure over the one before:
25
22
  // none < name < preview < content
26
- // ---------------------------------------------------------------------------
27
23
  // The ladder is ONE union, owned by the public API DTO (`ProfileProjectMemory`)
28
24
  // so a profile relationship's cap and a doc's authored rung are the same values
29
25
  // and clamp against each other without a conversion.
@@ -41,21 +37,17 @@ export function rungAtLeast(r, min) {
41
37
  export function minRung(a, b) {
42
38
  return rungRank(a) <= rungRank(b) ? a : b;
43
39
  }
44
- // ---------------------------------------------------------------------------
45
40
  // Identity — owned entirely by core/memory/identity.ts (ordering-prefix
46
41
  // normalization, INDEX collapse, explicit-name-then-path local identity,
47
42
  // namespace composition). Re-exported here so document parsing and identity
48
43
  // derivation stay one rule with one implementation.
49
- // ---------------------------------------------------------------------------
50
44
  export { normalizeNameSegment, normalizeDocName, resolveLocalName as resolveDocName } from '../memory/identity.js';
51
- // ---------------------------------------------------------------------------
52
45
  // Surfaces — explicit event routing (design: surfaces-design.md). A doc
53
46
  // declares WHEN it delivers as (event, match, rung) entries; matches are
54
47
  // always globs, always absolute in the event's namespace, with `./` as the
55
48
  // one relative spelling (anchoring to the doc's own container). A doc with
56
49
  // no `surfaces` at all does exactly one thing: appears in its directory's
57
50
  // listing.
58
- // ---------------------------------------------------------------------------
59
51
  export const SURFACE_EVENTS = ['boot', 'workspace-open', 'read', 'memory-read', 'command', 'pre-command'];
60
52
  /** The rungs an entry may deliver at. There is no `none` — silence is the
61
53
  * absence of an entry. (`Rung`'s `none` survives only as the internal
@@ -74,9 +66,7 @@ export function bootRung(doc, subject) {
74
66
  }
75
67
  return r;
76
68
  }
77
- // ---------------------------------------------------------------------------
78
69
  // Parse / validate.
79
- // ---------------------------------------------------------------------------
80
70
  /** Parse a raw frontmatter record (from `parseFrontmatterGeneric`, via the
81
71
  * resolver) into a typed schema with defaults applied. Returns `null` when the
82
72
  * record is absent or carries no valid `kind` — i.e. it is not a substrate
@@ -135,9 +125,7 @@ export function previewLine(doc) {
135
125
  const line = doc.whenAndWhyToRead.trim().replace(/\.+$/, '');
136
126
  return line === '' ? '' : `${line}.`;
137
127
  }
138
- // ---------------------------------------------------------------------------
139
128
  // Field coercion helpers (private).
140
- // ---------------------------------------------------------------------------
141
129
  function strField(v) {
142
130
  return typeof v === 'string' ? v : '';
143
131
  }
@@ -323,12 +323,10 @@ export function matchesPreCommandEntry(entry, subject, command) {
323
323
  const stripped = stripHeredocs(command);
324
324
  return entry.match.some((g) => commandGlobMatches(g, stripped));
325
325
  }
326
- // ---------------------------------------------------------------------------
327
326
  // Per-doc rung folds — a doc's delivery rung for one event occurrence is the
328
327
  // highest `at` over its matching entries of that event, `none` when none
329
328
  // match (entries OR together; `none` is the internal "does not deliver"
330
329
  // floor, never an authored rung).
331
- // ---------------------------------------------------------------------------
332
330
  /** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
333
331
  export function readDeliveryRung(doc, subject, absReadFile, readFrontmatter) {
334
332
  let r = 'none';
@@ -1,3 +1,4 @@
1
+ import { envNodeId } from '../../shared/env.js';
1
2
  import { spawnSync } from 'node:child_process';
2
3
  import { resolve } from 'node:path';
3
4
  import { breakPane, closePane, currentTmux, paneOfNode, splitWindow, } from '../runtime/placement-tmux.js';
@@ -6,7 +7,7 @@ function targetPane() {
6
7
  const callerPane = process.env['TMUX_PANE'];
7
8
  if (callerPane !== undefined && callerPane !== '')
8
9
  return callerPane;
9
- const nodeId = process.env['CRTR_NODE_ID'];
10
+ const nodeId = envNodeId();
10
11
  if (nodeId !== undefined && nodeId !== '') {
11
12
  const viewerPane = paneOfNode(nodeId);
12
13
  if (viewerPane !== null)
@@ -1,6 +1,6 @@
1
- import { ProcessTerminal, type Component, type Focusable, type TUI } from "@earendil-works/pi-tui";
2
- import { readRawScopeConfig } from "../config.js";
3
- import { type BindingId, type BindingResolution } from "../keybindings/index.js";
1
+ import { ProcessTerminal, type Component, type Focusable, type TUI } from '@earendil-works/pi-tui';
2
+ import { readRawScopeConfig } from '../config.js';
3
+ import { type BindingId, type BindingResolution } from '../keybindings/index.js';
4
4
  /** A full-screen page: a focusable TUI component that cleans up after itself. */
5
5
  export type TuiPageSurface = Component & Focusable & {
6
6
  dispose(): void;
@@ -6,10 +6,10 @@
6
6
  // restore path that survives an exception, a signal, and process exit. Anything
7
7
  // a single page needs beyond that — pi's model runtime, a canvas client — the
8
8
  // page's own runner builds and closes over before calling in here.
9
- import { ProcessTerminal, TuiMainScreen, } from "@earendil-works/pi-tui";
10
- import { applyTheme } from "../../clients/attach/config.js";
11
- import { readRawScopeConfig } from "../config.js";
12
- import { resolveKeybindings, } from "../keybindings/index.js";
9
+ import { ProcessTerminal, TuiMainScreen, } from '@earendil-works/pi-tui';
10
+ import { applyTheme } from '../../clients/attach/config.js';
11
+ import { readRawScopeConfig } from '../config.js';
12
+ import { resolveKeybindings, } from '../keybindings/index.js';
13
13
  /** Mount one page as the TUI's only child and resolve when it closes.
14
14
  *
15
15
  * `interceptCancel` is a process-level escape hatch for surfaces that must be
@@ -1,5 +1,5 @@
1
- import { getMarkdownTheme, getSelectListTheme, getSettingsListTheme, } from "@earendil-works/pi-coding-agent";
2
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
1
+ import { getMarkdownTheme, getSelectListTheme, getSettingsListTheme, } from '@earendil-works/pi-coding-agent';
2
+ import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
3
3
  const ansiGreen = (text) => `\x1b[32m${text}\x1b[39m`;
4
4
  const ansiYellow = (text) => `\x1b[33m${text}\x1b[39m`;
5
5
  export const theme = {
@@ -95,7 +95,7 @@ export function framed(lines, width) {
95
95
  export function fitTail(lines, budget) {
96
96
  return budget >= lines.length ? lines : lines.slice(lines.length - budget);
97
97
  }
98
- // --- Page chrome shared by the settings shell and the setup wizard ----------
98
+ // Page chrome shared by the settings shell and the setup wizard
99
99
  /** Rows the page frame spends on its own title, rules and footer. */
100
100
  export const PAGE_CHROME_ROWS = 4;
101
101
  /** Title row with right-aligned scope metadata, a rule under it, the body
package/dist/core/wake.js CHANGED
@@ -10,9 +10,7 @@
10
10
  // design §5.3 + D7 (engine evaluates UTC; the surface bakes the IANA zone
11
11
  // into a calendar `recur`). Tick-scale only — no sub-second precision.
12
12
  import { CronExpressionParser } from 'cron-parser';
13
- // ---------------------------------------------------------------------------
14
13
  // Constants
15
- // ---------------------------------------------------------------------------
16
14
  /** Cadence floor: reject any interval / cron min-spacing below this (AC-N4). */
17
15
  export const CADENCE_FLOOR_MS = 60_000;
18
16
  const DURATION_UNIT_MS = {
@@ -38,9 +36,7 @@ const CRON_ALIASES = {
38
36
  '@midnight': '0 0 * * *',
39
37
  '@hourly': '0 * * * *',
40
38
  };
41
- // ---------------------------------------------------------------------------
42
39
  // Internal helpers
43
- // ---------------------------------------------------------------------------
44
40
  function whenError(code, message, received) {
45
41
  return { error: { code, message, received } };
46
42
  }
@@ -127,9 +123,7 @@ function classifyIso(s) {
127
123
  return { zoned: true };
128
124
  return null; // trailing garbage -> not a valid ISO instant
129
125
  }
130
- // ---------------------------------------------------------------------------
131
126
  // parseWhen — resolve a one-shot <when> to an absolute UTC instant
132
- // ---------------------------------------------------------------------------
133
127
  /**
134
128
  * Resolve a `<when>` to a single UTC `fireAt`:
135
129
  * - relative duration (`90s`, `1h30m`) -> now + Σ
@@ -182,9 +176,7 @@ export function parseWhen(when, opts) {
182
176
  }
183
177
  return whenError('bad_when', `Could not parse "${received}". Use a duration ("5m", "1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00").`, received);
184
178
  }
185
- // ---------------------------------------------------------------------------
186
179
  // parseCadence — resolve --every to a stored recur + first fire
187
- // ---------------------------------------------------------------------------
188
180
  /**
189
181
  * Resolve a `<cadence>` (the `--every` value) to a stored `recur` JSON string
190
182
  * plus the first `fireAt`:
@@ -240,9 +232,7 @@ export function parseCadence(every, opts) {
240
232
  firstFireAt,
241
233
  };
242
234
  }
243
- // ---------------------------------------------------------------------------
244
235
  // cadenceDisplay — a recur JSON rendered for humans + agents
245
- // ---------------------------------------------------------------------------
246
236
  /**
247
237
  * Render a stored `recur` JSON as a compact, human-readable cadence:
248
238
  * - {"every":"6h"} -> `every 6h`
@@ -270,9 +260,7 @@ export function cadenceDisplay(recur) {
270
260
  }
271
261
  return 'none';
272
262
  }
273
- // ---------------------------------------------------------------------------
274
263
  // nextSlotAfter — the coalescing primitive (daemon advance)
275
- // ---------------------------------------------------------------------------
276
264
  /**
277
265
  * Earliest occurrence of `recur` strictly greater than `now`, as a UTC ISO
278
266
  * string. Anchors on `now` (not the stored `fire_at`), so it structurally
@@ -357,9 +357,7 @@ export function closeManagedWorktree(nodeId) {
357
357
  return closeManagedWorktreeLocked(nodeId);
358
358
  return withRepositoryWorktreeLock(wt.repo_root, () => closeManagedWorktreeLocked(nodeId));
359
359
  }
360
- // ---------------------------------------------------------------------------
361
360
  // Push-final auto-drop of a clean, zero-commit managed worktree
362
- // ---------------------------------------------------------------------------
363
361
  /** Delete a source branch only while the ref that proved it already delivered
364
362
  * is still at the exact inspected SHA. `containingRef` can be the recorded
365
363
  * local base or that base's remote-tracking upstream. */
@@ -0,0 +1,36 @@
1
+ // Every reconciler in crtrd's tick loop conforms to one shape:
2
+ // `run(now: number, ctx: TCtx): void | Promise<unknown>`. The call loop treats
3
+ // every reconciler identically — a 1-arg or discard-shaped signature is exactly
4
+ // the drift this test catches (a runtime arity check; a compile-time
5
+ // `satisfies` pass over the same five/seven classes would need a shared
6
+ // Reconciler<TCtx> interface that adds no real safety this doesn't already
7
+ // give, since `tsc --noEmit` already fails hard on a wrong-shaped ctx object
8
+ // at each call site in crtrd.ts).
9
+ import { test } from 'node:test';
10
+ import assert from 'node:assert/strict';
11
+ import { BrokerSupervisionReconciler } from '../reconcilers/broker-supervision.js';
12
+ import { ControllerDeathReconciler, DormantInboxReconciler } from '../reconcilers/dormant-inbox.js';
13
+ import { CronLaneReconciler } from '../reconcilers/cron-lane.js';
14
+ import { HumanDeliveryLaneReconciler } from '../reconcilers/human-delivery-lane.js';
15
+ import { PendingReviewSubmitReconciler } from '../reconcilers/pending-review-submit.js';
16
+ import { StorageMaintenanceReconciler } from '../reconcilers/storage-maintenance.js';
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const RECONCILERS = [
19
+ ['BrokerSupervisionReconciler', BrokerSupervisionReconciler],
20
+ ['ControllerDeathReconciler', ControllerDeathReconciler],
21
+ ['DormantInboxReconciler', DormantInboxReconciler],
22
+ ['CronLaneReconciler', CronLaneReconciler],
23
+ ['HumanDeliveryLaneReconciler', HumanDeliveryLaneReconciler],
24
+ ['PendingReviewSubmitReconciler', PendingReviewSubmitReconciler],
25
+ ['StorageMaintenanceReconciler', StorageMaintenanceReconciler],
26
+ ];
27
+ test('every crtrd reconciler declares run(now, ctx) — arity 2', () => {
28
+ const offenders = [];
29
+ for (const [name, Ctor] of RECONCILERS) {
30
+ const instance = new Ctor();
31
+ if (instance.run.length !== 2) {
32
+ offenders.push(`${name}.run has arity ${instance.run.length}, expected 2`);
33
+ }
34
+ }
35
+ assert.deepEqual(offenders, [], `reconciler(s) not shaped run(now, ctx):\n${offenders.join('\n')}`);
36
+ });
@@ -1,9 +1,13 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { test } from 'node:test';
3
+ import { mkdtempSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
3
6
  import { toErrorBody } from '../map.js';
4
7
  import { ApiError } from '../../../api/errors.js';
5
8
  import { apiErrorToCliError, InputError } from '../../../core/io.js';
6
9
  import { brokerLaunchFailed } from '../../../core/errors.js';
10
+ import { createManagedWorktree } from '../../../core/worktree.js';
7
11
  // Regression for crouter#521: choosing the root profile on a virgin machine
8
12
  // died with a bare `<error code="internal">\ninternal error` and ZERO cause —
9
13
  // the front-door `createNode({root:true})` boot failure was thrown as a plain
@@ -41,6 +45,29 @@ test('an unanticipated 500 surfaces its cause instead of a causeless "internal e
41
45
  // ...and the next-hint is the crtr-bug guidance, not "adjust the call".
42
46
  assert.match(cli.payload.next, /crtr bug/);
43
47
  });
48
+ // Regression for crouter#811: a `crtr node new --worktree` against a cwd that is
49
+ // not inside a git repository reached the caller as `500 internal`, hiding the
50
+ // domain error the worktree layer already raised for exactly that input.
51
+ test('a non-git cwd round-trips the worktree layer\'s own code + next-hint, never a 500', () => {
52
+ const outside = mkdtempSync(join(tmpdir(), 'crtr-nongit-'));
53
+ try {
54
+ let thrown;
55
+ try {
56
+ createManagedWorktree(outside, 'node-that-needs-a-repo');
57
+ }
58
+ catch (err) {
59
+ thrown = err;
60
+ }
61
+ assert.ok(thrown !== undefined, 'a worktree create outside a repository must fail');
62
+ assert.equal(toErrorBody(thrown).status, 400);
63
+ const cli = roundTrip(thrown);
64
+ assert.equal(cli.payload.error, 'not_git_repo');
65
+ assert.match(cli.payload.next, /--cwd/);
66
+ }
67
+ finally {
68
+ rmSync(outside, { recursive: true, force: true });
69
+ }
70
+ });
44
71
  test('a 500 body carries only the bounded first line, never a multi-line stack', () => {
45
72
  const withStack = new Error('top cause of the failure');
46
73
  withStack.stack = 'Error: top cause of the failure\n at foo (bar.js:1:1)\n at baz (qux.js:2:2)';
@@ -72,9 +72,7 @@ function countLines(path) {
72
72
  return 0;
73
73
  }
74
74
  }
75
- // ---------------------------------------------------------------------------
76
75
  // A1 — /healthz ok + socket mode 0600
77
- // ---------------------------------------------------------------------------
78
76
  test('A1: /healthz is ok and the unix socket is bound mode 0600', async () => {
79
77
  const health = await client.healthz();
80
78
  assert.equal(health.ok, true, 'GET /healthz → { ok: true }');
@@ -86,10 +84,8 @@ test('attention counts reads only the requested node slice', async () => {
86
84
  const result = await client.attentionCounts([root, 'unknown-node']);
87
85
  assert.deepEqual(result.counts, { [root]: 0, 'unknown-node': 0 });
88
86
  });
89
- // ---------------------------------------------------------------------------
90
87
  // A2 — POST /nodes → live node (row + broker + recorded pid), GET shows active,
91
88
  // view.sock exists
92
- // ---------------------------------------------------------------------------
93
89
  test('A2: POST /nodes boots a live broker; GET /nodes/{id} shows active + live pid + view.sock', async () => {
94
90
  const root = h.spawnRoot('a2 root'); // in-process resident, pi_pid = harness pid (alive)
95
91
  const detail = await client.createNode({ kind: 'developer', parent: root, prompt: 'a2 worker' });
@@ -103,31 +99,27 @@ test('A2: POST /nodes boots a live broker; GET /nodes/{id} shows active + live p
103
99
  assert.ok(meta?.pi_pid != null && isPidAlive(meta.pi_pid), 'row records a live broker pid');
104
100
  assert.ok(existsSync(h.brokerSock(id)), 'view.sock exists for the live broker');
105
101
  });
106
- // ---------------------------------------------------------------------------
107
102
  // A3 — POST .../messages appends inbox.jsonl; POST .../reports {tier:'final'}
108
103
  // writes the report, notifies subscribers, transitions the row
109
- // ---------------------------------------------------------------------------
110
104
  test('A3: messages append inbox.jsonl; final report writes + notifies subscriber + transitions row', async () => {
111
105
  const A = h.spawnRoot('a3 mgr');
112
106
  const B = (await client.createNode({ kind: 'developer', parent: A, prompt: 'a3 worker' })).node_id;
113
107
  await h.awaitBoot(B);
114
- // --- messages append inbox.jsonl (raw line count is cursor-independent) ---
108
+ // messages append inbox.jsonl (raw line count is cursor-independent)
115
109
  const inboxFile = inboxPath(B);
116
110
  const before = countLines(inboxFile);
117
111
  const msg = await client.sendMessage(B, { body: 'hello inbox' });
118
112
  assert.equal(msg.delivered, true, 'message reports delivered');
119
113
  await h.waitFor(() => countLines(inboxFile) > before, { label: 'inbox.jsonl appended', timeoutMs: 10_000 });
120
- // --- final report: file written + subscriber notified + row transitioned ---
114
+ // final report: file written + subscriber notified + row transitioned
121
115
  const res = await client.pushReport(B, { tier: 'final', body: 'a3 done result' });
122
116
  assert.ok(existsSync(res.report_path), `report file written at ${res.report_path}`);
123
117
  assert.ok(res.notified.includes(A), `final push notifies the spawn-subscribed manager A (notified=${JSON.stringify(res.notified)})`);
124
118
  assert.deepEqual(res.transitioned, { from: 'active', to: 'done' }, 'row transitioned active→done');
125
119
  assert.equal(h.node(B)?.status, 'done', 'row status is done after final');
126
120
  });
127
- // ---------------------------------------------------------------------------
128
121
  // A4 — WS attach reconstructs the same settled broker snapshot as a direct
129
122
  // ViewSocketClient; ?revive=0 on a dormant node → 409 without launching a broker
130
- // ---------------------------------------------------------------------------
131
123
  test('A4a: WS attach welcome.snapshot is byte-identical to a direct ViewSocketClient snapshot', async () => {
132
124
  const root = h.spawnRoot('a4 root');
133
125
  const id = (await client.createNode({ kind: 'developer', parent: root, prompt: 'a4 worker' })).node_id;
@@ -229,9 +221,7 @@ test('A4b: ?revive=0 on a dormant node refuses 409 without launching a broker',
229
221
  assert.equal(h.bootCount(dorm), bootsBefore, 'no broker was launched (boot count unchanged)');
230
222
  assert.equal(h.node(dorm)?.pi_pid ?? null, null, 'no pid recorded — the node stayed dormant');
231
223
  });
232
- // ---------------------------------------------------------------------------
233
224
  // A5 — POST .../close tears down the broker + cascades per edges
234
- // ---------------------------------------------------------------------------
235
225
  test('A5: close tears down the target broker and cascades its subtree, leaving the ancestor untouched', async () => {
236
226
  const A = h.spawnRoot('a5 root');
237
227
  const B = (await client.createNode({ kind: 'developer', parent: A, prompt: 'a5 mid' })).node_id;
@@ -259,12 +249,10 @@ test('A5: close tears down the target broker and cascades its subtree, leaving t
259
249
  assert.equal(h.node(C)?.status, 'canceled', 'cascaded child C is canceled');
260
250
  assert.equal(h.node(A)?.status, 'active', 'ancestor A is untouched (still active)');
261
251
  });
262
- // ---------------------------------------------------------------------------
263
252
  // A6 — PUT /profiles/{name} idempotent; PUT /model-auth/{provider} writes
264
253
  // auth.json (sandboxed PI_CODING_AGENT_DIR).
265
254
  // HONEST PROXY: reload fan is asserted as the file write only (the mtime
266
255
  // watcher lives in runDaemon, not createApiServer).
267
- // ---------------------------------------------------------------------------
268
256
  test('A6a: PUT /profiles/{name} is idempotent (twice → same profile id)', async () => {
269
257
  // Profiles write under `join(homedir(), '.crouter', 'profiles')` — homedir()
270
258
  // honors HOME, which the harness only overrides for SUBPROCESS children. For
@@ -312,11 +300,9 @@ test('A6b: PUT /model-auth/{provider} writes auth.json (reload fan asserted as f
312
300
  rmSync(piDir, { recursive: true, force: true });
313
301
  }
314
302
  });
315
- // ---------------------------------------------------------------------------
316
303
  // /v1/files/peek — host file read (capped → `truncated`), plus the
317
304
  // usage/relative/404 error mapping. Covers the handler's non-trivial
318
305
  // branching (usage / notFound / directory / truncation boundary).
319
- // ---------------------------------------------------------------------------
320
306
  const PEEK_CAP = 512 * 1024; // MUST mirror PEEK_MAX_BYTES in handlers/files.ts
321
307
  test('files/peek: 200 returns full content, untruncated, for a small file', async () => {
322
308
  const dir = mkdtempSync(join(tmpdir(), 'crtr-peek-'));
@@ -60,10 +60,8 @@ after(async () => {
60
60
  await server?.close();
61
61
  await h?.dispose();
62
62
  });
63
- // ---------------------------------------------------------------------------
64
63
  // B2 (attach hybrid) — ensureAttach revives a DORMANT node server-side; the
65
64
  // direct view.sock snapshot streams; DETACH LEAVES THE BROKER RUNNING.
66
- // ---------------------------------------------------------------------------
67
65
  test('B2-attach: ensureAttach revives a dormant node, snapshot streams, detach leaves the broker running', async () => {
68
66
  const root = h.spawnRoot('b10 attach root'); // in-process resident (alive pid)
69
67
  const id = (await client.createNode({ kind: 'developer', parent: root, prompt: 'b10 attach worker' })).node_id;
@@ -87,7 +85,7 @@ test('B2-attach: ensureAttach revives a dormant node, snapshot streams, detach l
87
85
  // reviveNode's double-launch guard reads the fleet entry, so attaching in the
88
86
  // window between the kill and the child-exit delivery is correctly a no-op.
89
87
  await h.awaitFleetExit(id);
90
- // --- ensureAttach (the exact call viewer.ts makes) revives it SERVER-SIDE ---
88
+ // ensureAttach (the exact call viewer.ts makes) revives it SERVER-SIDE
91
89
  const res = await client.ensureAttach(id, { resume: true, revive: true });
92
90
  assert.equal(res.node_id, id, 'ensureAttach echoes the node id');
93
91
  assert.equal(res.revived, true, 'server launched a fresh broker (revived=true) for the dormant node');
@@ -101,7 +99,7 @@ test('B2-attach: ensureAttach revives a dormant node, snapshot streams, detach l
101
99
  // hop viewer.ts keeps local). ---
102
100
  const attached = await kit.attach(id, 'observer', 'b10-attach-observer');
103
101
  assert.ok(attached.welcome.snapshot != null, 'welcome frame carries a reconstructed snapshot (snapshot streams)');
104
- // --- DETACH LEAVES THE BROKER RUNNING (reap-on-detach removed this batch) ---
102
+ // DETACH LEAVES THE BROKER RUNNING (reap-on-detach removed this batch)
105
103
  attached.close();
106
104
  // Give any (now-absent) reap path a beat to have fired, then assert the broker
107
105
  // is unmistakably still alive.
@@ -111,11 +109,9 @@ test('B2-attach: ensureAttach revives a dormant node, snapshot streams, detach l
111
109
  assert.ok(isPidAlive(afterDetachPid), 'detach left the broker RUNNING (no reap-on-detach)');
112
110
  assert.notEqual(h.node(id)?.status, 'done', 'detach did not finalize the node');
113
111
  });
114
- // ---------------------------------------------------------------------------
115
112
  // B2 (model-auth) — installCredential writes auth.json (content asserted), the
116
113
  // reload FAN acks against a live broker, and the API path is byte-identical to
117
114
  // the old direct-write path.
118
- // ---------------------------------------------------------------------------
119
115
  test('B2-modelauth: installCredential writes auth.json and the reload fan acks a live broker', async () => {
120
116
  // Sandbox pi's credential store so the write lands in a throwaway dir, never the
121
117
  // developer's real ~/.pi/agent/auth.json. authPath() honors PI_CODING_AGENT_DIR.
@@ -128,7 +124,7 @@ test('B2-modelauth: installCredential writes auth.json and the reload fan acks a
128
124
  const root = h.spawnRoot('b10 modelauth root');
129
125
  liveId = (await client.createNode({ kind: 'developer', parent: root, prompt: 'b10 fan target' })).node_id;
130
126
  await h.awaitBoot(liveId);
131
- // --- installCredential (the exact call setup.ts makes) writes auth.json ---
127
+ // installCredential (the exact call setup.ts makes) writes auth.json
132
128
  const res = await client.installCredential('anthropic', { kind: 'api_key', api_key: 'sk-b10-test-key' });
133
129
  assert.equal(res.installed, true, 'credential reports installed');
134
130
  assert.equal(res.managed, false, 'api_key install is not a managed-pool account');
@@ -150,14 +146,12 @@ test('B2-modelauth: installCredential writes auth.json and the reload fan acks a
150
146
  rmSync(apiDir, { recursive: true, force: true });
151
147
  }
152
148
  });
153
- // ---------------------------------------------------------------------------
154
149
  // B2 (model-auth, oauth) — non-managed OAuth install must PRESERVE
155
150
  // provider-specific extra credential fields (e.g. github-copilot's
156
151
  // `enterpriseUrl`, load-bearing for enterprise routing/refresh). The client
157
152
  // forwards non-standard keys through `extra` and the handler re-spreads them.
158
153
  // Regression guard for review M1 (a dropped enterpriseUrl breaks
159
154
  // GitHub-Enterprise Copilot).
160
- // ---------------------------------------------------------------------------
161
155
  test('B2-modelauth-oauth: installCredential(oauth) preserves extra fields (enterpriseUrl)', async () => {
162
156
  const savedPiDir = process.env['PI_CODING_AGENT_DIR'];
163
157
  const apiDir = mkdtempSync(join(tmpdir(), 'crtr-b10-oauth-api-'));
@@ -192,10 +186,8 @@ test('B2-modelauth-oauth: installCredential(oauth) preserves extra fields (enter
192
186
  rmSync(apiDir, { recursive: true, force: true });
193
187
  }
194
188
  });
195
- // ---------------------------------------------------------------------------
196
189
  // Credential truth/removal — GET exposes only connection state and DELETE
197
190
  // clears exactly its provider's durable store.
198
- // ---------------------------------------------------------------------------
199
191
  test('B2-modelauth-status: list is sanitized and delete clears only the selected provider', async () => {
200
192
  const savedPiDir = process.env['PI_CODING_AGENT_DIR'];
201
193
  const apiDir = mkdtempSync(join(tmpdir(), 'crtr-b10-status-'));
@@ -127,12 +127,10 @@ after(async () => {
127
127
  else
128
128
  process.env['CRTR_NODE_ID'] = savedNodeId;
129
129
  });
130
- // ---------------------------------------------------------------------------
131
130
  // push final — the report write, the subscriber fan, and the lifecycle
132
131
  // transition all happen server-side, driven through the real push.ts leaf. A
133
132
  // parent spawns a child (so the parent subscribes to the child); the child
134
133
  // pushes.
135
- // ---------------------------------------------------------------------------
136
134
  test('`crtr push final` from a child writes a report, notifies the subscribed parent, AND transitions the child row active→done', async () => {
137
135
  const parent = h.spawnRoot('leaf-api-parity final parent');
138
136
  const child = (await client.createNode({ kind: 'developer', parent, prompt: 'final worker' })).node_id;
@@ -153,11 +151,9 @@ test('`crtr push final` from a child writes a report, notifies the subscribed pa
153
151
  // — the re-homed guard only engages for a managed-worktree owner.
154
152
  assert.equal('worktree_auto_dropped' in out, false, 'a normal node final is unaffected by the worktree guard');
155
153
  });
156
- // ---------------------------------------------------------------------------
157
154
  // Re-homed worktree guard/auto-drop (ambiguity-2 follow-up): the pre-B-5 CLI
158
155
  // `push final` worktree behavior now lives SERVER-side in handlers/reports.ts,
159
156
  // exercised end-to-end through the real push.ts leaf.
160
- // ---------------------------------------------------------------------------
161
157
  test('re-homed guard: `crtr push final` on a node with a managed worktree holding REAL unlanded commits blocks with the parity open_managed_worktree InputError (round-tripped from the server), and does NOT finalize', async () => {
162
158
  const repo = initRepo();
163
159
  try {
@@ -255,13 +251,11 @@ test('`crtr push final` keeps an absent managed worktree open when its branch is
255
251
  rmSync(repo, { recursive: true, force: true });
256
252
  }
257
253
  });
258
- // ---------------------------------------------------------------------------
259
254
  // `node worktree close` error parity — the SPECIFIC WorktreeError code + its
260
255
  // `next` remediation round-trip verbatim through the server → ApiError → the
261
256
  // bespoke CLI mapper in node-worktree.ts, instead of collapsing to a generic
262
257
  // status bucket. That mapper exists for exactly this contract, so this is the
263
258
  // load-bearing regression to lock.
264
- // ---------------------------------------------------------------------------
265
259
  test('`crtr node worktree close` on a node with NO managed worktree round-trips the specific `no_managed_worktree` code + its exact next-hint (not a generic status bucket)', async () => {
266
260
  const id = 'wt-close-none';
267
261
  createNode({ node_id: id, name: id, created: new Date().toISOString(), cwd: process.cwd(), kind: 'developer', mode: 'base', lifecycle: 'terminal', status: 'active' });
@@ -5,7 +5,8 @@
5
5
  // is bridge.ts's (A-8) — NOT routed here.
6
6
  //
7
7
  // `{id}` is the target node being attached to.
8
- import { getNode, reviveNode } from '../../../index.js';
8
+ import { getNode } from '../../../core/canvas/canvas.js';
9
+ import { reviveNode } from '../../../core/runtime/revive.js';
9
10
  import { viewSocketPath } from '../../../core/canvas/paths.js';
10
11
  import { waitForBrokerViewSocket } from '../../../core/runtime/placement-tmux.js';
11
12
  import { notFound, usage } from '../../../core/errors.js';
@@ -6,7 +6,7 @@ import { editorLabel } from '../../../core/canvas/labels.js';
6
6
  import { assembleNodeSubject } from '../../../core/substrate/subject.js';
7
7
  import { withCanvasWrite } from '../../../core/canvas/db.js';
8
8
  import { notFound, usage } from '../../../core/errors.js';
9
- import { readCursor, readInboxSince, writeCursor } from '../../../core/feed/inbox.js';
9
+ import { hasUnseenWakeMail, readCursor, readInboxSince, writeCursor } from '../../../core/feed/inbox.js';
10
10
  import { clearYieldMessage } from '../../../core/runtime/kickoff.js';
11
11
  import { transition } from '../../../core/runtime/lifecycle.js';
12
12
  import { focusOf, tearDownNode } from '../../../core/runtime/placement.js';
@@ -224,9 +224,6 @@ function handleSessionBound(ctx) {
224
224
  });
225
225
  return { status: 200, body: result };
226
226
  }
227
- function hasUnseenNonDeferredInbox(nodeId) {
228
- return readInboxSince(nodeId, readCursor(nodeId)).some((entry) => entry.tier !== 'deferred');
229
- }
230
227
  /** Commit the complete broker-settlement policy while holding one canvas write
231
228
  * boundary. This handler intentionally never invokes the broker that called it:
232
229
  * the returned directive is the only Pi-side consequence. */
@@ -300,14 +297,14 @@ function settleBroker(nodeId, request) {
300
297
  if (decision.action === 'reprompt')
301
298
  return { action: 'reprompt', prompt: decision.message };
302
299
  if (decision.reason === 'finished') {
303
- return hasUnseenNonDeferredInbox(nodeId) ? { action: 'stay_dormant' } : { action: 'shutdown' };
300
+ return hasUnseenWakeMail(nodeId) ? { action: 'stay_dormant' } : { action: 'shutdown' };
304
301
  }
305
302
  if (decision.reason !== 'awaiting' && decision.reason !== 'scheduled') {
306
303
  return { action: 'stay_dormant' };
307
304
  }
308
305
  if (focusOf(nodeId) !== null
309
306
  || hasAttachedBrokerViewers(nodeId)
310
- || hasUnseenNonDeferredInbox(nodeId))
307
+ || hasUnseenWakeMail(nodeId))
311
308
  return { action: 'stay_dormant' };
312
309
  transition(nodeId, 'release');
313
310
  return { action: 'shutdown' };