@north-light/cli 0.3.591 → 0.3.592

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.
@@ -122,6 +122,11 @@ export declare class DaemonExitedBeforeReadyError extends Error {
122
122
  readonly stderrTail: string;
123
123
  constructor(message: string, stderrTail: string);
124
124
  }
125
+ /** The daemon's launch command could not be resolved, so no child was ever
126
+ * started — a standing failure that waiting longer cannot fix. */
127
+ export declare class DaemonLaunchError extends Error {
128
+ constructor(cause: unknown);
129
+ }
125
130
  /** A live daemon has bound its repair socket but cannot serve normal work
126
131
  * until its corpus migration succeeds. */
127
132
  export declare class DaemonStartupBlockedError extends Error {
@@ -200,7 +205,8 @@ export interface EnsureDaemonDeps {
200
205
  spawnDaemon?: () => Promise<SpawnDaemonResult>;
201
206
  now?: () => number;
202
207
  }
203
- /** Why the daemon this process autostarted died before becoming ready, or
208
+ /** Why the daemon this process autostarted died before becoming ready, or why
209
+ * its launch command could not be resolved (no daemon was started), or
204
210
  * undefined while it is starting, running, or exited cleanly (a clean exit may
205
211
  * be a lost singleton race, so only a non-zero/signal exit counts). A client
206
212
  * waiting for that daemon polls this so a startup failure ends the wait now
@@ -210,6 +216,7 @@ export declare function daemonAutostartStartupFailure(): string | undefined;
210
216
  * autostart is suppressed for this invocation, while `crtr sys join` holds
211
217
  * the join lock (see core/directory/join-lock.ts), while an attempt is in flight,
212
218
  * or while backing off from a recent failure.
213
- * Silently swallows spawn errors (the canvas still works without the daemon;
214
- * nodes just won't be auto-revived). */
219
+ * Does not throw: a spawn error is recorded, and a launch or early-exit failure
220
+ * is reported through `daemonAutostartStartupFailure()` so a waiting client
221
+ * fails with it. */
215
222
  export declare function ensureDaemon(deps?: EnsureDaemonDeps): void;
@@ -1,3 +1,3 @@
1
- var X=Object.defineProperty;var o=(e,t)=>X(e,"name",{value:t,configurable:!0});import{spawn as B,execFileSync as q}from"node:child_process";import{createRequire as V}from"node:module";import{dirname as k,join as D}from"node:path";import{fileURLToPath as Q,pathToFileURL as I}from"node:url";import{existsSync as H,lstatSync as Z,mkdirSync as ee,openSync as U,closeSync as j,fstatSync as K,readSync as te}from"node:fs";import{crtrHome as g}from"../core/canvas/paths.js";import{listProcessTable as re}from"../core/canvas/pid.js";import{bindDaemonEventSource as ne,eventSource as oe}from"../core/events/source.js";import{hostExecPath as O}from"../core/runtime/branded-host.js";import{isDaemonRunning as ae,readPidfile as E,isPidAlive as f}from"./pidfile.js";import{DAEMON_STARTUP_WINDOW_MS as G}from"./startup-policy.js";import{exclusiveLockOwnerPid as ie}from"../core/exclusive-lock.js";import{onDiskMigrationLockPath as se}from"../core/canvas/paths.js";import{envNoDaemonAutostart as ce}from"../shared/env.js";import{awaitJoinLockRelease as ue,joinInProgress as le,joinInProgressError as de,joinLockHolder as pe}from"../core/directory/join-lock.js";import{waitForDaemonAvailability as fe}from"../api/index.js";import{localClient as me}from"../api/node-transport.js";const he=["CRTR_NODE_ID","CRTR_KIND","CRTR_MODE","CRTR_LIFECYCLE","CRTR_NODE_CWD","CRTR_CONTEXT_DIR","CRTR_CYCLES","CRTR_PROFILE_ID","CRTR_PARENT_NODE_ID","CRTR_FRONT_DOOR","TMUX","TMUX_PANE","CRTR_BROKER_ENGINE","CRTR_PI_BINARY","NODE_OPTIONS"];function M(){const e={...process.env};for(const t of he)delete e[t];return e}o(M,"sanitizedDaemonEnv");function we(){for(let e=k(Q(import.meta.url));;e=k(e)){if(H(D(e,"bin","runtime-generation-manifest.mjs")))return e;if(k(e)===e)throw new Error(`cannot locate the crouter generation root from ${import.meta.url}`)}}o(we,"generationRoot");async function A(){const e=we(),t=I(D(e,"bin","runtime-generation-manifest.mjs")).href,{runtimeHome:r}=await import(t);try{Z(D(r(),"selected"))}catch(s){if(s.code!=="ENOENT")throw s;return["--conditions","crtr-src","--import",I(V(import.meta.url).resolve("tsx/esm")).href,D(e,"src","daemon","crtrd-cli.ts")]}const n=I(D(e,"bin","runtime-selector.mjs")).href,{tryResolveRuntime:a}=await import(n),c=a("daemon");if(!c.ok)throw new Error(`cannot resolve the selected runtime generation's daemon entry: ${c.error}`);return[c.runtime.entryPath]}o(A,"resolveDaemonLaunch");function _e(e,t){return e.includes(` --canvas-home ${t} `)||e.endsWith(` --canvas-home ${t}`)}o(_e,"daemonCommandDeclaresCanvasHome");function De(){const e=re();if(e===null)return[];const t=g(),r=[];for(const{pid:n,command:a}of e)a.includes("daemon/crtrd")&&_e(a,t)&&n!==process.pid&&r.push(n);return r}o(De,"findDaemonPids");function Ee(e){const t=De().filter(n=>n!==e);for(const n of t)try{process.kill(n,"SIGTERM")}catch{}if(t.length===0)return t;const r=Date.now()+2e3;for(;Date.now()<r&&t.some(n=>f(n));)q("sleep",["0.1"]);for(const n of t)if(f(n))try{process.kill(n,"SIGKILL")}catch{}return t}o(Ee,"sweepStrayDaemons");async function Ve(){const e=process.execve;if(e===void 0)throw new Error(`this Node.js ${process.version} runtime cannot replace PID 1 with the successor daemon`);const t=O(),r=await A();return e(t,[t,...r,"--canvas-home",g()],M())}o(Ve,"execDaemon");async function R(e){const t=await A(),r=B(O(),[...t,...e],{stdio:["ignore","pipe","pipe"],env:M()});let n="",a="";r.stdout.setEncoding("utf8").on("data",u=>{n+=u}),r.stderr.setEncoding("utf8").on("data",u=>{a+=u});const c=await new Promise((u,m)=>{r.once("error",m),r.once("close",p=>u(p))});if(c!==0)throw new Error(a.trim()||`the ${e[0]} process exited with code ${c}`);const s=n.trim().split(`
2
- `).at(-1)??"";return JSON.parse(s)}o(R,"runDaemonEntry");function Qe(){return R(["export-sqlite"])}o(Qe,"runDaemonExportSqlite");function Ze(e,t){return R(["import-dump",e,t])}o(Ze,"runDaemonImportDump");function et(e){return R(["import-dump",e,"bodies"])}o(et,"runDaemonBodiesDump");function tt(){return R(["import-dump","","install-id"])}o(tt,"runDaemonInstallId");const xe=3*6e4,z=2e4,N=10,P=1e3;async function L(e=P){const t=Math.min(e,P);return me({autostart:!1,timeoutMs:t}).probeHealthz(t)}o(L,"probeDaemonHealth");function ye(e){const t=e instanceof Error?e.message:String(e);if(typeof e!="object"||e===null)return t;const r=e.code,n=e.cause,a=typeof r=="string"?r:n?.code;return typeof a=="string"&&!t.includes(a)?`${t}: ${a}`:t}o(ye,"probeErrorMessage");async function J(e=P){const t=await L(e);if(t.startup_phase==="blocked"){const r=E()??process.pid;throw new v(r,t.startup_blocked??"daemon startup is blocked")}if(!t.ok)throw new Error(t.startup_blocked??"crtrd health check reported not ready")}o(J,"probeDaemonServing");async function rt(){try{return await J(),!0}catch{return!1}}o(rt,"isDaemonServing");function $(e){return new Promise(t=>{setTimeout(t,e)})}o($,"sleepMs");class C extends Error{static{o(this,"DaemonExitedBeforeReadyError")}stderrTail;constructor(t,r){super(r===""?t:`${t}
3
- ${r}`),this.name="DaemonExitedBeforeReadyError",this.stderrTail=r}}class v extends Error{static{o(this,"DaemonStartupBlockedError")}constructor(t,r){super(`daemon ${t} is running but normal startup is blocked: ${r}`),this.name="DaemonStartupBlockedError"}}class F extends Error{static{o(this,"DaemonNotPreparedError")}constructor(t,r){super(r===void 0?`daemon ${t} is serving but did not report the prepared startup phase`:`daemon ${t} reported startup phase ${r}, not prepared`),this.name="DaemonNotPreparedError"}}async function ge(e,t=G,r={},n="ready"){const a=r.readPidfile??E,c=r.isPidAlive??f,s=r.childExited,u=r.isDaemonServing,m=r.probeDaemonServing??(async w=>{if(u!==void 0){if(await u())return;throw new Error("crtrd health check reported not ready")}await J(w)}),p=r.probeDaemonHealth??L,x=r.sleepMs??$,y=r.now??Date.now;let i=null,l=null;return await fe({windowMs:t,pollIntervalMs:N,now:y,sleep:x,retry:o(w=>w!==l,"retry"),probe:o(async w=>{const h=a();if(h===null||!c(h)){const d=s?.();throw d!=null&&(d.signal!==null?l=new C(`daemon ${e} exited before becoming ready (by ${d.signal})`,r.childStderrTail?.()??""):d.code!==0&&(l=new C(`daemon ${e} exited before becoming ready (with exit code ${d.code??"?"})`,r.childStderrTail?.()??"")),l!==null)?l:new Error(`daemon ${e} has no live pidfile owner`)}if(n==="ready"){try{await m(w)}catch(d){throw d instanceof v&&(l=d),d}i=h;return}const _=await p(w);if(_.startup_phase==="prepared"){i=h;return}throw _.startup_phase==="blocked"?(l=new v(h,_.startup_blocked??"on-disk migration is blocked"),l):_.startup_phase==="ready"?(l=new F(h,_.startup_phase),l):new Error(`daemon ${h} is ${_.startup_phase??"not reporting a startup phase"}; waiting for prepared`)},"probe")}),i===e?null:i}o(ge,"verifyDaemonStartup");async function Se(e,t=z,r={}){const n=r.readPidfile??E,a=r.isPidAlive??f,c=r.sleepMs??$,s=r.now??Date.now,u=s()+t;for(;s()<=u;){if(!a(e)&&n()!==e)return;await c(N)}throw new Error(`daemon ${e} did not exit and release its pidfile within ${t}ms`)}o(Se,"waitForDaemonExit");const Re=2e3,ve=5e3;async function Y(e,t=z,r={}){const n=r.isPidAlive??f,a=r.sleepMs??$,c=r.now??Date.now;try{process.kill(e,"SIGTERM")}catch{}try{return await Se(e,t,r),"terminated"}catch{}try{process.kill(e,"SIGKILL")}catch{}const s=c()+Re;for(;c()<=s;){if(!n(e))return"killed";await a(N)}throw new Error(`daemon ${e} survived both SIGTERM and SIGKILL`)}o(Y,"stopDaemonProcess");async function nt(){const e=E(),t=e!==null&&f(e),r=t&&await Y(e)==="killed",n=Ee(null);return{stopped:t||n.length>0,...t?{pid:e}:{},strays_killed:n,force_killed:r}}o(nt,"stopAllDaemons");function Te(e,t,r=2048){let n;try{n=U(e,"r");const{size:a}=K(n),c=Math.max(t,a-r),s=a-c;if(s<=0)return"";const u=Buffer.alloc(s);return te(n,u,0,s,c),u.toString("utf8").trim()}catch{return""}finally{if(n!==void 0)try{j(n)}catch{}}}o(Te,"readLogTailFrom");async function be(e={}){if(process.platform==="linux"&&H("/run/crtr/launcher.sock")&&!f(E()??-1))throw new Error("crtrd is managed by the root launcher; it cannot self-daemonize");const t=e.targetPhase??"ready",r=E();if(r!==null&&f(r))try{const i=await L();if(t==="prepared"){if(i.startup_phase==="prepared")return{started:!1,existing_pid:r,startup_phase:i.startup_phase};throw new F(r,i.startup_phase)}return i.ok?{started:!1,existing_pid:r,...i.startup_phase===void 0?{}:{startup_phase:i.startup_phase}}:{started:!1,existing_pid:r,running:!0,serving:!1,probe_error:i.startup_blocked??"crtrd health check reported not ready",...i.startup_phase===void 0?{}:{startup_phase:i.startup_phase}}}catch(i){if(i instanceof F)throw i;return{started:!1,existing_pid:r,running:!0,serving:!1,probe_error:ye(i)}}ee(g(),{recursive:!0});const n=await A();e.waitForJoinLock===!0&&await ue(xe);const a=pe();if(a!==null&&a!==process.pid&&e.ignoreJoinLock!==!0)throw de(a);const c=D(g(),"crtrd.err"),s=U(c,"a"),u=(()=>{try{return K(s).size}catch{return 0}})();oe()===void 0&&ne();const m=B(O(),[...n,"--canvas-home",g(),...t==="prepared"?["--prepare"]:[]],{detached:!0,stdio:["ignore",s,s],env:M()});j(s);const p=m.pid;if(p===void 0)throw new Error("daemon spawn did not return a pid");let x=null;m.once("exit",(i,l)=>{x={code:i,signal:l}}),m.unref();let y;try{y=await ge(p,G,{childExited:o(()=>x,"childExited"),childStderrTail:o(()=>Te(c,u),"childStderrTail")},t)}catch(i){const l=ie(se())===p;if(x===null&&!l&&!(i instanceof v))try{await Y(p,ve)}catch{}throw i}return y!==null?{started:!1,existing_pid:y,startup_phase:t}:{started:!0,pid:p,startup_phase:t}}o(be,"spawnDaemon");function ke(){return ce()||process.argv.includes("--no-autostart")}o(ke,"autostartDisabled");const Ie=5e3,Oe=6e4;let W=!1,S,T=0,b=0;function Me(e){return Math.min(Ie*2**(e-1),Oe)}o(Me,"autostartBackoffMs");function Ae(){S=void 0,T=0,b=0}o(Ae,"resetDaemonAutostart");function ot(){return S}o(ot,"daemonAutostartStartupFailure");function at(e={}){const t=e.isDaemonRunning??ae,r=e.spawnDaemon??be,n=e.now??Date.now;if(!ke()&&!le()&&!W&&!(n()<b)){if(t()){Ae();return}S=void 0,W=!0,r().then(()=>{S=void 0,T=0,b=0},a=>{a instanceof C&&(S=a.message),T+=1,b=n()+Me(T)}).finally(()=>{W=!1})}}o(at,"ensureDaemon");export{he as DAEMON_ENV_STRIP_KEYS,C as DaemonExitedBeforeReadyError,F as DaemonNotPreparedError,v as DaemonStartupBlockedError,ke as autostartDisabled,ot as daemonAutostartStartupFailure,_e as daemonCommandDeclaresCanvasHome,at as ensureDaemon,Ve as execDaemon,De as findDaemonPids,rt as isDaemonServing,L as probeDaemonHealth,J as probeDaemonServing,ye as probeErrorMessage,Ae as resetDaemonAutostart,et as runDaemonBodiesDump,Qe as runDaemonExportSqlite,Ze as runDaemonImportDump,tt as runDaemonInstallId,M as sanitizedDaemonEnv,be as spawnDaemon,nt as stopAllDaemons,Y as stopDaemonProcess,Ee as sweepStrayDaemons,ge as verifyDaemonStartup,Se as waitForDaemonExit};
1
+ var q=Object.defineProperty;var o=(e,t)=>q(e,"name",{value:t,configurable:!0});import{spawn as B,execFileSync as V}from"node:child_process";import{createRequire as Q}from"node:module";import{dirname as k,join as E}from"node:path";import{fileURLToPath as Z,pathToFileURL as I}from"node:url";import{existsSync as H,lstatSync as ee,mkdirSync as te,openSync as U,closeSync as j,fstatSync as K,readSync as re}from"node:fs";import{crtrHome as g}from"../core/canvas/paths.js";import{listProcessTable as ne}from"../core/canvas/pid.js";import{bindDaemonEventSource as oe,eventSource as ae}from"../core/events/source.js";import{hostExecPath as O}from"../core/runtime/branded-host.js";import{isDaemonRunning as ie,readPidfile as D,isPidAlive as f}from"./pidfile.js";import{DAEMON_STARTUP_WINDOW_MS as G}from"./startup-policy.js";import{exclusiveLockOwnerPid as se}from"../core/exclusive-lock.js";import{onDiskMigrationLockPath as ce}from"../core/canvas/paths.js";import{envNoDaemonAutostart as ue}from"../shared/env.js";import{awaitJoinLockRelease as le,joinInProgress as de,joinInProgressError as pe,joinLockHolder as fe}from"../core/directory/join-lock.js";import{waitForDaemonAvailability as me}from"../api/index.js";import{localClient as he}from"../api/node-transport.js";const we=["CRTR_NODE_ID","CRTR_KIND","CRTR_MODE","CRTR_LIFECYCLE","CRTR_NODE_CWD","CRTR_CONTEXT_DIR","CRTR_CYCLES","CRTR_PROFILE_ID","CRTR_PARENT_NODE_ID","CRTR_FRONT_DOOR","TMUX","TMUX_PANE","CRTR_BROKER_ENGINE","CRTR_PI_BINARY","NODE_OPTIONS"];function M(){const e={...process.env};for(const t of we)delete e[t];return e}o(M,"sanitizedDaemonEnv");function _e(){for(let e=k(Z(import.meta.url));;e=k(e)){if(H(E(e,"bin","runtime-generation-manifest.mjs")))return e;if(k(e)===e)throw new Error(`cannot locate the crouter generation root from ${import.meta.url}`)}}o(_e,"generationRoot");async function A(){const e=_e(),t=I(E(e,"bin","runtime-generation-manifest.mjs")).href,{runtimeHome:r}=await import(t);try{ee(E(r(),"selected"))}catch(s){if(s.code!=="ENOENT")throw s;return["--conditions","crtr-src","--import",I(Q(import.meta.url).resolve("tsx/esm")).href,E(e,"src","daemon","crtrd-cli.ts")]}const n=I(E(e,"bin","runtime-selector.mjs")).href,{tryResolveRuntime:a}=await import(n),c=a("daemon");if(!c.ok)throw new Error(`cannot resolve the selected runtime generation's daemon entry: ${c.error}`);return[c.runtime.entryPath]}o(A,"resolveDaemonLaunch");function Ee(e,t){return e.includes(` --canvas-home ${t} `)||e.endsWith(` --canvas-home ${t}`)}o(Ee,"daemonCommandDeclaresCanvasHome");function De(){const e=ne();if(e===null)return[];const t=g(),r=[];for(const{pid:n,command:a}of e)a.includes("daemon/crtrd")&&Ee(a,t)&&n!==process.pid&&r.push(n);return r}o(De,"findDaemonPids");function xe(e){const t=De().filter(n=>n!==e);for(const n of t)try{process.kill(n,"SIGTERM")}catch{}if(t.length===0)return t;const r=Date.now()+2e3;for(;Date.now()<r&&t.some(n=>f(n));)V("sleep",["0.1"]);for(const n of t)if(f(n))try{process.kill(n,"SIGKILL")}catch{}return t}o(xe,"sweepStrayDaemons");async function Qe(){const e=process.execve;if(e===void 0)throw new Error(`this Node.js ${process.version} runtime cannot replace PID 1 with the successor daemon`);const t=O(),r=await A();return e(t,[t,...r,"--canvas-home",g()],M())}o(Qe,"execDaemon");async function R(e){const t=await A(),r=B(O(),[...t,...e],{stdio:["ignore","pipe","pipe"],env:M()});let n="",a="";r.stdout.setEncoding("utf8").on("data",u=>{n+=u}),r.stderr.setEncoding("utf8").on("data",u=>{a+=u});const c=await new Promise((u,m)=>{r.once("error",m),r.once("close",p=>u(p))});if(c!==0)throw new Error(a.trim()||`the ${e[0]} process exited with code ${c}`);const s=n.trim().split(`
2
+ `).at(-1)??"";return JSON.parse(s)}o(R,"runDaemonEntry");function Ze(){return R(["export-sqlite"])}o(Ze,"runDaemonExportSqlite");function et(e,t){return R(["import-dump",e,t])}o(et,"runDaemonImportDump");function tt(e){return R(["import-dump",e,"bodies"])}o(tt,"runDaemonBodiesDump");function rt(){return R(["import-dump","","install-id"])}o(rt,"runDaemonInstallId");const ye=3*6e4,z=2e4,N=10,P=1e3;async function L(e=P){const t=Math.min(e,P);return he({autostart:!1,timeoutMs:t}).probeHealthz(t)}o(L,"probeDaemonHealth");function ge(e){const t=e instanceof Error?e.message:String(e);if(typeof e!="object"||e===null)return t;const r=e.code,n=e.cause,a=typeof r=="string"?r:n?.code;return typeof a=="string"&&!t.includes(a)?`${t}: ${a}`:t}o(ge,"probeErrorMessage");async function J(e=P){const t=await L(e);if(t.startup_phase==="blocked"){const r=D()??process.pid;throw new v(r,t.startup_blocked??"daemon startup is blocked")}if(!t.ok)throw new Error(t.startup_blocked??"crtrd health check reported not ready")}o(J,"probeDaemonServing");async function nt(){try{return await J(),!0}catch{return!1}}o(nt,"isDaemonServing");function $(e){return new Promise(t=>{setTimeout(t,e)})}o($,"sleepMs");class C extends Error{static{o(this,"DaemonExitedBeforeReadyError")}stderrTail;constructor(t,r){super(r===""?t:`${t}
3
+ ${r}`),this.name="DaemonExitedBeforeReadyError",this.stderrTail=r}}class Y extends Error{static{o(this,"DaemonLaunchError")}constructor(t){super(t instanceof Error?t.message:String(t),{cause:t}),this.name="DaemonLaunchError"}}class v extends Error{static{o(this,"DaemonStartupBlockedError")}constructor(t,r){super(`daemon ${t} is running but normal startup is blocked: ${r}`),this.name="DaemonStartupBlockedError"}}class F extends Error{static{o(this,"DaemonNotPreparedError")}constructor(t,r){super(r===void 0?`daemon ${t} is serving but did not report the prepared startup phase`:`daemon ${t} reported startup phase ${r}, not prepared`),this.name="DaemonNotPreparedError"}}async function Se(e,t=G,r={},n="ready"){const a=r.readPidfile??D,c=r.isPidAlive??f,s=r.childExited,u=r.isDaemonServing,m=r.probeDaemonServing??(async w=>{if(u!==void 0){if(await u())return;throw new Error("crtrd health check reported not ready")}await J(w)}),p=r.probeDaemonHealth??L,x=r.sleepMs??$,y=r.now??Date.now;let i=null,l=null;return await me({windowMs:t,pollIntervalMs:N,now:y,sleep:x,retry:o(w=>w!==l,"retry"),probe:o(async w=>{const h=a();if(h===null||!c(h)){const d=s?.();throw d!=null&&(d.signal!==null?l=new C(`daemon ${e} exited before becoming ready (by ${d.signal})`,r.childStderrTail?.()??""):d.code!==0&&(l=new C(`daemon ${e} exited before becoming ready (with exit code ${d.code??"?"})`,r.childStderrTail?.()??"")),l!==null)?l:new Error(`daemon ${e} has no live pidfile owner`)}if(n==="ready"){try{await m(w)}catch(d){throw d instanceof v&&(l=d),d}i=h;return}const _=await p(w);if(_.startup_phase==="prepared"){i=h;return}throw _.startup_phase==="blocked"?(l=new v(h,_.startup_blocked??"on-disk migration is blocked"),l):_.startup_phase==="ready"?(l=new F(h,_.startup_phase),l):new Error(`daemon ${h} is ${_.startup_phase??"not reporting a startup phase"}; waiting for prepared`)},"probe")}),i===e?null:i}o(Se,"verifyDaemonStartup");async function Re(e,t=z,r={}){const n=r.readPidfile??D,a=r.isPidAlive??f,c=r.sleepMs??$,s=r.now??Date.now,u=s()+t;for(;s()<=u;){if(!a(e)&&n()!==e)return;await c(N)}throw new Error(`daemon ${e} did not exit and release its pidfile within ${t}ms`)}o(Re,"waitForDaemonExit");const ve=2e3,Te=5e3;async function X(e,t=z,r={}){const n=r.isPidAlive??f,a=r.sleepMs??$,c=r.now??Date.now;try{process.kill(e,"SIGTERM")}catch{}try{return await Re(e,t,r),"terminated"}catch{}try{process.kill(e,"SIGKILL")}catch{}const s=c()+ve;for(;c()<=s;){if(!n(e))return"killed";await a(N)}throw new Error(`daemon ${e} survived both SIGTERM and SIGKILL`)}o(X,"stopDaemonProcess");async function ot(){const e=D(),t=e!==null&&f(e),r=t&&await X(e)==="killed",n=xe(null);return{stopped:t||n.length>0,...t?{pid:e}:{},strays_killed:n,force_killed:r}}o(ot,"stopAllDaemons");function be(e,t,r=2048){let n;try{n=U(e,"r");const{size:a}=K(n),c=Math.max(t,a-r),s=a-c;if(s<=0)return"";const u=Buffer.alloc(s);return re(n,u,0,s,c),u.toString("utf8").trim()}catch{return""}finally{if(n!==void 0)try{j(n)}catch{}}}o(be,"readLogTailFrom");async function ke(e={}){if(process.platform==="linux"&&H("/run/crtr/launcher.sock")&&!f(D()??-1))throw new Error("crtrd is managed by the root launcher; it cannot self-daemonize");const t=e.targetPhase??"ready",r=D();if(r!==null&&f(r))try{const i=await L();if(t==="prepared"){if(i.startup_phase==="prepared")return{started:!1,existing_pid:r,startup_phase:i.startup_phase};throw new F(r,i.startup_phase)}return i.ok?{started:!1,existing_pid:r,...i.startup_phase===void 0?{}:{startup_phase:i.startup_phase}}:{started:!1,existing_pid:r,running:!0,serving:!1,probe_error:i.startup_blocked??"crtrd health check reported not ready",...i.startup_phase===void 0?{}:{startup_phase:i.startup_phase}}}catch(i){if(i instanceof F)throw i;return{started:!1,existing_pid:r,running:!0,serving:!1,probe_error:ge(i)}}te(g(),{recursive:!0});const n=await A().catch(i=>{throw new Y(i)});e.waitForJoinLock===!0&&await le(ye);const a=fe();if(a!==null&&a!==process.pid&&e.ignoreJoinLock!==!0)throw pe(a);const c=E(g(),"crtrd.err"),s=U(c,"a"),u=(()=>{try{return K(s).size}catch{return 0}})();ae()===void 0&&oe();const m=B(O(),[...n,"--canvas-home",g(),...t==="prepared"?["--prepare"]:[]],{detached:!0,stdio:["ignore",s,s],env:M()});j(s);const p=m.pid;if(p===void 0)throw new Error("daemon spawn did not return a pid");let x=null;m.once("exit",(i,l)=>{x={code:i,signal:l}}),m.unref();let y;try{y=await Se(p,G,{childExited:o(()=>x,"childExited"),childStderrTail:o(()=>be(c,u),"childStderrTail")},t)}catch(i){const l=se(ce())===p;if(x===null&&!l&&!(i instanceof v))try{await X(p,Te)}catch{}throw i}return y!==null?{started:!1,existing_pid:y,startup_phase:t}:{started:!0,pid:p,startup_phase:t}}o(ke,"spawnDaemon");function Ie(){return ue()||process.argv.includes("--no-autostart")}o(Ie,"autostartDisabled");const Oe=5e3,Me=6e4;let W=!1,S,T=0,b=0;function Ae(e){return Math.min(Oe*2**(e-1),Me)}o(Ae,"autostartBackoffMs");function Ne(){S=void 0,T=0,b=0}o(Ne,"resetDaemonAutostart");function at(){return S}o(at,"daemonAutostartStartupFailure");function it(e={}){const t=e.isDaemonRunning??ie,r=e.spawnDaemon??ke,n=e.now??Date.now;if(!Ie()&&!de()&&!W&&!(n()<b)){if(t()){Ne();return}S=void 0,W=!0,r().then(()=>{S=void 0,T=0,b=0},a=>{(a instanceof C||a instanceof Y)&&(S=a.message),T+=1,b=n()+Ae(T)}).finally(()=>{W=!1})}}o(it,"ensureDaemon");export{we as DAEMON_ENV_STRIP_KEYS,C as DaemonExitedBeforeReadyError,Y as DaemonLaunchError,F as DaemonNotPreparedError,v as DaemonStartupBlockedError,Ie as autostartDisabled,at as daemonAutostartStartupFailure,Ee as daemonCommandDeclaresCanvasHome,it as ensureDaemon,Qe as execDaemon,De as findDaemonPids,nt as isDaemonServing,L as probeDaemonHealth,J as probeDaemonServing,ge as probeErrorMessage,Ne as resetDaemonAutostart,tt as runDaemonBodiesDump,Ze as runDaemonExportSqlite,et as runDaemonImportDump,rt as runDaemonInstallId,M as sanitizedDaemonEnv,ke as spawnDaemon,ot as stopAllDaemons,X as stopDaemonProcess,xe as sweepStrayDaemons,Se as verifyDaemonStartup,Re as waitForDaemonExit};
@@ -1,4 +1,5 @@
1
1
  import { type NodeRow } from '../../../core/canvas/index.js';
2
+ import { type Execution } from '../../../core/canvas/executions.js';
2
3
  import type { FleetRegistry } from '../../../core/runtime/fleet.js';
3
4
  import { type FrozenCounts } from './freeze-lane.js';
4
5
  export interface NodeLifecycleContext {
@@ -9,7 +10,10 @@ export interface NodeLifecycleContext {
9
10
  }
10
11
  /** How long an active row with no engine may be a birth in flight rather than stranded. */
11
12
  export declare const LAUNCH_GRACE_MS = 120000;
12
- export declare function isLaunchInFlight(row: Pick<NodeRow, 'created' | 'launched_at'>, now: number): boolean;
13
+ /** A birth is in flight while it is younger than the grace and its launch has not already ended. A launch begins its
14
+ * execution before it records `launched_at`, so the node's latest execution is the one this birth made: once it is
15
+ * `exited` (a broker that died `lost`), the launch reached the ledger and failed, and the grace has nothing to protect. */
16
+ export declare function isLaunchInFlight(row: Pick<NodeRow, 'created' | 'launched_at'>, now: number, latest: Pick<Execution, 'state'> | null): boolean;
13
17
  export declare class NodeLifecycleReconciler {
14
18
  private readonly freezeLane;
15
19
  run(now: number, ctx: NodeLifecycleContext): Promise<FrozenCounts>;
@@ -1 +1 @@
1
- var b=Object.defineProperty;var o=(r,i)=>b(r,"name",{value:i,configurable:!0});import{clearFrozen as P,getNode as g}from"../../../core/canvas/index.js";import{isSafeNodeId as z}from"../../../core/canvas/paths.js";import{recordedPidLiveness as L}from"../../../core/canvas/pid.js";import{endExecution as v,hasAbsorbedProfilePauseStop as N,hasObservedExit as x,latestExecutionForNode as M,liveExecutionForNode as c}from"../../../core/canvas/executions.js";import{emitEvent as w}from"../../../core/events/emit.js";import{operationIdContext as D}from"../../../core/events/operation-id.js";import{readInterruptedTurn as F}from"../../../core/canvas/recovery.js";import{isProfilePaused as d}from"../../../core/profiles/manifest.js";import{isRecoveryHeld as I}from"../../../core/runtime/recovery-hold.js";import{hasUnseenWakeMail as p}from"../../../core/canvas/deliveries.js";import{hasIdleReleaseIntent as A,isParkedResident as C}from"../../../core/runtime/lifecycle.js";import{classifyDeadNode as S}from"../../fleet.js";import{applyParkEvent as T,hasPendingWake as U,parkEvent as W}from"../live-obligation.js";import{FreezeLaneReconciler as B}from"./freeze-lane.js";import{isInBackoff as E}from"./respawn-policy.js";import{asNodePerson as G,NodePersonUnresolvedError as k}from"./node-person.js";import{handleTerminatingRow as H}from"./terminating.js";import{wantsExecution as O}from"./wants-execution.js";import{enactRequestedStops as q}from"./requested-stops.js";import{endRemoteExecutionIfGone as j,recoverLaunching as J,wakeResident as K}from"./remote-wake.js";import{isDrained as Q}from"../../drain.js";function V(r){return r.status==="done"||r.status==="canceled"||r.status==="dead"}o(V,"isTerminal");const X=12e4;function Y(r,i){const e=Math.max(...[r.created,r.launched_at].map(n=>n==null?0:Date.parse(n)||0));return e>0&&i-e<X}o(Y,"isLaunchInFlight");async function R(r){const i=await c(r);return i!==null&&i.state==="running"&&i.pid!=null&&L(i.pid,i.processIdentity)==="dead"?i:null}o(R,"executionWithDeadPid");class we{static{o(this,"NodeLifecycleReconciler")}freezeLane=new B;async run(i,e){const n=[];await q(i);for(const s of e.rows)await D.fresh(async()=>{try{await G(s,()=>this.classifyRow(i,s,e,n))}catch(t){const l=s.node_id;w({level:t instanceof k?"warn":"error",event:t instanceof k?"node.lifecycle.person_unresolved":"node.lifecycle.classify_failed",...z(l)?{node_id:l}:{fields:{affected_node_id:l}},error:t})}});return await Q()?{stalled:0,waitingOnMail:0}:this.freezeLane.run(i,{fleet:e.fleet,candidates:n})}async classifyRow(i,e,n,s){const t=e.node_id;if(V(e)){if(H(e,await n.fleet.has(t)))return;if(C(e)&&!await n.fleet.has(t)&&!d(e.profile_id)&&!await I(t)&&!E(e,i)&&await p(t)){s.push({row:e,enact:"reopen"});return}if(e.pi_pid!=null){const a=await R(t);a!==null&&await v(a.executionId,{reason:"observed-dead"})}e.frozen_at!=null&&(e.final_report!=null||e.status==="dead"?await P(t):s.push({row:e,enact:"thaw"}));return}if(await n.fleet.has(t)){const a=await c(t);if(a?.runner!==void 0&&a.runner!=="process"&&a.state==="running"&&!d(e.profile_id)&&await p(t)){if(await K(a)==="lost")return}else a?.runner!==void 0&&a.runner!=="process"&&a.state==="running"&&await j(a);if(await n.fleet.has(t)){d(e.profile_id)?await n.fleet.interruptForProfilePause(t):await this.thawMark(e);return}}if(await J(t,i),d(e.profile_id)){await this.freezeLane.endEpisode(t);return}if(await I(t))return;const l=e.pi_pid==null?await M(t):null,f=l!==null&&x(l),m=l!==null&&!f&&N(l),h=e.pi_pid!=null?await R(t):null,_=f||m||h!==null;if(_&&!await n.fleet.hasProfilePauseInterruption(t)){const a=h??l;a!==null&&await v(a.executionId,{reason:"observed-dead"});const y=await c(t);if(y!==null&&y.executionId!==a?.executionId)return}if(!await O(e)){await this.thawMark(e),await this.settleParked(e);return}if(E(e,i))return;if(e.status==="idle"&&A(e)&&await p(t)){s.push({row:e,enact:"idle-release"});return}if(e.pi_pid==null&&(!f||e.frozen_at!=null)&&!m){if(e.respawn_not_before!=null){s.push({row:e,enact:"policy"});return}if(e.frozen_at!=null){s.push({row:e,enact:"thaw"});return}if(await n.fleet.isClaimed(t)||Y(e,i))return;w({level:"warn",event:"node.lifecycle.stranded_launch",node_id:t}),s.push({row:e,enact:"thaw"});return}if(!_)return;const u=S(await g(t),(await F(t))?.interrupted===!0,await U(t));if(u==="respawn-fresh"||u==="respawn-cycle"||u==="respawn-resume"){s.push({row:e,enact:"policy"});return}await n.fleet.applyDeadRowPolicy(t),await this.thawMark(e)}async thawMark(i){i.frozen_at!=null&&await P(i.node_id)}async settleParked(i){if(i.status!=="idle")return;await this.freezeLane.endEpisode(i.node_id);const e=await W(i.node_id,i.lifecycle);e!=="release"&&await T(i.node_id,e)}}export{X as LAUNCH_GRACE_MS,we as NodeLifecycleReconciler,Y as isLaunchInFlight};
1
+ var g=Object.defineProperty;var o=(s,i)=>g(s,"name",{value:i,configurable:!0});import{clearFrozen as P,getNode as z}from"../../../core/canvas/index.js";import{isSafeNodeId as L}from"../../../core/canvas/paths.js";import{recordedPidLiveness as x}from"../../../core/canvas/pid.js";import{endExecution as v,hasAbsorbedProfilePauseStop as N,hasObservedExit as M,latestExecutionForNode as w,liveExecutionForNode as c}from"../../../core/canvas/executions.js";import{emitEvent as I}from"../../../core/events/emit.js";import{operationIdContext as D}from"../../../core/events/operation-id.js";import{readInterruptedTurn as F}from"../../../core/canvas/recovery.js";import{isProfilePaused as d}from"../../../core/profiles/manifest.js";import{isRecoveryHeld as E}from"../../../core/runtime/recovery-hold.js";import{hasUnseenWakeMail as p}from"../../../core/canvas/deliveries.js";import{hasIdleReleaseIntent as A,isParkedResident as C}from"../../../core/runtime/lifecycle.js";import{classifyDeadNode as S}from"../../fleet.js";import{applyParkEvent as T,hasPendingWake as U,parkEvent as W}from"../live-obligation.js";import{FreezeLaneReconciler as B}from"./freeze-lane.js";import{isInBackoff as k}from"./respawn-policy.js";import{asNodePerson as G,NodePersonUnresolvedError as R}from"./node-person.js";import{handleTerminatingRow as H}from"./terminating.js";import{wantsExecution as O}from"./wants-execution.js";import{enactRequestedStops as q}from"./requested-stops.js";import{endRemoteExecutionIfGone as j,recoverLaunching as J,wakeResident as K}from"./remote-wake.js";import{isDrained as Q}from"../../drain.js";function V(s){return s.status==="done"||s.status==="canceled"||s.status==="dead"}o(V,"isTerminal");const X=12e4;function Y(s,i,e){const n=Math.max(...[s.created,s.launched_at].map(r=>r==null?0:Date.parse(r)||0));return n>0&&i-n<X&&e?.state!=="exited"}o(Y,"isLaunchInFlight");async function b(s){const i=await c(s);return i!==null&&i.state==="running"&&i.pid!=null&&x(i.pid,i.processIdentity)==="dead"?i:null}o(b,"executionWithDeadPid");class we{static{o(this,"NodeLifecycleReconciler")}freezeLane=new B;async run(i,e){const n=[];await q(i);for(const r of e.rows)await D.fresh(async()=>{try{await G(r,()=>this.classifyRow(i,r,e,n))}catch(t){const l=r.node_id;I({level:t instanceof R?"warn":"error",event:t instanceof R?"node.lifecycle.person_unresolved":"node.lifecycle.classify_failed",...L(l)?{node_id:l}:{fields:{affected_node_id:l}},error:t})}});return await Q()?{stalled:0,waitingOnMail:0}:this.freezeLane.run(i,{fleet:e.fleet,candidates:n})}async classifyRow(i,e,n,r){const t=e.node_id;if(V(e)){if(H(e,await n.fleet.has(t)))return;if(C(e)&&!await n.fleet.has(t)&&!d(e.profile_id)&&!await E(t)&&!k(e,i)&&await p(t)){r.push({row:e,enact:"reopen"});return}if(e.pi_pid!=null){const a=await b(t);a!==null&&await v(a.executionId,{reason:"observed-dead"})}e.frozen_at!=null&&(e.final_report!=null||e.status==="dead"?await P(t):r.push({row:e,enact:"thaw"}));return}if(await n.fleet.has(t)){const a=await c(t);if(a?.runner!==void 0&&a.runner!=="process"&&a.state==="running"&&!d(e.profile_id)&&await p(t)){if(await K(a)==="lost")return}else a?.runner!==void 0&&a.runner!=="process"&&a.state==="running"&&await j(a);if(await n.fleet.has(t)){d(e.profile_id)?await n.fleet.interruptForProfilePause(t):await this.thawMark(e);return}}if(await J(t,i),d(e.profile_id)){await this.freezeLane.endEpisode(t);return}if(await E(t))return;const l=e.pi_pid==null?await w(t):null,f=l!==null&&M(l),m=l!==null&&!f&&N(l),h=e.pi_pid!=null?await b(t):null,_=f||m||h!==null;if(_&&!await n.fleet.hasProfilePauseInterruption(t)){const a=h??l;a!==null&&await v(a.executionId,{reason:"observed-dead"});const y=await c(t);if(y!==null&&y.executionId!==a?.executionId)return}if(!await O(e)){await this.thawMark(e),await this.settleParked(e);return}if(k(e,i))return;if(e.status==="idle"&&A(e)&&await p(t)){r.push({row:e,enact:"idle-release"});return}if(e.pi_pid==null&&(!f||e.frozen_at!=null)&&!m){if(e.respawn_not_before!=null){r.push({row:e,enact:"policy"});return}if(e.frozen_at!=null){r.push({row:e,enact:"thaw"});return}if(await n.fleet.isClaimed(t)||Y(e,i,await w(t)))return;I({level:"warn",event:"node.lifecycle.stranded_launch",node_id:t}),r.push({row:e,enact:"thaw"});return}if(!_)return;const u=S(await z(t),(await F(t))?.interrupted===!0,await U(t));if(u==="respawn-fresh"||u==="respawn-cycle"||u==="respawn-resume"){r.push({row:e,enact:"policy"});return}await n.fleet.applyDeadRowPolicy(t),await this.thawMark(e)}async thawMark(i){i.frozen_at!=null&&await P(i.node_id)}async settleParked(i){if(i.status!=="idle")return;await this.freezeLane.endEpisode(i.node_id);const e=await W(i.node_id,i.lifecycle);e!=="release"&&await T(i.node_id,e)}}export{X as LAUNCH_GRACE_MS,we as NodeLifecycleReconciler,Y as isLaunchInFlight};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.591",
3
+ "version": "0.3.592",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -145,7 +145,7 @@
145
145
  },
146
146
  "license": "UNLICENSED",
147
147
  "dependencies": {
148
- "@north-light/identity": "0.3.591",
148
+ "@north-light/identity": "0.3.592",
149
149
  "@earendil-works/chord": "1.1.0",
150
150
  "@earendil-works/pi-agent-core": "1.1.0",
151
151
  "@earendil-works/pi-ai": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.591",
3
+ "version": "0.3.592",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.591",
3
+ "version": "0.3.592",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.591",
9
+ "version": "0.3.592",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -28,7 +28,7 @@
28
28
  "@milkdown/preset-gfm": "^7.22.3",
29
29
  "@milkdown/prose": "^7.22.3",
30
30
  "@milkdown/utils": "^7.22.3",
31
- "@north-light/identity": "0.3.591",
31
+ "@north-light/identity": "0.3.592",
32
32
  "cron-parser": "^5.6.0",
33
33
  "esbuild": "^0.27.7",
34
34
  "jose": "^6.2.12",
@@ -11956,17 +11956,17 @@
11956
11956
  },
11957
11957
  "packages/crouter-api": {
11958
11958
  "name": "@north-light/api",
11959
- "version": "0.3.591",
11959
+ "version": "0.3.592",
11960
11960
  "license": "UNLICENSED"
11961
11961
  },
11962
11962
  "packages/crouter-env-docker": {
11963
11963
  "name": "@north-light/env-docker",
11964
- "version": "0.3.591",
11964
+ "version": "0.3.592",
11965
11965
  "license": "UNLICENSED"
11966
11966
  },
11967
11967
  "packages/crouter-identity": {
11968
11968
  "name": "@north-light/identity",
11969
- "version": "0.3.591",
11969
+ "version": "0.3.592",
11970
11970
  "license": "UNLICENSED",
11971
11971
  "dependencies": {
11972
11972
  "jose": "^6.2.1"
@@ -11974,21 +11974,21 @@
11974
11974
  },
11975
11975
  "packages/crouter-plugin": {
11976
11976
  "name": "@north-light/plugin",
11977
- "version": "0.3.591",
11977
+ "version": "0.3.592",
11978
11978
  "license": "UNLICENSED",
11979
11979
  "dependencies": {
11980
- "@north-light/api": "0.3.591",
11981
- "@north-light/identity": "0.3.591"
11980
+ "@north-light/api": "0.3.592",
11981
+ "@north-light/identity": "0.3.592"
11982
11982
  }
11983
11983
  },
11984
11984
  "packages/crouter-sdk": {
11985
11985
  "name": "@north-light/sdk",
11986
- "version": "0.3.591",
11986
+ "version": "0.3.592",
11987
11987
  "license": "UNLICENSED",
11988
11988
  "dependencies": {
11989
- "@north-light/api": "0.3.591",
11990
- "@north-light/identity": "0.3.591",
11991
- "@north-light/plugin": "0.3.591",
11989
+ "@north-light/api": "0.3.592",
11990
+ "@north-light/identity": "0.3.592",
11991
+ "@north-light/plugin": "0.3.592",
11992
11992
  "jose": "^6.2.1"
11993
11993
  },
11994
11994
  "bin": {