@north-light/cli 0.3.413 → 0.3.415

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 (88) hide show
  1. package/dist/api/client.d.ts +20 -0
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/dto/common.d.ts +1 -1
  4. package/dist/api/dto/crons.d.ts +7 -1
  5. package/dist/api/dto/human-requests.d.ts +1 -1
  6. package/dist/api/dto/run-events.d.ts +1 -1
  7. package/dist/api/dto/usage.d.ts +27 -0
  8. package/dist/api/dto/usage.js +0 -0
  9. package/dist/api/errors.js +1 -1
  10. package/dist/api/index.d.ts +3 -2
  11. package/dist/api/index.js +1 -1
  12. package/dist/builtin-memory/crouter-sdk/README.md +22 -4
  13. package/dist/builtin-memory/crouter-sdk/client.md +32 -1
  14. package/dist/builtin-memory/crouter-sdk/errors.md +3 -0
  15. package/dist/builtin-memory/crouter-sdk/getting-started.md +26 -4
  16. package/dist/builtin-memory/crouter-sdk/release-notes.md +23 -0
  17. package/dist/builtin-memory/crouter-sdk/testing.md +9 -3
  18. package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -0
  19. package/dist/clients/attach/viewer.js +606 -606
  20. package/dist/commands/cron.js +1 -1
  21. package/dist/commands/node/create.js +1 -1
  22. package/dist/core/canvas/crons.d.ts +15 -1
  23. package/dist/core/canvas/crons.js +13 -13
  24. package/dist/core/canvas/delivery-contract.d.ts +1 -1
  25. package/dist/core/canvas/migrations.js +73 -66
  26. package/dist/core/canvas/types.d.ts +1 -1
  27. package/dist/core/command.d.ts +7 -0
  28. package/dist/core/command.js +9 -9
  29. package/dist/core/grants/mirror.d.ts +4 -0
  30. package/dist/core/grants/mirror.js +2 -2
  31. package/dist/core/layout.d.ts +2 -0
  32. package/dist/core/layout.js +2 -2
  33. package/dist/core/llm/run.d.ts +5 -1
  34. package/dist/core/llm/run.js +3 -3
  35. package/dist/core/providers/call.d.ts +2 -0
  36. package/dist/core/providers/call.js +1 -1
  37. package/dist/core/providers/errors.js +1 -1
  38. package/dist/core/providers/receipts.d.ts +2 -0
  39. package/dist/core/providers/receipts.js +2 -3
  40. package/dist/core/runs/operations.d.ts +2 -0
  41. package/dist/core/runs/operations.js +4 -4
  42. package/dist/core/runtime/broker/model-adapter.js +2 -2
  43. package/dist/core/runtime/run-command.d.ts +6 -0
  44. package/dist/core/runtime/run-command.js +1 -1
  45. package/dist/core/spaces/mounts.d.ts +2 -0
  46. package/dist/core/spaces/mounts.js +1 -1
  47. package/dist/core/spaces/process.d.ts +14 -2
  48. package/dist/core/spaces/process.js +1 -1
  49. package/dist/core/usage/ledger.d.ts +49 -0
  50. package/dist/core/usage/ledger.js +10 -0
  51. package/dist/daemon/api/app-listener.js +12 -12
  52. package/dist/daemon/api/handlers/crons.js +1 -1
  53. package/dist/daemon/api/handlers/llm.d.ts +8 -0
  54. package/dist/daemon/api/handlers/llm.js +3 -3
  55. package/dist/daemon/api/map.d.ts +1 -1
  56. package/dist/daemon/api/map.js +1 -1
  57. package/dist/daemon/api/operations.js +1 -1
  58. package/dist/daemon/cron/armed-context.d.ts +21 -4
  59. package/dist/daemon/cron/armed-context.js +1 -1
  60. package/dist/daemon/cron/authority.d.ts +27 -0
  61. package/dist/daemon/cron/authority.js +1 -0
  62. package/dist/daemon/cron/disposition.d.ts +2 -0
  63. package/dist/daemon/cron/disposition.js +1 -1
  64. package/dist/daemon/cron/escalation.js +3 -3
  65. package/dist/daemon/cron/folders.d.ts +5 -0
  66. package/dist/daemon/cron/folders.js +1 -0
  67. package/dist/daemon/cron/lease-recovery.js +1 -1
  68. package/dist/daemon/cron/settle.d.ts +10 -0
  69. package/dist/daemon/cron/settle.js +1 -1
  70. package/dist/daemon/cron/sinks.d.ts +2 -1
  71. package/dist/daemon/cron/sinks.js +2 -2
  72. package/dist/daemon/cron-run.js +1 -1
  73. package/dist/daemon/reconcilers/action-lane.js +1 -1
  74. package/dist/daemon/reconcilers/broker-supervision.d.ts +10 -0
  75. package/dist/daemon/reconcilers/broker-supervision.js +4 -4
  76. package/dist/daemon/reconcilers/cron-lane.d.ts +1 -0
  77. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  78. package/docs/sdk/README.md +22 -4
  79. package/docs/sdk/client.md +32 -1
  80. package/docs/sdk/errors.md +3 -0
  81. package/docs/sdk/getting-started.md +26 -4
  82. package/docs/sdk/release-notes.md +23 -0
  83. package/docs/sdk/testing.md +9 -3
  84. package/package.json +3 -3
  85. package/packages/crouter-identity/dist/index.d.ts +29 -2
  86. package/packages/crouter-identity/dist/index.js +81 -12
  87. package/packages/crouter-identity/package.json +1 -1
  88. package/runtime.lock.json +12 -12
@@ -1,12 +1,12 @@
1
- var Re=Object.defineProperty;var u=(e,t)=>Re(e,"name",{value:t,configurable:!0});import{prepared as E}from"../sqlite-statements.js";import{createHash as $}from"node:crypto";import{ApiError as Se}from"../../api/errors.js";import{isAbsolute as J,relative as Ne,resolve as ve,sep as Oe,join as be}from"node:path";import{SessionManager as De}from"@earendil-works/pi-coding-agent";import{covers as L}from"@north-light/identity";import{openDb as Y,withCanvasWrite as A}from"../canvas/db.js";import{deleteNodeWithOwned as Te}from"../graph/deletion.js";import{getById as Ie}from"../graph/objects.js";import{repairRunConversations as G}from"../canvas/sessions.js";import{getNode as S,nodeFiles as Me,removeNodeFiles as Le,updateNode as Ae}from"../canvas/canvas.js";import{fullName as Ce}from"../canvas/labels.js";import{renameWindow as ke}from"../runtime/placement.js";import{liveExecutionForNode as X}from"../canvas/executions.js";import{conversationStore as z,isConversationNotFound as xe}from"../conversation-store/index.js";import{enqueueNodeMail as We}from"../feed/messages.js";import{newestReportBody as Fe}from"../feed/reports.js";import{ensureAppProfile as He,loadExactProfileManifest as qe}from"../profiles/manifest.js";import{stopNodeSandboxes as Q}from"../spaces/stop.js";import{BrokerUnreachableError as Ue}from"../runtime/broker-request.js";import{interruptLive as Pe}from"../runtime/interactive-deliver.js";import{isBrokerLive as V}from"../runtime/model-swap.js";import{newNodeId as Be}from"../runtime/nodes.js";import{writeGoal as je}from"../runtime/kickoff.js";import{reviveNode as W}from"../runtime/revive.js";import{spawnChildPrepared as $e}from"../runtime/spawn.js";import{isolatedBrokerLauncherAvailable as Je}from"../runtime/host.js";import{resolveRunKind as Ye}from"../runtime/launch-target.js";import{cycleAwareMessages as Ge}from"../runtime/session-cycles.js";import{originFor as Z}from"../runtime/stamp/protocol.js";import{readOutputRequest as Xe,readOutputResult as K}from"../runtime/structured-output.js";import{admitLegacyStructuredOutput as ee}from"../../daemon/structured-requests/sidecar.js";import{runSchemaRepromptPending as ze}from"../runtime/stop-guard.js";import{toRunOutcomeDTO as Qe}from"../../daemon/api/map.js";import{openAssistantMessageStartSequence as Ve}from"../../daemon/runs/observer.js";import{closeNode as te}from"../runtime/close.js";import{parseScope as Ze}from"@north-light/identity";import{appendRunEvent as ne,subscribeRunEvents as Ke}from"./events.js";import{runActivity as et}from"./activity.js";import{cancelOpenQuestions as se,openQuestionIds as re}from"./questions.js";import{appSpace as oe}from"../layout.js";import{ensureAppSpace as tt}from"../spaces/space.js";import{assertUploadsUsable as ie,resolveStorageUrls as nt}from"../storage/attachments.js";import{storageClient as st,storageConfig as rt}from"../storage/client.js";import{deleteUploadRow as ot,insertUploadUses as ae,releaseRunUploads as it}from"../storage/tables.js";import{abortProviderCalls as de}from"../providers/inflight.js";const ce=u((e,t,r)=>nt(t,e,r),"defaultResolver"),at=3e3;function sn(e){return Number(E(e,"UPDATE idempotency_keys SET grantee = 'app:terminal' WHERE grantee = 'user:local'").run().changes)}u(sn,"rewriteLocalIdempotencyGrantee");class R extends Error{static{u(this,"RunError")}status;code;param;details;scope;retryable;constructor(t,r,n,i,c,a,o=t===503){super(n),this.status=t,this.code=r,this.param=i,this.details=c,this.scope=a,this.retryable=o}}const O=u((e,t)=>new R(400,"invalid_request",e,t),"invalid"),F=u(()=>new R(404,"not_found","run not found"),"missing"),H=u(()=>({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}),"usage"),b=u(()=>Y(),"db");function C(e,t,r=!1){const n=S(e);if(!n||n.parent!==null||n.run_id!==e||n.grantee!==t||!r&&n.deleting_at)throw F();return n}u(C,"ownedRun");const q=["waiting_on_user","idle","settled"];function I(e){if(e.status==="canceled"||e.status==="dead"||e.status==="done"&&e.intent!=="parked")return"settled";const t=E(b(),`SELECT 1 FROM node_rows n WHERE n.run_id = ? AND n.status = 'active' AND
1
+ var Se=Object.defineProperty;var u=(e,t)=>Se(e,"name",{value:t,configurable:!0});import{prepared as E}from"../sqlite-statements.js";import{createHash as $}from"node:crypto";import{ApiError as Ne}from"../../api/errors.js";import{isAbsolute as J,relative as ve,resolve as Oe,sep as be,join as De}from"node:path";import{SessionManager as Te}from"@earendil-works/pi-coding-agent";import{covers as L}from"@north-light/identity";import{openDb as Y,withCanvasWrite as A}from"../canvas/db.js";import{deleteNodeWithOwned as Ie}from"../graph/deletion.js";import{getById as Me}from"../graph/objects.js";import{repairRunConversations as G}from"../canvas/sessions.js";import{getNode as S,nodeFiles as Le,removeNodeFiles as Ae,updateNode as Ce}from"../canvas/canvas.js";import{fullName as xe}from"../canvas/labels.js";import{renameWindow as ke}from"../runtime/placement.js";import{liveExecutionForNode as X}from"../canvas/executions.js";import{conversationStore as z,isConversationNotFound as We}from"../conversation-store/index.js";import{enqueueNodeMail as Fe}from"../feed/messages.js";import{newestReportBody as He}from"../feed/reports.js";import{ensureAppProfile as qe,loadExactProfileManifest as Ue}from"../profiles/manifest.js";import{stopNodeSandboxes as Q}from"../spaces/stop.js";import{BrokerUnreachableError as Pe}from"../runtime/broker-request.js";import{interruptLive as Be}from"../runtime/interactive-deliver.js";import{isBrokerLive as V}from"../runtime/model-swap.js";import{newNodeId as je}from"../runtime/nodes.js";import{writeGoal as $e}from"../runtime/kickoff.js";import{reviveNode as F}from"../runtime/revive.js";import{spawnChildPrepared as Je}from"../runtime/spawn.js";import{isolatedBrokerLauncherAvailable as Ye}from"../runtime/host.js";import{resolveRunKind as Ge}from"../runtime/launch-target.js";import{cycleAwareMessages as Xe}from"../runtime/session-cycles.js";import{originFor as Z}from"../runtime/stamp/protocol.js";import{readOutputRequest as ze,readOutputResult as K}from"../runtime/structured-output.js";import{admitLegacyStructuredOutput as ee}from"../../daemon/structured-requests/sidecar.js";import{runSchemaRepromptPending as Qe}from"../runtime/stop-guard.js";import{toRunOutcomeDTO as Ve}from"../../daemon/api/map.js";import{openAssistantMessageStartSequence as Ze}from"../../daemon/runs/observer.js";import{closeNode as te}from"../runtime/close.js";import{parseScope as Ke}from"@north-light/identity";import{appendRunEvent as ne,subscribeRunEvents as et}from"./events.js";import{runActivity as tt}from"./activity.js";import{cancelOpenQuestions as se,openQuestionIds as re}from"./questions.js";import{appSpace as oe}from"../layout.js";import{ensureAppSpace as nt}from"../spaces/space.js";import{assertUploadsUsable as ie,resolveStorageUrls as st}from"../storage/attachments.js";import{storageClient as rt,storageConfig as ot}from"../storage/client.js";import{deleteUploadRow as it,insertUploadUses as ae,releaseRunUploads as at}from"../storage/tables.js";import{abortProviderCalls as de}from"../providers/inflight.js";const ce=u((e,t,r)=>st(t,e,r),"defaultResolver"),dt=3e3;function rn(e){return Number(E(e,"UPDATE idempotency_keys SET grantee = 'app:terminal' WHERE grantee = 'user:local'").run().changes)}u(rn,"rewriteLocalIdempotencyGrantee");class R extends Error{static{u(this,"RunError")}status;code;param;details;scope;retryable;constructor(t,r,n,i,c,a,o=t===503){super(n),this.status=t,this.code=r,this.param=i,this.details=c,this.scope=a,this.retryable=o}}const O=u((e,t)=>new R(400,"invalid_request",e,t),"invalid"),H=u(()=>new R(404,"not_found","run not found"),"missing"),q=u(()=>({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}),"usage"),b=u(()=>Y(),"db");function C(e,t,r=!1){const n=S(e);if(!n||n.parent!==null||n.run_id!==e||n.grantee!==t||!r&&n.deleting_at)throw H();return n}u(C,"ownedRun");const U=["waiting_on_user","idle","settled"];function I(e){if(e.status==="canceled"||e.status==="dead"||e.status==="done"&&e.intent!=="parked")return"settled";const t=E(b(),`SELECT 1 FROM node_rows n WHERE n.run_id = ? AND n.status = 'active' AND
2
2
  (n.last_settled_at IS NULL OR EXISTS (
3
3
  SELECT 1 FROM run_events e WHERE e.run_id = ? AND e.node_id = n.node_id AND e.type = 'node.turn.started'
4
4
  AND e.sequence_number > COALESCE((SELECT MAX(c.sequence_number) FROM run_events c
5
5
  WHERE c.run_id = e.run_id AND c.node_id = e.node_id AND c.type = 'node.turn.completed'), 0)
6
- )) LIMIT 1`).get(e.node_id,e.node_id),r=E(b(),"SELECT 1 FROM deliveries WHERE destination_id IN (SELECT node_id FROM nodes WHERE run_id = ?) AND state IN ('armed', 'pending') AND wake = 1 LIMIT 1").get(e.node_id);return t||r||!e.last_settled_at||ze(e)?"running":re(e.node_id).length?"waiting_on_user":"idle"}u(I,"runStatus");function rn(e){const t=S(e);return t!==null&&I(t)==="running"}u(rn,"runIsRunning");function M(e){G(e.node_id),e=S(e.node_id)??e;const t=I(e),r=t==="settled"?[]:re(e.node_id),n=Fe(e.node_id)||void 0,i=t==="settled"?Qe(e):null,c=i?.kind==="result"?K(e.node_id,e.grantee):void 0;return{run_id:e.node_id,name:e.name,status:t,...i?{outcome:i}:{},...c===void 0?{}:{result:c},...n!==void 0?{last_report:n}:{},pending_question_ids:r,...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(H())),payer:"user",scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:et(e.node_id,t)}}u(M,"runObject");const U=864e5,ue=255;function on(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>ue)throw O(`Idempotency-Key must be 1-${ue} characters`);return e}}u(on,"idempotencyKeyOf");function P(e,t,r,n=Y()){const i=E(n,"SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!i||Date.now()-Date.parse(i.created_at)>=U)){if(i.body_hash!==r)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:i.run_id,response:JSON.parse(i.response_json)}}}u(P,"findIdempotent");function me(e,t,r,n,i,c,a){E(e,"DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,r,new Date(Date.now()-U).toISOString()),E(e,"INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,r,n,i,JSON.stringify(c),a)}u(me,"recordIdempotent");function an(e=Date.now()){return A(t=>Number(E(t,"DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-U).toISOString()).changes))}u(an,"pruneExpiredIdempotencyKeys");function le(e,t,r,n,i){const c=n.text;let a=0;return{messageId:We(t,{kind:"message",label:c.slice(0,120),senderNodeId:"human",senderName:r,bodyJson:i?{body:c}:{body:c,start_turn:!1},urgency:i?"normal":"deferred",wake:i?1:0,withinEnqueue:u((d,p)=>{n.uses.length&&(ie(r,n.uses,d),ae(n.uses.map(({upload_id:y,path:g})=>({upload_id:y,path:g,run_id:t})),d)),a=ne(d,t,t,"node.message.accepted",{message_id:p,start_turn:i}).sequence_number},"withinEnqueue")}).messageId,sequence:a}}u(le,"acceptRunMessage");async function dn(e,t,r,n=ce){if(t.isolated&&!Je())throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");const i=$("sha256").update(JSON.stringify(t)).digest("hex");if(r){const s=P(e.grantee,r,i);if(s){const m=C(s.run_id,e.grantee);return!m.pi_pid&&I(m)==="running"&&(t.output_schema!==void 0&&!m.outcome_at&&Xe(m.node_id).state==="absent"&&K(m.node_id,m.grantee)===void 0&&ee(m.node_id,"terminal",t.output_schema),await W(m.node_id,{resume:!0,capacity:"freeze"})),s.response}}await tt(e.grantee);const c=t.profile===void 0?He(e.grantee):qe(t.profile),a=c.manifest.grantee??"app:terminal";if(a!==e.grantee){const s=`crtr:memory:rw:${a}:profile:${c.profileId}`;if(!e.resolved.some(m=>L(m,s))||t.scopes!==null&&!t.scopes.some(m=>L(m,s)))throw new R(403,"scope_missing",`missing scope ${s}`,"profile",void 0,s)}const o=Be(),d=be(oe(e.grantee).nodes,o);if(t.isolated&&t.cwd!==void 0){const s=Ne(d,ve(t.cwd));if(!J(t.cwd)||s===".."||s.startsWith(`..${Oe}`)||J(s))throw O("cwd must be inside the isolated root node folder","cwd")}const p=await n(o,e.grantee,t.prompt),y=p.text;let g;try{g=t.message===void 0?void 0:await n(o,e.grantee,t.message)}catch(s){throw p.cleanup(),s}let f;try{await $e({kind:Ye(c),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??d:oe(e.grantee).files,profile:c.profileId,prompt:y,nodeId:o,isolated:t.isolated??!1,...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:o,event_cursor:1,usage_json:JSON.stringify(H())},withinCreate:u((s,m)=>{const h=p.uses;if(h.length&&(ie(e.grantee,h,s),ae(h.map(({upload_id:w,path:k})=>({upload_id:w,path:k,run_id:o})),s)),je(o,y),f={run_id:o,node_id:o,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:"user",status:"running",created_at:m.created,metadata:t.metadata,sequence_number:0},ne(s,o,o,"run.started",{scopes:t.scopes,payer:"user",usage:H()}),g){const w=le(s,o,e.grantee,g,!1);f.message={run_id:o,message_id:w.messageId,sequence_number:w.sequence}}r&&me(s,e.grantee,r,i,o,f,m.created)},"withinCreate")},t.output_schema===void 0?void 0:s=>{ee(s,"terminal",t.output_schema)})}catch(s){if(S(o)||(p.cleanup(),g?.cleanup()),s instanceof Se&&(s.code==="isolated_run"||s.code==="invalid_request")){const m=s.details&&typeof s.details=="object"&&"param"in s.details&&typeof s.details.param=="string"?s.details.param:void 0;throw new R(s.status,s.code,s.message,m)}throw s}if(!f)throw new Error("run creation did not commit");return f}u(dn,"startRun");function cn(e,t){let r;if(t.after)try{const o=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!o||typeof o!="object"||!("activity"in o)||typeof o.activity!="string"||!("id"in o)||typeof o.id!="string")throw new Error("invalid cursor");r={activity:o.activity,id:o.id}}catch{throw O("invalid list cursor","after")}const i=b().prepare(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
6
+ )) LIMIT 1`).get(e.node_id,e.node_id),r=E(b(),"SELECT 1 FROM deliveries WHERE destination_id IN (SELECT node_id FROM nodes WHERE run_id = ?) AND state IN ('armed', 'pending') AND wake = 1 LIMIT 1").get(e.node_id);return t||r||!e.last_settled_at||Qe(e)?"running":re(e.node_id).length?"waiting_on_user":"idle"}u(I,"runStatus");function on(e){const t=S(e);return t!==null&&I(t)==="running"}u(on,"runIsRunning");function M(e){G(e.node_id),e=S(e.node_id)??e;const t=I(e),r=t==="settled"?[]:re(e.node_id),n=He(e.node_id)||void 0,i=t==="settled"?Ve(e):null,c=i?.kind==="result"?K(e.node_id,e.grantee):void 0;return{run_id:e.node_id,name:e.name,status:t,...i?{outcome:i}:{},...c===void 0?{}:{result:c},...n!==void 0?{last_report:n}:{},pending_question_ids:r,...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(q())),payer:"user",scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:tt(e.node_id,t)}}u(M,"runObject");const P=864e5,ue=255;function an(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>ue)throw O(`Idempotency-Key must be 1-${ue} characters`);return e}}u(an,"idempotencyKeyOf");function x(e,t,r,n=Y()){const i=E(n,"SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!i||Date.now()-Date.parse(i.created_at)>=P)){if(i.body_hash!==r)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:i.run_id,response:JSON.parse(i.response_json)}}}u(x,"findIdempotent");function me(e,t,r,n,i,c,a){E(e,"DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,r,new Date(Date.now()-P).toISOString()),E(e,"INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,r,n,i,JSON.stringify(c),a)}u(me,"recordIdempotent");function dn(e=Date.now()){return A(t=>Number(E(t,"DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-P).toISOString()).changes))}u(dn,"pruneExpiredIdempotencyKeys");function le(e,t,r,n,i){const c=n.text;let a=0;return{messageId:Fe(t,{kind:"message",label:c.slice(0,120),senderNodeId:"human",senderName:r,bodyJson:i?{body:c}:{body:c,start_turn:!1},urgency:i?"normal":"deferred",wake:i?1:0,withinEnqueue:u((d,p)=>{n.uses.length&&(ie(r,n.uses,d),ae(n.uses.map(({upload_id:y,path:g})=>({upload_id:y,path:g,run_id:t})),d)),a=ne(d,t,t,"node.message.accepted",{message_id:p,start_turn:i}).sequence_number},"withinEnqueue")}).messageId,sequence:a}}u(le,"acceptRunMessage");class _e extends Error{static{u(this,"IdempotentReplay")}winner;constructor(t){super("idempotent replay"),this.winner=t}}async function cn(e,t,r,n=ce){if(t.isolated&&!Ye())throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");const i=$("sha256").update(JSON.stringify(t)).digest("hex");if(r){const s=x(e.grantee,r,i);if(s){const m=C(s.run_id,e.grantee);return!m.pi_pid&&I(m)==="running"&&(t.output_schema!==void 0&&!m.outcome_at&&ze(m.node_id).state==="absent"&&K(m.node_id,m.grantee)===void 0&&ee(m.node_id,"terminal",t.output_schema),await F(m.node_id,{resume:!0,capacity:"freeze"})),s.response}}await nt(e.grantee);const c=t.profile===void 0?qe(e.grantee):Ue(t.profile),a=c.manifest.grantee??"app:terminal";if(a!==e.grantee){const s=`crtr:memory:rw:${a}:profile:${c.profileId}`;if(!e.resolved.some(m=>L(m,s))||t.scopes!==null&&!t.scopes.some(m=>L(m,s)))throw new R(403,"scope_missing",`missing scope ${s}`,"profile",void 0,s)}const o=je(),d=De(oe(e.grantee).nodes,o);if(t.isolated&&t.cwd!==void 0){const s=ve(d,Oe(t.cwd));if(!J(t.cwd)||s===".."||s.startsWith(`..${be}`)||J(s))throw O("cwd must be inside the isolated root node folder","cwd")}const p=await n(o,e.grantee,t.prompt),y=p.text;let g;try{g=t.message===void 0?void 0:await n(o,e.grantee,t.message)}catch(s){throw p.cleanup(),s}let f;try{await Je({kind:Ge(c),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??d:oe(e.grantee).files,profile:c.profileId,prompt:y,nodeId:o,isolated:t.isolated??!1,...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:o,event_cursor:1,usage_json:JSON.stringify(q())},withinCreate:u((s,m)=>{if(r){const w=x(e.grantee,r,i,s);if(w)throw new _e(w)}const h=p.uses;if(h.length&&(ie(e.grantee,h,s),ae(h.map(({upload_id:w,path:k})=>({upload_id:w,path:k,run_id:o})),s)),$e(o,y),f={run_id:o,node_id:o,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:"user",status:"running",created_at:m.created,metadata:t.metadata,sequence_number:0},ne(s,o,o,"run.started",{scopes:t.scopes,payer:"user",usage:q()}),g){const w=le(s,o,e.grantee,g,!1);f.message={run_id:o,message_id:w.messageId,sequence_number:w.sequence}}r&&me(s,e.grantee,r,i,o,f,m.created)},"withinCreate")},t.output_schema===void 0?void 0:s=>{ee(s,"terminal",t.output_schema)})}catch(s){if(S(o)||(p.cleanup(),g?.cleanup()),s instanceof _e)return s.winner.response;if(s instanceof Ne&&(s.code==="isolated_run"||s.code==="invalid_request")){const m=s.details&&typeof s.details=="object"&&"param"in s.details&&typeof s.details.param=="string"?s.details.param:void 0;throw new R(s.status,s.code,s.message,m)}throw s}if(!f)throw new Error("run creation did not commit");return f}u(cn,"startRun");function un(e,t){let r;if(t.after)try{const o=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!o||typeof o!="object"||!("activity"in o)||typeof o.activity!="string"||!("id"in o)||typeof o.id!="string")throw new Error("invalid cursor");r={activity:o.activity,id:o.id}}catch{throw O("invalid list cursor","after")}const i=b().prepare(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
7
7
  WHERE run_id = node_id AND deleting_at IS NULL ${e===null?"":"AND grantee = ?"}
8
- ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e]).filter(({node_id:o})=>!t.visible||t.visible(o)).filter(({activity:o,node_id:d})=>!r||o<r.activity||o===r.activity&&d<r.id).map(o=>({root:S(o.node_id),activity:o.activity})).filter(({root:o})=>!t.status||I(o)===t.status),c=i.slice(0,t.limit),a=c.at(-1);return{runs:c.map(({root:o})=>M(o)),next:i.length>t.limit&&a?Buffer.from(JSON.stringify({activity:a.activity,id:a.root.node_id})).toString("base64url"):null}}u(cn,"listRuns");async function un(e,t,r){const n=C(e,t),i=u(()=>M(C(e,t)),"read");return!r||q.includes(I(n))?i():new Promise((c,a)=>{const o=u(()=>{clearTimeout(y),clearInterval(p),d();try{c(i())}catch(g){a(g)}},"finish"),d=Ke(e,()=>{try{q.includes(String(i().status))&&o()}catch(g){clearTimeout(y),clearInterval(p),d(),a(g)}}),p=setInterval(()=>{q.includes(String(i().status))&&o()},250),y=setTimeout(o,r*1e3)})}u(un,"waitRun");async function mn(e,t,r=ce,n=!0,i){const c=e.status==="canceled"||e.outcome_reason==="closed",a=e.grantee??"app:terminal",o=$("sha256").update(JSON.stringify(["run.message",e.node_id,t,n])).digest("hex"),d=u(async f=>{const s=S(e.node_id),m=E(b(),"SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(f.message_id));return s&&!c&&n&&!s.pi_pid&&m&&await W(e.node_id,{resume:!0,capacity:"freeze"}),f},"replay");if(i){const f=P(a,i,o);if(f)return d(f.response)}if(c)throw new R(409,"run_settled","canceled run cannot receive a message");const p=await r(e.node_id,a,t),y=n&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;let g;try{g=A(f=>{if(i){const h=P(a,i,o,f);if(h)return{replayed:h.response}}y?.(e.node_id);const s=le(f,e.node_id,a,p,n),m={run_id:e.node_id,message_id:s.messageId,sequence_number:s.sequence};return i&&me(f,a,i,o,e.node_id,m,new Date().toISOString()),{result:m}})}catch(f){throw p.cleanup(),f}return"replayed"in g?(p.cleanup(),d(g.replayed)):(n&&!e.pi_pid&&await W(e.node_id,{resume:!0,capacity:"freeze"}),g.result)}u(mn,"messageRun");async function dt(e){const t=u(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let r=0;r<2;r++){const n=S(e);if(!n)throw F();if(!V(n)){if(n.streaming)throw t();return}const i=X(e)?.executionId;try{await Pe(e)==="turn"&&de({nodes:[e]});return}catch(c){if(!(c instanceof Ue))throw c;const a=S(e);if(!a)throw F();if(!a.streaming)return;if(r===0&&V(a)&&X(e)?.executionId!==i)continue;throw t()}}throw t()}u(dt,"interruptNode");async function ln(e,t="stop"){const r=t==="stop"?E(b(),"SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id).map(({node_id:n})=>n):[e.node_id];return await Promise.all(r.map(n=>dt(n))),M(S(e.node_id)??e)}u(ln,"interruptRun");async function _n(e,t={}){const r=E(b(),"SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id).map(a=>a.node_id);de({run:e.node_id});const n=[];if(I(e)!=="settled")n.push(...te(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"}).closed);else for(const a of r){if(a===e.node_id)continue;const o=S(a);o&&o.status!=="done"&&o.status!=="canceled"&&o.status!=="dead"&&n.push(...te(a,{preserveEmpty:!0,stopReason:"run_canceled"}).closed)}if(await se({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...n])].map(a=>Q(a,"run_canceled")))).some(a=>!a.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const c=S(e.node_id);return M(c??e)}u(_n,"cancelRun");const _e=200;function pn(e,t){if(typeof t!="string"||!t.trim())throw O("name is required","name");const r=t.trim();if(r.length>_e)throw O(`name exceeds ${_e} characters`,"name");const n=Ae(e.node_id,{name:r});return n.window&&ke(n.window,Ce(n)),M(n)}u(pn,"renameRun");async function fn(e,t={config:rt,client:st}){await se({run_id:e.node_id},"the run was deleted");const r=A(d=>(E(d,"UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),E(d,"SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id).map(p=>p.node_id)));if((await Promise.all(r.map(d=>Q(d,"run_deleted",{deleting:!0})))).some(d=>!d.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const i=r.map(d=>{const p=Me(d);if(p.app===null)throw new Error(`run node missing during deletion: ${d}`);return p}),c=E(b(),"SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:d}of c)try{z().delete(d)}catch(p){if(!xe(p))throw p}const a=A(d=>{for(const f of r){const s=Ie(d,f);s!==null&&s.deleted_at===null&&Te(d,s)}const p=it([e.node_id],d);E(d,"DELETE FROM run_events WHERE run_id = ?").run(e.node_id),E(d,"DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const y=r.map(()=>"?").join(","),g=d.prepare(`SELECT message_id FROM messages WHERE sender_node_id IN (${y}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${y})`).all(...r,...r);d.prepare(`DELETE FROM deliveries WHERE destination_id IN (${y})`).run(...r);for(const{message_id:f}of g)E(d,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(f,f);return p});for(const d of i)Le(d);const{notifyRunDeleted:o}=await import("./events.js");return o(e.node_id),{uploads_pending:await ct(a,t)}}u(fn,"deleteRun");async function ct(e,t){if(!e.length||!t.config())return e.length;let r;try{r=t.client()}catch{return e.length}const n=new Set,i=e.map(a=>r.deleteUpload(a).then(()=>{ot(a),n.add(a)},()=>{}));let c;return await Promise.race([Promise.all(i),new Promise(a=>{c=setTimeout(a,t.budgetMs??at),c.unref?.()})]),clearTimeout(c),e.length-n.size}u(ct,"deleteReleasedUploads");function gn(e,t,r){C(e,t),G(e);const n=b();n.exec("BEGIN");try{const c=E(n,"SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e).map(s=>({node_id:s.node_id,parent_node_id:s.parent,conversations:E(n,"SELECT DISTINCT session_id FROM sessions WHERE node_id = ? ORDER BY created_at").all(s.node_id).map(({session_id:m})=>{const{header:h,entries:w}=z().read(m);if(r==="entries")return{conversation_id:m,header:h,entries:w};const k=De.inMemory(h.cwd,void 0,[h,...w]),pe=Ge(k)??[],B=new Map(w.map((_,N)=>[_.id,N])),fe=w.filter(_=>_.type==="custom"&&_.customType==="crtr-system-prompt").map(_=>({id:_.id,message:{role:"custom",customType:"crtr-system-prompt",data:"data"in _?_.data:void 0,timestamp:_.timestamp}})),j=w.filter(_=>_.type==="custom_message"&&_.customType==="app_message").map(_=>({id:_.id,message:{role:"custom",customType:"app_message",content:"content"in _?_.content:"",timestamp:_.timestamp}})),ge=s.node_id===e?w.find(_=>{if(_.type!=="message"||_.message.role!=="user")return!1;const N=Z(w,_.message.timestamp);return N?.type==="runtime"&&N.kind==="kickoff"})?.id:void 0,Ee=[...pe.filter(({id:_})=>!j.some(N=>N.id===_)),...fe,...j].sort((_,N)=>(B.get(_.id)??Number.MAX_SAFE_INTEGER)-(B.get(N.id)??Number.MAX_SAFE_INTEGER)).map(({id:_,message:N})=>{const l=N,D=l.usage,x=l.role==="user"?Z(w,Number(l.timestamp)):null,we=x?.type==="human"||_===ge,T=l.role==="toolResult"?"tool_result":l.role==="bashExecution"?"bash_execution":l.role==="compactionSummary"?"compaction_summary":l.role==="branchSummary"?"branch_summary":l.role==="custom"&&l.customType==="crtr-system-prompt"?"system":l.role==="user"?we?"user":"custom":l.role,ye=T==="custom"?l.role==="custom"?l.customType:x?.type==="runtime"?x.kind:"unknown":void 0,he=T==="system"&&l.customType==="crtr-system-prompt"?[{type:"text",text:l.data?.text??""}]:typeof l.content=="string"?[{type:"text",text:l.content}]:Array.isArray(l.content)?l.content.map(v=>v.type==="toolCall"?{type:"tool_call",tool_call_id:v.id,name:v.name,arguments:v.arguments}:v.type==="image"?{type:"image",mime_type:v.mimeType}:v.type==="thinking"?{type:"thinking",text:v.thinking}:v):[];return{entry_id:_,role:T,...T==="custom"?{custom_type:ye}:{},time:typeof l.timestamp=="string"?l.timestamp:new Date(Number(l.timestamp??Date.now())).toISOString(),content:he,...T==="assistant"?{model:l.model,provider:l.provider,stop_reason:l.stopReason,...l.errorMessage?{error_message:l.errorMessage}:{},...D?{usage:{input_tokens:D.input??0,output_tokens:D.output??0,cache_read_tokens:D.cacheRead??0,cache_write_tokens:D.cacheWrite??0,cost_usd:D.cost?.total??0}}:{}}:{},...T==="tool_result"?{tool_call_id:l.toolCallId,tool_name:l.toolName,is_error:l.isError}:{}}})??[];return{conversation_id:m,messages:Ee}})})),o=E(n,"SELECT event_cursor FROM nodes WHERE node_id = ?").get(e).event_cursor-1,d=new Map(c.map(s=>[s.node_id,Ve(s.node_id)])),p=E(n,`SELECT node_id, sequence_number FROM run_events
8
+ ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e]).filter(({node_id:o})=>!t.visible||t.visible(o)).filter(({activity:o,node_id:d})=>!r||o<r.activity||o===r.activity&&d<r.id).map(o=>({root:S(o.node_id),activity:o.activity})).filter(({root:o})=>!t.status||I(o)===t.status),c=i.slice(0,t.limit),a=c.at(-1);return{runs:c.map(({root:o})=>M(o)),next:i.length>t.limit&&a?Buffer.from(JSON.stringify({activity:a.activity,id:a.root.node_id})).toString("base64url"):null}}u(un,"listRuns");async function mn(e,t,r){const n=C(e,t),i=u(()=>M(C(e,t)),"read");return!r||U.includes(I(n))?i():new Promise((c,a)=>{const o=u(()=>{clearTimeout(y),clearInterval(p),d();try{c(i())}catch(g){a(g)}},"finish"),d=et(e,()=>{try{U.includes(String(i().status))&&o()}catch(g){clearTimeout(y),clearInterval(p),d(),a(g)}}),p=setInterval(()=>{U.includes(String(i().status))&&o()},250),y=setTimeout(o,r*1e3)})}u(mn,"waitRun");async function ln(e,t,r=ce,n=!0,i){const c=e.status==="canceled"||e.outcome_reason==="closed",a=e.grantee??"app:terminal",o=$("sha256").update(JSON.stringify(["run.message",e.node_id,t,n])).digest("hex"),d=u(async f=>{const s=S(e.node_id),m=E(b(),"SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(f.message_id));return s&&!c&&n&&!s.pi_pid&&m&&await F(e.node_id,{resume:!0,capacity:"freeze"}),f},"replay");if(i){const f=x(a,i,o);if(f)return d(f.response)}if(c)throw new R(409,"run_settled","canceled run cannot receive a message");const p=await r(e.node_id,a,t),y=n&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;let g;try{g=A(f=>{if(i){const h=x(a,i,o,f);if(h)return{replayed:h.response}}y?.(e.node_id);const s=le(f,e.node_id,a,p,n),m={run_id:e.node_id,message_id:s.messageId,sequence_number:s.sequence};return i&&me(f,a,i,o,e.node_id,m,new Date().toISOString()),{result:m}})}catch(f){throw p.cleanup(),f}return"replayed"in g?(p.cleanup(),d(g.replayed)):(n&&!e.pi_pid&&await F(e.node_id,{resume:!0,capacity:"freeze"}),g.result)}u(ln,"messageRun");async function ct(e){const t=u(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let r=0;r<2;r++){const n=S(e);if(!n)throw H();if(!V(n)){if(n.streaming)throw t();return}const i=X(e)?.executionId;try{await Be(e)==="turn"&&de({nodes:[e]});return}catch(c){if(!(c instanceof Pe))throw c;const a=S(e);if(!a)throw H();if(!a.streaming)return;if(r===0&&V(a)&&X(e)?.executionId!==i)continue;throw t()}}throw t()}u(ct,"interruptNode");async function _n(e,t="stop"){const r=t==="stop"?E(b(),"SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id).map(({node_id:n})=>n):[e.node_id];return await Promise.all(r.map(n=>ct(n))),M(S(e.node_id)??e)}u(_n,"interruptRun");async function pn(e,t={}){const r=E(b(),"SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id).map(a=>a.node_id);de({run:e.node_id});const n=[];if(I(e)!=="settled")n.push(...te(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"}).closed);else for(const a of r){if(a===e.node_id)continue;const o=S(a);o&&o.status!=="done"&&o.status!=="canceled"&&o.status!=="dead"&&n.push(...te(a,{preserveEmpty:!0,stopReason:"run_canceled"}).closed)}if(await se({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...n])].map(a=>Q(a,"run_canceled")))).some(a=>!a.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const c=S(e.node_id);return M(c??e)}u(pn,"cancelRun");const pe=200;function fn(e,t){if(typeof t!="string"||!t.trim())throw O("name is required","name");const r=t.trim();if(r.length>pe)throw O(`name exceeds ${pe} characters`,"name");const n=Ce(e.node_id,{name:r});return n.window&&ke(n.window,xe(n)),M(n)}u(fn,"renameRun");async function gn(e,t={config:ot,client:rt}){await se({run_id:e.node_id},"the run was deleted");const r=A(d=>(E(d,"UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),E(d,"SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id).map(p=>p.node_id)));if((await Promise.all(r.map(d=>Q(d,"run_deleted",{deleting:!0})))).some(d=>!d.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const i=r.map(d=>{const p=Le(d);if(p.app===null)throw new Error(`run node missing during deletion: ${d}`);return p}),c=E(b(),"SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:d}of c)try{z().delete(d)}catch(p){if(!We(p))throw p}const a=A(d=>{for(const f of r){const s=Me(d,f);s!==null&&s.deleted_at===null&&Ie(d,s)}const p=at([e.node_id],d);E(d,"DELETE FROM run_events WHERE run_id = ?").run(e.node_id),E(d,"DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const y=r.map(()=>"?").join(","),g=d.prepare(`SELECT message_id FROM messages WHERE sender_node_id IN (${y}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${y})`).all(...r,...r);d.prepare(`DELETE FROM deliveries WHERE destination_id IN (${y})`).run(...r);for(const{message_id:f}of g)E(d,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(f,f);return p});for(const d of i)Ae(d);const{notifyRunDeleted:o}=await import("./events.js");return o(e.node_id),{uploads_pending:await ut(a,t)}}u(gn,"deleteRun");async function ut(e,t){if(!e.length||!t.config())return e.length;let r;try{r=t.client()}catch{return e.length}const n=new Set,i=e.map(a=>r.deleteUpload(a).then(()=>{it(a),n.add(a)},()=>{}));let c;return await Promise.race([Promise.all(i),new Promise(a=>{c=setTimeout(a,t.budgetMs??dt),c.unref?.()})]),clearTimeout(c),e.length-n.size}u(ut,"deleteReleasedUploads");function En(e,t,r){C(e,t),G(e);const n=b();n.exec("BEGIN");try{const c=E(n,"SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e).map(s=>({node_id:s.node_id,parent_node_id:s.parent,conversations:E(n,"SELECT DISTINCT session_id FROM sessions WHERE node_id = ? ORDER BY created_at").all(s.node_id).map(({session_id:m})=>{const{header:h,entries:w}=z().read(m);if(r==="entries")return{conversation_id:m,header:h,entries:w};const k=Te.inMemory(h.cwd,void 0,[h,...w]),fe=Xe(k)??[],B=new Map(w.map((_,N)=>[_.id,N])),ge=w.filter(_=>_.type==="custom"&&_.customType==="crtr-system-prompt").map(_=>({id:_.id,message:{role:"custom",customType:"crtr-system-prompt",data:"data"in _?_.data:void 0,timestamp:_.timestamp}})),j=w.filter(_=>_.type==="custom_message"&&_.customType==="app_message").map(_=>({id:_.id,message:{role:"custom",customType:"app_message",content:"content"in _?_.content:"",timestamp:_.timestamp}})),Ee=s.node_id===e?w.find(_=>{if(_.type!=="message"||_.message.role!=="user")return!1;const N=Z(w,_.message.timestamp);return N?.type==="runtime"&&N.kind==="kickoff"})?.id:void 0,we=[...fe.filter(({id:_})=>!j.some(N=>N.id===_)),...ge,...j].sort((_,N)=>(B.get(_.id)??Number.MAX_SAFE_INTEGER)-(B.get(N.id)??Number.MAX_SAFE_INTEGER)).map(({id:_,message:N})=>{const l=N,D=l.usage,W=l.role==="user"?Z(w,Number(l.timestamp)):null,ye=W?.type==="human"||_===Ee,T=l.role==="toolResult"?"tool_result":l.role==="bashExecution"?"bash_execution":l.role==="compactionSummary"?"compaction_summary":l.role==="branchSummary"?"branch_summary":l.role==="custom"&&l.customType==="crtr-system-prompt"?"system":l.role==="user"?ye?"user":"custom":l.role,he=T==="custom"?l.role==="custom"?l.customType:W?.type==="runtime"?W.kind:"unknown":void 0,Re=T==="system"&&l.customType==="crtr-system-prompt"?[{type:"text",text:l.data?.text??""}]:typeof l.content=="string"?[{type:"text",text:l.content}]:Array.isArray(l.content)?l.content.map(v=>v.type==="toolCall"?{type:"tool_call",tool_call_id:v.id,name:v.name,arguments:v.arguments}:v.type==="image"?{type:"image",mime_type:v.mimeType}:v.type==="thinking"?{type:"thinking",text:v.thinking}:v):[];return{entry_id:_,role:T,...T==="custom"?{custom_type:he}:{},time:typeof l.timestamp=="string"?l.timestamp:new Date(Number(l.timestamp??Date.now())).toISOString(),content:Re,...T==="assistant"?{model:l.model,provider:l.provider,stop_reason:l.stopReason,...l.errorMessage?{error_message:l.errorMessage}:{},...D?{usage:{input_tokens:D.input??0,output_tokens:D.output??0,cache_read_tokens:D.cacheRead??0,cache_write_tokens:D.cacheWrite??0,cost_usd:D.cost?.total??0}}:{}}:{},...T==="tool_result"?{tool_call_id:l.toolCallId,tool_name:l.toolName,is_error:l.isError}:{}}})??[];return{conversation_id:m,messages:we}})})),o=E(n,"SELECT event_cursor FROM nodes WHERE node_id = ?").get(e).event_cursor-1,d=new Map(c.map(s=>[s.node_id,Ze(s.node_id)])),p=E(n,`SELECT node_id, sequence_number FROM run_events
9
9
  WHERE run_id = ? AND sequence_number <= ? AND type IN ('node.thinking.done', 'node.output_text.done', 'usage')
10
10
  ORDER BY sequence_number`).all(e,o);for(const s of p){const m=d.get(s.node_id);m!=null&&s.sequence_number>=m&&d.set(s.node_id,null)}const y=E(n,`SELECT node_id, sequence_number, type, body_json FROM run_events
11
11
  WHERE run_id = ? AND sequence_number <= ? AND type IN ('node.thinking.delta', 'node.output_text.delta')
12
- ORDER BY sequence_number`).all(e,o),g=new Map;for(const s of y){const m=d.get(s.node_id);if(m==null||s.sequence_number<m)continue;const h=JSON.parse(s.body_json),w=g.get(s.node_id)??{thinking:"",text:""};s.type==="node.thinking.delta"?w.thinking+=h.delta:w.text+=h.delta,g.set(s.node_id,w)}const f=c.map(s=>({...s,in_progress_message:d.get(s.node_id)==null?null:g.get(s.node_id)??{thinking:"",text:""}}));return n.exec("COMMIT"),{run_id:e,latest_sequence:o,nodes:f}}catch(i){throw n.exec("ROLLBACK"),i}}u(gn,"traceRun");function En(e,t,r){if(t===void 0){if(!e.some(n=>L(n,"crtr:llm")))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return null}if(!Array.isArray(t)||t.some(n=>typeof n!="string"))throw O("scopes must be a list of scope strings","scopes");for(const n of t){try{Ze(n)}catch{throw O(`invalid scope ${n}`,"scopes")}if(/^crtr:files:(?:read|rw):app:/.test(n)&&n.split(":")[4]===r.slice(4))throw O(`own-app files cannot narrow a run: ${n}`,"scopes");if(!(/^crtr:memory:(?:read|rw):app:/.test(n)&&n.split(":")[4]===r.slice(4))&&!e.some(c=>L(c,n)))throw new R(403,"scope_missing",`missing scope ${n}`,void 0,void 0,n)}if(!t.includes("crtr:llm"))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return t}u(En,"requestedScopes");export{at as RUN_DELETE_STORAGE_BUDGET_MS,_e as RUN_NAME_MAX,R as RunError,_n as cancelRun,fn as deleteRun,on as idempotencyKeyOf,ln as interruptRun,cn as listRuns,mn as messageRun,C as ownedRun,an as pruneExpiredIdempotencyKeys,pn as renameRun,En as requestedScopes,sn as rewriteLocalIdempotencyGrantee,rn as runIsRunning,M as runObject,dn as startRun,gn as traceRun,un as waitRun};
12
+ ORDER BY sequence_number`).all(e,o),g=new Map;for(const s of y){const m=d.get(s.node_id);if(m==null||s.sequence_number<m)continue;const h=JSON.parse(s.body_json),w=g.get(s.node_id)??{thinking:"",text:""};s.type==="node.thinking.delta"?w.thinking+=h.delta:w.text+=h.delta,g.set(s.node_id,w)}const f=c.map(s=>({...s,in_progress_message:d.get(s.node_id)==null?null:g.get(s.node_id)??{thinking:"",text:""}}));return n.exec("COMMIT"),{run_id:e,latest_sequence:o,nodes:f}}catch(i){throw n.exec("ROLLBACK"),i}}u(En,"traceRun");function wn(e,t,r){if(t===void 0){if(!e.some(n=>L(n,"crtr:llm")))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return null}if(!Array.isArray(t)||t.some(n=>typeof n!="string"))throw O("scopes must be a list of scope strings","scopes");for(const n of t){try{Ke(n)}catch{throw O(`invalid scope ${n}`,"scopes")}if(/^crtr:files:(?:read|rw):app:/.test(n)&&n.split(":")[4]===r.slice(4))throw O(`own-app files cannot narrow a run: ${n}`,"scopes");if(!(/^crtr:memory:(?:read|rw):app:/.test(n)&&n.split(":")[4]===r.slice(4))&&!e.some(c=>L(c,n)))throw new R(403,"scope_missing",`missing scope ${n}`,void 0,void 0,n)}if(!t.includes("crtr:llm"))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return t}u(wn,"requestedScopes");export{P as IDEMPOTENCY_WINDOW_MS,dt as RUN_DELETE_STORAGE_BUDGET_MS,pe as RUN_NAME_MAX,R as RunError,pn as cancelRun,gn as deleteRun,an as idempotencyKeyOf,_n as interruptRun,un as listRuns,ln as messageRun,C as ownedRun,dn as pruneExpiredIdempotencyKeys,fn as renameRun,wn as requestedScopes,rn as rewriteLocalIdempotencyGrantee,on as runIsRunning,M as runObject,cn as startRun,En as traceRun,mn as waitRun};
@@ -1,2 +1,2 @@
1
- var q=Object.defineProperty;var l=(t,e)=>q(t,"name",{value:e,configurable:!0});import{setTimeout as F}from"node:timers/promises";import{createAssistantMessageEventStream as P,getCurrentSystemPrompt as W,getCurrentTools as j}from"@earendil-works/pi-ai";import{socketFetch as J}from"../../../api/node-transport.js";import{apiSocketPath as U}from"../../canvas/paths.js";import{currentNodeToken as Y}from"./node-token.js";import{envModelExact as T,envModelIntent as z}from"../../../shared/env.js";import{MODEL_CONTEXT_BODY_MAX_BYTES as k}from"../../llm/body-limits.js";import{modelRequestFromConfig as X}from"../../model-routes.js";import{parseProviderRateLimit as G,providerFailureDiagnostic as H}from"../../provider-failure.js";function de(t){t.checkAuth=async e=>t.getProvider(e)?{type:"api_key"}:void 0,t.getAvailable=async e=>t.getModels(e),t.getAvailableSnapshot=()=>t.getModels(),t.streamSimple=Z}l(de,"configureBrokerModelRuntime");const K=300,$="crtr:llm-refusal";class v extends Error{static{l(this,"LlmRefusalError")}retryAt;rateLimit;constructor(e,o,r){super(e),this.retryAt=o,this.rateLimit=r}}function L(t,e){const o=t===void 0?"the request body":`the request body (${t} bytes)`;return new Error(`request_too_large: ${o} exceeds the ${e}-byte model request limit; compact the conversation to shrink the context`)}l(L,"contextTooLargeError");function O(t){const e=t.details?.rate_limit;if(!e||typeof e!="object")return;const o=G(e);if(o===void 0)return;const{provider:r,model:n}=e;return typeof r=="string"&&r!==""&&typeof n=="string"&&n!==""?{...o,model:`${r}/${n}`}:o}l(O,"refusalRateLimit");function pe(t){for(const e of t.diagnostics??[]){if(e.type!==$)continue;const o=e.details?.retryAt;if(typeof o=="number"&&Number.isFinite(o)&&o>0)return o}}l(pe,"refusalRetryAt");class M extends Error{static{l(this,"ContentFilteredError")}}function C(t){return typeof t=="string"?t:t.map(e=>e.type==="text"?{type:"text",text:e.text}:{type:"image_url",image_url:{url:`data:${e.mimeType};base64,${e.data}`}})}l(C,"wireContent");function Q(t){const e=l(n=>n.role==="assistant"&&(n.stopReason==="error"||n.stopReason==="aborted"),"incomplete"),o=t.some(e)?t.filter(n=>!e(n)):t;let r=o.length;for(;r>0&&o[r-1]?.role==="assistant";)r--;return r===o.length?o:o.slice(0,r)}l(Q,"wireHistory");function V(t,e,o){const r=[],n=W(e.messages),c=j(e.messages);n?r.push({role:"system",content:n}):e.systemPrompt&&r.push({role:"system",content:e.systemPrompt});for(const d of Q(e.messages))d.role==="user"?r.push({role:"user",content:C(d.content)}):d.role==="toolResult"?r.push({role:"tool",content:C(d.content),tool_call_id:d.toolCallId}):d.role==="assistant"&&r.push({role:"assistant",content:d.content.filter(p=>p.type==="text").map(p=>p.text).join("")||null,tool_calls:d.content.filter(p=>p.type==="toolCall").map(p=>({id:p.id,type:"function",function:{name:p.name,arguments:JSON.stringify(p.arguments)}}))});const w=`${t.provider}/${t.id}`,y=z();return{model:X(w,y,T())?.kind==="logical"?y:w,model_policy:T()?"exact":"substitute",stream:!0,messages:r,...c.length?{tools:c.map(d=>({type:"function",function:{name:d.name,description:d.description,parameters:d.parameters}}))}:{},...o.reasoning?{reasoning_effort:o.reasoning}:{},...o.temperature!==void 0?{temperature:o.temperature}:{},...o.maxTokens!==void 0?{max_tokens:o.maxTokens}:{}}}l(V,"requestBody");function R(t,e){if(typeof e!="string")return!1;const o=e.indexOf("/");return o<1||o===e.length-1?!1:(t.provider=e.slice(0,o),t.model=e.slice(o+1),!0)}l(R,"applyServedModel");function D(t){if(t.details?.served_model!==void 0)return t.details.served_model;const e=t.details?.rate_limit;return typeof e?.provider=="string"&&typeof e.model=="string"?`${e.provider}/${e.model}`:void 0}l(D,"refusalRoute");function N(t){if(!t||typeof t!="object"||!("error"in t))throw new Error("llm returned an invalid response");const e=t.error;if(!e||typeof e.message!="string")throw new Error("llm returned an invalid refusal");return e}l(N,"parseRefusal");function Z(t,e,o={}){const r=P(),n={role:"assistant",content:[],api:t.api,provider:t.provider,model:t.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"pending",timestamp:Date.now()};return(async()=>{const c=Y();if(!c)throw new Error("llm requires a node token");const w=JSON.stringify(V(t,e,o)),y=Buffer.byteLength(w);if(y>k)throw L(y,k);let d=0;for(;;){o.signal?.throwIfAborted();const p=await J(U())("http://localhost/v1/llm",{method:"POST",headers:{authorization:`Bearer ${c}`,"content-type":"application/json"},body:w,signal:o.signal});if(!p.ok){const a=N(await p.json());if(p.status===413||a.code==="payload_too_large")throw L(y,k);if(R(n,D(a)),a.retryable!==!0)throw new v(a.message,void 0,O(a));const s=a.retry_after_s;if(typeof s!="number"||!Number.isFinite(s)||s<=0)throw new Error(a.message);const f=K-d;if(s>f){const u=Date.now()+s*1e3;throw new v(`${a.message} (retry after ${s}s, at ${new Date(u).toISOString()})`,u,O(a))}await F(s*1e3,void 0,{signal:o.signal}),d+=s;continue}if(!p.body)throw new Error("llm returned no stream");r.push({type:"start",partial:n});const B=p.body.getReader(),b=new TextDecoder;let h="",_="stop",E,S=!1,I=!1;const A=l(a=>{const s=JSON.parse(a);if(s.error){const i=N(s);throw R(n,D(i)),new Error(i.message)}if(s.served_model){if(I=!0,s.served_model.indexOf("/")<1)throw new Error("llm returned an invalid served_model");R(n,s.served_model)}const f=s.choices?.[0],u=f?.delta;if(u?.reasoning_content){const i=n.content.findIndex(x=>x.type==="thinking"),m=i<0?{type:"thinking",thinking:""}:n.content[i],g=i<0?n.content.push(m)-1:i;if(i<0&&r.push({type:"thinking_start",contentIndex:g,partial:n}),m.type!=="thinking")throw new Error("invalid thinking block");m.thinking+=u.reasoning_content,r.push({type:"thinking_delta",contentIndex:g,delta:u.reasoning_content,partial:n})}if(u?.content){const i=n.content.findIndex(x=>x.type==="text"),m=i<0?{type:"text",text:""}:n.content[i],g=i<0?n.content.push(m)-1:i;if(i<0&&r.push({type:"text_start",contentIndex:g,partial:n}),m.type!=="text")throw new Error("invalid text block");m.text+=u.content,r.push({type:"text_delta",contentIndex:g,delta:u.content,partial:n})}for(const i of u?.tool_calls??[]){const m={type:"toolCall",id:i.id,name:i.function.name,arguments:JSON.parse(i.function.arguments)},g=n.content.push(m)-1;r.push({type:"toolcall_start",contentIndex:g,partial:n}),r.push({type:"toolcall_end",contentIndex:g,toolCall:m,partial:n})}f?.finish_reason&&(_=f.finish_reason),f?.message?.refusal&&(E=f.message.refusal),s.usage&&(S=!0,n.usage={input:s.usage.prompt_tokens,output:s.usage.completion_tokens,cacheRead:s.usage.cache_read_tokens,cacheWrite:s.usage.cache_write_tokens,totalTokens:s.usage.total_tokens,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}})},"apply");for(;;){const{value:a,done:s}=await B.read();if(s)break;h+=b.decode(a,{stream:!0});let f;for(;(f=h.indexOf(`
2
- `))>=0;){const u=h.slice(0,f).trim();h=h.slice(f+1),u&&A(u)}}if(h+=b.decode(),h.trim()&&A(h.trim()),!S||!I)throw new Error("llm stream ended without usage or served_model");for(const[a,s]of n.content.entries())s.type==="text"&&r.push({type:"text_end",contentIndex:a,content:s.text,partial:n}),s.type==="thinking"&&r.push({type:"thinking_end",contentIndex:a,content:s.thinking,partial:n});if(_==="content_filter")throw new M(E??"Content filtered");if(!["stop","tool_calls","length"].includes(_))throw new Error(`llm returned unknown finish reason: ${_}`);n.stopReason=_==="tool_calls"?"toolUse":_==="length"?"length":"stop",r.push({type:"done",reason:n.stopReason,message:n}),r.end();return}})().catch(c=>{n.stopReason=o.signal?.aborted?"aborted":"error",n.errorMessage=c instanceof Error?c.message:String(c),c instanceof v&&(n.diagnostics=[{type:$,timestamp:Date.now(),...c.retryAt!==void 0?{details:{retryAt:c.retryAt}}:{}},...c.rateLimit!==void 0?[H({status:429,ratelimit:c.rateLimit})]:[]]),c instanceof M&&n.stopReason==="error"&&(n.rawStopReason="content_filter"),r.push({type:"error",reason:n.stopReason,error:n}),r.end()}),r}l(Z,"streamBrokerModel");export{K as LLM_BROKER_RETRY_WINDOW_S,$ as LLM_REFUSAL_DIAGNOSTIC,de as configureBrokerModelRuntime,pe as refusalRetryAt,V as requestBody,Z as streamBrokerModel,Q as wireHistory};
1
+ var F=Object.defineProperty;var d=(t,e)=>F(t,"name",{value:e,configurable:!0});import{setTimeout as P}from"node:timers/promises";import{createAssistantMessageEventStream as W,getCurrentSystemPrompt as j,getCurrentTools as J}from"@earendil-works/pi-ai";import{socketFetch as U}from"../../../api/node-transport.js";import{apiSocketPath as Y}from"../../canvas/paths.js";import{currentNodeToken as z}from"./node-token.js";import{envModelExact as L,envModelIntent as X}from"../../../shared/env.js";import{MODEL_CONTEXT_BODY_MAX_BYTES as k}from"../../llm/body-limits.js";import{modelRequestFromConfig as G}from"../../model-routes.js";import{parseProviderRateLimit as H,providerFailureDiagnostic as T}from"../../provider-failure.js";function de(t){t.checkAuth=async e=>t.getProvider(e)?{type:"api_key"}:void 0,t.getAvailable=async e=>t.getModels(e),t.getAvailableSnapshot=()=>t.getModels(),t.streamSimple=Z}d(de,"configureBrokerModelRuntime");const K=300,$="crtr:llm-refusal";class v extends Error{static{d(this,"LlmRefusalError")}retryAt;rateLimit;code;status;constructor(e,o,r,n,i){super(e),this.retryAt=o,this.rateLimit=r,this.code=n,this.status=i}}function O(t,e){const o=t===void 0?"the request body":`the request body (${t} bytes)`;return new Error(`request_too_large: ${o} exceeds the ${e}-byte model request limit; compact the conversation to shrink the context`)}d(O,"contextTooLargeError");function M(t){const e=t.details?.rate_limit;if(!e||typeof e!="object")return;const o=H(e);if(o===void 0)return;const{provider:r,model:n}=e;return typeof r=="string"&&r!==""&&typeof n=="string"&&n!==""?{...o,model:`${r}/${n}`}:o}d(M,"refusalRateLimit");function ue(t){for(const e of t.diagnostics??[]){if(e.type!==$)continue;const o=e.details?.retryAt;if(typeof o=="number"&&Number.isFinite(o)&&o>0)return o}}d(ue,"refusalRetryAt");class C extends Error{static{d(this,"ContentFilteredError")}}function N(t){return typeof t=="string"?t:t.map(e=>e.type==="text"?{type:"text",text:e.text}:{type:"image_url",image_url:{url:`data:${e.mimeType};base64,${e.data}`}})}d(N,"wireContent");function Q(t){const e=d(n=>n.role==="assistant"&&(n.stopReason==="error"||n.stopReason==="aborted"),"incomplete"),o=t.some(e)?t.filter(n=>!e(n)):t;let r=o.length;for(;r>0&&o[r-1]?.role==="assistant";)r--;return r===o.length?o:o.slice(0,r)}d(Q,"wireHistory");function V(t,e,o){const r=[],n=j(e.messages),i=J(e.messages);n?r.push({role:"system",content:n}):e.systemPrompt&&r.push({role:"system",content:e.systemPrompt});for(const u of Q(e.messages))u.role==="user"?r.push({role:"user",content:N(u.content)}):u.role==="toolResult"?r.push({role:"tool",content:N(u.content),tool_call_id:u.toolCallId}):u.role==="assistant"&&r.push({role:"assistant",content:u.content.filter(c=>c.type==="text").map(c=>c.text).join("")||null,tool_calls:u.content.filter(c=>c.type==="toolCall").map(c=>({id:c.id,type:"function",function:{name:c.name,arguments:JSON.stringify(c.arguments)}}))});const w=`${t.provider}/${t.id}`,y=X();return{model:G(w,y,L())?.kind==="logical"?y:w,model_policy:L()?"exact":"substitute",stream:!0,messages:r,...i.length?{tools:i.map(u=>({type:"function",function:{name:u.name,description:u.description,parameters:u.parameters}}))}:{},...o.reasoning?{reasoning_effort:o.reasoning}:{},...o.temperature!==void 0?{temperature:o.temperature}:{},...o.maxTokens!==void 0?{max_tokens:o.maxTokens}:{}}}d(V,"requestBody");function b(t,e){if(typeof e!="string")return!1;const o=e.indexOf("/");return o<1||o===e.length-1?!1:(t.provider=e.slice(0,o),t.model=e.slice(o+1),!0)}d(b,"applyServedModel");function q(t){if(t.details?.served_model!==void 0)return t.details.served_model;const e=t.details?.rate_limit;return typeof e?.provider=="string"&&typeof e.model=="string"?`${e.provider}/${e.model}`:void 0}d(q,"refusalRoute");function D(t){if(!t||typeof t!="object"||!("error"in t))throw new Error("llm returned an invalid response");const e=t.error;if(!e||typeof e.message!="string")throw new Error("llm returned an invalid refusal");return e}d(D,"parseRefusal");function Z(t,e,o={}){const r=W(),n={role:"assistant",content:[],api:t.api,provider:t.provider,model:t.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"pending",timestamp:Date.now()};return(async()=>{const i=z();if(!i)throw new Error("llm requires a node token");const w=JSON.stringify(V(t,e,o)),y=Buffer.byteLength(w);if(y>k)throw O(y,k);let u=0;for(;;){o.signal?.throwIfAborted();const c=await U(Y())("http://localhost/v1/llm",{method:"POST",headers:{authorization:`Bearer ${i}`,"content-type":"application/json"},body:w,signal:o.signal});if(!c.ok){const l=D(await c.json());if(c.status===413||l.code==="payload_too_large")throw O(y,k);if(b(n,q(l)),l.retryable!==!0)throw new v(l.message,void 0,M(l),l.code,c.status);const s=l.retry_after_s;if(typeof s!="number"||!Number.isFinite(s)||s<=0)throw new Error(l.message);const f=K-u;if(s>f){const p=Date.now()+s*1e3;throw new v(`${l.message} (retry after ${s}s, at ${new Date(p).toISOString()})`,p,M(l))}await P(s*1e3,void 0,{signal:o.signal}),u+=s;continue}if(!c.body)throw new Error("llm returned no stream");r.push({type:"start",partial:n});const B=c.body.getReader(),R=new TextDecoder;let h="",_="stop",E,S=!1,I=!1;const A=d(l=>{const s=JSON.parse(l);if(s.error){const a=D(s);throw b(n,q(a)),new Error(a.message)}if(s.served_model){if(I=!0,s.served_model.indexOf("/")<1)throw new Error("llm returned an invalid served_model");b(n,s.served_model)}const f=s.choices?.[0],p=f?.delta;if(p?.reasoning_content){const a=n.content.findIndex(x=>x.type==="thinking"),m=a<0?{type:"thinking",thinking:""}:n.content[a],g=a<0?n.content.push(m)-1:a;if(a<0&&r.push({type:"thinking_start",contentIndex:g,partial:n}),m.type!=="thinking")throw new Error("invalid thinking block");m.thinking+=p.reasoning_content,r.push({type:"thinking_delta",contentIndex:g,delta:p.reasoning_content,partial:n})}if(p?.content){const a=n.content.findIndex(x=>x.type==="text"),m=a<0?{type:"text",text:""}:n.content[a],g=a<0?n.content.push(m)-1:a;if(a<0&&r.push({type:"text_start",contentIndex:g,partial:n}),m.type!=="text")throw new Error("invalid text block");m.text+=p.content,r.push({type:"text_delta",contentIndex:g,delta:p.content,partial:n})}for(const a of p?.tool_calls??[]){const m={type:"toolCall",id:a.id,name:a.function.name,arguments:JSON.parse(a.function.arguments)},g=n.content.push(m)-1;r.push({type:"toolcall_start",contentIndex:g,partial:n}),r.push({type:"toolcall_end",contentIndex:g,toolCall:m,partial:n})}f?.finish_reason&&(_=f.finish_reason),f?.message?.refusal&&(E=f.message.refusal),s.usage&&(S=!0,n.usage={input:s.usage.prompt_tokens,output:s.usage.completion_tokens,cacheRead:s.usage.cache_read_tokens,cacheWrite:s.usage.cache_write_tokens,totalTokens:s.usage.total_tokens,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:typeof s.usage.usd_equivalent=="number"&&Number.isFinite(s.usage.usd_equivalent)?s.usage.usd_equivalent:0}})},"apply");for(;;){const{value:l,done:s}=await B.read();if(s)break;h+=R.decode(l,{stream:!0});let f;for(;(f=h.indexOf(`
2
+ `))>=0;){const p=h.slice(0,f).trim();h=h.slice(f+1),p&&A(p)}}if(h+=R.decode(),h.trim()&&A(h.trim()),!S||!I)throw new Error("llm stream ended without usage or served_model");for(const[l,s]of n.content.entries())s.type==="text"&&r.push({type:"text_end",contentIndex:l,content:s.text,partial:n}),s.type==="thinking"&&r.push({type:"thinking_end",contentIndex:l,content:s.thinking,partial:n});if(_==="content_filter")throw new C(E??"Content filtered");if(!["stop","tool_calls","length"].includes(_))throw new Error(`llm returned unknown finish reason: ${_}`);n.stopReason=_==="tool_calls"?"toolUse":_==="length"?"length":"stop",r.push({type:"done",reason:n.stopReason,message:n}),r.end();return}})().catch(i=>{n.stopReason=o.signal?.aborted?"aborted":"error",n.errorMessage=i instanceof Error?i.message:String(i),i instanceof v&&(n.diagnostics=[{type:$,timestamp:Date.now(),...i.retryAt!==void 0?{details:{retryAt:i.retryAt}}:{}},...i.rateLimit!==void 0?[T({status:429,ratelimit:i.rateLimit})]:[],...i.rateLimit===void 0&&i.code==="cap_exhausted"?[T({status:i.status??402,code:i.code})]:[]]),i instanceof C&&n.stopReason==="error"&&(n.rawStopReason="content_filter"),r.push({type:"error",reason:n.stopReason,error:n}),r.end()}),r}d(Z,"streamBrokerModel");export{K as LLM_BROKER_RETRY_WINDOW_S,$ as LLM_REFUSAL_DIAGNOSTIC,de as configureBrokerModelRuntime,ue as refusalRetryAt,V as requestBody,Z as streamBrokerModel,Q as wireHistory};
@@ -28,6 +28,12 @@ export declare function runAppCommand(options: RunCommandOnceOptions & {
28
28
  nodeId?: string;
29
29
  app: string;
30
30
  isolated?: boolean;
31
+ principal?: {
32
+ sub: string;
33
+ grantId: string | null;
34
+ scopes: string[] | null;
35
+ };
36
+ folder?: string;
31
37
  }): Promise<RunningCommand>;
32
38
  /** Start one admitted-PATH bash command in its own process group. The returned
33
39
  * handle exposes the pid immediately for callers that must record ownership
@@ -1 +1 @@
1
- var H=Object.defineProperty;var a=(e,s)=>H(e,"name",{value:s,configurable:!0});import{spawn as b}from"node:child_process";import{createHash as k}from"node:crypto";import{captureTeardownSnapshot as R,isAnyPidAlive as x,killProcessTreePids as I}from"../canvas/pid.js";import{CRON_DEATH_POLL_MS as B,CRON_KILL_GRACE_MS as G,newHeadCapture as L}from"../../daemon/cron/capture.js";import{resolveAdmittedHostCommand as M}from"./spawn-env.js";import{appProcessSandboxed as K,launchAppProcess as N}from"../spaces/process.js";async function V(e){if(!K()){if(e.isolated)throw new Error("isolated cron requires the root launcher");return q(e)}const s=Date.now(),n=await N({nodeId:e.nodeId,app:e.app,isolated:e.isolated,command:[M("bash",e.env,e.cwd),"-c",e.command],cwd:e.cwd,env:e.env,stdoutBytes:e.headBytes,stderrBytes:e.headBytes});let f=!1,o;const p=a(()=>{n.stop().catch(t=>{console.error(`app command ${n.pid} could not be stopped:`,t)})},"terminate");o=setTimeout(()=>{f=!0,p()},e.timeoutMs),o.unref?.();const i=n.completion.then(t=>({exitCode:t.exitCode,signal:t.signal,stdout:t.stdout,stderr:t.stderr,stdoutTruncated:t.stdoutTruncated,stderrTruncated:t.stderrTruncated,stdoutSha256:t.stdoutSha256,timedOut:f,durationMs:Date.now()-s,processError:null})).finally(()=>{o&&clearTimeout(o)});return{pid:n.pid,completion:i,terminate:p}}a(V,"runAppCommand");function q({command:e,cwd:s,env:n,timeoutMs:f,headBytes:o}){const p=Date.now(),i=b(M("bash",n,s),["-c",e],{cwd:s,env:n,detached:!0,stdio:["ignore","pipe","pipe"]}),t=i.pid??null,h=L(o),T=L(o),P=k("sha256");i.stdout?.on("data",d=>{P.update(d),h.take(d)}),i.stderr?.on("data",d=>T.take(d));let g=!1,w=null,l,v=null,c=!1,y=!1,u,m,C,r,D;const O=new Promise(d=>{D=d}),S=a(()=>{c||l===void 0||(c=!0,u!==void 0&&clearTimeout(u),m!==void 0&&clearTimeout(m),C!==void 0&&clearTimeout(C),D({exitCode:l,signal:v,stdout:h.decode(),stdoutTruncated:h.truncated(),stderr:T.decode(),stderrTruncated:T.truncated(),timedOut:g,durationMs:Date.now()-p,stdoutSha256:P.digest("hex"),processError:w}))},"finish"),A=a(()=>{if(r===void 0||!x(r.tree)){S();return}C=setTimeout(A,B)},"awaitTeardownDeath"),E=a(()=>{t===null||c||(r??=R(t,null),I(r.tree,"SIGTERM",r.identities),m===void 0&&(m=setTimeout(()=>{x(r.tree)&&I(r.tree,"SIGKILL",r.identities),y=!0,l!==void 0&&A()},G),m.unref?.()))},"terminate");return i.on("error",d=>{w===null&&(w=d),t===null&&(l=null,S())}),i.on("close",(d,_)=>{if(!c){if(l=d,v=_,u!==void 0&&clearTimeout(u),r!==void 0&&x(r.tree)){y&&A();return}S()}}),t!==null&&(u=setTimeout(()=>{c||(g=!0,E())},f),u.unref?.()),{pid:t,completion:O,terminate:E}}a(q,"runCommandOnce");export{V as runAppCommand,q as runCommandOnce};
1
+ var H=Object.defineProperty;var a=(e,u)=>H(e,"name",{value:u,configurable:!0});import{spawn as b}from"node:child_process";import{createHash as k}from"node:crypto";import{captureTeardownSnapshot as R,isAnyPidAlive as x,killProcessTreePids as I}from"../canvas/pid.js";import{CRON_DEATH_POLL_MS as B,CRON_KILL_GRACE_MS as G,newHeadCapture as L}from"../../daemon/cron/capture.js";import{resolveAdmittedHostCommand as M}from"./spawn-env.js";import{appProcessSandboxed as K,launchAppProcess as N}from"../spaces/process.js";async function V(e){if(!K()){if(e.isolated)throw new Error("isolated cron requires the root launcher");return q(e)}const u=Date.now(),n=await N({nodeId:e.nodeId,app:e.app,isolated:e.isolated,...e.principal===void 0?{}:{principal:e.principal},...e.folder===void 0?{}:{folder:e.folder},command:[M("bash",e.env,e.cwd),"-c",e.command],cwd:e.cwd,env:e.env,stdoutBytes:e.headBytes,stderrBytes:e.headBytes});let m=!1,i;const p=a(()=>{n.stop().catch(t=>{console.error(`app command ${n.pid} could not be stopped:`,t)})},"terminate");i=setTimeout(()=>{m=!0,p()},e.timeoutMs),i.unref?.();const o=n.completion.then(t=>({exitCode:t.exitCode,signal:t.signal,stdout:t.stdout,stderr:t.stderr,stdoutTruncated:t.stdoutTruncated,stderrTruncated:t.stderrTruncated,stdoutSha256:t.stdoutSha256,timedOut:m,durationMs:Date.now()-u,processError:null})).finally(()=>{i&&clearTimeout(i)});return{pid:n.pid,completion:o,terminate:p}}a(V,"runAppCommand");function q({command:e,cwd:u,env:n,timeoutMs:m,headBytes:i}){const p=Date.now(),o=b(M("bash",n,u),["-c",e],{cwd:u,env:n,detached:!0,stdio:["ignore","pipe","pipe"]}),t=o.pid??null,h=L(i),T=L(i),P=k("sha256");o.stdout?.on("data",d=>{P.update(d),h.take(d)}),o.stderr?.on("data",d=>T.take(d));let g=!1,w=null,s,v=null,c=!1,y=!1,l,f,C,r,D;const O=new Promise(d=>{D=d}),S=a(()=>{c||s===void 0||(c=!0,l!==void 0&&clearTimeout(l),f!==void 0&&clearTimeout(f),C!==void 0&&clearTimeout(C),D({exitCode:s,signal:v,stdout:h.decode(),stdoutTruncated:h.truncated(),stderr:T.decode(),stderrTruncated:T.truncated(),timedOut:g,durationMs:Date.now()-p,stdoutSha256:P.digest("hex"),processError:w}))},"finish"),A=a(()=>{if(r===void 0||!x(r.tree)){S();return}C=setTimeout(A,B)},"awaitTeardownDeath"),E=a(()=>{t===null||c||(r??=R(t,null),I(r.tree,"SIGTERM",r.identities),f===void 0&&(f=setTimeout(()=>{x(r.tree)&&I(r.tree,"SIGKILL",r.identities),y=!0,s!==void 0&&A()},G),f.unref?.()))},"terminate");return o.on("error",d=>{w===null&&(w=d),t===null&&(s=null,S())}),o.on("close",(d,_)=>{if(!c){if(s=d,v=_,l!==void 0&&clearTimeout(l),r!==void 0&&x(r.tree)){y&&A();return}S()}}),t!==null&&(l=setTimeout(()=>{c||(g=!0,E())},m),l.unref?.()),{pid:t,completion:O,terminate:E}}a(q,"runCommandOnce");export{V as runAppCommand,q as runCommandOnce};
@@ -12,6 +12,8 @@ export interface LaunchNode {
12
12
  withoutNode?: boolean;
13
13
  /** A node in an isolated run has no own working-files mount or network. */
14
14
  isolated?: boolean;
15
+ /** The folder of a process that has no node but owns one (an isolated cron's `<app space>/crons/<id>`): it takes the place of the node agent folder. */
16
+ folder?: string;
15
17
  cwd?: string;
16
18
  /** Hook code alone may be mounted from the installed plugin root, read-only. */
17
19
  pluginRoot?: string;
@@ -1 +1 @@
1
- var y=Object.defineProperty;var l=(t,r)=>y(t,"name",{value:r,configurable:!0});import{lstatSync as D}from"node:fs";import{join as u,relative as f,sep as c}from"node:path";import{appSpace as w,appsRoot as S,runtimeRoot as h,socketDir as d,userFilesRoot as R}from"../layout.js";import{nodeAgentDir as I,nodeRunDir as k}from"../canvas/paths.js";import{uidForApp as A}from"./users.js";import{permissionsForRun as E}from"./permissions.js";function x(t){const r=[];for(const e of t.otherFiles){const i=e.holder==="user"?R():w(e.holder).files;r.push({source:e.path==="."?i:u(i,e.path),writable:e.writable})}for(const e of t.otherNodes)r.push({source:w(e).nodes,writable:!1});return r}l(x,"grantedMounts");function m(t,r){const e=f(t,r);return e===""||e!==".."&&!e.startsWith(`..${c}`)&&!e.startsWith(c)}l(m,"beneath");function M(t,r){return m(R(),r)||m(S(),r)&&!m(w(t).root,r)}l(M,"isGrantedSource");function j(t,r){return r.some(e=>m(e.source,t.source)&&(e.writable||!t.writable))}l(j,"mountAllowed");function z(t,r){try{const e=x(E(t.app,t.sub,t.grantId,t.scopes));return r.mounts.every(i=>!M(t.app,i.source)||j(i,e))}catch{return!1}}l(z,"launchStillGranted");function B(t){const r=w(t.app),e=E(t.app,t.sub,t.grantId,t.scopes);if(t.isolated&&t.withoutNode)throw new Error("isolated launch requires a node");const i=t.isolated?I(t.nodeId,t.app):void 0;if(i&&t.cwd!==void 0&&!m(i,t.cwd))throw new Error("isolated cwd must be inside the node folder");const p=new Map,s=l((o,n,a=!1,W=!1)=>{if(a)try{D(o)}catch(b){if(b.code!=="ENOENT")throw b;W&&t.auditMissing(o);return}p.set(o,n||(p.get(o)??!1))},"add");for(const o of["/usr","/bin","/lib","/lib64","/opt/crouter","/etc/passwd","/etc/group","/etc/resolv.conf","/etc/hosts","/etc/ssl","/etc/ca-certificates"])s(o,!1,!0);t.withoutNode||s(I(t.nodeId,t.app),!0),t.isolated||s(r.home,!0),t.isolated||s(r.files,!0),s(r.cache,!0,!0),t.withoutNode||s(k(t.nodeId),!0),s(u(d(),"api.sock"),!0),s(u(d(),"store.sock"),!0);for(const o of x(e))s(o.source,o.writable,!0,!0);if(t.pluginRoot!==void 0){const o=l(a=>t.pluginRoot===a||f(a,t.pluginRoot)!==".."&&!f(a,t.pluginRoot).startsWith(`..${c}`),"within"),n=u(h(),"plugins");if([...p].some(([a])=>o(a))){if(p.get(t.pluginRoot)===!0)throw new Error("hook root cannot also be writable");s(t.pluginRoot,!1)}else if(o(n)&&t.pluginRoot!==n)s(t.pluginRoot,!1);else if(o("/opt/crouter"))s(t.pluginRoot,!1);else throw new Error("hook root is not readable with this app\u2019s mounts")}const N=t.cwd??i??r.files,g=[...p].sort(([o],[n])=>o.split(c).length-n.split(c).length||o.localeCompare(n)),v=u(h(),"nodes",t.nodeId,"job","broker.log"),F=process.platform!=="linux"?[k(t.nodeId),u(d(),"api.sock"),u(d(),"store.sock")]:[];if(g.some(([o])=>o!==t.pluginRoot&&!F.includes(o)&&(o===h()||!f(h(),o).startsWith("..")&&!f(h(),o).startsWith(c))))throw new Error("runtime records cannot be mounted");return{nodeId:t.nodeId,uid:A(t.app),mounts:g.map(([o,n])=>({source:o,writable:n})),cwd:N,env:t.env,command:t.command,log:v,...t.isolated?{network:"none"}:{}}}l(B,"compileLaunch");export{B as compileLaunch,x as grantedMounts,M as isGrantedSource,z as launchStillGranted,j as mountAllowed};
1
+ var y=Object.defineProperty;var l=(t,r)=>y(t,"name",{value:r,configurable:!0});import{lstatSync as D}from"node:fs";import{join as a,relative as f,sep as c}from"node:path";import{appSpace as w,appsRoot as S,runtimeRoot as h,socketDir as d,userFilesRoot as R}from"../layout.js";import{nodeAgentDir as I,nodeRunDir as k}from"../canvas/paths.js";import{uidForApp as A}from"./users.js";import{permissionsForRun as E}from"./permissions.js";function x(t){const r=[];for(const e of t.otherFiles){const i=e.holder==="user"?R():w(e.holder).files;r.push({source:e.path==="."?i:a(i,e.path),writable:e.writable})}for(const e of t.otherNodes)r.push({source:w(e).nodes,writable:!1});return r}l(x,"grantedMounts");function m(t,r){const e=f(t,r);return e===""||e!==".."&&!e.startsWith(`..${c}`)&&!e.startsWith(c)}l(m,"beneath");function M(t,r){return m(R(),r)||m(S(),r)&&!m(w(t).root,r)}l(M,"isGrantedSource");function j(t,r){return r.some(e=>m(e.source,t.source)&&(e.writable||!t.writable))}l(j,"mountAllowed");function z(t,r){try{const e=x(E(t.app,t.sub,t.grantId,t.scopes));return r.mounts.every(i=>!M(t.app,i.source)||j(i,e))}catch{return!1}}l(z,"launchStillGranted");function B(t){const r=w(t.app),e=E(t.app,t.sub,t.grantId,t.scopes);if(t.isolated&&t.withoutNode&&t.folder===void 0)throw new Error("isolated launch requires a node");const i=t.folder??(t.isolated?I(t.nodeId,t.app):void 0);if(i&&t.cwd!==void 0&&!m(i,t.cwd))throw new Error("isolated cwd must be inside the node folder");const p=new Map,s=l((o,n,u=!1,W=!1)=>{if(u)try{D(o)}catch(b){if(b.code!=="ENOENT")throw b;W&&t.auditMissing(o);return}p.set(o,n||(p.get(o)??!1))},"add");for(const o of["/usr","/bin","/lib","/lib64","/opt/crouter","/etc/passwd","/etc/group","/etc/resolv.conf","/etc/hosts","/etc/ssl","/etc/ca-certificates"])s(o,!1,!0);t.withoutNode||s(I(t.nodeId,t.app),!0),t.folder!==void 0&&s(t.folder,!0),t.isolated||s(r.home,!0),t.isolated||s(r.files,!0),s(r.cache,!0,!0),t.withoutNode||s(k(t.nodeId),!0),s(a(d(),"api.sock"),!0),s(a(d(),"store.sock"),!0);for(const o of x(e))s(o.source,o.writable,!0,!0);if(t.pluginRoot!==void 0){const o=l(u=>t.pluginRoot===u||f(u,t.pluginRoot)!==".."&&!f(u,t.pluginRoot).startsWith(`..${c}`),"within"),n=a(h(),"plugins");if([...p].some(([u])=>o(u))){if(p.get(t.pluginRoot)===!0)throw new Error("hook root cannot also be writable");s(t.pluginRoot,!1)}else if(o(n)&&t.pluginRoot!==n)s(t.pluginRoot,!1);else if(o("/opt/crouter"))s(t.pluginRoot,!1);else throw new Error("hook root is not readable with this app\u2019s mounts")}const N=t.cwd??i??r.files,g=[...p].sort(([o],[n])=>o.split(c).length-n.split(c).length||o.localeCompare(n)),v=a(h(),"nodes",t.nodeId,"job","broker.log"),F=process.platform!=="linux"?[k(t.nodeId),a(d(),"api.sock"),a(d(),"store.sock")]:[];if(g.some(([o])=>o!==t.pluginRoot&&!F.includes(o)&&(o===h()||!f(h(),o).startsWith("..")&&!f(h(),o).startsWith(c))))throw new Error("runtime records cannot be mounted");return{nodeId:t.nodeId,uid:A(t.app),mounts:g.map(([o,n])=>({source:o,writable:n})),cwd:N,env:t.env,command:t.command,log:v,...t.isolated?{network:"none"}:{}}}l(B,"compileLaunch");export{B as compileLaunch,x as grantedMounts,M as isGrantedSource,z as launchStillGranted,j as mountAllowed};
@@ -9,6 +9,14 @@ export interface AppProcessOptions {
9
9
  /** For node-backed work the persisted node setting always takes precedence. */
10
10
  isolated?: boolean;
11
11
  app?: string;
12
+ /** The grant a process without a node runs under (an app cron's stored principal). Absent, it runs under the app's whole current grant. */
13
+ principal?: {
14
+ sub: string;
15
+ grantId: string | null;
16
+ scopes: string[] | null;
17
+ };
18
+ /** The folder an isolated process without a node runs in (an isolated cron's own folder). */
19
+ folder?: string;
12
20
  command: string[];
13
21
  cwd: string;
14
22
  env: NodeJS.ProcessEnv;
@@ -33,9 +41,13 @@ export declare function __setAppProcessesSandboxedForTest(value: boolean | undef
33
41
  * `app`. This is the launcher's own `cwd is not mounted` rule applied to the mounts
34
42
  * `compileLaunch` produces, so a caller can refuse a directory at arm time instead of
35
43
  * at every run. `nodeId` is the node the process runs for, when it has one. */
36
- export declare function cwdIsMounted(app: string, cwd: string, nodeId?: string, isolated?: boolean): boolean;
44
+ export declare function cwdIsMounted(app: string, cwd: string, nodeId?: string, isolated?: boolean, folder?: string, principal?: {
45
+ sub: string;
46
+ grantId: string | null;
47
+ scopes: string[] | null;
48
+ }): boolean;
37
49
  export declare function stopAppProcess(pid: number, signal?: 'SIGTERM' | 'SIGKILL', nodeId?: string): Promise<void>;
38
50
  /** A node-backed isolated process always starts in its own agent folder.
39
51
  * Callers validate explicitly requested cwd separately (e.g. hook execution). */
40
- export declare function appProcessCwd(nodeId: string, app: string, cwd: string, isolated: boolean): string;
52
+ export declare function appProcessCwd(nodeId: string, app: string, cwd: string, isolated: boolean, folder?: string): string;
41
53
  export declare function launchAppProcess(options: AppProcessOptions): Promise<AppProcess>;
@@ -1 +1 @@
1
- var L=Object.defineProperty;var d=(e,n)=>L(e,"name",{value:n,configurable:!0});import{existsSync as I}from"node:fs";import{randomUUID as T}from"node:crypto";import{join as E}from"node:path";import{getNode as b}from"../canvas/canvas.js";import{nodeAgentDir as C,nodeHome as N,nodeRunDir as D}from"../canvas/paths.js";import{appSpace as O}from"../layout.js";import{activeGrant as $,activeGrantForApp as x,grantFor as k,grantsGeneration as v}from"../grants/mirror.js";import{emitEvent as _}from"../events/emit.js";import{ensureAppSpace as K}from"./space.js";import{compileLaunch as R,launchStillGranted as B}from"./mounts.js";import{pinSandboxRuntimeHome as F}from"./runtime-home.js";import{sandboxCacheEnv as H}from"./cache-env.js";import{launcherRequest as f}from"../../launcher/client.js";import{LAUNCHER_SOCKET as S}from"../../launcher/spec.js";function oe(){if(process.platform!=="linux")return!1;if(I(S))return!0;throw new Error("root launcher socket is unavailable; refusing an unsandboxed app process")}d(oe,"appProcessSandboxed");function de(){return y??(process.platform==="linux"&&I(S))}d(de,"appProcessesSandboxed");let y;function ie(e){y=e}d(ie,"__setAppProcessesSandboxedForTest");function ae(e,n,r,i=!1){const t=r===void 0?null:b(r),a=t?null:x(e);if(!t&&!a)throw new Error(`no active grant for ${e}`);return R({nodeId:r??"proc-cwd-check",app:e,sub:t?t.sub??"user:local":a.sub,grantId:t?t.grant_id??null:a.id,scopes:t?.scopes??null,command:["true"],env:{},...r===void 0?{withoutNode:!0}:{},isolated:t?.isolated===!0||i,auditMissing:d(()=>{},"auditMissing")}).mounts.some(({source:s})=>n===s||n.startsWith(s==="/"?"/":`${s}/`))}d(ae,"cwdIsMounted");async function A(e,n="SIGKILL",r){if(!("ok"in await f({operation:"stop",pid:e,signal:n,...r===void 0?{}:{nodeId:r}})))throw new Error("launcher did not stop process")}d(A,"stopAppProcess");function M(e,n,r,i){return i?C(e,n):r}d(M,"appProcessCwd");async function se(e){const n=e.nodeId===void 0?null:b(e.nodeId);if(e.nodeId!==void 0&&!n)throw new Error(`missing node ${e.nodeId}`);const r=n?.grantee??e.app??"app:terminal";if(e.app!==void 0&&e.app!==r)throw new Error("app does not match node");const i=n?null:x(r);if(!n&&!i)throw new Error(`no active grant for ${r}`);if(n&&r!=="app:terminal"&&!(n.grant_id?$(n.grant_id):k(r,n.sub??"user:local")))throw new Error(`no active grant for ${r}`);await K(r);const t=e.nodeId??`proc-${T()}`,a=n?.isolated===!0||e.isolated===!0;if(a&&!n)throw new Error("isolated process requires a live node");const m=O(r),s=M(t,r,e.cwd,a),w=n?N(t,r,a):m.home,G=F(Object.fromEntries(Object.entries({...e.env,...process.platform==="linux"?H(r):{},HOME:w,PI_CODING_AGENT_DIR:E(w,".pi","agent"),PWD:s,...e.nodeId===void 0?{}:{CRTR_API_SOCKET:E(D(t),"cli.sock")}}).filter(o=>o[1]!==void 0))),h={nodeId:t,app:r,sub:n?n.sub??"user:local":i.sub,grantId:n?n.grant_id??null:i.id,scopes:n?.scopes??null},P=d(()=>{const o=R({...h,command:e.command,env:G,...e.nodeId===void 0?{withoutNode:!0}:{},isolated:a,cwd:s,...e.pluginRoot===void 0?{}:{pluginRoot:e.pluginRoot},auditMissing:d(p=>_({level:"warn",event:"process.mount.missing",...e.nodeId===void 0?{}:{node_id:e.nodeId},fields:{source:p}}),"auditMissing")});if(e.pluginRoot!==void 0&&(o.pluginRoot=e.pluginRoot),e.capture===!1){if(e.nodeId===void 0||e.stdin!==void 0)throw new Error("uncaptured process requires a node and no stdin")}else o.capture={stdoutBytes:e.stdoutBytes??256*1024,stderrBytes:e.stderrBytes??256*1024,...e.stderrTail?{stderrTail:!0}:{},...e.stdin===void 0?{}:{stdin:e.stdin}};return o},"compile");let u;for(let o=0;o<2&&u===void 0;o++){const p=v(),g=P(),c=await f({operation:"launch",spec:g});if(!("pid"in c))throw new Error("launcher did not return a process id");if(v()===p||B(h,g)){u=c.pid;break}_({level:"info",event:"process.launch.regranted",...e.nodeId===void 0?{}:{node_id:e.nodeId},fields:{pid:c.pid}}),await A(c.pid,"SIGKILL").catch(()=>{})}if(u===void 0)throw new Error("grant changed during every launch attempt");const l=u;return{pid:l,get completion(){return f({operation:"wait",pid:l}).then(o=>{if(!("result"in o))throw new Error("launcher did not return process completion");return o.result})},stop:d((o="SIGKILL")=>A(l,o,e.nodeId),"stop")}}d(se,"launchAppProcess");export{ie as __setAppProcessesSandboxedForTest,M as appProcessCwd,oe as appProcessSandboxed,de as appProcessesSandboxed,ae as cwdIsMounted,se as launchAppProcess,A as stopAppProcess};
1
+ var N=Object.defineProperty;var i=(e,r)=>N(e,"name",{value:r,configurable:!0});import{existsSync as x}from"node:fs";import{randomUUID as $}from"node:crypto";import{join as w}from"node:path";import{getNode as _}from"../canvas/canvas.js";import{nodeAgentDir as D,nodeHome as O,nodeRunDir as k}from"../canvas/paths.js";import{appSpace as K}from"../layout.js";import{activeGrant as h,activeGrantForApp as R,grantFor as I,grantsGeneration as S}from"../grants/mirror.js";import{readConfig as B}from"../config.js";import{emitEvent as T}from"../events/emit.js";import{ensureAppSpace as F}from"./space.js";import{compileLaunch as y,launchStillGranted as H}from"./mounts.js";import{pinSandboxRuntimeHome as M}from"./runtime-home.js";import{sandboxCacheEnv as j}from"./cache-env.js";import{launcherRequest as b}from"../../launcher/client.js";import{LAUNCHER_SOCKET as A}from"../../launcher/spec.js";function ue(){if(process.platform!=="linux")return!1;if(x(A))return!0;throw new Error("root launcher socket is unavailable; refusing an unsandboxed app process")}i(ue,"appProcessSandboxed");function ie(){return G??(process.platform==="linux"&&x(A))}i(ie,"appProcessesSandboxed");let G;function ae(e){G=e}i(ae,"__setAppProcessesSandboxedForTest");function ce(e,r,n,o=!1,t,d){const s=n===void 0?null:_(n),a=d!==void 0?d.grantId?h(d.grantId):I(e,d.sub):s?null:R(e);if(d!==void 0?!a&&!(d.grantId===null&&e==="app:terminal"):!s&&!a)throw new Error(`no active grant for ${e}`);return y({nodeId:n??"proc-cwd-check",app:e,...d!==void 0?{sub:d.sub,grantId:a?.id??null,scopes:d.scopes}:{sub:s?s.sub??"user:local":a.sub,grantId:s?s.grant_id??null:a.id,scopes:s?.scopes??null},command:["true"],env:{},...n===void 0?{withoutNode:!0}:{},...t===void 0?{}:{folder:t},isolated:s?.isolated===!0||o,auditMissing:i(()=>{},"auditMissing")}).mounts.some(({source:c})=>r===c||r.startsWith(c==="/"?"/":`${c}/`))}i(ce,"cwdIsMounted");async function P(e,r="SIGKILL",n){if(!("ok"in await b({operation:"stop",pid:e,signal:r,...n===void 0?{}:{nodeId:n}})))throw new Error("launcher did not stop process")}i(P,"stopAppProcess");function q(e,r,n,o,t){return o?t??D(e,r):n}i(q,"appProcessCwd");async function le(e){const r=e.nodeId===void 0?null:_(e.nodeId);if(e.nodeId!==void 0&&!r)throw new Error(`missing node ${e.nodeId}`);const n=r?.grantee??e.app??"app:terminal";if(e.app!==void 0&&e.app!==n)throw new Error("app does not match node");const o=e.principal;let t=null;if(o!==void 0){if(t=o.grantId?h(o.grantId):I(n,o.sub),!(t===null&&o.grantId===null&&n==="app:terminal"&&!B("user").runtime?.issuer)&&(!t||t.grantee!==n||t.sub!==o.sub))throw new Error(`no active grant for ${n}`)}else if(r){if(n!=="app:terminal"&&!(r.grant_id?h(r.grant_id):I(n,r.sub??"user:local")))throw new Error(`no active grant for ${n}`)}else if(t=R(n),!t)throw new Error(`no active grant for ${n}`);await F(n);const d=e.nodeId??`proc-${$()}`,s=r?.isolated===!0||e.isolated===!0;if(s&&!r&&e.folder===void 0)throw new Error("isolated process requires a live node");const a=K(n),m=q(d,n,e.cwd,s,e.folder),c=r?O(d,n,s):s&&e.folder!==void 0?w(e.folder,"home"):a.home,C=M(Object.fromEntries(Object.entries({...e.env,...process.platform==="linux"?j(n):{},HOME:c,PI_CODING_AGENT_DIR:w(c,".pi","agent"),PWD:m,...e.nodeId===void 0?{}:{CRTR_API_SOCKET:w(k(d),"cli.sock")}}).filter(u=>u[1]!==void 0))),E=o!==void 0?{nodeId:d,app:n,sub:o.sub,grantId:t?.id??null,scopes:o.scopes}:{nodeId:d,app:n,sub:r?r.sub??"user:local":t.sub,grantId:r?r.grant_id??null:t.id,scopes:r?.scopes??null},L=i(()=>{const u=y({...E,command:e.command,env:C,...e.nodeId===void 0?{withoutNode:!0}:{},isolated:s,cwd:m,...e.folder===void 0?{}:{folder:e.folder},...e.pluginRoot===void 0?{}:{pluginRoot:e.pluginRoot},auditMissing:i(g=>T({level:"warn",event:"process.mount.missing",...e.nodeId===void 0?{}:{node_id:e.nodeId},fields:{source:g}}),"auditMissing")});if(e.pluginRoot!==void 0&&(u.pluginRoot=e.pluginRoot),e.capture===!1){if(e.nodeId===void 0||e.stdin!==void 0)throw new Error("uncaptured process requires a node and no stdin")}else u.capture={stdoutBytes:e.stdoutBytes??256*1024,stderrBytes:e.stderrBytes??256*1024,...e.stderrTail?{stderrTail:!0}:{},...e.stdin===void 0?{}:{stdin:e.stdin}};return u},"compile");let l;for(let u=0;u<2&&l===void 0;u++){const g=S(),v=L(),f=await b({operation:"launch",spec:v});if(!("pid"in f))throw new Error("launcher did not return a process id");if(S()===g||H(E,v)){l=f.pid;break}T({level:"info",event:"process.launch.regranted",...e.nodeId===void 0?{}:{node_id:e.nodeId},fields:{pid:f.pid}}),await P(f.pid,"SIGKILL").catch(()=>{})}if(l===void 0)throw new Error("grant changed during every launch attempt");const p=l;return{pid:p,get completion(){return b({operation:"wait",pid:p}).then(u=>{if(!("result"in u))throw new Error("launcher did not return process completion");return u.result})},stop:i((u="SIGKILL")=>P(p,u,e.nodeId),"stop")}}i(le,"launchAppProcess");export{ae as __setAppProcessesSandboxedForTest,q as appProcessCwd,ue as appProcessSandboxed,ie as appProcessesSandboxed,ce as cwdIsMounted,le as launchAppProcess,P as stopAppProcess};
@@ -0,0 +1,49 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { SpendUsageDTO } from '../../api/dto/usage.js';
3
+ /** The person's own agent. Its grant carries the directory's default cap, but the
4
+ * cap bounds what an app spends of the person's subscription, never the person's
5
+ * own work, so `app:terminal` is never stopped. */
6
+ export declare const UNCAPPED_GRANTEE = "app:terminal";
7
+ export type Payer = 'user' | 'app';
8
+ /** The UTC calendar month `YYYY-MM` of `at`. */
9
+ export declare function usageMonth(at?: Date): string;
10
+ /** 00:00Z on the first day of the UTC month after `month`. */
11
+ export declare function resetAt(month: string): string;
12
+ export interface UsageRecord {
13
+ grantee: string;
14
+ payer: Payer;
15
+ /** `model` for a completion, `provider:<id>` for a provider receipt. */
16
+ source: string;
17
+ /** The model that answered; `''` for a provider receipt. */
18
+ served_model: string;
19
+ input_tokens: number;
20
+ output_tokens: number;
21
+ cached_tokens: number;
22
+ /** List-price cost; null when the served model has no list price (it adds nothing to the total). */
23
+ usd_equivalent: number | null;
24
+ }
25
+ /** Add one completion's or receipt's usage to its grantee's month, inside the caller's write. */
26
+ export declare function addUsage(db: DatabaseSync, record: UsageRecord, at?: Date): void;
27
+ /** Record one model completion for `grantee`. */
28
+ export declare function recordModelUsage(grantee: string, payer: Payer, servedModel: string, usage: {
29
+ input: number;
30
+ output: number;
31
+ cacheRead: number;
32
+ }, usdEquivalent: number | null): void;
33
+ /** The grantee's user-paid month-to-date spend, receipts included. */
34
+ export declare function monthSpend(grantee: string, month?: string): number;
35
+ /** Who a user-paid call bills, and the cap that bounds it (`null` = uncapped). */
36
+ export interface SpendTarget {
37
+ grantee: string;
38
+ spend_cap: number | null;
39
+ }
40
+ /** The cap that applies to `target`: none for the person's own agent or a grant with no cap. */
41
+ export declare function effectiveCap(target: SpendTarget): number | null;
42
+ /** Refuse a user-paid call once the grantee's month spend reached its cap (SPD-2).
43
+ * The check runs before the call; one response may overshoot the cap (no
44
+ * reserve-then-settle), and the next call is refused. */
45
+ export declare function assertUnderCap(target: SpendTarget | undefined, at?: Date): void;
46
+ /** `usage.get` for one grantee and month. */
47
+ export declare function usageFor(grantee: string, spendCap: number | null, month?: string): SpendUsageDTO;
48
+ /** A `month` query value: `YYYY-MM` with a real month, years 1000-9999; absent means the current month. */
49
+ export declare function parseUsageMonth(value: string | null): string;
@@ -0,0 +1,10 @@
1
+ var q=Object.defineProperty;var s=(e,t)=>q(e,"name",{value:t,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{openDb as _,withCanvasWrite as k}from"../canvas/db.js";import{RunError as l}from"../runs/operations.js";const N="app:terminal";function p(e=new Date){return e.toISOString().slice(0,7)}s(p,"usageMonth");function c(e){const[t,n]=e.split("-").map(Number);return new Date(Date.UTC(t,n,1)).toISOString()}s(c,"resetAt");function S(e,t,n=new Date){const u=p(n);d(e,`INSERT INTO usage (grantee, month, payer, source, served_model, input_tokens, output_tokens, cached_tokens, usd_equivalent)
2
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3
+ ON CONFLICT(grantee, month, payer, source, served_model) DO UPDATE SET
4
+ input_tokens = input_tokens + excluded.input_tokens,
5
+ output_tokens = output_tokens + excluded.output_tokens,
6
+ cached_tokens = cached_tokens + excluded.cached_tokens,
7
+ usd_equivalent = CASE WHEN usd_equivalent IS NULL AND excluded.usd_equivalent IS NULL THEN NULL
8
+ ELSE COALESCE(usd_equivalent, 0) + COALESCE(excluded.usd_equivalent, 0) END`).run(t.grantee,u,t.payer,t.source,t.served_model,t.input_tokens,t.output_tokens,t.cached_tokens,t.usd_equivalent),t.payer==="user"&&t.usd_equivalent!==null&&t.usd_equivalent>0&&d(e,`INSERT INTO grantee_usage (grantee, month, usd_equivalent) VALUES (?, ?, ?)
9
+ ON CONFLICT(grantee, month) DO UPDATE SET usd_equivalent = usd_equivalent + excluded.usd_equivalent`).run(t.grantee,u,t.usd_equivalent)}s(S,"addUsage");function L(e,t,n,u,o){k(i=>S(i,{grantee:e,payer:t,source:"model",served_model:n,input_tokens:u.input,output_tokens:u.output,cached_tokens:u.cacheRead,usd_equivalent:o}))}s(L,"recordModelUsage");function m(e,t=p()){return d(_(),"SELECT usd_equivalent FROM grantee_usage WHERE grantee = ? AND month = ?").get(e,t)?.usd_equivalent??0}s(m,"monthSpend");function E(e){return e.grantee===N?null:e.spend_cap}s(E,"effectiveCap");function O(e,t=new Date){if(!e)return;const n=E(e);if(n===null)return;const u=p(t),o=m(e.grantee,u);if(!(o<n))throw new l(402,"cap_exhausted",`${e.grantee} reached its $${n} monthly spend cap ($${o.toFixed(2)} used this month)`,void 0,{spend_cap:n,usd_equivalent:o,reset_at:c(u)})}s(O,"assertUnderCap");function T(e,t,n=p()){const u=d(_(),`SELECT payer, source, served_model, input_tokens, output_tokens, usd_equivalent
10
+ FROM usage WHERE grantee = ? AND month = ? ORDER BY served_model`).all(e,n),o=s(r=>u.filter(a=>a.payer===r&&a.source==="model").map(({served_model:a,input_tokens:v,output_tokens:h,usd_equivalent:f})=>({served_model:a,input_tokens:v,output_tokens:h,usd_equivalent:f})),"byModel"),i=u.filter(r=>r.payer==="app").reduce((r,a)=>r+(a.usd_equivalent??0),0);return{grantee:e,month:n,usd_equivalent:m(e,n),by_model:o("user"),app_paid:{usd_equivalent:i,by_model:o("app")},spend_cap:E({grantee:e,spend_cap:t}),reset_at:c(n)}}s(T,"usageFor");function A(e){if(e===null)return p();if(!/^[1-9]\d{3}-(0[1-9]|1[0-2])$/.test(e))throw new l(400,"invalid_request","month must be YYYY-MM","month");return e}s(A,"parseUsageMonth");export{N as UNCAPPED_GRANTEE,S as addUsage,O as assertUnderCap,E as effectiveCap,m as monthSpend,A as parseUsageMonth,L as recordModelUsage,c as resetAt,T as usageFor,p as usageMonth};
@@ -1,24 +1,24 @@
1
- var B=Object.defineProperty;var s=(e,n)=>B(e,"name",{value:n,configurable:!0});import{randomUUID as L}from"node:crypto";import{createServer as $}from"node:http";import{Router as j}from"./router.js";import{OPERATIONS as V}from"./operations.js";import{errorCodes as D}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as G,RUNTIME_VERSION_HEADER as Y,minimumRuntimeRefusal as z}from"../../api/runtime-version.js";import{crtrVersion as x}from"../../shared/crtr-version.js";import{AppPrincipalResolver as F}from"./principal.js";import{handleRunOperation as Q,integer as W}from"./handlers/runs.js";import{parseLlmRequest as K,runLlm as Z}from"../../core/llm/run.js";import{CONTROL_BODY_MAX_BYTES as ee,MODEL_CONTEXT_BODY_MAX_BYTES as te}from"../../core/llm/body-limits.js";import{RunError as d,runIsRunning as ne}from"../../core/runs/operations.js";import{requireRun as P}from"./router.js";import{deferHead as re}from"./deferred-head.js";import{readRunEvents as J,registerGrantStream as ae,subscribeRunEvents as oe}from"../../core/runs/events.js";import{activeGrant as se}from"../../core/grants/mirror.js";import{releaseGrantWaiters as ie}from"../../core/grants/watch.js";import{emitEvent as ue}from"../../core/events/emit.js";import{runNotices as le}from"../run-notices.js";import{handleVendorSignIn as de,isVendorSignInPath as ce}from"./vendor-signin.js";import{listAppProfiles as fe}from"./handlers/app-profiles.js";import{handleTransferDownload as me,handleTransferOperation as pe,TRANSFER_OPERATIONS as he}from"./handlers/transfer.js";import{handleQuestionOperation as ye,QUESTION_OPERATIONS as _e}from"./handlers/questions.js";import{vendorStatus as we}from"./handlers/vendor-status.js";import{handleMemoryOperation as ve,MEMORY_OPERATIONS as Oe}from"./handlers/memory.js";import{abortOnClose as Re,callerFromApp as ge,handleProviderOperation as Se,PROVIDER_OPERATIONS as be,providerFailure as Ee}from"./handlers/providers.js";function Ne(e){return Object.prototype.hasOwnProperty.call(D,e)?D[e]:void 0}s(Ne,"fixedRow");function Te(e){const n=Ne(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}s(Te,"errorType");function Ae(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...a}=e;return a}s(Ae,"appDetails");function A(e,n,a){const u=Ae(e.details);return{error:{code:e.code,message:e.message,...u===void 0?{}:{details:u},type:Te(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...a?{run_id:a}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}s(A,"envelope");function Ie(e,n,a,u){const i=n instanceof d?n:new d(500,"server_failure","runtime request failed");if(n instanceof d||console.error("[app-listener]",n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
1
+ var B=Object.defineProperty;var s=(e,n)=>B(e,"name",{value:n,configurable:!0});import{randomUUID as L}from"node:crypto";import{createServer as $}from"node:http";import{Router as j}from"./router.js";import{OPERATIONS as V}from"./operations.js";import{errorCodes as D}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as F,RUNTIME_VERSION_HEADER as G,minimumRuntimeRefusal as Y}from"../../api/runtime-version.js";import{crtrVersion as x}from"../../shared/crtr-version.js";import{AppPrincipalResolver as z}from"./principal.js";import{handleRunOperation as Q,integer as W}from"./handlers/runs.js";import{parseLlmRequest as K,runLlm as Z}from"../../core/llm/run.js";import{parseUsageMonth as ee,usageFor as te}from"../../core/usage/ledger.js";import{capFields as ne}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as re,MODEL_CONTEXT_BODY_MAX_BYTES as ae}from"../../core/llm/body-limits.js";import{RunError as d,runIsRunning as oe}from"../../core/runs/operations.js";import{requireRun as I}from"./router.js";import{deferHead as se}from"./deferred-head.js";import{readRunEvents as J,registerGrantStream as ie,subscribeRunEvents as ue}from"../../core/runs/events.js";import{activeGrant as le}from"../../core/grants/mirror.js";import{releaseGrantWaiters as de}from"../../core/grants/watch.js";import{emitEvent as ce}from"../../core/events/emit.js";import{runNotices as fe}from"../run-notices.js";import{handleVendorSignIn as me,isVendorSignInPath as pe}from"./vendor-signin.js";import{listAppProfiles as he}from"./handlers/app-profiles.js";import{handleTransferDownload as _e,handleTransferOperation as ye,TRANSFER_OPERATIONS as we}from"./handlers/transfer.js";import{handleQuestionOperation as ve,QUESTION_OPERATIONS as ge}from"./handlers/questions.js";import{vendorStatus as Oe}from"./handlers/vendor-status.js";import{handleMemoryOperation as Re,MEMORY_OPERATIONS as Se}from"./handlers/memory.js";import{abortOnClose as be,callerFromApp as Ee,handleProviderOperation as Ne,PROVIDER_OPERATIONS as Te,providerFailure as Ae}from"./handlers/providers.js";function Pe(e){return Object.prototype.hasOwnProperty.call(D,e)?D[e]:void 0}s(Pe,"fixedRow");function Ie(e){const n=Pe(e);return n?n.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}s(Ie,"errorType");function Ce(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:n,...a}=e;return a}s(Ce,"appDetails");function A(e,n,a){const u=Ce(e.details);return{error:{code:e.code,message:e.message,...u===void 0?{}:{details:u},type:Ie(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:n,...a?{run_id:a}:{},...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}:{}}}}s(A,"envelope");function Me(e,n,a,u){const i=n instanceof d?n:new d(500,"server_failure","runtime request failed");if(n instanceof d||console.error("[app-listener]",n),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
2
2
  data: ${JSON.stringify(A(i,a,u))}
3
3
 
4
- `);return}e.writeHead(i.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":a,"X-Content-Type-Options":"nosniff",...i.code==="all_routes_exhausted"?{"Retry-After":String(i.details.retry_after_s)}:{}}),e.end(JSON.stringify(A(i,a,u)))}s(Ie,"fail");function Pe(e){return Array.isArray(e)?e[0]:e}s(Pe,"headerValue");function R(e,n,a){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(a))}s(R,"json");async function T(e,n=ee){const a=[];let u=0;for await(const i of e){if(u+=i.length,u>n)throw new d(400,"invalid_request","request body too large");a.push(i)}if(u)try{return JSON.parse(Buffer.concat(a).toString("utf8"))}catch{throw new d(400,"invalid_request","invalid JSON body")}}s(T,"bodyOf");function Ce(e,n){const a=e.headers["last-event-id"]??n.get("after");if(Array.isArray(a))throw new d(400,"invalid_request","invalid cursor","after");return W(a??null,"after",0,Number.MAX_SAFE_INTEGER,0)}s(Ce,"cursor");function Me(e){return`id: ${e.sequence_number}
4
+ `);return}e.writeHead(i.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":a,"X-Content-Type-Options":"nosniff",...i.code==="all_routes_exhausted"?{"Retry-After":String(i.details.retry_after_s)}:{}}),e.end(JSON.stringify(A(i,a,u)))}s(Me,"fail");function He(e){return Array.isArray(e)?e[0]:e}s(He,"headerValue");function w(e,n,a){e.writeHead(n,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(a))}s(w,"json");async function T(e,n=re){const a=[];let u=0;for await(const i of e){if(u+=i.length,u>n)throw new d(400,"invalid_request","request body too large");a.push(i)}if(u)try{return JSON.parse(Buffer.concat(a).toString("utf8"))}catch{throw new d(400,"invalid_request","invalid JSON body")}}s(T,"bodyOf");function ke(e,n){const a=e.headers["last-event-id"]??n.get("after");if(Array.isArray(a))throw new d(400,"invalid_request","invalid cursor","after");return W(a??null,"after",0,Number.MAX_SAFE_INTEGER,0)}s(ke,"cursor");function De(e){return`id: ${e.sequence_number}
5
5
  event: ${e.type}
6
6
  data: ${JSON.stringify(e)}
7
7
 
8
- `}s(Me,"frame");const He=6e4;function ke(e,n,a,u,i,N,w,v={}){P(u,a);let y=J(a,i);if(i>y.latest_sequence)throw new d(400,"invalid_request","cursor is beyond the run","after");if(i<y.latest_sequence&&(y.earliest_sequence===null||i<y.earliest_sequence-1))throw new d(409,"stream_gap","events have expired",void 0,{earliest_sequence:y.earliest_sequence,latest_sequence:y.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":w==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),w==="sse"?n.write(`: connected
8
+ `}s(De,"frame");const xe=6e4;function Je(e,n,a,u,i,N,v,g={}){I(u,a);let _=J(a,i);if(i>_.latest_sequence)throw new d(400,"invalid_request","cursor is beyond the run","after");if(i<_.latest_sequence&&(_.earliest_sequence===null||i<_.earliest_sequence-1))throw new d(409,"stream_gap","events have expired",void 0,{earliest_sequence:_.earliest_sequence,latest_sequence:_.latest_sequence});if(n.destroyed||e.destroyed)return n.destroy(),Promise.resolve();n.writeHead(200,{"Content-Type":v==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),v==="sse"?n.write(`: connected
9
9
 
10
- `):n.flushHeaders();let r=i;const t=le().streamOpened(a);let c=!1,_=!1,f=!1,p;const g=new Promise(m=>{p=m}),o=s(()=>{c||(c=!0,t(r),clearInterval(q),clearInterval(H),b(),I(),n.off("close",o),n.off("drain",h),n.end(),p())},"end"),h=s(()=>{f=!1,l()},"onDrain"),E=s(m=>{c||n.write(w==="sse"?`event: error
11
- data: ${JSON.stringify(A(m,N,a))}
10
+ `):n.flushHeaders();let r=i;const t=fe().streamOpened(a);let c=!1,y=!1,m=!1,p;const R=new Promise(h=>{p=h}),o=s(()=>{c||(c=!0,t(r),clearInterval(U),clearInterval(H),b(),P(),n.off("close",o),n.off("drain",f),n.end(),p())},"end"),f=s(()=>{m=!1,l()},"onDrain"),E=s(h=>{c||n.write(v==="sse"?`event: error
11
+ data: ${JSON.stringify(A(h,N,a))}
12
12
 
13
- `:`${JSON.stringify({type:"error",...A(m,N,a)})}
14
- `),o()},"error"),l=s(()=>{if(!(c||_)){_=!0;try{if(!O()){S();return}if(P(u,a),y=J(a,r),y.backlog_count>1e3||y.backlog_bytes>32*1024*1024){E(new d(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:r}));return}for(const m of y.events){if(f)break;f=!n.write(w==="sse"?Me(m):`${JSON.stringify(m)}
15
- `),r=m.sequence_number}}catch(m){E(m instanceof d?m:new d(500,"server_failure","stream failed"))}finally{_=!1}}},"flush"),S=s(()=>E(new d(401,"grant_revoked","grant is not active")),"revoked"),O=s(()=>"class"in u||se(u.grant)!==null,"grantActive"),b=oe(a,l),I="class"in u?()=>{}:ae(u.grant,S),q=setInterval(()=>{!c&&!f&&w==="sse"&&n.write(`: keepalive
13
+ `:`${JSON.stringify({type:"error",...A(h,N,a)})}
14
+ `),o()},"error"),l=s(()=>{if(!(c||y)){y=!0;try{if(!O()){S();return}if(I(u,a),_=J(a,r),_.backlog_count>1e3||_.backlog_bytes>32*1024*1024){E(new d(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:r}));return}for(const h of _.events){if(m)break;m=!n.write(v==="sse"?De(h):`${JSON.stringify(h)}
15
+ `),r=h.sequence_number}}catch(h){E(h instanceof d?h:new d(500,"server_failure","stream failed"))}finally{y=!1}}},"flush"),S=s(()=>E(new d(401,"grant_revoked","grant is not active")),"revoked"),O=s(()=>"class"in u||le(u.grant)!==null,"grantActive"),b=ue(a,l),P="class"in u?()=>{}:ie(u.grant,S),U=setInterval(()=>{!c&&!m&&v==="sse"&&n.write(`: keepalive
16
16
 
17
- `)},15e3),U=v.idleMs??He,X=v.isRunning??ne;let C=Date.now(),M=r;const H=setInterval(()=>{if(c||w!=="sse")return;const m=Date.now();let k=!0;try{k=X(a)}catch{}if(k||r!==M){C=m,M=r;return}m-C<U||f||(n.write(`event: idle
17
+ `)},15e3),q=g.idleMs??xe,X=g.isRunning??oe;let C=Date.now(),M=r;const H=setInterval(()=>{if(c||v!=="sse")return;const h=Date.now();let k=!0;try{k=X(a)}catch{}if(k||r!==M){C=h,M=r;return}h-C<q||m||(n.write(`event: idle
18
18
  data: {}
19
19
 
20
- `),o())},v.idleCheckMs??1e3);return H.unref(),n.on("drain",h),n.on("close",o),l(),g}s(ke,"streamRunEvents");function ut(e,n,a,u={}){if(!e.issuer||!e.audience||!e.listener?.address||!Number.isInteger(e.listener.port)||e.listener.port<1||e.listener.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(e.listener.address))throw new Error("runtime listener must bind a specific address");const i=new F(e.issuer,e.audience,a,e.jwksUrl),N=new j,w=new Map;for(const r of V.filter(t=>t.stage!==void 0)){const t=s(()=>({status:500}),"handler");N.register({method:r.method,pattern:r.path,handler:t}),w.set(t,r)}const v=$((r,t)=>{(async()=>{re(r,t,u.deferHead);const c=L();t.setHeader("Request-Id",c),t.setHeader(Y,x());let _;try{const f=z(Pe(r.headers[G.toLowerCase()]),x());if(f!==null)throw f.status===400?new d(400,f.code,f.message,f.param):new d(426,f.code,f.message,void 0,f.details);const p=new URL(r.url??"/","http://runtime.local");if(ce(p.pathname)){await de(r,t,p,i);return}if((r.method!=="GET"||p.pathname!=="/v1/health")&&(!i.isReady()||!n()))throw new d(503,"runtime_starting","runtime_starting");const g=N.match(r.method??"GET",p.pathname);if(!g)throw new d(404,"not_found","unknown operation");const o=w.get(g.handler);if(!o)throw new d(404,"not_found","unknown operation");if(_=g.params.run_id,o.name==="health"){R(t,200,{status:"ok",api_version:"2026-09-23"});return}if(o.name==="runtime.sync-notice"){if(!await i.verifySyncNotice(r.headers.authorization)){t.writeHead(204),t.end();return}ue({level:"info",event:"daemon.grants.sync_notice",fields:{request_id:c}});try{await a({fresh:!0})}catch{throw new d(503,"service_unavailable","grant sync failed")}t.writeHead(204),t.end();return}if(o.name==="runtime.vendor-status"){await i.verifyDirectoryRead(r.headers.authorization,"vendor_status"),R(t,200,we());return}const h=await i.resolve(r.headers.authorization,o.scope);if(t.setHeader("Request-Id",c),_&&P(h,_,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(o.name),o.name==="run.delete"),o.stream==="bytes"){await me(r,t,h,p.searchParams);return}if(o.stream){ke(r,t,_,h,Ce(r,p.searchParams),c,"sse",u.stream);return}if(o.name==="llm"){const l=K(await T(r,te)),S=new AbortController,O=s(()=>S.abort(),"abort");t.on("close",O);try{const b=await Z(l,S.signal,l.stream?I=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(I)}
20
+ `),o())},g.idleCheckMs??1e3);return H.unref(),n.on("drain",f),n.on("close",o),l(),R}s(Je,"streamRunEvents");function mt(e,n,a,u={}){if(!e.issuer||!e.audience||!e.listener?.address||!Number.isInteger(e.listener.port)||e.listener.port<1||e.listener.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(e.listener.address))throw new Error("runtime listener must bind a specific address");const i=new z(e.issuer,e.audience,a,e.jwksUrl),N=new j,v=new Map;for(const r of V.filter(t=>t.stage!==void 0)){const t=s(()=>({status:500}),"handler");N.register({method:r.method,pattern:r.path,handler:t}),v.set(t,r)}const g=$((r,t)=>{(async()=>{se(r,t,u.deferHead);const c=L();t.setHeader("Request-Id",c),t.setHeader(G,x());let y;try{const m=Y(He(r.headers[F.toLowerCase()]),x());if(m!==null)throw m.status===400?new d(400,m.code,m.message,m.param):new d(426,m.code,m.message,void 0,m.details);const p=new URL(r.url??"/","http://runtime.local");if(pe(p.pathname)){await me(r,t,p,i);return}if((r.method!=="GET"||p.pathname!=="/v1/health")&&(!i.isReady()||!n()))throw new d(503,"runtime_starting","runtime_starting");const R=N.match(r.method??"GET",p.pathname);if(!R)throw new d(404,"not_found","unknown operation");const o=v.get(R.handler);if(!o)throw new d(404,"not_found","unknown operation");if(y=R.params.run_id,o.name==="health"){w(t,200,{status:"ok",api_version:"2026-09-23"});return}if(o.name==="runtime.sync-notice"){if(!await i.verifySyncNotice(r.headers.authorization)){t.writeHead(204),t.end();return}ce({level:"info",event:"daemon.grants.sync_notice",fields:{request_id:c}});try{await a({fresh:!0})}catch{throw new d(503,"service_unavailable","grant sync failed")}t.writeHead(204),t.end();return}if(o.name==="runtime.vendor-status"){await i.verifyDirectoryRead(r.headers.authorization,"vendor_status"),w(t,200,Oe());return}const f=await i.resolve(r.headers.authorization,o.scope);if(t.setHeader("Request-Id",c),y&&I(f,y,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(o.name),o.name==="run.delete"),o.stream==="bytes"){await _e(r,t,f,p.searchParams);return}if(o.stream){Je(r,t,y,f,ke(r,p.searchParams),c,"sse",u.stream);return}if(o.name==="llm"){const l=K(await T(r,ae)),S=new AbortController,O=s(()=>S.abort(),"abort");t.on("close",O);try{const b=await Z(l,S.signal,l.stream?P=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(P)}
21
21
 
22
- `)}:void 0);l.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:f.grantee,spend_cap:f.spend_cap});l.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
23
23
 
24
- `)):R(t,200,b.completion)}finally{t.off("close",O)}return}if(o.name==="profile.list"){R(t,200,fe(h));return}if(_e.has(o.name)){const l=await ye(o.name,h,g.params.question_id,o.method==="POST"?await T(r):void 0);R(t,l.status,l.body);return}if(be.has(o.name)){const{signal:l,release:S}=Re(t);try{const O=await Se(o.name,ge(h),{params:g.params,body:o.method==="POST"?await T(r):void 0,query:p.searchParams},{requestId:c,signal:l});R(t,O.status,O.body)}catch(O){const b=Ee(O,c,!0);t.writeHead(b.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...b.headers}),t.end(JSON.stringify(b.body))}finally{S()}return}if(Oe.has(o.name)){const l=ve(o.name,{class:"app",app:h.grantee,scopes:h.resolved},p.searchParams);R(t,l.status,l.body);return}if(he.has(o.name)){const l=await pe(o,h,g.params,p.searchParams,await T(r));R(t,l.status,l.body);return}const E=await Q(o,h,_,p.searchParams,await T(r),r.headers["idempotency-key"]);R(t,E.status,E.body)}catch(f){Ie(t,f,c,_)}})()});return{ready:new Promise((r,t)=>{v.once("error",t),v.listen(e.listener.port,e.listener.address,()=>{v.off("error",t),r()})}),close:s(async()=>{i.close(),ie(),v.closeAllConnections(),await new Promise((r,t)=>v.close(c=>c?t(c):r()))},"close")}}s(ut,"createAppListener");export{He as RUN_STREAM_IDLE_MS,ut as createAppListener,Ce as cursor,ke as streamRunEvents};
24
+ `)):w(t,200,b.completion)}finally{t.off("close",O)}return}if(o.name==="usage.get"){w(t,200,te(f.grantee,f.spend_cap,ee(p.searchParams.get("month"))));return}if(o.name==="profile.list"){w(t,200,he(f));return}if(ge.has(o.name)){const l=await ve(o.name,f,R.params.question_id,o.method==="POST"?await T(r):void 0);w(t,l.status,l.body);return}if(Te.has(o.name)){const{signal:l,release:S}=be(t);try{const O=await Ne(o.name,Ee(f),{params:R.params,body:o.method==="POST"?await T(r):void 0,query:p.searchParams},{requestId:c,signal:l});w(t,O.status,O.body)}catch(O){const b=Ae(O,c,!0);t.writeHead(b.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...b.headers}),t.end(JSON.stringify(b.body))}finally{S()}return}if(Se.has(o.name)){const l=Re(o.name,{class:"app",app:f.grantee,scopes:f.resolved},p.searchParams);w(t,l.status,l.body);return}if(we.has(o.name)){const l=await ye(o,f,R.params,p.searchParams,await T(r));w(t,l.status,l.body);return}const E=await Q(o,f,y,p.searchParams,await T(r),r.headers["idempotency-key"]);w(t,E.status,E.body)}catch(m){Me(t,m,c,y)}})()});return{ready:new Promise((r,t)=>{g.once("error",t),g.listen(e.listener.port,e.listener.address,()=>{g.off("error",t),r()})}),close:s(async()=>{i.close(),de(),g.closeAllConnections(),await new Promise((r,t)=>g.close(c=>c?t(c):r()))},"close")}}s(mt,"createAppListener");export{xe as RUN_STREAM_IDLE_MS,mt as createAppListener,ke as cursor,Je as streamRunEvents};
@@ -1 +1 @@
1
- var K=Object.defineProperty;var o=(n,e)=>K(n,"name",{value:e,configurable:!0});import{randomUUID as Q}from"node:crypto";import{relative as V,resolve as R,sep as D}from"node:path";import{ApiError as i}from"../../../api/errors.js";import{armCron as X,cancelCron as ee,getCron as _,listCrons as N,listCronRuns as W,pokeHeldCrons as ne,setCronState as z}from"../../../core/canvas/crons.js";import{getNode as f}from"../../../core/canvas/canvas.js";import{nodeAgentDir as re}from"../../../core/canvas/node-agent-paths.js";import{appSpace as oe}from"../../../core/layout.js";import{appProcessesSandboxed as te,cwdIsMounted as se}from"../../../core/spaces/process.js";import{assertScope as B}from"../../../core/scopes.js";import{InputError as s}from"../../../core/io.js";import{nextSlotAfter as ie}from"../../../core/wake.js";import{executeCron as ae,markSelfCanceledCronRun as ce}from"../../cron-run.js";import{toCronDTO as w,toCronRunDTO as G}from"../map.js";import{assertCeiling as le,canReadNode as de}from"../router.js";import{requireUsableProfile as ue}from"./profiles.js";function j(n,e){const r=n[e];if(typeof r!="string"||r.trim()==="")throw new s({error:"missing_field",message:`cron arm request requires a non-empty "${e}"`,field:e,next:`Send a non-empty string "${e}".`});return r}o(j,"requireString");function k(n,e,r,t){if(n==null)return t;if(typeof n=="string"&&r.includes(n))return n;throw new s({error:`bad_${e}`,message:`"${String(n)}" is not a valid ${e}`,field:e,next:`Use one of: ${r.join(", ")}.`})}o(k,"requireEnum");function pe(n){if(n==="human")return JSON.stringify({human:!0});if(n.startsWith("node:")){const e=n.slice(5).trim();if(e==="")throw new s({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(f(e)===null)throw new s({error:"sink_node_not_found",message:`sink node ${e} 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:e})}if(n.startsWith("spawn:")){const e=n.slice(6).trim();if(e==="")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:e})}throw new s({error:"bad_sink",message:`"${n}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}o(pe,"parseSinkSpec");const fe=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function L(n,e){if(!fe.test(n)||Number.isNaN(Date.parse(n)))throw new s({error:`bad_${e}`,message:`"${n}" is not a UTC ISO instant`,field:e,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return n}o(L,"requireUtcIso");function he(n){try{ie(n,new Date)}catch(e){throw new s({error:"bad_recur",message:`recur is not advanceable: ${e instanceof Error?e.message:String(e)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return n}o(he,"requireRecur");function me(n){let e;try{e=JSON.parse(n)}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 e!="object"||e===null||Array.isArray(e))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,t]of Object.entries(e))if(typeof t!="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 n}o(me,"requireEnvJson");function x(n){const e=n.query.get("profile");return e!==null&&e.trim()!==""?e.trim():null}o(x,"callerProfile");function Z(n,e){return e===null||n.scope==="global"||n.profile===e}o(Z,"inScope");function H(n){return n.principal.class==="person"?null:n.principal.app}o(H,"callerApp");function T(n,e){return de(n.principal,e.grantee,e.created_by??void 0)}o(T,"visibleTo");function _e(n){return typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null}o(_e,"anchorNodeOf");function we(n,e,r,t,d){const c=typeof n.cwd=="string"&&n.cwd.trim()!==""?R(n.cwd):null;if(!te()){if(c===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 c}const O=t!==null&&f(t)!==null?t:void 0,g=o(C=>se(e,C,O,d),"reaches");if(c!==null&&g(c))return c;const a=oe(e).files;if(c!==null&&n.cwd_is_default!==!0)throw new i(400,"invalid_request",`cwd ${c} 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:c,default:a},void 0,{param:"cwd"});const l=r===null?null:f(r)?.cwd??null;return l!==null&&g(l)?l:a}o(we,"resolveArmedCwd");function ge(n){const e=n.body??{},r=j(e,"name"),t=j(e,"command"),d=L(j(e,"fire_at"),"fire_at"),c=typeof e.recur=="string"&&e.recur!==""?he(e.recur):null,O=typeof e.expires_at=="string"&&e.expires_at!==""?L(e.expires_at,"expires_at"):null,g=typeof e.env_json=="string"&&e.env_json!==""?me(e.env_json):null,a=typeof e.sink=="string"&&e.sink.trim()!==""?e.sink.trim():null,l=k(e.on_output,"on_output",["silent","on-failure","always","on-change"],a?.startsWith("node:")?"on-change":"on-failure"),C=k(e.overlap,"overlap",["skip","queue","replace"],"skip"),M=k(e.tier,"tier",["normal","urgent","critical"],"normal"),P=l==="always"||l==="on-change";if(P&&a===null)throw new s({error:"sink_required",message:`on_output "${l}" 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(!P&&a!==null)throw new s({error:"sink_unused",message:`on_output "${l}" 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 Y=a!==null?pe(a):"{}";a?.startsWith("spawn:")&&le(n.ceiling,"crtr:act");const m=n.principal.class==="person"?"app:terminal":n.principal.app,h=n.principal.class==="broker"?n.principal.node:typeof e.created_by=="string"&&e.created_by!==""?e.created_by:null;if(n.principal.class==="broker"&&e.created_by!=null&&e.created_by!==h)throw new i(403,"permission_error","a broker may only arm a cron as its own node");const p=h===null?null:f(h);if(p!==null){if(p.grantee!==m)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."});B(p,"crtr:schedule"),a?.startsWith("spawn:")&&B(p,"crtr:act")}if(e.isolated!==void 0&&typeof e.isolated!="boolean")throw new i(400,"invalid_request","isolated must be a boolean");if(p?.isolated&&e.isolated===!1)throw new i(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const y=p?.isolated===!0||e.isolated===!0;if(y&&p===null)throw new i(400,"invalid_request","an isolated cron requires an existing creator node");const v=y?re(h,m):null;if(v!==null){const I=V(v,typeof e.cwd=="string"&&e.cwd.trim()!==""?R(e.cwd):v);if(I===".."||I.startsWith(`..${D}`)||I.startsWith(D))throw new i(400,"invalid_request","an isolated cron must run in its creating node folder",{param:"cwd"})}const F=v??we(e,m,h,_e(e),y),u=typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null;if(u!==null&&f(u)!==null&&f(u).grantee!==m)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(u!==null&&f(u)===null)throw new s({error:"anchor_not_found",message:`anchor node ${u} 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 E=e.cancel_on_wake===!0;if(E&&u===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 b=e.run_timeout_s??300;if(typeof b!="number"||!Number.isInteger(b)||b<=0)throw new s({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(e.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const q=typeof e.profile=="string"&&e.profile!==""?ue(n,e.profile).profileId:null,U=k(e.scope,"scope",["profile","global"],q!==null?"profile":"global");if(U==="profile"&&q===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.'});const J=Q();return X({cron_id:J,name:r,created_by:h,grantee:m,isolated:y,command:t,fire_at:d,recur:c,tz:typeof e.tz=="string"?e.tz:null,expires_at:O,anchor_node:u,cancel_on_wake:E,cwd:F,env_json:g,profile:q,scope:U,run_timeout_s:b,overlap:C,on_output:l,sink:Y,tier:M}),{status:201,body:w(_(J),null)}}o(ge,"handleArm");function ye(n){const e=x(n);return{status:200,body:(e===null?N({all:!0}):[...N({profile:e}),...N({global:!0})].sort((t,d)=>t.fire_at<d.fire_at?-1:t.fire_at>d.fire_at?1:0)).filter(t=>T(n,t)).map(t=>w(t,A(t)))}}o(ye,"handleList");function A(n){return W(n.cron_id)[0]??null}o(A,"lastRunOf");function S(n){const e=n.params.cronId,r=_(e);if(r===null||!T(n,r))throw new i(404,"cron_not_found",`no cron ${e}`);if(!Z(r,x(n)))throw new i(404,"cron_not_found",`no cron ${e} in this profile \u2014 it is scoped to another profile`);return r}o(S,"requireCron");function $(n,e){const r=H(n);if(r!==null&&e.grantee!==r)throw new i(403,"other_app_run",`cron ${e.cron_id} belongs to another app`);return e}o($,"requireOwnCron");function ve(n){const e=S(n),r=W(e.cron_id);return{status:200,body:{cron:w(e,r[0]??null),runs:r.map(G)}}}o(ve,"handleShow");function be(n){const e=$(n,S(n));z(e.cron_id,"paused");const r=_(e.cron_id);return{status:200,body:w(r,A(r))}}o(be,"handlePause");function ke(n){const e=$(n,S(n));z(e.cron_id,"active");const r=_(e.cron_id);return{status:200,body:w(r,A(r))}}o(ke,"handleResume");async function Se(n){const e=$(n,S(n));if(e.run_state==="running")throw new i(409,"cron_running",`cron ${e.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await ae(e,{outOfBand:!0});if(r===null)throw new i(409,"cron_running",`cron ${e.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:G(r)}}o(Se,"handleRun");function $e(n){const e=new Date().toISOString();return{status:200,body:{unparked:ne(e,H(n)),at:e}}}o($e,"handlePoke");function Oe(n){const e=n.params.cronId,r=_(e);if(r!==null&&!T(n,r))throw new i(404,"cron_not_found",`no cron ${e}`);if(r!==null&&!Z(r,x(n)))throw new i(404,"cron_not_found",`no cron ${e} in this profile \u2014 it is scoped to another profile`);r!==null&&$(n,r);const t=n.query.get("run_id"),d=t!==null&&ce(e,t);return ee(e,{preserveRunningProcess:d}),{status:204}}o(Oe,"handleCancel");const Be=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:ge},{method:"GET",pattern:"/v1/crons",handler:ye},{method:"GET",pattern:"/v1/crons/:cronId",handler:ve},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:be},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:ke},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:$e},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Se},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:Oe}];export{Be as cronRoutes};
1
+ var Q=Object.defineProperty;var t=(e,n)=>Q(e,"name",{value:n,configurable:!0});import{randomUUID as V}from"node:crypto";import{relative as X,resolve as W,sep as z}from"node:path";import{ApiError as c}from"../../../api/errors.js";import{armCron as nn,cancelCron as en,getCron as v,listCrons as x,listCronRuns as G,pokeHeldCrons as rn,setCronState as B}from"../../../core/canvas/crons.js";import{getNode as _}from"../../../core/canvas/canvas.js";import{appSpace as on,cronFolder as tn}from"../../../core/layout.js";import{appProcessesSandboxed as sn,cwdIsMounted as an}from"../../../core/spaces/process.js";import{activeGrant as cn,activeGrantForApp as ln,grantFor as dn,terminalGrant as un}from"../../../core/grants/mirror.js";import{readConfig as pn}from"../../../core/config.js";import{assertScope as F}from"../../../core/scopes.js";import{InputError as s}from"../../../core/io.js";import{nextSlotAfter as fn}from"../../../core/wake.js";import{executeCron as _n,markSelfCanceledCronRun as hn}from"../../cron-run.js";import{toCronDTO as y,toCronRunDTO as L}from"../map.js";import{assertCeiling as mn,canReadNode as gn}from"../router.js";import{requireUsableProfile as wn}from"./profiles.js";function P(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(P,"requireString");function $(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($,"requireEnum");function bn(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(_(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(bn,"parseSinkSpec");const vn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function Z(e,n){if(!vn.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(Z,"requireUtcIso");function yn(e){try{fn(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(yn,"requireRecur");function kn(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(kn,"requireEnvJson");function A(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(A,"callerProfile");function H(e,n){return n===null||e.scope==="global"||e.profile===n}t(H,"inScope");function M(e){return e.principal.class==="person"?null:e.principal.app}t(M,"callerApp");function E(e,n){return gn(e.principal,n.grantee,n.created_by??void 0)}t(E,"visibleTo");function Sn(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(Sn,"anchorNodeOf");function $n(e,n,r,o,a,u){const l=typeof e.cwd=="string"&&e.cwd.trim()!==""?W(e.cwd):null;if(!sn()){if(l===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 l}const p=o!==null&&_(o)!==null?o:void 0,d=t(I=>an(n,I,p,a,void 0,u),"reaches");if(l!==null&&d(l))return l;const i=on(n).files;if(l!==null&&e.cwd_is_default!==!0)throw new c(400,"invalid_request",`cwd ${l} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${i}`,{cwd:l,default:i},void 0,{param:"cwd"});const h=r===null?null:_(r)?.cwd??null;return h!==null&&d(h)?h:i}t($n,"resolveArmedCwd");function On(e,n,r){const o=e.principal;if(o.class==="person"){const i=un();return{sub:i?.sub??"user:local",grant_id:i?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const i=ln(n);if(i===null)throw new c(401,"grant_revoked",`no active grant for ${n}`);const h=e.ceiling??o.scopes;return{sub:i.sub,grant_id:i.id,scopes:[...h??i.resolved],origin_run_id:null}}if(o.class!=="broker")throw new c(401,"unauthorized","this caller cannot arm a cron");const a=r??_(o.node),u=a?.grant_id??o.grant,l=a?.sub??o.sub,p=u!=null?cn(u):dn(n,l);if(p===null){if(n==="app:terminal"&&!pn("user").runtime?.issuer)return{sub:l,grant_id:u??null,scopes:a?.scopes??o.scopes,origin_run_id:a?.run_id??o.run};throw new c(401,"grant_revoked","the node grant is not active")}const d=a?.scopes??o.scopes;return{sub:p.sub,grant_id:p.id,scopes:d===null?[...p.resolved]:[...d],origin_run_id:a?.run_id??o.run}}t(On,"armPrincipal");function Cn(e){const n=e.body??{},r=P(n,"name"),o=P(n,"command"),a=Z(P(n,"fire_at"),"fire_at"),u=typeof n.recur=="string"&&n.recur!==""?yn(n.recur):null,l=typeof n.expires_at=="string"&&n.expires_at!==""?Z(n.expires_at,"expires_at"):null,p=typeof n.env_json=="string"&&n.env_json!==""?kn(n.env_json):null,d=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,i=$(n.on_output,"on_output",["silent","on-failure","always","on-change"],d?.startsWith("node:")?"on-change":"on-failure"),h=$(n.overlap,"overlap",["skip","queue","replace"],"skip"),I=$(n.tier,"tier",["normal","urgent","critical"],"normal"),J=i==="always"||i==="on-change";if(J&&d===null)throw new s({error:"sink_required",message:`on_output "${i}" 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(!J&&d!==null)throw new s({error:"sink_unused",message:`on_output "${i}" 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 Y=d!==null?bn(d):"{}";d?.startsWith("spawn:")&&mn(e.ceiling,"crtr:act");const w=e.principal.class==="person"?"app:terminal":e.principal.app,b=e.principal.class==="broker"?e.principal.node: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:_(b);if(m!==null){if(m.grantee!==w)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."});F(m,"crtr:schedule"),d?.startsWith("spawn:")&&F(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 N=m?.isolated===!0||n.isolated===!0,g=On(e,w,m),q=V(),k=N?tn(w,q):null;if(k!==null&&n.cwd_is_default!==!0){const T=X(k,typeof n.cwd=="string"&&n.cwd.trim()!==""?W(n.cwd):k);if(T===".."||T.startsWith(`..${z}`)||T.startsWith(z))throw new c(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const K=k??$n(n,w,b,Sn(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&&_(f)!==null&&_(f).grantee!==w)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(f!==null&&_(f)===null)throw new s({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 R=n.cancel_on_wake===!0;if(R&&f===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 S=n.run_timeout_s??300;if(typeof S!="number"||!Number.isInteger(S)||S<=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 j=typeof n.profile=="string"&&n.profile!==""?wn(e,n.profile).profileId:null,D=$(n.scope,"scope",["profile","global"],j!==null?"profile":"global");if(D==="profile"&&j===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 nn({cron_id:q,name:r,created_by:b,grantee:w,sub:g.sub,grant_id:g.grant_id,scopes:g.scopes,origin_run_id:g.origin_run_id,isolated:N,command:o,fire_at:a,recur:u,tz:typeof n.tz=="string"?n.tz:null,expires_at:l,anchor_node:f,cancel_on_wake:R,cwd:K,env_json:p,profile:j,scope:D,run_timeout_s:S,overlap:h,on_output:i,sink:Y,tier:I}),{status:201,body:y(v(q),null)}}t(Cn,"handleArm");function In(e){const n=A(e);return{status:200,body:(n===null?x({all:!0}):[...x({profile:n}),...x({global:!0})].sort((o,a)=>o.fire_at<a.fire_at?-1:o.fire_at>a.fire_at?1:0)).filter(o=>E(e,o)).map(o=>y(o,U(o)))}}t(In,"handleList");function U(e){return G(e.cron_id)[0]??null}t(U,"lastRunOf");function O(e){const n=e.params.cronId,r=v(n);if(r===null||!E(e,r))throw new c(404,"cron_not_found",`no cron ${n}`);if(!H(r,A(e)))throw new c(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(O,"requireCron");function C(e,n){const r=M(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(C,"requireOwnCron");function Nn(e){const n=O(e),r=G(n.cron_id);return{status:200,body:{cron:y(n,r[0]??null),runs:r.map(L)}}}t(Nn,"handleShow");function qn(e){const n=C(e,O(e));B(n.cron_id,"paused");const r=v(n.cron_id);return{status:200,body:y(r,U(r))}}t(qn,"handlePause");function jn(e){const n=C(e,O(e));B(n.cron_id,"active");const r=v(n.cron_id);return{status:200,body:y(r,U(r))}}t(jn,"handleResume");async function Tn(e){const n=C(e,O(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:L(r)}}t(Tn,"handleRun");function xn(e){const n=new Date().toISOString();return{status:200,body:{unparked:rn(n,M(e)),at:n}}}t(xn,"handlePoke");function Pn(e){const n=e.params.cronId,r=v(n);if(r!==null&&!E(e,r))throw new c(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!H(r,A(e)))throw new c(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&C(e,r);const o=e.query.get("run_id"),a=o!==null&&hn(n,o);return en(n,{preserveRunningProcess:a}),{status:204}}t(Pn,"handleCancel");const Qn=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:Cn},{method:"GET",pattern:"/v1/crons",handler:In},{method:"GET",pattern:"/v1/crons/:cronId",handler:Nn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:qn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:jn},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:xn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Tn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:Pn}];export{Qn as cronRoutes};
@@ -1,2 +1,10 @@
1
1
  import type { RouteTable } from '../router.js';
2
+ import { RunError } from '../../../core/runs/operations.js';
3
+ import { type SocketPrincipal } from '../server.js';
4
+ import { type SpendTarget } from '../../../core/usage/ledger.js';
5
+ /** Who a socket caller's model call bills: a broker's node bills its grant's app under
6
+ * that grant's cap; the person (and a local scoped token) bills the person's own agent, uncapped. */
7
+ export declare function spendTarget(principal: SocketPrincipal | undefined): SpendTarget;
8
+ /** `cap_exhausted` names when the count starts over and what the person can do (contract 2 error table). */
9
+ export declare function capFields(error: RunError): Record<string, unknown>;
2
10
  export declare const llmRoutes: RouteTable;