@north-light/crouter 0.3.312 → 0.3.313

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 (495) hide show
  1. package/dist/api/client.d.ts +27 -30
  2. package/dist/api/client.js +80 -67
  3. package/dist/api/dto/broker-ops.d.ts +10 -20
  4. package/dist/api/dto/canvas.d.ts +1 -10
  5. package/dist/api/dto/health.d.ts +113 -0
  6. package/dist/api/dto/human-requests.d.ts +14 -3
  7. package/dist/api/dto/human.d.ts +2 -48
  8. package/dist/api/dto/human.js +3 -2
  9. package/dist/api/dto/inbox.d.ts +14 -0
  10. package/dist/api/dto/mail.d.ts +43 -0
  11. package/dist/api/dto/nodes.d.ts +11 -8
  12. package/dist/api/dto/recovery.d.ts +96 -0
  13. package/dist/api/dto/reviews.d.ts +2 -2
  14. package/dist/api/routes.d.ts +9 -6
  15. package/dist/api/routes.js +11 -11
  16. package/dist/build-root.d.ts +4 -0
  17. package/dist/build-root.js +11 -1
  18. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +35 -47
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +1 -1
  20. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +12 -5
  21. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +12 -5
  22. package/dist/cli.js +10 -1
  23. package/dist/clients/__tests__/helpers/human-daemon.d.ts +11 -0
  24. package/dist/clients/__tests__/helpers/human-daemon.js +60 -0
  25. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  26. package/dist/clients/attach/chrome/inbox-strip.d.ts +3 -3
  27. package/dist/clients/attach/chrome/inbox-strip.js +17 -9
  28. package/dist/clients/attach/chrome/ticket-panel.d.ts +2 -2
  29. package/dist/clients/attach/chrome/ticket-panel.js +77 -46
  30. package/dist/clients/attach/overlays/graph.js +6 -9
  31. package/dist/clients/attach/render/chat-view.d.ts +5 -4
  32. package/dist/clients/attach/render/chat-view.js +22 -13
  33. package/dist/clients/attach/render/page-block.d.ts +11 -7
  34. package/dist/clients/attach/render/page-block.js +64 -55
  35. package/dist/clients/attach/viewer.js +746 -735
  36. package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +56 -56
  37. package/dist/clients/inbox/__tests__/integration/stale-row-activation.test.js +45 -32
  38. package/dist/clients/inbox/controller.d.ts +42 -14
  39. package/dist/clients/inbox/controller.js +240 -124
  40. package/dist/clients/inbox/dto-adapt.d.ts +28 -0
  41. package/dist/clients/inbox/dto-adapt.js +93 -0
  42. package/dist/clients/inbox/page-adapter.d.ts +5 -4
  43. package/dist/clients/inbox/page-adapter.js +12 -12
  44. package/dist/clients/inbox/resolve.d.ts +10 -5
  45. package/dist/clients/inbox/resolve.js +13 -12
  46. package/dist/clients/inbox/review/review-client.d.ts +0 -1
  47. package/dist/clients/inbox/review/review-client.js +0 -4
  48. package/dist/clients/inbox/review-adapter.d.ts +2 -1
  49. package/dist/clients/inbox/review-adapter.js +3 -5
  50. package/dist/clients/inbox/surface.d.ts +2 -2
  51. package/dist/clients/inbox/surface.js +19 -8
  52. package/dist/clients/inbox/tui.d.ts +3 -3
  53. package/dist/commands/__tests__/human.test.js +31 -15
  54. package/dist/commands/__tests__/integration/dashboard-snapshot.test.js +36 -11
  55. package/dist/commands/{sys/__tests__ → __tests__/integration}/migrate.test.js +142 -36
  56. package/dist/commands/__tests__/revive-now-gate.test.js +2 -0
  57. package/dist/commands/api-client.d.ts +5 -4
  58. package/dist/commands/api-client.js +13 -7
  59. package/dist/commands/canvas-history/shared.js +1 -1
  60. package/dist/commands/canvas-history.js +1 -1
  61. package/dist/commands/canvas-roster.js +1 -1
  62. package/dist/commands/canvas.js +1 -2
  63. package/dist/commands/human/feedback.js +20 -17
  64. package/dist/commands/human/prompts.js +45 -36
  65. package/dist/commands/human/queue.js +73 -123
  66. package/dist/commands/human/review.js +6 -16
  67. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +2 -0
  68. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +2 -0
  69. package/dist/commands/memory/__tests__/repository-root-lint.test.js +2 -0
  70. package/dist/commands/node-inspect-artifacts.js +1 -1
  71. package/dist/commands/node-lifecycle-revive.d.ts +2 -2
  72. package/dist/commands/node-lifecycle-revive.js +9 -19
  73. package/dist/commands/pkg/plugin-manage.js +0 -1
  74. package/dist/commands/profile/show.js +4 -12
  75. package/dist/commands/surface-tmux-spread.js +1 -5
  76. package/dist/commands/sys/__tests__/config-model-ladders.test.js +23 -30
  77. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +37 -11
  78. package/dist/commands/sys/branch.d.ts +2 -0
  79. package/dist/commands/sys/branch.js +19 -0
  80. package/dist/commands/sys/config.js +106 -33
  81. package/dist/commands/sys/daemon.d.ts +2 -0
  82. package/dist/commands/sys/daemon.js +130 -7
  83. package/dist/commands/sys/doctor.js +5 -15
  84. package/dist/commands/sys/migrate.js +21 -92
  85. package/dist/commands/sys/setup-settings-runner.js +4 -2
  86. package/dist/commands/sys.js +2 -15
  87. package/dist/core/__tests__/boot-fault-attempt.test.js +23 -1
  88. package/dist/core/__tests__/boot.test.js +32 -25
  89. package/dist/core/__tests__/broker-launch-candidates.test.js +49 -0
  90. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +149 -176
  91. package/dist/core/__tests__/canvas-inbox-watcher-naming.test.js +59 -10
  92. package/dist/core/__tests__/canvas-inbox-watcher.test.js +76 -20
  93. package/dist/core/__tests__/canvas.test.js +18 -47
  94. package/dist/core/__tests__/child-death-wake.test.js +29 -7
  95. package/dist/core/__tests__/cli-help-contract.test.js +2 -0
  96. package/dist/core/__tests__/close.test.js +29 -10
  97. package/dist/core/__tests__/collectible.test.js +2 -1
  98. package/dist/core/__tests__/config-change-delta.test.js +2 -0
  99. package/dist/core/__tests__/context-intro.test.js +13 -1
  100. package/dist/core/__tests__/cron-armed-context-failure.test.js +17 -12
  101. package/dist/core/__tests__/cron-held-settlement.test.js +21 -3
  102. package/dist/core/__tests__/cron-node-sink-parked-root.test.js +29 -10
  103. package/dist/core/__tests__/cron-self-cancel-delivery.test.js +14 -2
  104. package/dist/core/__tests__/cron-timeout-descendants.test.js +13 -1
  105. package/dist/core/__tests__/daemon-boot.test.js +51 -14
  106. package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
  107. package/dist/core/__tests__/fixtures/fake-engine.js +8 -3
  108. package/dist/core/__tests__/focuses.test.js +10 -5
  109. package/dist/core/__tests__/helpers/harness.js +50 -18
  110. package/dist/core/__tests__/history-inbox.test.js +12 -16
  111. package/dist/core/__tests__/human-action-delivery.test.js +48 -28
  112. package/dist/core/__tests__/human-actions.test.js +7 -3
  113. package/dist/core/__tests__/inline-memory-refs.test.js +7 -2
  114. package/dist/core/__tests__/integration/broker-fork-seam.test.js +37 -10
  115. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +42 -0
  116. package/dist/core/__tests__/integration/broker-snapshot-history.test.js +42 -29
  117. package/dist/core/__tests__/integration/broker-wedge-supervision.test.js +36 -35
  118. package/dist/core/__tests__/integration/command-plugins.test.js +15 -6
  119. package/dist/core/__tests__/integration/deferred-no-wake.test.js +113 -92
  120. package/dist/core/__tests__/integration/host-teardown-process-group.test.js +9 -5
  121. package/dist/core/__tests__/integration/lifecycle-hooks.test.js +16 -3
  122. package/dist/core/__tests__/integration/plugin-revalidate.test.js +4 -0
  123. package/dist/core/__tests__/integration/profile-pause.test.js +4 -4
  124. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +9 -12
  125. package/dist/core/__tests__/integration/revive.test.js +67 -21
  126. package/dist/core/__tests__/integration/spawn-root.test.js +51 -8
  127. package/dist/core/__tests__/integration/subscription-delivery.test.js +33 -21
  128. package/dist/core/__tests__/integration/worktree-land.test.js +5 -2
  129. package/dist/core/__tests__/integration/worktree-reap.test.js +15 -1
  130. package/dist/core/__tests__/kickoff.test.js +36 -39
  131. package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +2 -1
  132. package/dist/core/__tests__/lifecycle.test.js +2 -1
  133. package/dist/core/__tests__/live-lifecycle-prompt.test.js +13 -1
  134. package/dist/core/__tests__/memory-resolver-precedence.test.js +2 -0
  135. package/dist/core/__tests__/migration.test.js +145 -31
  136. package/dist/core/__tests__/model-pin-durability.test.js +4 -1
  137. package/dist/core/__tests__/model-routes-config.test.js +13 -5
  138. package/dist/core/__tests__/node-outcome.test.js +93 -26
  139. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
  140. package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -1
  141. package/dist/core/__tests__/on-read-nested-store.test.js +2 -1
  142. package/dist/core/__tests__/passive-subscription.test.js +50 -157
  143. package/dist/core/__tests__/plugin-kinds.test.js +6 -4
  144. package/dist/core/__tests__/plugin-link-symlinked-scope-root.test.js +6 -1
  145. package/dist/core/__tests__/plugin-page-components.test.js +6 -4
  146. package/dist/core/__tests__/preview-registry-sync.test.js +2 -0
  147. package/dist/core/__tests__/profile-project-memory-delivery.test.js +4 -1
  148. package/dist/core/__tests__/prune.test.js +11 -5
  149. package/dist/core/__tests__/push-final-guard.test.js +14 -7
  150. package/dist/core/__tests__/relaunch-root.test.js +30 -2
  151. package/dist/core/__tests__/remote-canvas-target.test.js +7 -1
  152. package/dist/core/__tests__/repository-root-identity.test.js +4 -0
  153. package/dist/core/__tests__/review-model-floor.test.js +6 -3
  154. package/dist/core/__tests__/revive-all.test.js +13 -3
  155. package/dist/core/__tests__/revive-capacity.test.js +26 -4
  156. package/dist/core/__tests__/revive-parked-fresh.test.js +31 -8
  157. package/dist/core/__tests__/seam/broker-attach-stream.test.js +1 -1
  158. package/dist/core/__tests__/seam/broker-cap-freeze.test.js +4 -3
  159. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +11 -14
  160. package/dist/core/__tests__/seam/broker-provider-retry.test.js +85 -71
  161. package/dist/core/__tests__/seam/dormancy-release.test.js +33 -19
  162. package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +7 -5
  163. package/dist/core/__tests__/seam/inbox-reference-guidance.test.js +20 -6
  164. package/dist/core/__tests__/seam/node-outcome-delivery.test.js +27 -3
  165. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +2 -1
  166. package/dist/core/__tests__/stop-guard.test.js +2 -1
  167. package/dist/core/__tests__/subscription-pool-credential-guard.test.js +4 -3
  168. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +18 -4
  169. package/dist/core/__tests__/yield-ensures-daemon.test.js +2 -1
  170. package/dist/core/bootstrap.js +2 -0
  171. package/dist/core/broker-client/__tests__/transport-socket.test.js +51 -6
  172. package/dist/core/broker-client/transport-socket.d.ts +1 -0
  173. package/dist/core/broker-client/transport-socket.js +13 -5
  174. package/dist/core/canvas/__tests__/attention.test.js +29 -35
  175. package/dist/core/canvas/__tests__/history-transcript.test.js +6 -6
  176. package/dist/core/canvas/__tests__/render-remote.test.js +25 -9
  177. package/dist/core/canvas/attention.d.ts +2 -5
  178. package/dist/core/canvas/attention.js +15 -64
  179. package/dist/core/canvas/boot.d.ts +6 -7
  180. package/dist/core/canvas/boot.js +5 -9
  181. package/dist/core/canvas/canvas.d.ts +13 -109
  182. package/dist/core/canvas/canvas.js +140 -401
  183. package/dist/core/canvas/crons.d.ts +2 -0
  184. package/dist/core/canvas/crons.js +16 -15
  185. package/dist/core/canvas/daemon-owner.js +10 -0
  186. package/dist/core/canvas/db.d.ts +2 -2
  187. package/dist/core/canvas/db.js +2 -18
  188. package/dist/core/canvas/deliveries.d.ts +104 -0
  189. package/dist/core/canvas/deliveries.js +354 -0
  190. package/dist/core/canvas/executions.d.ts +29 -0
  191. package/dist/core/canvas/executions.js +68 -0
  192. package/dist/core/canvas/history.js +35 -35
  193. package/dist/core/canvas/index.d.ts +3 -1
  194. package/dist/core/canvas/index.js +3 -1
  195. package/dist/core/canvas/migrations.js +20 -12
  196. package/dist/core/canvas/nav-model.js +1 -3
  197. package/dist/core/canvas/nav-render.d.ts +4 -4
  198. package/dist/core/canvas/nav-render.js +11 -21
  199. package/dist/core/canvas/node-sql-migration.d.ts +3 -0
  200. package/dist/core/canvas/node-sql-migration.js +261 -0
  201. package/dist/core/canvas/paths.d.ts +2 -16
  202. package/dist/core/canvas/paths.js +2 -31
  203. package/dist/core/canvas/recovery.d.ts +76 -0
  204. package/dist/core/canvas/recovery.js +588 -0
  205. package/dist/core/canvas/remote-canvas-source.js +4 -0
  206. package/dist/core/canvas/render-source.d.ts +16 -27
  207. package/dist/core/canvas/render-source.js +16 -45
  208. package/dist/core/canvas/sessions.d.ts +33 -0
  209. package/dist/core/canvas/sessions.js +78 -0
  210. package/dist/core/canvas/types.d.ts +33 -33
  211. package/dist/core/canvas/types.js +2 -5
  212. package/dist/core/canvas/waits.d.ts +18 -0
  213. package/dist/core/canvas/waits.js +70 -0
  214. package/dist/core/canvas/worktrees.d.ts +40 -0
  215. package/dist/core/canvas/worktrees.js +93 -0
  216. package/dist/core/config.d.ts +17 -12
  217. package/dist/core/config.js +106 -155
  218. package/dist/core/feed/__tests__/inbox-migrate.test.js +27 -31
  219. package/dist/core/feed/feed.d.ts +0 -6
  220. package/dist/core/feed/feed.js +68 -56
  221. package/dist/core/feed/inbox.d.ts +7 -81
  222. package/dist/core/feed/inbox.js +46 -378
  223. package/dist/core/feed/messages.d.ts +58 -0
  224. package/dist/core/feed/messages.js +217 -0
  225. package/dist/core/fs-utils.d.ts +4 -0
  226. package/dist/core/fs-utils.js +21 -1
  227. package/dist/core/human/answer.d.ts +1 -1
  228. package/dist/core/human/answer.js +1 -1
  229. package/dist/core/human/claim.d.ts +1 -1
  230. package/dist/core/human/claim.js +2 -4
  231. package/dist/core/human/component-docs.js +1 -1
  232. package/dist/core/human/feedback-companion.js +3 -2
  233. package/dist/core/human/feedback.d.ts +5 -8
  234. package/dist/core/human/feedback.js +57 -85
  235. package/dist/core/human/page-authoring.d.ts +30 -0
  236. package/dist/core/human/page-authoring.js +19 -0
  237. package/dist/core/human/requests.d.ts +136 -0
  238. package/dist/core/human/requests.js +515 -0
  239. package/dist/core/human/root.d.ts +0 -7
  240. package/dist/core/human/root.js +0 -17
  241. package/dist/core/human/scan.d.ts +2 -7
  242. package/dist/core/human/scan.js +48 -132
  243. package/dist/core/human/summary.d.ts +1 -1
  244. package/dist/core/human/summary.js +1 -1
  245. package/dist/core/human/tickets.d.ts +23 -83
  246. package/dist/core/human/tickets.js +92 -243
  247. package/dist/core/human/types.d.ts +5 -1
  248. package/dist/core/installed-plugins.js +8 -3
  249. package/dist/core/keybindings/__tests__/resolve.test.js +19 -17
  250. package/dist/core/locked-file.d.ts +6 -3
  251. package/dist/core/locked-file.js +36 -9
  252. package/dist/core/memory/extensions.js +2 -0
  253. package/dist/core/preview-registry.js +0 -1
  254. package/dist/core/profiles/__tests__/env-store.test.js +2 -0
  255. package/dist/core/profiles/default-binding.d.ts +0 -4
  256. package/dist/core/profiles/default-binding.js +12 -58
  257. package/dist/core/profiles/env-store.js +8 -3
  258. package/dist/core/profiles/manifest.d.ts +2 -37
  259. package/dist/core/profiles/manifest.js +35 -161
  260. package/dist/core/profiles/select.js +3 -0
  261. package/dist/core/review/__tests__/capture-origin.test.js +13 -2
  262. package/dist/core/review/__tests__/comments.test.js +28 -10
  263. package/dist/core/review/__tests__/remap.test.js +28 -10
  264. package/dist/core/review/__tests__/stage-identity.test.js +17 -7
  265. package/dist/core/review/birth.d.ts +1 -1
  266. package/dist/core/review/birth.js +34 -5
  267. package/dist/core/review/comments.js +1 -1
  268. package/dist/core/review/document.js +1 -1
  269. package/dist/core/review/project.d.ts +5 -1
  270. package/dist/core/review/project.js +14 -36
  271. package/dist/core/review/realize.d.ts +3 -6
  272. package/dist/core/review/realize.js +51 -168
  273. package/dist/core/review/remap.js +1 -1
  274. package/dist/core/review/stage.d.ts +3 -2
  275. package/dist/core/review/stage.js +2 -3
  276. package/dist/core/review/store.d.ts +3 -21
  277. package/dist/core/review/store.js +140 -186
  278. package/dist/core/review/types.d.ts +0 -1
  279. package/dist/core/runtime/__tests__/install-id-cache-scoping.test.js +9 -5
  280. package/dist/core/runtime/__tests__/spawn-env.test.js +8 -9
  281. package/dist/core/runtime/broker/auth-reload.js +9 -10
  282. package/dist/core/runtime/broker/daemon-ops.d.ts +25 -8
  283. package/dist/core/runtime/broker/daemon-ops.js +42 -9
  284. package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
  285. package/dist/core/runtime/broker/extension-abort.js +11 -0
  286. package/dist/core/runtime/broker/fault-retry.d.ts +15 -6
  287. package/dist/core/runtime/broker/fault-retry.js +142 -95
  288. package/dist/core/runtime/broker/inbox.d.ts +4 -32
  289. package/dist/core/runtime/broker/inbox.js +4 -92
  290. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  291. package/dist/core/runtime/broker/rebind.js +21 -12
  292. package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
  293. package/dist/core/runtime/broker-persona-guidance.js +2 -13
  294. package/dist/core/runtime/broker.js +28 -22
  295. package/dist/core/runtime/close.d.ts +3 -9
  296. package/dist/core/runtime/close.js +16 -27
  297. package/dist/core/runtime/fault.d.ts +3 -93
  298. package/dist/core/runtime/fault.js +9 -419
  299. package/dist/core/runtime/kickoff.d.ts +3 -6
  300. package/dist/core/runtime/kickoff.js +12 -18
  301. package/dist/core/runtime/launch.js +1 -1
  302. package/dist/core/runtime/lifecycle.js +40 -7
  303. package/dist/core/runtime/placement.js +2 -2
  304. package/dist/core/runtime/recycle.js +8 -2
  305. package/dist/core/runtime/reset.js +22 -6
  306. package/dist/core/runtime/revive.js +226 -218
  307. package/dist/core/runtime/spawn.js +14 -3
  308. package/dist/core/scoped-state/db.d.ts +7 -0
  309. package/dist/core/scoped-state/db.js +92 -0
  310. package/dist/core/scoped-state/migrate.d.ts +11 -0
  311. package/dist/core/scoped-state/migrate.js +198 -0
  312. package/dist/core/scoped-state/paths.d.ts +9 -0
  313. package/dist/core/scoped-state/paths.js +27 -0
  314. package/dist/core/scoped-state/profiles.d.ts +12 -0
  315. package/dist/core/scoped-state/profiles.js +62 -0
  316. package/dist/core/scoped-state/providers.d.ts +16 -0
  317. package/dist/core/scoped-state/providers.js +8 -0
  318. package/dist/core/scoped-state/schema.d.ts +3 -0
  319. package/dist/core/scoped-state/schema.js +26 -0
  320. package/dist/core/scoped-state/settings.d.ts +28 -0
  321. package/dist/core/scoped-state/settings.js +82 -0
  322. package/dist/core/subscription-state.d.ts +29 -48
  323. package/dist/core/subscription-state.js +176 -712
  324. package/dist/core/worktree-close.js +4 -4
  325. package/dist/core/worktree-mutation-async.js +2 -2
  326. package/dist/core/worktree-sweep.js +11 -16
  327. package/dist/core/worktree.js +6 -12
  328. package/dist/daemon/__tests__/autostart-storm.test.js +1 -39
  329. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +2 -2
  330. package/dist/daemon/__tests__/helpers/source-daemon.js +7 -6
  331. package/dist/daemon/__tests__/integration/migration-startup.test.js +59 -26
  332. package/dist/daemon/__tests__/node-deadline-lane.test.js +2 -1
  333. package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +39 -15
  334. package/dist/daemon/__tests__/node-outcome-delivery.test.js +39 -10
  335. package/dist/daemon/__tests__/reconciler-signature.test.js +2 -2
  336. package/dist/daemon/__tests__/subtree-filter-freshness.test.js +2 -2
  337. package/dist/daemon/api/__tests__/broker-settle-park.test.js +17 -12
  338. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +27 -5
  339. package/dist/daemon/api/__tests__/model-config.test.js +8 -8
  340. package/dist/daemon/api/__tests__/node-create-description.test.js +22 -5
  341. package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -1
  342. package/dist/daemon/api/__tests__/nodes-activity-query.test.js +10 -12
  343. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +4 -1
  344. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +4 -1
  345. package/dist/daemon/api/__tests__/reopen-delivery.test.js +26 -8
  346. package/dist/daemon/api/__tests__/seam/api-server.test.js +24 -32
  347. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +4 -1
  348. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +5 -4
  349. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +27 -41
  350. package/dist/daemon/api/__tests__/submit-result.test.js +3 -2
  351. package/dist/daemon/api/bridge.js +1 -1
  352. package/dist/daemon/api/handlers/bash-jobs.js +6 -7
  353. package/dist/daemon/api/handlers/broker-ops.js +47 -84
  354. package/dist/daemon/api/handlers/broker-recovery.d.ts +4 -0
  355. package/dist/daemon/api/handlers/broker-recovery.js +251 -0
  356. package/dist/daemon/api/handlers/canvas.js +2 -44
  357. package/dist/daemon/api/handlers/daemon.js +41 -0
  358. package/dist/daemon/api/handlers/feedback-comments.js +2 -2
  359. package/dist/daemon/api/handlers/health.js +10 -5
  360. package/dist/daemon/api/handlers/human-requests.js +168 -188
  361. package/dist/daemon/api/handlers/human.js +3 -151
  362. package/dist/daemon/api/handlers/inbox-page.d.ts +4 -0
  363. package/dist/daemon/api/handlers/inbox-page.js +89 -0
  364. package/dist/daemon/api/handlers/inbox.js +21 -120
  365. package/dist/daemon/api/handlers/messages.d.ts +1 -0
  366. package/dist/daemon/api/handlers/messages.js +73 -15
  367. package/dist/daemon/api/handlers/node-outcomes.js +58 -13
  368. package/dist/daemon/api/handlers/nodes.js +7 -13
  369. package/dist/daemon/api/handlers/reviews.js +4 -29
  370. package/dist/daemon/api/map.d.ts +25 -3
  371. package/dist/daemon/api/map.js +84 -14
  372. package/dist/daemon/api/server.js +68 -9
  373. package/dist/daemon/control.d.ts +18 -0
  374. package/dist/daemon/cron/disposition.d.ts +2 -0
  375. package/dist/daemon/cron/disposition.js +8 -5
  376. package/dist/daemon/cron/settle.js +37 -13
  377. package/dist/daemon/cron/sinks.d.ts +18 -1
  378. package/dist/daemon/cron/sinks.js +47 -93
  379. package/dist/daemon/crtrd-cli.js +4 -19
  380. package/dist/daemon/crtrd.d.ts +2 -0
  381. package/dist/daemon/crtrd.js +206 -81
  382. package/dist/daemon/fleet.js +5 -6
  383. package/dist/daemon/human/finish.d.ts +12 -24
  384. package/dist/daemon/human/finish.js +88 -285
  385. package/dist/daemon/human/settle.d.ts +40 -0
  386. package/dist/daemon/human/settle.js +89 -0
  387. package/dist/daemon/human/sweep.d.ts +20 -5
  388. package/dist/daemon/human/sweep.js +56 -60
  389. package/dist/daemon/manage.d.ts +21 -16
  390. package/dist/daemon/manage.js +85 -51
  391. package/dist/daemon/messaging/node-message.d.ts +1 -1
  392. package/dist/daemon/messaging/node-message.js +9 -12
  393. package/dist/daemon/park-activity.js +3 -3
  394. package/dist/daemon/reconcilers/bash-deadline.js +6 -7
  395. package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
  396. package/dist/daemon/reconcilers/broker-supervision.js +41 -19
  397. package/dist/daemon/reconcilers/controller-death.js +6 -18
  398. package/dist/daemon/reconcilers/delivery-lane.d.ts +9 -0
  399. package/dist/daemon/reconcilers/delivery-lane.js +130 -0
  400. package/dist/daemon/reconcilers/live-obligation.js +3 -3
  401. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  402. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -1
  403. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +10 -12
  404. package/dist/daemon/reconcilers/node-lifecycle/tick.js +5 -5
  405. package/dist/daemon/reconcilers/node-lifecycle/wants-execution.js +1 -1
  406. package/dist/daemon/reconcilers/pending-review-submit.js +3 -3
  407. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -8
  408. package/dist/daemon/reconcilers/storage-maintenance.js +13 -43
  409. package/dist/daemon/review/companion.js +5 -11
  410. package/dist/daemon/review/deliver.d.ts +1 -5
  411. package/dist/daemon/review/deliver.js +3 -52
  412. package/dist/daemon/review/finish.d.ts +2 -27
  413. package/dist/daemon/review/finish.js +79 -263
  414. package/dist/daemon/review/sweep.d.ts +3 -10
  415. package/dist/daemon/review/sweep.js +13 -131
  416. package/dist/daemon/startup-policy.d.ts +0 -5
  417. package/dist/daemon/startup-policy.js +0 -5
  418. package/dist/index.d.ts +2 -1
  419. package/dist/index.js +1 -1
  420. package/dist/migrations/__tests__/activation-concurrency.test.js +3 -2
  421. package/dist/migrations/__tests__/activation.test.js +9 -6
  422. package/dist/migrations/activation.d.ts +3 -2
  423. package/dist/migrations/activation.js +2 -4
  424. package/dist/migrations/corpus.d.ts +2 -5
  425. package/dist/migrations/corpus.js +9 -20
  426. package/dist/migrations/runner.d.ts +3 -1
  427. package/dist/migrations/runner.js +6 -2
  428. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +21 -89
  429. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +4 -2
  430. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +51 -33
  431. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +16 -2
  432. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +13 -1
  433. package/dist/pi-extensions/canvas-inbox-watcher.js +98 -244
  434. package/dist/pi-extensions/canvas-passive-context.d.ts +4 -8
  435. package/dist/pi-extensions/canvas-passive-context.js +85 -37
  436. package/dist/pi-extensions/canvas-stophook.js +47 -31
  437. package/dist/shared/env.d.ts +1 -0
  438. package/dist/shared/env.js +3 -0
  439. package/package.json +1 -1
  440. package/runtime.lock.json +5 -5
  441. package/dist/clients/attach/__tests__/page-block.test.js +0 -54
  442. package/dist/clients/inbox/review-visibility.d.ts +0 -5
  443. package/dist/clients/inbox/review-visibility.js +0 -19
  444. package/dist/commands/canvas-rebuild-index.d.ts +0 -2
  445. package/dist/commands/canvas-rebuild-index.js +0 -46
  446. package/dist/core/__tests__/human-cancel-guard.test.js +0 -110
  447. package/dist/core/__tests__/human-deliver.test.d.ts +0 -1
  448. package/dist/core/__tests__/human-deliver.test.js +0 -256
  449. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +0 -1
  450. package/dist/core/__tests__/human-node-not-supervised.test.js +0 -89
  451. package/dist/core/__tests__/integration/human-deliver-e2e.test.d.ts +0 -1
  452. package/dist/core/__tests__/integration/human-deliver-e2e.test.js +0 -131
  453. package/dist/core/__tests__/phase4-review-store.test.d.ts +0 -1
  454. package/dist/core/__tests__/phase4-review-store.test.js +0 -144
  455. package/dist/core/__tests__/watchdog-abort-arms-retry.test.d.ts +0 -1
  456. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +0 -110
  457. package/dist/core/canvas/human-deliveries.d.ts +0 -39
  458. package/dist/core/canvas/human-deliveries.js +0 -75
  459. package/dist/core/canvas/node-outcome-deliveries.d.ts +0 -64
  460. package/dist/core/canvas/node-outcome-deliveries.js +0 -159
  461. package/dist/core/feed/passive.d.ts +0 -3
  462. package/dist/core/feed/passive.js +0 -53
  463. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +0 -1
  464. package/dist/core/human/__tests__/inbox-cancel-worker.js +0 -7
  465. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +0 -1
  466. package/dist/core/human/__tests__/inbox-claim-worker.js +0 -17
  467. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +0 -1
  468. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +0 -8
  469. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +0 -1
  470. package/dist/core/human/__tests__/integration/inbox-core.test.js +0 -145
  471. package/dist/core/human/__tests__/page-tickets.test.d.ts +0 -1
  472. package/dist/core/human/__tests__/page-tickets.test.js +0 -161
  473. package/dist/core/review/ticket-filter.d.ts +0 -17
  474. package/dist/core/review/ticket-filter.js +0 -21
  475. package/dist/core/runtime/broker/passive.d.ts +0 -17
  476. package/dist/core/runtime/broker/passive.js +0 -44
  477. package/dist/core/runtime/busy.d.ts +0 -46
  478. package/dist/core/runtime/busy.js +0 -150
  479. package/dist/daemon/__tests__/startup-block-marker.test.d.ts +0 -1
  480. package/dist/daemon/__tests__/startup-block-marker.test.js +0 -86
  481. package/dist/daemon/api/__tests__/review-terminal-conflict.test.d.ts +0 -1
  482. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +0 -98
  483. package/dist/daemon/human/deliver-action.d.ts +0 -3
  484. package/dist/daemon/human/deliver-action.js +0 -9
  485. package/dist/daemon/node-outcome/deliver-outcome.d.ts +0 -3
  486. package/dist/daemon/node-outcome/deliver-outcome.js +0 -9
  487. package/dist/daemon/reconcilers/human-delivery-lane.d.ts +0 -10
  488. package/dist/daemon/reconcilers/human-delivery-lane.js +0 -41
  489. package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +0 -10
  490. package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +0 -41
  491. package/dist/daemon/startup-block-marker.d.ts +0 -25
  492. package/dist/daemon/startup-block-marker.js +0 -99
  493. /package/dist/{clients/attach/__tests__/page-block.test.d.ts → api/dto/mail.js} +0 -0
  494. /package/dist/{commands/sys/__tests__/migrate.test.d.ts → api/dto/recovery.js} +0 -0
  495. /package/dist/{core/__tests__/human-cancel-guard.test.d.ts → commands/__tests__/integration/migrate.test.d.ts} +0 -0
@@ -0,0 +1,7 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import type { ScopedStore } from './paths.js';
3
+ export declare function scopedStoreExists(store: ScopedStore): boolean;
4
+ export declare function openScopedStore(store: ScopedStore): DatabaseSync;
5
+ export declare function createScopedStore(store: ScopedStore, create: (db: DatabaseSync) => void): void;
6
+ export declare function withScopedWrite<T>(store: ScopedStore, run: (db: DatabaseSync) => T): T;
7
+ export declare function closeScopedStores(): void;
@@ -0,0 +1,92 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { chmodSync, existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
3
+ import { SCOPED_SCHEMA_VERSION } from './schema.js';
4
+ const handles = new Map();
5
+ function version(db) {
6
+ return Number(db.prepare('PRAGMA user_version').get().user_version);
7
+ }
8
+ function secure(path) {
9
+ if (existsSync(path))
10
+ chmodSync(path, 0o600);
11
+ for (const suffix of ['-wal', '-shm'])
12
+ if (existsSync(`${path}${suffix}`))
13
+ chmodSync(`${path}${suffix}`, 0o600);
14
+ }
15
+ export function scopedStoreExists(store) {
16
+ return existsSync(store.path);
17
+ }
18
+ export function openScopedStore(store) {
19
+ const cached = handles.get(store.path);
20
+ if (cached !== undefined)
21
+ return cached;
22
+ if (!existsSync(store.path))
23
+ throw new Error(`scoped SQL store is not initialized: ${store.path}; crtr was updated and \`crtr sys daemon restart\` converts local state`);
24
+ const db = new DatabaseSync(store.path);
25
+ db.exec('PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;');
26
+ const found = version(db);
27
+ if (found === 0) {
28
+ db.close();
29
+ throw new Error(`scoped SQL store is uninitialized: ${store.path}; crtr was updated and \`crtr sys daemon restart\` converts local state`);
30
+ }
31
+ if (found > SCOPED_SCHEMA_VERSION) {
32
+ db.close();
33
+ throw new Error(`scoped SQL store ${store.path} uses unsupported schema version ${found}`);
34
+ }
35
+ if (found !== SCOPED_SCHEMA_VERSION) {
36
+ db.close();
37
+ throw new Error(`scoped SQL store ${store.path} requires an explicit conversion`);
38
+ }
39
+ secure(store.path);
40
+ handles.set(store.path, db);
41
+ return db;
42
+ }
43
+ export function createScopedStore(store, create) {
44
+ if (existsSync(store.path))
45
+ throw new Error(`scoped SQL store already exists: ${store.path}`);
46
+ mkdirSync(store.root, { recursive: true, mode: 0o700 });
47
+ if (!statSync(store.root).isDirectory())
48
+ throw new Error(`scoped SQL root is not a directory: ${store.root}`);
49
+ chmodSync(store.root, 0o700);
50
+ writeFileSync(store.path, '', { mode: 0o600 });
51
+ const db = new DatabaseSync(store.path);
52
+ try {
53
+ db.exec('PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; BEGIN IMMEDIATE;');
54
+ create(db);
55
+ db.exec('COMMIT');
56
+ }
57
+ catch (error) {
58
+ try {
59
+ db.exec('ROLLBACK');
60
+ }
61
+ catch { }
62
+ throw error;
63
+ }
64
+ finally {
65
+ secure(store.path);
66
+ db.close();
67
+ }
68
+ }
69
+ export function withScopedWrite(store, run) {
70
+ const db = openScopedStore(store);
71
+ if (db.isTransaction)
72
+ return run(db);
73
+ db.exec('BEGIN IMMEDIATE');
74
+ try {
75
+ const value = run(db);
76
+ db.exec('COMMIT');
77
+ secure(store.path);
78
+ return value;
79
+ }
80
+ catch (error) {
81
+ try {
82
+ db.exec('ROLLBACK');
83
+ }
84
+ catch { }
85
+ throw error;
86
+ }
87
+ }
88
+ export function closeScopedStores() {
89
+ for (const db of handles.values())
90
+ db.close();
91
+ handles.clear();
92
+ }
@@ -0,0 +1,11 @@
1
+ export declare function initializeFreshScopedStore(input: {
2
+ kind: 'user' | 'project' | 'provider';
3
+ root: string;
4
+ }): void;
5
+ /** Conversion is intentionally explicit. Deployment coordination must prove all legacy writers at these shared roots are stopped before calling it. */
6
+ export declare function convertLegacyScopedStore(input: {
7
+ kind: 'user' | 'project' | 'provider';
8
+ root: string;
9
+ }): void;
10
+ /** Converts legacy provider pools only after the activation owner has stopped every writer sharing this agent directory. */
11
+ export declare function convertLegacyProviderStore(agentDir: string): Promise<void>;
@@ -0,0 +1,198 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { dirname, join, relative } from 'node:path';
4
+ import lockfile from 'proper-lockfile';
5
+ import { CONFIG_FILE, PROFILE_DIR, STATE_FILE } from '../../types.js';
6
+ import { LockedJsonFile } from '../locked-file.js';
7
+ import { createScopedStore, scopedStoreExists } from './db.js';
8
+ import { providerStore, projectStore, userStore } from './paths.js';
9
+ import { createScopedSchema } from './schema.js';
10
+ function writeOwnerRoot(db, key, root) { db.prepare('UPDATE scope_owners SET root=? WHERE owner_key=?').run(root, key); }
11
+ function json(path) { return JSON.parse(readFileSync(path, 'utf8')); }
12
+ function existsLegacy(paths) { return paths.some(existsSync); }
13
+ function preserve(root, paths) { const target = join(root, '.legacy-scoped-state'); mkdirSync(target, { recursive: true, mode: 0o700 }); chmodSync(target, 0o700); for (const path of paths)
14
+ if (existsSync(path) && statSync(path).isFile()) {
15
+ const fromRoot = relative(root, path);
16
+ if (fromRoot === '' || fromRoot.startsWith('..'))
17
+ throw new Error(`legacy state is outside its owning root: ${path}`);
18
+ const destination = join(target, fromRoot);
19
+ mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
20
+ copyFileSync(path, destination);
21
+ chmodSync(destination, 0o600);
22
+ } }
23
+ const PROFILE_ID_SHAPE = /^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/;
24
+ function userLegacyPaths(root) { const out = [join(root, CONFIG_FILE), join(root, STATE_FILE), join(root, 'profile-defaults.json')]; const profiles = join(root, PROFILE_DIR); if (existsSync(profiles))
25
+ for (const entry of readdirSync(profiles, { withFileTypes: true }))
26
+ if (entry.isDirectory() && PROFILE_ID_SHAPE.test(entry.name))
27
+ out.push(join(profiles, entry.name, 'profile.json'), join(profiles, entry.name, CONFIG_FILE), join(profiles, entry.name, STATE_FILE)); return out; }
28
+ function providerLegacyPaths(agentDir) { return [join(agentDir, 'anthropic-subscriptions.json'), join(agentDir, 'openai-codex-subscriptions.json'), join(agentDir, 'provider-rotation.json')]; }
29
+ function withLegacyProjectStateLock(root, run) {
30
+ const path = join(root, STATE_FILE);
31
+ const release = lockfile.lockSync(path, { realpath: false, retries: 0, stale: 30_000, update: 10_000 });
32
+ try {
33
+ return run();
34
+ }
35
+ finally {
36
+ release();
37
+ }
38
+ }
39
+ export function initializeFreshScopedStore(input) {
40
+ const store = input.kind === 'user' ? userStore(input.root) : input.kind === 'project' ? projectStore(input.root) : providerStore(input.root);
41
+ if (scopedStoreExists(store))
42
+ return;
43
+ const legacy = input.kind === 'user' ? userLegacyPaths(store.root) : input.kind === 'project' ? [join(store.root, STATE_FILE)] : providerLegacyPaths(store.root);
44
+ const legacyPath = legacy.find(existsSync);
45
+ if (legacyPath !== undefined)
46
+ throw new Error(`legacy scoped state exists at ${legacyPath}; crtr was updated and \`crtr sys daemon restart\` converts local state`);
47
+ createScopedStore(store, (db) => { createScopedSchema(db, input.kind); if (input.kind !== 'provider')
48
+ writeOwnerRoot(db, input.kind === 'user' ? 'user' : 'project', store.root); });
49
+ }
50
+ /** Conversion is intentionally explicit. Deployment coordination must prove all legacy writers at these shared roots are stopped before calling it. */
51
+ export function convertLegacyScopedStore(input) {
52
+ const store = input.kind === 'user' ? userStore(input.root) : input.kind === 'project' ? projectStore(input.root) : providerStore(input.root);
53
+ if (scopedStoreExists(store))
54
+ return;
55
+ if (input.kind === 'provider')
56
+ throw new Error('provider conversion requires convertLegacyProviderStore() under coordinated shared-agent-dir activation');
57
+ const paths = input.kind === 'user' ? userLegacyPaths(store.root) : [join(store.root, STATE_FILE)];
58
+ const convert = () => {
59
+ if (scopedStoreExists(store))
60
+ return;
61
+ createScopedStore(store, (db) => {
62
+ createScopedSchema(db, input.kind);
63
+ const owner = input.kind === 'user' ? 'user' : 'project';
64
+ writeOwnerRoot(db, owner, store.root);
65
+ if (input.kind === 'user')
66
+ importUser(db, store.root);
67
+ else
68
+ importState(db, owner, join(store.root, STATE_FILE));
69
+ });
70
+ if (existsLegacy(paths))
71
+ preserve(store.root, paths);
72
+ };
73
+ if (input.kind === 'project')
74
+ withLegacyProjectStateLock(store.root, convert);
75
+ else
76
+ convert();
77
+ }
78
+ function parseLegacyProviderPool(path) {
79
+ if (!existsSync(path))
80
+ return [];
81
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
82
+ if (!Array.isArray(raw))
83
+ throw new Error(`legacy subscription pool is malformed: ${path}`);
84
+ const labels = new Set();
85
+ return raw.map((item, position) => {
86
+ if (item === null || typeof item !== 'object' || Array.isArray(item))
87
+ throw new Error(`legacy subscription pool entry ${position + 1} is malformed: ${path}`);
88
+ const value = item;
89
+ const label = typeof value.label === 'string' ? value.label.trim() : '';
90
+ const refresh = typeof value.refresh === 'string' ? value.refresh.trim() : '';
91
+ const access = typeof value.access === 'string' ? value.access.trim() : '';
92
+ const expires = Number(value.expires);
93
+ if (!label || !refresh || !access || !Number.isFinite(expires))
94
+ throw new Error(`legacy subscription pool entry ${position + 1} is incomplete: ${path}`);
95
+ const labelKey = label.toLowerCase();
96
+ if (labels.has(labelKey))
97
+ throw new Error(`legacy subscription pool has duplicate label "${label}": ${path}`);
98
+ labels.add(labelKey);
99
+ const timing = (field) => Number.isFinite(Number(value[field])) ? Number(value[field]) : 0;
100
+ const accountId = typeof value.accountId === 'string' && value.accountId.trim() !== '' ? value.accountId.trim() : undefined;
101
+ return { label, refresh, access, expires, rateLimitedUntil: timing('rateLimitedUntil'), lastAttemptAt: timing('lastAttemptAt'), lastRateLimitedAt: timing('lastRateLimitedAt'), ...(value.authFailure === 'invalid_grant' ? { authFailure: 'invalid_grant' } : {}), ...(accountId ? { accountId } : {}) };
102
+ });
103
+ }
104
+ function parseLegacyRotation(path) {
105
+ if (!existsSync(path))
106
+ return {};
107
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
108
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
109
+ throw new Error(`legacy provider rotation settings are malformed: ${path}`);
110
+ const value = raw;
111
+ return { ...(value.defaultFallbackStrength === 'ultra' || value.defaultFallbackStrength === 'strong' || value.defaultFallbackStrength === 'medium' || value.defaultFallbackStrength === 'light' ? { defaultFallbackStrength: value.defaultFallbackStrength } : {}), ...(typeof value.revertWhenAvailable === 'boolean' ? { revertWhenAvailable: value.revertWhenAvailable } : {}) };
112
+ }
113
+ async function withLegacyProviderLocks(paths, run) {
114
+ const releases = [];
115
+ try {
116
+ for (const path of paths)
117
+ if (existsSync(path))
118
+ releases.push(await lockfile.lock(path, { realpath: false, retries: 0, stale: 30_000, update: 10_000 }));
119
+ return await run();
120
+ }
121
+ finally {
122
+ await Promise.allSettled(releases.reverse().map((release) => release()));
123
+ }
124
+ }
125
+ /** Converts legacy provider pools only after the activation owner has stopped every writer sharing this agent directory. */
126
+ export async function convertLegacyProviderStore(agentDir) {
127
+ const store = providerStore(agentDir);
128
+ if (scopedStoreExists(store))
129
+ return;
130
+ const paths = providerLegacyPaths(store.root);
131
+ await withLegacyProviderLocks(paths, async () => {
132
+ if (scopedStoreExists(store))
133
+ return;
134
+ const pools = new Map(paths.slice(0, 2).map((path, index) => [index === 0 ? 'anthropic' : 'openai-codex', parseLegacyProviderPool(path)]));
135
+ const rotation = parseLegacyRotation(paths[2]);
136
+ preserve(store.root, paths);
137
+ for (const [provider, pool] of pools) {
138
+ const accounts = Object.fromEntries(pool.map((entry) => { const id = randomUUID(); return [id, { refresh: entry.refresh, access: entry.access, expires: entry.expires, ...(entry.accountId ? { accountId: entry.accountId } : {}) }]; }));
139
+ const file = new LockedJsonFile(join(store.root, `${provider}-credentials.json`), true, '{"version":1,"accounts":{}}\n');
140
+ await file.withLockAsync(async (_current, _held, publish) => { publish(`${JSON.stringify({ version: 1, accounts })}\n`); return { result: undefined }; });
141
+ }
142
+ createScopedStore(store, (db) => {
143
+ createScopedSchema(db, 'provider');
144
+ for (const [provider, pool] of pools) {
145
+ const accounts = JSON.parse(readFileSync(join(store.root, `${provider}-credentials.json`), 'utf8'));
146
+ const ids = Object.keys(accounts.accounts);
147
+ const insert = db.prepare('INSERT INTO managed_accounts(provider_id,id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure) VALUES(?,?,?,?,?,?,?,?,?)');
148
+ pool.forEach((entry, position) => insert.run(provider, ids[position], entry.label, entry.label.toLowerCase(), position, entry.lastAttemptAt, entry.lastRateLimitedAt, entry.rateLimitedUntil, entry.authFailure ?? null));
149
+ }
150
+ if (rotation.defaultFallbackStrength !== undefined || rotation.revertWhenAvailable !== undefined)
151
+ db.prepare('INSERT INTO rotation_settings(singleton,default_fallback_strength,revert_when_available) VALUES(1,?,?)').run(rotation.defaultFallbackStrength ?? null, rotation.revertWhenAvailable === undefined ? null : rotation.revertWhenAvailable ? 1 : 0);
152
+ });
153
+ });
154
+ }
155
+ function importSettings(db, owner, path) { if (!existsSync(path))
156
+ return; const raw = json(path); if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
157
+ throw new Error(`legacy config is malformed: ${path}`); db.prepare('INSERT INTO scope_settings(owner_key,raw_json) VALUES(?,?)').run(owner, JSON.stringify(raw)); }
158
+ function importState(db, owner, path) { if (!existsSync(path))
159
+ return; const raw = json(path); if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
160
+ throw new Error(`legacy state is malformed: ${path}`); const state = raw; db.prepare('INSERT INTO scope_bookkeeping(owner_key,last_self_check,active_canvas) VALUES(?,?,?)').run(owner, state.last_self_check ?? null, state.activeCanvas ?? null); const market = db.prepare('INSERT INTO scope_marketplaces(owner_key,name,last_updated) VALUES(?,?,?)'); for (const [name, value] of Object.entries(state.marketplaces ?? {}))
161
+ market.run(owner, name, value.last_updated ?? null); const plugins = db.prepare('INSERT INTO scope_plugins(owner_key,name,last_updated,etag,checked_at) VALUES(?,?,?,?,?)'); for (const [name, value] of Object.entries(state.plugins ?? {}))
162
+ plugins.run(owner, name, value.last_updated ?? null, value.etag ?? null, value.checked_at ?? null); }
163
+ function importUser(db, root) {
164
+ importSettings(db, 'user', join(root, CONFIG_FILE));
165
+ importState(db, 'user', join(root, STATE_FILE));
166
+ const profiles = join(root, PROFILE_DIR);
167
+ if (existsSync(profiles))
168
+ for (const entry of readdirSync(profiles, { withFileTypes: true })) {
169
+ if (!entry.isDirectory() || !PROFILE_ID_SHAPE.test(entry.name))
170
+ continue;
171
+ const id = entry.name;
172
+ const profileRoot = join(profiles, id);
173
+ const path = join(profileRoot, 'profile.json');
174
+ if (!existsSync(path))
175
+ throw new Error(`legacy profile manifest is missing: ${path}`);
176
+ const manifest = json(path);
177
+ if (!manifest || typeof manifest !== 'object' || !Array.isArray(manifest.projects) || typeof manifest.name !== 'string' || typeof manifest.created_at !== 'string')
178
+ throw new Error(`legacy profile manifest is malformed: ${path}`);
179
+ db.prepare('INSERT INTO profiles(id,name,created_at,last_used_at,paused_at,home,default_kind,metadata_json) VALUES(?,?,?,?,?,?,?,?)').run(id, manifest.name, manifest.created_at, manifest.last_used_at ?? null, manifest.paused_at ?? null, manifest.home ?? null, manifest.default_kind ?? null, JSON.stringify(manifest.metadata ?? {}));
180
+ db.prepare('INSERT INTO scope_owners(owner_key,kind,root,profile_id) VALUES(?,?,?,?)').run(`profile:${id}`, 'profile', profileRoot, id);
181
+ const project = db.prepare('INSERT INTO profile_projects(profile_id,path,position,memory) VALUES(?,?,?,?)');
182
+ manifest.projects.forEach((p, position) => project.run(id, p.path, position, p.memory));
183
+ importSettings(db, `profile:${id}`, join(profileRoot, CONFIG_FILE));
184
+ importState(db, `profile:${id}`, join(profileRoot, STATE_FILE));
185
+ }
186
+ const defaults = join(root, 'profile-defaults.json');
187
+ if (existsSync(defaults)) {
188
+ const raw = json(defaults);
189
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
190
+ throw new Error(`legacy profile defaults are malformed: ${defaults}`);
191
+ const insert = db.prepare('INSERT INTO profile_defaults(directory,profile_id) VALUES(?,?)');
192
+ for (const [directory, id] of Object.entries(raw)) {
193
+ if (typeof id !== 'string')
194
+ throw new Error(`legacy profile default is malformed: ${defaults}`);
195
+ insert.run(directory, id);
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,9 @@
1
+ export type ScopedStore = {
2
+ path: string;
3
+ root: string;
4
+ kind: 'user' | 'project' | 'provider';
5
+ };
6
+ export declare function canonicalRoot(root: string): string;
7
+ export declare function userStore(root: string): ScopedStore;
8
+ export declare function projectStore(root: string): ScopedStore;
9
+ export declare function providerStore(agentDir: string): ScopedStore;
@@ -0,0 +1,27 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import { basename, dirname, join, resolve } from 'node:path';
3
+ export function canonicalRoot(root) {
4
+ const absolute = resolve(root);
5
+ let existing = absolute;
6
+ const missing = [];
7
+ while (!existsSync(existing)) {
8
+ const parent = dirname(existing);
9
+ if (parent === existing)
10
+ return absolute;
11
+ missing.unshift(basename(existing));
12
+ existing = parent;
13
+ }
14
+ return resolve(realpathSync(existing), ...missing);
15
+ }
16
+ export function userStore(root) {
17
+ const canonical = canonicalRoot(root);
18
+ return { path: join(canonical, 'operations.db'), root: canonical, kind: 'user' };
19
+ }
20
+ export function projectStore(root) {
21
+ const canonical = canonicalRoot(root);
22
+ return { path: join(canonical, 'operations.db'), root: canonical, kind: 'project' };
23
+ }
24
+ export function providerStore(agentDir) {
25
+ const canonical = canonicalRoot(agentDir);
26
+ return { path: join(canonical, 'crouter-operations.db'), root: canonical, kind: 'provider' };
27
+ }
@@ -0,0 +1,12 @@
1
+ import type { ProfileManifest } from '../../types.js';
2
+ export type StoredProfile = {
3
+ profileId: string;
4
+ manifest: ProfileManifest;
5
+ };
6
+ export declare function listStoredProfiles(root: string): StoredProfile[];
7
+ export declare function getStoredProfile(root: string, id: string): StoredProfile | null;
8
+ export declare function insertStoredProfile(root: string, profile: StoredProfile, profileRoot: string): void;
9
+ export declare function updateStoredProfile(root: string, id: string, mutate: (current: ProfileManifest) => ProfileManifest): StoredProfile | null;
10
+ export declare function deleteStoredProfile(root: string, id: string): boolean;
11
+ export declare function readStoredDefaults(root: string): Record<string, string>;
12
+ export declare function mutateStoredDefaults(root: string, mutate: (current: Record<string, string>) => Record<string, string>): Record<string, string>;
@@ -0,0 +1,62 @@
1
+ import { canonicalRoot, userStore } from './paths.js';
2
+ import { openScopedStore, withScopedWrite } from './db.js';
3
+ function store(root) { return userStore(root); }
4
+ function rowToProfile(row, projects) {
5
+ return { profileId: row.id, manifest: { schema_version: 1, name: row.name, projects, home: row.home, paused_at: row.paused_at, ...(row.default_kind === null ? {} : { default_kind: row.default_kind }), ...(Object.keys(JSON.parse(row.metadata_json)).length === 0 ? {} : { metadata: JSON.parse(row.metadata_json) }), created_at: row.created_at, last_used_at: row.last_used_at } };
6
+ }
7
+ function get(db, id) {
8
+ const row = db.prepare('SELECT id, name, created_at, last_used_at, paused_at, home, default_kind, metadata_json FROM profiles WHERE id=?').get(id);
9
+ if (!row)
10
+ return null;
11
+ const projects = db.prepare('SELECT path, memory FROM profile_projects WHERE profile_id=? ORDER BY position').all(id);
12
+ return rowToProfile(row, projects);
13
+ }
14
+ export function listStoredProfiles(root) {
15
+ const db = openScopedStore(store(root));
16
+ const ids = db.prepare('SELECT id FROM profiles ORDER BY rowid').all();
17
+ return ids.map(({ id }) => get(db, id)).filter(Boolean);
18
+ }
19
+ export function getStoredProfile(root, id) { return get(openScopedStore(store(root)), id); }
20
+ export function insertStoredProfile(root, profile, profileRoot) {
21
+ withScopedWrite(store(root), (db) => {
22
+ db.prepare('INSERT INTO profiles(id,name,created_at,last_used_at,paused_at,home,default_kind,metadata_json) VALUES(?,?,?,?,?,?,?,?)').run(profile.profileId, profile.manifest.name, profile.manifest.created_at, profile.manifest.last_used_at, profile.manifest.paused_at, profile.manifest.home, profile.manifest.default_kind ?? null, JSON.stringify(profile.manifest.metadata ?? {}));
23
+ db.prepare('INSERT INTO scope_owners(owner_key,kind,root,profile_id) VALUES(?,?,?,?)').run(`profile:${profile.profileId}`, 'profile', canonicalRoot(profileRoot), profile.profileId);
24
+ const insert = db.prepare('INSERT INTO profile_projects(profile_id,path,position,memory) VALUES(?,?,?,?)');
25
+ profile.manifest.projects.forEach((project, position) => insert.run(profile.profileId, project.path, position, project.memory));
26
+ });
27
+ }
28
+ export function updateStoredProfile(root, id, mutate) {
29
+ return withScopedWrite(store(root), (db) => {
30
+ const current = get(db, id);
31
+ if (!current)
32
+ return null;
33
+ const next = mutate(current.manifest);
34
+ db.prepare('UPDATE profiles SET name=?, last_used_at=?, paused_at=?, home=?, default_kind=?, metadata_json=? WHERE id=?').run(next.name, next.last_used_at, next.paused_at, next.home, next.default_kind ?? null, JSON.stringify(next.metadata ?? {}), id);
35
+ db.prepare('DELETE FROM profile_projects WHERE profile_id=?').run(id);
36
+ const insert = db.prepare('INSERT INTO profile_projects(profile_id,path,position,memory) VALUES(?,?,?,?)');
37
+ next.projects.forEach((project, position) => insert.run(id, project.path, position, project.memory));
38
+ return { profileId: id, manifest: next };
39
+ });
40
+ }
41
+ export function deleteStoredProfile(root, id) {
42
+ return withScopedWrite(store(root), (db) => {
43
+ if (!get(db, id))
44
+ return false;
45
+ const owner = `profile:${id}`;
46
+ db.prepare('DELETE FROM scope_settings WHERE owner_key=?').run(owner);
47
+ db.prepare('DELETE FROM scope_bookkeeping WHERE owner_key=?').run(owner);
48
+ db.prepare('DELETE FROM scope_marketplaces WHERE owner_key=?').run(owner);
49
+ db.prepare('DELETE FROM scope_plugins WHERE owner_key=?').run(owner);
50
+ db.prepare('DELETE FROM scope_owners WHERE owner_key=?').run(owner);
51
+ db.prepare('DELETE FROM profiles WHERE id=?').run(id);
52
+ return true;
53
+ });
54
+ }
55
+ export function readStoredDefaults(root) {
56
+ const db = openScopedStore(store(root));
57
+ return Object.fromEntries(db.prepare('SELECT directory,profile_id FROM profile_defaults').all().map((row) => [row.directory, row.profile_id]));
58
+ }
59
+ export function mutateStoredDefaults(root, mutate) {
60
+ return withScopedWrite(store(root), (db) => { const current = Object.fromEntries(db.prepare('SELECT directory,profile_id FROM profile_defaults').all().map((row) => [row.directory, row.profile_id])); const next = mutate(current); db.prepare('DELETE FROM profile_defaults').run(); const insert = db.prepare('INSERT INTO profile_defaults(directory,profile_id) VALUES(?,?)'); for (const [directory, id] of Object.entries(next))
61
+ insert.run(directory, id); return next; });
62
+ }
@@ -0,0 +1,16 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ export type ProviderRow = {
3
+ providerId: string;
4
+ id: string;
5
+ label: string;
6
+ labelKey: string;
7
+ position: number;
8
+ lastAttemptAt: number;
9
+ lastRateLimitedAt: number;
10
+ rateLimitedUntil: number;
11
+ authFailure?: 'invalid_grant';
12
+ };
13
+ export declare function providerRows(agentDir: string, providerId: string): ProviderRow[];
14
+ export declare function providerRowByReference(db: DatabaseSync, providerId: string, ref: string): ProviderRow | undefined;
15
+ export declare function providerWrite<T>(agentDir: string, run: (db: DatabaseSync) => T): T;
16
+ export declare function providerRevision(agentDir: string): number;
@@ -0,0 +1,8 @@
1
+ import { openScopedStore, withScopedWrite } from './db.js';
2
+ import { providerStore } from './paths.js';
3
+ function map(row) { return { providerId: row.provider_id, id: row.id, label: row.label, labelKey: row.label_key, position: row.position, lastAttemptAt: row.last_attempt_at, lastRateLimitedAt: row.last_rate_limited_at, rateLimitedUntil: row.rate_limited_until, ...(row.auth_failure ? { authFailure: row.auth_failure } : {}) }; }
4
+ const ROW_COLUMNS = 'provider_id,id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure';
5
+ export function providerRows(agentDir, providerId) { const db = openScopedStore(providerStore(agentDir)); return db.prepare(`SELECT ${ROW_COLUMNS} FROM managed_accounts WHERE provider_id=? ORDER BY position`).all(providerId).map(map); }
6
+ export function providerRowByReference(db, providerId, ref) { const needle = ref.trim().toLowerCase(); const position = Number.parseInt(needle, 10); const row = (Number.isFinite(position) && String(position) === needle && position >= 1 ? db.prepare(`SELECT ${ROW_COLUMNS} FROM managed_accounts WHERE provider_id=? AND position=?`).get(providerId, position - 1) : db.prepare(`SELECT ${ROW_COLUMNS} FROM managed_accounts WHERE provider_id=? AND label_key=?`).get(providerId, needle)); return row ? map(row) : undefined; }
7
+ export function providerWrite(agentDir, run) { return withScopedWrite(providerStore(agentDir), (db) => { const value = run(db); db.prepare('UPDATE provider_revision SET revision=revision+1 WHERE singleton=1').run(); return value; }); }
8
+ export function providerRevision(agentDir) { const db = openScopedStore(providerStore(agentDir)); return db.prepare('SELECT revision FROM provider_revision WHERE singleton=1').get().revision; }
@@ -0,0 +1,3 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ export declare const SCOPED_SCHEMA_VERSION = 1;
3
+ export declare function createScopedSchema(db: DatabaseSync, kind: 'user' | 'project' | 'provider'): void;
@@ -0,0 +1,26 @@
1
+ export const SCOPED_SCHEMA_VERSION = 1;
2
+ const SCOPE_SCHEMA = `
3
+ CREATE TABLE scope_owners (owner_key TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK(kind IN ('user','profile','project')), root TEXT NOT NULL UNIQUE, profile_id TEXT UNIQUE);
4
+ CREATE TABLE profiles (id TEXT PRIMARY KEY, name TEXT NOT NULL, created_at TEXT NOT NULL, last_used_at TEXT, paused_at TEXT, home TEXT, default_kind TEXT, metadata_json TEXT NOT NULL CHECK(json_valid(metadata_json)));
5
+ CREATE TABLE profile_projects (profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, path TEXT NOT NULL, position INTEGER NOT NULL, memory TEXT NOT NULL CHECK(memory IN ('none','name','preview','content')), PRIMARY KEY(profile_id,path), UNIQUE(profile_id,position));
6
+ CREATE TABLE profile_defaults (directory TEXT PRIMARY KEY, profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE);
7
+ CREATE TABLE scope_settings (owner_key TEXT PRIMARY KEY REFERENCES scope_owners(owner_key), raw_json TEXT NOT NULL CHECK(json_valid(raw_json)));
8
+ CREATE TABLE scope_bookkeeping (owner_key TEXT PRIMARY KEY REFERENCES scope_owners(owner_key), last_self_check TEXT, active_canvas TEXT);
9
+ CREATE TABLE scope_marketplaces (owner_key TEXT NOT NULL REFERENCES scope_owners(owner_key), name TEXT NOT NULL, last_updated TEXT, PRIMARY KEY(owner_key,name));
10
+ CREATE TABLE scope_plugins (owner_key TEXT NOT NULL REFERENCES scope_owners(owner_key), name TEXT NOT NULL, last_updated TEXT, etag TEXT, checked_at TEXT, PRIMARY KEY(owner_key,name));`;
11
+ const PROVIDER_SCHEMA = `
12
+ CREATE TABLE managed_accounts (provider_id TEXT NOT NULL, id TEXT NOT NULL, label TEXT NOT NULL, label_key TEXT NOT NULL, position INTEGER NOT NULL, last_attempt_at INTEGER NOT NULL, last_rate_limited_at INTEGER NOT NULL, rate_limited_until INTEGER NOT NULL, auth_failure TEXT CHECK(auth_failure IS NULL OR auth_failure='invalid_grant'), PRIMARY KEY(provider_id,id), UNIQUE(provider_id,label_key));
13
+ CREATE INDEX managed_accounts_order ON managed_accounts(provider_id,position);
14
+ CREATE TABLE rotation_settings (singleton INTEGER PRIMARY KEY CHECK(singleton=1), default_fallback_strength TEXT, revert_when_available INTEGER CHECK(revert_when_available IN (0,1)));
15
+ CREATE TABLE provider_revision (singleton INTEGER PRIMARY KEY CHECK(singleton=1), revision INTEGER NOT NULL);`;
16
+ export function createScopedSchema(db, kind) {
17
+ db.exec(kind === 'provider' ? PROVIDER_SCHEMA : SCOPE_SCHEMA);
18
+ if (kind !== 'provider') {
19
+ const ownerKey = kind === 'user' ? 'user' : 'project';
20
+ db.prepare('INSERT INTO scope_owners(owner_key, kind, root, profile_id) VALUES (?, ?, ?, NULL)').run(ownerKey, kind, '');
21
+ }
22
+ else {
23
+ db.prepare('INSERT INTO provider_revision(singleton, revision) VALUES (1, 0)').run();
24
+ }
25
+ db.exec(`PRAGMA user_version = ${SCOPED_SCHEMA_VERSION}`);
26
+ }
@@ -0,0 +1,28 @@
1
+ import type { ScopeState } from '../../types.js';
2
+ import type { ScopedStore } from './paths.js';
3
+ export type SettingsOwner = {
4
+ store: ScopedStore;
5
+ key: string;
6
+ kind: 'user' | 'profile' | 'project';
7
+ root: string;
8
+ profileId?: string;
9
+ };
10
+ export declare class SettingsRevisionConflictError extends Error {
11
+ constructor();
12
+ }
13
+ export declare function readRawSettingsWithRevision(owner: SettingsOwner): {
14
+ settings: Record<string, unknown> | null;
15
+ revision: string;
16
+ };
17
+ export declare function readRawSettings(owner: SettingsOwner): Record<string, unknown> | null;
18
+ export declare function replaceRawSettings(owner: SettingsOwner, next: Record<string, unknown>, ifRevision: string, validate: (settings: Record<string, unknown>) => void): {
19
+ revision: string;
20
+ changed: boolean;
21
+ };
22
+ export declare function writeRawSettings(owner: SettingsOwner, raw: Record<string, unknown>): void;
23
+ export declare function mutateRawSettings<T>(owner: SettingsOwner, mutate: (current: Record<string, unknown>) => {
24
+ value: T;
25
+ next: Record<string, unknown>;
26
+ }): T;
27
+ export declare function readBookkeeping(owner: SettingsOwner): ScopeState;
28
+ export declare function writeBookkeeping(owner: SettingsOwner, state: ScopeState): void;
@@ -0,0 +1,82 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { openScopedStore, withScopedWrite } from './db.js';
3
+ function ensureOwner(db, owner) {
4
+ const row = db.prepare('SELECT kind, root, profile_id FROM scope_owners WHERE owner_key = ?').get(owner.key);
5
+ if (!row)
6
+ throw new Error(`scoped SQL owner is missing: ${owner.key}`);
7
+ if (row.kind !== owner.kind || row.root !== owner.root || (owner.profileId ?? null) !== row.profile_id) {
8
+ throw new Error(`scoped SQL owner does not match its root: ${owner.key}`);
9
+ }
10
+ }
11
+ function revisionFor(row) {
12
+ return row === undefined ? 'absent' : createHash('sha256').update(row.raw_json).digest('hex');
13
+ }
14
+ function readRawSettingsRow(db, owner) {
15
+ return db.prepare('SELECT raw_json FROM scope_settings WHERE owner_key = ?').get(owner.key);
16
+ }
17
+ export class SettingsRevisionConflictError extends Error {
18
+ constructor() {
19
+ super('settings changed since the supplied revision');
20
+ this.name = 'SettingsRevisionConflictError';
21
+ }
22
+ }
23
+ export function readRawSettingsWithRevision(owner) {
24
+ const db = openScopedStore(owner.store);
25
+ ensureOwner(db, owner);
26
+ const row = readRawSettingsRow(db, owner);
27
+ return { revision: revisionFor(row), settings: row === undefined ? null : JSON.parse(row.raw_json) };
28
+ }
29
+ export function readRawSettings(owner) {
30
+ return readRawSettingsWithRevision(owner).settings;
31
+ }
32
+ export function replaceRawSettings(owner, next, ifRevision, validate) {
33
+ return withScopedWrite(owner.store, (db) => {
34
+ ensureOwner(db, owner);
35
+ const current = readRawSettingsRow(db, owner);
36
+ if (ifRevision !== revisionFor(current))
37
+ throw new SettingsRevisionConflictError();
38
+ validate(next);
39
+ const rawJson = JSON.stringify(next);
40
+ if (current?.raw_json === rawJson)
41
+ return { revision: revisionFor(current), changed: false };
42
+ db.prepare(`INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json`).run(owner.key, rawJson);
43
+ return { revision: revisionFor({ raw_json: rawJson }), changed: true };
44
+ });
45
+ }
46
+ export function writeRawSettings(owner, raw) {
47
+ withScopedWrite(owner.store, (db) => {
48
+ ensureOwner(db, owner);
49
+ db.prepare(`INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json`).run(owner.key, JSON.stringify(raw));
50
+ });
51
+ }
52
+ export function mutateRawSettings(owner, mutate) {
53
+ return withScopedWrite(owner.store, (db) => {
54
+ ensureOwner(db, owner);
55
+ const row = db.prepare('SELECT raw_json FROM scope_settings WHERE owner_key = ?').get(owner.key);
56
+ const result = mutate(row ? JSON.parse(row.raw_json) : {});
57
+ db.prepare(`INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json`).run(owner.key, JSON.stringify(result.next));
58
+ return result.value;
59
+ });
60
+ }
61
+ export function readBookkeeping(owner) {
62
+ const db = openScopedStore(owner.store);
63
+ ensureOwner(db, owner);
64
+ const bookkeeping = db.prepare('SELECT last_self_check, active_canvas FROM scope_bookkeeping WHERE owner_key = ?').get(owner.key);
65
+ const marketplaces = Object.fromEntries(db.prepare('SELECT name, last_updated FROM scope_marketplaces WHERE owner_key = ?').all(owner.key).map((row) => [row.name, row.last_updated === null ? {} : { last_updated: row.last_updated }]));
66
+ const plugins = Object.fromEntries(db.prepare('SELECT name, last_updated, etag, checked_at FROM scope_plugins WHERE owner_key = ?').all(owner.key).map((row) => [row.name, { ...(row.last_updated === null ? {} : { last_updated: row.last_updated }), ...(row.etag === null ? {} : { etag: row.etag }), ...(row.checked_at === null ? {} : { checked_at: row.checked_at }) }]));
67
+ return { marketplaces, plugins, ...(bookkeeping?.last_self_check === null || bookkeeping === undefined ? {} : { last_self_check: bookkeeping.last_self_check }), activeCanvas: bookkeeping?.active_canvas ?? null };
68
+ }
69
+ export function writeBookkeeping(owner, state) {
70
+ withScopedWrite(owner.store, (db) => {
71
+ ensureOwner(db, owner);
72
+ db.prepare(`INSERT INTO scope_bookkeeping(owner_key, last_self_check, active_canvas) VALUES (?, ?, ?) ON CONFLICT(owner_key) DO UPDATE SET last_self_check=excluded.last_self_check, active_canvas=excluded.active_canvas`).run(owner.key, state.last_self_check ?? null, state.activeCanvas ?? null);
73
+ db.prepare('DELETE FROM scope_marketplaces WHERE owner_key = ?').run(owner.key);
74
+ db.prepare('DELETE FROM scope_plugins WHERE owner_key = ?').run(owner.key);
75
+ const market = db.prepare('INSERT INTO scope_marketplaces(owner_key, name, last_updated) VALUES (?, ?, ?)');
76
+ for (const [name, value] of Object.entries(state.marketplaces))
77
+ market.run(owner.key, name, value.last_updated ?? null);
78
+ const plugin = db.prepare('INSERT INTO scope_plugins(owner_key, name, last_updated, etag, checked_at) VALUES (?, ?, ?, ?, ?)');
79
+ for (const [name, value] of Object.entries(state.plugins))
80
+ plugin.run(owner.key, name, value.last_updated ?? null, value.etag ?? null, value.checked_at ?? null);
81
+ });
82
+ }