@north-light/cli 0.3.579 → 0.3.581

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 (228) hide show
  1. package/dist/api/client.d.ts +6 -1
  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/execution-start.d.ts +14 -0
  6. package/dist/api/dto/health.d.ts +20 -0
  7. package/dist/api/dto/imports.d.ts +142 -0
  8. package/dist/api/dto/imports.js +0 -0
  9. package/dist/api/index.d.ts +1 -0
  10. package/dist/api/index.js +1 -1
  11. package/dist/api/routes.d.ts +2 -0
  12. package/dist/api/routes.js +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +2 -2
  14. package/dist/clients/attach/session/connection.d.ts +3 -1
  15. package/dist/clients/attach/session/connection.js +1 -1
  16. package/dist/clients/attach/viewer.js +510 -510
  17. package/dist/commands/api-client.d.ts +7 -4
  18. package/dist/commands/api-client.js +3 -3
  19. package/dist/commands/sys/daemon.js +1 -1
  20. package/dist/commands/sys/import/resume.d.ts +1 -0
  21. package/dist/commands/sys/import/resume.js +1 -0
  22. package/dist/commands/sys/import/start.d.ts +5 -0
  23. package/dist/commands/sys/import/start.js +4 -0
  24. package/dist/commands/sys/join.d.ts +2 -0
  25. package/dist/commands/sys/join.js +1 -1
  26. package/dist/commands/sys/machine/rotate.d.ts +1 -0
  27. package/dist/commands/sys/machine/rotate.js +1 -0
  28. package/dist/commands/sys/signin.d.ts +7 -0
  29. package/dist/commands/sys/signin.js +2 -0
  30. package/dist/commands/sys.js +1 -1
  31. package/dist/core/blobs/r2.d.ts +4 -0
  32. package/dist/core/blobs/r2.js +7 -4
  33. package/dist/core/canvas/daemon-owner.d.ts +1 -1
  34. package/dist/core/canvas/executions.d.ts +1 -1
  35. package/dist/core/canvas/export-sqlite.js +1 -1
  36. package/dist/core/canvas/node-tokens.d.ts +2 -0
  37. package/dist/core/canvas/node-tokens.js +3 -3
  38. package/dist/core/canvas/schema-pg/009-bodies.sql +10 -0
  39. package/dist/core/canvas/schema-version.d.ts +1 -1
  40. package/dist/core/canvas/schema-version.js +1 -1
  41. package/dist/core/directory/person-credential.d.ts +36 -0
  42. package/dist/core/directory/person-credential.js +1 -0
  43. package/dist/core/feed/feed.js +3 -3
  44. package/dist/core/feed/inbox.js +1 -1
  45. package/dist/core/feed/reports.js +2 -2
  46. package/dist/core/graph/access.d.ts +1 -1
  47. package/dist/core/graph/bodies-boot-copy.d.ts +14 -0
  48. package/dist/core/graph/bodies-boot-copy.js +1 -0
  49. package/dist/core/graph/bodies.d.ts +13 -6
  50. package/dist/core/graph/bodies.js +1 -1
  51. package/dist/core/graph/deletion.d.ts +4 -1
  52. package/dist/core/graph/deletion.js +3 -3
  53. package/dist/core/graph/documents.d.ts +4 -0
  54. package/dist/core/graph/documents.js +10 -10
  55. package/dist/core/graph/objects.js +11 -11
  56. package/dist/core/graph/package-docs.js +5 -5
  57. package/dist/core/graph/reader.js +2 -2
  58. package/dist/core/graph/repo-sync/exchange.js +2 -2
  59. package/dist/core/graph/repo-sync/ref-format.d.ts +0 -1
  60. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  61. package/dist/core/import/bundle/blob-keys.d.ts +10 -0
  62. package/dist/core/import/bundle/blob-keys.js +1 -0
  63. package/dist/core/import/bundle/layout.d.ts +89 -0
  64. package/dist/core/import/bundle/layout.js +5 -0
  65. package/dist/core/import/export/archive.d.ts +11 -0
  66. package/dist/core/import/export/archive.js +1 -0
  67. package/dist/core/import/export/cloud.d.ts +21 -0
  68. package/dist/core/import/export/cloud.js +1 -0
  69. package/dist/core/import/export/dump.d.ts +34 -0
  70. package/dist/core/import/export/dump.js +3 -0
  71. package/dist/core/import/export/files.d.ts +22 -0
  72. package/dist/core/import/export/files.js +1 -0
  73. package/dist/core/import/export/inline.d.ts +3 -0
  74. package/dist/core/import/export/inline.js +1 -0
  75. package/dist/core/import/export/source.d.ts +2 -0
  76. package/dist/core/import/export/source.js +1 -0
  77. package/dist/core/import/export/stores.d.ts +9 -0
  78. package/dist/core/import/export/stores.js +1 -0
  79. package/dist/core/import/load/bind.d.ts +19 -0
  80. package/dist/core/import/load/bind.js +15 -0
  81. package/dist/core/import/load/blobs.d.ts +10 -0
  82. package/dist/core/import/load/blobs.js +1 -0
  83. package/dist/core/import/load/catalog.d.ts +32 -0
  84. package/dist/core/import/load/catalog.js +9 -0
  85. package/dist/core/import/load/copy.d.ts +26 -0
  86. package/dist/core/import/load/copy.js +6 -0
  87. package/dist/core/import/load/path-map.d.ts +7 -0
  88. package/dist/core/import/load/path-map.js +1 -0
  89. package/dist/core/import/load/prepare.d.ts +37 -0
  90. package/dist/core/import/load/prepare.js +10 -0
  91. package/dist/core/import/load/rewrite.d.ts +39 -0
  92. package/dist/core/import/load/rewrite.js +9 -0
  93. package/dist/core/import/load/rollback.d.ts +3 -0
  94. package/dist/core/import/load/rollback.js +6 -0
  95. package/dist/core/import/load/run.d.ts +22 -0
  96. package/dist/core/import/load/run.js +3 -0
  97. package/dist/core/import/load/stage.d.ts +15 -0
  98. package/dist/core/import/load/stage.js +1 -0
  99. package/dist/core/import/load/stores.d.ts +24 -0
  100. package/dist/core/import/load/stores.js +2 -0
  101. package/dist/core/import/load/verify.d.ts +4 -0
  102. package/dist/core/import/load/verify.js +11 -0
  103. package/dist/core/peer/receiver.js +1 -1
  104. package/dist/core/runtime/broker.js +1 -1
  105. package/dist/core/runtime/revive.d.ts +5 -2
  106. package/dist/core/runtime/revive.js +2 -2
  107. package/dist/core/runtime/roadmap.js +3 -3
  108. package/dist/core/runtime/runner/blaxel/runner.js +3 -3
  109. package/dist/core/runtime/runner/index.d.ts +1 -0
  110. package/dist/core/runtime/runner/index.js +1 -1
  111. package/dist/core/runtime/runner/machine/call.d.ts +8 -0
  112. package/dist/core/runtime/runner/machine/call.js +3 -0
  113. package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
  114. package/dist/core/runtime/runner/machine/environment.js +1 -0
  115. package/dist/core/runtime/runner/machine/executor.d.ts +23 -0
  116. package/dist/core/runtime/runner/machine/executor.js +1 -0
  117. package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
  118. package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
  119. package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
  120. package/dist/core/runtime/runner/machine/ops.js +1 -0
  121. package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
  122. package/dist/core/runtime/runner/machine/runner.js +1 -0
  123. package/dist/core/runtime/runner/types.d.ts +7 -1
  124. package/dist/core/runtime/warm-pool.d.ts +2 -0
  125. package/dist/core/runtime/warm-pool.js +1 -1
  126. package/dist/core/secrets/env-rows.d.ts +1 -0
  127. package/dist/core/secrets/env-rows.js +1 -1
  128. package/dist/core/storage/purge.d.ts +6 -0
  129. package/dist/core/storage/purge.js +4 -0
  130. package/dist/core/subscription-state.d.ts +1 -0
  131. package/dist/core/subscription-state.js +1 -1
  132. package/dist/core/substrate/delivery/corpus.d.ts +2 -0
  133. package/dist/core/substrate/delivery/corpus.js +3 -3
  134. package/dist/core/substrate/delivery/deliver.js +3 -3
  135. package/dist/core/substrate/delivery/render-boot.js +25 -25
  136. package/dist/core/substrate/delivery/render-event.d.ts +1 -1
  137. package/dist/core/substrate/delivery/render-event.js +4 -4
  138. package/dist/core/substrate/delivery/types.d.ts +2 -2
  139. package/dist/daemon/api/bridge.js +3 -3
  140. package/dist/daemon/api/document-listener.js +4 -4
  141. package/dist/daemon/api/handlers/attach.js +1 -1
  142. package/dist/daemon/api/handlers/crons.js +1 -1
  143. package/dist/daemon/api/handlers/daemon.js +1 -1
  144. package/dist/daemon/api/handlers/docs.js +1 -1
  145. package/dist/daemon/api/handlers/execution-start.js +2 -2
  146. package/dist/daemon/api/handlers/files.js +1 -1
  147. package/dist/daemon/api/handlers/imports.d.ts +2 -0
  148. package/dist/daemon/api/handlers/imports.js +1 -0
  149. package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
  150. package/dist/daemon/api/handlers/machine-credential.js +1 -0
  151. package/dist/daemon/api/handlers/memory.js +4 -4
  152. package/dist/daemon/api/handlers/profiles.js +1 -1
  153. package/dist/daemon/api/handlers/providers.js +1 -1
  154. package/dist/daemon/api/handlers/reports.js +4 -4
  155. package/dist/daemon/api/handlers/runs.js +1 -1
  156. package/dist/daemon/api/import-auth.d.ts +6 -0
  157. package/dist/daemon/api/import-auth.js +1 -0
  158. package/dist/daemon/api/operations.d.ts +2 -2
  159. package/dist/daemon/api/operations.js +1 -1
  160. package/dist/daemon/api/router.d.ts +7 -1
  161. package/dist/daemon/api/router.js +1 -1
  162. package/dist/daemon/api/server.js +1 -1
  163. package/dist/daemon/boot-migrations.js +1 -1
  164. package/dist/daemon/broker-signals/bus.d.ts +3 -2
  165. package/dist/daemon/broker-signals/bus.js +1 -1
  166. package/dist/daemon/cron/armed-context.d.ts +1 -0
  167. package/dist/daemon/cron/armed-context.js +1 -1
  168. package/dist/daemon/cron/closed.d.ts +31 -0
  169. package/dist/daemon/cron/closed.js +1 -0
  170. package/dist/daemon/cron/ended.js +2 -2
  171. package/dist/daemon/cron/lease-recovery.js +1 -1
  172. package/dist/daemon/cron/machine-exec.d.ts +5 -0
  173. package/dist/daemon/cron/machine-exec.js +1 -0
  174. package/dist/daemon/cron/machine-fire.d.ts +26 -0
  175. package/dist/daemon/cron/machine-fire.js +1 -0
  176. package/dist/daemon/cron/recurrence.d.ts +14 -0
  177. package/dist/daemon/cron/recurrence.js +1 -0
  178. package/dist/daemon/cron/run-token.d.ts +7 -0
  179. package/dist/daemon/cron/run-token.js +1 -0
  180. package/dist/daemon/cron-run.js +2 -2
  181. package/dist/daemon/crtrd-cli.js +1 -1
  182. package/dist/daemon/crtrd.d.ts +2 -2
  183. package/dist/daemon/crtrd.js +10 -10
  184. package/dist/daemon/drain.d.ts +25 -0
  185. package/dist/daemon/drain.js +9 -0
  186. package/dist/daemon/due-sources.d.ts +1 -1
  187. package/dist/daemon/due-sources.js +1 -1
  188. package/dist/daemon/due.js +8 -8
  189. package/dist/daemon/fleet.js +5 -5
  190. package/dist/daemon/import-dump-run.d.ts +1 -0
  191. package/dist/daemon/import-dump-run.js +5 -0
  192. package/dist/daemon/machine-daemon.d.ts +7 -0
  193. package/dist/daemon/machine-daemon.js +1 -0
  194. package/dist/daemon/machine-link/client.d.ts +29 -0
  195. package/dist/daemon/machine-link/client.js +1 -0
  196. package/dist/daemon/machine-link/config.d.ts +14 -0
  197. package/dist/daemon/machine-link/config.js +3 -0
  198. package/dist/daemon/machine-link/credential.d.ts +7 -0
  199. package/dist/daemon/machine-link/credential.js +2 -0
  200. package/dist/daemon/machine-link/forward.d.ts +16 -0
  201. package/dist/daemon/machine-link/forward.js +1 -0
  202. package/dist/daemon/machine-link/frames.d.ts +49 -0
  203. package/dist/daemon/machine-link/frames.js +1 -0
  204. package/dist/daemon/machine-link/holder.d.ts +73 -0
  205. package/dist/daemon/machine-link/holder.js +9 -0
  206. package/dist/daemon/manage.d.ts +19 -3
  207. package/dist/daemon/manage.js +3 -3
  208. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  209. package/dist/daemon/reconcilers/delivery-lane.js +2 -2
  210. package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
  211. package/dist/daemon/reconcilers/import-lane.js +1 -0
  212. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  213. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
  214. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
  215. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  216. package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
  217. package/dist/daemon/reconcilers/purge-due-work.js +1 -0
  218. package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
  219. package/dist/daemon/reconcilers/row-claim.js +2 -2
  220. package/dist/daemon/review/finish.js +1 -1
  221. package/dist/daemon/runs/observer.js +3 -3
  222. package/dist/migrations/004-canvas-documents/apply.d.ts +2 -4
  223. package/dist/migrations/004-canvas-documents/apply.js +1 -1
  224. package/dist/migrations/004-canvas-documents/index.js +2 -2
  225. package/package.json +2 -2
  226. package/packages/crouter-identity/package.json +1 -1
  227. package/runtime.lock.json +13 -13
  228. package/scripts/sandbox-start.mjs +185 -2
@@ -0,0 +1,4 @@
1
+ var y=Object.defineProperty;var m=(r,n)=>y(r,"name",{value:n,configurable:!0});import{after as O,before as g,test as u}from"node:test";import t from"node:assert/strict";import{spawn as I}from"node:child_process";import{mkdirSync as f,mkdtempSync as h,readFileSync as A,rmSync as v,writeFileSync as T}from"node:fs";import{tmpdir as w}from"node:os";import{join as s}from"node:path";import{EventEmitter as P}from"node:events";import{Readable as q}from"node:stream";import{createNode as H}from"../../../canvas/canvas.js";import{closeDb as N,openDb as E}from"../../../canvas/db.js";import{isPidAlive as l}from"../../../canvas/pid.js";import{bindPerson as x,installTerminalGrant as C,useEmptyPersonSettings as k}from"../../../__tests__/helpers/person.js";import{setAppEnvVar as R}from"../../../profiles/app-env-store.js";import{setProfileEnvVar as _}from"../../../profiles/env-store.js";import{createProfile as D}from"../../../profiles/manifest.js";import{putEnvRow as L}from"../../../secrets/env-rows.js";import{appSpace as F}from"../../../layout.js";import{executionStartRoutes as U}from"../../../../daemon/api/handlers/execution-start.js";import{Router as V}from"../../../../daemon/api/router.js";import{createMachineExecutor as M,machineBrokerEnv as S}from"./executor.js";let p;const G={HOME:process.env.HOME,CRTR_HOME:process.env.CRTR_HOME};g(()=>{process.env.HOME=p=h(s(w(),"crtr-machine-exec-")),process.env.CRTR_HOME=s(p,".crouter"),f(process.env.CRTR_HOME,{recursive:!0})}),O(async()=>{await N(),v(p,{recursive:!0,force:!0});for(const[r,n]of Object.entries(G))n===void 0?delete process.env[r]:process.env[r]=n});async function j(r,n){const i=new V().registerAll(U),a=Object.assign(q.from([]),{method:"GET",url:`/v1/executions/${n}/start`,headers:{}});let o="";const e=Object.assign(new P,{headersSent:!1,writableEnded:!1,writableFinished:!1});return e.setHeader=()=>e,e.writeHead=()=>(e.headersSent=!0,e),e.end=c=>(c&&(o+=c),e.writableEnded=!0,e.writableFinished=!0,e),await i.handle(a,e,null,r),JSON.parse(o)}m(j,"bundleFor"),u("a machine broker gets the profile, app and deployment rows, the app pi dir and the launch env, with the process runner's precedence",async()=>{await k(),await E();const r=await C(),n=(await D("machine-env")).profileId;await R("app:terminal","SHARED","from-app",r),await R("app:terminal","APP_SECRET","app-secret",r),await _(n,"SHARED","from-profile",r),await _(n,"PROFILE_VAR","profile-value",r),await L({person:null,kind:"deployment",id:"_deployment"},"DEPLOY_VAR","deploy-value");const i=s(p,"work");f(i,{recursive:!0}),await H({node_id:"m1",name:"m1",created:new Date().toISOString(),cwd:i,kind:"general",mode:"base",lifecycle:"resident",status:"active",parent:null,profile_id:n,grantee:"app:terminal"}),await(await E()).prepare("INSERT INTO executions (execution_id, node_id, state, started_at, launch_json, runner) VALUES ('em1', 'm1', 'launching', 't', ?, 'machine')").run(JSON.stringify({argv:["x"],env:{LAUNCH_VAR:"launch",CRTR_NODE_ID:"m1"}}));const a={class:"broker",app:"app:terminal",node:"m1",run:null,uid:process.getuid?.()??0,sub:await x(),grant:null,client_id:null,scopes:null},o=await j(a,"em1");t.equal(o.machine_env?.APP_SECRET,"app-secret");const e=S(o,{apiUrl:"http://cloud.invalid",environmentId:"env1"},"em1");t.equal(e.APP_SECRET,"app-secret"),t.equal(e.PROFILE_VAR,"profile-value"),t.equal(e.DEPLOY_VAR,"deploy-value"),t.equal(e.SHARED,"from-profile","the profile's row wins over the app's"),t.equal(e.LAUNCH_VAR,"launch"),t.equal(e.PI_CODING_AGENT_DIR,s(F("app:terminal").home,".pi","agent")),t.equal(e.CRTR_API_URL,"http://cloud.invalid"),t.equal(e.CRTR_EXECUTION_ID,"em1"),t.doesNotThrow(()=>S({...o,node:{...o.node,profile:"profile-the-mac-does-not-hold"}},{apiUrl:"http://cloud.invalid",environmentId:"env1"},"em1")),t.equal(e.GITHUB_TOKEN,void 0)}),u("stop ends the broker and the detached command it left behind, and confirms both gone",async()=>{const r=h(s(w(),"crtr-machine-stop-")),n=s(r,"child.pid"),i=s(r,"broker-cli.js");T(i,`const { spawn } = require('node:child_process');
2
+ const c = spawn('sleep', ['300'], { detached: true, stdio: 'ignore' }); c.unref();
3
+ require('node:fs').writeFileSync(${JSON.stringify(n)}, String(c.pid));
4
+ setInterval(() => {}, 1000);`);const a=I(process.execPath,[i,"--canvas-home",r,"--epoch","exec-stop-1","node-stop-1"],{detached:!0,stdio:"ignore"});a.unref();try{let o=0;for(let d=0;d<100&&o===0;d++)try{o=Number(A(n,"utf8"))}catch{await new Promise(b=>setTimeout(b,50))}t.ok(o>0&&l(o),"the detached child is running");const c=await M({apiUrl:"http://cloud.invalid",environmentId:"env1",stopGraceS:3}).stop({execution:{executionId:"exec-stop-1",nodeId:"node-stop-1"},options:{reason:"test"}});t.deepEqual(c,{confirmed:!0}),t.equal(l(a.pid),!1,"the broker is gone"),t.equal(l(o),!1,"the detached child is gone")}finally{try{process.kill(-a.pid,"SIGKILL")}catch{}v(r,{recursive:!0,force:!0})}});
@@ -0,0 +1,127 @@
1
+ import type { RunnerExecution, RunnerStartInput, RunnerStatus, RunnerStopOptions, RunnerStopResult, RunnerViewerAddress, RunnerWakeResult } from '../types.js';
2
+ export declare const MACHINE_OPS: readonly ["start", "wake", "stop", "status", "delete", "viewer", "cron.run", "cron.cancel"];
3
+ export type MachineOp = (typeof MACHINE_OPS)[number];
4
+ /** A `start` call carries the whole start input and the node token the control plane minted: the Mac has no database, so it cannot mint one.
5
+ * The token sits in `machine_calls.args_json` until the row is deleted a day after it finishes; the execution's token row holds its hash. */
6
+ export type MachineStartArgs = RunnerStartInput & {
7
+ nodeToken: string;
8
+ };
9
+ export interface MachineStartResult {
10
+ pid: number | null;
11
+ }
12
+ export interface MachineWakeArgs {
13
+ execution: RunnerExecution;
14
+ }
15
+ export interface MachineStopArgs {
16
+ execution: RunnerExecution;
17
+ options: RunnerStopOptions;
18
+ }
19
+ export interface MachineStatusArgs {
20
+ execution: RunnerExecution;
21
+ }
22
+ export interface MachineDeleteArgs {
23
+ nodeId: string;
24
+ }
25
+ export interface MachineViewerArgs {
26
+ execution: RunnerExecution;
27
+ }
28
+ /** A cron fire on a machine. `runId` is the cron run id: it fences a second fire after a reconnect. */
29
+ export interface MachineCronRunArgs {
30
+ runId: string;
31
+ cronId: string;
32
+ command: string;
33
+ cwd: string;
34
+ env: Record<string, string>;
35
+ /** The cron's app: the Mac decides whether the armed profile applies (only the terminal's crons run under their profile). */
36
+ grantee: string;
37
+ /** The cron's profile id; the Mac rebuilds its declared PATH entries from its own copy of the profile. */
38
+ profileId: string | null;
39
+ /** The anchor node's id and app when it is still on the canvas: the Mac sets `CRTR_CONTEXT_DIR` to that node's context dir. */
40
+ contextAnchor: {
41
+ node: string;
42
+ grantee: string;
43
+ } | null;
44
+ timeoutS: number;
45
+ /** The cron's lease expiry (ISO); the machine does not start a run whose lease has passed. */
46
+ leaseExpiresAt: string;
47
+ }
48
+ export interface MachineCronRunResult {
49
+ exitCode: number | null;
50
+ stdoutHead: string | null;
51
+ stderrHead: string | null;
52
+ durationMs: number;
53
+ /** SHA-256 of the whole stdout (not of the head): what `on-change` compares. */
54
+ stdoutSha256: string;
55
+ /** True when the machine did not start the command (its lease had passed): the sender may send the same run id again. */
56
+ notStarted?: true;
57
+ }
58
+ export interface MachineCronCancelArgs {
59
+ runId: string;
60
+ }
61
+ export interface MachineCronCancelResult {
62
+ canceled: boolean;
63
+ }
64
+ export interface MachineOpTable {
65
+ start: {
66
+ args: MachineStartArgs;
67
+ result: MachineStartResult;
68
+ };
69
+ wake: {
70
+ args: MachineWakeArgs;
71
+ result: RunnerWakeResult;
72
+ };
73
+ stop: {
74
+ args: MachineStopArgs;
75
+ result: Pick<RunnerStopResult, 'confirmed'> & {
76
+ error?: string;
77
+ };
78
+ };
79
+ status: {
80
+ args: MachineStatusArgs;
81
+ result: RunnerStatus;
82
+ };
83
+ delete: {
84
+ args: MachineDeleteArgs;
85
+ result: null;
86
+ };
87
+ viewer: {
88
+ args: MachineViewerArgs;
89
+ result: RunnerViewerAddress;
90
+ };
91
+ 'cron.run': {
92
+ args: MachineCronRunArgs;
93
+ result: MachineCronRunResult;
94
+ };
95
+ 'cron.cancel': {
96
+ args: MachineCronCancelArgs;
97
+ result: MachineCronCancelResult;
98
+ };
99
+ }
100
+ /** No live link row for the environment: the caller leaves its work pending and retries on its own backoff. */
101
+ export declare class MachineOfflineError extends Error {
102
+ readonly environmentId: string;
103
+ readonly sent: boolean;
104
+ readonly code = "machine_offline";
105
+ /** True when the call was written to the link before it dropped: the machine may have run it. False: it ran nowhere. */
106
+ constructor(environmentId: string, detail?: string, sent?: boolean);
107
+ }
108
+ /** A forwarded `start` whose outcome is unknown (link dropped or call lost): the execution stays `launching`, the caller never aborts it
109
+ * and never starts another. After the link returns `status` of that execution decides: running is adopted, gone is ended. */
110
+ export declare class MachineLaunchUncertainError extends MachineOfflineError {
111
+ readonly uncertain = true;
112
+ constructor(environmentId: string, detail: string);
113
+ }
114
+ /** The holder claimed the call and never answered before its lease expired: the call may or may not have run. The caller runs `status` before a new `start`. */
115
+ export declare class MachineLostError extends Error {
116
+ readonly environmentId: string;
117
+ readonly callId: string;
118
+ readonly code = "machine_lost";
119
+ constructor(environmentId: string, callId: string);
120
+ }
121
+ /** The machine executed the call and it failed (`result {ok: false, error}`). */
122
+ export declare class MachineCallFailedError extends Error {
123
+ readonly environmentId: string;
124
+ readonly op: MachineOp;
125
+ readonly code = "machine_call_failed";
126
+ constructor(environmentId: string, op: MachineOp, message: string);
127
+ }
@@ -0,0 +1 @@
1
+ var o=Object.defineProperty;var t=(n,e)=>o(n,"name",{value:e,configurable:!0});const i=["start","wake","stop","status","delete","viewer","cron.run","cron.cancel"];class c extends Error{static{t(this,"MachineOfflineError")}environmentId;sent;code="machine_offline";constructor(e,r,s=!1){super(`machine ${e} is offline${r?`: ${r}`:""}`),this.environmentId=e,this.sent=s}}class l extends c{static{t(this,"MachineLaunchUncertainError")}uncertain=!0;constructor(e,r){super(e,r,!0)}}class h extends Error{static{t(this,"MachineLostError")}environmentId;callId;code="machine_lost";constructor(e,r){super(`call ${r} to machine ${e} was lost`),this.environmentId=e,this.callId=r}}class d extends Error{static{t(this,"MachineCallFailedError")}environmentId;op;code="machine_call_failed";constructor(e,r,s){super(`${r} on machine ${e} failed: ${s}`),this.environmentId=e,this.op=r}}export{i as MACHINE_OPS,d as MachineCallFailedError,l as MachineLaunchUncertainError,h as MachineLostError,c as MachineOfflineError};
@@ -0,0 +1,12 @@
1
+ import type { Runner, RunnerStartInput } from '../types.js';
2
+ export interface MachineRunnerDeps {
3
+ /** The machine environment a node runs in. Default: the node's profile's `environment_id`, which must be kind `machine`. */
4
+ environmentOf?(nodeId: string): Promise<string>;
5
+ /** Mints the execution's node token (the execution must be `launching` and have none). Default: `mintNodeToken`. */
6
+ mintToken?(input: RunnerStartInput): Promise<string>;
7
+ /** Whether this execution already has a node token (a previous `start` minted it). Default: a `node_tokens` row for the execution. */
8
+ tokenMinted?(executionId: string): Promise<boolean>;
9
+ }
10
+ /** The machine environment of a node's profile, or null when the profile points at no machine environment. */
11
+ export declare function machineEnvironmentOfNode(nodeId: string): Promise<string | null>;
12
+ export declare function createMachineRunner(deps?: MachineRunnerDeps): Runner;
@@ -0,0 +1 @@
1
+ var E=Object.defineProperty;var i=(e,t)=>E(e,"name",{value:t,configurable:!0});import{openDb as w}from"../../../canvas/db.js";import{mintExecutionToken as k}from"../../../canvas/node-tokens.js";import{callMachine as c}from"./call.js";import{machineEnvironmentOfProfile as p}from"./environment.js";import{MachineLaunchUncertainError as y,MachineLostError as m,MachineOfflineError as u}from"./ops.js";async function v(e){const t=await(await w()).prepare("SELECT profile_id FROM nodes WHERE node_id = ?").get(e);return t?.profile_id==null?null:await p(t.profile_id)}i(v,"machineEnvironmentOfNode");const I=i(async e=>{const t=await v(e);if(t===null)throw new Error(`node ${e} has no machine environment`);return t},"defaultEnvironmentOf"),M=i(async e=>await(await w()).prepare("SELECT 1 AS x FROM node_tokens WHERE broker_process_id = ?").get(e)!==void 0,"defaultTokenMinted"),h=i(e=>e instanceof Error?e:new Error(String(e)),"asError");function b(e={}){const t=e.environmentOf??I;return{kind:"machine",async start(n){const a=await t(n.nodeId),o=e.tokenMinted??M,s=i(r=>new y(a,`the launch of execution ${n.executionId} is unconfirmed: ${r}`),"uncertain"),f=await o(n.executionId);let d;try{d=await c(a,"status",{execution:n})}catch(r){throw f&&(r instanceof u||r instanceof m)?s("the machine could not be asked"):r}if(d.state==="running")return{pid:d.pid,exited:null};if(f)throw s("a start was handed over and no broker is running yet");const l=await(e.mintToken??(r=>k(r.executionId,r.nodeId)))(n);try{return{pid:(await c(a,"start",{...n,nodeToken:l})).pid,exited:null}}catch(r){throw r instanceof m||r instanceof u&&r.sent?s(r.message):r}},async wake(n){return await c(await t(n.nodeId),"wake",{execution:n})},async stop(n,a){try{const o=await c(await t(n.nodeId),"stop",{execution:n,options:a});return o.error===void 0?{confirmed:o.confirmed}:{confirmed:o.confirmed,error:new Error(o.error)}}catch(o){return{confirmed:!1,error:h(o)}}},async status(n){try{return await c(await t(n.nodeId),"status",{execution:n})}catch(a){return{state:"unknown",error:h(a).message}}},async delete(n){await c(await t(n),"delete",{nodeId:n})},async viewerAddress(n){return{kind:"machine",environmentId:await t(n.nodeId),nodeId:n.nodeId}}}}i(b,"createMachineRunner");export{b as createMachineRunner,v as machineEnvironmentOfNode};
@@ -1,4 +1,4 @@
1
- export type RunnerKind = 'process' | 'blaxel';
1
+ export type RunnerKind = 'process' | 'blaxel' | 'machine';
2
2
  /** The identity of one execution as the runner is asked about it. Rows already hold these. */
3
3
  export interface RunnerExecution {
4
4
  executionId: string;
@@ -70,6 +70,12 @@ export type RunnerViewerAddress = {
70
70
  kind: 'wss';
71
71
  url: string;
72
72
  headers: Record<string, string>;
73
+ }
74
+ /** The broker's `view.sock` is on a machine with no public address: the viewer opens a channel over that machine's link (design-import I13, `view.open`). */
75
+ | {
76
+ kind: 'machine';
77
+ environmentId: string;
78
+ nodeId: string;
73
79
  };
74
80
  export interface Runner {
75
81
  readonly kind: RunnerKind;
@@ -60,6 +60,8 @@ export declare function resumeWarmClaim(request: WarmRequest, nodeId: string, be
60
60
  * new owner. Only after winning the marker is the broker killed and the row +
61
61
  * dir deleted. Never throws: a failed reap is retried on the next sweep. */
62
62
  export declare function reapStaleSpares(): Promise<string[]>;
63
+ /** Remove every spare, live or not, with its broker. A drained daemon mints none, so the pool stays empty until the drain ends. */
64
+ export declare function emptyWarmPool(): Promise<void>;
63
65
  /** Claims the lease and renews it until the returned release runs; undefined when another holder has it. */
64
66
  export declare function holdMintLease(lane: string, key: string): Promise<(() => Promise<void>) | undefined>;
65
67
  /** Bring the pool for `request` back to depth 1, in the background. Never
@@ -1 +1 @@
1
- var k=Object.defineProperty;var n=(e,r)=>k(e,"name",{value:r,configurable:!0});import{createHash as x,randomUUID as N}from"node:crypto";import{openDb as R}from"../canvas/db.js";import{claimReconcilerLease as $,releaseReconcilerLease as L,renewReconcilerLease as b}from"../canvas/reconciler-leases.js";import{claimWarmSpare as C,deleteNode as E,getNode as p,listNodes as W,listWarmSpares as M,registerWarmSpare as I,unregisterWarmSpare as P,updateNode as A,warmSpareCount as w}from"../canvas/index.js";import{recordedPidLiveness as O}from"../canvas/pid.js";import{nowIso as u}from"../fs-utils.js";import{admitNodeCreate as D}from"../command-hooks/lifecycle-create.js";import{spawnChildPrepared as T}from"./spawn.js";import{blaxelEnvironmentOf as H}from"./blaxel-environment.js";import{newNodeId as F}from"./nodes.js";import{checkStart as K}from"./start-check.js";import{buildLaunchSpecAsync as U}from"./launch.js";import{setModelLive as j}from"./model-swap.js";import{setSituationalContextLive as B}from"./situational-live.js";import{deliverBearingsLive as G}from"./deliver-live.js";import{closeNode as y}from"./close.js";function h(){return process.env.CRTR_WARM_POOL!=="0"}n(h,"warmPoolEnabled");function J(e,r,a){const t=Object.entries(a.env??{}).sort(([s],[i])=>s<i?-1:s>i?1:0),o=JSON.stringify([e,r,a.tools??[],a.extensions??[],a.systemPrompt??null,t]);return x("sha256").update(o).digest("hex").slice(0,32)}n(J,"warmRecipeKey");async function d(e){const{launch:r}=await U(e.kind,e.mode,{lifecycle:"resident",hasManager:!1,...e.model!==null?{model:e.model}:{},cwd:e.cwd,profileId:e.profileId});return{key:J(e.cwd,e.profileId,r),launch:r,request:e}}n(d,"resolveRecipe");async function g(e){const r=e.creator===null?null:await p(e.creator);await K({grantee:r?.grantee??"app:terminal",sub:r?.sub,grant_id:r?.grant_id})}n(g,"checkClaimantStart");async function we(e,r={},a){await g(e);const t=await d(e);if(await w(t.key)===0)return null;const o=u(),s=await D({kind:e.kind,mode:e.mode,cwd:e.cwd,profile:e.profileId,root:!0,parentId:null,requestingNodeId:e.creator,lifecycle:"resident",replaces:null},e.creator===null?"app:terminal":await(async()=>{const c=await p(e.creator);if(!c?.grantee)throw new Error("warm node creator has no app");return c.grantee})());let i;try{i=await C(t.key,o,r)}finally{s()}return i===null?null:v(i,t,o,a)}n(we,"claimWarmNode");async function ue(e,r,a){return await g(e),v(r,await d(e),u(),a)}n(ue,"resumeWarmClaim");async function v(e,r,a,t){const{request:o}=r,i=(await p(e))?.launch?.model;await A(e,{created:a,kind:o.kind,mode:o.mode,name:o.name??o.kind,creator:o.creator,launch:r.launch,persona_ack:{kind:o.kind,mode:o.mode,lifecycle:"resident"},...o.model!==null?{model_override:o.model}:{}});try{t?.(e)}catch(l){return await m(e,"birth preparation",l)}try{await G(e)}catch(l){return await m(e,"bearings delivery",l)}const c=r.launch.model;if(c!==void 0&&c!==i)try{await j(e,c,r.launch.modelIntent)}catch(l){return await m(e,"model swap",l)}if(o.situationalContext!==null)try{await B(e,o.situationalContext)}catch(l){return await m(e,"situational-context delivery",l)}return e}n(v,"completeWarmClaim");async function m(e,r,a){console.error(`[warm-pool] ${r} failed on claim; closing the spare: ${a.message}`);try{await y(e)}catch{}return null}n(m,"abandonSpare");const z=3600*1e3;async function ye(){const e=[];let r;try{r=await M()}catch(t){return console.error(`[warm-pool] spare scan failed: ${t.message}`),e}const a=Date.now()-z;for(const t of r){if(await Q(t.node_id))continue;const o=Date.parse(t.created),s=Number.isNaN(o)||o<a,i=O(t.pi_pid,t.pi_pid_identity),c=(t.status==="active"||t.status==="idle")&&i==="alive";if(!(c&&!s)&&!(i==="indeterminate"&&!s))try{if(!await P(t.node_id))continue;if(c)try{await y(t.node_id)}catch{}await E(t.node_id),e.push(t.node_id)}catch(l){console.error(`[warm-pool] reap failed for ${t.node_id}: ${l.message}`)}}return e}n(ye,"reapStaleSpares");const f=6e4;async function _(e,r){const a=N();if(await $(e,r,a,f)===void 0)return;const t=setInterval(()=>{b(e,r,a,f).catch(()=>{})},f/3);return t.unref(),async()=>{clearInterval(t),await L(e,r,a)}}n(_,"holdMintLease");async function Q(e){return await(await R()).prepare("SELECT 1 FROM reconciler_leases WHERE lane = ? AND key = ? AND expires_at > ?").get("warm-pool-mint",e,new Date().toISOString())!==void 0}n(Q,"mintLeaseHeld");function he(e){if(h())try{d({...e,model:null,situationalContext:null}).then(S).catch(r=>console.error(`[warm-pool] recipe resolution failed: ${r.message}`))}catch(r){console.error(`[warm-pool] recipe resolution failed: ${r.message}`)}}n(he,"refillWarmPool");async function S(e){const{key:r}=e;if(await H(e.request.profileId)!==null)return;const a=await _("warm-pool",r);if(a!==void 0){if(await w(r)>0){await a();return}V(e,await F()).finally(()=>a().catch(()=>{}))}}n(S,"mint");async function V({key:e,request:r},a){const t=await _("warm-pool-mint",a);if(t===void 0){console.error(`[warm-pool] mint refused for ${r.kind}: the mint lease of ${a} is held elsewhere`);return}try{await T({nodeId:a,kind:r.kind,mode:r.mode,cwd:r.cwd,prompt:"",root:!0,readiness:"await",profile:r.profileId},async o=>{await I(o,e)})}catch(o){console.error(`[warm-pool] mint failed for ${r.kind}: ${o.message}`)}finally{await t().catch(()=>{})}}n(V,"birthSpare");const X=2;async function ge(){if(!h())return;let e;try{const r=new Set;e=[];for(const a of(await W({status:["active","idle"]})).reverse()){if(a.parent!==null||a.kind==="human")continue;const t=a.profile_id??null;if(!r.has(t??"")&&(r.add(t??""),e.push({cwd:a.cwd,profileId:t}),e.length>=X))break}}catch(r){console.error(`[warm-pool] prewarm scan failed: ${r.message}`);return}for(const r of e)try{d({kind:"general",mode:"base",cwd:r.cwd,profileId:r.profileId,model:null,situationalContext:null,name:null,creator:null}).then(S).catch(a=>console.error(`[warm-pool] prewarm failed for ${r.cwd}: ${a.message}`))}catch(a){console.error(`[warm-pool] prewarm failed for ${r.cwd}: ${a.message}`)}}n(ge,"prewarmRecentRecipes");export{V as birthSpare,we as claimWarmNode,_ as holdMintLease,ge as prewarmRecentRecipes,ye as reapStaleSpares,he as refillWarmPool,ue as resumeWarmClaim,h as warmPoolEnabled};
1
+ var N=Object.defineProperty;var n=(e,r)=>N(e,"name",{value:r,configurable:!0});import{createHash as R,randomUUID as L}from"node:crypto";import{openDb as b}from"../canvas/db.js";import{claimReconcilerLease as C,releaseReconcilerLease as E,renewReconcilerLease as W}from"../canvas/reconciler-leases.js";import{claimWarmSpare as M,deleteNode as u,getNode as f,listNodes as P,listWarmSpares as y,registerWarmSpare as I,unregisterWarmSpare as h,updateNode as A,warmSpareCount as g}from"../canvas/index.js";import{recordedPidLiveness as D}from"../canvas/pid.js";import{nowIso as _}from"../fs-utils.js";import{admitNodeCreate as O}from"../command-hooks/lifecycle-create.js";import{isDrained as T}from"../../daemon/drain.js";import{spawnChildPrepared as H}from"./spawn.js";import{blaxelEnvironmentOf as F}from"./blaxel-environment.js";import{newNodeId as K}from"./nodes.js";import{checkStart as U}from"./start-check.js";import{buildLaunchSpecAsync as j}from"./launch.js";import{setModelLive as B}from"./model-swap.js";import{setSituationalContextLive as G}from"./situational-live.js";import{deliverBearingsLive as J}from"./deliver-live.js";import{closeNode as p}from"./close.js";function v(){return process.env.CRTR_WARM_POOL!=="0"}n(v,"warmPoolEnabled");function z(e,r,a){const t=Object.entries(a.env??{}).sort(([s],[i])=>s<i?-1:s>i?1:0),o=JSON.stringify([e,r,a.tools??[],a.extensions??[],a.systemPrompt??null,t]);return R("sha256").update(o).digest("hex").slice(0,32)}n(z,"warmRecipeKey");async function d(e){const{launch:r}=await j(e.kind,e.mode,{lifecycle:"resident",hasManager:!1,...e.model!==null?{model:e.model}:{},cwd:e.cwd,profileId:e.profileId});return{key:z(e.cwd,e.profileId,r),launch:r,request:e}}n(d,"resolveRecipe");async function S(e){const r=e.creator===null?null:await f(e.creator);await U({grantee:r?.grantee??"app:terminal",sub:r?.sub,grant_id:r?.grant_id})}n(S,"checkClaimantStart");async function ye(e,r={},a){await S(e);const t=await d(e);if(await g(t.key)===0)return null;const o=_(),s=await O({kind:e.kind,mode:e.mode,cwd:e.cwd,profile:e.profileId,root:!0,parentId:null,requestingNodeId:e.creator,lifecycle:"resident",replaces:null},e.creator===null?"app:terminal":await(async()=>{const c=await f(e.creator);if(!c?.grantee)throw new Error("warm node creator has no app");return c.grantee})());let i;try{i=await M(t.key,o,r)}finally{s()}return i===null?null:k(i,t,o,a)}n(ye,"claimWarmNode");async function he(e,r,a){return await S(e),k(r,await d(e),_(),a)}n(he,"resumeWarmClaim");async function k(e,r,a,t){const{request:o}=r,i=(await f(e))?.launch?.model;await A(e,{created:a,kind:o.kind,mode:o.mode,name:o.name??o.kind,creator:o.creator,launch:r.launch,persona_ack:{kind:o.kind,mode:o.mode,lifecycle:"resident"},...o.model!==null?{model_override:o.model}:{}});try{t?.(e)}catch(l){return await m(e,"birth preparation",l)}try{await J(e)}catch(l){return await m(e,"bearings delivery",l)}const c=r.launch.model;if(c!==void 0&&c!==i)try{await B(e,c,r.launch.modelIntent)}catch(l){return await m(e,"model swap",l)}if(o.situationalContext!==null)try{await G(e,o.situationalContext)}catch(l){return await m(e,"situational-context delivery",l)}return e}n(k,"completeWarmClaim");async function m(e,r,a){console.error(`[warm-pool] ${r} failed on claim; closing the spare: ${a.message}`);try{await p(e)}catch{}return null}n(m,"abandonSpare");const Q=3600*1e3;async function ge(){const e=[];let r;try{r=await y()}catch(t){return console.error(`[warm-pool] spare scan failed: ${t.message}`),e}const a=Date.now()-Q;for(const t of r){if(await V(t.node_id))continue;const o=Date.parse(t.created),s=Number.isNaN(o)||o<a,i=D(t.pi_pid,t.pi_pid_identity),c=(t.status==="active"||t.status==="idle")&&i==="alive";if(!(c&&!s)&&!(i==="indeterminate"&&!s))try{if(!await h(t.node_id))continue;if(c)try{await p(t.node_id)}catch{}await u(t.node_id),e.push(t.node_id)}catch(l){console.error(`[warm-pool] reap failed for ${t.node_id}: ${l.message}`)}}return e}n(ge,"reapStaleSpares");async function _e(){for(const e of await y())if(await h(e.node_id)){try{await p(e.node_id)}catch(r){console.error(`[warm-pool] close failed for ${e.node_id}: ${r.message}`)}await u(e.node_id)}}n(_e,"emptyWarmPool");const w=6e4;async function x(e,r){const a=L();if(await C(e,r,a,w)===void 0)return;const t=setInterval(()=>{W(e,r,a,w).catch(()=>{})},w/3);return t.unref(),async()=>{clearInterval(t),await E(e,r,a)}}n(x,"holdMintLease");async function V(e){return await(await b()).prepare("SELECT 1 FROM reconciler_leases WHERE lane = ? AND key = ? AND expires_at > ?").get("warm-pool-mint",e,new Date().toISOString())!==void 0}n(V,"mintLeaseHeld");function ve(e){if(v())try{d({...e,model:null,situationalContext:null}).then($).catch(r=>console.error(`[warm-pool] recipe resolution failed: ${r.message}`))}catch(r){console.error(`[warm-pool] recipe resolution failed: ${r.message}`)}}n(ve,"refillWarmPool");async function $(e){if(await T())return;const{key:r}=e;if(await F(e.request.profileId)!==null)return;const a=await x("warm-pool",r);if(a!==void 0){if(await g(r)>0){await a();return}X(e,await K()).finally(()=>a().catch(()=>{}))}}n($,"mint");async function X({key:e,request:r},a){const t=await x("warm-pool-mint",a);if(t===void 0){console.error(`[warm-pool] mint refused for ${r.kind}: the mint lease of ${a} is held elsewhere`);return}try{await H({nodeId:a,kind:r.kind,mode:r.mode,cwd:r.cwd,prompt:"",root:!0,readiness:"await",profile:r.profileId},async o=>{await I(o,e)})}catch(o){console.error(`[warm-pool] mint failed for ${r.kind}: ${o.message}`)}finally{await t().catch(()=>{})}}n(X,"birthSpare");const Y=2;async function Se(){if(!v())return;let e;try{const r=new Set;e=[];for(const a of(await P({status:["active","idle"]})).reverse()){if(a.parent!==null||a.kind==="human")continue;const t=a.profile_id??null;if(!r.has(t??"")&&(r.add(t??""),e.push({cwd:a.cwd,profileId:t}),e.length>=Y))break}}catch(r){console.error(`[warm-pool] prewarm scan failed: ${r.message}`);return}for(const r of e)try{d({kind:"general",mode:"base",cwd:r.cwd,profileId:r.profileId,model:null,situationalContext:null,name:null,creator:null}).then($).catch(a=>console.error(`[warm-pool] prewarm failed for ${r.cwd}: ${a.message}`))}catch(a){console.error(`[warm-pool] prewarm failed for ${r.cwd}: ${a.message}`)}}n(Se,"prewarmRecentRecipes");export{X as birthSpare,ye as claimWarmNode,_e as emptyWarmPool,x as holdMintLease,Se as prewarmRecentRecipes,ge as reapStaleSpares,ve as refillWarmPool,he as resumeWarmClaim,v as warmPoolEnabled};
@@ -6,6 +6,7 @@ export interface EnvOwner {
6
6
  kind: EnvOwnerKind;
7
7
  id: string;
8
8
  }
9
+ export declare const aadOf: (owner: EnvOwner, name: string) => string;
9
10
  /** Insert or replace one value. Returns true when the name was new. */
10
11
  export declare function putEnvRow(owner: EnvOwner, name: string, value: string, key?: SecretsKey): Promise<boolean>;
11
12
  /** Seal one value into an open transaction, keeping a row that already holds the name. Used by the one-time boot copy. */
@@ -1,4 +1,4 @@
1
1
  var u=Object.defineProperty;var t=(e,n)=>u(e,"name",{value:n,configurable:!0});import{openDb as o}from"../canvas/db.js";import{loadSecretsKey as i,open as l,seal as c}from"./index.js";const E=t((e,n)=>`env_vars|${e.person??""}|${e.kind}|${e.id}|${n}`,"aadOf"),p=t(e=>e.person??"","personKey");async function D(e,n,a,d=i()){const{sealed:r,keyId:s}=c(a,d,E(e,n));return(await(await o()).prepare(`INSERT INTO env_vars (person, owner_kind, owner_id, name, value_sealed, key_id, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)
2
2
  ON CONFLICT (COALESCE(person, ''), owner_kind, owner_id, name) DO UPDATE SET value_sealed = EXCLUDED.value_sealed, key_id = EXCLUDED.key_id, updated_at = EXCLUDED.updated_at
3
3
  RETURNING (xmax = 0) AS created`).get(e.person,e.kind,e.id,n,r,s,new Date().toISOString())).created}t(D,"putEnvRow");async function N(e,n,a,d,r=i()){const{sealed:s,keyId:_}=c(d,r,E(n,a));await e.prepare(`INSERT INTO env_vars (person, owner_kind, owner_id, name, value_sealed, key_id, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)
4
- ON CONFLICT (COALESCE(person, ''), owner_kind, owner_id, name) DO NOTHING`).run(n.person,n.kind,n.id,a,s,_,new Date().toISOString())}t(N,"putEnvRowIfAbsent");async function C(e,n){return(await(await o()).prepare("DELETE FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ? AND name = ?").run(p(e),e.kind,e.id,n)).changes>0}t(C,"removeEnvRow");async function S(e){return(await(await o()).prepare("SELECT name FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ? ORDER BY name").all(p(e),e.kind,e.id)).map(a=>a.name)}t(S,"listEnvRowNames");async function m(e,n=i()){const a=await(await o()).prepare("SELECT name, value_sealed, key_id FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ?").all(p(e),e.kind,e.id),d=Object.create(null);for(const r of a)d[r.name]=l(r.value_sealed,r.key_id,n,E(e,r.name));return d}t(m,"readEnvRows");export{S as listEnvRowNames,D as putEnvRow,N as putEnvRowIfAbsent,m as readEnvRows,C as removeEnvRow};
4
+ ON CONFLICT (COALESCE(person, ''), owner_kind, owner_id, name) DO NOTHING`).run(n.person,n.kind,n.id,a,s,_,new Date().toISOString())}t(N,"putEnvRowIfAbsent");async function C(e,n){return(await(await o()).prepare("DELETE FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ? AND name = ?").run(p(e),e.kind,e.id,n)).changes>0}t(C,"removeEnvRow");async function S(e){return(await(await o()).prepare("SELECT name FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ? ORDER BY name").all(p(e),e.kind,e.id)).map(a=>a.name)}t(S,"listEnvRowNames");async function m(e,n=i()){const a=await(await o()).prepare("SELECT name, value_sealed, key_id FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ?").all(p(e),e.kind,e.id),d=Object.create(null);for(const r of a)d[r.name]=l(r.value_sealed,r.key_id,n,E(e,r.name));return d}t(m,"readEnvRows");export{E as aadOf,S as listEnvRowNames,D as putEnvRow,N as putEnvRowIfAbsent,m as readEnvRows,C as removeEnvRow};
@@ -0,0 +1,6 @@
1
+ export declare const PURGE_RETENTION_MS: number;
2
+ /** Purge up to `limit` free rows in one transaction; returns how many went. A reference added between the check and the delete
3
+ * (23503) leaves everything in place and the next pass tries again. */
4
+ export declare function purgeFreeRows(now: number, limit: number): Promise<number>;
5
+ /** Purge until a round finds nothing free or `budget` rows are gone; `holds` is checked before each round. */
6
+ export declare function purgeSoftDeleted(now: number, budget: number, holds?: () => Promise<boolean>, batch?: number): Promise<number>;
@@ -0,0 +1,4 @@
1
+ var c=Object.defineProperty;var a=(o,r)=>c(o,"name",{value:r,configurable:!0});import{withCanvasWrite as d}from"../canvas/db.js";const s=720*60*6e4,R=`o.deleted_at IS NOT NULL AND o.deleted_at < ? AND o.type <> 'node'
2
+ AND NOT EXISTS (SELECT 1 FROM objects x WHERE x.owner = o.id)
3
+ AND NOT EXISTS (SELECT 1 FROM revisions r WHERE r.author = o.id)
4
+ AND NOT EXISTS (SELECT 1 FROM object_kinds k WHERE k.owner = o.id AND EXISTS (SELECT 1 FROM objects t WHERE t.type = k.name))`;async function S(o,r){const E=new Date(o-s).toISOString();try{return await d(async e=>{const t=(await e.prepare(`SELECT o.id FROM objects o WHERE ${R} ORDER BY o.deleted_at, o.id LIMIT ?`).all(E,r)).map(i=>i.id);return t.length===0?0:(await e.prepare("DELETE FROM object_kinds WHERE owner = ANY(?::text[])").run(t),(await e.prepare("DELETE FROM objects WHERE id = ANY(?::text[]) AND deleted_at < ?").run(t,E)).changes)},{savepoint:!0})}catch(e){if(e.code==="23503")return 0;throw e}}a(S,"purgeFreeRows");async function N(o,r,E=async()=>!0,e=100){let t=0;for(;t<r&&await E();){const n=await S(o,Math.min(e,r-t));if(n===0)break;t+=n}return t}a(N,"purgeSoftDeleted");export{s as PURGE_RETENTION_MS,S as purgeFreeRows,N as purgeSoftDeleted};
@@ -38,6 +38,7 @@ export declare class RefreshBusyError extends Error {
38
38
  export declare class RefreshTimeoutError extends Error {
39
39
  constructor(provider: ManagedProviderId);
40
40
  }
41
+ export declare const aadOf: (person: string, provider: string, id: string, part: "refresh" | "access") => string;
41
42
  export declare function insertAccount(db: CanvasDb, person: string, provider: ManagedProviderId, row: ProviderRow, entry: StoredCredential): Promise<void>;
42
43
  export declare function upsertModelCooldown(db: CanvasDb, person: string, provider: string, accountId: string, family: string, until: number, rateLimitedAt: number): Promise<void>;
43
44
  export declare function readSubscriptionPool(provider: ManagedProviderId): Promise<SubscriptionCredential[]>;
@@ -1 +1 @@
1
- var _t=Object.defineProperty;var l=(t,a)=>_t(t,"name",{value:a,configurable:!0});import{createHash as ht,randomUUID as x}from"node:crypto";import{brokerConfigSnapshot as pt}from"./config.js";import{openDb as W,withCanvasWrite as N}from"./canvas/db.js";import{personIdAsync as y}from"./canvas/person.js";import{loadSecretsKey as K,open as X,seal as $}from"./secrets/index.js";import{modelCooldownFamily as wt}from"./rate-limit-scope.js";import{REFRESH_BUSY_PHRASE as At,REFRESH_TIMEOUT_PHRASE as bt}from"./provider-error-recognition.js";import{registerRotationReader as yt}from"./model-routes.js";import{coolingUntilFor as Et,getProviderLabel as h,isInvalidRefreshTokenError as Y,DuplicateSubscriptionError as O,ANTHROPIC_PROVIDER_ID as Lt,MAX_RATE_LIMIT_COOLDOWN_MS as B,INVALID_REFRESH_TOKEN_BACKOFF_MS as Rt}from"./managed-providers.js";export*from"./managed-providers.js";const J="strong";function Q(t){return t==="ultra"||t==="strong"||t==="medium"||t==="light"?t:void 0}l(Q,"normalizeStrength");function _(t){return t.toLowerCase()}l(_,"key");function j(t){return typeof t=="string"&&t.trim()?t.trim():void 0}l(j,"optionalString");function gt(t){const a=typeof t.label=="string"?t.label.trim():"",e=typeof t.refresh=="string"?t.refresh.trim():"",n=typeof t.access=="string"?t.access.trim():"",i=Number(t.expires);if(!a||!e||!n||!Number.isFinite(i))return;const r=j(t.accountId),s=j(t.clientId);return{label:a,refresh:e,access:n,expires:i,rateLimitedUntil:Number.isFinite(Number(t.rateLimitedUntil))?Number(t.rateLimitedUntil):0,lastAttemptAt:Number.isFinite(Number(t.lastAttemptAt))?Number(t.lastAttemptAt):0,lastRateLimitedAt:Number.isFinite(Number(t.lastRateLimitedAt))?Number(t.lastRateLimitedAt):0,...t.authFailure==="invalid_grant"?{authFailure:"invalid_grant"}:{},...r?{accountId:r}:{},...s?{clientId:s}:{},...Z(t.modelCooldowns)?{modelCooldowns:Z(t.modelCooldowns)}:{}}}l(gt,"credential");function Z(t){if(!t||typeof t!="object"||Array.isArray(t))return;const a=Object.entries(t).filter(([e,n])=>e.trim()!==""&&Number.isFinite(Number(n))).map(([e,n])=>[e,Number(n)]);return a.length?Object.fromEntries(a):void 0}l(Z,"modelCooldowns");function F(t,a){if(!a.label?.trim())throw new Error(`${h(t)} subscription label cannot be empty`);const e=gt(a);if(!e)throw new Error(`Refusing to persist incomplete ${h(t)} subscription "${a.label.trim()}"; pool rows require both OAuth tokens.`);return e}l(F,"assertCredential");function v(t,a){const e=t.accountId?.trim(),n=a.accountId?.trim();return!!(e&&n&&e!==n)}l(v,"differentAccount");function p(t){return t.accountId?.trim()||(t.refresh.trim()?`refresh:${ht("sha256").update(t.refresh.trim()).digest("hex")}`:void 0)}l(p,"identity");function tt(t,a){const e=j(a);return e?{...t,clientId:e}:t}l(tt,"withClientId");function et(t,a,e){const n=e.refresh?.trim()??"",i=e.access?.trim()??"",r=Number(e.expires);if(!n||!i||!Number.isFinite(r))throw new Error(`${h(t)} subscription "${a}" returned an incomplete credential; the stored credential was left unchanged.`);return{refresh:n,access:i,expires:r}}l(et,"assertTokens");const at=1e4,It="15s",xt="55P03";class nt extends Error{static{l(this,"RefreshBusyError")}constructor(a,e){super(`${h(a)} subscription "${e}" ${At}; treating it as cooling for this call`),this.name="RefreshBusyError"}}class Nt extends Error{static{l(this,"RefreshTimeoutError")}constructor(a){super(`${h(a)} ${bt} ${at} ms`),this.name="RefreshTimeoutError"}}const it="id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure,refresh_sealed,access_sealed,expires,vendor_account_id,client_id,key_id",I=l((t,a,e,n)=>`model_accounts|${t}|${a}|${e}|${n}`,"aadOf");function E(t){return{refresh:t.refresh,access:t.access,expires:t.expires,...t.accountId?{accountId:t.accountId}:{},...t.clientId?{clientId:t.clientId}:{}}}l(E,"stored");function Ft(t,a){return{providerId:t,id:a.id,label:a.label,labelKey:a.label_key,position:a.position,lastAttemptAt:a.last_attempt_at,lastRateLimitedAt:a.last_rate_limited_at,rateLimitedUntil:a.rate_limited_until,...a.auth_failure?{authFailure:a.auth_failure}:{}}}l(Ft,"rowOf");function rt(t,a,e,n=[]){if(!e.refresh_sealed||!e.access_sealed||e.expires===null||!e.key_id)throw new Error(`managed ${a} account ${e.id} has no credential record`);const i=K(),r=X(e.refresh_sealed,e.key_id,i,I(t,a,e.id,"refresh")),s=X(e.access_sealed,e.key_id,i,I(t,a,e.id,"access")),o=Ft(a,e),c={label:o.label,refresh:r,access:s,expires:e.expires,rateLimitedUntil:o.authFailure||!o.lastRateLimitedAt?o.rateLimitedUntil:Math.min(o.rateLimitedUntil,o.lastRateLimitedAt+B),lastAttemptAt:o.lastAttemptAt,lastRateLimitedAt:o.lastRateLimitedAt,...o.authFailure?{authFailure:o.authFailure}:{},...e.vendor_account_id?{accountId:e.vendor_account_id}:{},...e.client_id?{clientId:e.client_id}:{},...n.length?{modelCooldowns:Object.fromEntries(n.map(d=>[d.family,d.lastRateLimitedAt?Math.min(d.rateLimitedUntil,d.lastRateLimitedAt+B):d.rateLimitedUntil]))}:{}};return{row:o,entry:c}}l(rt,"toPublic");async function S(t,a,e){const n=new Map,i=await t.prepare("SELECT account_id,family,rate_limited_until,last_rate_limited_at FROM model_account_cooldowns WHERE person=? AND provider_id=? ORDER BY family").all(a,e);for(const r of i){const s=n.get(r.account_id)??[];s.push({family:r.family,rateLimitedUntil:r.rate_limited_until,lastRateLimitedAt:r.last_rate_limited_at}),n.set(r.account_id,s)}return n}l(S,"loadCooldowns");async function k(t,a,e,n=!1){const i=await t.prepare(`SELECT ${it} FROM model_accounts WHERE person=? AND provider_id=? ORDER BY position${n?" FOR UPDATE":""}`).all(a,e),r=await S(t,a,e);return i.map(s=>rt(a,e,s,r.get(s.id)))}l(k,"loadPool");async function st(t,a,e,n){const i=n.trim().toLowerCase(),r=Number.parseInt(i,10),s=Number.isFinite(r)&&String(r)===i&&r>=1,o=await t.prepare(`SELECT ${it} FROM model_accounts WHERE person=? AND provider_id=? AND ${s?"position=?":"label_key=?"} FOR UPDATE`).get(a,e,s?r-1:i);if(o)return rt(a,e,o,(await S(t,a,e)).get(o.id))}l(st,"lockedAccount");async function T(t){await t.exec(`SET LOCAL lock_timeout = '${It}'`)}l(T,"setLockTimeout");async function L(t,a){const e=await y();return N(async n=>(await T(n),await n.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(`model_accounts|${e}|${t}`),a({db:n,person:e,rows:await k(n,e,t,!0)})))}l(L,"locked");async function C(t,a,e,n,i){const r=K(),s=$(i.refresh,r,I(a,e,n.id,"refresh")),o=$(i.access,r,I(a,e,n.id,"access"));await t.prepare("INSERT INTO model_accounts(person,provider_id,id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure,refresh_sealed,access_sealed,expires,vendor_account_id,client_id,key_id) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run(a,e,n.id,n.label,n.labelKey,n.position,n.lastAttemptAt,n.lastRateLimitedAt,n.rateLimitedUntil,n.authFailure??null,s.sealed,o.sealed,i.expires,i.accountId??null,i.clientId??null,s.keyId)}l(C,"insertAccount");async function M(t,a,e,n,i){const r=K(),s=$(i.refresh,r,I(a,e,n,"refresh")),o=$(i.access,r,I(a,e,n,"access"));await t.prepare("UPDATE model_accounts SET refresh_sealed=?,access_sealed=?,expires=?,vendor_account_id=?,client_id=?,key_id=? WHERE person=? AND provider_id=? AND id=?").run(s.sealed,o.sealed,i.expires,i.accountId??null,i.clientId??null,s.keyId,a,e,n)}l(M,"setCredential");async function b(t,a,e){await t.prepare("UPDATE model_accounts SET label=?,label_key=?,position=?,last_attempt_at=?,last_rate_limited_at=?,rate_limited_until=?,auth_failure=? WHERE person=? AND provider_id=? AND id=?").run(e.label,e.labelKey,e.position,e.lastAttemptAt,e.lastRateLimitedAt,e.rateLimitedUntil,e.authFailure??null,a,e.providerId,e.id)}l(b,"updateHealth");async function ot(t,a,e,n,i,r,s){await t.prepare("INSERT INTO model_account_cooldowns(person,provider_id,account_id,family,rate_limited_until,last_rate_limited_at) VALUES(?,?,?,?,?,?) ON CONFLICT(person,provider_id,account_id,family) DO UPDATE SET rate_limited_until=excluded.rate_limited_until,last_rate_limited_at=excluded.last_rate_limited_at").run(a,e,n,i,r,s)}l(ot,"upsertModelCooldown");async function U(t,a,e,n){n===void 0?await t.prepare("DELETE FROM model_account_cooldowns WHERE person=? AND provider_id=?").run(a,e):await t.prepare("DELETE FROM model_account_cooldowns WHERE person=? AND provider_id=? AND account_id=?").run(a,e,n)}l(U,"deleteModelCooldowns");const R=l(async({db:t,person:a},e)=>(await k(t,a,e)).map(({entry:n})=>n),"publicPool"),lt=l(t=>t?.code===xt,"lockWaitExpired");function ct(t,a){let e;return Promise.race([a,new Promise((n,i)=>{e=setTimeout(()=>i(new Nt(t)),at)})]).finally(()=>clearTimeout(e))}l(ct,"withExchangeTimeout");async function V(t){return(await St(t)).map(({entry:a})=>a)}l(V,"readSubscriptionPool");async function St(t){const a=await y();return(await k(await W(),a,t)).map(({row:e,entry:n})=>({id:e.id,entry:n}))}l(St,"readSubscriptionPoolRows");async function qt(t,a){return L(t,async e=>{const{db:n,person:i,rows:r}=e,s=a.map(d=>F(t,d)),o=s.map((d,u)=>({entry:d,id:r[u]?.row.id??x()})),c=await S(n,i,t);await n.prepare("DELETE FROM model_accounts WHERE person=? AND provider_id=?").run(i,t),await U(n,i,t);for(const[d,{entry:u,id:m}]of o.entries()){await C(n,i,t,{providerId:t,id:m,label:u.label,labelKey:_(u.label),position:d,lastAttemptAt:u.lastAttemptAt,lastRateLimitedAt:u.lastRateLimitedAt,rateLimitedUntil:u.rateLimitedUntil,...u.authFailure?{authFailure:u.authFailure}:{}},E(u));for(const[f,A]of Object.entries(u.modelCooldowns??{}))await ot(n,i,t,m,f,A,c.get(m)?.find(D=>D.family===f)?.lastRateLimitedAt??u.lastRateLimitedAt)}return s})}l(qt,"writeSubscriptionPool");function H(t,a){const e=a.trim().toLowerCase(),n=Number.parseInt(e,10);return Number.isFinite(n)&&String(n)===e&&n>=1&&n<=t.length?n-1:t.findIndex(({entry:i})=>_(i.label)===e)}l(H,"resolve");const P=l((t,a)=>new O(`This ${h(t)} account is already added as \u201C${a}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`),"duplicateAccount");async function Tt(t,a){const e=F(t,a);return L(t,async n=>{const{db:i,person:r,rows:s}=n;if(s.some(({entry:d})=>_(d.label)===_(e.label)))throw new O(`${h(t)} subscription already exists: ${e.label}`);const o=p(e),c=s.find(({entry:d})=>p(d)===o);if(o&&c)throw P(t,c.entry.label);return await C(i,r,t,{providerId:t,id:x(),label:e.label,labelKey:_(e.label),position:s.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil,...e.authFailure?{authFailure:e.authFailure}:{}},E(e)),R(n,t)})}l(Tt,"addSubscription");async function Xt(t,a){const e=F(t,a);return(await V(t)).find(i=>_(i.label)===_(e.label))?Ut(t,e):Tt(t,e)}l(Xt,"upsertSubscription");async function Ut(t,a){const e=F(t,a);return L(t,async n=>{const{db:i,person:r,rows:s}=n,o=s.findIndex(({entry:f})=>_(f.label)===_(e.label)),c=s[o],d=s.find(({entry:f},A)=>A!==o&&p(f)===p(e));if(p(e)&&d)throw P(t,d.entry.label);if(!c)return await C(i,r,t,{providerId:t,id:x(),label:e.label,labelKey:_(e.label),position:s.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil},E(e)),R(n,t);const u=c.row,m=e.lastRateLimitedAt>=u.lastRateLimitedAt;return await M(i,r,t,u.id,E(e)),await b(i,r,{...u,authFailure:void 0,label:e.label,labelKey:_(e.label),lastAttemptAt:Math.max(u.lastAttemptAt,e.lastAttemptAt),lastRateLimitedAt:m?e.lastRateLimitedAt:u.lastRateLimitedAt,rateLimitedUntil:m?e.rateLimitedUntil:u.rateLimitedUntil}),v(c.entry,e)&&await U(i,r,t,u.id),R(n,t)})}l(Ut,"upsertSubscriptionWithUniqueAccount");async function Yt(t,a,e){return L(t,async n=>{const{db:i,person:r,rows:s}=n,o=H(s,a);if(o<0)throw new Error(`Unknown ${h(t)} subscription: ${a}`);const c=e.trim();if(!c)throw new Error(`${h(t)} subscription label cannot be empty`);if(s.some((d,u)=>u!==o&&_(d.entry.label)===_(c)))throw new O(`${h(t)} subscription already exists: ${c}`);return await b(i,r,{...s[o].row,label:c,labelKey:_(c)}),R(n,t)})}l(Yt,"renameSubscription");async function G(t,a,e){return L(t,async n=>{const{db:i,person:r,rows:s}=n,o=a(s);if(o<0)throw new Error(`Unknown ${h(t)} subscription: ${e}`);const c=s[o].row;return await i.prepare("DELETE FROM model_accounts WHERE person=? AND provider_id=? AND id=?").run(r,t,c.id),await U(i,r,t,c.id),R(n,t)})}l(G,"remove");async function Dt(t,a){return G(t,e=>H(e,a),a)}l(Dt,"removeManagedAccount");async function Jt(t,a){return G(t,e=>e.findIndex(({row:n})=>n.id===a),a)}l(Jt,"removeManagedAccountById");async function Qt(t,a){return G(t,e=>H(e,a),a)}l(Qt,"removeSubscription");async function $t(t){return L(t,async({db:a,person:e,rows:n})=>(await a.prepare("DELETE FROM model_accounts WHERE person=? AND provider_id=?").run(e,t),await U(a,e,t),n.length))}l($t,"removeAllManagedAccounts");async function Zt(t,a){return a===void 0?(await $t(t),[]):Dt(t,a)}l(Zt,"removeManagedProvider");async function vt(t,a){return L(t,async e=>{const{db:n,person:i,rows:r}=e,s=H(r,a);if(s<0)throw new Error(`Unknown ${h(t)} subscription: ${a}`);const o=[r[s],...r.filter((c,d)=>d!==s)];for(const[c,d]of o.entries())await b(n,i,{...d.row,position:c});return R(e,t)})}l(vt,"promoteSubscription");async function z(t,a,e){const n=await y();return await N(async i=>{await T(i);const r=await st(i,n,t,a);if(!r)throw new Error(`Unknown ${h(t)} subscription: ${a}`);await e(i,n,r.row)}),V(t)}l(z,"healthWrite");async function te(t,a,e=Date.now()){return z(t,a,(n,i,r)=>b(n,i,{...r,lastAttemptAt:Math.max(r.lastAttemptAt,e)}))}l(te,"markSubscriptionAttempt");async function ee(t,a,e,n=Date.now(),i=Date.now(),r={kind:"account"}){return e=Math.min(Math.max(0,e),B),z(t,a,async(s,o,c)=>{if(r.kind==="account"){if(i<c.lastRateLimitedAt)return;await b(s,o,{...c,lastAttemptAt:Math.max(c.lastAttemptAt,n),lastRateLimitedAt:i,rateLimitedUntil:i+e});return}await b(s,o,{...c,lastAttemptAt:Math.max(c.lastAttemptAt,n)});const d=(await S(s,o,t)).get(c.id)?.find(u=>u.family===r.family);d&&i<d.lastRateLimitedAt||await ot(s,o,t,c.id,r.family,i+e,i)})}l(ee,"markSubscriptionRateLimited");async function ae(t,a,e=Date.now(),n){const i=wt(n);return z(t,a,async(r,s,o)=>{if(e>=o.lastRateLimitedAt&&await b(r,s,{...o,authFailure:void 0,lastAttemptAt:Math.max(o.lastAttemptAt,e),rateLimitedUntil:0}),i===void 0)return;const c=(await S(r,s,t)).get(o.id)?.find(d=>d.family===i);c&&e>=c.lastRateLimitedAt&&await r.prepare("DELETE FROM model_account_cooldowns WHERE person=? AND provider_id=? AND account_id=? AND family=?").run(s,t,o.id,i)})}l(ae,"markSubscriptionSuccess");async function ut(t,a,e){const n=Date.now();await b(t,a,{...e,authFailure:"invalid_grant",lastAttemptAt:Math.max(e.lastAttemptAt,n),rateLimitedUntil:Math.max(e.rateLimitedUntil,n+Rt)})}l(ut,"recordInvalidGrant");async function ne(t,a,e){const n=await y();let i;try{i=await N(async r=>{await T(r);const s=await st(r,n,t,a);if(!s)throw new Error(`${h(t)} subscription "${a}" no longer exists`);if(s.entry.expires>Date.now())return{entry:s.entry};if(s.row.authFailure)throw new Error(`${h(t)} subscription "${s.entry.label}" refresh refused with status=400 invalid_grant; it needs a new sign-in`);let o;try{o=await ct(t,e(s.entry.refresh,s.entry.clientId))}catch(d){if(!Y(d))throw d;return await ut(r,n,s.row),{refused:d}}const c=tt(et(t,s.entry.label,o),o.clientId??s.entry.clientId);return await M(r,n,t,s.row.id,E({...s.entry,...c})),{entry:{...s.entry,...c}}})}catch(r){throw lt(r)?new nt(t,a):r}if("refused"in i)throw i.refused;return i.entry}l(ne,"refreshSubscriptionCredential");async function ie(t,a,e){const n=await y();let i;try{i=await N(async r=>{await T(r);const s=await k(r,n,t,!0),o=new Set;let c;for(;o.size<s.length;){const d=a(s.filter(({entry:m})=>!o.has(m.label)).map(({entry:m})=>m));if(!d)break;const u=s.find(({entry:m})=>m.label===d.label);o.add(u.entry.label);try{const m=await ct(t,e(u.entry));if(!m)return{entry:u.entry};const f=tt(et(t,u.entry.label,m),m.clientId??u.entry.clientId);return await M(r,n,t,u.row.id,E({...u.entry,...f})),{entry:{...u.entry,...f}}}catch(m){if(!Y(m))throw m;c=m,await ut(r,n,u.row)}}return c?{refused:c}:{}})}catch(r){throw lt(r)?new nt(t,a([])?.label??t):r}if("refused"in i)throw i.refused;return i.entry}l(ie,"modifyElectedSubscription");async function Ot(t,a){const e=a.accountId?.trim(),n=a.email?.trim();if(t!==Lt)return{...e?{accountId:e}:{},...n?{email:n}:{}};try{const i=await fetch("https://api.anthropic.com/api/oauth/profile",{headers:{Authorization:`Bearer ${a.access}`,Accept:"application/json"},signal:AbortSignal.timeout(3e3)});if(!i.ok)return e?{accountId:e}:{};const r=await i.json(),s=typeof r.account?.uuid=="string"&&r.account.uuid.trim()?r.account.uuid.trim():void 0,o=typeof r.account?.email=="string"&&r.account.email.trim()?r.account.email.trim():void 0;return{...s?{accountId:s}:{},...o?{email:o}:{}}}catch{return e?{accountId:e}:{}}}l(Ot,"enrichManagedLoginIdentity");async function re(t,a,e){const n=await Ot(t,e);return L(t,async i=>{const{db:r,person:s,rows:o}=i,c=e.autoLabel?void 0:a?.trim(),d=p({refresh:e.refresh,accountId:n.accountId}),u=(c?o.find(({entry:w})=>_(w.label)===_(c)):void 0)??o.find(({entry:w})=>p(w)===d);let m=u?.entry.label??c;if(!m&&e.autoLabel){const w=n.email||a?.trim()||`Account ${o.length+1}`;m=w;for(let g=2;o.some(({entry:ft})=>_(ft.label)===_(m));g++)m=`${w} (${g})`}if(!m)throw new Error(`${h(t)} subscription label cannot be empty`);const f=F(t,{label:m,refresh:e.refresh,access:e.access,expires:e.expires,...n.accountId?{accountId:n.accountId}:{},...e.clientId?{clientId:e.clientId}:{},rateLimitedUntil:0,lastAttemptAt:Date.now(),lastRateLimitedAt:e.lastRateLimitedAt});if(!u){if(o.some(({entry:g})=>_(g.label)===_(f.label)))throw new O(`${h(t)} subscription already exists: ${f.label}`);const w=o.find(({entry:g})=>p(g)===p(f));if(p(f)&&w)throw P(t,w.entry.label);return await C(r,s,t,{providerId:t,id:x(),label:f.label,labelKey:_(f.label),position:o.length,lastAttemptAt:f.lastAttemptAt,lastRateLimitedAt:f.lastRateLimitedAt,rateLimitedUntil:f.rateLimitedUntil},E(f)),R(i,t)}const A=u.row,D=o.find(({entry:w})=>w.label!==u.entry.label&&p(w)===p(f));if(p(f)&&D)throw P(t,D.entry.label);const q=f.lastRateLimitedAt>=A.lastRateLimitedAt;return await M(r,s,t,A.id,E(f)),await b(r,s,{...A,authFailure:void 0,lastAttemptAt:Math.max(A.lastAttemptAt,f.lastAttemptAt),lastRateLimitedAt:q?f.lastRateLimitedAt:A.lastRateLimitedAt,rateLimitedUntil:q?0:A.rateLimitedUntil}),v(u.entry,f)&&await U(r,s,t,A.id),R(i,t)})}l(re,"commitManagedLogin");const dt=new Map;function mt(t){return{defaultFallbackStrength:Q(t?.default_fallback_strength)??J,revertWhenAvailable:t?.revert_when_available===void 0||t.revert_when_available===null?!0:!!t.revert_when_available}}l(mt,"rotationFromRow");async function kt(){const t=await y(),a=mt(await(await W()).prepare("SELECT default_fallback_strength,revert_when_available FROM model_settings WHERE person=?").get(t));return dt.set(t,a),a}l(kt,"loadRotationConfig"),yt(()=>Ct());async function Ct(){const t=pt();return t?t.rotation:kt()}l(Ct,"readRotationConfig");async function se(t){const a=await y(),e=await N(async n=>{await T(n),await n.prepare("INSERT INTO model_settings(person) VALUES(?) ON CONFLICT(person) DO NOTHING").run(a);const i=mt(await n.prepare("SELECT default_fallback_strength,revert_when_available FROM model_settings WHERE person=? FOR UPDATE").get(a)),r={defaultFallbackStrength:Q(t.defaultFallbackStrength)??i.defaultFallbackStrength??J,revertWhenAvailable:t.revertWhenAvailable??i.revertWhenAvailable??!0};return await n.prepare("UPDATE model_settings SET default_fallback_strength=?,revert_when_available=?,revision=revision+1 WHERE person=?").run(r.defaultFallbackStrength,r.revertWhenAvailable?1:0,a),r});return dt.set(a,e),e}l(se,"writeRotationConfig");async function oe(){const t=await y();return(await(await W()).prepare("INSERT INTO model_settings(person,openai_device_id) VALUES(?,?) ON CONFLICT(person) DO UPDATE SET openai_device_id=COALESCE(model_settings.openai_device_id,EXCLUDED.openai_device_id) RETURNING openai_device_id").get(t,x())).openai_device_id}l(oe,"openAIDeviceId");async function le(t,a=Date.now(),e){const n=await V(t),i=n.filter(s=>s.authFailure===void 0&&Et(s,e)<=a),r=h(t);return i.length===0?`${r} pool exhausted`:`${r} ${i[0]?.label??t} (${i.length}/${n.length} ready)`}l(le,"formatStatusLine");export{at as REFRESH_EXCHANGE_TIMEOUT_MS,nt as RefreshBusyError,Nt as RefreshTimeoutError,Tt as addSubscription,re as commitManagedLogin,Ot as enrichManagedLoginIdentity,le as formatStatusLine,C as insertAccount,kt as loadRotationConfig,te as markSubscriptionAttempt,ee as markSubscriptionRateLimited,ae as markSubscriptionSuccess,ie as modifyElectedSubscription,oe as openAIDeviceId,vt as promoteSubscription,Ct as readRotationConfig,V as readSubscriptionPool,St as readSubscriptionPoolRows,ne as refreshSubscriptionCredential,$t as removeAllManagedAccounts,Dt as removeManagedAccount,Jt as removeManagedAccountById,Zt as removeManagedProvider,Qt as removeSubscription,Yt as renameSubscription,ot as upsertModelCooldown,Xt as upsertSubscription,Ut as upsertSubscriptionWithUniqueAccount,se as writeRotationConfig,qt as writeSubscriptionPool};
1
+ var _t=Object.defineProperty;var l=(t,a)=>_t(t,"name",{value:a,configurable:!0});import{createHash as ht,randomUUID as x}from"node:crypto";import{brokerConfigSnapshot as pt}from"./config.js";import{openDb as W,withCanvasWrite as N}from"./canvas/db.js";import{personIdAsync as y}from"./canvas/person.js";import{loadSecretsKey as K,open as X,seal as $}from"./secrets/index.js";import{modelCooldownFamily as wt}from"./rate-limit-scope.js";import{REFRESH_BUSY_PHRASE as At,REFRESH_TIMEOUT_PHRASE as bt}from"./provider-error-recognition.js";import{registerRotationReader as yt}from"./model-routes.js";import{coolingUntilFor as Et,getProviderLabel as h,isInvalidRefreshTokenError as Y,DuplicateSubscriptionError as O,ANTHROPIC_PROVIDER_ID as Lt,MAX_RATE_LIMIT_COOLDOWN_MS as B,INVALID_REFRESH_TOKEN_BACKOFF_MS as Rt}from"./managed-providers.js";export*from"./managed-providers.js";const J="strong";function Q(t){return t==="ultra"||t==="strong"||t==="medium"||t==="light"?t:void 0}l(Q,"normalizeStrength");function _(t){return t.toLowerCase()}l(_,"key");function j(t){return typeof t=="string"&&t.trim()?t.trim():void 0}l(j,"optionalString");function gt(t){const a=typeof t.label=="string"?t.label.trim():"",e=typeof t.refresh=="string"?t.refresh.trim():"",n=typeof t.access=="string"?t.access.trim():"",i=Number(t.expires);if(!a||!e||!n||!Number.isFinite(i))return;const r=j(t.accountId),s=j(t.clientId);return{label:a,refresh:e,access:n,expires:i,rateLimitedUntil:Number.isFinite(Number(t.rateLimitedUntil))?Number(t.rateLimitedUntil):0,lastAttemptAt:Number.isFinite(Number(t.lastAttemptAt))?Number(t.lastAttemptAt):0,lastRateLimitedAt:Number.isFinite(Number(t.lastRateLimitedAt))?Number(t.lastRateLimitedAt):0,...t.authFailure==="invalid_grant"?{authFailure:"invalid_grant"}:{},...r?{accountId:r}:{},...s?{clientId:s}:{},...Z(t.modelCooldowns)?{modelCooldowns:Z(t.modelCooldowns)}:{}}}l(gt,"credential");function Z(t){if(!t||typeof t!="object"||Array.isArray(t))return;const a=Object.entries(t).filter(([e,n])=>e.trim()!==""&&Number.isFinite(Number(n))).map(([e,n])=>[e,Number(n)]);return a.length?Object.fromEntries(a):void 0}l(Z,"modelCooldowns");function F(t,a){if(!a.label?.trim())throw new Error(`${h(t)} subscription label cannot be empty`);const e=gt(a);if(!e)throw new Error(`Refusing to persist incomplete ${h(t)} subscription "${a.label.trim()}"; pool rows require both OAuth tokens.`);return e}l(F,"assertCredential");function v(t,a){const e=t.accountId?.trim(),n=a.accountId?.trim();return!!(e&&n&&e!==n)}l(v,"differentAccount");function p(t){return t.accountId?.trim()||(t.refresh.trim()?`refresh:${ht("sha256").update(t.refresh.trim()).digest("hex")}`:void 0)}l(p,"identity");function tt(t,a){const e=j(a);return e?{...t,clientId:e}:t}l(tt,"withClientId");function et(t,a,e){const n=e.refresh?.trim()??"",i=e.access?.trim()??"",r=Number(e.expires);if(!n||!i||!Number.isFinite(r))throw new Error(`${h(t)} subscription "${a}" returned an incomplete credential; the stored credential was left unchanged.`);return{refresh:n,access:i,expires:r}}l(et,"assertTokens");const at=1e4,It="15s",xt="55P03";class nt extends Error{static{l(this,"RefreshBusyError")}constructor(a,e){super(`${h(a)} subscription "${e}" ${At}; treating it as cooling for this call`),this.name="RefreshBusyError"}}class Nt extends Error{static{l(this,"RefreshTimeoutError")}constructor(a){super(`${h(a)} ${bt} ${at} ms`),this.name="RefreshTimeoutError"}}const it="id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure,refresh_sealed,access_sealed,expires,vendor_account_id,client_id,key_id",I=l((t,a,e,n)=>`model_accounts|${t}|${a}|${e}|${n}`,"aadOf");function E(t){return{refresh:t.refresh,access:t.access,expires:t.expires,...t.accountId?{accountId:t.accountId}:{},...t.clientId?{clientId:t.clientId}:{}}}l(E,"stored");function Ft(t,a){return{providerId:t,id:a.id,label:a.label,labelKey:a.label_key,position:a.position,lastAttemptAt:a.last_attempt_at,lastRateLimitedAt:a.last_rate_limited_at,rateLimitedUntil:a.rate_limited_until,...a.auth_failure?{authFailure:a.auth_failure}:{}}}l(Ft,"rowOf");function rt(t,a,e,n=[]){if(!e.refresh_sealed||!e.access_sealed||e.expires===null||!e.key_id)throw new Error(`managed ${a} account ${e.id} has no credential record`);const i=K(),r=X(e.refresh_sealed,e.key_id,i,I(t,a,e.id,"refresh")),s=X(e.access_sealed,e.key_id,i,I(t,a,e.id,"access")),o=Ft(a,e),c={label:o.label,refresh:r,access:s,expires:e.expires,rateLimitedUntil:o.authFailure||!o.lastRateLimitedAt?o.rateLimitedUntil:Math.min(o.rateLimitedUntil,o.lastRateLimitedAt+B),lastAttemptAt:o.lastAttemptAt,lastRateLimitedAt:o.lastRateLimitedAt,...o.authFailure?{authFailure:o.authFailure}:{},...e.vendor_account_id?{accountId:e.vendor_account_id}:{},...e.client_id?{clientId:e.client_id}:{},...n.length?{modelCooldowns:Object.fromEntries(n.map(d=>[d.family,d.lastRateLimitedAt?Math.min(d.rateLimitedUntil,d.lastRateLimitedAt+B):d.rateLimitedUntil]))}:{}};return{row:o,entry:c}}l(rt,"toPublic");async function S(t,a,e){const n=new Map,i=await t.prepare("SELECT account_id,family,rate_limited_until,last_rate_limited_at FROM model_account_cooldowns WHERE person=? AND provider_id=? ORDER BY family").all(a,e);for(const r of i){const s=n.get(r.account_id)??[];s.push({family:r.family,rateLimitedUntil:r.rate_limited_until,lastRateLimitedAt:r.last_rate_limited_at}),n.set(r.account_id,s)}return n}l(S,"loadCooldowns");async function k(t,a,e,n=!1){const i=await t.prepare(`SELECT ${it} FROM model_accounts WHERE person=? AND provider_id=? ORDER BY position${n?" FOR UPDATE":""}`).all(a,e),r=await S(t,a,e);return i.map(s=>rt(a,e,s,r.get(s.id)))}l(k,"loadPool");async function st(t,a,e,n){const i=n.trim().toLowerCase(),r=Number.parseInt(i,10),s=Number.isFinite(r)&&String(r)===i&&r>=1,o=await t.prepare(`SELECT ${it} FROM model_accounts WHERE person=? AND provider_id=? AND ${s?"position=?":"label_key=?"} FOR UPDATE`).get(a,e,s?r-1:i);if(o)return rt(a,e,o,(await S(t,a,e)).get(o.id))}l(st,"lockedAccount");async function T(t){await t.exec(`SET LOCAL lock_timeout = '${It}'`)}l(T,"setLockTimeout");async function L(t,a){const e=await y();return N(async n=>(await T(n),await n.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(`model_accounts|${e}|${t}`),a({db:n,person:e,rows:await k(n,e,t,!0)})))}l(L,"locked");async function C(t,a,e,n,i){const r=K(),s=$(i.refresh,r,I(a,e,n.id,"refresh")),o=$(i.access,r,I(a,e,n.id,"access"));await t.prepare("INSERT INTO model_accounts(person,provider_id,id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure,refresh_sealed,access_sealed,expires,vendor_account_id,client_id,key_id) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run(a,e,n.id,n.label,n.labelKey,n.position,n.lastAttemptAt,n.lastRateLimitedAt,n.rateLimitedUntil,n.authFailure??null,s.sealed,o.sealed,i.expires,i.accountId??null,i.clientId??null,s.keyId)}l(C,"insertAccount");async function M(t,a,e,n,i){const r=K(),s=$(i.refresh,r,I(a,e,n,"refresh")),o=$(i.access,r,I(a,e,n,"access"));await t.prepare("UPDATE model_accounts SET refresh_sealed=?,access_sealed=?,expires=?,vendor_account_id=?,client_id=?,key_id=? WHERE person=? AND provider_id=? AND id=?").run(s.sealed,o.sealed,i.expires,i.accountId??null,i.clientId??null,s.keyId,a,e,n)}l(M,"setCredential");async function b(t,a,e){await t.prepare("UPDATE model_accounts SET label=?,label_key=?,position=?,last_attempt_at=?,last_rate_limited_at=?,rate_limited_until=?,auth_failure=? WHERE person=? AND provider_id=? AND id=?").run(e.label,e.labelKey,e.position,e.lastAttemptAt,e.lastRateLimitedAt,e.rateLimitedUntil,e.authFailure??null,a,e.providerId,e.id)}l(b,"updateHealth");async function ot(t,a,e,n,i,r,s){await t.prepare("INSERT INTO model_account_cooldowns(person,provider_id,account_id,family,rate_limited_until,last_rate_limited_at) VALUES(?,?,?,?,?,?) ON CONFLICT(person,provider_id,account_id,family) DO UPDATE SET rate_limited_until=excluded.rate_limited_until,last_rate_limited_at=excluded.last_rate_limited_at").run(a,e,n,i,r,s)}l(ot,"upsertModelCooldown");async function U(t,a,e,n){n===void 0?await t.prepare("DELETE FROM model_account_cooldowns WHERE person=? AND provider_id=?").run(a,e):await t.prepare("DELETE FROM model_account_cooldowns WHERE person=? AND provider_id=? AND account_id=?").run(a,e,n)}l(U,"deleteModelCooldowns");const R=l(async({db:t,person:a},e)=>(await k(t,a,e)).map(({entry:n})=>n),"publicPool"),lt=l(t=>t?.code===xt,"lockWaitExpired");function ct(t,a){let e;return Promise.race([a,new Promise((n,i)=>{e=setTimeout(()=>i(new Nt(t)),at)})]).finally(()=>clearTimeout(e))}l(ct,"withExchangeTimeout");async function V(t){return(await St(t)).map(({entry:a})=>a)}l(V,"readSubscriptionPool");async function St(t){const a=await y();return(await k(await W(),a,t)).map(({row:e,entry:n})=>({id:e.id,entry:n}))}l(St,"readSubscriptionPoolRows");async function qt(t,a){return L(t,async e=>{const{db:n,person:i,rows:r}=e,s=a.map(d=>F(t,d)),o=s.map((d,u)=>({entry:d,id:r[u]?.row.id??x()})),c=await S(n,i,t);await n.prepare("DELETE FROM model_accounts WHERE person=? AND provider_id=?").run(i,t),await U(n,i,t);for(const[d,{entry:u,id:m}]of o.entries()){await C(n,i,t,{providerId:t,id:m,label:u.label,labelKey:_(u.label),position:d,lastAttemptAt:u.lastAttemptAt,lastRateLimitedAt:u.lastRateLimitedAt,rateLimitedUntil:u.rateLimitedUntil,...u.authFailure?{authFailure:u.authFailure}:{}},E(u));for(const[f,A]of Object.entries(u.modelCooldowns??{}))await ot(n,i,t,m,f,A,c.get(m)?.find(D=>D.family===f)?.lastRateLimitedAt??u.lastRateLimitedAt)}return s})}l(qt,"writeSubscriptionPool");function H(t,a){const e=a.trim().toLowerCase(),n=Number.parseInt(e,10);return Number.isFinite(n)&&String(n)===e&&n>=1&&n<=t.length?n-1:t.findIndex(({entry:i})=>_(i.label)===e)}l(H,"resolve");const P=l((t,a)=>new O(`This ${h(t)} account is already added as \u201C${a}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`),"duplicateAccount");async function Tt(t,a){const e=F(t,a);return L(t,async n=>{const{db:i,person:r,rows:s}=n;if(s.some(({entry:d})=>_(d.label)===_(e.label)))throw new O(`${h(t)} subscription already exists: ${e.label}`);const o=p(e),c=s.find(({entry:d})=>p(d)===o);if(o&&c)throw P(t,c.entry.label);return await C(i,r,t,{providerId:t,id:x(),label:e.label,labelKey:_(e.label),position:s.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil,...e.authFailure?{authFailure:e.authFailure}:{}},E(e)),R(n,t)})}l(Tt,"addSubscription");async function Xt(t,a){const e=F(t,a);return(await V(t)).find(i=>_(i.label)===_(e.label))?Ut(t,e):Tt(t,e)}l(Xt,"upsertSubscription");async function Ut(t,a){const e=F(t,a);return L(t,async n=>{const{db:i,person:r,rows:s}=n,o=s.findIndex(({entry:f})=>_(f.label)===_(e.label)),c=s[o],d=s.find(({entry:f},A)=>A!==o&&p(f)===p(e));if(p(e)&&d)throw P(t,d.entry.label);if(!c)return await C(i,r,t,{providerId:t,id:x(),label:e.label,labelKey:_(e.label),position:s.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil},E(e)),R(n,t);const u=c.row,m=e.lastRateLimitedAt>=u.lastRateLimitedAt;return await M(i,r,t,u.id,E(e)),await b(i,r,{...u,authFailure:void 0,label:e.label,labelKey:_(e.label),lastAttemptAt:Math.max(u.lastAttemptAt,e.lastAttemptAt),lastRateLimitedAt:m?e.lastRateLimitedAt:u.lastRateLimitedAt,rateLimitedUntil:m?e.rateLimitedUntil:u.rateLimitedUntil}),v(c.entry,e)&&await U(i,r,t,u.id),R(n,t)})}l(Ut,"upsertSubscriptionWithUniqueAccount");async function Yt(t,a,e){return L(t,async n=>{const{db:i,person:r,rows:s}=n,o=H(s,a);if(o<0)throw new Error(`Unknown ${h(t)} subscription: ${a}`);const c=e.trim();if(!c)throw new Error(`${h(t)} subscription label cannot be empty`);if(s.some((d,u)=>u!==o&&_(d.entry.label)===_(c)))throw new O(`${h(t)} subscription already exists: ${c}`);return await b(i,r,{...s[o].row,label:c,labelKey:_(c)}),R(n,t)})}l(Yt,"renameSubscription");async function G(t,a,e){return L(t,async n=>{const{db:i,person:r,rows:s}=n,o=a(s);if(o<0)throw new Error(`Unknown ${h(t)} subscription: ${e}`);const c=s[o].row;return await i.prepare("DELETE FROM model_accounts WHERE person=? AND provider_id=? AND id=?").run(r,t,c.id),await U(i,r,t,c.id),R(n,t)})}l(G,"remove");async function Dt(t,a){return G(t,e=>H(e,a),a)}l(Dt,"removeManagedAccount");async function Jt(t,a){return G(t,e=>e.findIndex(({row:n})=>n.id===a),a)}l(Jt,"removeManagedAccountById");async function Qt(t,a){return G(t,e=>H(e,a),a)}l(Qt,"removeSubscription");async function $t(t){return L(t,async({db:a,person:e,rows:n})=>(await a.prepare("DELETE FROM model_accounts WHERE person=? AND provider_id=?").run(e,t),await U(a,e,t),n.length))}l($t,"removeAllManagedAccounts");async function Zt(t,a){return a===void 0?(await $t(t),[]):Dt(t,a)}l(Zt,"removeManagedProvider");async function vt(t,a){return L(t,async e=>{const{db:n,person:i,rows:r}=e,s=H(r,a);if(s<0)throw new Error(`Unknown ${h(t)} subscription: ${a}`);const o=[r[s],...r.filter((c,d)=>d!==s)];for(const[c,d]of o.entries())await b(n,i,{...d.row,position:c});return R(e,t)})}l(vt,"promoteSubscription");async function z(t,a,e){const n=await y();return await N(async i=>{await T(i);const r=await st(i,n,t,a);if(!r)throw new Error(`Unknown ${h(t)} subscription: ${a}`);await e(i,n,r.row)}),V(t)}l(z,"healthWrite");async function te(t,a,e=Date.now()){return z(t,a,(n,i,r)=>b(n,i,{...r,lastAttemptAt:Math.max(r.lastAttemptAt,e)}))}l(te,"markSubscriptionAttempt");async function ee(t,a,e,n=Date.now(),i=Date.now(),r={kind:"account"}){return e=Math.min(Math.max(0,e),B),z(t,a,async(s,o,c)=>{if(r.kind==="account"){if(i<c.lastRateLimitedAt)return;await b(s,o,{...c,lastAttemptAt:Math.max(c.lastAttemptAt,n),lastRateLimitedAt:i,rateLimitedUntil:i+e});return}await b(s,o,{...c,lastAttemptAt:Math.max(c.lastAttemptAt,n)});const d=(await S(s,o,t)).get(c.id)?.find(u=>u.family===r.family);d&&i<d.lastRateLimitedAt||await ot(s,o,t,c.id,r.family,i+e,i)})}l(ee,"markSubscriptionRateLimited");async function ae(t,a,e=Date.now(),n){const i=wt(n);return z(t,a,async(r,s,o)=>{if(e>=o.lastRateLimitedAt&&await b(r,s,{...o,authFailure:void 0,lastAttemptAt:Math.max(o.lastAttemptAt,e),rateLimitedUntil:0}),i===void 0)return;const c=(await S(r,s,t)).get(o.id)?.find(d=>d.family===i);c&&e>=c.lastRateLimitedAt&&await r.prepare("DELETE FROM model_account_cooldowns WHERE person=? AND provider_id=? AND account_id=? AND family=?").run(s,t,o.id,i)})}l(ae,"markSubscriptionSuccess");async function ut(t,a,e){const n=Date.now();await b(t,a,{...e,authFailure:"invalid_grant",lastAttemptAt:Math.max(e.lastAttemptAt,n),rateLimitedUntil:Math.max(e.rateLimitedUntil,n+Rt)})}l(ut,"recordInvalidGrant");async function ne(t,a,e){const n=await y();let i;try{i=await N(async r=>{await T(r);const s=await st(r,n,t,a);if(!s)throw new Error(`${h(t)} subscription "${a}" no longer exists`);if(s.entry.expires>Date.now())return{entry:s.entry};if(s.row.authFailure)throw new Error(`${h(t)} subscription "${s.entry.label}" refresh refused with status=400 invalid_grant; it needs a new sign-in`);let o;try{o=await ct(t,e(s.entry.refresh,s.entry.clientId))}catch(d){if(!Y(d))throw d;return await ut(r,n,s.row),{refused:d}}const c=tt(et(t,s.entry.label,o),o.clientId??s.entry.clientId);return await M(r,n,t,s.row.id,E({...s.entry,...c})),{entry:{...s.entry,...c}}})}catch(r){throw lt(r)?new nt(t,a):r}if("refused"in i)throw i.refused;return i.entry}l(ne,"refreshSubscriptionCredential");async function ie(t,a,e){const n=await y();let i;try{i=await N(async r=>{await T(r);const s=await k(r,n,t,!0),o=new Set;let c;for(;o.size<s.length;){const d=a(s.filter(({entry:m})=>!o.has(m.label)).map(({entry:m})=>m));if(!d)break;const u=s.find(({entry:m})=>m.label===d.label);o.add(u.entry.label);try{const m=await ct(t,e(u.entry));if(!m)return{entry:u.entry};const f=tt(et(t,u.entry.label,m),m.clientId??u.entry.clientId);return await M(r,n,t,u.row.id,E({...u.entry,...f})),{entry:{...u.entry,...f}}}catch(m){if(!Y(m))throw m;c=m,await ut(r,n,u.row)}}return c?{refused:c}:{}})}catch(r){throw lt(r)?new nt(t,a([])?.label??t):r}if("refused"in i)throw i.refused;return i.entry}l(ie,"modifyElectedSubscription");async function Ot(t,a){const e=a.accountId?.trim(),n=a.email?.trim();if(t!==Lt)return{...e?{accountId:e}:{},...n?{email:n}:{}};try{const i=await fetch("https://api.anthropic.com/api/oauth/profile",{headers:{Authorization:`Bearer ${a.access}`,Accept:"application/json"},signal:AbortSignal.timeout(3e3)});if(!i.ok)return e?{accountId:e}:{};const r=await i.json(),s=typeof r.account?.uuid=="string"&&r.account.uuid.trim()?r.account.uuid.trim():void 0,o=typeof r.account?.email=="string"&&r.account.email.trim()?r.account.email.trim():void 0;return{...s?{accountId:s}:{},...o?{email:o}:{}}}catch{return e?{accountId:e}:{}}}l(Ot,"enrichManagedLoginIdentity");async function re(t,a,e){const n=await Ot(t,e);return L(t,async i=>{const{db:r,person:s,rows:o}=i,c=e.autoLabel?void 0:a?.trim(),d=p({refresh:e.refresh,accountId:n.accountId}),u=(c?o.find(({entry:w})=>_(w.label)===_(c)):void 0)??o.find(({entry:w})=>p(w)===d);let m=u?.entry.label??c;if(!m&&e.autoLabel){const w=n.email||a?.trim()||`Account ${o.length+1}`;m=w;for(let g=2;o.some(({entry:ft})=>_(ft.label)===_(m));g++)m=`${w} (${g})`}if(!m)throw new Error(`${h(t)} subscription label cannot be empty`);const f=F(t,{label:m,refresh:e.refresh,access:e.access,expires:e.expires,...n.accountId?{accountId:n.accountId}:{},...e.clientId?{clientId:e.clientId}:{},rateLimitedUntil:0,lastAttemptAt:Date.now(),lastRateLimitedAt:e.lastRateLimitedAt});if(!u){if(o.some(({entry:g})=>_(g.label)===_(f.label)))throw new O(`${h(t)} subscription already exists: ${f.label}`);const w=o.find(({entry:g})=>p(g)===p(f));if(p(f)&&w)throw P(t,w.entry.label);return await C(r,s,t,{providerId:t,id:x(),label:f.label,labelKey:_(f.label),position:o.length,lastAttemptAt:f.lastAttemptAt,lastRateLimitedAt:f.lastRateLimitedAt,rateLimitedUntil:f.rateLimitedUntil},E(f)),R(i,t)}const A=u.row,D=o.find(({entry:w})=>w.label!==u.entry.label&&p(w)===p(f));if(p(f)&&D)throw P(t,D.entry.label);const q=f.lastRateLimitedAt>=A.lastRateLimitedAt;return await M(r,s,t,A.id,E(f)),await b(r,s,{...A,authFailure:void 0,lastAttemptAt:Math.max(A.lastAttemptAt,f.lastAttemptAt),lastRateLimitedAt:q?f.lastRateLimitedAt:A.lastRateLimitedAt,rateLimitedUntil:q?0:A.rateLimitedUntil}),v(u.entry,f)&&await U(r,s,t,A.id),R(i,t)})}l(re,"commitManagedLogin");const dt=new Map;function mt(t){return{defaultFallbackStrength:Q(t?.default_fallback_strength)??J,revertWhenAvailable:t?.revert_when_available===void 0||t.revert_when_available===null?!0:!!t.revert_when_available}}l(mt,"rotationFromRow");async function kt(){const t=await y(),a=mt(await(await W()).prepare("SELECT default_fallback_strength,revert_when_available FROM model_settings WHERE person=?").get(t));return dt.set(t,a),a}l(kt,"loadRotationConfig"),yt(()=>Ct());async function Ct(){const t=pt();return t?t.rotation:kt()}l(Ct,"readRotationConfig");async function se(t){const a=await y(),e=await N(async n=>{await T(n),await n.prepare("INSERT INTO model_settings(person) VALUES(?) ON CONFLICT(person) DO NOTHING").run(a);const i=mt(await n.prepare("SELECT default_fallback_strength,revert_when_available FROM model_settings WHERE person=? FOR UPDATE").get(a)),r={defaultFallbackStrength:Q(t.defaultFallbackStrength)??i.defaultFallbackStrength??J,revertWhenAvailable:t.revertWhenAvailable??i.revertWhenAvailable??!0};return await n.prepare("UPDATE model_settings SET default_fallback_strength=?,revert_when_available=?,revision=revision+1 WHERE person=?").run(r.defaultFallbackStrength,r.revertWhenAvailable?1:0,a),r});return dt.set(a,e),e}l(se,"writeRotationConfig");async function oe(){const t=await y();return(await(await W()).prepare("INSERT INTO model_settings(person,openai_device_id) VALUES(?,?) ON CONFLICT(person) DO UPDATE SET openai_device_id=COALESCE(model_settings.openai_device_id,EXCLUDED.openai_device_id) RETURNING openai_device_id").get(t,x())).openai_device_id}l(oe,"openAIDeviceId");async function le(t,a=Date.now(),e){const n=await V(t),i=n.filter(s=>s.authFailure===void 0&&Et(s,e)<=a),r=h(t);return i.length===0?`${r} pool exhausted`:`${r} ${i[0]?.label??t} (${i.length}/${n.length} ready)`}l(le,"formatStatusLine");export{at as REFRESH_EXCHANGE_TIMEOUT_MS,nt as RefreshBusyError,Nt as RefreshTimeoutError,I as aadOf,Tt as addSubscription,re as commitManagedLogin,Ot as enrichManagedLoginIdentity,le as formatStatusLine,C as insertAccount,kt as loadRotationConfig,te as markSubscriptionAttempt,ee as markSubscriptionRateLimited,ae as markSubscriptionSuccess,ie as modifyElectedSubscription,oe as openAIDeviceId,vt as promoteSubscription,Ct as readRotationConfig,V as readSubscriptionPool,St as readSubscriptionPoolRows,ne as refreshSubscriptionCredential,$t as removeAllManagedAccounts,Dt as removeManagedAccount,Jt as removeManagedAccountById,Zt as removeManagedProvider,Qt as removeSubscription,Yt as renameSubscription,ot as upsertModelCooldown,Xt as upsertSubscription,Ut as upsertSubscriptionWithUniqueAccount,se as writeRotationConfig,qt as writeSubscriptionPool};
@@ -13,6 +13,8 @@ export declare function typedLimitKey(owner: ObjectRow): string;
13
13
  * reader's own profile is never capped, even when a bare key shares its handle
14
14
  * with another owner; an owner the profile does not name is uncapped. */
15
15
  export declare function deliveryLimitFor(manifest: ProfileManifest | null, owner: ObjectRow, reader?: Reader): Rung;
16
+ /** Fetch the bodies of every document in `docs` that has not read its own, in one query, so each `body()` call after it answers from memory. */
17
+ export declare function primeBodies(db: CanvasDb, docs: readonly DeliveryDoc[]): Promise<void>;
16
18
  /** Documents with a rule on one of `on` that the reader may read, in view order. At `boot` a node's own documents come in even without a
17
19
  * boot rule. */
18
20
  export declare function loadCorpus(db: CanvasDb, reader: Reader, on: RuleOn[]): Promise<DeliveryDoc[]>;
@@ -1,3 +1,3 @@
1
- var S=Object.defineProperty;var a=(n,e)=>S(n,"name",{value:e,configurable:!0});import{permits as D,resolveReader as L}from"../../permissions/evaluator.js";import{bodyOf as M}from"../../graph/bodies.js";import{resolveName as A,viewOf as v,viewResolutions as $}from"../../graph/names.js";import{rulesOn as C,rulesOf as g}from"../../graph/rules.js";import{loadExactProfileManifest as H}from"../../profiles/manifest.js";import{assembleNodeSubject as I}from"../subject.js";import{mapAsync as x}from"../../async-array.js";function R(n){return n.type==="node"?n.id:n.name}a(R,"ownerHandle");function T(n){return`${n.type}:${R(n)}`}a(T,"typedLimitKey");function q(n,e,o){if(o?.class==="node"&&o.profile!==null&&o.profile.id===e.id)return"content";const i=n?.delivery_limits;return i?i[T(e)]??i[R(e)]??"content":"content"}a(q,"deliveryLimitFor");async function N(n,e){if(e.class!=="node"||e.profile===null)return null;const o=await n.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'profile'").get(e.profile.id);if(!o)throw new Error(`profile object ${e.profile.id} has no profile identity`);return H(o.external_id).manifest}a(N,"readerManifest");async function h(n,e,o,i){const t=await D(n,e,"read","o");return await n.prepareUncached(`SELECT o.*, d.heads_key, d.unlisted, d.source_path FROM objects o JOIN documents d ON d.object = o.id
2
- WHERE o.type = 'document' AND o.deleted_at IS NULL AND ${o} AND ${t.sql} ORDER BY o.seq`).all(...i,...t.params)}a(h,"documentRows");async function F(n,e){const o=e.heads_key.split(","),i=await n.prepareUncached(`SELECT body_hash FROM revisions WHERE document = ? AND rev_id IN (${o.map(()=>"?").join(",")})
3
- ORDER BY number DESC LIMIT 1`).get(e.id,...o);if(!i)throw new Error(`document ${e.id} has no head revision ${e.heads_key}`);return i.body_hash}a(F,"headBodyHash");async function j(n,e,o){return{view:o,ev:await L(n,e)}}a(j,"resolveContext");async function O(n,e,o,i,t,w,c){const{view:r}=o,p=r.findIndex(k=>k.id===t.owner),d=p>=0?r[p]:await n.prepare("SELECT * FROM objects WHERE id = ?").get(t.owner),u=R(d),m=c?.get(t.name),y=m===void 0?await A(n,e,t.name,o):m===null?{missing:!0}:{object:{id:m}},f=await F(n,t),{heads_key:E,unlisted:s,source_path:l,..._}=t;return{object:_,owner:d,ownerHandle:u,viewRank:p>=0?p:r.length,ownNode:e.class==="node"&&t.owner===e.node.id,shipped:l!==null,kind:t.kind==="preference"?"preference":"knowledge",name:t.name,displayName:"object"in y&&y.object.id===t.id?t.name:`${u}/${t.name}`,preview:t.preview,unlisted:s!==0,headsKey:E,bodyHash:f,body:a(()=>M(f,l),"body"),rules:w,limit:q(i,d,e)}}a(O,"toDeliveryDoc");async function X(n,e,o){const i=await v(n,e),t=await j(n,e,i),w=await N(n,e),c=new Map,r=await D(n,t.ev,"read","o"),p={sql:`${r.sql} AND (NOT EXISTS (SELECT 1 FROM objects own WHERE own.id = o.owner AND own.type = 'node') OR o.owner = ?)`,params:[...r.params,e.class==="node"?e.node.id:""]};for(const s of await C(n,o,p))c.set(s.object,[...c.get(s.object)??[],s]);const d=e.class==="node"&&o.includes("boot")?await h(n,t.ev,"o.owner = ?",[e.node.id]):[],u=[...new Set([...c.keys(),...d.map(s=>s.id)])],m=u.length===0?[]:await h(n,t.ev,`o.id IN (${u.map(()=>"?").join(",")})`,u),y=new Map(i.map((s,l)=>[s.id,l])),f=a(s=>y.get(s.owner)??i.length,"rankOf"),E=await $(n,t,m.map(s=>s.name));return await x(m.sort((s,l)=>f(s)-f(l)||s.seq-l.seq),async s=>await O(n,e,t,w,s,c.get(s.id)??[],E))}a(X,"loadCorpus");async function z(n,e,o){const i=await j(n,e,await v(n,e)),t=(await h(n,i.ev,"o.id = ?",[o.id]))[0];if(!t)throw new Error(`document ${o.id} has no documents row or is not readable`);return await O(n,e,i,await N(n,e),t,await g(n,t))}a(z,"deliveryDocOf");async function G(n,e,o){const i=await j(n,e,await v(n,e)),t=await N(n,e),w=await h(n,i.ev,"o.owner = ? AND d.unlisted = 0",[o.id]),c=await $(n,i,w.map(r=>r.name));return await x(w,async r=>await O(n,e,i,t,r,await g(n,r),c))}a(G,"listingCorpus");async function Q(n,e){if(e.class!=="node")throw new Error(`nodeSubject needs a node reader, got ${e.class}`);const o=await I(e.node.id,[...e.repos,...e.projectRepos]);if(o===null)throw new Error(`no node ${e.node.id}`);return o}a(Q,"nodeSubject");export{z as deliveryDocOf,q as deliveryLimitFor,G as listingCorpus,X as loadCorpus,Q as nodeSubject,R as ownerHandle,T as typedLimitKey};
1
+ var H=Object.defineProperty;var a=(t,e)=>H(t,"name",{value:e,configurable:!0});import{permits as D,resolveReader as L}from"../../permissions/evaluator.js";import{get as A,getMany as C}from"../../graph/bodies.js";import{resolveName as I,viewOf as g,viewResolutions as $}from"../../graph/names.js";import{rulesOn as T,rulesOf as k}from"../../graph/rules.js";import{loadExactProfileManifest as B}from"../../profiles/manifest.js";import{assembleNodeSubject as q}from"../subject.js";import{mapAsync as _}from"../../async-array.js";function N(t){return t.type==="node"?t.id:t.name}a(N,"ownerHandle");function F(t){return`${t.type}:${N(t)}`}a(F,"typedLimitKey");function W(t,e,o){if(o?.class==="node"&&o.profile!==null&&o.profile.id===e.id)return"content";const i=t?.delivery_limits;return i?i[F(e)]??i[N(e)]??"content":"content"}a(W,"deliveryLimitFor");async function j(t,e){if(e.class!=="node"||e.profile===null)return null;const o=await t.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'profile'").get(e.profile.id);if(!o)throw new Error(`profile object ${e.profile.id} has no profile identity`);return B(o.external_id).manifest}a(j,"readerManifest");async function h(t,e,o,i){const n=await D(t,e,"read","o");return await t.prepareUncached(`SELECT o.*, d.heads_key, d.unlisted, d.source_path FROM objects o JOIN documents d ON d.object = o.id
2
+ WHERE o.type = 'document' AND o.deleted_at IS NULL AND ${o} AND ${n.sql} ORDER BY o.seq`).all(...i,...n.params)}a(h,"documentRows");async function b(t,e){const o=e.heads_key.split(","),i=await t.prepareUncached(`SELECT body_hash FROM revisions WHERE document = ? AND rev_id IN (${o.map(()=>"?").join(",")})
3
+ ORDER BY number DESC LIMIT 1`).get(e.id,...o);if(!i)throw new Error(`document ${e.id} has no head revision ${e.heads_key}`);return i.body_hash}a(b,"headBodyHash");async function O(t,e,o){return{view:o,ev:await L(t,e)}}a(O,"resolveContext");async function x(t,e,o,i,n,l,c){const{view:r}=o,w=r.findIndex(M=>M.id===n.owner),m=w>=0?r[w]:await t.prepare("SELECT * FROM objects WHERE id = ?").get(n.owner),p=N(m),u=c?.get(n.name),y=u===void 0?await I(t,e,n.name,o):u===null?{missing:!0}:{object:{id:u}},f=await b(t,n),{heads_key:v,unlisted:s,source_path:d,...S}=n,R={object:S,owner:m,ownerHandle:p,viewRank:w>=0?w:r.length,ownNode:e.class==="node"&&n.owner===e.node.id,shipped:d!==null,kind:n.kind==="preference"?"preference":"knowledge",name:n.name,displayName:"object"in y&&y.object.id===n.id?n.name:`${p}/${n.name}`,preview:n.preview,unlisted:s!==0,headsKey:v,bodyHash:f,body:a(async()=>E.get(R)??U(R,await A(t,f)),"body"),rules:l,limit:W(i,m,e)};return R}a(x,"toDeliveryDoc");const E=new WeakMap;function U(t,e){return E.set(t,e),e}a(U,"remember");async function V(t,e){const o=e.filter(n=>!E.has(n)),i=await C(t,o.map(n=>n.bodyHash));for(const n of o)E.set(n,i.get(n.bodyHash))}a(V,"primeBodies");async function Z(t,e,o){const i=await g(t,e),n=await O(t,e,i),l=await j(t,e),c=new Map,r=await D(t,n.ev,"read","o"),w={sql:`${r.sql} AND (NOT EXISTS (SELECT 1 FROM objects own WHERE own.id = o.owner AND own.type = 'node') OR o.owner = ?)`,params:[...r.params,e.class==="node"?e.node.id:""]};for(const s of await T(t,o,w))c.set(s.object,[...c.get(s.object)??[],s]);const m=e.class==="node"&&o.includes("boot")?await h(t,n.ev,"o.owner = ?",[e.node.id]):[],p=[...new Set([...c.keys(),...m.map(s=>s.id)])],u=p.length===0?[]:await h(t,n.ev,`o.id IN (${p.map(()=>"?").join(",")})`,p),y=new Map(i.map((s,d)=>[s.id,d])),f=a(s=>y.get(s.owner)??i.length,"rankOf"),v=await $(t,n,u.map(s=>s.name));return await _(u.sort((s,d)=>f(s)-f(d)||s.seq-d.seq),async s=>await x(t,e,n,l,s,c.get(s.id)??[],v))}a(Z,"loadCorpus");async function ee(t,e,o){const i=await O(t,e,await g(t,e)),n=(await h(t,i.ev,"o.id = ?",[o.id]))[0];if(!n)throw new Error(`document ${o.id} has no documents row or is not readable`);return await x(t,e,i,await j(t,e),n,await k(t,n))}a(ee,"deliveryDocOf");async function te(t,e,o){const i=await O(t,e,await g(t,e)),n=await j(t,e),l=await h(t,i.ev,"o.owner = ? AND d.unlisted = 0",[o.id]),c=await $(t,i,l.map(r=>r.name));return await _(l,async r=>await x(t,e,i,n,r,await k(t,r),c))}a(te,"listingCorpus");async function ne(t,e){if(e.class!=="node")throw new Error(`nodeSubject needs a node reader, got ${e.class}`);const o=await q(e.node.id,[...e.repos,...e.projectRepos]);if(o===null)throw new Error(`no node ${e.node.id}`);return o}a(ne,"nodeSubject");export{ee as deliveryDocOf,W as deliveryLimitFor,te as listingCorpus,Z as loadCorpus,ne as nodeSubject,N as ownerHandle,V as primeBodies,F as typedLimitKey};
@@ -1,6 +1,6 @@
1
- var A=Object.defineProperty;var d=(t,e)=>A(t,"name",{value:e,configurable:!0});import{dirname as L}from"node:path";import{clearLedger as F,demoteTranscript as $,freezePreferences as g,loadLedger as M,saveLedger as P}from"../../graph/exposures.js";import{recordWatch as T}from"../../graph/watches.js";import{publish as H}from"../../graph/events.js";import{viewOf as x}from"../../graph/names.js";import{checkoutsForCwd as y}from"../../graph/repo-sync/repos.js";import{liveLookups as W,profileObject as Y,viewRepos as B}from"../../graph/reader.js";import{ownerForGrantee as U}from"../../graph/objects.js";import{resolveName as J}from"../../graph/names.js";import{SCOPE_VOCABULARY as K}from"../../scopes.js";import{getNode as V}from"../../canvas/index.js";import{recordMemoryReadsIn as G}from"../../canvas/memory-reads.js";import{lookupProfile as X}from"../../profiles/manifest.js";import{getDefaultProfileId as z}from"../../profiles/default-binding.js";import{effectiveScopes as Q,profileNameFor as Z,scopeForCwd as q}from"../subject-fields.js";import{spineDepth as ee}from"../subject.js";import{appForNode as te}from"../../canvas/node-agent-paths.js";import{interpolateNodePaths as ne}from"../../canvas/paths.js";import{readableProjectForScopes as oe}from"../../spaces/permissions.js";import{ApiError as u}from"../../../api/errors.js";import{buildContextBearings as re}from"../../runtime/bearings.js";import{deliveryDocOf as b,listingCorpus as ae,loadCorpus as w,nodeSubject as j}from"./corpus.js";import{listingPrefixes as ie}from"./listings.js";import{planDelivery as se}from"./plan.js";import{renderEvent as N,renderExplicitRead as k}from"./render-event.js";import{renderConfigDelta as ce,renderKnowledge as de,renderPreferences as O,renderWorkspaceOpen as _}from"./render-boot.js";import{mapAsync as le}from"../../async-array.js";function f(t){if(t.class!=="node")throw new Error(`delivery needs a node reader, got ${t.class}`);return t}d(f,"nodeOf");const S="hypothetical";function v(t){return t.node.id===S}d(v,"isHypothetical");async function R(t,e){if(!v(e))return await te(e.node.id);const n=await t.prepare("SELECT external_id FROM identities WHERE object = ?").get(e.app.id);if(!n)throw new Error(`app object ${e.app.id} has no identity`);return n.external_id}d(R,"granteeOf");async function pe(t,e,n,r=async a=>await y(t,a)){const a=f(e).node,c=new Set,o=new Date().toISOString(),i=[];for(const{doc:s,level:l}of n)l!=="content"||c.has(s.object.id)||(c.add(s.object.id),await T(t,a,s.object,"deliver",{readHeadsKey:s.headsKey}),await H(t,{event:"read",object:s.object,actor:a,attrs:{}},r),i.push({name:s.name,scope:s.ownerHandle,at:o,type:"loaded"}));v(f(e))||await G(t,a.id,i)}d(pe,"recordContentDelivery");async function E(t,e,n){const r=(await y(t,L(n))).filter(a=>!e.repos.some(c=>c.id===a.repo.id));return r.length===0?e:{...e,repos:[...e.repos,...r.map(a=>a.repo)],checkouts:[...e.checkouts,...r]}}d(E,"fileReadReader");async function ue(t,e,n){const r=await R(t,e);if(!(n.startsWith("/")&&(process.platform!=="linux"&&r==="app:terminal"||oe(n,r,e.scopes??[],null))))throw new u(403,"scope_missing","read file is outside the run mounts")}d(ue,"assertReadableFile");async function me(t){const e=f(t.reader),n=e.node,r=await R(t.db,e),a=await w(t.db,t.reader,["slash-command"]),c=a.map(o=>{const i=o.body();return{name:o.displayName,command_name:o.displayName,description:o.preview,canonical_body:i,execution_body:ne(i,n.id,r)}});return{docs:a,commands:c}}d(me,"slashCommands");function fe(t){if(t.shipped)return"builtin";switch(t.owner.type){case"node":return"node";case"repo":return"project";case"profile":return"profile";default:return"user"}}d(fe,"scopeOf");async function we(t,e){const n=[],r=new Set;for(const a of await x(t,e)){const c=await ae(t,e,a);for(const o of c){n.push({name:o.displayName,kind:o.kind,scope:fe(o),shortForm:o.preview,gatewayVisible:!o.shipped}),r.add(o.displayName);const i=o.displayName===o.name?"":`${o.ownerHandle}/`;for(const s of ie(c,o.name))r.add(`${i}${s}`)}}return{refs:n,names:[...r]}}d(we,"composerNames");async function D(t,e){const n=f(t.reader),{db:r,ledger:a}=t,c=d(()=>{if(typeof e.subject!="string"||e.subject==="")throw new u(400,"usage",`delivery activity ${e.activity} requires a subject`);return e.subject},"requireSubject");switch(e.activity){case"boot":{if(e.inherited===!0){for(const[s,l]of a.objects)if(l.transcript===void 0)a.objects.delete(s);else{const{system:p,...m}=l;a.objects.set(s,m)}for(const[s,l]of a.listed)l.transcript===void 0?a.listed.delete(s):a.listed.set(s,{transcript:l.transcript});return a.preferenceSnapshot=null,{result:{content:"",inherited:!0},delivered:[]}}const o=v(n)?await ye(t):await re(n.node.id,t),i=await O({...t,channel:"system"});return g(a,i.content),{result:{content:o.content},delivered:[...o.delivered,...i.delivered]}}case"system-prompt":{if(a.preferenceSnapshot!==null)return{result:{content:a.preferenceSnapshot},delivered:[]};const o=await O({...t,channel:"system"});return{result:{content:g(a,o.content)},delivered:o.delivered}}case"compact":{for(const o of await w(r,n,["pre-command"]))$(a,o.object.id);return{result:{content:""},delivered:[]}}case"persona":{const o=await ce(t);return{result:{content:o.content},delivered:o.delivered}}case"file-read":{const o=c();await ue(r,n,o);const i=await E(r,n,o),s={...t,reader:i},l=await N(s,"file-read",{absoluteFile:o},await w(r,i,["file-read"]));return{result:{content:l.content},delivered:l.delivered}}case"command":case"pre-command":{const o=await N(t,e.activity,{command:c()});return{result:{content:o.content},delivered:o.delivered}}case"slash-command":{const{docs:o,commands:i}=await me(t);if(e.subject===void 0||e.subject==="")return{result:{content:"",commands:i},delivered:[]};const s=o.findIndex(m=>m.displayName===e.subject||m.name===e.subject);if(s<0)throw new u(404,"not_found",`no slash command named ${e.subject}`);const l=o[s],p=await k(t,l,!0);return{result:{content:"",execution_body:i[s].execution_body,blocks:p.attachments===""?[]:[p.attachments]},delivered:[{doc:l,level:"content"},...p.delivered]}}case"workspace-open":{const o=await _(t);return{result:{content:o.content},delivered:o.delivered}}case"document-read":{const o=await C(r,n,c()),i=await k(t,await b(r,n,o),!0);return{result:{content:i.attachments},delivered:i.delivered}}case"names":return{result:{content:"",...await we(r,n)},delivered:[]};default:throw new u(400,"usage",`unknown delivery activity ${String(e.activity)}`)}}d(D,"render");async function ye(t){const e=await de(t),n=await _(t);return{content:[e.content,n.content].filter(r=>r!=="").join(`
2
- `),delivered:[...e.delivered,...n.delivered]}}d(ye,"hypotheticalBearings");async function C(t,e,n){const r=await J(t,e,n);if("object"in r&&r.object.type==="document")return r.object;throw"ambiguous"in r?new u(409,"ambiguous",`${n} names ${r.ambiguous.length} documents; name one as <type>:<owner>/<name>`):new u(404,"not_found",`no document ${n} the reader can read`)}d(C,"readDocument");const ve=["workspace-open","document-read"];async function Ge(t,e,n){if(ve.includes(n.activity))throw new u(400,"usage",`delivery activity ${n.activity} is available to a dry run only`);const r=f(e).node;n.activity==="boot"&&n.inherited!==!0&&await F(t,r.id);const a=await M(t,r.id),c={db:t,reader:e,subject:await j(t,e),ledger:a,channel:"transcript"},{result:o,delivered:i}=await D(c,n);return await pe(t,e,i),await P(t,a),o}d(Ge,"deliver");const he={boot:"boot","workspace-open":"workspace-open","file-read":"file-read","document-read":"document-read",command:"command","pre-command":"pre-command","slash-command":"slash-command"};async function Xe(t,e){const n=await V(t);if(n===null)throw new u(404,"not_found",`no node ${t}`);return{cwd:e.cwd??n.cwd,profile:e.profile!==void 0?e.profile:n.profile_id,grantee:e.grantee??n.grantee,kind:e.kind??n.kind,mode:e.mode??n.mode,lifecycle:e.lifecycle??n.lifecycle,hasManager:e.hasManager??(n.parent??null)!==null,depth:e.depth??await ee(t)}}d(Xe,"hypotheticalFrom");function ge(t,e){if(t===null)return null;if(t===void 0||t==="")return z(e);const n=X(t);if(n.kind!=="resolved"){const r=`no profile ${t}`;throw new u(404,"not_found",r,{error:"not_found",message:r,next:"List profile ids and names with `crtr profile list`."})}return n.entry.profileId}d(ge,"resolveProfileId");async function ze(t,e){const n=await U(t,e.grantee??"app:terminal"),r=ge(e.profile,e.cwd),a=await B(e.cwd,r,async p=>await y(t,p),W(t)),{repos:c}=a,o=e.mode??"base",s={class:"node",node:{id:S,seq:0,type:"node",kind:e.kind??"general",owner:n.id,privacy:"private",name:"hypothetical",preview:"",created_at:new Date(0).toISOString(),deleted_at:null},app:n,scopes:[...K],cwd:e.cwd,...a,profile:r===null?null:await Y(t,r,n)},l={kind:e.kind??"general",mode:o,lifecycle:e.lifecycle??(o==="orchestrator"?"resident":"terminal"),hasManager:e.hasManager??!0,cwd:e.cwd,scope:q(e.cwd),orchestration:{depth:e.depth??0},profile:Z(r),app:e.grantee??"app:terminal",scopes:Q(null),repo:[...c,...a.projectRepos].map(p=>p.name)};return{reader:s,subject:l}}d(ze,"hypotheticalReader");async function Qe(t,e,n,r,a){const o={node:f(e).node.id,preferenceSnapshot:null,objects:new Map,listed:new Map},i={...a??await j(t,e),...r},s={db:t,reader:e,subject:i,ledger:o,channel:"transcript"},{result:l}=await D(s,n),p=he[n.activity];let m=null;if(p!==void 0){const I=n.activity==="file-read"?{absoluteFile:n.subject}:n.activity==="command"||n.activity==="pre-command"?{command:n.subject}:n.activity==="document-read"&&n.subject!==void 0?{doc:await b(t,e,await C(t,e,n.subject))}:{},h=n.activity==="file-read"&&n.subject!==void 0?await E(t,f(e),n.subject):e;m=await se(t,i,h,await w(t,h,[p]),p,I)}return{result:l,plan:m}}d(Qe,"inspect");async function Ze(t,e,n){const r=await t.prepare(`SELECT o.*, MAX(r.number) AS to_seq, string_agg(DISTINCT a.id, ',') AS authors
1
+ var L=Object.defineProperty;var d=(t,e)=>L(t,"name",{value:e,configurable:!0});import{dirname as F}from"node:path";import{clearLedger as $,demoteTranscript as M,freezePreferences as b,loadLedger as P,saveLedger as T}from"../../graph/exposures.js";import{recordWatch as H}from"../../graph/watches.js";import{publish as x}from"../../graph/events.js";import{viewOf as W}from"../../graph/names.js";import{checkoutsForCwd as y}from"../../graph/repo-sync/repos.js";import{liveLookups as Y,profileObject as B,viewRepos as U}from"../../graph/reader.js";import{ownerForGrantee as J}from"../../graph/objects.js";import{resolveName as K}from"../../graph/names.js";import{SCOPE_VOCABULARY as V}from"../../scopes.js";import{getNode as G}from"../../canvas/index.js";import{recordMemoryReadsIn as X}from"../../canvas/memory-reads.js";import{lookupProfile as z}from"../../profiles/manifest.js";import{getDefaultProfileId as Q}from"../../profiles/default-binding.js";import{effectiveScopes as Z,profileNameFor as q,scopeForCwd as ee}from"../subject-fields.js";import{spineDepth as te}from"../subject.js";import{appForNode as ne}from"../../canvas/node-agent-paths.js";import{interpolateNodePaths as oe}from"../../canvas/paths.js";import{readableProjectForScopes as re}from"../../spaces/permissions.js";import{ApiError as u}from"../../../api/errors.js";import{buildContextBearings as ae}from"../../runtime/bearings.js";import{deliveryDocOf as g,listingCorpus as ie,loadCorpus as w,nodeSubject as j,primeBodies as se}from"./corpus.js";import{listingPrefixes as ce}from"./listings.js";import{planDelivery as de}from"./plan.js";import{renderEvent as N,renderExplicitRead as k}from"./render-event.js";import{renderConfigDelta as le,renderKnowledge as pe,renderPreferences as O,renderWorkspaceOpen as _}from"./render-boot.js";import{mapAsync as S}from"../../async-array.js";function f(t){if(t.class!=="node")throw new Error(`delivery needs a node reader, got ${t.class}`);return t}d(f,"nodeOf");const R="hypothetical";function v(t){return t.node.id===R}d(v,"isHypothetical");async function E(t,e){if(!v(e))return await ne(e.node.id);const n=await t.prepare("SELECT external_id FROM identities WHERE object = ?").get(e.app.id);if(!n)throw new Error(`app object ${e.app.id} has no identity`);return n.external_id}d(E,"granteeOf");async function ue(t,e,n,r=async a=>await y(t,a)){const a=f(e).node,c=new Set,o=new Date().toISOString(),i=[];for(const{doc:s,level:l}of n)l!=="content"||c.has(s.object.id)||(c.add(s.object.id),await H(t,a,s.object,"deliver",{readHeadsKey:s.headsKey}),await x(t,{event:"read",object:s.object,actor:a,attrs:{}},r),i.push({name:s.name,scope:s.ownerHandle,at:o,type:"loaded"}));v(f(e))||await X(t,a.id,i)}d(ue,"recordContentDelivery");async function D(t,e,n){const r=(await y(t,F(n))).filter(a=>!e.repos.some(c=>c.id===a.repo.id));return r.length===0?e:{...e,repos:[...e.repos,...r.map(a=>a.repo)],checkouts:[...e.checkouts,...r]}}d(D,"fileReadReader");async function me(t,e,n){const r=await E(t,e);if(!(n.startsWith("/")&&(process.platform!=="linux"&&r==="app:terminal"||re(n,r,e.scopes??[],null))))throw new u(403,"scope_missing","read file is outside the run mounts")}d(me,"assertReadableFile");async function fe(t){const e=f(t.reader),n=e.node,r=await E(t.db,e),a=await w(t.db,t.reader,["slash-command"]);await se(t.db,a);const c=await S(a,async o=>{const i=await o.body();return{name:o.displayName,command_name:o.displayName,description:o.preview,canonical_body:i,execution_body:oe(i,n.id,r)}});return{docs:a,commands:c}}d(fe,"slashCommands");function we(t){if(t.shipped)return"builtin";switch(t.owner.type){case"node":return"node";case"repo":return"project";case"profile":return"profile";default:return"user"}}d(we,"scopeOf");async function ye(t,e){const n=[],r=new Set;for(const a of await W(t,e)){const c=await ie(t,e,a);for(const o of c){n.push({name:o.displayName,kind:o.kind,scope:we(o),shortForm:o.preview,gatewayVisible:!o.shipped}),r.add(o.displayName);const i=o.displayName===o.name?"":`${o.ownerHandle}/`;for(const s of ce(c,o.name))r.add(`${i}${s}`)}}return{refs:n,names:[...r]}}d(ye,"composerNames");async function C(t,e){const n=f(t.reader),{db:r,ledger:a}=t,c=d(()=>{if(typeof e.subject!="string"||e.subject==="")throw new u(400,"usage",`delivery activity ${e.activity} requires a subject`);return e.subject},"requireSubject");switch(e.activity){case"boot":{if(e.inherited===!0){for(const[s,l]of a.objects)if(l.transcript===void 0)a.objects.delete(s);else{const{system:p,...m}=l;a.objects.set(s,m)}for(const[s,l]of a.listed)l.transcript===void 0?a.listed.delete(s):a.listed.set(s,{transcript:l.transcript});return a.preferenceSnapshot=null,{result:{content:"",inherited:!0},delivered:[]}}const o=v(n)?await ve(t):await ae(n.node.id,t),i=await O({...t,channel:"system"});return b(a,i.content),{result:{content:o.content},delivered:[...o.delivered,...i.delivered]}}case"system-prompt":{if(a.preferenceSnapshot!==null)return{result:{content:a.preferenceSnapshot},delivered:[]};const o=await O({...t,channel:"system"});return{result:{content:b(a,o.content)},delivered:o.delivered}}case"compact":{for(const o of await w(r,n,["pre-command"]))M(a,o.object.id);return{result:{content:""},delivered:[]}}case"persona":{const o=await le(t);return{result:{content:o.content},delivered:o.delivered}}case"file-read":{const o=c();await me(r,n,o);const i=await D(r,n,o),s={...t,reader:i},l=await N(s,"file-read",{absoluteFile:o},await w(r,i,["file-read"]));return{result:{content:l.content},delivered:l.delivered}}case"command":case"pre-command":{const o=await N(t,e.activity,{command:c()});return{result:{content:o.content},delivered:o.delivered}}case"slash-command":{const{docs:o,commands:i}=await fe(t);if(e.subject===void 0||e.subject==="")return{result:{content:"",commands:i},delivered:[]};const s=o.findIndex(m=>m.displayName===e.subject||m.name===e.subject);if(s<0)throw new u(404,"not_found",`no slash command named ${e.subject}`);const l=o[s],p=await k(t,l,!0);return{result:{content:"",execution_body:i[s].execution_body,blocks:p.attachments===""?[]:[p.attachments]},delivered:[{doc:l,level:"content"},...p.delivered]}}case"workspace-open":{const o=await _(t);return{result:{content:o.content},delivered:o.delivered}}case"document-read":{const o=await I(r,n,c()),i=await k(t,await g(r,n,o),!0);return{result:{content:i.attachments},delivered:i.delivered}}case"names":return{result:{content:"",...await ye(r,n)},delivered:[]};default:throw new u(400,"usage",`unknown delivery activity ${String(e.activity)}`)}}d(C,"render");async function ve(t){const e=await pe(t),n=await _(t);return{content:[e.content,n.content].filter(r=>r!=="").join(`
2
+ `),delivered:[...e.delivered,...n.delivered]}}d(ve,"hypotheticalBearings");async function I(t,e,n){const r=await K(t,e,n);if("object"in r&&r.object.type==="document")return r.object;throw"ambiguous"in r?new u(409,"ambiguous",`${n} names ${r.ambiguous.length} documents; name one as <type>:<owner>/<name>`):new u(404,"not_found",`no document ${n} the reader can read`)}d(I,"readDocument");const he=["workspace-open","document-read"];async function Xe(t,e,n){if(he.includes(n.activity))throw new u(400,"usage",`delivery activity ${n.activity} is available to a dry run only`);const r=f(e).node;n.activity==="boot"&&n.inherited!==!0&&await $(t,r.id);const a=await P(t,r.id),c={db:t,reader:e,subject:await j(t,e),ledger:a,channel:"transcript"},{result:o,delivered:i}=await C(c,n);return await ue(t,e,i),await T(t,a),o}d(Xe,"deliver");const be={boot:"boot","workspace-open":"workspace-open","file-read":"file-read","document-read":"document-read",command:"command","pre-command":"pre-command","slash-command":"slash-command"};async function ze(t,e){const n=await G(t);if(n===null)throw new u(404,"not_found",`no node ${t}`);return{cwd:e.cwd??n.cwd,profile:e.profile!==void 0?e.profile:n.profile_id,grantee:e.grantee??n.grantee,kind:e.kind??n.kind,mode:e.mode??n.mode,lifecycle:e.lifecycle??n.lifecycle,hasManager:e.hasManager??(n.parent??null)!==null,depth:e.depth??await te(t)}}d(ze,"hypotheticalFrom");function ge(t,e){if(t===null)return null;if(t===void 0||t==="")return Q(e);const n=z(t);if(n.kind!=="resolved"){const r=`no profile ${t}`;throw new u(404,"not_found",r,{error:"not_found",message:r,next:"List profile ids and names with `crtr profile list`."})}return n.entry.profileId}d(ge,"resolveProfileId");async function Qe(t,e){const n=await J(t,e.grantee??"app:terminal"),r=ge(e.profile,e.cwd),a=await U(e.cwd,r,async p=>await y(t,p),Y(t)),{repos:c}=a,o=e.mode??"base",s={class:"node",node:{id:R,seq:0,type:"node",kind:e.kind??"general",owner:n.id,privacy:"private",name:"hypothetical",preview:"",created_at:new Date(0).toISOString(),deleted_at:null},app:n,scopes:[...V],cwd:e.cwd,...a,profile:r===null?null:await B(t,r,n)},l={kind:e.kind??"general",mode:o,lifecycle:e.lifecycle??(o==="orchestrator"?"resident":"terminal"),hasManager:e.hasManager??!0,cwd:e.cwd,scope:ee(e.cwd),orchestration:{depth:e.depth??0},profile:q(r),app:e.grantee??"app:terminal",scopes:Z(null),repo:[...c,...a.projectRepos].map(p=>p.name)};return{reader:s,subject:l}}d(Qe,"hypotheticalReader");async function Ze(t,e,n,r,a){const o={node:f(e).node.id,preferenceSnapshot:null,objects:new Map,listed:new Map},i={...a??await j(t,e),...r},s={db:t,reader:e,subject:i,ledger:o,channel:"transcript"},{result:l}=await C(s,n),p=be[n.activity];let m=null;if(p!==void 0){const A=n.activity==="file-read"?{absoluteFile:n.subject}:n.activity==="command"||n.activity==="pre-command"?{command:n.subject}:n.activity==="document-read"&&n.subject!==void 0?{doc:await g(t,e,await I(t,e,n.subject))}:{},h=n.activity==="file-read"&&n.subject!==void 0?await D(t,f(e),n.subject):e;m=await de(t,i,h,await w(t,h,[p]),p,A)}return{result:l,plan:m}}d(Ze,"inspect");async function qe(t,e,n){const r=await t.prepare(`SELECT o.*, MAX(r.number) AS to_seq, string_agg(DISTINCT a.id, ',') AS authors
3
3
  FROM watches w JOIN objects o ON o.id = w.object AND o.type = 'document' AND o.deleted_at IS NULL
4
4
  JOIN revisions r ON r.document = o.id LEFT JOIN objects a ON a.id = r.author
5
5
  WHERE w.watcher = ? AND r.created_at > ? AND (r.author IS NULL OR r.author != ?)
6
- GROUP BY o.id ORDER BY o.seq`).all(e.id,n,e.id);return await le(r,async({to_seq:a,authors:c,...o})=>{const i=await t.prepare("SELECT MAX(number) AS seq FROM revisions WHERE document = ? AND created_at <= ?").get(o.id,n);return{doc:o,from:i.seq,to:a,authors:c===null?[]:c.split(",")}})}d(Ze,"changedWhileDone");export{ve as DRY_RUN_ONLY,Ze as changedWhileDone,Ge as deliver,Xe as hypotheticalFrom,ze as hypotheticalReader,Qe as inspect,pe as recordContentDelivery};
6
+ GROUP BY o.id ORDER BY o.seq`).all(e.id,n,e.id);return await S(r,async({to_seq:a,authors:c,...o})=>{const i=await t.prepare("SELECT MAX(number) AS seq FROM revisions WHERE document = ? AND created_at <= ?").get(o.id,n);return{doc:o,from:i.seq,to:a,authors:c===null?[]:c.split(",")}})}d(qe,"changedWhileDone");export{he as DRY_RUN_ONLY,qe as changedWhileDone,Xe as deliver,ze as hypotheticalFrom,Qe as hypotheticalReader,Ze as inspect,ue as recordContentDelivery};