@north-light/cli 0.3.414 → 0.3.416
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +20 -0
- package/dist/api/client.js +3 -3
- package/dist/api/dto/common.d.ts +1 -1
- package/dist/api/dto/crons.d.ts +7 -1
- package/dist/api/dto/human-requests.d.ts +1 -1
- package/dist/api/dto/run-events.d.ts +1 -1
- package/dist/api/dto/usage.d.ts +27 -0
- package/dist/api/dto/usage.js +0 -0
- package/dist/api/errors.js +1 -1
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.js +1 -1
- package/dist/builtin-memory/crouter-sdk/README.md +22 -4
- package/dist/builtin-memory/crouter-sdk/client.md +32 -1
- package/dist/builtin-memory/crouter-sdk/errors.md +3 -0
- package/dist/builtin-memory/crouter-sdk/getting-started.md +26 -4
- package/dist/builtin-memory/crouter-sdk/release-notes.md +23 -0
- package/dist/builtin-memory/crouter-sdk/testing.md +9 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -0
- package/dist/cli.js +7 -7
- package/dist/clients/attach/viewer.js +606 -606
- package/dist/commands/cron.js +1 -1
- package/dist/commands/node/create.js +1 -1
- package/dist/core/canvas/crons.d.ts +15 -1
- package/dist/core/canvas/crons.js +13 -13
- package/dist/core/canvas/delivery-contract.d.ts +1 -1
- package/dist/core/canvas/migrations.js +73 -66
- package/dist/core/canvas/types.d.ts +1 -1
- package/dist/core/command.d.ts +7 -0
- package/dist/core/command.js +9 -9
- package/dist/core/grants/mirror.d.ts +4 -0
- package/dist/core/grants/mirror.js +2 -2
- package/dist/core/grants/on-sync.js +1 -1
- package/dist/core/graph/custom.js +5 -5
- package/dist/core/graph/reader.js +3 -3
- package/dist/core/graph/spaces/app.js +1 -1
- package/dist/core/graph/types.d.ts +5 -0
- package/dist/core/layout.d.ts +2 -0
- package/dist/core/layout.js +2 -2
- package/dist/core/llm/run.d.ts +5 -1
- package/dist/core/llm/run.js +3 -3
- package/dist/core/providers/call.d.ts +2 -0
- package/dist/core/providers/call.js +1 -1
- package/dist/core/providers/errors.js +1 -1
- package/dist/core/providers/receipts.d.ts +2 -0
- package/dist/core/providers/receipts.js +2 -3
- package/dist/core/runs/confinement.d.ts +2 -0
- package/dist/core/runs/confinement.js +2 -2
- package/dist/core/runs/operations.d.ts +2 -0
- package/dist/core/runs/operations.js +4 -4
- package/dist/core/runtime/broker/model-adapter.js +2 -2
- package/dist/core/runtime/run-command.d.ts +10 -0
- package/dist/core/runtime/run-command.js +1 -1
- package/dist/core/spaces/mounts.d.ts +7 -0
- package/dist/core/spaces/mounts.js +1 -1
- package/dist/core/spaces/process.d.ts +22 -2
- package/dist/core/spaces/process.js +1 -1
- package/dist/core/usage/ledger.d.ts +49 -0
- package/dist/core/usage/ledger.js +10 -0
- package/dist/daemon/api/app-listener.js +12 -12
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/llm.d.ts +8 -0
- package/dist/daemon/api/handlers/llm.js +3 -3
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/map.d.ts +1 -1
- package/dist/daemon/api/map.js +1 -1
- package/dist/daemon/api/operations.d.ts +1 -1
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +15 -0
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.d.ts +5 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/cron/armed-context.d.ts +21 -4
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/authority.d.ts +27 -0
- package/dist/daemon/cron/authority.js +1 -0
- package/dist/daemon/cron/disposition.d.ts +2 -0
- package/dist/daemon/cron/disposition.js +1 -1
- package/dist/daemon/cron/escalation.js +3 -3
- package/dist/daemon/cron/folders.d.ts +5 -0
- package/dist/daemon/cron/folders.js +1 -0
- package/dist/daemon/cron/lease-recovery.js +1 -1
- package/dist/daemon/cron/live-runs.d.ts +28 -0
- package/dist/daemon/cron/live-runs.js +1 -1
- package/dist/daemon/cron/run-socket.d.ts +30 -0
- package/dist/daemon/cron/run-socket.js +1 -0
- package/dist/daemon/cron/settle.d.ts +10 -0
- package/dist/daemon/cron/settle.js +1 -1
- package/dist/daemon/cron/sinks.d.ts +2 -1
- package/dist/daemon/cron/sinks.js +2 -2
- package/dist/daemon/cron-run.js +2 -1
- package/dist/daemon/crtrd.js +7 -7
- package/dist/daemon/reconcilers/action-lane.js +1 -1
- package/dist/daemon/reconcilers/broker-supervision.d.ts +10 -0
- package/dist/daemon/reconcilers/broker-supervision.js +4 -4
- package/dist/daemon/reconcilers/cron-lane.d.ts +1 -0
- package/dist/daemon/reconcilers/cron-lane.js +1 -1
- package/dist/shared/env.d.ts +6 -5
- package/dist/shared/env.js +2 -2
- package/docs/sdk/README.md +22 -4
- package/docs/sdk/client.md +32 -1
- package/docs/sdk/errors.md +3 -0
- package/docs/sdk/getting-started.md +26 -4
- package/docs/sdk/release-notes.md +23 -0
- package/docs/sdk/testing.md +9 -3
- package/package.json +2 -2
- package/packages/crouter-identity/dist/index.d.ts +29 -2
- package/packages/crouter-identity/dist/index.js +81 -12
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var V=Object.defineProperty;var c=(e,r)=>V(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as W}from"../../api/command-manifest/result.js";import{callerAllows as S,requireGroup as X}from"../../daemon/api/handlers/providers.js";import{grantFor as Y,terminalGrant as Z}from"../grants/mirror.js";import{assertUnderCap as B,UNCAPPED_GRANTEE as ee}from"../usage/ledger.js";import{listInstalledPlugins as re}from"../resolver.js";import{ProviderError as u}from"./errors.js";import{ensureProviderBundle as oe,refreshProviderBundle as te,resolveProviderBundle as M,unavailableProviderError as ne}from"./bundles.js";import{prepareFileCall as ie,resignForRetry as D,resolveFileResult as se}from"./files.js";import{consumeApproval as ae,requireProviderApproval as de}from"./approval.js";import{outboundToken as pe}from"./mint.js";import{discardFile as ue}from"./token-cache.js";import{registerProviderCall as le}from"./inflight.js";import{buildRequestPlan as ce}from"../command-plugins/transport/http-plan.js";import{sendProviderRequest as fe,ProviderTransportError as me}from"./request.js";import{acceptProviderReceipt as ge}from"./receipts.js";import{writeProviderAudit as ve}from"./audit.js";function O(e,r){const n=[],o=c((i,a)=>{const d=[...a,i.name];if(i.kind==="branch"){for(const v of i.children)o(v,d);return}const l=d[0]===r?d.slice(1).join(" "):d.join(" ");n.push({tool:l,leaf:i})},"walk");for(const i of e.roots)o(i,[]);return n}c(O,"providerLeaves");function J(e,r,n){if(e.kind!=="branch")return null;const o=[];for(const h of e.children)if(h.kind==="branch"){const w=J(h,r,n);w&&o.push(w)}else if("group"in h&&h.group&&S(n,`${r}:${h.group}`)){const{kind:w,name:A,description:t,whenToUse:k,tier:m,summary:q,params:_,output:j,effects:$,group:f}=h;o.push({kind:w,name:A,description:t,whenToUse:k,...m===void 0?{}:{tier:m},summary:q,params:_,output:j,effects:$,group:f})}if(o.length===0)return null;const{kind:i,name:a,description:d,whenToUse:l,tier:v,summary:g,model:b,rootEntry:y}=e;return{kind:i,name:a,description:d,whenToUse:l,...v===void 0?{}:{tier:v},summary:g,...b===void 0?{}:{model:b},...y===void 0?{}:{rootEntry:y},children:o}}c(J,"providerHelpBranch");async function Be(e,r){const n=await M(r),o=O(n.commands,r),i=re("user").find(a=>a.name===r)?.manifest.description??r;return{provider:r,status:"available",major:n.major,display_name:i,groups:n.provider.groups.filter(a=>S(e,`${r}:${a.group}`)).map(a=>({group:a.group,leaves:o.filter(({leaf:d})=>d.group===a.group).map(({tool:d,leaf:l})=>({tool:d,description:l.description,summary:l.summary,params:l.params,output:l.output}))})),commands:n.commands.roots.flatMap(a=>{const d=J(a,r,e);return d?[d]:[]})}}c(Be,"providerTools");function he(e){return ge(e.headers["crtr-receipt"],e)}c(he,"onProviderResponse");function G(e){if(!(e.class==="person"||!e.sub||!e.grantee))return{grantee:e.grantee,spend_cap:Y(e.grantee,e.sub)?.spend_cap??null}}c(G,"capTarget");function we(e){if(e.class==="person"){const r=Z()?.sub;if(!r)throw new u("grant_revoked","This machine has no active crouter cloud grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:r,grantee:r}}if(!e.sub||!e.grantee)throw new u("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}c(we,"identity");function _e(e,r){return[`${e.id}:${r.group}`]}c(_e,"callScope");function be(e,r){const n=O(e.commands,e.id).find(o=>o.tool===r);if(!n)throw new u("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!n.leaf.group)throw new u("not_found",`tool "${r}" has no group`,{param:"tool"});return n.leaf}c(be,"leafFor");function H(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}c(H,"errorBody");function ye(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const n=Number(r);if(Number.isFinite(n)&&n>=0)return n;const o=Date.parse(r);return Number.isFinite(o)?Math.max(0,Math.ceil((o-Date.now())/1e3)):void 0}c(ye,"retryAfter");function L(e,r,n,o=!1){const i=H(r),a=typeof i.code=="string"?i.code:void 0,d=typeof i.message=="string"?i.message:`provider answered HTTP ${e}`,l=typeof i.field=="string"?i.field:void 0,g={origin:"provider",details:{code:a??null,message:d,...l?{field:l}:{},...typeof i.next=="string"?{next:i.next}:{},...i.received!==void 0?{received:i.received}:{},status:e},...l?{param:l}:{}};return e===429?new u("provider_rate_limited",d,{...g,retry_after_s:ye(n["retry-after"])}):e===401||e>=500?new u("provider_unavailable",d,g):e===403&&a==="provider_refused"?new u("provider_refused",d,g):e===403&&a==="approval_required"?new u("provider_error",o?"provider asked for approval again":"provider approval question is invalid",{...g,status:e}):new u("provider_error",d,{...g,status:e})}c(L,"mapProviderResponse");function z(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.endpoint,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}c(z,"approvalShape");async function De(e,r,n){const o=new AbortController,i=c(()=>o.abort(n.signal.reason),"abort");n.signal.aborted?i():n.signal.addEventListener("abort",i,{once:!0});let a=n.requestId;const d=le({request_id:a,run_id:e.run_id,node_id:e.node_id,controller:o});let l,v,g,b,y,h,w,A="allowed";try{try{await oe(r.provider)}catch{throw ne(r.provider)}let t=await M(r.provider,r.version),k=!1,m,q,_,j=!1,$=!1;for(;;){const f=be(t,r.tool);g=`${t.id}:${f.group}`,X(e,t.id,f.group),B(G(e));let F=null;if(r.approval&&!j){const s=ae(e,r,z(t,f));_=s.option,F=s.prepared,w=r.approval,a=s.requestId,j=!0}const R={params:f.params.filter(s=>"type"in s&&s.type==="file").map(s=>s.name),output:f.output.find(s=>s.type==="file")?.name??null},U=JSON.stringify(R);if(q!==void 0&&q!==U)throw new u("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});q=U,F&&(m=await D(e,R,r.arguments,F,{signal:o.signal})),m??=await ie(e,R,r.arguments,{signal:o.signal});const{sub:I,grantee:x}=we(e);B(G(e));const E=await pe({audience:t.transport.endpoint,sub:I,grantee:x,initiating_app:x,scope:_e(t,f)},{signal:o.signal});b=E.jti;const K={registration:{name:t.id,endpoint:t.transport.endpoint},...t.commands.baseUrl?{baseUrl:t.commands.baseUrl}:{},...t.commands.timeouts?{timeouts:t.commands.timeouts}:{},params:f.params,rest:f.rest,output:f.output,commandPath:[t.id,...r.tool.split(" ")]},Q=ce(K,m.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:a,fileMode:"pass-through",extraHeaders:{...e.run_id?{"Crtr-Run-Id":e.run_id}:{},..._?{"Crtr-Approval":_}:{},...m.headers}});v??=Date.now()+(t.commands.timeouts?.requestMs??3e4);let p;try{if(p=await fe(Q,{connectMs:t.commands.timeouts?.connectMs,requestMs:t.commands.timeouts?.requestMs,deadlineAt:v,signal:o.signal}),o.signal.aborted)throw o.signal.reason??new Error("provider call aborted")}catch(s){throw o.signal.aborted?o.signal.reason??s:s instanceof me?new u("provider_unavailable",s.message,{origin:"provider",details:{status:null,code:s.kind}}):s}if(y??=he({provider:t.id,tool:r.tool,request_id:a,run_id:e.run_id,sub:I,grantee:x,ledger_grantee:e.class==="person"?ee:x,jti:E.jti,status:p.status,headers:p.headers}),h=p.headers["crtr-responder"],p.status===401&&ue(E.key),p.status>=200&&p.status<300){const s=p.body;if(!s||typeof s!="object"||Array.isArray(s))throw new u("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:p.status,message:"provider result is not a JSON object"}});let T;try{T=await se(e,m,s,{signal:o.signal})}catch(P){throw P instanceof u&&P.code==="provider_error"?new u("provider_bad_response",P.message,{origin:"provider",details:{status:p.status,message:P.message}}):P}l=T.cleanup;const C=W(f.output,T.result);if(C)throw new u("provider_bad_response",`provider result has an invalid output field "${C.field}"`,{origin:"provider",param:C.field,details:{status:p.status,...C}});if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");return{status:200,body:T.result}}const N=H(p.body);if(p.status===403&&N.code==="approval_required"&&!_&&!$){$=!0;const s=await de(e,r,a,N.question,o.signal,z(t,f),m);if(w=s.questionId,_=s.option,m=await D(e,R,r.arguments,m,{signal:o.signal}),v=void 0,o.signal.aborted)throw new u("user_denied","the approval wait was canceled");continue}if(p.status===403&&N.code==="approval_required"&&_)throw L(p.status,p.body,p.headers,!0);if(p.status!==401&&N.manifest_stale===!0&&!k){k=!0,await te(t.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}}),t=await M(t.id,r.version);continue}throw L(p.status,p.body,p.headers,!!_)}}catch(t){throw t instanceof u&&t.question_id&&(w=t.question_id),A=`refused:${t instanceof u?t.code:"server_error"}`,l?.(),t}finally{try{ve({request_id:a,caller:e,provider:r.provider,tool:r.tool,scope_checked:g,outcome:A,jti:b,receipt_id:y,responder:h,question_id:w})}finally{d()}n.signal.removeEventListener("abort",i)}}c(De,"callProvider");export{De as callProvider,L as mapProviderResponse,he as onProviderResponse,O as providerLeaves,Be as providerTools};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var m=Object.defineProperty;var d=(r,t)=>m(r,"name",{value:t,configurable:!0});import{RunError as _}from"../runs/operations.js";import{errorCodes as i}from"../../api/error-codes.js";const g={provider_error:{type:"invalid_request_error",status:400,retryable:!1},provider_rate_limited:i.provider_rate_limited,provider_unavailable:i.provider_unavailable,provider_refused:i.provider_refused,provider_not_connected:i.provider_not_connected,provider_version_retired:i.provider_version_retired,storage_unavailable:i.storage_unavailable,approval_pending:{...i.approval_pending,user_action:"answer_question"},user_denied:i.user_denied},b={unauthorized:i.unauthorized,grant_revoked:i.grant_revoked,scope_missing:i.scope_missing,permission_error:i.permission_error,invalid_request:i.invalid_request,not_found:i.not_found,not_implemented:i.not_implemented,runtime_starting:i.runtime_starting,server_error:i.server_error,provider_bad_response:i.provider_bad_response};function a(r,t){return g[r]??b[r]??(Object.prototype.hasOwnProperty.call(i,r)?i[r]:void 0)??{type:t===401?"authentication_error":t===403?"permission_error":t===429?"rate_limit_error":t>=500?"server_error":"invalid_request_error",status:t,retryable:t>=500}}d(a,"codeRow");class u extends _{static{d(this,"ProviderError")}origin;type;retry_after_s;question_id;user_action;run_id;constructor(t,o,n={}){const e=a(t,n.status??500);super(n.status??e.status,t,o,n.param,n.details,n.scope,e.retryable),this.type=e.type,this.origin=n.origin??"runtime",n.retry_after_s!==void 0&&(this.retry_after_s=n.retry_after_s),n.question_id!==void 0&&(this.question_id=n.question_id),n.run_id!==void 0&&(this.run_id=n.run_id),e.user_action!==void 0&&(this.user_action=e.user_action)}}function O(r){return new u("scope_missing",`missing scope ${r}`,{scope:r})}d(O,"scopeMissing");function q(r){if(!(r instanceof Error))return null;const{status:t,code:o,details:n,retryable:e}=r;return typeof t!="number"||typeof o!="string"||t<400||t>599?null:new _(t,o,r.message,void 0,n,void 0,typeof e=="boolean"?e:a(o,t).retryable)}d(q,"shaped");function h(r){if(r.code==="unauthorized"||r.code==="grant_revoked")return"reconnect";if(r.code==="scope_missing"&&r.scope)return`grant_scope:${r.scope}`}d(h,"directoryAction");function w(r,t,o,n){const e=r instanceof _?r:q(r)??new u("server_error","provider request failed"),s=e instanceof u?e:null,v=a(e.code,e.status),c=s?.run_id??n,p=s?.user_action??(e.code==="cap_exhausted"?"raise_cap":o?h(e):void 0),f=e.code==="cap_exhausted"?e.details?.reset_at:void 0,y={code:e.code,message:e.message,...e.details===void 0?{}:{details:e.details},type:s?.type??v.type,...e.param?{param:e.param}:{},origin:s?.origin??"runtime",retryable:e.retryable,...s?.retry_after_s===void 0?{}:{retry_after_s:s.retry_after_s},...typeof f=="string"?{reset_at:f}:{},request_id:t,...c?{run_id:c}:{},...s?.question_id?{question_id:s.question_id}:{},...p?{user_action:p}:{},...e.scope?{scope:e.scope}:{}},l={"Request-Id":t};return s?.retry_after_s!==void 0&&(l["Retry-After"]=String(s.retry_after_s)),{status:e.status,body:{error:y},headers:l}}d(w,"providerErrorBody");export{g as PROVIDER_CODES,u as ProviderError,a as codeRow,w as providerErrorBody,O as scopeMissing};
|
|
@@ -6,4 +6,6 @@ export declare function acceptProviderReceipt(value: string | string[] | undefin
|
|
|
6
6
|
sub: string;
|
|
7
7
|
grantee: string;
|
|
8
8
|
run_id: string | null;
|
|
9
|
+
/** Whose month the receipt adds to when it differs from the receipt's grantee (the person's calls count under `app:terminal`). */
|
|
10
|
+
ledger_grantee?: string;
|
|
9
11
|
}): string | undefined;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
(provider, receipt_id, grantee, run_id, usd_equivalent, received_at) VALUES (?, ?, ?, ?, ?, ?)`).run(
|
|
3
|
-
ON CONFLICT(grantee, month) DO UPDATE SET usd_equivalent = usd_equivalent + excluded.usd_equivalent`).run(t.grantee,p,e.usd_equivalent),t.run_id){const o=s(i,"SELECT usage_json FROM nodes WHERE node_id = ? AND run_id = ?").get(t.run_id,t.run_id);if(o){const u=o.usage_json?JSON.parse(o.usage_json):{};u.cost_usd=(typeof u.cost_usd=="number"?u.cost_usd:0)+e.usd_equivalent,s(i,"UPDATE nodes SET usage_json = ? WHERE node_id = ?").run(JSON.stringify(u),t.run_id)}}}),e.receipt_id}_(E,"acceptProviderReceipt");export{E as acceptProviderReceipt};
|
|
1
|
+
var p=Object.defineProperty;var _=(t,r)=>p(t,"name",{value:r,configurable:!0});import{prepared as o}from"../sqlite-statements.js";import{withCanvasWrite as a}from"../canvas/db.js";import{addUsage as g}from"../usage/ledger.js";function E(t,r){const s=Array.isArray(t)?t[0]:t;if(!s||s.length>16384)return;let i;try{i=JSON.parse(s)}catch{return}if(!i||typeof i!="object"||Array.isArray(i))return;const e=i;if(!(typeof e.receipt_id!="string"||!e.receipt_id||e.receipt_id.length>256||e.provider!==`app:${r.provider}`||e.tool!==r.tool||e.request_id!==r.request_id||e.sub!==r.sub||e.grantee!==r.grantee||e.payer!==r.sub||(e.run_id??null)!==r.run_id||typeof e.unit!="string"||!e.unit||typeof e.units!="number"||!Number.isFinite(e.units)||e.units<0||typeof e.time!="string"||!Number.isFinite(Date.parse(e.time))||typeof e.usd_equivalent!="number"||!Number.isFinite(e.usd_equivalent)||e.usd_equivalent<0))return a(n=>{if(o(n,`INSERT OR IGNORE INTO provider_receipts
|
|
2
|
+
(provider, receipt_id, grantee, run_id, usd_equivalent, received_at) VALUES (?, ?, ?, ?, ?, ?)`).run(r.provider,e.receipt_id,r.grantee,r.run_id,e.usd_equivalent,new Date().toISOString()).changes&&(g(n,{grantee:r.ledger_grantee??r.grantee,payer:"user",source:`provider:${r.provider}`,served_model:"",input_tokens:0,output_tokens:0,cached_tokens:0,usd_equivalent:e.usd_equivalent}),r.run_id)){const d=o(n,"SELECT usage_json FROM nodes WHERE node_id = ? AND run_id = ?").get(r.run_id,r.run_id);if(d){const u=d.usage_json?JSON.parse(d.usage_json):{};u.cost_usd=(typeof u.cost_usd=="number"?u.cost_usd:0)+e.usd_equivalent,o(n,"UPDATE nodes SET usage_json = ? WHERE node_id = ?").run(JSON.stringify(u),r.run_id)}}}),e.receipt_id}_(E,"acceptProviderReceipt");export{E as acceptProviderReceipt};
|
|
@@ -19,5 +19,7 @@ export declare function runsSeeEachOther(a: RunPlace, b: RunPlace): boolean;
|
|
|
19
19
|
/** Whether a node caller of `app` may reach `target` (a node id) under run confinement.
|
|
20
20
|
* Confinement applies only within one app; cross-app reach stays with the scope rules. */
|
|
21
21
|
export declare function nodeMayReach(callerNode: string, callerApp: string, targetNode: string, targetApp: string | null, db?: DatabaseSync): boolean;
|
|
22
|
+
/** The same question for a caller that has a place but no node (a cron command: its origin run). */
|
|
23
|
+
export declare function placeMayReach(caller: RunPlace, callerApp: string, targetNode: string, targetApp: string | null, db?: DatabaseSync): boolean;
|
|
22
24
|
/** The node ids of one run (its root and every node carrying its `run_id`). */
|
|
23
25
|
export declare function runNodeIds(run: string, db?: DatabaseSync): string[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
LEFT JOIN nodes r ON r.node_id = n.run_id WHERE n.node_id = ?`).get(e);if(!t){const
|
|
1
|
+
var a=Object.defineProperty;var n=(e,r)=>a(e,"name",{value:r,configurable:!0});import{openDb as i,prepared as u}from"../canvas/db.js";function l(e,r=i()){const t=u(r,`SELECT n.run_id, n.isolated, r.isolated AS root_isolated FROM nodes n
|
|
2
|
+
LEFT JOIN nodes r ON r.node_id = n.run_id WHERE n.node_id = ?`).get(e);if(!t){const o=u(r,"SELECT run_id FROM deleted_isolated_nodes WHERE node_id = ?").get(e);return o?{run:o.run_id,isolated:!0}:null}return{run:t.run_id??e,isolated:t.isolated===1||t.root_isolated===1}}n(l,"runPlace");function R(e,r=i()){const t=l(e,r);t?.isolated&&u(r,"INSERT OR REPLACE INTO deleted_isolated_nodes (node_id, run_id) VALUES (?, ?)").run(e,t.run)}n(R,"rememberDeletedIsolated");function f(e,r=i()){const t=u(r,"SELECT registered_by FROM custom_objects WHERE object = ?").get(e);return t?.registered_by?l(t.registered_by,r)??{run:t.registered_by,isolated:!1}:null}n(f,"objectPlace");function _(e,r){return e.run===r.run||!e.isolated&&!r.isolated}n(_,"runsSeeEachOther");function O(e,r,t,o,s=i()){const d=l(e,s);return d===null?!0:c(d,r,t,o,s)}n(O,"nodeMayReach");function c(e,r,t,o,s=i()){if((o??"app:terminal")!==r)return!0;const d=l(t,s);return d===null?!e.isolated:_(e,d)}n(c,"placeMayReach");function g(e,r=i()){return u(r,"SELECT node_id FROM nodes WHERE run_id = ? OR node_id = ?").all(e,e).map(t=>t.node_id)}n(g,"runNodeIds");export{O as nodeMayReach,f as objectPlace,c as placeMayReach,R as rememberDeletedIsolated,g as runNodeIds,l as runPlace,_ as runsSeeEachOther};
|
|
@@ -28,6 +28,8 @@ export type RunStatus = 'running' | 'waiting_on_user' | 'idle' | 'settled';
|
|
|
28
28
|
/** Whether the run has a turn in flight or work owed to it (the `running` run status). */
|
|
29
29
|
export declare function runIsRunning(runId: string): boolean;
|
|
30
30
|
export declare function runObject(root: NodeMeta): Record<string, unknown>;
|
|
31
|
+
/** How long a kept key replays: a record this old or older reads as absent. The SDK's conformance scenarios hold FakeCloud to it. */
|
|
32
|
+
export declare const IDEMPOTENCY_WINDOW_MS = 86400000;
|
|
31
33
|
/** An `Idempotency-Key` header value: absent, or 1–255 characters. */
|
|
32
34
|
export declare function idempotencyKeyOf(header: unknown): string | undefined;
|
|
33
35
|
/** Delete keys past their window; returns how many were removed. */
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
var
|
|
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||
|
|
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(
|
|
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(
|
|
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
|
|
2
|
-
`))>=0;){const
|
|
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,16 @@ 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;
|
|
37
|
+
cron?: {
|
|
38
|
+
executionId: string;
|
|
39
|
+
anchorDir?: string;
|
|
40
|
+
};
|
|
31
41
|
}): Promise<RunningCommand>;
|
|
32
42
|
/** Start one admitted-PATH bash command in its own process group. The returned
|
|
33
43
|
* handle exposes the pid immediately for callers that must record ownership
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var H=Object.defineProperty;var a=(e,
|
|
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},...e.cron===void 0?{}:{cron:e.cron},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,7 +12,14 @@ 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;
|
|
18
|
+
/** A cron execution (launch id `cron-<execution_id>`): its only daemon path is `cli.sock` in its own run folder
|
|
19
|
+
* `nodeRunDir(nodeId)`, so neither `api.sock` nor `store.sock` is mounted. `anchorDir` is an anchored cron's anchor agent folder. */
|
|
20
|
+
cron?: {
|
|
21
|
+
anchorDir?: string;
|
|
22
|
+
};
|
|
16
23
|
/** Hook code alone may be mounted from the installed plugin root, read-only. */
|
|
17
24
|
pluginRoot?: string;
|
|
18
25
|
/** Missing granted directories are omitted and reported to the daemon. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var W=Object.defineProperty;var l=(t,o)=>W(t,"name",{value:o,configurable:!0});import{lstatSync as y}from"node:fs";import{join as c,relative as f,sep as a}from"node:path";import{appSpace as m,appsRoot as S,runtimeRoot as h,socketDir as w,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 o=[];for(const e of t.otherFiles){const s=e.holder==="user"?R():m(e.holder).files;o.push({source:e.path==="."?s:c(s,e.path),writable:e.writable})}for(const e of t.otherNodes)o.push({source:m(e).nodes,writable:!1});return o}l(x,"grantedMounts");function d(t,o){const e=f(t,o);return e===""||e!==".."&&!e.startsWith(`..${a}`)&&!e.startsWith(a)}l(d,"beneath");function M(t,o){return d(R(),o)||d(S(),o)&&!d(m(t).root,o)}l(M,"isGrantedSource");function j(t,o){return o.some(e=>d(e.source,t.source)&&(e.writable||!t.writable))}l(j,"mountAllowed");function z(t,o){try{const e=x(E(t.app,t.sub,t.grantId,t.scopes));return o.mounts.every(s=>!M(t.app,s.source)||j(s,e))}catch{return!1}}l(z,"launchStillGranted");function B(t){const o=m(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 s=t.folder??(t.isolated?I(t.nodeId,t.app):void 0);if(s&&t.cwd!==void 0&&!d(s,t.cwd))throw new Error("isolated cwd must be inside the node folder");const p=new Map,i=l((r,n,u=!1,F=!1)=>{if(u)try{y(r)}catch(b){if(b.code!=="ENOENT")throw b;F&&t.auditMissing(r);return}p.set(r,n||(p.get(r)??!1))},"add");for(const r of["/usr","/bin","/lib","/lib64","/opt/crouter","/etc/passwd","/etc/group","/etc/resolv.conf","/etc/hosts","/etc/ssl","/etc/ca-certificates"])i(r,!1,!0);t.withoutNode||i(I(t.nodeId,t.app),!0),t.folder!==void 0&&i(t.folder,!0),t.cron?.anchorDir!==void 0&&i(t.cron.anchorDir,!0),t.isolated||i(o.home,!0),t.isolated||i(o.files,!0),i(o.cache,!0,!0),(!t.withoutNode||t.cron!==void 0)&&i(k(t.nodeId),!0),t.cron===void 0&&(i(c(w(),"api.sock"),!0),i(c(w(),"store.sock"),!0));for(const r of x(e))i(r.source,r.writable,!0,!0);if(t.pluginRoot!==void 0){const r=l(u=>t.pluginRoot===u||f(u,t.pluginRoot)!==".."&&!f(u,t.pluginRoot).startsWith(`..${a}`),"within"),n=c(h(),"plugins");if([...p].some(([u])=>r(u))){if(p.get(t.pluginRoot)===!0)throw new Error("hook root cannot also be writable");i(t.pluginRoot,!1)}else if(r(n)&&t.pluginRoot!==n)i(t.pluginRoot,!1);else if(r("/opt/crouter"))i(t.pluginRoot,!1);else throw new Error("hook root is not readable with this app\u2019s mounts")}const D=t.cwd??s??o.files,g=[...p].sort(([r],[n])=>r.split(a).length-n.split(a).length||r.localeCompare(n)),N=c(h(),"nodes",t.nodeId,"job","broker.log"),v=process.platform!=="linux"?[k(t.nodeId),c(w(),"api.sock"),c(w(),"store.sock")]:[];if(g.some(([r])=>r!==t.pluginRoot&&!v.includes(r)&&(r===h()||!f(h(),r).startsWith("..")&&!f(h(),r).startsWith(a))))throw new Error("runtime records cannot be mounted");return{nodeId:t.nodeId,uid:A(t.app),mounts:g.map(([r,n])=>({source:r,writable:n})),cwd:D,env:t.env,command:t.command,log:N,...t.isolated?{network:"none"}:{}}}l(B,"compileLaunch");export{B as compileLaunch,x as grantedMounts,M as isGrantedSource,z as launchStillGranted,j as mountAllowed};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type CapturedExit } from '../../launcher/spec.js';
|
|
2
|
+
/** The launcher's node id for one cron execution; its run folder is `nodeRunDir(<this>)`. */
|
|
3
|
+
export declare function cronLaunchId(executionId: string): string;
|
|
2
4
|
export interface AppProcess {
|
|
3
5
|
pid: number;
|
|
4
6
|
completion: Promise<CapturedExit>;
|
|
@@ -9,6 +11,20 @@ export interface AppProcessOptions {
|
|
|
9
11
|
/** For node-backed work the persisted node setting always takes precedence. */
|
|
10
12
|
isolated?: boolean;
|
|
11
13
|
app?: string;
|
|
14
|
+
/** 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. */
|
|
15
|
+
principal?: {
|
|
16
|
+
sub: string;
|
|
17
|
+
grantId: string | null;
|
|
18
|
+
scopes: string[] | null;
|
|
19
|
+
};
|
|
20
|
+
/** A cron execution: launch id `cron-<executionId>`, no node, and no `api.sock`/`store.sock` — its only API path is
|
|
21
|
+
* its own execution folder's `cli.sock`. `anchorDir` is an anchored cron's anchor agent folder (rw). Needs `principal`. */
|
|
22
|
+
cron?: {
|
|
23
|
+
executionId: string;
|
|
24
|
+
anchorDir?: string;
|
|
25
|
+
};
|
|
26
|
+
/** The folder an isolated process without a node runs in (an isolated cron's own folder). */
|
|
27
|
+
folder?: string;
|
|
12
28
|
command: string[];
|
|
13
29
|
cwd: string;
|
|
14
30
|
env: NodeJS.ProcessEnv;
|
|
@@ -33,9 +49,13 @@ export declare function __setAppProcessesSandboxedForTest(value: boolean | undef
|
|
|
33
49
|
* `app`. This is the launcher's own `cwd is not mounted` rule applied to the mounts
|
|
34
50
|
* `compileLaunch` produces, so a caller can refuse a directory at arm time instead of
|
|
35
51
|
* 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
|
|
52
|
+
export declare function cwdIsMounted(app: string, cwd: string, nodeId?: string, isolated?: boolean, folder?: string, principal?: {
|
|
53
|
+
sub: string;
|
|
54
|
+
grantId: string | null;
|
|
55
|
+
scopes: string[] | null;
|
|
56
|
+
}): boolean;
|
|
37
57
|
export declare function stopAppProcess(pid: number, signal?: 'SIGTERM' | 'SIGKILL', nodeId?: string): Promise<void>;
|
|
38
58
|
/** A node-backed isolated process always starts in its own agent folder.
|
|
39
59
|
* Callers validate explicitly requested cwd separately (e.g. hook execution). */
|
|
40
|
-
export declare function appProcessCwd(nodeId: string, app: string, cwd: string, isolated: boolean): string;
|
|
60
|
+
export declare function appProcessCwd(nodeId: string, app: string, cwd: string, isolated: boolean, folder?: string): string;
|
|
41
61
|
export declare function launchAppProcess(options: AppProcessOptions): Promise<AppProcess>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var C=Object.defineProperty;var i=(e,r)=>C(e,"name",{value:r,configurable:!0});import{existsSync as v}from"node:fs";import{randomUUID as $}from"node:crypto";import{join as h}from"node:path";import{getNode as _}from"../canvas/canvas.js";import{nodeAgentDir as N,nodeHome as O,nodeRunDir as k}from"../canvas/paths.js";import{appSpace as K}from"../layout.js";import{activeGrant as w,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 q(e){return`cron-${e}`}i(q,"cronLaunchId");function ie(){if(process.platform!=="linux")return!1;if(v(A))return!0;throw new Error("root launcher socket is unavailable; refusing an unsandboxed app process")}i(ie,"appProcessSandboxed");function se(){return D??(process.platform==="linux"&&v(A))}i(se,"appProcessesSandboxed");let D;function ce(e){D=e}i(ce,"__setAppProcessesSandboxedForTest");function fe(e,r,n,o=!1,t,d){const u=n===void 0?null:_(n),s=d!==void 0?d.grantId?w(d.grantId):I(e,d.sub):u?null:R(e);if(d!==void 0?!s&&!(d.grantId===null&&e==="app:terminal"):!u&&!s)throw new Error(`no active grant for ${e}`);return y({nodeId:n??"proc-cwd-check",app:e,...d!==void 0?{sub:d.sub,grantId:s?.id??null,scopes:d.scopes}:{sub:u?u.sub??"user:local":s.sub,grantId:u?u.grant_id??null:s.id,scopes:u?.scopes??null},command:["true"],env:{},...n===void 0?{withoutNode:!0}:{},...t===void 0?{}:{folder:t},isolated:u?.isolated===!0||o,auditMissing:i(()=>{},"auditMissing")}).mounts.some(({source:c})=>r===c||r.startsWith(c==="/"?"/":`${c}/`))}i(fe,"cwdIsMounted");async function G(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(G,"stopAppProcess");function U(e,r,n,o,t){return o?t??N(e,r):n}i(U,"appProcessCwd");async function le(e){if(e.cron!==void 0&&(e.nodeId!==void 0||e.principal===void 0))throw new Error("a cron launch has a stored principal and no node");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?w(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?w(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.cron===void 0?e.nodeId??`proc-${$()}`:q(e.cron.executionId),u=r?.isolated===!0||e.isolated===!0;if(u&&!r&&e.folder===void 0)throw new Error("isolated process requires a live node");const s=K(n),m=U(d,n,e.cwd,u,e.folder),c=r?O(d,n,u):u&&e.folder!==void 0?h(e.folder,"home"):s.home,L=M(Object.fromEntries(Object.entries({...e.env,...process.platform==="linux"?j(n):{},HOME:c,PI_CODING_AGENT_DIR:h(c,".pi","agent"),PWD:m,...e.nodeId===void 0&&e.cron===void 0?{}:{CRTR_API_SOCKET:h(k(d),"cli.sock")}}).filter(a=>a[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},P=i(()=>{const a=y({...E,command:e.command,env:L,...e.nodeId===void 0?{withoutNode:!0}:{},isolated:u,cwd:m,...e.folder===void 0?{}:{folder:e.folder},...e.cron===void 0?{}:{cron:e.cron.anchorDir===void 0?{}:{anchorDir:e.cron.anchorDir}},...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&&(a.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 a.capture={stdoutBytes:e.stdoutBytes??256*1024,stderrBytes:e.stderrBytes??256*1024,...e.stderrTail?{stderrTail:!0}:{},...e.stdin===void 0?{}:{stdin:e.stdin}};return a},"compile");let f;for(let a=0;a<2&&f===void 0;a++){const g=S(),x=P(),l=await b({operation:"launch",spec:x});if(!("pid"in l))throw new Error("launcher did not return a process id");if(S()===g||H(E,x)){f=l.pid;break}T({level:"info",event:"process.launch.regranted",...e.nodeId===void 0?{}:{node_id:e.nodeId},fields:{pid:l.pid}}),await G(l.pid,"SIGKILL").catch(()=>{})}if(f===void 0)throw new Error("grant changed during every launch attempt");const p=f;return{pid:p,get completion(){return b({operation:"wait",pid:p}).then(a=>{if(!("result"in a))throw new Error("launcher did not return process completion");return a.result})},stop:i((a="SIGKILL")=>G(p,a,e.cron===void 0?e.nodeId:d),"stop")}}i(le,"launchAppProcess");export{ce as __setAppProcessesSandboxedForTest,U as appProcessCwd,ie as appProcessSandboxed,se as appProcessesSandboxed,q as cronLaunchId,fe as cwdIsMounted,le as launchAppProcess,G 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};
|