@north-light/cli 0.3.561 → 0.3.563

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 (579) 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/build-root.d.ts +1 -0
  23. package/dist/build-root.js +1 -1
  24. package/dist/builtin-memory/crouter-sdk/release-notes.md +4 -0
  25. package/dist/cli.js +7 -7
  26. package/dist/clients/attach/chrome/bash-jobs.d.ts +11 -1
  27. package/dist/clients/attach/chrome/bash-jobs.js +1 -1
  28. package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
  29. package/dist/clients/attach/chrome/roster.js +6 -4
  30. package/dist/clients/attach/overlays/auth.js +2 -2
  31. package/dist/clients/attach/render/chat-view.js +1 -1
  32. package/dist/clients/attach/viewer.js +550 -567
  33. package/dist/commands/canvas-config.js +2 -2
  34. package/dist/commands/canvas-use.js +1 -1
  35. package/dist/commands/human.js +2 -2
  36. package/dist/commands/node/bash.js +3 -3
  37. package/dist/commands/node/create.js +2 -2
  38. package/dist/commands/person-settings.d.ts +1 -0
  39. package/dist/commands/person-settings.js +1 -0
  40. package/dist/commands/pkg/market-manage.d.ts +2 -2
  41. package/dist/commands/pkg/market-manage.js +1 -1
  42. package/dist/commands/pkg/plugin-manage.js +13 -13
  43. package/dist/commands/pkg/shared.js +1 -1
  44. package/dist/commands/profile/default.js +1 -1
  45. package/dist/commands/push.js +2 -3
  46. package/dist/commands/sys/config.js +1 -1
  47. package/dist/commands/sys/connect.js +1 -1
  48. package/dist/commands/sys/context/sysprompt.js +2 -2
  49. package/dist/commands/sys/doctor.js +1 -1
  50. package/dist/commands/sys/join.js +1 -1
  51. package/dist/commands/sys/panels/broker-limits-panel.js +1 -1
  52. package/dist/commands/sys/panels/keybindings-panel.js +1 -1
  53. package/dist/commands/sys/panels/models-panel.d.ts +8 -2
  54. package/dist/commands/sys/panels/models-panel.js +1 -1
  55. package/dist/commands/sys/panels/prefs-panel.d.ts +1 -0
  56. package/dist/commands/sys/panels/prefs-panel.js +1 -1
  57. package/dist/commands/sys/panels/profiles-panel.js +1 -1
  58. package/dist/commands/sys/panels/provider-panel.d.ts +4 -0
  59. package/dist/commands/sys/panels/provider-panel.js +1 -1
  60. package/dist/commands/sys/provider-login.js +5 -5
  61. package/dist/commands/sys/setup-core.js +3 -3
  62. package/dist/commands/sys/setup-settings-runner.js +1 -1
  63. package/dist/commands/sys/spend.js +3 -3
  64. package/dist/commands/sys/update.js +1 -1
  65. package/dist/core/auth-file.d.ts +11 -19
  66. package/dist/core/auth-file.js +1 -2
  67. package/dist/core/auto-update.d.ts +1 -1
  68. package/dist/core/auto-update.js +4 -4
  69. package/dist/core/bash-jobs.d.ts +29 -71
  70. package/dist/core/bash-jobs.js +9 -10
  71. package/dist/core/blobs/directory.d.ts +3 -0
  72. package/dist/core/blobs/directory.js +1 -0
  73. package/dist/core/blobs/fault.d.ts +3 -0
  74. package/dist/core/blobs/fault.js +1 -0
  75. package/dist/core/blobs/index.d.ts +21 -0
  76. package/dist/core/blobs/index.js +1 -0
  77. package/dist/core/blobs/r2.d.ts +9 -0
  78. package/dist/core/blobs/r2.js +4 -0
  79. package/dist/core/bootstrap.d.ts +1 -1
  80. package/dist/core/bootstrap.js +2 -2
  81. package/dist/core/canvas/canvas.d.ts +9 -7
  82. package/dist/core/canvas/canvas.js +8 -8
  83. package/dist/core/canvas/crons.d.ts +3 -2
  84. package/dist/core/canvas/crons.js +3 -3
  85. package/dist/core/canvas/daemon-owner.d.ts +5 -0
  86. package/dist/core/canvas/daemon-owner.js +1 -1
  87. package/dist/core/canvas/db.d.ts +10 -9
  88. package/dist/core/canvas/db.js +4 -4
  89. package/dist/core/canvas/executions.d.ts +100 -4
  90. package/dist/core/canvas/executions.js +1 -1
  91. package/dist/core/canvas/export-sqlite.js +1 -1
  92. package/dist/core/canvas/focuses.d.ts +4 -0
  93. package/dist/core/canvas/focuses.js +1 -1
  94. package/dist/core/canvas/history.js +9 -9
  95. package/dist/core/canvas/managed-worktree-sweep-candidates.d.ts +2 -1
  96. package/dist/core/canvas/managed-worktree-sweep-candidates.js +3 -2
  97. package/dist/core/canvas/node-tokens.d.ts +1 -4
  98. package/dist/core/canvas/node-tokens.js +3 -3
  99. package/dist/core/canvas/paths.d.ts +0 -1
  100. package/dist/core/canvas/paths.js +1 -1
  101. package/dist/core/canvas/person-state.d.ts +26 -0
  102. package/dist/core/canvas/person-state.js +1 -0
  103. package/dist/core/canvas/person.d.ts +3 -10
  104. package/dist/core/canvas/person.js +7 -7
  105. package/dist/core/canvas/reconciler-leases.d.ts +12 -0
  106. package/dist/core/canvas/reconciler-leases.js +8 -0
  107. package/dist/core/canvas/recovery.d.ts +4 -0
  108. package/dist/core/canvas/recovery.js +1 -1
  109. package/dist/core/canvas/schema-pg/003-fleet-removals.sql +1 -1
  110. package/dist/core/canvas/schema-pg/005-fleet-blobs.sql +13 -0
  111. package/dist/core/canvas/schema-pg/006-job-rows.sql +20 -0
  112. package/dist/core/canvas/schema-pg/007-ledger-check.sql +117 -0
  113. package/dist/core/canvas/schema-version.d.ts +1 -1
  114. package/dist/core/canvas/schema-version.js +1 -1
  115. package/dist/core/canvas/sessions.d.ts +8 -11
  116. package/dist/core/canvas/sessions.js +1 -4
  117. package/dist/core/canvas/tree.d.ts +4 -2
  118. package/dist/core/canvas/tree.js +6 -6
  119. package/dist/core/canvas/types.d.ts +7 -0
  120. package/dist/core/command-hooks/transport/exec-lifecycle.js +1 -1
  121. package/dist/core/command-plugins/bundle.d.ts +1 -1
  122. package/dist/core/command-plugins/bundle.js +1 -1
  123. package/dist/core/command-plugins/compose.d.ts +1 -1
  124. package/dist/core/command-plugins/discovery.d.ts +0 -1
  125. package/dist/core/command-plugins/discovery.js +1 -1
  126. package/dist/core/command-plugins/help-addenda.js +1 -1
  127. package/dist/core/command-plugins/presence.d.ts +2 -0
  128. package/dist/core/command-plugins/presence.js +1 -1
  129. package/dist/core/config.d.ts +16 -15
  130. package/dist/core/config.js +1 -1
  131. package/dist/core/conversation-store/client-worker.js +1 -1
  132. package/dist/core/conversation-store/client.js +2 -2
  133. package/dist/core/conversation-store/index.d.ts +9 -11
  134. package/dist/core/conversation-store/index.js +1 -1
  135. package/dist/core/conversation-store/patch.js +1 -1
  136. package/dist/core/conversation-store/row-store.d.ts +14 -0
  137. package/dist/core/conversation-store/row-store.js +13 -0
  138. package/dist/core/directory/join.d.ts +5 -3
  139. package/dist/core/directory/join.js +1 -1
  140. package/dist/core/env-name.d.ts +1 -0
  141. package/dist/core/env-name.js +1 -1
  142. package/dist/core/environment-recipe.d.ts +15 -0
  143. package/dist/core/environment-recipe.js +1 -0
  144. package/dist/core/environments.js +1 -1
  145. package/dist/core/events/emit.js +2 -2
  146. package/dist/core/events/run-event-chunks.d.ts +25 -0
  147. package/dist/core/events/run-event-chunks.js +5 -0
  148. package/dist/core/feed/messages.js +5 -5
  149. package/dist/core/grants/mirror.d.ts +15 -0
  150. package/dist/core/grants/mirror.js +15 -3
  151. package/dist/core/grants/on-sync.d.ts +9 -5
  152. package/dist/core/grants/on-sync.js +1 -1
  153. package/dist/core/grants/remove.d.ts +1 -6
  154. package/dist/core/grants/remove.js +1 -1
  155. package/dist/core/grants/sync.js +1 -1
  156. package/dist/core/grants/watch.d.ts +15 -9
  157. package/dist/core/grants/watch.js +1 -1
  158. package/dist/core/graph/custom.js +8 -8
  159. package/dist/core/graph/jobs.d.ts +83 -5
  160. package/dist/core/graph/jobs.js +11 -2
  161. package/dist/core/graph/kinds/job.js +2 -2
  162. package/dist/core/graph/names.js +7 -7
  163. package/dist/core/graph/objects.d.ts +5 -1
  164. package/dist/core/graph/objects.js +11 -11
  165. package/dist/core/graph/reader.d.ts +3 -0
  166. package/dist/core/graph/reader.js +3 -3
  167. package/dist/core/graph/repo-sync/identity.d.ts +3 -1
  168. package/dist/core/graph/repo-sync/identity.js +1 -1
  169. package/dist/core/graph/repo-sync/repos.d.ts +1 -1
  170. package/dist/core/graph/repo-sync/repos.js +2 -2
  171. package/dist/core/graph/repo-sync/sync.js +2 -2
  172. package/dist/core/installed-plugins.js +1 -1
  173. package/dist/core/keybindings/persistence.d.ts +1 -1
  174. package/dist/core/keybindings/persistence.js +1 -1
  175. package/dist/core/layout-migrate/loose-keys.d.ts +1 -1
  176. package/dist/core/layout-migrate/loose-keys.js +1 -1
  177. package/dist/core/ledger/account.d.ts +12 -0
  178. package/dist/core/ledger/account.js +2 -0
  179. package/dist/core/ledger/charges.d.ts +2 -2
  180. package/dist/core/ledger/charges.js +1 -1
  181. package/dist/core/ledger/check.d.ts +5 -1
  182. package/dist/core/ledger/check.js +1 -7
  183. package/dist/core/ledger/chunks.d.ts +8 -0
  184. package/dist/core/ledger/chunks.js +2 -0
  185. package/dist/core/ledger/index.d.ts +2 -1
  186. package/dist/core/ledger/index.js +1 -1
  187. package/dist/core/ledger/infra-allowance.d.ts +9 -0
  188. package/dist/core/ledger/infra-allowance.js +1 -0
  189. package/dist/core/ledger/limits.js +3 -3
  190. package/dist/core/ledger/payer.d.ts +1 -1
  191. package/dist/core/ledger/payer.js +1 -1
  192. package/dist/core/ledger/read.d.ts +14 -4
  193. package/dist/core/ledger/read.js +2 -3
  194. package/dist/core/ledger/totals.d.ts +5 -16
  195. package/dist/core/ledger/totals.js +4 -4
  196. package/dist/core/ledger/types.d.ts +12 -3
  197. package/dist/core/llm/run.js +2 -2
  198. package/dist/core/managed-providers.d.ts +72 -0
  199. package/dist/core/managed-providers.js +1 -0
  200. package/dist/core/model-auth/api-credential-store.d.ts +10 -0
  201. package/dist/core/model-auth/api-credential-store.js +1 -0
  202. package/dist/core/model-auth/daemon-login.d.ts +1 -1
  203. package/dist/core/model-auth/daemon-login.js +1 -1
  204. package/dist/core/model-auth/flows.d.ts +5 -4
  205. package/dist/core/model-auth/flows.js +1 -1
  206. package/dist/core/model-auth/handoff-jti.d.ts +3 -4
  207. package/dist/core/model-auth/handoff-jti.js +1 -1
  208. package/dist/core/model-auth/readiness.js +1 -1
  209. package/dist/core/model-config.d.ts +1 -1
  210. package/dist/core/model-config.js +1 -1
  211. package/dist/core/model-routes-pool.d.ts +14 -0
  212. package/dist/core/model-routes-pool.js +1 -0
  213. package/dist/core/model-routes.d.ts +11 -16
  214. package/dist/core/model-routes.js +1 -1
  215. package/dist/core/peer/ingest.d.ts +2 -0
  216. package/dist/core/peer/ingest.js +1 -1
  217. package/dist/core/peer/stubs.d.ts +8 -0
  218. package/dist/core/peer/stubs.js +1 -1
  219. package/dist/core/permissions/evaluator.d.ts +7 -2
  220. package/dist/core/permissions/evaluator.js +2 -2
  221. package/dist/core/plugin-env.js +1 -1
  222. package/dist/core/pool-credential-store.d.ts +2 -5
  223. package/dist/core/pool-credential-store.js +1 -1
  224. package/dist/core/profiles/app-env-store.d.ts +5 -5
  225. package/dist/core/profiles/app-env-store.js +1 -2
  226. package/dist/core/profiles/default-binding.d.ts +5 -5
  227. package/dist/core/profiles/default-binding.js +1 -1
  228. package/dist/core/profiles/env-store.d.ts +10 -21
  229. package/dist/core/profiles/env-store.js +1 -2
  230. package/dist/core/profiles/manifest.d.ts +14 -18
  231. package/dist/core/profiles/manifest.js +1 -1
  232. package/dist/core/profiles/relocate.js +1 -1
  233. package/dist/core/profiles/select.js +21 -21
  234. package/dist/core/provider-credential.d.ts +1 -1
  235. package/dist/core/provider-credential.js +1 -1
  236. package/dist/core/provider-management.d.ts +3 -4
  237. package/dist/core/provider-management.js +1 -1
  238. package/dist/core/providers/bundles.js +2 -2
  239. package/dist/core/providers/call.d.ts +3 -34
  240. package/dist/core/providers/description.d.ts +34 -0
  241. package/dist/core/providers/description.js +0 -0
  242. package/dist/core/providers/token-cache.d.ts +14 -14
  243. package/dist/core/providers/token-cache.js +1 -1
  244. package/dist/core/review/companion.js +1 -1
  245. package/dist/core/runs/activity.d.ts +1 -1
  246. package/dist/core/runs/activity.js +1 -1
  247. package/dist/core/runs/events.d.ts +7 -5
  248. package/dist/core/runs/events.js +2 -6
  249. package/dist/core/runs/operations.d.ts +1 -1
  250. package/dist/core/runs/operations.js +6 -6
  251. package/dist/core/runtime/bearings.js +1 -1
  252. package/dist/core/runtime/broker/attachments-fetch.d.ts +5 -0
  253. package/dist/core/runtime/broker/attachments-fetch.js +1 -0
  254. package/dist/core/runtime/broker/bash-job-launch.d.ts +4 -2
  255. package/dist/core/runtime/broker/bash-job-launch.js +1 -1
  256. package/dist/core/runtime/broker/boot-state.d.ts +9 -0
  257. package/dist/core/runtime/broker/boot-state.js +1 -0
  258. package/dist/core/runtime/broker/cli-relay.d.ts +2 -1
  259. package/dist/core/runtime/broker/cli-relay.js +1 -1
  260. package/dist/core/runtime/broker/client-registry.d.ts +3 -3
  261. package/dist/core/runtime/broker/daemon-ops.d.ts +10 -1
  262. package/dist/core/runtime/broker/daemon-ops.js +1 -1
  263. package/dist/core/runtime/broker/fault-retry.d.ts +2 -0
  264. package/dist/core/runtime/broker/fault-retry.js +1 -1
  265. package/dist/core/runtime/broker/frame-engine-control.js +1 -1
  266. package/dist/core/runtime/broker/held-deferred-inbox.d.ts +3 -0
  267. package/dist/core/runtime/broker/held-deferred-inbox.js +1 -1
  268. package/dist/core/runtime/broker/job-reporter.d.ts +18 -0
  269. package/dist/core/runtime/broker/job-reporter.js +2 -0
  270. package/dist/core/runtime/broker/model-adapter.d.ts +3 -0
  271. package/dist/core/runtime/broker/model-adapter.js +3 -2
  272. package/dist/core/runtime/broker/signal-stream.js +1 -1
  273. package/dist/core/runtime/broker/spilled-body.d.ts +5 -0
  274. package/dist/core/runtime/broker/spilled-body.js +1 -0
  275. package/dist/core/runtime/broker/summaries-post.d.ts +5 -0
  276. package/dist/core/runtime/broker/summaries-post.js +1 -0
  277. package/dist/core/runtime/broker/tool-groups.d.ts +3 -0
  278. package/dist/core/runtime/broker/tool-groups.js +1 -1
  279. package/dist/core/runtime/broker/turn-replies.d.ts +1 -0
  280. package/dist/core/runtime/broker/turn-replies.js +1 -1
  281. package/dist/core/runtime/broker-persona-guidance.js +2 -2
  282. package/dist/core/runtime/broker.js +1 -1
  283. package/dist/core/runtime/deliver-live.js +1 -1
  284. package/dist/core/runtime/first-boot-rows.d.ts +8 -0
  285. package/dist/core/runtime/first-boot-rows.js +2 -0
  286. package/dist/core/runtime/fleet.d.ts +13 -12
  287. package/dist/core/runtime/front-door-login.js +1 -1
  288. package/dist/core/runtime/front-door.js +6 -6
  289. package/dist/core/runtime/headless-pi.js +1 -1
  290. package/dist/core/runtime/host.d.ts +14 -3
  291. package/dist/core/runtime/host.js +3 -3
  292. package/dist/core/runtime/invocation.d.ts +6 -0
  293. package/dist/core/runtime/kickoff.d.ts +2 -14
  294. package/dist/core/runtime/kickoff.js +17 -19
  295. package/dist/core/runtime/launch-config-worker.js +1 -1
  296. package/dist/core/runtime/launch.js +2 -2
  297. package/dist/core/runtime/model-registry.js +1 -1
  298. package/dist/core/runtime/node-read.d.ts +4 -6
  299. package/dist/core/runtime/node-read.js +3 -3
  300. package/dist/core/runtime/nodes.js +1 -1
  301. package/dist/core/runtime/outcome-document.d.ts +0 -1
  302. package/dist/core/runtime/outcome-document.js +1 -1
  303. package/dist/core/runtime/output-result.d.ts +15 -0
  304. package/dist/core/runtime/output-result.js +5 -0
  305. package/dist/core/runtime/placement.d.ts +17 -1
  306. package/dist/core/runtime/placement.js +1 -1
  307. package/dist/core/runtime/provider-credential-gate.d.ts +6 -2
  308. package/dist/core/runtime/provider-credential-gate.js +2 -2
  309. package/dist/core/runtime/recovery-hold.d.ts +21 -2
  310. package/dist/core/runtime/recovery-hold.js +2 -1
  311. package/dist/core/runtime/recycle.js +1 -1
  312. package/dist/core/runtime/reset.js +1 -1
  313. package/dist/core/runtime/revive.d.ts +2 -2
  314. package/dist/core/runtime/revive.js +2 -2
  315. package/dist/core/runtime/runner/blaxel/client.d.ts +81 -0
  316. package/dist/core/runtime/runner/blaxel/client.js +1 -0
  317. package/dist/core/runtime/runner/blaxel/image-build.d.ts +74 -0
  318. package/dist/core/runtime/runner/blaxel/image-build.js +5 -0
  319. package/dist/core/runtime/runner/blaxel/runner.d.ts +41 -0
  320. package/dist/core/runtime/runner/blaxel/runner.js +3 -0
  321. package/dist/core/runtime/runner/index.d.ts +8 -0
  322. package/dist/core/runtime/runner/index.js +1 -0
  323. package/dist/core/runtime/runner/process.d.ts +11 -0
  324. package/dist/core/runtime/runner/process.js +1 -0
  325. package/dist/core/runtime/runner/types.d.ts +95 -0
  326. package/dist/core/runtime/runner/types.js +0 -0
  327. package/dist/core/runtime/situational-context.d.ts +3 -6
  328. package/dist/core/runtime/situational-context.js +2 -2
  329. package/dist/core/runtime/situational-live.js +1 -1
  330. package/dist/core/runtime/spawn-env-rows.d.ts +5 -0
  331. package/dist/core/runtime/spawn-env-rows.js +1 -0
  332. package/dist/core/runtime/spawn-env.d.ts +4 -18
  333. package/dist/core/runtime/spawn-env.js +1 -1
  334. package/dist/core/runtime/spawn.js +2 -2
  335. package/dist/core/runtime/stop-guard.d.ts +1 -1
  336. package/dist/core/runtime/stop-guard.js +2 -8
  337. package/dist/core/runtime/structured-output.d.ts +0 -67
  338. package/dist/core/runtime/structured-output.js +3 -8
  339. package/dist/core/runtime/tmux-driver.d.ts +8 -0
  340. package/dist/core/runtime/tmux-driver.js +6 -6
  341. package/dist/core/runtime/view-socket.d.ts +20 -0
  342. package/dist/core/runtime/view-socket.js +3 -1
  343. package/dist/core/runtime/warm-pool.d.ts +13 -1
  344. package/dist/core/runtime/warm-pool.js +1 -1
  345. package/dist/core/scoped-state/legacy-profiles.d.ts +11 -0
  346. package/dist/core/scoped-state/legacy-profiles.js +1 -0
  347. package/dist/core/scoped-state/materialized-plugins.d.ts +18 -0
  348. package/dist/core/scoped-state/materialized-plugins.js +1 -0
  349. package/dist/core/scoped-state/migrate.d.ts +8 -1
  350. package/dist/core/scoped-state/migrate.js +3 -3
  351. package/dist/core/scoped-state/person-rows.d.ts +10 -0
  352. package/dist/core/scoped-state/person-rows.js +6 -0
  353. package/dist/core/scoped-state/person-store.d.ts +72 -0
  354. package/dist/core/scoped-state/person-store.js +1 -0
  355. package/dist/core/secrets/blaxel-launch.d.ts +24 -0
  356. package/dist/core/secrets/blaxel-launch.js +4 -0
  357. package/dist/core/secrets/blaxel-register.d.ts +2 -0
  358. package/dist/core/secrets/blaxel-register.js +1 -0
  359. package/dist/core/secrets/env-rows.d.ts +17 -0
  360. package/dist/core/secrets/env-rows.js +4 -0
  361. package/dist/core/secrets/environment-owner.d.ts +8 -0
  362. package/dist/core/secrets/environment-owner.js +1 -0
  363. package/dist/core/secrets/environment-secrets.d.ts +14 -0
  364. package/dist/core/secrets/environment-secrets.js +1 -0
  365. package/dist/core/secrets/index.d.ts +2 -0
  366. package/dist/core/secrets/index.js +1 -0
  367. package/dist/core/secrets/key.d.ts +3 -0
  368. package/dist/core/secrets/key.js +2 -0
  369. package/dist/core/secrets/seal.d.ts +18 -0
  370. package/dist/core/secrets/seal.js +1 -0
  371. package/dist/core/spaces/mounts.d.ts +1 -1
  372. package/dist/core/spaces/mounts.js +1 -1
  373. package/dist/core/spaces/process.d.ts +1 -1
  374. package/dist/core/spaces/stop.d.ts +7 -5
  375. package/dist/core/spaces/stop.js +2 -2
  376. package/dist/core/spaces/users.d.ts +2 -1
  377. package/dist/core/spaces/users.js +1 -1
  378. package/dist/core/storage/attachments.d.ts +11 -8
  379. package/dist/core/storage/attachments.js +1 -1
  380. package/dist/core/storage/tables.d.ts +4 -0
  381. package/dist/core/storage/tables.js +3 -3
  382. package/dist/core/subscription-state.d.ts +34 -72
  383. package/dist/core/subscription-state.js +1 -4
  384. package/dist/core/substrate/delivery/plan.js +1 -1
  385. package/dist/core/user-settings.js +1 -1
  386. package/dist/daemon/api/app-listener.js +10 -10
  387. package/dist/daemon/api/auth/person-bearer.d.ts +14 -0
  388. package/dist/daemon/api/auth/person-bearer.js +1 -0
  389. package/dist/daemon/api/bridge.js +4 -4
  390. package/dist/daemon/api/document-listener.js +3 -3
  391. package/dist/daemon/api/environment-of.d.ts +18 -0
  392. package/dist/daemon/api/environment-of.js +5 -0
  393. package/dist/daemon/api/handlers/analytics.js +1 -1
  394. package/dist/daemon/api/handlers/app-profiles.d.ts +2 -2
  395. package/dist/daemon/api/handlers/app-profiles.js +1 -1
  396. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  397. package/dist/daemon/api/handlers/bash.js +1 -1
  398. package/dist/daemon/api/handlers/broker-ops.d.ts +3 -0
  399. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  400. package/dist/daemon/api/handlers/broker-recovery.js +1 -1
  401. package/dist/daemon/api/handlers/broker-signals.d.ts +0 -3
  402. package/dist/daemon/api/handlers/broker-signals.js +2 -2
  403. package/dist/daemon/api/handlers/canvas.js +4 -4
  404. package/dist/daemon/api/handlers/charges.d.ts +1 -1
  405. package/dist/daemon/api/handlers/charges.js +1 -1
  406. package/dist/daemon/api/handlers/conversations.d.ts +2 -0
  407. package/dist/daemon/api/handlers/conversations.js +3 -0
  408. package/dist/daemon/api/handlers/crons.js +1 -1
  409. package/dist/daemon/api/handlers/daemon.js +1 -1
  410. package/dist/daemon/api/handlers/delivery.d.ts +1 -1
  411. package/dist/daemon/api/handlers/delivery.js +1 -1
  412. package/dist/daemon/api/handlers/docs.js +1 -1
  413. package/dist/daemon/api/handlers/exec.js +1 -1
  414. package/dist/daemon/api/handlers/execution-start.d.ts +2 -0
  415. package/dist/daemon/api/handlers/execution-start.js +2 -0
  416. package/dist/daemon/api/handlers/files.js +1 -1
  417. package/dist/daemon/api/handlers/git-credential.d.ts +11 -0
  418. package/dist/daemon/api/handlers/git-credential.js +1 -0
  419. package/dist/daemon/api/handlers/hook-exec.d.ts +1 -1
  420. package/dist/daemon/api/handlers/hook-exec.js +1 -1
  421. package/dist/daemon/api/handlers/human-requests.js +1 -1
  422. package/dist/daemon/api/handlers/inbox.js +1 -1
  423. package/dist/daemon/api/handlers/messages.js +1 -1
  424. package/dist/daemon/api/handlers/model-config.js +1 -1
  425. package/dist/daemon/api/handlers/modelauth.d.ts +1 -1
  426. package/dist/daemon/api/handlers/modelauth.js +1 -1
  427. package/dist/daemon/api/handlers/node-events.d.ts +6 -0
  428. package/dist/daemon/api/handlers/node-events.js +3 -3
  429. package/dist/daemon/api/handlers/node-outcomes.js +1 -1
  430. package/dist/daemon/api/handlers/node-records.js +3 -2
  431. package/dist/daemon/api/handlers/nodes.js +1 -1
  432. package/dist/daemon/api/handlers/objects.js +3 -3
  433. package/dist/daemon/api/handlers/profiles.js +1 -1
  434. package/dist/daemon/api/handlers/providers.js +1 -1
  435. package/dist/daemon/api/handlers/reports.js +9 -13
  436. package/dist/daemon/api/handlers/reviews.js +1 -1
  437. package/dist/daemon/api/handlers/runs.js +1 -1
  438. package/dist/daemon/api/handlers/settings.d.ts +2 -0
  439. package/dist/daemon/api/handlers/settings.js +1 -0
  440. package/dist/daemon/api/handlers/structured-requests.js +1 -1
  441. package/dist/daemon/api/handlers/vendor-status.d.ts +1 -1
  442. package/dist/daemon/api/handlers/vendor-status.js +1 -1
  443. package/dist/daemon/api/handlers/worktree.js +1 -1
  444. package/dist/daemon/api/map.d.ts +2 -2
  445. package/dist/daemon/api/map.js +2 -2
  446. package/dist/daemon/api/operations.js +1 -1
  447. package/dist/daemon/api/reader.d.ts +8 -1
  448. package/dist/daemon/api/reader.js +1 -1
  449. package/dist/daemon/api/router.d.ts +4 -2
  450. package/dist/daemon/api/router.js +1 -1
  451. package/dist/daemon/api/server.d.ts +15 -3
  452. package/dist/daemon/api/server.js +1 -1
  453. package/dist/daemon/api/vendor-signin.d.ts +2 -1
  454. package/dist/daemon/api/vendor-signin.js +19 -19
  455. package/dist/daemon/api/viewer-presence.d.ts +20 -6
  456. package/dist/daemon/api/viewer-presence.js +2 -1
  457. package/dist/daemon/boot-settings.d.ts +3 -0
  458. package/dist/daemon/boot-settings.js +1 -0
  459. package/dist/daemon/broker-signals/bus.d.ts +48 -11
  460. package/dist/daemon/broker-signals/bus.js +1 -1
  461. package/dist/daemon/cron/armed-context.js +1 -1
  462. package/dist/daemon/cron/lease-recovery.js +1 -1
  463. package/dist/daemon/cron/live-runs.d.ts +6 -0
  464. package/dist/daemon/cron/live-runs.js +1 -1
  465. package/dist/daemon/cron/run-socket.js +1 -1
  466. package/dist/daemon/cron-run.js +2 -2
  467. package/dist/daemon/crtrd-cli.js +1 -1
  468. package/dist/daemon/crtrd.d.ts +10 -0
  469. package/dist/daemon/crtrd.js +18 -17
  470. package/dist/daemon/delivery/run-delivery.d.ts +1 -1
  471. package/dist/daemon/delivery/run-delivery.js +1 -1
  472. package/dist/daemon/due-sources.d.ts +1 -1
  473. package/dist/daemon/due-sources.js +1 -1
  474. package/dist/daemon/due.js +7 -6
  475. package/dist/daemon/fleet.d.ts +6 -6
  476. package/dist/daemon/fleet.js +9 -9
  477. package/dist/daemon/park-activity.d.ts +2 -1
  478. package/dist/daemon/park-activity.js +1 -1
  479. package/dist/daemon/park-pending.d.ts +20 -13
  480. package/dist/daemon/park-pending.js +3 -1
  481. package/dist/daemon/profile-delete.js +1 -1
  482. package/dist/daemon/reconcilers/bash-deadline.d.ts +15 -10
  483. package/dist/daemon/reconcilers/bash-deadline.js +1 -3
  484. package/dist/daemon/reconcilers/broker-supervision.d.ts +13 -3
  485. package/dist/daemon/reconcilers/broker-supervision.js +4 -4
  486. package/dist/daemon/reconcilers/cron-lane.d.ts +0 -1
  487. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  488. package/dist/daemon/reconcilers/fault-retry-backstop.d.ts +2 -9
  489. package/dist/daemon/reconcilers/fault-retry-backstop.js +1 -1
  490. package/dist/daemon/reconcilers/grant-effects.d.ts +8 -0
  491. package/dist/daemon/reconcilers/grant-effects.js +1 -0
  492. package/dist/daemon/reconcilers/grove-instance-sweep.d.ts +1 -3
  493. package/dist/daemon/reconcilers/grove-instance-sweep.js +1 -1
  494. package/dist/daemon/reconcilers/ledger-r2.d.ts +27 -0
  495. package/dist/daemon/reconcilers/ledger-r2.js +7 -0
  496. package/dist/daemon/reconcilers/ledger-sandbox.d.ts +55 -0
  497. package/dist/daemon/reconcilers/ledger-sandbox.js +9 -0
  498. package/dist/daemon/reconcilers/ledger-storage.d.ts +22 -0
  499. package/dist/daemon/reconcilers/ledger-storage.js +5 -0
  500. package/dist/daemon/reconcilers/live-obligation.d.ts +3 -2
  501. package/dist/daemon/reconcilers/live-obligation.js +1 -1
  502. package/dist/daemon/reconcilers/managed-worktree-sweep.d.ts +0 -5
  503. package/dist/daemon/reconcilers/managed-worktree-sweep.js +1 -1
  504. package/dist/daemon/reconcilers/node-deadline.d.ts +2 -1
  505. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  506. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -2
  507. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  508. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +31 -0
  509. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -0
  510. package/dist/daemon/reconcilers/node-lifecycle/requested-stops.d.ts +5 -0
  511. package/dist/daemon/reconcilers/node-lifecycle/requested-stops.js +2 -0
  512. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  513. package/dist/daemon/reconcilers/pending-review-submit.js +1 -1
  514. package/dist/daemon/reconcilers/recipe-image.d.ts +49 -0
  515. package/dist/daemon/reconcilers/recipe-image.js +7 -0
  516. package/dist/daemon/reconcilers/row-claim.d.ts +19 -0
  517. package/dist/daemon/reconcilers/row-claim.js +2 -0
  518. package/dist/daemon/reconcilers/storage-due-work.d.ts +18 -3
  519. package/dist/daemon/reconcilers/storage-due-work.js +2 -1
  520. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -13
  521. package/dist/daemon/reconcilers/storage-maintenance.js +1 -1
  522. package/dist/daemon/reconcilers/structured-request-lane.js +1 -1
  523. package/dist/daemon/reconcilers/throttled-lane.d.ts +11 -0
  524. package/dist/daemon/reconcilers/throttled-lane.js +1 -0
  525. package/dist/daemon/review/finish.js +1 -1
  526. package/dist/daemon/run-notices.d.ts +3 -0
  527. package/dist/daemon/run-notices.js +1 -1
  528. package/dist/daemon/runs/append-section.d.ts +4 -1
  529. package/dist/daemon/runs/append-section.js +2 -2
  530. package/dist/daemon/runs/observer.d.ts +3 -2
  531. package/dist/daemon/runs/observer.js +3 -2
  532. package/dist/daemon/runs/view-transport.d.ts +19 -0
  533. package/dist/daemon/runs/view-transport.js +1 -0
  534. package/dist/daemon/structured-requests/boot.d.ts +2 -1
  535. package/dist/daemon/structured-requests/boot.js +1 -1
  536. package/dist/daemon/structured-requests/effects.d.ts +4 -3
  537. package/dist/daemon/structured-requests/effects.js +1 -1
  538. package/dist/daemon/structured-requests/notifier.d.ts +2 -0
  539. package/dist/daemon/structured-requests/notifier.js +1 -1
  540. package/dist/daemon/structured-requests/sidecar.d.ts +4 -3
  541. package/dist/daemon/structured-requests/sidecar.js +1 -1
  542. package/dist/launcher/validate.js +1 -1
  543. package/dist/migrations/004-canvas-documents/index.js +2 -2
  544. package/dist/pi-extensions/broker-local.d.ts +3 -3
  545. package/dist/pi-extensions/broker-local.js +1 -2
  546. package/dist/pi-extensions/canvas-bash-valve.js +6 -6
  547. package/dist/pi-extensions/canvas-context-intro.js +2 -2
  548. package/dist/pi-extensions/canvas-elapsed.js +2 -2
  549. package/dist/pi-extensions/canvas-goal-capture.js +1 -1
  550. package/dist/pi-extensions/canvas-inbox-watcher.js +2 -2
  551. package/dist/pi-extensions/canvas-review-boundary.d.ts +11 -1
  552. package/dist/pi-extensions/canvas-review-boundary.js +3 -3
  553. package/dist/pi-extensions/canvas-stophook.js +1 -1
  554. package/dist/pi-extensions/canvas-structured-output.d.ts +5 -3
  555. package/dist/pi-extensions/canvas-structured-output.js +6 -11
  556. package/dist/pi-extensions/canvas-tool-guide.d.ts +9 -2
  557. package/dist/pi-extensions/canvas-tool-guide.js +6 -6
  558. package/dist/shared/inbox-entry-body.js +7 -7
  559. package/dist/types.d.ts +12 -0
  560. package/docs/sdk/release-notes.md +4 -0
  561. package/package.json +7 -4
  562. package/packages/crouter-identity/package.json +1 -1
  563. package/runtime.lock.json +13 -13
  564. package/scripts/git-credential-crtr +33 -0
  565. package/scripts/sandbox-bootstrap.sh +54 -0
  566. package/scripts/sandbox-start.mjs +320 -0
  567. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +0 -78
  568. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +0 -1179
  569. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +0 -1406
  570. package/dist/core/conversation-store/file-store.d.ts +0 -37
  571. package/dist/core/conversation-store/file-store.js +0 -9
  572. package/dist/core/conversation-store/listener.d.ts +0 -4
  573. package/dist/core/conversation-store/listener.js +0 -8
  574. package/dist/core/scoped-state/profiles.d.ts +0 -27
  575. package/dist/core/scoped-state/profiles.js +0 -1
  576. package/dist/core/scoped-state/providers.d.ts +0 -24
  577. package/dist/core/scoped-state/providers.js +0 -1
  578. package/dist/core/scoped-state/settings.d.ts +0 -28
  579. package/dist/core/scoped-state/settings.js +0 -1
@@ -0,0 +1,72 @@
1
+ export declare const ANTHROPIC_PROVIDER_ID = "anthropic";
2
+ export declare const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
3
+ /** pi's built-in `openai` provider signed in with a ChatGPT account (dynamic OAuth client). Distinct from Codex: its own pool, credential file and sign-in. */
4
+ export declare const OPENAI_CHATGPT_PROVIDER_ID = "openai";
5
+ /** Longest a recorded 429 keeps an account or model family cooling, whatever the provider's Retry-After or window reset says: after this the next real call probes the provider, and a success clears the row. */
6
+ export declare const MAX_RATE_LIMIT_COOLDOWN_MS: number;
7
+ export declare const INVALID_REFRESH_TOKEN_BACKOFF_MS: number;
8
+ export type ManagedProviderId = typeof ANTHROPIC_PROVIDER_ID | typeof OPENAI_CODEX_PROVIDER_ID | typeof OPENAI_CHATGPT_PROVIDER_ID;
9
+ export declare const MANAGED_PROVIDER_IDS: readonly ManagedProviderId[];
10
+ export type ModelStrength = 'ultra' | 'strong' | 'medium' | 'light';
11
+ export type RotationConfig = {
12
+ defaultFallbackStrength?: ModelStrength;
13
+ revertWhenAvailable?: boolean;
14
+ };
15
+ /** `rateLimitedUntil` cools the whole account; `modelCooldowns` cools one model family on it (family → epoch ms, stored rows including expired ones). Read both through `coolingUntilFor`. */
16
+ export type SubscriptionCredential = {
17
+ label: string;
18
+ refresh: string;
19
+ access: string;
20
+ expires: number;
21
+ rateLimitedUntil: number;
22
+ lastAttemptAt: number;
23
+ lastRateLimitedAt: number;
24
+ authFailure?: 'invalid_grant';
25
+ accountId?: string;
26
+ clientId?: string;
27
+ modelCooldowns?: Record<string, number>;
28
+ };
29
+ export type ProviderCandidate = {
30
+ providerId: string;
31
+ modelId: string;
32
+ thinkingLevel?: string;
33
+ registered: boolean;
34
+ authenticated: boolean;
35
+ coolingUntil?: number;
36
+ automaticFallback?: boolean;
37
+ routeId?: string;
38
+ credentialSource?: string;
39
+ family?: string;
40
+ strength?: ModelStrength;
41
+ rank?: number;
42
+ };
43
+ export type ProviderCandidateRejection = 'unregistered' | 'non-managed-fallback' | 'unauthenticated' | 'cooling';
44
+ /** When this account can next serve `modelId`: the later of its account-wide cooldown and the cooldown on that model's family. Without a model only the account-wide cooldown applies. 0 or a past time means ready. */
45
+ export declare function coolingUntilFor(entry: Pick<SubscriptionCredential, 'rateLimitedUntil' | 'modelCooldowns'>, modelId?: string): number;
46
+ /** Every cooldown on an account still in force at `now`: `scope` is `account` or the model family. */
47
+ export declare function activeCooldowns(entry: Pick<SubscriptionCredential, 'rateLimitedUntil' | 'modelCooldowns'>, now?: number): Array<{
48
+ scope: string;
49
+ until: number;
50
+ }>;
51
+ /** Human text for an account's active cooldowns (`cooldown 12m`, `opus cooldown 3d 4h`), or undefined when it is ready. */
52
+ export declare function formatCooldowns(entry: Pick<SubscriptionCredential, 'rateLimitedUntil' | 'modelCooldowns'>, now?: number): string | undefined;
53
+ /** Whether a successful call on `modelId` has stored cooldown or auth health to clear; callers skip the write otherwise (every provider write bumps provider_revision). */
54
+ export declare function subscriptionNeedsSuccessWrite(entry: Pick<SubscriptionCredential, 'rateLimitedUntil' | 'authFailure' | 'modelCooldowns'>, modelId?: string): boolean;
55
+ export declare function isManagedProvider(providerId: string | undefined): providerId is ManagedProviderId;
56
+ export declare function getProviderLabel(providerId: ManagedProviderId): string;
57
+ export declare function resolveProviderCandidates(candidates: readonly ProviderCandidate[], now?: number, options?: {
58
+ pinnedToFirst?: boolean;
59
+ }): {
60
+ candidates: ProviderCandidate[];
61
+ rejected: Array<{
62
+ candidate: ProviderCandidate;
63
+ reason: ProviderCandidateRejection;
64
+ }>;
65
+ };
66
+ export declare class DuplicateSubscriptionError extends Error {
67
+ constructor(message: string);
68
+ }
69
+ export declare function isInvalidRefreshTokenError(error: unknown): boolean;
70
+ export declare function parseRetryAfterHeader(value: string | undefined, now?: number): number | undefined;
71
+ export declare function retryAfterMsFromHeaders(headers: Headers | Record<string, string> | undefined): number | undefined;
72
+ export declare function retryAfterMsFromError(error: unknown): number | undefined;
@@ -0,0 +1 @@
1
+ var m=Object.defineProperty;var o=(e,t)=>m(e,"name",{value:t,configurable:!0});import{modelCooldownFamily as c}from"./rate-limit-scope.js";const d="anthropic",f="openai-codex",l="openai",M=600*1e3,C=720*60*60*1e3,O=[d,f,l],p={anthropic:{label:"Claude"},"openai-codex":{label:"OpenAI Codex"},openai:{label:"ChatGPT"}};function R(e,t){const n=c(t);return Math.max(e.rateLimitedUntil||0,n===void 0?0:e.modelCooldowns?.[n]??0)}o(R,"coolingUntilFor");function h(e,t=Date.now()){return[...(e.rateLimitedUntil||0)>t?[{scope:"account",until:e.rateLimitedUntil}]:[],...Object.entries(e.modelCooldowns??{}).filter(([,n])=>n>t).map(([n,r])=>({scope:n,until:r}))]}o(h,"activeCooldowns");function A(e,t=Date.now()){const n=h(e,t).map(({scope:r,until:i})=>`${r==="account"?"":`${r} `}cooldown ${x(i-t)}`);return n.length?n.join(", "):void 0}o(A,"formatCooldowns");function x(e){const t=Math.max(1,Math.ceil(e/6e4));if(t<60)return`${t}m`;const n=Math.floor(t/60);return n<24?`${n}h${t%60?` ${t%60}m`:""}`:`${Math.floor(n/24)}d${n%24?` ${n%24}h`:""}`}o(x,"formatWait");function b(e,t){const n=c(t);return n!==void 0&&e.modelCooldowns?.[n]!==void 0}o(b,"hasModelCooldownRow");function I(e,t){return!!(e.rateLimitedUntil||e.authFailure)||b(e,t)}o(I,"subscriptionNeedsSuccessWrite");function N(e){return e===d||e===f||e===l}o(N,"isManagedProvider");function F(e){return p[e].label}o(F,"getProviderLabel");function P(e,t=Date.now(),n={}){const r=[],i=[];for(let s=0;s<e.length;s++){const a=e[s],u=a.registered?a.automaticFallback===!1?"non-managed-fallback":a.authenticated?(a.coolingUntil??0)>t?"cooling":void 0:"unauthenticated":"unregistered";if(!u)r.push(a);else if(i.push({candidate:a,reason:u}),n.pinnedToFirst&&s===0&&u!=="cooling")break}return{candidates:r,rejected:i}}o(P,"resolveProviderCandidates");class w extends Error{static{o(this,"DuplicateSubscriptionError")}constructor(t){super(t),this.name="DuplicateSubscriptionError"}}function $(e){const t=e,n=[t?.errorMessage,t?.message,e instanceof Error?e.message:""].filter(i=>typeof i=="string").join(" ");return(typeof t?.status=="number"?t.status:Number(n.match(/\bstatus[=:]\s*(\d+)/i)?.[1]))===400&&/invalid_grant|refresh token .*invalid|refresh token .*not found/i.test(n)}o($,"isInvalidRefreshTokenError");function D(e,t=Date.now()){if(!e?.trim())return;const n=Number(e.trim());if(Number.isFinite(n))return Math.max(0,Math.round(n*1e3));const r=Date.parse(e);return Number.isFinite(r)?Math.max(0,r-t):void 0}o(D,"parseRetryAfterHeader");function _(e){if(!e)return;const t=o(r=>e instanceof Headers?e.get(r)??void 0:Object.entries(e).find(([i])=>i.toLowerCase()===r)?.[1],"get"),n=t("retry-after-ms");return n!==void 0&&n.trim()!==""&&Number.isFinite(Number(n))?Math.max(0,Number(n)):D(t("retry-after"))}o(_,"retryAfterMsFromHeaders");function y(e){if(!e||typeof e!="object"||!("headers"in e))return;const t=e.headers;if(!(!(t instanceof Headers)&&(!t||typeof t!="object"||Array.isArray(t))))return _(t)}o(y,"retryAfterMsFromError");export{d as ANTHROPIC_PROVIDER_ID,w as DuplicateSubscriptionError,C as INVALID_REFRESH_TOKEN_BACKOFF_MS,O as MANAGED_PROVIDER_IDS,M as MAX_RATE_LIMIT_COOLDOWN_MS,l as OPENAI_CHATGPT_PROVIDER_ID,f as OPENAI_CODEX_PROVIDER_ID,h as activeCooldowns,R as coolingUntilFor,A as formatCooldowns,F as getProviderLabel,$ as isInvalidRefreshTokenError,N as isManagedProvider,D as parseRetryAfterHeader,P as resolveProviderCandidates,y as retryAfterMsFromError,_ as retryAfterMsFromHeaders,I as subscriptionNeedsSuccessWrite};
@@ -0,0 +1,10 @@
1
+ import type { Credential, CredentialInfo, CredentialStore } from '@earendil-works/pi-ai';
2
+ import type { CrtrClient } from '../../api/client.js';
3
+ export declare class ApiCredentialStore implements CredentialStore {
4
+ private readonly client;
5
+ constructor(client: () => CrtrClient);
6
+ read(providerId: string): Promise<Credential | undefined>;
7
+ list(): Promise<readonly CredentialInfo[]>;
8
+ modify(providerId: string, fn: (current: Credential | undefined) => Promise<Credential | undefined>): Promise<Credential | undefined>;
9
+ delete(providerId: string): Promise<void>;
10
+ }
@@ -0,0 +1 @@
1
+ var h=Object.defineProperty;var s=(a,e)=>h(a,"name",{value:e,configurable:!0});import{isManagedProvider as l}from"../managed-providers.js";class y{static{s(this,"ApiCredentialStore")}client;constructor(e){this.client=e}async read(e){const i=(await this.client().listModelAuth()).providers.find(t=>t.provider===e);if(!(i===void 0||!i.configured))return i.managed?{type:"oauth",access:"",refresh:"",expires:Math.max(...i.accounts.map(n=>Date.parse(n.expires_at)))}:i.kind==="api_key"?{type:"api_key",key:""}:{type:"oauth",access:"",refresh:"",expires:Number.MAX_SAFE_INTEGER}}async list(){return(await this.client().listModelAuth()).providers.filter(e=>e.configured).map(e=>({providerId:e.provider,type:e.managed?"oauth":e.kind}))}async modify(e,i){if(l(e))throw new Error(`${e} signs in through the daemon (model_auth.start), not through the credential store`);const t=await i(await this.read(e));if(t!==void 0){if(t.type==="api_key")await this.client().installCredential(e,{kind:"api_key",api_key:t.key??""});else{const{type:n,access:o,refresh:c,expires:d,...r}=t;await this.client().installCredential(e,{kind:"oauth",access_token:o,refresh_token:c,expires_at:new Date(d).toISOString(),...Object.keys(r).length?{extra:r}:{}})}return t}}async delete(e){await this.client().removeCredential(e)}}export{y as ApiCredentialStore};
@@ -1,6 +1,6 @@
1
1
  import type { ProviderAuthInteraction } from '@earendil-works/pi-ai';
2
2
  import type { CrtrClient } from '../../api/client.js';
3
- import { type ManagedProviderId } from '../subscription-state.js';
3
+ import { type ManagedProviderId } from '../managed-providers.js';
4
4
  /** A self-host box with a browser on this machine: the vendor's redirect can
5
5
  * reach the daemon's loopback listener (RT-LLM-41). Over SSH or on a headless
6
6
  * host the code page / device flow is the only one that works. */
@@ -1 +1 @@
1
- var m=Object.defineProperty;var s=(e,t)=>m(e,"name",{value:t,configurable:!0});import{ApiError as c}from"../../api/errors.js";import{OPENAI_CHATGPT_PROVIDER_ID as f}from"../subscription-state.js";function g(e=process.env){return e.SSH_CONNECTION||e.SSH_TTY?!1:process.platform==="darwin"||process.platform==="win32"?!0:!!(e.DISPLAY||e.WAYLAND_DISPLAY)}s(g,"localBrowserAvailable");function p(e){return e.aborted}s(p,"cancelled");function h(e,t){return t.aborted?Promise.reject(new Error("Login cancelled")):new Promise((a,r)=>{const o=s(()=>r(new Error("Login cancelled")),"onAbort");t.addEventListener("abort",o,{once:!0}),e.then(a,r).finally(()=>t.removeEventListener("abort",o))})}s(h,"raceAbort");function y(e){if(!(!(e instanceof c)||e.status!==404||!/no route for/.test(e.message)))return new Error("The running crtr daemon predates daemon-run sign-in. Run `crtr sys daemon restart`, then sign in again.")}s(y,"olderDaemon");async function u(e,t){try{return await e.startModelAuth(t)}catch(a){throw y(a)??a}}s(u,"startFlow");async function b(e,t,a,r){const o={provider:t===f?"openai-chatgpt":t,...a?{label:a}:{}};if(!r)return{flow:await u(e,o),loopback:!1};try{return{flow:await u(e,{...o,redirect:"loopback"}),loopback:!0}}catch(n){if(n instanceof c&&n.status===409&&n.param==="redirect")return{flow:await u(e,o),loopback:!1};throw n}}s(b,"start");async function d(e,t,a){for(;!p(a);){const r=await h(e.getModelAuthFlow(t,25),a);if(r.status==="connected")return;if(r.status!=="pending")throw new Error(r.message??`sign-in ${r.status}`)}throw new Error("Login cancelled")}s(d,"awaitFlow");async function v(e,t,a,r={}){const{flow:o,loopback:n}=await b(e,t,r.label,r.loopback??g()),i=s(()=>{e.cancelModelAuth(o.flow_id).catch(()=>{})},"cancel");if(p(a.signal))throw i(),new Error("Login cancelled");a.signal.addEventListener("abort",i,{once:!0});try{await A(e,o,n,a)}finally{a.signal.removeEventListener("abort",i)}}s(v,"daemonManagedLogin");async function A(e,t,a,r){if(t.user_code!==void 0){r.notify({type:"device_code",userCode:t.user_code,verificationUri:t.url,expiresInSeconds:900}),await d(e,t.flow_id,r.signal);return}r.notify({type:"auth_url",url:t.url,instructions:t.instructions});const o=t.provider===f;if(a&&!o){await d(e,t.flow_id,r.signal);return}if(a){const n=new AbortController,i=AbortSignal.any([r.signal,n.signal]),l=w(e,t,r,i);l.catch(()=>{});try{await Promise.race([d(e,t.flow_id,r.signal),l])}finally{n.abort()}return}if(o){await w(e,t,r,r.signal);return}for(;;){const n=await r.prompt({type:"manual_code",message:"Paste the code Claude shows"});try{await e.completeModelAuth(t.flow_id,{input:n});return}catch(i){if(i instanceof c&&i.status===400&&(i.param==="input"||i.param==="state")){r.notify({type:"info",message:`${i.message} \u2014 paste it again.`});continue}throw i}}}s(A,"drive");async function w(e,t,a,r){for(;;){const o=await a.prompt({type:"manual_code",message:"Paste the final redirect URL from your browser",placeholder:"http://127.0.0.1:1455/auth/callback?...",signal:r});try{await e.completeModelAuth(t.flow_id,{input:o});return}catch(n){if(n instanceof c&&n.status===400&&(n.param==="input"||n.param==="state")){a.notify({type:"info",message:`${n.message} \u2014 paste it again.`});continue}throw n}}}s(w,"pasteRedirect");export{v as daemonManagedLogin,g as localBrowserAvailable};
1
+ var m=Object.defineProperty;var s=(e,t)=>m(e,"name",{value:t,configurable:!0});import{ApiError as c}from"../../api/errors.js";import{OPENAI_CHATGPT_PROVIDER_ID as f}from"../managed-providers.js";function g(e=process.env){return e.SSH_CONNECTION||e.SSH_TTY?!1:process.platform==="darwin"||process.platform==="win32"?!0:!!(e.DISPLAY||e.WAYLAND_DISPLAY)}s(g,"localBrowserAvailable");function p(e){return e.aborted}s(p,"cancelled");function h(e,t){return t.aborted?Promise.reject(new Error("Login cancelled")):new Promise((a,r)=>{const o=s(()=>r(new Error("Login cancelled")),"onAbort");t.addEventListener("abort",o,{once:!0}),e.then(a,r).finally(()=>t.removeEventListener("abort",o))})}s(h,"raceAbort");function y(e){if(!(!(e instanceof c)||e.status!==404||!/no route for/.test(e.message)))return new Error("The running crtr daemon predates daemon-run sign-in. Run `crtr sys daemon restart`, then sign in again.")}s(y,"olderDaemon");async function u(e,t){try{return await e.startModelAuth(t)}catch(a){throw y(a)??a}}s(u,"startFlow");async function b(e,t,a,r){const o={provider:t===f?"openai-chatgpt":t,...a?{label:a}:{}};if(!r)return{flow:await u(e,o),loopback:!1};try{return{flow:await u(e,{...o,redirect:"loopback"}),loopback:!0}}catch(n){if(n instanceof c&&n.status===409&&n.param==="redirect")return{flow:await u(e,o),loopback:!1};throw n}}s(b,"start");async function d(e,t,a){for(;!p(a);){const r=await h(e.getModelAuthFlow(t,25),a);if(r.status==="connected")return;if(r.status!=="pending")throw new Error(r.message??`sign-in ${r.status}`)}throw new Error("Login cancelled")}s(d,"awaitFlow");async function v(e,t,a,r={}){const{flow:o,loopback:n}=await b(e,t,r.label,r.loopback??g()),i=s(()=>{e.cancelModelAuth(o.flow_id).catch(()=>{})},"cancel");if(p(a.signal))throw i(),new Error("Login cancelled");a.signal.addEventListener("abort",i,{once:!0});try{await A(e,o,n,a)}finally{a.signal.removeEventListener("abort",i)}}s(v,"daemonManagedLogin");async function A(e,t,a,r){if(t.user_code!==void 0){r.notify({type:"device_code",userCode:t.user_code,verificationUri:t.url,expiresInSeconds:900}),await d(e,t.flow_id,r.signal);return}r.notify({type:"auth_url",url:t.url,instructions:t.instructions});const o=t.provider===f;if(a&&!o){await d(e,t.flow_id,r.signal);return}if(a){const n=new AbortController,i=AbortSignal.any([r.signal,n.signal]),l=w(e,t,r,i);l.catch(()=>{});try{await Promise.race([d(e,t.flow_id,r.signal),l])}finally{n.abort()}return}if(o){await w(e,t,r,r.signal);return}for(;;){const n=await r.prompt({type:"manual_code",message:"Paste the code Claude shows"});try{await e.completeModelAuth(t.flow_id,{input:n});return}catch(i){if(i instanceof c&&i.status===400&&(i.param==="input"||i.param==="state")){r.notify({type:"info",message:`${i.message} \u2014 paste it again.`});continue}throw i}}}s(A,"drive");async function w(e,t,a,r){for(;;){const o=await a.prompt({type:"manual_code",message:"Paste the final redirect URL from your browser",placeholder:"http://127.0.0.1:1455/auth/callback?...",signal:r});try{await e.completeModelAuth(t.flow_id,{input:o});return}catch(n){if(n instanceof c&&n.status===400&&(n.param==="input"||n.param==="state")){a.notify({type:"info",message:`${n.message} \u2014 paste it again.`});continue}throw n}}}s(w,"pasteRedirect");export{v as daemonManagedLogin,g as localBrowserAvailable};
@@ -41,7 +41,7 @@ export declare function startModelAuthFlow(input: {
41
41
  /** `model_auth.cancel`: the person gave up at the CLI. Ends the flow so it stops
42
42
  * polling, frees its loopback port, and never writes a credential. Unknown or
43
43
  * finished flows are left alone. */
44
- export declare function cancelModelAuthFlow(flowId: string): FlowView | undefined;
44
+ export declare function cancelModelAuthFlow(flowId: string): Promise<FlowView | undefined>;
45
45
  /** `model_auth.complete` (RT-LLM-22, 31, 43, 49). Returns once the credential is
46
46
  * written; a vendor refusal ends the flow as failed and throws. */
47
47
  export declare function completeModelAuthFlow(flowId: unknown, input: unknown): Promise<{
@@ -56,15 +56,16 @@ export interface FlowView {
56
56
  message?: string;
57
57
  jti?: string;
58
58
  }
59
- /** A flow's current state, or undefined when it is unknown or long finished. */
60
- export declare function readModelAuthFlow(flowId: string): FlowView | undefined;
59
+ /** A flow's current state, or undefined when it is unknown or long finished. A flow this process does not hold but whose row exists is pending. */
60
+ export declare function readModelAuthFlow(flowId: string): Promise<FlowView | undefined>;
61
61
  /** How a finished flow failed, for the log and the redirect to the directory. An unknown or lost flow is a `runtime` failure. */
62
62
  export declare function modelAuthFlowFailure(flowId: string): {
63
63
  category: SignInFailureCategory;
64
64
  step?: string;
65
65
  fields: Record<string, EventField>;
66
66
  };
67
- /** Wait up to `waitMs` for a flow to leave `pending`; answers its state either way. */
67
+ /** Wait up to `waitMs` for a flow to leave `pending`; answers its state either way. A flow another API process holds is followed through its row:
68
+ * the row is deleted when that process settles the flow, and a provider that then holds an account answers `connected`, else `failed`. */
68
69
  export declare function waitModelAuthFlow(flowId: string, waitMs: number, signal?: AbortSignal): Promise<FlowView | undefined>;
69
70
  /** Test seam: forget every flow (a daemon restart does this by construction). */
70
71
  export declare function resetModelAuthFlowsForTest(): void;
@@ -1 +1 @@
1
- var Z=Object.defineProperty;var s=(e,t)=>Z(e,"name",{value:t,configurable:!0});import{createHash as W,randomBytes as C,randomUUID as Q}from"node:crypto";import{createServer as ee}from"node:http";import{RunError as u}from"../runs/operations.js";import{describeOutboundFailure as te}from"../events/outbound.js";import{ANTHROPIC_PROVIDER_ID as y,OPENAI_CHATGPT_PROVIDER_ID as w,OPENAI_CODEX_PROVIDER_ID as D,commitManagedLogin as ne,openAIDeviceId as re,readSubscriptionPool as ie}from"../subscription-state.js";const $=900,G="9d1c250a-e61b-44d9-88ed-5944d1962f5e",oe="https://claude.ai/oauth/authorize",ae="https://console.anthropic.com/oauth/code/callback",H={port:53692,path:"/callback",redirect:"http://localhost:53692/callback"},se="org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload",E="app_EMoamEEZ73f0CkXaXp7hrann",ce="https://auth.openai.com/codex/device",M="https://auth.openai.com/deviceauth/callback",F={port:1455,path:"/auth/callback",redirect:"http://localhost:1455/auth/callback"},de="openid profile email offline_access",ue=900,q="https://api.openai.com/auth",he="dynamic_agent_client",pe="crtr",le="https://auth.openai.com/api/accounts/authorize",j="https://api.openai.com/v1",b={port:1455,path:"/auth/callback",redirect:"http://127.0.0.1:1455/auth/callback"},O="chatgpt.tokens.use.direct",fe=`openid profile email offline_access resource.invoke ${O}`,_e=180*1e3,ge=s(()=>process.env.CRTR_MODEL_AUTH_ANTHROPIC_TOKEN_URL||"https://platform.claude.com/v1/oauth/token","anthropicTokenUrl"),S=s(()=>(process.env.CRTR_MODEL_AUTH_OPENAI_BASE||"https://auth.openai.com").replace(/\/$/,""),"openaiBase");class A extends u{static{s(this,"VendorSignInError")}category;step;url;httpStatus;constructor(t,n,i,o,r,c){super(502,"upstream_refused",o),this.category=t,this.step=n,this.url=i,this.httpStatus=c,r!==void 0&&(this.cause=r)}logFields(){return{category:this.category,...this.httpStatus===void 0?{}:{http_status:this.httpStatus},...te(this.cause??this,{step:this.step,url:this.url})}}}async function P(e,t,n){try{return await fetch(t,n)}catch(i){throw new A("network",e,t,`could not reach ${new URL(t).host}${new URL(t).pathname}: ${i instanceof Error?i.message:String(i)}`,i)}}s(P,"vendorFetch");async function x(e,t,n){let i;try{i=await n.text()}catch(o){throw new A("network",e,t,`connection to ${new URL(t).host}${new URL(t).pathname} dropped while reading the answer: ${o instanceof Error?o.message:String(o)}`,o)}try{return JSON.parse(i)}catch{throw _(e,t,`${new URL(t).host}${new URL(t).pathname} answered with a body that is not JSON`,n.status)}}s(x,"vendorJson");const _=s((e,t,n,i)=>new A("refused",e,t,n,void 0,i),"refusal"),g=new Map,T=new Map;function R(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}s(R,"base64url");function me(){const e=R(C(32));return{verifier:e,challenge:R(W("sha256").update(e).digest())}}s(me,"pkce");const z="openai-chatgpt";function J(e){if(e==="openai")return D;if(e===z)return w;if(e===y||e===D)return e;throw new u(400,"invalid_request","provider must be anthropic, openai-codex, or openai-chatgpt","provider")}s(J,"managedProvider");function Re(e){return e===w?z:e}s(Re,"modelAuthApiProvider");function Le(e){return J(e)}s(Le,"modelAuthProvider");function f(e,t,n,i){if(e.status==="pending"){e.status=t,n!==void 0&&(e.message=n),i instanceof A&&(e.failure=i),clearTimeout(e.timer),e.abort.abort(),e.server?.close(),e.server?.closeIdleConnections(),T.get(e.provider)===e.id&&T.delete(e.provider);for(const o of e.waiters)o();e.waiters.clear(),setTimeout(()=>{g.get(e.id)===e&&g.delete(e.id)},6e4).unref()}}s(f,"settle");async function L(e,t){if(e.status!=="pending")throw new Error("the sign-in ended before the credential was written");await ne(e.provider,e.label,{access:t.access,refresh:t.refresh,expires:t.expires,...t.accountId?{accountId:t.accountId}:{},...t.clientId?{clientId:t.clientId}:{},...t.email?{email:t.email}:{},lastRateLimitedAt:e.lastRateLimitedAt,autoLabel:e.label===void 0})}s(L,"writeCredential");async function B(e,t,n){const i=ge(),o=await P("token_exchange",i,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({grant_type:"authorization_code",client_id:G,code:t,state:n,redirect_uri:e.redirectUri,code_verifier:e.verifier}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!o.ok)throw _("token_exchange",i,`Anthropic token exchange failed (${o.status})`,o.status);const r=await x("token_exchange",i,o);if(typeof r.access_token!="string"||typeof r.refresh_token!="string"||typeof r.expires_in!="number"||!Number.isFinite(r.expires_in))throw _("token_exchange",i,"Anthropic token exchange returned no credential");await L(e,{access:r.access_token,refresh:r.refresh_token,expires:Date.now()+r.expires_in*1e3-300*1e3})}s(B,"exchangeAnthropic");function we(e){const t=e.split(".")[1];let n;try{n=JSON.parse(Buffer.from(t??"","base64url").toString("utf8"))[q]?.chatgpt_account_id}catch{n=void 0}if(typeof n!="string"||n==="")throw new Error("OpenAI token carries no ChatGPT account id");return n}s(we,"openaiAccountId");async function V(e,t,n,i){const o=`${S()}/oauth/token`,r=await P("token_exchange",o,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:E,code:t,code_verifier:n,redirect_uri:i}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!r.ok)throw _("token_exchange",o,`OpenAI token exchange failed (${r.status})`,r.status);const c=await x("token_exchange",o,r);if(typeof c.access_token!="string"||typeof c.refresh_token!="string"||typeof c.expires_in!="number")throw _("token_exchange",o,"OpenAI token exchange returned no credential");await L(e,{access:c.access_token,refresh:c.refresh_token,expires:Date.now()+c.expires_in*1e3,accountId:we(c.access_token)})}s(V,"exchangeOpenAI");function K(e){try{const t=JSON.parse(Buffer.from(e?.split(".")[1]??"","base64url").toString("utf8"));return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:void 0}catch{return}}s(K,"jwtPayload");function ye(...e){for(const t of e){const n=K(t)?.[q];if(typeof n?.chatgpt_account_id=="string"&&n.chatgpt_account_id)return n.chatgpt_account_id}}s(ye,"chatgptAccountId");function X(e,t){const n=e.searchParams.get("error");if(n)throw new u(400,"invalid_request",`ChatGPT authorization failed: ${n}`,"input");const i=e.searchParams.get("code");if(!i)throw new u(400,"invalid_request","the redirect URL has no authorization code","input");if(e.searchParams.get("state")!==t)throw new u(400,"invalid_request","the pasted redirect URL belongs to a different sign-in","state");const o=e.searchParams.get("client_id")?.trim();if(!o)throw new u(400,"invalid_request","the redirect URL carries no issued client id","input");return{code:i,clientId:o}}s(X,"chatgptCallback");async function Y(e,t,n){const i=`${S()}/api/accounts/oauth/token`,o=await P("token_exchange",i,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:n,code:t,code_verifier:e.verifier,redirect_uri:e.redirectUri,resource:j}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!o.ok)throw _("token_exchange",i,`ChatGPT token exchange failed (${o.status})`,o.status);const r=await x("token_exchange",i,o);if(typeof r.access_token!="string"||!r.access_token||typeof r.refresh_token!="string"||!r.refresh_token||typeof r.expires_in!="number"||!Number.isFinite(r.expires_in)||r.expires_in<=0||typeof r.id_token!="string"||!r.id_token)throw _("token_exchange",i,"ChatGPT token exchange returned no credential");if(!(typeof r.scope=="string"?r.scope.trim().split(/\s+/):[]).includes(O))throw _("token_exchange",i,`ChatGPT sign-in did not grant ${O}`);const p=ye(r.access_token,r.id_token),a=K(r.id_token)?.email;await L(e,{access:r.access_token,refresh:r.refresh_token,expires:Date.now()+r.expires_in*1e3-_e,clientId:n,...p?{accountId:p}:{},...typeof a=="string"&&a?{email:a}:{}})}s(Y,"exchangeChatGPT");function ve(e,t){return new Promise((n,i)=>{if(t.aborted){i(new Error("flow ended"));return}const o=s(()=>{clearTimeout(r),i(new Error("flow ended"))},"onAbort"),r=setTimeout(()=>{t.removeEventListener("abort",o),n()},e);t.addEventListener("abort",o,{once:!0})})}s(ve,"sleep");async function be(e,t){const n=Math.min(e.expiresAt,Date.now()+ue*1e3);let i=Math.max(1e3,Math.floor(t.intervalS*1e3));try{for(;Date.now()<n&&(await ve(Math.min(i,Math.max(0,n-Date.now())),e.abort.signal),!(Date.now()>=n));){const o=`${S()}/api/accounts/deviceauth/token`,r=await P("poll",o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_auth_id:t.deviceAuthId,user_code:t.userCode}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(r.ok){const a=await x("poll",o,r);if(typeof a.authorization_code!="string"||typeof a.code_verifier!="string")throw _("poll",o,"OpenAI device approval returned no code");await V(e,a.authorization_code,a.code_verifier,M),f(e,"connected");return}if(r.status===403||r.status===404)continue;let c;try{c=await r.text()}catch(a){throw new A("network","poll",o,`connection to ${new URL(o).host}${new URL(o).pathname} dropped while reading the answer: ${a instanceof Error?a.message:String(a)}`,a)}let p;try{const a=JSON.parse(c).error;p=typeof a=="object"&&a!==null?a.code:a}catch{p=void 0}if(p!=="deviceauth_authorization_pending"){if(p==="slow_down"){i+=5e3;continue}throw _("poll",o,`OpenAI device sign-in failed (${r.status})`,r.status)}}f(e,"expired","the OpenAI code expired")}catch(o){e.status==="pending"&&f(e,"failed",o instanceof Error?o.message:String(o),o)}}s(be,"pollDevice");function U(e,t){const n=ee((i,o)=>{const r=new URL(i.url??"/","http://localhost"),c=s((l,h)=>{o.writeHead(l,{"Content-Type":"text/plain; charset=utf-8",Connection:"close"}),o.end(h)},"reply");if(r.pathname!==t.path){c(404,"Not found.");return}const p=r.searchParams.get("code");if(r.searchParams.get("state")!==e.state||!p){c(400,"Sign-in could not be completed: state mismatch or missing code.");return}let a="";if(e.provider===w)try{a=X(r,e.state).clientId}catch(l){c(400,`Sign-in could not be completed: ${l instanceof Error?l.message:String(l)}.`);return}if(e.status!=="pending"||e.exchanging){c(409,"This sign-in has already finished.");return}e.exchanging=!0,(e.provider===y?B(e,p,e.state):e.provider===w?Y(e,p,a):V(e,p,e.verifier,e.redirectUri)).then(()=>{c(200,"Signed in. You can close this window."),f(e,"connected")},l=>{c(502,"The vendor refused the sign-in."),f(e,"failed",l instanceof Error?l.message:String(l),l)})});return e.server=n,new Promise((i,o)=>{n.once("error",r=>o(new u(409,"conflict",`cannot listen on localhost:${t.port} for the vendor redirect (${r.code??r.message})`,"redirect"))),n.listen(t.port,"127.0.0.1",()=>i())})}s(U,"listenLoopback");async function Ue(e){const t=J(e.provider);if(e.redirect!==void 0&&e.redirect!=="loopback")throw new u(400,"invalid_request",'redirect must be "loopback" when present',"redirect");if(e.label!==void 0&&typeof e.label!="string")throw new u(400,"invalid_request","label must be a string when present","label");const n=typeof e.label=="string"&&e.label.trim()?e.label.trim():void 0,i=T.get(t);if(i!==void 0){const h=g.get(i);h&&f(h,"failed","replaced by a newer sign-in")}const{verifier:o,challenge:r}=me(),c=e.redirect==="loopback",p=Date.now()+$*1e3,a={id:Q(),provider:t,kind:c?"loopback":t===y?"code_page":t===w?"redirect_paste":"device",state:t===y?o:C(16).toString("hex"),verifier:o,redirectUri:t===y?c?H.redirect:ae:t===w?b.redirect:c?F.redirect:M,expiresAt:p,status:"pending",...e.jti?{jti:e.jti}:{},...n?{label:n}:{},lastRateLimitedAt:Math.max(0,...ie(t).map(h=>h.lastRateLimitedAt)),abort:new AbortController,timer:setTimeout(()=>f(a,"expired","the sign-in expired"),$*1e3),waiters:new Set,exchanging:!1};a.timer.unref(),Ae(a);const v=s(()=>new u(409,"conflict","a newer sign-in for this provider replaced this one","provider"),"replacedDuringStart"),l={flow_id:a.id,provider:t,expires_at:new Date(p).toISOString()};try{if(t===y){const d=new URLSearchParams;if(d.set("code","true"),d.set("client_id",G),d.set("response_type","code"),d.set("redirect_uri",a.redirectUri),d.set("scope",se),d.set("code_challenge",r),d.set("code_challenge_method","S256"),d.set("state",a.state),c&&await U(a,H),a.status!=="pending")throw v();return{...l,url:`${oe}?${d.toString()}`,instructions:c?"Sign in to Claude in your browser; this finishes by itself.":"Sign in to Claude, then copy the code Claude shows and paste it here."}}if(t===w){const d=new URL(le);if(d.search=new URLSearchParams({client_id:he,agent_name_hint:pe,ext_agent_host_id:`urn:uuid:${re()}`,response_type:"code",redirect_uri:a.redirectUri,resource:j,scope:fe,state:a.state,code_challenge:r,code_challenge_method:"S256",nonce:R(C(32))}).toString(),c&&await U(a,b),a.status!=="pending")throw v();return{...l,url:d.toString(),instructions:c?"Sign in with ChatGPT in your browser; this finishes by itself. If it does not, paste the final redirect URL here.":`Sign in with ChatGPT, then copy the full URL your browser lands on (it starts with ${b.redirect} and may show a connection error) and paste it here.`}}if(c){const d=new URL("https://auth.openai.com/oauth/authorize");if(d.searchParams.set("response_type","code"),d.searchParams.set("client_id",E),d.searchParams.set("redirect_uri",a.redirectUri),d.searchParams.set("scope",de),d.searchParams.set("code_challenge",r),d.searchParams.set("code_challenge_method","S256"),d.searchParams.set("state",a.state),d.searchParams.set("id_token_add_organizations","true"),d.searchParams.set("codex_cli_simplified_flow","true"),d.searchParams.set("originator","pi"),await U(a,F),a.status!=="pending")throw v();return{...l,url:d.toString(),instructions:"Sign in to ChatGPT in your browser; this finishes by itself."}}const h=`${S()}/api/accounts/deviceauth/usercode`,I=await P("device_code_start",h,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_id:E}),signal:AbortSignal.timeout(3e4)});if(!I.ok)throw _("device_code_start",h,`OpenAI refused to issue a device code (${I.status})`,I.status);const m=await x("device_code_start",h,I),k=typeof m.interval=="string"?Number(m.interval.trim()):m.interval;if(typeof m.device_auth_id!="string"||typeof m.user_code!="string"||typeof k!="number"||!Number.isFinite(k)||k<0)throw _("device_code_start",h,"OpenAI returned an invalid device code");if(a.status!=="pending")throw v();return be(a,{deviceAuthId:m.device_auth_id,userCode:m.user_code,intervalS:k}),{...l,url:ce,user_code:m.user_code,instructions:"Open the link, sign in to ChatGPT, and enter the code. The code expires in 15 minutes."}}catch(h){throw f(a,"failed",h instanceof Error?h.message:String(h),h),g.delete(a.id),h instanceof u?h:new u(502,"upstream_refused",`vendor sign-in could not start: ${h instanceof Error?h.message:String(h)}`)}}s(Ue,"startModelAuthFlow");function Ae(e){g.set(e.id,e),T.set(e.provider,e.id)}s(Ae,"register");function Ne(e){const t=g.get(e);if(t)return f(t,"failed","cancelled"),N(t)}s(Ne,"cancelModelAuthFlow");function Pe(e){const t=e.trim();if(!t)return{};try{const n=new URL(t);return{code:n.searchParams.get("code")??void 0,state:n.searchParams.get("state")??void 0}}catch{}if(t.includes("#")){const[n,i]=t.split("#",2);return{code:n,state:i}}if(t.includes("code=")){const n=new URLSearchParams(t);return{code:n.get("code")??void 0,state:n.get("state")??void 0}}return{code:t}}s(Pe,"parseCodeInput");function xe(e){const t=typeof e=="string"?g.get(e):void 0;if(!t||t.status!=="pending")throw new u(400,"invalid_request","no in-flight sign-in has this flow_id","flow_id");return t}s(xe,"pendingFlow");async function De(e,t){const n=xe(e);if(n.provider===w)return Te(n,t);if(n.kind!=="code_page")throw new u(400,"invalid_request","this sign-in completes without a pasted code","flow_id");if(n.exchanging)throw new u(400,"invalid_request","this sign-in is already being completed","flow_id");if(typeof t!="string")throw new u(400,"invalid_request","input must be the code Claude shows","input");const{code:i,state:o}=Pe(t);if(!i)throw new u(400,"invalid_request","input must be the code Claude shows","input");if(o!==void 0&&o!==n.state)throw new u(400,"invalid_request","the pasted code belongs to a different sign-in","state");n.exchanging=!0;try{await B(n,i,o??n.state)}catch(r){throw f(n,"failed",r instanceof Error?r.message:String(r),r),new u(502,"upstream_refused","Anthropic refused the code")}return f(n,"connected"),{flow_id:n.id,provider:n.provider,status:"connected"}}s(De,"completeModelAuthFlow");async function Te(e,t){if(e.exchanging)throw new u(400,"invalid_request","this sign-in is already being completed","flow_id");const n=new URL(b.redirect);let i;try{i=typeof t=="string"?new URL(t.trim()):void 0}catch{i=void 0}if(!i||i.origin!==n.origin||i.pathname!==n.pathname)throw new u(400,"invalid_request",`input must be the full redirect URL starting with ${b.redirect}`,"input");const{code:o,clientId:r}=X(i,e.state);e.exchanging=!0;try{await Y(e,o,r)}catch(c){throw f(e,"failed",c instanceof Error?c.message:String(c),c),new u(502,"upstream_refused","OpenAI refused the ChatGPT sign-in")}return f(e,"connected"),{flow_id:e.id,provider:e.provider,status:"connected"}}s(Te,"completeChatGPTFlow");function N(e){return{flow_id:e.id,provider:e.provider,status:e.status,...e.message?{message:e.message}:{},...e.jti?{jti:e.jti}:{}}}s(N,"view");function $e(e){const t=g.get(e);return t?N(t):void 0}s($e,"readModelAuthFlow");function Ge(e){const t=g.get(e);return t?.status==="expired"?{category:"expired",fields:{category:"expired"}}:t?.failure?{category:t.failure.category,step:t.failure.step,fields:t.failure.logFields()}:{category:"runtime",fields:{category:"runtime"}}}s(Ge,"modelAuthFlowFailure");async function He(e,t,n){const i=g.get(e);if(i)return i.status==="pending"&&t>0&&!n?.aborted&&await new Promise(o=>{const r=s(()=>{clearTimeout(c),n?.removeEventListener("abort",r),i.waiters.delete(r),o()},"done"),c=setTimeout(r,t);n?.addEventListener("abort",r,{once:!0}),i.waiters.add(r)}),N(i)}s(He,"waitModelAuthFlow");function Me(){for(const e of g.values())f(e,"failed","reset");g.clear(),T.clear()}s(Me,"resetModelAuthFlowsForTest");export{$ as MODEL_AUTH_FLOW_TTL_S,z as OPENAI_CHATGPT_API_PROVIDER,A as VendorSignInError,Ne as cancelModelAuthFlow,De as completeModelAuthFlow,Re as modelAuthApiProvider,Ge as modelAuthFlowFailure,Le as modelAuthProvider,$e as readModelAuthFlow,Me as resetModelAuthFlowsForTest,Ue as startModelAuthFlow,He as waitModelAuthFlow};
1
+ var he=Object.defineProperty;var o=(e,t)=>he(e,"name",{value:t,configurable:!0});import{createHash as z,randomBytes as V,randomUUID as le}from"node:crypto";import{createServer as _e}from"node:http";import{RunError as p}from"../runs/operations.js";import{openDb as O}from"../canvas/db.js";import{personIdAsync as R,runAsPerson as fe}from"../canvas/person.js";import{loadSecretsKey as N,open as B,seal as ge}from"../secrets/index.js";import{describeOutboundFailure as me}from"../events/outbound.js";import{ANTHROPIC_PROVIDER_ID as m,OPENAI_CHATGPT_PROVIDER_ID as w,OPENAI_CODEX_PROVIDER_ID as W,commitManagedLogin as we,openAIDeviceId as ye,readSubscriptionPool as U}from"../subscription-state.js";const K=900,X="9d1c250a-e61b-44d9-88ed-5944d1962f5e",ve="https://claude.ai/oauth/authorize",Y="https://console.anthropic.com/oauth/code/callback",Z={port:53692,path:"/callback",redirect:"http://localhost:53692/callback"},be="org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload",$="app_EMoamEEZ73f0CkXaXp7hrann",Ae="https://auth.openai.com/codex/device",Q="https://auth.openai.com/deviceauth/callback",ee={port:1455,path:"/auth/callback",redirect:"http://localhost:1455/auth/callback"},xe="openid profile email offline_access",Pe=900,te="https://api.openai.com/auth",Se="dynamic_agent_client",Ee="crtr",Te="https://auth.openai.com/api/accounts/authorize",ne="https://api.openai.com/v1",b={port:1455,path:"/auth/callback",redirect:"http://127.0.0.1:1455/auth/callback"},M="chatgpt.tokens.use.direct",ke=`openid profile email offline_access resource.invoke ${M}`,Oe=180*1e3,Re=o(()=>process.env.CRTR_MODEL_AUTH_ANTHROPIC_TOKEN_URL||"https://platform.claude.com/v1/oauth/token","anthropicTokenUrl"),I=o(()=>(process.env.CRTR_MODEL_AUTH_OPENAI_BASE||"https://auth.openai.com").replace(/\/$/,""),"openaiBase");class A extends p{static{o(this,"VendorSignInError")}category;step;url;httpStatus;constructor(t,n,r,a,i,s){super(502,"upstream_refused",a),this.category=t,this.step=n,this.url=r,this.httpStatus=s,i!==void 0&&(this.cause=i)}logFields(){return{category:this.category,...this.httpStatus===void 0?{}:{http_status:this.httpStatus},...me(this.cause??this,{step:this.step,url:this.url})}}}async function x(e,t,n){try{return await fetch(t,n)}catch(r){throw new A("network",e,t,`could not reach ${new URL(t).host}${new URL(t).pathname}: ${r instanceof Error?r.message:String(r)}`,r)}}o(x,"vendorFetch");async function P(e,t,n){let r;try{r=await n.text()}catch(a){throw new A("network",e,t,`connection to ${new URL(t).host}${new URL(t).pathname} dropped while reading the answer: ${a instanceof Error?a.message:String(a)}`,a)}try{return JSON.parse(r)}catch{throw f(e,t,`${new URL(t).host}${new URL(t).pathname} answered with a body that is not JSON`,n.status)}}o(P,"vendorJson");const f=o((e,t,n,r)=>new A("refused",e,t,n,void 0,r),"refusal"),g=new Map,S=new Map,C=o((e,t)=>`${e}|${t}`,"providerKey"),F=o((e,t)=>`model_sign_in_flows|${e}|${t}`,"flowAad"),re=o(e=>z("sha256").update(`state:${e}`).digest("hex").slice(0,32),"openaiState");async function Ie(e){const t=e.person,{sealed:n,keyId:r}=ge(JSON.stringify({verifier:e.verifier,...e.label?{label:e.label}:{},...e.jti?{jti:e.jti}:{}}),N(),F(t,e.id)),a=await O();await a.prepare("DELETE FROM model_sign_in_flows WHERE person = ? AND expires_at < ?").run(t,new Date().toISOString()),await a.prepare("INSERT INTO model_sign_in_flows (flow_id, provider, person, verifier_sealed, key_id, expires_at) VALUES (?, ?, ?, ?, ?, ?)").run(e.id,e.provider,t,n,r,new Date(e.expiresAt).toISOString())}o(Ie,"writeFlowRow");async function Ce(e){const t=await R(),n=await(await O()).prepare("SELECT provider, verifier_sealed, key_id, expires_at FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").get(e,t);if(!n||Date.parse(n.expires_at)<=Date.now())return;const r=JSON.parse(B(n.verifier_sealed,n.key_id,N(),F(t,e))),a=n.provider;if(a!==m&&a!==w)return;const i={id:e,person:t,provider:a,kind:a===m?"code_page":"redirect_paste",state:a===m?r.verifier:re(r.verifier),verifier:r.verifier,redirectUri:a===m?Y:b.redirect,expiresAt:Date.parse(n.expires_at),status:"pending",...r.jti?{jti:r.jti}:{},...r.label?{label:r.label}:{},lastRateLimitedAt:Math.max(0,...(await U(a)).map(s=>s.lastRateLimitedAt)),abort:new AbortController,timer:setTimeout(()=>_(i,"expired","the sign-in expired"),Math.max(0,Date.parse(n.expires_at)-Date.now())),waiters:new Set,exchanging:!1};return i.timer.unref(),i}o(Ce,"readFlowRow");function H(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}o(H,"base64url");function Le(){const e=H(V(32));return{verifier:e,challenge:H(z("sha256").update(e).digest())}}o(Le,"pkce");const ie="openai-chatgpt";function ae(e){if(e==="openai")return W;if(e===ie)return w;if(e===m||e===W)return e;throw new p(400,"invalid_request","provider must be anthropic, openai-codex, or openai-chatgpt","provider")}o(ae,"managedProvider");function Ze(e){return e===w?ie:e}o(Ze,"modelAuthApiProvider");function Qe(e){return ae(e)}o(Qe,"modelAuthProvider");function _(e,t,n,r){if(e.status==="pending"){e.status=t,n!==void 0&&(e.message=n),r instanceof A&&(e.failure=r),clearTimeout(e.timer),e.abort.abort(),e.server?.close(),e.server?.closeIdleConnections(),S.get(C(e.person,e.provider))===e.id&&S.delete(C(e.person,e.provider)),O().then(async a=>{await a.prepare("DELETE FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").run(e.id,e.person)}).catch(()=>{});for(const a of e.waiters)a();e.waiters.clear(),setTimeout(()=>{g.get(e.id)===e&&g.delete(e.id)},6e4).unref()}}o(_,"settle");async function G(e,t){if(e.status!=="pending")throw new Error("the sign-in ended before the credential was written");await fe(e.person,()=>we(e.provider,e.label,{access:t.access,refresh:t.refresh,expires:t.expires,...t.accountId?{accountId:t.accountId}:{},...t.clientId?{clientId:t.clientId}:{},...t.email?{email:t.email}:{},lastRateLimitedAt:e.lastRateLimitedAt,autoLabel:e.label===void 0}))}o(G,"writeCredential");async function oe(e,t,n){const r=Re(),a=await x("token_exchange",r,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({grant_type:"authorization_code",client_id:X,code:t,state:n,redirect_uri:e.redirectUri,code_verifier:e.verifier}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!a.ok)throw f("token_exchange",r,`Anthropic token exchange failed (${a.status})`,a.status);const i=await P("token_exchange",r,a);if(typeof i.access_token!="string"||typeof i.refresh_token!="string"||typeof i.expires_in!="number"||!Number.isFinite(i.expires_in))throw f("token_exchange",r,"Anthropic token exchange returned no credential");await G(e,{access:i.access_token,refresh:i.refresh_token,expires:Date.now()+i.expires_in*1e3-300*1e3})}o(oe,"exchangeAnthropic");function De(e){const t=e.split(".")[1];let n;try{n=JSON.parse(Buffer.from(t??"","base64url").toString("utf8"))[te]?.chatgpt_account_id}catch{n=void 0}if(typeof n!="string"||n==="")throw new Error("OpenAI token carries no ChatGPT account id");return n}o(De,"openaiAccountId");async function se(e,t,n,r){const a=`${I()}/oauth/token`,i=await x("token_exchange",a,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:$,code:t,code_verifier:n,redirect_uri:r}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!i.ok)throw f("token_exchange",a,`OpenAI token exchange failed (${i.status})`,i.status);const s=await P("token_exchange",a,i);if(typeof s.access_token!="string"||typeof s.refresh_token!="string"||typeof s.expires_in!="number")throw f("token_exchange",a,"OpenAI token exchange returned no credential");await G(e,{access:s.access_token,refresh:s.refresh_token,expires:Date.now()+s.expires_in*1e3,accountId:De(s.access_token)})}o(se,"exchangeOpenAI");function ce(e){try{const t=JSON.parse(Buffer.from(e?.split(".")[1]??"","base64url").toString("utf8"));return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:void 0}catch{return}}o(ce,"jwtPayload");function Ne(...e){for(const t of e){const n=ce(t)?.[te];if(typeof n?.chatgpt_account_id=="string"&&n.chatgpt_account_id)return n.chatgpt_account_id}}o(Ne,"chatgptAccountId");function de(e,t){const n=e.searchParams.get("error");if(n)throw new p(400,"invalid_request",`ChatGPT authorization failed: ${n}`,"input");const r=e.searchParams.get("code");if(!r)throw new p(400,"invalid_request","the redirect URL has no authorization code","input");if(e.searchParams.get("state")!==t)throw new p(400,"invalid_request","the pasted redirect URL belongs to a different sign-in","state");const a=e.searchParams.get("client_id")?.trim();if(!a)throw new p(400,"invalid_request","the redirect URL carries no issued client id","input");return{code:r,clientId:a}}o(de,"chatgptCallback");async function ue(e,t,n){const r=`${I()}/api/accounts/oauth/token`,a=await x("token_exchange",r,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:n,code:t,code_verifier:e.verifier,redirect_uri:e.redirectUri,resource:ne}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!a.ok)throw f("token_exchange",r,`ChatGPT token exchange failed (${a.status})`,a.status);const i=await P("token_exchange",r,a);if(typeof i.access_token!="string"||!i.access_token||typeof i.refresh_token!="string"||!i.refresh_token||typeof i.expires_in!="number"||!Number.isFinite(i.expires_in)||i.expires_in<=0||typeof i.id_token!="string"||!i.id_token)throw f("token_exchange",r,"ChatGPT token exchange returned no credential");if(!(typeof i.scope=="string"?i.scope.trim().split(/\s+/):[]).includes(M))throw f("token_exchange",r,`ChatGPT sign-in did not grant ${M}`);const l=Ne(i.access_token,i.id_token),d=ce(i.id_token)?.email;await G(e,{access:i.access_token,refresh:i.refresh_token,expires:Date.now()+i.expires_in*1e3-Oe,clientId:n,...l?{accountId:l}:{},...typeof d=="string"&&d?{email:d}:{}})}o(ue,"exchangeChatGPT");function Ue(e,t){return new Promise((n,r)=>{if(t.aborted){r(new Error("flow ended"));return}const a=o(()=>{clearTimeout(i),r(new Error("flow ended"))},"onAbort"),i=setTimeout(()=>{t.removeEventListener("abort",a),n()},e);t.addEventListener("abort",a,{once:!0})})}o(Ue,"sleep");async function $e(e,t){const n=Math.min(e.expiresAt,Date.now()+Pe*1e3);let r=Math.max(1e3,Math.floor(t.intervalS*1e3));try{for(;Date.now()<n&&(await Ue(Math.min(r,Math.max(0,n-Date.now())),e.abort.signal),!(Date.now()>=n));){const a=`${I()}/api/accounts/deviceauth/token`,i=await x("poll",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_auth_id:t.deviceAuthId,user_code:t.userCode}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(i.ok){const d=await P("poll",a,i);if(typeof d.authorization_code!="string"||typeof d.code_verifier!="string")throw f("poll",a,"OpenAI device approval returned no code");await se(e,d.authorization_code,d.code_verifier,Q),_(e,"connected");return}if(i.status===403||i.status===404)continue;let s;try{s=await i.text()}catch(d){throw new A("network","poll",a,`connection to ${new URL(a).host}${new URL(a).pathname} dropped while reading the answer: ${d instanceof Error?d.message:String(d)}`,d)}let l;try{const d=JSON.parse(s).error;l=typeof d=="object"&&d!==null?d.code:d}catch{l=void 0}if(l!=="deviceauth_authorization_pending"){if(l==="slow_down"){r+=5e3;continue}throw f("poll",a,`OpenAI device sign-in failed (${i.status})`,i.status)}}_(e,"expired","the OpenAI code expired")}catch(a){e.status==="pending"&&_(e,"failed",a instanceof Error?a.message:String(a),a)}}o($e,"pollDevice");function j(e,t){const n=_e((r,a)=>{const i=new URL(r.url??"/","http://localhost"),s=o((c,v)=>{a.writeHead(c,{"Content-Type":"text/plain; charset=utf-8",Connection:"close"}),a.end(v)},"reply");if(i.pathname!==t.path){s(404,"Not found.");return}const l=i.searchParams.get("code");if(i.searchParams.get("state")!==e.state||!l){s(400,"Sign-in could not be completed: state mismatch or missing code.");return}let d="";if(e.provider===w)try{d=de(i,e.state).clientId}catch(c){s(400,`Sign-in could not be completed: ${c instanceof Error?c.message:String(c)}.`);return}if(e.status!=="pending"||e.exchanging){s(409,"This sign-in has already finished.");return}e.exchanging=!0,(e.provider===m?oe(e,l,e.state):e.provider===w?ue(e,l,d):se(e,l,e.verifier,e.redirectUri)).then(()=>{s(200,"Signed in. You can close this window."),_(e,"connected")},c=>{s(502,"The vendor refused the sign-in."),_(e,"failed",c instanceof Error?c.message:String(c),c)})});return e.server=n,new Promise((r,a)=>{n.once("error",i=>a(new p(409,"conflict",`cannot listen on localhost:${t.port} for the vendor redirect (${i.code??i.message})`,"redirect"))),n.listen(t.port,"127.0.0.1",()=>r())})}o(j,"listenLoopback");async function et(e){const t=ae(e.provider);if(e.redirect!==void 0&&e.redirect!=="loopback")throw new p(400,"invalid_request",'redirect must be "loopback" when present',"redirect");if(e.label!==void 0&&typeof e.label!="string")throw new p(400,"invalid_request","label must be a string when present","label");const n=typeof e.label=="string"&&e.label.trim()?e.label.trim():void 0,r=await R(),a=Math.max(0,...(await U(t)).map(h=>h.lastRateLimitedAt)),i=S.get(C(r,t));if(i!==void 0){const h=g.get(i);h&&_(h,"failed","replaced by a newer sign-in")}const{verifier:s,challenge:l}=Le(),d=e.redirect==="loopback",D=Date.now()+K*1e3,c={id:le(),person:r,provider:t,kind:d?"loopback":t===m?"code_page":t===w?"redirect_paste":"device",state:t===m?s:re(s),verifier:s,redirectUri:t===m?d?Z.redirect:Y:t===w?b.redirect:d?ee.redirect:Q,expiresAt:D,status:"pending",...e.jti?{jti:e.jti}:{},...n?{label:n}:{},lastRateLimitedAt:a,abort:new AbortController,timer:setTimeout(()=>_(c,"expired","the sign-in expired"),K*1e3),waiters:new Set,exchanging:!1};c.timer.unref(),pe(c),await Ie(c);const v=o(()=>new p(409,"conflict","a newer sign-in for this provider replaced this one","provider"),"replacedDuringStart"),E={flow_id:c.id,provider:t,expires_at:new Date(D).toISOString()};try{if(t===m){const u=new URLSearchParams;if(u.set("code","true"),u.set("client_id",X),u.set("response_type","code"),u.set("redirect_uri",c.redirectUri),u.set("scope",be),u.set("code_challenge",l),u.set("code_challenge_method","S256"),u.set("state",c.state),d&&await j(c,Z),c.status!=="pending")throw v();return{...E,url:`${ve}?${u.toString()}`,instructions:d?"Sign in to Claude in your browser; this finishes by itself.":"Sign in to Claude, then copy the code Claude shows and paste it here."}}if(t===w){const u=new URL(Te);if(u.search=new URLSearchParams({client_id:Se,agent_name_hint:Ee,ext_agent_host_id:`urn:uuid:${await ye()}`,response_type:"code",redirect_uri:c.redirectUri,resource:ne,scope:ke,state:c.state,code_challenge:l,code_challenge_method:"S256",nonce:H(V(32))}).toString(),d&&await j(c,b),c.status!=="pending")throw v();return{...E,url:u.toString(),instructions:d?"Sign in with ChatGPT in your browser; this finishes by itself. If it does not, paste the final redirect URL here.":`Sign in with ChatGPT, then copy the full URL your browser lands on (it starts with ${b.redirect} and may show a connection error) and paste it here.`}}if(d){const u=new URL("https://auth.openai.com/oauth/authorize");if(u.searchParams.set("response_type","code"),u.searchParams.set("client_id",$),u.searchParams.set("redirect_uri",c.redirectUri),u.searchParams.set("scope",xe),u.searchParams.set("code_challenge",l),u.searchParams.set("code_challenge_method","S256"),u.searchParams.set("state",c.state),u.searchParams.set("id_token_add_organizations","true"),u.searchParams.set("codex_cli_simplified_flow","true"),u.searchParams.set("originator","pi"),await j(c,ee),c.status!=="pending")throw v();return{...E,url:u.toString(),instructions:"Sign in to ChatGPT in your browser; this finishes by itself."}}const h=`${I()}/api/accounts/deviceauth/usercode`,T=await x("device_code_start",h,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_id:$}),signal:AbortSignal.timeout(3e4)});if(!T.ok)throw f("device_code_start",h,`OpenAI refused to issue a device code (${T.status})`,T.status);const y=await P("device_code_start",h,T),k=typeof y.interval=="string"?Number(y.interval.trim()):y.interval;if(typeof y.device_auth_id!="string"||typeof y.user_code!="string"||typeof k!="number"||!Number.isFinite(k)||k<0)throw f("device_code_start",h,"OpenAI returned an invalid device code");if(c.status!=="pending")throw v();return $e(c,{deviceAuthId:y.device_auth_id,userCode:y.user_code,intervalS:k}),{...E,url:Ae,user_code:y.user_code,instructions:"Open the link, sign in to ChatGPT, and enter the code. The code expires in 15 minutes."}}catch(h){throw _(c,"failed",h instanceof Error?h.message:String(h),h),g.delete(c.id),h instanceof p?h:new p(502,"upstream_refused",`vendor sign-in could not start: ${h instanceof Error?h.message:String(h)}`)}}o(et,"startModelAuthFlow");async function L(e){const t=g.get(e);return t&&t.person===await R()?t:void 0}o(L,"ownFlow");function pe(e){g.set(e.id,e),S.set(C(e.person,e.provider),e.id)}o(pe,"register");async function tt(e){const t=await L(e);if(t)return _(t,"failed","cancelled"),q(t)}o(tt,"cancelModelAuthFlow");function Me(e){const t=e.trim();if(!t)return{};try{const n=new URL(t);return{code:n.searchParams.get("code")??void 0,state:n.searchParams.get("state")??void 0}}catch{}if(t.includes("#")){const[n,r]=t.split("#",2);return{code:n,state:r}}if(t.includes("code=")){const n=new URLSearchParams(t);return{code:n.get("code")??void 0,state:n.get("state")??void 0}}return{code:t}}o(Me,"parseCodeInput");async function Fe(e){let t=typeof e=="string"?await L(e):void 0;if(!t&&typeof e=="string"&&!g.has(e)&&(t=await Ce(e),t&&pe(t)),!t||t.status!=="pending")throw new p(400,"invalid_request","no in-flight sign-in has this flow_id","flow_id");return t}o(Fe,"pendingFlow");async function nt(e,t){const n=await Fe(e);if(n.provider===w)return He(n,t);if(n.kind!=="code_page")throw new p(400,"invalid_request","this sign-in completes without a pasted code","flow_id");if(n.exchanging)throw new p(400,"invalid_request","this sign-in is already being completed","flow_id");if(typeof t!="string")throw new p(400,"invalid_request","input must be the code Claude shows","input");const{code:r,state:a}=Me(t);if(!r)throw new p(400,"invalid_request","input must be the code Claude shows","input");if(a!==void 0&&a!==n.state)throw new p(400,"invalid_request","the pasted code belongs to a different sign-in","state");n.exchanging=!0;try{await oe(n,r,a??n.state)}catch(i){throw _(n,"failed",i instanceof Error?i.message:String(i),i),new p(502,"upstream_refused","Anthropic refused the code")}return _(n,"connected"),{flow_id:n.id,provider:n.provider,status:"connected"}}o(nt,"completeModelAuthFlow");async function He(e,t){if(e.exchanging)throw new p(400,"invalid_request","this sign-in is already being completed","flow_id");const n=new URL(b.redirect);let r;try{r=typeof t=="string"?new URL(t.trim()):void 0}catch{r=void 0}if(!r||r.origin!==n.origin||r.pathname!==n.pathname)throw new p(400,"invalid_request",`input must be the full redirect URL starting with ${b.redirect}`,"input");const{code:a,clientId:i}=de(r,e.state);e.exchanging=!0;try{await ue(e,a,i)}catch(s){throw _(e,"failed",s instanceof Error?s.message:String(s),s),new p(502,"upstream_refused","OpenAI refused the ChatGPT sign-in")}return _(e,"connected"),{flow_id:e.id,provider:e.provider,status:"connected"}}o(He,"completeChatGPTFlow");function q(e){return{flow_id:e.id,provider:e.provider,status:e.status,...e.message?{message:e.message}:{},...e.jti?{jti:e.jti}:{}}}o(q,"view");async function J(e){const t=await R(),n=await(await O()).prepare("SELECT provider, verifier_sealed, key_id, expires_at FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").get(e,t);if(!n||Date.parse(n.expires_at)<=Date.now())return;const r=JSON.parse(B(n.verifier_sealed,n.key_id,N(),F(t,e)));return{flow_id:e,provider:n.provider,status:"pending",...r.jti?{jti:r.jti}:{}}}o(J,"pendingRowView");async function rt(e){const t=await L(e);return t?q(t):g.has(e)?void 0:await J(e)}o(rt,"readModelAuthFlow");function it(e){const t=g.get(e);return t?.status==="expired"?{category:"expired",fields:{category:"expired"}}:t?.failure?{category:t.failure.category,step:t.failure.step,fields:t.failure.logFields()}:{category:"runtime",fields:{category:"runtime"}}}o(it,"modelAuthFlowFailure");async function at(e,t,n){const r=await L(e);return r?(r.status==="pending"&&t>0&&!n?.aborted&&await new Promise(a=>{const i=o(()=>{clearTimeout(s),n?.removeEventListener("abort",i),r.waiters.delete(i),a()},"done"),s=setTimeout(i,t);n?.addEventListener("abort",i,{once:!0}),r.waiters.add(i)}),q(r)):g.has(e)?void 0:await je(e,t,n)}o(at,"waitModelAuthFlow");const Ge=1e3;async function je(e,t,n){const r=await J(e);if(!r)return;const a=Date.now()+t;for(;!n?.aborted&&Date.now()<a&&(await new Promise(i=>setTimeout(i,Math.min(Ge,Math.max(0,a-Date.now())))),!n?.aborted);)if(!await J(e)){const i=(await U(r.provider)).length>0;return{...r,status:i?"connected":"failed"}}return r}o(je,"waitRowFlow");function ot(){for(const e of g.values())_(e,"failed","reset");g.clear(),S.clear()}o(ot,"resetModelAuthFlowsForTest");export{K as MODEL_AUTH_FLOW_TTL_S,ie as OPENAI_CHATGPT_API_PROVIDER,A as VendorSignInError,tt as cancelModelAuthFlow,nt as completeModelAuthFlow,Ze as modelAuthApiProvider,it as modelAuthFlowFailure,Qe as modelAuthProvider,rt as readModelAuthFlow,ot as resetModelAuthFlowsForTest,et as startModelAuthFlow,at as waitModelAuthFlow};
@@ -1,4 +1,3 @@
1
- export declare function handoffJtiConsumed(jti: string): boolean;
2
- /** Record `jti` as consumed until `exp`; false when it already was. Expired
3
- * entries are pruned on every write. */
4
- export declare function consumeHandoffJti(jti: string, exp: number): boolean;
1
+ export declare function handoffJtiConsumed(jti: string): Promise<boolean>;
2
+ /** Record `jti` as consumed until `exp`; false when it already was. Expired rows are pruned on every write. */
3
+ export declare function consumeHandoffJti(jti: string, exp: number): Promise<boolean>;
@@ -1 +1 @@
1
- var c=Object.defineProperty;var e=(t,n)=>c(t,"name",{value:n,configurable:!0});import{join as u}from"node:path";import{atomicWriteJson as a,readJsonIfExists as m}from"../fs-utils.js";import{secretsDir as d}from"../layout.js";function i(){return u(d(),"vendor-handoff-jti.json")}e(i,"jtiPath");function f(t){const n=m(i())??{},o={};for(const[s,r]of Object.entries(n))typeof r=="number"&&r>t&&(o[s]=r);return o}e(f,"readLive");function b(t){return Object.hasOwn(f(Date.now()/1e3),t)}e(b,"handoffJtiConsumed");function v(t,n){const o=f(Date.now()/1e3);return Object.hasOwn(o,t)?!1:(o[t]=n,a(i(),o,{mode:384}),!0)}e(v,"consumeHandoffJti");export{v as consumeHandoffJti,b as handoffJtiConsumed};
1
+ var u=Object.defineProperty;var n=(e,a)=>u(e,"name",{value:a,configurable:!0});import{openDb as r}from"../canvas/db.js";const t="handoff_jti|";async function s(e){const a=await(await r()).prepare("SELECT value FROM canvas_meta WHERE key = ?").get(`${t}${e}`);return a!==void 0&&Number(a.value)>Date.now()/1e3}n(s,"handoffJtiConsumed");async function f(e,a){const o=await r();await o.prepare("DELETE FROM canvas_meta WHERE key LIKE ? AND CAST(value AS DOUBLE PRECISION) <= ?").run(`${t}%`,Date.now()/1e3);const{changes:E}=await o.prepare("INSERT INTO canvas_meta (key, value) VALUES (?, ?) ON CONFLICT (key) DO NOTHING").run(`${t}${e}`,String(a));return E>0}n(f,"consumeHandoffJti");export{f as consumeHandoffJti,s as handoffJtiConsumed};
@@ -1 +1 @@
1
- var w=Object.defineProperty;var l=(e,t)=>w(e,"name",{value:t,configurable:!0});import{join as g}from"node:path";import{ApiError as c}from"../../api/index.js";import{secretsDir as v}from"../layout.js";import{PoolCredentialStore as y}from"../pool-credential-store.js";import{resolveLaunchTarget as _}from"../runtime/launch-target.js";import{coolingUntilFor as p,isManagedProvider as h,readSubscriptionPool as k}from"../subscription-state.js";function S(e){const t=e.indexOf("/"),o=t>0?e.slice(0,t):"";if(o==="")throw new c(422,"unresolved_model","the selected launch has no concrete model provider");return o}l(S,"providerFromModel");async function O(e){const o=(await _({root:!0,...e.profile!=null?{profile:e.profile}:{},...e.cwd!=null?{cwd:e.cwd}:{},...e.pin_cwd!=null?{pin_cwd:e.pin_cwd}:{},...e.mode!=null?{mode:e.mode}:{},...e.kind!=null?{kind:e.kind}:{},...e.model!=null?{model:e.model}:{}},{readOnlyProfileSelection:!0})).launch.model;if(o===void 0)throw new c(422,"unresolved_model","the selected launch has no concrete model");const r=S(o);if(h(r)){const a=k(r);if(a.length>0){const d=a.filter(n=>n.authFailure===void 0);if(d.length===0)return{provider:r,model:o,credential:"unusable",reason:"invalid_grant"};const s=Date.now(),u=d.some(n=>p(n,o)<=s),m=Math.min(...d.map(n=>p(n,o)));return{provider:r,model:o,credential:"ready",...!u&&m>s?{rate_limited_until:new Date(m).toISOString()}:{}}}}const i=await new y(g(v(),"auth.json")).read(r),f=i?.type==="oauth"||i?.type==="api_key"&&!!i.key;return{provider:r,model:o,credential:f?"ready":"missing"}}l(O,"modelAuthReadiness");export{O as modelAuthReadiness};
1
+ var w=Object.defineProperty;var l=(e,t)=>w(e,"name",{value:t,configurable:!0});import{ApiError as m}from"../../api/index.js";import{PoolCredentialStore as g}from"../pool-credential-store.js";import{resolveLaunchTarget as v}from"../runtime/launch-target.js";import{coolingUntilFor as p,isManagedProvider as y,readSubscriptionPool as _}from"../subscription-state.js";function h(e){const t=e.indexOf("/"),o=t>0?e.slice(0,t):"";if(o==="")throw new m(422,"unresolved_model","the selected launch has no concrete model provider");return o}l(h,"providerFromModel");async function A(e){const o=(await v({root:!0,...e.profile!=null?{profile:e.profile}:{},...e.cwd!=null?{cwd:e.cwd}:{},...e.pin_cwd!=null?{pin_cwd:e.pin_cwd}:{},...e.mode!=null?{mode:e.mode}:{},...e.kind!=null?{kind:e.kind}:{},...e.model!=null?{model:e.model}:{}},{readOnlyProfileSelection:!0})).launch.model;if(o===void 0)throw new m(422,"unresolved_model","the selected launch has no concrete model");const r=h(o);if(y(r)){const a=await _(r);if(a.length>0){const d=a.filter(n=>n.authFailure===void 0);if(d.length===0)return{provider:r,model:o,credential:"unusable",reason:"invalid_grant"};const s=Date.now(),f=d.some(n=>p(n,o)<=s),c=Math.min(...d.map(n=>p(n,o)));return{provider:r,model:o,credential:"ready",...!f&&c>s?{rate_limited_until:new Date(c).toISOString()}:{}}}}const i=await new g().read(r),u=i?.type==="oauth"||i?.type==="api_key"&&!!i.key;return{provider:r,model:o,credential:u?"ready":"missing"}}l(A,"modelAuthReadiness");export{A as modelAuthReadiness};
@@ -17,4 +17,4 @@ export type CoreModelConfig = {
17
17
  /** Validate the complete Core-owned payload before its single user-scope write. */
18
18
  export declare function validateCoreModelConfig(value: unknown): CoreModelConfig;
19
19
  /** Write Core-owned fields atomically while retaining unrelated config and kind fields. */
20
- export declare function replaceCoreModelConfig(config: CoreModelConfig): void;
20
+ export declare function replaceCoreModelConfig(config: CoreModelConfig): Promise<void>;
@@ -1 +1 @@
1
- var O=Object.defineProperty;var h=(r,t)=>O(r,"name",{value:t,configurable:!0});import{updateRawConfigAtomically as j}from"./config.js";const b=["ultra","strong","medium","light"],R=/^[^/\s]+\/(ultra|strong|medium|light)$/;function u(r,t){if(r===null||typeof r!="object"||Array.isArray(r))throw new Error(`${t} must be an object`);return r}h(u,"object");function c(r,t){if(typeof r!="string"||r.trim()==="")throw new Error(`${t} must be a non-empty string`);return r}h(c,"nonempty");function M(r){const t=u(r,"model config"),g=u(t.modelRoutes,"modelRoutes"),s={};for(const[e,a]of Object.entries(g)){c(e,"route id");const i=u(a,`modelRoutes.${e}`),l=c(i.providerId,`modelRoutes.${e}.providerId`),d=u(i.models,`modelRoutes.${e}.models`),p={};for(const m of b){const y=c(d[m],`modelRoutes.${e}.models.${m}`),k=y.indexOf("/");if(k<=0||k===y.length-1||y.slice(0,k)!==l)throw new Error(`modelRoutes.${e}.models.${m} must be a ${l}/id ref`);p[m]=y}for(const m of Object.keys(d))if(!b.includes(m))throw new Error(`modelRoutes.${e}.models.${m} is not a model strength`);s[e]={family:c(i.family,`modelRoutes.${e}.family`),credentialSource:c(i.credentialSource,`modelRoutes.${e}.credentialSource`),providerId:l,models:p}}const o=u(t.modelRouting,"modelRouting");for(const e of["credentialSourceOrder","familyOrder"])if(o[e]!==void 0&&(!Array.isArray(o[e])||!o[e].every(a=>typeof a=="string"&&a.trim()!=="")))throw new Error(`modelRouting.${e} must be an array of non-empty strings`);if(o.strengthFallback!==void 0&&(!Array.isArray(o.strengthFallback)||!o.strengthFallback.every(e=>b.includes(e))))throw new Error("modelRouting.strengthFallback must be an array of model strengths");const n=new Set(Object.values(s).map(e=>e.family)),f=new Set(Object.values(s).map(e=>e.credentialSource));for(const e of o.familyOrder??[])if(!n.has(e))throw new Error(`modelRouting.familyOrder names unknown route family ${e}`);for(const e of o.credentialSourceOrder??[])if(!f.has(e))throw new Error(`modelRouting.credentialSourceOrder names unknown credential source ${e}`);const $=u(t.kinds,"kinds"),w={};for(const[e,a]of Object.entries($)){c(e,"kind");const i=u(a,`kinds.${e}`),l=c(i.model,`kinds.${e}.model`);if(!R.test(l))throw new Error(`kinds.${e}.model must be family/strength`);if(!n.has(l.slice(0,l.lastIndexOf("/"))))throw new Error(`kinds.${e}.model names unknown route family`);const d=i.orchestratorModel===void 0?void 0:c(i.orchestratorModel,`kinds.${e}.orchestratorModel`);if(d!==void 0&&!R.test(d))throw new Error(`kinds.${e}.orchestratorModel must be family/strength`);if(d!==void 0&&!n.has(d.slice(0,d.lastIndexOf("/"))))throw new Error(`kinds.${e}.orchestratorModel names unknown route family`);w[e]={model:l,...d===void 0?{}:{orchestratorModel:d}}}return{modelRoutes:s,modelRouting:structuredClone(o),kinds:w}}h(M,"validateCoreModelConfig");function S(r){j("user",t=>{const g=t.kinds!==null&&typeof t.kinds=="object"&&!Array.isArray(t.kinds)?t.kinds:{},s=Object.fromEntries(Object.entries(g).map(([o,n])=>{const f=n!==null&&typeof n=="object"&&!Array.isArray(n)?n:{},{model:$,orchestratorModel:w,...e}=f;return[o,e]}));for(const[o,n]of Object.entries(r.kinds)){const f=s[o]!==null&&typeof s[o]=="object"&&!Array.isArray(s[o])?s[o]:{};s[o]={...f,model:n.model,orchestratorModel:n.orchestratorModel??n.model}}return{...t,modelRoutes:structuredClone(r.modelRoutes),modelRouting:structuredClone(r.modelRouting),kinds:s}})}h(S,"replaceCoreModelConfig");export{S as replaceCoreModelConfig,M as validateCoreModelConfig};
1
+ var O=Object.defineProperty;var h=(r,t)=>O(r,"name",{value:t,configurable:!0});import{updateRawConfigAtomically as j}from"./config.js";const b=["ultra","strong","medium","light"],R=/^[^/\s]+\/(ultra|strong|medium|light)$/;function a(r,t){if(r===null||typeof r!="object"||Array.isArray(r))throw new Error(`${t} must be an object`);return r}h(a,"object");function c(r,t){if(typeof r!="string"||r.trim()==="")throw new Error(`${t} must be a non-empty string`);return r}h(c,"nonempty");function M(r){const t=a(r,"model config"),g=a(t.modelRoutes,"modelRoutes"),s={};for(const[e,u]of Object.entries(g)){c(e,"route id");const i=a(u,`modelRoutes.${e}`),l=c(i.providerId,`modelRoutes.${e}.providerId`),d=a(i.models,`modelRoutes.${e}.models`),p={};for(const m of b){const y=c(d[m],`modelRoutes.${e}.models.${m}`),k=y.indexOf("/");if(k<=0||k===y.length-1||y.slice(0,k)!==l)throw new Error(`modelRoutes.${e}.models.${m} must be a ${l}/id ref`);p[m]=y}for(const m of Object.keys(d))if(!b.includes(m))throw new Error(`modelRoutes.${e}.models.${m} is not a model strength`);s[e]={family:c(i.family,`modelRoutes.${e}.family`),credentialSource:c(i.credentialSource,`modelRoutes.${e}.credentialSource`),providerId:l,models:p}}const o=a(t.modelRouting,"modelRouting");for(const e of["credentialSourceOrder","familyOrder"])if(o[e]!==void 0&&(!Array.isArray(o[e])||!o[e].every(u=>typeof u=="string"&&u.trim()!=="")))throw new Error(`modelRouting.${e} must be an array of non-empty strings`);if(o.strengthFallback!==void 0&&(!Array.isArray(o.strengthFallback)||!o.strengthFallback.every(e=>b.includes(e))))throw new Error("modelRouting.strengthFallback must be an array of model strengths");const n=new Set(Object.values(s).map(e=>e.family)),f=new Set(Object.values(s).map(e=>e.credentialSource));for(const e of o.familyOrder??[])if(!n.has(e))throw new Error(`modelRouting.familyOrder names unknown route family ${e}`);for(const e of o.credentialSourceOrder??[])if(!f.has(e))throw new Error(`modelRouting.credentialSourceOrder names unknown credential source ${e}`);const $=a(t.kinds,"kinds"),w={};for(const[e,u]of Object.entries($)){c(e,"kind");const i=a(u,`kinds.${e}`),l=c(i.model,`kinds.${e}.model`);if(!R.test(l))throw new Error(`kinds.${e}.model must be family/strength`);if(!n.has(l.slice(0,l.lastIndexOf("/"))))throw new Error(`kinds.${e}.model names unknown route family`);const d=i.orchestratorModel===void 0?void 0:c(i.orchestratorModel,`kinds.${e}.orchestratorModel`);if(d!==void 0&&!R.test(d))throw new Error(`kinds.${e}.orchestratorModel must be family/strength`);if(d!==void 0&&!n.has(d.slice(0,d.lastIndexOf("/"))))throw new Error(`kinds.${e}.orchestratorModel names unknown route family`);w[e]={model:l,...d===void 0?{}:{orchestratorModel:d}}}return{modelRoutes:s,modelRouting:structuredClone(o),kinds:w}}h(M,"validateCoreModelConfig");async function S(r){await j("user",t=>{const g=t.kinds!==null&&typeof t.kinds=="object"&&!Array.isArray(t.kinds)?t.kinds:{},s=Object.fromEntries(Object.entries(g).map(([o,n])=>{const f=n!==null&&typeof n=="object"&&!Array.isArray(n)?n:{},{model:$,orchestratorModel:w,...e}=f;return[o,e]}));for(const[o,n]of Object.entries(r.kinds)){const f=s[o]!==null&&typeof s[o]=="object"&&!Array.isArray(s[o])?s[o]:{};s[o]={...f,model:n.model,orchestratorModel:n.orchestratorModel??n.model}}return{...t,modelRoutes:structuredClone(r.modelRoutes),modelRouting:structuredClone(r.modelRouting),kinds:s}})}h(S,"replaceCoreModelConfig");export{S as replaceCoreModelConfig,M as validateCoreModelConfig};
@@ -0,0 +1,14 @@
1
+ import { type InitialModelRegistryLike, type InitialModelRouteSelection, type ModelRegistryLike, type ModelRequest, type RouteAvailability, type RoutedCandidate } from './model-routes.js';
2
+ /** Probe the common availability facts consumed unchanged by resolveProviderCandidates. Reads the person's credential rows: daemon-side callers only. */
3
+ export declare function probeRouteAvailability(candidate: RoutedCandidate, registry: ModelRegistryLike, now?: number): Promise<RouteAvailability>;
4
+ /** Select the first viable route for a new SDK session. Route declarations own
5
+ * preference order; logical requests append registered models not declared by a
6
+ * route. The caller owns broker faults/events and the no-viable outcome. */
7
+ export declare function selectInitialModelRoute<Model extends {
8
+ provider: string;
9
+ id: string;
10
+ }>(request: ModelRequest, registry: InitialModelRegistryLike<Model>, options?: {
11
+ cwd?: string;
12
+ profileId?: string | null;
13
+ now?: number;
14
+ }): Promise<InitialModelRouteSelection<Model>>;
@@ -0,0 +1 @@
1
+ var m=Object.defineProperty;var c=(t,r)=>m(t,"name",{value:r,configurable:!0});import{isManagedProvider as l,resolveProviderCandidates as w}from"./managed-providers.js";import{readSubscriptionPool as y}from"./subscription-state.js";import{expandModelCandidates as b,routeAvailability as h}from"./model-routes.js";async function $(t,r,i=Date.now()){return h(t,r,l(t.providerId)?await y(t.providerId):void 0,i)}c($,"probeRouteAvailability");async function P(t,r,i={}){const n=await b(t,i.cwd,i.profileId),f=new Set(n.map(e=>`${e.providerId}/${e.modelId}`)),I=t.kind==="logical"?r.getAll().filter(e=>!f.has(`${e.provider}/${e.id}`)).map((e,d)=>({routeId:`registry:${e.provider}/${e.id}`,credentialSource:"registry",family:e.provider,strength:"strong",providerId:e.provider,modelId:e.id,rank:n.length+d})):[],a=[...n,...I],p=i.now??Date.now(),s=new Map;for(const e of a)s.set(e,await $(e,r,p));const g=a.some(e=>{const d=s.get(e);return l(e.providerId)&&d.registered&&d.authenticated}),u=w(a.map(e=>({...e,...s.get(e),automaticFallback:e.routeId.startsWith("registry:")?l(e.providerId)||!g:!0})),p,{pinnedToFirst:t.kind==="exact"}),o=u.candidates[0],v=o===void 0?void 0:r.find(o.providerId,o.modelId);return{...o!==void 0&&v!==void 0?{selected:{model:v,candidate:o}}:{},rejected:u.rejected}}c(P,"selectInitialModelRoute");export{$ as probeRouteAvailability,P as selectInitialModelRoute};
@@ -1,5 +1,5 @@
1
1
  import type { ModelStrength } from '../types.js';
2
- import { type ProviderCandidate, type ProviderCandidateRejection } from './subscription-state.js';
2
+ import { type ProviderCandidate, type ProviderCandidateRejection, type SubscriptionCredential } from './managed-providers.js';
3
3
  export type ModelRequest = {
4
4
  kind: 'logical';
5
5
  family: string;
@@ -66,8 +66,13 @@ export declare function parseModelSpec(spec: string): {
66
66
  modelId: string;
67
67
  thinkingLevel?: string;
68
68
  } | undefined;
69
+ type RotationReader = () => Promise<{
70
+ defaultFallbackStrength?: ModelStrength;
71
+ }>;
72
+ /** The process that owns the person's rows (daemon, API) registers how to read their rotation settings; a broker reads its launch snapshot, and a CLI process has neither and gets the default. */
73
+ export declare function registerRotationReader(reader: RotationReader): void;
69
74
  /** Expand config-derived route order only. Availability never affects rank. */
70
- export declare function expandModelCandidates(request: ModelRequest, cwd?: string, profileId?: string | null): RoutedCandidate[];
75
+ export declare function expandModelCandidates(request: ModelRequest, cwd?: string, profileId?: string | null): Promise<RoutedCandidate[]>;
71
76
  /** Identify the one permanently invalid recipe shape: a concrete/exact request
72
77
  * whose requested provider/model is absent from the serving registry. Auth and
73
78
  * cooldown are deliberately outside this discriminator so callers retain their
@@ -75,18 +80,8 @@ export declare function expandModelCandidates(request: ModelRequest, cwd?: strin
75
80
  export declare function unregisteredConcreteModel(request: ModelRequest | undefined, registry: ModelRegistryLike, options?: {
76
81
  cwd?: string;
77
82
  profileId?: string | null;
78
- }): UnregisteredConcreteModel | undefined;
79
- /** Probe the common availability facts consumed unchanged by resolveProviderCandidates. */
80
- export declare function probeRouteAvailability(candidate: RoutedCandidate, registry: ModelRegistryLike, now?: number): RouteAvailability;
81
- /** Select the first viable route for a new SDK session. Route declarations own
82
- * preference order; logical requests append registered models not declared by a
83
- * route. The caller owns broker faults/events and the no-viable outcome. */
84
- export declare function selectInitialModelRoute<Model extends {
85
- provider: string;
86
- id: string;
87
- }>(request: ModelRequest, registry: InitialModelRegistryLike<Model>, options?: {
88
- cwd?: string;
89
- profileId?: string | null;
90
- now?: number;
91
- }): InitialModelRouteSelection<Model>;
83
+ }): Promise<UnregisteredConcreteModel | undefined>;
84
+ /** The availability facts of one route given the managed pool already read for its provider (undefined for a provider that has no pool). */
85
+ export declare function routeAvailability(candidate: RoutedCandidate, registry: ModelRegistryLike, pool: readonly SubscriptionCredential[] | undefined, now?: number): RouteAvailability;
92
86
  export declare function modelRequestFromConfig(model: string | undefined, modelIntent: string | undefined, exact: boolean): ModelRequest | undefined;
87
+ export {};
@@ -1 +1 @@
1
- var E=Object.defineProperty;var f=(e,t)=>E(e,"name",{value:t,configurable:!0});import{readMergedLaunchConfig as U}from"./config.js";import{isManagedProvider as F,readRotationConfig as D,coolingUntilFor as L,readSubscriptionPool as G,resolveProviderCandidates as H}from"./subscription-state.js";const S="app",b=["ultra","strong","medium","light"],A=new Set(["off","minimal","low","medium","high","xhigh","max"]);function x(e){const t=e.indexOf("/");if(t<=0||t===e.length-1)return;const o=e.slice(0,t),n=e.slice(t+1),s=n.lastIndexOf(":"),u=s>0?n.slice(s+1).toLowerCase():void 0;return{providerId:o,modelId:u!==void 0&&A.has(u)?n.slice(0,s):n,...u!==void 0&&A.has(u)&&u!=="off"?{thinkingLevel:u}:{}}}f(x,"parseModelSpec");function K(e){const t=f(o=>{const n=e[o].strong.slice(0,e[o].strong.indexOf("/"));return{family:o,credentialSource:"default",providerId:n,models:e[o]}},"route");return{anthropic:t("anthropic"),openai:t("openai")}}f(K,"synthesizeRoutes");function $(e,t){return[e,...t.filter(o=>o!==e)]}f($,"ordered");function _(e,t){return $(e,t).filter((o,n,s)=>s.indexOf(o)===n)}f(_,"strengths");function j(e,t,o){if(e.kind==="exact"){const r=x(e.spec);return r?[{routeId:"exact",credentialSource:"exact",family:r.providerId,strength:"strong",...r,rank:0}]:[]}if(e.kind==="logical"&&e.family===S){const r=x(e.spec);return r?[{routeId:S,credentialSource:S,family:S,strength:e.strength,...r,rank:0}]:[]}const n=U(t,o??null),s={...n.modelRoutes??{},...n.legacyModelRoutes??{}},u=Object.keys(s).length>0?s:K(n.modelLadders),l=Object.entries(u),m=n.modelRouting?.strengthFallback??[D().defaultFallbackStrength??"strong"],y=l.map(([,r])=>r.family).filter((r,d,a)=>a.indexOf(r)===d),I=l.map(([,r])=>r.credentialSource).filter((r,d,a)=>a.indexOf(r)===d),c=e.kind==="concrete"?x(e.spec):void 0,v=e.kind==="logical"?e:void 0,i=f((r,d)=>{const a=x(r);return a!==void 0&&c!==void 0&&a.providerId===c.providerId&&a.modelId===c.modelId&&(!d||a.thinkingLevel===c.thinkingLevel)},"matchesConcrete"),p=c===void 0?void 0:l.find(([,r])=>Object.values(r.models).some(d=>i(d,!1))),O=v?.strength??(()=>{if(!c)return"strong";for(const r of[!0,!1])for(const d of r?b:[...b].reverse())if(l.some(([,a])=>{const h=a.models[d];return h!==void 0&&i(h,r)}))return d;return"strong"})(),R=v?.family??p?.[1].family??c?.providerId??"anthropic",T=c?I:n.modelRouting?.credentialSourceOrder??I,N=c?[R]:n.modelRouting?.familyOrder??y,w=[],C=new Set,M=f((r,d,a,h)=>{const g=x(h);if(!g||c&&g.modelId!==c.modelId)return;const k=JSON.stringify([r,g.providerId,g.modelId,g.thinkingLevel]);C.has(k)||(C.add(k),w.push({routeId:r,credentialSource:d.credentialSource,family:d.family,strength:a,...g,rank:w.length}))},"append");if(c){const[r,d]=p??["concrete",{family:R,credentialSource:"default"}];M(r,d,O,e.spec)}for(const r of T)for(const d of c?[O]:_(O,m))for(const a of $(R,N))for(const[h,g]of l){if(g.credentialSource!==r||g.family!==a)continue;const k=g.models[d];k&&M(h,g,d,k)}return w.map((r,d)=>({...r,rank:d}))}f(j,"expandModelCandidates");function X(e,t,o={}){if(e===void 0||e.kind==="logical")return;const n=j(e,o.cwd,o.profileId)[0];if(!(n===void 0||t.find(n.providerId,n.modelId)!==void 0))return{request:e,candidate:n,reason:"unregistered"}}f(X,"unregisteredConcreteModel");function P(e,t,o=Date.now()){const n=t.find(e.providerId,e.modelId);if(n===void 0)return{registered:!1,authenticated:!1};if(!F(e.providerId))return{registered:!0,authenticated:t.hasConfiguredAuth(n)};const s=G(e.providerId).filter(l=>l.authFailure===void 0),u=s.length>0&&s.every(l=>L(l,e.modelId)>o);return{registered:!0,authenticated:s.length>0||t.hasConfiguredAuth(n),...u?{coolingUntil:Math.min(...s.map(l=>L(l,e.modelId)))}:{}}}f(P,"probeRouteAvailability");function Y(e,t,o={}){const n=j(e,o.cwd,o.profileId),s=new Set(n.map(i=>`${i.providerId}/${i.modelId}`)),u=e.kind==="logical"?t.getAll().filter(i=>!s.has(`${i.provider}/${i.id}`)).map((i,p)=>({routeId:`registry:${i.provider}/${i.id}`,credentialSource:"registry",family:i.provider,strength:"strong",providerId:i.provider,modelId:i.id,rank:n.length+p})):[],l=[...n,...u],m=o.now??Date.now(),y=l.some(i=>{const p=P(i,t,m);return F(i.providerId)&&p.registered&&p.authenticated}),I=H(l.map(i=>({...i,...P(i,t,m),automaticFallback:i.routeId.startsWith("registry:")?F(i.providerId)||!y:!0})),m,{pinnedToFirst:e.kind==="exact"}),c=I.candidates[0],v=c===void 0?void 0:t.find(c.providerId,c.modelId);return{...c!==void 0&&v!==void 0?{selected:{model:v,candidate:c}}:{},rejected:I.rejected}}f(Y,"selectInitialModelRoute");function B(e,t,o){if(e){if(o)return{kind:"exact",spec:e};if(t){const n=t.lastIndexOf("/"),s=n>0?t.slice(n+1):"";if(b.includes(s))return{kind:"logical",family:t.slice(0,n),strength:s,spec:e}}return{kind:"concrete",spec:e}}}f(B,"modelRequestFromConfig");export{S as APP_FAMILY,j as expandModelCandidates,B as modelRequestFromConfig,x as parseModelSpec,P as probeRouteAvailability,Y as selectInitialModelRoute,X as unregisteredConcreteModel};
1
+ var E=Object.defineProperty;var s=(e,t)=>E(e,"name",{value:t,configurable:!0});import{readMergedLaunchConfig as U}from"./config.js";import{brokerConfigSnapshot as G}from"./config.js";import{coolingUntilFor as L}from"./managed-providers.js";const I="app",y=["ultra","strong","medium","light"],M=new Set(["off","minimal","low","medium","high","xhigh","max"]);function p(e){const t=e.indexOf("/");if(t<=0||t===e.length-1)return;const o=e.slice(0,t),r=e.slice(t+1),d=r.lastIndexOf(":"),a=d>0?r.slice(d+1).toLowerCase():void 0;return{providerId:o,modelId:a!==void 0&&M.has(a)?r.slice(0,d):r,...a!==void 0&&M.has(a)&&a!=="off"?{thinkingLevel:a}:{}}}s(p,"parseModelSpec");function H(e){const t=s(o=>{const r=e[o].strong.slice(0,e[o].strong.indexOf("/"));return{family:o,credentialSource:"default",providerId:r,models:e[o]}},"route");return{anthropic:t("anthropic"),openai:t("openai")}}s(H,"synthesizeRoutes");function A(e,t){return[e,...t.filter(o=>o!==e)]}s(A,"ordered");function P(e,t){return A(e,t).filter((o,r,d)=>d.indexOf(o)===r)}s(P,"strengths");let v;function Y(e){v=e}s(Y,"registerRotationReader");async function _(){const e=G();return e?e.rotation:v?v():{}}s(_,"rotationConfig");async function z(e,t,o){if(e.kind==="exact"){const n=p(e.spec);return n?[{routeId:"exact",credentialSource:"exact",family:n.providerId,strength:"strong",...n,rank:0}]:[]}if(e.kind==="logical"&&e.family===I){const n=p(e.spec);return n?[{routeId:I,credentialSource:I,family:I,strength:e.strength,...n,rank:0}]:[]}const r=U(t,o??null),d={...r.modelRoutes??{},...r.legacyModelRoutes??{}},a=Object.keys(d).length>0?d:H(r.modelLadders),u=Object.entries(a),g=r.modelRouting?.strengthFallback??[(await _()).defaultFallbackStrength??"strong"],N=u.map(([,n])=>n.family).filter((n,i,c)=>c.indexOf(n)===i),O=u.map(([,n])=>n.credentialSource).filter((n,i,c)=>c.indexOf(n)===i),f=e.kind==="concrete"?p(e.spec):void 0,R=e.kind==="logical"?e:void 0,C=s((n,i)=>{const c=p(n);return c!==void 0&&f!==void 0&&c.providerId===f.providerId&&c.modelId===f.modelId&&(!i||c.thinkingLevel===f.thinkingLevel)},"matchesConcrete"),F=f===void 0?void 0:u.find(([,n])=>Object.values(n.models).some(i=>C(i,!1))),x=R?.strength??(()=>{if(!f)return"strong";for(const n of[!0,!1])for(const i of n?y:[...y].reverse())if(u.some(([,c])=>{const m=c.models[i];return m!==void 0&&C(m,n)}))return i;return"strong"})(),k=R?.family??F?.[1].family??f?.providerId??"anthropic",T=f?O:r.modelRouting?.credentialSourceOrder??O,j=f?[k]:r.modelRouting?.familyOrder??N,S=[],b=new Set,w=s((n,i,c,m)=>{const l=p(m);if(!l||f&&l.modelId!==f.modelId)return;const h=JSON.stringify([n,l.providerId,l.modelId,l.thinkingLevel]);b.has(h)||(b.add(h),S.push({routeId:n,credentialSource:i.credentialSource,family:i.family,strength:c,...l,rank:S.length}))},"append");if(f){const[n,i]=F??["concrete",{family:k,credentialSource:"default"}];w(n,i,x,e.spec)}for(const n of T)for(const i of f?[x]:P(x,g))for(const c of A(k,j))for(const[m,l]of u){if(l.credentialSource!==n||l.family!==c)continue;const h=l.models[i];h&&w(m,l,i,h)}return S.map((n,i)=>({...n,rank:i}))}s(z,"expandModelCandidates");async function B(e,t,o={}){if(e===void 0||e.kind==="logical")return;const r=(await z(e,o.cwd,o.profileId))[0];if(!(r===void 0||t.find(r.providerId,r.modelId)!==void 0))return{request:e,candidate:r,reason:"unregistered"}}s(B,"unregisteredConcreteModel");function Q(e,t,o,r=Date.now()){const d=t.find(e.providerId,e.modelId);if(d===void 0)return{registered:!1,authenticated:!1};if(o===void 0)return{registered:!0,authenticated:t.hasConfiguredAuth(d)};const a=o.filter(g=>g.authFailure===void 0),u=a.length>0&&a.every(g=>L(g,e.modelId)>r);return{registered:!0,authenticated:a.length>0||t.hasConfiguredAuth(d),...u?{coolingUntil:Math.min(...a.map(g=>L(g,e.modelId)))}:{}}}s(Q,"routeAvailability");function V(e,t,o){if(e){if(o)return{kind:"exact",spec:e};if(t){const r=t.lastIndexOf("/"),d=r>0?t.slice(r+1):"";if(y.includes(d))return{kind:"logical",family:t.slice(0,r),strength:d,spec:e}}return{kind:"concrete",spec:e}}}s(V,"modelRequestFromConfig");export{I as APP_FAMILY,z as expandModelCandidates,V as modelRequestFromConfig,p as parseModelSpec,Y as registerRotationReader,Q as routeAvailability,B as unregisteredConcreteModel};
@@ -19,6 +19,8 @@ export interface IngestResult {
19
19
  export declare const isCatalogLag: (code: string) => boolean;
20
20
  /** The delivery key prefix of one sender's message; a delivery's full key adds `:<destination>`. */
21
21
  export declare const ingestKey: (via: string, messageId: string) => string;
22
+ /** Postgres `deadlock_detected` (40P01) or `serialization_failure` (40001). */
23
+ export declare const isTransientDbError: (error: unknown) => boolean;
22
24
  /** Apply a batch from machine `via`. One event's failure rejects only that event. Runs in the caller's write transaction.
23
25
  *
24
26
  * The home sends one row per destination while the receipt is per message, so the destination's half is handled for every row:
@@ -1 +1 @@
1
- var g=Object.defineProperty;var o=(e,t)=>g(e,"name",{value:t,configurable:!0});import{CrtrError as k}from"../errors.js";import{getById as u}from"../graph/objects.js";import{publish as O}from"../graph/events.js";import{markLinkWatch as R,recordWatch as N}from"../graph/watches.js";import{armObjectWaitForWatch as T}from"../graph/object-waits.js";import{ackRows as h,claimRows as L}from"./outbox-store.js";import{ensureStub as y,localOwnerActor as C,stubOf as w}from"./stubs.js";import{withCanvasWrite as S}from"../canvas/db.js";const K=100,P=o(e=>e==="unknown_event"||e==="unknown_kind","isCatalogLag"),f=o((e,t)=>`peer:${e}:${t}`,"ingestKey");async function $(e,t){return await e.prepare("SELECT 1 FROM peer_ingested WHERE key = ?").get(t)!==void 0?!0:await e.prepare("SELECT 1 FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ? LIMIT 1").get(`${t}:`,`${t};`)!==void 0}o($,"applied");const m=o(async(e,t,n)=>{await e.prepare("INSERT INTO peer_ingested_destinations (key, ingested_at) VALUES (?, ?) ON CONFLICT DO NOTHING").run(`${t}:${n}`,Date.now())},"markDestination");async function F(e,t,n){return await e.prepare("SELECT 1 FROM peer_ingested_destinations WHERE key = ?").get(`${t}:${n}`)!==void 0||await e.prepare("SELECT 1 FROM deliveries WHERE idempotency_key = ?").get(`${t}:${n}`)!==void 0}o(F,"destinationDone");async function I(e,t,n){return await C(e,t)??await y(e,t.id,t.type,n,void 0,t)}o(I,"actorFor");async function E(e,t,n,r,i){const c=n.actor===null?null:await I(e,n.actor,t),l=n.body_text,a=n.body===void 0&&l===void 0?void 0:{...n.body??{},...l===void 0?{}:{body:l}};await O(e,{event:n.event,object:r,actor:c,level:n.level,attrs:n.attrs,...a===void 0?{}:{messageBody:a},idempotencyKey:f(t,n.message_id),...i===void 0?{}:{onlyWatchers:i}})}o(E,"apply");async function W(e,t){const n=`ev:${t.message_id}:`;if(!t.key.startsWith(n)||t.key.length===n.length)return null;const r=await u(e,t.key.slice(n.length));return r!==null&&r.type==="node"&&r.deleted_at===null&&await w(e,r)===null?r:null}o(W,"localDestination");async function U(e,t,n){const r={accepted:[],rejected:[]};for(const i of n){const c=f(t,i.message_id),l=await $(e,c);try{await S(async()=>{const a=await W(e,i);if(l&&a===null)return;const s=await y(e,i.object.id,i.object.type,t,void 0,i.object),d=i.linked===!0&&a!==null&&s.owner!==null?await u(e,s.owner):null,p=d!==null&&d.type==="app"?d:null;if(a!==null&&await e.prepare("SELECT 1 FROM watches WHERE watcher = ? AND object = ?").get(a.id,s.id)===void 0?(await N(e,a,s,"act",{patterns:[">"],...p===null?{}:{linkApp:p}}),await T(e,a,s)):a!==null&&p!==null&&await R(e,a,s,p),l)await F(e,c,a.id)||await E(e,t,i,s,[a.id]);else{await E(e,t,i,s),await e.prepare("INSERT INTO peer_ingested (key, ingested_at) VALUES (?, ?) ON CONFLICT DO NOTHING").run(c,Date.now());for(const _ of await e.prepare("SELECT destination_id FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ?").all(`${c}:`,`${c};`))await m(e,c,_.destination_id)}a!==null&&await m(e,c,a.id)},{savepoint:!0}),r.accepted.push(i.key)}catch(a){const s=a instanceof k?a.code:"invalid_event";r.rejected.push({key:i.key,code:s,message:a instanceof Error?a.message:String(a)})}}return r}o(U,"ingestPeerEvents");const X=o(async(e,t,n)=>(await w(e,t))?.home_machine===n,"isStubOf"),q=o(async(e,t,n)=>await L(e,t,n),"claimFor"),v=o(async(e,t,n)=>await h(e,t,n),"ackFor");export{K as MAX_EVENTS_PER_CALL,v as ackFor,q as claimFor,f as ingestKey,U as ingestPeerEvents,P as isCatalogLag,X as isStubOf};
1
+ var _=Object.defineProperty;var o=(e,t)=>_(e,"name",{value:t,configurable:!0});import{CrtrError as O}from"../errors.js";import{getById as w}from"../graph/objects.js";import{publish as h}from"../graph/events.js";import{markLinkWatch as L,recordWatch as R}from"../graph/watches.js";import{armObjectWaitForWatch as T}from"../graph/object-waits.js";import{ackRows as N,claimRows as C}from"./outbox-store.js";import{actorLockKeys as S,ensureStub as f,localOwnerActor as $,lockInOrder as F,stubLockKeys as I,stubOf as m}from"./stubs.js";import{withCanvasWrite as j}from"../canvas/db.js";const z=100,J=o(e=>e==="unknown_event"||e==="unknown_kind","isCatalogLag"),y=o((e,t)=>`peer:${e}:${t}`,"ingestKey");async function x(e,t){return await e.prepare("SELECT 1 FROM peer_ingested WHERE key = ?").get(t)!==void 0?!0:await e.prepare("SELECT 1 FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ? LIMIT 1").get(`${t}:`,`${t};`)!==void 0}o(x,"applied");const E=o(async(e,t,n)=>{await e.prepare("INSERT INTO peer_ingested_destinations (key, ingested_at) VALUES (?, ?) ON CONFLICT DO NOTHING").run(`${t}:${n}`,Date.now())},"markDestination");async function D(e,t,n){return await e.prepare("SELECT 1 FROM peer_ingested_destinations WHERE key = ?").get(`${t}:${n}`)!==void 0||await e.prepare("SELECT 1 FROM deliveries WHERE idempotency_key = ?").get(`${t}:${n}`)!==void 0}o(D,"destinationDone");async function W(e,t,n){return await $(e,t)??await f(e,t.id,t.type,n,void 0,t)}o(W,"actorFor");async function k(e,t,n,r,l){const i=n.actor===null?null:await W(e,n.actor,t),s=n.body_text,p=n.body===void 0&&s===void 0?void 0:{...n.body??{},...s===void 0?{}:{body:s}};await h(e,{event:n.event,object:r,actor:i,level:n.level,attrs:n.attrs,...p===void 0?{}:{messageBody:p},idempotencyKey:y(t,n.message_id),...l===void 0?{}:{onlyWatchers:l}})}o(k,"apply");async function A(e,t){const n=`ev:${t.message_id}:`;if(!t.key.startsWith(n)||t.key.length===n.length)return null;const r=await w(e,t.key.slice(n.length));return r!==null&&r.type==="node"&&r.deleted_at===null&&await m(e,r)===null?r:null}o(A,"localDestination");const H=o(e=>{const t=e?.code;return t==="40P01"||t==="40001"},"isTransientDbError");async function Q(e,t,n){const r={accepted:[],rejected:[]},l=[];for(const i of n)l.push(`ingest:${y(t,i.message_id)}`,...await I(e,i.object.id,i.object.type,null,i.object)),i.actor!==null&&l.push(...await S(e,i.actor));await F(e,l);for(const i of n){const s=y(t,i.message_id),p=await x(e,s);try{await j(async()=>{const a=await A(e,i);if(p&&a===null)return;const c=await f(e,i.object.id,i.object.type,t,void 0,i.object),d=i.linked===!0&&a!==null&&c.owner!==null?await w(e,c.owner):null,u=d!==null&&d.type==="app"?d:null;if(a!==null&&await e.prepare("SELECT 1 FROM watches WHERE watcher = ? AND object = ?").get(a.id,c.id)===void 0?(await R(e,a,c,"act",{patterns:[">"],...u===null?{}:{linkApp:u}}),await T(e,a,c)):a!==null&&u!==null&&await L(e,a,c,u),p)await D(e,s,a.id)||await k(e,t,i,c,[a.id]);else{await k(e,t,i,c),await e.prepare("INSERT INTO peer_ingested (key, ingested_at) VALUES (?, ?) ON CONFLICT DO NOTHING").run(s,Date.now());for(const g of await e.prepare("SELECT destination_id FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ?").all(`${s}:`,`${s};`))await E(e,s,g.destination_id)}a!==null&&await E(e,s,a.id)},{savepoint:!0}),r.accepted.push(i.key)}catch(a){if(H(a))throw a;const c=a instanceof O?a.code:"invalid_event";r.rejected.push({key:i.key,code:c,message:a instanceof Error?a.message:String(a)})}}return r}o(Q,"ingestPeerEvents");const Y=o(async(e,t,n)=>(await m(e,t))?.home_machine===n,"isStubOf"),Z=o(async(e,t,n)=>await C(e,t,n),"claimFor"),v=o(async(e,t,n)=>await N(e,t,n),"ackFor");export{z as MAX_EVENTS_PER_CALL,v as ackFor,Z as claimFor,y as ingestKey,Q as ingestPeerEvents,J as isCatalogLag,Y as isStubOf,H as isTransientDbError};
@@ -56,6 +56,14 @@ export declare const isStub: (db: CanvasDb, object: ObjectRow) => Promise<boolea
56
56
  * owner, not a thing that lives on one machine: it is the same object here as there, so it is never given a stub (a stub would collide
57
57
  * with the local owner object of the same handle, as with an installed plugin's app). */
58
58
  export declare function localOwnerActor(db: CanvasDb, ref: ObjectRef): Promise<ObjectRow | null>;
59
+ /** Lock every key in `keys` in one global order (family rank, then lock hash), so no two transactions that lock overlapping sets can wait on
60
+ * each other. Locks are held to the end of the transaction, and taking one again is free. */
61
+ export declare function lockInOrder(db: CanvasDb, keys: readonly string[]): Promise<void>;
62
+ /** Every advisory lock `ensureStub` takes for this object: the stub, the custom kind its reference declares, and each owner it may create.
63
+ * A caller that touches several stubs locks the union with `lockInOrder` first, so the order never depends on which stub comes first. */
64
+ export declare function stubLockKeys(db: CanvasDb, id: string, type: string, identity: StubIdentity | null, ref: ObjectRef | undefined): Promise<string[]>;
65
+ /** The advisory locks `actorFor` takes for an event's actor. */
66
+ export declare function actorLockKeys(db: CanvasDb, ref: ObjectRef): Promise<string[]>;
59
67
  /**
60
68
  * The stub of the object `id` that lives on `machine`, created on first sight. Idempotent: a second
61
69
  * call returns the same row and, given an `envelope`, replaces the identity it stores. Refuses
@@ -1 +1 @@
1
- var T=Object.defineProperty;var a=(n,e)=>T(n,"name",{value:e,configurable:!0});import{createObject as O,getById as c,ownerForGrantee as _}from"../graph/objects.js";import{readLegacyPrivacy as x}from"../graph/types.js";import{upsertKind as A}from"../graph/custom.js";import{CrtrError as w}from"../errors.js";import{ExitCode as d}from"../../types.js";import{normalizeMachineId as b}from"./names.js";import{personId as g,personIdAsync as H}from"../canvas/person.js";import{writeActsFor as N}from"../permissions/facts.js";const W=["public","private"],D=a(n=>{const e=n.act.act;return e===void 0?{sub:n.sub,app:null,node:null,node_scopes:null,place:null}:{sub:n.sub,app:e.sub,node:e.node,node_scopes:e.node_scopes,place:e.place}},"identityOfEnvelope");async function h(n,e){const i=typeof e=="string"?await c(n,e):e;if(i===null)return null;const t=await n.prepare("SELECT home_machine, envelope_json FROM peer_stubs WHERE object = ?").get(i.id);return t?{object:i,home_machine:t.home_machine,identity:t.envelope_json===null?null:JSON.parse(t.envelope_json)}:null}a(h,"stubOf");const G=a(async(n,e)=>await n.prepare("SELECT 1 FROM peer_stubs WHERE object = ?").get(e.id)!==void 0,"isStub");async function E(n){return await _(n,g())}a(E,"localPerson");async function f(n,e){return await _(n,e)}a(f,"localApp");async function K(n,e){return e.type==="app"?await f(n,`app:${e.name}`):e.type==="person"?await E(n):null}a(K,"localOwnerActor");async function v(n,e,i){if(e!==null)return e.app===null?await E(n):await f(n,e.app);const t=i?.owner;return t?.type==="app"?await f(n,t.external_id??`app:${t.handle}`):await E(n)}a(v,"ownerFor");async function P(n,e,i,t,m,r){const p=m===void 0?null:D(m),s=x(r?.privacy??"public");if(!W.includes(s))throw new w("invalid_event",`invalid privacy ${JSON.stringify(s)}`,d.GENERAL,{id:e});const o=await c(n,e);if(o!==null){const l=await h(n,o);if(l===null)throw new w("conflict",`${e} is a local ${o.type}, not a stub of ${t}`,d.GENERAL,{id:e});if(l.home_machine!==t)throw new w("conflict",`${e} is already a stub of ${l.home_machine}, not ${t}`,d.GENERAL,{id:e,home_machine:l.home_machine});return p!==null&&(await n.prepare("UPDATE peer_stubs SET envelope_json = ? WHERE object = ?").run(JSON.stringify(p),o.id),await n.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(o.id)),r!==void 0&&(await S(n,i,r),r.privacy!==void 0&&o.privacy!==s)?(await n.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(s,o.id),await c(n,o.id)):o}const y=await v(n,p,r);await S(n,i,r);const u=await O(n,{id:e,type:i,kind:r?.kind??null,owner:y.id,privacy:s,name:r?.name??e,preview:r?.preview??"",home_machine:t});return await n.prepare("INSERT INTO peer_stubs (object, home_machine, envelope_json, created_at) VALUES (?, ?, ?, ?)").run(u.id,t,p===null?null:JSON.stringify(p),new Date().toISOString()),y.type==="app"&&await N(n,u.id,await H()),p!==null&&await n.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(u.id),await c(n,u.id)}a(P,"ensureStub");async function S(n,e,i){const t=i?.kind_decl;t===void 0||t.name!==e||await A(n,{name:t.name,owner:await v(n,null,{...i,owner:t.owner}),description:t.description,events:t.events,read:null,source:"peer"})}a(S,"holdKind");async function V(n,e,i){const t=await P(n,e,"node",b(i));return await n.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(t.id),await c(n,t.id)}a(V,"ensureNodeStub");async function z(n,e,i){const t=await h(n,e);t===null||t.home_machine===i||(await n.prepare("UPDATE peer_stubs SET home_machine = ? WHERE object = ?").run(i,t.object.id),await n.prepare("UPDATE objects SET home_machine = ? WHERE id = ?").run(i,t.object.id))}a(z,"rehomeStub");async function B(n,e){const i=await j(n,e.owner===null?null:await c(n,e.owner)),t=await R(n,e.type);return{id:e.id,type:e.type,kind:e.kind,name:e.name,preview:e.preview,owner:i,privacy:e.privacy,...t===null?{}:{kind_decl:t}}}a(B,"describeObject");async function j(n,e){if(e===null||e.type!=="person"&&e.type!=="app"&&e.type!=="repo"&&e.type!=="profile")return null;const i=await n.prepare("SELECT scheme, external_id FROM identities WHERE object = ?").get(e.id);return{id:e.id,type:e.type,scheme:i?.scheme??null,external_id:i?.external_id??null,handle:e.name}}a(j,"ownerRefOf");async function R(n,e){const i=await n.prepare("SELECT owner, description, events_json FROM object_kinds WHERE name = ?").get(e),t=i===void 0?null:await j(n,await c(n,i.owner));return i===void 0||t===null?null:{name:e,description:i.description,events:JSON.parse(i.events_json),owner:t}}a(R,"kindDeclarationOf");async function Y(n,e,i){const t=await R(n,e.type);return t===null?i:{...i,kind_decl:t}}a(Y,"withKindDecl");export{B as describeObject,V as ensureNodeStub,P as ensureStub,D as identityOfEnvelope,G as isStub,R as kindDeclarationOf,K as localOwnerActor,z as rehomeStub,h as stubOf,Y as withKindDecl};
1
+ var T=Object.defineProperty;var r=(n,e)=>T(n,"name",{value:e,configurable:!0});import{createObject as A,getById as s,ownerForGrantee as f,ownerLockKey as L}from"../graph/objects.js";import{readLegacyPrivacy as N}from"../graph/types.js";import{upsertKind as g}from"../graph/custom.js";import{CrtrError as y}from"../errors.js";import{ExitCode as m}from"../../types.js";import{normalizeMachineId as W}from"./names.js";import{personId as d,personIdAsync as $}from"../canvas/person.js";import{writeActsFor as b}from"../permissions/facts.js";const I=["public","private"],H=r(n=>{const e=n.act.act;return e===void 0?{sub:n.sub,app:null,node:null,node_scopes:null,place:null}:{sub:n.sub,app:e.sub,node:e.node,node_scopes:e.node_scopes,place:e.place}},"identityOfEnvelope");async function S(n,e){const t=typeof e=="string"?await s(n,e):e;if(t===null)return null;const i=await n.prepare("SELECT home_machine, envelope_json FROM peer_stubs WHERE object = ?").get(t.id);return i?{object:t,home_machine:i.home_machine,identity:i.envelope_json===null?null:JSON.parse(i.envelope_json)}:null}r(S,"stubOf");const Q=r(async(n,e)=>await n.prepare("SELECT 1 FROM peer_stubs WHERE object = ?").get(e.id)!==void 0,"isStub");async function C(n){return await f(n,d())}r(C,"localPerson");async function D(n,e){return await f(n,e)}r(D,"localApp");async function X(n,e){return e.type==="app"?await D(n,`app:${e.name}`):e.type==="person"?await C(n):null}r(X,"localOwnerActor");function h(n,e){return n!==null?n.app===null?d():n.app:e?.type==="app"?e.external_id??`app:${e.handle}`:d()}r(h,"ownerGrantee");async function k(n,e,t){return await f(n,h(e,t?.owner))}r(k,"ownerFor");const P=["ingest:","stub:","owner:","kind:"],v=r(n=>{const e=P.findIndex(t=>n.startsWith(t));if(e<0)throw new Error(`lockInOrder: ${JSON.stringify(n)} is not a known lock key`);return e},"rankOf");async function U(n,e){const t=[...new Set(e)];if(t.length===0)return;const p=(await n.prepareUncached(t.map((a,o)=>`SELECT ${o} AS i, hashtext(?) AS h`).join(" UNION ALL ")).all(...t)).map(a=>({key:t[Number(a.i)],hash:Number(a.h)})).sort((a,o)=>v(a.key)-v(o.key)||a.hash-o.hash);for(const{key:a}of p)await n.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(a)}r(U,"lockInOrder");async function E(n,e){const[t,i]=e.startsWith("app:")?["app",e]:["user",e];return await n.prepare("SELECT 1 FROM identities WHERE scheme = ? AND external_id = ?").get(t,i)===void 0?[L(t,i)]:[]}r(E,"missingOwnerKey");async function O(n,e,t,i,p){const a=[`stub:${e}`],o=p?.kind_decl;return o!==void 0&&o.name===t&&a.push(`kind:${t}`,...await E(n,h(null,o.owner))),await s(n,e)===null&&a.push(...await E(n,h(i,p?.owner))),a}r(O,"stubLockKeys");async function Z(n,e){return e.type==="app"?await E(n,`app:${e.name}`):e.type==="person"?await E(n,d()):await O(n,e.id,e.type,null,e)}r(Z,"actorLockKeys");async function K(n,e,t,i,p,a){const o=p===void 0?null:H(p),u=N(a?.privacy??"public");if(!I.includes(u))throw new y("invalid_event",`invalid privacy ${JSON.stringify(u)}`,m.GENERAL,{id:e});await U(n,await O(n,e,t,o,a));const c=await s(n,e);if(c!==null){const w=await S(n,c);if(w===null)throw new y("conflict",`${e} is a local ${c.type}, not a stub of ${i}`,m.GENERAL,{id:e});if(w.home_machine!==i)throw new y("conflict",`${e} is already a stub of ${w.home_machine}, not ${i}`,m.GENERAL,{id:e,home_machine:w.home_machine});return o!==null&&(await n.prepare("UPDATE peer_stubs SET envelope_json = ? WHERE object = ?").run(JSON.stringify(o),c.id),await n.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(c.id)),a!==void 0&&(await x(n,t,a),a.privacy!==void 0&&c.privacy!==u)?(await n.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(u,c.id),await s(n,c.id)):c}const _=await k(n,o,a);await x(n,t,a);const l=await A(n,{id:e,type:t,kind:a?.kind??null,owner:_.id,privacy:u,name:a?.name??e,preview:a?.preview??"",home_machine:i});return await n.prepare("INSERT INTO peer_stubs (object, home_machine, envelope_json, created_at) VALUES (?, ?, ?, ?)").run(l.id,i,o===null?null:JSON.stringify(o),new Date().toISOString()),_.type==="app"&&await b(n,l.id,await $()),o!==null&&await n.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(l.id),await s(n,l.id)}r(K,"ensureStub");async function x(n,e,t){const i=t?.kind_decl;i===void 0||i.name!==e||await g(n,{name:i.name,owner:await k(n,null,{...t,owner:i.owner}),description:i.description,events:i.events,read:null,source:"peer"})}r(x,"holdKind");async function ee(n,e,t){const i=await K(n,e,"node",W(t));return await n.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(i.id),await s(n,i.id)}r(ee,"ensureNodeStub");async function ne(n,e,t){const i=await S(n,e);i===null||i.home_machine===t||(await n.prepare("UPDATE peer_stubs SET home_machine = ? WHERE object = ?").run(t,i.object.id),await n.prepare("UPDATE objects SET home_machine = ? WHERE id = ?").run(t,i.object.id))}r(ne,"rehomeStub");async function te(n,e){const t=await R(n,e.owner===null?null:await s(n,e.owner)),i=await j(n,e.type);return{id:e.id,type:e.type,kind:e.kind,name:e.name,preview:e.preview,owner:t,privacy:e.privacy,...i===null?{}:{kind_decl:i}}}r(te,"describeObject");async function R(n,e){if(e===null||e.type!=="person"&&e.type!=="app"&&e.type!=="repo"&&e.type!=="profile")return null;const t=await n.prepare("SELECT scheme, external_id FROM identities WHERE object = ?").get(e.id);return{id:e.id,type:e.type,scheme:t?.scheme??null,external_id:t?.external_id??null,handle:e.name}}r(R,"ownerRefOf");async function j(n,e){const t=await n.prepare("SELECT owner, description, events_json FROM object_kinds WHERE name = ?").get(e),i=t===void 0?null:await R(n,await s(n,t.owner));return t===void 0||i===null?null:{name:e,description:t.description,events:JSON.parse(t.events_json),owner:i}}r(j,"kindDeclarationOf");async function ie(n,e,t){const i=await j(n,e.type);return i===null?t:{...t,kind_decl:i}}r(ie,"withKindDecl");export{Z as actorLockKeys,te as describeObject,ee as ensureNodeStub,K as ensureStub,H as identityOfEnvelope,Q as isStub,j as kindDeclarationOf,X as localOwnerActor,U as lockInOrder,ne as rehomeStub,O as stubLockKeys,S as stubOf,ie as withKindDecl};
@@ -5,14 +5,19 @@ import { type SubjectSet } from './compile.js';
5
5
  import { type Frag } from './sql.js';
6
6
  export type Permission = 'read' | 'edit' | 'message' | 'use' | 'share';
7
7
  export declare const PERMISSIONS: readonly Permission[];
8
- /** A reader with its user and subject set resolved: build once, pass to many `permits`/`check` calls. */
8
+ /** A reader with its user and subject set resolved: build once, pass to many `permits`/`check` calls. `userId` is null for a reader
9
+ * that acts for no person (see `readerUser`): its subjects are `*` alone. */
9
10
  export interface EvalReader {
10
11
  reader: Reader;
11
- userId: string;
12
+ userId: string | null;
12
13
  subjects: SubjectSet;
13
14
  }
14
15
  /** The user a reader acts for: a person's own identity; an app call's `sub`; a node's `sub`; otherwise the install's person. */
15
16
  export declare function userOf(db: CanvasDb, reader: Reader): Promise<string>;
17
+ /** The user a reader reads as: `userOf`, except that on a cloud role an app reader with no `sub` (a runtime-owned call, such as
18
+ * package link derivation at boot) acts for no person and answers null. `userOf` itself keeps throwing there, so no write path
19
+ * records an object as kept for a person that is not there. */
20
+ export declare function readerUser(db: CanvasDb, reader: Reader): Promise<string | null>;
16
21
  /** The user ids a subject stands for (the user and `*`) and every group it reaches, directly or through nested groups. */
17
22
  export declare function subjectSet(db: CanvasDb, userId: string): Promise<SubjectSet>;
18
23
  export declare function resolveReader(db: CanvasDb, reader: Reader): Promise<EvalReader>;