@north-light/cli 0.3.587 → 0.3.589
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtin-memory/crouter-sdk/release-notes.md +1 -1
- package/dist/core/runtime/runner/blaxel/runner.js +3 -3
- package/dist/core/runtime/runner/types.d.ts +7 -1
- package/dist/core/runtime/view-socket.d.ts +2 -1
- package/dist/core/runtime/view-socket.js +2 -1
- package/dist/daemon/api/bridge.js +3 -3
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +2 -1
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
- package/docs/sdk/release-notes.md +1 -1
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
|
@@ -9,7 +9,7 @@ when-and-why-to-read: When you need Release notes, this knowledge should be read
|
|
|
9
9
|
|
|
10
10
|
Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
|
|
11
11
|
|
|
12
|
-
## 0.3.
|
|
12
|
+
## 0.3.585
|
|
13
13
|
|
|
14
14
|
`GET /v1/nodes/{id}/jobs/{jobId}/output` reads a background bash job's stored output, last 256 KiB by default (`max_bytes` up to 4 MiB, or `tail_lines`), from the blob store, so it works after the job's sandbox is deleted. It answers `job_not_found` for a job the node does not own and an empty `output` for a job that has written nothing. `CrtrClient.readBashJobOutput` calls it. Nothing existing changes.
|
|
15
15
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
${
|
|
3
|
-
${
|
|
1
|
+
var R=Object.defineProperty;var i=(n,r)=>R(n,"name",{value:r,configurable:!0});import{openDb as k}from"../../../canvas/db.js";import{mintExecutionToken as I}from"../../../canvas/node-tokens.js";import{BlaxelApiError as T,isWorkloadUnavailable as x}from"./client.js";const f=4180,d="view",C="node /opt/crtr/current/scripts/sandbox-start.mjs --wake",S=3,N=/^crtr-sandbox-start: failed (\S+)/m,m=i(n=>`broker-${n}`,"brokerProcessName"),E=i(n=>n.sandboxName??`${n.nodeId}-${n.attempt}`,"sandboxNameFor");async function y(n){return(await(await k()).prepare("SELECT DISTINCT sandbox_name FROM executions WHERE node_id = ? AND sandbox_name IS NOT NULL").all(n)).map(l=>l.sandbox_name)}i(y,"defaultSandboxNames");function P(n){const r=`${n.logs}
|
|
2
|
+
${n.stderr}
|
|
3
|
+
${n.stdout}`;return{state:"failed",reason:N.exec(r)?.[1]??"start_failed",code:n.exitCode}}i(P,"failureOf");function h(n){const{client:r}=n,l=(n.stopGraceS??10)*1e3,b=new Map;async function u(t){return await r.getSandbox(E(t))}return i(u,"sandboxOf"),{kind:"blaxel",async start(t){const a=await n.launchContext(t),e=E(t),o=await r.createSandbox({name:e,image:a.image,memoryMb:a.memoryMb,ports:[{name:d,target:f}]}),s=m(t.executionId);if((await r.getProcess(o,s))?.status==="running")return{pid:null,exited:null};await r.ensurePreview(e,d,f);const w=await(n.mintToken??(p=>I(p.executionId,p.nodeId)))(t),g={...a.secrets,CRTR_API_URL:a.apiUrl,CRTR_EXECUTION_ID:t.executionId,CRTR_NODE_TOKEN:w,CRTR_RELEASE:t.release,CRTR_RELEASE_URL:a.releaseUrl,CRTR_RELEASE_SHA256:a.releaseSha256};return await r.startProcess(o,{name:s,command:n.bootstrap(),env:g,waitForCompletion:!1}),{pid:null,exited:null}},async wake(t){const a=await u(t);if(a===null)return"no_broker";try{const e=await r.exec(a,`wake-${t.executionId}-${Date.now()}`,`${C} ${t.executionId}`,30);if(e.exitCode===0)return"signalled";if(e.exitCode===S)return"no_broker";throw new Error(`wake of ${t.executionId} failed (exit ${e.exitCode}): ${(e.stderr||e.logs).slice(0,300)}`)}catch(e){if(x(e))return"no_broker";throw e}},async stop(t,a){try{const e=await u(t);if(e===null)return{confirmed:!0};const o=m(t.executionId),s=i(async()=>(await r.getProcess(e,o))?.status==="running","running");if(!await s())return{confirmed:!0};await r.stopProcess(e,o);const c=Date.now()+l;for(;Date.now()<c;){if(!await s())return{confirmed:!0};await new Promise(w=>setTimeout(w,250))}return await r.killProcess(e,o),{confirmed:!await s()}}catch(e){return x(e)?{confirmed:!0}:{confirmed:!1,error:e instanceof Error?e:new Error(String(e))}}},async status(t,a){try{const e=await u(t);if(e===null)return{state:"gone"};if(a?.idle===!0)return{state:"running",pid:null};const o=await r.getProcess(e,m(t.executionId));return o===null?{state:"gone"}:o.status==="running"?{state:"running",pid:null}:o.status==="failed"||o.status==="completed"&&o.exitCode!==0?P(o):{state:"gone"}}catch(e){return x(e)?{state:"gone"}:{state:"unknown",error:e instanceof Error?e.message:String(e)}}},async delete(t){for(const a of await(n.sandboxNamesForNode??y)(t))await r.deleteSandbox(a)},async viewerAddress(t){const a=E(t),e=b.get(a);if(e&&e.expiresAt-Date.now()>5*6e4)return _(e.url,e.token);const o=await r.ensurePreview(a,d,f),s=new Date(Date.now()+60*6e4),{token:c}=await r.createPreviewToken(a,d,s);return b.set(a,{url:o,token:c,expiresAt:s.getTime()}),_(o,c)},async sandboxState(t){return(await u(t))?.state??null}}}i(h,"createBlaxelRunner");function _(n,r){return{kind:"wss",url:`${n.replace(/^https:/,"wss:")}/view`,headers:{"x-blaxel-preview-token":r}}}i(_,"viewer");export{f as BROKER_VIEW_PORT,T as BlaxelApiError,S as EXIT_NO_BROKER,N as FAILURE_LINE,C as WAKE_COMMAND,m as brokerProcessName,h as createBlaxelRunner,E as sandboxNameFor};
|
|
@@ -50,6 +50,12 @@ export type RunnerStatus = {
|
|
|
50
50
|
state: 'unknown';
|
|
51
51
|
error: string;
|
|
52
52
|
};
|
|
53
|
+
export interface RunnerStatusOptions {
|
|
54
|
+
/** The execution's broker has posted idle (`executions.idle_at`), so its environment may be frozen. A runner whose environment
|
|
55
|
+
* thaws or bills on any request must then answer from its control plane alone and send nothing to the workload: the Blaxel runner
|
|
56
|
+
* reports `running` for a sandbox that exists and `gone` for one that does not. The process and machine runners ignore it. */
|
|
57
|
+
idle?: boolean;
|
|
58
|
+
}
|
|
53
59
|
export type RunnerWakeResult = 'signalled' | 'no_broker';
|
|
54
60
|
export interface RunnerStopOptions {
|
|
55
61
|
/** Why: written to `executions.stop_reason` by the caller; carried here for the runner's diagnostics. */
|
|
@@ -91,7 +97,7 @@ export interface Runner {
|
|
|
91
97
|
wake(execution: RunnerExecution): Promise<RunnerWakeResult>;
|
|
92
98
|
/** Stop the execution's broker. Repeating it on a stopped execution confirms. */
|
|
93
99
|
stop(execution: RunnerExecution, options: RunnerStopOptions): Promise<RunnerStopResult>;
|
|
94
|
-
status(execution: RunnerExecution): Promise<RunnerStatus>;
|
|
100
|
+
status(execution: RunnerExecution, options?: RunnerStatusOptions): Promise<RunnerStatus>;
|
|
95
101
|
/** Remove everything the runner holds for the node: the run dir (process) or the sandbox (Blaxel). */
|
|
96
102
|
delete(nodeId: string): Promise<void>;
|
|
97
103
|
/** Where a viewer reaches this execution's broker. */
|
|
@@ -53,7 +53,8 @@ export interface BrokerViewLink {
|
|
|
53
53
|
on(event: 'close', listener: (hadError: boolean) => void): this;
|
|
54
54
|
}
|
|
55
55
|
/** A broker's `/view` WebSocket as a {@link BrokerViewLink}. Each broker message is one frame, delivered as one
|
|
56
|
-
* newline-terminated chunk; each line written is sent as one text message
|
|
56
|
+
* newline-terminated chunk; each line written is sent as one text message that keeps its newline: the broker reads
|
|
57
|
+
* this WebSocket as a byte stream through its line decoder, which completes a frame only at a newline. The headers (the preview token) are
|
|
57
58
|
* sent on the handshake only and kept nowhere else. */
|
|
58
59
|
export declare function connectWssView(url: string, headers: Record<string, string>): BrokerViewLink;
|
|
59
60
|
/** Resolve true once the broker's `/view` WebSocket accepts a handshake within `waitMs`, retrying while it refuses;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
var m=Object.defineProperty;var c=(n,e)=>m(n,"name",{value:e,configurable:!0});import{EventEmitter as p}from"node:events";import{createConnection as y,Socket as E}from"node:net";import{WebSocket as u}from"ws";import{CLIENT_READ_CAPS as S}from"./broker-protocol.js";import{nodeRunDir as x,viewSocketPath as k}from"../canvas/paths.js";import{isBeneathRefusal as O,socketBeneath as _}from"../spaces/open-beneath.js";const w=3e4,d=100,K=w;function h(n){return new Promise(e=>{setTimeout(e,n)})}c(h,"sleep");function R(n){let e;try{e=_(x(n),k(n))}catch(t){const r=new E;return process.nextTick(()=>r.destroy(t)),r}const s=y(e.path);return s.once("connect",e.release),s.once("close",e.release),s}c(R,"connectViewSocket");function T(n,e){return new Promise((s,t)=>{let r=!1,i=null;const f=c(o=>{r||(r=!0,i!==null&&i.destroy(),o instanceof Error?t(o):s(o))},"finish");i=R(n),i.once("connect",()=>f(!0)),i.once("error",o=>f(O(o)?o:!1)),i.setTimeout(e,()=>f(!1))})}c(T,"probeViewSocket");async function B(n,e,s=w){const t=Date.now()+s,r=e?.then(i=>({exit:i}));for(;Date.now()<=t;){const i=Math.max(0,t-Date.now()),f=T(n,Math.min(d,i)).then(a=>a?"ready":"retry"),o=r===void 0?await f:await Promise.race([f,r]);if(o==="ready")return{ready:!0};if(typeof o=="object")return{ready:!1,reason:"exited",status:o.exit};if(r!==void 0){const a=await Promise.race([h(d).then(()=>"sleep"),r]);if(typeof a=="object")return{ready:!1,reason:"exited",status:a.exit}}else await h(d)}return{ready:!1,reason:"timeout",waitedMs:s}}c(B,"awaitBrokerViewSocket");async function I(n,e,s=w){return(await B(n,e,s)).ready}c(I,"waitForBrokerViewSocket");const M=4180;function C(n,e){return new g(n,e)}c(C,"connectWssView");class g extends p{static{c(this,"WssViewLink")}ws;failed=!1;ended=!1;terminated=!1;pending=[];constructor(e,s){super(),this.ws=new u(e,{headers:s,maxPayload:S.maxLineBytes}),this.ws.on("open",()=>{for(const t of this.pending.splice(0))this.ws.send(t);this.emit("connect")}),this.ws.on("message",t=>{const r=Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t));this.emit("data",r.at(-1)===10?r:Buffer.concat([r,Buffer.from(`
|
|
2
2
|
`)]))}),this.ws.on("unexpected-response",(t,r)=>{this.fail(Object.assign(new Error(`broker view refused the upgrade (${r.statusCode??"no status"})`),{code:"ECONNREFUSED"})),this.ws.terminate()}),this.ws.on("error",t=>this.fail(t)),this.ws.on("close",t=>{this.ended||(t===1006&&!this.terminated&&this.fail(Object.assign(new Error("broker view connection dropped (1006)"),{code:"ECONNRESET"})),this.ended=!0,this.emit("close",this.failed))})}get destroyed(){return this.ended||this.ws.readyState===u.CLOSING||this.ws.readyState===u.CLOSED}write(e){const s=e.endsWith(`
|
|
3
|
-
`)?e
|
|
3
|
+
`)?e:`${e}
|
|
4
|
+
`;return this.ws.readyState===u.CONNECTING?(this.pending.push(s),!0):this.ws.readyState!==u.OPEN?!1:(this.ws.send(s),!0)}destroy(){return this.terminated=!0,this.ws.terminate(),this}fail(e){this.failed||(this.failed=!0,this.emit("error",e))}}async function F(n,e,s,t=w){const r=Date.now()+t;let i=!1;for(s?.then(()=>{i=!0});Date.now()<=r&&!i;){if(await new Promise(o=>{const a=C(n,e),l=setTimeout(()=>{a.destroy(),o(!1)},Math.max(1,r-Date.now()));a.on("connect",()=>{clearTimeout(l),a.destroy(),o(!0)}),a.on("error",()=>{}),a.on("close",()=>{clearTimeout(l),o(!1)})}))return!0;if(Date.now()+d>r)break;await h(d)}return!1}c(F,"waitForWssView");export{M as BROKER_VIEW_PORT,K as BROKER_VIEW_SOCKET_WAIT_MS,B as awaitBrokerViewSocket,R as connectViewSocket,C as connectWssView,T as probeViewSocket,I as waitForBrokerViewSocket,F as waitForWssView};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var j=Object.defineProperty;var
|
|
1
|
+
var j=Object.defineProperty;var a=(r,e)=>j(r,"name",{value:e,configurable:!0});import{WebSocketServer as K}from"ws";import{isSafeNodeId as N,viewSocketPath as Q}from"../../core/canvas/paths.js";import{getNode as T}from"../../core/canvas/index.js";import{requestPersonId as z,runAsPerson as G}from"../../core/canvas/person-state.js";import{BROKER_READ_CAPS as X,CLIENT_READ_CAPS as Y,FrameDecoder as Z,FrameOverflowError as I}from"../../core/runtime/broker-protocol.js";import{emitEvent as A}from"../../core/events/emit.js";import{classify as R}from"../../core/fault-classifier.js";import{clearFault as g,markNodeRead as ee,recordFault as $}from"../../core/canvas/recovery.js";import{decideAttachRevive as re}from"../../core/runtime/revive.js";import{connectViewSocket as te,connectWssView as ne,probeViewSocket as ae,waitForBrokerViewSocket as ie,waitForWssView as P}from"../../core/runtime/view-socket.js";import{liveExecutionForNode as oe,recordExecutionAwake as ce}from"../../core/canvas/executions.js";import{settleSandboxBeforeWake as se}from"../reconcilers/ledger-sandbox.js";import{runnerForExecution as le}from"../../core/runtime/runner/index.js";import{isBeneathRefusal as fe}from"../../core/spaces/open-beneath.js";import{openMachineView as ue,probeMachineView as me,waitForMachineView as de}from"../machine-link/forward.js";import{attachViewer as F,detachViewer as he,holdViewerForRedial as we,presentViewer as pe}from"./viewer-presence.js";const ye=3e4,ve=new K({noServer:!0,maxPayload:X.maxLineBytes}),ke=25e3,ge=6e4;function be(r,e=ke,s=ge){let n=!0,h=!1,u=Date.now();const l=a(()=>{n=!0},"acknowledge"),i=a(()=>{h||(h=!0,clearInterval(o),r.off("pong",l))},"stop");r.on("pong",l);const o=setInterval(()=>{if(Date.now()-u>s){n=!0;return}if(!n){i(),r.terminate();return}n=!1;try{r.ping()}catch{i(),r.terminate()}},e);return o.unref(),{stop:i,noteActivity:a(()=>{u=Date.now()},"noteActivity")}}a(be,"startRemoteAttachHeartbeat");function C(r){const e=Buffer.from(r,"utf8");return e.byteLength<=123?r:e.subarray(0,123).toString("utf8")}a(C,"clampReason");function y(r,e,s,n,h){const u=JSON.stringify({error:{code:n,message:h,origin:"runtime"}}),l=`HTTP/1.1 ${e} ${s}\r
|
|
2
2
|
content-type: application/json; charset=utf-8\r
|
|
3
3
|
content-length: ${Buffer.byteLength(u)}\r
|
|
4
4
|
connection: close\r
|
|
5
5
|
\r
|
|
6
|
-
`+u;try{r.end(l)}catch{try{r.destroy()}catch{}}}
|
|
6
|
+
`+u;try{r.end(l)}catch{try{r.destroy()}catch{}}}a(y,"refuseUpgrade");function _e(r){if(new URL(r.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const s=r.headers["x-crtr-attach-revive"];return(Array.isArray(s)?s[0]:s)==="0"}a(_e,"reviveOptedOut");function Ae(r){return new URL(r.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}a(Ae,"wakeParkedRequested");function O(r){return r===1e3||r===1001||r===1005}a(O,"isCleanViewerClose");function w(r,e,s){s.catch(n=>A({level:"error",event:e,node_id:r,error:n}))}a(w,"detach");async function D(r){const e=await oe(r);return e===null||e.runner==="process"?{kind:"unix",path:Q(r)}:(e.runner==="blaxel"&&e.idleAt!==null&&(await se(e.executionId,Date.now()),await ce(e.executionId)),le(e.runner).viewerAddress(e))}a(D,"brokerViewAddress");function Ee(r,e){return e.kind==="machine"?ue(e.environmentId,r):e.kind==="wss"?ne(e.url,e.headers):te(r)}a(Ee,"connectBrokerView");function Se(r,e,s){return e.kind==="machine"?me(e.environmentId,r,s):e.kind==="wss"?P(e.url,e.headers,void 0,s):ae(r,s)}a(Se,"probeBrokerView");async function xe(r,e,s,n){const h=[];let u=a(t=>{h.push(t)},"relayMessage");if(r.on("message",t=>u(t)),!N(e)){try{r.close(1008,C(`invalid node id: ${e}`))}catch{}return}if(await T(e)===null){try{r.close(1011,C(`no node ${e}`))}catch{}return}await g(e,{link:"viewer\u2194crtrd"});const l=Ee(e,s),i=new Z(Y);let o=!1,v=!1,d,b=!1,V=!1,E="controller",B=Promise.resolve(),_=!1;const S=[];let x=!1;l.on("connect",()=>{b=!0,!o&&_&&w(e,"daemon.bridge.presence_failed",F(e,r)),w(e,"daemon.bridge.connect_failed",g(e,{link:"relay\u2194broker"}))});const p=a((t,f,c=!1)=>{if(!o){if(o=!0,w(e,"daemon.bridge.presence_failed",c?we(e,r):he(e,r)),d?.stop(),!l.destroyed)try{l.destroy()}catch{}try{r.close(t,C(f))}catch{}}},"teardown");d=be(r);const W=a(async t=>{d?.noteActivity();let f;try{f=i.push(t)}catch(c){const m=c instanceof I?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;p(1009,m);return}S.push(...f),x||await M()},"onData"),M=a(async()=>{x=!0;try{for(;S.length>0&&!o;){const t=S.splice(0);let f;try{f=await n(E)}catch(m){A({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:m}),p(1011,"revoked");return}if(o)return;if(!f){p(1008,"revoked");return}let c=!1;for(const m of t)r.readyState===r.OPEN&&(r.send(JSON.stringify(m)),m?.type==="welcome"&&(c=!0));if(_&&c)try{await pe(e,r)&&await ee(e)}catch(m){A({level:"warn",event:"daemon.bridge.present_failed",node_id:e,error:m})}}}finally{x=!1}},"drainOutbound");l.on("data",t=>w(e,"daemon.bridge.data_failed",W(t)));const U=a(async t=>{const f=R("relay\u2194broker",t);await $(e,{link:"relay\u2194broker",op:"bridge socket",kind:f.kind,retry:{disposition:"manual"},message:t.message});const c=t.code==="ECONNREFUSED"||t.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${t.message}`;p(1011,c,!0)},"onSocketError");l.on("error",t=>w(e,"daemon.bridge.socket_error_failed",U(t)));const H=a(async t=>{o||t||(await g(e,{link:"relay\u2194broker"}),p(1e3,"broker socket closed",!0))},"onSocketClose");l.on("close",t=>w(e,"daemon.bridge.socket_close_failed",H(t)));const L=a(async(t,f)=>{const c=f.toString("utf8");if(o){O(t)&&await g(e,{link:"viewer\u2194crtrd"});return}if(O(t))await g(e,{link:"viewer\u2194crtrd"});else if(!v){v=!0;const m=R("viewer\u2194crtrd",{code:t,reason:c});await $(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:m.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${t})`})}p(t,c||`remote attach closed (${t})`)},"onWsClose");r.on("close",(t,f)=>w(e,"daemon.bridge.ws_close_failed",L(t,f)));const J=a(async()=>{if(!v){v=!0;const t=R("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});await $(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:t.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}p(1011,"remote attach websocket error")},"onWsError");r.on("error",()=>w(e,"daemon.bridge.ws_error_failed",J())),u=a(t=>{if(o||l.destroyed)return;d?.noteActivity();const c=(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t))).toString("utf8");if(!V)try{const k=JSON.parse(c);k?.type==="hello"&&(V=!0,E=k.role==="observer"?"observer":"controller",_=k.attends!==!1,_&&b&&w(e,"daemon.bridge.presence_failed",F(e,r)))}catch{}const m=c.endsWith(`
|
|
7
7
|
`)?c:c+`
|
|
8
|
-
`,q=
|
|
8
|
+
`,q=a(()=>{try{l.write(m)}catch{}},"write");B=B.then(async()=>{o||(await n(E)?q():p(1008,"revoked"))}).catch(k=>{A({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:k}),p(1011,"revoked")})},"relayMessage");for(const t of h.splice(0))u(t)}a(xe,"bridgeConnection");async function qe(r,e,s,n,h){const u=z(),l=u===void 0?h:i=>G(u,()=>h(i));e.on("error",()=>{});try{if(!N(n)){y(e,400,"Bad Request","invalid_node_id",`invalid node id: ${n}`);return}if(await T(n)===null){y(e,404,"Not Found","node_not_found",`no node ${n}`);return}let i;if(_e(r)){if(i=await D(n),!await Se(n,i,250)){y(e,409,"Conflict","node_dormant",`node ${n} is dormant and revive was opted out (?revive=0)`);return}}else{let o;try{const d=await re(n,{resume:!0,capacity:"refuse"},Ae(r));if(d.parked){y(e,409,"Conflict","node_dormant",`node ${n} is parked; attach with ?wakeParked=1 to wake it`);return}o=d.result.launch?.exited}catch(d){const b=d instanceof Error?d.message:String(d);y(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${b}`);return}if(i=await D(n),!(i.kind==="machine"?await de(i.environmentId,n,o,ye):i.kind==="wss"?await P(i.url,i.headers,o):await ie(n,o))){y(e,409,"Conflict","broker_not_ready",`broker view socket for ${n} did not become listenable after revive`);return}}if(e.destroyed)return;ve.handleUpgrade(r,e,s,o=>w(n,"daemon.bridge.connection_failed",xe(o,n,i,l)))}catch(i){const o=i instanceof Error?i.message:String(i);if(fe(i)){y(e,400,"Bad Request","invalid_request",o);return}y(e,500,"Internal Server Error","internal",`attach upgrade failed: ${o}`)}}a(qe,"handleAttachUpgrade");export{ke as REMOTE_ATTACH_HEARTBEAT_MS,ge as REMOTE_ATTACH_QUIET_MS,qe as handleAttachUpgrade,O as isCleanViewerClose,y as refuseUpgrade,be as startRemoteAttachHeartbeat};
|
|
@@ -26,7 +26,8 @@ export declare function imageForWake(environmentId: string): Promise<Environment
|
|
|
26
26
|
* a new attempt on the accepted one. `lost` means no broker answered: the execution is ended and the caller starts a new attempt. */
|
|
27
27
|
export declare function wakeResident(execution: Execution): Promise<'woken' | 'lost'>;
|
|
28
28
|
/** A `running` remote execution is live while its runner reports the broker process running, frozen or not. When the runner
|
|
29
|
-
* reports it gone (or ended non-zero), that exact execution is ended; a runner that cannot tell leaves it as it is.
|
|
29
|
+
* reports it gone (or ended non-zero), that exact execution is ended; a runner that cannot tell leaves it as it is. An idle
|
|
30
|
+
* execution is asked without touching its workload (`RunnerStatusOptions.idle`), so a frozen sandbox is not woken by the question. */
|
|
30
31
|
export declare function endRemoteExecutionIfGone(execution: Execution): Promise<boolean>;
|
|
31
32
|
/** The recovery table for a `launching` Blaxel execution found by a claim after its starter is gone. */
|
|
32
33
|
export declare function recoverLaunching(nodeId: string, now: number): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var f=Object.defineProperty;var a=(e,t)=>f(e,"name",{value:t,configurable:!0});import{liveExecutionForNode as m,endExecution as
|
|
1
|
+
var f=Object.defineProperty;var a=(e,t)=>f(e,"name",{value:t,configurable:!0});import{liveExecutionForNode as m,endExecution as d,recordExecutionAwake as p,endLaunchingExecution as s,requestExecutionStop as w}from"../../../core/canvas/executions.js";import{openDb as h}from"../../../core/canvas/db.js";import{isCloudRole as _}from"../../../core/canvas/person-state.js";import{emitEvent as u}from"../../../core/events/emit.js";import{findRefusal as x,refusalError as E,refusedMark as g,writeCharge as v}from"../../../core/ledger/index.js";import{transition as c}from"../../../core/runtime/lifecycle.js";import{runnerForExecution as l}from"../../../core/runtime/runner/index.js";import{machineEnvironmentOfProfile as b}from"../../../core/runtime/runner/machine/environment.js";import{startCandidateOf as k}from"../../../core/runtime/start-check.js";import{settleSandboxBeforeWake as y}from"../ledger-sandbox.js";import{crtrVersion as I}from"../../../shared/crtr-version.js";import{resolveEnvironmentImage as S}from"../recipe-image.js";const A=45*6e4,L=12e4,D=a(()=>I(),"acceptedRelease");async function G(e){return e===null||!_()?null:await b(e)}a(G,"remoteMachineEnvironmentOf");async function q(e){const t=await k(e);if(t===null)return;const n={scope:"call",payer:t.payer,funding:"bill",sub:t.sub,grantee:t.grantee,provider:"blaxel"},r=await x(n),o=await h();if(r===null){await o.prepare("UPDATE nodes SET spend_refusal = NULL WHERE node_id = ? AND spend_refusal IS NOT NULL").run(e.node_id);return}throw await o.prepare("UPDATE nodes SET spend_refusal = ? WHERE node_id = ?").run(JSON.stringify({limit:r.limit.id,usd:r.usd,reset_at:r.reset_at,at:new Date().toISOString()}),e.node_id),await v({id:`refused:sandbox:${e.node_id}:${r.limit.id}:${r.month}`,kind:"sandbox",what:"wake",usd:null,payer:n.payer,funding:"bill",sub:n.sub,grantee:n.grantee,node:e.node_id,run:e.run_id??null,provider:"blaxel",refused:g(r)}),E(r)}a(q,"checkBilledWake");class J extends Error{static{a(this,"EnvironmentImagePending")}environmentId;imageError;constructor(t,n){super(`environment ${t} has no built image yet${n===null?"":` (${n})`}; the wake waits for the build.`),this.environmentId=t,this.imageError=n,this.name="EnvironmentImagePending"}}async function V(e){return await S(e)}a(V,"imageForWake");async function j(e){return e.release!==D()&&await w(e.nodeId,{afterTurn:!1,reason:"release"}),await l(e.runner).wake(e)==="no_broker"?(await d(e.executionId,{reason:"lost"}),"lost"):(await y(e.executionId,Date.now()),await p(e.executionId),"woken")}a(j,"wakeResident");async function z(e){const t=await l(e.runner).status(e,{idle:e.idleAt!==null});if(t.state!=="gone"&&t.state!=="failed")return!1;const n=await d(e.executionId,{reason:"lost"});return n&&u({level:"warn",event:"execution.lost",node_id:e.nodeId}),n}a(z,"endRemoteExecutionIfGone");async function K(e,t){const n=await m(e);if(n===null||n.state!=="launching"||n.runner==="process")return;const r=l(n.runner),o=t-Date.parse(n.startedAt),i=await r.status(n);if(i.state!=="unknown"){if(i.state==="running"){if(o<=A||!await s(n.executionId,{reason:"launch_timeout"}))return;await r.stop(n,{reason:"launch_timeout"}),await c(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:"the sandbox launch did not report within the launch deadline"}}}),u({level:"warn",event:"execution.launch_timeout",node_id:e});return}if(i.state==="failed"){if(!await s(n.executionId,{reason:"launch_failed"}))return;await c(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:`the sandbox start script failed: ${i.reason}`}}});return}if(o>L){if(!await s(n.executionId,{reason:"launch_lost"}))return;u({level:"warn",event:"execution.launch_lost",node_id:e})}}}a(K,"recoverLaunching");export{J as EnvironmentImagePending,A as LAUNCH_DEADLINE_MS,L as LAUNCH_LOST_GRACE_MS,D as acceptedRelease,q as checkBilledWake,z as endRemoteExecutionIfGone,V as imageForWake,K as recoverLaunching,G as remoteMachineEnvironmentOf,j as wakeResident};
|
|
@@ -7,7 +7,7 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
|
|
|
7
7
|
|
|
8
8
|
Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
|
|
9
9
|
|
|
10
|
-
## 0.3.
|
|
10
|
+
## 0.3.585
|
|
11
11
|
|
|
12
12
|
`GET /v1/nodes/{id}/jobs/{jobId}/output` reads a background bash job's stored output, last 256 KiB by default (`max_bytes` up to 4 MiB, or `tail_lines`), from the blob store, so it works after the job's sandbox is deleted. It answers `job_not_found` for a job the node does not own and an empty `output` for a job that has written nothing. `CrtrClient.readBashJobOutput` calls it. Nothing existing changes.
|
|
13
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.589",
|
|
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.
|
|
148
|
+
"@north-light/identity": "0.3.589",
|
|
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",
|
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.589",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/cli",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.589",
|
|
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.
|
|
31
|
+
"@north-light/identity": "0.3.589",
|
|
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.
|
|
11959
|
+
"version": "0.3.589",
|
|
11960
11960
|
"license": "UNLICENSED"
|
|
11961
11961
|
},
|
|
11962
11962
|
"packages/crouter-env-docker": {
|
|
11963
11963
|
"name": "@north-light/env-docker",
|
|
11964
|
-
"version": "0.3.
|
|
11964
|
+
"version": "0.3.589",
|
|
11965
11965
|
"license": "UNLICENSED"
|
|
11966
11966
|
},
|
|
11967
11967
|
"packages/crouter-identity": {
|
|
11968
11968
|
"name": "@north-light/identity",
|
|
11969
|
-
"version": "0.3.
|
|
11969
|
+
"version": "0.3.589",
|
|
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.
|
|
11977
|
+
"version": "0.3.589",
|
|
11978
11978
|
"license": "UNLICENSED",
|
|
11979
11979
|
"dependencies": {
|
|
11980
|
-
"@north-light/api": "0.3.
|
|
11981
|
-
"@north-light/identity": "0.3.
|
|
11980
|
+
"@north-light/api": "0.3.589",
|
|
11981
|
+
"@north-light/identity": "0.3.589"
|
|
11982
11982
|
}
|
|
11983
11983
|
},
|
|
11984
11984
|
"packages/crouter-sdk": {
|
|
11985
11985
|
"name": "@north-light/sdk",
|
|
11986
|
-
"version": "0.3.
|
|
11986
|
+
"version": "0.3.589",
|
|
11987
11987
|
"license": "UNLICENSED",
|
|
11988
11988
|
"dependencies": {
|
|
11989
|
-
"@north-light/api": "0.3.
|
|
11990
|
-
"@north-light/identity": "0.3.
|
|
11991
|
-
"@north-light/plugin": "0.3.
|
|
11989
|
+
"@north-light/api": "0.3.589",
|
|
11990
|
+
"@north-light/identity": "0.3.589",
|
|
11991
|
+
"@north-light/plugin": "0.3.589",
|
|
11992
11992
|
"jose": "^6.2.1"
|
|
11993
11993
|
},
|
|
11994
11994
|
"bin": {
|