@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
@@ -24,8 +24,10 @@ export interface Cron {
24
24
  sub: string;
25
25
  /** The grant the cron runs under; null only for `app:terminal` with no issuer, or a migrated row with no grant. */
26
26
  grant_id: string | null;
27
- /** Scopes the cron is bounded to; null only for person-armed terminal crons (the grant's whole current set). */
27
+ /** Scopes the cron is bounded to; null for person-armed terminal crons and crons armed from an exec execution (the grant's whole current set is the ceiling). */
28
28
  scopes: string[] | null;
29
+ /** Armed by a `POST /v1/exec` process: every node it births checks model sign-in first. */
30
+ armed_from_exec: boolean;
29
31
  /** The canvas run of the arming node (confinement; `run.cancel` later). Not the per-fire execution id. */
30
32
  origin_run_id: string | null;
31
33
  /** The running fire's execution id (`CRTR_RUN_ID`) while leased; null when idle. */
@@ -91,6 +93,7 @@ export interface ArmCronSpec {
91
93
  grant_id?: string | null;
92
94
  scopes?: readonly string[] | null;
93
95
  origin_run_id?: string | null;
96
+ armed_from_exec?: boolean;
94
97
  isolated?: boolean;
95
98
  command: string;
96
99
  fire_at: string;
@@ -1,8 +1,8 @@
1
- var w=Object.defineProperty;var a=(n,r)=>w(n,"name",{value:r,configurable:!0});import{withCanvasWrite as c,openDb as i}from"./db.js";import{capturePidIdentities as l,identitiesMatch as R,killProcessGroup as m}from"./pid.js";import{isProfilePaused as h}from"../profiles/manifest.js";import{stopAppProcess as D}from"../spaces/process.js";import{emitEvent as S}from"../events/emit.js";import{personId as p}from"./person.js";const N=20;function O(n){if(typeof n!="string")return null;try{const r=JSON.parse(n);return Array.isArray(r)?r.filter(e=>typeof e=="string"):null}catch{return null}}a(O,"scopesFrom");function s(n){return{cron_id:n.cron_id,name:n.name,created_by:n.created_by??null,grantee:n.grantee,sub:n.sub??p(),grant_id:n.grant_id??null,scopes:O(n.scopes_json),origin_run_id:n.origin_run_id??null,run_execution_id:n.run_execution_id??null,isolated:Number(n.isolated)!==0,command:n.command,fire_at:n.fire_at,recur:n.recur??null,tz:n.tz??null,expires_at:n.expires_at??null,anchor_node:n.anchor_node??null,cancel_on_wake:Number(n.cancel_on_wake)!==0,cwd:n.cwd,env_json:n.env_json??null,profile:n.profile??null,scope:n.scope,run_timeout_s:Number(n.run_timeout_s),overlap:n.overlap,on_output:n.on_output,sink:n.sink,tier:n.tier,last_output_hash:n.last_output_hash??null,held:Number(n.held)!==0,last_poke_at:n.last_poke_at??null,state:n.state,run_state:n.run_state,run_pid:n.run_pid??null,run_pid_identity:n.run_pid_identity??null,run_started_at:n.run_started_at??null,run_lease_owner:n.run_lease_owner??null,run_out_of_band:Number(n.run_out_of_band)!==0,created:n.created,updated:n.updated}}a(s,"cronFrom");function E(n){try{const r=JSON.parse(n);if(typeof r.node=="string")return{kind:"node",node:r.node};if(typeof r.spawn=="string")return{kind:"spawn",spawn:r.spawn};if(r.human===!0)return{kind:"human"}}catch{}return null}a(E,"parseCronSink");function F(n){const r=E(n);if(r===null)return null;switch(r.kind){case"node":return`node:${r.node}`;case"spawn":return`spawn:${r.spawn}`;case"human":return"human"}}a(F,"cronSinkDisplay");async function y(n,r="SIGTERM"){if(!_(n))return!1;if(process.platform!=="linux")return m(n.run_pid,r),!0;try{return await D(n.run_pid,r,n.anchor_node??void 0),!0}catch(e){if(!_(n))return!1;throw e}}a(y,"stopLeasedCronRun");function d(n,r="SIGTERM"){return _(n)?(y(n,r).catch(e=>{S({level:"error",event:"cron.run.stop_failed",error:e,fields:{cron_id:n.cron_id,pid:n.run_pid}})}),!0):!1}a(d,"killLeasedCronRun");function _(n){const r=n.run_pid;if(r==null||n.run_pid_identity==null)return!1;const e=l([r]);if(e===null)return!1;const t=e.get(r);return t===void 0?!1:R(t,n.run_pid_identity)}a(_,"verifyLeasedCronRun");async function U(n){return await c(async r=>{const e=[];if(n.cancel_on_wake&&n.anchor_node!==null){const t=(await r.prepare("SELECT * FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1").all(n.anchor_node)).map(s);for(const o of t)o.run_state==="running"&&d(o),await r.prepare("DELETE FROM crons WHERE cron_id = ?").run(o.cron_id),e.push(o.cron_id)}return await L(r,n),{cron_id:n.cron_id,replaced:e}})}a(U,"armCron");async function L(n,r){const e=new Date().toISOString();await n.prepare(`INSERT INTO crons (
1
+ var w=Object.defineProperty;var a=(n,r)=>w(n,"name",{value:r,configurable:!0});import{withCanvasWrite as c,openDb as i}from"./db.js";import{capturePidIdentities as l,identitiesMatch as R,killProcessGroup as m}from"./pid.js";import{isProfilePaused as h}from"../profiles/manifest.js";import{stopAppProcess as D}from"../spaces/process.js";import{emitEvent as S}from"../events/emit.js";import{personId as p}from"./person.js";const N=20;function O(n){if(typeof n!="string")return null;try{const r=JSON.parse(n);return Array.isArray(r)?r.filter(e=>typeof e=="string"):null}catch{return null}}a(O,"scopesFrom");function s(n){return{cron_id:n.cron_id,name:n.name,created_by:n.created_by??null,grantee:n.grantee,sub:n.sub??p(),grant_id:n.grant_id??null,scopes:O(n.scopes_json),origin_run_id:n.origin_run_id??null,run_execution_id:n.run_execution_id??null,armed_from_exec:Number(n.armed_from_exec??0)!==0,isolated:Number(n.isolated)!==0,command:n.command,fire_at:n.fire_at,recur:n.recur??null,tz:n.tz??null,expires_at:n.expires_at??null,anchor_node:n.anchor_node??null,cancel_on_wake:Number(n.cancel_on_wake)!==0,cwd:n.cwd,env_json:n.env_json??null,profile:n.profile??null,scope:n.scope,run_timeout_s:Number(n.run_timeout_s),overlap:n.overlap,on_output:n.on_output,sink:n.sink,tier:n.tier,last_output_hash:n.last_output_hash??null,held:Number(n.held)!==0,last_poke_at:n.last_poke_at??null,state:n.state,run_state:n.run_state,run_pid:n.run_pid??null,run_pid_identity:n.run_pid_identity??null,run_started_at:n.run_started_at??null,run_lease_owner:n.run_lease_owner??null,run_out_of_band:Number(n.run_out_of_band)!==0,created:n.created,updated:n.updated}}a(s,"cronFrom");function E(n){try{const r=JSON.parse(n);if(typeof r.node=="string")return{kind:"node",node:r.node};if(typeof r.spawn=="string")return{kind:"spawn",spawn:r.spawn};if(r.human===!0)return{kind:"human"}}catch{}return null}a(E,"parseCronSink");function F(n){const r=E(n);if(r===null)return null;switch(r.kind){case"node":return`node:${r.node}`;case"spawn":return`spawn:${r.spawn}`;case"human":return"human"}}a(F,"cronSinkDisplay");async function y(n,r="SIGTERM"){if(!d(n))return!1;if(process.platform!=="linux")return m(n.run_pid,r),!0;try{return await D(n.run_pid,r,n.anchor_node??void 0),!0}catch(e){if(!d(n))return!1;throw e}}a(y,"stopLeasedCronRun");function _(n,r="SIGTERM"){return d(n)?(y(n,r).catch(e=>{S({level:"error",event:"cron.run.stop_failed",error:e,fields:{cron_id:n.cron_id,pid:n.run_pid}})}),!0):!1}a(_,"killLeasedCronRun");function d(n){const r=n.run_pid;if(r==null||n.run_pid_identity==null)return!1;const e=l([r]);if(e===null)return!1;const t=e.get(r);return t===void 0?!1:R(t,n.run_pid_identity)}a(d,"verifyLeasedCronRun");async function U(n){return await c(async r=>{const e=[];if(n.cancel_on_wake&&n.anchor_node!==null){const t=(await r.prepare("SELECT * FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1").all(n.anchor_node)).map(s);for(const o of t)o.run_state==="running"&&_(o),await r.prepare("DELETE FROM crons WHERE cron_id = ?").run(o.cron_id),e.push(o.cron_id)}return await L(r,n),{cron_id:n.cron_id,replaced:e}})}a(U,"armCron");async function L(n,r){const e=new Date().toISOString();await n.prepare(`INSERT INTO crons (
2
2
  cron_id, name, created_by, grantee, sub, grant_id, scopes_json, origin_run_id, isolated, command, fire_at, recur, tz, expires_at,
3
3
  anchor_node, cancel_on_wake, cwd, env_json, profile, scope,
4
- run_timeout_s, overlap, on_output, sink, tier, created, updated
5
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(r.cron_id,r.name,r.created_by,r.grantee??"app:terminal",r.sub??p(),r.grant_id??null,r.scopes==null?null:JSON.stringify(r.scopes),r.origin_run_id??null,r.isolated?1:0,r.command,r.fire_at,r.recur,r.tz,r.expires_at,r.anchor_node,r.cancel_on_wake?1:0,r.cwd,r.env_json,r.profile,r.scope,r.run_timeout_s,r.overlap,r.on_output,r.sink,r.tier,e,e)}a(L,"insertCron");async function M(n){const r=await(await i()).prepare("SELECT * FROM crons WHERE cron_id = ?").get(n);return r===void 0?null:s(r)}a(M,"getCron");async function v(n){const r=await i();let e;return"profile"in n?e=await r.prepare("SELECT * FROM crons WHERE scope = 'profile' AND profile = ? ORDER BY fire_at").all(n.profile):"global"in n?e=await r.prepare("SELECT * FROM crons WHERE scope = 'global' ORDER BY fire_at").all():e=await r.prepare("SELECT * FROM crons ORDER BY fire_at").all(),e.map(s)}a(v,"listCrons");async function I(n,r){return await c(async e=>{const t=await e.prepare("SELECT * FROM crons WHERE cron_id = ?").get(n);if(t===void 0)return!1;const o=s(t);return o.run_state==="running"&&r?.preserveRunningProcess!==!0&&d(o),await e.prepare("DELETE FROM crons WHERE cron_id = ?").run(n),!0})}a(I,"cancelCron");function f(n,r){const e=[...new Set(r)];return e.length===0?{sql:"profile = ?",params:[n]}:{sql:`profile = ? OR anchor_node IN (${e.map(()=>"?").join(",")})`,params:[n,...e]}}a(f,"profileCronWhere");async function P(n,r){const e=f(n,r);return(await(await i()).prepareUncached(`SELECT cron_id FROM crons WHERE ${e.sql} ORDER BY created, cron_id`).all(...e.params)).map(({cron_id:t})=>t)}a(P,"cronIdsForProfileDeletion");async function b(n,r){return await c(async e=>{const t=f(n,r),o=(await e.prepareUncached(`SELECT * FROM crons WHERE ${t.sql} ORDER BY created, cron_id`).all(...t.params)).map(s);for(const u of o)u.run_state==="running"&&d(u);return await e.prepareUncached(`DELETE FROM crons WHERE ${t.sql}`).run(...t.params),o.map(({cron_id:u})=>u)})}a(b,"deleteCronsForProfile");async function B(n){return(await(await i()).prepare("SELECT cron_id FROM crons WHERE profile = ? ORDER BY created, cron_id").all(n)).map(({cron_id:r})=>r)}a(B,"cronIdsForProfileDetach");async function Y(n){return await c(async r=>{const e=(await r.prepare("SELECT cron_id FROM crons WHERE profile = ? ORDER BY created, cron_id").all(n)).map(({cron_id:t})=>t);return e.length>0&&await r.prepare("UPDATE crons SET profile = NULL, scope = 'global', updated = ? WHERE profile = ?").run(new Date().toISOString(),n),e})}a(Y,"globalizeCronsForProfile");async function $(n){return(await(await i()).prepare("DELETE FROM crons WHERE cron_id = ?").run(n)).changes>0}a($,"consumeCron");async function j(n,r){return(await(await i()).prepare("UPDATE crons SET state = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)).changes>0}a(j,"setCronState");async function q(n,r){await(await i()).prepare("UPDATE crons SET last_output_hash = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)}a(q,"setCronLastOutputHash");async function z(n){const r=await(await i()).prepare(`SELECT anchor_node, cancel_on_wake, sink
4
+ run_timeout_s, overlap, on_output, sink, tier, created, updated, armed_from_exec
5
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(r.cron_id,r.name,r.created_by,r.grantee??"app:terminal",r.sub??p(),r.grant_id??null,r.scopes==null?null:JSON.stringify(r.scopes),r.origin_run_id??null,r.isolated?1:0,r.command,r.fire_at,r.recur,r.tz,r.expires_at,r.anchor_node,r.cancel_on_wake?1:0,r.cwd,r.env_json,r.profile,r.scope,r.run_timeout_s,r.overlap,r.on_output,r.sink,r.tier,e,e,r.armed_from_exec?1:0)}a(L,"insertCron");async function M(n){const r=await(await i()).prepare("SELECT * FROM crons WHERE cron_id = ?").get(n);return r===void 0?null:s(r)}a(M,"getCron");async function v(n){const r=await i();let e;return"profile"in n?e=await r.prepare("SELECT * FROM crons WHERE scope = 'profile' AND profile = ? ORDER BY fire_at").all(n.profile):"global"in n?e=await r.prepare("SELECT * FROM crons WHERE scope = 'global' ORDER BY fire_at").all():e=await r.prepare("SELECT * FROM crons ORDER BY fire_at").all(),e.map(s)}a(v,"listCrons");async function I(n,r){return await c(async e=>{const t=await e.prepare("SELECT * FROM crons WHERE cron_id = ?").get(n);if(t===void 0)return!1;const o=s(t);return o.run_state==="running"&&r?.preserveRunningProcess!==!0&&_(o),await e.prepare("DELETE FROM crons WHERE cron_id = ?").run(n),!0})}a(I,"cancelCron");function f(n,r){const e=[...new Set(r)];return e.length===0?{sql:"profile = ?",params:[n]}:{sql:`profile = ? OR anchor_node IN (${e.map(()=>"?").join(",")})`,params:[n,...e]}}a(f,"profileCronWhere");async function b(n,r){const e=f(n,r);return(await(await i()).prepareUncached(`SELECT cron_id FROM crons WHERE ${e.sql} ORDER BY created, cron_id`).all(...e.params)).map(({cron_id:t})=>t)}a(b,"cronIdsForProfileDeletion");async function P(n,r){return await c(async e=>{const t=f(n,r),o=(await e.prepareUncached(`SELECT * FROM crons WHERE ${t.sql} ORDER BY created, cron_id`).all(...t.params)).map(s);for(const u of o)u.run_state==="running"&&_(u);return await e.prepareUncached(`DELETE FROM crons WHERE ${t.sql}`).run(...t.params),o.map(({cron_id:u})=>u)})}a(P,"deleteCronsForProfile");async function B(n){return(await(await i()).prepare("SELECT cron_id FROM crons WHERE profile = ? ORDER BY created, cron_id").all(n)).map(({cron_id:r})=>r)}a(B,"cronIdsForProfileDetach");async function Y(n){return await c(async r=>{const e=(await r.prepare("SELECT cron_id FROM crons WHERE profile = ? ORDER BY created, cron_id").all(n)).map(({cron_id:t})=>t);return e.length>0&&await r.prepare("UPDATE crons SET profile = NULL, scope = 'global', updated = ? WHERE profile = ?").run(new Date().toISOString(),n),e})}a(Y,"globalizeCronsForProfile");async function $(n){return(await(await i()).prepare("DELETE FROM crons WHERE cron_id = ?").run(n)).changes>0}a($,"consumeCron");async function j(n,r){return(await(await i()).prepare("UPDATE crons SET state = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)).changes>0}a(j,"setCronState");async function q(n,r){await(await i()).prepare("UPDATE crons SET last_output_hash = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)}a(q,"setCronLastOutputHash");async function z(n){const r=await(await i()).prepare(`SELECT anchor_node, cancel_on_wake, sink
6
6
  FROM crons
7
7
  WHERE (anchor_node = ? AND cancel_on_wake = 1 AND state = 'active')
8
8
  OR (
@@ -11,7 +11,7 @@ var w=Object.defineProperty;var a=(n,r)=>w(n,"name",{value:r,configurable:!0});i
11
11
  run_state = 'running'
12
12
  OR (run_state = 'idle' AND state = 'active' AND (expires_at IS NULL OR expires_at > ?))
13
13
  )
14
- )`).all(n,new Date().toISOString());for(const e of r){if(e.anchor_node===n&&Number(e.cancel_on_wake)!==0)return!0;const t=E(e.sink);if(t?.kind==="node"&&t.node===n)return!0}return!1}a(z,"hasPendingCronWake");async function G(n){return await c(async r=>{const e=(await r.prepare("SELECT * FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1").all(n)).map(s);for(const t of e)t.run_state==="running"&&d(t),await r.prepare("DELETE FROM crons WHERE cron_id = ?").run(t.cron_id);return e})}a(G,"cancelCronsOnWake");async function J(n,r){await(await i()).prepare("UPDATE crons SET fire_at = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)}a(J,"advanceCron");async function V(n){return(await(await i()).prepare(`SELECT * FROM crons
14
+ )`).all(n,new Date().toISOString());for(const e of r){if(e.anchor_node===n&&Number(e.cancel_on_wake)!==0)return!0;const t=E(e.sink);if(t?.kind==="node"&&t.node===n)return!0}return!1}a(z,"hasPendingCronWake");async function G(n){return await c(async r=>{const e=(await r.prepare("SELECT * FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1").all(n)).map(s);for(const t of e)t.run_state==="running"&&_(t),await r.prepare("DELETE FROM crons WHERE cron_id = ?").run(t.cron_id);return e})}a(G,"cancelCronsOnWake");async function J(n,r){await(await i()).prepare("UPDATE crons SET fire_at = ?, updated = ? WHERE cron_id = ?").run(r,new Date().toISOString(),n)}a(J,"advanceCron");async function V(n){return(await(await i()).prepare(`SELECT * FROM crons
15
15
  WHERE state = 'active' AND run_state = 'idle' AND fire_at <= ?
16
16
  ORDER BY fire_at, created, cron_id`).all(n)).map(s).filter(r=>!h(r.profile))}a(V,"dueClockCrons");async function K(n,r,e,t,o=null){const u=await(await i()).prepare(`UPDATE crons
17
17
  SET run_state = 'running', run_started_at = ?, run_pid = NULL, run_pid_identity = NULL,
@@ -33,4 +33,4 @@ var w=Object.defineProperty;var a=(n,r)=>w(n,"name",{value:r,configurable:!0});i
33
33
  WHERE cron_id = ? AND run_id NOT IN (
34
34
  SELECT run_id FROM cron_runs WHERE cron_id = ?
35
35
  ORDER BY started DESC, run_id DESC LIMIT ?
36
- )`).run(r.cron_id,r.cron_id,N),!0)}a(g,"recordCronRunLocked");async function an(n){return await c(async r=>await g(r,n))}a(an,"recordCronRun");export{N as CRON_RUN_RING,K as acquireCronRunLease,J as advanceCron,U as armCron,Q as attachCronRunPid,I as cancelCron,G as cancelCronsOnWake,$ as consumeCron,P as cronIdsForProfileDeletion,B as cronIdsForProfileDetach,F as cronSinkDisplay,b as deleteCronsForProfile,V as dueClockCrons,nn as dueOverlapCrons,rn as expiredIdleCrons,M as getCron,Y as globalizeCronsForProfile,z as hasPendingCronWake,d as killLeasedCronRun,en as listCronRuns,v as listCrons,E as parseCronSink,Z as pokeHeldCrons,an as recordCronRun,g as recordCronRunLocked,X as releaseCronRunLease,tn as runningCrons,q as setCronLastOutputHash,j as setCronState,y as stopLeasedCronRun,_ as verifyLeasedCronRun};
36
+ )`).run(r.cron_id,r.cron_id,N),!0)}a(g,"recordCronRunLocked");async function an(n){return await c(async r=>await g(r,n))}a(an,"recordCronRun");export{N as CRON_RUN_RING,K as acquireCronRunLease,J as advanceCron,U as armCron,Q as attachCronRunPid,I as cancelCron,G as cancelCronsOnWake,$ as consumeCron,b as cronIdsForProfileDeletion,B as cronIdsForProfileDetach,F as cronSinkDisplay,P as deleteCronsForProfile,V as dueClockCrons,nn as dueOverlapCrons,rn as expiredIdleCrons,M as getCron,Y as globalizeCronsForProfile,z as hasPendingCronWake,_ as killLeasedCronRun,en as listCronRuns,v as listCrons,E as parseCronSink,Z as pokeHeldCrons,an as recordCronRun,g as recordCronRunLocked,X as releaseCronRunLease,tn as runningCrons,q as setCronLastOutputHash,j as setCronState,y as stopLeasedCronRun,d as verifyLeasedCronRun};
@@ -0,0 +1,6 @@
1
+ -- 004-cron-armed-from-exec.sql — a cron armed by a `POST /v1/exec` process (design-northlight-mcp "Model sign-in refusal"):
2
+ -- every node birth from such a cron checks model sign-in, as a birth from the exec itself does. Additive only.
3
+ ALTER TABLE crons ADD COLUMN IF NOT EXISTS armed_from_exec bigint NOT NULL DEFAULT 0;
4
+
5
+ -- The previous build must still open this database: the step above only adds (D73).
6
+ INSERT INTO canvas_meta (key, value) VALUES ('schema_min_reader', '2') ON CONFLICT (key) DO NOTHING;
@@ -1,7 +1,7 @@
1
1
  /** The canvas schema version this build supports: the number of the highest delta in `schema-pg/` (the baseline
2
2
  * `schema-pg.sql` is version 1). `scripts/install-runtime.mjs` reads this module from a candidate generation to refuse
3
3
  * one below the live database's `schema_min_reader`. */
4
- export declare const CANVAS_SCHEMA_VERSION = 3;
4
+ export declare const CANVAS_SCHEMA_VERSION = 4;
5
5
  /** The Postgres schema holding the canvas tables: `CRTR_DATABASE_SCHEMA` (`crtr` in the cloud deploy). Unset or empty
6
6
  * means the role's own search_path (`public` on a Mac and on a VM). A name that is not a plain identifier is an error:
7
7
  * it is put in a `search_path` connection option. */
@@ -1 +1 @@
1
- var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=3;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
1
+ var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=4;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
package/dist/core/io.js CHANGED
@@ -1,7 +1,7 @@
1
- var b=Object.defineProperty;var i=(e,t)=>b(e,"name",{value:t,configurable:!0});import{appendFileSync as _,statSync as w}from"node:fs";import{CrtrError as c}from"./errors.js";import{ExitCode as u}from"../types.js";import{PREVIEW_RESULT_PATH_ENV as m}from"./preview-result-path.js";import{renderError as E}from"./render.js";import{ApiError as v}from"../api/index.js";let p=!1,d;const A=256*1024;function B(e){const t=process.env[m];d=t===void 0||t===""?void 0:{path:e,jsonl:[]}}i(B,"beginPreview");function y(e){const t=process.env[m];if(t===void 0||t==="")return;const s=`${JSON.stringify({path:d?.path??"",...e})}
2
- `;try{if((w(t,{throwIfNoEntry:!1})?.size??0)+Buffer.byteLength(s,"utf8")>A)return;_(t,s,"utf8")}catch{}}i(y,"publishPreview");function q(e){d!==void 0&&(e!=null?y({result:e}):(d.jsonl.length>0||d.error!==void 0)&&y({...d.jsonl.length>0?{jsonl:d.jsonl}:{},...d.error===void 0?{}:{error:d.error}}))}i(q,"publishPreviewResult");function h(e){d?.jsonl.push(e)}i(h,"recordPreviewLine");function F(e){try{const t=JSON.parse(e);t!==null&&typeof t=="object"&&!Array.isArray(t)&&h(t)}catch{}}i(F,"recordPreviewJsonLine");function G(e){d!==void 0&&(d.error=e)}i(G,"recordPreviewError");function K(e){p=e}i(K,"setJsonOutput");function z(){return p}i(z,"isJsonOutput");class g extends c{static{i(this,"InputError")}payload;constructor(t,s=u.USAGE){super(t.error,t.message,s,{...t}),this.name="InputError",this.payload=t}}async function D(){if(process.stdin.isTTY)return"";const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf8")}i(D,"readStdinRaw");async function V(e=200){return process.stdin.isTTY?"":await new Promise(t=>{const s=[];let n=!1;const r=i(()=>{n||(n=!0,clearTimeout(a),process.stdin.removeListener("data",o),process.stdin.removeListener("end",l),process.stdin.removeListener("error",f),process.stdin.pause(),t(Buffer.concat(s).toString("utf8")))},"finish"),o=i(x=>s.push(x),"onData"),l=i(()=>r(),"onEnd"),f=i(()=>r(),"onError"),a=setTimeout(r,e);process.stdin.on("data",o),process.stdin.on("end",l),process.stdin.on("error",f),process.stdin.resume()})}i(V,"peekStdinRaw");function Y(e){process.stdout.write(JSON.stringify(e,null,2)+`
3
- `)}i(Y,"emit");function H(e){h(e),process.stdout.write(JSON.stringify(e)+`
1
+ var _=Object.defineProperty;var i=(e,t)=>_(e,"name",{value:t,configurable:!0});import{appendFileSync as b,statSync as w}from"node:fs";import{CrtrError as c}from"./errors.js";import{ExitCode as a}from"../types.js";import{PREVIEW_RESULT_PATH_ENV as h}from"./preview-result-path.js";import{renderError as A}from"./render.js";import{ApiError as E}from"../api/index.js";let p=!1,f;const v=256*1024;function q(e){const t=process.env[h];f=t===void 0||t===""?void 0:{path:e,jsonl:[]}}i(q,"beginPreview");function y(e){const t=process.env[h];if(t===void 0||t==="")return;const s=`${JSON.stringify({path:f?.path??"",...e})}
2
+ `;try{if((w(t,{throwIfNoEntry:!1})?.size??0)+Buffer.byteLength(s,"utf8")>v)return;b(t,s,"utf8")}catch{}}i(y,"publishPreview");function B(e){f!==void 0&&(e!=null?y({result:e}):(f.jsonl.length>0||f.error!==void 0)&&y({...f.jsonl.length>0?{jsonl:f.jsonl}:{},...f.error===void 0?{}:{error:f.error}}))}i(B,"publishPreviewResult");function g(e){f?.jsonl.push(e)}i(g,"recordPreviewLine");function G(e){try{const t=JSON.parse(e);t!==null&&typeof t=="object"&&!Array.isArray(t)&&g(t)}catch{}}i(G,"recordPreviewJsonLine");function F(e){f!==void 0&&(f.error=e)}i(F,"recordPreviewError");function K(e){p=e}i(K,"setJsonOutput");function z(){return p}i(z,"isJsonOutput");class m extends c{static{i(this,"InputError")}payload;constructor(t,s=a.USAGE){super(t.error,t.message,s,{...t}),this.name="InputError",this.payload=t}}async function D(){if(process.stdin.isTTY)return"";const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf8")}i(D,"readStdinRaw");async function V(e=200){return process.stdin.isTTY?"":await new Promise(t=>{const s=[];let n=!1;const r=i(()=>{n||(n=!0,clearTimeout(u),process.stdin.removeListener("data",o),process.stdin.removeListener("end",l),process.stdin.removeListener("error",d),process.stdin.pause(),t(Buffer.concat(s).toString("utf8")))},"finish"),o=i(x=>s.push(x),"onData"),l=i(()=>r(),"onEnd"),d=i(()=>r(),"onError"),u=setTimeout(r,e);process.stdin.on("data",o),process.stdin.on("end",l),process.stdin.on("error",d),process.stdin.resume()})}i(V,"peekStdinRaw");function Y(e){process.stdout.write(JSON.stringify(e,null,2)+`
3
+ `)}i(Y,"emit");function H(e){g(e),process.stdout.write(JSON.stringify(e)+`
4
4
  `)}i(H,"emitLine");function M(e){return new Promise(t=>{let s=!1;const n=i(o=>{o.code==="EPIPE"&&r(!1)},"onErr"),r=i(o=>{s||(s=!0,process.stdout.off("error",n),t(o))},"finish");process.stdout.on("error",n);try{process.stdout.write(e,o=>r(o==null))}catch{r(!1)}})}i(M,"writeStdout");function X(e){process.stderr.write(e+`
5
- `)}i(X,"diag");function S(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}i(S,"isRecord");function T(e){if(e instanceof g)return e.payload;const t=e.details!==void 0?e.details:{},s=t.primaryCompleted===!0&&t.result!==null&&typeof t.result=="object"&&!Array.isArray(t.result)?O(t.result):void 0,n=t.next!==void 0?t.next:"Inspect the error and adjust the call. See -h for the schema.",r={error:e.code,message:e.message,received:t.received,field:t.field,...typeof t.http_status=="number"?{http_status:t.http_status}:{},...typeof t.retry_after_s=="number"?{retry_after_s:t.retry_after_s}:{},...S(t.details)?{details:t.details}:{}};return s===void 0?{...r,next:n}:{...r,primaryCompleted:!0,result:s,next:n}}i(T,"payloadOf");function O(e){const t=new WeakSet;try{const s=JSON.stringify(e,(n,r)=>{if(typeof r=="bigint")return{$crtrType:"bigint",value:r.toString()};if(typeof r=="number"&&!Number.isFinite(r))return{$crtrType:"number",value:String(r)};if(typeof r>"u"||typeof r=="function"||typeof r=="symbol")return{$crtrType:typeof r};if(r!==null&&typeof r=="object"){if(t.has(r))return{$crtrType:"circular"};t.add(r)}return r});if(s!==void 0){const n=JSON.parse(s);if(n!==null&&typeof n=="object"&&!Array.isArray(n))return n}}catch{}return{$crtrType:"unavailable",message:"The completed primary result could not be represented as JSON."}}i(O,"jsonSafeResult");const N={400:"invalid_request",404:"not_found",409:"conflict",422:"unprocessable"};function R(e,t,s={}){if(e.code==="daemon_restarting")return new c("daemon_restarting",e.message,u.NETWORK,{next:"crtrd is now on the new generation \u2014 retry the command."});if(e.code==="layout_migration_required")return new c("layout_migration_required",e.message,u.NETWORK,{next:"Run `crtr sys migrate` while the runtime is offline, then restart it."});if(e.code==="daemon_unavailable")return new c("daemon_unavailable",e.message,u.NETWORK,{next:"Start it with `crtr sys daemon start`, then retry."});const n=e.details,r=j(e,n,s.providerCall===!0);if(e.status===503||s.providerCall===!0&&e.status===504&&r.code!==void 0){const f=r.code??e.code,a={next:r.next??t??"Retry the command; if it persists, inspect the unavailable service named in the error."};return r.details!==void 0&&(a.details=r.details),r.code!==void 0&&(r.field!==void 0&&(a.field=r.field),r.received!==void 0&&(a.received=r.received),r.retryAfterS!==void 0&&(a.retry_after_s=r.retryAfterS)),new c(f,e.message,u.NETWORK,a)}const o=Array.isArray(n?.violations)?n.violations.map(f=>`${String(f.field)} ${String(f.problem)}${f.received!==void 0?` (received ${JSON.stringify(f.received)})`:""}`):[],l=n&&typeof n.error=="string"&&typeof n.message=="string"&&typeof n.next=="string"?n:{error:r.code??N[e.status]??e.code,...r.field!==void 0?{field:r.field}:{},...r.received!==void 0?{received:r.received}:{},...r.retryAfterS!==void 0?{retry_after_s:r.retryAfterS}:{},...r.details!==void 0?{details:r.details}:{},message:o.length>0?`${e.message}: ${o.join("; ")}`:e.message.trim()!==""?e.message:`request refused (HTTP ${e.status}, ${e.code}) with no message`,next:r.next??t??(r.retryAfterS!==void 0?`Wait ${r.retryAfterS}s, then retry; do not retry sooner.`:e.status>=500?"This is a crtr bug, not a bad call. Retry; if it persists, report it (the daemon log holds the full cause).":e.status===404?"List nodes with `crtr node inspect list`.":"Inspect the error and adjust the call. See -h for the schema.")};return new g(l,P(l.error,e.status))}i(R,"apiErrorToCliError");function j(e,t,s){const n=i(o=>typeof o=="string"&&/^[a-z][a-z0-9_]*$/.test(o),"snake");if(s&&e.origin!==void 0&&e.origin!=="provider"&&n(e.code)){const o={code:e.code};return(e.status===503||e.status===504)&&(o.next=e.retryable===!0?"Retry the command; if it persists, the machine or service named in the error is unavailable.":"Read the error before retrying: the action may already have run."),S(e.details)&&(o.details=e.details),e.param!==void 0&&(o.field=e.param),typeof e.retryAfterS=="number"&&(o.retryAfterS=e.retryAfterS),o}if(e.origin!=="provider"||t===void 0||t===null||typeof t!="object")return{};const r={};return typeof t.status=="number"&&n(t.code)&&(r.code=t.code),typeof t.next=="string"&&t.next.trim()!==""&&(r.next=t.next),typeof t.field=="string"&&t.field!==""?r.field=t.field:e.param!==void 0&&(r.field=e.param),t.received!==void 0&&t.received!==null&&(r.received=t.received),typeof e.retryAfterS=="number"&&(r.retryAfterS=e.retryAfterS),r}i(j,"providerSupplied");function P(e,t){return e==="not_found"||t===404?u.NOT_FOUND:e==="ambiguous"?u.AMBIGUOUS:u.USAGE}i(P,"exitForCliCode");function $(e){if(e instanceof v){$(R(e));return}if(e instanceof c){const n=T(e);d!==void 0&&y({error:n});const r=p?JSON.stringify(n,null,2):E(n);process.stdout.write(r+`
5
+ `)}i(X,"diag");function S(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}i(S,"isRecord");function T(e){if(e instanceof m)return e.payload;const t=e.details!==void 0?e.details:{},s=t.primaryCompleted===!0&&t.result!==null&&typeof t.result=="object"&&!Array.isArray(t.result)?O(t.result):void 0,n=t.next!==void 0?t.next:"Inspect the error and adjust the call. See -h for the schema.",r={error:e.code,message:e.message,received:t.received,field:t.field,...typeof t.http_status=="number"?{http_status:t.http_status}:{},...typeof t.retry_after_s=="number"?{retry_after_s:t.retry_after_s}:{},...S(t.details)?{details:t.details}:{}};return s===void 0?{...r,next:n}:{...r,primaryCompleted:!0,result:s,next:n}}i(T,"payloadOf");function O(e){const t=new WeakSet;try{const s=JSON.stringify(e,(n,r)=>{if(typeof r=="bigint")return{$crtrType:"bigint",value:r.toString()};if(typeof r=="number"&&!Number.isFinite(r))return{$crtrType:"number",value:String(r)};if(typeof r>"u"||typeof r=="function"||typeof r=="symbol")return{$crtrType:typeof r};if(r!==null&&typeof r=="object"){if(t.has(r))return{$crtrType:"circular"};t.add(r)}return r});if(s!==void 0){const n=JSON.parse(s);if(n!==null&&typeof n=="object"&&!Array.isArray(n))return n}}catch{}return{$crtrType:"unavailable",message:"The completed primary result could not be represented as JSON."}}i(O,"jsonSafeResult");const N={400:"invalid_request",404:"not_found",409:"conflict",422:"unprocessable"};function R(e,t,s={}){if(e.code==="daemon_restarting")return new c("daemon_restarting",e.message,a.NETWORK,{next:"crtrd is now on the new generation \u2014 retry the command."});if(e.code==="layout_migration_required")return new c("layout_migration_required",e.message,a.NETWORK,{next:"Run `crtr sys migrate` while the runtime is offline, then restart it."});if(e.code==="daemon_unavailable")return new c("daemon_unavailable",e.message,a.NETWORK,{next:"Start it with `crtr sys daemon start`, then retry."});if(e.code==="model_sign_in_required"){const d=e.details?.url;return new m({error:e.code,message:e.message,next:typeof d=="string"?`Ask the person to sign in at ${d}, then retry.`:"Ask the person to sign in to an AI subscription on their Northlight account, then retry.",...typeof d=="string"?{details:{url:d}}:{}},a.USAGE)}const n=e.details,r=$(e,n,s.providerCall===!0);if(e.status===503||s.providerCall===!0&&e.status===504&&r.code!==void 0){const d=r.code??e.code,u={next:r.next??t??"Retry the command; if it persists, inspect the unavailable service named in the error."};return r.details!==void 0&&(u.details=r.details),r.code!==void 0&&(r.field!==void 0&&(u.field=r.field),r.received!==void 0&&(u.received=r.received),r.retryAfterS!==void 0&&(u.retry_after_s=r.retryAfterS)),new c(d,e.message,a.NETWORK,u)}const o=Array.isArray(n?.violations)?n.violations.map(d=>`${String(d.field)} ${String(d.problem)}${d.received!==void 0?` (received ${JSON.stringify(d.received)})`:""}`):[],l=n&&typeof n.error=="string"&&typeof n.message=="string"&&typeof n.next=="string"?n:{error:r.code??N[e.status]??e.code,...r.field!==void 0?{field:r.field}:{},...r.received!==void 0?{received:r.received}:{},...r.retryAfterS!==void 0?{retry_after_s:r.retryAfterS}:{},...r.details!==void 0?{details:r.details}:{},message:o.length>0?`${e.message}: ${o.join("; ")}`:e.message.trim()!==""?e.message:`request refused (HTTP ${e.status}, ${e.code}) with no message`,next:r.next??t??(r.retryAfterS!==void 0?`Wait ${r.retryAfterS}s, then retry; do not retry sooner.`:e.status>=500?"This is a crtr bug, not a bad call. Retry; if it persists, report it (the daemon log holds the full cause).":e.status===404?"List nodes with `crtr node inspect list`.":"Inspect the error and adjust the call. See -h for the schema.")};return new m(l,j(l.error,e.status))}i(R,"apiErrorToCliError");function $(e,t,s){const n=i(o=>typeof o=="string"&&/^[a-z][a-z0-9_]*$/.test(o),"snake");if(s&&e.origin!==void 0&&e.origin!=="provider"&&n(e.code)){const o={code:e.code};return(e.status===503||e.status===504)&&(o.next=e.retryable===!0?"Retry the command; if it persists, the machine or service named in the error is unavailable.":"Read the error before retrying: the action may already have run."),S(e.details)&&(o.details=e.details),e.param!==void 0&&(o.field=e.param),typeof e.retryAfterS=="number"&&(o.retryAfterS=e.retryAfterS),o}if(e.origin!=="provider"||t===void 0||t===null||typeof t!="object")return{};const r={};return typeof t.status=="number"&&n(t.code)&&(r.code=t.code),typeof t.next=="string"&&t.next.trim()!==""&&(r.next=t.next),typeof t.field=="string"&&t.field!==""?r.field=t.field:e.param!==void 0&&(r.field=e.param),t.received!==void 0&&t.received!==null&&(r.received=t.received),typeof e.retryAfterS=="number"&&(r.retryAfterS=e.retryAfterS),r}i($,"providerSupplied");function j(e,t){return e==="not_found"||t===404?a.NOT_FOUND:e==="ambiguous"?a.AMBIGUOUS:a.USAGE}i(j,"exitForCliCode");function P(e){if(e instanceof E){P(R(e));return}if(e instanceof c){const n=T(e);f!==void 0&&y({error:n});const r=p?JSON.stringify(n,null,2):A(n);process.stdout.write(r+`
6
6
  `),process.exitCode=e.exitCode;return}const t=e,s=t!=null&&typeof t.message=="string"?t.message:String(e);process.stderr.write(JSON.stringify({error:"internal",message:s,next:"This is a crtr bug, not a bad call. Retry; if it persists, report it."},null,2)+`
7
- `),process.exitCode=u.GENERAL}i($,"handle");export{g as InputError,A as PREVIEW_RESULT_MAX_BYTES,R as apiErrorToCliError,B as beginPreview,X as diag,Y as emit,H as emitLine,$ as handle,z as isJsonOutput,V as peekStdinRaw,q as publishPreviewResult,D as readStdinRaw,G as recordPreviewError,F as recordPreviewJsonLine,h as recordPreviewLine,K as setJsonOutput,M as writeStdout};
7
+ `),process.exitCode=a.GENERAL}i(P,"handle");export{m as InputError,v as PREVIEW_RESULT_MAX_BYTES,R as apiErrorToCliError,q as beginPreview,X as diag,Y as emit,H as emitLine,P as handle,z as isJsonOutput,V as peekStdinRaw,B as publishPreviewResult,D as readStdinRaw,F as recordPreviewError,G as recordPreviewJsonLine,g as recordPreviewLine,K as setJsonOutput,M as writeStdout};
@@ -0,0 +1,12 @@
1
+ import { type ModeDTO, type ModelAuthReadinessDTO } from '../../api/index.js';
2
+ /** A prospective launch: each field absent or null takes the default a root launch would. */
3
+ export interface ProspectiveLaunch {
4
+ profile?: string | null;
5
+ cwd?: string | null;
6
+ pin_cwd?: string | null;
7
+ kind?: string | null;
8
+ model?: string | null;
9
+ mode?: ModeDTO | null;
10
+ }
11
+ /** Resolve `launch` (a root launch when it names no parent) and report its provider's credential state. */
12
+ export declare function modelAuthReadiness(launch: ProspectiveLaunch): Promise<ModelAuthReadinessDTO>;
@@ -0,0 +1 @@
1
+ var w=Object.defineProperty;var l=(e,t)=>w(e,"name",{value:t,configurable:!0});import{join as g}from"node:path";import{ApiError as c}from"../../api/index.js";import{secretsDir as v}from"../layout.js";import{PoolCredentialStore as y}from"../pool-credential-store.js";import{resolveLaunchTarget as _}from"../runtime/launch-target.js";import{coolingUntilFor as p,isManagedProvider as h,readSubscriptionPool as k}from"../subscription-state.js";function S(e){const t=e.indexOf("/"),o=t>0?e.slice(0,t):"";if(o==="")throw new c(422,"unresolved_model","the selected launch has no concrete model provider");return o}l(S,"providerFromModel");async function O(e){const o=(await _({root:!0,...e.profile!=null?{profile:e.profile}:{},...e.cwd!=null?{cwd:e.cwd}:{},...e.pin_cwd!=null?{pin_cwd:e.pin_cwd}:{},...e.mode!=null?{mode:e.mode}:{},...e.kind!=null?{kind:e.kind}:{},...e.model!=null?{model:e.model}:{}},{readOnlyProfileSelection:!0})).launch.model;if(o===void 0)throw new c(422,"unresolved_model","the selected launch has no concrete model");const r=S(o);if(h(r)){const a=k(r);if(a.length>0){const d=a.filter(n=>n.authFailure===void 0);if(d.length===0)return{provider:r,model:o,credential:"unusable",reason:"invalid_grant"};const s=Date.now(),u=d.some(n=>p(n,o)<=s),m=Math.min(...d.map(n=>p(n,o)));return{provider:r,model:o,credential:"ready",...!u&&m>s?{rate_limited_until:new Date(m).toISOString()}:{}}}}const i=await new y(g(v(),"auth.json")).read(r),f=i?.type==="oauth"||i?.type==="api_key"&&!!i.key;return{provider:r,model:o,credential:f?"ready":"missing"}}l(O,"modelAuthReadiness");export{O as modelAuthReadiness};
@@ -1,24 +1,24 @@
1
- var Y=Object.defineProperty;var i=(e,n)=>Y(e,"name",{value:n,configurable:!0});import{randomUUID as Q}from"node:crypto";import{createServer as W}from"node:http";import{Router as V,causedByOf as q}from"./router.js";import{toErrorBody as K}from"./map.js";import{objectRoutes as Z}from"./handlers/objects.js";import{customObjectRoutes as ee}from"./handlers/custom-objects.js";import{checkRoutes as te}from"./handlers/check.js";import{chargeRoutes as re}from"./handlers/charges.js";import{docRoutes as ne}from"./handlers/docs.js";import{relationRoutes as ae}from"./handlers/relations.js";import{schemaRoutes as oe}from"./handlers/schemas.js";import{OPERATIONS as ie}from"./operations.js";import{runtimeReadiness as se}from"../readiness.js";import{errorCodes as $}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as ue,RUNTIME_VERSION_HEADER as ce,minimumRuntimeRefusal as de}from"../../api/runtime-version.js";import{CONTRACT_HEADER as le,CONTRACT_LEVEL as X,REQUIRED_CONTRACT_HEADER as me,contractRefusal as fe}from"../../api/contract.js";import{crtrVersion as J}from"../../shared/crtr-version.js";import{newTraceId as pe,parseTraceparent as he}from"../../shared/traceparent.js";import{AppPrincipalResolver as we}from"./principal.js";import{isDaemonRoute as ye,runtimeOwner as _e,servePeer as z}from"../peer/ingress.js";import{applySyncNotice as ve}from"./sync-notice.js";import{ownMachineId as ge}from"../../core/peer/directory.js";import{handleRunOperation as Re,integer as Oe}from"./handlers/runs.js";import{llmCallerOf as be,parseLlmRequest as Ee,runLlm as Ae}from"../../core/llm/run.js";import{parseUsageMonth as Te,usageFor as Se}from"../../core/ledger/index.js";import{capFields as Ne}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as Ce,MODEL_CONTEXT_BODY_MAX_BYTES as Ie}from"../../core/llm/body-limits.js";import{RunError as m,runIsRunning as Pe}from"../../core/runs/operations.js";import{requireRun as L}from"./router.js";import{deferHead as ke}from"./deferred-head.js";import{readRunEvents as G,registerGrantStream as He,subscribeRunEvents as Me}from"../../core/runs/events.js";import{activeGrant as F,payerOfGrant as De}from"../../core/grants/mirror.js";async function je(e){if(await De(e)==="app")throw new m(401,"grant_revoked","this app-paid grant is no longer active");return null}i(je,"appPaidGone");import{releaseGrantWaiters as xe}from"../../core/grants/watch.js";import{emitEvent as Be}from"../../core/events/emit.js";import{runNotices as Je}from"../run-notices.js";import{handleVendorSignIn as Le,isVendorSignInPath as Ue}from"./vendor-signin.js";import{listAppProfiles as Ve}from"./handlers/app-profiles.js";import{handleTransferDownload as $e,handleTransferOperation as Xe,TRANSFER_OPERATIONS as ze}from"./handlers/transfer.js";import{handleQuestionOperation as Ge,QUESTION_OPERATIONS as Fe}from"./handlers/questions.js";import{removeVendorAccount as Ye,vendorStatus as Qe}from"./handlers/vendor-status.js";import{handleMemoryOperation as We,MEMORY_OPERATIONS as qe}from"./handlers/memory.js";import{abortOnClose as Ke,callerFromApp as Ze,handleProviderOperation as et,PROVIDER_OPERATIONS as tt,providerFailure as rt}from"./handlers/providers.js";function nt(e){return Object.prototype.hasOwnProperty.call($,e)?$[e]:void 0}i(nt,"fixedRow");function at(e){const n=nt(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}i(at,"errorType");function ot(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...o}=e;return o}i(ot,"appDetails");function B(e,n,o){const p=ot(e.details);return{error:{code:e.code,message:e.message,...p===void 0?{}:{details:p},type:at(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...o?{run_id:o}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...Ne(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}i(B,"envelope");function it(e,n,o,p,f){const d=n instanceof m?n:new m(500,"server_failure","runtime request failed");if(n instanceof m||console.error("[app-listener]",`trace_id=${p}`,n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
2
- data: ${JSON.stringify(B(d,o,f))}
1
+ var Q=Object.defineProperty;var s=(e,n)=>Q(e,"name",{value:n,configurable:!0});import{randomUUID as W}from"node:crypto";import{createServer as q}from"node:http";import{Router as V,causedByOf as K}from"./router.js";import{toErrorBody as Z}from"./map.js";import{objectRoutes as ee}from"./handlers/objects.js";import{customObjectRoutes as te}from"./handlers/custom-objects.js";import{checkRoutes as re}from"./handlers/check.js";import{chargeRoutes as ne}from"./handlers/charges.js";import{docRoutes as ae}from"./handlers/docs.js";import{relationRoutes as oe}from"./handlers/relations.js";import{schemaRoutes as ie}from"./handlers/schemas.js";import{OPERATIONS as se}from"./operations.js";import{runtimeReadiness as ue}from"../readiness.js";import{errorCodes as X}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as ce,RUNTIME_VERSION_HEADER as de,minimumRuntimeRefusal as le}from"../../api/runtime-version.js";import{CONTRACT_HEADER as me,CONTRACT_LEVEL as $,REQUIRED_CONTRACT_HEADER as fe,contractRefusal as pe}from"../../api/contract.js";import{crtrVersion as J}from"../../shared/crtr-version.js";import{newTraceId as he,parseTraceparent as we}from"../../shared/traceparent.js";import{AppPrincipalResolver as ye}from"./principal.js";import{isDaemonRoute as _e,runtimeOwner as ve,servePeer as z}from"../peer/ingress.js";import{applySyncNotice as ge}from"./sync-notice.js";import{ownMachineId as Re}from"../../core/peer/directory.js";import{handleRunOperation as Oe,integer as Ee}from"./handlers/runs.js";import{llmCallerOf as be,parseLlmRequest as Ae,runLlm as Te}from"../../core/llm/run.js";import{parseUsageMonth as Se,usageFor as Ne}from"../../core/ledger/index.js";import{capFields as Ce}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as Ie,MODEL_CONTEXT_BODY_MAX_BYTES as Pe}from"../../core/llm/body-limits.js";import{RunError as m,runIsRunning as ke}from"../../core/runs/operations.js";import{requireRun as L}from"./router.js";import{deferHead as Me}from"./deferred-head.js";import{readRunEvents as G,registerGrantStream as De,subscribeRunEvents as He}from"../../core/runs/events.js";import{activeGrant as Y,payerOfGrant as xe}from"../../core/grants/mirror.js";async function Be(e){if(await xe(e)==="app")throw new m(401,"grant_revoked","this app-paid grant is no longer active");return null}s(Be,"appPaidGone");import{releaseGrantWaiters as je}from"../../core/grants/watch.js";import{emitEvent as Je}from"../../core/events/emit.js";import{runNotices as Le}from"../run-notices.js";import{handleVendorSignIn as Ue,isVendorSignInPath as Ve}from"./vendor-signin.js";import{listAppProfiles as Xe}from"./handlers/app-profiles.js";import{handleTransferDownload as $e,handleTransferOperation as ze,TRANSFER_OPERATIONS as Ge}from"./handlers/transfer.js";import{handleQuestionOperation as Ye,QUESTION_OPERATIONS as Fe}from"./handlers/questions.js";import{removeVendorAccount as Qe,vendorStatus as We}from"./handlers/vendor-status.js";import{handleMemoryOperation as qe,MEMORY_OPERATIONS as Ke}from"./handlers/memory.js";import{EXEC_BODY_MAX_BYTES as Ze,handleExec as et}from"./handlers/exec.js";import{abortOnClose as F,callerFromApp as tt,handleProviderOperation as rt,PROVIDER_OPERATIONS as nt,providerFailure as at}from"./handlers/providers.js";function ot(e){return Object.prototype.hasOwnProperty.call(X,e)?X[e]:void 0}s(ot,"fixedRow");function it(e){const n=ot(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}s(it,"errorType");function st(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...o}=e;return o}s(st,"appDetails");function j(e,n,o){const p=st(e.details);return{error:{code:e.code,message:e.message,...p===void 0?{}:{details:p},type:it(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...o?{run_id:o}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...Ce(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}s(j,"envelope");function ut(e,n,o,p,f){const d=n instanceof m?n:new m(500,"server_failure","runtime request failed");if(n instanceof m||console.error("[app-listener]",`trace_id=${p}`,n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
2
+ data: ${JSON.stringify(j(d,o,f))}
3
3
 
4
- `);return}e.writeHead(d.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":o,"X-Content-Type-Options":"nosniff",...d.code==="all_routes_exhausted"?{"Retry-After":String(d.details.retry_after_s)}:{}}),e.end(JSON.stringify(B(d,o,f)))}i(it,"fail");function U(e){return Array.isArray(e)?e[0]:e}i(U,"headerValue");function g(e,n,o){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}i(g,"json");async function C(e,n=Ce){const o=[];let p=0;for await(const f of e){if(p+=f.length,p>n)throw new m(400,"invalid_request","request body too large");o.push(f)}if(p)try{return JSON.parse(Buffer.concat(o).toString("utf8"))}catch{throw new m(400,"invalid_request","invalid JSON body")}}i(C,"bodyOf");function st(e,n){const o=e.headers["last-event-id"]??n.get("after");if(Array.isArray(o))throw new m(400,"invalid_request","invalid cursor","after");return Oe(o??null,"after",0,Number.MAX_SAFE_INTEGER,0)}i(st,"cursor");function ut(e){return`id: ${e.sequence_number}
4
+ `);return}e.writeHead(d.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":o,"X-Content-Type-Options":"nosniff",...d.code==="all_routes_exhausted"?{"Retry-After":String(d.details.retry_after_s)}:{}}),e.end(JSON.stringify(j(d,o,f)))}s(ut,"fail");function U(e){return Array.isArray(e)?e[0]:e}s(U,"headerValue");function g(e,n,o){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}s(g,"json");async function C(e,n=Ie){const o=[];let p=0;for await(const f of e){if(p+=f.length,p>n)throw new m(400,"invalid_request","request body too large");o.push(f)}if(p)try{return JSON.parse(Buffer.concat(o).toString("utf8"))}catch{throw new m(400,"invalid_request","invalid JSON body")}}s(C,"bodyOf");function ct(e,n){const o=e.headers["last-event-id"]??n.get("after");if(Array.isArray(o))throw new m(400,"invalid_request","invalid cursor","after");return Ee(o??null,"after",0,Number.MAX_SAFE_INTEGER,0)}s(ct,"cursor");function dt(e){return`id: ${e.sequence_number}
5
5
  event: ${e.type}
6
6
  data: ${JSON.stringify(e)}
7
7
 
8
- `}i(ut,"frame");const ct=6e4;async function dt(e,n,o,p,f,d,E,I={},M){await L(p,o);let w=await G(o,f);if(f>w.latest_sequence)throw new m(400,"invalid_request","cursor is beyond the run","after");if(f<w.latest_sequence&&(w.earliest_sequence===null||f<w.earliest_sequence-1))throw new m(409,"stream_gap","events have expired",void 0,{earliest_sequence:w.earliest_sequence,latest_sequence:w.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":E==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),E==="sse"?n.write(`: connected
8
+ `}s(dt,"frame");const lt=6e4;async function mt(e,n,o,p,f,d,b,I={},D){await L(p,o);let w=await G(o,f);if(f>w.latest_sequence)throw new m(400,"invalid_request","cursor is beyond the run","after");if(f<w.latest_sequence&&(w.earliest_sequence===null||f<w.earliest_sequence-1))throw new m(409,"stream_gap","events have expired",void 0,{earliest_sequence:w.earliest_sequence,latest_sequence:w.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":b==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),b==="sse"?n.write(`: connected
9
9
 
10
- `):n.flushHeaders();let T=f;const r=await Je().streamOpened(o);let t=!1,y=!1,b=!1,A;const P=new Promise(u=>{A=u}),_=i(()=>{t||(t=!0,r(T).catch(u=>console.error("[app-listener]",...M===void 0?[]:[`trace_id=${M}`],u)),clearInterval(N),clearInterval(O),H(),j(),n.off("close",_),n.off("drain",h),n.end(),A())},"end"),h=i(()=>{b=!1,a()},"onDrain"),R=i(u=>{t||n.write(E==="sse"?`event: error
11
- data: ${JSON.stringify(B(u,d,o))}
10
+ `):n.flushHeaders();let T=f;const r=await Le().streamOpened(o);let t=!1,y=!1,E=!1,A;const P=new Promise(c=>{A=c}),v=s(()=>{t||(t=!0,r(T).catch(c=>console.error("[app-listener]",...D===void 0?[]:[`trace_id=${D}`],c)),clearInterval(N),clearInterval(O),M(),x(),n.off("close",v),n.off("drain",h),n.end(),A())},"end"),h=s(()=>{E=!1,a()},"onDrain"),R=s(c=>{t||n.write(b==="sse"?`event: error
11
+ data: ${JSON.stringify(j(c,d,o))}
12
12
 
13
- `:`${JSON.stringify({type:"error",...B(u,d,o)})}
14
- `),_()},"error"),a=i(async()=>{if(!(t||y)){y=!0;try{if(!await S()){D();return}if(await L(p,o),w=await G(o,T),w.backlog_count>1e3||w.backlog_bytes>32*1024*1024){R(new m(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:T}));return}for(const u of w.events){if(b)break;b=!n.write(E==="sse"?ut(u):`${JSON.stringify(u)}
15
- `),T=u.sequence_number}}catch(u){R(u instanceof m?u:new m(500,"server_failure","stream failed"))}finally{y=!1}}},"flush"),D=i(()=>R(new m(401,"grant_revoked","grant is not active")),"revoked"),S=i(async()=>"class"in p||await F(p.grant)!==null,"grantActive"),H=await Me(o,a),j="class"in p?()=>{}:He(p.grant,D),N=setInterval(()=>{!t&&!b&&E==="sse"&&n.write(`: keepalive
13
+ `:`${JSON.stringify({type:"error",...j(c,d,o)})}
14
+ `),v()},"error"),a=s(async()=>{if(!(t||y)){y=!0;try{if(!await S()){H();return}if(await L(p,o),w=await G(o,T),w.backlog_count>1e3||w.backlog_bytes>32*1024*1024){R(new m(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:T}));return}for(const c of w.events){if(E)break;E=!n.write(b==="sse"?dt(c):`${JSON.stringify(c)}
15
+ `),T=c.sequence_number}}catch(c){R(c instanceof m?c:new m(500,"server_failure","stream failed"))}finally{y=!1}}},"flush"),H=s(()=>R(new m(401,"grant_revoked","grant is not active")),"revoked"),S=s(async()=>"class"in p||await Y(p.grant)!==null,"grantActive"),M=await He(o,a),x="class"in p?()=>{}:De(p.grant,H),N=setInterval(()=>{!t&&!E&&b==="sse"&&n.write(`: keepalive
16
16
 
17
- `)},15e3),c=I.idleMs??ct,x=I.isRunning??Pe;let s=Date.now(),v=T;const l=i(async()=>{if(t||E!=="sse")return;const u=Date.now();let k=!0;try{k=await x(o)}catch{}if(!t){if(k||T!==v){s=u,v=T;return}u-s<c||b||(n.write(`event: idle
17
+ `)},15e3),u=I.idleMs??lt,B=I.isRunning??ke;let i=Date.now(),_=T;const l=s(async()=>{if(t||b!=="sse")return;const c=Date.now();let k=!0;try{k=await B(o)}catch{}if(!t){if(k||T!==_){i=c,_=T;return}c-i<u||E||(n.write(`event: idle
18
18
  data: {}
19
19
 
20
- `),_())}},"checkIdle"),O=setInterval(()=>{l().catch(u=>Be({level:"error",event:"api.run_stream.idle_check_failed",error:u}))},I.idleCheckMs??1e3);return O.unref(),n.on("drain",h),n.on("close",_),await a(),P}i(dt,"streamRunEvents");async function er(e,n,o,p={}){const f=e.listener;if(!e.issuer||!e.audience||!f?.address||!Number.isInteger(f.port)||f.port<1||f.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(f.address))throw new Error("runtime listener must bind a specific address");const d=new we(e.issuer,e.audience,o,e.jwksUrl),E=new V,I=new Map;for(const r of ie.filter(t=>t.stage!==void 0)){const t=i(()=>({status:500}),"handler");E.register({method:r.method,pattern:r.path,handler:t}),I.set(t,r)}const M=new V().registerAll(Z).registerAll(ee).registerAll(ne).registerAll(oe).registerAll(ae).registerAll(te).registerAll(re),w=W((r,t)=>{(async()=>{ke(r,t,p.deferHead);const y=Q();t.setHeader("Request-Id",y);const b=he(U(r.headers.traceparent))??pe();t.setHeader(ce,J()),t.setHeader(le,X);let A;try{const P=U(r.headers[ue.toLowerCase()]),_=de(P,J())??fe(U(r.headers[me.toLowerCase()]),X,{legacyMinimum:P,runtimeVersion:J()});if(_!==null)throw _.status===400?new m(400,_.code,_.message,_.param):new m(426,_.code,_.message,void 0,_.details);const h=new URL(r.url??"/","http://runtime.local");if(Ue(h.pathname)){await Le(r,t,h,d);return}const R=E.match(r.method??"GET",h.pathname),a=R?I.get(R.handler):void 0,D=a?.name==="runtime.sync-notice"||a?.name==="runtime.vendor-status"||a?.name==="runtime.vendor-account-remove";let S,H;if(!D&&r.headers.authorization!==void 0)try{S=await d.verifyCall(r.headers.authorization)}catch(s){H=s}const j={issuer:e.issuer,jwks:d.jwks,audience:i(async()=>e.audience,"audience"),owner:_e,self:ge,syncGrants:o,ready:i(async()=>d.isReady()&&await n(),"ready")};if(S?.chain[0]?.sub.startsWith("runtime:")){await z(r,t,j,S);return}if(S===void 0&&a===void 0&&await ye(r.method??"GET",h.pathname)){await z(r,t,j,{status:401,code:"unauthorized",message:H===void 0?"a machine token is required":"invalid machine token"});return}if(a?.name==="ready"){const s=await n(),v=await se({jwksReady:d.isReady(),grantsReady:i(()=>s,"grantsReady")});g(t,v.ready?200:503,v);return}if((r.method!=="GET"||h.pathname!=="/v1/health")&&(!d.isReady()||!await n()))throw new m(503,"runtime_starting","runtime_starting");if(!R||!a)throw new m(404,"not_found","unknown operation");A=R.params.run_id;const N=q(r.headers);if(a.name==="health"){g(t,200,{status:"ok",api_version:"2026-09-23"});return}if(a.name==="runtime.sync-notice"){await ve(await d.verifySyncNotice(r.headers.authorization),o,y),t.writeHead(204),t.end();return}if(a.name==="runtime.vendor-status"){await d.verifyDirectoryRead(r.headers.authorization,"vendor_status"),g(t,200,Qe());return}if(a.name==="runtime.vendor-account-remove"){await d.verifyDirectoryRead(r.headers.authorization,"vendor_account_remove"),g(t,200,await Ye(await C(r)));return}if(S===void 0)throw H??new m(401,"unauthorized","a directory access token is required");const c=await d.resolveVerified(S,a.scope);if(t.setHeader("Request-Id",y),A&&await L(c,A,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(a.name),a.name==="run.delete"),a.stage==="objects"){const s=M.match(a.method,h.pathname);if(!s)throw new m(404,"not_found","unknown operation");const v={...N===void 0?{}:{causedBy:N},method:a.method,path:h.pathname,params:s.params,query:h.searchParams,body:await C(r,s.maxBodyBytes),ceiling:c.resolved,principal:{class:"app",app:c.grantee,scopes:c.resolved,sub:c.sub},req:r,res:t};let l;try{l=await s.handler(v)}catch(O){const{status:u,body:k}=K(O);throw u>=500&&console.error("[app-listener]",`trace_id=${b}`,O),new m(u,k.error.code,k.error.message,void 0,k.error.details)}if(t.headersSent)return;if(l.rawBody!==void 0||l.status===204||l.body===void 0){t.writeHead(l.status,l.headers??{}),t.end(l.rawBody);return}t.writeHead(l.status,{"Content-Type":"application/json; charset=utf-8",...l.headers??{}}),t.end(JSON.stringify(l.body));return}if(a.stream==="bytes"){await $e(r,t,c,h.searchParams);return}if(a.stream){await dt(r,t,A,c,st(r,h.searchParams),y,"sse",p.stream,b);return}if(a.name==="llm"){const s=Ee(await C(r,Ie)),v=new AbortController,l=i(()=>v.abort(),"abort");t.on("close",l);try{const O=await Ae(s,v.signal,s.stream?u=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(u)}
20
+ `),v())}},"checkIdle"),O=setInterval(()=>{l().catch(c=>Je({level:"error",event:"api.run_stream.idle_check_failed",error:c}))},I.idleCheckMs??1e3);return O.unref(),n.on("drain",h),n.on("close",v),await a(),P}s(mt,"streamRunEvents");async function nr(e,n,o,p={}){const f=e.listener;if(!e.issuer||!e.audience||!f?.address||!Number.isInteger(f.port)||f.port<1||f.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(f.address))throw new Error("runtime listener must bind a specific address");const d=new ye(e.issuer,e.audience,o,e.jwksUrl),b=new V,I=new Map;for(const r of se.filter(t=>t.stage!==void 0)){const t=s(()=>({status:500}),"handler");b.register({method:r.method,pattern:r.path,handler:t}),I.set(t,r)}const D=new V().registerAll(ee).registerAll(te).registerAll(ae).registerAll(ie).registerAll(oe).registerAll(re).registerAll(ne),w=q((r,t)=>{(async()=>{Me(r,t,p.deferHead);const y=W();t.setHeader("Request-Id",y);const E=we(U(r.headers.traceparent))??he();t.setHeader(de,J()),t.setHeader(me,$);let A;try{const P=U(r.headers[ce.toLowerCase()]),v=le(P,J())??pe(U(r.headers[fe.toLowerCase()]),$,{legacyMinimum:P,runtimeVersion:J()});if(v!==null)throw v.status===400?new m(400,v.code,v.message,v.param):new m(426,v.code,v.message,void 0,v.details);const h=new URL(r.url??"/","http://runtime.local");if(Ve(h.pathname)){await Ue(r,t,h,d);return}const R=b.match(r.method??"GET",h.pathname),a=R?I.get(R.handler):void 0,H=a?.name==="runtime.sync-notice"||a?.name==="runtime.vendor-status"||a?.name==="runtime.vendor-account-remove";let S,M;if(!H&&r.headers.authorization!==void 0)try{S=await d.verifyCall(r.headers.authorization)}catch(i){M=i}const x={issuer:e.issuer,jwks:d.jwks,audience:s(async()=>e.audience,"audience"),owner:ve,self:Re,syncGrants:o,ready:s(async()=>d.isReady()&&await n(),"ready")};if(S?.chain[0]?.sub.startsWith("runtime:")){await z(r,t,x,S);return}if(S===void 0&&a===void 0&&await _e(r.method??"GET",h.pathname)){await z(r,t,x,{status:401,code:"unauthorized",message:M===void 0?"a machine token is required":"invalid machine token"});return}if(a?.name==="ready"){const i=await n(),_=await ue({jwksReady:d.isReady(),grantsReady:s(()=>i,"grantsReady")});g(t,_.ready?200:503,_);return}if((r.method!=="GET"||h.pathname!=="/v1/health")&&(!d.isReady()||!await n()))throw new m(503,"runtime_starting","runtime_starting");if(!R||!a)throw new m(404,"not_found","unknown operation");A=R.params.run_id;const N=K(r.headers);if(a.name==="health"){g(t,200,{status:"ok",api_version:"2026-09-23"});return}if(a.name==="runtime.sync-notice"){await ge(await d.verifySyncNotice(r.headers.authorization),o,y),t.writeHead(204),t.end();return}if(a.name==="runtime.vendor-status"){await d.verifyDirectoryRead(r.headers.authorization,"vendor_status"),g(t,200,We());return}if(a.name==="runtime.vendor-account-remove"){await d.verifyDirectoryRead(r.headers.authorization,"vendor_account_remove"),g(t,200,await Qe(await C(r)));return}if(S===void 0)throw M??new m(401,"unauthorized","a directory access token is required");const u=await d.resolveVerified(S,a.scope);if(t.setHeader("Request-Id",y),A&&await L(u,A,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(a.name),a.name==="run.delete"),a.stage==="objects"){const i=D.match(a.method,h.pathname);if(!i)throw new m(404,"not_found","unknown operation");const _={...N===void 0?{}:{causedBy:N},method:a.method,path:h.pathname,params:i.params,query:h.searchParams,body:await C(r,i.maxBodyBytes),ceiling:u.resolved,principal:{class:"app",app:u.grantee,scopes:u.resolved,sub:u.sub},req:r,res:t};let l;try{l=await i.handler(_)}catch(O){const{status:c,body:k}=Z(O);throw c>=500&&console.error("[app-listener]",`trace_id=${E}`,O),new m(c,k.error.code,k.error.message,void 0,k.error.details)}if(t.headersSent)return;if(l.rawBody!==void 0||l.status===204||l.body===void 0){t.writeHead(l.status,l.headers??{}),t.end(l.rawBody);return}t.writeHead(l.status,{"Content-Type":"application/json; charset=utf-8",...l.headers??{}}),t.end(JSON.stringify(l.body));return}if(a.stream==="bytes"){await $e(r,t,u,h.searchParams);return}if(a.stream){await mt(r,t,A,u,ct(r,h.searchParams),y,"sse",p.stream,E);return}if(a.name==="llm"){const i=Ae(await C(r,Pe)),_=new AbortController,l=s(()=>_.abort(),"abort");t.on("close",l);try{const O=await Te(i,_.signal,i.stream?c=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(c)}
21
21
 
22
- `)}:void 0,void 0,{grantee:c.grantee,sub:c.sub,spend_cap:c.spend_cap,request:y},be(await F(c.grant)??await je(c.grant)),N);s.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
22
+ `)}:void 0,void 0,{grantee:u.grantee,sub:u.sub,spend_cap:u.spend_cap,request:y},be(await Y(u.grant)??await Be(u.grant)),N);i.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
23
23
 
24
- `)):g(t,200,O.completion)}finally{t.off("close",l)}return}if(a.name==="usage.get"){g(t,200,await Se(c.grantee,Te(h.searchParams.get("month"))));return}if(a.name==="profile.list"){g(t,200,Ve(c));return}if(Fe.has(a.name)){const s=await Ge(a.name,c,R.params.question_id,a.method==="POST"?await C(r):void 0);g(t,s.status,s.body);return}if(tt.has(a.name)){const{signal:s,release:v}=Ke(t);try{const l=await et(a.name,Ze(c),{params:R.params,body:a.method==="POST"?await C(r):void 0,query:h.searchParams},{requestId:y,traceId:b,signal:s,...N===void 0?{}:{causedBy:N}});g(t,l.status,l.body)}catch(l){const O=rt(l,y,!0);t.writeHead(O.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...O.headers}),t.end(JSON.stringify(O.body))}finally{v()}return}if(qe.has(a.name)){const s=await We(a.name,{class:"app",app:c.grantee,scopes:c.resolved},h.searchParams);g(t,s.status,s.body);return}if(ze.has(a.name)){const s=await Xe(a,c,R.params,h.searchParams,await C(r));g(t,s.status,s.body);return}const x=await Re(a,c,A,h.searchParams,await C(r),r.headers["idempotency-key"],N);g(t,x.status,x.body)}catch(P){it(t,P,y,b,A)}})()});return{ready:new Promise((r,t)=>{w.once("error",t),w.listen(f.port,f.address,()=>{w.off("error",t),r()})}),close:i(async()=>{d.close(),xe(),w.closeAllConnections(),await new Promise((r,t)=>w.close(y=>y?t(y):r()))},"close")}}i(er,"createAppListener");export{ct as RUN_STREAM_IDLE_MS,er as createAppListener,st as cursor,dt as streamRunEvents};
24
+ `)):g(t,200,O.completion)}finally{t.off("close",l)}return}if(a.name==="exec"){const{signal:i,release:_}=F(t);try{g(t,200,await et(u,await C(r,Ze),i))}finally{_()}return}if(a.name==="usage.get"){g(t,200,await Ne(u.grantee,Se(h.searchParams.get("month"))));return}if(a.name==="profile.list"){g(t,200,Xe(u));return}if(Fe.has(a.name)){const i=await Ye(a.name,u,R.params.question_id,a.method==="POST"?await C(r):void 0);g(t,i.status,i.body);return}if(nt.has(a.name)){const{signal:i,release:_}=F(t);try{const l=await rt(a.name,tt(u),{params:R.params,body:a.method==="POST"?await C(r):void 0,query:h.searchParams},{requestId:y,traceId:E,signal:i,...N===void 0?{}:{causedBy:N}});g(t,l.status,l.body)}catch(l){const O=at(l,y,!0);t.writeHead(O.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...O.headers}),t.end(JSON.stringify(O.body))}finally{_()}return}if(Ke.has(a.name)){const i=await qe(a.name,{class:"app",app:u.grantee,scopes:u.resolved},h.searchParams);g(t,i.status,i.body);return}if(Ge.has(a.name)){const i=await ze(a,u,R.params,h.searchParams,await C(r));g(t,i.status,i.body);return}const B=await Oe(a,u,A,h.searchParams,await C(r),r.headers["idempotency-key"],N);g(t,B.status,B.body)}catch(P){ut(t,P,y,E,A)}})()});return{ready:new Promise((r,t)=>{w.once("error",t),w.listen(f.port,f.address,()=>{w.off("error",t),r()})}),close:s(async()=>{d.close(),je(),w.closeAllConnections(),await new Promise((r,t)=>w.close(y=>y?t(y):r()))},"close")}}s(nr,"createAppListener");export{lt as RUN_STREAM_IDLE_MS,nr as createAppListener,ct as cursor,mt as streamRunEvents};
@@ -1 +1 @@
1
- var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as z,sep as G}from"node:path";import{ApiError as c}from"../../../api/errors.js";import{armCron as rn,parseCronSink as on,getCron as v,listCrons as P,listCronRuns as F,pokeHeldCrons as tn,setCronState as L}from"../../../core/canvas/crons.js";import{getNode as _}from"../../../core/canvas/canvas.js";import{appSpace as sn,cronFolder as an}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn,terminalGrant as fn}from"../../../core/grants/mirror.js";import{assertScope as Z}from"../../../core/scopes.js";import{InputError as a}from"../../../core/io.js";import{nextSlotAfter as wn}from"../../../core/wake.js";import{executeCron as _n,markSelfCanceledCronRun as hn}from"../../cron-run.js";import{toCronDTO as k,toCronRunDTO as H}from"../map.js";import{appOf as T,assertCeiling as mn,canReadNode as gn}from"../router.js";import{checkCronBirth as yn,checkCronFire as bn}from"../../cron/authority.js";import{endCron as vn}from"../../cron/ended.js";import{requireUsableProfile as kn}from"./profiles.js";import{filterAsync as Sn,mapAsync as $n}from"../../../core/async-array.js";import{personId as On}from"../../../core/canvas/person.js";function E(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new a({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(E,"requireString");function O(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new a({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(O,"requireEnum");async function Cn(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new a({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(await _(n)===null)throw new a({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new a({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new a({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(Cn,"parseSinkSpec");const In=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function M(e,n){if(!In.test(e)||Number.isNaN(Date.parse(e)))throw new a({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(M,"requireUtcIso");function qn(e){try{wn(e,new Date)}catch(n){throw new a({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(qn,"requireRecur");function Nn(e){let n;try{n=JSON.parse(e)}catch{throw new a({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new a({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new a({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(Nn,"requireEnvJson");function U(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(U,"callerProfile");function Y(e,n){return n===null||e.scope==="global"||e.profile===n}t(Y,"inScope");function K(e){return e.principal.class==="person"?null:T(e.principal)}t(K,"callerApp");async function J(e,n){return await gn(e.principal,n.grantee,n.created_by??void 0)}t(J,"visibleTo");function jn(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(jn,"anchorNodeOf");async function An(e,n,r,o,i,l){const u=typeof e.cwd=="string"&&e.cwd.trim()!==""?z(e.cwd):null;if(!cn()){if(u===null)throw new a({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return u}const h=o!==null&&await _(o)!==null?o:void 0,d=t(async q=>await ln(n,q,h,i,void 0,l),"reaches");if(u!==null&&await d(u))return u;const s=sn(n).files;if(u!==null&&e.cwd_is_default!==!0)throw new c(400,"invalid_request",`cwd ${u} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${s}`,{cwd:u,default:s},void 0,{param:"cwd"});const p=r===null?null:(await _(r))?.cwd??null;return p!==null&&await d(p)?p:s}t(An,"resolveArmedCwd");async function Pn(e,n,r){const o=e.principal;if(o.class==="person"){const s=await fn();return{sub:s?.sub??On(),grant_id:s?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const s=await un(n);if(s===null)throw new c(401,"grant_revoked",`no active grant for ${n}`);const p=e.ceiling??o.scopes;return{sub:s.sub,grant_id:s.id,scopes:p===null?null:[...p],origin_run_id:null}}if(o.class==="cron"){const s=await bn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!s.allowed)throw new c(s.reason==="scope_missing"?403:401,s.reason==="scope_missing"?"scope_missing":"grant_revoked",s.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?null:[...o.scopes],origin_run_id:o.origin_run}}if(o.class!=="broker")throw new c(401,"unauthorized","this caller cannot arm a cron");const i=r??await _(o.node),l=i?.grant_id??o.grant,u=i?.sub??o.sub,h=l!=null?await dn(l):await pn(n,u);if(h===null)throw new c(401,"grant_revoked","the node grant is not active");const d=i?.scopes??o.scopes;return{sub:h.sub,grant_id:h.id,scopes:d===null?null:[...d],origin_run_id:i?.run_id??o.run}}t(Pn,"armPrincipal");async function Tn(e){const n=e.body??{},r=E(n,"name"),o=E(n,"command"),i=M(E(n,"fire_at"),"fire_at"),l=typeof n.recur=="string"&&n.recur!==""?qn(n.recur):null,u=typeof n.expires_at=="string"&&n.expires_at!==""?M(n.expires_at,"expires_at"):null,h=typeof n.env_json=="string"&&n.env_json!==""?Nn(n.env_json):null,d=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,s=O(n.on_output,"on_output",["silent","on-failure","always","on-change"],d?.startsWith("node:")?"on-change":"on-failure"),p=O(n.overlap,"overlap",["skip","queue","replace"],"skip"),q=O(n.tier,"tier",["normal","urgent","critical"],"normal"),D=s==="always"||s==="on-change";if(D&&d===null)throw new a({error:"sink_required",message:`on_output "${s}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!D&&d!==null)throw new a({error:"sink_unused",message:`on_output "${s}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=d!==null?await Cn(d):"{}";if(d?.startsWith("spawn:")&&(mn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const w=await yn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes});if(!w.allowed)throw new c(w.reason==="scope_missing"?403:401,w.reason==="scope_missing"?"scope_missing":"grant_revoked",w.message)}const y=T(e.principal)??"app:terminal",b=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==b)throw new c(403,"permission_error","a broker may only arm a cron as its own node");const m=b===null?null:await _(b);if(m!==null){if(m.grantee!==y)throw new a({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});Z(m,"crtr:schedule"),d?.startsWith("spawn:")&&Z(m,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new c(400,"invalid_request","isolated must be a boolean");if(m?.isolated&&n.isolated===!1)throw new c(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const W=m?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(W&&n.isolated===!1)throw new c(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const N=W||n.isolated===!0,g=await Pn(e,y,m),j=nn(),S=N?an(y,j):null;if(S!==null&&n.cwd_is_default!==!0){const w=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?z(n.cwd):S);if(w===".."||w.startsWith(`..${G}`)||w.startsWith(G))throw new c(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??await An(n,y,b,jn(n),N,{sub:g.sub,grantId:g.grant_id,scopes:g.scopes}),f=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(f!==null&&await _(f)!==null&&(await _(f)).grantee!==y)throw new a({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(f!==null&&await _(f)===null)throw new a({error:"anchor_not_found",message:`anchor node ${f} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const B=n.cancel_on_wake===!0;if(B&&f===null)throw new a({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new a({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const A=typeof n.profile=="string"&&n.profile!==""?(await kn(e,n.profile)).profileId:null,x=O(n.scope,"scope",["profile","global"],A!==null?"profile":"global");if(x==="profile"&&A===null)throw new a({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return await rn({cron_id:j,name:r,created_by:b,grantee:y,sub:g.sub,grant_id:g.grant_id,scopes:g.scopes,origin_run_id:g.origin_run_id,isolated:N,command:o,fire_at:i,recur:l,tz:typeof n.tz=="string"?n.tz:null,expires_at:u,anchor_node:f,cancel_on_wake:B,cwd:V,env_json:h,profile:A,scope:x,run_timeout_s:$,overlap:p,on_output:s,sink:Q,tier:q}),{status:201,body:k(await v(j),null)}}t(Tn,"handleArm");async function En(e){const n=U(e),r=e.query.get("created_by");if(r==="")throw new c(400,"invalid_request","created_by must be nonempty when present");const o=await Sn((n===null?await P({all:!0}):[...await P({profile:n}),...await P({global:!0})].sort((i,l)=>i.fire_at<l.fire_at?-1:i.fire_at>l.fire_at?1:0)).filter(i=>r===null||i.created_by===r),async i=>await J(e,i));return{status:200,body:await $n(o,async i=>k(i,await R(i)))}}t(En,"handleList");async function R(e){return(await F(e.cron_id))[0]??null}t(R,"lastRunOf");async function C(e){const n=e.params.cronId,r=await v(n);if(r===null||!await J(e,r))throw new c(404,"cron_not_found",`no cron ${n}`);if(!Y(r,U(e)))throw new c(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(C,"requireCron");function I(e,n){const r=K(e);if(r!==null&&n.grantee!==r)throw new c(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");async function Un(e){const n=await C(e),r=await F(n.cron_id);return{status:200,body:{cron:k(n,r[0]??null),runs:r.map(H)}}}t(Un,"handleShow");async function Jn(e){const n=I(e,await C(e));await L(n.cron_id,"paused");const r=await v(n.cron_id);return{status:200,body:k(r,await R(r))}}t(Jn,"handlePause");async function Rn(e){const n=I(e,await C(e));await L(n.cron_id,"active");const r=await v(n.cron_id);return{status:200,body:k(r,await R(r))}}t(Rn,"handleResume");async function Dn(e){const n=I(e,await C(e));if(n.run_state==="running")throw new c(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await _n(n,{outOfBand:!0});if(r===null)throw new c(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:H(r)}}t(Dn,"handleRun");async function Wn(e){const n=new Date().toISOString();return{status:200,body:{unparked:await tn(n,K(e)),at:n}}}t(Wn,"handlePoke");function Bn(e,n){const r=e.principal;if(r.class==="cron")return r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const i=on(n.sink);return i?.kind==="node"&&i.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=T(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Bn,"cancelNotice");async function xn(e){const n=e.params.cronId,r=await v(n);if(r!==null&&!await J(e,r))throw new c(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!Y(r,U(e)))throw new c(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),i=e.principal.class==="cron"?o===e.principal.execution&&n===e.principal.cron:!0,l=o!==null&&i&&hn(n,o);return r!==null&&await vn(r,l?null:Bn(e,r),{preserveRunningProcess:l}),{status:204}}t(xn,"handleCancel");const ce=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:Tn},{method:"GET",pattern:"/v1/crons",handler:En},{method:"GET",pattern:"/v1/crons/:cronId",handler:Un},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:Jn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:Rn},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:Wn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Dn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:xn}];export{ce as cronRoutes};
1
+ var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as z,sep as G}from"node:path";import{ApiError as c}from"../../../api/errors.js";import{armCron as rn,parseCronSink as on,getCron as v,listCrons as P,listCronRuns as F,pokeHeldCrons as tn,setCronState as L}from"../../../core/canvas/crons.js";import{getNode as _}from"../../../core/canvas/canvas.js";import{appSpace as an,cronFolder as sn}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn,terminalGrant as fn}from"../../../core/grants/mirror.js";import{assertScope as Z}from"../../../core/scopes.js";import{InputError as s}from"../../../core/io.js";import{nextSlotAfter as wn}from"../../../core/wake.js";import{executeCron as _n,markSelfCanceledCronRun as hn}from"../../cron-run.js";import{toCronDTO as k,toCronRunDTO as H}from"../map.js";import{appOf as T,assertCeiling as mn,canReadNode as gn}from"../router.js";import{checkCronBirth as yn,checkCronFire as bn,cronRefusalError as vn}from"../../cron/authority.js";import{endCron as kn}from"../../cron/ended.js";import{requireUsableProfile as Sn}from"./profiles.js";import{filterAsync as $n,mapAsync as On}from"../../../core/async-array.js";import{personId as Cn}from"../../../core/canvas/person.js";function E(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new s({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(E,"requireString");function O(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new s({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(O,"requireEnum");async function In(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new s({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(await _(n)===null)throw new s({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new s({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new s({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(In,"parseSinkSpec");const qn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function M(e,n){if(!qn.test(e)||Number.isNaN(Date.parse(e)))throw new s({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(M,"requireUtcIso");function Nn(e){try{wn(e,new Date)}catch(n){throw new s({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(Nn,"requireRecur");function jn(e){let n;try{n=JSON.parse(e)}catch{throw new s({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new s({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new s({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(jn,"requireEnvJson");function U(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(U,"callerProfile");function Y(e,n){return n===null||e.scope==="global"||e.profile===n}t(Y,"inScope");function K(e){return e.principal.class==="person"?null:T(e.principal)}t(K,"callerApp");async function J(e,n){return await gn(e.principal,n.grantee,n.created_by??void 0)}t(J,"visibleTo");function An(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(An,"anchorNodeOf");async function Pn(e,n,r,o,i,d){const u=typeof e.cwd=="string"&&e.cwd.trim()!==""?z(e.cwd):null;if(!cn()){if(u===null)throw new s({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return u}const h=o!==null&&await _(o)!==null?o:void 0,l=t(async q=>await ln(n,q,h,i,void 0,d),"reaches");if(u!==null&&await l(u))return u;const a=an(n).files;if(u!==null&&e.cwd_is_default!==!0)throw new c(400,"invalid_request",`cwd ${u} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${a}`,{cwd:u,default:a},void 0,{param:"cwd"});const p=r===null?null:(await _(r))?.cwd??null;return p!==null&&await l(p)?p:a}t(Pn,"resolveArmedCwd");async function Tn(e,n,r){const o=e.principal;if(o.class==="person"){const a=await fn();return{sub:a?.sub??Cn(),grant_id:a?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const a=await un(n);if(a===null)throw new c(401,"grant_revoked",`no active grant for ${n}`);const p=e.ceiling??o.scopes;return{sub:a.sub,grant_id:a.id,scopes:p===null?null:[...p],origin_run_id:null}}if(o.class==="cron"){const a=await bn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!a.allowed)throw new c(a.reason==="scope_missing"?403:401,a.reason==="scope_missing"?"scope_missing":"grant_revoked",a.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?null:[...o.scopes],origin_run_id:o.origin_run,armed_from_exec:o.from_exec}}if(o.class!=="broker")throw new c(401,"unauthorized","this caller cannot arm a cron");const i=r??await _(o.node),d=i?.grant_id??o.grant,u=i?.sub??o.sub,h=d!=null?await dn(d):await pn(n,u);if(h===null)throw new c(401,"grant_revoked","the node grant is not active");const l=i?.scopes??o.scopes;return{sub:h.sub,grant_id:h.id,scopes:l===null?null:[...l],origin_run_id:i?.run_id??o.run}}t(Tn,"armPrincipal");async function En(e){const n=e.body??{},r=E(n,"name"),o=E(n,"command"),i=M(E(n,"fire_at"),"fire_at"),d=typeof n.recur=="string"&&n.recur!==""?Nn(n.recur):null,u=typeof n.expires_at=="string"&&n.expires_at!==""?M(n.expires_at,"expires_at"):null,h=typeof n.env_json=="string"&&n.env_json!==""?jn(n.env_json):null,l=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,a=O(n.on_output,"on_output",["silent","on-failure","always","on-change"],l?.startsWith("node:")?"on-change":"on-failure"),p=O(n.overlap,"overlap",["skip","queue","replace"],"skip"),q=O(n.tier,"tier",["normal","urgent","critical"],"normal"),x=a==="always"||a==="on-change";if(x&&l===null)throw new s({error:"sink_required",message:`on_output "${a}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!x&&l!==null)throw new s({error:"sink_unused",message:`on_output "${a}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=l!==null?await In(l):"{}";if(l?.startsWith("spawn:")&&(mn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const y=await yn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,from_exec:e.principal.from_exec},{kind:l.slice(6)});if(!y.allowed)throw vn(y)}const g=T(e.principal)??"app:terminal",b=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==b)throw new c(403,"permission_error","a broker may only arm a cron as its own node");const m=b===null?null:await _(b);if(m!==null){if(m.grantee!==g)throw new s({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});Z(m,"crtr:schedule"),l?.startsWith("spawn:")&&Z(m,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new c(400,"invalid_request","isolated must be a boolean");if(m?.isolated&&n.isolated===!1)throw new c(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const D=m?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(D&&n.isolated===!1)throw new c(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const N=D||n.isolated===!0,f=await Tn(e,g,m),j=nn(),S=N?sn(g,j):null;if(S!==null&&n.cwd_is_default!==!0){const y=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?z(n.cwd):S);if(y===".."||y.startsWith(`..${G}`)||y.startsWith(G))throw new c(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??await Pn(n,g,b,An(n),N,{sub:f.sub,grantId:f.grant_id,scopes:f.scopes}),w=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(w!==null&&await _(w)!==null&&(await _(w)).grantee!==g)throw new s({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(w!==null&&await _(w)===null)throw new s({error:"anchor_not_found",message:`anchor node ${w} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const W=n.cancel_on_wake===!0;if(W&&w===null)throw new s({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new s({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const A=typeof n.profile=="string"&&n.profile!==""?(await Sn(e,n.profile)).profileId:null,B=O(n.scope,"scope",["profile","global"],A!==null?"profile":"global");if(B==="profile"&&A===null)throw new s({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return await rn({cron_id:j,name:r,created_by:b,grantee:g,sub:f.sub,grant_id:f.grant_id,scopes:f.scopes,origin_run_id:f.origin_run_id,armed_from_exec:f.armed_from_exec===!0,isolated:N,command:o,fire_at:i,recur:d,tz:typeof n.tz=="string"?n.tz:null,expires_at:u,anchor_node:w,cancel_on_wake:W,cwd:V,env_json:h,profile:A,scope:B,run_timeout_s:$,overlap:p,on_output:a,sink:Q,tier:q}),{status:201,body:k(await v(j),null)}}t(En,"handleArm");async function Un(e){const n=U(e),r=e.query.get("created_by");if(r==="")throw new c(400,"invalid_request","created_by must be nonempty when present");const o=await $n((n===null?await P({all:!0}):[...await P({profile:n}),...await P({global:!0})].sort((i,d)=>i.fire_at<d.fire_at?-1:i.fire_at>d.fire_at?1:0)).filter(i=>r===null||i.created_by===r),async i=>await J(e,i));return{status:200,body:await On(o,async i=>k(i,await R(i)))}}t(Un,"handleList");async function R(e){return(await F(e.cron_id))[0]??null}t(R,"lastRunOf");async function C(e){const n=e.params.cronId,r=await v(n);if(r===null||!await J(e,r))throw new c(404,"cron_not_found",`no cron ${n}`);if(!Y(r,U(e)))throw new c(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(C,"requireCron");function I(e,n){const r=K(e);if(r!==null&&n.grantee!==r)throw new c(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");async function Jn(e){const n=await C(e),r=await F(n.cron_id);return{status:200,body:{cron:k(n,r[0]??null),runs:r.map(H)}}}t(Jn,"handleShow");async function Rn(e){const n=I(e,await C(e));await L(n.cron_id,"paused");const r=await v(n.cron_id);return{status:200,body:k(r,await R(r))}}t(Rn,"handlePause");async function xn(e){const n=I(e,await C(e));await L(n.cron_id,"active");const r=await v(n.cron_id);return{status:200,body:k(r,await R(r))}}t(xn,"handleResume");async function Dn(e){const n=I(e,await C(e));if(n.run_state==="running")throw new c(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await _n(n,{outOfBand:!0});if(r===null)throw new c(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:H(r)}}t(Dn,"handleRun");async function Wn(e){const n=new Date().toISOString();return{status:200,body:{unparked:await tn(n,K(e)),at:n}}}t(Wn,"handlePoke");function Bn(e,n){const r=e.principal;if(r.class==="cron")return r.kind==="exec"?{kind:"canceled",by:r.app}:r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const i=on(n.sink);return i?.kind==="node"&&i.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=T(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Bn,"cancelNotice");async function zn(e){const n=e.params.cronId,r=await v(n);if(r!==null&&!await J(e,r))throw new c(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!Y(r,U(e)))throw new c(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),i=e.principal.class==="cron"?e.principal.kind==="cron"&&o===e.principal.execution&&n===e.principal.cron:!0,d=o!==null&&i&&hn(n,o);return r!==null&&await kn(r,d?null:Bn(e,r),{preserveRunningProcess:d}),{status:204}}t(zn,"handleCancel");const le=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:En},{method:"GET",pattern:"/v1/crons",handler:Un},{method:"GET",pattern:"/v1/crons/:cronId",handler:Jn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:Rn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:xn},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:Wn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Dn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:zn}];export{le as cronRoutes};
@@ -0,0 +1,6 @@
1
+ import type { ExecRunDTO } from '../../../api/index.js';
2
+ import type { AppPrincipal } from '../principal.js';
3
+ /** Request body ceiling: the argv limit plus the other field. */
4
+ export declare const EXEC_BODY_MAX_BYTES: number;
5
+ /** Run one exec for `caller`; `signal` aborts when the HTTP request is dropped. */
6
+ export declare function handleExec(caller: AppPrincipal, body: unknown, signal: AbortSignal): Promise<ExecRunDTO>;
@@ -0,0 +1 @@
1
+ var k=Object.defineProperty;var o=(t,u)=>k(t,"name",{value:u,configurable:!0});import{randomUUID as V}from"node:crypto";import{covers as Y}from"@north-light/identity";import{appSpace as j}from"../../../core/layout.js";import{emitEvent as M}from"../../../core/events/emit.js";import{RunError as i}from"../../../core/runs/operations.js";import{readAppEnvVars as F}from"../../../core/profiles/app-env-store.js";import{ADMITTED_HOST_PATH_ENV as R,buildOperationalEnvBase as $,resolveAdmittedHostCommand as J}from"../../../core/runtime/spawn-env.js";import{appProcessesSandboxed as G,launchAppProcess as K}from"../../../core/spaces/process.js";import{uidForApp as z}from"../../../core/spaces/users.js";import{envHomeOverride as Q}from"../../../shared/env.js";import{closeCronExecution as W,openCronExecution as Z}from"../../cron/run-socket.js";const ee=60,U=100,C=32*1024,pe=C+1024,q=256*1024,L=4,l=new Map;function te(t){if(typeof t!="object"||t===null||Array.isArray(t))throw new i(400,"invalid_request","exec requires an object body");const u=t,r=u.argv;if(!Array.isArray(r)||r.length===0||!r.every(e=>typeof e=="string"))throw new i(400,"invalid_request","argv must be a non-empty array of strings","argv");if(r.some(e=>e.includes("\0")))throw new i(400,"invalid_request","argv strings cannot contain NUL","argv");if(Buffer.byteLength(JSON.stringify(r))>=C)throw new i(400,"invalid_request","argv must be less than 32 KB as JSON","argv");const s=u.timeout_s??ee;if(typeof s!="number"||!Number.isFinite(s)||s<=0||s>U)throw new i(400,"invalid_request",`timeout_s must be greater than 0 and at most ${U}`,"timeout_s");return{argv:r,timeout_s:s}}o(te,"parse");async function _e(t,u,r){if(!t.resolved.some(n=>Y(n,"crtr:schedule")))throw new i(403,"scope_missing","missing scope crtr:schedule",void 0,void 0,"crtr:schedule");const s=te(u);if(!G())throw new i(503,"sandbox_unavailable","this runtime has no root launcher, so it cannot sandbox an app process",void 0,void 0,void 0,!1);const e=t.grantee,h=l.get(e)??0;if(h>=L)throw new i(429,"busy",`${e} already has ${L} exec processes running; retry when one finishes`,void 0,void 0,void 0,!0);l.set(e,h+1);const f=V(),p=Date.now();let a,b=!1,E=!1;const y=o(()=>{if(E)return;E=!0,b&&W(f);const n=(l.get(e)??1)-1;n>0?l.set(e,n):l.delete(e),M({level:"info",event:"exec.run",fields:{app:e,argv:s.argv.join(" ").slice(0,200),exit_code:a?.exit_code??null,duration_ms:Date.now()-p}})},"release");let c=null,x=o(()=>{},"wake");const A=new Promise(n=>{x=o(()=>n(null),"wake")}),_=o(n=>{c===null&&(c=n,x())},"cancel"),H=setTimeout(()=>_("timeout"),s.timeout_s*1e3),T=o(()=>_("abort"),"onAbort");r.addEventListener("abort",T,{once:!0}),r.aborted&&_("abort");const I=o(()=>({exit_code:null,signal:null,stdout:"",stdout_truncated:!1,stderr:"",stderr_truncated:!1,timed_out:c==="timeout",duration_ms:Date.now()-p}),"unstarted");let D=!1;try{const n=j(e).files,O=$({targetCwd:n,targetProfileId:null,skipSpawnAllow:!0}),g=Q(),S={...O,...F(e),CRTR_RUN_ID:f,...g===void 0||g===""?{}:{CRTR_HOME:g},[R]:O[R]},N=J("crtr",S,n),v={executionId:f,kind:"exec",cronId:null,fromExec:!0,grantee:e,sub:t.sub,grantId:t.grant,scopes:null,originRunId:null,isolated:!1,anchor:null,uid:await z(e),rebuilt:!1,refusal:null};if(await Z(v,n,null),b=!0,c!==null)return a=I(),a;const P=K({app:e,principal:{sub:t.sub,grantId:t.grant,scopes:null},cron:{executionId:f},command:[N,...s.argv],cwd:n,env:S,stdoutBytes:q,stderrBytes:q}),m=await Promise.race([P,A]);if(m===null)return D=!0,P.then(w=>w.stop()).catch(()=>{}).finally(y),a=I(),a;const X=o(()=>{m.stop().catch(w=>M({level:"error",event:"exec.stop_failed",error:w,fields:{app:e,pid:m.pid}}))},"stop");A.then(X);const d=await m.completion;if(v.refusal!==null)throw new i(401,"grant_revoked",v.refusal.message);const B=c==="timeout";return a={exit_code:B?null:d.exitCode,signal:d.signal,stdout:d.stdout,stdout_truncated:d.stdoutTruncated,stderr:d.stderr,stderr_truncated:d.stderrTruncated,timed_out:B,duration_ms:Date.now()-p},a}finally{clearTimeout(H),r.removeEventListener("abort",T),D||y()}}o(_e,"handleExec");export{pe as EXEC_BODY_MAX_BYTES,_e as handleExec};
@@ -1 +1 @@
1
- var E=Object.defineProperty;var a=(n,e)=>E(n,"name",{value:e,configurable:!0});import{join as M}from"node:path";import{deleteAuthCredential as A,readAuthCredentials as P,setAuthCredential as q}from"../../../core/auth-file.js";import{PoolCredentialStore as j}from"../../../core/pool-credential-store.js";import{secretsDir as T}from"../../../core/layout.js";import{ApiError as l}from"../../../api/index.js";import{RunError as O}from"../../../core/runs/operations.js";import{usage as h}from"../../../core/errors.js";import{resolveLaunchTarget as C}from"../../../core/runtime/launch-target.js";import{MANAGED_PROVIDER_IDS as $,OPENAI_CHATGPT_PROVIDER_ID as R,commitManagedLogin as L,coolingUntilFor as S,isManagedProvider as p,readSubscriptionPool as y,removeAllManagedAccounts as x}from"../../../core/subscription-state.js";import{cancelModelAuthFlow as G,completeModelAuthFlow as N,startModelAuthFlow as U,waitModelAuthFlow as V}from"../../../core/model-auth/flows.js";import{waitSeconds as K}from"../long-poll-wait.js";function c(){return M(T(),"auth.json")}a(c,"authPath");function D(n,e){if(typeof e!="string"||e==="")throw new l(400,"invalid_request",`oauth credential requires \`${n}\``);const t=Date.parse(e);if(Number.isNaN(t))throw new l(400,"invalid_request",`\`${n}\` is not a valid ISO timestamp`);return t}a(D,"parseExpiry");function w(n,e,t){if(typeof t!="string"||t==="")throw new l(400,"invalid_request",`${n} credential requires \`${e}\``);return t}a(w,"requireField");function B(n,e=Date.now()){const t=Object.entries(n.modelCooldowns??{}).filter(([,o])=>o>e).map(([o,r])=>({family:o,rate_limited_until:new Date(r).toISOString()}));return{label:n.label,expires_at:new Date(n.expires).toISOString(),rate_limited_until:new Date(n.rateLimitedUntil).toISOString(),...t.length?{model_cooldowns:t}:{},...n.authFailure?{auth_failure:n.authFailure}:{},...n.accountId?{account_id:n.accountId}:{}}}a(B,"toManagedAccountStatus");function H(n){const e=y(n).map(t=>B(t));return{provider:n,managed:!0,configured:e.length>0,accounts:e}}a(H,"managedStatus");function _(n,e){const t=n.query.getAll(e);if(t.length>1)throw h(`${e} must appear at most once`);return t[0]}a(_,"singleQueryValue");function J(n){const e=n.indexOf("/"),t=e>0?n.slice(0,e):"";if(t==="")throw new l(422,"unresolved_model","the selected launch has no concrete model provider");return t}a(J,"providerFromModel");async function Q(){const n=await P(c()),e=a(o=>o===R&&n[o]!==void 0&&y(o).length===0,"legacyApiKey"),t=$.filter(o=>!e(o)).map(H);for(const[o,r]of Object.entries(n))p(o)&&!e(o)||r.type!=="api_key"&&r.type!=="oauth"||t.push({provider:o,managed:!1,configured:!0,kind:r.type});return{status:200,body:{providers:t}}}a(Q,"handleList");async function z(n){for(const[s]of n.query)if(s!=="profile"&&s!=="cwd"&&s!=="kind"&&s!=="model")throw h(`unknown model-auth readiness query parameter: ${s}`);const e=_(n,"profile"),t=_(n,"cwd"),o=_(n,"kind"),r=_(n,"model");if(o!==void 0&&o.trim()==="")throw h("kind must be a non-empty string when provided");if(r!==void 0&&r.trim()==="")throw h("model must be a non-empty string when provided");const d=await C({root:!0,...e!==void 0?{profile:e}:{},...t!==void 0?{cwd:t}:{},...o!==void 0?{kind:o}:{},...r!==void 0?{model:r}:{}},{readOnlyProfileSelection:!0});if(d.launch.model===void 0)throw new l(422,"unresolved_model","the selected launch has no concrete model");const i=J(d.launch.model);if(p(i)){const s=y(i);if(s.length>0){const m=s.filter(f=>f.authFailure===void 0);if(m.length===0)return{status:200,body:{provider:i,model:d.launch.model,credential:"unusable",reason:"invalid_grant"}};const g=Date.now(),v=d.launch.model,I=m.some(f=>S(f,v)<=g),k=Math.min(...m.map(f=>S(f,v)));return{status:200,body:{provider:i,model:d.launch.model,credential:"ready",...!I&&k>g?{rate_limited_until:new Date(k).toISOString()}:{}}}}}const u=await new j(c()).read(i),b=u?.type==="oauth"||u?.type==="api_key"&&!!u.key;return{status:200,body:{provider:i,model:d.launch.model,credential:b?"ready":"missing"}}}a(z,"handleReadiness");async function W(n){const e=n.params.provider,t=n.body;if(t===void 0||typeof t!="object"||typeof t.kind!="string")throw new l(400,"invalid_request","a credential `kind` is required");switch(t.kind){case"api_key":{const o=w("api_key","api_key",t.api_key);return await q(e,{type:"api_key",key:o},c()),{status:200,body:{provider:e,installed:!0,managed:!1}}}case"oauth":{if(p(e))throw new l(422,"unprocessable",`provider ${e} stores oauth credentials as managed accounts \u2014 use kind \`managed_account\``);const o=w("oauth","access_token",t.access_token),r=w("oauth","refresh_token",t.refresh_token),d=D("expires_at",t.expires_at),i=t.extra??{};await q(e,{type:"oauth",...i,access:o,refresh:r,expires:d},c());const u={provider:e,installed:!0,managed:!1};return typeof t.account_id=="string"&&t.account_id!==""&&(u.account_id=t.account_id),{status:200,body:u}}case"managed_account":{if(!p(e))throw new l(422,"unprocessable",`provider ${e} does not support managed accounts`);const o=typeof t.label=="string"&&t.label.trim()?t.label.trim():void 0;if(!o&&t.auto_label!==!0)throw new l(400,"invalid_request","managed_account credential requires `label` or `auto_label`");const r=typeof t.account_id=="string"&&t.account_id!==""?t.account_id:void 0,d=w("managed_account","access_token",t.access_token),i=w("managed_account","refresh_token",t.refresh_token),u=D("expires_at",t.expires_at),b=typeof t.last_rate_limited_at=="number"?t.last_rate_limited_at:Math.max(0,...y(e).map(m=>m.lastRateLimitedAt));await L(e,o,{refresh:i,access:d,expires:u,...r?{accountId:r}:{},lastRateLimitedAt:b,autoLabel:t.auto_label===!0});const s={provider:e,installed:!0,managed:!0};return r&&(s.account_id=r),{status:200,body:s}}default:throw new l(400,"invalid_request",`unknown credential kind: ${String(t.kind)}`)}}a(W,"handleInstall");async function X(n){const e=n.params.provider;if(p(e)){const o=await x(e),r=await A(e,c());return{status:200,body:{provider:e,removed_accounts:o+(r?1:0)}}}const t=await A(e,c())?1:0;return{status:200,body:{provider:e,removed_accounts:t}}}a(X,"handleDelete");function F(n){const e=n.body;if(e===void 0)return{};if(typeof e!="object"||e===null||Array.isArray(e))throw new l(400,"invalid_request","request body must be a JSON object");return e}a(F,"bodyObject");async function Y(n){const e=F(n);return{status:200,body:await U({provider:e.provider,...e.redirect!==void 0?{redirect:e.redirect}:{},...e.label!==void 0?{label:e.label}:{}})}}a(Y,"handleFlowStart");async function Z(n){const e=F(n);return{status:200,body:await N(n.params.flow_id,e.input)}}a(Z,"handleFlowComplete");async function ee(n){const e=G(n.params.flow_id);if(!e)throw new O(400,"invalid_request","no sign-in has this flow_id","flow_id");const{jti:t,...o}=e;return{status:200,body:o}}a(ee,"handleFlowCancel");async function te(n){for(const[i]of n.query)if(i!=="wait")throw h(`unknown model-auth flow query parameter: ${i}`);_(n,"wait");const e=K("model_auth.get",n.query),t=new AbortController;n.res.once("close",()=>t.abort());const o=await V(n.params.flow_id,e*1e3,t.signal);if(!o)throw new O(404,"not_found","no sign-in has this flow_id","flow_id");const{jti:r,...d}=o;return{status:200,body:d}}a(te,"handleFlowGet");const he=[{method:"POST",pattern:"/v1/model-auth/flows",ownerOnly:!0,handler:Y},{method:"POST",pattern:"/v1/model-auth/flows/:flow_id/complete",ownerOnly:!0,handler:Z},{method:"POST",pattern:"/v1/model-auth/flows/:flow_id/cancel",ownerOnly:!0,handler:ee},{method:"GET",pattern:"/v1/model-auth/flows/:flow_id",ownerOnly:!0,handler:te},{method:"GET",pattern:"/v1/model-auth",handler:Q},{method:"GET",pattern:"/v1/model-auth/readiness",handler:z},{method:"PUT",pattern:"/v1/model-auth/:provider",ownerOnly:!0,handler:W},{method:"DELETE",pattern:"/v1/model-auth/:provider",ownerOnly:!0,handler:X}];export{H as managedStatus,he as modelAuthRoutes};
1
+ var q=Object.defineProperty;var r=(n,e)=>q(n,"name",{value:e,configurable:!0});import{join as O}from"node:path";import{deleteAuthCredential as _,readAuthCredentials as S,setAuthCredential as y}from"../../../core/auth-file.js";import{secretsDir as D}from"../../../core/layout.js";import{ApiError as s}from"../../../api/index.js";import{RunError as b}from"../../../core/runs/operations.js";import{usage as u}from"../../../core/errors.js";import{modelAuthReadiness as E}from"../../../core/model-auth/readiness.js";import{MANAGED_PROVIDER_IDS as I,OPENAI_CHATGPT_PROVIDER_ID as j,commitManagedLogin as F,isManagedProvider as p,readSubscriptionPool as h,removeAllManagedAccounts as P}from"../../../core/subscription-state.js";import{cancelModelAuthFlow as M,completeModelAuthFlow as R,startModelAuthFlow as T,waitModelAuthFlow as $}from"../../../core/model-auth/flows.js";import{waitSeconds as C}from"../long-poll-wait.js";function l(){return O(D(),"auth.json")}r(l,"authPath");function g(n,e){if(typeof e!="string"||e==="")throw new s(400,"invalid_request",`oauth credential requires \`${n}\``);const t=Date.parse(e);if(Number.isNaN(t))throw new s(400,"invalid_request",`\`${n}\` is not a valid ISO timestamp`);return t}r(g,"parseExpiry");function c(n,e,t){if(typeof t!="string"||t==="")throw new s(400,"invalid_request",`${n} credential requires \`${e}\``);return t}r(c,"requireField");function L(n,e=Date.now()){const t=Object.entries(n.modelCooldowns??{}).filter(([,o])=>o>e).map(([o,a])=>({family:o,rate_limited_until:new Date(a).toISOString()}));return{label:n.label,expires_at:new Date(n.expires).toISOString(),rate_limited_until:new Date(n.rateLimitedUntil).toISOString(),...t.length?{model_cooldowns:t}:{},...n.authFailure?{auth_failure:n.authFailure}:{},...n.accountId?{account_id:n.accountId}:{}}}r(L,"toManagedAccountStatus");function G(n){const e=h(n).map(t=>L(t));return{provider:n,managed:!0,configured:e.length>0,accounts:e}}r(G,"managedStatus");function f(n,e){const t=n.query.getAll(e);if(t.length>1)throw u(`${e} must appear at most once`);return t[0]}r(f,"singleQueryValue");async function N(){const n=await S(l()),e=r(o=>o===j&&n[o]!==void 0&&h(o).length===0,"legacyApiKey"),t=I.filter(o=>!e(o)).map(G);for(const[o,a]of Object.entries(n))p(o)&&!e(o)||a.type!=="api_key"&&a.type!=="oauth"||t.push({provider:o,managed:!1,configured:!0,kind:a.type});return{status:200,body:{providers:t}}}r(N,"handleList");async function x(n){for(const[i]of n.query)if(i!=="profile"&&i!=="cwd"&&i!=="kind"&&i!=="model")throw u(`unknown model-auth readiness query parameter: ${i}`);const e=f(n,"profile"),t=f(n,"cwd"),o=f(n,"kind"),a=f(n,"model");if(o!==void 0&&o.trim()==="")throw u("kind must be a non-empty string when provided");if(a!==void 0&&a.trim()==="")throw u("model must be a non-empty string when provided");return{status:200,body:await E({...e!==void 0?{profile:e}:{},...t!==void 0?{cwd:t}:{},...o!==void 0?{kind:o}:{},...a!==void 0?{model:a}:{}})}}r(x,"handleReadiness");async function V(n){const e=n.params.provider,t=n.body;if(t===void 0||typeof t!="object"||typeof t.kind!="string")throw new s(400,"invalid_request","a credential `kind` is required");switch(t.kind){case"api_key":{const o=c("api_key","api_key",t.api_key);return await y(e,{type:"api_key",key:o},l()),{status:200,body:{provider:e,installed:!0,managed:!1}}}case"oauth":{if(p(e))throw new s(422,"unprocessable",`provider ${e} stores oauth credentials as managed accounts \u2014 use kind \`managed_account\``);const o=c("oauth","access_token",t.access_token),a=c("oauth","refresh_token",t.refresh_token),i=g("expires_at",t.expires_at),d=t.extra??{};await y(e,{type:"oauth",...d,access:o,refresh:a,expires:i},l());const m={provider:e,installed:!0,managed:!1};return typeof t.account_id=="string"&&t.account_id!==""&&(m.account_id=t.account_id),{status:200,body:m}}case"managed_account":{if(!p(e))throw new s(422,"unprocessable",`provider ${e} does not support managed accounts`);const o=typeof t.label=="string"&&t.label.trim()?t.label.trim():void 0;if(!o&&t.auto_label!==!0)throw new s(400,"invalid_request","managed_account credential requires `label` or `auto_label`");const a=typeof t.account_id=="string"&&t.account_id!==""?t.account_id:void 0,i=c("managed_account","access_token",t.access_token),d=c("managed_account","refresh_token",t.refresh_token),m=g("expires_at",t.expires_at),k=typeof t.last_rate_limited_at=="number"?t.last_rate_limited_at:Math.max(0,...h(e).map(A=>A.lastRateLimitedAt));await F(e,o,{refresh:d,access:i,expires:m,...a?{accountId:a}:{},lastRateLimitedAt:k,autoLabel:t.auto_label===!0});const w={provider:e,installed:!0,managed:!0};return a&&(w.account_id=a),{status:200,body:w}}default:throw new s(400,"invalid_request",`unknown credential kind: ${String(t.kind)}`)}}r(V,"handleInstall");async function K(n){const e=n.params.provider;if(p(e)){const o=await P(e),a=await _(e,l());return{status:200,body:{provider:e,removed_accounts:o+(a?1:0)}}}const t=await _(e,l())?1:0;return{status:200,body:{provider:e,removed_accounts:t}}}r(K,"handleDelete");function v(n){const e=n.body;if(e===void 0)return{};if(typeof e!="object"||e===null||Array.isArray(e))throw new s(400,"invalid_request","request body must be a JSON object");return e}r(v,"bodyObject");async function U(n){const e=v(n);return{status:200,body:await T({provider:e.provider,...e.redirect!==void 0?{redirect:e.redirect}:{},...e.label!==void 0?{label:e.label}:{}})}}r(U,"handleFlowStart");async function H(n){const e=v(n);return{status:200,body:await R(n.params.flow_id,e.input)}}r(H,"handleFlowComplete");async function J(n){const e=M(n.params.flow_id);if(!e)throw new b(400,"invalid_request","no sign-in has this flow_id","flow_id");const{jti:t,...o}=e;return{status:200,body:o}}r(J,"handleFlowCancel");async function Q(n){for(const[d]of n.query)if(d!=="wait")throw u(`unknown model-auth flow query parameter: ${d}`);f(n,"wait");const e=C("model_auth.get",n.query),t=new AbortController;n.res.once("close",()=>t.abort());const o=await $(n.params.flow_id,e*1e3,t.signal);if(!o)throw new b(404,"not_found","no sign-in has this flow_id","flow_id");const{jti:a,...i}=o;return{status:200,body:i}}r(Q,"handleFlowGet");const re=[{method:"POST",pattern:"/v1/model-auth/flows",ownerOnly:!0,handler:U},{method:"POST",pattern:"/v1/model-auth/flows/:flow_id/complete",ownerOnly:!0,handler:H},{method:"POST",pattern:"/v1/model-auth/flows/:flow_id/cancel",ownerOnly:!0,handler:J},{method:"GET",pattern:"/v1/model-auth/flows/:flow_id",ownerOnly:!0,handler:Q},{method:"GET",pattern:"/v1/model-auth",handler:N},{method:"GET",pattern:"/v1/model-auth/readiness",handler:x},{method:"PUT",pattern:"/v1/model-auth/:provider",ownerOnly:!0,handler:V},{method:"DELETE",pattern:"/v1/model-auth/:provider",ownerOnly:!0,handler:K}];export{G as managedStatus,re as modelAuthRoutes};