@north-light/cli 0.3.558 → 0.3.560

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.
Files changed (46) hide show
  1. package/dist/api/client.js +3 -3
  2. package/dist/api/contract.d.ts +1 -1
  3. package/dist/api/contract.js +1 -1
  4. package/dist/api/dto/exec.d.ts +10 -0
  5. package/dist/api/dto/exec.js +0 -0
  6. package/dist/api/error-codes.d.ts +15 -0
  7. package/dist/api/error-codes.js +1 -1
  8. package/dist/api/index.d.ts +1 -0
  9. package/dist/api/index.js +1 -1
  10. package/dist/api/routes.d.ts +1 -0
  11. package/dist/api/routes.js +1 -1
  12. package/dist/build-root.js +1 -1
  13. package/dist/builtin-memory/crouter-sdk/release-notes.md +15 -0
  14. package/dist/builtin-memory/crouter-sdk/resources.md +1 -1
  15. package/dist/clients/attach/viewer.js +465 -465
  16. package/dist/core/canvas/crons.d.ts +4 -1
  17. package/dist/core/canvas/crons.js +5 -5
  18. package/dist/core/canvas/schema-pg/004-cron-armed-from-exec.sql +6 -0
  19. package/dist/core/canvas/schema-version.d.ts +1 -1
  20. package/dist/core/canvas/schema-version.js +1 -1
  21. package/dist/core/io.js +5 -5
  22. package/dist/core/model-auth/readiness.d.ts +12 -0
  23. package/dist/core/model-auth/readiness.js +1 -0
  24. package/dist/daemon/api/app-listener.js +13 -13
  25. package/dist/daemon/api/handlers/crons.js +1 -1
  26. package/dist/daemon/api/handlers/exec.d.ts +6 -0
  27. package/dist/daemon/api/handlers/exec.js +1 -0
  28. package/dist/daemon/api/handlers/modelauth.js +1 -1
  29. package/dist/daemon/api/handlers/nodes.js +1 -1
  30. package/dist/daemon/api/operations.js +1 -1
  31. package/dist/daemon/api/router.d.ts +11 -4
  32. package/dist/daemon/cron/armed-context.d.ts +2 -1
  33. package/dist/daemon/cron/armed-context.js +1 -1
  34. package/dist/daemon/cron/authority.d.ts +16 -4
  35. package/dist/daemon/cron/authority.js +1 -1
  36. package/dist/daemon/cron/escalation.js +3 -3
  37. package/dist/daemon/cron/live-runs.d.ts +12 -3
  38. package/dist/daemon/cron/run-socket.js +1 -1
  39. package/dist/daemon/cron/sinks.js +2 -2
  40. package/dist/daemon/cron-run.js +2 -2
  41. package/dist/daemon/reconcilers/action-lane.js +1 -1
  42. package/docs/sdk/release-notes.md +15 -0
  43. package/docs/sdk/resources.md +1 -1
  44. package/package.json +2 -2
  45. package/packages/crouter-identity/package.json +1 -1
  46. package/runtime.lock.json +13 -13
@@ -1,3 +1,3 @@
1
- var m=Object.defineProperty;var i=(e,r)=>m(e,"name",{value:r,configurable:!0});import{getNode as f}from"../../core/canvas/canvas.js";import{nodeAgentDir as h}from"../../core/canvas/node-agent-paths.js";import{newNodeId as p}from"../../core/runtime/nodes.js";import{fullName as w}from"../../core/canvas/labels.js";import{listCronRuns as $,setCronState as _}from"../../core/canvas/crons.js";import{emitEvent as l}from"../../core/events/emit.js";import{spawnChild as y}from"../../core/runtime/spawn.js";import{cronWakeOrigin as g}from"../../core/runtime/bearings.js";import{cadenceDisplay as b}from"../../core/wake.js";import{cronBirthPlacement as v,cronBirthPrincipalOf as O,resolveLiveProfileOrRoot as k}from"./armed-context.js";import{checkCronBirth as S}from"./authority.js";function T(e,r){return r!==null?r:e.timedOut?`run timed out after ${e.runTimeoutS}s (killed; recorded exit -1)`:e.processErrorMessage!==null?`run process error: ${e.processErrorMessage}`:`command exited ${e.exitCode??"(no exit observed)"}`}i(T,"failureSummary");async function x(e,r,s){const n=e.created_by!=null?await f(e.created_by):null,o=e.created_by==null?"created by: (no creating node recorded)":n!==null?`created by: node ${e.created_by} ("${w(n)}") \u2014 still on the canvas; consider telling it what happened`:`created by: node ${e.created_by} (now gone)`,a=e.recur!=null?`every ${b(e.recur)}`:`one-shot at ${e.fire_at}`,d=(await $(e.cron_id)).slice(0,10).map(t=>{const c=t.exit_code===null?"no exit":`exit ${t.exit_code}`,u=(t.stderr_head??t.stdout_head??"").split(`
2
- `)[0]?.slice(0,120)??"";return`- ${t.started} \u2014 ${c}, ${t.duration_ms}ms \u2014 ${t.delivered??""}${u!==""?` \u2014 ${u}`:""}`});return["A scheduled cron FAILED and has paused itself. You were spawned to deal with it.","",`- cron: "${e.name}" (${e.cron_id})`,`- schedule: ${a}${e.tz!=null?` (tz ${e.tz})`:""}`,`- context: cwd ${e.cwd}${e.profile!=null?`, profile ${e.profile}`:""}`,`- ${o}`,`- this failure: ${T(r,s)}`,"","## The command","","```bash",e.command,"```","","## This run","",`- exit: ${r.timedOut?"-1 (timeout)":String(r.exitCode)}`,`- stdout (head): ${r.stdoutHead===""?"(empty)":""}`,...r.stdoutHead!==""?["```",r.stdoutHead,"```"]:[],`- stderr (head): ${r.stderrHead===""?"(empty)":""}`,...r.stderrHead!==""?["```",r.stderrHead,"```"]:[],"","## Recent runs (most recent first, this failure not yet recorded)","",...d.length>0?d:["(no prior runs recorded)"],"","## Your mandate","","Diagnose why it failed and decide what to do:",`- Transient or fixable: fix the environment/command context, verify with \`crtr cron run ${e.cron_id}\` (fires now, out of band, shows you the result), then \`crtr cron resume ${e.cron_id}\` to re-arm it.`,`- Obsolete: \`crtr cron cancel ${e.cron_id}\`.`,"- The user's call (or the fix needs a change you cannot make): `crtr human send`.","","The cron stays paused until someone resumes it \u2014 nothing refires while you work."].join(`
3
- `)}i(x,"buildEscalationKickoff");async function j(e,r,s){await _(e.cron_id,"paused");try{const n=O(e),o=await S(n);if(!o.allowed)return l({level:"warn",event:"cron.birth.refused",fields:{cron_id:e.cron_id,grant_id:e.grant_id,reason:o.reason,stage:"escalation"}}),`failure: cron paused; no escalation node (refused: ${o.reason})`;const a=await p(),d=await v(n,a),t=await y({nodeId:a,kind:"general",isolated:e.isolated,cwd:e.isolated?await h(a,e.grantee):e.cwd,prompt:await x(e,r,s),...d,readiness:"launch",profile:k(e),wakeOrigin:await g(e,"node_birth")});return l({level:"info",event:"cron.failure.escalated",fields:{cron_id:e.cron_id,name:e.name,escalation_node:t.node.node_id}}),`failure: cron paused, escalation node ${t.node.node_id} spawned`}catch(n){return l({level:"error",event:"cron.escalation.spawn_failed",error:n,fields:{cron_id:e.cron_id,name:e.name}}),`failure: cron paused; escalation spawn failed: ${n.message}`}}i(j,"escalateFailure");function M(e){return[`${e}.`,"This is a ONE-SHOT, so it has been PAUSED rather than released: its side effects may have completed in full,","and re-firing would enact them a second time. Determine whether the work actually landed, then either","`crtr cron run` it to redo it deliberately, or `crtr cron cancel` it."].join(" ")}i(M,"crashedOneShotSummary");export{M as crashedOneShotSummary,j as escalateFailure,T as failureSummary};
1
+ var f=Object.defineProperty;var i=(e,r)=>f(e,"name",{value:r,configurable:!0});import{getNode as h}from"../../core/canvas/canvas.js";import{nodeAgentDir as p}from"../../core/canvas/node-agent-paths.js";import{newNodeId as w}from"../../core/runtime/nodes.js";import{fullName as $}from"../../core/canvas/labels.js";import{listCronRuns as _,setCronState as y}from"../../core/canvas/crons.js";import{emitEvent as l}from"../../core/events/emit.js";import{spawnChild as g}from"../../core/runtime/spawn.js";import{cronWakeOrigin as b}from"../../core/runtime/bearings.js";import{cadenceDisplay as v}from"../../core/wake.js";import{cronBirthPlacement as k,cronBirthPrincipalOf as O,resolveLiveProfileOrRoot as c}from"./armed-context.js";import{checkCronBirth as S}from"./authority.js";function T(e,r){return r!==null?r:e.timedOut?`run timed out after ${e.runTimeoutS}s (killed; recorded exit -1)`:e.processErrorMessage!==null?`run process error: ${e.processErrorMessage}`:`command exited ${e.exitCode??"(no exit observed)"}`}i(T,"failureSummary");async function x(e,r,s){const n=e.created_by!=null?await h(e.created_by):null,o=e.created_by==null?"created by: (no creating node recorded)":n!==null?`created by: node ${e.created_by} ("${$(n)}") \u2014 still on the canvas; consider telling it what happened`:`created by: node ${e.created_by} (now gone)`,a=e.recur!=null?`every ${v(e.recur)}`:`one-shot at ${e.fire_at}`,d=(await _(e.cron_id)).slice(0,10).map(t=>{const m=t.exit_code===null?"no exit":`exit ${t.exit_code}`,u=(t.stderr_head??t.stdout_head??"").split(`
2
+ `)[0]?.slice(0,120)??"";return`- ${t.started} \u2014 ${m}, ${t.duration_ms}ms \u2014 ${t.delivered??""}${u!==""?` \u2014 ${u}`:""}`});return["A scheduled cron FAILED and has paused itself. You were spawned to deal with it.","",`- cron: "${e.name}" (${e.cron_id})`,`- schedule: ${a}${e.tz!=null?` (tz ${e.tz})`:""}`,`- context: cwd ${e.cwd}${e.profile!=null?`, profile ${e.profile}`:""}`,`- ${o}`,`- this failure: ${T(r,s)}`,"","## The command","","```bash",e.command,"```","","## This run","",`- exit: ${r.timedOut?"-1 (timeout)":String(r.exitCode)}`,`- stdout (head): ${r.stdoutHead===""?"(empty)":""}`,...r.stdoutHead!==""?["```",r.stdoutHead,"```"]:[],`- stderr (head): ${r.stderrHead===""?"(empty)":""}`,...r.stderrHead!==""?["```",r.stderrHead,"```"]:[],"","## Recent runs (most recent first, this failure not yet recorded)","",...d.length>0?d:["(no prior runs recorded)"],"","## Your mandate","","Diagnose why it failed and decide what to do:",`- Transient or fixable: fix the environment/command context, verify with \`crtr cron run ${e.cron_id}\` (fires now, out of band, shows you the result), then \`crtr cron resume ${e.cron_id}\` to re-arm it.`,`- Obsolete: \`crtr cron cancel ${e.cron_id}\`.`,"- The user's call (or the fix needs a change you cannot make): `crtr human send`.","","The cron stays paused until someone resumes it \u2014 nothing refires while you work."].join(`
3
+ `)}i(x,"buildEscalationKickoff");async function j(e,r,s){await y(e.cron_id,"paused");try{const n=O(e),o=await S(n,{kind:"general",cwd:e.cwd,profile:c(e)});if(!o.allowed)return l({level:"warn",event:"cron.birth.refused",fields:{cron_id:e.cron_id,grant_id:e.grant_id,reason:o.reason,stage:"escalation"}}),`failure: cron paused; no escalation node (refused: ${o.reason})`;const a=await w(),d=await k(n,a),t=await g({nodeId:a,kind:"general",isolated:e.isolated,cwd:e.isolated?await p(a,e.grantee):e.cwd,prompt:await x(e,r,s),...d,readiness:"launch",profile:c(e),wakeOrigin:await b(e,"node_birth")});return l({level:"info",event:"cron.failure.escalated",fields:{cron_id:e.cron_id,name:e.name,escalation_node:t.node.node_id}}),`failure: cron paused, escalation node ${t.node.node_id} spawned`}catch(n){return l({level:"error",event:"cron.escalation.spawn_failed",error:n,fields:{cron_id:e.cron_id,name:e.name}}),`failure: cron paused; escalation spawn failed: ${n.message}`}}i(j,"escalateFailure");function M(e){return[`${e}.`,"This is a ONE-SHOT, so it has been PAUSED rather than released: its side effects may have completed in full,","and re-firing would enact them a second time. Determine whether the work actually landed, then either","`crtr cron run` it to redo it deliberately, or `crtr cron cancel` it."].join(" ")}i(M,"crashedOneShotSummary");export{M as crashedOneShotSummary,j as escalateFailure,T as failureSummary};
@@ -10,12 +10,21 @@ export declare function liveRunFor(cronId: string): LiveRun | undefined;
10
10
  export declare function registerLiveRun(cronId: string, run: LiveRun): void;
11
11
  export declare function clearLiveRun(cronId: string, runId: string): void;
12
12
  export declare function markSelfCanceledCronRun(cronId: string, runId: string): boolean;
13
+ /** `cron`: one fire of a cron row (`cronId` names it). `exec`: one `POST /v1/exec` call, with no cron row. */
14
+ export type CronExecutionKind = {
15
+ kind: 'cron';
16
+ cronId: string;
17
+ } | {
18
+ kind: 'exec';
19
+ cronId: null;
20
+ };
13
21
  /** One cron execution's identity while it runs: the stored cron principal keyed by execution id.
14
22
  * Its socket answers as this principal; the grant sweep and the router read it. A principal is
15
23
  * never read off the cron row after the fire, so two executions of one run keep separate scopes. */
16
- export interface CronExecution {
24
+ export type CronExecution = CronExecutionKind & {
17
25
  executionId: string;
18
- cronId: string;
26
+ /** An exec, or a fire of a cron armed from one: every node it starts checks model sign-in first. */
27
+ fromExec: boolean;
19
28
  grantee: string;
20
29
  sub: string;
21
30
  grantId: string | null;
@@ -32,7 +41,7 @@ export interface CronExecution {
32
41
  reason: CronRefusalReason;
33
42
  message: string;
34
43
  } | null;
35
- }
44
+ };
36
45
  export declare function registerCronExecution(execution: CronExecution): void;
37
46
  export declare function cronExecutionFor(executionId: string): CronExecution | undefined;
38
47
  export declare function dropCronExecution(executionId: string): void;
@@ -1 +1 @@
1
- var k=Object.defineProperty;var i=(r,n)=>k(r,"name",{value:n,configurable:!0});import{createServer as F}from"node:http";import{chmodSync as g,existsSync as x,mkdirSync as A,readdirSync as O,rmSync as v,unlinkSync as z}from"node:fs";import{dirname as H,join as f}from"node:path";import{peerUid as M}from"../../native/linux.js";import{nodeRunDir as s}from"../../core/canvas/paths.js";import{readConfig as U,readMergedLaunchConfig as j}from"../../core/config.js";import{readRotationConfig as q}from"../../core/subscription-state.js";import{runningCrons as _}from"../../core/canvas/crons.js";import{emitEvent as u}from"../../core/events/emit.js";import{cronLaunchId as a}from"../../core/spaces/process.js";import{ensureAppSpace as B}from"../../core/spaces/space.js";import{launcherPresent as y,listSandboxes as b}from"../../core/grants/sandboxes.js";import{uidForApp as J}from"../../core/spaces/users.js";import{setModeBeneath as L,writeFileBeneath as N}from"../../core/spaces/open-beneath.js";import{cronExecutionFor as w,cronExecutions as D,dropCronExecution as S,registerCronExecution as C}from"./live-runs.js";let p=null;function sr(r){p=r}i(sr,"setCronRequestHandler");const E=i(r=>process.platform==="linux"?M(r.socket):null,"linuxPeerUid");let I=E;function dr(r){I=r??E}i(dr,"__setCronPeerUidForTest");const m=new Map;function T(r){return f(s(a(r)),"cli.sock")}i(T,"cronSocketPath");function W(r){return{class:"cron",app:r.grantee,sub:r.sub,grant:r.grantId,scopes:r.scopes,cron:r.cronId,execution:r.executionId,origin_run:r.originRunId,isolated:r.isolated,anchor:r.anchor,uid:r.uid}}i(W,"cronPrincipalClassOf");function d(r,n,t,o){r.writeHead(n,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:t,message:o,type:"invalid_request_error",origin:"runtime",retryable:!1}}))}i(d,"refuse");function G(r){return(n,t)=>{try{const o=w(r);if(o===void 0){d(t,401,"unauthorized","this cron execution has ended");return}const e=I(n);if(e!==null&&e!==o.uid){u({event:"api.principal.denied",level:"warn",fields:{method:n.method,execution:r}}),d(t,401,"unauthorized","missing or invalid bearer token");return}if(p===null){d(t,503,"daemon_unavailable","the API server is not running");return}p(n,t,W(o))}catch(o){u({event:"api.principal.failed",level:"error",error:o instanceof Error?o:new Error(String(o))}),t.headersSent||d(t,401,"unauthorized","missing or invalid bearer token")}}}i(G,"onCronRequest");async function P(r){const n=T(r);x(n)&&z(n);const t=F(G(r));await new Promise((o,e)=>{t.once("error",e),t.listen(n,()=>{t.off("error",e),o()})}),g(n,438),m.set(r,t)}i(P,"listen");async function lr(r,n,t){const o=s(a(r.executionId));C(r);try{A(o,{recursive:!0,mode:504}),g(o,504);const e={user:U("user"),launch:j(n,t),rotation:q()},c=f(o,"cron-launch.json");if(N(o,c,JSON.stringify({configSnapshot:e}),{atomic:!0,mode:416}),y()){const l=await B(r.grantee);L(o,c,416,{uid:process.getuid(),gid:l.gid})}await P(r.executionId)}catch(e){throw R(r.executionId),e}}i(lr,"openCronExecution");function R(r){S(r);const n=m.get(r);m.delete(r),n!==void 0&&(n.closeAllConnections(),n.close());try{v(s(a(r)),{recursive:!0,force:!0})}catch{}}i(R,"closeCronExecution");let h=Promise.resolve();function fr(){return h}i(fr,"cronExecutionsReady");function pr(r=b){return h=(async()=>{if(r===b&&!y())return;const t=new Set((await r()).map(e=>e.nodeId));for(const e of await _()){const c=e.run_execution_id;if(!(c==null||!t.has(a(c))||w(c)!==void 0))try{C({executionId:c,cronId:e.cron_id,grantee:e.grantee,sub:e.sub,grantId:e.grant_id,scopes:e.scopes,originRunId:e.origin_run_id,isolated:e.isolated,anchor:e.anchor_node,uid:await J(e.grantee),rebuilt:!0,refusal:null}),await P(c),u({level:"info",event:"cron.execution.rebuilt",fields:{cron_id:e.cron_id,execution_id:c}})}catch(l){S(c),u({level:"error",event:"cron.execution.rebuild_failed",error:l,fields:{cron_id:e.cron_id,execution_id:c}})}}if(process.platform!=="linux")return;const o=H(s(a("x")));if(x(o)){for(const e of O(o))if(!(!e.startsWith("cron-")||t.has(e)))try{v(f(o,e),{recursive:!0,force:!0})}catch{}}})().catch(t=>{u({level:"error",event:"cron.execution.rebuild_failed",error:t})}),h}i(pr,"rebuildCronExecutions");async function mr(){const r=new Set((await _()).map(n=>n.run_execution_id));for(const n of D())n.rebuilt&&!r.has(n.executionId)&&R(n.executionId)}i(mr,"reapRebuiltCronExecutions");export{dr as __setCronPeerUidForTest,R as closeCronExecution,fr as cronExecutionsReady,W as cronPrincipalClassOf,T as cronSocketPath,lr as openCronExecution,mr as reapRebuiltCronExecutions,pr as rebuildCronExecutions,sr as setCronRequestHandler};
1
+ var R=Object.defineProperty;var i=(r,n)=>R(r,"name",{value:n,configurable:!0});import{createServer as F}from"node:http";import{chmodSync as x,existsSync as g,mkdirSync as A,readdirSync as O,rmSync as _,unlinkSync as z}from"node:fs";import{dirname as H,join as f}from"node:path";import{peerUid as M}from"../../native/linux.js";import{nodeRunDir as s}from"../../core/canvas/paths.js";import{readConfig as U,readMergedLaunchConfig as j}from"../../core/config.js";import{readRotationConfig as q}from"../../core/subscription-state.js";import{runningCrons as v}from"../../core/canvas/crons.js";import{emitEvent as u}from"../../core/events/emit.js";import{cronLaunchId as a}from"../../core/spaces/process.js";import{ensureAppSpace as B}from"../../core/spaces/space.js";import{launcherPresent as y,listSandboxes as b}from"../../core/grants/sandboxes.js";import{uidForApp as J}from"../../core/spaces/users.js";import{setModeBeneath as L,writeFileBeneath as N}from"../../core/spaces/open-beneath.js";import{cronExecutionFor as w,cronExecutions as D,dropCronExecution as S,registerCronExecution as C}from"./live-runs.js";let p=null;function sr(r){p=r}i(sr,"setCronRequestHandler");const E=i(r=>process.platform==="linux"?M(r.socket):null,"linuxPeerUid");let k=E;function dr(r){k=r??E}i(dr,"__setCronPeerUidForTest");const m=new Map;function T(r){return f(s(a(r)),"cli.sock")}i(T,"cronSocketPath");function W(r){const n={class:"cron",app:r.grantee,sub:r.sub,grant:r.grantId,scopes:r.scopes,execution:r.executionId,from_exec:r.fromExec,origin_run:r.originRunId,isolated:r.isolated,anchor:r.anchor,uid:r.uid};return r.kind==="cron"?{...n,kind:"cron",cron:r.cronId}:{...n,kind:"exec",cron:null}}i(W,"cronPrincipalClassOf");function d(r,n,t,o){r.writeHead(n,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:t,message:o,type:"invalid_request_error",origin:"runtime",retryable:!1}}))}i(d,"refuse");function G(r){return(n,t)=>{try{const o=w(r);if(o===void 0){d(t,401,"unauthorized","this cron execution has ended");return}const e=k(n);if(e!==null&&e!==o.uid){u({event:"api.principal.denied",level:"warn",fields:{method:n.method,execution:r}}),d(t,401,"unauthorized","missing or invalid bearer token");return}if(p===null){d(t,503,"daemon_unavailable","the API server is not running");return}p(n,t,W(o))}catch(o){u({event:"api.principal.failed",level:"error",error:o instanceof Error?o:new Error(String(o))}),t.headersSent||d(t,401,"unauthorized","missing or invalid bearer token")}}}i(G,"onCronRequest");async function I(r){const n=T(r);g(n)&&z(n);const t=F(G(r));await new Promise((o,e)=>{t.once("error",e),t.listen(n,()=>{t.off("error",e),o()})}),x(n,438),m.set(r,t)}i(I,"listen");async function lr(r,n,t){const o=s(a(r.executionId));C(r);try{A(o,{recursive:!0,mode:504}),x(o,504);const e={user:U("user"),launch:j(n,t),rotation:q()},c=f(o,"cron-launch.json");if(N(o,c,JSON.stringify({configSnapshot:e}),{atomic:!0,mode:416}),y()){const l=await B(r.grantee);L(o,c,416,{uid:process.getuid(),gid:l.gid})}await I(r.executionId)}catch(e){throw P(r.executionId),e}}i(lr,"openCronExecution");function P(r){S(r);const n=m.get(r);m.delete(r),n!==void 0&&(n.closeAllConnections(),n.close());try{_(s(a(r)),{recursive:!0,force:!0})}catch{}}i(P,"closeCronExecution");let h=Promise.resolve();function fr(){return h}i(fr,"cronExecutionsReady");function pr(r=b){return h=(async()=>{if(r===b&&!y())return;const t=new Set((await r()).map(e=>e.nodeId));for(const e of await v()){const c=e.run_execution_id;if(!(c==null||!t.has(a(c))||w(c)!==void 0))try{C({executionId:c,kind:"cron",cronId:e.cron_id,fromExec:e.armed_from_exec,grantee:e.grantee,sub:e.sub,grantId:e.grant_id,scopes:e.scopes,originRunId:e.origin_run_id,isolated:e.isolated,anchor:e.anchor_node,uid:await J(e.grantee),rebuilt:!0,refusal:null}),await I(c),u({level:"info",event:"cron.execution.rebuilt",fields:{cron_id:e.cron_id,execution_id:c}})}catch(l){S(c),u({level:"error",event:"cron.execution.rebuild_failed",error:l,fields:{cron_id:e.cron_id,execution_id:c}})}}if(process.platform!=="linux")return;const o=H(s(a("x")));if(g(o)){for(const e of O(o))if(!(!e.startsWith("cron-")||t.has(e)))try{_(f(o,e),{recursive:!0,force:!0})}catch{}}})().catch(t=>{u({level:"error",event:"cron.execution.rebuild_failed",error:t})}),h}i(pr,"rebuildCronExecutions");async function mr(){const r=new Set((await v()).map(n=>n.run_execution_id));for(const n of D())n.rebuilt&&!r.has(n.executionId)&&P(n.executionId)}i(mr,"reapRebuiltCronExecutions");export{dr as __setCronPeerUidForTest,P as closeCronExecution,fr as cronExecutionsReady,W as cronPrincipalClassOf,T as cronSocketPath,lr as openCronExecution,mr as reapRebuiltCronExecutions,pr as rebuildCronExecutions,sr as setCronRequestHandler};
@@ -1,2 +1,2 @@
1
- var u=Object.defineProperty;var t=(e,o)=>u(e,"name",{value:o,configurable:!0});import{newNodeId as l}from"../../core/runtime/nodes.js";import{nodeAgentDir as f}from"../../core/canvas/node-agent-paths.js";import{getNode as s}from"../../core/canvas/canvas.js";import{parseCronSink as p}from"../../core/canvas/crons.js";import{hasNoNaturalCycle as c}from"../../core/runtime/revive-all.js";import{isParked as k}from"../../core/runtime/lifecycle.js";import{cronWakeOrigin as w}from"../../core/runtime/bearings.js";import{emitEvent as h}from"../../core/events/emit.js";import{cronBirthPrincipalOf as y,resolveLiveProfile as _,resolveLiveProfileName as v}from"./armed-context.js";import{checkCronBirth as g}from"./authority.js";async function J(e,o,i){const n=p(e.sink);if(n===null)return{kind:"none",delivered:"no sink armed \u2014 nothing delivered"};if(n.kind==="node"){const r=await s(n.node);if(r===null)throw new Error(`node sink ${n.node} is gone`);if(c(r)&&!k(r))throw new Error(`node sink ${n.node} has no natural cycle ahead (done or finalized)`);return{kind:"node",destinationNodeId:n.node,senderNodeId:e.created_by!==null&&await s(e.created_by)!==null?e.created_by:null,label:`\u23F0 cron ${e.name}`,stdout:i,urgency:e.tier}}if(n.kind==="spawn"){const r=y(e),a=await g(r);if(!a.allowed)return h({level:"warn",event:"cron.birth.refused",fields:{cron_id:e.cron_id,grant_id:e.grant_id,reason:a.reason,stage:"enqueue"}}),{kind:"none",delivered:`refused: ${a.reason}`,refused:!0};const d=await l(),m=[`Scheduled cron "${e.name}" (${e.cron_id}) fired and delivered its output to you as your kickoff.`,"","## Command output","",i].join(`
2
- `);return{kind:"action",adapter:{adapter:"cron_spawn",cronId:e.cron_id,nodeId:d,kind:n.spawn,cwd:e.isolated?await f(d,e.grantee):e.cwd,isolated:e.isolated,principal:r,profile:_(e),wakeOrigin:await w(e,"node_birth"),prompt:m},bodyJson:{runId:o,stdout:i}}}return{kind:"action",adapter:{adapter:"cron_human",cronId:e.cron_id,requestId:await l(),title:`\u23F0 cron ${e.name}`,summary:`Scheduled cron "${e.name}" delivered output for your attention.`,sessionName:`cron ${e.name} (${e.cron_id})`,profileName:v(e)??null},bodyJson:{runId:o,stdout:i}}}t(J,"resolveCronSink");export{J as resolveCronSink};
1
+ var u=Object.defineProperty;var t=(e,o)=>u(e,"name",{value:o,configurable:!0});import{newNodeId as l}from"../../core/runtime/nodes.js";import{nodeAgentDir as p}from"../../core/canvas/node-agent-paths.js";import{getNode as s}from"../../core/canvas/canvas.js";import{parseCronSink as w}from"../../core/canvas/crons.js";import{hasNoNaturalCycle as k}from"../../core/runtime/revive-all.js";import{isParked as c}from"../../core/runtime/lifecycle.js";import{cronWakeOrigin as h}from"../../core/runtime/bearings.js";import{emitEvent as y}from"../../core/events/emit.js";import{cronBirthPrincipalOf as _,resolveLiveProfile as m,resolveLiveProfileName as v}from"./armed-context.js";import{checkCronBirth as g}from"./authority.js";async function J(e,o,i){const n=w(e.sink);if(n===null)return{kind:"none",delivered:"no sink armed \u2014 nothing delivered"};if(n.kind==="node"){const r=await s(n.node);if(r===null)throw new Error(`node sink ${n.node} is gone`);if(k(r)&&!c(r))throw new Error(`node sink ${n.node} has no natural cycle ahead (done or finalized)`);return{kind:"node",destinationNodeId:n.node,senderNodeId:e.created_by!==null&&await s(e.created_by)!==null?e.created_by:null,label:`\u23F0 cron ${e.name}`,stdout:i,urgency:e.tier}}if(n.kind==="spawn"){const r=_(e),d=await g(r,{kind:n.spawn,cwd:e.cwd,profile:m(e)});if(!d.allowed)return y({level:"warn",event:"cron.birth.refused",fields:{cron_id:e.cron_id,grant_id:e.grant_id,reason:d.reason,stage:"enqueue"}}),{kind:"none",delivered:`refused: ${d.reason}`,refused:!0};const a=await l(),f=[`Scheduled cron "${e.name}" (${e.cron_id}) fired and delivered its output to you as your kickoff.`,"","## Command output","",i].join(`
2
+ `);return{kind:"action",adapter:{adapter:"cron_spawn",cronId:e.cron_id,nodeId:a,kind:n.spawn,cwd:e.isolated?await p(a,e.grantee):e.cwd,isolated:e.isolated,principal:r,profile:m(e),wakeOrigin:await h(e,"node_birth"),prompt:f},bodyJson:{runId:o,stdout:i}}}return{kind:"action",adapter:{adapter:"cron_human",cronId:e.cron_id,requestId:await l(),title:`\u23F0 cron ${e.name}`,summary:`Scheduled cron "${e.name}" delivered output for your attention.`,sessionName:`cron ${e.name} (${e.cron_id})`,profileName:v(e)??null},bodyJson:{runId:o,stdout:i}}}t(J,"resolveCronSink");export{J as resolveCronSink};
@@ -1,2 +1,2 @@
1
- var N=Object.defineProperty;var i=(e,t)=>N(e,"name",{value:t,configurable:!0});import{randomUUID as Y}from"node:crypto";import{CRON_OUTPUT_HEAD_BYTES as E,EMPTY_STDOUT_SHA256 as q,formatCronOutputHead as A}from"./cron/capture.js";import{clearLiveRun as g,cronDaemonInstanceId as $,registerLiveRun as J}from"./cron/live-runs.js";import{closeCronExecution as V,openCronExecution as X}from"./cron/run-socket.js";import{appProcessSandboxed as z}from"../core/spaces/process.js";import{nodeAgentDir as G}from"../core/canvas/paths.js";import{uidForApp as K}from"../core/spaces/users.js";import{buildArmedRunContext as Q}from"./cron/armed-context.js";import{ensureCronFolder as W}from"./cron/folders.js";import{checkCronFire as Z,cronPrincipalOf as j}from"./cron/authority.js";import{refusalError as ee}from"../core/ledger/index.js";import{startRefusal as te}from"../core/runtime/start-check.js";import{decideHeldSettle as re,EXIT_HELD as ne,HELD_DELIVERED as oe}from"./cron/held.js";import{settleRun as O}from"./cron/settle.js";import{applyOverlapPolicies as ae,expireDueCrons as se}from"./cron/passes.js";import{dueClockCrons as de,acquireCronRunLease as ie,releaseCronRunLease as le,attachCronRunPid as ue,advanceCron as ce,setCronState as fe}from"../core/canvas/crons.js";import{emitEvent as L}from"../core/events/emit.js";import{runAppCommand as me}from"../core/runtime/run-command.js";import{nextSlotAfter as pe}from"../core/wake.js";import{CRON_OUTPUT_HEAD_BYTES as Ne}from"./cron/capture.js";import{cronDaemonInstanceId as qe,markSelfCanceledCronRun as Je}from"./cron/live-runs.js";import{recoverStaleCronLeases as Xe}from"./cron/lease-recovery.js";async function he(e,t){const{runId:a,outOfBand:s,startedAtIso:u,finishedAtMs:c,code:d,timedOut:n,processError:f}=t,_=!s&&!n&&f==null&&d===ne&&!t.wasReplaced&&!t.wasSelfCanceled;let p,r;return t.wasReplaced?(p={kind:"stated",delivered:"replaced (overlap=replace)"},r="release"):_?(p={kind:"stated",delivered:oe},r=await re(e,u,c)??"release"):(p={kind:"disposed",ctx:{runId:a,startedAtIso:u,exitCode:n?-1:d,timedOut:n,runTimeoutS:e.run_timeout_s,processErrorMessage:f!=null?f.message:null,stdoutHead:t.stdoutHead,stdoutSha256:t.stdoutSha256,stderrHead:t.stderrHead},selfCanceled:t.wasSelfCanceled},r=s?"release":{kind:"clear"}),O(e,{outOfBand:s,outcome:p,record:{runId:a,startedAtIso:u,finishedAtMs:c,durationMs:c-t.startedAtMs,exitCode:n?-1:d,stdoutHead:t.stdoutHead===""?null:t.stdoutHead,stderrHead:f!=null?`run process error: ${f.message}`.slice(0,E):t.stderrHead===""?null:t.stderrHead},lease:r})}i(he,"settleClosedRun");function R(e,t,a,s){const u=Date.now(),c=`${a}: ${s.message}`.slice(0,E);return O(e,{outOfBand:t.outOfBand,outcome:{kind:"disposed",ctx:{runId:t.runId,startedAtIso:t.startedAtIso,exitCode:null,timedOut:!1,runTimeoutS:e.run_timeout_s,processErrorMessage:s.message,stdoutHead:"",stdoutSha256:q,stderrHead:c}},record:{runId:t.runId,startedAtIso:t.startedAtIso,finishedAtMs:u,durationMs:u-t.startedAtMs,exitCode:null,stdoutHead:null,stderrHead:c},lease:"release"})}i(R,"settleUnspawnedRun");async function P(e,t){let a;try{a=pe(e.recur,new Date(t))}catch(s){return L({level:"error",event:"cron.recur.unparseable",error:s,fields:{cron_id:e.cron_id,name:e.name,recur:e.recur}}),await fe(e.cron_id,"paused"),new Error(`unparseable recurrence ${JSON.stringify(e.recur)}: ${s.message} \u2014 this cron cannot be rescheduled until its recurrence is fixed (\`crtr cron cancel\` and re-arm it), so it has been paused.`)}return await ce(e.cron_id,a),null}i(P,"advanceRecurrence");function Pe(e,t){return B(e,t).settled}i(Pe,"executeCron");function B(e,t){const{outOfBand:a}=t;let s=i(()=>{},"admit");const u=new Promise(d=>{s=d}),c=new Promise((d,n)=>{let f=!1,_=i(()=>{},"undo");const p=i(r=>{if(_(),!f){n(r);return}f=!1,le(e.cron_id).then(()=>n(r),m=>{L({level:"error",event:"cron.lease.release_failed",error:m,fields:{cron_id:e.cron_id}}),n(r)})},"fail");(async()=>{const r=Y(),m=Date.now(),C=new Date(m).toISOString(),S={runId:r,outOfBand:a,startedAtIso:C,startedAtMs:m};if(!await ie(e.cron_id,$,C,a,r)){d(null);return}f=!0;let h=await Z(j(e));if(h.allowed){const o=await te({grantee:e.grantee,sub:e.sub,grant_id:e.grant_id});o!==null&&(h={allowed:!1,reason:"limit_reached",message:ee(o).message})}if(!h.allowed){try{if(!a&&e.recur!=null){const w=await P(e,m);if(w!==null)throw w}}catch(w){R(e,S,"run could not be armed",w).then(d,n);return}const o=Date.now();O(e,{outOfBand:a,outcome:{kind:"refused",reason:h.reason,message:h.message},record:{runId:r,startedAtIso:C,finishedAtMs:o,durationMs:o-m,exitCode:null,stdoutHead:null,stderrHead:`${h.reason}: ${h.message}`.slice(0,E)},lease:"release"}).then(d,n);return}let M;try{if(e.isolated&&W(e.grantee,e.cron_id),{env:M}=await Q(e,r),!a&&e.recur!=null){const o=await P(e,m);if(o!==null)throw o}}catch(o){R(e,S,"run could not be armed",o).then(d,n);return}let b=null,T=!1;const D={runId:r,replaced:!1,selfCanceled:!1,kill:i(()=>{b!==null?b():T=!0},"kill")};J(e.cron_id,D),_=i(()=>g(e.cron_id,r),"undo"),s();const x=z()?{executionId:r,cronId:e.cron_id,grantee:e.grantee,sub:e.sub,grantId:e.grant_id,scopes:e.scopes,originRunId:e.origin_run_id,isolated:e.isolated,anchor:e.anchor_node,uid:await K(e.grantee),rebuilt:!1,refusal:null}:null,I=i(()=>{x!==null&&V(r)},"closeExecution");_=i(()=>{g(e.cron_id,r),I()},"undo"),i(async()=>(x!==null&&await X(x,e.cwd,e.profile),me({command:e.command,cwd:e.cwd,env:M,app:e.grantee,isolated:e.isolated,principal:{sub:e.sub,grantId:e.grant_id,scopes:e.scopes},...e.isolated?{folder:e.cwd}:{},...x!==null?{cron:{executionId:r,...e.anchor_node===null||e.isolated?{}:{anchorDir:G(e.anchor_node,e.grantee)}}}:e.anchor_node===null||e.isolated?{}:{nodeId:e.anchor_node},timeoutMs:e.run_timeout_s*1e3,headBytes:E})),"launch")().then(async o=>{if(o.pid===null){g(e.cron_id,r),I(),o.completion.then(l=>{R(e,S,"run failed to spawn",l.processError??new Error("bash did not provide a process id")).then(d,n)},n);return}_=i(()=>{g(e.cron_id,r),I(),o.terminate()},"undo");let w=!1;const v=i(()=>{o.completion.then(l=>{const y=D.replaced,U=D.selfCanceled,H=x?.refusal??null;if(g(e.cron_id,r),I(),w){d(null);return}if(H!==null&&!y){const k=Date.now(),F=A(l.stderr,l.stderrTruncated);O(e,{outOfBand:a,outcome:{kind:"refused",reason:H.reason,message:H.message},record:{runId:r,startedAtIso:C,finishedAtMs:k,durationMs:k-m,exitCode:l.exitCode,stdoutHead:A(l.stdout,l.stdoutTruncated)||null,stderrHead:`${H.reason}: ${H.message}
2
- ${F}`.trim().slice(0,E)},lease:"release"}).then(d,n);return}he(e,{runId:r,outOfBand:a,startedAtIso:C,startedAtMs:m,finishedAtMs:Date.now(),code:l.exitCode,timedOut:l.timedOut,processError:l.processError,stdoutHead:A(l.stdout,l.stdoutTruncated),stderrHead:A(l.stderr,l.stderrTruncated),stdoutSha256:l.stdoutSha256,wasReplaced:y,wasSelfCanceled:U}).then(d,n)},n)},"settleCommand");if(!await ue(e.cron_id,$,C,o.pid)&&!D.selfCanceled){w=!0,o.terminate(),v();return}b=o.terminate,T&&o.terminate(),v()},o=>{g(e.cron_id,r),I(),R(e,S,"run failed to spawn",o).then(d,n)}).catch(p)})().catch(p)});return c.then(s,s),{admitted:u,settled:c}}i(B,"startCron");async function Be(e,t={}){const a=new Date(e).toISOString();await se(a),await ae(e,a);const s=[];for(const u of await de(a)){const{admitted:c,settled:d}=B(u,{outOfBand:!1}),n=d.then(()=>{});n.catch(()=>{}),await c,s.push(n),t.register?.(n)}for(const u of s)u.catch(()=>{});return s}i(Be,"runDueCrons");export{Ne as CRON_OUTPUT_HEAD_BYTES,qe as cronDaemonInstanceId,Pe as executeCron,Je as markSelfCanceledCronRun,Xe as recoverStaleCronLeases,Be as runDueCrons,B as startCron};
1
+ var N=Object.defineProperty;var i=(e,t)=>N(e,"name",{value:t,configurable:!0});import{randomUUID as Y}from"node:crypto";import{CRON_OUTPUT_HEAD_BYTES as E,EMPTY_STDOUT_SHA256 as q,formatCronOutputHead as A}from"./cron/capture.js";import{clearLiveRun as g,cronDaemonInstanceId as $,registerLiveRun as J}from"./cron/live-runs.js";import{closeCronExecution as V,openCronExecution as X}from"./cron/run-socket.js";import{appProcessSandboxed as z}from"../core/spaces/process.js";import{nodeAgentDir as G}from"../core/canvas/paths.js";import{uidForApp as K}from"../core/spaces/users.js";import{buildArmedRunContext as Q}from"./cron/armed-context.js";import{ensureCronFolder as W}from"./cron/folders.js";import{checkCronFire as Z,cronPrincipalOf as j}from"./cron/authority.js";import{refusalError as ee}from"../core/ledger/index.js";import{startRefusal as te}from"../core/runtime/start-check.js";import{decideHeldSettle as re,EXIT_HELD as ne,HELD_DELIVERED as oe}from"./cron/held.js";import{settleRun as O}from"./cron/settle.js";import{applyOverlapPolicies as ae,expireDueCrons as se}from"./cron/passes.js";import{dueClockCrons as de,acquireCronRunLease as ie,releaseCronRunLease as le,attachCronRunPid as ue,advanceCron as ce,setCronState as fe}from"../core/canvas/crons.js";import{emitEvent as L}from"../core/events/emit.js";import{runAppCommand as me}from"../core/runtime/run-command.js";import{nextSlotAfter as pe}from"../core/wake.js";import{CRON_OUTPUT_HEAD_BYTES as Ne}from"./cron/capture.js";import{cronDaemonInstanceId as qe,markSelfCanceledCronRun as Je}from"./cron/live-runs.js";import{recoverStaleCronLeases as Xe}from"./cron/lease-recovery.js";async function he(e,t){const{runId:a,outOfBand:s,startedAtIso:u,finishedAtMs:c,code:d,timedOut:n,processError:f}=t,w=!s&&!n&&f==null&&d===ne&&!t.wasReplaced&&!t.wasSelfCanceled;let p,r;return t.wasReplaced?(p={kind:"stated",delivered:"replaced (overlap=replace)"},r="release"):w?(p={kind:"stated",delivered:oe},r=await re(e,u,c)??"release"):(p={kind:"disposed",ctx:{runId:a,startedAtIso:u,exitCode:n?-1:d,timedOut:n,runTimeoutS:e.run_timeout_s,processErrorMessage:f!=null?f.message:null,stdoutHead:t.stdoutHead,stdoutSha256:t.stdoutSha256,stderrHead:t.stderrHead},selfCanceled:t.wasSelfCanceled},r=s?"release":{kind:"clear"}),O(e,{outOfBand:s,outcome:p,record:{runId:a,startedAtIso:u,finishedAtMs:c,durationMs:c-t.startedAtMs,exitCode:n?-1:d,stdoutHead:t.stdoutHead===""?null:t.stdoutHead,stderrHead:f!=null?`run process error: ${f.message}`.slice(0,E):t.stderrHead===""?null:t.stderrHead},lease:r})}i(he,"settleClosedRun");function R(e,t,a,s){const u=Date.now(),c=`${a}: ${s.message}`.slice(0,E);return O(e,{outOfBand:t.outOfBand,outcome:{kind:"disposed",ctx:{runId:t.runId,startedAtIso:t.startedAtIso,exitCode:null,timedOut:!1,runTimeoutS:e.run_timeout_s,processErrorMessage:s.message,stdoutHead:"",stdoutSha256:q,stderrHead:c}},record:{runId:t.runId,startedAtIso:t.startedAtIso,finishedAtMs:u,durationMs:u-t.startedAtMs,exitCode:null,stdoutHead:null,stderrHead:c},lease:"release"})}i(R,"settleUnspawnedRun");async function P(e,t){let a;try{a=pe(e.recur,new Date(t))}catch(s){return L({level:"error",event:"cron.recur.unparseable",error:s,fields:{cron_id:e.cron_id,name:e.name,recur:e.recur}}),await fe(e.cron_id,"paused"),new Error(`unparseable recurrence ${JSON.stringify(e.recur)}: ${s.message} \u2014 this cron cannot be rescheduled until its recurrence is fixed (\`crtr cron cancel\` and re-arm it), so it has been paused.`)}return await ce(e.cron_id,a),null}i(P,"advanceRecurrence");function Pe(e,t){return B(e,t).settled}i(Pe,"executeCron");function B(e,t){const{outOfBand:a}=t;let s=i(()=>{},"admit");const u=new Promise(d=>{s=d}),c=new Promise((d,n)=>{let f=!1,w=i(()=>{},"undo");const p=i(r=>{if(w(),!f){n(r);return}f=!1,le(e.cron_id).then(()=>n(r),m=>{L({level:"error",event:"cron.lease.release_failed",error:m,fields:{cron_id:e.cron_id}}),n(r)})},"fail");(async()=>{const r=Y(),m=Date.now(),C=new Date(m).toISOString(),S={runId:r,outOfBand:a,startedAtIso:C,startedAtMs:m};if(!await ie(e.cron_id,$,C,a,r)){d(null);return}f=!0;let h=await Z(j(e));if(h.allowed){const o=await te({grantee:e.grantee,sub:e.sub,grant_id:e.grant_id});o!==null&&(h={allowed:!1,reason:"limit_reached",message:ee(o).message})}if(!h.allowed){try{if(!a&&e.recur!=null){const _=await P(e,m);if(_!==null)throw _}}catch(_){R(e,S,"run could not be armed",_).then(d,n);return}const o=Date.now();O(e,{outOfBand:a,outcome:{kind:"refused",reason:h.reason,message:h.message},record:{runId:r,startedAtIso:C,finishedAtMs:o,durationMs:o-m,exitCode:null,stdoutHead:null,stderrHead:`${h.reason}: ${h.message}`.slice(0,E)},lease:"release"}).then(d,n);return}let M;try{if(e.isolated&&W(e.grantee,e.cron_id),{env:M}=await Q(e,r),!a&&e.recur!=null){const o=await P(e,m);if(o!==null)throw o}}catch(o){R(e,S,"run could not be armed",o).then(d,n);return}let b=null,T=!1;const D={runId:r,replaced:!1,selfCanceled:!1,kill:i(()=>{b!==null?b():T=!0},"kill")};J(e.cron_id,D),w=i(()=>g(e.cron_id,r),"undo"),s();const x=z()?{executionId:r,kind:"cron",cronId:e.cron_id,fromExec:e.armed_from_exec,grantee:e.grantee,sub:e.sub,grantId:e.grant_id,scopes:e.scopes,originRunId:e.origin_run_id,isolated:e.isolated,anchor:e.anchor_node,uid:await K(e.grantee),rebuilt:!1,refusal:null}:null,I=i(()=>{x!==null&&V(r)},"closeExecution");w=i(()=>{g(e.cron_id,r),I()},"undo"),i(async()=>(x!==null&&await X(x,e.cwd,e.profile),me({command:e.command,cwd:e.cwd,env:M,app:e.grantee,isolated:e.isolated,principal:{sub:e.sub,grantId:e.grant_id,scopes:e.scopes},...e.isolated?{folder:e.cwd}:{},...x!==null?{cron:{executionId:r,...e.anchor_node===null||e.isolated?{}:{anchorDir:G(e.anchor_node,e.grantee)}}}:e.anchor_node===null||e.isolated?{}:{nodeId:e.anchor_node},timeoutMs:e.run_timeout_s*1e3,headBytes:E})),"launch")().then(async o=>{if(o.pid===null){g(e.cron_id,r),I(),o.completion.then(l=>{R(e,S,"run failed to spawn",l.processError??new Error("bash did not provide a process id")).then(d,n)},n);return}w=i(()=>{g(e.cron_id,r),I(),o.terminate()},"undo");let _=!1;const v=i(()=>{o.completion.then(l=>{const k=D.replaced,U=D.selfCanceled,H=x?.refusal??null;if(g(e.cron_id,r),I(),_){d(null);return}if(H!==null&&!k){const y=Date.now(),F=A(l.stderr,l.stderrTruncated);O(e,{outOfBand:a,outcome:{kind:"refused",reason:H.reason,message:H.message},record:{runId:r,startedAtIso:C,finishedAtMs:y,durationMs:y-m,exitCode:l.exitCode,stdoutHead:A(l.stdout,l.stdoutTruncated)||null,stderrHead:`${H.reason}: ${H.message}
2
+ ${F}`.trim().slice(0,E)},lease:"release"}).then(d,n);return}he(e,{runId:r,outOfBand:a,startedAtIso:C,startedAtMs:m,finishedAtMs:Date.now(),code:l.exitCode,timedOut:l.timedOut,processError:l.processError,stdoutHead:A(l.stdout,l.stdoutTruncated),stderrHead:A(l.stderr,l.stderrTruncated),stdoutSha256:l.stdoutSha256,wasReplaced:k,wasSelfCanceled:U}).then(d,n)},n)},"settleCommand");if(!await ue(e.cron_id,$,C,o.pid)&&!D.selfCanceled){_=!0,o.terminate(),v();return}b=o.terminate,T&&o.terminate(),v()},o=>{g(e.cron_id,r),I(),R(e,S,"run failed to spawn",o).then(d,n)}).catch(p)})().catch(p)});return c.then(s,s),{admitted:u,settled:c}}i(B,"startCron");async function Be(e,t={}){const a=new Date(e).toISOString();await se(a),await ae(e,a);const s=[];for(const u of await de(a)){const{admitted:c,settled:d}=B(u,{outOfBand:!1}),n=d.then(()=>{});n.catch(()=>{}),await c,s.push(n),t.register?.(n)}for(const u of s)u.catch(()=>{});return s}i(Be,"runDueCrons");export{Ne as CRON_OUTPUT_HEAD_BYTES,qe as cronDaemonInstanceId,Pe as executeCron,Je as markSelfCanceledCronRun,Xe as recoverStaleCronLeases,Be as runDueCrons,B as startCron};
@@ -1 +1 @@
1
- var p=Object.defineProperty;var d=(e,t)=>p(e,"name",{value:t,configurable:!0});import{rmSync as g}from"node:fs";import{claimDaemonAction as h,dueDaemonActions as w,settleDaemonAction as _}from"../../core/canvas/daemon-actions.js";import{getNode as y}from"../../core/canvas/canvas.js";import{emitEvent as l}from"../../core/events/emit.js";import{operationIdContext as c}from"../../core/events/operation-id.js";import{createPageRequest as I,getRequest as u}from"../../core/human/requests.js";import{synthesizedPageDocument as v,writeSynthesizedPage as k}from"../../core/human/page-synth.js";import{preparePage as $}from"../../core/human/page-authoring.js";import{resolvePageComponents as b}from"../../core/config.js";import{spawnChild as q}from"../../core/runtime/spawn.js";import{isStartRefusal as D}from"../../core/runtime/start-check.js";import{runDelivery as P}from"../delivery/run-delivery.js";import{deliveryDaemonInstanceId as m}from"./delivery-lane.js";import{cronBirthPlacement as S}from"../cron/armed-context.js";import{checkCronBirth as N}from"../cron/authority.js";async function o(e,t,n){await _(e.actionId,e.attempts,t,n,Date.now())}d(o,"settle");async function f(e,t,n){await o(e,t,{state:"permanent_failed",failure:{kind:"spawn_error",message:n}})}d(f,"failed");async function z(e,t,n){const i=await y(n.nodeId);if(i!==null){if(i.frozen_at!==null||i.pi_session_id!==null||i.status==="idle"||i.status==="done"||i.status==="canceled"){await o(e,t,{state:"acknowledged"});return}await f(e,t,i.status==="dead"?`cron spawn ${n.nodeId} recorded a failed birth`:`cron spawn ${n.nodeId} found an incomplete birth`);return}const r=n.principal;if(r===void 0){c.fresh(()=>l({level:"warn",event:"cron.birth.refused",node_id:n.nodeId,fields:{cron_id:n.cronId??null,action_id:e.actionId,reason:"grant_missing",stage:"delivery"}})),await o(e,t,{state:"permanent_failed",failure:{kind:"grant_refused",message:"grant_missing: the queued birth carries no cron principal"}});return}const a=await N(r);if(!a.allowed){c.fresh(()=>l({level:"warn",event:"cron.birth.refused",node_id:n.nodeId,fields:{cron_id:r.cron_id,action_id:e.actionId,grant_id:r.grant_id,reason:a.reason,stage:"delivery"}})),await o(e,t,{state:"permanent_failed",failure:{kind:"grant_refused",message:`${a.reason}: ${a.message}`}});return}try{await q({nodeId:n.nodeId,kind:n.kind,cwd:n.cwd,prompt:n.prompt,...await S(r,n.nodeId),readiness:"launch",profile:n.profile,isolated:n.isolated===!0,wakeOrigin:n.wakeOrigin}),await o(e,t,{state:"acknowledged"})}catch(s){if(D(s)){await o(e,t,{state:"permanent_failed",failure:{kind:"grant_refused",message:s.message}});return}await f(e,t,`cron spawn ${n.nodeId} failed: ${s instanceof Error?s.message:String(s)}`),c.fresh(()=>l({level:"error",event:"delivery.cron_spawn_failed",node_id:n.nodeId,error:s,fields:{action_id:e.actionId}}))}}d(z,"deliverSpawn");function A(e,t){const n=k(v(e.title,e.summary,t,!1));try{return $({sourceFile:n.path,source:{sessionName:e.sessionName,...e.profileName===null?{}:{profileName:e.profileName}},productKinds:b(),delivery:{placement:"inline",inbox:!0,reply:!1}})}finally{g(n.dir,{recursive:!0,force:!0})}}d(A,"preparedCronPage");async function C(e,t,n){if(await u(n.requestId)!==null){await o(e,t,{state:"acknowledged"});return}const i=e.body,r=typeof i?.stdout=="string"?i.stdout:null;if(r===null){await f(e,t,`cron human ${n.requestId} has no frozen stdout`);return}try{await I({request_id:n.requestId,prepared:A(n,r)}),await o(e,t,{state:"acknowledged"})}catch(a){await f(e,t,`cron human ${n.requestId} failed: ${a instanceof Error?a.message:String(a)}`)}}d(C,"deliverHuman");async function E(e,t){const n=e.adapter;if(e.body===null){await f(e,t,`daemon action ${e.actionId} has no body`);return}if(n.adapter==="subprocess"){let i=n.node_id;if(i===void 0){const r=e.idempotencyKey;if(!r.startsWith("human-action:"))throw new Error(`daemon action ${e.actionId} has no originating app`);const a=await u(r.slice(13));if(!a)throw new Error(`daemon action ${e.actionId} has no originating human request`);i=a.requester_node_id}await P({argv:n.argv,cwd:n.cwd,document:e.body,attempt:e.attempts,nodeId:i,...n.app?{app:n.app}:{}},async r=>await o(e,t,r));return}if(n.adapter==="cron_spawn"){await z(e,t,n);return}if(n.adapter==="cron_human"){await C(e,t,n);return}await f(e,t,`daemon action ${e.actionId} has unknown adapter ${String(n.adapter)}`)}d(E,"deliverAction");class V{static{d(this,"ActionLaneReconciler")}async run(t,n){if(!n.lifecycle.acceptsDetachedWork())return;let i;try{i=await w(t)}catch(r){c.fresh(()=>l({level:"error",event:"daemon_action.scan_failed",error:r}));return}for(const r of i)try{const a=await h(r.actionId,t,m);if(a===null)continue;n.lifecycle.registerDetached(E(a,m).catch(s=>{c.fresh(()=>l({level:"error",event:"daemon_action.failed",error:s,fields:{action_id:a.actionId}}))}))}catch(a){c.fresh(()=>l({level:"error",event:"daemon_action.claim_failed",error:a,fields:{action_id:r.actionId}}))}}}export{V as ActionLaneReconciler};
1
+ var p=Object.defineProperty;var d=(n,r)=>p(n,"name",{value:r,configurable:!0});import{rmSync as g}from"node:fs";import{claimDaemonAction as h,dueDaemonActions as w,settleDaemonAction as _}from"../../core/canvas/daemon-actions.js";import{getNode as y}from"../../core/canvas/canvas.js";import{emitEvent as l}from"../../core/events/emit.js";import{operationIdContext as c}from"../../core/events/operation-id.js";import{createPageRequest as I,getRequest as u}from"../../core/human/requests.js";import{synthesizedPageDocument as v,writeSynthesizedPage as k}from"../../core/human/page-synth.js";import{preparePage as $}from"../../core/human/page-authoring.js";import{resolvePageComponents as b}from"../../core/config.js";import{spawnChild as q}from"../../core/runtime/spawn.js";import{isStartRefusal as D}from"../../core/runtime/start-check.js";import{runDelivery as P}from"../delivery/run-delivery.js";import{deliveryDaemonInstanceId as m}from"./delivery-lane.js";import{cronBirthPlacement as S}from"../cron/armed-context.js";import{checkCronBirth as N}from"../cron/authority.js";async function o(n,r,e){await _(n.actionId,n.attempts,r,e,Date.now())}d(o,"settle");async function f(n,r,e){await o(n,r,{state:"permanent_failed",failure:{kind:"spawn_error",message:e}})}d(f,"failed");async function z(n,r,e){const i=await y(e.nodeId);if(i!==null){if(i.frozen_at!==null||i.pi_session_id!==null||i.status==="idle"||i.status==="done"||i.status==="canceled"){await o(n,r,{state:"acknowledged"});return}await f(n,r,i.status==="dead"?`cron spawn ${e.nodeId} recorded a failed birth`:`cron spawn ${e.nodeId} found an incomplete birth`);return}const t=e.principal;if(t===void 0){c.fresh(()=>l({level:"warn",event:"cron.birth.refused",node_id:e.nodeId,fields:{cron_id:e.cronId??null,action_id:n.actionId,reason:"grant_missing",stage:"delivery"}})),await o(n,r,{state:"permanent_failed",failure:{kind:"grant_refused",message:"grant_missing: the queued birth carries no cron principal"}});return}const a=await N(t,{kind:e.kind,cwd:e.cwd,profile:e.profile});if(!a.allowed){c.fresh(()=>l({level:"warn",event:"cron.birth.refused",node_id:e.nodeId,fields:{cron_id:t.cron_id,action_id:n.actionId,grant_id:t.grant_id,reason:a.reason,stage:"delivery"}})),await o(n,r,{state:"permanent_failed",failure:{kind:"grant_refused",message:`${a.reason}: ${a.message}`}});return}try{await q({nodeId:e.nodeId,kind:e.kind,cwd:e.cwd,prompt:e.prompt,...await S(t,e.nodeId),readiness:"launch",profile:e.profile,isolated:e.isolated===!0,wakeOrigin:e.wakeOrigin}),await o(n,r,{state:"acknowledged"})}catch(s){if(D(s)){await o(n,r,{state:"permanent_failed",failure:{kind:"grant_refused",message:s.message}});return}await f(n,r,`cron spawn ${e.nodeId} failed: ${s instanceof Error?s.message:String(s)}`),c.fresh(()=>l({level:"error",event:"delivery.cron_spawn_failed",node_id:e.nodeId,error:s,fields:{action_id:n.actionId}}))}}d(z,"deliverSpawn");function A(n,r){const e=k(v(n.title,n.summary,r,!1));try{return $({sourceFile:e.path,source:{sessionName:n.sessionName,...n.profileName===null?{}:{profileName:n.profileName}},productKinds:b(),delivery:{placement:"inline",inbox:!0,reply:!1}})}finally{g(e.dir,{recursive:!0,force:!0})}}d(A,"preparedCronPage");async function C(n,r,e){if(await u(e.requestId)!==null){await o(n,r,{state:"acknowledged"});return}const i=n.body,t=typeof i?.stdout=="string"?i.stdout:null;if(t===null){await f(n,r,`cron human ${e.requestId} has no frozen stdout`);return}try{await I({request_id:e.requestId,prepared:A(e,t)}),await o(n,r,{state:"acknowledged"})}catch(a){await f(n,r,`cron human ${e.requestId} failed: ${a instanceof Error?a.message:String(a)}`)}}d(C,"deliverHuman");async function E(n,r){const e=n.adapter;if(n.body===null){await f(n,r,`daemon action ${n.actionId} has no body`);return}if(e.adapter==="subprocess"){let i=e.node_id;if(i===void 0){const t=n.idempotencyKey;if(!t.startsWith("human-action:"))throw new Error(`daemon action ${n.actionId} has no originating app`);const a=await u(t.slice(13));if(!a)throw new Error(`daemon action ${n.actionId} has no originating human request`);i=a.requester_node_id}await P({argv:e.argv,cwd:e.cwd,document:n.body,attempt:n.attempts,nodeId:i,...e.app?{app:e.app}:{}},async t=>await o(n,r,t));return}if(e.adapter==="cron_spawn"){await z(n,r,e);return}if(e.adapter==="cron_human"){await C(n,r,e);return}await f(n,r,`daemon action ${n.actionId} has unknown adapter ${String(e.adapter)}`)}d(E,"deliverAction");class V{static{d(this,"ActionLaneReconciler")}async run(r,e){if(!e.lifecycle.acceptsDetachedWork())return;let i;try{i=await w(r)}catch(t){c.fresh(()=>l({level:"error",event:"daemon_action.scan_failed",error:t}));return}for(const t of i)try{const a=await h(t.actionId,r,m);if(a===null)continue;e.lifecycle.registerDetached(E(a,m).catch(s=>{c.fresh(()=>l({level:"error",event:"daemon_action.failed",error:s,fields:{action_id:a.actionId}}))}))}catch(a){c.fresh(()=>l({level:"error",event:"daemon_action.claim_failed",error:a,fields:{action_id:t.actionId}}))}}}export{V as ActionLaneReconciler};
@@ -7,6 +7,21 @@ 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.560
11
+
12
+ A read waits through a hosted runtime being created or replaced, instead of failing with `503`. No new option.
13
+
14
+ - **Reads wait up to 60 seconds.** When the hosting router answers a `GET` or `HEAD` with its `503 runtime_unreachable` carrying `details: { runtime_state: "creating" | "recovering", forwarded: false }` (the request reached no runtime), the SDK sleeps for `Retry-After` and sends it again, within the call's `signal` and `deadline`/`timeout` and at most 60 seconds in all. If the runtime is still unavailable then, the call fails with that same `503`. `maxRetries: 0` turns the wait off.
15
+ - **Writes are never retried on this state.** A `POST`, `PUT`, `PATCH` or `DELETE` gets the `503` straight away, as before. The live event stream's own reconnect is unchanged.
16
+
17
+ ## 0.3.559
18
+
19
+ An app can run the `crtr` command line in its own sandbox on the person's runtime. Additive.
20
+
21
+ - **`ConnectedCrouter.exec(argv, { timeoutS? })`** (`POST /v1/exec`, scopes `crtr:act` and `crtr:schedule`) runs `crtr` with `argv` (no shell, no stdin; under 32 KB as JSON text) in the app's files folder, as the app under its current grant, and returns `exit_code`, `signal`, `stdout`, `stderr` (256 KiB each, with a `*_truncated` flag), `timed_out` and `duration_ms`. A non-zero exit or a timeout resolves. `timeoutS` defaults to 60 and is at most 100. A fifth exec while 4 of the app's are running is refused `429 busy` (retryable), and a runtime with no root launcher (a person's own Mac) refuses `503 sandbox_unavailable`.
22
+ - **`409 model_sign_in_required`.** An agent started from an exec, or by a cron armed from one, is refused when the person has not signed in to a model for it; `details.url` is the directory's AI subscription page to send them to. `crtr` prints the message and that URL.
23
+ - **FakeCloud** serves `exec`: `cloud.exec.respond(handler)` sets the answer (throw `FakeCloud.error(...)` for a refusal) and `cloud.exec.calls` lists the accepted calls. It checks both scopes and the 4-live cap.
24
+
10
25
  ## 0.3.554
11
26
 
12
27
  `RunObject.recap` (`runs.get`, `runs.list`): the root's latest idle recap, `{ goal, state, at }`, where `goal` says what the conversation is for and `state` where it stands. The runtime writes it after a turn once the root goes idle; it is absent before the first one. Additive.
@@ -38,7 +38,7 @@ Every namespace exported by the client. Namespaces are camelCase. Verbs are `cre
38
38
  | `grant` | `get` (`wait`, `since`: hold until `version` changes) | `GET /v1/grant` |
39
39
  | `health` | `get` | `GET /v1/health` |
40
40
 
41
- `runs`, `uploads`, `attachments`, `shares`, `questions`, `docs` and the app side of `grant` and `providers` are the application-facing API an app reaches through the app listener; `oauth.client(userId)` returns a client typed to exactly that set (`AppClient`); `customObjects` (`register`, `publish`, `unregister`) and `canvas` (`watch`, `unwatch`) are served too, for the custom objects your app owns; `oauth.appClient(userId)` returns the same `AppClient` type without a stored sign-in ([Connections](https://sdk.docs.northlight.ai/docs/concepts/connections#acting-without-a-sign-in)). `nodes`, `bash`, `crons`, `human`, `models`, `structuredRequests`, `system`, `auth`, the rest of `canvas` and `customObjects` (`receive`, `ack`), `files.read/write/list` and `profiles.ensure/retrieve` are the owner's own daemon and are not served to an app. An app schedules work with its own timer calling `runs.start`, or a run's agent runs `crtr cron add` under `crtr:schedule`. See [Apps on Northlight](./guides/apps-on-northlight.md) and [Apps with many users](./guides/apps-with-many-users.md). `memory` is the person's `public` documents ([Memory](./memory.md)). `docs` writes, reads and edits documents the calling app owns: `docs.write({name, body, preview, kind?, rules?})` returns the canonical `name`; `docs.get(name)` returns `body`, `revision`, `rules` and the edit `base`; `docs.edit(name, {body, rationale, base?})` changes it in place and refuses a stale `base` with `ConflictError` (`stale_revision`). To keep one document current, `get` it (catch `NotFoundError` on first run to `write`), compare `body` to your text, and `edit` only when they differ. `system.health` (`/healthz`) is the daemon's liveness check; `health.get` is the app listener's.
41
+ `runs`, `uploads`, `attachments`, `shares`, `questions`, `docs` and the app side of `grant` and `providers` are the application-facing API an app reaches through the app listener; `oauth.client(userId)` returns a client typed to exactly that set (`AppClient`); `customObjects` (`register`, `publish`, `unregister`) and `canvas` (`watch`, `unwatch`) are served too, for the custom objects your app owns; `oauth.appClient(userId)` returns the same `AppClient` type without a stored sign-in ([Connections](https://sdk.docs.northlight.ai/docs/concepts/connections#acting-without-a-sign-in)). Both return a `ConnectedCrouter`, which also has `exec(argv, { timeoutS? })` (`POST /v1/exec`, scopes `crtr:act` and `crtr:schedule`): it runs `crtr <argv>` with no shell in the app's sandbox on the person's runtime and returns the same result shape as `bash.run`; at most 4 run at once per app (`429 busy`). `nodes`, `bash`, `crons`, `human`, `models`, `structuredRequests`, `system`, `auth`, the rest of `canvas` and `customObjects` (`receive`, `ack`), `files.read/write/list` and `profiles.ensure/retrieve` are the owner's own daemon and are not served to an app. An app schedules work with its own timer calling `runs.start`, or a run's agent runs `crtr cron add` under `crtr:schedule`. See [Apps on Northlight](./guides/apps-on-northlight.md) and [Apps with many users](./guides/apps-with-many-users.md). `memory` is the person's `public` documents ([Memory](./memory.md)). `docs` writes, reads and edits documents the calling app owns: `docs.write({name, body, preview, kind?, rules?})` returns the canonical `name`; `docs.get(name)` returns `body`, `revision`, `rules` and the edit `base`; `docs.edit(name, {body, rationale, base?})` changes it in place and refuses a stale `base` with `ConflictError` (`stale_revision`). To keep one document current, `get` it (catch `NotFoundError` on first run to `write`), compare `body` to your text, and `edit` only when they differ. `system.health` (`/healthz`) is the daemon's liveness check; `health.get` is the app listener's.
42
42
 
43
43
  Action methods keep the product's literal name (`fork`, `revive`, `promote`, `yield`) rather than being renamed into a generic verb. Every request-capable method takes `RequestOptions` as its final argument after its path, body, or query arguments. `RequestOptions` is `{ headers?, signal?, timeout?, maxRetries? }`; stream event options exclude `timeout` and add `after`.
44
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.558",
3
+ "version": "0.3.560",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -141,7 +141,7 @@
141
141
  },
142
142
  "license": "UNLICENSED",
143
143
  "dependencies": {
144
- "@north-light/identity": "0.3.558",
144
+ "@north-light/identity": "0.3.560",
145
145
  "@earendil-works/pi-agent-core": "1.1.0",
146
146
  "@earendil-works/pi-ai": "1.1.0",
147
147
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.558",
3
+ "version": "0.3.560",
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.558",
3
+ "version": "0.3.560",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.558",
9
+ "version": "0.3.560",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -24,7 +24,7 @@
24
24
  "@milkdown/preset-gfm": "^7.22.3",
25
25
  "@milkdown/prose": "^7.22.3",
26
26
  "@milkdown/utils": "^7.22.3",
27
- "@north-light/identity": "0.3.558",
27
+ "@north-light/identity": "0.3.560",
28
28
  "cron-parser": "^5.6.0",
29
29
  "esbuild": "^0.27.7",
30
30
  "jose": "^6.2.12",
@@ -12455,17 +12455,17 @@
12455
12455
  },
12456
12456
  "packages/crouter-api": {
12457
12457
  "name": "@north-light/api",
12458
- "version": "0.3.558",
12458
+ "version": "0.3.560",
12459
12459
  "license": "UNLICENSED"
12460
12460
  },
12461
12461
  "packages/crouter-env-docker": {
12462
12462
  "name": "@north-light/env-docker",
12463
- "version": "0.3.558",
12463
+ "version": "0.3.560",
12464
12464
  "license": "UNLICENSED"
12465
12465
  },
12466
12466
  "packages/crouter-identity": {
12467
12467
  "name": "@north-light/identity",
12468
- "version": "0.3.558",
12468
+ "version": "0.3.560",
12469
12469
  "license": "UNLICENSED",
12470
12470
  "dependencies": {
12471
12471
  "jose": "^6.2.1"
@@ -12473,21 +12473,21 @@
12473
12473
  },
12474
12474
  "packages/crouter-plugin": {
12475
12475
  "name": "@north-light/plugin",
12476
- "version": "0.3.558",
12476
+ "version": "0.3.560",
12477
12477
  "license": "UNLICENSED",
12478
12478
  "dependencies": {
12479
- "@north-light/api": "0.3.558",
12480
- "@north-light/identity": "0.3.558"
12479
+ "@north-light/api": "0.3.560",
12480
+ "@north-light/identity": "0.3.560"
12481
12481
  }
12482
12482
  },
12483
12483
  "packages/crouter-sdk": {
12484
12484
  "name": "@north-light/sdk",
12485
- "version": "0.3.558",
12485
+ "version": "0.3.560",
12486
12486
  "license": "UNLICENSED",
12487
12487
  "dependencies": {
12488
- "@north-light/api": "0.3.558",
12489
- "@north-light/identity": "0.3.558",
12490
- "@north-light/plugin": "0.3.558",
12488
+ "@north-light/api": "0.3.560",
12489
+ "@north-light/identity": "0.3.560",
12490
+ "@north-light/plugin": "0.3.560",
12491
12491
  "jose": "^6.2.1"
12492
12492
  },
12493
12493
  "bin": {