@north-light/cli 0.3.441 → 0.3.443

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.
@@ -27,6 +27,13 @@ export declare function syncDirectory(path: string): void;
27
27
  * tmux inbox watcher, or crtrd serving a ticket read — sees either the old
28
28
  * bytes or the new ones and never a truncated file mid-write. */
29
29
  export declare function atomicWriteText(path: string, content: string | Uint8Array, opts?: AtomicWriteOptions): void;
30
+ /** Publish `path` with `content` so it is never observable empty or partial, and
31
+ * never replaces an existing file. The bytes reach stable storage under a
32
+ * temporary name first; a hard link then makes them appear at `path` in one
33
+ * step and fails with EEXIST if another initializer won. The containing
34
+ * directory is fsynced so the new entry survives a volume handoff or power loss.
35
+ * Returns false when `path` already existed. */
36
+ export declare function publishFileExclusive(path: string, content: string, mode: number): boolean;
30
37
  /** Fsync every file and directory under `root` (and `root` itself), so a tree about to be renamed into place
31
38
  * reaches stable storage before the rename can. */
32
39
  export declare function syncTree(root: string): void;
@@ -1,3 +1,3 @@
1
- var E=Object.defineProperty;var e=(r,t)=>E(r,"name",{value:t,configurable:!0});import{existsSync as c,lstatSync as W,mkdirSync as b,readdirSync as f,readFileSync as l,rmSync as S,statSync as w,symlinkSync as g,writeFileSync as a,cpSync as T,readlinkSync as I,renameSync as $,chmodSync as L,realpathSync as j,closeSync as F,fsyncSync as J,openSync as O}from"node:fs";import{dirname as u,join as x,relative as C,sep as M}from"node:path";import{homedir as h,platform as k}from"node:os";function D(r){try{return j(r)}catch{return r}}e(D,"realpathOrSelf");function V(r,t=h()){return r==="~"?t:r.startsWith("~/")||k()==="win32"&&r.startsWith("~\\")?x(t,r.slice(2)):r}e(V,"expandTilde");function X(r,t=h()){return r===t?"~":r.startsWith(t+M)?"~"+r.slice(t.length):r}e(X,"tildify");function y(r){b(r,{recursive:!0})}e(y,"ensureDir");function Y(r,t){y(u(r)),a(r,JSON.stringify(t,null,2)+`
2
- `,"utf8")}e(Y,"writeJson");function Z(r,t,n){q(r,JSON.stringify(t,null,2)+`
3
- `,n)}e(Z,"atomicWriteJson");function N(r){const t=O(r,"r");try{J(t)}finally{F(t)}}e(N,"syncFile");function v(r){const t=O(r,"r");try{J(t)}finally{F(t)}}e(v,"syncDirectory");function q(r,t,n){n?.createParent!==!1&&y(u(r));const i=`${r}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,o=n?.mode??(c(r)?w(r).mode&511:384);try{a(i,t,{mode:o}),L(i,o),n?.durable===!0&&N(i),$(i,r),n?.durable===!0&&v(u(r))}finally{c(i)&&S(i,{force:!0})}}e(q,"atomicWriteText");function z(r){for(const t of f(r,{withFileTypes:!0})){const n=x(r,t.name);t.isDirectory()?z(n):t.isFile()&&N(n)}v(r)}e(z,"syncTree");function A(r){return JSON.parse(l(r,"utf8"))}e(A,"readJson");function _(r){try{return A(r)}catch(t){if(t.code==="ENOENT")return null;throw t}}e(_,"readJsonIfExists");function P(r){try{return JSON.parse(l(r,"utf8"))}catch{return null}}e(P,"readJsonOrNull");function rr(r){return c(r)?l(r,"utf8"):null}e(rr,"readTextIfExists");function tr(r){return l(r,"utf8")}e(tr,"readText");function er(r,t){y(u(r)),a(r,t,"utf8")}e(er,"writeText");function B(r){try{return w(r).isDirectory()}catch{return!1}}e(B,"isDir");function G(r){try{return W(r).isSymbolicLink()}catch{return!1}}e(G,"isSymlink");function nr(r){return c(r)}e(nr,"pathExists");function ir(r){return c(r)?f(r,{withFileTypes:!0}).filter(t=>t.isDirectory()||t.isSymbolicLink()).map(t=>t.name):[]}e(ir,"listDirs");function or(r){return c(r)?f(r):[]}e(or,"listEntries");function H(r){!c(r)&&!G(r)||S(r,{recursive:!0,force:!0})}e(H,"removePath");function cr(r,t,n={}){y(u(t)),H(t);const i=k()==="win32";if(!n.noSymlink&&!i){const o=C(D(u(t)),D(r));try{return g(o,t,B(r)?"dir":"file"),"symlink"}catch{return T(r,t,{recursive:!0}),"copy"}}return T(r,t,{recursive:!0}),"copy"}e(cr,"linkOrCopy");function ur(r){try{return I(r)}catch{return null}}e(ur,"readSymlinkTarget");function sr(r,t=()=>!0,n=()=>!1){const i=[];if(!c(r))return i;const o=[r];for(;o.length;){const m=o.pop();let p;try{p=f(m,{withFileTypes:!0})}catch{continue}for(const s of p){const d=x(m,s.name);s.isDirectory()?n(s.name)||o.push(d):s.isFile()&&t(s.name)&&i.push(d)}}return i}e(sr,"walkFiles");function fr(){return new Date().toISOString()}e(fr,"nowIso");export{Z as atomicWriteJson,q as atomicWriteText,y as ensureDir,V as expandTilde,B as isDir,G as isSymlink,cr as linkOrCopy,ir as listDirs,or as listEntries,fr as nowIso,nr as pathExists,A as readJson,_ as readJsonIfExists,P as readJsonOrNull,ur as readSymlinkTarget,tr as readText,rr as readTextIfExists,D as realpathOrSelf,H as removePath,v as syncDirectory,N as syncFile,z as syncTree,X as tildify,sr as walkFiles,Y as writeJson,er as writeText};
1
+ var D=Object.defineProperty;var e=(r,t)=>D(r,"name",{value:t,configurable:!0});import{existsSync as u,lstatSync as N,mkdirSync as $,readdirSync as y,readFileSync as a,rmSync as F,statSync as k,symlinkSync as b,writeFileSync as m,cpSync as E,readlinkSync as W,renameSync as I,chmodSync as L,realpathSync as M,closeSync as p,fsyncSync as S,openSync as d,linkSync as j,unlinkSync as B,writeSync as C}from"node:fs";import{dirname as f,join as w,relative as X,sep as q}from"node:path";import{homedir as J,platform as O}from"node:os";function g(r){try{return M(r)}catch{return r}}e(g,"realpathOrSelf");function Z(r,t=J()){return r==="~"?t:r.startsWith("~/")||O()==="win32"&&r.startsWith("~\\")?w(t,r.slice(2)):r}e(Z,"expandTilde");function _(r,t=J()){return r===t?"~":r.startsWith(t+q)?"~"+r.slice(t.length):r}e(_,"tildify");function x(r){$(r,{recursive:!0})}e(x,"ensureDir");function P(r,t){x(f(r)),m(r,JSON.stringify(t,null,2)+`
2
+ `,"utf8")}e(P,"writeJson");function rr(r,t,i){z(r,JSON.stringify(t,null,2)+`
3
+ `,i)}e(rr,"atomicWriteJson");function v(r){const t=d(r,"r");try{S(t)}finally{p(t)}}e(v,"syncFile");function h(r){const t=d(r,"r");try{S(t)}finally{p(t)}}e(h,"syncDirectory");function z(r,t,i){i?.createParent!==!1&&x(f(r));const n=`${r}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,o=i?.mode??(u(r)?k(r).mode&511:384);try{m(n,t,{mode:o}),L(n,o),i?.durable===!0&&v(n),I(n,r),i?.durable===!0&&h(f(r))}finally{u(n)&&F(n,{force:!0})}}e(z,"atomicWriteText");function tr(r,t,i){const n=`${r}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`,o=d(n,"wx",i);try{try{const c=Buffer.from(t,"utf8");for(let s=0;s<c.length;)s+=C(o,c,s,c.length-s);S(o)}finally{p(o)}try{j(n,r)}catch(c){if(c.code==="EEXIST")return!1;throw c}return h(f(r)),!0}finally{try{B(n)}catch{}}}e(tr,"publishFileExclusive");function A(r){for(const t of y(r,{withFileTypes:!0})){const i=w(r,t.name);t.isDirectory()?A(i):t.isFile()&&v(i)}h(r)}e(A,"syncTree");function G(r){return JSON.parse(a(r,"utf8"))}e(G,"readJson");function er(r){try{return G(r)}catch(t){if(t.code==="ENOENT")return null;throw t}}e(er,"readJsonIfExists");function nr(r){try{return JSON.parse(a(r,"utf8"))}catch{return null}}e(nr,"readJsonOrNull");function ir(r){return u(r)?a(r,"utf8"):null}e(ir,"readTextIfExists");function or(r){return a(r,"utf8")}e(or,"readText");function cr(r,t){x(f(r)),m(r,t,"utf8")}e(cr,"writeText");function H(r){try{return k(r).isDirectory()}catch{return!1}}e(H,"isDir");function K(r){try{return N(r).isSymbolicLink()}catch{return!1}}e(K,"isSymlink");function ur(r){return u(r)}e(ur,"pathExists");function sr(r){return u(r)?y(r,{withFileTypes:!0}).filter(t=>t.isDirectory()||t.isSymbolicLink()).map(t=>t.name):[]}e(sr,"listDirs");function fr(r){return u(r)?y(r):[]}e(fr,"listEntries");function Q(r){!u(r)&&!K(r)||F(r,{recursive:!0,force:!0})}e(Q,"removePath");function lr(r,t,i={}){x(f(t)),Q(t);const n=O()==="win32";if(!i.noSymlink&&!n){const o=X(g(f(t)),g(r));try{return b(o,t,H(r)?"dir":"file"),"symlink"}catch{return E(r,t,{recursive:!0}),"copy"}}return E(r,t,{recursive:!0}),"copy"}e(lr,"linkOrCopy");function yr(r){try{return W(r)}catch{return null}}e(yr,"readSymlinkTarget");function ar(r,t=()=>!0,i=()=>!1){const n=[];if(!u(r))return n;const o=[r];for(;o.length;){const c=o.pop();let s;try{s=y(c,{withFileTypes:!0})}catch{continue}for(const l of s){const T=w(c,l.name);l.isDirectory()?i(l.name)||o.push(T):l.isFile()&&t(l.name)&&n.push(T)}}return n}e(ar,"walkFiles");function xr(){return new Date().toISOString()}e(xr,"nowIso");export{rr as atomicWriteJson,z as atomicWriteText,x as ensureDir,Z as expandTilde,H as isDir,K as isSymlink,lr as linkOrCopy,sr as listDirs,fr as listEntries,xr as nowIso,ur as pathExists,tr as publishFileExclusive,G as readJson,er as readJsonIfExists,nr as readJsonOrNull,yr as readSymlinkTarget,or as readText,ir as readTextIfExists,g as realpathOrSelf,Q as removePath,h as syncDirectory,v as syncFile,A as syncTree,_ as tildify,ar as walkFiles,P as writeJson,cr as writeText};
@@ -34,6 +34,14 @@ export interface ActiveGrant extends GrantRecord {
34
34
  * authorization): absent or non-object reads as `{}`, and a malformed entry
35
35
  * (see `dependencyOf`) is dropped. A directory older than the Context stage sends none. */
36
36
  export declare function dependenciesOf(value: unknown): GrantDependencies;
37
+ /** A grant this machine only mirrors. An unsandboxed machine runs only
38
+ * `app:terminal`; the directory also fans the account's hosted-runtime app
39
+ * grants out to it, read-only, so a forwarded app caller can be authorized
40
+ * (`grantFor`, `activeGrant`). They drive nothing here: no sandbox stop, removal
41
+ * cascade, provider install, or run notice. */
42
+ export declare function mirrorOnly(grant: Pick<GrantRecord, 'grantee'>): boolean;
43
+ /** Every mirrored grant this machine acts on: `allGrants()` without the mirror-only ones. */
44
+ export declare function localGrants(): GrantRecord[];
37
45
  /** Every mirrored grant, whatever its status. */
38
46
  export declare function allGrants(): GrantRecord[];
39
47
  /** The active form of a grant record read earlier, or null when it is not active or unparsable. */
@@ -58,6 +66,9 @@ export declare function grantSyncStateFor(grantee: string, sub: string): GrantSy
58
66
  export declare function grantRecord(grantId: string): GrantRecord | null;
59
67
  export declare function activeGrant(grantId: string): ActiveGrant | null;
60
68
  export declare function grantFor(grantee: string, sub: string): ActiveGrant | null;
69
+ /** The app's own active grant on this machine. Null for a mirror-only grant: an
70
+ * unsandboxed machine runs no app, so a fanned-out hosted grant is never "the
71
+ * app's grant" for a local app caller, sandbox or install. */
61
72
  export declare function activeGrantForApp(grantee: string): ActiveGrant | null;
62
73
  /** Where a run's notice goes: its grant's registered `run_notice_url`, with the grant's `sub` and `grantee`. Null when the run has no active grant or the grantee registered no URL. */
63
74
  export declare function runNoticeTarget(grantId: string): {
@@ -1,4 +1,4 @@
1
- var g=Object.defineProperty;var r=(e,t)=>g(e,"name",{value:t,configurable:!0});import{createHash as _}from"node:crypto";import{prepared as n}from"../sqlite-statements.js";import{parseScope as y}from"@north-light/identity";import{openDb as i,withCanvasWrite as S}from"../canvas/db.js";const v=["not_started","pending","ready","failed"];function c(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(t=>{if(!t||typeof t!="object"||typeof t.scope!="string"||!["always","ask"].includes(t.mode))throw new TypeError("invalid grant scope entry");return{scope:t.scope,mode:t.mode}})}r(c,"entries");function T(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:t,message:a,next:u}=e;return typeof t!="string"||!v.includes(t)||a!==void 0&&typeof a!="string"||typeof u!="string"||!u?null:{state:t,...a===void 0?{}:{message:a},next:u}}r(T,"dependencyOf");function l(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const t={};for(const[a,u]of Object.entries(e)){const s=T(u);s&&(t[a]=s)}return t}r(l,"dependenciesOf");function E(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:c(JSON.parse(e.scopes_json)),resolved_scopes:c(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:l(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,synced_at:e.synced_at}}r(E,"record");function p(e){if(!e||e.status!=="active")return null;try{return m(E(e))}catch(t){if(t instanceof TypeError||t instanceof SyntaxError)return null;throw t}}r(p,"active");function h(){return n(i(),"SELECT * FROM grants").all().flatMap(e=>{try{return[E(e)]}catch(t){if(t instanceof TypeError||t instanceof SyntaxError)return[];throw t}})}r(h,"allGrants");function m(e){if(e.status!=="active")return null;try{const t=e.resolved_scopes.map(({scope:a})=>y(a));return{...e,resolved:e.resolved_scopes.flatMap(({mode:a},u)=>a==="always"?[t[u]]:[])}}catch(t){if(t instanceof TypeError||t instanceof SyntaxError)return null;throw t}}r(m,"activeRecord");function d(e){return Array.isArray(e)?`[${e.map(d).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(t=>e[t]!==void 0).map(t=>`${JSON.stringify(t)}:${d(e[t])}`).join(",")}}`:JSON.stringify(e)??"null"}r(d,"canonicalJson");function L(e){const t={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{}};return _("sha256").update(d(t)).digest("base64url").slice(0,22)}r(L,"grantVersion");function F(e){return!!n(i(),"SELECT 1 FROM grants WHERE id = ?").get(e)}r(F,"hasGrant");function C(e){return n(i(),"SELECT rev, status FROM grants WHERE id = ?").get(e)??null}r(C,"grantSyncState");function M(e,t){return n(i(),"SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,t)??null}r(M,"grantSyncStateFor");function b(e){const t=n(i(),"SELECT * FROM grants WHERE id = ?").get(e);if(!t)return null;try{return E(t)}catch(a){if(a instanceof TypeError||a instanceof SyntaxError)return null;throw a}}r(b,"grantRecord");function k(e){return p(n(i(),"SELECT * FROM grants WHERE id = ?").get(e))}r(k,"activeGrant");function H(e,t){return p(n(i(),"SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,t))}r(H,"grantFor");function W(e){const t=n(i(),"SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(t.length>1)throw new Error(`multiple active grants for ${e}`);return p(t[0])}r(W,"activeGrantForApp");function j(e){const t=n(i(),"SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return t&&t.url?{url:t.url,sub:t.sub,grantee:t.grantee}:null}r(j,"runNoticeTarget");function D(){const e=n(i(),"SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return p(e[0])}r(D,"terminalGrant");function G(){return n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value??null}r(G,"grantsSource");function I(e){return n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value===e}r(I,"hasSyncedGrants");function J(e){return n(e,"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value!=="local"?0:(n(e,"DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number(n(e,"DELETE FROM grants").run().changes))}r(J,"dropLocalGrantMirror");function U(e,t,a){const u=new Set;for(const s of e){if(u.has(s.id)||!s.id||!s.runtime||!s.sub||!s.grantee||!Number.isInteger(s.rev)||!["active","revoked","removed"].includes(s.status))throw new TypeError("invalid grant sync response");u.add(s.id),c(s.scopes),c(s.resolved_scopes)}S(s=>{s.exec("DELETE FROM grants");const f=n(s,`INSERT INTO grants
1
+ var y=Object.defineProperty;var t=(e,r)=>y(e,"name",{value:r,configurable:!0});import{createHash as _}from"node:crypto";import{prepared as n}from"../sqlite-statements.js";import{parseScope as S}from"@north-light/identity";import{openDb as i,withCanvasWrite as v}from"../canvas/db.js";import{isUnsandboxedMachine as T}from"../runtime/unsandboxed.js";const m=["not_started","pending","ready","failed"];function c(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(c,"entries");function O(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:a,next:u}=e;return typeof r!="string"||!m.includes(r)||a!==void 0&&typeof a!="string"||typeof u!="string"||!u?null:{state:r,...a===void 0?{}:{message:a},next:u}}t(O,"dependencyOf");function f(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[a,u]of Object.entries(e)){const s=O(u);s&&(r[a]=s)}return r}t(f,"dependenciesOf");function l(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:c(JSON.parse(e.scopes_json)),resolved_scopes:c(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:f(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,synced_at:e.synced_at}}t(l,"record");function p(e){if(!e||e.status!=="active")return null;try{return x(l(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(p,"active");function d(e){return e.grantee!=="app:terminal"&&T()}t(d,"mirrorOnly");function M(){return R().filter(e=>!d(e))}t(M,"localGrants");function R(){return n(i(),"SELECT * FROM grants").all().flatMap(e=>{try{return[l(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(R,"allGrants");function x(e){if(e.status!=="active")return null;try{const r=e.resolved_scopes.map(({scope:a})=>S(a));return{...e,resolved:e.resolved_scopes.flatMap(({mode:a},u)=>a==="always"?[r[u]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(x,"activeRecord");function E(e){return Array.isArray(e)?`[${e.map(E).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${E(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(E,"canonicalJson");function b(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{}};return _("sha256").update(E(r)).digest("base64url").slice(0,22)}t(b,"grantVersion");function k(e){return!!n(i(),"SELECT 1 FROM grants WHERE id = ?").get(e)}t(k,"hasGrant");function H(e){return n(i(),"SELECT rev, status FROM grants WHERE id = ?").get(e)??null}t(H,"grantSyncState");function W(e,r){return n(i(),"SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,r)??null}t(W,"grantSyncStateFor");function j(e){const r=n(i(),"SELECT * FROM grants WHERE id = ?").get(e);if(!r)return null;try{return l(r)}catch(a){if(a instanceof TypeError||a instanceof SyntaxError)return null;throw a}}t(j,"grantRecord");function D(e){return p(n(i(),"SELECT * FROM grants WHERE id = ?").get(e))}t(D,"activeGrant");function G(e,r){return p(n(i(),"SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,r))}t(G,"grantFor");function I(e){if(d({grantee:e}))return null;const r=n(i(),"SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return p(r[0])}t(I,"activeGrantForApp");function J(e){const r=n(i(),"SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return r&&r.url&&!d(r)?{url:r.url,sub:r.sub,grantee:r.grantee}:null}t(J,"runNoticeTarget");function U(){const e=n(i(),"SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return p(e[0])}t(U,"terminalGrant");function w(){return n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value??null}t(w,"grantsSource");function $(e){return n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value===e}t($,"hasSyncedGrants");function V(e){return n(e,"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value!=="local"?0:(n(e,"DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number(n(e,"DELETE FROM grants").run().changes))}t(V,"dropLocalGrantMirror");function P(e,r,a){const u=new Set;for(const s of e){if(u.has(s.id)||!s.id||!s.runtime||!s.sub||!s.grantee||!Number.isInteger(s.rev)||!["active","revoked","removed"].includes(s.status))throw new TypeError("invalid grant sync response");u.add(s.id),c(s.scopes),c(s.resolved_scopes)}v(s=>{s.exec("DELETE FROM grants");const g=n(s,`INSERT INTO grants
2
2
  (id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
3
3
  status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, synced_at)
4
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const o of e)f.run(o.id,o.runtime,o.sub,o.grantee,o.rev,JSON.stringify(o.scopes),JSON.stringify(o.resolved_scopes),o.spend_cap,o.expires_at,o.status,o.revoked_at,o.approved_at,o.payer,JSON.stringify(l(o.dependencies)),o.run_notice_url??null,o.synced_at);n(s,"INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(a),n(s,"INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT)").run()})}r(U,"replaceGrants");function w(){const e=n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}r(w,"grantsGeneration");export{k as activeGrant,W as activeGrantForApp,m as activeRecord,h as allGrants,l as dependenciesOf,J as dropLocalGrantMirror,H as grantFor,b as grantRecord,C as grantSyncState,M as grantSyncStateFor,L as grantVersion,w as grantsGeneration,G as grantsSource,F as hasGrant,I as hasSyncedGrants,U as replaceGrants,j as runNoticeTarget,D as terminalGrant};
4
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const o of e)g.run(o.id,o.runtime,o.sub,o.grantee,o.rev,JSON.stringify(o.scopes),JSON.stringify(o.resolved_scopes),o.spend_cap,o.expires_at,o.status,o.revoked_at,o.approved_at,o.payer,JSON.stringify(f(o.dependencies)),o.run_notice_url??null,o.synced_at);n(s,"INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(a),n(s,"INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT)").run()})}t(P,"replaceGrants");function X(){const e=n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(X,"grantsGeneration");export{D as activeGrant,I as activeGrantForApp,x as activeRecord,R as allGrants,f as dependenciesOf,V as dropLocalGrantMirror,G as grantFor,j as grantRecord,H as grantSyncState,W as grantSyncStateFor,b as grantVersion,X as grantsGeneration,w as grantsSource,k as hasGrant,$ as hasSyncedGrants,M as localGrants,d as mirrorOnly,P as replaceGrants,J as runNoticeTarget,U as terminalGrant};
@@ -1 +1 @@
1
- var w=Object.defineProperty;var c=(n,r)=>w(n,"name",{value:r,configurable:!0});import{prepared as v}from"../sqlite-statements.js";import{relative as b,sep as g}from"node:path";import{activeBackgroundBashJobs as x,readJobPgid as y}from"../bash-jobs.js";import{appSpace as S}from"../layout.js";import{getNode as l,recordSettledTurn as k}from"../canvas/index.js";import{transition as E}from"../runtime/lifecycle.js";import{openDb as _}from"../canvas/db.js";import{contextDir as R}from"../canvas/node-agent-paths.js";import{emitEvent as p}from"../events/emit.js";import{closeGrantStreams as C}from"../runs/events.js";import{grantedMounts as G,isGrantedSource as I,mountAllowed as P}from"../spaces/mounts.js";import{permissionsForRun as m}from"../spaces/permissions.js";import{stopNodeSandboxes as T}from"../spaces/stop.js";import{discardChangedGrantTokens as F}from"../providers/token-cache.js";import{activeGrantForApp as M,allGrants as O}from"./mirror.js";import{runRemovalCascades as A}from"./remove.js";import{notifyGrantChanges as D}from"./watch.js";import{launcherPresent as N,listSandboxes as W,stopSandboxPid as j}from"./sandboxes.js";import{checkCronFire as B}from"../../daemon/cron/authority.js";import{cronExecutionFor as J}from"../../daemon/cron/live-runs.js";import{cronExecutionsReady as L}from"../../daemon/cron/run-socket.js";function h(){return new Map(v(_(),"SELECT uid, app, status FROM app_users").all().map(n=>[n.uid,{app:n.app,status:n.status}]))}c(h,"appUserOwners");function U(n,r=h()){const o=r.get(n.uid),i=n.nodeId.startsWith("cron-"),e=i?J(n.nodeId.slice(5)):void 0;if(i&&(e===void 0||!o))return"permission_revoked";if(e!==void 0&&o.status==="active"){const s=B({grantee:e.grantee,sub:e.sub,grant_id:e.grantId,scopes:e.scopes});if(!s.allowed)return e.refusal??={reason:s.reason,message:s.message},"permission_revoked"}if(!o||o.app==="app:terminal")return null;if(o.status!=="active")return"app_removed";const t=l(n.nodeId);try{let s;if(e!==void 0)s=m(e.grantee,e.sub,e.grantId,e.scopes);else if(t&&t.grantee===o.app)s=m(o.app,t.sub??"user:local",t.grant_id??null,t.scopes??null);else{const d=M(o.app);if(!d)return"permission_revoked";s=m(o.app,d.sub,d.id,null)}const u=G(s),f=S(o.app).root;return n.mounts.every(d=>{const a=b(f,d.source);return a===""||a!==".."&&!a.startsWith(`..${g}`)&&!a.startsWith(g)||!I(o.app,d.source)||P(d,u)})?null:"permission_revoked"}catch{return"permission_revoked"}}c(U,"exceeds");async function $(n){const r=l(n);if(!r)return;(r.status==="active"||r.status==="idle")&&E(n,"release");const o=new Date().toISOString();k(n,o),r.run_id&&r.run_id!==n&&v(_(),"UPDATE nodes SET last_settled_at = COALESCE(last_settled_at, ?) WHERE node_id = ?").run(o,r.run_id)}c($,"afterRevocationStop");function H(n){const r=new Set,o=l(n);o?.pi_pid!=null&&r.add(o.pi_pid);try{const i=R(n,o?.grantee);for(const e of x(i,!0)){const t=y(i,e.jobId);t!==void 0&&r.add(t)}}catch{}return r}c(H,"nodePids");async function q(){if(!N())return{completion:Promise.resolve()};await L();const n=await W(),r=h(),o=n.flatMap(t=>{const s=U(t,r);return s?[{sandbox:t,reason:s}]:[]}),i=[],e=new Map;for(const t of o){p({level:"info",event:"daemon.grants.sandbox_stop",fields:{sandbox_node:t.sandbox.nodeId,pid:t.sandbox.pid,grantee:r.get(t.sandbox.uid).app,reason:t.reason}});const s=e.get(t.sandbox.nodeId)??[];s.push(t),e.set(t.sandbox.nodeId,s)}for(const[t,s]of e){const u=l(t),f=u?H(t):new Set,d=u!==null&&s.some(({sandbox:a})=>f.has(a.pid));i.push((async()=>{if(d){const a=await T(t,"permission_revoked",{holdRecovery:!0,after:c(()=>$(t),"after")});if(!a.confirmed)throw a.error??new Error(`stop of ${t} was not confirmed`)}for(const{sandbox:a}of s)(!d||!f.has(a.pid))&&await j(a.pid)})().catch(a=>{p({level:"error",event:"daemon.grants.sandbox_stop_failed",...u?{node_id:t}:{fields:{sandbox_node:t}},error:a})}))}return{completion:Promise.all(i).then(()=>{})}}c(q,"sweepSandboxes");function z(n,r){try{const{pairs:o,tokens:i}=F(n,r);o&&p({level:"info",event:"daemon.grants.outbound_tokens_discarded",fields:{grants:o,tokens:i}})}catch(o){p({level:"error",event:"daemon.grants.outbound_tokens_discard_failed",error:o})}}c(z,"discardOutboundTokens");async function _e(n,r=o=>{}){const o=O();try{D(n,o)}catch(e){p({level:"error",event:"daemon.grants.notify_failed",error:e})}z(n,o);for(const e of n){if(e.status!=="active"||o.find(u=>u.id===e.id)?.status==="active")continue;const s=C(e.id);s&&p({level:"info",event:"daemon.grants.streams_closed",fields:{grant:e.id,grantee:e.grantee,streams:s}})}try{const{completion:e}=await q();r(e)}catch(e){p({level:"error",event:"daemon.grants.sweep_failed",error:e})}const i=new Set(o.filter(e=>e.status==="removed"&&n.find(t=>t.id===e.id)?.status!=="removed").map(e=>e.grantee));r(A(i).catch(()=>{})),r(import("../providers/bundles.js").then(({syncProviderBundles:e})=>e()).catch(e=>{p({level:"error",event:"daemon.providers.bundle_sync_failed",error:e})}))}c(_e,"onGrantSync");export{U as exceeds,_e as onGrantSync,q as sweepSandboxes};
1
+ var w=Object.defineProperty;var c=(n,r)=>w(n,"name",{value:r,configurable:!0});import{prepared as v}from"../sqlite-statements.js";import{relative as b,sep as g}from"node:path";import{activeBackgroundBashJobs as x,readJobPgid as y}from"../bash-jobs.js";import{appSpace as S}from"../layout.js";import{getNode as l,recordSettledTurn as k}from"../canvas/index.js";import{transition as E}from"../runtime/lifecycle.js";import{openDb as _}from"../canvas/db.js";import{contextDir as R}from"../canvas/node-agent-paths.js";import{emitEvent as p}from"../events/emit.js";import{closeGrantStreams as C}from"../runs/events.js";import{grantedMounts as G,isGrantedSource as I,mountAllowed as P}from"../spaces/mounts.js";import{permissionsForRun as m}from"../spaces/permissions.js";import{stopNodeSandboxes as O}from"../spaces/stop.js";import{discardChangedGrantTokens as T}from"../providers/token-cache.js";import{activeGrantForApp as F,allGrants as M,mirrorOnly as A}from"./mirror.js";import{runRemovalCascades as D}from"./remove.js";import{notifyGrantChanges as N}from"./watch.js";import{launcherPresent as W,listSandboxes as j,stopSandboxPid as B}from"./sandboxes.js";import{checkCronFire as J}from"../../daemon/cron/authority.js";import{cronExecutionFor as L}from"../../daemon/cron/live-runs.js";import{cronExecutionsReady as U}from"../../daemon/cron/run-socket.js";function h(){return new Map(v(_(),"SELECT uid, app, status FROM app_users").all().map(n=>[n.uid,{app:n.app,status:n.status}]))}c(h,"appUserOwners");function $(n,r=h()){const o=r.get(n.uid),i=n.nodeId.startsWith("cron-"),e=i?L(n.nodeId.slice(5)):void 0;if(i&&(e===void 0||!o))return"permission_revoked";if(e!==void 0&&o.status==="active"){const s=J({grantee:e.grantee,sub:e.sub,grant_id:e.grantId,scopes:e.scopes});if(!s.allowed)return e.refusal??={reason:s.reason,message:s.message},"permission_revoked"}if(!o||o.app==="app:terminal")return null;if(o.status!=="active")return"app_removed";const t=l(n.nodeId);try{let s;if(e!==void 0)s=m(e.grantee,e.sub,e.grantId,e.scopes);else if(t&&t.grantee===o.app)s=m(o.app,t.sub??"user:local",t.grant_id??null,t.scopes??null);else{const d=F(o.app);if(!d)return"permission_revoked";s=m(o.app,d.sub,d.id,null)}const u=G(s),f=S(o.app).root;return n.mounts.every(d=>{const a=b(f,d.source);return a===""||a!==".."&&!a.startsWith(`..${g}`)&&!a.startsWith(g)||!I(o.app,d.source)||P(d,u)})?null:"permission_revoked"}catch{return"permission_revoked"}}c($,"exceeds");async function H(n){const r=l(n);if(!r)return;(r.status==="active"||r.status==="idle")&&E(n,"release");const o=new Date().toISOString();k(n,o),r.run_id&&r.run_id!==n&&v(_(),"UPDATE nodes SET last_settled_at = COALESCE(last_settled_at, ?) WHERE node_id = ?").run(o,r.run_id)}c(H,"afterRevocationStop");function q(n){const r=new Set,o=l(n);o?.pi_pid!=null&&r.add(o.pi_pid);try{const i=R(n,o?.grantee);for(const e of x(i,!0)){const t=y(i,e.jobId);t!==void 0&&r.add(t)}}catch{}return r}c(q,"nodePids");async function z(){if(!W())return{completion:Promise.resolve()};await U();const n=await j(),r=h(),o=n.flatMap(t=>{const s=$(t,r);return s?[{sandbox:t,reason:s}]:[]}),i=[],e=new Map;for(const t of o){p({level:"info",event:"daemon.grants.sandbox_stop",fields:{sandbox_node:t.sandbox.nodeId,pid:t.sandbox.pid,grantee:r.get(t.sandbox.uid).app,reason:t.reason}});const s=e.get(t.sandbox.nodeId)??[];s.push(t),e.set(t.sandbox.nodeId,s)}for(const[t,s]of e){const u=l(t),f=u?q(t):new Set,d=u!==null&&s.some(({sandbox:a})=>f.has(a.pid));i.push((async()=>{if(d){const a=await O(t,"permission_revoked",{holdRecovery:!0,after:c(()=>H(t),"after")});if(!a.confirmed)throw a.error??new Error(`stop of ${t} was not confirmed`)}for(const{sandbox:a}of s)(!d||!f.has(a.pid))&&await B(a.pid)})().catch(a=>{p({level:"error",event:"daemon.grants.sandbox_stop_failed",...u?{node_id:t}:{fields:{sandbox_node:t}},error:a})}))}return{completion:Promise.all(i).then(()=>{})}}c(z,"sweepSandboxes");function K(n,r){try{const{pairs:o,tokens:i}=T(n,r);o&&p({level:"info",event:"daemon.grants.outbound_tokens_discarded",fields:{grants:o,tokens:i}})}catch(o){p({level:"error",event:"daemon.grants.outbound_tokens_discard_failed",error:o})}}c(K,"discardOutboundTokens");async function he(n,r=o=>{}){const o=M();try{N(n,o)}catch(e){p({level:"error",event:"daemon.grants.notify_failed",error:e})}K(n,o);for(const e of n){if(e.status!=="active"||o.find(u=>u.id===e.id)?.status==="active")continue;const s=C(e.id);s&&p({level:"info",event:"daemon.grants.streams_closed",fields:{grant:e.id,grantee:e.grantee,streams:s}})}try{const{completion:e}=await z();r(e)}catch(e){p({level:"error",event:"daemon.grants.sweep_failed",error:e})}const i=new Set(o.filter(e=>!A(e)&&e.status==="removed"&&n.find(t=>t.id===e.id)?.status!=="removed").map(e=>e.grantee));r(D(i).catch(()=>{})),r(import("../providers/bundles.js").then(({syncProviderBundles:e})=>e()).catch(e=>{p({level:"error",event:"daemon.providers.bundle_sync_failed",error:e})}))}c(he,"onGrantSync");export{$ as exceeds,he as onGrantSync,z as sweepSandboxes};
@@ -1 +1 @@
1
- var S=Object.defineProperty;var m=(r,i)=>S(r,"name",{value:i,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{rmSync as h}from"node:fs";import{join as w}from"node:path";import{deleteNodes as O}from"../canvas/canvas.js";import{openDb as c,withCanvasWrite as E}from"../canvas/db.js";import{cancelCron as T}from"../canvas/crons.js";import{emitEvent as l}from"../events/emit.js";import{appSpace as C,appsRoot as u,secretsDir as D}from"../layout.js";import{notifyRunDeleted as L}from"../runs/events.js";import{cancelOpenQuestions as W}from"../runs/questions.js";import{removeTreeBeneath as F}from"../spaces/open-beneath.js";import{markGranteeUploadsDeletePending as H,releaseRunUploads as N,turnSharesOffFor as M}from"../storage/tables.js";import{stopNodeSandboxes as b}from"../spaces/stop.js";import{activeAppUser as p,markAppUserRemoved as I}from"../spaces/users.js";import{launcherRequest as y}from"../../launcher/client.js";import{LaunchRefused as A}from"../../launcher/spec.js";import{allGrants as P}from"./mirror.js";import{launcherPresent as g,listSandboxes as U,stopSandboxPid as $}from"./sandboxes.js";let v=Promise.resolve();function ae(r=new Set){const i=v.then(async()=>{const t=P(),o=new Set(t.filter(n=>n.status==="removed").map(n=>n.grantee));for(const n of o)if(!t.some(a=>a.grantee===n&&a.status==="active")&&!(p(n)===null&&!r.has(n)))try{await x(n)}catch(a){l({level:"error",event:"daemon.grants.removal_failed",error:a,fields:{grantee:n}})}});return v=i.catch(()=>{}),i}m(ae,"runRemovalCascades");async function x(r){if(r==="app:terminal")throw new Error("app:terminal cannot be removed");const i=C(r),t=p(r);l({level:"info",event:"daemon.grants.removal_started",fields:{grantee:r,uid:t?.uid??null}}),await W({grantee:r},"the app was removed");const o=E(e=>(d(e,"UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id IN (SELECT node_id FROM node_rows WHERE grantee = ?)").run(new Date().toISOString(),r),d(e,"SELECT node_id FROM node_rows WHERE grantee = ? ORDER BY created DESC").all(r).map(s=>s.node_id)));for(const{cron_id:e}of d(c(),"SELECT cron_id FROM crons WHERE grantee = ?").all(r))T(e);const a=(await Promise.all(o.map(e=>b(e,"app_removed",{deleting:!0})))).filter(e=>!e.confirmed);if(a.length)throw new Error(`sandbox teardown could not be confirmed for ${a.length} node(s) of ${r}`);if(t&&g())for(const e of await U())e.uid===t.uid&&await $(e.pid);const R=o.filter(e=>d(c(),"SELECT run_id FROM nodes WHERE node_id = ?").get(e)?.run_id===e);E(e=>{if(M(r,e),N(o,e),H(r,e),!o.length)return;const s=o.map(()=>"?").join(","),_=e.prepare(`SELECT message_id FROM messages WHERE sender_node_id IN (${s}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${s})`).all(...o,...o);e.prepare(`DELETE FROM deliveries WHERE destination_id IN (${s})`).run(...o);for(const{message_id:f}of _)d(e,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(f,f)}),O(o);for(const e of R)L(e);if(h(w(D(),"env",i.root.slice(u().length+1)),{recursive:!0,force:!0}),F(u(),i.root),t&&g())try{await y({operation:"user.delete",uid:t.uid})}catch(e){if(!(e instanceof A&&e.refusal==="invalid_uid"))throw e}t&&I(t.uid),l({level:"info",event:"daemon.grants.removal_completed",fields:{grantee:r,uid:t?.uid??null,nodes:o.length}})}m(x,"removeApp");export{x as removeApp,ae as runRemovalCascades};
1
+ var S=Object.defineProperty;var m=(r,i)=>S(r,"name",{value:i,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{rmSync as h}from"node:fs";import{join as w}from"node:path";import{deleteNodes as O}from"../canvas/canvas.js";import{openDb as c,withCanvasWrite as E}from"../canvas/db.js";import{cancelCron as T}from"../canvas/crons.js";import{emitEvent as l}from"../events/emit.js";import{appSpace as C,appsRoot as u,secretsDir as D}from"../layout.js";import{notifyRunDeleted as L}from"../runs/events.js";import{cancelOpenQuestions as W}from"../runs/questions.js";import{removeTreeBeneath as F}from"../spaces/open-beneath.js";import{markGranteeUploadsDeletePending as H,releaseRunUploads as N,turnSharesOffFor as M}from"../storage/tables.js";import{stopNodeSandboxes as b}from"../spaces/stop.js";import{activeAppUser as p,markAppUserRemoved as I}from"../spaces/users.js";import{launcherRequest as y}from"../../launcher/client.js";import{LaunchRefused as A}from"../../launcher/spec.js";import{localGrants as P}from"./mirror.js";import{launcherPresent as g,listSandboxes as U,stopSandboxPid as $}from"./sandboxes.js";let v=Promise.resolve();function ae(r=new Set){const i=v.then(async()=>{const t=P(),o=new Set(t.filter(n=>n.status==="removed").map(n=>n.grantee));for(const n of o)if(!t.some(a=>a.grantee===n&&a.status==="active")&&!(p(n)===null&&!r.has(n)))try{await x(n)}catch(a){l({level:"error",event:"daemon.grants.removal_failed",error:a,fields:{grantee:n}})}});return v=i.catch(()=>{}),i}m(ae,"runRemovalCascades");async function x(r){if(r==="app:terminal")throw new Error("app:terminal cannot be removed");const i=C(r),t=p(r);l({level:"info",event:"daemon.grants.removal_started",fields:{grantee:r,uid:t?.uid??null}}),await W({grantee:r},"the app was removed");const o=E(e=>(d(e,"UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id IN (SELECT node_id FROM node_rows WHERE grantee = ?)").run(new Date().toISOString(),r),d(e,"SELECT node_id FROM node_rows WHERE grantee = ? ORDER BY created DESC").all(r).map(s=>s.node_id)));for(const{cron_id:e}of d(c(),"SELECT cron_id FROM crons WHERE grantee = ?").all(r))T(e);const a=(await Promise.all(o.map(e=>b(e,"app_removed",{deleting:!0})))).filter(e=>!e.confirmed);if(a.length)throw new Error(`sandbox teardown could not be confirmed for ${a.length} node(s) of ${r}`);if(t&&g())for(const e of await U())e.uid===t.uid&&await $(e.pid);const R=o.filter(e=>d(c(),"SELECT run_id FROM nodes WHERE node_id = ?").get(e)?.run_id===e);E(e=>{if(M(r,e),N(o,e),H(r,e),!o.length)return;const s=o.map(()=>"?").join(","),_=e.prepare(`SELECT message_id FROM messages WHERE sender_node_id IN (${s}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${s})`).all(...o,...o);e.prepare(`DELETE FROM deliveries WHERE destination_id IN (${s})`).run(...o);for(const{message_id:f}of _)d(e,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(f,f)}),O(o);for(const e of R)L(e);if(h(w(D(),"env",i.root.slice(u().length+1)),{recursive:!0,force:!0}),F(u(),i.root),t&&g())try{await y({operation:"user.delete",uid:t.uid})}catch(e){if(!(e instanceof A&&e.refusal==="invalid_uid"))throw e}t&&I(t.uid),l({level:"info",event:"daemon.grants.removal_completed",fields:{grantee:r,uid:t?.uid??null,nodes:o.length}})}m(x,"removeApp");export{x as removeApp,ae as runRemovalCascades};
@@ -1 +1 @@
1
- var k=Object.defineProperty;var n=(e,r)=>k(e,"name",{value:r,configurable:!0});import{createPrivateKey as w,randomUUID as v,sign as S}from"node:crypto";import{readFileSync as b}from"node:fs";import{join as E}from"node:path";import{crtrHome as j}from"../canvas/paths.js";import{isUnsandboxedMachine as x}from"../runtime/unsandboxed.js";import{emitEvent as p}from"../events/emit.js";import{allGrants as $,dependenciesOf as T,replaceGrants as A}from"./mirror.js";import{onGrantSync as N}from"./on-sync.js";const z=300;function G(e,r){const t=Math.floor(Date.now()/1e3),i=Buffer.from(JSON.stringify({alg:"ES256",typ:"JWT",kid:e.kid})).toString("base64url"),a=Buffer.from(JSON.stringify({iss:e.client_id,sub:e.client_id,aud:r,iat:t,exp:t+300,jti:v()})).toString("base64url"),o=`${i}.${a}`,s=S("sha256",Buffer.from(o),{key:w({key:e.jwk,format:"jwk"}),dsaEncoding:"ieee-p1363"});return`${o}.${s.toString("base64url")}`}n(G,"assertion");function y(){const e=JSON.parse(b(E(j(),"secrets","runtime-client.json"),"utf8"));if(!e.jwk||e.alg!=="ES256"||!e.kid||!e.client_id||!e.runtime||e.jwk.kid!==e.kid||e.jwk.kty!=="EC"||e.jwk.crv!=="P-256"||!e.jwk.d)throw new TypeError("invalid runtime client key");return e}n(y,"runtimeClient");function H(e=y()){return e.runtime.startsWith("runtime:")?e.runtime.slice(8):e.runtime}n(H,"runtimeId");function R(e){return`This machine was removed from your Northlight account. To use it again, create a join token at ${e.replace(/\/$/,"")}/account/machines, and run \`crtr sys join\` from a plain terminal.`}n(R,"machineRevokedMessage");class g extends Error{static{n(this,"MachineRevokedError")}constructor(r){super(R(r)),this.name="MachineRevokedError"}}async function O(e,r){const t=y(),i=`${e.replace(/\/$/,"")}/token`,a=new URLSearchParams({grant_type:"client_credentials",client_id:t.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:G(t,i),...r===void 0?{}:{audience:r}}),o=await fetch(i,{method:"POST",body:a,signal:AbortSignal.timeout(1e4)});if(!o.ok)throw o.status===401&&x()&&await B(o)?new g(e):new Error(`directory token request failed: ${o.status}`);const s=await o.json();if(typeof s.access_token!="string")throw new TypeError("directory token response has no access_token");return{access_token:s.access_token,expires_at:D(s.access_token,s.expires_in)}}n(O,"mintDirectoryToken");async function B(e){try{return(await e.json()).error==="invalid_client"}catch{return!1}}n(B,"isInvalidClient");function D(e,r){if(typeof r=="number"&&Number.isFinite(r)&&r>0)return Date.now()+r*1e3;try{const t=JSON.parse(Buffer.from(e.split(".")[1]??"","base64url").toString("utf8"));if(typeof t.exp=="number")return t.exp*1e3}catch{}return Date.now()+3e5}n(D,"tokenExpiry");function J(e,r){if(!e||typeof e!="object")throw new TypeError("invalid grant sync response");const t=e;if(t.runtime!==(r.runtime.startsWith("runtime:")?r.runtime:`runtime:${r.runtime}`)||typeof t.id!="string"||typeof t.sub!="string"||typeof t.grantee!="string"||!Number.isInteger(t.rev)||!Array.isArray(t.scopes)||!Array.isArray(t.resolved_scopes)||!["active","revoked","removed"].includes(t.status??"")||typeof t.synced_at!="string")throw new TypeError("invalid grant sync response");const a=e.revisions?.find(o=>o.rev===t.rev)?.approved_at;if(typeof a!="string")throw new TypeError("grant revision has no approval time");return{...t,spend_cap:t.spend_cap??null,expires_at:t.expires_at??null,revoked_at:t.revoked_at??null,approved_at:a,payer:"user",dependencies:T(t.dependencies),run_notice_url:typeof t.run_notice_url=="string"&&t.run_notice_url!==""?t.run_notice_url:null}}n(J,"responseGrant");let m;function K(e){return m?m(e):Promise.resolve()}n(K,"requestGrantSync");function V(e,r=t=>{}){let t;const i=e.replace(/\/$/,""),a=n(async()=>{const c=y(),u=await O(i),f=await fetch(`${i}/v1/runtime/grants`,{headers:{Authorization:`Bearer ${u.access_token}`},signal:AbortSignal.timeout(1e4)});if(!f.ok)throw new Error(`directory grant sync failed: ${f.status}`);const d=await f.json();if(!Array.isArray(d.grants)||typeof d.synced_at!="string")throw new TypeError("invalid grant sync response");const h=$();A(d.grants.map(l=>J(l,c)),d.synced_at,i),p({level:"info",event:"daemon.grants.synced",fields:{grants:d.grants.length,synced_at:d.synced_at}});try{await N(h,r)}catch(l){p({level:"error",event:"daemon.grants.on_sync_failed",error:l})}},"run");let o;const s=n(()=>{const c=a().catch(u=>{throw u instanceof g?p({level:"error",event:"daemon.grants.machine_revoked",error:u}):p({level:"error",event:"daemon.grants.sync_failed",error:u}),u}).finally(()=>{t===c&&(t=void 0)});return t=c,c},"start"),_=n((c={})=>t?c.fresh?(o??=t.catch(()=>{}).then(()=>(o=void 0,t??s())),o):t:s(),"sync");return m=_,_}n(V,"createGrantSync");export{z as GRANT_SYNC_INTERVAL_S,g as MachineRevokedError,G as assertion,V as createGrantSync,R as machineRevokedMessage,O as mintDirectoryToken,K as requestGrantSync,y as runtimeClient,H as runtimeId};
1
+ var w=Object.defineProperty;var n=(e,r)=>w(e,"name",{value:r,configurable:!0});import{createPrivateKey as v,randomUUID as S,sign as b}from"node:crypto";import{readFileSync as E}from"node:fs";import{join as j}from"node:path";import{crtrHome as x}from"../canvas/paths.js";import{isUnsandboxedMachine as g}from"../runtime/unsandboxed.js";import{emitEvent as f}from"../events/emit.js";import{allGrants as $,dependenciesOf as T,replaceGrants as A}from"./mirror.js";import{onGrantSync as N}from"./on-sync.js";const z=300;function O(e,r){const t=Math.floor(Date.now()/1e3),s=Buffer.from(JSON.stringify({alg:"ES256",typ:"JWT",kid:e.kid})).toString("base64url"),c=Buffer.from(JSON.stringify({iss:e.client_id,sub:e.client_id,aud:r,iat:t,exp:t+300,jti:S()})).toString("base64url"),o=`${s}.${c}`,i=b("sha256",Buffer.from(o),{key:v({key:e.jwk,format:"jwk"}),dsaEncoding:"ieee-p1363"});return`${o}.${i.toString("base64url")}`}n(O,"assertion");function y(){const e=JSON.parse(E(j(x(),"secrets","runtime-client.json"),"utf8"));if(!e.jwk||e.alg!=="ES256"||!e.kid||!e.client_id||!e.runtime||e.jwk.kid!==e.kid||e.jwk.kty!=="EC"||e.jwk.crv!=="P-256"||!e.jwk.d)throw new TypeError("invalid runtime client key");return e}n(y,"runtimeClient");function H(e=y()){return e.runtime.startsWith("runtime:")?e.runtime.slice(8):e.runtime}n(H,"runtimeId");function G(e){return`This machine was removed from your Northlight account. To use it again, create a join token at ${e.replace(/\/$/,"")}/account/machines, and run \`crtr sys join\` from a plain terminal.`}n(G,"machineRevokedMessage");class h extends Error{static{n(this,"MachineRevokedError")}constructor(r){super(G(r)),this.name="MachineRevokedError"}}async function R(e,r){const t=y(),s=`${e.replace(/\/$/,"")}/token`,c=new URLSearchParams({grant_type:"client_credentials",client_id:t.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:O(t,s),...r===void 0?{}:{audience:r}}),o=await fetch(s,{method:"POST",body:c,signal:AbortSignal.timeout(1e4)});if(!o.ok)throw o.status===401&&g()&&await B(o)?new h(e):new Error(`directory token request failed: ${o.status}`);const i=await o.json();if(typeof i.access_token!="string")throw new TypeError("directory token response has no access_token");return{access_token:i.access_token,expires_at:D(i.access_token,i.expires_in)}}n(R,"mintDirectoryToken");async function B(e){try{return(await e.json()).error==="invalid_client"}catch{return!1}}n(B,"isInvalidClient");function D(e,r){if(typeof r=="number"&&Number.isFinite(r)&&r>0)return Date.now()+r*1e3;try{const t=JSON.parse(Buffer.from(e.split(".")[1]??"","base64url").toString("utf8"));if(typeof t.exp=="number")return t.exp*1e3}catch{}return Date.now()+3e5}n(D,"tokenExpiry");function J(e,r){if(!e||typeof e!="object")throw new TypeError("invalid grant sync response");const t=e,s=r.runtime.startsWith("runtime:")?r.runtime:`runtime:${r.runtime}`;if(!(t.runtime===s||g()&&typeof t.runtime=="string"&&t.runtime.startsWith("runtime:")&&t.grantee!=="app:terminal")||typeof t.id!="string"||typeof t.sub!="string"||typeof t.grantee!="string"||!Number.isInteger(t.rev)||!Array.isArray(t.scopes)||!Array.isArray(t.resolved_scopes)||!["active","revoked","removed"].includes(t.status??"")||typeof t.synced_at!="string")throw new TypeError("invalid grant sync response");const i=e.revisions?.find(d=>d.rev===t.rev)?.approved_at;if(typeof i!="string")throw new TypeError("grant revision has no approval time");return{...t,spend_cap:t.spend_cap??null,expires_at:t.expires_at??null,revoked_at:t.revoked_at??null,approved_at:i,payer:"user",dependencies:T(t.dependencies),run_notice_url:typeof t.run_notice_url=="string"&&t.run_notice_url!==""?t.run_notice_url:null}}n(J,"responseGrant");let _;function K(e){return _?_(e):Promise.resolve()}n(K,"requestGrantSync");function V(e,r=t=>{}){let t;const s=e.replace(/\/$/,""),c=n(async()=>{const a=y(),u=await R(s),l=await fetch(`${s}/v1/runtime/grants`,{headers:{Authorization:`Bearer ${u.access_token}`},signal:AbortSignal.timeout(1e4)});if(!l.ok)throw new Error(`directory grant sync failed: ${l.status}`);const p=await l.json();if(!Array.isArray(p.grants)||typeof p.synced_at!="string")throw new TypeError("invalid grant sync response");const k=$();A(p.grants.map(m=>J(m,a)),p.synced_at,s),f({level:"info",event:"daemon.grants.synced",fields:{grants:p.grants.length,synced_at:p.synced_at}});try{await N(k,r)}catch(m){f({level:"error",event:"daemon.grants.on_sync_failed",error:m})}},"run");let o;const i=n(()=>{const a=c().catch(u=>{throw u instanceof h?f({level:"error",event:"daemon.grants.machine_revoked",error:u}):f({level:"error",event:"daemon.grants.sync_failed",error:u}),u}).finally(()=>{t===a&&(t=void 0)});return t=a,a},"start"),d=n((a={})=>t?a.fresh?(o??=t.catch(()=>{}).then(()=>(o=void 0,t??i())),o):t:i(),"sync");return _=d,d}n(V,"createGrantSync");export{z as GRANT_SYNC_INTERVAL_S,h as MachineRevokedError,O as assertion,V as createGrantSync,G as machineRevokedMessage,R as mintDirectoryToken,K as requestGrantSync,y as runtimeClient,H as runtimeId};
@@ -1,4 +1,4 @@
1
- var X=Object.defineProperty;var l=(t,e)=>X(t,"name",{value:e,configurable:!0});import{DatabaseSync as _}from"node:sqlite";import{randomUUID as N}from"node:crypto";import{symlinkSync as Z,chmodSync as j,chownSync as M,closeSync as $,cpSync as ee,existsSync as d,fsyncSync as R,lchownSync as te,lstatSync as f,mkdirSync as w,openSync as k,readFileSync as y,readlinkSync as D,readSync as U,readdirSync as h,renameSync as b,rmSync as L,rmdirSync as g,unlinkSync as A,writeSync as q}from"node:fs";import{request as ne}from"node:http";import{basename as T,dirname as m,isAbsolute as re,join as c,relative as S,resolve as oe}from"node:path";import{LAYOUT_VERSION as C}from"../layout.js";import{InputError as O}from"../io.js";import{oldToNewPathMap as se}from"./paths.js";import{conversations as ie,memoryAndProfiles as ce,previewStores as ae,profileFolders as ue,repairMovedRepositories as le,worktrees as de}from"./stores.js";import{storedPaths as fe}from"./stored-paths.js";import{OldToNewPathMap as Ce,oldToNewPathMap as Oe}from"./paths.js";const pe=new Set([".DS_Store","Thumbs.db","desktop.ini"]);function he(t){return pe.has(t)||t.startsWith("._")}l(he,"isJunkFile");function F(t,e,n){const o=c(t.map.old.root,"runtime","set-aside",e);let r=c(o,n);for(let i=2;d(r);i++)r=c(o,`${n}.${i}`);return r}l(F,"setAsidePath");function I(t,e,n,o=T(t)){const r=F(n,e,o);return w(m(r),{recursive:!0}),b(t,r),n.moved++,n.changes.push(`${t} -> ${r} (set aside)`),r}l(I,"setAside");function me(t){const e=f(t);return e.isDirectory()?h(t).length===0:e.isFile()&&e.size===0}l(me,"empty");function W(t,e,n,o=!1){const r=f(t),i=f(e);if(r.isDirectory()&&i.isDirectory()&&!o)return{action:"merge"};if(r.isFile()&&i.isFile()&&Q(t,e))return{action:"drop-source"};if(me(e))return{action:"remove-destination",line:`stray-destination: removed empty ${i.isDirectory()?"folder":"file"} ${e} that predates the migration`};const s=F(n,"replaced",S(n.map.old.root,e));return{action:"set-aside-destination",line:`stray-destination: ${e} predates the migration; moved it to ${s} and put ${t} in its place`}}l(W,"claim");function E(t,e,n,o=!1){if(d(t)&&t!==e){if(d(e)){const r=W(t,e,n,o);if(r.line&&n.resolutions.push(r.line),r.action==="merge"){for(const i of h(t))E(c(t,i),c(e,i),n);g(t);return}if(r.action==="drop-source"){L(t,{recursive:!0}),n.changes.push(`${t} (identical to ${e}; removed)`);return}r.action==="remove-destination"?(L(e,{recursive:!0}),n.changes.push(`${e} (removed)`)):I(e,"replaced",n,S(n.map.old.root,e))}w(m(e),{recursive:!0}),b(t,e),n.moved++,n.changes.push(`${t} -> ${e}`)}}l(E,"movePath");function z(t,e,n,o=!1){if(!d(t)||t===e||!d(e))return;const r=W(t,e,n,o);if(r.line&&n.resolutions.push(r.line),r.action==="merge")for(const i of h(t))z(c(t,i),c(e,i),n)}l(z,"previewMove");function B(t){const{map:e}=t,n=c(e.old.canvas,"nodes"),o=c(e.old.canvas,"canvas.db"),r=c(e.old.root,"runtime","canvas.db"),i=d(o)?o:d(r)&&f(r).size>0?r:"";if(!i){if(d(n))throw new Error("old node records exist without canvas.db");return null}const s=new _(i,{readOnly:t.dryRun||i!==o});try{if(i===o&&!t.dryRun&&s.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get().busy!==0)throw new Error("old canvas WAL is busy; stop every database user before migrating");return new Map(s.prepare("SELECT node_id, grantee FROM nodes").all().map(a=>{const u=a.grantee==="user:local"?"terminal":a.grantee.startsWith("app:")?a.grantee.slice(4):"";if(!u)throw new Error(`node ${a.node_id} has no app assignment`);return[a.node_id,u]}))}finally{s.close()}}l(B,"canvasNodeApps");function J(t,e){const n=c(t.map.old.canvas,"nodes");if(!d(n))return[];const o=[];for(const r of h(n)){const i=c(n,r),s=f(i).isDirectory();e.has(r)&&s||o.push({path:i,remove:!s&&he(r)})}return o}l(J,"orphanNodeEntries");function G(t,e){return t.remove?`node-leftover: deleted ${t.path} (not a node record)`:`node-leftover: ${t.path} has no node row; moved to ${F(e,"nodes",T(t.path))}`}l(G,"orphanLine");function ye(t){const{map:e}=t,n=c(e.old.canvas,"nodes"),o=B(t);if(o){for(const[r,i]of o)e.node(r,i);if(d(n)){for(const r of J(t,o)){if(t.resolutions.push(G(r,t)),r.remove){A(r.path),t.changes.push(`${r.path} (removed)`);continue}const i=I(r.path,"nodes",t),s=c(e.old.targetRoot,S(e.old.root,i));for(const a of new Set([e.old.storedCanvas,e.old.storedScope,c(e.old.storedScope,"canvas")]))e.add(c(a,"nodes",T(r.path)),s)}for(const[r,i]of o){const s=c(n,r);if(d(s)){for(const a of["context","reports","attachments"])E(c(s,a),c(e.old.root,"apps",i,"nodes",r,a),t);for(const a of h(s))["worktree","view.sock","broker-launch.json","system-prompt.md","situational-context.md","injected-docs.json"].includes(a)||E(c(s,a),c(e.old.root,"runtime","nodes",r,a),t);h(s).length===0&&g(s)}}h(n).length===0&&g(n)}}}l(ye,"records");const H=["auth.json","anthropic-credentials.json","openai-codex-credentials.json","openai-credentials.json","crouter-operations.db"];function ve(t,e,n){if(!d(t))return;if(!f(t).isFile())throw new Error(`credential source is not a regular file: ${t}`);const o=y(t);if(d(e)){(!f(e).isFile()||!y(e).equals(o))&&n.resolutions.push(`credential: kept the existing ${e}; the older ${t} differs and stays where it is`);return}w(m(e),{recursive:!0});const r=c(m(e),`.${N()}.tmp`);try{const i=k(r,"wx",384);try{for(let s=0;s<o.length;)s+=q(i,o,s,o.length-s);R(i)}finally{$(i)}if(b(r,e),!y(e).equals(o))throw new Error(`credential verification failed: ${e}`)}finally{d(r)&&A(r)}n.moved++,n.changes.push(`${t} -> ${e} (source retained)`)}l(ve,"copyCredential");function K(t){if(!d(t))return;const e=new _(t);try{if(e.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get().busy!==0)throw new Error(`SQLite WAL is busy: ${t}`)}finally{e.close()}}l(K,"checkpointDb");function Q(t,e){if(f(t).size!==f(e).size)return!1;const n=k(t,"r"),o=k(e,"r"),r=Buffer.allocUnsafe(64*1024),i=Buffer.allocUnsafe(r.length);try{for(;;){const s=U(n,r,0,r.length,null);if(s===0)return!0;let a=0;for(;a<s;){const u=U(o,i,a,s-a,null);if(u===0)return!1;a+=u}if(!r.subarray(0,s).equals(i.subarray(0,s)))return!1}}finally{$(n),$(o)}}l(Q,"sameFile");function P(t,e,n=!1){let o;try{o=f(e)}catch(u){if(u.code==="ENOENT")return!1;throw u}const r=f(t);if(r.isSymbolicLink())return o.isSymbolicLink()&&D(t)===D(e);if(r.isFile())return o.isFile()&&Q(t,e);if(!r.isDirectory()||!o.isDirectory())return!1;const i=h(t).filter(u=>!n||u!==".git"),s=h(e).filter(u=>!n||u!==".git"),a=new Set(s);return i.length===s.length&&i.every(u=>a.has(u)&&P(c(t,u),c(e,u),n))}l(P,"sameTree");function we(t,e,n){if(!d(t))return;if(!f(t).isDirectory())throw new Error(`hosted work is not a directory: ${t}`);if(d(e)){if(P(t,e,!0))return;if(!f(e).isDirectory()||h(e).length!==0){n.resolutions.push(`hosted-work: kept the existing ${e}; the old ${t} differs and stays where it is`);return}g(e)}w(m(e),{recursive:!0});const o=c(m(e),`.work-${N()}.migrating`);try{if(ee(t,o,{recursive:!0,verbatimSymlinks:!0,preserveTimestamps:!0}),!P(t,o))throw new Error(`hosted work copy verification failed: ${t}`);b(o,e),n.moved++,n.changes.push(`${t} -> ${e} (source retained)`)}finally{d(o)&&L(o,{recursive:!0,force:!0})}}l(we,"copyHostedWork");function V(t){const{old:e}=t.map,n=c(e.root,"runtime"),o=c(n,"scope"),r=[];for(const i of["canvas.db","canvas.db-wal","canvas.db-shm","on-disk-migrations.json","requests","reviews","plugins","marketplaces","crtrd.log","crtrd.err","cache"])r.push([c(e.canvas,i),c(n,i)]);for(const i of["prompts","personas","config.json","profile-defaults.json","operations.db","operations.db-wal","operations.db-shm"])r.push([c(e.scope,i),c(o,i)]);for(const i of["support","prompt-reviews","plugins","marketplaces"])r.push([c(e.scope,i),c(n,i)]);return r.push([c(e.canvas,"secrets"),c(n,"secrets")]),r}l(V,"topLevelMoves");function be(t){const{old:e}=t.map,n=c(e.root,"runtime");K(c(e.scope,"operations.db"));for(const[o,r]of V(t)){const i=ge(o);E(o,r,t,!0),$e(i,o,r,t)}K(c(e.agent,"crouter-operations.db"));for(const o of H)ve(c(e.agent,o),c(n,"secrets",o),t);process.platform==="linux"&&we(e.work,c(e.root,"user","files"),t)}l(be,"topLevel");function ge(t){if(!d(t)||!f(t).isDirectory())return[];const e=[];for(const n of h(t)){const o=c(t,n);if(!f(o).isSymbolicLink())continue;const r=D(o);re(r)||e.push({name:n,target:r})}return e}l(ge,"relativeLinks");function $e(t,e,n,o){if(!(t.length===0||!d(n)))for(const{name:r,target:i}of t){const s=c(n,r);if(!ke(s))continue;const a=o.map.mapPath(oe(e,i)),u=S(n,a);u!==i&&(A(s),Z(u,s),o.changes.push(`${s}: link ${i} -> ${u}`))}}l($e,"retargetLinks");function ke(t){try{return f(t),!0}catch{return!1}}l(ke,"lstatExists");function Se(t){if(process.platform!=="linux")return;if(process.getuid?.()!==0)throw new Error("Linux layout migration requires root for the ownership pass");const e=y("/etc/passwd","utf8").split(`
2
- `).find(s=>s.startsWith("crtrd:")),n=y("/etc/group","utf8").split(`
3
- `).find(s=>s.startsWith("crtr-data:"));if(!e||!n)throw new Error("layout migration requires crtrd user and crtr-data group");const o=Number(e.split(":")[2]),r=Number(n.split(":")[2]);if(!Number.isInteger(o)||!Number.isInteger(r))throw new Error("invalid migration user or group");const i=l((s,a)=>{if(!d(s))return;const u=f(s);if(u.isSymbolicLink()){te(s,o,a?r:u.gid);return}if(M(s,o,a?r:u.gid),u.isDirectory()){j(s,a?1528:448);for(const v of h(s))i(c(s,v),a)}else a&&j(s,u.mode&73?504:432)},"walk");i(c(t,"runtime"),!1),i(c(t,"user"),!0),i(c(t,"apps"),!0)}l(Se,"ownership");function Ee(t){return new Promise(e=>{const n=ne({socketPath:t,path:"/healthz",method:"GET",timeout:1e3},o=>{o.resume(),e(!0)});n.on("timeout",()=>n.destroy(new Error("daemon probe timed out"))),n.on("error",o=>{o.code==="ENOENT"||o.code==="ECONNREFUSED"?e(!1):e(!0)}),n.end()})}l(Ee,"daemonAnswers");async function Re(t){const e=se(),n=e.old,o=c(n.root,"runtime","layout");if(d(o)){if(y(o,"utf8").trim()!==C)throw new Error("unsupported runtime layout version");return null}const r=new Set([c(n.canvas,"crtrd.sock"),c(n.scope,"crtrd.sock"),process.platform==="linux"?"/run/crtr/api.sock":c(n.root,"runtime","api.sock")]);for(const p of r)if(await Ee(p))throw new O({error:"daemon_running",message:`cannot migrate while a daemon answers on ${p}`,next:"Stop the daemon that owns this volume, then run `crtr sys migrate` again."});if(process.platform==="linux"&&!d(n.agent))throw new O({error:"credential_volume_missing",message:`the old credential volume is not mounted at ${n.agent}`,next:`Mount the old provider-auth volume at ${n.agent}, then run \`crtr sys migrate\` again.`});const i=H.map(p=>c(n.agent,p)).filter(p=>!d(p)),s={map:e,moved:0,changes:[],resolutions:[],bodyPaths:[],absentCredentials:i,dryRun:t};if(t){const p=B(s);if(p)for(const x of J(s,p))s.resolutions.push(G(x,s));for(const[x,Y]of V(s))z(x,Y,s,!0);return ae(s),s}ue(s);const a=[ye,de,be,le,ce,ie,fe];for(const p of a)p(s);n.canvas!==n.root&&d(n.canvas)&&h(n.canvas).length===0&&g(n.canvas),Se(n.root),w(m(o),{recursive:!0});const u=`${o}.${N()}.tmp`,v=k(u,"wx",384);try{if(process.platform==="linux"){const p=f(m(o));M(u,p.uid,p.gid)}q(v,`${C}
4
- `),R(v)}finally{$(v)}return b(u,o),s}l(Re,"migrateLayout");export{Ce as OldToNewPathMap,W as claim,Ee as daemonAnswers,he as isJunkFile,Re as migrateLayout,E as movePath,Oe as oldToNewPathMap,z as previewMove,Q as sameFile,I as setAside,F as setAsidePath};
1
+ var Y=Object.defineProperty;var l=(t,e)=>Y(t,"name",{value:e,configurable:!0});import{DatabaseSync as M}from"node:sqlite";import{randomUUID as D}from"node:crypto";import{symlinkSync as Z,linkSync as ee,chmodSync as _,chownSync as j,closeSync as g,cpSync as te,existsSync as d,fsyncSync as R,lchownSync as ne,lstatSync as f,mkdirSync as v,openSync as S,readFileSync as w,readlinkSync as L,readSync as U,readdirSync as h,renameSync as k,rmSync as T,rmdirSync as b,unlinkSync as $,writeSync as q}from"node:fs";import{request as re}from"node:http";import{basename as A,dirname as y,isAbsolute as oe,join as c,relative as E,resolve as se}from"node:path";import{LAYOUT_VERSION as ie,assertSupportedLayoutMarker as C}from"../layout.js";import{InputError as I}from"../io.js";import{syncDirectory as ce}from"../fs-utils.js";import{oldToNewPathMap as ae}from"./paths.js";import{conversations as ue,memoryAndProfiles as le,previewStores as de,profileFolders as fe,repairMovedRepositories as pe,worktrees as he}from"./stores.js";import{storedPaths as ye}from"./stored-paths.js";import{OldToNewPathMap as ze,oldToNewPathMap as Be}from"./paths.js";const me=new Set([".DS_Store","Thumbs.db","desktop.ini"]);function ve(t){return me.has(t)||t.startsWith("._")}l(ve,"isJunkFile");function F(t,e,n){const r=c(t.map.old.root,"runtime","set-aside",e);let o=c(r,n);for(let i=2;d(o);i++)o=c(r,`${n}.${i}`);return o}l(F,"setAsidePath");function O(t,e,n,r=A(t)){const o=F(n,e,r);return v(y(o),{recursive:!0}),k(t,o),n.moved++,n.changes.push(`${t} -> ${o} (set aside)`),o}l(O,"setAside");function we(t){const e=f(t);return e.isDirectory()?h(t).length===0:e.isFile()&&e.size===0}l(we,"empty");function W(t,e,n,r=!1){const o=f(t),i=f(e);if(o.isDirectory()&&i.isDirectory()&&!r)return{action:"merge"};if(o.isFile()&&i.isFile()&&Q(t,e))return{action:"drop-source"};if(we(e))return{action:"remove-destination",line:`stray-destination: removed empty ${i.isDirectory()?"folder":"file"} ${e} that predates the migration`};const s=F(n,"replaced",E(n.map.old.root,e));return{action:"set-aside-destination",line:`stray-destination: ${e} predates the migration; moved it to ${s} and put ${t} in its place`}}l(W,"claim");function x(t,e,n,r=!1){if(d(t)&&t!==e){if(d(e)){const o=W(t,e,n,r);if(o.line&&n.resolutions.push(o.line),o.action==="merge"){for(const i of h(t))x(c(t,i),c(e,i),n);b(t);return}if(o.action==="drop-source"){T(t,{recursive:!0}),n.changes.push(`${t} (identical to ${e}; removed)`);return}o.action==="remove-destination"?(T(e,{recursive:!0}),n.changes.push(`${e} (removed)`)):O(e,"replaced",n,E(n.map.old.root,e))}v(y(e),{recursive:!0}),k(t,e),n.moved++,n.changes.push(`${t} -> ${e}`)}}l(x,"movePath");function z(t,e,n,r=!1){if(!d(t)||t===e||!d(e))return;const o=W(t,e,n,r);if(o.line&&n.resolutions.push(o.line),o.action==="merge")for(const i of h(t))z(c(t,i),c(e,i),n)}l(z,"previewMove");function B(t){const{map:e}=t,n=c(e.old.canvas,"nodes"),r=c(e.old.canvas,"canvas.db"),o=c(e.old.root,"runtime","canvas.db"),i=d(r)?r:d(o)&&f(o).size>0?o:"";if(!i){if(d(n))throw new Error("old node records exist without canvas.db");return null}const s=new M(i,{readOnly:t.dryRun||i!==r});try{if(i===r&&!t.dryRun&&s.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get().busy!==0)throw new Error("old canvas WAL is busy; stop every database user before migrating");return new Map(s.prepare("SELECT node_id, grantee FROM nodes").all().map(a=>{const u=a.grantee==="user:local"?"terminal":a.grantee.startsWith("app:")?a.grantee.slice(4):"";if(!u)throw new Error(`node ${a.node_id} has no app assignment`);return[a.node_id,u]}))}finally{s.close()}}l(B,"canvasNodeApps");function J(t,e){const n=c(t.map.old.canvas,"nodes");if(!d(n))return[];const r=[];for(const o of h(n)){const i=c(n,o),s=f(i).isDirectory();e.has(o)&&s||r.push({path:i,remove:!s&&ve(o)})}return r}l(J,"orphanNodeEntries");function G(t,e){return t.remove?`node-leftover: deleted ${t.path} (not a node record)`:`node-leftover: ${t.path} has no node row; moved to ${F(e,"nodes",A(t.path))}`}l(G,"orphanLine");function be(t){const{map:e}=t,n=c(e.old.canvas,"nodes"),r=B(t);if(r){for(const[o,i]of r)e.node(o,i);if(d(n)){for(const o of J(t,r)){if(t.resolutions.push(G(o,t)),o.remove){$(o.path),t.changes.push(`${o.path} (removed)`);continue}const i=O(o.path,"nodes",t),s=c(e.old.targetRoot,E(e.old.root,i));for(const a of new Set([e.old.storedCanvas,e.old.storedScope,c(e.old.storedScope,"canvas")]))e.add(c(a,"nodes",A(o.path)),s)}for(const[o,i]of r){const s=c(n,o);if(d(s)){for(const a of["context","reports","attachments"])x(c(s,a),c(e.old.root,"apps",i,"nodes",o,a),t);for(const a of h(s))["worktree","view.sock","broker-launch.json","system-prompt.md","situational-context.md","injected-docs.json"].includes(a)||x(c(s,a),c(e.old.root,"runtime","nodes",o,a),t);h(s).length===0&&b(s)}}h(n).length===0&&b(n)}}}l(be,"records");const H=["auth.json","anthropic-credentials.json","openai-codex-credentials.json","openai-credentials.json","crouter-operations.db"];function ge(t,e,n){if(!d(t))return;if(!f(t).isFile())throw new Error(`credential source is not a regular file: ${t}`);const r=w(t);if(d(e)){(!f(e).isFile()||!w(e).equals(r))&&n.resolutions.push(`credential: kept the existing ${e}; the older ${t} differs and stays where it is`);return}v(y(e),{recursive:!0});const o=c(y(e),`.${D()}.tmp`);try{const i=S(o,"wx",384);try{for(let s=0;s<r.length;)s+=q(i,r,s,r.length-s);R(i)}finally{g(i)}if(k(o,e),!w(e).equals(r))throw new Error(`credential verification failed: ${e}`)}finally{d(o)&&$(o)}n.moved++,n.changes.push(`${t} -> ${e} (source retained)`)}l(ge,"copyCredential");function K(t){if(!d(t))return;const e=new M(t);try{if(e.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get().busy!==0)throw new Error(`SQLite WAL is busy: ${t}`)}finally{e.close()}}l(K,"checkpointDb");function Q(t,e){if(f(t).size!==f(e).size)return!1;const n=S(t,"r"),r=S(e,"r"),o=Buffer.allocUnsafe(64*1024),i=Buffer.allocUnsafe(o.length);try{for(;;){const s=U(n,o,0,o.length,null);if(s===0)return!0;let a=0;for(;a<s;){const u=U(r,i,a,s-a,null);if(u===0)return!1;a+=u}if(!o.subarray(0,s).equals(i.subarray(0,s)))return!1}}finally{g(n),g(r)}}l(Q,"sameFile");function P(t,e,n=!1){let r;try{r=f(e)}catch(u){if(u.code==="ENOENT")return!1;throw u}const o=f(t);if(o.isSymbolicLink())return r.isSymbolicLink()&&L(t)===L(e);if(o.isFile())return r.isFile()&&Q(t,e);if(!o.isDirectory()||!r.isDirectory())return!1;const i=h(t).filter(u=>!n||u!==".git"),s=h(e).filter(u=>!n||u!==".git"),a=new Set(s);return i.length===s.length&&i.every(u=>a.has(u)&&P(c(t,u),c(e,u),n))}l(P,"sameTree");function Se(t,e,n){if(!d(t))return;if(!f(t).isDirectory())throw new Error(`hosted work is not a directory: ${t}`);if(d(e)){if(P(t,e,!0))return;if(!f(e).isDirectory()||h(e).length!==0){n.resolutions.push(`hosted-work: kept the existing ${e}; the old ${t} differs and stays where it is`);return}b(e)}v(y(e),{recursive:!0});const r=c(y(e),`.work-${D()}.migrating`);try{if(te(t,r,{recursive:!0,verbatimSymlinks:!0,preserveTimestamps:!0}),!P(t,r))throw new Error(`hosted work copy verification failed: ${t}`);k(r,e),n.moved++,n.changes.push(`${t} -> ${e} (source retained)`)}finally{d(r)&&T(r,{recursive:!0,force:!0})}}l(Se,"copyHostedWork");function V(t){const{old:e}=t.map,n=c(e.root,"runtime"),r=c(n,"scope"),o=[];for(const i of["canvas.db","canvas.db-wal","canvas.db-shm","on-disk-migrations.json","requests","reviews","plugins","marketplaces","crtrd.log","crtrd.err","cache"])o.push([c(e.canvas,i),c(n,i)]);for(const i of["prompts","personas","config.json","profile-defaults.json","operations.db","operations.db-wal","operations.db-shm"])o.push([c(e.scope,i),c(r,i)]);for(const i of["support","prompt-reviews","plugins","marketplaces"])o.push([c(e.scope,i),c(n,i)]);return o.push([c(e.canvas,"secrets"),c(n,"secrets")]),o}l(V,"topLevelMoves");function ke(t){const{old:e}=t.map,n=c(e.root,"runtime");K(c(e.scope,"operations.db"));for(const[r,o]of V(t)){const i=$e(r);x(r,o,t,!0),Ee(i,r,o,t)}K(c(e.agent,"crouter-operations.db"));for(const r of H)ge(c(e.agent,r),c(n,"secrets",r),t);process.platform==="linux"&&Se(e.work,c(e.root,"user","files"),t)}l(ke,"topLevel");function $e(t){if(!d(t)||!f(t).isDirectory())return[];const e=[];for(const n of h(t)){const r=c(t,n);if(!f(r).isSymbolicLink())continue;const o=L(r);oe(o)||e.push({name:n,target:o})}return e}l($e,"relativeLinks");function Ee(t,e,n,r){if(!(t.length===0||!d(n)))for(const{name:o,target:i}of t){const s=c(n,o);if(!xe(s))continue;const a=r.map.mapPath(se(e,i)),u=E(n,a);u!==i&&($(s),Z(u,s),r.changes.push(`${s}: link ${i} -> ${u}`))}}l(Ee,"retargetLinks");function xe(t){try{return f(t),!0}catch{return!1}}l(xe,"lstatExists");function Ne(t){if(process.platform!=="linux")return;if(process.getuid?.()!==0)throw new Error("Linux layout migration requires root for the ownership pass");const e=w("/etc/passwd","utf8").split(`
2
+ `).find(s=>s.startsWith("crtrd:")),n=w("/etc/group","utf8").split(`
3
+ `).find(s=>s.startsWith("crtr-data:"));if(!e||!n)throw new Error("layout migration requires crtrd user and crtr-data group");const r=Number(e.split(":")[2]),o=Number(n.split(":")[2]);if(!Number.isInteger(r)||!Number.isInteger(o))throw new Error("invalid migration user or group");const i=l((s,a)=>{if(!d(s))return;const u=f(s);if(u.isSymbolicLink()){ne(s,r,a?o:u.gid);return}if(j(s,r,a?o:u.gid),u.isDirectory()){_(s,a?1528:448);for(const m of h(s))i(c(s,m),a)}else a&&_(s,u.mode&73?504:432)},"walk");i(c(t,"runtime"),!1),i(c(t,"user"),!0),i(c(t,"apps"),!0)}l(Ne,"ownership");function De(t){return new Promise(e=>{const n=re({socketPath:t,path:"/healthz",method:"GET",timeout:1e3},r=>{r.resume(),e(!0)});n.on("timeout",()=>n.destroy(new Error("daemon probe timed out"))),n.on("error",r=>{r.code==="ENOENT"||r.code==="ECONNREFUSED"?e(!1):e(!0)}),n.end()})}l(De,"daemonAnswers");async function Ie(t){const e=ae(),n=e.old,r=c(n.root,"runtime","layout");if(d(r))return C(r),null;const o=new Set([c(n.canvas,"crtrd.sock"),c(n.scope,"crtrd.sock"),process.platform==="linux"?"/run/crtr/api.sock":c(n.root,"runtime","api.sock")]);for(const p of o)if(await De(p))throw new I({error:"daemon_running",message:`cannot migrate while a daemon answers on ${p}`,next:"Stop the daemon that owns this volume, then run `crtr sys migrate` again."});if(process.platform==="linux"&&!d(n.agent))throw new I({error:"credential_volume_missing",message:`the old credential volume is not mounted at ${n.agent}`,next:`Mount the old provider-auth volume at ${n.agent}, then run \`crtr sys migrate\` again.`});const i=H.map(p=>c(n.agent,p)).filter(p=>!d(p)),s={map:e,moved:0,changes:[],resolutions:[],bodyPaths:[],absentCredentials:i,dryRun:t};if(t){const p=B(s);if(p)for(const N of J(s,p))s.resolutions.push(G(N,s));for(const[N,X]of V(s))z(N,X,s,!0);return de(s),s}fe(s);const a=[be,he,ke,pe,le,ue,ye];for(const p of a)p(s);n.canvas!==n.root&&d(n.canvas)&&h(n.canvas).length===0&&b(n.canvas),Ne(n.root),v(y(r),{recursive:!0});const u=`${r}.${D()}.tmp`,m=S(u,"wx",384);try{try{if(process.platform==="linux"){const p=f(y(r));j(u,p.uid,p.gid)}q(m,`${ie}
4
+ `),R(m)}finally{g(m)}try{ee(u,r)}catch(p){if(p.code==="EEXIST")C(r);else throw p}}finally{$(u)}return ce(y(r)),s}l(Ie,"migrateLayout");export{ze as OldToNewPathMap,W as claim,De as daemonAnswers,ve as isJunkFile,Ie as migrateLayout,x as movePath,Be as oldToNewPathMap,z as previewMove,Q as sameFile,O as setAside,F as setAsidePath};
@@ -31,6 +31,14 @@ export declare function cronFolder(app: string, cronId: string): string;
31
31
  * and the terminal wherever a sandbox exists, keeps its own app home. Callers
32
32
  * keep `PI_CODING_AGENT_DIR` in the app home either way. */
33
33
  export declare function appProcessHome(app: string): string;
34
+ /** The marker's byte length and an escaped, truncated preview, safe to print: a
35
+ * corrupt marker (empty, truncated, NUL-filled by a lost write) is otherwise
36
+ * indistinguishable from an intentional newer layout in a crash log. */
37
+ export declare function describeLayoutMarker(bytes: Uint8Array): string;
38
+ /** Refuse any marker other than the supported version. A non-matching marker is
39
+ * never rewritten: an empty or damaged one means the volume lost a write, and
40
+ * stamping the current version over it would assert a layout nobody verified. */
41
+ export declare function assertSupportedLayoutMarker(path: string): void;
34
42
  /** A missing version on an occupied volume is never initialized by daemon startup. */
35
43
  export declare function layoutNeedsMigration(): boolean;
36
44
  export declare function initializeEmptyLayout(): void;
@@ -1,2 +1,2 @@
1
- var w=Object.defineProperty;var t=(r,o)=>w(r,"name",{value:o,configurable:!0});import{existsSync as i,mkdirSync as m,readFileSync as E,statSync as R,writeFileSync as _}from"node:fs";import{homedir as S,userInfo as T}from"node:os";import{basename as l,dirname as A,isAbsolute as O,join as e,resolve as f}from"node:path";import{envHomeOverride as $}from"../shared/env.js";import{readConfig as b}from"./config.js";const d="1";function s(){const r=f($()||(process.platform==="linux"?"/":e(S(),".crouter")));if(g()&&r===z())throw new Error(`refusing to use the account's real crouter root ${r} from a node --test process; point CRTR_HOME (not only HOME) at a temporary directory`);return r}t(s,"layoutRoot");function g(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}t(g,"runningUnderNodeTest");let c;function z(){if(c===void 0)try{c=f(T().homedir,".crouter")}catch{c=null}return c}t(z,"accountLayoutRoot");function u(){return e(s(),"runtime")}t(u,"runtimeRoot");function h(){return e(s(),"user")}t(h,"userRoot");function x(){return e(s(),"apps")}t(x,"appsRoot");function y(){return e(u(),"secrets")}t(y,"secretsDir");function D(r,o){if(!/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/.test(o))throw new Error(`invalid profile id: ${o}`);const n=p(r);return{memory:e(n.memory,"profiles",o),env:e(y(),"env",l(n.root),o),home:n.home}}t(D,"profileSpace");function v(){return process.platform==="linux"?"/run/crtr":u()}t(v,"socketDir");function a(){return e(u(),"layout")}t(a,"layoutVersionFile");function F(){const r=b("user").paths?.user_files??(process.platform==="linux"?e(h(),"files"):A(s()));if(!O(r))throw new Error("paths.user_files must be absolute");return f(r)}t(F,"userFilesRoot");function p(r){const o=r.startsWith("app:")?r.slice(4):r;if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o)||o==="."||o==="..")throw new Error(`invalid app id: ${r}`);const n=e(x(),o);return{root:n,memory:e(n,"memory"),conversations:e(n,"conversations"),nodes:e(n,"nodes"),files:e(n,"files"),home:e(n,"home"),cache:e(n,"cache")}}t(p,"appSpace");function M(r,o){if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o))throw new Error(`invalid cron id: ${o}`);return e(p(r).root,"crons",o)}t(M,"cronFolder");function V(r){const o=p(r);return process.platform==="linux"?o.home:l(o.root)==="terminal"?F():o.home}t(V,"appProcessHome");function C(){if(process.env.CRTR_LAYOUT_REFUSE==="1")return!0;if(i(a())){if(E(a(),"utf8").trim()!==d)throw new Error("unsupported runtime layout version");return!1}const r=s(),o=process.platform==="linux"&&r==="/"&&i("/volume")?"/volume":r;return i(e(o,"canvas"))||i(e(o,"canvas.db"))?!0:i(e(u(),"canvas.db"))}t(C,"layoutNeedsMigration");function Y(){if(C())throw new Error("layout_migration_required");for(const r of[u(),h(),x(),y()])m(r,{recursive:!0,mode:448});if(m(v(),{recursive:!0,mode:493}),process.platform==="linux"&&(R(v()).mode&511)!==493)throw new Error("launcher socket directory must be mode 0755");i(a())||_(a(),`${d}
2
- `,{flag:"wx",mode:384})}t(Y,"initializeEmptyLayout");export{d as LAYOUT_VERSION,V as appProcessHome,p as appSpace,x as appsRoot,M as cronFolder,Y as initializeEmptyLayout,C as layoutNeedsMigration,s as layoutRoot,a as layoutVersionFile,D as profileSpace,u as runtimeRoot,y as secretsDir,v as socketDir,F as userFilesRoot,h as userRoot};
1
+ var $=Object.defineProperty;var n=(r,o)=>$(r,"name",{value:o,configurable:!0});import{existsSync as i,mkdirSync as d,readFileSync as S,statSync as R}from"node:fs";import{homedir as _,userInfo as g}from"node:os";import{basename as x,dirname as f,isAbsolute as A,join as t,resolve as p}from"node:path";import{envHomeOverride as T}from"../shared/env.js";import{readConfig as O}from"./config.js";import{publishFileExclusive as z,syncDirectory as h}from"./fs-utils.js";const m="1";function s(){const r=p(T()||(process.platform==="linux"?"/":t(_(),".crouter")));if(C()&&r===L())throw new Error(`refusing to use the account's real crouter root ${r} from a node --test process; point CRTR_HOME (not only HOME) at a temporary directory`);return r}n(s,"layoutRoot");function C(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}n(C,"runningUnderNodeTest");let c;function L(){if(c===void 0)try{c=p(g().homedir,".crouter")}catch{c=null}return c}n(L,"accountLayoutRoot");function u(){return t(s(),"runtime")}n(u,"runtimeRoot");function v(){return t(s(),"user")}n(v,"userRoot");function y(){return t(s(),"apps")}n(y,"appsRoot");function w(){return t(u(),"secrets")}n(w,"secretsDir");function Y(r,o){if(!/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/.test(o))throw new Error(`invalid profile id: ${o}`);const e=l(r);return{memory:t(e.memory,"profiles",o),env:t(w(),"env",x(e.root),o),home:e.home}}n(Y,"profileSpace");function E(){return process.platform==="linux"?"/run/crtr":u()}n(E,"socketDir");function a(){return t(u(),"layout")}n(a,"layoutVersionFile");function k(){const r=O("user").paths?.user_files??(process.platform==="linux"?t(v(),"files"):f(s()));if(!A(r))throw new Error("paths.user_files must be absolute");return p(r)}n(k,"userFilesRoot");function l(r){const o=r.startsWith("app:")?r.slice(4):r;if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o)||o==="."||o==="..")throw new Error(`invalid app id: ${r}`);const e=t(y(),o);return{root:e,memory:t(e,"memory"),conversations:t(e,"conversations"),nodes:t(e,"nodes"),files:t(e,"files"),home:t(e,"home"),cache:t(e,"cache")}}n(l,"appSpace");function q(r,o){if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o))throw new Error(`invalid cron id: ${o}`);return t(l(r).root,"crons",o)}n(q,"cronFolder");function P(r){const o=l(r);return process.platform==="linux"?o.home:x(o.root)==="terminal"?k():o.home}n(P,"appProcessHome");function F(r){const o=Array.from(r.subarray(0,32),e=>e===92?"\\\\":e===34?'\\"':e===10?"\\n":e===13?"\\r":e===9?"\\t":e>=32&&e<127?String.fromCharCode(e):`\\x${e.toString(16).padStart(2,"0")}`).join("");return`${r.length} bytes, preview "${o}"${r.length>32?"\u2026":""}`}n(F,"describeLayoutMarker");function M(r){const o=S(r);if(o.toString("utf8").trim()!==m)throw new Error(`unsupported runtime layout version: ${r} holds ${F(o)}; expected "${m}"`)}n(M,"assertSupportedLayoutMarker");function N(){if(process.env.CRTR_LAYOUT_REFUSE==="1")return!0;if(i(a()))return M(a()),!1;const r=s(),o=process.platform==="linux"&&r==="/"&&i("/volume")?"/volume":r;return i(t(o,"canvas"))||i(t(o,"canvas.db"))?!0:i(t(u(),"canvas.db"))}n(N,"layoutNeedsMigration");function W(){if(N())throw new Error("layout_migration_required");for(const r of[u(),v(),y(),w()]){const o=d(r,{recursive:!0,mode:448});if(o!==void 0){for(let e=r;h(e),e!==o;e=f(e));h(f(o))}}if(d(E(),{recursive:!0,mode:493}),process.platform==="linux"&&(R(E()).mode&511)!==493)throw new Error("launcher socket directory must be mode 0755");i(a())||z(a(),`${m}
2
+ `,384)}n(W,"initializeEmptyLayout");export{m as LAYOUT_VERSION,P as appProcessHome,l as appSpace,y as appsRoot,M as assertSupportedLayoutMarker,q as cronFolder,F as describeLayoutMarker,W as initializeEmptyLayout,N as layoutNeedsMigration,s as layoutRoot,a as layoutVersionFile,Y as profileSpace,u as runtimeRoot,w as secretsDir,E as socketDir,k as userFilesRoot,v as userRoot};
@@ -1 +1 @@
1
- var T=Object.defineProperty;var r=(c,t)=>T(c,"name",{value:t,configurable:!0});import{chmodSync as f,existsSync as a,fsyncSync as u,mkdirSync as F,openSync as m,readFileSync as y,renameSync as L,writeFileSync as S,closeSync as w}from"node:fs";import{dirname as _}from"node:path";import x from"proper-lockfile";const b={encoding:"utf-8",mode:384},E=3e4,O=5e3;class K{static{r(this,"LockedJsonFile")}path;durable;initialContent;constructor(t,e=!1,i="{}"){this.path=t,this.durable=e,this.initialContent=i}readUnlocked(){return a(this.path)?y(this.path,"utf-8"):void 0}ensureFile(){const t=_(this.path);a(t)||F(t,{recursive:!0,mode:448}),a(this.path)||(S(this.path,this.initialContent,b),f(this.path,384))}async withLockAsync(t){this.ensureFile();let e,i;const s=r(()=>{if(i)throw i},"throwIfCompromised");try{e=await x.lock(this.path,{realpath:!1,retries:{retries:10,factor:2,minTimeout:100,maxTimeout:1e4,randomize:!0},stale:E,update:O,onCompromised:r(h=>{i=h},"onCompromised")}),s();const k=a(this.path)?y(this.path,"utf-8"):void 0;let l=!1;const d=r(h=>{if(l)throw new Error(`locked file was published twice: ${this.path}`);s();const o=`${this.path}.${process.pid}.${Date.now()}.tmp`;if(S(o,h,b),f(o,384),this.durable){const n=m(o,"r");try{u(n)}finally{w(n)}}if(L(o,this.path),this.durable){const n=m(_(this.path),"r");try{u(n)}finally{w(n)}}l=!0},"publish"),{result:C,next:p}=await t(k,s,d);return s(),p!==void 0&&d(p),s(),C}finally{if(e)try{await e()}catch{}}}}export{K as LockedJsonFile};
1
+ var k=Object.defineProperty;var o=(c,t)=>k(c,"name",{value:t,configurable:!0});import{chmodSync as f,existsSync as a,fsyncSync as u,mkdirSync as C,openSync as m,readFileSync as y,renameSync as F,writeFileSync as T,closeSync as S}from"node:fs";import{dirname as w}from"node:path";import x from"proper-lockfile";import{publishFileExclusive as E}from"./fs-utils.js";const L={encoding:"utf-8",mode:384},O=3e4,$=5e3;class M{static{o(this,"LockedJsonFile")}path;durable;initialContent;constructor(t,i=!1,e="{}"){this.path=t,this.durable=i,this.initialContent=e}readUnlocked(){return a(this.path)?y(this.path,"utf-8"):void 0}ensureFile(){const t=w(this.path);a(t)||C(t,{recursive:!0,mode:448}),a(this.path)||(E(this.path,this.initialContent,384),f(this.path,384))}async withLockAsync(t){this.ensureFile();let i,e;const s=o(()=>{if(e)throw e},"throwIfCompromised");try{i=await x.lock(this.path,{realpath:!1,retries:{retries:10,factor:2,minTimeout:100,maxTimeout:1e4,randomize:!0},stale:O,update:$,onCompromised:o(h=>{e=h},"onCompromised")}),s();const b=a(this.path)?y(this.path,"utf-8"):void 0;let l=!1;const p=o(h=>{if(l)throw new Error(`locked file was published twice: ${this.path}`);s();const r=`${this.path}.${process.pid}.${Date.now()}.tmp`;if(T(r,h,L),f(r,384),this.durable){const n=m(r,"r");try{u(n)}finally{S(n)}}if(F(r,this.path),this.durable){const n=m(w(this.path),"r");try{u(n)}finally{S(n)}}l=!0},"publish"),{result:_,next:d}=await t(b,s,p);return s(),d!==void 0&&p(d),s(),_}finally{if(i)try{await i()}catch{}}}}export{M as LockedJsonFile};
@@ -1,2 +1,2 @@
1
- var X=Object.defineProperty;var i=(e,n)=>X(e,"name",{value:n,configurable:!0});import{existsSync as h,readdirSync as H,readFileSync as y,rmSync as b,statSync as x}from"node:fs";import{join as u}from"node:path";import{validateCommandManifest as Y}from"../../api/command-manifest/manifest.js";import{crtrHome as O}from"../canvas/paths.js";import{withCanvasWrite as ee}from"../canvas/db.js";import{bundleVersion as ne,isInstalledGeneration as re,recordCheckedGeneration as te}from"../command-plugins/bundle.js";import{readConfig as U,readState as oe}from"../config.js";import{emitEvent as g}from"../events/emit.js";import{withExclusiveDirectoryLockAsync as ie}from"../exclusive-lock.js";import{atomicWriteText as M,ensureDir as I,pathExists as ae}from"../fs-utils.js";import{reconcilePackageDocuments as se}from"../graph/package-docs.js";import{readPluginManifest as W}from"../manifest.js";import{bundleSwapLockPath as de}from"../plugin-swap-lock.js";import{pluginsDir as ue,userScopeRoot as ce}from"../scope.js";import{allGrants as fe}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const T="[A-Za-z0-9_-]{1,64}",le=new RegExp(`^(${T}):(${T})$`),z=new RegExp(`^${T}$`),pe=3e4,me=3e4;function ve(e){const n=new Set;for(const r of e)if(r.status==="active")for(const{scope:t}of r.resolved_scopes){const o=le.exec(t);o&&o[1]!=="crtr"&&n.add(o[1])}return[...n].sort()}i(ve,"providerIdsFromGrants");function w(e){return u(ue("user"),e)}i(w,"pluginRoot");function F(){return u(O(),"providers")}i(F,"providersRoot");function G(e,n){return u(F(),e,`v${n}`)}i(G,"majorDir");function j(e){if(!z.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}i(j,"assertId");function A(){const e=U("user").runtime?.issuer;if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e.replace(/\/+$/,"")}i(A,"directoryBase");async function q(e,n){const r=`${A()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await fetch(r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(pe)})}catch(s){return{status:"failed",message:`could not reach ${r}: ${s instanceof Error?s.message:String(s)}`}}if(t.status===304)return n.ifNoneMatch===void 0?{status:"failed",message:`HTTP 304 from ${r} without a conditional request`}:{status:304};if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300)return await t.body?.cancel().catch(()=>{}),{status:"failed",message:`HTTP ${t.status} from ${r}`};let o;try{o=new Uint8Array(await t.arrayBuffer())}catch(s){return{status:"failed",message:`reading ${r} failed: ${s instanceof Error?s.message:String(s)}`}}const a=t.headers.get("etag");return{status:200,raw:o,...a!==null?{etag:a}:{}}}i(q,"fetchProviderBundle");const _=new WeakSet;class S extends Error{static{i(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function V(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(o=>r[o]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}i(V,"wrongBundle");function C(e,n,r){g({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof S?{issues:r.issues}:{}},error:r})}i(C,"logInvalid");function Z(e,n,r){g({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r}})}i(Z,"logFailure");async function L(){return import("../../commands/pkg/plugin-manage.js")}i(L,"pluginManage");const P=new Map,E=i(e=>`${O()}\0${e}`,"installKey");function he(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}i(he,"publicInstallCause");function ge(e){if(m(e)===void 0)return P.get(E(e))??{status:"installing"}}i(ge,"providerInstallState");function we(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${ge(e)?.error??"bundle installation pending"}`)}i(we,"unavailableProviderError");function m(e){const n=W(w(e));if(!(n?.provider===void 0||n.transport?.kind!=="http")&&ae(u(w(e),"commands.json"))){try{if(x(u(w(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:{kind:"http",endpoint:n.transport.endpoint}}}}i(m,"installedManifest");async function k(e,n,r=!1){const t=ce();m(e)===void 0&&P.set(E(e),{status:"installing"});try{return I(u(t,"tmp")),await ie(de(t,e),async()=>{const o=w(e),a=W(o);if(a!==null&&a.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const s=m(e)!==void 0;if(s&&r)return"unchanged";const f=s&&n?oe("user").plugins[e]?.etag:void 0,d=await q(e,f!==void 0?{ifNoneMatch:f}:{});if(d.status===304)return"unchanged";if(d.status===404||d.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${d.message})`,{param:"provider"});if(d.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${d.message}`);const l=ne(d.raw);if(s&&re(e,"user",o,l))return te("user",e,d.etag,{swapped:!1}),"unchanged";const{installPluginArchive:v}=await L();try{await v(e,d.raw,{scope:"user",scopeRootPath:t,root:o,version:l,etag:d.etag,enable:!s,bundle:{endpoint:`${A()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:A()},accept:i(p=>V(e,void 0,p),"accept")})}catch(p){C(e,void 0,p);const N=new c("not_found",`provider "${e}" served an invalid bundle: ${p instanceof Error?p.message:String(p)}`,{param:"provider"});throw _.add(N),N}try{ee(p=>se(p))}catch(p){g({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:p})}return g({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,version:l,replaced:s}}),P.delete(E(e)),s?"updated":"installed"},{timeoutMs:me,timeoutError:i(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(o){throw m(e)===void 0&&P.set(E(e),{status:"install_failed",error:he(o)}),o instanceof c?o:(g({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:o}),we(e))}}i(k,"refreshInstalled");async function We(e){j(e),m(e)===void 0&&await k(e,!1,!0)}i(We,"ensureProviderBundle");async function ze(e,n){return j(e),n.version!==void 0&&m(e)?.provider.major!==n.version?R(e,n.version,n.conditional):k(e,n.conditional)}i(ze,"refreshProviderBundle");async function J(e,n,r){const{validatePluginArchive:t}=await L(),o=await t(r);if(o.bundle===void 0)throw new S(`provider "${e}" major ${n} bundle is invalid: ${o.issues.map(s=>s.message).join("; ")}`,o.issues);const a=V(e,n,o.bundle);if(a!==void 0)throw new S(`provider "${e}" major ${n} bundle is invalid: ${a}`,[]);if(o.bundle.transport===void 0)throw new S(`provider "${e}" major ${n} bundle declares no transport`,[]);return o.bundle}i(J,"validateArchive");function $e(e){I(e),M(u(e,"retired"),`${new Date().toISOString()}
1
+ var X=Object.defineProperty;var i=(e,n)=>X(e,"name",{value:n,configurable:!0});import{existsSync as h,readdirSync as H,readFileSync as y,rmSync as b,statSync as x}from"node:fs";import{join as u}from"node:path";import{validateCommandManifest as Y}from"../../api/command-manifest/manifest.js";import{crtrHome as O}from"../canvas/paths.js";import{withCanvasWrite as ee}from"../canvas/db.js";import{bundleVersion as ne,isInstalledGeneration as re,recordCheckedGeneration as te}from"../command-plugins/bundle.js";import{readConfig as U,readState as oe}from"../config.js";import{emitEvent as g}from"../events/emit.js";import{withExclusiveDirectoryLockAsync as ie}from"../exclusive-lock.js";import{atomicWriteText as M,ensureDir as I,pathExists as ae}from"../fs-utils.js";import{reconcilePackageDocuments as se}from"../graph/package-docs.js";import{readPluginManifest as W}from"../manifest.js";import{bundleSwapLockPath as de}from"../plugin-swap-lock.js";import{pluginsDir as ue,userScopeRoot as ce}from"../scope.js";import{localGrants as fe}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const T="[A-Za-z0-9_-]{1,64}",le=new RegExp(`^(${T}):(${T})$`),z=new RegExp(`^${T}$`),pe=3e4,me=3e4;function ve(e){const n=new Set;for(const r of e)if(r.status==="active")for(const{scope:t}of r.resolved_scopes){const o=le.exec(t);o&&o[1]!=="crtr"&&n.add(o[1])}return[...n].sort()}i(ve,"providerIdsFromGrants");function w(e){return u(ue("user"),e)}i(w,"pluginRoot");function F(){return u(O(),"providers")}i(F,"providersRoot");function G(e,n){return u(F(),e,`v${n}`)}i(G,"majorDir");function j(e){if(!z.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}i(j,"assertId");function A(){const e=U("user").runtime?.issuer;if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e.replace(/\/+$/,"")}i(A,"directoryBase");async function q(e,n){const r=`${A()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await fetch(r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(pe)})}catch(s){return{status:"failed",message:`could not reach ${r}: ${s instanceof Error?s.message:String(s)}`}}if(t.status===304)return n.ifNoneMatch===void 0?{status:"failed",message:`HTTP 304 from ${r} without a conditional request`}:{status:304};if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300)return await t.body?.cancel().catch(()=>{}),{status:"failed",message:`HTTP ${t.status} from ${r}`};let o;try{o=new Uint8Array(await t.arrayBuffer())}catch(s){return{status:"failed",message:`reading ${r} failed: ${s instanceof Error?s.message:String(s)}`}}const a=t.headers.get("etag");return{status:200,raw:o,...a!==null?{etag:a}:{}}}i(q,"fetchProviderBundle");const _=new WeakSet;class S extends Error{static{i(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function V(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(o=>r[o]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}i(V,"wrongBundle");function C(e,n,r){g({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof S?{issues:r.issues}:{}},error:r})}i(C,"logInvalid");function Z(e,n,r){g({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r}})}i(Z,"logFailure");async function L(){return import("../../commands/pkg/plugin-manage.js")}i(L,"pluginManage");const P=new Map,E=i(e=>`${O()}\0${e}`,"installKey");function he(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}i(he,"publicInstallCause");function ge(e){if(m(e)===void 0)return P.get(E(e))??{status:"installing"}}i(ge,"providerInstallState");function we(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${ge(e)?.error??"bundle installation pending"}`)}i(we,"unavailableProviderError");function m(e){const n=W(w(e));if(!(n?.provider===void 0||n.transport?.kind!=="http")&&ae(u(w(e),"commands.json"))){try{if(x(u(w(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:{kind:"http",endpoint:n.transport.endpoint}}}}i(m,"installedManifest");async function k(e,n,r=!1){const t=ce();m(e)===void 0&&P.set(E(e),{status:"installing"});try{return I(u(t,"tmp")),await ie(de(t,e),async()=>{const o=w(e),a=W(o);if(a!==null&&a.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const s=m(e)!==void 0;if(s&&r)return"unchanged";const f=s&&n?oe("user").plugins[e]?.etag:void 0,d=await q(e,f!==void 0?{ifNoneMatch:f}:{});if(d.status===304)return"unchanged";if(d.status===404||d.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${d.message})`,{param:"provider"});if(d.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${d.message}`);const l=ne(d.raw);if(s&&re(e,"user",o,l))return te("user",e,d.etag,{swapped:!1}),"unchanged";const{installPluginArchive:v}=await L();try{await v(e,d.raw,{scope:"user",scopeRootPath:t,root:o,version:l,etag:d.etag,enable:!s,bundle:{endpoint:`${A()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:A()},accept:i(p=>V(e,void 0,p),"accept")})}catch(p){C(e,void 0,p);const N=new c("not_found",`provider "${e}" served an invalid bundle: ${p instanceof Error?p.message:String(p)}`,{param:"provider"});throw _.add(N),N}try{ee(p=>se(p))}catch(p){g({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:p})}return g({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,version:l,replaced:s}}),P.delete(E(e)),s?"updated":"installed"},{timeoutMs:me,timeoutError:i(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(o){throw m(e)===void 0&&P.set(E(e),{status:"install_failed",error:he(o)}),o instanceof c?o:(g({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:o}),we(e))}}i(k,"refreshInstalled");async function We(e){j(e),m(e)===void 0&&await k(e,!1,!0)}i(We,"ensureProviderBundle");async function ze(e,n){return j(e),n.version!==void 0&&m(e)?.provider.major!==n.version?R(e,n.version,n.conditional):k(e,n.conditional)}i(ze,"refreshProviderBundle");async function J(e,n,r){const{validatePluginArchive:t}=await L(),o=await t(r);if(o.bundle===void 0)throw new S(`provider "${e}" major ${n} bundle is invalid: ${o.issues.map(s=>s.message).join("; ")}`,o.issues);const a=V(e,n,o.bundle);if(a!==void 0)throw new S(`provider "${e}" major ${n} bundle is invalid: ${a}`,[]);if(o.bundle.transport===void 0)throw new S(`provider "${e}" major ${n} bundle declares no transport`,[]);return o.bundle}i(J,"validateArchive");function $e(e){I(e),M(u(e,"retired"),`${new Date().toISOString()}
2
2
  `),b(u(e,"bundle.tar"),{force:!0}),b(u(e,"etag"),{force:!0})}i($e,"retire");function K(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}i(K,"retiredError");async function R(e,n,r){const t=G(e,n),o=u(t,"bundle.tar"),a=h(o),s=a&&r&&h(u(t,"etag"))?y(u(t,"etag"),"utf8"):void 0,f=await q(e,{version:n,...s!==void 0?{ifNoneMatch:s}:{}});if(f.status===304)return"unchanged";if(f.status===410)throw $e(t),g({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),K(e,n);if(f.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(f.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${f.message}`);try{await J(e,n,f.raw)}catch(l){C(e,n,l);const v=new c(a?"provider_unavailable":"not_found",l instanceof Error?l.message:String(l),{param:"version"});throw _.add(v),v}I(t),b(u(t,"retired"),{force:!0});const d=a&&Buffer.from(f.raw).equals(y(o));return d||M(o,f.raw),f.etag!==void 0?M(u(t,"etag"),f.etag):b(u(t,"etag"),{force:!0}),d?"unchanged":a?"updated":"installed"}i(R,"refreshPinned");function ye(){const e=[],n=F();if(!h(n))return e;for(const r of H(n)){if(!z.test(r))continue;let t;try{t=H(u(n,r))}catch{continue}for(const o of t){const a=/^v([1-9][0-9]*)$/.exec(o);a&&h(u(n,r,o,"bundle.tar"))&&e.push([r,Number(a[1])])}}return e}i(ye,"cachedMajors");function be(e){const n=Y(e,{transport:"http",reservedCoreNames:new Set});if(n.manifest===void 0)throw new c("not_found","the provider bundle's commands.json no longer validates");return n.manifest}i(be,"readCommands");const B=new Map,Q=new Map;function _e(e,n){const r=x(n),t=`${r.ino}:${r.mtimeMs}:${r.size}`,o=Q.get(e);if(o?.stamp===t)return o.commands;const a=be(JSON.parse(y(n,"utf8")));return Q.set(e,{stamp:t,commands:a}),a}i(_e,"installedCommands");async function Fe(e,n){j(e);const r=m(e);if(n===void 0||r?.provider.major===n){if(r===void 0)throw new c("not_found",`provider "${e}" has no installed bundle`,{param:"provider"});const l=w(e),v=_e(e,u(l,"commands.json"));return{id:e,major:r.provider.major,provider:r.provider,commands:v,transport:r.transport,source:"installed",root:l}}if(!Number.isInteger(n)||n<1)throw new c("invalid_request","version must be an integer of at least 1",{param:"version"});const t=G(e,n);if(h(u(t,"retired")))throw K(e,n);const o=u(t,"bundle.tar");h(o)||await R(e,n,!1);const a=`${e}/${n}`,s=x(o),f=`${s.mtimeMs}:${s.size}`;let d=B.get(a)?.stamp===f?B.get(a).bundle:void 0;if(d===void 0){try{d=await J(e,n,new Uint8Array(y(o)))}catch(l){throw C(e,n,l),new c("not_found",`provider "${e}" has no valid bundle for major ${n}`,{param:"version"})}B.set(a,{stamp:f,bundle:d})}return{id:e,major:n,provider:d.provider,commands:d.commands,transport:{kind:"http",endpoint:d.transport.endpoint},source:"pinned"}}i(Fe,"resolveProviderBundle");let $=null,D=!1;async function Se(){if(U("user").runtime?.issuer){for(const e of ve(fe()))try{await k(e,!0)}catch(n){n instanceof Object&&_.has(n)||Z(e,void 0,n instanceof Error?n.message:String(n))}for(const[e,n]of ye())try{await R(e,n,!0)}catch(r){if(r instanceof c&&r.code==="provider_version_retired"||r instanceof Object&&_.has(r))continue;Z(e,n,r instanceof Error?r.message:String(r))}}}i(Se,"syncOnce");function Ge(){return $!==null?(D=!0,$):($=(async()=>{try{do D=!1,await Se();while(D)}finally{$=null}})(),$)}i(Ge,"syncProviderBundles");export{We as ensureProviderBundle,ve as providerIdsFromGrants,ge as providerInstallState,ze as refreshProviderBundle,Fe as resolveProviderBundle,Ge as syncProviderBundles,we as unavailableProviderError};
@@ -1,2 +1,2 @@
1
- var T=Object.defineProperty;var n=(e,o)=>T(e,"name",{value:o,configurable:!0});import{DatabaseSync as p}from"node:sqlite";import{forgetPrepared as R}from"../sqlite-statements.js";import{chmodSync as m,existsSync as s,mkdirSync as y,readdirSync as O,rmSync as I,statSync as P,writeFileSync as v}from"node:fs";import{join as i}from"node:path";import j from"proper-lockfile";import{CONFIG_FILE as S,PROFILE_DIR as N,STATE_FILE as f}from"../../types.js";import{withExclusiveDirectoryLock as _}from"../exclusive-lock.js";import{layoutNeedsMigration as k}from"../layout.js";import{providerStore as C,projectStore as D,userStore as G}from"./paths.js";import{createScopedSchema as g,ensureProviderModelCooldowns as $,PROVIDER_SCHEMA_VERSION as w,SCOPED_SCHEMA_VERSION as d}from"./schema.js";const l=new Map,B=/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/;function F(e,o,r){e.prepare("UPDATE scope_owners SET root=? WHERE owner_key=?").run(r,o)}n(F,"writeOwnerRoot");function c(e){return Number(e.prepare("PRAGMA user_version").get().user_version)}n(c,"version");function L(e,o){const r=c(e);if(r===0)throw new Error(`scoped SQL store is uninitialized: ${o.path}; crtr was updated and \`crtr sys daemon restart\` converts local state`);const t=o.kind==="provider"?w:d;if(r>t)throw new Error(`scoped SQL store ${o.path} uses unsupported schema version ${r}`);if(r!==t)throw new Error(`scoped SQL store ${o.path} requires an explicit conversion`)}n(L,"assertVersion");function x(e,o){if(o.kind==="provider"){c(e)===w&&(e.exec("PRAGMA busy_timeout = 5000"),$(e));return}if(!(c(e)>=d)){e.exec("PRAGMA busy_timeout = 5000; BEGIN IMMEDIATE");try{c(e)===1&&(e.prepare("PRAGMA table_info(profiles)").all().some(({name:t})=>t==="grantee")||e.exec("ALTER TABLE profiles ADD COLUMN grantee TEXT"),e.exec("PRAGMA user_version = 2")),c(e)===2&&(e.prepare("PRAGMA table_info(profiles)").all().some(({name:t})=>t==="home")&&e.exec("ALTER TABLE profiles DROP COLUMN home"),e.exec("PRAGMA user_version = 3")),c(e)===3&&(e.exec("ALTER TABLE profiles ADD COLUMN delivery_limits_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(delivery_limits_json))"),e.exec("PRAGMA user_version = 4")),c(e)===4&&(e.exec("CREATE TABLE IF NOT EXISTS legacy_project_caps (profile_id TEXT NOT NULL, path TEXT NOT NULL, memory TEXT NOT NULL CHECK(memory IN ('none','name','preview')), PRIMARY KEY(profile_id,path))"),e.prepare("PRAGMA table_info(profile_projects)").all().some(({name:t})=>t==="memory")&&e.exec(`INSERT INTO legacy_project_caps SELECT profile_id, path, memory FROM profile_projects WHERE memory != 'content';
2
- ALTER TABLE profile_projects DROP COLUMN memory;`),e.exec(`PRAGMA user_version = ${d}`)),e.exec("COMMIT")}catch(r){throw e.exec("ROLLBACK"),r}}}n(x,"upgradeScopedStore");function ne(e){const o=new p(e.path);try{x(o,e),L(o,e)}finally{o.close()}}n(ne,"assertScopedStoreReady");function U(e){if(!M(e))return;const o=new p(e.path,{readOnly:!0});let r;try{r=c(o)}finally{o.close()}if(r===0)for(const t of[e.path,`${e.path}-wal`,`${e.path}-shm`])I(t,{force:!0})}n(U,"discardIncompleteScopedStore");function H(e,o){return _(i(e,".init.lock"),()=>{const r=i(e,f),t=Date.now()+5e3;let a=10;for(;;){let E;try{E=j.lockSync(r,{realpath:!1,retries:0,stale:3e4,update:1e4})}catch(u){if(u.code!=="ELOCKED")throw u;const A=t-Date.now();if(A<=0)throw new Error(`timed out waiting for project state conversion lock: ${r}`,{cause:u});Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,Math.min(a,A)),a=Math.min(a*2,250);continue}try{return o()}finally{E()}}})}n(H,"withProjectStateLock");function h(e){s(e)&&m(e,384);for(const o of["-wal","-shm"])s(`${e}${o}`)&&m(`${e}${o}`,384)}n(h,"secure");function M(e){return s(e.path)}n(M,"scopedStoreExists");function K(e){if(e.kind==="project")return[i(e.root,f)];if(e.kind==="provider")return[i(e.root,"anthropic-subscriptions.json"),i(e.root,"openai-codex-subscriptions.json"),i(e.root,"provider-rotation.json")];const o=[i(e.root,S),i(e.root,f),i(e.root,"profile-defaults.json")],r=i(e.root,N);if(s(r))for(const t of O(r,{withFileTypes:!0}))t.isDirectory()&&B.test(t.name)&&o.push(i(r,t.name,"profile.json"),i(r,t.name,S),i(r,t.name,f));return o}n(K,"legacyScopedStorePaths");function X(e){const o=e.kind==="user"?G(e.root):e.kind==="project"?D(e.root):C(e.root);o.kind!=="project"&&y(o.root,{recursive:!0,mode:448});const r=n(()=>{if(o.kind==="project"&&U(o),M(o))return;const t=K(o).find(s);if(t!==void 0)throw new Error(`legacy scoped state exists at ${t}; crtr was updated and \`crtr sys daemon restart\` converts local state`);Q(o,a=>{g(a,o.kind),o.kind!=="provider"&&F(a,o.kind,o.root)})},"initialize");o.kind==="project"?H(o.root,r):_(i(o.root,".init.lock"),r)}n(X,"initializeFreshScopedStore");function z(e){if(k())throw new Error("layout_migration_required");const o=l.get(e.path);if(o!==void 0)return o;X(e);const r=new p(e.path);try{return r.exec("PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;"),x(r,e),L(r,e),h(e.path),l.set(e.path,r),r}catch(t){throw r.close(),t}}n(z,"openScopedStore");function Q(e,o){if(s(e.path))throw new Error(`scoped SQL store already exists: ${e.path}`);if(e.kind!=="project"&&y(e.root,{recursive:!0,mode:448}),!P(e.root).isDirectory())throw new Error(`scoped SQL root is not a directory: ${e.root}`);(e.kind!=="project"||process.platform!=="linux")&&m(e.root,448),v(e.path,"",{mode:384});const r=new p(e.path);try{r.exec("PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; BEGIN IMMEDIATE;"),o(r),r.exec("COMMIT")}catch(t){try{r.exec("ROLLBACK")}catch{}throw t}finally{h(e.path),r.close()}}n(Q,"createScopedStore");function ie(e,o){const r=z(e);if(r.isTransaction)return o(r);r.exec("BEGIN IMMEDIATE");try{const t=o(r);return r.exec("COMMIT"),h(e.path),t}catch(t){try{r.exec("ROLLBACK")}catch{}throw t}}n(ie,"withScopedWrite");function ce(){for(const e of l.values())R(e),e.close();l.clear()}n(ce,"closeScopedStores");export{B as PROFILE_ID_SHAPE,ne as assertScopedStoreReady,ce as closeScopedStores,Q as createScopedStore,U as discardIncompleteScopedStore,X as initializeFreshScopedStore,K as legacyScopedStorePaths,z as openScopedStore,M as scopedStoreExists,H as withProjectStateLock,ie as withScopedWrite,F as writeOwnerRoot};
1
+ var T=Object.defineProperty;var n=(e,o)=>T(e,"name",{value:o,configurable:!0});import{DatabaseSync as p}from"node:sqlite";import{forgetPrepared as R}from"../sqlite-statements.js";import{chmodSync as m,existsSync as s,mkdirSync as y,readdirSync as O,rmSync as I,statSync as P,writeFileSync as v}from"node:fs";import{join as i}from"node:path";import j from"proper-lockfile";import{CONFIG_FILE as S,PROFILE_DIR as N,STATE_FILE as f}from"../../types.js";import{withExclusiveDirectoryLock as _}from"../exclusive-lock.js";import{syncDirectory as k,syncFile as D}from"../fs-utils.js";import{layoutNeedsMigration as C}from"../layout.js";import{providerStore as G,projectStore as g,userStore as $}from"./paths.js";import{createScopedSchema as B,ensureProviderModelCooldowns as F,PROVIDER_SCHEMA_VERSION as w,SCOPED_SCHEMA_VERSION as d}from"./schema.js";const l=new Map,U=/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/;function H(e,o,r){e.prepare("UPDATE scope_owners SET root=? WHERE owner_key=?").run(r,o)}n(H,"writeOwnerRoot");function c(e){return Number(e.prepare("PRAGMA user_version").get().user_version)}n(c,"version");function L(e,o){const r=c(e);if(r===0)throw new Error(`scoped SQL store is uninitialized: ${o.path}; crtr was updated and \`crtr sys daemon restart\` converts local state`);const t=o.kind==="provider"?w:d;if(r>t)throw new Error(`scoped SQL store ${o.path} uses unsupported schema version ${r}`);if(r!==t)throw new Error(`scoped SQL store ${o.path} requires an explicit conversion`)}n(L,"assertVersion");function x(e,o){if(o.kind==="provider"){c(e)===w&&(e.exec("PRAGMA busy_timeout = 5000"),F(e));return}if(!(c(e)>=d)){e.exec("PRAGMA busy_timeout = 5000; BEGIN IMMEDIATE");try{c(e)===1&&(e.prepare("PRAGMA table_info(profiles)").all().some(({name:t})=>t==="grantee")||e.exec("ALTER TABLE profiles ADD COLUMN grantee TEXT"),e.exec("PRAGMA user_version = 2")),c(e)===2&&(e.prepare("PRAGMA table_info(profiles)").all().some(({name:t})=>t==="home")&&e.exec("ALTER TABLE profiles DROP COLUMN home"),e.exec("PRAGMA user_version = 3")),c(e)===3&&(e.exec("ALTER TABLE profiles ADD COLUMN delivery_limits_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(delivery_limits_json))"),e.exec("PRAGMA user_version = 4")),c(e)===4&&(e.exec("CREATE TABLE IF NOT EXISTS legacy_project_caps (profile_id TEXT NOT NULL, path TEXT NOT NULL, memory TEXT NOT NULL CHECK(memory IN ('none','name','preview')), PRIMARY KEY(profile_id,path))"),e.prepare("PRAGMA table_info(profile_projects)").all().some(({name:t})=>t==="memory")&&e.exec(`INSERT INTO legacy_project_caps SELECT profile_id, path, memory FROM profile_projects WHERE memory != 'content';
2
+ ALTER TABLE profile_projects DROP COLUMN memory;`),e.exec(`PRAGMA user_version = ${d}`)),e.exec("COMMIT")}catch(r){throw e.exec("ROLLBACK"),r}}}n(x,"upgradeScopedStore");function ae(e){const o=new p(e.path);try{x(o,e),L(o,e)}finally{o.close()}}n(ae,"assertScopedStoreReady");function K(e){if(!M(e))return;const o=new p(e.path,{readOnly:!0});let r;try{r=c(o)}finally{o.close()}if(r===0)for(const t of[e.path,`${e.path}-wal`,`${e.path}-shm`])I(t,{force:!0})}n(K,"discardIncompleteScopedStore");function X(e,o){return _(i(e,".init.lock"),()=>{const r=i(e,f),t=Date.now()+5e3;let a=10;for(;;){let E;try{E=j.lockSync(r,{realpath:!1,retries:0,stale:3e4,update:1e4})}catch(u){if(u.code!=="ELOCKED")throw u;const A=t-Date.now();if(A<=0)throw new Error(`timed out waiting for project state conversion lock: ${r}`,{cause:u});Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,Math.min(a,A)),a=Math.min(a*2,250);continue}try{return o()}finally{E()}}})}n(X,"withProjectStateLock");function h(e){s(e)&&m(e,384);for(const o of["-wal","-shm"])s(`${e}${o}`)&&m(`${e}${o}`,384)}n(h,"secure");function M(e){return s(e.path)}n(M,"scopedStoreExists");function z(e){if(e.kind==="project")return[i(e.root,f)];if(e.kind==="provider")return[i(e.root,"anthropic-subscriptions.json"),i(e.root,"openai-codex-subscriptions.json"),i(e.root,"provider-rotation.json")];const o=[i(e.root,S),i(e.root,f),i(e.root,"profile-defaults.json")],r=i(e.root,N);if(s(r))for(const t of O(r,{withFileTypes:!0}))t.isDirectory()&&U.test(t.name)&&o.push(i(r,t.name,"profile.json"),i(r,t.name,S),i(r,t.name,f));return o}n(z,"legacyScopedStorePaths");function Q(e){const o=e.kind==="user"?$(e.root):e.kind==="project"?g(e.root):G(e.root);o.kind!=="project"&&y(o.root,{recursive:!0,mode:448});const r=n(()=>{if(o.kind==="project"&&K(o),M(o))return;const t=z(o).find(s);if(t!==void 0)throw new Error(`legacy scoped state exists at ${t}; crtr was updated and \`crtr sys daemon restart\` converts local state`);V(o,a=>{B(a,o.kind),o.kind!=="provider"&&H(a,o.kind,o.root)})},"initialize");o.kind==="project"?X(o.root,r):_(i(o.root,".init.lock"),r)}n(Q,"initializeFreshScopedStore");function W(e){if(C())throw new Error("layout_migration_required");const o=l.get(e.path);if(o!==void 0)return o;Q(e);const r=new p(e.path);try{return r.exec("PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;"),x(r,e),L(r,e),h(e.path),l.set(e.path,r),r}catch(t){throw r.close(),t}}n(W,"openScopedStore");function V(e,o){if(s(e.path))throw new Error(`scoped SQL store already exists: ${e.path}`);if(e.kind!=="project"&&y(e.root,{recursive:!0,mode:448}),!P(e.root).isDirectory())throw new Error(`scoped SQL root is not a directory: ${e.root}`);(e.kind!=="project"||process.platform!=="linux")&&m(e.root,448),v(e.path,"",{mode:384});const r=new p(e.path);try{r.exec("PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; BEGIN IMMEDIATE;"),o(r),r.exec("COMMIT")}catch(t){try{r.exec("ROLLBACK")}catch{}throw t}finally{h(e.path),r.close()}D(e.path),k(e.root)}n(V,"createScopedStore");function se(e,o){const r=W(e);if(r.isTransaction)return o(r);r.exec("BEGIN IMMEDIATE");try{const t=o(r);return r.exec("COMMIT"),h(e.path),t}catch(t){try{r.exec("ROLLBACK")}catch{}throw t}}n(se,"withScopedWrite");function pe(){for(const e of l.values())R(e),e.close();l.clear()}n(pe,"closeScopedStores");export{U as PROFILE_ID_SHAPE,ae as assertScopedStoreReady,pe as closeScopedStores,V as createScopedStore,K as discardIncompleteScopedStore,Q as initializeFreshScopedStore,z as legacyScopedStorePaths,W as openScopedStore,M as scopedStoreExists,X as withProjectStateLock,se as withScopedWrite,H as writeOwnerRoot};
@@ -21,10 +21,9 @@ export interface ScopedApiToken {
21
21
  created: string;
22
22
  }
23
23
  export declare function readSecrets(scope: Scope): SecretsStore;
24
- /** Writes secrets.json directly (never via `fs-utils.writeJson`, which has no
25
- * mode control) so the file is created 0600 from the first byte, then
26
- * `chmodSync`s belt-and-suspenders in case the file pre-existed with looser
27
- * permissions (e.g. hand-copied from another machine). */
24
+ /** Replaces secrets.json whole (temp file, fsync, rename, directory fsync) at mode 0600 -- forced even
25
+ * over a pre-existing looser file, e.g. one hand-copied from another machine -- so a killed VM or a
26
+ * volume handoff never leaves the relay tokens empty or partial. */
28
27
  export declare function writeSecrets(scope: Scope, store: SecretsStore): void;
29
28
  export declare function getRelayToken(ref: string, scope: Scope): string | undefined;
30
29
  export declare function setRelayToken(ref: string, token: string, scope: Scope): void;
@@ -1,2 +1 @@
1
- var f=Object.defineProperty;var t=(e,o)=>f(e,"name",{value:o,configurable:!0});import{createHash as u}from"node:crypto";import{existsSync as y,readFileSync as d,writeFileSync as l,chmodSync as A}from"node:fs";import{join as m}from"node:path";import{scopeRoot as S,requireScopeRoot as g,userScopeRoot as a}from"./scope.js";import{secretsDir as k}from"./layout.js";import{ensureDir as x}from"./fs-utils.js";const h="secrets.json";function T(e){return m(e===a()?k():e,h)}t(T,"secretsPathFor");function s(e){const o=S(e);if(!o)return{relayTokens:{}};const n=T(o);if(!y(n))return{relayTokens:{}};try{const r=JSON.parse(d(n,"utf8")),c=Array.isArray(r.scopedApiTokens)?r.scopedApiTokens.filter(i=>typeof i?.token=="string"&&i.token!==""&&typeof i.grantee=="string"&&/^app:[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(i.grantee)&&Array.isArray(i.scopes)&&typeof i.created=="string"):[];return{relayTokens:r.relayTokens??{},...typeof r.apiToken=="string"&&r.apiToken!==""?{apiToken:r.apiToken}:{},...c.length>0?{scopedApiTokens:c}:{}}}catch{return{relayTokens:{}}}}t(s,"readSecrets");function p(e,o){const n=g(e),r=T(n);x(n===a()?k():n),l(r,JSON.stringify(o,null,2)+`
2
- `,{mode:384}),A(r,384)}t(p,"writeSecrets");function J(e,o){return s(o).relayTokens[e]}t(J,"getRelayToken");function N(e,o,n){const r=s(n);r.relayTokens[e]=o,p(n,r)}t(N,"setRelayToken");function O(e,o){const n=s(o);delete n.relayTokens[e],p(o,n)}t(O,"deleteRelayToken");function Z(e){return s(e).apiToken}t(Z,"getApiToken");function _(e,o){const n=s(o);n.apiToken=e,p(o,n)}t(_,"setApiToken");function q(e){return s(e).scopedApiTokens??[]}t(q,"listScopedApiTokens");function C(e,o){const n=s(o);n.scopedApiTokens=[...n.scopedApiTokens??[],e],p(o,n)}t(C,"addScopedApiToken");function H(e){return u("sha256").update(e).digest("hex").slice(0,12)}t(H,"fingerprint");export{C as addScopedApiToken,O as deleteRelayToken,H as fingerprint,Z as getApiToken,J as getRelayToken,q as listScopedApiTokens,s as readSecrets,_ as setApiToken,N as setRelayToken,p as writeSecrets};
1
+ var u=Object.defineProperty;var t=(e,o)=>u(e,"name",{value:o,configurable:!0});import{createHash as f}from"node:crypto";import{existsSync as y,readFileSync as d}from"node:fs";import{join as l}from"node:path";import{scopeRoot as A,requireScopeRoot as m,userScopeRoot as a}from"./scope.js";import{secretsDir as k}from"./layout.js";import{atomicWriteJson as x,ensureDir as g}from"./fs-utils.js";const S="secrets.json";function T(e){return l(e===a()?k():e,S)}t(T,"secretsPathFor");function s(e){const o=A(e);if(!o)return{relayTokens:{}};const r=T(o);if(!y(r))return{relayTokens:{}};try{const n=JSON.parse(d(r,"utf8")),c=Array.isArray(n.scopedApiTokens)?n.scopedApiTokens.filter(i=>typeof i?.token=="string"&&i.token!==""&&typeof i.grantee=="string"&&/^app:[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(i.grantee)&&Array.isArray(i.scopes)&&typeof i.created=="string"):[];return{relayTokens:n.relayTokens??{},...typeof n.apiToken=="string"&&n.apiToken!==""?{apiToken:n.apiToken}:{},...c.length>0?{scopedApiTokens:c}:{}}}catch{return{relayTokens:{}}}}t(s,"readSecrets");function p(e,o){const r=m(e),n=T(r);g(r===a()?k():r),x(n,o,{mode:384,durable:!0})}t(p,"writeSecrets");function J(e,o){return s(o).relayTokens[e]}t(J,"getRelayToken");function Z(e,o,r){const n=s(r);n.relayTokens[e]=o,p(r,n)}t(Z,"setRelayToken");function _(e,o){const r=s(o);delete r.relayTokens[e],p(o,r)}t(_,"deleteRelayToken");function b(e){return s(e).apiToken}t(b,"getApiToken");function q(e,o){const r=s(o);r.apiToken=e,p(o,r)}t(q,"setApiToken");function w(e){return s(e).scopedApiTokens??[]}t(w,"listScopedApiTokens");function C(e,o){const r=s(o);r.scopedApiTokens=[...r.scopedApiTokens??[],e],p(o,r)}t(C,"addScopedApiToken");function H(e){return f("sha256").update(e).digest("hex").slice(0,12)}t(H,"fingerprint");export{C as addScopedApiToken,_ as deleteRelayToken,H as fingerprint,b as getApiToken,J as getRelayToken,w as listScopedApiTokens,s as readSecrets,q as setApiToken,Z as setRelayToken,p as writeSecrets};
@@ -1 +1 @@
1
- var E=Object.defineProperty;var s=(e,r)=>E(e,"name",{value:r,configurable:!0});import{randomUUID as P}from"node:crypto";import{readConfig as I}from"../../../core/config.js";import{activeGrantForApp as R,allGrants as _}from"../../../core/grants/mirror.js";import{getNode as O}from"../../../core/canvas/canvas.js";import{scopeAllowed as $}from"../../../core/scopes.js";import{ProviderError as u,providerErrorBody as q,scopeMissing as x}from"../../../core/providers/errors.js";import{callProvider as G,providerTools as N}from"../../../core/providers/call.js";import{providerIdsFromGrants as T,providerInstallState as F}from"../../../core/providers/bundles.js";import{listInstalledPlugins as j}from"../../../core/resolver.js";import{listAudit as C,writeProviderAudit as D}from"../../../core/providers/audit.js";import{emitEvent as g}from"../../../core/events/emit.js";import{resolvedScopes as h}from"../router.js";const fe=new Set(["provider.tools","provider.call","audit.list"]);function M(e){if(e.class==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null};if(e.class==="app"){const r=R(e.app);if(!r)throw new u("grant_revoked",`app ${e.app} has no active grant`);return{class:"app",sub:r.sub,grantee:e.app,client_id:null,resolved:r.resolved,nodeScopes:e.scopes,run_id:null,node_id:null}}return e.class==="cron"?{class:"app",sub:e.sub,grantee:e.app,client_id:null,resolved:h(e),nodeScopes:e.scopes,run_id:e.origin_run,node_id:null}:{class:"broker",sub:e.sub,grantee:e.app,client_id:e.client_id,resolved:h(e),nodeScopes:e.scopes,run_id:e.run??O(e.node)?.run_id??null,node_id:e.node}}s(M,"callerFromRequest");function ce(e){return{class:"app",sub:e.sub,grantee:e.grantee,client_id:e.client_id,resolved:e.resolved,nodeScopes:null,run_id:null,node_id:null}}s(ce,"callerFromApp");const U=/^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64})$/;function k(e){const r=U.exec(e);return r!==null&&r[1]!=="crtr"}s(k,"isProviderScope");const w=new WeakMap;function z(e){let r=w.get(e);return r===void 0&&(r=new Set(e),w.set(e,r)),r}s(z,"heldSet");function y(e,r){return e===null?!0:k(r)?z(e).has(r):$(e,r)}s(y,"holds");function S(e,r){return e.class==="person"?!0:y(e.resolved??[],r)&&y(e.nodeScopes,r)}s(S,"callerAllows");function pe(e,r,t){const n=`${r}:${t}`;if(!S(e,n))throw x(n)}s(pe,"requireGroup");function L(){return!!I("user").runtime?.issuer}s(L,"providerOperationsEnabled");const V=/^[A-Za-z0-9_-]{1,64}$/;function p(e,r){return new u("invalid_request",e,{param:r})}s(p,"invalid");function A(e){if(typeof e!="string"||!V.test(e)||e==="crtr")throw p("provider must be a provider id","provider");return e}s(A,"parseProvider");function Z(e){if(!e||typeof e!="object"||Array.isArray(e))throw new u("invalid_request","a JSON object is required");const r=e,t=A(r.provider),n=r.tool;if(typeof n!="string"||!n.trim())throw p("tool must be a non-empty string","tool");const i=r.arguments??{};if(!i||typeof i!="object"||Array.isArray(i))throw p("arguments must be an object","arguments");const a=r.version;if(a!==void 0&&(typeof a!="number"||!Number.isInteger(a)||a<1))throw p("version must be an integer >= 1","version");const o=r.approval;if(o!==void 0&&(typeof o!="string"||!o))throw p("approval must be a question id","approval");return{provider:t,tool:n.trim(),arguments:i,...a===void 0?{}:{version:a},...o===void 0?{}:{approval:o}}}s(Z,"parseCallInput");function B(e,r=new URLSearchParams){const t=s((o,f,c)=>{const l=r.get(o);if(l===null)return f;if(!/^\d+$/.test(l)||Number(l)>c||Number(l)<1)throw p(`invalid ${o}`,o);return Number(l)},"numeric"),n=r.has("after")?t("after",0,Number.MAX_SAFE_INTEGER):void 0,i=t("limit",100,1e3),a=Object.fromEntries(["operation","outcome","run_id","since","until"].filter(o=>r.has(o)).map(o=>[o,r.get(o)]));return{status:200,body:C(e.class==="person"?null:e.grantee,{after:n,limit:i,...a})}}s(B,"auditList");async function H(e,r,t){const n=performance.now(),i=new Set(j("user").filter(d=>d.enabled&&d.manifest.provider?.id===d.name).map(d=>d.name)),a=T(_()),o=[...new Set([...i,...a])].sort();if(r!==void 0&&!o.includes(r))throw new u("not_found",`provider "${r}" is not available`);const f=r===void 0?o:[r],c=e.class==="person"?_().flatMap(d=>d.status==="active"?d.resolved_scopes.map(({scope:v})=>v):[]):e.resolved??[],l=await Promise.all(f.map(async d=>{if(!c.some(m=>m.startsWith(`${d}:`)&&S(e,m)))return null;if(i.has(d)){const m=await N(e,d);return m.groups.length>0?m:null}const b=F(d);return{provider:d,display_name:d,status:b.status,...b.error?{error:b.error}:{},groups:[],commands:[]}})).then(d=>d.filter(v=>v!==null));if(g({level:"debug",event:"api.provider.tools",duration_ms:performance.now()-n,fields:{caller:e.class,provider:r??null,providers:l.length}}),r!==void 0){if(l.length===0)throw new u("not_found",`provider "${r}" is not available`);return{status:200,body:l[0]}}return{status:200,body:{providers:l}}}s(H,"providerTools");async function W(e,r,t){return G(e,r,t)}s(W,"providerCall");async function J(e,r,t,n){if(!L())throw new u("not_found","provider operations need a directory-connected runtime");if(e==="audit.list")return B(r,t.query);if(e==="provider.tools")return H(r,t.params.provider===void 0?void 0:A(t.params.provider),n);if(e==="provider.call"){let i;try{i=Z(t.body)}catch(a){const o=t.body&&typeof t.body=="object"&&!Array.isArray(t.body)?t.body:{};throw D({request_id:n.requestId,caller:r,provider:typeof o.provider=="string"?o.provider:"",tool:typeof o.tool=="string"?o.tool:"",outcome:`refused:${a instanceof u?a.code:"invalid_request"}`}),a}return W(r,i,n)}throw new u("not_found","unknown operation")}s(J,"handleProviderOperation");function X(e){const r=new AbortController,t=s(()=>{e.writableFinished||r.abort()},"abort");return e.on("close",t),{signal:r.signal,release:s(()=>e.off("close",t),"release")}}s(X,"abortOnClose");function K(e,r,t,n){const i=q(e,r,t,n??void 0);return i.status>=500&&!(e instanceof u)&&g({event:"api.provider.failed",level:"error",error:e instanceof Error?e:new Error(String(e)),fields:{request_id:r}}),i}s(K,"providerFailure");const me=[{method:"GET",pattern:"/v1/audit",name:"audit.list"},{method:"GET",pattern:"/v1/providers/tools",name:"provider.tools"},{method:"GET",pattern:"/v1/providers/:provider/tools",name:"provider.tools"},{method:"POST",pattern:"/v1/provider/call",name:"provider.call"}].map(({method:e,pattern:r,name:t})=>({method:e,pattern:r,handler:s(async n=>{const i=P(),{signal:a,release:o}=X(n.res);let f=null;try{return f=M(n.principal),{...await J(t,f,{params:n.params,body:n.body,query:new URL(n.req.url??"/","http://runtime.local").searchParams},{requestId:i,signal:a}),headers:{"Request-Id":i}}}catch(c){return K(c,i,!1,f?.run_id)}finally{o()}},"handler")}));export{fe as PROVIDER_OPERATIONS,X as abortOnClose,S as callerAllows,ce as callerFromApp,M as callerFromRequest,J as handleProviderOperation,Z as parseCallInput,A as parseProvider,K as providerFailure,L as providerOperationsEnabled,me as providerRoutes,pe as requireGroup};
1
+ var E=Object.defineProperty;var s=(e,r)=>E(e,"name",{value:r,configurable:!0});import{randomUUID as P}from"node:crypto";import{readConfig as I}from"../../../core/config.js";import{activeGrantForApp as R,localGrants as _}from"../../../core/grants/mirror.js";import{getNode as O}from"../../../core/canvas/canvas.js";import{scopeAllowed as $}from"../../../core/scopes.js";import{ProviderError as u,providerErrorBody as q,scopeMissing as x}from"../../../core/providers/errors.js";import{callProvider as G,providerTools as N}from"../../../core/providers/call.js";import{providerIdsFromGrants as T,providerInstallState as F}from"../../../core/providers/bundles.js";import{listInstalledPlugins as j}from"../../../core/resolver.js";import{listAudit as C,writeProviderAudit as D}from"../../../core/providers/audit.js";import{emitEvent as g}from"../../../core/events/emit.js";import{resolvedScopes as h}from"../router.js";const fe=new Set(["provider.tools","provider.call","audit.list"]);function M(e){if(e.class==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null};if(e.class==="app"){const r=R(e.app);if(!r)throw new u("grant_revoked",`app ${e.app} has no active grant`);return{class:"app",sub:r.sub,grantee:e.app,client_id:null,resolved:r.resolved,nodeScopes:e.scopes,run_id:null,node_id:null}}return e.class==="cron"?{class:"app",sub:e.sub,grantee:e.app,client_id:null,resolved:h(e),nodeScopes:e.scopes,run_id:e.origin_run,node_id:null}:{class:"broker",sub:e.sub,grantee:e.app,client_id:e.client_id,resolved:h(e),nodeScopes:e.scopes,run_id:e.run??O(e.node)?.run_id??null,node_id:e.node}}s(M,"callerFromRequest");function ce(e){return{class:"app",sub:e.sub,grantee:e.grantee,client_id:e.client_id,resolved:e.resolved,nodeScopes:null,run_id:null,node_id:null}}s(ce,"callerFromApp");const U=/^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64})$/;function k(e){const r=U.exec(e);return r!==null&&r[1]!=="crtr"}s(k,"isProviderScope");const w=new WeakMap;function z(e){let r=w.get(e);return r===void 0&&(r=new Set(e),w.set(e,r)),r}s(z,"heldSet");function y(e,r){return e===null?!0:k(r)?z(e).has(r):$(e,r)}s(y,"holds");function S(e,r){return e.class==="person"?!0:y(e.resolved??[],r)&&y(e.nodeScopes,r)}s(S,"callerAllows");function pe(e,r,t){const n=`${r}:${t}`;if(!S(e,n))throw x(n)}s(pe,"requireGroup");function L(){return!!I("user").runtime?.issuer}s(L,"providerOperationsEnabled");const V=/^[A-Za-z0-9_-]{1,64}$/;function p(e,r){return new u("invalid_request",e,{param:r})}s(p,"invalid");function A(e){if(typeof e!="string"||!V.test(e)||e==="crtr")throw p("provider must be a provider id","provider");return e}s(A,"parseProvider");function Z(e){if(!e||typeof e!="object"||Array.isArray(e))throw new u("invalid_request","a JSON object is required");const r=e,t=A(r.provider),n=r.tool;if(typeof n!="string"||!n.trim())throw p("tool must be a non-empty string","tool");const i=r.arguments??{};if(!i||typeof i!="object"||Array.isArray(i))throw p("arguments must be an object","arguments");const a=r.version;if(a!==void 0&&(typeof a!="number"||!Number.isInteger(a)||a<1))throw p("version must be an integer >= 1","version");const o=r.approval;if(o!==void 0&&(typeof o!="string"||!o))throw p("approval must be a question id","approval");return{provider:t,tool:n.trim(),arguments:i,...a===void 0?{}:{version:a},...o===void 0?{}:{approval:o}}}s(Z,"parseCallInput");function B(e,r=new URLSearchParams){const t=s((o,f,c)=>{const l=r.get(o);if(l===null)return f;if(!/^\d+$/.test(l)||Number(l)>c||Number(l)<1)throw p(`invalid ${o}`,o);return Number(l)},"numeric"),n=r.has("after")?t("after",0,Number.MAX_SAFE_INTEGER):void 0,i=t("limit",100,1e3),a=Object.fromEntries(["operation","outcome","run_id","since","until"].filter(o=>r.has(o)).map(o=>[o,r.get(o)]));return{status:200,body:C(e.class==="person"?null:e.grantee,{after:n,limit:i,...a})}}s(B,"auditList");async function H(e,r,t){const n=performance.now(),i=new Set(j("user").filter(d=>d.enabled&&d.manifest.provider?.id===d.name).map(d=>d.name)),a=T(_()),o=[...new Set([...i,...a])].sort();if(r!==void 0&&!o.includes(r))throw new u("not_found",`provider "${r}" is not available`);const f=r===void 0?o:[r],c=e.class==="person"?_().flatMap(d=>d.status==="active"?d.resolved_scopes.map(({scope:v})=>v):[]):e.resolved??[],l=await Promise.all(f.map(async d=>{if(!c.some(m=>m.startsWith(`${d}:`)&&S(e,m)))return null;if(i.has(d)){const m=await N(e,d);return m.groups.length>0?m:null}const b=F(d);return{provider:d,display_name:d,status:b.status,...b.error?{error:b.error}:{},groups:[],commands:[]}})).then(d=>d.filter(v=>v!==null));if(g({level:"debug",event:"api.provider.tools",duration_ms:performance.now()-n,fields:{caller:e.class,provider:r??null,providers:l.length}}),r!==void 0){if(l.length===0)throw new u("not_found",`provider "${r}" is not available`);return{status:200,body:l[0]}}return{status:200,body:{providers:l}}}s(H,"providerTools");async function W(e,r,t){return G(e,r,t)}s(W,"providerCall");async function J(e,r,t,n){if(!L())throw new u("not_found","provider operations need a directory-connected runtime");if(e==="audit.list")return B(r,t.query);if(e==="provider.tools")return H(r,t.params.provider===void 0?void 0:A(t.params.provider),n);if(e==="provider.call"){let i;try{i=Z(t.body)}catch(a){const o=t.body&&typeof t.body=="object"&&!Array.isArray(t.body)?t.body:{};throw D({request_id:n.requestId,caller:r,provider:typeof o.provider=="string"?o.provider:"",tool:typeof o.tool=="string"?o.tool:"",outcome:`refused:${a instanceof u?a.code:"invalid_request"}`}),a}return W(r,i,n)}throw new u("not_found","unknown operation")}s(J,"handleProviderOperation");function X(e){const r=new AbortController,t=s(()=>{e.writableFinished||r.abort()},"abort");return e.on("close",t),{signal:r.signal,release:s(()=>e.off("close",t),"release")}}s(X,"abortOnClose");function K(e,r,t,n){const i=q(e,r,t,n??void 0);return i.status>=500&&!(e instanceof u)&&g({event:"api.provider.failed",level:"error",error:e instanceof Error?e:new Error(String(e)),fields:{request_id:r}}),i}s(K,"providerFailure");const me=[{method:"GET",pattern:"/v1/audit",name:"audit.list"},{method:"GET",pattern:"/v1/providers/tools",name:"provider.tools"},{method:"GET",pattern:"/v1/providers/:provider/tools",name:"provider.tools"},{method:"POST",pattern:"/v1/provider/call",name:"provider.call"}].map(({method:e,pattern:r,name:t})=>({method:e,pattern:r,handler:s(async n=>{const i=P(),{signal:a,release:o}=X(n.res);let f=null;try{return f=M(n.principal),{...await J(t,f,{params:n.params,body:n.body,query:new URL(n.req.url??"/","http://runtime.local").searchParams},{requestId:i,signal:a}),headers:{"Request-Id":i}}}catch(c){return K(c,i,!1,f?.run_id)}finally{o()}},"handler")}));export{fe as PROVIDER_OPERATIONS,X as abortOnClose,S as callerAllows,ce as callerFromApp,M as callerFromRequest,J as handleProviderOperation,Z as parseCallInput,A as parseProvider,K as providerFailure,L as providerOperationsEnabled,me as providerRoutes,pe as requireGroup};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.441",
3
+ "version": "0.3.443",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -137,7 +137,7 @@
137
137
  },
138
138
  "license": "UNLICENSED",
139
139
  "dependencies": {
140
- "@north-light/identity": "0.3.441",
140
+ "@north-light/identity": "0.3.443",
141
141
  "@earendil-works/pi-agent-core": "1.1.0",
142
142
  "@earendil-works/pi-ai": "1.1.0",
143
143
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.441",
3
+ "version": "0.3.443",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",