@north-light/cli 0.3.420 → 0.3.422
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/api/error-codes.d.ts +10 -0
- package/dist/api/error-codes.js +1 -1
- package/dist/clients/attach/viewer.js +480 -480
- package/dist/commands/sys/join.js +1 -1
- package/dist/core/directory/join.d.ts +4 -2
- package/dist/core/directory/join.js +1 -1
- package/dist/core/grants/sync.d.ts +6 -0
- package/dist/core/grants/sync.js +1 -1
- package/dist/core/providers/errors.js +1 -1
- package/dist/core/providers/mint.js +1 -1
- package/dist/core/runtime/host.js +3 -3
- package/dist/core/runtime/nodes.js +1 -1
- package/dist/core/runtime/revive.js +2 -2
- package/dist/core/runtime/run-command.js +1 -1
- package/dist/core/runtime/unsandboxed.d.ts +3 -0
- package/dist/core/runtime/unsandboxed.js +1 -0
- package/dist/core/spaces/process.js +1 -1
- package/dist/daemon/api/app-listener.js +13 -13
- package/dist/daemon/api/vendor-signin.d.ts +3 -0
- package/dist/daemon/api/vendor-signin.js +162 -47
- package/dist/daemon/cron/authority.d.ts +1 -1
- package/dist/daemon/cron/authority.js +1 -1
- package/dist/daemon/crtrd.js +7 -7
- package/dist/types.d.ts +3 -1
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
var r=Object.defineProperty;var t=(e,n)=>r(e,"name",{value:n,configurable:!0});import{defineLeaf as i}from"../../core/command.js";import{joinDirectory as s}from"../../core/directory/join.js";const u=i({name:"join",description:"register this runtime with its directory using an enrollment token, offline",whenToUse:"at a hosted runtime's first boot, before the daemon starts, with the enrollment token provisioning passed in
|
|
1
|
+
var r=Object.defineProperty;var t=(e,n)=>r(e,"name",{value:n,configurable:!0});import{defineLeaf as i}from"../../core/command.js";import{joinDirectory as s}from"../../core/directory/join.js";const u=i({name:"join",description:"register this runtime with its directory using an enrollment token, offline",whenToUse:"at a hosted runtime's first boot, before the daemon starts, with the enrollment token provisioning passed in, or on your own computer, with a token from `<issuer>/account/machines`. Not for a runtime that is already running: the daemon must be stopped.",help:{name:"sys join",summary:"exchange a directory enrollment token for a new runtime client key and record the returned identity",guide:"Generates a new ES256 client key, sends only its public key to `PUT <directory>/v1/runtimes/registration` with the token as bearer, and learns the runtime id, client id, issuer, owner and URL from the answer. Only after that answer does it write the key file, set the user settings `runtime.issuer` and `runtime.audience`, and rewrite `user:local` profile grantees to `app:terminal`. canvas.db belongs to the daemon: its next start rewrites `user:local` there, before it serves anything. Presenting the token of the stored successful registration again finishes those local writes without calling the directory. Any other token generates a new key and registers again.",params:[{kind:"flag",name:"directory",type:"string",required:!0,constraint:"Directory base URL (http or https) that minted the token."},{kind:"flag",name:"token",type:"string",required:!0,sensitive:!0,constraint:"Single-use enrollment token. Sensitive: never echoed in output or errors."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Label shown for this machine on the account page. Default: this computer's hostname."}],output:[{name:"runtime",type:"string",required:!0,constraint:"Runtime principal from the directory, `runtime:<id>`."},{name:"client_id",type:"string",required:!0,constraint:"OAuth client id registered for the new key."},{name:"issuer",type:"string",required:!0,constraint:"Canonical issuer, written to `runtime.issuer`."},{name:"owner",type:"string",required:!0,constraint:"Owner principal, `user:<id>`, that replaces `user:local` subjects."},{name:"canvas_rewrite",type:"string",required:!0,constraint:"Always `next daemon start`: when canvas.db identity fields are rewritten."},{name:"url",type:"string",required:!0,constraint:"Runtime URL, written to `runtime.audience`."},{name:"registered",type:"boolean",required:!0,constraint:"False when the token matched the stored registration and the directory was not called."}],outputKind:"object",effects:["Refuses while a daemon answers on the API socket.","On a new token: calls the directory once; on success writes `<runtime>/secrets/runtime-client.json` (mode 0600) with the private key, registration and the token's SHA-256, replacing any earlier key file. A refused or failed call writes nothing.","Sends name, platform, crtr version, installed plugin ids and sandbox state with the public key. Before the request it writes `runtime-client.pending.json` (mode 0600); a rerun with the same token and directory reuses that key, so a lost reply is retried safely. The file is deleted on success or a refusal.","Sets `runtime.issuer` and `runtime.audience` in user settings. When the directory says this machine is not the account's default (`is_default: false`) it replaces the whole `runtime` setting with `{issuer, audience, unsandboxed: true}`: no app listener, and only `app:terminal` nodes run.","Rewrites `user:local` profile grantees to `app:terminal`.","At every later daemon start, before any listener: exact `user:local` subjects in canvas.db nodes and node_tokens become the owner (as do missing subjects on `app:terminal` nodes), `user:local` grantees in nodes, run_events, idempotency_keys and node_tokens become `app:terminal`, references to the unjoined runtime's `local:terminal` grant are cleared, and that local grant mirror is deleted so grant sync installs the directory's. Application grantees and free text are unchanged; a start with nothing left to rewrite changes no rows."]},run:t(async e=>({...await s(e.directory,e.token,e.name||void 0),canvas_rewrite:"next daemon start"}),"run")});export{u as sysJoinLeaf};
|
|
@@ -13,8 +13,10 @@ interface JoinedClient {
|
|
|
13
13
|
owner: string;
|
|
14
14
|
url: string;
|
|
15
15
|
token_sha256: string;
|
|
16
|
+
machine_id?: string;
|
|
17
|
+
is_default?: boolean;
|
|
16
18
|
}
|
|
17
|
-
type Registration = Pick<JoinedClient, 'runtime' | 'client_id' | 'issuer' | 'owner' | 'url'>;
|
|
19
|
+
type Registration = Pick<JoinedClient, 'runtime' | 'client_id' | 'issuer' | 'owner' | 'url' | 'machine_id' | 'is_default'>;
|
|
18
20
|
export interface JoinResult extends Registration {
|
|
19
21
|
/** False when the token matched the stored successful registration and no directory call was made. */
|
|
20
22
|
registered: boolean;
|
|
@@ -23,5 +25,5 @@ export interface JoinResult extends Registration {
|
|
|
23
25
|
* wrote the client file (no file, or one provisioned without join, which has
|
|
24
26
|
* no `token_sha256`). The daemon applies OWN-11 to canvas.db from it at every start. */
|
|
25
27
|
export declare function joinedOwner(): string | null;
|
|
26
|
-
export declare function joinDirectory(directory: string, token: string): Promise<JoinResult>;
|
|
28
|
+
export declare function joinDirectory(directory: string, token: string, name?: string): Promise<JoinResult>;
|
|
27
29
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var E=Object.defineProperty;var s=(e,t)=>E(e,"name",{value:t,configurable:!0});import{createHash as J,generateKeyPairSync as U,randomUUID as D}from"node:crypto";import{existsSync as I,mkdirSync as L,readdirSync as O,rmSync as j}from"node:fs";import{hostname as R}from"node:os";import{join as x}from"node:path";import{InputError as f}from"../io.js";import{network as h}from"../errors.js";import{secretsDir as w}from"../layout.js";import{apiSocketPath as C}from"../canvas/paths.js";import{daemonAnswers as K}from"../layout-migrate/index.js";import{atomicWriteJson as _,readJsonIfExists as g}from"../fs-utils.js";import{updateConfig as N}from"../config.js";import{launcherPresent as A}from"../grants/sandboxes.js";import{crtrVersion as H}from"../../shared/crtr-version.js";import{pluginsDir as T}from"../scope.js";import{userScopeRoot as W}from"../scope.js";import{rewriteLocalProfileGrantees as q}from"../scoped-state/profiles.js";function p(){return x(w(),"runtime-client.json")}s(p,"clientPath");function y(e){if(typeof e!="string")return!1;try{const{protocol:t}=new URL(e);return t==="http:"||t==="https:"}catch{return!1}}s(y,"isUrl");function b(e){const t=e??{},{runtime:c,client_id:a,issuer:i,owner:n,url:r}=t;if(typeof c!="string"||!/^runtime:.+/.test(c)||typeof a!="string"||a===""||!y(i)||typeof n!="string"||!/^user:.+/.test(n)||!y(r))throw h("directory registration answer is missing runtime, client_id, issuer, owner, or url");const{machine_id:o,is_default:u}=t;return{runtime:c,client_id:a,issuer:i,owner:n,url:r,...typeof o=="string"?{machine_id:o}:{},...typeof u=="boolean"?{is_default:u}:{}}}s(b,"registration");function d(){return x(w(),"runtime-client.pending.json")}s(d,"pendingPath");function z(){try{const e=T("user");return!e||!I(e)?[]:O(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&!t.name.startsWith(".")).map(t=>t.name).sort()}catch{return[]}}s(z,"installedPluginIds");async function B(e,t,c,a){const i=`${e.replace(/\/$/,"")}/v1/runtimes/registration`;let n;try{n=await fetch(i,{method:"PUT",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify({jwk:c,name:a,platform:`${process.platform}-${process.arch}`,crtr_version:H(),plugins:z(),sandboxed:A()}),signal:AbortSignal.timeout(3e4)})}catch(o){throw h(`directory registration request to ${i} failed: ${o instanceof Error?o.message:String(o)}`)}const r=await n.text();if(!n.ok)throw n.status<500&&j(d(),{force:!0}),new f({error:"join_refused",message:`directory refused the registration: ${n.status} ${r.slice(0,500)}`,next:`Create a new join token at ${e.replace(/\/$/,"")}/account/machines (tokens are single-use and expire after 10 minutes), stop crtr, and run join again.`});try{return b(JSON.parse(r))}catch(o){throw o instanceof SyntaxError?h("directory registration answer is not JSON"):o}}s(B,"register");function S(e){N("user",t=>{t.runtime=e.is_default===!1?{issuer:e.issuer,audience:e.url,unsandboxed:!0}:{...t.runtime,issuer:e.issuer,audience:e.url}}),q(W())}s(S,"commit");function ue(){const e=g(p());if(e?.token_sha256===void 0)return null;const{owner:t}=e;if(typeof t!="string"||!/^user:.+/.test(t))throw new Error(`${p()} was written by join but has a missing or invalid owner`);return t}s(ue,"joinedOwner");async function me(e,t,c=R()){if(!y(e))throw new f({error:"invalid_directory",message:"directory must be an http or https URL",received:e,field:"directory",next:"Pass the directory base URL, for example https://directory.example."});if(t==="")throw new f({error:"invalid_token",message:"token is empty",field:"token",next:"Pass the enrollment token provisioning supplied."});const a=C();if(await K(a))throw new f({error:"daemon_running",message:`cannot join while a daemon answers on ${a}`,next:"Join before the daemon starts: stop the daemon that owns this volume, then run `crtr sys join` again."});const i=J("sha256").update(t).digest("hex"),n=g(p());if(n?.token_sha256===i){const m=b(n);return S(m),{...m,registered:!1}}L(w(),{recursive:!0,mode:448});let r=g(d());if(r?.token_sha256!==i||r.directory!==e||!r.jwk||!r.public_jwk){const{privateKey:m,publicKey:v}=U("ec",{namedCurve:"P-256"}),k=D();r={token_sha256:i,directory:e,jwk:{...m.export({format:"jwk"}),kid:k,alg:"ES256",use:"sig"},public_jwk:{...v.export({format:"jwk"}),kid:k,alg:"ES256",use:"sig"}},_(d(),r,{mode:384})}const{jwk:o,public_jwk:u}=r,$=o.kid,l=await B(e,t,u,c),P={jwk:o,kid:$,alg:"ES256",...l,token_sha256:i};return _(p(),P,{mode:384}),j(d(),{force:!0}),S(l),{...l,registered:!0}}s(me,"joinDirectory");export{me as joinDirectory,ue as joinedOwner};
|
|
@@ -15,6 +15,12 @@ export declare function assertion(client: RuntimeClient, endpoint: string): stri
|
|
|
15
15
|
export declare function runtimeClient(): RuntimeClient;
|
|
16
16
|
/** The runtime's id as storage names it: the directory principal id without `runtime:`. */
|
|
17
17
|
export declare function runtimeId(client?: RuntimeClient): string;
|
|
18
|
+
/** The text a person sees when the directory no longer recognises this machine. */
|
|
19
|
+
export declare function machineRevokedMessage(issuer: string): string;
|
|
20
|
+
/** The directory answered `invalid_client` to this runtime's key: the machine was revoked. */
|
|
21
|
+
export declare class MachineRevokedError extends Error {
|
|
22
|
+
constructor(issuer: string);
|
|
23
|
+
}
|
|
18
24
|
export interface DirectoryToken {
|
|
19
25
|
access_token: string;
|
|
20
26
|
expires_at: number;
|
package/dist/core/grants/sync.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var k=Object.defineProperty;var
|
|
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, stop crtr, and run \`crtr sys join\`.`}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 +1 @@
|
|
|
1
|
-
var m=Object.defineProperty;var d=(r,
|
|
1
|
+
var m=Object.defineProperty;var d=(r,i)=>m(r,"name",{value:i,configurable:!0});import{RunError as _}from"../runs/operations.js";import{errorCodes as t}from"../../api/error-codes.js";const g={provider_error:{type:"invalid_request_error",status:400,retryable:!1},provider_rate_limited:t.provider_rate_limited,provider_unavailable:t.provider_unavailable,provider_refused:t.provider_refused,provider_not_connected:t.provider_not_connected,provider_version_retired:t.provider_version_retired,storage_unavailable:t.storage_unavailable,approval_pending:{...t.approval_pending,user_action:"answer_question"},user_denied:t.user_denied},b={unauthorized:t.unauthorized,grant_revoked:t.grant_revoked,machine_revoked:t.machine_revoked,scope_missing:t.scope_missing,permission_error:t.permission_error,invalid_request:t.invalid_request,not_found:t.not_found,not_implemented:t.not_implemented,runtime_starting:t.runtime_starting,server_error:t.server_error,provider_bad_response:t.provider_bad_response};function a(r,i){return g[r]??b[r]??(Object.prototype.hasOwnProperty.call(t,r)?t[r]:void 0)??{type:i===401?"authentication_error":i===403?"permission_error":i===429?"rate_limit_error":i>=500?"server_error":"invalid_request_error",status:i,retryable:i>=500}}d(a,"codeRow");class u extends _{static{d(this,"ProviderError")}origin;type;retry_after_s;question_id;user_action;run_id;constructor(i,s,n={}){const e=a(i,n.status??500);super(n.status??e.status,i,s,n.param,n.details,n.scope,e.retryable),this.type=e.type,this.origin=n.origin??"runtime",n.retry_after_s!==void 0&&(this.retry_after_s=n.retry_after_s),n.question_id!==void 0&&(this.question_id=n.question_id),n.run_id!==void 0&&(this.run_id=n.run_id),e.user_action!==void 0&&(this.user_action=e.user_action)}}function O(r){return new u("scope_missing",`missing scope ${r}`,{scope:r})}d(O,"scopeMissing");function q(r){if(!(r instanceof Error))return null;const{status:i,code:s,details:n,retryable:e}=r;return typeof i!="number"||typeof s!="string"||i<400||i>599?null:new _(i,s,r.message,void 0,n,void 0,typeof e=="boolean"?e:a(s,i).retryable)}d(q,"shaped");function h(r){if(r.code==="unauthorized"||r.code==="grant_revoked")return"reconnect";if(r.code==="scope_missing"&&r.scope)return`grant_scope:${r.scope}`}d(h,"directoryAction");function k(r,i,s,n){const e=r instanceof _?r:q(r)??new u("server_error","provider request failed"),o=e instanceof u?e:null,v=a(e.code,e.status),c=o?.run_id??n,p=o?.user_action??(e.code==="cap_exhausted"?"raise_cap":s?h(e):void 0),f=e.code==="cap_exhausted"?e.details?.reset_at:void 0,y={code:e.code,message:e.message,...e.details===void 0?{}:{details:e.details},type:o?.type??v.type,...e.param?{param:e.param}:{},origin:o?.origin??"runtime",retryable:e.retryable,...o?.retry_after_s===void 0?{}:{retry_after_s:o.retry_after_s},...typeof f=="string"?{reset_at:f}:{},request_id:i,...c?{run_id:c}:{},...o?.question_id?{question_id:o.question_id}:{},...p?{user_action:p}:{},...e.scope?{scope:e.scope}:{}},l={"Request-Id":i};return o?.retry_after_s!==void 0&&(l["Retry-After"]=String(o.retry_after_s)),{status:e.status,body:{error:y},headers:l}}d(k,"providerErrorBody");export{g as PROVIDER_CODES,u as ProviderError,a as codeRow,k as providerErrorBody,O as scopeMissing};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var j=Object.defineProperty;var
|
|
1
|
+
var j=Object.defineProperty;var a=(r,e)=>j(r,"name",{value:e,configurable:!0});import{createHash as T}from"node:crypto";import{readConfig as M}from"../config.js";import{emitEvent as $}from"../events/emit.js";import{grantSyncStateFor as E}from"../grants/mirror.js";import{assertion as A,machineRevokedMessage as N,requestGrantSync as O,runtimeClient as P}from"../grants/sync.js";import{isUnsandboxedMachine as I}from"../runtime/unsandboxed.js";import{ProviderError as h}from"./errors.js";import{cacheKey as F,cachedToken as G,discardEpoch as L,storeToken as U}from"./token-cache.js";import{discardFile as ae}from"./token-cache.js";const C=1e4;function D(r){const e=r.issuer??M("user").runtime?.issuer;if(!e)throw new h("not_found","provider calls need a directory-connected runtime");return e.replace(/\/$/,"")}a(D,"issuerOf");function l(r,e,t){return new h(r,e,{origin:"directory",details:t})}a(l,"directoryError");function J(r,e,t,i){const n=typeof e.error=="string"?e.error:null,o=typeof e.error_description=="string"?e.error_description:void 0,c={directory_status:r,...n?{directory_error:n}:{},...o?{directory_description:o}:{}},u=o?`: ${o}`:"";switch(n){case"invalid_client":return I()?{error:l("machine_revoked",N(i),c),grantRefusal:!1}:{error:l("server_error",`the directory refused the outbound token request (${n})${u}`,c),grantRefusal:!1};case"invalid_request":return{error:l("server_error",`the directory refused the outbound token request (${n})${u}`,c),grantRefusal:!1};case"invalid_target":return{error:l("not_found",`the directory knows no provider at ${t.audience}${u}`,c),grantRefusal:!1};case"invalid_grant":return{error:l("grant_revoked",`the directory has no active grant for ${t.grantee}${u}`,c),grantRefusal:!0};case"provider_not_connected":return{error:l("provider_not_connected",o??"The user has not connected this provider.",c),grantRefusal:!1};case"invalid_scope":{const s=typeof e.scope=="string"&&e.scope?e.scope:t.scope.length===1?t.scope[0]:void 0,g=s?`missing scope ${s}`:`the grant does not cover ${t.scope.join(" ")}`;return{error:new h("scope_missing",g,{origin:"directory",details:c,...s?{scope:s}:{}}),grantRefusal:!0}}default:return{error:l("server_error",`the directory token endpoint answered ${r}${n?` ${n}`:""}${u}`,c),grantRefusal:!1}}}a(J,"refusal");function B(r){try{return JSON.parse(Buffer.from(r.split(".")[1]??"","base64url").toString("utf8"))}catch{return{}}}a(B,"claims");function H(r){const e=r.grant_rev,t=r.grant_status;return typeof e!="number"||!Number.isInteger(e)||e<0||typeof t!="string"?null:{rev:e,status:t}}a(H,"refusedGrantState");function K(r,e){const t=a(i=>i.status==="active"?0:1,"phase");return r.rev>e.rev||r.rev===e.rev&&t(r)>t(e)}a(K,"newer");function z(r,e,t){if(!e)return;const i=E(r.grantee,r.sub)??{rev:0,status:"removed"};if(!K(e,i))return;const n=t.syncGrants??O;Promise.resolve().then(()=>n({fresh:!0})).catch(()=>{})}a(z,"syncIfNewer");async function Q(r,e={}){const t=D(e),i=`${t}/token`,n=P(),o=new URLSearchParams({grant_type:"client_credentials",client_id:n.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:A(n,i),audience:r.audience,sub:r.sub,grantee:r.grantee,scope:[...new Set(r.scope)].sort().join(" "),initiating_app:r.initiating_app}),c=AbortSignal.timeout(C),u=e.signal?AbortSignal.any([e.signal,c]):c;let s,g;try{s=await(e.fetch??fetch)(i,{method:"POST",body:o,signal:u,headers:{Accept:"application/json"}}),g=await s.text()}catch(d){throw e.signal?.aborted?e.signal.reason:new h("server_error",`the directory token endpoint could not be reached: ${d instanceof Error?d.message:String(d)}`,{origin:"directory"})}let f=null;try{f=g?JSON.parse(g):null}catch{f=null}const p=f&&typeof f=="object"&&!Array.isArray(f)?f:{};if(!s.ok){const{error:d,grantRefusal:R}=J(s.status,p,r,t);throw d.code==="machine_revoked"&&$({level:"error",event:"daemon.grants.machine_revoked",fields:{audience:r.audience}}),$({level:"info",event:"daemon.providers.mint_refused",fields:{audience:r.audience,grantee:r.grantee,code:d.code,directory_error:typeof p.error=="string"?p.error:null,status:s.status}}),R&&z(r,H(p),e),d}const m=p.access_token;if(typeof m!="string"||!m)throw new h("server_error","the directory token response has no access_token",{origin:"directory"});const y=B(m),S=Math.floor((e.now?.()??Date.now())/1e3),_=p.expires_in,v=typeof _=="number"&&Number.isFinite(_)&&_>0?S+Math.floor(_):null,w=typeof y.exp=="number"?y.exp:null,b=v!==null&&w!==null?Math.min(v,w):v??w;if(b===null)throw new h("server_error","the directory token response names no expiry",{origin:"directory"});const x=typeof y.jti=="string"&&y.jti?y.jti:T("sha256").update(m).digest("hex");return{access_token:m,jti:x,exp:b}}a(Q,"mintOutboundToken");const k=new Map;async function oe(r,e={}){const t=F(r),i=e.now??Date.now,n=G(t,i());if(n)return{...n,key:t};let o=k.get(t);if(!o){const u=L(t);o=Q(r,{...e,signal:void 0}).then(s=>(U(t,s,u),s)).finally(()=>{k.delete(t)}),o.catch(()=>{}),k.set(t,o)}return{...e.signal?await V(o,e.signal):await o,key:t}}a(oe,"outboundToken");function V(r,e){return e.aborted?Promise.reject(e.reason):new Promise((t,i)=>{const n=a(()=>i(e.reason),"onAbort");e.addEventListener("abort",n,{once:!0}),r.then(t,i).finally(()=>e.removeEventListener("abort",n))})}a(V,"abortable");export{C as MINT_TIMEOUT_MS,ae as discardFile,Q as mintOutboundToken,oe as outboundToken};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var _e=Object.defineProperty;var u=(e,n)=>_e(e,"name",{value:n,configurable:!0});import{
|
|
2
|
-
`;if(t===r&&o!==l){if(JSON.stringify(f)!==JSON.stringify(c(f)))throw new Error("terminal models.json contains credentials; move them into daemon secrets");P(e,r,432);return}z(e,r,l,{atomic:!0,mode:432}),P(e,r,432)}u(
|
|
3
|
-
`,{atomic:!0,mode:432}),P(v,re,432);const ne=w(X(e),`token-${d}`),C=j(ne,"wx+",384);ae(ne),
|
|
1
|
+
var _e=Object.defineProperty;var u=(e,n)=>_e(e,"name",{value:n,configurable:!0});import{assertGranteeRunsHere as be}from"./unsandboxed.js";import{spawn as Se}from"node:child_process";import{mkdirSync as ce,existsSync as $,unlinkSync as ae,openSync as j,closeSync as T,writeSync as ye,chmodSync as Pe,lstatSync as Re}from"node:fs";import{ensureAppSpace as xe}from"../spaces/space.js";import{sandboxCacheEnv as Oe}from"../spaces/cache-env.js";import{compileLaunch as Te,launchStillGranted as De}from"../spaces/mounts.js";import{grantsGeneration as le}from"../grants/mirror.js";import{LAUNCHER_SOCKET as U}from"../../launcher/spec.js";import{launcherRequest as G}from"../../launcher/client.js";import{piAgentDir as Ne}from"./package-health.js";import{existsBeneath as Ae,readFileBeneath as Ce,readTextBeneathIfExists as Le,setModeBeneath as P,withDirBeneath as W,writeFileBeneath as z}from"../spaces/open-beneath.js";import{dirname as V,join as w,resolve as Be,isAbsolute as $e}from"node:path";import{fileURLToPath as ue}from"node:url";import{connectViewSocket as je}from"./view-socket.js";import{appSpace as F}from"../layout.js";import{readConfig as Ge,readMergedLaunchConfig as Fe}from"../config.js";import{readRotationConfig as Ke}from"../subscription-state.js";import{getNode as b}from"../canvas/index.js";import{crtrHome as fe,nodeRunDir as X,nodeRunJobDir as Me,jobDir as He,viewSocketPath as Je,nodeAgentDir as qe,contextDir as Ue,nodeHome as We}from"../canvas/paths.js";import{encodeFrame as ze}from"./broker-protocol.js";import{buildBrokerEnv as Ve}from"./spawn-env.js";import{hostExecPathFresh as Xe}from"./branded-host.js";import{isPidAlive as Q,killProcessTreePids as de,isAnyPidAlive as Qe,captureTeardownSnapshot as Ye}from"../canvas/pid.js";import{assertMandatoryCanvasExtensions as Ze}from"./canvas-extensions.js";import{boundFleet as Ie,brokerCapReached as er,brokerThresholdsForDaemon as rr}from"./fleet.js";import{emitEvent as g}from"../events/emit.js";import{bindBrokerEventSource as nr,eventSource as tr}from"../events/source.js";import{envBrokerEngine as or}from"../../shared/env.js";import{conversationIdFromPath as ir,conversationStore as sr,ConversationNotFoundError as me,storeAddress as cr}from"../conversation-store/index.js";import{takeNodeToken as ar}from"../canvas/node-tokens.js";function K(){const e=V(ue(import.meta.url));return w(e,"broker-cli.js")}u(K,"resolveBrokerEntry");const lr="@earendil-works/pi-coding-agent";function ur(e,n){const t=w(Ne(),"models.json"),r=w(n,"models.json"),o=Le(V(t),t);if(o===null)return;const f=JSON.parse(o),c=u(i=>Array.isArray(i)?i.map(c):i===null||typeof i!="object"?i:Object.fromEntries(Object.entries(i).filter(([s])=>!/^(apiKey|authorization|headers|token|secret)$/i.test(s)).map(([s,E])=>[s,c(E)])),"scrub"),l=`${JSON.stringify(c(f),null,2)}
|
|
2
|
+
`;if(t===r&&o!==l){if(JSON.stringify(f)!==JSON.stringify(c(f)))throw new Error("terminal models.json contains credentials; move them into daemon secrets");P(e,r,432);return}z(e,r,l,{atomic:!0,mode:432}),P(e,r,432)}u(ur,"writeBrokerModels");function pe(e){return e.env.CRTR_BROKER_ENGINE??or()}u(pe,"resolveEngineSpec");function he(e){return e.includes("/")||e.endsWith(".jsonl")}u(he,"isPlainLaunchSessionPath");function fr(e,n){return $e(e)?e:Be(n,e)}u(fr,"resolvePlainLaunchPath");function dr(e,n,t,r){if(t==="--session"&&!he(r))throw new Error(`[broker] refusing to launch ${e}: launch recipe passes bare --session id '${r}' \u2014 the broker fatally rejects bare ids; pass the .jsonl path instead.`);if(!he(r))return;const o=fr(r,n),f=ir(o);try{if(f===null)throw new me(o);sr().header(f)}catch(c){if(!(c instanceof me))throw c;const l=t==="--session"?"session file":"fork source";throw new Error(`[broker] refusing to launch ${e}: launch recipe references missing ${l} '${r}' (${o})`)}}u(dr,"preflightLaunchSessionArg");function mr(e,n,t){const r=X(e);ce(r,{recursive:!0}),process.platform==="linux"&&$(U)&&Re(r).uid===process.getuid()&&Pe(r,504);const o=w(r,"broker-launch.json"),f={user:Ge("user"),launch:Fe(t,n.env.CRTR_PROFILE_ID||null),rotation:Ke()};z(r,o,JSON.stringify({...n,configSnapshot:f}),{atomic:!0});const c=Ce(r,o).toString("utf8"),l=JSON.parse(c);if(!Array.isArray(l.argv))throw new Error(`[broker] refusing to launch ${e}: broker-launch.json is missing the argv array`);const i=pe(n);try{const d=import.meta.resolve(i);if(d.startsWith("file:")&&!$(ue(d)))throw new Error(`resolved to missing file ${d}`)}catch(d){throw new Error(`[broker] refusing to launch ${e}: cannot resolve broker engine '${i}' from ${K()} \u2014 ${d.message}`)}const s=l.argv,E=[];for(let d=0;d<s.length;d++){const S=s[d];if(S==="-e"){const D=s[++d];D!==void 0&&E.push(D);continue}if(S!=="--session"&&S!=="--fork")continue;const R=s[++d];R!==void 0&&dr(e,t,S,R)}try{Ze(E)}catch(d){throw new Error(`[broker] refusing to launch ${e}: ${d.message}`)}}u(mr,"preflightBrokerLaunch");const pr=2e3,hr=2e3,wr=150;async function M(e,n){const t=Date.now()+n;for(;Qe(e);){const r=t-Date.now();if(r<=0)return!1;await new Promise(o=>setTimeout(o,Math.min(wr,r)))}return!0}u(M,"waitForTreeExit");async function gr(e,n,t){return e.length===0?{confirmed:!0}:t&&await M(e,pr)?{confirmed:!0}:(de(e,"SIGTERM",n),await M(e,hr)?{confirmed:!0}:(de(e,"SIGKILL",n),await M(e,5e3)?{confirmed:!0}:{confirmed:!1,error:new Error(`broker process tree for ${e.join(", ")} remained alive after SIGKILL`)}))}u(gr,"terminateBrokerTree");function we(e,n){const t=b(e),r=t?.pi_pid,o=r!=null?Ye(r,t?.pi_pid_identity??null,n.excludePid):null;return o?.reused===!0&&g({level:"warn",event:"broker.teardown.pid_reuse_refused",node_id:e,fields:{recorded_pid:r,expected_identity:t?.pi_pid_identity??null}}),{tree:o?.tree??[],identities:o?.identities??null,nodeId:e,launcherPid:$(U)&&o?.tree.includes(r)?r:null}}u(we,"prepareBrokerTeardown");async function ge(e){if(e.launcherPid!==null){try{await G({operation:"stop",pid:e.launcherPid})}catch(n){return Q(e.launcherPid)?{confirmed:!1,error:n instanceof Error?n:new Error(String(n))}:{confirmed:!0}}return await M([e.launcherPid],5e3)?{confirmed:!0}:{confirmed:!1,error:new Error(`launcher pid ${e.launcherPid} remained alive`)}}return new Promise(n=>{let t=!1,r=!1;const o=u(i=>{r||(r=!0,n(i))},"settle"),f=u(()=>{try{ae(Je(e.nodeId))}catch{}},"cleanupSocket"),c=u(i=>{gr(e.tree,e.identities,i).then(o,s=>o({confirmed:!1,error:s instanceof Error?s:new Error(String(s))}))},"finish"),l=je(e.nodeId);l.once("error",()=>{t||(f(),c(!1))}),l.once("connect",()=>{t=!0;try{l.write(ze({type:"shutdown"}))}catch{}l.end(),f(),c(!0)})})}u(ge,"runPreparedBrokerTeardown");const H=new Map;function Xr(e,n={},t){const r=H.get(e);if(r!==void 0)return t!==void 0&&r.after.push(t),r.completion;let o;const f=new Promise(i=>{o=i}),c={completion:f,resolve:o,after:t===void 0?[]:[t]};H.set(e,c);let l;try{l=we(e,n)}catch(i){const s={confirmed:!1,error:i instanceof Error?i:new Error(String(i))};return H.delete(e),g({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:s.error}),c.resolve(s),f}return setImmediate(()=>{H.delete(e),ge(l).then(async i=>{if(i.confirmed)for(const s of c.after)try{await s()}catch(E){g({level:"error",event:"broker.teardown.after_failed",node_id:e,error:E})}else g({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:i.error});c.resolve(i)},i=>{const s={confirmed:!1,error:i instanceof Error?i:new Error(String(i))};g({level:"error",event:"broker.teardown.unconfirmed",node_id:e,error:s.error}),c.resolve(s)})}),f}u(Xr,"requestBrokerTeardown");function Ee(){return process.platform==="linux"&&$(U)}u(Ee,"isolatedBrokerLauncherAvailable");const Qr={async launch(e,n,t){if(be(b(e)?.grantee),b(e)?.isolated===!0&&!Ee())throw new Error("isolated broker requires the Linux root launcher");mr(e,n,t.cwd);const r=Ie();if(!r.reserveSlot(e))throw er(rr().automaticReviveCap,r.size());let o=null,f,c=!1;try{const l=He(e);ce(l,{recursive:!0}),o=j(w(l,"broker.log"),"a");const i=pe(n),s={...Ve(n),CRTR_BROKER_ENGINE:i,CRTR_STORE_ADDRESS:cr()};if(i!==lr){const a=n.env.NODE_OPTIONS??process.env.NODE_OPTIONS;a!==void 0&&(s.NODE_OPTIONS=a)}tr()===void 0&&nr(e);const E=b(e)?.run_id;if(E){const a=b(E);if(!a||a.deleting_at)throw new Error("run is deleting")}const d=n.env.CRTR_EXECUTION_ID;if(!d)throw new Error("broker launch has no execution id");const S=ar(d),R=Ee();if(b(e)?.isolated===!0&&!R)throw new Error("isolated broker requires the Linux root launcher");if(process.platform==="linux"&&process.env.CRTR_LAUNCHER_SOCKET&&!R)throw new Error("root launcher socket is unavailable; refusing an unsandboxed broker");if(R){const a=b(e);if(!a)throw new Error(`missing node ${e}`);const m=a.grantee??"app:terminal",A=await xe(m);Object.assign(s,Oe(m));const p={uid:process.getuid(),gid:A.gid},k=X(e);for(const[h,y]of[[F(m).nodes,qe(e,m)],[F(m).nodes,Ue(e,m)],[k,Me(e)]])W(h,y,()=>{},!0),P(h,y,1528,p);P(k,w(k,"broker-launch.json"),416,p);const I=w(k,"system-prompt.md");Ae(k,I)&&P(k,I,432,p);const ee=a.isolated===!0,v=We(e,m,ee);ee&&(W(F(m).nodes,v,()=>{},!0),P(F(m).nodes,v,1528,p),s.HOME=v,s.PI_CODING_AGENT_DIR=w(v,".pi","agent"));const q=w(v,".pi","agent");W(v,q,()=>{},!0),ur(v,q);const re=w(q,"settings.json"),ke=w(V(K()),"../../builtin-pi-packages/pi-crtr-extensions");z(v,re,`${JSON.stringify({packages:[ke]})}
|
|
3
|
+
`,{atomic:!0,mode:432}),P(v,re,432);const ne=w(X(e),`token-${d}`),C=j(ne,"wx+",384);ae(ne),ye(C,S);const te=[j(`/proc/self/fd/${C}`,"r"),j(`/proc/self/fd/${C}`,"r")];T(C);const oe={nodeId:e,app:m,sub:a.sub??"user:local",grantId:a.grant_id??null,scopes:a.scopes??null,isolated:a.isolated===!0,...a.isolated===!0?{cwd:a.cwd}:{},command:[process.execPath,K(),"--canvas-home",fe(),"--epoch",r.epoch(),e],env:Object.fromEntries(Object.entries({...s,...a.isolated===!0?{PWD:a.cwd}:{}}).filter(h=>h[1]!==void 0)),auditMissing:u(h=>g({level:"warn",event:"broker.mount.missing",node_id:e,fields:{source:h}}),"auditMissing")};try{let h;for(const O of te){const L=le(),_=Te(oe),B=await G({operation:"launch",spec:_},[O]);if(!("pid"in B))throw new Error("launcher did not return a pid");if(le()===L||De(oe,_)){h=B.pid;break}g({level:"info",event:"broker.launch.regranted",node_id:e,fields:{pid:B.pid}}),await G({operation:"stop",pid:B.pid,signal:"SIGKILL"}).catch(()=>{})}if(h===void 0)throw new Error("grant changed during every launch attempt");const y=h,ve=Date.now(),ie=new Promise(O=>{const L=setInterval(()=>{Q(y)||(clearInterval(L),G({operation:"exit.status",pid:y}).then(_=>"oomKilled"in _&&_.oomKilled,()=>!1).then(_=>{g({level:_?"warn":"info",event:"broker.exited",node_id:e,fields:{pid:y,uptime_ms:Date.now()-ve,oom_killed:_}}),O({code:null,signal:null,..._?{oomKilled:!0}:{}})}))},250);L.unref()});T(o),o=null;const se={pid:y,exited:ie};return r.register(e,se),c=!0,ie.then(O=>r.onChildExit(e,O,d)),se}finally{for(const h of te)T(h)}}const D=await Xe(),x=Se(D,[K(),"--canvas-home",fe(),"--epoch",r.epoch(),e],{cwd:t.cwd,detached:!0,stdio:["ignore",o,o,"pipe"],env:s});f=x;const N=x.stdio[3];if(!N||!("end"in N))throw new Error("broker token pipe did not open");N.on("error",a=>g({event:"broker.token_pipe.failed",level:"error",node_id:e,error:a})),N.end(S),T(o),o=null;const Y=Date.now(),Z=new Promise(a=>{let m=!1;const A=u(p=>{m||(m=!0,a(p))},"finish");x.once("exit",(p,k)=>{g({level:p===0?"info":"warn",event:"broker.exited",node_id:e,fields:{code:p,signal:k,uptime_ms:Date.now()-Y}}),A({code:p,signal:k})}),x.once("error",p=>{g({level:"error",event:"broker.launch.failed",node_id:e,error:p,fields:{uptime_ms:Date.now()-Y}}),A({code:null,signal:null})})});x.unref();const J={pid:x.pid??null,exited:Z};return J.pid!==null&&(r.register(e,J),c=!0,Z.then(a=>r.onChildExit(e,a,d))),J}catch(l){if(!c&&f?.pid!=null)try{f.kill("SIGTERM")}catch{}throw l}finally{c||r.releaseReservation(e),o!==null&&T(o)}},isAlive(e){return Q((typeof e=="string"?b(e):e)?.pi_pid)},teardown(e,n={}){try{return ge(we(e,n))}catch(t){return Promise.resolve({confirmed:!1,error:t instanceof Error?t:new Error(String(t))})}},signal(e,n){const t=b(e)?.pi_pid;if(t!=null)try{process.kill(t,n)}catch{}}};export{Qr as headlessBrokerHost,Ee as isolatedBrokerLauncherAvailable,mr as preflightBrokerLaunch,Xr as requestBrokerTeardown};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var q=Object.defineProperty;var a=(e,r)=>q(e,"name",{value:r,configurable:!0});import{envHomeOverride as L,envRuntimeHomeOverride as W,INBOX_BETWEEN_TURNS_ENV as G,scopeAllowed as R}from"../../shared/env.js";import{validateScopes as J}from"../scopes.js";import{activeGrant as A,terminalGrant as C}from"../grants/mirror.js";import{appendRunEvent as K}from"../runs/events.js";import{readConfig as I}from"../config.js";import{InputError as N}from"../io.js";import{ApiError as g}from"../../api/errors.js";import{assertGranteeRunsHere as X}from"./unsandboxed.js";import{randomBytes as z}from"node:crypto";import{existsSync as $}from"node:fs";import{resolve as Y,relative as V,isAbsolute as B,join as Q,sep as Z}from"node:path";import{appSpace as ee}from"../layout.js";import{createNode as ne,assertRunNotDeleting as re,conversationOwner as te,getNode as c,getRow as ie}from"../canvas/index.js";import{ensureAppSpace as oe}from"../spaces/space.js";import{LAUNCHER_SOCKET as F}from"../../launcher/spec.js";import{resolveInstallId as le}from"../canvas/install-id.js";import{isAppRun as ae}from"../canvas/app-run.js";import{canvasDbPath as se,contextDir as de}from"../canvas/paths.js";import{conversationIdFromPath as ue}from"../conversation-store/index.js";import{usage as M}from"../errors.js";import{assertProfileAvailableForBirth as ce}from"../profiles/deletion-reservation.js";import{loadExactProfileManifest as fe}from"../profiles/manifest.js";import{admitNodeCreate as pe}from"../command-hooks/lifecycle-create.js";import{getById as _e,nodeObject as E,ownerForGrantee as me}from"../graph/objects.js";import{recordActedOn as ge}from"../graph/edges.js";import{recordWatch as we,copyWatchesForFork as he}from"../graph/watches.js";import{publish as Re}from"../graph/events.js";let k;const P=new Map;function rn(e){k=e}a(rn,"setInstallId");function ve(){if(k!==void 0)return k;const e=se(),r=P.get(e);if(r!==void 0)return r;const o=le();return P.set(e,o),o}a(ve,"installId");function Ee(){return`${Date.now().toString(36)}-${z(4).toString("hex")}`}a(Ee,"newJobId");function ye(){return`${ve()}-${Ee()}`}a(ye,"newNodeId");const S=128,Ce=/^[a-z0-9-]+$/;class j extends Error{static{a(this,"NodeIdConflictError")}nodeId;constructor(r){super(`node already exists: ${r}`),this.nodeId=r,this.name="NodeIdConflictError"}}function T(e){if(e.length===0||e.length>S)throw M(`invalid --node-id '${e}': must be 1-${S} characters`,{next:`Pass an id-safe string (lowercase letters, digits, hyphens) of at most ${S} characters.`});if(!Ce.test(e))throw M(`invalid --node-id '${e}': must contain only lowercase letters, digits, and hyphens (e.g. 'remote42', 'my-node-1')`,{next:"Use only lowercase letters, digits, and hyphens."})}a(T,"validateNodeIdFormat");function tn(e){if(T(e),c(e)!==null)throw new j(e)}a(tn,"preflightNodeId");function Ie(e){if(T(e),c(e)!==null)throw new j(e);return e}a(Ie,"validatedExplicitNodeId");function on(e){let r=e;const o=new Set;for(;;){if(o.has(r))return r;o.add(r);const s=ie(r);if(s===null||s.parent==null)return r;r=s.parent}}a(on,"rootOfSpine");function ln(){const e=process.env;return{nodeId:e.CRTR_NODE_ID??null,kind:e.CRTR_KIND??null,mode:e.CRTR_MODE??null}}a(ln,"currentNodeContext");function Ne(e){return ae(e)}a(Ne,"inboxBetweenTurns");function an(e){const r={CRTR_NODE_ID:e.node_id,CRTR_KIND:e.kind,CRTR_MODE:e.mode,CRTR_LIFECYCLE:e.lifecycle,CRTR_NODE_CWD:e.cwd,CRTR_CONTEXT_DIR:de(e.node_id,e.grantee),CRTR_CYCLES:(e.cycles??0).toString(),CRTR_PROFILE_ID:e.profile_id??"",PI_CLAUDE_OAUTH_REINJECT_SCOPE:"never"};e.parent&&(r.CRTR_PARENT_NODE_ID=e.parent),e.run_id&&(r.CRTR_RUN_ID=e.run_id),Ne(e)&&(r[G]="1"),e.scopes!==null&&e.scopes!==void 0&&(r.CRTR_SCOPES=e.scopes.join(",")),r.CRTR_GRANTEE=e.grantee??"app:terminal";const o=L();o!==void 0&&o!==""&&(r.CRTR_HOME=o);const s=W();s!==void 0&&s!==""&&(r.CRTR_RUNTIME_HOME=s);const f=process.env.CRTR_SUBTREE;return f!==void 0&&f!==""&&(r.CRTR_SUBTREE=f),{...r,...e.launch?.env??{}}}a(an,"nodeEnv");function ke(e){const r=ue(e),o=r===null?null:te(r);return o===null?null:c(o)}a(ke,"forkSourceNode");async function sn(e){const r=e.nodeId!==void 0?(T(e.nodeId),e.nodeId):ye(),o=e.parent??null,s=o===null,f=e.spawnedBy??o,_=e.creator??null,x=e.mode??"base",y=e.lifecycle??(s?"resident":"terminal"),w=e.run===void 0?C():null,n={node_id:r,name:e.name??e.kind,description:e.description,cycles:0,created:new Date().toISOString(),cwd:e.cwd,host_kind:"broker",profile_id:e.profile_id??null,kind:e.kind,mode:x,lifecycle:y,persona_ack:{kind:e.kind,mode:x,lifecycle:y},status:"active",parent:o,spawned_by:f,creator:_,fork_from:e.forkFrom,fork_source_file:e.forkSourceFile,review_binding:e.reviewBinding,model_override:e.modelOverride??null,scopes:e.scopes??null,isolated:e.isolated===!0,grantee:e.run?.grantee??"app:terminal",sub:e.run?.sub??w?.sub??null,grant_id:e.run?.grant_id??w?.id??null,client_id:e.run?.client_id??null,metadata_json:s?e.run?.metadata_json??null:null,run_id:s?r:e.run?.run_id??null,event_cursor:s?e.run?.event_cursor??1:null,usage_json:s?e.run?.usage_json??JSON.stringify({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}):null,passive_default:e.passiveDefault??!1,...e.managedWorktree!==void 0?{managed_worktree:e.managedWorktree}:{},intent:null,deadline_at:e.deadlineAt??null,pi_session_id:null,pi_session_file:null,launch:e.launch},v=e.scopes===void 0?void 0:J(e.scopes),h=o===null?null:c(o);if(o!==null&&h===null)throw new Error(`cannot spawn under unknown parent node: ${o}`);if(h?.run_id&&c(h.run_id)?.deleting_at)throw new Error("run is deleting");const u=_===null?null:c(_);if(_!==null&&u===null)throw new Error(`cannot spawn from unknown creator node: ${_}`);if(u?.run_id&&c(u.run_id)?.deleting_at)throw new Error("run is deleting");const D=e.forkSourceFile==null?null:ke(e.forkSourceFile),O=e.replaces==null?null:c(e.replaces),m=h??D??O??u??(f===null?null:c(f)),b=[h,u,D,O,f===null?null:c(f)].some(t=>t?.isolated===!0||t?.run_id!=null&&c(t.run_id)?.isolated===!0);if(b&&e.isolated===!1)throw new g(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});if(n.isolated=b||e.isolated===!0,m!==null){const t=u??m;if(u&&m.grantee&&u.grantee&&u.grantee!==m.grantee)throw new g(403,"other_app_run","cannot create under another app's node");const i=u&&u!==m?[m,u]:[m],d=[];for(const l of i){l.scopes!==null&&l.scopes!==void 0&&d.push(l.scopes);const p=l.grantee==="app:terminal"?C():l.grant_id?A(l.grant_id):null;p?d.push(p.resolved):l.grantee==="app:terminal"&&!I("user").runtime?.issuer||d.push([])}if(v!==void 0&&!v.every(l=>d.every(p=>R(p,l))))throw new N({error:"unknown_scope",message:"child scopes must be covered by the creating node, its source and their current grants",received:v,field:"scopes",next:"Request only scopes the creating node currently holds."});if(n.scopes=v??(i.every(l=>l.scopes==null)&&i.every(l=>l.grant_id===t.grant_id&&l.sub===t.sub)?null:i.length===1?m.scopes??null:[...new Set(d.flat().filter(l=>d.every(p=>R(p,l))))]),n.scopes?.includes("crtr:act")&&!R(n.scopes,"crtr:llm"))throw new N({error:"invalid_scope",message:"crtr:act requires crtr:llm",received:n.scopes,field:"scopes",next:"Include crtr:llm or omit crtr:act."});n.grantee=t.grantee==="user:local"?"app:terminal":t.grantee,n.sub=t.sub??(n.grantee==="app:terminal"?w?.sub:null)??null,n.grant_id=t.grant_id??(n.grantee==="app:terminal"?w?.id:null)??null,n.client_id=t.client_id,n.run_id=s?r:t.run_id??null}if(X(n.grantee),e.run===void 0&&n.grantee==="app:terminal"&&I("user").runtime?.issuer&&w===null)throw new g(403,"scope_missing","terminal grant is not active");if(n.isolated){if(process.platform!=="linux"||!$(F))throw new g(400,"isolated_unavailable","isolated nodes require the Linux root launcher");if(e.managedWorktree!==void 0)throw new g(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const t=Q(ee(n.grantee??"app:terminal").nodes,r),i=V(t,Y(n.cwd));if(!B(n.cwd)||i===".."||i.startsWith(`..${Z}`)||B(i))throw new g(400,"invalid_request","isolated node cwd must be inside its own folder",{param:"cwd"})}if(n.profile_id){const t=fe(n.profile_id).manifest.grantee??"app:terminal";if(t!==n.grantee){const i=`crtr:memory:rw:${t}:profile:${n.profile_id}`,d=n.grantee==="app:terminal"?C():n.grant_id?A(n.grant_id):null;if(!(n.grantee==="app:terminal"&&!I("user").runtime?.issuer)&&(!d||d.grantee!==n.grantee||!R(d.resolved,i))||!R(n.scopes,i))throw new N({error:"scope_missing",message:`missing scope ${i}`,received:n.profile_id,field:"profile",next:"Select a profile covered by this run and its current grant."})}}if(ce(n.profile_id??null),!n.grantee)throw new Error("node creation requires an app");const H=await pe({kind:n.kind,mode:n.mode,cwd:n.cwd,profile:n.profile_id??null,root:s,parentId:o,requestingNodeId:_,lifecycle:y,replaces:e.replaces??null},n.grantee),U=a((t,i)=>{const d=E(t,i.node_id),l=_??o??e.actor??f??null,p=l!==null&&_e(t,l)?.type==="node"?E(t,l):me(t,e.run===void 0?"user:local":i.grantee);ge(t,p,d),o!==null&&e.subscribe!==!1&&we(t,E(t,o),d,"spawn"),e.forkSourceNode&&he(t,E(t,e.forkSourceNode),d),Re(t,{event:"created",object:d,actor:p,attrs:{name:i.name,kind:i.kind,creator:_??null}})},"recordBirth");try{e.nodeId!==void 0&&Ie(e.nodeId),await e.beforeCreate?.(),(process.platform!=="linux"||$(F))&&await oe(n.grantee??"app:terminal"),ne(n,{outcomeDelivery:e.outcomeDelivery,withinCreate:a((t,i)=>{i.run_id&&i.run_id!==i.node_id&&re(t,i.run_id),s&&e.run===void 0&&K(t,r,r,"run.started",{scopes:i.scopes??null,payer:"user",usage:JSON.parse(i.usage_json)}),e.withinCreate?.(t,i),U(t,i)},"withinCreate")})}finally{H()}return n}a(sn,"spawnNode");export{j as NodeIdConflictError,ln as currentNodeContext,ke as forkSourceNode,Ne as inboxBetweenTurns,Ee as newJobId,ye as newNodeId,an as nodeEnv,tn as preflightNodeId,on as rootOfSpine,rn as setInstallId,sn as spawnNode};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var V=Object.defineProperty;var s=(e,r)=>V(e,"name",{value:r,configurable:!0});import{existsSync as P}from"node:fs";import{conversationIdFromPath as _,conversationStore as h,ConversationNotFoundError as A}from"../conversation-store/index.js";import{isAbsolute as
|
|
1
|
+
var V=Object.defineProperty;var s=(e,r)=>V(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as X}from"./unsandboxed.js";import{existsSync as P}from"node:fs";import{conversationIdFromPath as _,conversationStore as h,ConversationNotFoundError as A}from"../conversation-store/index.js";import{isAbsolute as Y}from"node:path";import{findNodeBySessionFile as q,getNode as g,updateNode as y,beginExecution as Q,recordExecutionPid as Z,abortExecution as b,endLiveExecution as I,unbindSession as O,recordLaunch as ee,setFrozen as re,clearFrozen as ie,fullName as D}from"../canvas/index.js";import{emitEvent as ne}from"../events/emit.js";import{cancelCronsOnWake as oe}from"../canvas/crons.js";import{isParked as te,transition as R}from"./lifecycle.js";import{publishNotice as se}from"./close.js";import{InputError as E}from"../io.js";import{buildPiArgv as m}from"./launch.js";import{buildReviveKickoff as le,drainBearings as ce,hasRetainedReports as ae,readGoal as S,readYieldMessage as ue,RUNTIME_RESTART_CONTINUATION as fe}from"./kickoff.js";import{readRoadmap as T}from"./roadmap.js";import{encodeKickoffOrigin as he,KICKOFF_ORIGIN_ENV as de}from"./stamp/protocol.js";import{headlessBrokerHost as B}from"./host.js";import{boundFleet as z,brokerCapReached as pe,brokerThresholdsForDaemon as U}from"./fleet.js";import{beginBootFaultAttempt as ve,clearFault as we}from"../canvas/recovery.js";import{clearNodeLedger as H,forkNodeLedger as _e,noticeChangedWhileDone as me}from"./ledger.js";import{forkSourceNode as ke,rootOfSpine as ge}from"./nodes.js";import{isReviewCompanionBound as ye}from"../review/companion.js";import{assertProfileActive as be}from"../profiles/manifest.js";import{isPidAlive as Re}from"../canvas/pid.js";import{crtrHome as Ee,jobDir as Se}from"../canvas/paths.js";import{contextDir as xe,ensureNodeDirs as $e,nodeAgentDir as Ne}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as Ce}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as Fe}from"../command-hooks/transport/exec-lifecycle.js";function Pe(e,r){const i=e.pi_session_file,n=i?_(i):null;if(n===null)return{};try{const t=h().header(n);return r?{resumeSessionPath:t.path}:{resumeSessionPath:t.path,newCycle:!0}}catch(t){if(t instanceof A)return{};throw t}}s(Pe,"resumeArgs");function K(e){const r=_(e);if(r===null)return!1;try{return h().header(r),!0}catch(i){if(i instanceof A)return!1;throw i}}s(K,"conversationExists");function Ae(e){const r=_(e);if(r===null)return!0;const i=h().header(r).header.cwd;return typeof i!="string"||P(i)}s(Ae,"sessionCwdExists");function Oe(e,r,i){const n=r.pi_session_file;if(!n||!K(n))return{};const t=q(n);return t!==null&&t.node_id!==e&&Re(t.pi_pid)?{}:Ae(n)?Pe(r,i):{}}s(Oe,"resumableSessionArgs");function De(e){return T(e)===null&&ue(e)===null&&!ae(e)}s(De,"isUnstartedBirth");function ar(e){const r=S(e);if(r!==null&&r.trim()!=="")return!0;const i=T(e);return i!==null&&i.trim()!==""}s(ar,"hasFreshGroundState");async function ur(e,r){const i=await L(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(ur,"reviveNode");async function fr(e,r,i){return L(e,{...r,viewerAttach:!0},i)}s(fr,"decideAttachRevive");async function L(e,r,i){let n=g(e);if(n===null)throw new Error(`reviveNode: unknown node ${e}`);if(n=g(e)??n,n.run_id&&g(n.run_id)?.deleting_at)throw new Error("run is deleting");if(n.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${n.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);be(n.profile_id),X(n.grantee);const t=z();if(t.has(e))return R(e,n.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"already-live"}};if(t.isClaimed(e))return{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&n.lifecycle==="resident"&&te(n))return{parked:!0};if(!t.reserveSlot(e)){if(r.capacity==="refuse")throw pe(U().automaticReviveCap,t.size());return n.frozen_at==null&&(re(e,new Date().toISOString()),I(e,{reason:"frozen"}),ne({level:"info",event:"broker.frozen",node_id:e,fields:{live_count:t.size(),hard_cap:U().automaticReviveCap}})),{parked:!1,result:{window:null,session:n.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await Te(e,n,r)}}finally{t.releaseReservation(e)}}s(L,"decideRevive");async function Te(e,r,i){if(!P(r.cwd)){const d=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new E({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:d})}r.host_kind!=="broker"&&(y(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!ye(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(O(e),r.pi_session_id=null,r.pi_session_file=null);const t=r.fork_from!=null&&r.pi_session_id==null,c=r.fork_source_file;if(t&&(typeof c!="string"||!Y(c)))throw new E({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const f=t&&typeof c=="string";if(f&&!K(c))throw new E({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${c}`,next:`Restore ${c}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const a=Q(e,{daemonEpoch:z().epoch()});try{r.cycles=(r.cycles??0)+1,y(e,{cycles:r.cycles});const G=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,p=f?{}:Oe(e,r,G),v=p.resumeSessionPath!==void 0,x=v&&p.newCycle===!0,k=v&&!x,$=!v&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&De(e),M=f||$,N=r.cycle_pending===!0||x;y(e,{cycle_pending:N}),r.cycle_pending=N,!f&&!v&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(O(e),r.pi_session_id=null,r.pi_session_file=null);let l,u=null;const C=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(f){const o=S(e),F=_(c);if(F===null)throw new Error(`invalid fork source ${c}`);const J=h().header(h().fork(F,e)).path;l=m(r,{resumeSessionPath:J,...o!=null&&o!==""?{prompt:o}:{}}),o!=null&&o!==""&&(u=C()),_e(e,ke(c)?.node_id??null)}else if(k)l=m(r,{...p,...i.wakeReason==="runtime-restart-abort"?{prompt:fe}:{}}),i.wakeReason==="runtime-restart-abort"&&(u={type:"runtime",kind:"restart-continuation"});else if($){const o=S(e)?.trim();l=m(r,o!==void 0&&o!==""?{prompt:o}:{}),o!==void 0&&o!==""&&(u=C()),H(e)}else{const o=ce(r,a);l=m(r,{...p,prompt:le(r,o,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&o.unreadMessageIds?.length&&(l.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(o.unreadMessageIds)),u=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},H(e)}l.env={...l.env,CRTR_SUBTREE:ge(e)},u!==null&&(l.env[de]=he(u)),r.status==="done"&&r.last_settled_at!=null&&me(e,r.last_settled_at),R(e,r.intent==="refresh"?"refresh-launch":"revive"),we(e,{preserveEpisode:!0});const j=ve(e);i.recovery!==!0&&i.viewerAttach!==!0&&oe(e),$e(e,r.grantee);const W=Ce(r.cwd,r.profile_id).plans.get("node:start")??[];await Fe(W,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:Ne(e),contextDir:xe(e),jobDir:Se(e)},runtime:{isBirth:M,canvasHome:Ee(),profile:r.profile_id??null}}),l.env={...l.env,CRTR_EXECUTION_ID:a};let w;try{if(w=await B.launch(e,l,{cwd:r.cwd,name:D(r),resuming:k}),w.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(o){try{R(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:o instanceof Error?o.message:String(o)}}}),se(e,"notice.crash",`Child crashed \u2014 ${D(r)} (${e}) failed to relaunch and is now dead: ${o.message}
|
|
2
2
|
|
|
3
|
-
It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw b(a,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${o.message}`)}if(
|
|
3
|
+
It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw b(a,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${o.message}`)}if(ee(e,new Date().toISOString()),Z(a,e,w.pid)==="shutdown")throw b(a,{reason:"superseded"}),B.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return ie(e),{window:null,session:r.tmux_session??null,resumed:k,launch:w,faultExplanation:j.explanation,outcome:"launched"}}catch(d){throw b(a,{reason:"launch_failed"}),d}}s(Te,"launchRevive");export{fr as decideAttachRevive,ar as hasFreshGroundState,Pe as resumeArgs,ur as reviveNode};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var _=Object.defineProperty;var a=(e,u)=>_(e,"name",{value:u,configurable:!0});import{spawn as R}from"node:child_process";import{createHash as b}from"node:crypto";import{captureTeardownSnapshot as k,isAnyPidAlive as x,killProcessTreePids as H}from"../canvas/pid.js";import{CRON_DEATH_POLL_MS as G,CRON_KILL_GRACE_MS as B,newHeadCapture as I}from"../../daemon/cron/capture.js";import{resolveAdmittedHostCommand as L}from"./spawn-env.js";import{appProcessSandboxed as K,launchAppProcess as N}from"../spaces/process.js";import{assertGranteeRunsHere as q}from"./unsandboxed.js";async function X(e){if(q(e.app),!K()){if(e.isolated)throw new Error("isolated cron requires the root launcher");return $(e)}const u=Date.now(),n=await N({nodeId:e.nodeId,app:e.app,isolated:e.isolated,...e.principal===void 0?{}:{principal:e.principal},...e.folder===void 0?{}:{folder:e.folder},...e.cron===void 0?{}:{cron:e.cron},command:[L("bash",e.env,e.cwd),"-c",e.command],cwd:e.cwd,env:e.env,stdoutBytes:e.headBytes,stderrBytes:e.headBytes});let m=!1,i;const p=a(()=>{n.stop().catch(t=>{console.error(`app command ${n.pid} could not be stopped:`,t)})},"terminate");i=setTimeout(()=>{m=!0,p()},e.timeoutMs),i.unref?.();const o=n.completion.then(t=>({exitCode:t.exitCode,signal:t.signal,stdout:t.stdout,stderr:t.stderr,stdoutTruncated:t.stdoutTruncated,stderrTruncated:t.stderrTruncated,stdoutSha256:t.stdoutSha256,timedOut:m,durationMs:Date.now()-u,processError:null})).finally(()=>{i&&clearTimeout(i)});return{pid:n.pid,completion:o,terminate:p}}a(X,"runAppCommand");function $({command:e,cwd:u,env:n,timeoutMs:m,headBytes:i}){const p=Date.now(),o=R(L("bash",n,u),["-c",e],{cwd:u,env:n,detached:!0,stdio:["ignore","pipe","pipe"]}),t=o.pid??null,h=I(i),T=I(i),P=b("sha256");o.stdout?.on("data",d=>{P.update(d),h.take(d)}),o.stderr?.on("data",d=>T.take(d));let g=!1,w=null,s,v=null,c=!1,y=!1,l,f,C,r,D;const M=new Promise(d=>{D=d}),S=a(()=>{c||s===void 0||(c=!0,l!==void 0&&clearTimeout(l),f!==void 0&&clearTimeout(f),C!==void 0&&clearTimeout(C),D({exitCode:s,signal:v,stdout:h.decode(),stdoutTruncated:h.truncated(),stderr:T.decode(),stderrTruncated:T.truncated(),timedOut:g,durationMs:Date.now()-p,stdoutSha256:P.digest("hex"),processError:w}))},"finish"),A=a(()=>{if(r===void 0||!x(r.tree)){S();return}C=setTimeout(A,G)},"awaitTeardownDeath"),E=a(()=>{t===null||c||(r??=k(t,null),H(r.tree,"SIGTERM",r.identities),f===void 0&&(f=setTimeout(()=>{x(r.tree)&&H(r.tree,"SIGKILL",r.identities),y=!0,s!==void 0&&A()},B),f.unref?.()))},"terminate");return o.on("error",d=>{w===null&&(w=d),t===null&&(s=null,S())}),o.on("close",(d,O)=>{if(!c){if(s=d,v=O,l!==void 0&&clearTimeout(l),r!==void 0&&x(r.tree)){y&&A();return}S()}}),t!==null&&(l=setTimeout(()=>{c||(g=!0,E())},m),l.unref?.()),{pid:t,completion:M,terminate:E}}a($,"runCommandOnce");export{X as runAppCommand,$ as runCommandOnce};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var o=Object.defineProperty;var e=(r,n)=>o(r,"name",{value:n,configurable:!0});import{ApiError as t}from"../../api/errors.js";import{readConfig as i}from"../config.js";function a(){return i("user").runtime?.unsandboxed===!0}e(a,"isUnsandboxedMachine");function p(r){const n=r??"app:terminal";if(!(n==="app:terminal"||!a()))throw new t(403,"unsandboxed_machine",`This computer runs only your own terminal agents; ${n} runs on your hosted machine.`)}e(p,"assertGranteeRunsHere");export{p as assertGranteeRunsHere,a as isUnsandboxedMachine};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var C=Object.defineProperty;var i=(e,r)=>C(e,"name",{value:r,configurable:!0});import{existsSync as v}from"node:fs";import{
|
|
1
|
+
var C=Object.defineProperty;var i=(e,r)=>C(e,"name",{value:r,configurable:!0});import{existsSync as v}from"node:fs";import{assertGranteeRunsHere as $}from"../runtime/unsandboxed.js";import{randomUUID as N}from"node:crypto";import{join as h}from"node:path";import{getNode as R}from"../canvas/canvas.js";import{nodeAgentDir as O,nodeHome as k,nodeRunDir as H}from"../canvas/paths.js";import{appSpace as K}from"../layout.js";import{activeGrant as w,activeGrantForApp as _,grantFor as I,grantsGeneration as S}from"../grants/mirror.js";import{readConfig as B}from"../config.js";import{emitEvent as G}from"../events/emit.js";import{ensureAppSpace as F}from"./space.js";import{compileLaunch as T,launchStillGranted as M}from"./mounts.js";import{pinSandboxRuntimeHome as j}from"./runtime-home.js";import{sandboxCacheEnv as q}from"./cache-env.js";import{launcherRequest as b}from"../../launcher/client.js";import{LAUNCHER_SOCKET as y}from"../../launcher/spec.js";function U(e){return`cron-${e}`}i(U,"cronLaunchId");function ce(){if(process.platform!=="linux")return!1;if(v(y))return!0;throw new Error("root launcher socket is unavailable; refusing an unsandboxed app process")}i(ce,"appProcessSandboxed");function fe(){return A??(process.platform==="linux"&&v(y))}i(fe,"appProcessesSandboxed");let A;function le(e){A=e}i(le,"__setAppProcessesSandboxedForTest");function me(e,r,n,o=!1,t,d){const u=n===void 0?null:R(n),s=d!==void 0?d.grantId?w(d.grantId):I(e,d.sub):u?null:_(e);if(d!==void 0?!s&&!(d.grantId===null&&e==="app:terminal"):!u&&!s)throw new Error(`no active grant for ${e}`);return T({nodeId:n??"proc-cwd-check",app:e,...d!==void 0?{sub:d.sub,grantId:s?.id??null,scopes:d.scopes}:{sub:u?u.sub??"user:local":s.sub,grantId:u?u.grant_id??null:s.id,scopes:u?.scopes??null},command:["true"],env:{},...n===void 0?{withoutNode:!0}:{},...t===void 0?{}:{folder:t},isolated:u?.isolated===!0||o,auditMissing:i(()=>{},"auditMissing")}).mounts.some(({source:c})=>r===c||r.startsWith(c==="/"?"/":`${c}/`))}i(me,"cwdIsMounted");async function D(e,r="SIGKILL",n){if(!("ok"in await b({operation:"stop",pid:e,signal:r,...n===void 0?{}:{nodeId:n}})))throw new Error("launcher did not stop process")}i(D,"stopAppProcess");function W(e,r,n,o,t){return o?t??O(e,r):n}i(W,"appProcessCwd");async function pe(e){if(e.cron!==void 0&&(e.nodeId!==void 0||e.principal===void 0))throw new Error("a cron launch has a stored principal and no node");const r=e.nodeId===void 0?null:R(e.nodeId);if(e.nodeId!==void 0&&!r)throw new Error(`missing node ${e.nodeId}`);const n=r?.grantee??e.app??"app:terminal";if($(n),e.app!==void 0&&e.app!==n)throw new Error("app does not match node");const o=e.principal;let t=null;if(o!==void 0){if(t=o.grantId?w(o.grantId):I(n,o.sub),!(t===null&&o.grantId===null&&n==="app:terminal"&&!B("user").runtime?.issuer)&&(!t||t.grantee!==n||t.sub!==o.sub))throw new Error(`no active grant for ${n}`)}else if(r){if(n!=="app:terminal"&&!(r.grant_id?w(r.grant_id):I(n,r.sub??"user:local")))throw new Error(`no active grant for ${n}`)}else if(t=_(n),!t)throw new Error(`no active grant for ${n}`);await F(n);const d=e.cron===void 0?e.nodeId??`proc-${N()}`:U(e.cron.executionId),u=r?.isolated===!0||e.isolated===!0;if(u&&!r&&e.folder===void 0)throw new Error("isolated process requires a live node");const s=K(n),m=W(d,n,e.cwd,u,e.folder),c=r?k(d,n,u):u&&e.folder!==void 0?h(e.folder,"home"):s.home,L=j(Object.fromEntries(Object.entries({...e.env,...process.platform==="linux"?q(n):{},HOME:c,PI_CODING_AGENT_DIR:h(c,".pi","agent"),PWD:m,...e.nodeId===void 0&&e.cron===void 0?{}:{CRTR_API_SOCKET:h(H(d),"cli.sock")}}).filter(a=>a[1]!==void 0))),E=o!==void 0?{nodeId:d,app:n,sub:o.sub,grantId:t?.id??null,scopes:o.scopes}:{nodeId:d,app:n,sub:r?r.sub??"user:local":t.sub,grantId:r?r.grant_id??null:t.id,scopes:r?.scopes??null},P=i(()=>{const a=T({...E,command:e.command,env:L,...e.nodeId===void 0?{withoutNode:!0}:{},isolated:u,cwd:m,...e.folder===void 0?{}:{folder:e.folder},...e.cron===void 0?{}:{cron:e.cron.anchorDir===void 0?{}:{anchorDir:e.cron.anchorDir}},...e.pluginRoot===void 0?{}:{pluginRoot:e.pluginRoot},auditMissing:i(g=>G({level:"warn",event:"process.mount.missing",...e.nodeId===void 0?{}:{node_id:e.nodeId},fields:{source:g}}),"auditMissing")});if(e.pluginRoot!==void 0&&(a.pluginRoot=e.pluginRoot),e.capture===!1){if(e.nodeId===void 0||e.stdin!==void 0)throw new Error("uncaptured process requires a node and no stdin")}else a.capture={stdoutBytes:e.stdoutBytes??256*1024,stderrBytes:e.stderrBytes??256*1024,...e.stderrTail?{stderrTail:!0}:{},...e.stdin===void 0?{}:{stdin:e.stdin}};return a},"compile");let f;for(let a=0;a<2&&f===void 0;a++){const g=S(),x=P(),l=await b({operation:"launch",spec:x});if(!("pid"in l))throw new Error("launcher did not return a process id");if(S()===g||M(E,x)){f=l.pid;break}G({level:"info",event:"process.launch.regranted",...e.nodeId===void 0?{}:{node_id:e.nodeId},fields:{pid:l.pid}}),await D(l.pid,"SIGKILL").catch(()=>{})}if(f===void 0)throw new Error("grant changed during every launch attempt");const p=f;return{pid:p,get completion(){return b({operation:"wait",pid:p}).then(a=>{if(!("result"in a))throw new Error("launcher did not return process completion");return a.result})},stop:i((a="SIGKILL")=>D(p,a,e.cron===void 0?e.nodeId:d),"stop")}}i(pe,"launchAppProcess");export{le as __setAppProcessesSandboxedForTest,W as appProcessCwd,ce as appProcessSandboxed,fe as appProcessesSandboxed,U as cronLaunchId,me as cwdIsMounted,pe as launchAppProcess,D as stopAppProcess};
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
var B=Object.defineProperty;var
|
|
2
|
-
data: ${JSON.stringify(
|
|
1
|
+
var B=Object.defineProperty;var i=(e,n)=>B(e,"name",{value:n,configurable:!0});import{randomUUID as L}from"node:crypto";import{createServer as $}from"node:http";import{Router as j}from"./router.js";import{OPERATIONS as V}from"./operations.js";import{errorCodes as x}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as F,RUNTIME_VERSION_HEADER as G,minimumRuntimeRefusal as Y}from"../../api/runtime-version.js";import{crtrVersion as J}from"../../shared/crtr-version.js";import{AppPrincipalResolver as z}from"./principal.js";import{handleRunOperation as Q,integer as W}from"./handlers/runs.js";import{parseLlmRequest as K,runLlm as Z}from"../../core/llm/run.js";import{parseUsageMonth as ee,usageFor as te}from"../../core/usage/ledger.js";import{capFields as ne}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as re,MODEL_CONTEXT_BODY_MAX_BYTES as ae}from"../../core/llm/body-limits.js";import{RunError as d,runIsRunning as oe}from"../../core/runs/operations.js";import{requireRun as C}from"./router.js";import{deferHead as se}from"./deferred-head.js";import{readRunEvents as U,registerGrantStream as ie,subscribeRunEvents as ue}from"../../core/runs/events.js";import{activeGrant as ce}from"../../core/grants/mirror.js";import{releaseGrantWaiters as de}from"../../core/grants/watch.js";import{emitEvent as le}from"../../core/events/emit.js";import{runNotices as fe}from"../run-notices.js";import{handleVendorSignIn as me,isVendorSignInPath as pe}from"./vendor-signin.js";import{listAppProfiles as he}from"./handlers/app-profiles.js";import{handleTransferDownload as _e,handleTransferOperation as ye,TRANSFER_OPERATIONS as we}from"./handlers/transfer.js";import{handleQuestionOperation as ve,QUESTION_OPERATIONS as ge}from"./handlers/questions.js";import{vendorStatus as Oe}from"./handlers/vendor-status.js";import{handleMemoryOperation as Re,MEMORY_OPERATIONS as Se}from"./handlers/memory.js";import{abortOnClose as be,callerFromApp as Ee,handleProviderOperation as Ne,PROVIDER_OPERATIONS as Te,providerFailure as Ae}from"./handlers/providers.js";function Pe(e){return Object.prototype.hasOwnProperty.call(x,e)?x[e]:void 0}i(Pe,"fixedRow");function Ie(e){const n=Pe(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}i(Ie,"errorType");function Ce(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...r}=e;return r}i(Ce,"appDetails");function P(e,n,r){const u=Ce(e.details);return{error:{code:e.code,message:e.message,...u===void 0?{}:{details:u},type:Ie(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...r?{run_id:r}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...ne(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}i(P,"envelope");function Me(e,n,r,u){const s=n instanceof d?n:new d(500,"server_failure","runtime request failed");if(n instanceof d||console.error("[app-listener]",n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
|
|
2
|
+
data: ${JSON.stringify(P(s,r,u))}
|
|
3
3
|
|
|
4
|
-
`);return}e.writeHead(
|
|
4
|
+
`);return}e.writeHead(s.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":r,"X-Content-Type-Options":"nosniff",...s.code==="all_routes_exhausted"?{"Retry-After":String(s.details.retry_after_s)}:{}}),e.end(JSON.stringify(P(s,r,u)))}i(Me,"fail");function He(e){return Array.isArray(e)?e[0]:e}i(He,"headerValue");function v(e,n,r){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(r))}i(v,"json");async function A(e,n=re){const r=[];let u=0;for await(const s of e){if(u+=s.length,u>n)throw new d(400,"invalid_request","request body too large");r.push(s)}if(u)try{return JSON.parse(Buffer.concat(r).toString("utf8"))}catch{throw new d(400,"invalid_request","invalid JSON body")}}i(A,"bodyOf");function ke(e,n){const r=e.headers["last-event-id"]??n.get("after");if(Array.isArray(r))throw new d(400,"invalid_request","invalid cursor","after");return W(r??null,"after",0,Number.MAX_SAFE_INTEGER,0)}i(ke,"cursor");function De(e){return`id: ${e.sequence_number}
|
|
5
5
|
event: ${e.type}
|
|
6
6
|
data: ${JSON.stringify(e)}
|
|
7
7
|
|
|
8
|
-
`}
|
|
8
|
+
`}i(De,"frame");const xe=6e4;function Je(e,n,r,u,s,g,O,E={}){C(u,r);let f=U(r,s);if(s>f.latest_sequence)throw new d(400,"invalid_request","cursor is beyond the run","after");if(s<f.latest_sequence&&(f.earliest_sequence===null||s<f.earliest_sequence-1))throw new d(409,"stream_gap","events have expired",void 0,{earliest_sequence:f.earliest_sequence,latest_sequence:f.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":O==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),O==="sse"?n.write(`: connected
|
|
9
9
|
|
|
10
|
-
`):n.flushHeaders();let
|
|
11
|
-
data: ${JSON.stringify(
|
|
10
|
+
`):n.flushHeaders();let S=s;const a=fe().streamOpened(r);let t=!1,m=!1,y=!1,h;const _=new Promise(p=>{h=p}),w=i(()=>{t||(t=!0,a(S),clearInterval(I),clearInterval(k),R(),b(),n.off("close",w),n.off("drain",o),n.end(),h())},"end"),o=i(()=>{y=!1,N()},"onDrain"),c=i(p=>{t||n.write(O==="sse"?`event: error
|
|
11
|
+
data: ${JSON.stringify(P(p,g,r))}
|
|
12
12
|
|
|
13
|
-
`:`${JSON.stringify({type:"error",...
|
|
14
|
-
`),
|
|
15
|
-
`),
|
|
13
|
+
`:`${JSON.stringify({type:"error",...P(p,g,r)})}
|
|
14
|
+
`),w()},"error"),N=i(()=>{if(!(t||m)){m=!0;try{if(!T()){l();return}if(C(u,r),f=U(r,S),f.backlog_count>1e3||f.backlog_bytes>32*1024*1024){c(new d(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:S}));return}for(const p of f.events){if(y)break;y=!n.write(O==="sse"?De(p):`${JSON.stringify(p)}
|
|
15
|
+
`),S=p.sequence_number}}catch(p){c(p instanceof d?p:new d(500,"server_failure","stream failed"))}finally{m=!1}}},"flush"),l=i(()=>c(new d(401,"grant_revoked","grant is not active")),"revoked"),T=i(()=>"class"in u||ce(u.grant)!==null,"grantActive"),R=ue(r,N),b="class"in u?()=>{}:ie(u.grant,l),I=setInterval(()=>{!t&&!y&&O==="sse"&&n.write(`: keepalive
|
|
16
16
|
|
|
17
|
-
`)},15e3),q=
|
|
17
|
+
`)},15e3),q=E.idleMs??xe,X=E.isRunning??oe;let M=Date.now(),H=S;const k=setInterval(()=>{if(t||O!=="sse")return;const p=Date.now();let D=!0;try{D=X(r)}catch{}if(D||S!==H){M=p,H=S;return}p-M<q||y||(n.write(`event: idle
|
|
18
18
|
data: {}
|
|
19
19
|
|
|
20
|
-
`),
|
|
20
|
+
`),w())},E.idleCheckMs??1e3);return k.unref(),n.on("drain",o),n.on("close",w),N(),_}i(Je,"streamRunEvents");function mt(e,n,r,u={}){const s=e.listener;if(!e.issuer||!e.audience||!s?.address||!Number.isInteger(s.port)||s.port<1||s.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(s.address))throw new Error("runtime listener must bind a specific address");const g=new z(e.issuer,e.audience,r,e.jwksUrl),O=new j,E=new Map;for(const a of V.filter(t=>t.stage!==void 0)){const t=i(()=>({status:500}),"handler");O.register({method:a.method,pattern:a.path,handler:t}),E.set(t,a)}const f=$((a,t)=>{(async()=>{se(a,t,u.deferHead);const m=L();t.setHeader("Request-Id",m),t.setHeader(G,J());let y;try{const h=Y(He(a.headers[F.toLowerCase()]),J());if(h!==null)throw h.status===400?new d(400,h.code,h.message,h.param):new d(426,h.code,h.message,void 0,h.details);const _=new URL(a.url??"/","http://runtime.local");if(pe(_.pathname)){await me(a,t,_,g);return}if((a.method!=="GET"||_.pathname!=="/v1/health")&&(!g.isReady()||!n()))throw new d(503,"runtime_starting","runtime_starting");const w=O.match(a.method??"GET",_.pathname);if(!w)throw new d(404,"not_found","unknown operation");const o=E.get(w.handler);if(!o)throw new d(404,"not_found","unknown operation");if(y=w.params.run_id,o.name==="health"){v(t,200,{status:"ok",api_version:"2026-09-23"});return}if(o.name==="runtime.sync-notice"){if(!await g.verifySyncNotice(a.headers.authorization)){t.writeHead(204),t.end();return}le({level:"info",event:"daemon.grants.sync_notice",fields:{request_id:m}});try{await r({fresh:!0})}catch{throw new d(503,"service_unavailable","grant sync failed")}t.writeHead(204),t.end();return}if(o.name==="runtime.vendor-status"){await g.verifyDirectoryRead(a.headers.authorization,"vendor_status"),v(t,200,Oe());return}const c=await g.resolve(a.headers.authorization,o.scope);if(t.setHeader("Request-Id",m),y&&C(c,y,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(o.name),o.name==="run.delete"),o.stream==="bytes"){await _e(a,t,c,_.searchParams);return}if(o.stream){Je(a,t,y,c,ke(a,_.searchParams),m,"sse",u.stream);return}if(o.name==="llm"){const l=K(await A(a,ae)),T=new AbortController,R=i(()=>T.abort(),"abort");t.on("close",R);try{const b=await Z(l,T.signal,l.stream?I=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(I)}
|
|
21
21
|
|
|
22
|
-
`)}:void 0,void 0,{grantee:
|
|
22
|
+
`)}:void 0,void 0,{grantee:c.grantee,spend_cap:c.spend_cap});l.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
|
|
23
23
|
|
|
24
|
-
`)):
|
|
24
|
+
`)):v(t,200,b.completion)}finally{t.off("close",R)}return}if(o.name==="usage.get"){v(t,200,te(c.grantee,c.spend_cap,ee(_.searchParams.get("month"))));return}if(o.name==="profile.list"){v(t,200,he(c));return}if(ge.has(o.name)){const l=await ve(o.name,c,w.params.question_id,o.method==="POST"?await A(a):void 0);v(t,l.status,l.body);return}if(Te.has(o.name)){const{signal:l,release:T}=be(t);try{const R=await Ne(o.name,Ee(c),{params:w.params,body:o.method==="POST"?await A(a):void 0,query:_.searchParams},{requestId:m,signal:l});v(t,R.status,R.body)}catch(R){const b=Ae(R,m,!0);t.writeHead(b.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...b.headers}),t.end(JSON.stringify(b.body))}finally{T()}return}if(Se.has(o.name)){const l=Re(o.name,{class:"app",app:c.grantee,scopes:c.resolved},_.searchParams);v(t,l.status,l.body);return}if(we.has(o.name)){const l=await ye(o,c,w.params,_.searchParams,await A(a));v(t,l.status,l.body);return}const N=await Q(o,c,y,_.searchParams,await A(a),a.headers["idempotency-key"]);v(t,N.status,N.body)}catch(h){Me(t,h,m,y)}})()});return{ready:new Promise((a,t)=>{f.once("error",t),f.listen(s.port,s.address,()=>{f.off("error",t),a()})}),close:i(async()=>{g.close(),de(),f.closeAllConnections(),await new Promise((a,t)=>f.close(m=>m?t(m):a()))},"close")}}i(mt,"createAppListener");export{xe as RUN_STREAM_IDLE_MS,mt as createAppListener,ke as cursor,Je as streamRunEvents};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { type StartedFlow } from '../../core/model-auth/flows.js';
|
|
2
3
|
import type { AppPrincipalResolver } from './principal.js';
|
|
3
4
|
export declare const VENDOR_SIGNIN_PATH = "/signin/vendor";
|
|
4
5
|
export declare function isVendorSignInPath(pathname: string): boolean;
|
|
6
|
+
export declare function anthropicPage(flow: StartedFlow, nonce: string, returnTo: string, handoff: string): string;
|
|
7
|
+
export declare function openaiPage(flow: StartedFlow, nonce: string, returnTo: string, handoff: string, now?: number): string;
|
|
5
8
|
/** Serve one `/signin/vendor*` request; errors propagate as RunErrors so the
|
|
6
9
|
* listener answers them with contract 2's envelope (RT-LLM-53). */
|
|
7
10
|
export declare function handleVendorSignIn(req: IncomingMessage, res: ServerResponse, url: URL, principal: AppPrincipalResolver): Promise<void>;
|