@north-light/cli 0.3.537 → 0.3.539
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.
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/profiles.d.ts +2 -0
- package/dist/builtin-memory/crouter-sdk/release-notes.md +8 -0
- package/dist/clients/attach/viewer.js +454 -454
- package/dist/commands/profile/list.js +1 -1
- package/dist/commands/profile/show.js +1 -1
- package/dist/commands/sys/join.js +1 -1
- package/dist/commands/sys/sync-shared.d.ts +1 -1
- package/dist/commands/sys/sync-shared.js +1 -1
- package/dist/core/canvas/canvas.d.ts +0 -6
- package/dist/core/canvas/canvas.js +17 -22
- package/dist/core/canvas/crons.js +10 -10
- package/dist/core/canvas/db.js +4 -4
- package/dist/core/canvas/export-sqlite.js +1 -1
- package/dist/core/canvas/legacy-person.d.ts +4 -0
- package/dist/core/canvas/legacy-person.js +1 -0
- package/dist/core/canvas/migrations.js +58 -58
- package/dist/core/canvas/node-tokens.d.ts +0 -4
- package/dist/core/canvas/node-tokens.js +3 -3
- package/dist/core/canvas/person.d.ts +39 -0
- package/dist/core/canvas/person.js +18 -0
- package/dist/core/canvas/schema-pg.sql +11 -0
- package/dist/core/directory/join.d.ts +2 -2
- package/dist/core/environments.d.ts +10 -0
- package/dist/core/environments.js +1 -0
- package/dist/core/grants/local.d.ts +4 -4
- package/dist/core/grants/local.js +1 -1
- package/dist/core/grants/mirror.d.ts +1 -1
- package/dist/core/grants/on-sync.js +1 -1
- package/dist/core/grants/sync.js +1 -1
- package/dist/core/graph/kinds/environment.d.ts +2 -0
- package/dist/core/graph/kinds/environment.js +2 -0
- package/dist/core/graph/kinds/registry.js +1 -1
- package/dist/core/graph/objects.d.ts +1 -1
- package/dist/core/graph/objects.js +7 -7
- package/dist/core/graph/reader.d.ts +0 -1
- package/dist/core/graph/reader.js +3 -3
- package/dist/core/layout-migrate/index.js +3 -3
- package/dist/core/layout-migrate/stores.js +3 -3
- package/dist/core/peer/stubs.js +1 -1
- package/dist/core/runs/events.d.ts +0 -2
- package/dist/core/runs/events.js +4 -4
- package/dist/core/runs/operations.d.ts +0 -3
- package/dist/core/runs/operations.js +6 -6
- package/dist/core/runtime/close.js +1 -1
- package/dist/core/runtime/host.js +3 -3
- package/dist/core/runtime/launch-target.d.ts +10 -1
- package/dist/core/runtime/launch-target.js +1 -1
- package/dist/core/runtime/nodes.js +1 -1
- package/dist/core/scoped-state/db.js +2 -2
- package/dist/core/scoped-state/profiles.js +1 -1
- package/dist/core/scoped-state/schema.d.ts +4 -0
- package/dist/core/scoped-state/schema.js +3 -3
- package/dist/core/spaces/process.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/hook-exec.js +1 -1
- package/dist/daemon/api/handlers/memory.js +5 -5
- package/dist/daemon/api/handlers/messages.js +2 -2
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/reader.js +1 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/vendor-signin.js +15 -15
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/authority.d.ts +1 -2
- package/dist/daemon/cron/authority.js +1 -1
- package/dist/daemon/crtrd.js +12 -12
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +3 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +1 -1
- package/dist/migrations/004-canvas-documents/apply.js +1 -1
- package/dist/migrations/004-canvas-documents/index.js +2 -2
- package/dist/types.d.ts +2 -0
- package/docs/cli/join-northlight.md +1 -1
- package/docs/sdk/release-notes.md +8 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
- package/dist/daemon/joined-owner.d.ts +0 -5
- package/dist/daemon/joined-owner.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
var a=Object.defineProperty;var
|
|
1
|
+
var a=Object.defineProperty;var r=(t,e)=>a(t,"name",{value:e,configurable:!0});import{defineLeaf as s}from"../../core/command.js";import{emitLine as o}from"../../core/io.js";import{cliClient as p}from"../api-client.js";const f=s({name:"list",description:"inventory every profile visible to you and its project relationships",whenToUse:"browse every profile that exists \u2014 one line each with its id, name, explicit project relationships, owning app, paused state, and last-used time. Reach for `profile show` once you have picked one, for its full manifest.",help:{name:"profile list",summary:"every profile the daemon shows the caller (its own app's, plus any another app shared) as JSONL, sorted by name then profile_id",output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"name",type:"string",required:!0,constraint:"Manifest name."},{name:"projects_count",type:"int",required:!0,constraint:"Number of `{path}` project relationships on the manifest."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"Explicit `{path}` relationships in manifest order. `path` defines profile purview; the repos enclosing each path are in the view of every node of the profile."},{name:"app",type:"string",required:!0,constraint:"The app that owns this profile."},{name:"paused_at",type:"string | null",required:!0,constraint:"ISO 8601 pause timestamp, or null when the profile is active."},{name:"default_kind",type:"string",required:!0,constraint:"Default node kind for creates that omit --kind; `general` when none is stored."},{name:"environment_id",type:"string",required:!0,constraint:"Object id of the environment the profile runs in; the machine environment when none is stored."},{name:"last_used_at",type:"string | null",required:!0,constraint:"ISO 8601 timestamp of the most recent selection, or null if never selected."}],outputKind:"jsonl",effects:["None. Read-only."]},run:r(async()=>{const t=(await p().listProfiles()).sort((e,n)=>{const i=e.name.localeCompare(n.name);return i!==0?i:e.id.localeCompare(n.id)});for(const e of t)o({profile_id:e.id,name:e.name,projects_count:e.projects.length,projects:e.projects,app:e.app,paused_at:e.paused_at,default_kind:e.default_kind,environment_id:e.environment_id,last_used_at:e.last_used_at});o({follow_up:"Show one in full with `crtr profile show <profile>`."})},"run")});export{f as listLeaf};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var n=Object.defineProperty;var i=(r,e)=>n(r,"name",{value:e,configurable:!0});import{defineLeaf as a}from"../../core/command.js";import{resolveProfileViaDaemon as o}from"./daemon-profiles.js";const l=a({name:"show",description:"one profile's manifest in full",whenToUse:"you need a profile's full project list, owning app, id, and timestamps \u2014 after `profile list` narrows to a name, to find where its nodes will run, or to inspect what a node's bearings named as its profile.",help:{name:"profile show",summary:"print one profile's settings and directory path",params:[{kind:"positional",name:"profile",required:!0,constraint:"Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr. A name shared by several profiles fails listing every matching id."}],output:[{name:"profile_id",type:"string",required:!0,constraint:"Stable directory id."},{name:"name",type:"string",required:!0,constraint:"Manifest name."},{name:"projects",type:"ProfileProject[]",required:!0,constraint:"Explicit `{path}` relationships in manifest order. `path` defines profile purview; the repos enclosing each path are in the view of every node of the profile."},{name:"app",type:"string",required:!0,constraint:"The app that owns this profile."},{name:"paused_at",type:"string | null",required:!0,constraint:"ISO 8601 pause timestamp, or null when the profile is active."},{name:"default_kind",type:"string",required:!0,constraint:"Default node kind for creates that omit --kind; `general` when none is stored."},{name:"metadata",type:"object",required:!0,constraint:"Stored metadata map \u2014 each entry reaches brokers launched under the profile as `CRTR_PROFILE_META_<KEY>` env. Empty when none."},{name:"environment_id",type:"string",required:!0,constraint:"Object id of the environment the profile runs in; the machine environment when none is stored."},{name:"created_at",type:"string",required:!0,constraint:"ISO 8601 creation timestamp."},{name:"last_used_at",type:"string | null",required:!0,constraint:"ISO 8601 timestamp of the most recent selection, or null if never selected."},{name:"profile_path",type:"string",required:!0,constraint:"Absolute path to the profile directory, `<app space>/profiles/<id>`: its settings root. Documents the profile owns are canvas documents, read with `crtr canvas read`, not files here."},{name:"follow_up",type:"string",required:!0,constraint:"Concrete next commands, including the SQL-backed raw settings surface."}],outputKind:"object",effects:["None. Read-only."]},run:i(async r=>{const e=await o(r.profile),t=e.id;return{profile_id:t,name:e.name,projects:e.projects,app:e.app,paused_at:e.paused_at,default_kind:e.default_kind,metadata:e.metadata,environment_id:e.environment_id,created_at:e.created_at,last_used_at:e.last_used_at,profile_path:e.profile_path,follow_up:e.paused_at===null?`Inspect its SQL-backed settings with \`crtr sys config get-raw --scope profile --profile ${t}\`; manage its purview with \`crtr profile project add ${t} --dir <path>\`, pause it with \`crtr profile pause ${t}\`, or set its default kind with \`crtr profile kind ${t} <kind>\`.`:`Inspect its SQL-backed settings with \`crtr sys config get-raw --scope profile --profile ${t}\`; manage its purview with \`crtr profile project add ${t} --dir <path>\`, or resume it with \`crtr profile resume ${t}\`.`}},"run")});export{l as showLeaf};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=Object.defineProperty;var n=(e,r)=>s(e,"name",{value:r,configurable:!0});import{defineLeaf as o}from"../../core/command.js";import{existsSync as a}from"node:fs";import{join as d}from"node:path";import{pluginsDir as
|
|
1
|
+
var s=Object.defineProperty;var n=(e,r)=>s(e,"name",{value:r,configurable:!0});import{defineLeaf as o}from"../../core/command.js";import{existsSync as a}from"node:fs";import{join as d}from"node:path";import{pluginsDir as l}from"../../core/scope.js";import{joinDirectory as c}from"../../core/directory/join.js";import{apiSocketPath as u}from"../../core/canvas/paths.js";import{listProcessTable as h}from"../../core/canvas/pid.js";import{daemonAnswers as m}from"../../core/layout-migrate/index.js";import{envNodeId as p}from"../../shared/env.js";import{isDaemonRunning as f}from"../../daemon/pidfile.js";import{findDaemonPids as w,spawnDaemon as y,stopAllDaemons as g}from"../../daemon/manage.js";function k(){const e=h();if(e===null||(p()??"")!=="")return!0;const r=new Map(e.map(t=>[t.pid,t])),i=new Set;for(let t=process.ppid;t>1&&!i.has(t);t=r.get(t)?.ppid??0)if(i.add(t),r.get(t)?.command.includes("broker-cli"))return!0;return!1}n(k,"insideBroker");function b(){if(!(process.platform==="linux"&&a("/run/crtr/launcher.sock")))return{running:n(async()=>f()||w().length>0||await m(u()),"running"),stop:n(async()=>{await g()},"stop"),start:n(async()=>{await y()},"start"),firstSync:n(async()=>{const e=l("user"),r=Date.now()+2e4;for(;e&&Date.now()<r&&!a(d(e,"cloud","commands.json"));)await new Promise(i=>setTimeout(i,250))},"firstSync"),insideNode:k}}n(b,"daemonControl");const I=o({name:"join",description:"register this runtime with its directory using an enrollment token, offline",whenToUse:"on your own computer, with a token from `<issuer>/account/machines`: run it from a plain terminal, with any viewers open. It stops the daemon, joins, and starts it again. Also at a hosted runtime's first boot, before the daemon starts, with the enrollment token provisioning passed in.",help:{name:"sys join",summary:"exchange a directory enrollment token for a new runtime client key and record the returned identity",guide:"Generates a new ES256 client key, sends only its public key to `PUT <directory>/v1/runtimes/registration` with the token as bearer, and learns the runtime id, client id, issuer, owner and URL from the answer. Only after that answer does it write the key file, set the user settings `runtime.issuer` and `runtime.audience`, and rewrite the legacy local person's profile grantees to `app:terminal`. canvas.db belongs to the daemon: its next start replaces this install's person with the owner everywhere in canvas.db, before it serves anything. On your own computer a running daemon is no obstacle: join takes a `join.lock` in the runtime directory (so no open viewer's redial can autostart a daemon while it works), registers with the directory while the daemon still serves, stops the daemon, writes the identity, and starts the daemon again, whose start applies the canvas.db change. Run it from a plain terminal, not from inside a node: a node's broker dies with the daemon, so join refuses `join_inside_node` instead. Where the launcher owns the daemon (a hosted container) join keeps the offline contract and refuses `daemon_running`. Presenting the token of the stored successful registration again finishes those local writes without calling the directory. Any other token generates a new key and registers again.",params:[{kind:"flag",name:"directory",type:"string",required:!0,constraint:"Directory base URL (http or https) that minted the token."},{kind:"flag",name:"token",type:"string",required:!0,sensitive:!0,constraint:"Single-use enrollment token. Sensitive: never echoed in output or errors."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Label shown for this machine on the account page. Default: this computer's hostname."}],output:[{name:"runtime",type:"string",required:!0,constraint:"Runtime principal from the directory, `runtime:<id>`."},{name:"client_id",type:"string",required:!0,constraint:"OAuth client id registered for the new key."},{name:"issuer",type:"string",required:!0,constraint:"Canonical issuer, written to `runtime.issuer`."},{name:"owner",type:"string",required:!0,constraint:"Owner principal, `user:<id>`, that replaces this install's own person at the next daemon start."},{name:"canvas_rewrite",type:"string",required:!0,constraint:"Always `next daemon start`: when canvas.db identity fields are rewritten."},{name:"url",type:"string",required:!0,constraint:"Runtime URL, written to `runtime.audience`."},{name:"registered",type:"boolean",required:!0,constraint:"False when the token matched the stored registration and the directory was not called."},{name:"daemon",type:"string",required:!0,constraint:"`restarted` (a daemon was stopped and started on the joined settings), `started` (none was running; join started one and waited, up to 20s, for its first grant sync to install the account's provider plugins, so `crtr cloud \u2026` resolves), `not_running` (none was started: a hosted container's launcher owns its daemon), or `restart_failed` (joined, but the new daemon did not start: see `daemon_error`, then run `crtr sys daemon start`)."},{name:"daemon_error",type:"string",required:!1,constraint:"Why the new daemon did not start, with `daemon: restart_failed`."}],outputKind:"object",effects:["Holds `<runtime>/join.lock` (its pid) from the start until the daemon is back; `ensureDaemon` and `sys daemon start` (without `--force`) decline to start a daemon while a live process holds it (including a start already in flight when the join began). A lock whose holder died is reclaimed.","A running daemon is stopped (SIGTERM, then SIGKILL, plus strays) after the directory accepts the registration and before the local writes, then started again. Every broker dies with it and is revived by the new daemon. If the join fails after the stop, the daemon is started again anyway. With no daemon running join starts one anyway, because a bare `crtr cloud \u2026` is an unknown command (it never autostarts a daemon) until a daemon's grant sync has installed the provider plugins. Refuses `join_inside_node` when run from inside a node and a daemon is running; refuses `daemon_running` where a container launcher owns the daemon.","On a new token: calls the directory once; on success writes `<runtime>/secrets/runtime-client.json` (mode 0600) with the private key, registration and the token's SHA-256, replacing any earlier key file. A refused or failed call writes nothing.","Sends name, platform, crtr version, installed plugin ids and sandbox state with the public key. Before the request it writes `runtime-client.pending.json` (mode 0600); a rerun with the same token and directory reuses that key, so a lost reply is retried safely. The file is deleted on success or a refusal.","Sets `runtime.issuer` and `runtime.audience` in user settings. When the directory says this machine is not the account's default (`is_default: false`) it replaces the whole `runtime` setting with `{issuer, audience, unsandboxed: true}`: no app listener, and only `app:terminal` nodes run.","Rewrites the legacy local person's profile grantees to `app:terminal`.","At the next daemon start, before any listener, the person this install minted (or still the legacy local person) is replaced by the owner in every canvas.db column that holds a person: node, token, grant, cron, audit and usage subjects and grantees, the person's identity, and queued cron births. References to the install's local terminal grant are cleared and that local grant mirror is deleted so grant sync installs the directory's. Free text is unchanged. The start after that changes nothing."]},run:n(async e=>({...await c(e.directory,e.token,e.name||void 0,b()),canvas_rewrite:"next daemon start"}),"run")});export{I as sysJoinLeaf};
|
|
@@ -30,7 +30,7 @@ export interface SyncOwner {
|
|
|
30
30
|
}
|
|
31
31
|
/** The repo enclosing `dir`. Throws when `dir` is in no repo. */
|
|
32
32
|
export declare function repoOwner(client: CrtrClient, dir: string): Promise<SyncOwner>;
|
|
33
|
-
/** The person on this runtime
|
|
33
|
+
/** The person on this runtime: the install's person object always holds the handle `user`. */
|
|
34
34
|
export declare function userOwner(client: CrtrClient): Promise<SyncOwner>;
|
|
35
35
|
/** Write `doc` as a new document, or replace an existing one's body and fields
|
|
36
36
|
* (read first, as every edit requires). Answers the document's full name and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var l=Object.defineProperty;var a=(n,e)=>l(n,"name",{value:e,configurable:!0});import{ApiError as f}from"../../api/index.js";import{identifyCheckout as p}from"../../core/graph/repo-sync/identity.js";import{rulesFromFrontmatter as h}from"../../core/graph/rules.js";import{usage as w}from"../../core/errors.js";function S(n){if(typeof n=="string")return n;if(typeof n=="number"||typeof n=="boolean")return String(n)}a(S,"scalarString");function m(n){let e=n.replace(/\s+/g," ").trim();return e=e.replace(/^[,;:.\s]+/,"").replace(/[,;:.\s]+$/,""),/^[A-Z][a-z]/.test(e)&&(e=e.charAt(0).toLowerCase()+e.slice(1)),e}a(m,"cleanClause");const y=/\b(?:use\s+this\s+skill\s+when|use\s+this\s+when|this\s+skill\s+should\s+be\s+used\s+when|used\s+when|use\s+when)\b\s*/i;function v(n,e){const t=n.replace(/\s+/g," ").trim();if(t==="")return`When this migrated ${e} applies, this ${e} should be read.`;const r=t.match(y);if(r&&r.index!==void 0){const s=m(t.slice(0,r.index)),i=m(t.slice(r.index+r[0].length));if(i!==""){let o=`When ${i}, this ${e} should be read`;return s!==""&&(o+=` because ${s}`),`${o}.`}}return`When ${m(t)}, this ${e} should be read.`}a(v,"routeFromDescription");function C(n){return n.replace(/\\/g,"/").replace(/\.md$/i,"").split("/").map(t=>t.trim().toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"")).filter(Boolean).join("/")}a(C,"normalizedName");function E(n){return h(n).map(e=>({on:e.on,deliver:e.deliver,...e.match!==void 0?{match:e.match}:{},...e.matchFrontmatter!==void 0?{match_frontmatter:e.matchFrontmatter}:{},...e.if!==void 0?{if:e.if}:{}}))}a(E,"rulesFromLegacyFrontmatter");async function g(n,e){let t;do{const r=await n.objects.list({type:"repo",machine:"self",limit:100,...t?{cursor:t}:{}});for(const s of r.items){const i=await n.objects.read(s.id);if(i.state!=="object"||i.type!=="repo")continue;const o=i.data;if(o.identity.external_id===e)return o.handle}t=r.next_cursor??void 0}while(t);return null}a(g,"repoOwnerByIdentity");async function d(n,e){const t=new Set;let r;do{const s=await n.objects.list({type:"document",owner:e,machine:"self",limit:100,...r?{cursor:r}:{}});for(const i of s.items)t.add(i.name);r=s.next_cursor??void 0}while(r);return t}a(d,"documentNames");async function W(n,e){const t=p(e);if(!t.ok)throw w(`${e} is not inside a repo: ${t.reason}`);const r=await g(n,t.externalId);return{owner:"repo",cwd:t.root,handle:r??t.handleHint,existing:r?await d(n,r):new Set}}a(W,"repoOwner");async function k(n){let e=null,t;do{const r=await n.objects.list({type:"person",machine:"self",limit:100,...t?{cursor:t}:{}});e=r.items.find(s=>s.name==="user")?.name??null,t=e===null?r.next_cursor??void 0:void 0}while(t);return{owner:"user",cwd:process.cwd(),handle:e??"user",existing:e?await d(n,e):new Set}}a(k,"userOwner");async function z(n,e,t,r){const s={preview:t.preview,...t.summary!==void 0&&t.summary!==""?{summary:t.summary}:{},...t.privacy!==void 0?{privacy:t.privacy}:{},rules:t.rules};if(!e.existing.has(t.name)){const c=await n.docs.write({name:t.name,body:t.body,kind:t.kind,owner:e.owner,cwd:e.cwd,...s});return e.handle=c.name.slice(0,c.name.length-t.name.length-1),e.existing.add(t.name),{name:c.name,changed:!0}}const i=`${e.handle}/${t.name}`,o=await n.objects.read(i),u=o.state==="object"&&o.type==="document"?o.data.bodies:null;return o.state==="object"&&u!==null&&u.length===1&&u[0]===t.body&&o.object.preview===t.preview?{name:i,changed:!1}:{name:(await n.docs.edit(i,{body:t.body,rationale:r,...s})).name,changed:!0}}a(z,"putDoc");function A(n){if(n instanceof f)return n.message;throw n}a(A,"writeFailure");export{C as normalizedName,z as putDoc,W as repoOwner,v as routeFromDescription,E as rulesFromLegacyFrontmatter,S as scalarString,k as userOwner,A as writeFailure};
|
|
@@ -53,12 +53,6 @@ export declare function dueNodeDeadlines(nowIso: string): Promise<NodeRow[]>;
|
|
|
53
53
|
export declare function setNodeDeadline(nodeId: string, deadlineAt: string | null): Promise<boolean>;
|
|
54
54
|
/** Shift active/idle profile deadlines by a completed profile pause duration. */
|
|
55
55
|
export declare function shiftProfileNodeDeadlines(profileId: string, pauseDurationMs: number): Promise<number>;
|
|
56
|
-
/** OWN-11 for a joined runtime: a `user:local` subject becomes the owner, a
|
|
57
|
-
* `user:local` grantee becomes `app:terminal` (no node has the owner as grantee),
|
|
58
|
-
* a terminal node born before the local grant mirror (no subject) gets the owner,
|
|
59
|
-
* and a reference to the dropped local grant is cleared so the node resolves the
|
|
60
|
-
* synced grant by grantee and subject. */
|
|
61
|
-
export declare function rewriteLocalNodeIdentity(db: CanvasDb, owner: string, localGrantId: string): Promise<number>;
|
|
62
56
|
type NodeUpdate = Partial<Omit<NodeIdentity, 'node_id' | 'pi_session_id' | 'pi_session_file' | 'managed_worktree'>>;
|
|
63
57
|
/** Update node identity/configuration columns. Sessions and worktrees have named stores. */
|
|
64
58
|
export declare function updateNode(nodeId: string, patch: NodeUpdate): Promise<NodeMeta>;
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Z=Object.defineProperty;var a=(e,n)=>Z(e,"name",{value:n,configurable:!0});import{envNodeId as x}from"../../shared/env.js";import{rmdirSync as ee,rmSync as ne,statSync as te}from"node:fs";import{dirname as ae,join as U,resolve as ie}from"node:path";import{removeTreeBeneath as W}from"../spaces/open-beneath.js";import{emitEvent as oe}from"../events/emit.js";import{withCanvasWrite as p,openDb as r}from"./db.js";import{conversationStore as re,ConversationNotFoundError as se}from"../conversation-store/index.js";import{hasUnseenWakeMail as de,UNSEEN_WAKE_MAIL_SQL as ue}from"./deliveries.js";import{isAppRun as j}from"./app-run.js";import{registerOutcomeAction as I}from"./outcome-actions.js";import{publish as le}from"../graph/events.js";import{nodeObject as ce}from"../graph/objects.js";import{findNodeBySessionFileSql as _e}from"./sessions.js";import{hydrateFault as pe}from"./recovery.js";import{hasUnreconciledWorktree as fe,openWorktree as Ee,readWorktree as we,worktreeFromRow as ye}from"./worktrees.js";import{isPidAlive as me,isPidPresent as F,recordedPidLiveness as Ne}from"./pid.js";import{ensureHome as Re,jobDir as Se,nodeDir as he,nodeRunDir as C}from"./paths.js";import{appForNode as Oe,ensureNodeDirs as Te,nodeAgentDir as Le}from"./node-agent-paths.js";import{appSpace as b}from"../layout.js";import{createObject as De,getById as H,ownerForGrantee as ge,setObjectKind as ve,setObjectName as ke}from"../graph/objects.js";import{deleteNodeWithOwned as Ae}from"../graph/deletion.js";import{SUBTREE_ALL_IDS_SQL as xe,SUBTREE_IDS_SQL as Ue}from"./tree.js";import{filterAsync as y,mapAsync as h}from"../async-array.js";function E(e,n){if(e==null)return null;const t=JSON.stringify(e);if(t===void 0)throw new TypeError(`${n} must be JSON-serializable`);return t}a(E,"jsonValue");async function cn(e,n){if((await e.prepare("SELECT deleting_at FROM nodes WHERE node_id = ?").get(n))?.deleting_at)throw new Error("run is deleting")}a(cn,"assertRunNotDeleting");async function We(e,n){const t=await ge(n,e.grantee??"app:terminal");await De(n,{id:e.node_id,type:"node",kind:e.kind,owner:t.id,privacy:"public",name:e.name,preview:""}),await n.prepare(`INSERT INTO nodes (node_id, description, title, icon, cycles, mode, lifecycle, cwd, host_kind, profile_id, parent, spawned_by, creator, fork_from, fork_source_file, created, passive_default, cycle_pending, model_override, persona_ack_json, review_binding_json, launch_config_json, scopes_json, isolated, status, intent, final_report, finalized_at, deadline_at, "window", tmux_session, pane, sub, grant_id, client_id, metadata_json, run_id, event_cursor, usage_json, deleting_at) VALUES (${Array(40).fill("?").join(", ")})`).run(e.node_id,e.description??null,e.title??null,e.icon??null,e.cycles??null,e.mode,e.lifecycle,e.cwd,e.host_kind??null,e.profile_id??null,e.parent??null,e.spawned_by??null,e.creator??null,e.fork_from??null,e.fork_source_file??null,e.created,e.passive_default===!0?1:0,e.cycle_pending===!0?1:0,e.model_override??null,E(e.persona_ack,"persona_ack"),E(e.review_binding,"review_binding"),E(e.launch,"launch"),E(e.scopes,"scopes"),e.isolated===!0?1:0,e.status??"active",e.intent??null,e.final_report??null,e.finalized_at??null,e.deadline_at??null,e.window??null,e.tmux_session??null,e.pane??null,e.sub??null,e.grant_id??null,e.client_id??null,e.metadata_json??null,e.run_id??null,e.event_cursor??null,e.usage_json??null,e.deleting_at??null)}a(We,"seedRow");function M(e){if(e.fault_id==null)return null;const n=Object.entries(e).filter(([t])=>t.startsWith("fault_")).map(([t,i])=>[t==="fault_id"?t:t.slice(6),i]);return pe(Object.fromEntries(n))}a(M,"faultFrom");function f(e){const n={node_id:e.node_id,name:e.name,description:e.description??void 0,cycles:e.cycles??void 0,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,profile_id:e.profile_id??null,scopes:e.scopes_json==null?null:JSON.parse(e.scopes_json),isolated:e.isolated===1,grantee:e.grantee,sub:e.sub??null,grant_id:e.grant_id??null,client_id:e.client_id??null,metadata_json:e.metadata_json??null,run_id:e.run_id??null,event_cursor:e.event_cursor??null,last_activity_at:e.last_activity_at??null,usage_json:e.usage_json??null,last_turn_error_json:e.last_turn_error_json??null,deleting_at:e.deleting_at??null,parent:e.parent??null,created:e.created,intent:e.intent??null,frozen_at:e.frozen_at??null,respawn_failures:e.respawn_failures??0,respawn_not_before:e.respawn_not_before??null,launched_at:e.launched_at??null,last_settled_at:e.last_settled_at??null,terminal_reason:e.terminal_reason??null,final_report:e.final_report??null,finalized_at:e.finalized_at??null,outcome_kind:e.outcome_kind??null,outcome_reason:e.outcome_reason??null,outcome_at:e.outcome_at??null,outcome_revision:e.outcome_revision??0,outcome_detail_json:e.outcome_detail_json??null,deadline_at:e.deadline_at??null,telemetry_tokens_in:e.telemetry_tokens_in??null,telemetry_context_tokens:e.telemetry_context_tokens??null,telemetry_last_activity:e.telemetry_last_activity??null,telemetry_updated_at:e.telemetry_updated_at??null,pi_pid:e.pi_pid??null,pi_pid_identity:e.pi_pid_identity??null,window:e.window??null,tmux_session:e.tmux_session??null,pane:e.pane??null,fault:null,streaming:!1},t=F(n.pi_pid),i=M(e);return n.fault=i?.link==="pi\u2192provider"&&i.retry.by==="daemon"&&i.retry.disposition==="auto"&&!t?null:i,n.streaming=e.turn_open_at!==null&&e.turn_closed_at===null&&t,n}a(f,"rowFrom");async function P(e,n){return n??=await we(e.node_id),{...f(e),title:e.title??void 0,icon:e.icon??void 0,spawned_by:e.spawned_by??null,creator:e.creator??null,fork_from:e.fork_from??null,fork_source_file:e.fork_source_file??null,passive_default:e.passive_default===1,cycle_pending:e.cycle_pending===1,model_override:e.model_override??null,scopes:e.scopes_json==null?null:JSON.parse(e.scopes_json),persona_ack:e.persona_ack_json==null?void 0:JSON.parse(e.persona_ack_json),review_binding:e.review_binding_json==null?void 0:JSON.parse(e.review_binding_json),launch:e.launch_config_json==null?void 0:JSON.parse(e.launch_config_json),pi_session_id:e.pi_session_id??null,pi_session_file:e.pi_session_file??null,...n===null?{}:{managed_worktree:n}}}a(P,"metaFrom");async function _n(e,n={}){return Re(),await p(async t=>{await We(e,t),e.managed_worktree!==void 0&&e.managed_worktree!==null&&await Ee(e.node_id,e.managed_worktree,t),n.outcomeDelivery!==void 0&&await I(t,e.node_id,n.outcomeDelivery),await n.withinCreate?.(t,e)}),await Te(e.node_id),await N(e.node_id)}a(_n,"createNode");async function N(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE node_id = ?").get(e);return n===void 0?null:await P(n)}a(N,"getNode");const je=1440*60*1e3;function $(e){return{requestKey:e.request_key,nodeId:e.node_id,state:e.state}}a($,"createRequestRecord");async function pn(e){const n=await(await r()).prepareUncached("SELECT request_key, node_id, state FROM create_requests WHERE request_key = ?").get(e);return n===void 0?null:$(n)}a(pn,"findCreateRequest");async function fn(e){return await p(async n=>{const t=Date.now();await n.prepareUncached("DELETE FROM create_requests WHERE expires_at <= ?").run(t);const i=await n.prepareUncached("SELECT request_key, node_id, state FROM create_requests WHERE request_key = ?").get(e);return i!==void 0?$(i):(await n.prepareUncached("INSERT INTO create_requests (request_key, node_id, expires_at, state) VALUES (?, NULL, ?, 'reserved')").run(e,t+je),{requestKey:e,nodeId:null,state:"reserved"})})}a(fn,"reserveCreateRequest");async function En(e=Date.now()){return Number((await(await r()).prepareUncached("DELETE FROM create_requests WHERE expires_at <= ?").run(e)).changes)}a(En,"pruneExpiredCreateRequests");async function wn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET node_id = ?, state = 'complete' WHERE request_key = ?").run(n,e)}a(wn,"recordCreateRequestNode");async function yn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET node_id = ?, state = 'reserved' WHERE request_key = ?").run(n,e)}a(yn,"reserveCreateRequestNode");async function mn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET state = 'complete' WHERE request_key = ? AND node_id = ?").run(e,n)}a(mn,"completeCreateRequest");async function Nn(e,n){await p(async t=>{if(n===null){await t.prepareUncached("DELETE FROM create_requests WHERE request_key = ? AND node_id IS NULL").run(e);return}await t.prepareUncached("DELETE FROM create_requests WHERE request_key = ? AND node_id = ?").run(e,n)})}a(Nn,"abandonCreateRequest");function Rn(e){return ie(e)}a(Rn,"canonicalSessionFile");async function Sn(e){const n=await _e(e);return n===null?null:await N(n)}a(Sn,"findNodeBySessionFile");async function hn(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE node_id = ?").get(e);return n?f(n):null}a(hn,"getRow");async function On(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE pane = ?").get(e);return n?f(n):null}a(On,"getRowByPane");async function Tn(e){return(await(await r()).prepare(`SELECT * FROM node_rows
|
|
2
2
|
WHERE deadline_at IS NOT NULL AND outcome_at IS NULL
|
|
3
3
|
AND deadline_at <= ? AND status IN ('active','idle') AND kind != 'human'
|
|
4
|
-
ORDER BY deadline_at, created, node_id`).all(e)).map(f)}
|
|
5
|
-
WHERE profile_id = ? AND deadline_at IS NOT NULL AND status IN ('active', 'idle')`).all(e),o=t.prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ? AND deadline_at = ?");let s=0;for(const d of
|
|
6
|
-
WHERE o.type = 'node' AND i.scheme = 'local-user' AND i.external_id = 'user:local'`,s=await e.prepareUncached(`${a} LIMIT 1`).get()!==void 0?Number((await e.prepareUncached(`UPDATE objects SET owner = ? WHERE id IN (${a})`).run((await h(e,"app:terminal")).id)).changes):0,d=`SELECT o.id FROM objects o JOIN peer_stubs s ON s.object = o.id JOIN identities i ON i.object = o.owner
|
|
7
|
-
WHERE o.type = 'node' AND o.reception = 2 AND s.envelope_json ->> 'app' IS NULL
|
|
8
|
-
AND i.scheme = 'app' AND i.external_id = 'app:terminal'`,l=await e.prepareUncached(`${d} LIMIT 1`).get()!==void 0?Number((await e.prepareUncached(`UPDATE objects SET owner = ? WHERE id IN (${d})`).run((await h(e,"user:local")).id)).changes):0;return Number((await e.prepare("UPDATE nodes SET sub = ? WHERE sub = 'user:local'").run(n)).changes)+s+l+Number((await e.prepare(`UPDATE nodes SET sub = ? WHERE sub IS NULL AND node_id IN (
|
|
9
|
-
SELECT o.id FROM objects o JOIN identities i ON i.object = o.owner
|
|
10
|
-
WHERE o.type = 'node' AND i.scheme = 'app' AND i.external_id = 'app:terminal')`).run(n)).changes)+Number((await e.prepare("UPDATE nodes SET grant_id = NULL WHERE grant_id = ?").run(t)).changes)}i(gn,"rewriteLocalNodeIdentity");function T(e,n){if(e==null)return null;if(typeof e=="string"||typeof e=="number"||typeof e=="bigint"||e instanceof Uint8Array)return e;throw new TypeError(`${n} must be a SQLite scalar`)}i(T,"sqlInput");async function An(e,n){const t=[],a=i((s,d=s)=>{Object.prototype.hasOwnProperty.call(n,s)&&t.push([d,T(n[s],s)])},"add");if(a("description"),a("title"),a("icon"),a("cycles"),a("mode"),a("lifecycle"),a("cwd"),a("host_kind"),a("profile_id"),a("parent"),a("spawned_by"),a("creator"),a("fork_from"),a("fork_source_file"),a("model_override"),Object.prototype.hasOwnProperty.call(n,"passive_default")&&t.push(["passive_default",n.passive_default===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"cycle_pending")&&t.push(["cycle_pending",n.cycle_pending===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"persona_ack")&&t.push(["persona_ack_json",E(n.persona_ack,"persona_ack")]),Object.prototype.hasOwnProperty.call(n,"review_binding")&&t.push(["review_binding_json",E(n.review_binding,"review_binding")]),Object.prototype.hasOwnProperty.call(n,"isolated"))throw new TypeError("isolated cannot change after node creation");Object.prototype.hasOwnProperty.call(n,"scopes")&&t.push(["scopes_json",E(n.scopes,"scopes")]),Object.prototype.hasOwnProperty.call(n,"launch")&&t.push(["launch_config_json",E(n.launch,"launch")]);const o=i(s=>Object.prototype.hasOwnProperty.call(n,s),"has");return await p(async s=>{const d=await M(s,e);if(d===null||d.type!=="node")throw new Error(`unknown node: ${e}`);if(o("name")&&await ve(s,d,T(n.name,"name")),o("kind")&&await Ae(s,d,T(n.kind,"kind")),t.length===0)return;if((await s.prepareUncached(`UPDATE nodes SET ${t.map(([u])=>`${u} = ?`).join(", ")} WHERE node_id = ?`).run(...t.map(([,u])=>u),e)).changes!==1)throw new Error(`unknown node: ${e}`)}),await N(e)}i(An,"updateNode");async function vn(e,n){await(await r()).prepare("UPDATE nodes SET status = ? WHERE node_id = ?").run(n,e)}i(vn,"setStatus");async function kn(e,n){await(await r()).prepare("UPDATE nodes SET intent = ? WHERE node_id = ?").run(n??null,e)}i(kn,"setIntent");async function Un(e,n,t={}){if(t.outcome===void 0){const o=[],s=[];Object.prototype.hasOwnProperty.call(n,"status")&&(o.push("status = ?"),s.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(o.push("intent = ?"),s.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(o.push("terminal_reason = ?"),s.push(n.terminal_reason??null));const d=["node_id = ?"];s.push(e),t.fromStatuses!==void 0&&(d.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),s.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&d.push("final_report IS NULL");const l=await(await r()).prepareUncached(`UPDATE nodes SET ${o.join(", ")} WHERE ${d.join(" AND ")}`).run(...s);return Number(l.changes)}const a=t.outcome;return await p(async o=>{const s=[],d=[];Object.prototype.hasOwnProperty.call(n,"status")&&(s.push("status = ?"),d.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(s.push("intent = ?"),d.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(s.push("terminal_reason = ?"),d.push(n.terminal_reason??null));const l=i((m,c)=>a.supersede===!0?"?":`CASE WHEN outcome_at IS NULL THEN ? ELSE ${c} END`,"outcomeValue");s.push(`outcome_kind = ${l(a.kind,"outcome_kind")}`,`outcome_reason = ${l(a.reason,"outcome_reason")}`,`outcome_detail_json = ${l(a.detailJson,"outcome_detail_json")}`,`outcome_revision = ${a.supersede===!0?"outcome_revision + 1":"CASE WHEN outcome_at IS NULL THEN outcome_revision + 1 ELSE outcome_revision END"}`,`outcome_at = ${l(a.at,"outcome_at")}`),d.push(a.kind,a.reason,a.detailJson,a.at);const u=["node_id = ?"];d.push(e),t.fromStatuses!==void 0&&(u.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),d.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&u.push("final_report IS NULL");const _=await o.prepareUncached(`UPDATE nodes SET ${s.join(", ")} WHERE ${u.join(" AND ")}`).run(...d);if(Number(_.changes)===0)return 0;const S=await o.prepare("SELECT outcome_at FROM nodes WHERE node_id = ?").get(e);return(a.supersede===!0||S.outcome_at===a.at)&&a.documentJson!==null&&a.documentJson!==void 0&&await ce(o,{event:"outcome",object:await _e(o,e),actor:null,attrs:{revision:Number((await o.prepare("SELECT outcome_revision FROM nodes WHERE node_id = ?").get(e)).outcome_revision)},messageBody:JSON.parse(a.documentJson)}),Number(_.changes)})}i(Un,"transitionStatusIntent");async function xn(e){return(await(await r()).prepare("SELECT final_report FROM nodes WHERE node_id = ?").get(e))?.final_report??null}i(xn,"getFinalReport");async function In(e,n){return(await(await r()).prepare("UPDATE nodes SET final_report = NULL, finalized_at = NULL WHERE node_id = ? AND final_report = ?").run(e,n)).changes>0}i(In,"clearFinalReportIfMatches");async function jn(e,n){await(await r()).prepare('UPDATE nodes SET tmux_session = ?, "window" = ?, pane = ? WHERE node_id = ?').run(n.tmux_session??null,n.window??null,n.pane??null,e)}i(jn,"setPresence");async function Wn(e,n){await(await r()).prepare("UPDATE nodes SET launched_at = ?, frozen_at = NULL, respawn_not_before = NULL WHERE node_id = ?").run(n,e)}i(Wn,"recordLaunch");async function bn(e,n){await(await r()).prepare("UPDATE nodes SET last_settled_at = ? WHERE node_id = ?").run(n,e)}i(bn,"recordSettledTurn");async function Fn(e,n){await(await r()).prepare("UPDATE nodes SET frozen_at = ? WHERE node_id = ?").run(n,e)}i(Fn,"setFrozen");async function Cn(e){await(await r()).prepare("UPDATE nodes SET frozen_at = NULL WHERE node_id = ?").run(e)}i(Cn,"clearFrozen");async function Hn(e,n){await(await r()).prepare("UPDATE nodes SET respawn_failures = ?, respawn_not_before = ? WHERE node_id = ?").run(n.failures,n.notBefore,e)}i(Hn,"recordRespawnAttempt");const w="node_id NOT IN (SELECT node_id FROM warm_pool)",B=["state","cleanup_state","path","branch","repo_root","base_ref","base_sha","created_at","closed_at","next_attempt_at","attempts","quarantined","sweep_reason","sweep_detail","sweep_last_at","sweep_observed_json","abandoned_json","land_intent_json","disposal_json"];function We(e){return e.worktree_state===null?null:me(Object.fromEntries(B.map(n=>[n,e[`worktree_${n}`]])))}i(We,"worktreeFromProjection");async function Mn(e){const n=B.map(a=>`w.${a} AS worktree_${a}`).join(", "),t=await(await r()).prepareUncached(`SELECT n.*, ${n} FROM node_rows n LEFT JOIN worktrees w ON w.node_id = n.node_id WHERE n.profile_id = ? ORDER BY n.created`).all(e);return await O(t,async a=>await $(a,We(a)))}i(Mn,"listNodesByProfile");async function Pn(){return(await(await r()).prepare(`SELECT
|
|
4
|
+
ORDER BY deadline_at, created, node_id`).all(e)).map(f)}a(Tn,"dueNodeDeadlines");async function Ln(e,n){return Number((await(await r()).prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ?").run(n,e)).changes)===1}a(Ln,"setNodeDeadline");async function Dn(e,n){return!Number.isFinite(n)||n<=0?0:await p(async t=>{const i=await t.prepare(`SELECT node_id, deadline_at FROM nodes
|
|
5
|
+
WHERE profile_id = ? AND deadline_at IS NOT NULL AND status IN ('active', 'idle')`).all(e),o=t.prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ? AND deadline_at = ?");let s=0;for(const d of i){const c=Date.parse(d.deadline_at);Number.isNaN(c)||(s+=Number((await o.run(new Date(c+n).toISOString(),d.node_id,d.deadline_at)).changes))}return s})}a(Dn,"shiftProfileNodeDeadlines");function O(e,n){if(e==null)return null;if(typeof e=="string"||typeof e=="number"||typeof e=="bigint"||e instanceof Uint8Array)return e;throw new TypeError(`${n} must be a SQLite scalar`)}a(O,"sqlInput");async function gn(e,n){const t=[],i=a((s,d=s)=>{Object.prototype.hasOwnProperty.call(n,s)&&t.push([d,O(n[s],s)])},"add");if(i("description"),i("title"),i("icon"),i("cycles"),i("mode"),i("lifecycle"),i("cwd"),i("host_kind"),i("profile_id"),i("parent"),i("spawned_by"),i("creator"),i("fork_from"),i("fork_source_file"),i("model_override"),Object.prototype.hasOwnProperty.call(n,"passive_default")&&t.push(["passive_default",n.passive_default===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"cycle_pending")&&t.push(["cycle_pending",n.cycle_pending===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"persona_ack")&&t.push(["persona_ack_json",E(n.persona_ack,"persona_ack")]),Object.prototype.hasOwnProperty.call(n,"review_binding")&&t.push(["review_binding_json",E(n.review_binding,"review_binding")]),Object.prototype.hasOwnProperty.call(n,"isolated"))throw new TypeError("isolated cannot change after node creation");Object.prototype.hasOwnProperty.call(n,"scopes")&&t.push(["scopes_json",E(n.scopes,"scopes")]),Object.prototype.hasOwnProperty.call(n,"launch")&&t.push(["launch_config_json",E(n.launch,"launch")]);const o=a(s=>Object.prototype.hasOwnProperty.call(n,s),"has");return await p(async s=>{const d=await H(s,e);if(d===null||d.type!=="node")throw new Error(`unknown node: ${e}`);if(o("name")&&await ke(s,d,O(n.name,"name")),o("kind")&&await ve(s,d,O(n.kind,"kind")),t.length===0)return;if((await s.prepareUncached(`UPDATE nodes SET ${t.map(([u])=>`${u} = ?`).join(", ")} WHERE node_id = ?`).run(...t.map(([,u])=>u),e)).changes!==1)throw new Error(`unknown node: ${e}`)}),await N(e)}a(gn,"updateNode");async function vn(e,n){await(await r()).prepare("UPDATE nodes SET status = ? WHERE node_id = ?").run(n,e)}a(vn,"setStatus");async function kn(e,n){await(await r()).prepare("UPDATE nodes SET intent = ? WHERE node_id = ?").run(n??null,e)}a(kn,"setIntent");async function An(e,n,t={}){if(t.outcome===void 0){const o=[],s=[];Object.prototype.hasOwnProperty.call(n,"status")&&(o.push("status = ?"),s.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(o.push("intent = ?"),s.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(o.push("terminal_reason = ?"),s.push(n.terminal_reason??null));const d=["node_id = ?"];s.push(e),t.fromStatuses!==void 0&&(d.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),s.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&d.push("final_report IS NULL");const c=await(await r()).prepareUncached(`UPDATE nodes SET ${o.join(", ")} WHERE ${d.join(" AND ")}`).run(...s);return Number(c.changes)}const i=t.outcome;return await p(async o=>{const s=[],d=[];Object.prototype.hasOwnProperty.call(n,"status")&&(s.push("status = ?"),d.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(s.push("intent = ?"),d.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(s.push("terminal_reason = ?"),d.push(n.terminal_reason??null));const c=a((m,l)=>i.supersede===!0?"?":`CASE WHEN outcome_at IS NULL THEN ? ELSE ${l} END`,"outcomeValue");s.push(`outcome_kind = ${c(i.kind,"outcome_kind")}`,`outcome_reason = ${c(i.reason,"outcome_reason")}`,`outcome_detail_json = ${c(i.detailJson,"outcome_detail_json")}`,`outcome_revision = ${i.supersede===!0?"outcome_revision + 1":"CASE WHEN outcome_at IS NULL THEN outcome_revision + 1 ELSE outcome_revision END"}`,`outcome_at = ${c(i.at,"outcome_at")}`),d.push(i.kind,i.reason,i.detailJson,i.at);const u=["node_id = ?"];d.push(e),t.fromStatuses!==void 0&&(u.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),d.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&u.push("final_report IS NULL");const _=await o.prepareUncached(`UPDATE nodes SET ${s.join(", ")} WHERE ${u.join(" AND ")}`).run(...d);if(Number(_.changes)===0)return 0;const S=await o.prepare("SELECT outcome_at FROM nodes WHERE node_id = ?").get(e);return(i.supersede===!0||S.outcome_at===i.at)&&i.documentJson!==null&&i.documentJson!==void 0&&await le(o,{event:"outcome",object:await ce(o,e),actor:null,attrs:{revision:Number((await o.prepare("SELECT outcome_revision FROM nodes WHERE node_id = ?").get(e)).outcome_revision)},messageBody:JSON.parse(i.documentJson)}),Number(_.changes)})}a(An,"transitionStatusIntent");async function xn(e){return(await(await r()).prepare("SELECT final_report FROM nodes WHERE node_id = ?").get(e))?.final_report??null}a(xn,"getFinalReport");async function Un(e,n){return(await(await r()).prepare("UPDATE nodes SET final_report = NULL, finalized_at = NULL WHERE node_id = ? AND final_report = ?").run(e,n)).changes>0}a(Un,"clearFinalReportIfMatches");async function Wn(e,n){await(await r()).prepare('UPDATE nodes SET tmux_session = ?, "window" = ?, pane = ? WHERE node_id = ?').run(n.tmux_session??null,n.window??null,n.pane??null,e)}a(Wn,"setPresence");async function jn(e,n){await(await r()).prepare("UPDATE nodes SET launched_at = ?, frozen_at = NULL, respawn_not_before = NULL WHERE node_id = ?").run(n,e)}a(jn,"recordLaunch");async function In(e,n){await(await r()).prepare("UPDATE nodes SET last_settled_at = ? WHERE node_id = ?").run(n,e)}a(In,"recordSettledTurn");async function Fn(e,n){await(await r()).prepare("UPDATE nodes SET frozen_at = ? WHERE node_id = ?").run(n,e)}a(Fn,"setFrozen");async function Cn(e){await(await r()).prepare("UPDATE nodes SET frozen_at = NULL WHERE node_id = ?").run(e)}a(Cn,"clearFrozen");async function bn(e,n){await(await r()).prepare("UPDATE nodes SET respawn_failures = ?, respawn_not_before = ? WHERE node_id = ?").run(n.failures,n.notBefore,e)}a(bn,"recordRespawnAttempt");const w="node_id NOT IN (SELECT node_id FROM warm_pool)",q=["state","cleanup_state","path","branch","repo_root","base_ref","base_sha","created_at","closed_at","next_attempt_at","attempts","quarantined","sweep_reason","sweep_detail","sweep_last_at","sweep_observed_json","abandoned_json","land_intent_json","disposal_json"];function Ie(e){return e.worktree_state===null?null:ye(Object.fromEntries(q.map(n=>[n,e[`worktree_${n}`]])))}a(Ie,"worktreeFromProjection");async function Hn(e){const n=q.map(i=>`w.${i} AS worktree_${i}`).join(", "),t=await(await r()).prepareUncached(`SELECT n.*, ${n} FROM node_rows n LEFT JOIN worktrees w ON w.node_id = n.node_id WHERE n.profile_id = ? ORDER BY n.created`).all(e);return await h(t,async i=>await P(i,Ie(i)))}a(Hn,"listNodesByProfile");async function Mn(){return(await(await r()).prepare(`SELECT
|
|
11
6
|
node_id, name, description, kind, mode, lifecycle, status, cwd, profile_id,
|
|
12
7
|
grantee, parent, created, frozen_at, terminal_reason, final_report,
|
|
13
8
|
telemetry_tokens_in, telemetry_updated_at, pi_pid, turn_open_at, turn_closed_at,
|
|
@@ -15,13 +10,13 @@ var ee=Object.defineProperty;var i=(e,n)=>ee(e,"name",{value:n,configurable:!0})
|
|
|
15
10
|
fault_operation_id, fault_anchor_entry_id, fault_retry_disposition,
|
|
16
11
|
fault_retry_by, fault_retry_attempt, fault_retry_max, fault_retry_next_at,
|
|
17
12
|
fault_provider_error_json
|
|
18
|
-
FROM node_rows WHERE ${w} ORDER BY created`).all()).map(f)}
|
|
13
|
+
FROM node_rows WHERE ${w} ORDER BY created`).all()).map(f)}a(Mn,"browseNodes");async function Pn(e,n){await(await r()).prepare(`UPDATE nodes
|
|
19
14
|
SET telemetry_tokens_in = ?,
|
|
20
15
|
telemetry_context_tokens = COALESCE(?, telemetry_context_tokens),
|
|
21
16
|
telemetry_last_activity = COALESCE(?, telemetry_last_activity),
|
|
22
17
|
telemetry_updated_at = ?
|
|
23
18
|
WHERE node_id = ?
|
|
24
|
-
AND (telemetry_updated_at IS NULL OR telemetry_updated_at < ?)`).run(n.tokensIn,n.contextTokens,n.lastActivity,n.updatedAt,e,n.updatedAt)}
|
|
19
|
+
AND (telemetry_updated_at IS NULL OR telemetry_updated_at < ?)`).run(n.tokensIn,n.contextTokens,n.lastActivity,n.updatedAt,e,n.updatedAt)}a(Pn,"updateNodeTelemetry");async function $n(){return(await(await r()).prepare(`SELECT
|
|
25
20
|
node_id, name, description, cycles, kind, status, frozen_at,
|
|
26
21
|
telemetry_context_tokens, telemetry_last_activity,
|
|
27
22
|
pi_pid, turn_open_at, turn_closed_at,
|
|
@@ -29,14 +24,14 @@ var ee=Object.defineProperty;var i=(e,n)=>ee(e,"name",{value:n,configurable:!0})
|
|
|
29
24
|
fault_operation_id, fault_anchor_entry_id, fault_retry_disposition,
|
|
30
25
|
fault_retry_by, fault_retry_attempt, fault_retry_max, fault_retry_next_at,
|
|
31
26
|
fault_provider_error_json
|
|
32
|
-
FROM node_rows WHERE ${w} ORDER BY created`).all()).map(n=>{const t=n.pi_pid??null,
|
|
27
|
+
FROM node_rows WHERE ${w} ORDER BY created`).all()).map(n=>{const t=n.pi_pid??null,i=F(t),o=M(n);return{node_id:n.node_id,name:n.name,description:n.description??null,cycles:n.cycles??null,kind:n.kind,status:n.status,frozen_at:n.frozen_at??null,pi_pid:t,telemetry_context_tokens:n.telemetry_context_tokens??null,telemetry_last_activity:n.telemetry_last_activity??null,fault:o?.link==="pi\u2192provider"&&o.retry.by==="daemon"&&o.retry.disposition==="auto"&&!i?null:o,streaming:n.turn_open_at!==null&&n.turn_closed_at===null&&i}})}a($n,"graphNodes");async function Fe(e){const n=[w],t=[];if(e?.under!==void 0&&(e.underInclusive===!0?(n.push(`(node_id IN (${xe}) OR node_id = ?)`),t.push(e.under,e.under,e.under)):(n.push(`node_id IN (${Ue})`),t.push(e.under,e.under))),e?.lifecycle!==void 0&&(n.push("lifecycle = ?"),t.push(e.lifecycle)),e?.kind!==void 0&&(n.push("kind = ?"),t.push(e.kind)),e?.mode!==void 0&&(n.push("mode = ?"),t.push(e.mode)),e?.profileId!==void 0&&(n.push("profile_id = ?"),t.push(e.profileId)),e?.profilePrefix!==void 0&&(n.push("profile_id LIKE ? ESCAPE '\\'"),t.push(`${e.profilePrefix.replace(/[\\%_]/g,o=>`\\${o}`)}%`)),e?.parent!==void 0&&(n.push("parent = ?"),t.push(e.parent)),e?.topLevel===!0&&n.push("parent IS NULL"),e?.hanging===!0&&n.push("parent IS NOT NULL AND parent NOT IN (SELECT node_id FROM node_rows)"),e?.status!==void 0){const o=Array.isArray(e.status)?e.status:[e.status];n.push(`status IN (${o.map(()=>"?").join(",")})`),t.push(...o)}return e?.withRecordedPid===!0&&n.push("pi_pid IS NOT NULL"),e?.withRecordedPidOrFrozen===!0&&n.push("(pi_pid IS NOT NULL OR frozen_at IS NOT NULL)"),(await(await r()).prepareUncached(`SELECT * FROM node_rows WHERE ${n.join(" AND ")} ORDER BY created`).all(...t)).map(f)}a(Fe,"listNodes");const Ce=`SELECT
|
|
33
28
|
node_id, created, launched_at, last_settled_at, finalized_at,
|
|
34
29
|
outcome_at, last_activity_at, pane, "window", tmux_session
|
|
35
30
|
FROM nodes
|
|
36
31
|
WHERE ${w}
|
|
37
32
|
AND status IN ('done', 'canceled', 'dead')
|
|
38
33
|
AND (pane IS NOT NULL OR "window" IS NOT NULL OR tmux_session IS NOT NULL)
|
|
39
|
-
ORDER BY created`;async function
|
|
34
|
+
ORDER BY created`;async function qn(){return await(await r()).prepare(Ce).all()}a(qn,"listTerminalPlacementResidue");const be=`WITH candidates AS MATERIALIZED (
|
|
40
35
|
SELECT node_id FROM nodes WHERE status IN ('active', 'idle')
|
|
41
36
|
UNION
|
|
42
37
|
SELECT node_id FROM nodes
|
|
@@ -50,19 +45,19 @@ var ee=Object.defineProperty;var i=(e,n)=>ee(e,"name",{value:n,configurable:!0})
|
|
|
50
45
|
AND v.node_id NOT IN (SELECT node_id FROM warm_pool)
|
|
51
46
|
AND v.kind != 'human'
|
|
52
47
|
AND (v.status IN ('active', 'idle') OR (v.status IN ('done', 'canceled', 'dead') AND (v.pi_pid IS NOT NULL OR v.frozen_at IS NOT NULL)))
|
|
53
|
-
ORDER BY v.created`;async function
|
|
54
|
-
WHERE ${
|
|
55
|
-
AND EXISTS (SELECT 1 FROM deliveries d WHERE d.destination_id = v.node_id AND ${
|
|
56
|
-
ORDER BY v.created`).all()).map(f)}
|
|
48
|
+
ORDER BY v.created`;async function Bn(){return(await(await r()).prepare(be).all()).map(f)}a(Bn,"listNodesForSupervision");const B=a(e=>`(${e}.status = 'done' AND ${e}.intent = 'parked' AND ${e}.lifecycle = 'resident' AND ${e}.final_report IS NULL AND ${e}.deleting_at IS NULL)`,"PARKED_RESIDENT_SQL"),He=a(e=>`(${e}.status IN ('active', 'idle') OR ${B(e)} OR (${e}.status IN ('done', 'canceled', 'dead') AND ${e}.frozen_at IS NOT NULL AND ${e}.final_report IS NULL))`,"RESUMABLE_NODE_SQL");async function zn(e){return await(await r()).prepare(`SELECT node_id, cwd FROM nodes WHERE ${He("nodes")} AND node_id != ?`).all(e)}a(zn,"resumableNodeCwds");async function Yn(){return(await(await r()).prepare(`SELECT v.* FROM node_rows v
|
|
49
|
+
WHERE ${B("v")}
|
|
50
|
+
AND EXISTS (SELECT 1 FROM deliveries d WHERE d.destination_id = v.node_id AND ${ue("d")}) AND v.kind != 'human' AND v.node_id NOT IN (SELECT node_id FROM warm_pool)
|
|
51
|
+
ORDER BY v.created`).all()).map(f)}a(Yn,"listParkedResidentsWithWakeMail");async function Jn(){return await(await r()).prepare(`SELECT node_id, name, created, grantee FROM node_rows WHERE ${w} ORDER BY created`).all()}a(Jn,"listNodeIdentities");async function Qn(e,n){await(await r()).prepare("INSERT INTO warm_pool (node_id, recipe_key, created) VALUES (?, ?, ?) ON CONFLICT (node_id) DO UPDATE SET recipe_key = excluded.recipe_key, created = excluded.created").run(e,n,new Date().toISOString())}a(Qn,"registerWarmSpare");async function Vn(e){return(await(await r()).prepare("DELETE FROM warm_pool WHERE node_id = ?").run(e)).changes>0}a(Vn,"unregisterWarmSpare");async function T(e,n){const t=`SELECT w.node_id AS node_id, w.recipe_key AS recipe_key, w.created AS created,
|
|
57
52
|
n.status AS status, n.pi_pid AS pi_pid, n.pi_pid_identity AS pi_pid_identity
|
|
58
|
-
FROM warm_pool w JOIN node_rows n ON n.node_id = w.node_id`+(n===void 0?"":" WHERE w.recipe_key = ?")+" ORDER BY w.created";return(n===void 0?await e.prepareUncached(t).all():await e.prepareUncached(t).all(n)).map(o=>({node_id:o.node_id,recipe_key:o.recipe_key,created:o.created,status:o.status,pi_pid:o.pi_pid??null,pi_pid_identity:o.pi_pid_identity??null}))}
|
|
59
|
-
FROM node_rows WHERE ${w} ORDER BY created`).all()).map(e=>({node_id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,parent:e.parent??null,created:e.created,description:e.description??null}))}
|
|
53
|
+
FROM warm_pool w JOIN node_rows n ON n.node_id = w.node_id`+(n===void 0?"":" WHERE w.recipe_key = ?")+" ORDER BY w.created";return(n===void 0?await e.prepareUncached(t).all():await e.prepareUncached(t).all(n)).map(o=>({node_id:o.node_id,recipe_key:o.recipe_key,created:o.created,status:o.status,pi_pid:o.pi_pid??null,pi_pid_identity:o.pi_pid_identity??null}))}a(T,"warmSpareRowsFor");async function Kn(){return await T(await r())}a(Kn,"listWarmSpares");async function Gn(e){return await(await r()).prepare("SELECT 1 FROM warm_pool WHERE node_id = ?").get(e)!==void 0}a(Gn,"isWarmSpare");function z(e){return e.status!=="active"&&e.status!=="idle"?!1:Ne(e.pi_pid,e.pi_pid_identity)==="alive"}a(z,"spareIsClaimable");async function Xn(e,n,t={}){return await p(async i=>{const o=(await T(i,e)).find(z);return o===void 0||t.requestKey!==void 0&&(await i.prepareUncached("UPDATE create_requests SET node_id = ?, state = 'warm_claimed' WHERE request_key = ? AND node_id IS NULL").run(o.node_id,t.requestKey)).changes===0||(await i.prepare("DELETE FROM warm_pool WHERE node_id = ?").run(o.node_id)).changes===0?null:(await i.prepare("UPDATE nodes SET created = ?, deadline_at = ? WHERE node_id = ?").run(n,t.deadlineAt??null,o.node_id),t.outcomeDelivery!==void 0&&await I(i,o.node_id,t.outcomeDelivery),o.node_id)})}a(Xn,"claimWarmSpare");async function Zn(e){return(await T(await r(),e)).filter(z).length}a(Zn,"warmSpareCount");async function et(e){return(await(await r()).prepare("SELECT node_id FROM nodes WHERE parent = ? ORDER BY created").all(e)).map(n=>n.node_id)}a(et,"childrenOf");async function nt(){return(await(await r()).prepareUncached(`SELECT node_id, name, kind, mode, lifecycle, status, cwd, host_kind, parent, created, description
|
|
54
|
+
FROM node_rows WHERE ${w} ORDER BY created`).all()).map(e=>({node_id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,parent:e.parent??null,created:e.created,description:e.description??null}))}a(nt,"rosterNodes");async function tt(e){return(await(await r()).prepareUncached(`SELECT * FROM node_rows
|
|
60
55
|
WHERE parent IS NULL AND kind != 'human'
|
|
61
56
|
AND status IN ('active','idle') AND cwd = ?
|
|
62
57
|
AND ${w}
|
|
63
|
-
ORDER BY created DESC`).all(e)).map(f)}
|
|
64
|
-
AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.node_id = n.node_id) LIMIT 1`);for(const o of e)if(!(await t.get(o)!==void 0||await
|
|
58
|
+
ORDER BY created DESC`).all(e)).map(f)}a(tt,"listResumableRoots");export*from"./tree.js";async function at(e,n,t){return await p(async i=>{const o=await i.prepare("SELECT status, final_report, frozen_at FROM nodes WHERE node_id = ?").get(e);return o===void 0?{kind:"missing"}:n(o)?{kind:"terminal",status:o.status,final_report:o.final_report}:{kind:"ran",result:await t()}})}a(at,"withFreshTerminalGuard");async function R(e){return await fe(e)}a(R,"hasUnreconciledManagedWorktree");async function Y(e,n={}){return!["done","dead","canceled"].includes(e.status)||e.pi_pid!==null||e.frozen_at!==null||await R(e.node_id)||e.node_id===x()||j(e)?!1:n.pastTtl===!0||!["crashed","stranded",null].includes(e.terminal_reason??null)}a(Y,"isCollectible");async function L(e){if(e.length===0)return[];const n=e.map(()=>"?").join(",");return(await(await r()).prepareUncached(`SELECT DISTINCT session_id FROM sessions WHERE node_id IN (${n}) AND session_id IS NOT NULL`).all(...e)).map(({session_id:i})=>i)}a(L,"nodeConversationIds");async function D(e){const n=await r(),t=n.prepare("SELECT 1 FROM sessions WHERE session_id = ? LIMIT 1"),i=n.prepare(`SELECT 1 FROM nodes n WHERE n.fork_source_file LIKE '%\\_' || ? || '.jsonl' ESCAPE '\\'
|
|
59
|
+
AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.node_id = n.node_id) LIMIT 1`);for(const o of e)if(!(await t.get(o)!==void 0||await i.get(o)!==void 0))try{await re().delete(o)}catch(s){if(!(s instanceof se))throw s}}a(D,"deleteNodeConversations");async function g(e,n){const t=await H(e,n);return t===null||t.deleted_at!==null?!1:(await Ae(e,t),!0)}a(g,"deleteNodeObject");async function v(e){return{nodeId:e,app:await N(e)===null?null:await Oe(e)}}a(v,"nodeFiles");async function J({nodeId:e,app:n}){n!==null&&W(b(n).nodes,await Le(e,n)),W(ae(C(e)),C(e))}a(J,"removeNodeAppTrees");function Q({nodeId:e}){return[he(e)]}a(Q,"nodeOwnedDirs");function V({nodeId:e,app:n}){if(n!==null)try{ee(U(b(n).conversations,e))}catch(t){if(!["ENOENT","ENOTEMPTY"].includes(t.code??""))throw t}}a(V,"removeNodeConversationDir");async function k(e){await J(e);for(const n of Q(e))ne(n,{recursive:!0,force:!0});V(e)}a(k,"removeNodeFiles");async function it(e){if(await R(e))return{deleted:!1};const n=await v(e),t=await L([e]),i=await p(async o=>(await g(o,e),{deleted:!0}));return await D(t),await k(n),i}a(it,"deleteNode");async function ot(e){const n=[...new Set(e)];if(n.length===0)return[];const t=await y(n,R);if(t.length>0)throw new Error(`refusing to delete nodes with unreconciled managed worktrees: ${t.join(", ")}`);const i=await h(n,v),o=await L(n),s=n.map(()=>"?").join(","),d=(await(await r()).prepareUncached(`SELECT node_id FROM nodes WHERE node_id IN (${s}) ORDER BY created`).all(...n)).map(({node_id:u})=>u),c=await y(d,async u=>await p(async _=>await g(_,u)));await D(o);for(const u of c){const _=i[n.indexOf(u)];await A(_,async()=>await k(_))}return c}a(ot,"deleteNodes");async function rt(e){const n=e.dryRun??!1,t=e.includeStale??!1,i=Date.now()-e.ttlDays*864e5,o=new Date(i).toISOString(),s=x()??"",d=await r(),c=a(l=>X(l)<i,"quiet"),u=a(l=>({node_id:l.node_id,status:l.status,created:l.created}),"toPruned"),_=(await y((await d.prepare(`SELECT * FROM node_rows
|
|
65
60
|
WHERE status IN ('dead', 'done', 'canceled') AND created < ?
|
|
66
|
-
ORDER BY created`).all(o)).map(f),async
|
|
61
|
+
ORDER BY created`).all(o)).map(f),async l=>await Y(l,{pastTtl:!0})&&c(l)&&!await e.held(l.node_id))).map(u),S=t?(await y((await d.prepare(`SELECT * FROM node_rows
|
|
67
62
|
WHERE status IN ('active', 'idle') AND created < ?
|
|
68
|
-
ORDER BY created`).all(o)).map(f),async
|
|
63
|
+
ORDER BY created`).all(o)).map(f),async l=>l.node_id!==s&&!me(l.pi_pid??null)&&(l.frozen_at??null)===null&&(l.respawn_not_before??null)===null&&l.intent!=="idle-release"&&!j(l)&&!await de(l.node_id)&&!await R(l.node_id)&&c(l)&&!await e.held(l.node_id))).map(u):[],m=await K([..._,...S]);return n||m.length===0?{pruned:m,dryRun:n}:(await G(m,e.removeDirs),{pruned:m,dryRun:n})}a(rt,"pruneNodes");async function K(e){const t=(await r()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ?"),i=new Set(e.map(o=>o.node_id));return await y(e,async o=>(await t.all(o.node_id,o.node_id)).every(s=>i.has(s.node_id)))}a(K,"retainRunRootsWithMembers");async function G(e,n){if(e.length===0)return;const t=await h(e,async o=>await v(o.node_id)),i=await L(e.map(o=>o.node_id));for(const o of e)await p(async s=>await g(s,o.node_id));if(await D(i),n!==void 0){for(const o of t)await A(o,async()=>{await J(o),V(o)});n(t.flatMap(Q));return}for(const o of t)await A(o,async()=>await k(o))}a(G,"deletePrunedNodes");async function A(e,n){try{n()}catch(t){oe({level:"error",event:"canvas.node_files.remove_failed",error:t,fields:{node_id:e.nodeId}})}}a(A,"eachNodeBestEffort");function X(e){let n=0;for(const t of[e.created,e.launched_at,e.last_settled_at,e.finalized_at,e.outcome_at,e.last_activity_at]){if(t==null)continue;const i=Date.parse(t);Number.isFinite(i)&&i>n&&(n=i)}try{const t=te(U(Se(e.node_id),"telemetry.json"),{throwIfNoEntry:!1});t!==void 0&&t.mtimeMs>n&&(n=t.mtimeMs)}catch{}return n}a(X,"lastActivityMs");async function st(e){const n=e.dryRun??!1,t=await Fe(),i=await y(t,async u=>await Y(u)&&!await e.held(u.node_id)),o=t.length-i.length,s=Math.max(0,e.maxNodes-o),d=i.map(u=>({row:u,recency:X(u)})).sort((u,_)=>_.recency-u.recency).slice(s).map(({row:u})=>({node_id:u.node_id,status:u.status,created:u.created})),c=await K(d);return n||c.length===0?{pruned:c,dryRun:n}:(await G(c),{pruned:c,dryRun:n})}a(st,"pruneToLimit");export{B as PARKED_RESIDENT_SQL,He as RESUMABLE_NODE_SQL,Nn as abandonCreateRequest,cn as assertRunNotDeleting,Mn as browseNodes,Rn as canonicalSessionFile,et as childrenOf,Xn as claimWarmSpare,Un as clearFinalReportIfMatches,Cn as clearFrozen,mn as completeCreateRequest,_n as createNode,it as deleteNode,ot as deleteNodes,Tn as dueNodeDeadlines,pn as findCreateRequest,Sn as findNodeBySessionFile,xn as getFinalReport,N as getNode,hn as getRow,On as getRowByPane,$n as graphNodes,Y as isCollectible,Gn as isWarmSpare,X as lastActivityMs,Jn as listNodeIdentities,Fe as listNodes,Hn as listNodesByProfile,Bn as listNodesForSupervision,Yn as listParkedResidentsWithWakeMail,tt as listResumableRoots,qn as listTerminalPlacementResidue,Kn as listWarmSpares,v as nodeFiles,be as nodesForSupervisionSql,En as pruneExpiredCreateRequests,rt as pruneNodes,st as pruneToLimit,wn as recordCreateRequestNode,jn as recordLaunch,bn as recordRespawnAttempt,In as recordSettledTurn,Qn as registerWarmSpare,k as removeNodeFiles,fn as reserveCreateRequest,yn as reserveCreateRequestNode,zn as resumableNodeCwds,nt as rosterNodes,Fn as setFrozen,kn as setIntent,Ln as setNodeDeadline,Wn as setPresence,vn as setStatus,Dn as shiftProfileNodeDeadlines,Ce as terminalPlacementResidueSql,An as transitionStatusIntent,Vn as unregisterWarmSpare,gn as updateNode,Pn as updateNodeTelemetry,Zn as warmSpareCount,at as withFreshTerminalGuard};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var
|
|
1
|
+
var w=Object.defineProperty;var a=(n,r)=>w(n,"name",{value:r,configurable:!0});import{withCanvasWrite as c,openDb as i}from"./db.js";import{capturePidIdentities as l,identitiesMatch as R,killProcessGroup as m}from"./pid.js";import{isProfilePaused as h}from"../profiles/manifest.js";import{stopAppProcess as D}from"../spaces/process.js";import{emitEvent as S}from"../events/emit.js";import{personId as p}from"./person.js";const N=20;function O(n){if(typeof n!="string")return null;try{const r=JSON.parse(n);return Array.isArray(r)?r.filter(e=>typeof e=="string"):null}catch{return null}}a(O,"scopesFrom");function s(n){return{cron_id:n.cron_id,name:n.name,created_by:n.created_by??null,grantee:n.grantee,sub:n.sub??p(),grant_id:n.grant_id??null,scopes:O(n.scopes_json),origin_run_id:n.origin_run_id??null,run_execution_id:n.run_execution_id??null,isolated:Number(n.isolated)!==0,command:n.command,fire_at:n.fire_at,recur:n.recur??null,tz:n.tz??null,expires_at:n.expires_at??null,anchor_node:n.anchor_node??null,cancel_on_wake:Number(n.cancel_on_wake)!==0,cwd:n.cwd,env_json:n.env_json??null,profile:n.profile??null,scope:n.scope,run_timeout_s:Number(n.run_timeout_s),overlap:n.overlap,on_output:n.on_output,sink:n.sink,tier:n.tier,last_output_hash:n.last_output_hash??null,held:Number(n.held)!==0,last_poke_at:n.last_poke_at??null,state:n.state,run_state:n.run_state,run_pid:n.run_pid??null,run_pid_identity:n.run_pid_identity??null,run_started_at:n.run_started_at??null,run_lease_owner:n.run_lease_owner??null,run_out_of_band:Number(n.run_out_of_band)!==0,created:n.created,updated:n.updated}}a(s,"cronFrom");function E(n){try{const r=JSON.parse(n);if(typeof r.node=="string")return{kind:"node",node:r.node};if(typeof r.spawn=="string")return{kind:"spawn",spawn:r.spawn};if(r.human===!0)return{kind:"human"}}catch{}return null}a(E,"parseCronSink");function F(n){const r=E(n);if(r===null)return null;switch(r.kind){case"node":return`node:${r.node}`;case"spawn":return`spawn:${r.spawn}`;case"human":return"human"}}a(F,"cronSinkDisplay");async function y(n,r="SIGTERM"){if(!_(n))return!1;if(process.platform!=="linux")return m(n.run_pid,r),!0;try{return await D(n.run_pid,r,n.anchor_node??void 0),!0}catch(e){if(!_(n))return!1;throw e}}a(y,"stopLeasedCronRun");function d(n,r="SIGTERM"){return _(n)?(y(n,r).catch(e=>{S({level:"error",event:"cron.run.stop_failed",error:e,fields:{cron_id:n.cron_id,pid:n.run_pid}})}),!0):!1}a(d,"killLeasedCronRun");function _(n){const r=n.run_pid;if(r==null||n.run_pid_identity==null)return!1;const e=l([r]);if(e===null)return!1;const t=e.get(r);return t===void 0?!1:R(t,n.run_pid_identity)}a(_,"verifyLeasedCronRun");async function U(n){return await c(async r=>{const e=[];if(n.cancel_on_wake&&n.anchor_node!==null){const t=(await r.prepare("SELECT * FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1").all(n.anchor_node)).map(s);for(const o of t)o.run_state==="running"&&d(o),await r.prepare("DELETE FROM crons WHERE cron_id = ?").run(o.cron_id),e.push(o.cron_id)}return await L(r,n),{cron_id:n.cron_id,replaced:e}})}a(U,"armCron");async function L(n,r){const e=new Date().toISOString();await n.prepare(`INSERT INTO crons (
|
|
2
2
|
cron_id, name, created_by, grantee, sub, grant_id, scopes_json, origin_run_id, isolated, command, fire_at, recur, tz, expires_at,
|
|
3
3
|
anchor_node, cancel_on_wake, cwd, env_json, profile, scope,
|
|
4
4
|
run_timeout_s, overlap, on_output, sink, tier, created, updated
|
|
5
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(r.cron_id,r.name,r.created_by,r.grantee??"app:terminal",r.sub??
|
|
5
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(r.cron_id,r.name,r.created_by,r.grantee??"app:terminal",r.sub??p(),r.grant_id??null,r.scopes==null?null:JSON.stringify(r.scopes),r.origin_run_id??null,r.isolated?1:0,r.command,r.fire_at,r.recur,r.tz,r.expires_at,r.anchor_node,r.cancel_on_wake?1:0,r.cwd,r.env_json,r.profile,r.scope,r.run_timeout_s,r.overlap,r.on_output,r.sink,r.tier,e,e)}a(L,"insertCron");async function M(n){const r=await(await i()).prepare("SELECT * FROM crons WHERE cron_id = ?").get(n);return r===void 0?null:s(r)}a(M,"getCron");async function v(n){const r=await i();let e;return"profile"in n?e=await r.prepare("SELECT * FROM crons WHERE scope = 'profile' AND profile = ? ORDER BY fire_at").all(n.profile):"global"in n?e=await r.prepare("SELECT * FROM crons WHERE scope = 'global' ORDER BY fire_at").all():e=await r.prepare("SELECT * FROM crons ORDER BY fire_at").all(),e.map(s)}a(v,"listCrons");async function I(n,r){return await c(async e=>{const t=await e.prepare("SELECT * FROM crons WHERE cron_id = ?").get(n);if(t===void 0)return!1;const o=s(t);return o.run_state==="running"&&r?.preserveRunningProcess!==!0&&d(o),await e.prepare("DELETE FROM crons WHERE cron_id = ?").run(n),!0})}a(I,"cancelCron");function f(n,r){const e=[...new Set(r)];return e.length===0?{sql:"profile = ?",params:[n]}:{sql:`profile = ? OR anchor_node IN (${e.map(()=>"?").join(",")})`,params:[n,...e]}}a(f,"profileCronWhere");async function P(n,r){const e=f(n,r);return(await(await i()).prepareUncached(`SELECT cron_id FROM crons WHERE ${e.sql} ORDER BY created, cron_id`).all(...e.params)).map(({cron_id:t})=>t)}a(P,"cronIdsForProfileDeletion");async function b(n,r){return await c(async e=>{const t=f(n,r),o=(await e.prepareUncached(`SELECT * FROM crons WHERE ${t.sql} ORDER BY created, cron_id`).all(...t.params)).map(s);for(const u of o)u.run_state==="running"&&d(u);return await e.prepareUncached(`DELETE FROM crons WHERE ${t.sql}`).run(...t.params),o.map(({cron_id:u})=>u)})}a(b,"deleteCronsForProfile");async function B(n){return(await(await i()).prepare("SELECT cron_id FROM crons WHERE profile = ? ORDER BY created, cron_id").all(n)).map(({cron_id:r})=>r)}a(B,"cronIdsForProfileDetach");async function Y(n){return await c(async r=>{const e=(await r.prepare("SELECT cron_id FROM crons WHERE profile = ? ORDER BY created, cron_id").all(n)).map(({cron_id:t})=>t);return e.length>0&&await r.prepare("UPDATE crons SET profile = NULL, scope = 'global', updated = ? WHERE profile = ?").run(new Date().toISOString(),n),e})}a(Y,"globalizeCronsForProfile");async function $(n){return(await(await i()).prepare("DELETE FROM crons WHERE cron_id = ?").run(n)).changes>0}a($,"consumeCron");async function j(n,r){return(await(await i()).prepare("UPDATE crons SET state = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)).changes>0}a(j,"setCronState");async function q(n,r){await(await i()).prepare("UPDATE crons SET last_output_hash = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)}a(q,"setCronLastOutputHash");async function z(n){const r=await(await i()).prepare(`SELECT anchor_node, cancel_on_wake, sink
|
|
6
6
|
FROM crons
|
|
7
7
|
WHERE (anchor_node = ? AND cancel_on_wake = 1 AND state = 'active')
|
|
8
8
|
OR (
|
|
@@ -11,26 +11,26 @@ var f=Object.defineProperty;var a=(n,r)=>f(n,"name",{value:r,configurable:!0});i
|
|
|
11
11
|
run_state = 'running'
|
|
12
12
|
OR (run_state = 'idle' AND state = 'active' AND (expires_at IS NULL OR expires_at > ?))
|
|
13
13
|
)
|
|
14
|
-
)`).all(n,new Date().toISOString());for(const e of r){if(e.anchor_node===n&&Number(e.cancel_on_wake)!==0)return!0;const t=
|
|
14
|
+
)`).all(n,new Date().toISOString());for(const e of r){if(e.anchor_node===n&&Number(e.cancel_on_wake)!==0)return!0;const t=E(e.sink);if(t?.kind==="node"&&t.node===n)return!0}return!1}a(z,"hasPendingCronWake");async function G(n){return await c(async r=>{const e=(await r.prepare("SELECT * FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1").all(n)).map(s);for(const t of e)t.run_state==="running"&&d(t),await r.prepare("DELETE FROM crons WHERE cron_id = ?").run(t.cron_id);return e})}a(G,"cancelCronsOnWake");async function J(n,r){await(await i()).prepare("UPDATE crons SET fire_at = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)}a(J,"advanceCron");async function V(n){return(await(await i()).prepare(`SELECT * FROM crons
|
|
15
15
|
WHERE state = 'active' AND run_state = 'idle' AND fire_at <= ?
|
|
16
|
-
ORDER BY fire_at, created, cron_id`).all(n)).map(s).filter(r=>!h(r.profile))}a(
|
|
16
|
+
ORDER BY fire_at, created, cron_id`).all(n)).map(s).filter(r=>!h(r.profile))}a(V,"dueClockCrons");async function K(n,r,e,t,o=null){const u=await(await i()).prepare(`UPDATE crons
|
|
17
17
|
SET run_state = 'running', run_started_at = ?, run_pid = NULL, run_pid_identity = NULL,
|
|
18
18
|
run_lease_owner = ?, run_out_of_band = ?, run_execution_id = ?, updated = ?
|
|
19
|
-
WHERE cron_id = ? AND run_state = 'idle'`).run(e,r,t?1:0,o,new Date().toISOString(),n);return Number(u.changes)>0}a(
|
|
19
|
+
WHERE cron_id = ? AND run_state = 'idle'`).run(e,r,t?1:0,o,new Date().toISOString(),n);return Number(u.changes)>0}a(K,"acquireCronRunLease");async function Q(n,r,e,t){const o=l([t])?.get(t)??null,u=await(await i()).prepare(`UPDATE crons SET run_pid = ?, run_pid_identity = ?, updated = ?
|
|
20
20
|
WHERE cron_id = ? AND run_state = 'running' AND run_lease_owner = ?
|
|
21
|
-
AND run_started_at = ? AND run_pid IS NULL`).run(t,o,new Date().toISOString(),n,r,e);return Number(u.changes)>0}a(
|
|
21
|
+
AND run_started_at = ? AND run_pid IS NULL`).run(t,o,new Date().toISOString(),n,r,e);return Number(u.changes)>0}a(Q,"attachCronRunPid");async function X(n,r){let e="";const t=[];r!==void 0&&(r.kind==="clear"?e=", held = 0":r.kind==="park"?(e=r.fireAt!==void 0?", held = 1, fire_at = ?":", held = 1",r.fireAt!==void 0&&t.push(r.fireAt)):(e=", held = 0, fire_at = ?",t.push(r.nowIso))),await(await i()).prepareUncached(`UPDATE crons
|
|
22
22
|
SET run_state = 'idle', run_started_at = NULL, run_pid = NULL, run_pid_identity = NULL,
|
|
23
23
|
run_lease_owner = NULL, run_out_of_band = 0, run_execution_id = NULL, updated = ?${e}
|
|
24
|
-
WHERE cron_id = ?`).run(new Date().toISOString(),...t,n)}a(
|
|
24
|
+
WHERE cron_id = ?`).run(new Date().toISOString(),...t,n)}a(X,"releaseCronRunLease");async function Z(n,r=null){return await c(async e=>{const t=r===null?"":" AND grantee = ?",o=r===null?[]:[r];await e.prepareUncached(`UPDATE crons SET last_poke_at = ? WHERE 1 = 1${t}`).run(n,...o);const u=await e.prepareUncached(`UPDATE crons SET fire_at = ?, held = 0, updated = ? WHERE held = 1 AND state = 'active' AND run_state = 'idle'${t}`).run(n,n,...o);return Number(u.changes)})}a(Z,"pokeHeldCrons");async function nn(n){return(await(await i()).prepare(`SELECT * FROM crons
|
|
25
25
|
WHERE state = 'active' AND run_state = 'running' AND run_out_of_band = 0
|
|
26
26
|
AND recur IS NOT NULL AND fire_at <= ?
|
|
27
|
-
ORDER BY fire_at, created, cron_id`).all(n)).map(s)}a(
|
|
27
|
+
ORDER BY fire_at, created, cron_id`).all(n)).map(s)}a(nn,"dueOverlapCrons");async function rn(n){return(await(await i()).prepare(`SELECT * FROM crons
|
|
28
28
|
WHERE expires_at IS NOT NULL AND expires_at <= ? AND run_state = 'idle'
|
|
29
|
-
ORDER BY expires_at`).all(n)).map(s)}a(
|
|
29
|
+
ORDER BY expires_at`).all(n)).map(s)}a(rn,"expiredIdleCrons");async function en(n){return(await(await i()).prepare("SELECT * FROM cron_runs WHERE cron_id = ? ORDER BY started DESC, run_id DESC").all(n)).map(r=>({run_id:r.run_id,cron_id:r.cron_id,started:r.started,finished:r.finished,duration_ms:Number(r.duration_ms),exit_code:r.exit_code??null,stdout_head:r.stdout_head??null,stderr_head:r.stderr_head??null,delivered:r.delivered??null}))}a(en,"listCronRuns");async function tn(){return(await(await i()).prepare("SELECT * FROM crons WHERE run_state = 'running'").all()).map(s)}a(tn,"runningCrons");async function g(n,r){return await n.prepare("SELECT 1 FROM crons WHERE cron_id = ?").get(r.cron_id)===void 0?!1:(await n.prepare(`INSERT INTO cron_runs (
|
|
30
30
|
run_id, cron_id, started, finished, duration_ms, exit_code,
|
|
31
31
|
stdout_head, stderr_head, delivered
|
|
32
32
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(r.run_id,r.cron_id,r.started,r.finished,r.duration_ms,r.exit_code,r.stdout_head,r.stderr_head,r.delivered),await n.prepare(`DELETE FROM cron_runs
|
|
33
33
|
WHERE cron_id = ? AND run_id NOT IN (
|
|
34
34
|
SELECT run_id FROM cron_runs WHERE cron_id = ?
|
|
35
35
|
ORDER BY started DESC, run_id DESC LIMIT ?
|
|
36
|
-
)`).run(r.cron_id,r.cron_id,
|
|
36
|
+
)`).run(r.cron_id,r.cron_id,N),!0)}a(g,"recordCronRunLocked");async function an(n){return await c(async r=>await g(r,n))}a(an,"recordCronRun");export{N as CRON_RUN_RING,K as acquireCronRunLease,J as advanceCron,U as armCron,Q as attachCronRunPid,I as cancelCron,G as cancelCronsOnWake,$ as consumeCron,P as cronIdsForProfileDeletion,B as cronIdsForProfileDetach,F as cronSinkDisplay,b as deleteCronsForProfile,V as dueClockCrons,nn as dueOverlapCrons,rn as expiredIdleCrons,M as getCron,Y as globalizeCronsForProfile,z as hasPendingCronWake,d as killLeasedCronRun,en as listCronRuns,v as listCrons,E as parseCronSink,Z as pokeHeldCrons,an as recordCronRun,g as recordCronRunLocked,X as releaseCronRunLease,tn as runningCrons,q as setCronLastOutputHash,j as setCronState,y as stopLeasedCronRun,_ as verifyLeasedCronRun};
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
`;
|
|
3
|
-
`,384))return await s.query(`DROP DATABASE ${o}`),w(e,n())}finally{await s.end()}return w(e,o)}a(tt,"testDatabaseUrl");async function et(){const e=process.env.CRTR_TEST_PG_URL;if(e)return tt(e);if(Z())throw new Error("CRTR_TEST_PG_URL is not set: a test must reach its own Postgres (run the suite through the lane runner); refusing to open a real canvas database");return
|
|
4
|
-
${
|
|
1
|
+
var R=Object.defineProperty;var a=(e,t)=>R(e,"name",{value:t,configurable:!0});import{AsyncLocalStorage as L}from"node:async_hooks";import{randomBytes as x}from"node:crypto";import{appendFileSync as k,mkdirSync as M,readFileSync as y}from"node:fs";import{dirname as N,join as T}from"node:path";import{fileURLToPath as P}from"node:url";import E from"pg";import{resolveDatabaseUrl as D}from"../../daemon/postgres/supervisor.js";import{publishFileExclusive as q}from"../fs-utils.js";import{initializeEmptyLayout as B,runtimeRoot as l}from"../layout.js";import{translatePlaceholders as I,translateOnce as U}from"../statements.js";import{ensureHome as H}from"./paths.js";import{CANVAS_SCHEMA_VERSION as h}from"./schema-version.js";const F="crtr_test_template";class K extends Error{static{a(this,"CanvasConstraintError")}constraint;code="23505";constructor(t,n,r){super(t,r),this.name="CanvasConstraintError",this.constraint=n}}function V(e){const t=process.env.CRTR_DB_OPEN_LEDGER;if(t)try{const n=`${new Date().toISOString()} ${process.pid} ${process.argv[1]??"<unknown>"} ${e.replace(/\/\/([^:@/]*):[^@/]*@/,"//$1@")}
|
|
2
|
+
`;k(t,n)}catch{}}a(V,"recordOpenLedger");const G=1e3,d=new L;class W{static{a(this,"FifoLock")}tail=Promise.resolve();pending=0;get busy(){return this.pending>0}async acquire(){this.pending++;const t=this.tail;let n;return this.tail=new Promise(r=>{n=r}),await t,()=>{this.pending--,n()}}}const J=new Set([114,3802]),Y={getTypeParser:a(((e,t)=>J.has(e)?n=>n:e===20||e===1700?n=>{const r=Number(n);if(e===20&&!Number.isSafeInteger(r))throw new Error(`canvas db: bigint ${n} does not fit a JS number`);return r}:E.types.getTypeParser(e,t)),"getTypeParser")};class j{static{a(this,"Handle")}url;lock=new W;db;client=null;connecting=null;schemaChecked=!1;active=0;constructor(t){this.url=t;const n=this;this.db={prepare:a(r=>this.statement(r,!0),"prepare"),prepareUncached:a(r=>this.statement(r,!1),"prepareUncached"),exec:a(r=>this.gated(async()=>{await this.send({text:r})}),"exec"),get inTransaction(){return g(n)}}}async connected(){return this.client!==null?this.client:(this.connecting??=this.connect().finally(()=>{this.connecting=null}),this.connecting)}async connect(){V(this.url);const t=new E.Client({connectionString:this.url,types:Y,application_name:`crtr-${process.pid}`,connectionTimeoutMillis:1e4});t.on("error",n=>{console.warn(`[canvas db] connection error: ${n.message}`),this.drop(t)}),t.on("end",()=>this.drop(t)),await t.connect();try{this.schemaChecked||(await Q(t),this.schemaChecked=!0)}catch(n){throw await t.end().catch(()=>{}),n}return t.connection.stream.unref(),this.client=t,t}drop(t){this.client===t&&(this.client=null)}async send(t){const n=await this.connected(),r=d.getStore();if(r!==void 0&&r.open&&r.handle===this&&r.client!==n)throw new Error("canvas db: the connection was lost during this transaction; it was rolled back");return this.sendOn(n,t)}async sendOn(t,n){const r=t.connection.stream;this.active++===0&&r.ref();try{return await t.query(n)}catch(o){throw this.mapError(o,t)}finally{--this.active===0&&r.unref()}}mapError(t,n){const r=t;return r.code==="23505"?new K(r.message??"unique constraint violation",r.constraint,{cause:t}):((r.code==="57P01"||r.code==="ECONNRESET"||r.code==="EPIPE"||/Connection terminated|connection error|Client was closed/i.test(r.message??""))&&(this.drop(n),n.end().catch(()=>{})),t)}async gated(t){if(g(this)){const r=d.getStore(),o=performance.now();try{return await t()}finally{r.statementMs+=performance.now()-o}}if(!this.lock.busy)return t();const n=await this.lock.acquire();try{return await t()}finally{n()}}statement(t,n){const r=n?(()=>{const s=I(t);return{text:s.text,name:s.name}})():{text:U(t).text},o=a(s=>this.gated(()=>this.send({...r,values:s.map(z)})),"query");return{get:a(async(...s)=>(await o(s)).rows[0],"get"),all:a(async(...s)=>(await o(s)).rows,"all"),run:a(async(...s)=>({changes:(await o(s)).rowCount??0}),"run")}}async close(){const t=this.client;this.client=null,t!==null&&(t.connection.stream.ref(),await t.end().catch(()=>{}))}}function z(e){return ArrayBuffer.isView(e)&&!Buffer.isBuffer(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):e}a(z,"bindable");function g(e){const t=d.getStore();return t!==void 0&&t.open&&t.handle===e}a(g,"ownsTransaction");const X=N(P(import.meta.url)),S=T(X,"schema-pg.sql"),v=7427001;async function A(e){if((await e.query("SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'canvas_meta'")).rowCount===0)return;const n=await e.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'");return n.rows[0]===void 0?void 0:Number(n.rows[0].value)}a(A,"storedSchemaVersion");async function Q(e){await e.query("SELECT pg_advisory_lock($1)",[v]);try{let t=await A(e);if(t===void 0)await e.query(y(S,"utf8")),t=await A(e);else if(t===h){const n=/-- environments:begin\n([\s\S]*?)-- environments:end/.exec(y(S,"utf8"));if(n===null)throw new Error(`${S} has no environments block`);await e.query(n[1])}if(t!==h)throw new Error(t!==void 0&&t>h?`unsupported canvas schema version ${t} (this build supports ${h}); this build is older than the database. Select a newer runtime generation.`:`canvas schema version ${String(t)} does not match this build's ${h}`)}finally{await e.query("SELECT pg_advisory_unlock($1)",[v]).catch(()=>{})}}a(Q,"ensureSchema");function Z(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}a(Z,"runningUnderNodeTest");function w(e,t){const n=/^(postgres(?:ql)?:\/\/[^/?#]*)(\/[^?#]*)?(.*)$/s.exec(e);if(n===null)throw new Error(`not a postgres URL: ${e.replace(/\/\/[^@/]*@/,"//")}`);return`${n[1]}/${t}${n[3]}`}a(w,"withDatabase");async function tt(e){const t=T(l(),"test-db"),n=a(()=>{try{return y(t,"utf8").trim()||void 0}catch(m){if(m.code==="ENOENT")return;throw m}},"recorded"),r=n();if(r!==void 0)return w(e,r);M(l(),{recursive:!0});const o=`t_${x(8).toString("hex")}`,s=new E.Client({connectionString:w(e,"postgres")});await s.connect();try{if(await s.query(`CREATE DATABASE ${o} TEMPLATE ${F} STRATEGY file_copy`),!q(t,`${o}
|
|
3
|
+
`,384))return await s.query(`DROP DATABASE ${o}`),w(e,n())}finally{await s.end()}return w(e,o)}a(tt,"testDatabaseUrl");async function et(){const e=process.env.CRTR_TEST_PG_URL;if(e)return tt(e);if(Z())throw new Error("CRTR_TEST_PG_URL is not set: a test must reach its own Postgres (run the suite through the lane runner); refusing to open a real canvas database");return D(l())}a(et,"canvasDatabaseUrl");const f=new Map,p=new Map;async function $(){B(),H();const e=await et();p.set(l(),e);let t=f.get(e);return t===void 0&&f.set(e,t=new j(e)),await t.connected(),t}a($,"openHandle");async function pt(){return(await $()).db}a(pt,"openDb");async function yt(){const e=p.get(l());if(e===void 0)return;p.delete(l());const t=f.get(e);t!==void 0&&(f.delete(e),await t.close())}a(yt,"closeDb");async function Et(){const e=[...f.values()];f.clear(),p.clear(),await Promise.all(e.map(t=>t.close()))}a(Et,"closeAllDbs");function gt(e){const t=d.getStore();if(t===void 0||!t.open){queueMicrotask(e);return}t.afterCommit.push(e)}a(gt,"afterCommit");let nt=0;async function St(e,t={}){const n=await $(),r=n.db;if(g(n)){if(!t.savepoint&&!t.rollback)return e(r);const i=`canvas_write_${++nt}`;await n.send({text:`SAVEPOINT ${i}`});const c=d.getStore()?.afterCommit,_=c?.length??0;let C;try{C=await e(r)}catch(O){throw await n.send({text:`ROLLBACK TO ${i}; RELEASE ${i}`}),c?.splice(_),O}return await n.send({text:t.rollback?`ROLLBACK TO ${i}; RELEASE ${i}`:`RELEASE ${i}`}),t.rollback&&c?.splice(_),C}const o=await n.lock.acquire(),s=performance.now(),m=new Error("canvas write transaction entered here");let b=!1;const u={handle:n,client:void 0,open:!0,afterCommit:[],statementMs:0};try{const i=await n.connected();await n.sendOn(i,{text:"BEGIN"}),u.client=i;try{const c=await d.run(u,()=>e(r));return await n.sendOn(i,{text:t.rollback?"ROLLBACK":"COMMIT"}),b=!t.rollback,c}catch(c){try{await n.sendOn(i,{text:"ROLLBACK"})}catch{}throw c}}finally{u.open=!1;const i=performance.now()-s;if(i-u.statementMs>G&&console.warn(`[canvas db] write transaction held ${Math.round(i)}ms (${Math.round(u.statementMs)}ms in its statements); its body did non-db work while every other db caller waited
|
|
4
|
+
${m.stack}`),o(),b)for(const c of u.afterCommit)queueMicrotask(c)}}a(St,"withCanvasWrite");export{K as CanvasConstraintError,gt as afterCommit,et as canvasDatabaseUrl,Et as closeAllDbs,yt as closeDb,pt as openDb,St as withCanvasWrite};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var V=Object.defineProperty;var f=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as k,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as ee,OBJECT_COLUMNS as te,pgColumns as ne,quote as c,TABLE_ORDER as C}from"./import-sqlite.js";const re=5e3,se=new Set,oe={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ae={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version",ee,F]);function ie(i){const a=J(A(Q(),"crtr-export-")),r=process.env.CRTR_HOME;process.env.CRTR_HOME=a,k(A(a,"runtime","nodes"),{recursive:!0});try{const s=new W(i);return s.exec("PRAGMA journal_mode = DELETE;"),s.exec("PRAGMA foreign_keys = ON;"),z(s),s.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),s}finally{r===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=r,P(a,{recursive:!0,force:!0})}}f(ie,"createEmptyDatabase");async function he(i,a=()=>{}){const r=Z();if(Y(r))throw new Error(`${r} already exists; refusing to overwrite it`);k(A(r,".."),{recursive:!0,mode:448});const s=`${r}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,types:{getTypeParser:f(((l,o)=>l===20?d=>Number(d):j.types.getTypeParser(l,o)),"getTypeParser")}});await t.connect();let
|
|
1
|
+
var V=Object.defineProperty;var f=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as k,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as ee,OBJECT_COLUMNS as te,pgColumns as ne,quote as c,TABLE_ORDER as C}from"./import-sqlite.js";const re=5e3,se=new Set(["environments"]),oe={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ae={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version",ee,F]);function ie(i){const a=J(A(Q(),"crtr-export-")),r=process.env.CRTR_HOME;process.env.CRTR_HOME=a,k(A(a,"runtime","nodes"),{recursive:!0});try{const s=new W(i);return s.exec("PRAGMA journal_mode = DELETE;"),s.exec("PRAGMA foreign_keys = ON;"),z(s),s.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),s}finally{r===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=r,P(a,{recursive:!0,force:!0})}}f(ie,"createEmptyDatabase");async function he(i,a=()=>{}){const r=Z();if(Y(r))throw new Error(`${r} already exists; refusing to overwrite it`);k(A(r,".."),{recursive:!0,mode:448});const s=`${r}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,types:{getTypeParser:f(((l,o)=>l===20?d=>Number(d):j.types.getTypeParser(l,o)),"getTypeParser")}});await t.connect();let m;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");m=ie(s);const o=m;o.exec("PRAGMA foreign_keys = OFF;");const d=await ne(t),L=[...d.keys()].filter(e=>!se.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const _={};for(const[e,O]of Object.entries(ae)){const h=new Map;for(const E of(await t.query(`SELECT ${O} AS id, seq FROM ${e}`)).rows)h.set(E.id,Number(E.seq));_[e]=h}const B=_.objects;let M=0;o.exec("BEGIN");for(const e of C){const O=Date.now(),h=new Set(o.prepare(`PRAGMA table_info(${c(e)})`).all().map(n=>n.name)),E=d.get(e);if(E===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=E.some(n=>n.name==="seq"),$=E.filter(n=>h.has(n.name)),K=new Set(te[e]??[]),R=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",v=$.map(n=>n.type==="jsonb"?`${c(n.name)}::text AS ${c(n.name)}`:c(n.name));e==="watches"&&v.push("watch_id AS _watch_id"),R&&v.push("seq AS _seq");const y=$.map(n=>c(n.name)),U=o.prepare(`INSERT INTO ${c(e)} (${R?["rowid",...y]:y}) VALUES (${(R?["?",...y]:y).map(()=>"?").join(", ")})`),q=G?"seq":oe[e],T=`export_${e}`;await t.query(`DECLARE ${T} NO SCROLL CURSOR FOR SELECT ${v.join(", ")} FROM ${c(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${q===void 0?"":` ORDER BY ${q}`}`);let b=0;for(;;){const n=await t.query(`FETCH ${re} FROM ${T}`);if(n.rows.length===0)break;for(const N of n.rows){const D=R?[Number(N._seq)]:[];for(const p of $){let w=N[p.name];e==="revision_parents"&&(p.name==="child"||p.name==="parent")?w=x(_.revisions,"revision",e,p.name,w):e==="watch_subscriptions"&&p.name==="watch"?w=x(_.watches,"watch",e,p.name,w):K.has(p.name)&&(w=x(B,"object",e,p.name,w)),D.push(w??null)}U.run(...D)}b+=n.rows.length}await t.query(`CLOSE ${T}`),M+=b,a(`export ${e}: ${b} rows in ${Date.now()-O} ms`)}o.exec("COMMIT"),await t.query("COMMIT");const g=o.prepare("PRAGMA foreign_key_check").all();if(g.length>0)throw new Error(`export: ${g.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(g[0])}`);await ce(t,o);const S=o.prepare("PRAGMA user_version").get().user_version;if(S!==I.length)throw new Error(`export: exported canvas.db is at user_version ${S}, expected ${I.length}`);return o.close(),m=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:r})]),X(s,r),{path:r,rows:M,ms:Date.now()-u,userVersion:S}}catch(l){await t.query("ROLLBACK").catch(()=>{});try{m?.close()}catch{}for(const o of["","-wal","-shm","-journal"])P(`${s}${o}`,{force:!0});throw l}finally{await t.end().catch(()=>{})}}f(he,"exportPostgresToSqlite");function x(i,a,r,s,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${r}.${s} references ${a} ${String(u)}, which was not exported (`);return t}f(x,"mapId");async function ce(i,a){const r=[];for(const s of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${c(s)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${c(s)}`).get().n),m=u;s==="canvas_meta"&&(m=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),m!==t&&r.push(`${s}: Postgres ${m} rows, SQLite ${t}`)}if(r.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
|
|
2
2
|
${r.join(`
|
|
3
3
|
`)}`)}f(ce,"verify");export{he as exportPostgresToSqlite};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The person principal every install carried before it minted its own: read only by the one-shot that retires it and by the legacy SQLite/layout importers. Pure so CLI-reachable modules can import it. */
|
|
2
|
+
export declare const LEGACY_LOCAL_PERSON = "user:local";
|
|
3
|
+
/** The id of the local terminal grant an install carried before its grant became `terminal:<install id>`; nodes, tokens, crons and queued births may still reference it. */
|
|
4
|
+
export declare const LEGACY_LOCAL_TERMINAL_GRANT = "local:terminal";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const L="user:local",o="local:terminal";export{L as LEGACY_LOCAL_PERSON,o as LEGACY_LOCAL_TERMINAL_GRANT};
|