@north-light/cli 0.3.561 → 0.3.562

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 (573) hide show
  1. package/dist/api/client.d.ts +27 -4
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/bash-jobs.d.ts +5 -1
  6. package/dist/api/dto/broker-ops.d.ts +25 -1
  7. package/dist/api/dto/charges.d.ts +9 -2
  8. package/dist/api/dto/delivery.d.ts +26 -2
  9. package/dist/api/dto/execution-start.d.ts +57 -0
  10. package/dist/api/dto/execution-start.js +0 -0
  11. package/dist/api/dto/modelauth.d.ts +5 -0
  12. package/dist/api/dto/node-records.d.ts +16 -0
  13. package/dist/api/dto/objects.d.ts +1 -1
  14. package/dist/api/dto/reports.d.ts +0 -3
  15. package/dist/api/dto/run-events.d.ts +7 -1
  16. package/dist/api/dto/settings.d.ts +55 -0
  17. package/dist/api/dto/settings.js +0 -0
  18. package/dist/api/index.d.ts +2 -0
  19. package/dist/api/index.js +1 -1
  20. package/dist/api/routes.d.ts +11 -0
  21. package/dist/api/routes.js +1 -1
  22. package/dist/builtin-memory/crouter-sdk/release-notes.md +4 -0
  23. package/dist/cli.js +7 -7
  24. package/dist/clients/attach/chrome/bash-jobs.d.ts +11 -1
  25. package/dist/clients/attach/chrome/bash-jobs.js +1 -1
  26. package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
  27. package/dist/clients/attach/chrome/roster.js +6 -4
  28. package/dist/clients/attach/overlays/auth.js +2 -2
  29. package/dist/clients/attach/render/chat-view.js +1 -1
  30. package/dist/clients/attach/viewer.js +550 -567
  31. package/dist/commands/canvas-config.js +2 -2
  32. package/dist/commands/canvas-use.js +1 -1
  33. package/dist/commands/human.js +2 -2
  34. package/dist/commands/node/bash.js +3 -3
  35. package/dist/commands/node/create.js +2 -2
  36. package/dist/commands/person-settings.d.ts +1 -0
  37. package/dist/commands/person-settings.js +1 -0
  38. package/dist/commands/pkg/market-manage.d.ts +2 -2
  39. package/dist/commands/pkg/market-manage.js +1 -1
  40. package/dist/commands/pkg/plugin-manage.js +13 -13
  41. package/dist/commands/pkg/shared.js +1 -1
  42. package/dist/commands/profile/default.js +1 -1
  43. package/dist/commands/push.js +2 -3
  44. package/dist/commands/sys/config.js +1 -1
  45. package/dist/commands/sys/connect.js +1 -1
  46. package/dist/commands/sys/context/sysprompt.js +2 -2
  47. package/dist/commands/sys/doctor.js +1 -1
  48. package/dist/commands/sys/panels/broker-limits-panel.js +1 -1
  49. package/dist/commands/sys/panels/keybindings-panel.js +1 -1
  50. package/dist/commands/sys/panels/models-panel.d.ts +8 -2
  51. package/dist/commands/sys/panels/models-panel.js +1 -1
  52. package/dist/commands/sys/panels/prefs-panel.d.ts +1 -0
  53. package/dist/commands/sys/panels/prefs-panel.js +1 -1
  54. package/dist/commands/sys/panels/profiles-panel.js +1 -1
  55. package/dist/commands/sys/panels/provider-panel.d.ts +4 -0
  56. package/dist/commands/sys/panels/provider-panel.js +1 -1
  57. package/dist/commands/sys/provider-login.js +5 -5
  58. package/dist/commands/sys/setup-core.js +3 -3
  59. package/dist/commands/sys/setup-settings-runner.js +1 -1
  60. package/dist/commands/sys/spend.js +3 -3
  61. package/dist/commands/sys/update.js +1 -1
  62. package/dist/core/auth-file.d.ts +11 -19
  63. package/dist/core/auth-file.js +1 -2
  64. package/dist/core/auto-update.d.ts +1 -1
  65. package/dist/core/auto-update.js +4 -4
  66. package/dist/core/bash-jobs.d.ts +29 -71
  67. package/dist/core/bash-jobs.js +9 -10
  68. package/dist/core/blobs/directory.d.ts +3 -0
  69. package/dist/core/blobs/directory.js +1 -0
  70. package/dist/core/blobs/fault.d.ts +3 -0
  71. package/dist/core/blobs/fault.js +1 -0
  72. package/dist/core/blobs/index.d.ts +21 -0
  73. package/dist/core/blobs/index.js +1 -0
  74. package/dist/core/blobs/r2.d.ts +9 -0
  75. package/dist/core/blobs/r2.js +4 -0
  76. package/dist/core/bootstrap.d.ts +1 -1
  77. package/dist/core/bootstrap.js +2 -2
  78. package/dist/core/canvas/canvas.d.ts +9 -7
  79. package/dist/core/canvas/canvas.js +8 -8
  80. package/dist/core/canvas/crons.d.ts +3 -2
  81. package/dist/core/canvas/crons.js +3 -3
  82. package/dist/core/canvas/daemon-owner.d.ts +5 -0
  83. package/dist/core/canvas/daemon-owner.js +1 -1
  84. package/dist/core/canvas/db.d.ts +10 -9
  85. package/dist/core/canvas/db.js +4 -4
  86. package/dist/core/canvas/executions.d.ts +100 -4
  87. package/dist/core/canvas/executions.js +1 -1
  88. package/dist/core/canvas/export-sqlite.js +1 -1
  89. package/dist/core/canvas/focuses.d.ts +4 -0
  90. package/dist/core/canvas/focuses.js +1 -1
  91. package/dist/core/canvas/history.js +9 -9
  92. package/dist/core/canvas/managed-worktree-sweep-candidates.d.ts +2 -1
  93. package/dist/core/canvas/managed-worktree-sweep-candidates.js +3 -2
  94. package/dist/core/canvas/node-tokens.d.ts +1 -4
  95. package/dist/core/canvas/node-tokens.js +3 -3
  96. package/dist/core/canvas/paths.d.ts +0 -1
  97. package/dist/core/canvas/paths.js +1 -1
  98. package/dist/core/canvas/person-state.d.ts +26 -0
  99. package/dist/core/canvas/person-state.js +1 -0
  100. package/dist/core/canvas/person.d.ts +3 -10
  101. package/dist/core/canvas/person.js +7 -7
  102. package/dist/core/canvas/reconciler-leases.d.ts +12 -0
  103. package/dist/core/canvas/reconciler-leases.js +8 -0
  104. package/dist/core/canvas/recovery.d.ts +4 -0
  105. package/dist/core/canvas/recovery.js +1 -1
  106. package/dist/core/canvas/schema-pg/003-fleet-removals.sql +1 -1
  107. package/dist/core/canvas/schema-pg/005-fleet-blobs.sql +13 -0
  108. package/dist/core/canvas/schema-pg/006-job-rows.sql +20 -0
  109. package/dist/core/canvas/schema-pg/007-ledger-check.sql +117 -0
  110. package/dist/core/canvas/schema-version.d.ts +1 -1
  111. package/dist/core/canvas/schema-version.js +1 -1
  112. package/dist/core/canvas/sessions.d.ts +8 -11
  113. package/dist/core/canvas/sessions.js +1 -4
  114. package/dist/core/canvas/tree.d.ts +4 -2
  115. package/dist/core/canvas/tree.js +6 -6
  116. package/dist/core/canvas/types.d.ts +7 -0
  117. package/dist/core/command-hooks/transport/exec-lifecycle.js +1 -1
  118. package/dist/core/command-plugins/bundle.d.ts +1 -1
  119. package/dist/core/command-plugins/bundle.js +1 -1
  120. package/dist/core/command-plugins/compose.d.ts +1 -1
  121. package/dist/core/command-plugins/discovery.d.ts +0 -1
  122. package/dist/core/command-plugins/discovery.js +1 -1
  123. package/dist/core/command-plugins/help-addenda.js +1 -1
  124. package/dist/core/command-plugins/presence.d.ts +2 -0
  125. package/dist/core/command-plugins/presence.js +1 -1
  126. package/dist/core/config.d.ts +16 -15
  127. package/dist/core/config.js +1 -1
  128. package/dist/core/conversation-store/client-worker.js +1 -1
  129. package/dist/core/conversation-store/client.js +2 -2
  130. package/dist/core/conversation-store/index.d.ts +9 -11
  131. package/dist/core/conversation-store/index.js +1 -1
  132. package/dist/core/conversation-store/patch.js +1 -1
  133. package/dist/core/conversation-store/row-store.d.ts +14 -0
  134. package/dist/core/conversation-store/row-store.js +13 -0
  135. package/dist/core/directory/join.js +1 -1
  136. package/dist/core/env-name.d.ts +1 -0
  137. package/dist/core/env-name.js +1 -1
  138. package/dist/core/environment-recipe.d.ts +15 -0
  139. package/dist/core/environment-recipe.js +1 -0
  140. package/dist/core/environments.js +1 -1
  141. package/dist/core/events/emit.js +2 -2
  142. package/dist/core/events/run-event-chunks.d.ts +25 -0
  143. package/dist/core/events/run-event-chunks.js +5 -0
  144. package/dist/core/feed/messages.js +5 -5
  145. package/dist/core/grants/mirror.d.ts +15 -0
  146. package/dist/core/grants/mirror.js +15 -3
  147. package/dist/core/grants/on-sync.d.ts +9 -5
  148. package/dist/core/grants/on-sync.js +1 -1
  149. package/dist/core/grants/remove.d.ts +1 -6
  150. package/dist/core/grants/remove.js +1 -1
  151. package/dist/core/grants/sync.js +1 -1
  152. package/dist/core/grants/watch.d.ts +15 -9
  153. package/dist/core/grants/watch.js +1 -1
  154. package/dist/core/graph/custom.js +8 -8
  155. package/dist/core/graph/jobs.d.ts +83 -5
  156. package/dist/core/graph/jobs.js +11 -2
  157. package/dist/core/graph/kinds/job.js +2 -2
  158. package/dist/core/graph/names.js +7 -7
  159. package/dist/core/graph/objects.d.ts +5 -1
  160. package/dist/core/graph/objects.js +11 -11
  161. package/dist/core/graph/reader.d.ts +3 -0
  162. package/dist/core/graph/reader.js +3 -3
  163. package/dist/core/graph/repo-sync/identity.d.ts +3 -1
  164. package/dist/core/graph/repo-sync/identity.js +1 -1
  165. package/dist/core/graph/repo-sync/repos.d.ts +1 -1
  166. package/dist/core/graph/repo-sync/repos.js +2 -2
  167. package/dist/core/graph/repo-sync/sync.js +2 -2
  168. package/dist/core/installed-plugins.js +1 -1
  169. package/dist/core/keybindings/persistence.d.ts +1 -1
  170. package/dist/core/keybindings/persistence.js +1 -1
  171. package/dist/core/layout-migrate/loose-keys.d.ts +1 -1
  172. package/dist/core/layout-migrate/loose-keys.js +1 -1
  173. package/dist/core/ledger/account.d.ts +12 -0
  174. package/dist/core/ledger/account.js +2 -0
  175. package/dist/core/ledger/charges.d.ts +2 -2
  176. package/dist/core/ledger/charges.js +1 -1
  177. package/dist/core/ledger/check.d.ts +5 -1
  178. package/dist/core/ledger/check.js +1 -7
  179. package/dist/core/ledger/chunks.d.ts +8 -0
  180. package/dist/core/ledger/chunks.js +2 -0
  181. package/dist/core/ledger/index.d.ts +2 -1
  182. package/dist/core/ledger/index.js +1 -1
  183. package/dist/core/ledger/infra-allowance.d.ts +9 -0
  184. package/dist/core/ledger/infra-allowance.js +1 -0
  185. package/dist/core/ledger/limits.js +3 -3
  186. package/dist/core/ledger/payer.d.ts +1 -1
  187. package/dist/core/ledger/payer.js +1 -1
  188. package/dist/core/ledger/read.d.ts +14 -4
  189. package/dist/core/ledger/read.js +2 -3
  190. package/dist/core/ledger/totals.d.ts +5 -16
  191. package/dist/core/ledger/totals.js +4 -4
  192. package/dist/core/ledger/types.d.ts +12 -3
  193. package/dist/core/llm/run.js +2 -2
  194. package/dist/core/managed-providers.d.ts +72 -0
  195. package/dist/core/managed-providers.js +1 -0
  196. package/dist/core/model-auth/api-credential-store.d.ts +10 -0
  197. package/dist/core/model-auth/api-credential-store.js +1 -0
  198. package/dist/core/model-auth/daemon-login.d.ts +1 -1
  199. package/dist/core/model-auth/daemon-login.js +1 -1
  200. package/dist/core/model-auth/flows.d.ts +5 -4
  201. package/dist/core/model-auth/flows.js +1 -1
  202. package/dist/core/model-auth/handoff-jti.d.ts +3 -4
  203. package/dist/core/model-auth/handoff-jti.js +1 -1
  204. package/dist/core/model-auth/readiness.js +1 -1
  205. package/dist/core/model-config.d.ts +1 -1
  206. package/dist/core/model-config.js +1 -1
  207. package/dist/core/model-routes-pool.d.ts +14 -0
  208. package/dist/core/model-routes-pool.js +1 -0
  209. package/dist/core/model-routes.d.ts +11 -16
  210. package/dist/core/model-routes.js +1 -1
  211. package/dist/core/peer/ingest.d.ts +2 -0
  212. package/dist/core/peer/ingest.js +1 -1
  213. package/dist/core/peer/stubs.d.ts +8 -0
  214. package/dist/core/peer/stubs.js +1 -1
  215. package/dist/core/permissions/evaluator.d.ts +7 -2
  216. package/dist/core/permissions/evaluator.js +2 -2
  217. package/dist/core/plugin-env.js +1 -1
  218. package/dist/core/pool-credential-store.d.ts +2 -5
  219. package/dist/core/pool-credential-store.js +1 -1
  220. package/dist/core/profiles/app-env-store.d.ts +5 -5
  221. package/dist/core/profiles/app-env-store.js +1 -2
  222. package/dist/core/profiles/default-binding.d.ts +5 -5
  223. package/dist/core/profiles/default-binding.js +1 -1
  224. package/dist/core/profiles/env-store.d.ts +10 -21
  225. package/dist/core/profiles/env-store.js +1 -2
  226. package/dist/core/profiles/manifest.d.ts +14 -18
  227. package/dist/core/profiles/manifest.js +1 -1
  228. package/dist/core/profiles/relocate.js +1 -1
  229. package/dist/core/profiles/select.js +21 -21
  230. package/dist/core/provider-credential.d.ts +1 -1
  231. package/dist/core/provider-credential.js +1 -1
  232. package/dist/core/provider-management.d.ts +3 -4
  233. package/dist/core/provider-management.js +1 -1
  234. package/dist/core/providers/bundles.js +2 -2
  235. package/dist/core/providers/call.d.ts +3 -34
  236. package/dist/core/providers/description.d.ts +34 -0
  237. package/dist/core/providers/description.js +0 -0
  238. package/dist/core/providers/token-cache.d.ts +14 -14
  239. package/dist/core/providers/token-cache.js +1 -1
  240. package/dist/core/review/companion.js +1 -1
  241. package/dist/core/runs/activity.d.ts +1 -1
  242. package/dist/core/runs/activity.js +1 -1
  243. package/dist/core/runs/events.d.ts +7 -5
  244. package/dist/core/runs/events.js +2 -6
  245. package/dist/core/runs/operations.d.ts +1 -1
  246. package/dist/core/runs/operations.js +6 -6
  247. package/dist/core/runtime/bearings.js +1 -1
  248. package/dist/core/runtime/broker/attachments-fetch.d.ts +5 -0
  249. package/dist/core/runtime/broker/attachments-fetch.js +1 -0
  250. package/dist/core/runtime/broker/bash-job-launch.d.ts +4 -2
  251. package/dist/core/runtime/broker/bash-job-launch.js +1 -1
  252. package/dist/core/runtime/broker/boot-state.d.ts +9 -0
  253. package/dist/core/runtime/broker/boot-state.js +1 -0
  254. package/dist/core/runtime/broker/cli-relay.d.ts +2 -1
  255. package/dist/core/runtime/broker/cli-relay.js +1 -1
  256. package/dist/core/runtime/broker/client-registry.d.ts +3 -3
  257. package/dist/core/runtime/broker/daemon-ops.d.ts +10 -1
  258. package/dist/core/runtime/broker/daemon-ops.js +1 -1
  259. package/dist/core/runtime/broker/fault-retry.d.ts +2 -0
  260. package/dist/core/runtime/broker/fault-retry.js +1 -1
  261. package/dist/core/runtime/broker/frame-engine-control.js +1 -1
  262. package/dist/core/runtime/broker/held-deferred-inbox.d.ts +3 -0
  263. package/dist/core/runtime/broker/held-deferred-inbox.js +1 -1
  264. package/dist/core/runtime/broker/job-reporter.d.ts +18 -0
  265. package/dist/core/runtime/broker/job-reporter.js +2 -0
  266. package/dist/core/runtime/broker/model-adapter.d.ts +3 -0
  267. package/dist/core/runtime/broker/model-adapter.js +3 -2
  268. package/dist/core/runtime/broker/signal-stream.js +1 -1
  269. package/dist/core/runtime/broker/spilled-body.d.ts +5 -0
  270. package/dist/core/runtime/broker/spilled-body.js +1 -0
  271. package/dist/core/runtime/broker/summaries-post.d.ts +5 -0
  272. package/dist/core/runtime/broker/summaries-post.js +1 -0
  273. package/dist/core/runtime/broker/tool-groups.d.ts +3 -0
  274. package/dist/core/runtime/broker/tool-groups.js +1 -1
  275. package/dist/core/runtime/broker/turn-replies.d.ts +1 -0
  276. package/dist/core/runtime/broker/turn-replies.js +1 -1
  277. package/dist/core/runtime/broker-persona-guidance.js +2 -2
  278. package/dist/core/runtime/broker.js +1 -1
  279. package/dist/core/runtime/deliver-live.js +1 -1
  280. package/dist/core/runtime/first-boot-rows.d.ts +8 -0
  281. package/dist/core/runtime/first-boot-rows.js +2 -0
  282. package/dist/core/runtime/fleet.d.ts +13 -12
  283. package/dist/core/runtime/front-door-login.js +1 -1
  284. package/dist/core/runtime/front-door.js +6 -6
  285. package/dist/core/runtime/headless-pi.js +1 -1
  286. package/dist/core/runtime/host.d.ts +14 -3
  287. package/dist/core/runtime/host.js +3 -3
  288. package/dist/core/runtime/invocation.d.ts +6 -0
  289. package/dist/core/runtime/kickoff.d.ts +2 -14
  290. package/dist/core/runtime/kickoff.js +17 -19
  291. package/dist/core/runtime/launch-config-worker.js +1 -1
  292. package/dist/core/runtime/launch.js +2 -2
  293. package/dist/core/runtime/model-registry.js +1 -1
  294. package/dist/core/runtime/node-read.d.ts +4 -6
  295. package/dist/core/runtime/node-read.js +3 -3
  296. package/dist/core/runtime/nodes.js +1 -1
  297. package/dist/core/runtime/outcome-document.d.ts +0 -1
  298. package/dist/core/runtime/outcome-document.js +1 -1
  299. package/dist/core/runtime/output-result.d.ts +15 -0
  300. package/dist/core/runtime/output-result.js +5 -0
  301. package/dist/core/runtime/placement.d.ts +17 -1
  302. package/dist/core/runtime/placement.js +1 -1
  303. package/dist/core/runtime/provider-credential-gate.d.ts +6 -2
  304. package/dist/core/runtime/provider-credential-gate.js +2 -2
  305. package/dist/core/runtime/recovery-hold.d.ts +21 -2
  306. package/dist/core/runtime/recovery-hold.js +2 -1
  307. package/dist/core/runtime/recycle.js +1 -1
  308. package/dist/core/runtime/reset.js +1 -1
  309. package/dist/core/runtime/revive.d.ts +2 -2
  310. package/dist/core/runtime/revive.js +2 -2
  311. package/dist/core/runtime/runner/blaxel/client.d.ts +81 -0
  312. package/dist/core/runtime/runner/blaxel/client.js +1 -0
  313. package/dist/core/runtime/runner/blaxel/image-build.d.ts +74 -0
  314. package/dist/core/runtime/runner/blaxel/image-build.js +5 -0
  315. package/dist/core/runtime/runner/blaxel/runner.d.ts +41 -0
  316. package/dist/core/runtime/runner/blaxel/runner.js +3 -0
  317. package/dist/core/runtime/runner/index.d.ts +8 -0
  318. package/dist/core/runtime/runner/index.js +1 -0
  319. package/dist/core/runtime/runner/process.d.ts +11 -0
  320. package/dist/core/runtime/runner/process.js +1 -0
  321. package/dist/core/runtime/runner/types.d.ts +95 -0
  322. package/dist/core/runtime/runner/types.js +0 -0
  323. package/dist/core/runtime/situational-context.d.ts +3 -6
  324. package/dist/core/runtime/situational-context.js +2 -2
  325. package/dist/core/runtime/situational-live.js +1 -1
  326. package/dist/core/runtime/spawn-env-rows.d.ts +5 -0
  327. package/dist/core/runtime/spawn-env-rows.js +1 -0
  328. package/dist/core/runtime/spawn-env.d.ts +4 -18
  329. package/dist/core/runtime/spawn-env.js +1 -1
  330. package/dist/core/runtime/spawn.js +2 -2
  331. package/dist/core/runtime/stop-guard.d.ts +1 -1
  332. package/dist/core/runtime/stop-guard.js +2 -8
  333. package/dist/core/runtime/structured-output.d.ts +0 -67
  334. package/dist/core/runtime/structured-output.js +3 -8
  335. package/dist/core/runtime/tmux-driver.d.ts +8 -0
  336. package/dist/core/runtime/tmux-driver.js +6 -6
  337. package/dist/core/runtime/view-socket.d.ts +20 -0
  338. package/dist/core/runtime/view-socket.js +3 -1
  339. package/dist/core/runtime/warm-pool.d.ts +13 -1
  340. package/dist/core/runtime/warm-pool.js +1 -1
  341. package/dist/core/scoped-state/legacy-profiles.d.ts +11 -0
  342. package/dist/core/scoped-state/legacy-profiles.js +1 -0
  343. package/dist/core/scoped-state/materialized-plugins.d.ts +18 -0
  344. package/dist/core/scoped-state/materialized-plugins.js +1 -0
  345. package/dist/core/scoped-state/migrate.d.ts +8 -1
  346. package/dist/core/scoped-state/migrate.js +3 -3
  347. package/dist/core/scoped-state/person-rows.d.ts +10 -0
  348. package/dist/core/scoped-state/person-rows.js +6 -0
  349. package/dist/core/scoped-state/person-store.d.ts +72 -0
  350. package/dist/core/scoped-state/person-store.js +1 -0
  351. package/dist/core/secrets/blaxel-launch.d.ts +24 -0
  352. package/dist/core/secrets/blaxel-launch.js +4 -0
  353. package/dist/core/secrets/blaxel-register.d.ts +2 -0
  354. package/dist/core/secrets/blaxel-register.js +1 -0
  355. package/dist/core/secrets/env-rows.d.ts +17 -0
  356. package/dist/core/secrets/env-rows.js +4 -0
  357. package/dist/core/secrets/environment-owner.d.ts +8 -0
  358. package/dist/core/secrets/environment-owner.js +1 -0
  359. package/dist/core/secrets/environment-secrets.d.ts +14 -0
  360. package/dist/core/secrets/environment-secrets.js +1 -0
  361. package/dist/core/secrets/index.d.ts +2 -0
  362. package/dist/core/secrets/index.js +1 -0
  363. package/dist/core/secrets/key.d.ts +3 -0
  364. package/dist/core/secrets/key.js +2 -0
  365. package/dist/core/secrets/seal.d.ts +18 -0
  366. package/dist/core/secrets/seal.js +1 -0
  367. package/dist/core/spaces/mounts.d.ts +1 -1
  368. package/dist/core/spaces/mounts.js +1 -1
  369. package/dist/core/spaces/process.d.ts +1 -1
  370. package/dist/core/spaces/stop.d.ts +7 -5
  371. package/dist/core/spaces/stop.js +2 -2
  372. package/dist/core/spaces/users.d.ts +2 -1
  373. package/dist/core/spaces/users.js +1 -1
  374. package/dist/core/storage/attachments.d.ts +11 -8
  375. package/dist/core/storage/attachments.js +1 -1
  376. package/dist/core/storage/tables.d.ts +4 -0
  377. package/dist/core/storage/tables.js +3 -3
  378. package/dist/core/subscription-state.d.ts +34 -72
  379. package/dist/core/subscription-state.js +1 -4
  380. package/dist/core/substrate/delivery/plan.js +1 -1
  381. package/dist/core/user-settings.js +1 -1
  382. package/dist/daemon/api/app-listener.js +10 -10
  383. package/dist/daemon/api/auth/person-bearer.d.ts +14 -0
  384. package/dist/daemon/api/auth/person-bearer.js +1 -0
  385. package/dist/daemon/api/bridge.js +4 -4
  386. package/dist/daemon/api/document-listener.js +3 -3
  387. package/dist/daemon/api/environment-of.d.ts +18 -0
  388. package/dist/daemon/api/environment-of.js +5 -0
  389. package/dist/daemon/api/handlers/analytics.js +1 -1
  390. package/dist/daemon/api/handlers/app-profiles.d.ts +2 -2
  391. package/dist/daemon/api/handlers/app-profiles.js +1 -1
  392. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  393. package/dist/daemon/api/handlers/bash.js +1 -1
  394. package/dist/daemon/api/handlers/broker-ops.d.ts +3 -0
  395. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  396. package/dist/daemon/api/handlers/broker-recovery.js +1 -1
  397. package/dist/daemon/api/handlers/broker-signals.d.ts +0 -3
  398. package/dist/daemon/api/handlers/broker-signals.js +2 -2
  399. package/dist/daemon/api/handlers/canvas.js +4 -4
  400. package/dist/daemon/api/handlers/charges.d.ts +1 -1
  401. package/dist/daemon/api/handlers/charges.js +1 -1
  402. package/dist/daemon/api/handlers/conversations.d.ts +2 -0
  403. package/dist/daemon/api/handlers/conversations.js +3 -0
  404. package/dist/daemon/api/handlers/crons.js +1 -1
  405. package/dist/daemon/api/handlers/daemon.js +1 -1
  406. package/dist/daemon/api/handlers/delivery.d.ts +1 -1
  407. package/dist/daemon/api/handlers/delivery.js +1 -1
  408. package/dist/daemon/api/handlers/docs.js +1 -1
  409. package/dist/daemon/api/handlers/exec.js +1 -1
  410. package/dist/daemon/api/handlers/execution-start.d.ts +2 -0
  411. package/dist/daemon/api/handlers/execution-start.js +2 -0
  412. package/dist/daemon/api/handlers/files.js +1 -1
  413. package/dist/daemon/api/handlers/git-credential.d.ts +11 -0
  414. package/dist/daemon/api/handlers/git-credential.js +1 -0
  415. package/dist/daemon/api/handlers/hook-exec.d.ts +1 -1
  416. package/dist/daemon/api/handlers/hook-exec.js +1 -1
  417. package/dist/daemon/api/handlers/human-requests.js +1 -1
  418. package/dist/daemon/api/handlers/inbox.js +1 -1
  419. package/dist/daemon/api/handlers/messages.js +1 -1
  420. package/dist/daemon/api/handlers/model-config.js +1 -1
  421. package/dist/daemon/api/handlers/modelauth.d.ts +1 -1
  422. package/dist/daemon/api/handlers/modelauth.js +1 -1
  423. package/dist/daemon/api/handlers/node-events.d.ts +6 -0
  424. package/dist/daemon/api/handlers/node-events.js +3 -3
  425. package/dist/daemon/api/handlers/node-outcomes.js +1 -1
  426. package/dist/daemon/api/handlers/node-records.js +3 -2
  427. package/dist/daemon/api/handlers/nodes.js +1 -1
  428. package/dist/daemon/api/handlers/objects.js +3 -3
  429. package/dist/daemon/api/handlers/profiles.js +1 -1
  430. package/dist/daemon/api/handlers/providers.js +1 -1
  431. package/dist/daemon/api/handlers/reports.js +9 -13
  432. package/dist/daemon/api/handlers/reviews.js +1 -1
  433. package/dist/daemon/api/handlers/runs.js +1 -1
  434. package/dist/daemon/api/handlers/settings.d.ts +2 -0
  435. package/dist/daemon/api/handlers/settings.js +1 -0
  436. package/dist/daemon/api/handlers/structured-requests.js +1 -1
  437. package/dist/daemon/api/handlers/vendor-status.d.ts +1 -1
  438. package/dist/daemon/api/handlers/vendor-status.js +1 -1
  439. package/dist/daemon/api/handlers/worktree.js +1 -1
  440. package/dist/daemon/api/map.d.ts +2 -2
  441. package/dist/daemon/api/map.js +2 -2
  442. package/dist/daemon/api/operations.js +1 -1
  443. package/dist/daemon/api/reader.d.ts +8 -1
  444. package/dist/daemon/api/reader.js +1 -1
  445. package/dist/daemon/api/router.d.ts +4 -2
  446. package/dist/daemon/api/router.js +1 -1
  447. package/dist/daemon/api/server.d.ts +15 -3
  448. package/dist/daemon/api/server.js +1 -1
  449. package/dist/daemon/api/vendor-signin.d.ts +2 -1
  450. package/dist/daemon/api/vendor-signin.js +19 -19
  451. package/dist/daemon/api/viewer-presence.d.ts +20 -6
  452. package/dist/daemon/api/viewer-presence.js +2 -1
  453. package/dist/daemon/broker-signals/bus.d.ts +48 -11
  454. package/dist/daemon/broker-signals/bus.js +1 -1
  455. package/dist/daemon/cron/armed-context.js +1 -1
  456. package/dist/daemon/cron/lease-recovery.js +1 -1
  457. package/dist/daemon/cron/live-runs.d.ts +6 -0
  458. package/dist/daemon/cron/live-runs.js +1 -1
  459. package/dist/daemon/cron/run-socket.js +1 -1
  460. package/dist/daemon/cron-run.js +2 -2
  461. package/dist/daemon/crtrd-cli.js +1 -1
  462. package/dist/daemon/crtrd.d.ts +10 -0
  463. package/dist/daemon/crtrd.js +18 -17
  464. package/dist/daemon/delivery/run-delivery.d.ts +1 -1
  465. package/dist/daemon/delivery/run-delivery.js +1 -1
  466. package/dist/daemon/due-sources.d.ts +1 -1
  467. package/dist/daemon/due-sources.js +1 -1
  468. package/dist/daemon/due.js +7 -6
  469. package/dist/daemon/fleet.d.ts +6 -6
  470. package/dist/daemon/fleet.js +9 -9
  471. package/dist/daemon/park-activity.d.ts +2 -1
  472. package/dist/daemon/park-activity.js +1 -1
  473. package/dist/daemon/park-pending.d.ts +20 -13
  474. package/dist/daemon/park-pending.js +3 -1
  475. package/dist/daemon/profile-delete.js +1 -1
  476. package/dist/daemon/reconcilers/bash-deadline.d.ts +15 -10
  477. package/dist/daemon/reconcilers/bash-deadline.js +1 -3
  478. package/dist/daemon/reconcilers/broker-supervision.d.ts +13 -3
  479. package/dist/daemon/reconcilers/broker-supervision.js +4 -4
  480. package/dist/daemon/reconcilers/cron-lane.d.ts +0 -1
  481. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  482. package/dist/daemon/reconcilers/fault-retry-backstop.d.ts +2 -9
  483. package/dist/daemon/reconcilers/fault-retry-backstop.js +1 -1
  484. package/dist/daemon/reconcilers/grant-effects.d.ts +8 -0
  485. package/dist/daemon/reconcilers/grant-effects.js +1 -0
  486. package/dist/daemon/reconcilers/grove-instance-sweep.d.ts +1 -3
  487. package/dist/daemon/reconcilers/grove-instance-sweep.js +1 -1
  488. package/dist/daemon/reconcilers/ledger-r2.d.ts +27 -0
  489. package/dist/daemon/reconcilers/ledger-r2.js +7 -0
  490. package/dist/daemon/reconcilers/ledger-sandbox.d.ts +55 -0
  491. package/dist/daemon/reconcilers/ledger-sandbox.js +9 -0
  492. package/dist/daemon/reconcilers/ledger-storage.d.ts +22 -0
  493. package/dist/daemon/reconcilers/ledger-storage.js +5 -0
  494. package/dist/daemon/reconcilers/live-obligation.d.ts +3 -2
  495. package/dist/daemon/reconcilers/live-obligation.js +1 -1
  496. package/dist/daemon/reconcilers/managed-worktree-sweep.d.ts +0 -5
  497. package/dist/daemon/reconcilers/managed-worktree-sweep.js +1 -1
  498. package/dist/daemon/reconcilers/node-deadline.d.ts +2 -1
  499. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  500. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -2
  501. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  502. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +31 -0
  503. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -0
  504. package/dist/daemon/reconcilers/node-lifecycle/requested-stops.d.ts +5 -0
  505. package/dist/daemon/reconcilers/node-lifecycle/requested-stops.js +2 -0
  506. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  507. package/dist/daemon/reconcilers/pending-review-submit.js +1 -1
  508. package/dist/daemon/reconcilers/recipe-image.d.ts +49 -0
  509. package/dist/daemon/reconcilers/recipe-image.js +7 -0
  510. package/dist/daemon/reconcilers/row-claim.d.ts +19 -0
  511. package/dist/daemon/reconcilers/row-claim.js +2 -0
  512. package/dist/daemon/reconcilers/storage-due-work.d.ts +18 -3
  513. package/dist/daemon/reconcilers/storage-due-work.js +2 -1
  514. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -13
  515. package/dist/daemon/reconcilers/storage-maintenance.js +1 -1
  516. package/dist/daemon/reconcilers/structured-request-lane.js +1 -1
  517. package/dist/daemon/reconcilers/throttled-lane.d.ts +11 -0
  518. package/dist/daemon/reconcilers/throttled-lane.js +1 -0
  519. package/dist/daemon/review/finish.js +1 -1
  520. package/dist/daemon/run-notices.d.ts +3 -0
  521. package/dist/daemon/run-notices.js +1 -1
  522. package/dist/daemon/runs/append-section.d.ts +4 -1
  523. package/dist/daemon/runs/append-section.js +2 -2
  524. package/dist/daemon/runs/observer.d.ts +3 -2
  525. package/dist/daemon/runs/observer.js +3 -2
  526. package/dist/daemon/runs/view-transport.d.ts +19 -0
  527. package/dist/daemon/runs/view-transport.js +1 -0
  528. package/dist/daemon/structured-requests/boot.d.ts +2 -1
  529. package/dist/daemon/structured-requests/boot.js +1 -1
  530. package/dist/daemon/structured-requests/effects.d.ts +4 -3
  531. package/dist/daemon/structured-requests/effects.js +1 -1
  532. package/dist/daemon/structured-requests/notifier.d.ts +2 -0
  533. package/dist/daemon/structured-requests/notifier.js +1 -1
  534. package/dist/daemon/structured-requests/sidecar.d.ts +4 -3
  535. package/dist/daemon/structured-requests/sidecar.js +1 -1
  536. package/dist/launcher/validate.js +1 -1
  537. package/dist/migrations/004-canvas-documents/index.js +2 -2
  538. package/dist/pi-extensions/broker-local.d.ts +3 -3
  539. package/dist/pi-extensions/broker-local.js +1 -2
  540. package/dist/pi-extensions/canvas-bash-valve.js +6 -6
  541. package/dist/pi-extensions/canvas-context-intro.js +2 -2
  542. package/dist/pi-extensions/canvas-elapsed.js +2 -2
  543. package/dist/pi-extensions/canvas-goal-capture.js +1 -1
  544. package/dist/pi-extensions/canvas-inbox-watcher.js +2 -2
  545. package/dist/pi-extensions/canvas-review-boundary.d.ts +11 -1
  546. package/dist/pi-extensions/canvas-review-boundary.js +3 -3
  547. package/dist/pi-extensions/canvas-stophook.js +1 -1
  548. package/dist/pi-extensions/canvas-structured-output.d.ts +5 -3
  549. package/dist/pi-extensions/canvas-structured-output.js +6 -11
  550. package/dist/pi-extensions/canvas-tool-guide.d.ts +9 -2
  551. package/dist/pi-extensions/canvas-tool-guide.js +6 -6
  552. package/dist/shared/inbox-entry-body.js +7 -7
  553. package/dist/types.d.ts +12 -0
  554. package/docs/sdk/release-notes.md +4 -0
  555. package/package.json +7 -4
  556. package/packages/crouter-identity/package.json +1 -1
  557. package/runtime.lock.json +13 -13
  558. package/scripts/git-credential-crtr +33 -0
  559. package/scripts/sandbox-bootstrap.sh +54 -0
  560. package/scripts/sandbox-start.mjs +320 -0
  561. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +0 -78
  562. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +0 -1179
  563. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +0 -1406
  564. package/dist/core/conversation-store/file-store.d.ts +0 -37
  565. package/dist/core/conversation-store/file-store.js +0 -9
  566. package/dist/core/conversation-store/listener.d.ts +0 -4
  567. package/dist/core/conversation-store/listener.js +0 -8
  568. package/dist/core/scoped-state/profiles.d.ts +0 -27
  569. package/dist/core/scoped-state/profiles.js +0 -1
  570. package/dist/core/scoped-state/providers.d.ts +0 -24
  571. package/dist/core/scoped-state/providers.js +0 -1
  572. package/dist/core/scoped-state/settings.d.ts +0 -28
  573. package/dist/core/scoped-state/settings.js +0 -1
@@ -6,7 +6,7 @@
6
6
  -- database that still holds un-migrated rows in `usage`, `grantee_usage` or `provider_receipts` would strand them. Refuse until a
7
7
  -- cycle 4 build has run against it. A fresh database, or one that never had cycle-3 rows, has all three empty, so the chain from the
8
8
  -- baseline to here still applies (D71). The three tables stay: the version-2 build still lists them in its SQLite import and export,
9
- -- and a later delta (004, with the `jobs` columns, once the reader floor reaches 3) drops them.
9
+ -- and a later delta (007, with the `jobs` columns, once the reader floor reaches 3) drops them.
10
10
  DO $$
11
11
  BEGIN
12
12
  IF NOT EXISTS (SELECT 1 FROM canvas_meta WHERE key = 'ledger_migrated')
@@ -0,0 +1,13 @@
1
+ -- 005-fleet-blobs.sql — cycle 5 additions for the blob store (design-fleet "Data model", D98, D100). One transaction, additions only:
2
+ -- `schema_min_reader` is not touched and stays 2.
3
+ -- The previous release (version 3, the build that shipped 003) opens the result: it never names `blob_key`, and the index changes no
4
+ -- row it reads or writes.
5
+
6
+ -- D98: the control plane puts each fetched upload in the blob store at `attachments/<run root node>/<sha256>` and records that key here.
7
+ -- Null for a use recorded before this release, and for any use whose bytes the storage service still holds.
8
+ ALTER TABLE upload_uses ADD COLUMN IF NOT EXISTS blob_key text;
9
+
10
+ -- D100: the node blob-deletion pass in `storage-due-work` claims node rows whose `deleted_at` is more than 90 days old. Partial, so it
11
+ -- holds only the soft-deleted node rows, not every object. A plain CREATE INDEX (the chain runs each step in a transaction, so not
12
+ -- CONCURRENTLY): it blocks writes to `objects` for the time it takes to scan the table.
13
+ CREATE INDEX IF NOT EXISTS objects_node_deleted ON objects(deleted_at) WHERE type = 'node';
@@ -0,0 +1,20 @@
1
+ -- 006-job-rows.sql — cycle 5 additions for bash jobs as rows (design-fleet "A bash job outlives the turn", D7). One transaction, additions only:
2
+ -- `schema_min_reader` is not touched and stays 2. The previous release never names these columns.
3
+ -- Each column holds a fact another process reads that the bash valve used to leave in a file under `context/jobs/<id>/`.
4
+ -- The broker's valve is the only writer (POST /v1/nodes/:id/jobs/:jobId/report); `job_dir` and `log_path` stay nullable and unused until the removals delta.
5
+ ALTER TABLE jobs
6
+ ADD COLUMN IF NOT EXISTS purpose text,
7
+ ADD COLUMN IF NOT EXISTS quiet_ok boolean NOT NULL DEFAULT false,
8
+ ADD COLUMN IF NOT EXISTS pgid bigint,
9
+ ADD COLUMN IF NOT EXISTS pgid_identity text,
10
+ ADD COLUMN IF NOT EXISTS activity_last_ms bigint,
11
+ ADD COLUMN IF NOT EXISTS activity_sampled_ms bigint,
12
+ ADD COLUMN IF NOT EXISTS activity_files text,
13
+ ADD COLUMN IF NOT EXISTS silence_activity_ms bigint,
14
+ ADD COLUMN IF NOT EXISTS silence_threshold_ms bigint,
15
+ ADD COLUMN IF NOT EXISTS exit_reported_at text;
16
+
17
+ -- A job recorded before this delta has no output key: give it the key the new rows get. Its deadline and process identity
18
+ -- are still in its supervisor files, which the daemon's reconciler moves onto the row.
19
+ UPDATE jobs SET output_key = 'jobs/' || o.owner || '/' || jobs.object || '/output'
20
+ FROM objects o WHERE o.id = jobs.object AND jobs.output_key IS NULL;
@@ -0,0 +1,117 @@
1
+ -- 007-ledger-check.sql — cycle 5 L7 (design-fleet "Money", D58, D59, D89): the one limit rule as a SQL function, and the
2
+ -- `charges` rollup trigger that now adds `usd` on INSERT. One transaction, additions only: `schema_min_reader` is not touched
3
+ -- and stays 2. The previous build never names `ledger_check`, `ledger_holder` or the new column.
4
+
5
+ -- A writer at version 7 or above sets this on every row it inserts. The previous build leaves it false and keeps calling
6
+ -- `addToTotals` for `usd`, so the trigger must not add that row's `usd` a second time.
7
+ ALTER TABLE charges ADD COLUMN IF NOT EXISTS usd_rolled_by_trigger boolean NOT NULL DEFAULT false;
8
+
9
+ -- D-3's `$0` card-stop bill limit (D68) has `source card`.
10
+ ALTER TABLE limits DROP CONSTRAINT IF EXISTS limits_source_check;
11
+ ALTER TABLE limits ADD CONSTRAINT limits_source_check CHECK (source IN ('grant_sync', 'person', 'operator', 'billing', 'card'));
12
+
13
+ -- Replaces 002's function. INSERT adds `billed_usd` for every row (as 002 did) and adds `usd` only for a marked row. UPDATE is
14
+ -- as in 002: it moves both amounts, the old negative and the new positive; the old `usd` is in the rollup whichever way it got
15
+ -- there. Refused rows and `take` rows are never rolled up, as in `rebuildTotals`.
16
+ CREATE OR REPLACE FUNCTION charge_totals_rollup() RETURNS trigger LANGUAGE plpgsql AS $$
17
+ BEGIN
18
+ IF TG_OP = 'UPDATE' AND OLD.refused IS NULL AND OLD.kind <> 'take' THEN
19
+ INSERT INTO charge_totals (month, payer, funding, sub, grantee, provider, usd, billed_usd)
20
+ VALUES (OLD.month, OLD.payer, OLD.funding, COALESCE(OLD.sub, ''), OLD.grantee, COALESCE(OLD.provider, ''),
21
+ -COALESCE(OLD.usd, 0), -COALESCE(OLD.billed_usd, 0))
22
+ ON CONFLICT (month, payer, funding, sub, grantee, provider) DO UPDATE
23
+ SET usd = charge_totals.usd + EXCLUDED.usd, billed_usd = charge_totals.billed_usd + EXCLUDED.billed_usd;
24
+ END IF;
25
+ IF NEW.refused IS NULL AND NEW.kind <> 'take'
26
+ AND (TG_OP = 'UPDATE' OR COALESCE(NEW.billed_usd, 0) <> 0 OR (NEW.usd_rolled_by_trigger AND COALESCE(NEW.usd, 0) <> 0)) THEN
27
+ INSERT INTO charge_totals (month, payer, funding, sub, grantee, provider, usd, billed_usd)
28
+ VALUES (NEW.month, NEW.payer, NEW.funding, COALESCE(NEW.sub, ''), NEW.grantee, COALESCE(NEW.provider, ''),
29
+ CASE WHEN TG_OP = 'UPDATE' OR NEW.usd_rolled_by_trigger THEN COALESCE(NEW.usd, 0) ELSE 0 END, COALESCE(NEW.billed_usd, 0))
30
+ ON CONFLICT (month, payer, funding, sub, grantee, provider) DO UPDATE
31
+ SET usd = charge_totals.usd + EXCLUDED.usd, billed_usd = charge_totals.billed_usd + EXCLUDED.billed_usd;
32
+ END IF;
33
+ RETURN NULL;
34
+ END $$;
35
+
36
+ -- Who Northlight invoices for a payer (`billingAccountOf`, directory `billing/payer.ts`): a user payer is its own account; an
37
+ -- app payer's account is the team with an `admin` tuple on the app, else the user with one; null when it has neither. A
38
+ -- directory tuple's team subject is type `crtr/group` with the id `team:<id>`; a person is type `user` with the bare id.
39
+ CREATE FUNCTION ledger_holder(payer text) RETURNS text LANGUAGE sql STABLE AS $$
40
+ SELECT CASE
41
+ WHEN payer LIKE 'user:%' THEN payer
42
+ WHEN payer LIKE 'app:%' THEN COALESCE(
43
+ (SELECT r.subject_id FROM relations r
44
+ WHERE r.resource_type = 'app' AND r.resource_id = substr(payer, 5) AND r.relation = 'admin'
45
+ AND r.subject_type = 'crtr/group' AND r.subject_id LIKE 'team:%' ORDER BY r.subject_id LIMIT 1),
46
+ (SELECT 'user:' || r.subject_id FROM relations r
47
+ WHERE r.resource_type = 'app' AND r.resource_id = substr(payer, 5) AND r.relation = 'admin'
48
+ AND r.subject_type = 'user' ORDER BY r.subject_id LIMIT 1))
49
+ END
50
+ $$;
51
+
52
+ -- The one limit rule (design-ledger "The check", cycle 4's `findRefusal`, now in SQL). Input `{payer, account, funding, sub,
53
+ -- grantee, provider, scope: 'call' | 'start', month?}`; `month` defaults to the current UTC month. Result: null, or the first
54
+ -- refusing limit (by id) as `{limit, usd, month, reset_at}`. No reservation: a limit refuses when the month's rollup
55
+ -- is already at or over it.
56
+ -- * A limit matches when its `payer`, `account`, `funding`, `grantee`, `provider` and `sub` filters (null = any) equal the
57
+ -- candidate's. A `call` is checked against every such limit; a `start` only against limits with null `funding` and
58
+ -- `provider`. A limit's `account` matches the candidate's `account`, or the holder `ledger_holder(payer)` finds when the candidate gives none (a house-paid payer's candidate carries none, and the house limit is on the team's account).
59
+ -- * Spent = this month's `charge_totals` rows matching the limit's non-null filters. A limit with an `account` filter counts
60
+ -- the rows whose payer is that account or an app it holds now (D89). A limit with `funding bill` sums `billed_usd`; any
61
+ -- other limit sums `usd` (so the house limit counts full price).
62
+ CREATE FUNCTION ledger_check(candidate jsonb) RETURNS jsonb LANGUAGE plpgsql STABLE AS $$
63
+ DECLARE
64
+ m text := COALESCE(candidate->>'month', to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM'));
65
+ scope text := candidate->>'scope';
66
+ pay text := candidate->>'payer';
67
+ acct text := COALESCE(candidate->>'account', ledger_holder(candidate->>'payer'));
68
+ result jsonb;
69
+ BEGIN
70
+ IF scope IS NULL OR scope NOT IN ('call', 'start') THEN RAISE EXCEPTION 'ledger_check: scope must be call or start'; END IF;
71
+ IF pay IS NULL THEN RAISE EXCEPTION 'ledger_check: payer is required'; END IF;
72
+ SELECT jsonb_build_object(
73
+ 'limit', jsonb_build_object('id', q.id, 'payer', q.payer, 'account', q.account, 'funding', q.funding, 'grantee', q.grantee,
74
+ 'provider', q.provider, 'sub', q.sub, 'usd_per_month', q.usd_per_month, 'source', q.source,
75
+ 'updated_at', q.updated_at),
76
+ 'usd', q.spent, 'month', m,
77
+ 'reset_at', to_char((m || '-01')::date + interval '1 month', 'YYYY-MM-DD"T"00:00:00.000"Z"'))
78
+ INTO result
79
+ FROM (SELECT l.*, s.spent
80
+ FROM limits l
81
+ -- The payers an `account` limit counts: the account itself and the apps it holds now. Built once per limit from the
82
+ -- apps with an `admin` tuple for the account (one `relations_subject` lookup), each kept only when `ledger_holder`
83
+ -- agrees (a team admin wins over a user admin), so the answer equals `ledger_holder`'s and the sum below can use the
84
+ -- (month, payer, ...) key. A limit with no `account` has the one payer; every limit has a payer or an account.
85
+ CROSS JOIN LATERAL (
86
+ SELECT CASE WHEN l.account IS NULL THEN ARRAY[l.payer] ELSE
87
+ ARRAY[l.account] || COALESCE((SELECT array_agg(a.app)
88
+ FROM (SELECT DISTINCT 'app:' || r.resource_id AS app FROM relations r
89
+ WHERE r.subject_type = CASE WHEN l.account LIKE 'team:%' THEN 'crtr/group' ELSE 'user' END
90
+ AND r.subject_id = CASE WHEN l.account LIKE 'user:%' THEN substr(l.account, 6) ELSE l.account END
91
+ AND r.relation = 'admin' AND r.resource_type = 'app') a
92
+ WHERE ledger_holder(a.app) = l.account), ARRAY[]::text[]) END AS payers) h
93
+ CROSS JOIN LATERAL (
94
+ SELECT COALESCE(SUM(CASE WHEN l.funding = 'bill' THEN t.billed_usd ELSE t.usd END), 0) AS spent
95
+ FROM charge_totals t
96
+ WHERE t.month = m
97
+ AND (l.payer IS NULL OR t.payer = l.payer)
98
+ AND t.payer = ANY(h.payers)
99
+ AND (l.funding IS NULL OR t.funding = l.funding)
100
+ AND (l.grantee IS NULL OR t.grantee = l.grantee)
101
+ AND (l.provider IS NULL OR t.provider = l.provider)
102
+ AND (l.sub IS NULL OR t.sub = l.sub)) s
103
+ WHERE (l.payer IS NOT NULL OR l.account IS NOT NULL)
104
+ AND (l.payer IS NULL OR l.payer = pay)
105
+ AND (l.account IS NULL OR l.account = acct)
106
+ AND (l.grantee IS NULL OR l.grantee = candidate->>'grantee')
107
+ AND (l.sub IS NULL OR l.sub = COALESCE(candidate->>'sub', ''))
108
+ AND (CASE WHEN scope = 'call'
109
+ THEN (l.funding IS NULL OR l.funding = candidate->>'funding')
110
+ AND (l.provider IS NULL OR l.provider = COALESCE(candidate->>'provider', ''))
111
+ ELSE l.funding IS NULL AND l.provider IS NULL END)
112
+ AND s.spent >= l.usd_per_month
113
+ ORDER BY l.id LIMIT 1) q;
114
+ RETURN result;
115
+ END $$;
116
+
117
+ INSERT INTO canvas_meta (key, value) VALUES ('schema_min_reader', '2') ON CONFLICT (key) DO NOTHING;
@@ -1,7 +1,7 @@
1
1
  /** The canvas schema version this build supports: the number of the highest delta in `schema-pg/` (the baseline
2
2
  * `schema-pg.sql` is version 1). `scripts/install-runtime.mjs` reads this module from a candidate generation to refuse
3
3
  * one below the live database's `schema_min_reader`. */
4
- export declare const CANVAS_SCHEMA_VERSION = 4;
4
+ export declare const CANVAS_SCHEMA_VERSION = 7;
5
5
  /** The Postgres schema holding the canvas tables: `CRTR_DATABASE_SCHEMA` (`crtr` in the cloud deploy). Unset or empty
6
6
  * means the role's own search_path (`public` on a Mac and on a VM). A name that is not a plain identifier is an error:
7
7
  * it is put in a `search_path` connection option. */
@@ -1 +1 @@
1
- var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=4;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
1
+ var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=7;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
@@ -31,27 +31,24 @@ export declare function transferSession(input: {
31
31
  transcriptPath: string | null;
32
32
  }): Promise<'transferred' | 'shutdown' | 'changed'>;
33
33
  export declare function unbindSession(nodeId: string): Promise<void>;
34
- /** Rewrite the header `cwd` of every transcript this node owns from `oldCwd` to `newCwd`, keeping
35
- * the sessions bound. Pi opens a transcript with its header cwd and a resume is vetoed once that
36
- * directory is gone, so a same-directory relocation must move the header too. Each file is replaced
37
- * atomically (temp + fsync + rename) and a run node's indexed `event_offset` shifts by the byte
38
- * delta, in one canvas write that also serializes against `appendSection`. Returns files rewritten. */
34
+ /** Rewrite the header `cwd` of every conversation this node owns from `oldCwd` to `newCwd`, keeping the sessions bound. Returns conversations rewritten. */
39
35
  export declare function relocateSessionCwd(nodeId: string, oldCwd: string, newCwd: string): Promise<number>;
40
36
  export declare function setCyclePending(nodeId: string, pending: boolean): Promise<void>;
41
37
  export declare function conversationOwner(conversationId: string): Promise<string | null>;
42
- /** Map only the indexed conversation paths; repair each file on first use. */
43
- export declare function openCanvasConversationStore(root: string): ConversationStore & {
38
+ /** Open the row store: durable tail rows plus immutable blob parts. `root` is unused; it stays so test callers keep one signature. */
39
+ export declare function openCanvasConversationStore(_root?: string): ConversationStore & {
40
+ commit(id: string): Promise<number>;
41
+ importLegacy(runId?: string): Promise<void>;
44
42
  recover(rows: Array<{
45
43
  session_id: string;
46
44
  node_id: string;
47
45
  transcript_path: string | null;
48
46
  }>): void;
49
- repair(id: string): void;
50
47
  };
48
+ /** Record each run conversation's existing transcript file as part 0 (first boot after the file store). */
51
49
  export declare function repairRunConversations(runId: string): Promise<void>;
52
- /** Open the canvas conversation store from the user `store` settings, install it in this
53
- * process, and serve it for brokers. Unset settings use pi's sessions dir and
54
- * `<CRTR_HOME>/store.sock`. */
50
+ /** Install the row store in this process and import any transcript files not yet recorded as part 0.
51
+ * Brokers reach it over the /v1 API; no socket is bound here. */
55
52
  export declare function serveCanvasConversationStore(): Promise<{
56
53
  close(): Promise<void>;
57
54
  }>;
@@ -1,4 +1 @@
1
- var v=Object.defineProperty;var a=(n,e)=>v(n,"name",{value:e,configurable:!0});import{randomUUID as w}from"node:crypto";import{closeSync as y,existsSync as N,fsyncSync as C,openSync as T,readFileSync as U,readSync as A,renameSync as W,statSync as H,truncateSync as I,unlinkSync as D,writeFileSync as M}from"node:fs";import{openDb as c,withCanvasWrite as l}from"./db.js";import{canonicalSessionFile as R}from"./canvas.js";import{join as O}from"node:path";import{readConfig as x}from"../config.js";import{appSpace as F,socketDir as P}from"../layout.js";import{createFileStore as B,conversationStore as $,defaultConversationRoot as k,installConversationStore as b}from"../conversation-store/index.js";import{listenConversationStore as J}from"../conversation-store/listener.js";import{appendConversationEvents as h}from"../../daemon/runs/append-section.js";function S(){return new Date().toISOString()}a(S,"now");async function m(n,e,s){return await n.prepare("SELECT 1 FROM executions WHERE execution_id = ? AND node_id = ? AND state != 'exited'").get(s,e)!==void 0}a(m,"liveExecution");function j(n){return{bindingId:n.binding_id,sessionId:n.session_id??null,nodeId:n.node_id,transcriptPath:n.transcript_path??null,sourceSessionId:n.source_session_id??null,forkEntryId:n.fork_entry_id??null,createdAt:n.created_at,boundAt:n.bound_at,unboundAt:n.unbound_at??null}}a(j,"hydrate");async function rn(n){const e=await(await c()).prepare("SELECT * FROM sessions WHERE node_id = ? AND unbound_at IS NULL").get(n);return e===void 0?null:j(e)}a(rn,"readLiveSession");async function an(n){return(await(await c()).prepare("SELECT node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL").get(R(n)))?.node_id??null}a(an,"findNodeBySessionFileSql");async function dn(n,e){return await l(async s=>{if(!await m(s,n,e.executionId))return"shutdown";const i=S(),o=e.transcriptPath===null?null:R(e.transcriptPath),r=await s.prepare("SELECT binding_id, session_id FROM sessions WHERE node_id = ? AND unbound_at IS NULL").get(n);if(r!==void 0&&r.session_id===e.sessionId)await s.prepare("UPDATE sessions SET transcript_path = ?, source_session_id = ?, bound_at = ? WHERE binding_id = ?").run(o,e.sourceSessionId??null,i,r.binding_id);else{r!==void 0&&await s.prepare("UPDATE sessions SET unbound_at = ? WHERE binding_id = ?").run(i,r.binding_id);const t=e.sessionId===null?void 0:await s.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? ORDER BY created_at DESC LIMIT 1").get(n,e.sessionId);t?await s.prepare("UPDATE sessions SET transcript_path = ?, source_session_id = ?, bound_at = ?, unbound_at = NULL WHERE binding_id = ?").run(o,e.sourceSessionId??null,i,t.binding_id):await s.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at) VALUES (?, ?, ?, ?, ?, NULL, ?, ?)").run(w(),e.sessionId,n,o,e.sourceSessionId??null,i,i)}return await s.prepare("UPDATE nodes SET cycle_pending = 0 WHERE node_id = ?").run(n),"bound"})}a(dn,"bindSession");async function cn(n,e){await l(async s=>{const i=S();await s.prepare("UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL").run(i,n),await s.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at) VALUES (?, ?, ?, ?, NULL, NULL, ?, ?)").run(w(),e.sessionId,n,e.transcriptPath===null?null:R(e.transcriptPath),i,i),await s.prepare("UPDATE nodes SET cycle_pending = 0 WHERE node_id = ?").run(n)})}a(cn,"resetSession");async function _n(n){return await l(async e=>{if(!await m(e,n.toNodeId,n.executionId))return"shutdown";if(await e.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND binding_id = ? AND unbound_at IS NULL").get(n.fromNodeId,n.fromBindingId)===void 0)return"changed";if(await e.prepare("SELECT run_id FROM nodes WHERE node_id IN (?, ?) AND run_id IS NOT NULL LIMIT 1").get(n.fromNodeId,n.toNodeId))throw new Error("run conversation transfer is not supported");const o=S();return await e.prepare("UPDATE sessions SET unbound_at = ? WHERE binding_id = ?").run(o,n.fromBindingId),await e.prepare("UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL").run(o,n.toNodeId),await e.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at) VALUES (?, ?, ?, ?, NULL, NULL, ?, ?)").run(w(),n.sessionId,n.toNodeId,n.transcriptPath===null?null:R(n.transcriptPath),o,o),await e.prepare("UPDATE nodes SET cycle_pending = 0 WHERE node_id = ?").run(n.toNodeId),"transferred"})}a(_n,"transferSession");async function un(n){await(await c()).prepare("UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL").run(S(),n)}a(un,"unbindSession");async function En(n,e,s){return await l(async i=>{const o=await i.prepare("SELECT binding_id, transcript_path FROM sessions WHERE node_id = ? AND transcript_path IS NOT NULL").all(n),r=[];try{for(const t of o){if(!N(t.transcript_path))continue;const _=U(t.transcript_path),f=_.indexOf(10);if(f<0)continue;let u;try{u=JSON.parse(_.subarray(0,f).toString("utf8"))}catch{continue}if(u.type!=="session"||u.cwd!==e)continue;const E=Buffer.concat([Buffer.from(JSON.stringify({...u,cwd:s}),"utf8"),_.subarray(f)]),d=`${t.transcript_path}.${w()}.tmp`;r.push({binding_id:t.binding_id,path:t.transcript_path,temp:d,delta:E.length-_.length}),M(d,E,{flag:"wx",mode:384});const p=T(d,"r");try{C(p)}finally{y(p)}}for(const t of r)t.delta!==0&&await i.prepare("UPDATE sessions SET event_offset = event_offset + ? WHERE binding_id = ? AND event_offset IS NOT NULL AND event_offset > 0").run(t.delta,t.binding_id);for(const t of r)W(t.temp,t.path)}finally{for(const t of r)N(t.temp)&&D(t.temp)}return r.length})}a(En,"relocateSessionCwd");async function pn(n,e){await(await c()).prepare("UPDATE nodes SET cycle_pending = ? WHERE node_id = ?").run(e?1:0,n)}a(pn,"setCyclePending");const L="unbound_at IS NULL DESC, created_at DESC, seq DESC";async function fn(n){return(await(await c()).prepare(`SELECT node_id FROM sessions WHERE session_id = ? ORDER BY ${L} LIMIT 1`).get(n))?.node_id??null}a(fn,"conversationOwner");const Y={appendSection:a(async(n,e,s)=>{const i=e.kind==="create"?e:void 0;i&&await l(async o=>{if(!await o.prepare("SELECT node_id FROM nodes WHERE node_id = ?").get(i.nodeId))throw new Error(`missing conversation owner ${i.nodeId}`);const t=S();await o.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at, event_offset) VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?, 0)").run(w(),n,i.nodeId,i.path,i.sourceId??null,t,t,t)}),await l(async o=>{const r=await o.prepare(`SELECT node_id, binding_id, input_ids_json FROM sessions WHERE session_id = ? ORDER BY ${L} LIMIT 1`).get(n),t=i?i.nodeId:r?.node_id;if(!t)throw new Error(`conversation ${n} has no active owner`);const _=await o.prepare("SELECT run_id FROM nodes WHERE node_id = ?").get(t);if(!_)throw new Error(`cannot write conversation ${n}: missing node ${t}`);if(e.kind==="replace"&&_.run_id!==null)throw new Error(`run conversation ${n} is append-only; replace refused`);const f=r.binding_id,u=s();if(_.run_id!==null&&e.kind!=="replace"){const E=i?.sourceId?[]:e.entries,d=await h(o,_.run_id,t,E,JSON.parse(r?.input_ids_json??"[]"));await o.prepare("UPDATE sessions SET event_offset = ?, indexed_entry_id = ?, input_ids_json = ? WHERE binding_id = ?").run(u,E.at(-1)?.id??null,JSON.stringify(d),f)}})},"appendSection"),async abortCreate(n){await(await c()).prepare("DELETE FROM sessions WHERE session_id = ? AND event_offset = 0").run(n)},async delete(n){await(await c()).prepare("DELETE FROM sessions WHERE session_id = ?").run(n)},async pathFor(n){return(await(await c()).prepare(`SELECT transcript_path FROM sessions WHERE session_id = ? ORDER BY ${L} LIMIT 1`).get(n))?.transcript_path??void 0},async createPath(n,e){const s=await(await c()).prepare("SELECT grantee FROM node_rows WHERE node_id = ?").get(n);if(!s)throw new Error(`missing conversation owner ${n}`);return O(F(s.grantee).conversations,n,`${e.timestamp.replace(/[:.]/g,"-")}_${e.id}.jsonl`)},async repair(n,e){await V(n,e)},async owners(){const n=new Map,e=await(await c()).prepare(`SELECT session_id, node_id FROM sessions WHERE session_id IS NOT NULL ORDER BY session_id, ${L}`).all();for(const s of e)n.has(s.session_id)||n.set(s.session_id,s.node_id);return n}};async function V(n,e){const s=await(await c()).prepare(`SELECT s.binding_id, s.node_id, s.event_offset, s.input_ids_json, s.source_session_id, n.run_id
2
- FROM sessions s JOIN nodes n ON n.node_id = s.node_id WHERE s.session_id = ? ORDER BY s.unbound_at IS NULL DESC, s.created_at DESC, s.seq DESC LIMIT 1`).get(n);if(!s?.run_id||s.event_offset===null)return;if(s.event_offset===0&&!N(e)){await(await c()).prepare("DELETE FROM sessions WHERE binding_id = ?").run(s.binding_id);return}const i=H(e).size;if(i<s.event_offset)throw new Error(`conversation shorter than indexed offset: ${n}`);if(i===s.event_offset)return;const o=T(e,"r"),r=[];try{const d=Buffer.allocUnsafe(65536);for(let p=s.event_offset;p<i;){const g=A(o,d,0,Math.min(d.length,i-p),p);if(!g)throw new Error(`conversation read ended early: ${n}`);r.push(Buffer.from(d.subarray(0,g))),p+=g}}finally{y(o)}const t=Buffer.concat(r).toString("utf8"),_=t.lastIndexOf(`
3
- `);if(_<0){s.event_offset===0?(D(e),await(await c()).prepare("DELETE FROM sessions WHERE binding_id = ?").run(s.binding_id)):I(e,s.event_offset);return}const f=t.slice(0,_+1),u=f.split(`
4
- `).filter(Boolean).map(d=>JSON.parse(d)).filter(d=>d.type!=="session"),E=s.event_offset+Buffer.byteLength(f);E<i&&I(e,E),await l(async d=>{const p=s.source_session_id?[]:await h(d,s.run_id,s.node_id,u,JSON.parse(s.input_ids_json));await d.prepare("UPDATE sessions SET event_offset = ?, indexed_entry_id = ?, input_ids_json = ? WHERE binding_id = ?").run(E,u.at(-1)?.id??null,JSON.stringify(p),s.binding_id)})}a(V,"repairConversationTail");function q(n){return B(n,Y)}a(q,"openCanvasConversationStore");async function ln(n){const e=await(await c()).prepare("SELECT DISTINCT s.session_id FROM sessions s JOIN nodes n ON n.node_id = s.node_id WHERE n.run_id = ? AND s.session_id IS NOT NULL").all(n),s=$();for(const i of e)s.repair?.(i.session_id)}a(ln,"repairRunConversations");async function wn(){const n=x("user").store,e=n?.address??O(P(),"store.sock"),s=q(n?.root??k());b(s),process.env.CRTR_STORE_ADDRESS=e;const i=await J(e,s);return{async close(){await i.close(),b(void 0)}}}a(wn,"serveCanvasConversationStore");export{dn as bindSession,fn as conversationOwner,an as findNodeBySessionFileSql,q as openCanvasConversationStore,rn as readLiveSession,En as relocateSessionCwd,ln as repairRunConversations,cn as resetSession,wn as serveCanvasConversationStore,pn as setCyclePending,_n as transferSession,un as unbindSession};
1
+ var l=Object.defineProperty;var i=(n,e)=>l(n,"name",{value:e,configurable:!0});import{randomUUID as _}from"node:crypto";import{openDb as a,withCanvasWrite as c}from"./db.js";import{canonicalSessionFile as d}from"./canvas.js";import{conversationStore as f,installConversationStore as p}from"../conversation-store/index.js";import{createRowStore as L,relocateConversationHeader as I}from"../conversation-store/row-store.js";function u(){return new Date().toISOString()}i(u,"now");async function S(n,e,s){return await n.prepare("SELECT 1 FROM executions WHERE execution_id = ? AND node_id = ? AND state != 'exited'").get(s,e)!==void 0}i(S,"liveExecution");function w(n){return{bindingId:n.binding_id,sessionId:n.session_id??null,nodeId:n.node_id,transcriptPath:n.transcript_path??null,sourceSessionId:n.source_session_id??null,forkEntryId:n.fork_entry_id??null,createdAt:n.created_at,boundAt:n.bound_at,unboundAt:n.unbound_at??null}}i(w,"hydrate");async function b(n){const e=await(await a()).prepare("SELECT * FROM sessions WHERE node_id = ? AND unbound_at IS NULL").get(n);return e===void 0?null:w(e)}i(b,"readLiveSession");async function C(n){return(await(await a()).prepare("SELECT node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL").get(d(n)))?.node_id??null}i(C,"findNodeBySessionFileSql");async function h(n,e){return await c(async s=>{if(!await S(s,n,e.executionId))return"shutdown";if(e.sessionId!==null&&await s.prepare("SELECT 1 FROM sessions WHERE session_id = ? AND node_id <> ? LIMIT 1").get(e.sessionId,n)!==void 0)throw new Error(`session ${e.sessionId} is bound to another node`);const o=u(),r=e.transcriptPath===null?null:d(e.transcriptPath),t=await s.prepare("SELECT binding_id, session_id FROM sessions WHERE node_id = ? AND unbound_at IS NULL").get(n);if(t!==void 0&&t.session_id===e.sessionId)await s.prepare("UPDATE sessions SET transcript_path = ?, source_session_id = ?, bound_at = ? WHERE binding_id = ?").run(r,e.sourceSessionId??null,o,t.binding_id);else{t!==void 0&&await s.prepare("UPDATE sessions SET unbound_at = ? WHERE binding_id = ?").run(o,t.binding_id);const E=e.sessionId===null?void 0:await s.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? ORDER BY created_at DESC LIMIT 1").get(n,e.sessionId);E?await s.prepare("UPDATE sessions SET transcript_path = ?, source_session_id = ?, bound_at = ?, unbound_at = NULL WHERE binding_id = ?").run(r,e.sourceSessionId??null,o,E.binding_id):await s.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at) VALUES (?, ?, ?, ?, ?, NULL, ?, ?)").run(_(),e.sessionId,n,r,e.sourceSessionId??null,o,o)}return await s.prepare("UPDATE nodes SET cycle_pending = 0 WHERE node_id = ?").run(n),"bound"})}i(h,"bindSession");async function W(n,e){await c(async s=>{const o=u();await s.prepare("UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL").run(o,n),await s.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at) VALUES (?, ?, ?, ?, NULL, NULL, ?, ?)").run(_(),e.sessionId,n,e.transcriptPath===null?null:d(e.transcriptPath),o,o),await s.prepare("UPDATE nodes SET cycle_pending = 0 WHERE node_id = ?").run(n)})}i(W,"resetSession");async function H(n){return await c(async e=>{if(!await S(e,n.toNodeId,n.executionId))return"shutdown";if(await e.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND binding_id = ? AND unbound_at IS NULL").get(n.fromNodeId,n.fromBindingId)===void 0)return"changed";if(await e.prepare("SELECT run_id FROM nodes WHERE node_id IN (?, ?) AND run_id IS NOT NULL LIMIT 1").get(n.fromNodeId,n.toNodeId))throw new Error("run conversation transfer is not supported");const r=u();return await e.prepare("UPDATE sessions SET unbound_at = ? WHERE binding_id = ?").run(r,n.fromBindingId),await e.prepare("UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL").run(r,n.toNodeId),await e.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at) VALUES (?, ?, ?, ?, NULL, NULL, ?, ?)").run(_(),n.sessionId,n.toNodeId,n.transcriptPath===null?null:d(n.transcriptPath),r,r),await e.prepare("UPDATE nodes SET cycle_pending = 0 WHERE node_id = ?").run(n.toNodeId),"transferred"})}i(H,"transferSession");async function O(n){await(await a()).prepare("UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL").run(u(),n)}i(O,"unbindSession");async function P(n,e,s){const o=await(await a()).prepare("SELECT DISTINCT session_id FROM sessions WHERE node_id = ? AND session_id IS NOT NULL").all(n);let r=0;for(const t of o)await I(t.session_id,e,s)&&r++;return r}i(P,"relocateSessionCwd");async function x(n,e){await(await a()).prepare("UPDATE nodes SET cycle_pending = ? WHERE node_id = ?").run(e?1:0,n)}i(x,"setCyclePending");const N="unbound_at IS NULL DESC, created_at DESC, seq DESC";async function m(n){return(await(await a()).prepare(`SELECT node_id FROM sessions WHERE session_id = ? ORDER BY ${N} LIMIT 1`).get(n))?.node_id??null}i(m,"conversationOwner");function T(n){return L()}i(T,"openCanvasConversationStore");async function v(n){await f().importLegacy?.(n)}i(v,"repairRunConversations");async function M(){const n=T();return p(n),await n.importLegacy(),{async close(){p(void 0)}}}i(M,"serveCanvasConversationStore");export{h as bindSession,m as conversationOwner,C as findNodeBySessionFileSql,T as openCanvasConversationStore,b as readLiveSession,P as relocateSessionCwd,v as repairRunConversations,W as resetSession,M as serveCanvasConversationStore,x as setCyclePending,H as transferSession,O as unbindSession};
@@ -6,8 +6,10 @@ export interface RosterEdge {
6
6
  active: boolean;
7
7
  created: string;
8
8
  }
9
- /** Every in-tree node→node watch, oldest first — the roster's topology half. */
10
- export declare function rosterEdges(): Promise<RosterEdge[]>;
9
+ /** Every in-tree node→node watch, oldest first — the roster's topology half. `visible` is the node ids the requester may
10
+ * see: only edges between two of them are read, so each person on a shared database gets their own edges; `null` reads
11
+ * every edge. */
12
+ export declare function rosterEdges(visible?: ReadonlySet<string> | null): Promise<RosterEdge[]>;
11
13
  /** `subscriber` asks to watch `publisher` (an `ask` watch, in the tree). Re-running sets the
12
14
  * pair's tree subscriptions active or passive (`{"3":"next-turn"}`). */
13
15
  export declare function subscribe(subscriber: string, publisher: string, active?: boolean): Promise<void>;
@@ -1,19 +1,19 @@
1
- var O=Object.defineProperty;var i=(o,t)=>O(o,"name",{value:t,configurable:!0});import{openDb as c,withCanvasWrite as d}from"./db.js";import{getById as m,nodeObject as r}from"../graph/objects.js";import{recordWatch as y,unwatch as I,configFromJson as N,configToJson as b}from"../graph/watches.js";import{publish as T}from"../graph/events.js";import{DEFAULT_LISTEN as v}from"../graph/types.js";const D=["spawn","job-start","ask"],S="('spawn','job-start','ask')",R=[{threshold:3,delivery:"next-turn"}];function l(o){const t=o.find(a=>a.threshold<=7);return t!==void 0&&t.delivery!=="next-turn"}i(l,"wakesAtSeven");async function w(o,t,...a){const e=await o.prepareUncached(`SELECT wo.id AS from_id, oo.id AS to_id, ws.config_json, ws.created_at,
1
+ var O=Object.defineProperty;var i=(o,t)=>O(o,"name",{value:t,configurable:!0});import{openDb as c,withCanvasWrite as u}from"./db.js";import{getById as m,nodeObject as r}from"../graph/objects.js";import{recordWatch as y,unwatch as I,configFromJson as l,configToJson as b}from"../graph/watches.js";import{publish as T}from"../graph/events.js";import{DEFAULT_LISTEN as D}from"../graph/types.js";const v=["spawn","job-start","ask"],E="('spawn','job-start','ask')",R=[{threshold:3,delivery:"next-turn"}];function S(o){const t=o.find(a=>a.threshold<=7);return t!==void 0&&t.delivery!=="next-turn"}i(S,"wakesAtSeven");async function w(o,t,...a){const e=await o.prepareUncached(`SELECT wo.id AS from_id, oo.id AS to_id, ws.config_json, ws.created_at,
2
2
  wo.listen_json, wn.status AS from_status, onode.status AS to_status
3
3
  FROM watches w
4
4
  JOIN objects wo ON wo.id = w.watcher AND wo.type = 'node'
5
5
  JOIN objects oo ON oo.id = w.object AND oo.type = 'node'
6
- JOIN watch_subscriptions ws ON ws.watch = w.watch_id AND ws.started_by IN ${S}
6
+ JOIN watch_subscriptions ws ON ws.watch = w.watch_id AND ws.started_by IN ${E}
7
7
  JOIN nodes wn ON wn.node_id = wo.id
8
8
  JOIN nodes onode ON onode.node_id = oo.id
9
9
  WHERE w.in_tree = 1 ${t}
10
- ORDER BY ws.created_at, ws.seq`).all(...a),s=new Map;for(const n of e){const u=n.config_json,_=n.listen_json,f=u!=null?N(u):_!=null?N(_):v,p=`${n.from_id}\0${n.to_id}`,E=s.get(p);if(E){E.active||=l(f);continue}s.set(p,{from_id:n.from_id,to_id:n.to_id,active:l(f),created:n.created_at,from_status:n.from_status??null,to_status:n.to_status??null})}return[...s.values()]}i(w,"treeEdges");async function F(){return(await w(await c(),"")).map(({from_id:o,to_id:t,active:a,created:e})=>({from_id:o,to_id:t,active:a,created:e}))}i(F,"rosterEdges");async function W(o,t,a=!0){await d(async e=>{const s=await r(e,o),n=await r(e,t);await y(e,s,n,"ask"),await j(o,t,a),await T(e,{event:"watched",object:n,actor:s,attrs:{}})})}i(W,"subscribe");async function M(o,t){await d(async a=>{const e=await r(a,o),s=await r(a,t);for(const n of D)await I(a,e,s,{startedBy:n})})}i(M,"unsubscribe");async function j(o,t,a){await d(async e=>{await e.prepareUncached(`UPDATE watch_subscriptions SET config_json = ?
11
- WHERE started_by IN ${S}
12
- AND watch IN (SELECT watch_id FROM watches WHERE watcher = ? AND object = ?)`).run(a?null:b(R),(await r(e,o)).id,(await r(e,t)).id)})}i(j,"setSubscriptionActive");async function $(o){return(await w(await c(),"AND oo.id = ?",o)).map(t=>({node_id:t.from_id,active:t.active,created:t.created}))}i($,"subscribersOf");async function B(o){return(await w(await c(),"AND wo.id = ?",o)).map(t=>({node_id:t.to_id,active:t.active,created:t.created}))}i(B,"subscriptionsOf");function h(o){return`WITH RECURSIVE walk(id) AS (
10
+ ORDER BY ws.created_at, ws.seq`).all(...a),s=new Map;for(const n of e){const d=n.config_json,_=n.listen_json,f=d!=null?l(d):_!=null?l(_):D,N=`${n.from_id}\0${n.to_id}`,p=s.get(N);if(p){p.active||=S(f);continue}s.set(N,{from_id:n.from_id,to_id:n.to_id,active:S(f),created:n.created_at,from_status:n.from_status??null,to_status:n.to_status??null})}return[...s.values()]}i(w,"treeEdges");async function F(o=null){const t=o===null?null:[...o];return(t===null?await w(await c(),""):await w(await c(),"AND wo.id = ANY(?::text[]) AND oo.id = ANY(?::text[])",t,t)).map(({from_id:e,to_id:s,active:n,created:d})=>({from_id:e,to_id:s,active:n,created:d}))}i(F,"rosterEdges");async function W(o,t,a=!0){await u(async e=>{const s=await r(e,o),n=await r(e,t);await y(e,s,n,"ask"),await j(o,t,a),await T(e,{event:"watched",object:n,actor:s,attrs:{}})})}i(W,"subscribe");async function M(o,t){await u(async a=>{const e=await r(a,o),s=await r(a,t);for(const n of v)await I(a,e,s,{startedBy:n})})}i(M,"unsubscribe");async function j(o,t,a){await u(async e=>{await e.prepareUncached(`UPDATE watch_subscriptions SET config_json = ?
11
+ WHERE started_by IN ${E}
12
+ AND watch IN (SELECT watch_id FROM watches WHERE watcher = ? AND object = ?)`).run(a?null:b(R),(await r(e,o)).id,(await r(e,t)).id)})}i(j,"setSubscriptionActive");async function $(o){return(await w(await c(),"AND oo.id = ?",o)).map(t=>({node_id:t.from_id,active:t.active,created:t.created}))}i($,"subscribersOf");async function B(o){return(await w(await c(),"AND wo.id = ?",o)).map(t=>({node_id:t.to_id,active:t.active,created:t.created}))}i(B,"subscriptionsOf");function A(o){return`WITH RECURSIVE walk(id) AS (
13
13
  SELECT CAST(? AS text)
14
14
  UNION
15
15
  SELECT w.object FROM walk
16
16
  JOIN watches w ON w.watcher = walk.id AND w.in_tree = 1
17
17
  ${o?`JOIN objects oo ON oo.id = w.object AND oo.type = 'node' AND oo.kind IS DISTINCT FROM 'human'
18
18
  JOIN nodes onode ON onode.node_id = oo.id`:"JOIN nodes onode ON onode.node_id = w.object"}
19
- ) SELECT id FROM walk WHERE id != ?`}i(h,"subtreeSql");const H=h(!0),L=h(!1);async function P(o){const t=await c(),a=await m(t,o);return a?.type!=="node"?[]:(await t.prepareUncached(L).all(a.id,a.id)).map(e=>e.id)}i(P,"view");const A=new Set(["active","idle"]);async function Q(o){return(await w(await c(),"AND wo.id = ?",o)).some(t=>t.active&&A.has(t.to_status??""))}i(Q,"hasActiveLiveSubscription");async function V(o){return(await w(await c(),"AND oo.id = ?",o)).some(t=>t.active&&A.has(t.from_status??""))}i(V,"hasLiveSubscriber");export{L as SUBTREE_ALL_IDS_SQL,H as SUBTREE_IDS_SQL,Q as hasActiveLiveSubscription,V as hasLiveSubscriber,F as rosterEdges,j as setSubscriptionActive,W as subscribe,$ as subscribersOf,B as subscriptionsOf,M as unsubscribe,P as view};
19
+ ) SELECT id FROM walk WHERE id != ?`}i(A,"subtreeSql");const H=A(!0),x=A(!1);async function P(o){const t=await c(),a=await m(t,o);return a?.type!=="node"?[]:(await t.prepareUncached(x).all(a.id,a.id)).map(e=>e.id)}i(P,"view");const h=new Set(["active","idle"]);async function Q(o){return(await w(await c(),"AND wo.id = ?",o)).some(t=>t.active&&h.has(t.to_status??""))}i(Q,"hasActiveLiveSubscription");async function V(o){return(await w(await c(),"AND oo.id = ?",o)).some(t=>t.active&&h.has(t.from_status??""))}i(V,"hasLiveSubscriber");export{x as SUBTREE_ALL_IDS_SQL,H as SUBTREE_IDS_SQL,Q as hasActiveLiveSubscription,V as hasLiveSubscriber,F as rosterEdges,j as setSubscriptionActive,W as subscribe,$ as subscribersOf,B as subscriptionsOf,M as unsubscribe,P as view};
@@ -68,6 +68,13 @@ export interface LaunchSpec {
68
68
  systemPrompt?: string;
69
69
  /** Extra env injected into the pi process. */
70
70
  env: Record<string, string>;
71
+ /** A managed-worktree request for a node in a Blaxel sandbox (D102): the start script switches the image's clone of
72
+ * `repository` to a new `branch` cut from `base`. Absent on a Mac, where crtrd makes the worktree. */
73
+ worktree?: {
74
+ repository: string;
75
+ branch: string;
76
+ base: string;
77
+ };
71
78
  }
72
79
  /** The repository fingerprint a refusal was decided against. An examination
73
80
  * that reads the same values can skip the whole proof cascade: nothing about
@@ -1 +1 @@
1
- var M=Object.defineProperty;var c=(e,t)=>M(e,"name",{value:t,configurable:!0});import{randomBytes as k}from"node:crypto";import{spawn as _}from"node:child_process";import{PROCESS_GROUP_KILL_GRACE_MS as S,posixProcessGroupSpawnOptions as R,terminateProcessGroup as V}from"../../../hook-process.js";import{isRecord as L}from"../../../shared/predicates.js";import{diag as x}from"../../io.js";import{LIFECYCLE_CATALOG as O}from"../lifecycle-catalog.js";import{getNode as A}from"../../canvas/canvas.js";import{buildAppProcessEnv as C}from"../../runtime/spawn-env.js";import{appProcessSandboxed as D,launchAppProcess as G}from"../../spaces/process.js";const P=10*1024*1024;async function H(e,t,n){try{const r=await B(e,t,n);r.ok||x(`crtr: ${g(e)} failed: ${r.error.code}: ${r.error.message}`)}catch(r){x(`crtr: ${g(e)} failed: ${w(r)}`)}}c(H,"invokeLifecycleHook");async function te(e,t){if(e.length===0)return;const{hookTimeoutMs:n,eventBudgetMs:r}=O[e[0].event],s=Date.now()+r;for(const o of e){const a=s-Date.now(),u=Math.min(n,a-S);if(u<=0){x(`crtr: lifecycle hook event ${o.event} exceeded its ${r}ms aggregate budget; skipped ${g(o)}`);return}await H(o,t,u)}}c(te,"invokeLifecycleHooks");async function re(e,t){if(e.length===0)return null;const{hookTimeoutMs:n,eventBudgetMs:r}=O["node:create"],s=Date.now()+r;for(const o of e){const a=s-Date.now(),u=Math.min(n,a-S);if(u<=0)return T(o,`lifecycle hook event node:create exceeded its ${r}ms aggregate budget`);try{const i=await B(o,t,u);if(!i.ok)return{hook:o,message:i.error.message,next:i.error.next??E(o)}}catch(i){return T(o,w(i))}}return null}c(re,"invokeBlockingLifecycleHooks");function T(e,t){return{hook:e,message:`${g(e)} failed: ${t}`,next:E(e)}}c(T,"refusal");function ne(e){return E(e)}c(ne,"lifecycleHookAttribution");async function B(e,t,n){const r=e.event==="node:create"?N(e,t):I(e,t),s=D()?await J(e,t,JSON.stringify(r),n):await q(e,JSON.stringify(r),n);if(s.signal!==null)throw new Error(`executable was killed by signal ${s.signal}`);const o=K(s.stdout);return o.ok&&s.status!==0&&x(`crtr: ${g(e)} returned ok:true but exited ${s.status} (honoring the envelope)`),o}c(B,"executeLifecycleHook");function I(e,t){return{protocolVersion:1,op:e.op,event:e.event,phase:e.phase,operationId:k(16).toString("hex"),node:t.node,runtime:t.runtime,context:{cwd:t.node.cwd}}}c(I,"nodeRequest");function N(e,t){return{protocolVersion:1,op:e.op,event:"node:create",phase:"before",operationId:k(16).toString("hex"),create:t.create,runtime:t.runtime,context:{cwd:t.create.cwd}}}c(N,"createRequest");async function J(e,t,n,r){const s=e.event==="node:create",o=s?t.create.cwd:t.node.cwd,a=s?void 0:t.node.id,u=s?t.app:(await A(a))?.grantee;if(!u)throw new Error(`missing app for lifecycle hook ${e.event}`);const i=C(o,t.runtime.profile,u),m=await G({...s?{app:u}:{nodeId:a},command:[e.executable,"--crtr-hook-protocol","1"],cwd:e.plugin.root,pluginRoot:e.plugin.root,env:i,stdin:n,stdoutBytes:P,stderrBytes:64*1024});let l;try{const p=await Promise.race([m.completion,new Promise((b,f)=>{l=setTimeout(()=>{m.stop().then(()=>f(new Error(`executable timed out after ${r}ms`)),f)},r)})]);if(p.stdoutTruncated)throw new Error("stdout exceeded the 10 MiB cap");return{stdout:p.stdout,status:p.exitCode,signal:p.signal}}finally{l&&clearTimeout(l)}}c(J,"runSandboxedLifecycleProcess");async function q(e,t,n){let r;try{r=_(e.executable,["--crtr-hook-protocol","1"],{cwd:e.plugin.root,env:process.env,stdio:["pipe","pipe","inherit"],...R()})}catch(s){throw new Error(`executable could not be run (${w(s)})`)}return new Promise((s,o)=>{let a=0;const u=[];let i,m=!1,l=!1,p=null,b=null,f;const y=c(()=>{if(!(!m||i!==void 0&&!l)){if(f!==void 0&&clearTimeout(f),i?.kind==="timeout")return o(new Error(`executable timed out after ${n}ms`));if(i?.kind==="output_limit")return o(new Error("stdout exceeded the 10 MiB cap"));if(i?.kind==="spawn")return o(new Error(`executable could not be run (${w(i.error)})`));if(i?.kind==="cleanup")return o(new Error(`executable cleanup failed (${w(i.error)})`));s({stdout:Buffer.concat(u).toString("utf8"),status:p,signal:b})}},"finish"),$=c(d=>{i===void 0&&(i=d,f!==void 0&&clearTimeout(f),V(r).then(()=>{l=!0,y()},h=>{i={kind:"cleanup",error:h},l=!0,y()}))},"terminate");r.stdout.on("data",d=>{if(a+=d.byteLength,a>P)return $({kind:"output_limit"});u.push(d)}),r.once("error",d=>{if(r.pid===void 0){i={kind:"spawn",error:d},m=!0,l=!0,y();return}$({kind:"spawn",error:d})}),r.once("close",(d,h)=>{p=d,b=h,m=!0,y()}),f=setTimeout(()=>$({kind:"timeout"}),n),r.stdin.end(t)})}c(q,"runLifecycleProcess");function K(e){let t;try{t=JSON.parse(e.trim())}catch{throw new Error("stdout was not a single JSON envelope")}if(!L(t)||t.protocolVersion!==1)throw new Error("stdout was not a protocolVersion: 1 envelope");if(t.ok===!0&&v(t,["protocolVersion","ok"]))return{protocolVersion:1,ok:!0};if(t.ok!==!1||!v(t,["protocolVersion","ok","error"]))throw new Error("response envelope had missing or extra fields");const n=t.error;if(!L(n)||!v(n,["code","message"],["next"])||typeof n.code!="string"||n.code===""||typeof n.message!="string"||n.message===""||n.next!==void 0&&typeof n.next!="string")throw new Error("failure envelope had invalid error fields");return{protocolVersion:1,ok:!1,error:{code:n.code,message:n.message,...n.next===void 0?{}:{next:n.next}}}}c(K,"parseEnvelope");function v(e,t,n=[]){return t.every(r=>Object.hasOwn(e,r))&&Object.keys(e).every(r=>t.includes(r)||n.includes(r))}c(v,"exactKeys");function g(e){return`plugin "${e.plugin.name}" lifecycle hook "${e.op}" for ${e.event}`}c(g,"label");function E(e){return`Refused by plugin ${e.plugin.name}, op ${e.op}.`}c(E,"attribution");function w(e){return e instanceof Error?e.message:String(e)}c(w,"describeError");export{re as invokeBlockingLifecycleHooks,H as invokeLifecycleHook,te as invokeLifecycleHooks,ne as lifecycleHookAttribution};
1
+ var M=Object.defineProperty;var c=(e,t)=>M(e,"name",{value:t,configurable:!0});import{randomBytes as k}from"node:crypto";import{spawn as _}from"node:child_process";import{PROCESS_GROUP_KILL_GRACE_MS as S,posixProcessGroupSpawnOptions as R,terminateProcessGroup as V}from"../../../hook-process.js";import{isRecord as L}from"../../../shared/predicates.js";import{diag as x}from"../../io.js";import{LIFECYCLE_CATALOG as O}from"../lifecycle-catalog.js";import{getNode as A}from"../../canvas/canvas.js";import{buildAppProcessEnv as C}from"../../runtime/spawn-env-rows.js";import{appProcessSandboxed as D,launchAppProcess as G}from"../../spaces/process.js";const P=10*1024*1024;async function H(e,t,n){try{const r=await B(e,t,n);r.ok||x(`crtr: ${g(e)} failed: ${r.error.code}: ${r.error.message}`)}catch(r){x(`crtr: ${g(e)} failed: ${w(r)}`)}}c(H,"invokeLifecycleHook");async function te(e,t){if(e.length===0)return;const{hookTimeoutMs:n,eventBudgetMs:r}=O[e[0].event],s=Date.now()+r;for(const o of e){const a=s-Date.now(),u=Math.min(n,a-S);if(u<=0){x(`crtr: lifecycle hook event ${o.event} exceeded its ${r}ms aggregate budget; skipped ${g(o)}`);return}await H(o,t,u)}}c(te,"invokeLifecycleHooks");async function re(e,t){if(e.length===0)return null;const{hookTimeoutMs:n,eventBudgetMs:r}=O["node:create"],s=Date.now()+r;for(const o of e){const a=s-Date.now(),u=Math.min(n,a-S);if(u<=0)return T(o,`lifecycle hook event node:create exceeded its ${r}ms aggregate budget`);try{const i=await B(o,t,u);if(!i.ok)return{hook:o,message:i.error.message,next:i.error.next??E(o)}}catch(i){return T(o,w(i))}}return null}c(re,"invokeBlockingLifecycleHooks");function T(e,t){return{hook:e,message:`${g(e)} failed: ${t}`,next:E(e)}}c(T,"refusal");function ne(e){return E(e)}c(ne,"lifecycleHookAttribution");async function B(e,t,n){const r=e.event==="node:create"?N(e,t):I(e,t),s=D()?await J(e,t,JSON.stringify(r),n):await q(e,JSON.stringify(r),n);if(s.signal!==null)throw new Error(`executable was killed by signal ${s.signal}`);const o=K(s.stdout);return o.ok&&s.status!==0&&x(`crtr: ${g(e)} returned ok:true but exited ${s.status} (honoring the envelope)`),o}c(B,"executeLifecycleHook");function I(e,t){return{protocolVersion:1,op:e.op,event:e.event,phase:e.phase,operationId:k(16).toString("hex"),node:t.node,runtime:t.runtime,context:{cwd:t.node.cwd}}}c(I,"nodeRequest");function N(e,t){return{protocolVersion:1,op:e.op,event:"node:create",phase:"before",operationId:k(16).toString("hex"),create:t.create,runtime:t.runtime,context:{cwd:t.create.cwd}}}c(N,"createRequest");async function J(e,t,n,r){const s=e.event==="node:create",o=s?t.create.cwd:t.node.cwd,a=s?void 0:t.node.id,u=s?t.app:(await A(a))?.grantee;if(!u)throw new Error(`missing app for lifecycle hook ${e.event}`);const i=await C(o,t.runtime.profile,u),m=await G({...s?{app:u}:{nodeId:a},command:[e.executable,"--crtr-hook-protocol","1"],cwd:e.plugin.root,pluginRoot:e.plugin.root,env:i,stdin:n,stdoutBytes:P,stderrBytes:64*1024});let l;try{const p=await Promise.race([m.completion,new Promise((b,f)=>{l=setTimeout(()=>{m.stop().then(()=>f(new Error(`executable timed out after ${r}ms`)),f)},r)})]);if(p.stdoutTruncated)throw new Error("stdout exceeded the 10 MiB cap");return{stdout:p.stdout,status:p.exitCode,signal:p.signal}}finally{l&&clearTimeout(l)}}c(J,"runSandboxedLifecycleProcess");async function q(e,t,n){let r;try{r=_(e.executable,["--crtr-hook-protocol","1"],{cwd:e.plugin.root,env:process.env,stdio:["pipe","pipe","inherit"],...R()})}catch(s){throw new Error(`executable could not be run (${w(s)})`)}return new Promise((s,o)=>{let a=0;const u=[];let i,m=!1,l=!1,p=null,b=null,f;const y=c(()=>{if(!(!m||i!==void 0&&!l)){if(f!==void 0&&clearTimeout(f),i?.kind==="timeout")return o(new Error(`executable timed out after ${n}ms`));if(i?.kind==="output_limit")return o(new Error("stdout exceeded the 10 MiB cap"));if(i?.kind==="spawn")return o(new Error(`executable could not be run (${w(i.error)})`));if(i?.kind==="cleanup")return o(new Error(`executable cleanup failed (${w(i.error)})`));s({stdout:Buffer.concat(u).toString("utf8"),status:p,signal:b})}},"finish"),$=c(d=>{i===void 0&&(i=d,f!==void 0&&clearTimeout(f),V(r).then(()=>{l=!0,y()},h=>{i={kind:"cleanup",error:h},l=!0,y()}))},"terminate");r.stdout.on("data",d=>{if(a+=d.byteLength,a>P)return $({kind:"output_limit"});u.push(d)}),r.once("error",d=>{if(r.pid===void 0){i={kind:"spawn",error:d},m=!0,l=!0,y();return}$({kind:"spawn",error:d})}),r.once("close",(d,h)=>{p=d,b=h,m=!0,y()}),f=setTimeout(()=>$({kind:"timeout"}),n),r.stdin.end(t)})}c(q,"runLifecycleProcess");function K(e){let t;try{t=JSON.parse(e.trim())}catch{throw new Error("stdout was not a single JSON envelope")}if(!L(t)||t.protocolVersion!==1)throw new Error("stdout was not a protocolVersion: 1 envelope");if(t.ok===!0&&v(t,["protocolVersion","ok"]))return{protocolVersion:1,ok:!0};if(t.ok!==!1||!v(t,["protocolVersion","ok","error"]))throw new Error("response envelope had missing or extra fields");const n=t.error;if(!L(n)||!v(n,["code","message"],["next"])||typeof n.code!="string"||n.code===""||typeof n.message!="string"||n.message===""||n.next!==void 0&&typeof n.next!="string")throw new Error("failure envelope had invalid error fields");return{protocolVersion:1,ok:!1,error:{code:n.code,message:n.message,...n.next===void 0?{}:{next:n.next}}}}c(K,"parseEnvelope");function v(e,t,n=[]){return t.every(r=>Object.hasOwn(e,r))&&Object.keys(e).every(r=>t.includes(r)||n.includes(r))}c(v,"exactKeys");function g(e){return`plugin "${e.plugin.name}" lifecycle hook "${e.op}" for ${e.event}`}c(g,"label");function E(e){return`Refused by plugin ${e.plugin.name}, op ${e.op}.`}c(E,"attribution");function w(e){return e instanceof Error?e.message:String(e)}c(w,"describeError");export{re as invokeBlockingLifecycleHooks,H as invokeLifecycleHook,te as invokeLifecycleHooks,ne as lifecycleHookAttribution};
@@ -98,7 +98,7 @@ export declare function isInstalledGeneration(name: string, scope: Scope, root:
98
98
  * are recorded either way, because both describe the bytes now at the root. */
99
99
  export declare function recordCheckedGeneration(scope: Scope, name: string, etag: string | undefined, options: {
100
100
  swapped: boolean;
101
- }): void;
101
+ }): Promise<void>;
102
102
  /** Every file lands by temp file plus rename, fsynced, so a crash mid-write leaves the previous bytes or
103
103
  * the whole new ones — never a 0-byte `plugin.json` or `commands.json` that crashes the next boot. */
104
104
  export declare function writeStagedBundle(stagingRoot: string, manifest: PluginManifest, bundle: ValidatedBundle): void;
@@ -1 +1 @@
1
- var P=Object.defineProperty;var c=(e,o)=>P(e,"name",{value:o,configurable:!0});import{Parser as J}from"tar";import{createHash as I}from"node:crypto";import{chmodSync as W,renameSync as k}from"node:fs";import{dirname as $,join as w,resolve as V,sep as z}from"node:path";import{validateCommandManifest as B}from"../../api/command-manifest/manifest.js";import{ensureScopeInitialized as R,invalidPluginKindsReasons as F,readConfig as M,updateConfig as D,updateState as T}from"../config.js";import{general as x}from"../errors.js";import{atomicWriteJson as C,atomicWriteText as q,ensureDir as j,nowIso as E,pathExists as _,removePath as S,syncDirectory as H,syncTree as U}from"../fs-utils.js";import{invalidPageComponentsReasons as K,normalizePageComponents as G}from"../human/page-catalog.js";import{validateMemoryExtensionDeclarations as X}from"../plugin-extensions.js";import{objectBlocksShapeReasons as Z}from"../plugin-objects-manifest.js";import{validateBundleTransport as L}from"./endpoint.js";const O=/^[A-Za-z0-9_-]{1,64}$/,Y=new Set(["id","major","groups"]),Q=new Set(["group","consent_line","rate","who","allowlist"]),ee=new Set(["own","user","allowlist","any"]),ne=/^team:[a-z0-9]+$/,oe=new Set(["bundleVersion","kinds","page_components","memory_extensions","version","transport","provider","object_kinds","scopes","groups"]);function re(e,o,n,r,s,a){return{code:e,message:o,received:n,expected:r,next:s,...a!==void 0?{path:a}:{}}}c(re,"issue");function u(e,o,n,r,s){return re("plugin_bundle_invalid",e,o,n,r,s)}c(u,"bundleInvalid");function se(e){if(e[0]===31&&e[1]===139)return u("bundle archive must be uncompressed tar data","gzip-compressed archive","an uncompressed tar archive","Serve the archive without compression.");if(e.length===0||e.length%512!==0)return u("bundle archive must be a non-empty whole number of 512-byte tar blocks",`${e.length} bytes`,"a non-empty tar stream with a 512-byte block length","Serve one complete uncompressed tar archive.");if(e.length<1024||!e.subarray(-1024).every(o=>o===0))return u("bundle archive is missing the required two zero-block terminator","archive tail is not 1024 zero bytes","a complete tar stream ending in two 512-byte zero blocks","Serve the complete uncompressed tar archive.")}c(se,"archiveEnvelopeIssue");function te(e,o){if(e.length===0||e.includes("\0")||e.includes("\\")||e.startsWith("/")||/^[A-Za-z]:($|\/)/.test(e))return u("bundle member path must be a safe relative POSIX path",e,"a relative path without absolute, drive-letter, backslash, or NUL forms","Use a safe relative POSIX member path.",e);if(o==="file"&&e.endsWith("/"))return u("only directory members may have a trailing slash",e,"a regular-file path without a trailing slash","Emit this member as a regular file path.",e);if((o==="directory"&&e.endsWith("/")?e.slice(0,-1):e).split("/").some(r=>r===""||r==="."||r===".."))return u("bundle member path contains an unsafe segment",e,"non-empty segments other than . or ..","Use a safe relative POSIX member path.",e)}c(te,"validateMemberName");function ie(e,o){if(o==="file")return e==="bundle.json"||e==="commands.json"||e==="hooks.json"||e.startsWith("memory/")&&e.endsWith(".md")||e.startsWith("hooks/")?void 0:u("bundle contains an unsupported file member",e,"bundle.json, commands.json, hooks.json, memory/**/*.md, or hooks/**","Remove unsupported members from the archive.",e);if(!(e==="memory"||e.startsWith("memory/")||e==="hooks"||e.startsWith("hooks/")))return u("bundle contains an unsupported directory member",e,"memory or hooks, or a directory below either subtree","Remove unsupported directory members from the archive.",e)}c(ie,"validateMemberShape");function de(e){return e.type==="directory"&&e.path.endsWith("/")?e.path.slice(0,-1):e.path}c(de,"materializedPath");function ae(e){const o=new Map;for(const n of e){const r=de(n),s=o.get(r);if(s!==void 0)return u(s===n.type?"bundle contains a duplicate member":"bundle claims one path as both a file and a directory",`${r} (${s} and ${n.type})`,"each materialized path exactly once, with one type","Emit each archive path once, as a single member type.",r);o.set(r,n.type)}for(const[n,r]of o)if(r==="file"){for(const s of o.keys())if(s.startsWith(`${n}/`))return u("bundle nests a member beneath a regular-file path",`${s} beneath file ${n}`,"members nested only beneath directory paths","Emit the parent path as a directory or remove the nested member.",s)}}c(ae,"validateMemberTree");async function ue(e){const o=[],n=new Set;let r;return await new Promise((s,a)=>{const p=new J({strict:!0,maxMetaEntrySize:Number.MAX_SAFE_INTEGER,gzip:!1,brotli:!1,zstd:!1,onReadEntry:c(t=>{const d=t.path,f=t.type==="File"||t.type==="OldFile"?"file":t.type==="Directory"?"directory":void 0;if(f===void 0||t.unsupported||t.ignore||t.linkpath!==void 0){r??=u("bundle member must be a regular file or directory without a link target",`${d} (${t.type})`,"a regular file or directory member without link metadata","Remove links and unsupported member types from the archive.",d),t.resume();return}r??=te(d,f),r??=ie(d,f),n.has(d)&&(r??=u("bundle contains a duplicate member",d,"each member path exactly once","Emit each archive member only once.",d)),n.add(d);const i=[];t.on("data",b=>i.push(b)),t.on("end",()=>{r===void 0&&o.push({path:d,type:f,bytes:new Uint8Array(Buffer.concat(i))})})},"onReadEntry")});p.on("ignoredEntry",t=>{const d=t.path;r??=u("bundle contains an ignored or unsupported archive member",`${d} (${t.type})`,"only supported regular file or directory members","Remove ignored or unsupported members from the archive.",d),t.resume()}),p.once("error",a),p.once("end",s);try{p.end(Buffer.from(e))}catch(t){a(t)}}).catch(s=>{r??=u("bundle archive could not be parsed",s instanceof Error?s.message:String(s),"a valid complete uncompressed tar archive","Fix the archive format and serve it again.")}),r===void 0?{members:o}:{issue:r}}c(ue,"parseArchive");function N(e){return typeof e=="number"&&Number.isInteger(e)&&e>=1}c(N,"isPositiveInt");function le(e){const o=c((t,d,f,i)=>({issue:u(t,d,f,"Fix the provider block in bundle.json.",`bundle.json.${i}`)}),"bad");if(typeof e!="object"||e===null||Array.isArray(e))return o("bundle.json provider must be an object",JSON.stringify(e),"{ id, major, groups }","provider");const n=e,r=Object.keys(n).filter(t=>!Y.has(t));if(r.length>0)return o("bundle.json provider has unknown keys",r.join(", "),"only: id, major, groups","provider");if(typeof n.id!="string"||!O.test(n.id))return o("provider id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(n.id),'a provider id of 1-64 letters, digits, "_" or "-"',"provider.id");if(!N(n.major))return o("provider major must be an integer of at least 1",JSON.stringify(n.major),"the served major as an integer >= 1","provider.major");const s=n.groups;if(!Array.isArray(s)||s.length===0)return o("provider groups must be a non-empty array",JSON.stringify(s),"at least one { group, consent_line, rate?, who, allowlist }","provider.groups");const a=[],p=new Set;for(let t=0;t<s.length;t++){const d=`provider.groups[${t}]`,f=s[t];if(typeof f!="object"||f===null||Array.isArray(f))return o("provider group must be an object",JSON.stringify(f),"{ group, consent_line, rate?, who, allowlist }",d);const i=f,b=Object.keys(i).filter(v=>!Q.has(v));if(b.length>0)return o("provider group has unknown keys",b.join(", "),"only: group, consent_line, rate, who, allowlist",d);if(typeof i.group!="string"||!O.test(i.group))return o("provider group id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(i.group),'a group id of 1-64 letters, digits, "_" or "-"',`${d}.group`);if(p.has(i.group))return o("provider group ids must be unique",i.group,"each group declared once",`${d}.group`);if(p.add(i.group),typeof i.consent_line!="string"||i.consent_line.trim().length===0)return o("provider group consent_line must be a non-empty string",JSON.stringify(i.consent_line),"the consent line shown for this group",`${d}.consent_line`);const h=i.rate;if(h!=null&&(typeof h!="number"||!Number.isFinite(h)||h<0))return o("provider group rate must be a non-negative number or null",JSON.stringify(h),"a finite number >= 0, null, or omitted",`${d}.rate`);if(typeof i.who!="string"||!(ee.has(i.who)||ne.test(i.who)))return o("provider group who must be own|user|allowlist|any|team:<id>",JSON.stringify(i.who),"own | user | allowlist | any | team:<id> (id of lowercase letters and digits)",`${d}.who`);const m=i.allowlist;if(!Array.isArray(m)||!m.every(v=>typeof v=="string"&&v.length>0))return o("provider group allowlist must be an array of non-empty strings",JSON.stringify(m),"an array of app principals ([] unless who is allowlist)",`${d}.allowlist`);if(m.length>0&&i.who!=="allowlist")return o("provider group allowlist must be empty unless who is allowlist",`who ${i.who}`,'who: "allowlist", or allowlist: []',`${d}.allowlist`);a.push({group:i.group,consent_line:i.consent_line,...h!==void 0?{rate:h}:{},who:i.who,allowlist:m})}return{provider:{id:n.id,major:n.major,groups:a}}}c(le,"parseProviderBlock");function pe(e){const o=[],n=c((r,s)=>{const a=[...s,r.name];if(r.kind==="leaf")o.push({path:a.join(" "),leaf:r});else for(const p of r.children)n(p,a)},"walk");for(const r of e.roots)n(r,[]);for(const r of e.coreMounts)n(r.node,r.parent);return o}c(pe,"collectLeaves");function ce(e,o){const n=e.provider;if(n===void 0)return;if(e.transport===void 0)return u("a provider bundle must declare its transport","(missing)",'transport { kind: "http", endpoint } in bundle.json',"Add the provider endpoint as bundle.json transport.","bundle.json.transport");if(e.transport.kind==="http"&&e.transport.authEnv!==void 0)return u("a provider bundle must not declare authEnv",e.transport.authEnv,"no authEnv: the runtime presents its own outbound token","Remove transport.authEnv from bundle.json.","bundle.json.transport.authEnv");const r=new Set(n.groups.map(s=>s.group));for(const{path:s,leaf:a}of pe(o)){const p="group"in a?a.group:void 0;if(p===void 0)return u(`provider leaf "${s}" has no group`,s,"a group on every leaf of a provider bundle",`Add a group to leaf "${s}".`,`commands.json:${s}.group`);if(!r.has(p))return u(`provider leaf "${s}" names undeclared group "${p}"`,p,`one of: ${[...r].join(", ")}`,`Declare group "${p}" in the provider block, or move leaf "${s}" to a declared group.`,`commands.json:${s}.group`);if("rest"in a&&a.rest.streaming===!0)return u(`provider leaf "${s}" streams`,"rest.streaming: true","no streaming leaves in a provider bundle",`Make leaf "${s}" return one response.`,`commands.json:${s}.rest.streaming`)}}c(ce,"providerBundleIssue");function me(e){let o;try{o=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issue:u("bundle.json is not valid JSON","invalid JSON",'{"bundleVersion":1}',"Write valid bundle metadata.","bundle.json")}}if(typeof o!="object"||o===null||Array.isArray(o))return{issue:u("bundle.json must be a JSON object",JSON.stringify(o),"an object with bundleVersion set to 1 and optionally kinds",'Set bundle.json to {"bundleVersion":1}.',"bundle.json")};const n=o,r=Object.keys(n).filter(d=>!oe.has(d));if(n.bundleVersion!==1||r.length>0)return{issue:u("bundle.json must contain bundleVersion 1 and at most version, transport, provider, kinds, page_components, memory_extensions, object_kinds, scopes, and groups",JSON.stringify(o),"an object with bundleVersion set to 1 and optional version, transport, provider, kinds, page_components, memory_extensions, object_kinds, scopes, and groups",'Set bundle.json to {"bundleVersion":1} plus optional contribution blocks.',"bundle.json")};if(n.kinds!==void 0){const d=F(n.kinds);if(d.length>0)return{issue:u("bundle.json kinds block is invalid",d.join("; "),"each entry a full KindConfig (whenToUse required) or a sparse patch of launch fields","Fix the kinds entries in bundle.json.","bundle.json")}}if(n.page_components!==void 0){const d=K(n.page_components);if(d.length>0)return{issue:u("bundle.json page_components block is invalid",d.join("; "),"an array of page component registrations, each a kind string or {kind, description?, useWhen?, doc?, display?}","Fix the page_components entries in bundle.json.","bundle.json")}}const s=Z(n);if(n.provider!==void 0&&n.scopes!==void 0&&s.push("scopes is for local plugins; a provider gets its scopes from directory consent"),s.length>0)return{issue:u("bundle.json object_kinds or scopes block is invalid",s.join("; "),"object_kinds keyed by kind name with description, events and optional read.command; scopes an array of crtr:objects:<verb>:<target> strings (local plugins only)","Fix the object_kinds and scopes blocks in bundle.json.","bundle.json")};let a;if(n.memory_extensions!==void 0)try{a=X(n.memory_extensions)}catch(d){return{issue:u("bundle.json memory_extensions block is invalid",d instanceof Error?d.message:String(d),"a valid memory_extensions declaration object","Fix the memory_extensions declarations in bundle.json.","bundle.json")}}if(n.version!==void 0&&!N(n.version))return{issue:u("bundle.json version must be an integer of at least 1",JSON.stringify(n.version),"the plugin major as an integer >= 1, or omitted","Fix version in bundle.json.","bundle.json.version")};let p;if(n.transport!==void 0){const d=L(n.transport);if(d.transport===void 0)return{issue:u("bundle.json transport is invalid",d.errors.join("; "),'{ kind: "http", endpoint, authEnv? } or { kind: "machine" }',"Fix transport in bundle.json.","bundle.json.transport")};p=d.transport}let t;if(n.provider!==void 0){const d=le(n.provider);if(d.issue!==void 0)return{issue:d.issue};if(t=d.provider,n.version!==void 0&&n.version!==t.major)return{issue:u("bundle.json provider major must equal version",`version ${String(n.version)}, major ${t.major}`,"one major","Set provider.major and version to the same integer.","bundle.json.provider.major")}}return{...n.version!==void 0?{version:n.version}:{},...p!==void 0?{transport:p}:{},...t!==void 0?{provider:t}:{},...n.kinds!==void 0?{kinds:n.kinds}:{},...n.page_components!==void 0?{page_components:G(n.page_components)}:{},...a!==void 0?{memory_extensions:a}:{},...n.object_kinds!==void 0?{object_kinds:n.object_kinds}:{},...n.scopes!==void 0?{scopes:n.scopes}:{}}}c(me,"parseBundleMetadata");function fe(e,o,n,r=new Set){let s;try{s=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issues:[u("commands.json is not valid JSON","invalid JSON","a valid command-manifest JSON object","Regenerate commands.json.","commands.json")]}}const a=B(s,{transport:"http",reservedCoreNames:o,extensibleCoreBranches:r,...n!==void 0?{coreCommandPaths:n}:{}});return{...a.manifest!==void 0?{commands:a.manifest}:{},issues:a.issues}}c(fe,"parseCommands");async function he(e,o){const n=se(e);if(n!==void 0)return{issues:[n]};const r=await ue(e);if(r.issue!==void 0||r.members===void 0)return{issues:[r.issue]};const s=ae(r.members);if(s!==void 0)return{issues:[s]};const a=r.members.find(l=>l.path==="bundle.json"&&l.type==="file"),p=r.members.find(l=>l.path==="commands.json"&&l.type==="file");if(a===void 0||p===void 0)return{issues:[u("bundle must contain exactly one bundle.json and one commands.json regular file",`bundle.json=${a!==void 0}, commands.json=${p!==void 0}`,"one regular bundle.json and one regular commands.json member","Add the required metadata members to the archive.")]};const t=r.members.find(l=>l.path==="hooks.json"&&l.type==="file"),d=r.members.filter(l=>l.type==="file"&&l.path.startsWith("hooks/"));if((t!==void 0||r.members.some(l=>l.path==="hooks"||l.path.startsWith("hooks/")))&&(t===void 0||!d.some(l=>l.path==="hooks/dispatch")))return{issues:[u("bundle hook payload must contain both hooks.json and hooks/dispatch regular files",`hooks.json=${t!==void 0}, hooks/dispatch=${d.some(l=>l.path==="hooks/dispatch")}`,"both hooks.json and hooks/dispatch regular files","Add both required hook members or remove the hook payload.")]};const i=me(a.bytes);if(i.issue!==void 0)return{issues:[i.issue]};const b=fe(p.bytes,o.reservedCoreNames,o.coreCommandPaths,o.extensibleCoreBranches);if(b.commands===void 0)return{issues:b.issues};const h=b.commands.version;if(i.version!==void 0&&h!==void 0&&i.version!==h)return{issues:[u("bundle.json version and commands.json version differ",`bundle.json ${i.version}, commands.json ${h}`,"one plugin major","Emit the same version in bundle.json and commands.json.","bundle.json.version")]};const m=i.version??h;if(i.provider!==void 0&&m===void 0)return{issues:[u("a provider bundle requires a plugin version","no version in bundle.json or commands.json",`version ${i.provider.major}, the provider major`,"Emit the provider major as the plugin version.","bundle.json.version")]};if(i.provider!==void 0&&m!==i.provider.major)return{issues:[u("bundle provider major must equal the plugin version",`version ${m}, major ${i.provider.major}`,"one major","Emit the provider major as the plugin version.","bundle.json.provider.major")]};const v=ce(i,b.commands);return v!==void 0?{issues:[v]}:{bundle:{bundleVersion:1,...m!==void 0?{version:m}:{},...i.transport!==void 0?{transport:i.transport}:{},...i.provider!==void 0?{provider:i.provider}:{},...i.kinds!==void 0?{kinds:i.kinds}:{},...i.page_components!==void 0?{page_components:i.page_components}:{},...i.memory_extensions!==void 0?{memory_extensions:i.memory_extensions}:{},...i.object_kinds!==void 0?{object_kinds:i.object_kinds}:{},...i.scopes!==void 0?{scopes:i.scopes}:{},commands:b.commands,commandsBytes:p.bytes,directories:r.members.filter(l=>l.type==="directory").map(l=>({path:l.path})),memory:r.members.filter(l=>l.type==="file"&&l.path.startsWith("memory/")).map(l=>({path:l.path,bytes:l.bytes})),...t!==void 0?{hooks:{manifest:t.bytes,files:d.map(l=>({path:l.path,bytes:l.bytes}))}}:{}},issues:[]}}c(he,"validatePluginBundle");function be(e){return I("sha256").update(e).digest("hex").slice(0,12)}c(be,"bundleVersion");function Je(e,o,n,r){return M(o).plugins[e]?.version!==r?!1:_(w(n,"commands.json"))}c(Je,"isInstalledGeneration");function ve(e,o,n,r){T(e,s=>{s.plugins[o]===void 0&&(s.plugins[o]={}),r.swapped&&(s.plugins[o].last_updated=E()),s.plugins[o].checked_at=E(),n===void 0?delete s.plugins[o].etag:s.plugins[o].etag=n})}c(ve,"recordCheckedGeneration");function g(e,o){const n=V(e,o);if(n===e||!n.startsWith(e+z))throw x(`bundle member path escapes the staging directory: ${o}`);return n}c(g,"stagedPath");function ge(e,o,n){const r=c((s,a,p=420)=>q(s,a,{mode:p,durable:!0}),"write");C(g(e,".crouter-plugin/plugin.json"),o,{durable:!0});for(const s of n.directories)j(g(e,s.path));r(g(e,"commands.json"),n.commandsBytes);for(const s of n.memory)r(g(e,s.path),s.bytes);if(n.hooks!==void 0){r(g(e,"hooks.json"),n.hooks.manifest);for(const s of n.hooks.files)r(g(e,s.path),s.bytes);W(g(e,"hooks/dispatch"),493)}U(e)}c(ge,"writeStagedBundle");async function Ie(e,o,n){const{scope:r,scopeRootPath:s,root:a}=n,p=await he(o,n.validation);if(p.bundle===void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,p.issues);const t=p.bundle,d=n.accept?.(t);if(d!==void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,[u(d,e,"the bundle this install asked for","Serve the requested bundle.")]);const f=n.version??be(o),i=t.provider!==void 0&&t.transport!==void 0?t.transport:n.transport,b={name:e,version:f,commands:"commands.json",transport:i,bundle:n.bundle,...t.kinds!==void 0?{kinds:t.kinds}:{},...t.page_components!==void 0?{page_components:t.page_components}:{},...t.memory_extensions!==void 0?{memory_extensions:t.memory_extensions}:{},...t.object_kinds!==void 0?{object_kinds:t.object_kinds}:{},...t.scopes!==void 0?{scopes:t.scopes}:{},...t.hooks!==void 0?{hooks:"hooks.json",hookExecutable:"hooks/dispatch"}:{},...t.provider!==void 0?{provider:t.provider}:{}},h=w(s,"tmp"),m=w(h,`${e}.${process.pid}`),v=`${m}.prev`;if(_(v))throw x(`cannot replace plugin "${e}": prior interrupted replacement remains at ${v}`);j(h),j($(a)),S(m);let l;try{j(m),ge(m,b,t),l=await n.validateStaged(m,b);let y=!1;_(a)&&(k(a,v),y=!0);try{k(m,a)}catch(A){throw y&&k(v,a),A}H($(a)),y&&S(v)}finally{S(m)}return R(r,s),D(r,y=>{y.plugins[e]={enabled:n.enable?!0:y.plugins[e]?.enabled??!0,version:f}}),ve(r,e,n.etag,{swapped:!0}),{manifest:b,validated:t,version:f,root:a,staged:l}}c(Ie,"installBundleArchive");export{be as bundleVersion,Ie as installBundleArchive,Je as isInstalledGeneration,ve as recordCheckedGeneration,he as validatePluginBundle,ge as writeStagedBundle};
1
+ var P=Object.defineProperty;var c=(e,o)=>P(e,"name",{value:o,configurable:!0});import{Parser as J}from"tar";import{createHash as I}from"node:crypto";import{chmodSync as W,renameSync as k}from"node:fs";import{dirname as $,join as w,resolve as V,sep as z}from"node:path";import{validateCommandManifest as B}from"../../api/command-manifest/manifest.js";import{ensureScopeInitialized as R,invalidPluginKindsReasons as F,readConfig as M,updateConfig as D,updateState as T}from"../config.js";import{general as x}from"../errors.js";import{atomicWriteJson as C,atomicWriteText as q,ensureDir as j,nowIso as E,pathExists as _,removePath as S,syncDirectory as H,syncTree as U}from"../fs-utils.js";import{invalidPageComponentsReasons as K,normalizePageComponents as G}from"../human/page-catalog.js";import{validateMemoryExtensionDeclarations as X}from"../plugin-extensions.js";import{objectBlocksShapeReasons as Z}from"../plugin-objects-manifest.js";import{validateBundleTransport as L}from"./endpoint.js";const O=/^[A-Za-z0-9_-]{1,64}$/,Y=new Set(["id","major","groups"]),Q=new Set(["group","consent_line","rate","who","allowlist"]),ee=new Set(["own","user","allowlist","any"]),ne=/^team:[a-z0-9]+$/,oe=new Set(["bundleVersion","kinds","page_components","memory_extensions","version","transport","provider","object_kinds","scopes","groups"]);function re(e,o,n,r,s,d){return{code:e,message:o,received:n,expected:r,next:s,...d!==void 0?{path:d}:{}}}c(re,"issue");function u(e,o,n,r,s){return re("plugin_bundle_invalid",e,o,n,r,s)}c(u,"bundleInvalid");function se(e){if(e[0]===31&&e[1]===139)return u("bundle archive must be uncompressed tar data","gzip-compressed archive","an uncompressed tar archive","Serve the archive without compression.");if(e.length===0||e.length%512!==0)return u("bundle archive must be a non-empty whole number of 512-byte tar blocks",`${e.length} bytes`,"a non-empty tar stream with a 512-byte block length","Serve one complete uncompressed tar archive.");if(e.length<1024||!e.subarray(-1024).every(o=>o===0))return u("bundle archive is missing the required two zero-block terminator","archive tail is not 1024 zero bytes","a complete tar stream ending in two 512-byte zero blocks","Serve the complete uncompressed tar archive.")}c(se,"archiveEnvelopeIssue");function te(e,o){if(e.length===0||e.includes("\0")||e.includes("\\")||e.startsWith("/")||/^[A-Za-z]:($|\/)/.test(e))return u("bundle member path must be a safe relative POSIX path",e,"a relative path without absolute, drive-letter, backslash, or NUL forms","Use a safe relative POSIX member path.",e);if(o==="file"&&e.endsWith("/"))return u("only directory members may have a trailing slash",e,"a regular-file path without a trailing slash","Emit this member as a regular file path.",e);if((o==="directory"&&e.endsWith("/")?e.slice(0,-1):e).split("/").some(r=>r===""||r==="."||r===".."))return u("bundle member path contains an unsafe segment",e,"non-empty segments other than . or ..","Use a safe relative POSIX member path.",e)}c(te,"validateMemberName");function ie(e,o){if(o==="file")return e==="bundle.json"||e==="commands.json"||e==="hooks.json"||e.startsWith("memory/")&&e.endsWith(".md")||e.startsWith("hooks/")?void 0:u("bundle contains an unsupported file member",e,"bundle.json, commands.json, hooks.json, memory/**/*.md, or hooks/**","Remove unsupported members from the archive.",e);if(!(e==="memory"||e.startsWith("memory/")||e==="hooks"||e.startsWith("hooks/")))return u("bundle contains an unsupported directory member",e,"memory or hooks, or a directory below either subtree","Remove unsupported directory members from the archive.",e)}c(ie,"validateMemberShape");function ae(e){return e.type==="directory"&&e.path.endsWith("/")?e.path.slice(0,-1):e.path}c(ae,"materializedPath");function de(e){const o=new Map;for(const n of e){const r=ae(n),s=o.get(r);if(s!==void 0)return u(s===n.type?"bundle contains a duplicate member":"bundle claims one path as both a file and a directory",`${r} (${s} and ${n.type})`,"each materialized path exactly once, with one type","Emit each archive path once, as a single member type.",r);o.set(r,n.type)}for(const[n,r]of o)if(r==="file"){for(const s of o.keys())if(s.startsWith(`${n}/`))return u("bundle nests a member beneath a regular-file path",`${s} beneath file ${n}`,"members nested only beneath directory paths","Emit the parent path as a directory or remove the nested member.",s)}}c(de,"validateMemberTree");async function ue(e){const o=[],n=new Set;let r;return await new Promise((s,d)=>{const p=new J({strict:!0,maxMetaEntrySize:Number.MAX_SAFE_INTEGER,gzip:!1,brotli:!1,zstd:!1,onReadEntry:c(t=>{const a=t.path,f=t.type==="File"||t.type==="OldFile"?"file":t.type==="Directory"?"directory":void 0;if(f===void 0||t.unsupported||t.ignore||t.linkpath!==void 0){r??=u("bundle member must be a regular file or directory without a link target",`${a} (${t.type})`,"a regular file or directory member without link metadata","Remove links and unsupported member types from the archive.",a),t.resume();return}r??=te(a,f),r??=ie(a,f),n.has(a)&&(r??=u("bundle contains a duplicate member",a,"each member path exactly once","Emit each archive member only once.",a)),n.add(a);const i=[];t.on("data",b=>i.push(b)),t.on("end",()=>{r===void 0&&o.push({path:a,type:f,bytes:new Uint8Array(Buffer.concat(i))})})},"onReadEntry")});p.on("ignoredEntry",t=>{const a=t.path;r??=u("bundle contains an ignored or unsupported archive member",`${a} (${t.type})`,"only supported regular file or directory members","Remove ignored or unsupported members from the archive.",a),t.resume()}),p.once("error",d),p.once("end",s);try{p.end(Buffer.from(e))}catch(t){d(t)}}).catch(s=>{r??=u("bundle archive could not be parsed",s instanceof Error?s.message:String(s),"a valid complete uncompressed tar archive","Fix the archive format and serve it again.")}),r===void 0?{members:o}:{issue:r}}c(ue,"parseArchive");function N(e){return typeof e=="number"&&Number.isInteger(e)&&e>=1}c(N,"isPositiveInt");function le(e){const o=c((t,a,f,i)=>({issue:u(t,a,f,"Fix the provider block in bundle.json.",`bundle.json.${i}`)}),"bad");if(typeof e!="object"||e===null||Array.isArray(e))return o("bundle.json provider must be an object",JSON.stringify(e),"{ id, major, groups }","provider");const n=e,r=Object.keys(n).filter(t=>!Y.has(t));if(r.length>0)return o("bundle.json provider has unknown keys",r.join(", "),"only: id, major, groups","provider");if(typeof n.id!="string"||!O.test(n.id))return o("provider id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(n.id),'a provider id of 1-64 letters, digits, "_" or "-"',"provider.id");if(!N(n.major))return o("provider major must be an integer of at least 1",JSON.stringify(n.major),"the served major as an integer >= 1","provider.major");const s=n.groups;if(!Array.isArray(s)||s.length===0)return o("provider groups must be a non-empty array",JSON.stringify(s),"at least one { group, consent_line, rate?, who, allowlist }","provider.groups");const d=[],p=new Set;for(let t=0;t<s.length;t++){const a=`provider.groups[${t}]`,f=s[t];if(typeof f!="object"||f===null||Array.isArray(f))return o("provider group must be an object",JSON.stringify(f),"{ group, consent_line, rate?, who, allowlist }",a);const i=f,b=Object.keys(i).filter(v=>!Q.has(v));if(b.length>0)return o("provider group has unknown keys",b.join(", "),"only: group, consent_line, rate, who, allowlist",a);if(typeof i.group!="string"||!O.test(i.group))return o("provider group id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(i.group),'a group id of 1-64 letters, digits, "_" or "-"',`${a}.group`);if(p.has(i.group))return o("provider group ids must be unique",i.group,"each group declared once",`${a}.group`);if(p.add(i.group),typeof i.consent_line!="string"||i.consent_line.trim().length===0)return o("provider group consent_line must be a non-empty string",JSON.stringify(i.consent_line),"the consent line shown for this group",`${a}.consent_line`);const h=i.rate;if(h!=null&&(typeof h!="number"||!Number.isFinite(h)||h<0))return o("provider group rate must be a non-negative number or null",JSON.stringify(h),"a finite number >= 0, null, or omitted",`${a}.rate`);if(typeof i.who!="string"||!(ee.has(i.who)||ne.test(i.who)))return o("provider group who must be own|user|allowlist|any|team:<id>",JSON.stringify(i.who),"own | user | allowlist | any | team:<id> (id of lowercase letters and digits)",`${a}.who`);const m=i.allowlist;if(!Array.isArray(m)||!m.every(v=>typeof v=="string"&&v.length>0))return o("provider group allowlist must be an array of non-empty strings",JSON.stringify(m),"an array of app principals ([] unless who is allowlist)",`${a}.allowlist`);if(m.length>0&&i.who!=="allowlist")return o("provider group allowlist must be empty unless who is allowlist",`who ${i.who}`,'who: "allowlist", or allowlist: []',`${a}.allowlist`);d.push({group:i.group,consent_line:i.consent_line,...h!==void 0?{rate:h}:{},who:i.who,allowlist:m})}return{provider:{id:n.id,major:n.major,groups:d}}}c(le,"parseProviderBlock");function pe(e){const o=[],n=c((r,s)=>{const d=[...s,r.name];if(r.kind==="leaf")o.push({path:d.join(" "),leaf:r});else for(const p of r.children)n(p,d)},"walk");for(const r of e.roots)n(r,[]);for(const r of e.coreMounts)n(r.node,r.parent);return o}c(pe,"collectLeaves");function ce(e,o){const n=e.provider;if(n===void 0)return;if(e.transport===void 0)return u("a provider bundle must declare its transport","(missing)",'transport { kind: "http", endpoint } in bundle.json',"Add the provider endpoint as bundle.json transport.","bundle.json.transport");if(e.transport.kind==="http"&&e.transport.authEnv!==void 0)return u("a provider bundle must not declare authEnv",e.transport.authEnv,"no authEnv: the runtime presents its own outbound token","Remove transport.authEnv from bundle.json.","bundle.json.transport.authEnv");const r=new Set(n.groups.map(s=>s.group));for(const{path:s,leaf:d}of pe(o)){const p="group"in d?d.group:void 0;if(p===void 0)return u(`provider leaf "${s}" has no group`,s,"a group on every leaf of a provider bundle",`Add a group to leaf "${s}".`,`commands.json:${s}.group`);if(!r.has(p))return u(`provider leaf "${s}" names undeclared group "${p}"`,p,`one of: ${[...r].join(", ")}`,`Declare group "${p}" in the provider block, or move leaf "${s}" to a declared group.`,`commands.json:${s}.group`);if("rest"in d&&d.rest.streaming===!0)return u(`provider leaf "${s}" streams`,"rest.streaming: true","no streaming leaves in a provider bundle",`Make leaf "${s}" return one response.`,`commands.json:${s}.rest.streaming`)}}c(ce,"providerBundleIssue");function me(e){let o;try{o=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issue:u("bundle.json is not valid JSON","invalid JSON",'{"bundleVersion":1}',"Write valid bundle metadata.","bundle.json")}}if(typeof o!="object"||o===null||Array.isArray(o))return{issue:u("bundle.json must be a JSON object",JSON.stringify(o),"an object with bundleVersion set to 1 and optionally kinds",'Set bundle.json to {"bundleVersion":1}.',"bundle.json")};const n=o,r=Object.keys(n).filter(a=>!oe.has(a));if(n.bundleVersion!==1||r.length>0)return{issue:u("bundle.json must contain bundleVersion 1 and at most version, transport, provider, kinds, page_components, memory_extensions, object_kinds, scopes, and groups",JSON.stringify(o),"an object with bundleVersion set to 1 and optional version, transport, provider, kinds, page_components, memory_extensions, object_kinds, scopes, and groups",'Set bundle.json to {"bundleVersion":1} plus optional contribution blocks.',"bundle.json")};if(n.kinds!==void 0){const a=F(n.kinds);if(a.length>0)return{issue:u("bundle.json kinds block is invalid",a.join("; "),"each entry a full KindConfig (whenToUse required) or a sparse patch of launch fields","Fix the kinds entries in bundle.json.","bundle.json")}}if(n.page_components!==void 0){const a=K(n.page_components);if(a.length>0)return{issue:u("bundle.json page_components block is invalid",a.join("; "),"an array of page component registrations, each a kind string or {kind, description?, useWhen?, doc?, display?}","Fix the page_components entries in bundle.json.","bundle.json")}}const s=Z(n);if(n.provider!==void 0&&n.scopes!==void 0&&s.push("scopes is for local plugins; a provider gets its scopes from directory consent"),s.length>0)return{issue:u("bundle.json object_kinds or scopes block is invalid",s.join("; "),"object_kinds keyed by kind name with description, events and optional read.command; scopes an array of crtr:objects:<verb>:<target> strings (local plugins only)","Fix the object_kinds and scopes blocks in bundle.json.","bundle.json")};let d;if(n.memory_extensions!==void 0)try{d=X(n.memory_extensions)}catch(a){return{issue:u("bundle.json memory_extensions block is invalid",a instanceof Error?a.message:String(a),"a valid memory_extensions declaration object","Fix the memory_extensions declarations in bundle.json.","bundle.json")}}if(n.version!==void 0&&!N(n.version))return{issue:u("bundle.json version must be an integer of at least 1",JSON.stringify(n.version),"the plugin major as an integer >= 1, or omitted","Fix version in bundle.json.","bundle.json.version")};let p;if(n.transport!==void 0){const a=L(n.transport);if(a.transport===void 0)return{issue:u("bundle.json transport is invalid",a.errors.join("; "),'{ kind: "http", endpoint, authEnv? } or { kind: "machine" }',"Fix transport in bundle.json.","bundle.json.transport")};p=a.transport}let t;if(n.provider!==void 0){const a=le(n.provider);if(a.issue!==void 0)return{issue:a.issue};if(t=a.provider,n.version!==void 0&&n.version!==t.major)return{issue:u("bundle.json provider major must equal version",`version ${String(n.version)}, major ${t.major}`,"one major","Set provider.major and version to the same integer.","bundle.json.provider.major")}}return{...n.version!==void 0?{version:n.version}:{},...p!==void 0?{transport:p}:{},...t!==void 0?{provider:t}:{},...n.kinds!==void 0?{kinds:n.kinds}:{},...n.page_components!==void 0?{page_components:G(n.page_components)}:{},...d!==void 0?{memory_extensions:d}:{},...n.object_kinds!==void 0?{object_kinds:n.object_kinds}:{},...n.scopes!==void 0?{scopes:n.scopes}:{}}}c(me,"parseBundleMetadata");function fe(e,o,n,r=new Set){let s;try{s=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issues:[u("commands.json is not valid JSON","invalid JSON","a valid command-manifest JSON object","Regenerate commands.json.","commands.json")]}}const d=B(s,{transport:"http",reservedCoreNames:o,extensibleCoreBranches:r,...n!==void 0?{coreCommandPaths:n}:{}});return{...d.manifest!==void 0?{commands:d.manifest}:{},issues:d.issues}}c(fe,"parseCommands");async function he(e,o){const n=se(e);if(n!==void 0)return{issues:[n]};const r=await ue(e);if(r.issue!==void 0||r.members===void 0)return{issues:[r.issue]};const s=de(r.members);if(s!==void 0)return{issues:[s]};const d=r.members.find(l=>l.path==="bundle.json"&&l.type==="file"),p=r.members.find(l=>l.path==="commands.json"&&l.type==="file");if(d===void 0||p===void 0)return{issues:[u("bundle must contain exactly one bundle.json and one commands.json regular file",`bundle.json=${d!==void 0}, commands.json=${p!==void 0}`,"one regular bundle.json and one regular commands.json member","Add the required metadata members to the archive.")]};const t=r.members.find(l=>l.path==="hooks.json"&&l.type==="file"),a=r.members.filter(l=>l.type==="file"&&l.path.startsWith("hooks/"));if((t!==void 0||r.members.some(l=>l.path==="hooks"||l.path.startsWith("hooks/")))&&(t===void 0||!a.some(l=>l.path==="hooks/dispatch")))return{issues:[u("bundle hook payload must contain both hooks.json and hooks/dispatch regular files",`hooks.json=${t!==void 0}, hooks/dispatch=${a.some(l=>l.path==="hooks/dispatch")}`,"both hooks.json and hooks/dispatch regular files","Add both required hook members or remove the hook payload.")]};const i=me(d.bytes);if(i.issue!==void 0)return{issues:[i.issue]};const b=fe(p.bytes,o.reservedCoreNames,o.coreCommandPaths,o.extensibleCoreBranches);if(b.commands===void 0)return{issues:b.issues};const h=b.commands.version;if(i.version!==void 0&&h!==void 0&&i.version!==h)return{issues:[u("bundle.json version and commands.json version differ",`bundle.json ${i.version}, commands.json ${h}`,"one plugin major","Emit the same version in bundle.json and commands.json.","bundle.json.version")]};const m=i.version??h;if(i.provider!==void 0&&m===void 0)return{issues:[u("a provider bundle requires a plugin version","no version in bundle.json or commands.json",`version ${i.provider.major}, the provider major`,"Emit the provider major as the plugin version.","bundle.json.version")]};if(i.provider!==void 0&&m!==i.provider.major)return{issues:[u("bundle provider major must equal the plugin version",`version ${m}, major ${i.provider.major}`,"one major","Emit the provider major as the plugin version.","bundle.json.provider.major")]};const v=ce(i,b.commands);return v!==void 0?{issues:[v]}:{bundle:{bundleVersion:1,...m!==void 0?{version:m}:{},...i.transport!==void 0?{transport:i.transport}:{},...i.provider!==void 0?{provider:i.provider}:{},...i.kinds!==void 0?{kinds:i.kinds}:{},...i.page_components!==void 0?{page_components:i.page_components}:{},...i.memory_extensions!==void 0?{memory_extensions:i.memory_extensions}:{},...i.object_kinds!==void 0?{object_kinds:i.object_kinds}:{},...i.scopes!==void 0?{scopes:i.scopes}:{},commands:b.commands,commandsBytes:p.bytes,directories:r.members.filter(l=>l.type==="directory").map(l=>({path:l.path})),memory:r.members.filter(l=>l.type==="file"&&l.path.startsWith("memory/")).map(l=>({path:l.path,bytes:l.bytes})),...t!==void 0?{hooks:{manifest:t.bytes,files:a.map(l=>({path:l.path,bytes:l.bytes}))}}:{}},issues:[]}}c(he,"validatePluginBundle");function be(e){return I("sha256").update(e).digest("hex").slice(0,12)}c(be,"bundleVersion");function Je(e,o,n,r){return M(o).plugins[e]?.version!==r?!1:_(w(n,"commands.json"))}c(Je,"isInstalledGeneration");async function ve(e,o,n,r){await T(e,s=>{s.plugins[o]===void 0&&(s.plugins[o]={}),r.swapped&&(s.plugins[o].last_updated=E()),s.plugins[o].checked_at=E(),n===void 0?delete s.plugins[o].etag:s.plugins[o].etag=n})}c(ve,"recordCheckedGeneration");function g(e,o){const n=V(e,o);if(n===e||!n.startsWith(e+z))throw x(`bundle member path escapes the staging directory: ${o}`);return n}c(g,"stagedPath");function ge(e,o,n){const r=c((s,d,p=420)=>q(s,d,{mode:p,durable:!0}),"write");C(g(e,".crouter-plugin/plugin.json"),o,{durable:!0});for(const s of n.directories)j(g(e,s.path));r(g(e,"commands.json"),n.commandsBytes);for(const s of n.memory)r(g(e,s.path),s.bytes);if(n.hooks!==void 0){r(g(e,"hooks.json"),n.hooks.manifest);for(const s of n.hooks.files)r(g(e,s.path),s.bytes);W(g(e,"hooks/dispatch"),493)}U(e)}c(ge,"writeStagedBundle");async function Ie(e,o,n){const{scope:r,scopeRootPath:s,root:d}=n,p=await he(o,n.validation);if(p.bundle===void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,p.issues);const t=p.bundle,a=n.accept?.(t);if(a!==void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,[u(a,e,"the bundle this install asked for","Serve the requested bundle.")]);const f=n.version??be(o),i=t.provider!==void 0&&t.transport!==void 0?t.transport:n.transport,b={name:e,version:f,commands:"commands.json",transport:i,bundle:n.bundle,...t.kinds!==void 0?{kinds:t.kinds}:{},...t.page_components!==void 0?{page_components:t.page_components}:{},...t.memory_extensions!==void 0?{memory_extensions:t.memory_extensions}:{},...t.object_kinds!==void 0?{object_kinds:t.object_kinds}:{},...t.scopes!==void 0?{scopes:t.scopes}:{},...t.hooks!==void 0?{hooks:"hooks.json",hookExecutable:"hooks/dispatch"}:{},...t.provider!==void 0?{provider:t.provider}:{}},h=w(s,"tmp"),m=w(h,`${e}.${process.pid}`),v=`${m}.prev`;if(_(v))throw x(`cannot replace plugin "${e}": prior interrupted replacement remains at ${v}`);j(h),j($(d)),S(m);let l;try{j(m),ge(m,b,t),l=await n.validateStaged(m,b);let y=!1;_(d)&&(k(d,v),y=!0);try{k(m,d)}catch(A){throw y&&k(v,d),A}H($(d)),y&&S(v)}finally{S(m)}return await R(r,s),await D(r,y=>{y.plugins[e]={enabled:n.enable?!0:y.plugins[e]?.enabled??!0,version:f}}),await ve(r,e,n.etag,{swapped:!0}),{manifest:b,validated:t,version:f,root:d,staged:l}}c(Ie,"installBundleArchive");export{be as bundleVersion,Ie as installBundleArchive,Je as isInstalledGeneration,ve as recordCheckedGeneration,he as validatePluginBundle,ge as writeStagedBundle};
@@ -1,6 +1,6 @@
1
1
  import type { BranchDef, LeafDef } from '../command.js';
2
2
  import type { CommandRegistrySnapshot, CoreBranchMount } from '../command-manifests/registry.js';
3
- import type { ProviderToolDescription, UnavailableProviderToolDescription } from '../providers/call.js';
3
+ import type { ProviderToolDescription, UnavailableProviderToolDescription } from '../providers/description.js';
4
4
  import type { ValidatedContribution, ValidatedCoreMount } from './discovery.js';
5
5
  /** A sandbox sees no user plugin files. Compose only daemon-projected help data,
6
6
  * with an adapter that cannot execute plugin code or send provider HTTP. */
@@ -38,7 +38,6 @@ export interface PluginCommandValidation {
38
38
  coreMounts: ValidatedCoreMount[];
39
39
  issues: CommandDiscoveryIssue[];
40
40
  }
41
- export declare function effectiveCommandPlugins(startDir?: string, profileId?: string | null): InstalledPlugin[];
42
41
  export declare function validatePluginCommands(plugin: InstalledPlugin, reservedNames?: ReadonlySet<string>, coreCommandPaths?: ReadonlySet<string>, extensibleCoreBranches?: ReadonlySet<string>): PluginCommandValidation;
43
42
  export declare function discoverPluginCommandCandidates(startDir?: string, profileId?: string | null): PluginCommandValidation[];
44
43
  export declare function validateEffectiveCommandPlugins(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null, coreCommandPaths?: ReadonlySet<string>, extensibleCoreBranches?: ReadonlySet<string>): PluginCommandValidation[];
@@ -1 +1 @@
1
- var P=Object.defineProperty;var u=(e,n)=>P(e,"name",{value:n,configurable:!0});import{realpathSync as k,readFileSync as N,statSync as g}from"node:fs";import{isSandboxedNode as F}from"../../shared/env.js";import{isAbsolute as O,resolve as _,sep as R}from"node:path";import{listInstalledPlugins as q,listInstalledPluginsInRoot as A}from"../resolver.js";import{projectScopeRoots as E}from"../scope.js";import{validateCommandManifest as I}from"../../api/command-manifest/manifest.js";import{qualifiedTopLevelCommandName as J,resolveCommandRegistry as $}from"../command-manifests/registry.js";import{adaptPluginContributions as T,adaptPluginCoreMounts as B}from"./compose.js";import{validateHttpPluginTransport as H}from"./endpoint.js";import{providerRootClaims as L}from"../providers/roots.js";import{applyCommandExtensions as W}from"./extensions.js";function w(e=process.cwd(),n){const i=new Set,o=[],r=u(s=>{i.has(s.name)||(i.add(s.name),o.push(s))},"consider");for(const s of E(e,n))for(const m of A("project",s))r(m);if(!F())for(const s of q("user"))r(s);const{skipped:a}=L(o);return o.filter(s=>!a.has(s.name)).filter(s=>s.enabled&&(s.manifest.commands!==void 0||s.manifest.transport!==void 0))}u(w,"effectiveCommandPlugins");function X(e,n){return i=>n.push({...i,plugin:e.name})}u(X,"issueFor");function v(e,n){if(typeof n!="string"||n.length===0)return null;try{const i=k(e),o=k(_(e,n));return(o===i||o.startsWith(i+R))&&g(o).isFile()?o:null}catch{return null}}u(v,"safePath");function z(e,n){const i=e.manifest.transport;if(i===void 0){n.push({code:"command_manifest_invalid",plugin:e.name,message:"commands requires transport in plugin.json",received:"(missing)",expected:"a transport object",next:"Add plugin.json.transport.",path:"transport"});return}if(typeof i!="object"||i===null||Array.isArray(i)){n.push({code:"command_manifest_invalid",plugin:e.name,message:"transport must be an object",received:Array.isArray(i)?"array":typeof i,expected:"an exec or http transport object",next:"Fix plugin.json.transport.",path:"transport"});return}const o=i;if(o.kind==="http"){const a=H(o);if(a.transport!==void 0)return a.transport;n.push({code:"command_manifest_invalid",plugin:e.name,message:a.errors.join("; "),received:JSON.stringify(i),expected:"a valid HTTP transport",next:"Fix plugin.json.transport.",path:"transport"});return}if(o.kind==="machine"){if(e.manifest.provider===void 0||Object.keys(o).length!==1){n.push({code:"command_manifest_invalid",plugin:e.name,message:e.manifest.provider===void 0?"a machine transport belongs to a capability provider bundle":"transport has unknown keys",received:JSON.stringify(i),expected:'{ kind: "machine" } on a provider bundle',next:"Fix plugin.json.transport.",path:"transport"});return}return{kind:"machine"}}const r=Object.keys(o).filter(a=>!["kind","executable"].includes(a));if(o.kind!=="exec"||r.length||o.executable!==void 0&&(typeof o.executable!="string"||o.executable.length===0)){n.push({code:"command_manifest_invalid",plugin:e.name,message:r.length?`transport has unknown keys: ${r.join(", ")}`:"exec transport requires a non-empty executable when declared",received:JSON.stringify(i),expected:'{ kind: "exec", executable?: "plugin-relative path" }',next:"Fix plugin.json.transport.",path:"transport"});return}return{kind:"exec",...o.executable!==void 0?{executable:o.executable}:{}}}u(z,"transportFor");function y(e){return e.kind==="leaf"||e.children.some(y)}u(y,"hasExecutableLeaf");function M(e){return[...e.roots,...e.coreMounts.map(n=>n.node)]}u(M,"manifestNodes");function G(e,n,i,o){if(n.executable===void 0){if(!M(i).some(y))return n;o.push({code:"command_manifest_invalid",plugin:e.name,message:"exec transport requires executable when commands.json declares executable leaves",received:"(missing)",expected:'{ kind: "exec", executable: "plugin-relative path" }',next:"Add plugin.json.transport.executable.",path:"transport.executable"});return}const r=v(e.root,n.executable);if(r===null){o.push({code:"command_path_unsafe",plugin:e.name,message:"executable escapes the plugin root or is not a regular file",received:n.executable,expected:"a relative path to a regular file inside the plugin root",next:"Fix plugin.json.transport.executable.",path:"transport.executable"});return}if((g(r).mode&73)===0){o.push({code:"command_not_executable",plugin:e.name,message:"executable lacks the POSIX exec bit",received:n.executable,expected:"a file with an executable permission bit",next:"chmod +x the plugin executable, then update the plugin.",path:"transport.executable"});return}return{kind:"exec",executable:r}}u(G,"validateExecExecutable");function K(e){return O(e)||e.startsWith(".")||e.includes("/")||e.includes("\\")}u(K,"namesPath");function Q(e,n,i){let o=!0;const r=u(a=>{if(a.kind!=="branch")return;const s=a.passthrough;if(s!==void 0&&K(s.bin)){const m=v(e.root,s.bin);m===null?(i.push({code:"command_path_unsafe",plugin:e.name,message:"passthrough binary escapes the plugin root or is not a regular file",received:s.bin,expected:"a relative path to a regular file inside the plugin root",next:"Fix passthrough.bin in commands.json.",path:"passthrough.bin"}),o=!1):(g(m).mode&73)===0?(i.push({code:"command_not_executable",plugin:e.name,message:"passthrough binary lacks the POSIX exec bit",received:s.bin,expected:"a file with an executable permission bit",next:"chmod +x the plugin executable, then update commands.json.",path:"passthrough.bin"}),o=!1):a.passthrough={...s,bin:m}}for(const m of a.children)r(m)},"visit");for(const a of M(n))r(a);return o}u(Q,"resolvePassthroughBinaries");function S(e,n=new Set,i,o=new Set){const r=[],a=e.manifest.commands,s=e.manifest.transport;let m=_(e.root,typeof a=="string"?a:"commands.json");const c=u(x=>({plugin:e,manifestPath:m,...x===void 0?{}:{transport:x},contributions:[],coreMounts:[],issues:r}),"empty");if(a===void 0&&s===void 0)return c();if(typeof a!="string"||a.length===0)return r.push({code:"command_manifest_invalid",plugin:e.name,message:"transport requires commands in plugin.json",received:"(missing)",expected:"a plugin-root-relative commands path",next:"Add plugin.json.commands or remove transport.",path:"commands"}),c();const t=z(e,r);if(t===void 0)return c();const d=v(e.root,a);if(d===null)return r.push({code:"command_path_unsafe",plugin:e.name,message:"commands pointer escapes the plugin root or is not a regular file",received:a,expected:"a relative path to a regular file inside the plugin root",next:"Fix plugin.json.commands.",path:"commands"}),c(t);m=d;let p;try{p=N(d)}catch{p=null}if(p===null)return r.push({code:"command_manifest_unreadable",plugin:e.name,message:"commands.json is missing or unreadable",received:d,expected:"a readable JSON file",next:"Reinstall or update the plugin."}),c(t);let h;try{h=JSON.parse(Buffer.from(p).toString("utf8"))}catch{return r.push({code:"command_manifest_invalid",plugin:e.name,message:"commands.json is not valid JSON",received:d,expected:"a JSON object",next:"Regenerate commands.json."}),c(t)}const f=I(h,{transport:t.kind==="machine"?"http":t.kind,reservedCoreNames:n,extensibleCoreBranches:o,...i!==void 0?{coreCommandPaths:i}:{}});if(f.manifest===void 0)return f.issues.forEach(X(e,r)),c(t);const l=t.kind==="exec"?G(e,t,f.manifest,r):t;if(l===void 0)return c(t);if(l.kind==="exec"&&!Q(e,f.manifest,r))return c(l);const b=e.manifest.provider,j=f.manifest.roots;if(!j.every(x=>x.kind==="branch"))throw new Error("plugin manifest validation returned a top-level leaf");return{plugin:e,manifestPath:m,transport:l,manifest:f.manifest,contributions:j.filter(x=>b===void 0||y(x)).map(x=>({plugin:e,transport:l,node:x,manifest:f.manifest,manifestName:x.name,...b!==void 0?{provider:b}:{}})),coreMounts:f.manifest.coreMounts.map(x=>({plugin:e,transport:l,parent:x.parent,node:x.node,manifest:f.manifest,...b!==void 0?{provider:b}:{}})),issues:r}}u(S,"validatePluginCommands");function ce(e=process.cwd(),n){return w(e,n).map(i=>S(i))}u(ce,"discoverPluginCommandCandidates");function C(e,n=process.cwd(),i,o,r=new Set){const a=w(n,i).map(c=>S(c,e,o,r)),s=new Map;for(const c of a)for(const t of c.contributions){const d=s.get(t.node.name);d===void 0?s.set(t.node.name,[t]):d.push(t)}for(const[c,t]of s)if(t.length>1)for(const d of t){const{plugin:p}=d,h=J({name:p.name,sourceMarketplace:p.sourceMarketplace},c),f=a.find(l=>l.plugin.root===p.root);f.contributions=f.contributions.map(l=>l===d?{...l,node:{...l.node,name:h}}:l),f.issues.push({code:"command_collision",plugin:p.name,path:c,message:`top-level command "${c}" is claimed by ${t.map(l=>l.plugin.name).join(", ")} \u2014 mounted as "${h}"`,received:t.map(l=>l.plugin.name).join(", "),expected:"an origin-qualified top-level command for each claimant",next:`Invoke "${h}" to use this plugin command.`})}const m=new Map;for(const c of a)for(const t of c.coreMounts){const d=[...t.parent,t.node.name].join("/"),p=m.get(d);p===void 0?m.set(d,[t]):p.push(t)}for(const[c,t]of m)if(t.length>1)for(const d of t){const p=a.find(h=>h.plugin.root===d.plugin.root);p.coreMounts=p.coreMounts.filter(h=>h!==d),p.issues.push({code:"command_collision",plugin:d.plugin.name,path:c.replace("/","."),message:`contributed command "${d.node.name}" conflicts with another plugin child below the extensible core branch`,received:t.map(h=>h.plugin.name).join(", "),expected:`a unique child name below ${d.parent.join(".")}`,next:"Rename the contributed command or remove this mount."})}return a}u(C,"validateEffectiveCommandPlugins");function U(e,n){return{...n,contributor:{name:e.name,scope:e.scope,sourceMarketplace:e.sourceMarketplace}}}u(U,"contributorIssue");function de(e,n=process.cwd(),i,o,r=new Set){const a=C(e,n,i,o,r);return{contributions:a.flatMap(s=>s.contributions),coreMounts:a.flatMap(s=>s.coreMounts),issues:a.flatMap(s=>s.issues)}}u(de,"discoverCommandContributions");function ue(e,n=process.cwd(),i,o,r=new Set){const a=C(e,n,i,o,r),s=T(a.flatMap(t=>t.contributions)).contributions,m=$(s,e);return{contributions:W(m.contributions,n).contributions,coreMounts:B(a.flatMap(t=>t.coreMounts)),issues:[...a.flatMap(t=>t.issues.map(d=>U(t.plugin,d))),...m.issues]}}u(ue,"buildExternalCommandSnapshot");export{ue as buildExternalCommandSnapshot,de as discoverCommandContributions,ce as discoverPluginCommandCandidates,w as effectiveCommandPlugins,C as validateEffectiveCommandPlugins,S as validatePluginCommands};
1
+ var P=Object.defineProperty;var m=(e,t)=>P(e,"name",{value:t,configurable:!0});import{realpathSync as k,readFileSync as F,statSync as b}from"node:fs";import{isAbsolute as N,resolve as _,sep as O}from"node:path";import{validateCommandManifest as q}from"../../api/command-manifest/manifest.js";import{qualifiedTopLevelCommandName as A,resolveCommandRegistry as E}from"../command-manifests/registry.js";import{adaptPluginContributions as J,adaptPluginCoreMounts as $}from"./compose.js";import{validateHttpPluginTransport as R}from"./endpoint.js";import{effectiveCommandPlugins as w}from"./presence.js";import{applyCommandExtensions as T}from"./extensions.js";function I(e,t){return i=>t.push({...i,plugin:e.name})}m(I,"issueFor");function v(e,t){if(typeof t!="string"||t.length===0)return null;try{const i=k(e),o=k(_(e,t));return(o===i||o.startsWith(i+O))&&b(o).isFile()?o:null}catch{return null}}m(v,"safePath");function B(e,t){const i=e.manifest.transport;if(i===void 0){t.push({code:"command_manifest_invalid",plugin:e.name,message:"commands requires transport in plugin.json",received:"(missing)",expected:"a transport object",next:"Add plugin.json.transport.",path:"transport"});return}if(typeof i!="object"||i===null||Array.isArray(i)){t.push({code:"command_manifest_invalid",plugin:e.name,message:"transport must be an object",received:Array.isArray(i)?"array":typeof i,expected:"an exec or http transport object",next:"Fix plugin.json.transport.",path:"transport"});return}const o=i;if(o.kind==="http"){const a=R(o);if(a.transport!==void 0)return a.transport;t.push({code:"command_manifest_invalid",plugin:e.name,message:a.errors.join("; "),received:JSON.stringify(i),expected:"a valid HTTP transport",next:"Fix plugin.json.transport.",path:"transport"});return}if(o.kind==="machine"){if(e.manifest.provider===void 0||Object.keys(o).length!==1){t.push({code:"command_manifest_invalid",plugin:e.name,message:e.manifest.provider===void 0?"a machine transport belongs to a capability provider bundle":"transport has unknown keys",received:JSON.stringify(i),expected:'{ kind: "machine" } on a provider bundle',next:"Fix plugin.json.transport.",path:"transport"});return}return{kind:"machine"}}const s=Object.keys(o).filter(a=>!["kind","executable"].includes(a));if(o.kind!=="exec"||s.length||o.executable!==void 0&&(typeof o.executable!="string"||o.executable.length===0)){t.push({code:"command_manifest_invalid",plugin:e.name,message:s.length?`transport has unknown keys: ${s.join(", ")}`:"exec transport requires a non-empty executable when declared",received:JSON.stringify(i),expected:'{ kind: "exec", executable?: "plugin-relative path" }',next:"Fix plugin.json.transport.",path:"transport"});return}return{kind:"exec",...o.executable!==void 0?{executable:o.executable}:{}}}m(B,"transportFor");function y(e){return e.kind==="leaf"||e.children.some(y)}m(y,"hasExecutableLeaf");function M(e){return[...e.roots,...e.coreMounts.map(t=>t.node)]}m(M,"manifestNodes");function H(e,t,i,o){if(t.executable===void 0){if(!M(i).some(y))return t;o.push({code:"command_manifest_invalid",plugin:e.name,message:"exec transport requires executable when commands.json declares executable leaves",received:"(missing)",expected:'{ kind: "exec", executable: "plugin-relative path" }',next:"Add plugin.json.transport.executable.",path:"transport.executable"});return}const s=v(e.root,t.executable);if(s===null){o.push({code:"command_path_unsafe",plugin:e.name,message:"executable escapes the plugin root or is not a regular file",received:t.executable,expected:"a relative path to a regular file inside the plugin root",next:"Fix plugin.json.transport.executable.",path:"transport.executable"});return}if((b(s).mode&73)===0){o.push({code:"command_not_executable",plugin:e.name,message:"executable lacks the POSIX exec bit",received:t.executable,expected:"a file with an executable permission bit",next:"chmod +x the plugin executable, then update the plugin.",path:"transport.executable"});return}return{kind:"exec",executable:s}}m(H,"validateExecExecutable");function L(e){return N(e)||e.startsWith(".")||e.includes("/")||e.includes("\\")}m(L,"namesPath");function W(e,t,i){let o=!0;const s=m(a=>{if(a.kind!=="branch")return;const d=a.passthrough;if(d!==void 0&&L(d.bin)){const l=v(e.root,d.bin);l===null?(i.push({code:"command_path_unsafe",plugin:e.name,message:"passthrough binary escapes the plugin root or is not a regular file",received:d.bin,expected:"a relative path to a regular file inside the plugin root",next:"Fix passthrough.bin in commands.json.",path:"passthrough.bin"}),o=!1):(b(l).mode&73)===0?(i.push({code:"command_not_executable",plugin:e.name,message:"passthrough binary lacks the POSIX exec bit",received:d.bin,expected:"a file with an executable permission bit",next:"chmod +x the plugin executable, then update commands.json.",path:"passthrough.bin"}),o=!1):a.passthrough={...d,bin:l}}for(const l of a.children)s(l)},"visit");for(const a of M(t))s(a);return o}m(W,"resolvePassthroughBinaries");function S(e,t=new Set,i,o=new Set){const s=[],a=e.manifest.commands,d=e.manifest.transport;let l=_(e.root,typeof a=="string"?a:"commands.json");const r=m(x=>({plugin:e,manifestPath:l,...x===void 0?{}:{transport:x},contributions:[],coreMounts:[],issues:s}),"empty");if(a===void 0&&d===void 0)return r();if(typeof a!="string"||a.length===0)return s.push({code:"command_manifest_invalid",plugin:e.name,message:"transport requires commands in plugin.json",received:"(missing)",expected:"a plugin-root-relative commands path",next:"Add plugin.json.commands or remove transport.",path:"commands"}),r();const n=B(e,s);if(n===void 0)return r();const c=v(e.root,a);if(c===null)return s.push({code:"command_path_unsafe",plugin:e.name,message:"commands pointer escapes the plugin root or is not a regular file",received:a,expected:"a relative path to a regular file inside the plugin root",next:"Fix plugin.json.commands.",path:"commands"}),r(n);l=c;let p;try{p=F(c)}catch{p=null}if(p===null)return s.push({code:"command_manifest_unreadable",plugin:e.name,message:"commands.json is missing or unreadable",received:c,expected:"a readable JSON file",next:"Reinstall or update the plugin."}),r(n);let h;try{h=JSON.parse(Buffer.from(p).toString("utf8"))}catch{return s.push({code:"command_manifest_invalid",plugin:e.name,message:"commands.json is not valid JSON",received:c,expected:"a JSON object",next:"Regenerate commands.json."}),r(n)}const f=q(h,{transport:n.kind==="machine"?"http":n.kind,reservedCoreNames:t,extensibleCoreBranches:o,...i!==void 0?{coreCommandPaths:i}:{}});if(f.manifest===void 0)return f.issues.forEach(I(e,s)),r(n);const u=n.kind==="exec"?H(e,n,f.manifest,s):n;if(u===void 0)return r(n);if(u.kind==="exec"&&!W(e,f.manifest,s))return r(u);const g=e.manifest.provider,j=f.manifest.roots;if(!j.every(x=>x.kind==="branch"))throw new Error("plugin manifest validation returned a top-level leaf");return{plugin:e,manifestPath:l,transport:u,manifest:f.manifest,contributions:j.filter(x=>g===void 0||y(x)).map(x=>({plugin:e,transport:u,node:x,manifest:f.manifest,manifestName:x.name,...g!==void 0?{provider:g}:{}})),coreMounts:f.manifest.coreMounts.map(x=>({plugin:e,transport:u,parent:x.parent,node:x.node,manifest:f.manifest,...g!==void 0?{provider:g}:{}})),issues:s}}m(S,"validatePluginCommands");function ee(e=process.cwd(),t){return w(e,t).map(i=>S(i))}m(ee,"discoverPluginCommandCandidates");function C(e,t=process.cwd(),i,o,s=new Set){const a=w(t,i).map(r=>S(r,e,o,s)),d=new Map;for(const r of a)for(const n of r.contributions){const c=d.get(n.node.name);c===void 0?d.set(n.node.name,[n]):c.push(n)}for(const[r,n]of d)if(n.length>1)for(const c of n){const{plugin:p}=c,h=A({name:p.name,sourceMarketplace:p.sourceMarketplace},r),f=a.find(u=>u.plugin.root===p.root);f.contributions=f.contributions.map(u=>u===c?{...u,node:{...u.node,name:h}}:u),f.issues.push({code:"command_collision",plugin:p.name,path:r,message:`top-level command "${r}" is claimed by ${n.map(u=>u.plugin.name).join(", ")} \u2014 mounted as "${h}"`,received:n.map(u=>u.plugin.name).join(", "),expected:"an origin-qualified top-level command for each claimant",next:`Invoke "${h}" to use this plugin command.`})}const l=new Map;for(const r of a)for(const n of r.coreMounts){const c=[...n.parent,n.node.name].join("/"),p=l.get(c);p===void 0?l.set(c,[n]):p.push(n)}for(const[r,n]of l)if(n.length>1)for(const c of n){const p=a.find(h=>h.plugin.root===c.plugin.root);p.coreMounts=p.coreMounts.filter(h=>h!==c),p.issues.push({code:"command_collision",plugin:c.plugin.name,path:r.replace("/","."),message:`contributed command "${c.node.name}" conflicts with another plugin child below the extensible core branch`,received:n.map(h=>h.plugin.name).join(", "),expected:`a unique child name below ${c.parent.join(".")}`,next:"Rename the contributed command or remove this mount."})}return a}m(C,"validateEffectiveCommandPlugins");function X(e,t){return{...t,contributor:{name:e.name,scope:e.scope,sourceMarketplace:e.sourceMarketplace}}}m(X,"contributorIssue");function ne(e,t=process.cwd(),i,o,s=new Set){const a=C(e,t,i,o,s);return{contributions:a.flatMap(d=>d.contributions),coreMounts:a.flatMap(d=>d.coreMounts),issues:a.flatMap(d=>d.issues)}}m(ne,"discoverCommandContributions");function te(e,t=process.cwd(),i,o,s=new Set){const a=C(e,t,i,o,s),d=J(a.flatMap(n=>n.contributions)).contributions,l=E(d,e);return{contributions:T(l.contributions,t).contributions,coreMounts:$(a.flatMap(n=>n.coreMounts)),issues:[...a.flatMap(n=>n.issues.map(c=>X(n.plugin,c))),...l.issues]}}m(te,"buildExternalCommandSnapshot");export{te as buildExternalCommandSnapshot,ne as discoverCommandContributions,ee as discoverPluginCommandCandidates,C as validateEffectiveCommandPlugins,S as validatePluginCommands};
@@ -1 +1 @@
1
- var p=Object.defineProperty;var l=(r,n)=>p(r,"name",{value:n,configurable:!0});import{effectiveCommandPlugins as h,validatePluginCommands as c}from"./discovery.js";async function v(r,n,u,f){if(r.length===0)return[];const o=await n.extensibleCoreBranches(),a=[],i=h(u,f).filter(t=>{const e=c(t,n.reservedCoreNames,void 0,o).manifest;return e?.helpAddenda?.[r]===void 0?!1:(a.push(...Object.keys(e.helpAddenda),...e.coreMounts.map(d=>[...d.parent,d.node.name].join(" "))),!0)});if(i.length===0)return[];const m=await n.coreCommandPaths(a),s=[];for(const t of i){const e=c(t,n.reservedCoreNames,m,o).manifest?.helpAddenda?.[r];e!==void 0&&s.push({plugin:t.name,text:e})}return s.sort((t,e)=>t.plugin.localeCompare(e.plugin)),s}l(v,"collectHelpAddenda");export{v as collectHelpAddenda};
1
+ var p=Object.defineProperty;var l=(r,n)=>p(r,"name",{value:n,configurable:!0});import{validatePluginCommands as c}from"./discovery.js";import{effectiveCommandPlugins as h}from"./presence.js";async function x(r,n,f,u){if(r.length===0)return[];const s=await n.extensibleCoreBranches(),i=[],a=h(f,u).filter(t=>{const e=c(t,n.reservedCoreNames,void 0,s).manifest;return e?.helpAddenda?.[r]===void 0?!1:(i.push(...Object.keys(e.helpAddenda),...e.coreMounts.map(d=>[...d.parent,d.node.name].join(" "))),!0)});if(a.length===0)return[];const m=await n.coreCommandPaths(i),o=[];for(const t of a){const e=c(t,n.reservedCoreNames,m,s).manifest?.helpAddenda?.[r];e!==void 0&&o.push({plugin:t.name,text:e})}return o.sort((t,e)=>t.plugin.localeCompare(e.plugin)),o}l(x,"collectHelpAddenda");export{x as collectHelpAddenda};