@north-light/crouter 0.3.232 → 0.3.234

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 (392) hide show
  1. package/dist/api/client.js +16 -15
  2. package/dist/builtin-memory/internal/memory-loading.md +4 -3
  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 +532 -534
  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 +0 -8
  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 +5 -18
  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/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
  42. package/dist/commands/memory/find.js +2 -2
  43. package/dist/commands/memory/lint.js +2 -1
  44. package/dist/commands/memory/read.d.ts +2 -7
  45. package/dist/commands/memory/read.js +5 -16
  46. package/dist/commands/memory/shared.d.ts +1 -1
  47. package/dist/commands/memory/shared.js +7 -10
  48. package/dist/commands/memory/write.js +4 -3
  49. package/dist/commands/node/bash.js +2 -1
  50. package/dist/commands/node/create.js +2 -1
  51. package/dist/commands/node/inspect.js +0 -6
  52. package/dist/commands/node/lifecycle.js +4 -13
  53. package/dist/commands/node/message.js +3 -2
  54. package/dist/commands/node/subscription.js +2 -1
  55. package/dist/commands/node/wait.js +4 -3
  56. package/dist/commands/node-context.js +2 -1
  57. package/dist/commands/node-lifecycle-revive.js +0 -2
  58. package/dist/commands/node-worktree.js +2 -1
  59. package/dist/commands/pkg/browse/actions.d.ts +1 -1
  60. package/dist/commands/pkg/browse/actions.js +4 -4
  61. package/dist/commands/pkg/browse/catalog.d.ts +1 -1
  62. package/dist/commands/pkg/browse/catalog.js +15 -15
  63. package/dist/commands/pkg/browse/command-view.d.ts +1 -1
  64. package/dist/commands/pkg/browse/command-view.js +3 -3
  65. package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
  66. package/dist/commands/pkg/browse/detail-panel.js +8 -8
  67. package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
  68. package/dist/commands/pkg/browse/doc-view.js +3 -3
  69. package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
  70. package/dist/commands/pkg/browse/list-panel.js +3 -3
  71. package/dist/commands/pkg/browse/model.d.ts +3 -3
  72. package/dist/commands/pkg/browse/shell.d.ts +2 -2
  73. package/dist/commands/pkg/browse/shell.js +13 -13
  74. package/dist/commands/pkg/plugin-manage.js +2 -1
  75. package/dist/commands/pkg/shared.js +0 -2
  76. package/dist/commands/profile/delete.js +2 -1
  77. package/dist/commands/push.js +2 -3
  78. package/dist/commands/revive.js +0 -2
  79. package/dist/commands/surface/node/placement.js +2 -1
  80. package/dist/commands/surface-edit.js +2 -1
  81. package/dist/commands/sys/config.js +0 -4
  82. package/dist/commands/sys/daemon.js +0 -10
  83. package/dist/commands/sys/doctor.js +3 -2
  84. package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
  85. package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
  86. package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
  87. package/dist/commands/sys/panels/dependencies-panel.js +2 -2
  88. package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
  89. package/dist/commands/sys/panels/keybindings-panel.js +5 -5
  90. package/dist/commands/sys/panels/models-panel.d.ts +4 -4
  91. package/dist/commands/sys/panels/models-panel.js +11 -10
  92. package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
  93. package/dist/commands/sys/panels/plugins-panel.js +2 -2
  94. package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
  95. package/dist/commands/sys/panels/prefs-panel.js +4 -4
  96. package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
  97. package/dist/commands/sys/panels/profiles-panel.js +14 -13
  98. package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
  99. package/dist/commands/sys/panels/provider-panel.js +8 -8
  100. package/dist/commands/sys/settings-shell.d.ts +3 -3
  101. package/dist/commands/sys/settings-shell.js +15 -15
  102. package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
  103. package/dist/commands/sys/setup-settings-runner.js +5 -5
  104. package/dist/commands/sys/setup-wizard.d.ts +4 -4
  105. package/dist/commands/sys/setup-wizard.js +8 -8
  106. package/dist/commands/sys/support/prepare.js +4 -4
  107. package/dist/commands/sys/support/submit.js +2 -2
  108. package/dist/commands/sys/sync-deps.js +0 -2
  109. package/dist/commands/sys/sync-project-guidance.js +2 -1
  110. package/dist/commands/sys/sysprompt.js +2 -1
  111. package/dist/core/__tests__/boot.test.js +0 -8
  112. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
  113. package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
  114. package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
  115. package/dist/core/__tests__/canvas.test.js +1 -1
  116. package/dist/core/__tests__/child-death-wake.test.js +0 -2
  117. package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
  118. package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
  119. package/dist/core/__tests__/daemon-wedge.test.js +0 -4
  120. package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
  121. package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
  122. package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
  123. package/dist/core/__tests__/focuses.test.js +0 -6
  124. package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
  125. package/dist/core/__tests__/fs-utils.test.js +50 -0
  126. package/dist/core/__tests__/helpers/harness.js +1 -1
  127. package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
  128. package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
  129. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
  130. package/dist/core/__tests__/integration/command-plugins.test.js +0 -24
  131. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
  132. package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
  133. package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
  134. package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
  135. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
  136. package/dist/core/__tests__/integration/revive.test.js +0 -12
  137. package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
  138. package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
  139. package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
  140. package/dist/core/__tests__/lifecycle.test.js +0 -18
  141. package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
  142. package/dist/core/__tests__/migration.test.js +2 -2
  143. package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
  144. package/dist/core/__tests__/pid-identity-match.test.js +1 -1
  145. package/dist/core/__tests__/plugin-kinds.test.js +1 -1
  146. package/dist/core/__tests__/plugin-page-components.test.js +1 -1
  147. package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
  148. package/dist/core/__tests__/respawn-throttle.test.js +0 -4
  149. package/dist/core/__tests__/revive-all.test.js +5 -5
  150. package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
  151. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
  152. package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
  153. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
  154. package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
  155. package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
  156. package/dist/core/activity-signal.d.ts +7 -0
  157. package/dist/core/activity-signal.js +38 -0
  158. package/dist/core/auto-update.js +2 -1
  159. package/dist/core/bootstrap.js +2 -1
  160. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  161. package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
  162. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  163. package/dist/core/canvas/browse/app.js +3 -2
  164. package/dist/core/canvas/browse/model.d.ts +1 -1
  165. package/dist/core/canvas/browse/model.js +0 -4
  166. package/dist/core/canvas/canvas.d.ts +22 -0
  167. package/dist/core/canvas/canvas.js +50 -18
  168. package/dist/core/canvas/crons.js +0 -6
  169. package/dist/core/canvas/db.d.ts +14 -20
  170. package/dist/core/canvas/db.js +30 -1332
  171. package/dist/core/canvas/focuses.js +0 -4
  172. package/dist/core/canvas/history.js +2 -1
  173. package/dist/core/canvas/migrations.d.ts +20 -0
  174. package/dist/core/canvas/migrations.js +1339 -0
  175. package/dist/core/canvas/nav-model.js +1 -11
  176. package/dist/core/canvas/nav-render.js +0 -8
  177. package/dist/core/canvas/remote-canvas-source.js +0 -2
  178. package/dist/core/canvas/render-source.js +31 -13
  179. package/dist/core/command-manifests/manifest.js +0 -10
  180. package/dist/core/command-manifests/schema.js +0 -4
  181. package/dist/core/command-plugins/transport/http-invoke.js +0 -14
  182. package/dist/core/command.js +0 -4
  183. package/dist/core/config.js +2 -1
  184. package/dist/core/feed/inbox.d.ts +10 -0
  185. package/dist/core/feed/inbox.js +18 -12
  186. package/dist/core/fs-utils.d.ts +9 -2
  187. package/dist/core/fs-utils.js +7 -15
  188. package/dist/core/help.js +0 -8
  189. package/dist/core/host-exports/export.d.ts +2 -2
  190. package/dist/core/host-exports/export.js +9 -12
  191. package/dist/core/human/answer.d.ts +2 -2
  192. package/dist/core/human/convention.d.ts +2 -5
  193. package/dist/core/human/convention.js +3 -13
  194. package/dist/core/human/page-schema.d.ts +2 -2
  195. package/dist/core/human/page-schema.js +2 -2
  196. package/dist/core/human/root.js +6 -23
  197. package/dist/core/human/scan.js +24 -7
  198. package/dist/core/human/tickets.d.ts +4 -1
  199. package/dist/core/human/tickets.js +20 -6
  200. package/dist/core/io.js +0 -12
  201. package/dist/core/memory/identity.js +0 -4
  202. package/dist/core/memory/inline-ref-inventory.js +3 -2
  203. package/dist/core/memory/lint.js +3 -6
  204. package/dist/core/memory-resolver.d.ts +5 -0
  205. package/dist/core/memory-resolver.js +14 -12
  206. package/dist/core/pagination.js +0 -2
  207. package/dist/core/preview-registry.d.ts +14 -0
  208. package/dist/core/preview-registry.js +2 -4
  209. package/dist/core/preview-result-path.js +2 -1
  210. package/dist/core/profiles/manifest.d.ts +37 -5
  211. package/dist/core/profiles/manifest.js +44 -24
  212. package/dist/core/profiles/select.js +0 -4
  213. package/dist/core/review/signal.js +4 -23
  214. package/dist/core/review/ticket-filter.d.ts +3 -3
  215. package/dist/core/review/ticket-filter.js +4 -9
  216. package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
  217. package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
  218. package/dist/core/runtime/bearings-render.d.ts +56 -0
  219. package/dist/core/runtime/bearings-render.js +241 -0
  220. package/dist/core/runtime/bearings.d.ts +2 -17
  221. package/dist/core/runtime/bearings.js +12 -198
  222. package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
  223. package/dist/core/runtime/broker/auth-reload.js +149 -0
  224. package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
  225. package/dist/core/runtime/broker/engine-drive.js +257 -0
  226. package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
  227. package/dist/core/runtime/broker/engine-routing.js +211 -0
  228. package/dist/core/runtime/broker/event-projection.js +3 -2
  229. package/dist/core/runtime/broker/fault-retry.js +5 -4
  230. package/dist/core/runtime/broker/frame-client.d.ts +38 -0
  231. package/dist/core/runtime/broker/frame-client.js +129 -0
  232. package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
  233. package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
  234. package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
  235. package/dist/core/runtime/broker/frame-engine-control.js +231 -0
  236. package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
  237. package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
  238. package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
  239. package/dist/core/runtime/broker/frame-model-query.js +69 -0
  240. package/dist/core/runtime/broker/frame-read.d.ts +19 -0
  241. package/dist/core/runtime/broker/frame-read.js +116 -0
  242. package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
  243. package/dist/core/runtime/broker/frame-replies.js +64 -0
  244. package/dist/core/runtime/broker/frame-session.d.ts +34 -0
  245. package/dist/core/runtime/broker/frame-session.js +203 -0
  246. package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
  247. package/dist/core/runtime/broker/turn-ignition.js +74 -0
  248. package/dist/core/runtime/broker-extension-render.js +9 -85
  249. package/dist/core/runtime/broker-protocol.js +0 -4
  250. package/dist/core/runtime/broker-sdk.js +2 -1
  251. package/dist/core/runtime/broker.d.ts +1 -1
  252. package/dist/core/runtime/broker.js +5 -23
  253. package/dist/core/runtime/fault.js +3 -2
  254. package/dist/core/runtime/headless-pi.js +3 -2
  255. package/dist/core/runtime/host.js +2 -3
  256. package/dist/core/runtime/kickoff.js +0 -4
  257. package/dist/core/runtime/launch-target.js +2 -1
  258. package/dist/core/runtime/launch.js +3 -6
  259. package/dist/core/runtime/lifecycle.js +17 -26
  260. package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
  261. package/dist/core/runtime/naming.js +0 -2
  262. package/dist/core/runtime/nodes.js +2 -1
  263. package/dist/core/runtime/persona.js +0 -6
  264. package/dist/core/runtime/placement-tmux.js +2 -5
  265. package/dist/core/runtime/placement.js +4 -11
  266. package/dist/core/runtime/recap.js +4 -3
  267. package/dist/core/runtime/reopen.js +4 -7
  268. package/dist/core/runtime/reset.js +0 -4
  269. package/dist/core/runtime/resume-root.js +0 -2
  270. package/dist/core/runtime/revive.js +17 -13
  271. package/dist/core/runtime/session-list-cache.js +2 -2
  272. package/dist/core/runtime/shell-expansion.js +0 -2
  273. package/dist/core/runtime/spawn.js +0 -2
  274. package/dist/core/runtime/stream-watchdog.d.ts +0 -1
  275. package/dist/core/runtime/stream-watchdog.js +13 -18
  276. package/dist/core/runtime/tmux-bindings.js +0 -2
  277. package/dist/core/runtime/tmux-driver.d.ts +12 -0
  278. package/dist/core/runtime/tmux-driver.js +18 -13
  279. package/dist/core/runtime/warm-pool.js +0 -4
  280. package/dist/core/scope.js +4 -3
  281. package/dist/core/spawn.js +2 -3
  282. package/dist/core/subscription-state.js +10 -17
  283. package/dist/core/substrate/__tests__/surface-match-pre-command.test.d.ts +1 -0
  284. package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +92 -0
  285. package/dist/core/substrate/frontmatter-validation.js +1 -1
  286. package/dist/core/substrate/injected-store.d.ts +6 -0
  287. package/dist/core/substrate/injected-store.js +24 -0
  288. package/dist/core/substrate/on-read.d.ts +17 -1
  289. package/dist/core/substrate/on-read.js +36 -2
  290. package/dist/core/substrate/render.js +0 -12
  291. package/dist/core/substrate/schema.d.ts +3 -3
  292. package/dist/core/substrate/schema.js +4 -16
  293. package/dist/core/substrate/surface-match.d.ts +19 -0
  294. package/dist/core/substrate/surface-match.js +60 -2
  295. package/dist/core/termrender/display.js +2 -1
  296. package/dist/core/tui/page-host.d.ts +3 -3
  297. package/dist/core/tui/page-host.js +4 -4
  298. package/dist/core/tui/panel.js +3 -3
  299. package/dist/core/wake.js +0 -12
  300. package/dist/core/worktree.js +0 -2
  301. package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
  302. package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
  303. package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
  304. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
  305. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
  306. package/dist/daemon/api/handlers/attach.js +2 -1
  307. package/dist/daemon/api/handlers/broker-ops.js +3 -6
  308. package/dist/daemon/api/handlers/canvas.js +12 -18
  309. package/dist/daemon/api/handlers/feedback-comments.js +0 -8
  310. package/dist/daemon/api/handlers/health.js +3 -2
  311. package/dist/daemon/api/handlers/human-requests.js +0 -10
  312. package/dist/daemon/api/handlers/human.js +1 -5
  313. package/dist/daemon/api/handlers/inbox.js +0 -18
  314. package/dist/daemon/api/handlers/memory.js +1 -1
  315. package/dist/daemon/api/handlers/messages.js +4 -8
  316. package/dist/daemon/api/handlers/nodes.js +10 -21
  317. package/dist/daemon/api/handlers/reports.js +1 -1
  318. package/dist/daemon/api/handlers/reviews.js +1 -6
  319. package/dist/daemon/api/map.js +0 -6
  320. package/dist/daemon/api/router.js +0 -4
  321. package/dist/daemon/api/server.js +2 -5
  322. package/dist/daemon/boot-migrations.d.ts +14 -0
  323. package/dist/daemon/boot-migrations.js +31 -0
  324. package/dist/daemon/cron/armed-context.d.ts +21 -0
  325. package/dist/daemon/cron/armed-context.js +100 -0
  326. package/dist/daemon/cron/capture.d.ts +20 -0
  327. package/dist/daemon/cron/capture.js +45 -0
  328. package/dist/daemon/cron/disposition.d.ts +19 -0
  329. package/dist/daemon/cron/disposition.js +52 -0
  330. package/dist/daemon/cron/escalation.d.ts +12 -0
  331. package/dist/daemon/cron/escalation.js +124 -0
  332. package/dist/daemon/cron/held.d.ts +4 -0
  333. package/dist/daemon/cron/held.js +20 -0
  334. package/dist/daemon/cron/lease-recovery.d.ts +3 -0
  335. package/dist/daemon/cron/lease-recovery.js +119 -0
  336. package/dist/daemon/cron/live-runs.d.ts +11 -0
  337. package/dist/daemon/cron/live-runs.js +23 -0
  338. package/dist/daemon/cron/passes.d.ts +11 -0
  339. package/dist/daemon/cron/passes.js +78 -0
  340. package/dist/daemon/cron/settle.d.ts +28 -0
  341. package/dist/daemon/cron/settle.js +37 -0
  342. package/dist/daemon/cron/sinks.d.ts +2 -0
  343. package/dist/daemon/cron/sinks.js +100 -0
  344. package/dist/daemon/cron-run.d.ts +5 -41
  345. package/dist/daemon/cron-run.js +193 -882
  346. package/dist/daemon/crtrd-cli.js +2 -1
  347. package/dist/daemon/crtrd.js +22 -22
  348. package/dist/daemon/fleet.js +0 -4
  349. package/dist/daemon/human/deliver-action.js +2 -1
  350. package/dist/daemon/human/sweep.js +15 -4
  351. package/dist/daemon/manage.d.ts +2 -2
  352. package/dist/daemon/manage.js +4 -13
  353. package/dist/daemon/pidfile.js +2 -2
  354. package/dist/daemon/reconcilers/broker-supervision.js +26 -29
  355. package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
  356. package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
  357. package/dist/daemon/reconcilers/live-obligation.js +2 -2
  358. package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
  359. package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
  360. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
  361. package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
  362. package/dist/daemon/review/companion.js +2 -2
  363. package/dist/daemon/review/sweep.js +8 -3
  364. package/dist/migrations/001-surfaces-frontmatter.js +0 -6
  365. package/dist/migrations/corpus.js +2 -5
  366. package/dist/migrations/registry.js +15 -2
  367. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
  368. package/dist/pi-extensions/__tests__/pre-command-gate.test.d.ts +1 -0
  369. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +220 -0
  370. package/dist/pi-extensions/broker-local.js +2 -1
  371. package/dist/pi-extensions/canvas-bash-valve.js +3 -17
  372. package/dist/pi-extensions/canvas-context-intro.js +2 -7
  373. package/dist/pi-extensions/canvas-doc-substrate.d.ts +14 -0
  374. package/dist/pi-extensions/canvas-doc-substrate.js +77 -5
  375. package/dist/pi-extensions/canvas-goal-capture.js +2 -1
  376. package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
  377. package/dist/pi-extensions/canvas-passive-context.js +2 -5
  378. package/dist/pi-extensions/canvas-preview-result.js +2 -1
  379. package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
  380. package/dist/pi-extensions/canvas-recap.js +3 -16
  381. package/dist/pi-extensions/canvas-review-boundary.js +2 -5
  382. package/dist/pi-extensions/canvas-stophook.js +2 -26
  383. package/dist/pi-extensions/canvas-structured-output.js +2 -1
  384. package/dist/pi-extensions/canvas-tool-guide.js +2 -1
  385. package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
  386. package/dist/shared/__tests__/env-boundary.test.js +93 -0
  387. package/dist/shared/env.d.ts +68 -0
  388. package/dist/shared/env.js +171 -0
  389. package/package.json +3 -1
  390. package/runtime.lock.json +2 -2
  391. package/dist/core/canvas/render.d.ts +0 -47
  392. package/dist/core/canvas/render.js +0 -245
@@ -1,6 +1,6 @@
1
- import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
2
- import { type TUI } from "@earendil-works/pi-tui";
3
- import { type SetupPanel } from "../../../core/tui/panel.js";
1
+ import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
2
+ import { type TUI } from '@earendil-works/pi-tui';
3
+ import { type SetupPanel } from '../../../core/tui/panel.js';
4
4
  type AuthStatus = ReturnType<ModelRegistry["getProviderAuthStatus"]>;
5
5
  type ProviderAuthType = "oauth" | "api_key";
6
6
  type ProviderRow = {
@@ -1,11 +1,11 @@
1
- import { LoginDialogComponent, } from "@earendil-works/pi-coding-agent";
2
- import { Input, matchesKey, truncateToWidth, } from "@earendil-works/pi-tui";
3
- import { execFile } from "node:child_process";
4
- import { dialogAuthInteraction } from "../../../core/auth-interaction.js";
5
- import { isManagedProvider, promoteSubscription, readSubscriptionPool, renameSubscription, } from "../../../core/subscription-state.js";
6
- import { removeProviderRow } from "../../../core/provider-management.js";
7
- import { cliClient } from "../../api-client.js";
8
- import { fitTail, theme, visibleRange, } from "../../../core/tui/panel.js";
1
+ import { LoginDialogComponent, } from '@earendil-works/pi-coding-agent';
2
+ import { Input, matchesKey, truncateToWidth, } from '@earendil-works/pi-tui';
3
+ import { execFile } from 'node:child_process';
4
+ import { dialogAuthInteraction } from '../../../core/auth-interaction.js';
5
+ import { isManagedProvider, promoteSubscription, readSubscriptionPool, renameSubscription, } from '../../../core/subscription-state.js';
6
+ import { removeProviderRow } from '../../../core/provider-management.js';
7
+ import { cliClient } from '../../api-client.js';
8
+ import { fitTail, theme, visibleRange, } from '../../../core/tui/panel.js';
9
9
  /** Curated setup-catalog view over pi's OAuth providers + the model registry. */
10
10
  export function buildProviderCatalog(runtime, registry) {
11
11
  const oauth = runtime
@@ -1,6 +1,6 @@
1
- import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
2
- import { TUI, type Component, type Focusable } from "@earendil-works/pi-tui";
3
- import { type BindingId, type BindingResolution } from "../../core/keybindings/index.js";
1
+ import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
2
+ import { TUI, type Component, type Focusable } from '@earendil-works/pi-tui';
3
+ import { type BindingId, type BindingResolution } from '../../core/keybindings/index.js';
4
4
  export type DestinationId = "providers" | "models" | "keybindings" | "display" | "profiles" | "panes" | "broker-limits" | "updates";
5
5
  export declare class SettingsShell implements Component, Focusable {
6
6
  private readonly tui;
@@ -2,18 +2,18 @@
2
2
  //
3
3
  // This is the persistent settings editor. The first-run installer lives in
4
4
  // setup-wizard.ts; both surfaces reuse only the panels they actually share.
5
- import { matchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
6
- import { configPath } from "../../core/config.js";
7
- import { formatGesture, matchesPiTuiInput, } from "../../core/keybindings/index.js";
8
- import { reconcileInboxFrontDoor } from "./setup-core.js";
9
- import { runSetupSettingsProcess } from "./setup-settings-runner.js";
10
- import { BrokerLimitsPanel } from "./panels/broker-limits-panel.js";
11
- import { KeybindingsPanel } from "./panels/keybindings-panel.js";
12
- import { ModelsPanel } from "./panels/models-panel.js";
13
- import { PAGE_CHROME_ROWS, columns, padAnsi, pageFrame, theme, } from "../../core/tui/panel.js";
14
- import { PrefsPanel } from "./panels/prefs-panel.js";
15
- import { ProfilesPanel } from "./panels/profiles-panel.js";
16
- import { ProviderPanel } from "./panels/provider-panel.js";
5
+ import { matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
6
+ import { configPath } from '../../core/config.js';
7
+ import { formatGesture, matchesPiTuiInput, } from '../../core/keybindings/index.js';
8
+ import { reconcileInboxFrontDoor } from './setup-core.js';
9
+ import { runSetupSettingsProcess } from './setup-settings-runner.js';
10
+ import { BrokerLimitsPanel } from './panels/broker-limits-panel.js';
11
+ import { KeybindingsPanel } from './panels/keybindings-panel.js';
12
+ import { ModelsPanel } from './panels/models-panel.js';
13
+ import { PAGE_CHROME_ROWS, columns, padAnsi, pageFrame, theme, } from '../../core/tui/panel.js';
14
+ import { PrefsPanel } from './panels/prefs-panel.js';
15
+ import { ProfilesPanel } from './panels/profiles-panel.js';
16
+ import { ProviderPanel } from './panels/provider-panel.js';
17
17
  /** Rail column width, including its badge gutter. */
18
18
  const RAIL_WIDTH = 22;
19
19
  /** Below this the rail costs more than it explains; the page falls back to a
@@ -114,7 +114,7 @@ export class SettingsShell {
114
114
  }
115
115
  }
116
116
  }
117
- // --- focus and navigation -------------------------------------------------
117
+ // focus and navigation
118
118
  set focused(value) {
119
119
  this._focused = value;
120
120
  this.syncFocus();
@@ -164,7 +164,7 @@ export class SettingsShell {
164
164
  ? "disabled"
165
165
  : formatGesture(gestures[0].split(" > ").pop());
166
166
  }
167
- // --- input ----------------------------------------------------------------
167
+ // input
168
168
  /** Panels that have taken over the keyboard — a live text field, a modal, an
169
169
  * open picker — keep every key including Escape until they release it. */
170
170
  panelOwnsKeyboard(data) {
@@ -270,7 +270,7 @@ export class SettingsShell {
270
270
  this.keybindings.focused = false;
271
271
  this.brokerLimits.focused = false;
272
272
  }
273
- // --- rendering ------------------------------------------------------------
273
+ // rendering
274
274
  renderRail(height) {
275
275
  const out = [];
276
276
  let group;
@@ -1,6 +1,6 @@
1
- import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
2
- import type { BindingId, BindingResolution } from "../../core/keybindings/index.js";
3
- import { type TuiPageContext, type TuiPageControl, type TuiPageSurface } from "../../core/tui/page-host.js";
1
+ import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
2
+ import type { BindingId, BindingResolution } from '../../core/keybindings/index.js';
3
+ import { type TuiPageContext, type TuiPageControl, type TuiPageSurface } from '../../core/tui/page-host.js';
4
4
  /** Setup and settings both edit pi's model configuration, so their host adds
5
5
  * the model runtime to the shared page context. Every other page concern —
6
6
  * theme, keybindings, terminal lifecycle — belongs to `runTuiPage`. */
@@ -1,8 +1,8 @@
1
- import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
2
- import { join } from "node:path";
3
- import { defaultAgentDir } from "../../clients/attach/config.js";
4
- import { PoolCredentialStore } from "../../core/pool-credential-store.js";
5
- import { runTuiPage, } from "../../core/tui/page-host.js";
1
+ import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
2
+ import { join } from 'node:path';
3
+ import { defaultAgentDir } from '../../clients/attach/config.js';
4
+ import { PoolCredentialStore } from '../../core/pool-credential-store.js';
5
+ import { runTuiPage, } from '../../core/tui/page-host.js';
6
6
  export async function runSetupSettingsProcess(createSurface, interceptCancel) {
7
7
  const agentDir = defaultAgentDir();
8
8
  const runtime = await ModelRuntime.create({
@@ -1,7 +1,7 @@
1
- import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
2
- import { TUI, type Component, type Focusable } from "@earendil-works/pi-tui";
3
- import { type BindingId, type BindingResolution } from "../../core/keybindings/index.js";
4
- import { type SetupPackageId, type SetupPackageManifestEntry, type SetupSubmission, type SetupSystemInstallPlan } from "./setup-core.js";
1
+ import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
2
+ import { TUI, type Component, type Focusable } from '@earendil-works/pi-tui';
3
+ import { type BindingId, type BindingResolution } from '../../core/keybindings/index.js';
4
+ import { type SetupPackageId, type SetupPackageManifestEntry, type SetupSubmission, type SetupSystemInstallPlan } from './setup-core.js';
5
5
  /** The host owns step navigation, focus, submission and composition. Each step
6
6
  * owns its own mutable UI state. */
7
7
  export declare class SetupWizard implements Component, Focusable {
@@ -5,14 +5,14 @@
5
5
  // broker limits, updates — lives in settings-shell.ts. Both hosts share the
6
6
  // panels in ./panels; this one is a linear stepper that ends in a single
7
7
  // confirmed install, that one is a persistent editor that saves as you go.
8
- import { matchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
9
- import { formatGesture, matchesPiTuiInput, } from "../../core/keybindings/index.js";
10
- import { buildSetupInitialState, executeSetupSubmission, formatSetupExecutionSummary, packagePlanAction, reconcileInboxFrontDoor, renderSetupStaticInstructions, } from "./setup-core.js";
11
- import { runSetupSettingsProcess } from "./setup-settings-runner.js";
12
- import { DependenciesPanel } from "./panels/dependencies-panel.js";
13
- import { PAGE_CHROME_ROWS, pageFrame, theme, } from "../../core/tui/panel.js";
14
- import { PluginsPanel } from "./panels/plugins-panel.js";
15
- import { ProviderPanel } from "./panels/provider-panel.js";
8
+ import { matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
9
+ import { formatGesture, matchesPiTuiInput, } from '../../core/keybindings/index.js';
10
+ import { buildSetupInitialState, executeSetupSubmission, formatSetupExecutionSummary, packagePlanAction, reconcileInboxFrontDoor, renderSetupStaticInstructions, } from './setup-core.js';
11
+ import { runSetupSettingsProcess } from './setup-settings-runner.js';
12
+ import { DependenciesPanel } from './panels/dependencies-panel.js';
13
+ import { PAGE_CHROME_ROWS, pageFrame, theme, } from '../../core/tui/panel.js';
14
+ import { PluginsPanel } from './panels/plugins-panel.js';
15
+ import { ProviderPanel } from './panels/provider-panel.js';
16
16
  const STEPS = ["Providers", "Plugins", "Dependencies"];
17
17
  /** The host owns step navigation, focus, submission and composition. Each step
18
18
  * owns its own mutable UI state. */
@@ -16,7 +16,7 @@ const BUNDLE_NAME = 'bundle.jsonl';
16
16
  const CAP_BYTES = 2_097_152;
17
17
  const MAX_RUNTIME_STATE_BYTES = MAX_EVENT_RECORD_BYTES - 1;
18
18
  const CATEGORIES = ['events', 'runtime_state', 'raw_residue'];
19
- // ---- reference generation ----
19
+ // reference generation
20
20
  function pad(value, length) {
21
21
  return String(value).padStart(length, '0');
22
22
  }
@@ -26,7 +26,7 @@ function generateReference(now) {
26
26
  const time = `${pad(now.getUTCHours(), 2)}${pad(now.getUTCMinutes(), 2)}${pad(now.getUTCSeconds(), 2)}${pad(now.getUTCMilliseconds(), 3)}`;
27
27
  return `${date}T${time}Z-${randomBytes(4).toString('hex')}`;
28
28
  }
29
- // ---- compact bundle records ----
29
+ // compact bundle records
30
30
  function compactLine(record) {
31
31
  return Buffer.from(`${JSON.stringify(record)}\n`, 'utf8');
32
32
  }
@@ -123,7 +123,7 @@ function collectRuntimeState(nodeId, ts, sources) {
123
123
  }
124
124
  return records;
125
125
  }
126
- // ---- manifest + cap fitting ----
126
+ // manifest + cap fitting
127
127
  function renderManifestBuffer(base, sources) {
128
128
  const manifest = {
129
129
  reference: base.reference,
@@ -157,7 +157,7 @@ function fitToCap(records, sources, base) {
157
157
  state.truncated = true;
158
158
  }
159
159
  }
160
- // ---- core ----
160
+ // core
161
161
  /** Output-free: collects and writes the prepared artifact and returns a
162
162
  * small result. Never prints; the leaf below owns argv/rendering. */
163
163
  export function prepareSupportBundle(nodeId) {
@@ -43,7 +43,7 @@ export function defaultGhRunner(args, input) {
43
43
  const included = args.includes('--include') ? splitIncludedResponse(raw) : { body: raw, httpStatus: undefined };
44
44
  return { code: result.status, stdout: included.body, stderr, httpStatus: included.httpStatus };
45
45
  }
46
- // ---- symlink-free, TOCTOU-safe filesystem access ----
46
+ // symlink-free, TOCTOU-safe filesystem access
47
47
  // `~/.crouter/support/<reference>/` is user-writable, so every open here rejects symlinks
48
48
  // and confirms the thing opened is the thing later read/uploaded — never an arbitrary path.
49
49
  function closeSafe(fd) {
@@ -194,7 +194,7 @@ function openReference(reference) {
194
194
  throw error;
195
195
  }
196
196
  }
197
- // ---- manifest and bundle validation ----
197
+ // manifest and bundle validation
198
198
  // An ordinary manifest: every field is either fixed input (the reference) or independently
199
199
  // checked against its own declared shape. Nothing here is self-referential — the cap is
200
200
  // enforced by directly measuring the two files, not by trusting a declared byte count.
@@ -449,9 +449,7 @@ export function runDepsSync(opts) {
449
449
  writeText(join(depsDir, '.gitignore'), '*\n');
450
450
  return rows;
451
451
  }
452
- // ---------------------------------------------------------------------------
453
452
  // The `sys sync deps` leaf.
454
- // ---------------------------------------------------------------------------
455
453
  function renderDepsSummary(rows) {
456
454
  const imported = rows.filter((r) => r.status === 'imported').length;
457
455
  const wouldImport = rows.filter((r) => r.status === 'would-import').length;
@@ -2,6 +2,7 @@
2
2
  // CLAUDE.md/AGENTS.md/.claude/rules into repository-root and nested
3
3
  // `.crouter/memory/` stores. Only the repository root owns the namespace and
4
4
  // workspace front door; nested stores derive their prefix from their path.
5
+ import { envProfileId } from '../../shared/env.js';
5
6
  import { existsSync, readdirSync, statSync } from 'node:fs';
6
7
  import { basename, join, resolve } from 'node:path';
7
8
  import { defineLeaf } from '../../core/command.js';
@@ -33,7 +34,7 @@ function projectDocRoots(sourceArg) {
33
34
  if (sourceArg !== undefined)
34
35
  return [resolve(sourceArg)];
35
36
  const roots = [resolve(process.cwd())];
36
- const profileId = process.env['CRTR_PROFILE_ID'];
37
+ const profileId = envProfileId();
37
38
  if (profileId !== undefined && profileId !== '') {
38
39
  try {
39
40
  const { manifest } = loadProfileManifest(profileId);
@@ -1,3 +1,4 @@
1
+ import { envNodeId } from '../../shared/env.js';
1
2
  import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
3
  import { tmpdir } from 'node:os';
3
4
  import { join } from 'node:path';
@@ -34,7 +35,7 @@ export const sysSyspromptLeaf = defineLeaf({
34
35
  preview: { suppressOutput: true },
35
36
  },
36
37
  run: async (input) => {
37
- const nodeId = input['node']?.trim() || process.env['CRTR_NODE_ID'];
38
+ const nodeId = input['node']?.trim() || envNodeId();
38
39
  if (nodeId === undefined || nodeId === '') {
39
40
  throw new InputError({ error: 'empty_node', message: 'a node id is required', field: 'node', next: 'Pass a node id or run from inside a node.' });
40
41
  }
@@ -59,9 +59,7 @@ after(() => {
59
59
  rmSync(home, { recursive: true, force: true });
60
60
  delete process.env['CRTR_HOME'];
61
61
  });
62
- // ---------------------------------------------------------------------------
63
62
  // bootChanged — pure core
64
- // ---------------------------------------------------------------------------
65
63
  test('bootChanged: no persisted history is never a change (never clears a genuinely live pid)', () => {
66
64
  assert.equal(bootChanged(ident('boot-A', 1_000_000), null), false);
67
65
  });
@@ -84,12 +82,8 @@ test('bootChanged: wall-clock fallback (no boot_id) — epochs within tolerance
84
82
  test('bootChanged: wall-clock fallback (no boot_id) — epochs far apart are a DIFFERENT boot', () => {
85
83
  assert.equal(bootChanged(ident(null, 1_000_000 + 10 * 60_000), ident(null, 1_000_000)), true, '10min apart — different boot');
86
84
  });
87
- // ---------------------------------------------------------------------------
88
85
  // reconcileBootLiveness — the impure sweep against a real canvas.db
89
- // ---------------------------------------------------------------------------
90
- // ---------------------------------------------------------------------------
91
86
  // migrateLegacyPidIdentities — one-time in-place format migration at startup
92
- // ---------------------------------------------------------------------------
93
87
  test('migrateLegacyPidIdentities: a live legacy lstart#ticks broker is re-recorded to <bootId>#<ticks>', () => {
94
88
  createNode(node('n1'));
95
89
  setPidIdentity('n1', 111, 'Sat Jul 11 17:51:45 2026#53459'); // legacy baseline
@@ -193,7 +187,6 @@ test('reconcileBootLiveness: after reconciling, the SAME boot no longer re-trigg
193
187
  assert.deepEqual(result, { changed: false, cleared: [], sameBootProven: true });
194
188
  assert.equal(getRow('n1')?.pi_pid, 999, 'a pid recorded AFTER reconciliation, within the same boot, is never touched');
195
189
  });
196
- // ---------------------------------------------------------------------------
197
190
  // bootProvenSame + the daemon migration gate (Major-1 regression)
198
191
  //
199
192
  // The legacy identity migration blesses a recorded pid as a current-boot
@@ -202,7 +195,6 @@ test('reconcileBootLiveness: after reconciling, the SAME boot no longer re-trigg
202
195
  // NOT that proof — it is also true with no/malformed marker or the wall-clock
203
196
  // epoch fallback, where an unrelated cross-boot process can collide on
204
197
  // pid+ticks and be permanently baptized.
205
- // ---------------------------------------------------------------------------
206
198
  test('bootProvenSame: exact equal boot_id on both sides is proof', () => {
207
199
  assert.equal(bootProvenSame(ident('boot-A', 2_000), ident('boot-A', 1_000)), true);
208
200
  });
@@ -3,12 +3,33 @@
3
3
  // and rejects any reach to the single DB accessor module.
4
4
  import { test } from 'node:test';
5
5
  import assert from 'node:assert/strict';
6
- import { existsSync, readFileSync } from 'node:fs';
6
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
7
7
  import { dirname, join, normalize, resolve } from 'node:path';
8
8
  import ts from 'typescript';
9
9
  import { CANVAS_EXTENSION_NAMES } from '../canvas/extensions.js';
10
10
  const SOURCE_ROOT = resolve(process.cwd(), 'src');
11
11
  const DB_MODULE = normalize(join(SOURCE_ROOT, 'core/canvas/db.ts'));
12
+ // The builtin pi-crtr-extensions package loads into the SAME broker process
13
+ // as the shipped pi-extensions/ roots below (its own package.json declares
14
+ // `pi.extensions: ["extensions/*.ts", "extensions/*/index.ts"]`), so it must
15
+ // clear the identical canvas.db-free bar. Read the glob's two shapes directly
16
+ // off disk rather than hardcoding a name list, so a future extension added to
17
+ // the package is covered automatically.
18
+ function builtinPiCrtrExtensionRoots() {
19
+ const extDir = join(SOURCE_ROOT, 'builtin-pi-packages/pi-crtr-extensions/extensions');
20
+ const roots = [];
21
+ for (const entry of readdirSync(extDir, { withFileTypes: true })) {
22
+ if (entry.isFile() && entry.name.endsWith('.ts') && !entry.name.includes('.test.')) {
23
+ roots.push(join(extDir, entry.name));
24
+ }
25
+ else if (entry.isDirectory()) {
26
+ const indexFile = join(extDir, entry.name, 'index.ts');
27
+ if (existsSync(indexFile))
28
+ roots.push(indexFile);
29
+ }
30
+ }
31
+ return roots;
32
+ }
12
33
  function resolveLocalImport(from, specifier) {
13
34
  if (!specifier.startsWith('.'))
14
35
  return null;
@@ -45,6 +66,7 @@ test('every shipped broker extension and the prospective preflight are transitiv
45
66
  const roots = [
46
67
  ...CANVAS_EXTENSION_NAMES.map((name) => join(SOURCE_ROOT, 'pi-extensions', `${name}.ts`)),
47
68
  join(SOURCE_ROOT, 'core/runtime/prospective-inventory-cli.ts'),
69
+ ...builtinPiCrtrExtensionRoots(),
48
70
  ];
49
71
  for (const root of roots) {
50
72
  const reach = dbReach(root);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ // canvas.ts declares itself "the one place that reads/writes the node+edge
2
+ // model" for the runtime layer (spawn, push, lifecycle, daemon) that builds on
3
+ // it — every node/edge write from `runtime/` goes through a named canvas.ts
4
+ // accessor, never a raw SQL statement of its own. This grep-enforces that
5
+ // boundary at the one layer the convention actually claims: `core/runtime/`.
6
+ // (Other canvas.db tables — reviews, review_comments — are a SEPARATE
7
+ // subsystem's tables with their own owner in `core/review/`, and `feed.ts`'s
8
+ // `commitCanonicalFinal` is the one documented sanctioned exception outside
9
+ // `runtime/` entirely — see `core/canvas/.crouter/memory/INDEX.md` — so
10
+ // neither is in scope here.)
11
+ import { test } from 'node:test';
12
+ import assert from 'node:assert/strict';
13
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
14
+ import { join, resolve } from 'node:path';
15
+ const RUNTIME_ROOT = resolve(process.cwd(), 'src/core/runtime');
16
+ function tsFilesUnder(dir) {
17
+ const out = [];
18
+ for (const entry of readdirSync(dir)) {
19
+ const path = join(dir, entry);
20
+ const stat = statSync(path);
21
+ if (stat.isDirectory()) {
22
+ if (entry === '__tests__')
23
+ continue;
24
+ out.push(...tsFilesUnder(path));
25
+ }
26
+ else if (entry.endsWith('.ts') && !entry.endsWith('.test.ts')) {
27
+ out.push(path);
28
+ }
29
+ }
30
+ return out;
31
+ }
32
+ test('no file under core/runtime/ prepares a raw SQL statement — canvas.ts is the one node+edge DAO', () => {
33
+ const offenders = [];
34
+ for (const file of tsFilesUnder(RUNTIME_ROOT)) {
35
+ const source = readFileSync(file, 'utf8');
36
+ const lines = source.split('\n');
37
+ lines.forEach((line, index) => {
38
+ // Skip comment lines referencing `.prepare(` in prose (backtick-quoted).
39
+ if (/`\.prepare\(`/.test(line))
40
+ return;
41
+ if (line.includes('.prepare('))
42
+ offenders.push(`${file}:${index + 1}`);
43
+ });
44
+ }
45
+ assert.deepEqual(offenders, [], `raw .prepare( call(s) outside core/canvas/:\n${offenders.join('\n')}`);
46
+ });
@@ -118,7 +118,7 @@ test('recordSpawn writes the audit-only spawned_by edge', () => {
118
118
  assert.equal(subscriptionsOf('child').length, 0);
119
119
  assert.equal(subscribersOf('parent').length, 0);
120
120
  });
121
- // --- Durability contract: meta.json is durable and never truncated -------
121
+ // Durability contract: meta.json is durable and never truncated
122
122
  // writeMeta uses atomic rename with fsync on both the temp file and parent
123
123
  // directory to ensure atomicity and durability. readMeta/rebuildIndex tolerate
124
124
  // an empty/corrupt meta.json as MISSING (returning null) rather than throwing,
@@ -110,7 +110,6 @@ after(() => {
110
110
  delete process.env['CRTR_HOME'];
111
111
  resetFleetForTests();
112
112
  });
113
- // ===========================================================================
114
113
  // BROKER CUT — exit-event authority REPLACES daemon pane-gone reaping. A booted
115
114
  // child whose engine pid is dead (and whose viewer pane is gone) is REVIVABLE,
116
115
  // not reaped, and the daemon's tick fans NO wake to the dormant parent. This
@@ -120,7 +119,6 @@ after(() => {
120
119
  // node recovery rides real exit events), the daemon treats them all identically
121
120
  // — no reap, no liveness wake — so the old CRUX (no spurious wake on healthy
122
121
  // dormancy) holds by construction.
123
- // ===========================================================================
124
122
  test('exit-event authority: a dead-engine booted child is revivable, NOT reaped, and the tick fans NO wake to its dormant parent', async () => {
125
123
  inboxWaitingParent('PARENT');
126
124
  deadEngineChild('CHILD');
@@ -0,0 +1,145 @@
1
+ // Run with: npm test -- --test-name-pattern='armed'
2
+ //
3
+ // A cron run executes under the context it was ARMED under: the `--env`
4
+ // snapshot and the profile stored on the row. When that stored context cannot
5
+ // be rebuilt, the run must FAIL LOUDLY rather than execute the command under a
6
+ // context nobody armed — the class of defect that otherwise runs wrong, every
7
+ // tick, forever, with a clean run log.
8
+ import { after, afterEach, before, beforeEach, test } from 'node:test';
9
+ import assert from 'node:assert/strict';
10
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+ import { listNodes } from '../canvas/canvas.js';
14
+ import { armCron, getCron, listCronRuns } from '../canvas/crons.js';
15
+ import { closeDb } from '../canvas/db.js';
16
+ import { createProfile, profileManifestPath, profileRoot } from '../profiles/manifest.js';
17
+ import { resetScopeCache } from '../scope.js';
18
+ import { headlessBrokerHost } from '../runtime/host.js';
19
+ import { resetFleetForTests } from '../runtime/fleet.js';
20
+ import { executeCron } from '../../daemon/cron-run.js';
21
+ import { bindTestFleet } from './helpers/fleet.js';
22
+ let userHome;
23
+ let canvasHome;
24
+ let priorHome;
25
+ let priorCanvasHome;
26
+ let fleet;
27
+ let originalLaunch;
28
+ /** The marker a run writes if the command actually executed. */
29
+ function marker(id) {
30
+ return join(canvasHome, `${id}.ran`);
31
+ }
32
+ function arm({ cron_id, ...overrides }) {
33
+ armCron({
34
+ cron_id,
35
+ name: cron_id,
36
+ created_by: null,
37
+ // Records the profile the command actually ran under, so a root fallback is
38
+ // observable rather than inferred.
39
+ command: `printf "%s" "$CRTR_PROFILE_ID" > ${marker(cron_id)}`,
40
+ fire_at: new Date(Date.now() - 1_000).toISOString(),
41
+ recur: null, // a one-shot: retention is observable
42
+ tz: null,
43
+ expires_at: null,
44
+ anchor_node: null,
45
+ cancel_on_wake: false,
46
+ cwd: canvasHome,
47
+ env_json: null,
48
+ profile: null,
49
+ scope: 'profile',
50
+ run_timeout_s: 30,
51
+ overlap: 'skip',
52
+ on_output: 'on-failure',
53
+ sink: '{}',
54
+ tier: 'normal',
55
+ ...overrides,
56
+ });
57
+ }
58
+ before(() => {
59
+ // realpath: the profile store persists realpath-resolved paths, and macOS
60
+ // hands back a symlinked /var/folders prefix.
61
+ userHome = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-cron-armed-user-')));
62
+ canvasHome = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-cron-armed-canvas-')));
63
+ priorHome = process.env['HOME'];
64
+ priorCanvasHome = process.env['CRTR_HOME'];
65
+ process.env['HOME'] = userHome; // the profile store lives under the USER root
66
+ process.env['CRTR_HOME'] = canvasHome;
67
+ resetScopeCache();
68
+ });
69
+ beforeEach(() => {
70
+ closeDb();
71
+ rmSync(canvasHome, { recursive: true, force: true });
72
+ mkdirSync(canvasHome, { recursive: true });
73
+ resetFleetForTests();
74
+ fleet = bindTestFleet();
75
+ originalLaunch = headlessBrokerHost.launch;
76
+ headlessBrokerHost.launch = ((nodeId) => {
77
+ const handle = { pid: 0x7ffffffe, exited: new Promise(() => { }) };
78
+ fleet.fleet.register(nodeId, handle);
79
+ return handle;
80
+ });
81
+ });
82
+ afterEach(() => {
83
+ headlessBrokerHost.launch = originalLaunch;
84
+ closeDb();
85
+ resetFleetForTests();
86
+ });
87
+ after(() => {
88
+ rmSync(canvasHome, { recursive: true, force: true });
89
+ rmSync(userHome, { recursive: true, force: true });
90
+ if (priorHome === undefined)
91
+ delete process.env['HOME'];
92
+ else
93
+ process.env['HOME'] = priorHome;
94
+ if (priorCanvasHome === undefined)
95
+ delete process.env['CRTR_HOME'];
96
+ else
97
+ process.env['CRTR_HOME'] = priorCanvasHome;
98
+ resetScopeCache();
99
+ });
100
+ /** Every way an armed context fails settles identically: no child, a record
101
+ * naming the cause, a paused row that keeps its one-shot, and one escalation
102
+ * node. */
103
+ function assertSettledAsArmingFailure(id, record, cause, nodesBefore) {
104
+ assert.equal(existsSync(marker(id)), false, `${id}: the command never ran`);
105
+ assert.equal(record?.exit_code, null, `${id}: no process existed to exit`);
106
+ assert.match(record?.stderr_head ?? '', cause, `${id}: the record names the cause`);
107
+ assert.match(record?.delivered ?? '', /^failure: cron paused, escalation node /, `${id}: the failure is escalated`);
108
+ assert.equal(listNodes().length, nodesBefore + 1, `${id}: exactly one escalation node was created`);
109
+ const row = getCron(id);
110
+ assert.notEqual(row, null, `${id}: the one-shot is retained, not consumed`);
111
+ assert.equal(row.state, 'paused', `${id}: the row pauses, so it does not refire every tick`);
112
+ assert.equal(row.run_state, 'idle', `${id}: no lease was ever taken`);
113
+ assert.deepEqual(listCronRuns(id).map((r) => r.run_id), [record.run_id], `${id}: the failure left exactly one record`);
114
+ }
115
+ test('a corrupt armed env snapshot fails the run instead of silently dropping the operator variables', async () => {
116
+ // Both shapes the arm-time validator rejects, so neither can reach a row
117
+ // through the supported path: unparseable JSON, and a non-string value.
118
+ const corrupt = [
119
+ ['env-unparseable', '{"API_BASE": '],
120
+ ['env-nonstring', '{"API_BASE": 42}'],
121
+ ];
122
+ for (const [id, envJson] of corrupt) {
123
+ arm({ cron_id: id, env_json: envJson });
124
+ const nodesBefore = listNodes().length;
125
+ const record = await executeCron(getCron(id), { outOfBand: false });
126
+ assertSettledAsArmingFailure(id, record, /run could not be armed: the armed env snapshot/, nodesBefore);
127
+ }
128
+ });
129
+ test('an unreadable profile manifest fails the run, while a deleted profile still runs as root', async () => {
130
+ const unreadable = createProfile('cron-unreadable').profileId;
131
+ const deleted = createProfile('cron-deleted').profileId;
132
+ // Corrupt one manifest in place and delete the other profile outright: the
133
+ // two states the old resolver could not tell apart.
134
+ writeFileSync(profileManifestPath(unreadable), '{ not json', 'utf8');
135
+ rmSync(profileRoot(deleted), { recursive: true, force: true });
136
+ arm({ cron_id: 'profile-unreadable', profile: unreadable });
137
+ const nodesBefore = listNodes().length;
138
+ const failed = await executeCron(getCron('profile-unreadable'), { outOfBand: false });
139
+ assertSettledAsArmingFailure('profile-unreadable', failed, /run could not be armed: the armed profile .* manifest could not be read/, nodesBefore);
140
+ arm({ cron_id: 'profile-deleted', profile: deleted });
141
+ const ran = await executeCron(getCron('profile-deleted'), { outOfBand: false });
142
+ assert.equal(ran?.exit_code, 0, 'a deleted profile is the spec root fallback — the run proceeds');
143
+ assert.equal(readFileSync(marker('profile-deleted'), 'utf8'), '', 'the run carries no profile id: it ran as root');
144
+ assert.equal(getCron('profile-deleted'), null, 'an ordinary settled one-shot is consumed');
145
+ });
@@ -37,12 +37,10 @@ test('wedgeVerdict: fires ONLY when busy + quiet-past-grace + near-zero cpu all
37
37
  assert.equal(wedgeVerdict({ busy: true, quietForMs: WEDGE_QUIET_MS, cpuPercent: 0 }), 'wedged');
38
38
  assert.equal(wedgeVerdict({ busy: true, quietForMs: WEDGE_QUIET_MS + 60_000, cpuPercent: WEDGE_CPU_MAX_PERCENT }), 'wedged', 'at the cpu ceiling (inclusive)');
39
39
  });
40
- // ---------------------------------------------------------------------------
41
40
  // sumTreeCpu (pure) — process-tree CPU sampling. Review fix for issue #110:
42
41
  // sampling only the broker pid false-fired on a broker awaiting a busy CHILD
43
42
  // subprocess. These lock the tree-sum behavior directly, independent of any
44
43
  // `ps` shell-out.
45
- // ---------------------------------------------------------------------------
46
44
  test('sumTreeCpu: a busy CHILD subprocess exonerates an idle broker (the false-positive this fixes)', () => {
47
45
  const ps = [
48
46
  ' 100 1 0.0', // broker, idle
@@ -85,11 +83,9 @@ test('sumTreeCpu: malformed/blank lines are skipped; empty output returns null',
85
83
  assert.equal(sumTreeCpu('', 100), null);
86
84
  assert.equal(sumTreeCpu(' \n \n', 100), null);
87
85
  });
88
- // ---------------------------------------------------------------------------
89
86
  // treeHasDescendants (pure) — issue #119: the broker-alone case has no
90
87
  // subprocess to kill, so remediation must branch on this instead of assuming
91
88
  // a runaway child always exists.
92
- // ---------------------------------------------------------------------------
93
89
  test('treeHasDescendants: broker alone in its own tree — nothing to kill', () => {
94
90
  const ps = [' 100 1 0.0'].join('\n');
95
91
  assert.equal(treeHasDescendants(ps, 100), false);
@@ -20,9 +20,7 @@ function row(over = {}) {
20
20
  ...over,
21
21
  };
22
22
  }
23
- // ===========================================================================
24
23
  // The eight table rows, in evaluation order (design flow B).
25
- // ===========================================================================
26
24
  test('row 1: a deleted row (orphan of a pruned/closed node) is forgotten', () => {
27
25
  assert.equal(classifyDeadNode(null, false), 'forget');
28
26
  assert.equal(classifyDeadNode(null, true), 'forget', 'a stale busy marker cannot resurrect a deleted row');
@@ -68,11 +66,9 @@ test('row 7: an ordinary fork dead before binding its child session retries the
68
66
  test('row 8: sessionless with no fork claim — a fresh launch died before session_start — is a boot failure', () => {
69
67
  assert.equal(classifyDeadNode(row(), false), 'boot-failure');
70
68
  });
71
- // ===========================================================================
72
69
  // retryResumeMode (pure): only an unconfirmed cycling launch must cycle again.
73
70
  // An interrupted ordinary turn resumes strictly; `#respawn` adds the runtime
74
71
  // continuation that re-drives the stopped work in the same conversation.
75
- // ===========================================================================
76
72
  test('retryResumeMode: pending cycles recover as cycles; every confirmed saved session resumes strictly', () => {
77
73
  assert.equal(retryResumeMode({ cycle_pending: true }), false, 'a pending cycling attempt retries AS a cycle');
78
74
  assert.equal(retryResumeMode({ cycle_pending: false }), true, 'an ordinary saved session resumes strictly');