@north-light/cli 0.3.572 → 0.3.574

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.
@@ -0,0 +1,10 @@
1
+ import { type ActiveGrant } from './mirror.js';
2
+ /** The request's person, or on a Mac the install's person. Null when there is none: a hosted first boot before its first grant snapshot, or a cloud
3
+ * role outside a request (a lane there works for the person on the row it claimed; it never borrows "the only grant"). */
4
+ export declare function callerPerson(): Promise<string | null>;
5
+ /** The terminal grant of the request person (or, on a Mac, the install's person); null when the call has no person. */
6
+ export declare function callerTerminalGrant(): Promise<ActiveGrant | null>;
7
+ /** The terminal grant of the person a node row belongs to: its own `sub`, else (a node born before its person was known) the caller's. */
8
+ export declare function nodeTerminalGrant(node: {
9
+ sub?: string | null;
10
+ }): Promise<ActiveGrant | null>;
@@ -0,0 +1 @@
1
+ var a=Object.defineProperty;var n=(r,e)=>a(r,"name",{value:e,configurable:!0});import{PersonOutsideRequestError as o,PersonUnresolvedError as i,personIdAsync as s}from"../canvas/person.js";import{terminalGrant as t}from"./mirror.js";async function c(){try{return await s()}catch(r){if(r instanceof i||r instanceof o)return null;throw r}}n(c,"callerPerson");async function l(){const r=await c();return r===null?null:await t(r)}n(l,"callerTerminalGrant");async function p(r){return r.sub?await t(r.sub):await l()}n(p,"nodeTerminalGrant");export{c as callerPerson,l as callerTerminalGrant,p as nodeTerminalGrant};
@@ -1 +1 @@
1
- var b=Object.defineProperty;var a=(t,e)=>b(t,"name",{value:e,configurable:!0});function d(t){return typeof t=="string"?t:""}a(d,"text");function n(t,e,o={}){return{kind:t,matchedOn:e,...o}}a(n,"signal");function i(t,e){return e.matchedOn==="text"?{...t,textKind:e.kind,textMatch:e.match,textMatches:e.textMatches}:t}a(i,"withTextMatch");const h=[["rate-limit",/rate.?limit|\b429\b|too many requests|usage[ _]limit|usage_not_included|usagelimiterror|insufficient_quota|quota exceeded/i],["protocol",/\b413\b|payload too large|entity too large|request too large|entity\.too\.large|body exceeds/i],["auth",/invalid_grant|refresh token|unauthori[sz]ed|invalid.?api.?key|authentication failed|\b401\b|\b403\b/i],["context-overflow",/context overflow recovery failed/i],["protocol",/websocket.*(?:closed.*\b(?:1002|1003|1007|1008|1009|1010)\b|protocol error|policy violation|unsupported data|invalid (?:frame|payload)|message too big|mandatory extension)/i],["overloaded-only",/lock file is already being held/i],["overloaded",/overloaded|temporarily unavailable/i],["service-unavailable",/service.?unavailable/i],["overloaded-only",/internal server error|api.?error|\b5\d\d\b|capacity|server.{0,3}busy/i],["connection",/connection|econnreset|etimedout|enotfound|econnrefused|network|fetch failed|socket hang|timed? out|timeout|websocket\s+(?:was\s+)?closed|websocket stream closed before response\.completed|websocket\s+error|websocket.*interrupt/i],["socket",/socket/i],["connection-only",/eai_again|epipe|ehostunreach|enetunreach|\bterminated\b|other side closed|premature close/i]];function f(t){const e=h.flatMap(([c,r])=>{const s=t.match(r);return s?[{kind:c,match:s[0]}]:[]}),o=e[0];return o===void 0?n("other","none"):{...n(o.kind,"text",{match:o.match}),textMatches:e}}a(f,"recognizeText");function E(t){const e=t.retryAfterMs,o=f(t.message??""),c=d(t.code)||d(t.errno);if(/^(ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOENT|ENOTFOUND|EAI_AGAIN|EPIPE|EHOSTUNREACH|ENETUNREACH)$/i.test(c))return i(n("connection","errno",{retryAfterMs:e}),o);if(/^ELOCKED$/i.test(c))return i(n("overloaded","errno",{retryAfterMs:e}),o);const r=typeof t.status=="number"?t.status:void 0;if(r!==void 0){if(r===401||r===403)return i(n("auth","status",{status:r,retryAfterMs:e}),o);if(r===429)return i(n("rate-limit","status",{status:r,retryAfterMs:e}),o);if(r===500||r===502||r===503||r===504)return i(n("transient-http","status",{status:r,retryAfterMs:e}),o);if(r>=500&&r<=599)return i(n("overloaded","status",{status:r,retryAfterMs:e}),o);if(r>=400&&r<=499)return i(n("protocol","status",{status:r,retryAfterMs:e}),o)}const s=typeof t.wsCloseCode=="number"?t.wsCloseCode:void 0;if(s===1008)return i(n("protocol","websocket-close",{retryAfterMs:e}),o);if(s===1006)return i(n("connection","websocket-close",{retryAfterMs:e}),o);if(s===1011){const l=d(t.wsCloseReason),u=f(l);return/no running broker/i.test(l)||u.kind==="connection"||u.kind==="connection-only"?i(n("connection","websocket-close",{retryAfterMs:e}),o):i(n("other","websocket-close",{retryAfterMs:e}),o)}return e===void 0?o:{...o,retryAfterMs:e}}a(E,"recognizeProviderError");export{E as recognizeProviderError};
1
+ var b=Object.defineProperty;var a=(t,e)=>b(t,"name",{value:e,configurable:!0});function d(t){return typeof t=="string"?t:""}a(d,"text");function n(t,e,o={}){return{kind:t,matchedOn:e,...o}}a(n,"signal");function i(t,e){return e.matchedOn==="text"?{...t,textKind:e.kind,textMatch:e.match,textMatches:e.textMatches}:t}a(i,"withTextMatch");const h=[["protocol",/workspace api usage limits?/i],["rate-limit",/rate.?limit|\b429\b|too many requests|usage[ _]limit|usage_not_included|usagelimiterror|insufficient_quota|quota exceeded/i],["protocol",/\b413\b|payload too large|entity too large|request too large|entity\.too\.large|body exceeds/i],["auth",/invalid_grant|refresh token|unauthori[sz]ed|invalid.?api.?key|authentication failed|\b401\b|\b403\b/i],["context-overflow",/context overflow recovery failed/i],["protocol",/websocket.*(?:closed.*\b(?:1002|1003|1007|1008|1009|1010)\b|protocol error|policy violation|unsupported data|invalid (?:frame|payload)|message too big|mandatory extension)/i],["overloaded-only",/lock file is already being held/i],["overloaded",/overloaded|temporarily unavailable/i],["service-unavailable",/service.?unavailable/i],["overloaded-only",/internal server error|api.?error|\b5\d\d\b|capacity|server.{0,3}busy/i],["connection",/connection|econnreset|etimedout|enotfound|econnrefused|network|fetch failed|socket hang|timed? out|timeout|websocket\s+(?:was\s+)?closed|websocket stream closed before response\.completed|websocket\s+error|websocket.*interrupt/i],["socket",/socket/i],["connection-only",/eai_again|epipe|ehostunreach|enetunreach|\bterminated\b|other side closed|premature close/i]];function f(t){const e=h.flatMap(([c,r])=>{const s=t.match(r);return s?[{kind:c,match:s[0]}]:[]}),o=e[0];return o===void 0?n("other","none"):{...n(o.kind,"text",{match:o.match}),textMatches:e}}a(f,"recognizeText");function E(t){const e=t.retryAfterMs,o=f(t.message??""),c=d(t.code)||d(t.errno);if(/^(ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOENT|ENOTFOUND|EAI_AGAIN|EPIPE|EHOSTUNREACH|ENETUNREACH)$/i.test(c))return i(n("connection","errno",{retryAfterMs:e}),o);if(/^ELOCKED$/i.test(c))return i(n("overloaded","errno",{retryAfterMs:e}),o);const r=typeof t.status=="number"?t.status:void 0;if(r!==void 0){if(r===401||r===403)return i(n("auth","status",{status:r,retryAfterMs:e}),o);if(r===429)return i(n("rate-limit","status",{status:r,retryAfterMs:e}),o);if(r===500||r===502||r===503||r===504)return i(n("transient-http","status",{status:r,retryAfterMs:e}),o);if(r>=500&&r<=599)return i(n("overloaded","status",{status:r,retryAfterMs:e}),o);if(r>=400&&r<=499)return i(n("protocol","status",{status:r,retryAfterMs:e}),o)}const s=typeof t.wsCloseCode=="number"?t.wsCloseCode:void 0;if(s===1008)return i(n("protocol","websocket-close",{retryAfterMs:e}),o);if(s===1006)return i(n("connection","websocket-close",{retryAfterMs:e}),o);if(s===1011){const l=d(t.wsCloseReason),u=f(l);return/no running broker/i.test(l)||u.kind==="connection"||u.kind==="connection-only"?i(n("connection","websocket-close",{retryAfterMs:e}),o):i(n("other","websocket-close",{retryAfterMs:e}),o)}return e===void 0?o:{...o,retryAfterMs:e}}a(E,"recognizeProviderError");export{E as recognizeProviderError};
@@ -1,4 +1,4 @@
1
- var l=Object.defineProperty;var s=(i,n)=>l(i,"name",{value:n,configurable:!0});import{openDb as a}from"../canvas/db.js";import{runtimeId as h}from"../grants/sync.js";import{terminalGrant as m}from"../grants/mirror.js";async function b(i){const{caller:n}=i,e=n.sub??(n.class==="person"?(await m())?.sub??null:null),t=n.grantee??e,u=e&&t?[e,t,`runtime:${h()}`]:null;await(await a()).prepare(`INSERT INTO audit_rows
1
+ var l=Object.defineProperty;var s=(e,n)=>l(e,"name",{value:n,configurable:!0});import{openDb as a}from"../canvas/db.js";import{runtimeId as h}from"../grants/sync.js";import{callerTerminalGrant as m}from"../grants/terminal.js";async function b(e){const{caller:n}=e,i=n.sub??(n.class==="person"?(await m())?.sub??null:null),t=n.grantee??i,u=i&&t?[i,t,`runtime:${h()}`]:null;await(await a()).prepare(`INSERT INTO audit_rows
2
2
  (request_id, time, grantee, sub, client_id, class, operation, scope_checked,
3
3
  outcome, node_id, run_id, resource, initiator_chain, provider, tool, jti, receipt_id, responder, question_id)
4
- VALUES (?, ?, ?, ?, ?, ?, 'provider.call', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(i.request_id,new Date().toISOString(),t,e,n.client_id,n.class,i.scope_checked??null,i.outcome,n.node_id,n.run_id,`${i.provider} ${i.tool}`,u?JSON.stringify(u):null,i.provider,i.tool,i.jti??null,i.receipt_id??null,i.responder??null,i.question_id??null)}s(b,"writeProviderAudit");async function E(i,n){const e=[],t=[];i!==null&&(e.push("grantee = ?"),t.push(i)),n.sub!==void 0&&(e.push("sub = ?"),t.push(n.sub)),n.after!==void 0&&(e.push("id < ?"),t.push(n.after));for(const o of["operation","outcome","run_id"])n[o]!==void 0&&(e.push(`${o} = ?`),t.push(n[o]));n.since!==void 0&&(e.push("time >= ?"),t.push(n.since)),n.until!==void 0&&(e.push("time <= ?"),t.push(n.until));const u=await(await a()).prepareUncached(`SELECT * FROM audit_rows ${e.length?`WHERE ${e.join(" AND ")}`:""} ORDER BY id DESC LIMIT ?`).all(...t,n.limit+1),d=u.length>n.limit;d&&u.pop();const p=d?String(u.at(-1)?.id):null;return{rows:u.map(({id:o,initiator_chain:r,...c})=>({...c,initiator_chain:typeof r=="string"?JSON.parse(r):null})),next:p}}s(E,"listAudit");export{E as listAudit,b as writeProviderAudit};
4
+ VALUES (?, ?, ?, ?, ?, ?, 'provider.call', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.request_id,new Date().toISOString(),t,i,n.client_id,n.class,e.scope_checked??null,e.outcome,n.node_id,n.run_id,`${e.provider} ${e.tool}`,u?JSON.stringify(u):null,e.provider,e.tool,e.jti??null,e.receipt_id??null,e.responder??null,e.question_id??null)}s(b,"writeProviderAudit");async function E(e,n){const i=[],t=[];e!==null&&(i.push("grantee = ?"),t.push(e)),n.sub!==void 0&&(i.push("sub = ?"),t.push(n.sub)),n.after!==void 0&&(i.push("id < ?"),t.push(n.after));for(const o of["operation","outcome","run_id"])n[o]!==void 0&&(i.push(`${o} = ?`),t.push(n[o]));n.since!==void 0&&(i.push("time >= ?"),t.push(n.since)),n.until!==void 0&&(i.push("time <= ?"),t.push(n.until));const u=await(await a()).prepareUncached(`SELECT * FROM audit_rows ${i.length?`WHERE ${i.join(" AND ")}`:""} ORDER BY id DESC LIMIT ?`).all(...t,n.limit+1),d=u.length>n.limit;d&&u.pop();const c=d?String(u.at(-1)?.id):null;return{rows:u.map(({id:o,initiator_chain:r,...p})=>({...p,initiator_chain:typeof r=="string"?JSON.parse(r):null})),next:c}}s(E,"listAudit");export{E as listAudit,b as writeProviderAudit};
@@ -40,6 +40,10 @@ export interface ProviderResponseContext {
40
40
  headers: Record<string, string | string[] | undefined>;
41
41
  }
42
42
  export declare function onProviderResponse(context: ProviderResponseContext): Promise<string | undefined>;
43
+ export declare function identity(caller: ProviderCaller): Promise<{
44
+ sub: string;
45
+ grantee: string;
46
+ }>;
43
47
  export declare function mapProviderResponse(status: number, body: unknown, headers: Record<string, string | string[] | undefined>, approved?: boolean): ProviderError;
44
48
  /** `Crtr-Caller` for a provider HTTP call: the calling node and run, that node's profile, and this machine once the runtime is joined (`ownMachineId`
45
49
  * throws on an unjoined runtime). Empty fields are omitted; no field means no header. Never carries a token or scopes. */
@@ -1 +1 @@
1
- var ie=Object.defineProperty;var g=(e,r)=>ie(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as J}from"../../api/command-manifest/result.js";import{callerAllows as ae,requireGroup as se}from"../../daemon/api/handlers/providers.js";import{terminalGrant as de}from"../grants/mirror.js";import{findRefusal as ue,payerOf as le,refusalError as pe,refusedMark as ce,writeCharge as fe}from"../ledger/index.js";import{listInstalledPlugins as me}from"../resolver.js";import{ProviderError as l}from"./errors.js";import{resolveProviderRef as ge}from"./roots.js";import{providerToolPath as ve}from"../plugin-objects-manifest.js";import{ensureProviderBundle as he,refreshProviderBundle as we,resolveProviderBundle as H,unavailableProviderError as ye}from"./bundles.js";import{prepareFileCall as be,resignForRetry as K,resolveFileResult as W}from"./files.js";import{consumeApproval as _e,requireProviderApproval as Ae}from"./approval.js";import{outboundToken as je}from"./mint.js";import{discardFile as ke}from"./token-cache.js";import{registerProviderCall as Pe}from"./inflight.js";import{buildRequestPlan as $e}from"../command-plugins/transport/http-plan.js";import{newTraceId as qe}from"../../shared/traceparent.js";import{sendProviderRequest as Ce,ProviderTransportError as Ee}from"./request.js";import{acceptProviderReceipt as Re}from"./receipts.js";import{writeProviderAudit as Ie}from"./audit.js";import{selectDevice as Te,sendDeviceCall as Me,deviceFailure as Se,deviceUnreachable as X,DEFAULT_DEVICE_REQUEST_MS as Be}from"./device.js";import{isLibsSearch as Y,wantsBundling as Ne,callLibsSearch as Oe,bundleExec as Fe,mapLibOutcome as De,reportLibCalls as Ue}from"./device-libs.js";import{ownMachineId as Le}from"../peer/directory.js";import{directoryIssuer as Je}from"../directory/issuer.js";import{formatCrtrCaller as Z}from"../command-plugins/caller-header.js";import{getNode as He}from"../canvas/canvas.js";import{envelopeForProviderCaller as Ge}from"../peer/envelope.js";const xe="app:terminal";function ee(e){const r=[],i=g((c,t,n)=>{const a=[...t,c.name],s=n||c.tier==="hidden";if(c.kind==="branch"){for(const v of c.children)i(v,a,s);return}const j=ve(e.roots.length,a);r.push({tool:j,leaf:c,hidden:s})},"walk");for(const c of e.roots)i(c,[],!1);return r}g(ee,"providerLeaves");function re(e){if(e.kind!=="branch")return null;const r=[];for(const h of e.children)if(h.kind==="branch"){const _=re(h);_&&r.push(_)}else if("group"in h&&h.group){const{kind:_,name:d,description:A,whenToUse:w,tier:y,summary:k,params:P,output:o,effects:p,group:m}=h;r.push({kind:_,name:d,description:A,whenToUse:w,...y===void 0?{}:{tier:y},summary:k,params:P,output:o,effects:p,group:m})}if(r.length===0)return null;const{kind:i,name:c,description:t,whenToUse:n,tier:a,summary:s,model:j,rootEntry:v}=e;return{kind:i,name:c,description:t,whenToUse:n,...a===void 0?{}:{tier:a},summary:s,...j===void 0?{}:{model:j},...v===void 0?{}:{rootEntry:v},children:r}}g(re,"providerHelpBranch");async function $r(e,r){const i=await H(r),c=ee(i.commands),t=me("user").find(n=>n.name===r)?.manifest.description??r;return{provider:r,status:"available",major:i.major,display_name:t,transport:i.transport.kind,groups:i.provider.groups.filter(n=>ae(e,`${r}:${n.group}`)).map(n=>({group:n.group,leaves:c.filter(({leaf:a,hidden:s})=>!s&&a.group===n.group).map(({tool:a,leaf:s})=>({tool:a,description:s.description,summary:s.summary,params:s.params,output:s.output}))})),commands:i.commands.roots.flatMap(n=>{const a=re(n);return a?[a]:[]})}}g($r,"providerTools");async function ze(e){return await Re(e.headers["crtr-receipt"],e)}g(ze,"onProviderResponse");async function Qe(e){if(e.class==="person"){const r=(await de())?.sub;if(!r)throw new l("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:r,grantee:r}}if(!e.sub||!e.grantee)throw new l("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}g(Qe,"identity");function Ve(e,r){return[`${e.id}:${r.group}`]}g(Ve,"callScope");function Ke(e,r){const i=ee(e.commands).find(c=>c.tool===r);if(!i)throw new l("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!i.leaf.group)throw new l("not_found",`tool "${r}" has no group`,{param:"tool"});return i.leaf}g(Ke,"leafFor");function G(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}g(G,"errorBody");function We(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const i=Number(r);if(Number.isFinite(i)&&i>=0)return i;const c=Date.parse(r);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}g(We,"retryAfter");function N(e,r,i,c=!1){const t=G(r),n=typeof t.code=="string"?t.code:void 0,a=typeof t.message=="string"?t.message:`provider answered HTTP ${e}`,s=typeof t.field=="string"?t.field:void 0,v={origin:"provider",details:{code:n??null,message:a,...s?{field:s}:{},...typeof t.next=="string"?{next:t.next}:{},...t.received!==void 0?{received:t.received}:{},status:e},...s?{param:s}:{}};return e===429?new l("provider_rate_limited",a,{...v,retry_after_s:We(i["retry-after"])}):e===401||e>=500?new l("provider_unavailable",a,v):e===403&&n==="provider_refused"?new l("provider_refused",a,v):e===403&&n==="approval_required"?new l("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...v,status:e}):new l("provider_error",a,{...v,status:e})}g(N,"mapProviderResponse");function te(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}g(te,"approvalShape");async function Xe(e){const r=e.origin!==void 0?Z({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):Z({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?(await He(e.node_id))?.profile_id??void 0:void 0,machine:Je()!==null?Le():void 0});return r===""?{}:{"Crtr-Caller":r}}g(Xe,"crtrCallerHeader");async function qr(e,r,i){const c=ge(r.provider);if(c===null)throw new l("not_found",`provider "${r.provider}" is not available`,{param:"provider"});const t={...r,provider:c},n=new AbortController,a=g(()=>n.abort(i.signal.reason),"abort");i.signal.aborted?a():i.signal.addEventListener("abort",a,{once:!0});let s=i.requestId;const j=i.traceId??qe(),v=Pe({request_id:s,run_id:e.run_id,node_id:e.node_id,controller:n});let h,_,d,A,w,y,k,P="allowed";try{try{await he(t.provider)}catch{throw ye(t.provider)}let o=await H(t.provider,t.version),p=!1,m,O,$,x=!1,z=!1,F,Q=0;for(;;){const b=Ke(o,t.tool);if(d=`${o.id}:${b.group}`,se(e,o.id,b.group),o.transport.kind!=="machine"&&t.machine!==void 0)throw new l("invalid_argument",`provider "${o.id}" is not served by a device, so it takes no machine`,{param:"machine"});o.transport.kind==="machine"&&F===void 0&&(F=await Te(t.machine,void 0,{newest:Y(o.major,t.tool)}),Q=Date.now()+(o.commands.timeouts?.requestMs??Be));let D=null;if(t.approval&&!x){const u=await _e(e,t,te(o,b));$=u.option,D=u.prepared,k=t.approval,s=u.requestId,x=!0}const I={params:b.params.filter(u=>"type"in u&&u.type==="file").map(u=>u.name),output:b.output.find(u=>u.type==="file")?.name??null},V=JSON.stringify(I);if(O!==void 0&&O!==V)throw new l("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});O=V,D&&(m=await K(e,I,t.arguments,D,{signal:n.signal})),m??=await be(e,I,t.arguments,{signal:n.signal});const{sub:q,grantee:T}=await Qe(e);if(o.transport.kind==="machine"){const u=await Ye(e,o,t,b,m,{sub:q,requestId:s,deadlineAt:Q,signal:n.signal,machine:F});return y=u.responder,h=u.cleanup,{status:200,body:u.result}}const C=await je({audience:o.transport.endpoint,sub:q,grantee:T,initiating_app:T,scope:Ve(o,b)},{signal:n.signal});A=C.jti;const U=e.class==="person"?xe:T,E=le({call:"tool",tokenPayer:C.payer,sub:q}),L=await ue({scope:"call",payer:E.payer,funding:E.funding,sub:q,grantee:U,provider:`app:${o.id}`});if(L!==null)throw await fe({request:s,kind:"tool",what:`${o.id}:${t.tool}`,usd:0,payer:E.payer,funding:E.funding,sub:q,grantee:U,node:e.node_id,run:e.run_id,provider:`app:${o.id}`,causedBy:i.causedBy??null,refused:ce(L)}),pe(L);const oe={registration:{name:o.id,endpoint:o.transport.endpoint},...o.commands.baseUrl?{baseUrl:o.commands.baseUrl}:{},...o.commands.timeouts?{timeouts:o.commands.timeouts}:{},params:b.params,rest:b.rest,output:b.output,commandPath:[o.id,...t.tool.split(" ")]},ne=$e(oe,m.arguments,void 0,{authorization:`Bearer ${C.access_token}`,requestId:s,traceId:j,fileMode:"pass-through",extraHeaders:{...i.causedBy?{"Crtr-Caused-By":i.causedBy}:{},...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...await Xe(e),...$?{"Crtr-Approval":$}:{},...m.headers}});_??=Date.now()+(o.commands.timeouts?.requestMs??3e4);let f;try{if(f=await Ce(ne,{connectMs:o.commands.timeouts?.connectMs,requestMs:o.commands.timeouts?.requestMs,deadlineAt:_,signal:n.signal}),n.signal.aborted)throw n.signal.reason??new Error("provider call aborted")}catch(u){throw n.signal.aborted?n.signal.reason??u:u instanceof Ee?new l("provider_unavailable",u.message,{origin:"provider",details:{status:null,code:u.kind}}):u}if(w??=await ze({provider:o.id,tool:t.tool,request_id:s,run_id:e.run_id,sub:q,grantee:T,payer:C.payer,ledger_grantee:U,funding:E.funding,node_id:e.node_id,caused_by:i.causedBy??null,jti:C.jti,status:f.status,headers:f.headers}),y=f.headers["crtr-responder"],f.status===401&&ke(C.key),f.status>=200&&f.status<300){const u=f.body;if(!u||typeof u!="object"||Array.isArray(u))throw new l("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let S;try{S=await W(e,m,u,{signal:n.signal})}catch(R){throw R instanceof l&&R.code==="provider_error"?new l("provider_bad_response",R.message,{origin:"provider",details:{status:f.status,message:R.message}}):R}h=S.cleanup;const B=J(b.output,S.result);if(B)throw new l("provider_bad_response",`provider result has an invalid output field "${B.field}"`,{origin:"provider",param:B.field,details:{status:f.status,...B}});if(n.signal.aborted)throw n.signal.reason??new Error("provider call aborted");return{status:200,body:S.result}}const M=G(f.body);if(f.status===403&&M.code==="approval_required"&&!$&&!z){z=!0;const u=await Ae(e,t,s,M.question,n.signal,te(o,b),m);if(k=u.questionId,$=u.option,m=await K(e,I,t.arguments,m,{signal:n.signal}),_=void 0,n.signal.aborted)throw new l("user_denied","the approval wait was canceled");continue}if(f.status===403&&M.code==="approval_required"&&$)throw N(f.status,f.body,f.headers,!0);if(f.status!==401&&M.manifest_stale===!0&&!p){p=!0,await we(o.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}}),o=await H(o.id,t.version);continue}throw N(f.status,f.body,f.headers,!!$)}}catch(o){throw o instanceof l&&o.question_id&&(k=o.question_id),y===void 0&&o instanceof l&&typeof o.responder=="string"&&(y=o.responder),P=`refused:${o instanceof l?o.code:"server_error"}`,await h?.(),o}finally{try{await Ie({request_id:s,caller:e,provider:t.provider,tool:t.tool,scope_checked:d,outcome:P,jti:A,receipt_id:w,responder:y,question_id:k})}finally{v()}i.signal.removeEventListener("abort",a)}}g(qr,"callProvider");async function Ye(e,r,i,c,t,n){const{machine:a}=n,s=await Ge(e,n.sub),j={machine:a,envelope:s,signal:n.signal,deadlineAt:n.deadlineAt,mapFailure:N};if(Y(r.major,i.tool)){const p=await Oe(j,t.arguments,r.major),m=J(c.output,p);if(m)throw new l("provider_bad_response",`the libraries result has an invalid output field "${m.field}"`,{origin:"provider",param:m.field,details:{status:200,...m}});return{result:p,responder:void 0,cleanup:void 0}}let v=t.arguments,h=null,_="";if(Ne(r.major,i.tool,t.arguments)){const p=await Fe(j,t.arguments,r.major);if(n.deadlineAt-Date.now()<=0)throw X(a,"timeout","");p!==null&&(v=p.arguments,h=p.services,_=p.release)}const d=await Me(a,{tool:i.tool,arguments:v,version:r.major,requestId:n.requestId,runId:e.run_id,deadlineAt:n.deadlineAt,upload:t.upload===null?null:{id:t.upload.upload_id,link:t.upload.upload_link}},s,n.signal);if(n.signal.aborted)throw n.signal.reason??new Error("provider call aborted");if("unreachable"in d)throw X(a,d.unreachable.reason,d.unreachable.message);const A=d.headers["crtr-responder"],w=G(d.body);if(d.status<200||d.status>=300){const p=Se(a,d.status,typeof w.code=="string"?w.code:void 0,typeof w.message=="string"?w.message:void 0,d.body),m=p===null&&d.status>=500&&typeof w.code=="string"?new l("provider_error",typeof w.message=="string"?w.message:`the device answered HTTP ${d.status}`,{origin:"runtime",status:d.status,details:{code:w.code,status:d.status}}):null;throw Object.assign(p??m??N(d.status,d.body,d.headers),{responder:A})}const y=d.body&&typeof d.body=="object"&&!Array.isArray(d.body)?d.body.result:void 0;if(!y||typeof y!="object"||Array.isArray(y))throw Object.assign(new l("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:d.status,message:"the device result is not a JSON object"}}),{responder:A});let k=y;if(h!==null){const p=De(k,h);if(Ue(a,s,_,p.calls),"error"in p)throw Object.assign(p.error,{responder:A});k=p.result}let P;try{P=await W(e,t,k,{signal:n.signal})}catch(p){throw p instanceof l&&p.code==="provider_error"?Object.assign(new l("provider_bad_response",p.message,{origin:"provider",details:{status:d.status,message:p.message}}),{responder:A}):p}const o=J(c.output,P.result);if(o)throw await P.cleanup?.(),Object.assign(new l("provider_bad_response",`the device result has an invalid output field "${o.field}"`,{origin:"provider",param:o.field,details:{status:d.status,...o}}),{responder:A});return{result:P.result,responder:A,cleanup:P.cleanup}}g(Ye,"callDevice");export{qr as callProvider,Xe as crtrCallerHeader,N as mapProviderResponse,ze as onProviderResponse,ee as providerLeaves,$r as providerTools};
1
+ var de=Object.defineProperty;var m=(e,r)=>de(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as x}from"../../api/command-manifest/result.js";import{callerAllows as ue,requireGroup as le}from"../../daemon/api/handlers/providers.js";import{callerTerminalGrant as pe}from"../grants/terminal.js";import{findRefusal as ce,payerOf as fe,refusalError as me,refusedMark as ve,writeCharge as ge}from"../ledger/index.js";import{listInstalledPlugins as he}from"../resolver.js";import{ProviderError as l}from"./errors.js";import{resolveProviderRef as we}from"./roots.js";import{providerToolPath as ye}from"../plugin-objects-manifest.js";import{ensureProviderBundle as be,refreshProviderBundle as X,resolveProviderBundle as O,unavailableProviderError as _e}from"./bundles.js";import{prepareFileCall as Ae,resignForRetry as Y,resolveFileResult as Z}from"./files.js";import{consumeApproval as je,requireProviderApproval as ke}from"./approval.js";import{outboundToken as Pe}from"./mint.js";import{discardFile as $e}from"./token-cache.js";import{registerProviderCall as qe}from"./inflight.js";import{buildRequestPlan as Ce}from"../command-plugins/transport/http-plan.js";import{newTraceId as Ee}from"../../shared/traceparent.js";import{sendProviderRequest as Re,ProviderTransportError as Te}from"./request.js";import{acceptProviderReceipt as Ie}from"./receipts.js";import{writeProviderAudit as Me}from"./audit.js";import{selectDevice as Se,sendDeviceCall as Be,deviceFailure as Ne,deviceUnreachable as ee,DEFAULT_DEVICE_REQUEST_MS as Oe}from"./device.js";import{isLibsSearch as re,wantsBundling as Fe,callLibsSearch as De,bundleExec as Ue,mapLibOutcome as Le,reportLibCalls as Je}from"./device-libs.js";import{ownMachineId as He}from"../peer/directory.js";import{directoryIssuer as xe}from"../directory/issuer.js";import{formatCrtrCaller as te}from"../command-plugins/caller-header.js";import{getNode as Ge}from"../canvas/canvas.js";import{envelopeForProviderCaller as ze}from"../peer/envelope.js";const Qe="app:terminal";function oe(e){const r=[],n=m((c,t,i)=>{const s=[...t,c.name],d=i||c.tier==="hidden";if(c.kind==="branch"){for(const v of c.children)n(v,s,d);return}const j=ye(e.roots.length,s);r.push({tool:j,leaf:c,hidden:d})},"walk");for(const c of e.roots)n(c,[],!1);return r}m(oe,"providerLeaves");function ie(e){if(e.kind!=="branch")return null;const r=[];for(const g of e.children)if(g.kind==="branch"){const b=ie(g);b&&r.push(b)}else if("group"in g&&g.group){const{kind:b,name:u,description:_,whenToUse:h,tier:w,summary:k,params:P,output:o,effects:p,group:A}=g;r.push({kind:b,name:u,description:_,whenToUse:h,...w===void 0?{}:{tier:w},summary:k,params:P,output:o,effects:p,group:A})}if(r.length===0)return null;const{kind:n,name:c,description:t,whenToUse:i,tier:s,summary:d,model:j,rootEntry:v}=e;return{kind:n,name:c,description:t,whenToUse:i,...s===void 0?{}:{tier:s},summary:d,...j===void 0?{}:{model:j},...v===void 0?{}:{rootEntry:v},children:r}}m(ie,"providerHelpBranch");async function Cr(e,r){const n=await O(r),c=oe(n.commands),t=he("user").find(i=>i.name===r)?.manifest.description??r;return{provider:r,status:"available",major:n.major,display_name:t,transport:n.transport.kind,groups:n.provider.groups.filter(i=>ue(e,`${r}:${i.group}`)).map(i=>({group:i.group,leaves:c.filter(({leaf:s,hidden:d})=>!d&&s.group===i.group).map(({tool:s,leaf:d})=>({tool:s,description:d.description,summary:d.summary,params:d.params,output:d.output}))})),commands:n.commands.roots.flatMap(i=>{const s=ie(i);return s?[s]:[]})}}m(Cr,"providerTools");async function Ve(e){return await Ie(e.headers["crtr-receipt"],e)}m(Ve,"onProviderResponse");async function Ke(e){if(e.class==="person"){const r=(await pe())?.sub;if(!r)throw new l("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:r,grantee:r}}if(!e.sub||!e.grantee)throw new l("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}m(Ke,"identity");function We(e,r){return[`${e.id}:${r.group}`]}m(We,"callScope");function Xe(e,r){const n=oe(e.commands).find(c=>c.tool===r);if(!n)throw new l("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!n.leaf.group)throw new l("not_found",`tool "${r}" has no group`,{param:"tool"});return n.leaf}m(Xe,"leafFor");function G(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}m(G,"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 c=Date.parse(r);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}m(Ye,"retryAfter");function F(e,r,n,c=!1){const t=G(r),i=typeof t.code=="string"?t.code:void 0,s=typeof t.message=="string"?t.message:`provider answered HTTP ${e}`,d=typeof t.field=="string"?t.field:void 0,v={origin:"provider",details:{code:i??null,message:s,...d?{field:d}:{},...typeof t.next=="string"?{next:t.next}:{},...t.received!==void 0?{received:t.received}:{},status:e},...d?{param:d}:{}};return e===429?new l("provider_rate_limited",s,{...v,retry_after_s:Ye(n["retry-after"])}):e===401||e>=500?new l("provider_unavailable",s,v):e===403&&i==="provider_refused"?new l("provider_refused",s,v):e===403&&i==="approval_required"?new l("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...v,status:e}):new l("provider_error",s,{...v,status:e})}m(F,"mapProviderResponse");function ne(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}m(ne,"approvalShape");async function Ze(e){const r=e.origin!==void 0?te({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):te({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?(await Ge(e.node_id))?.profile_id??void 0:void 0,machine:xe()!==null?He():void 0});return r===""?{}:{"Crtr-Caller":r}}m(Ze,"crtrCallerHeader");async function Er(e,r,n){const c=we(r.provider);if(c===null)throw new l("not_found",`provider "${r.provider}" is not available`,{param:"provider"});const t={...r,provider:c},i=new AbortController,s=m(()=>i.abort(n.signal.reason),"abort");n.signal.aborted?s():n.signal.addEventListener("abort",s,{once:!0});let d=n.requestId;const j=n.traceId??Ee(),v=qe({request_id:d,run_id:e.run_id,node_id:e.node_id,controller:i});let g,b,u,_,h,w,k,P="allowed";try{try{await be(t.provider)}catch{throw _e(t.provider)}let o=await O(t.provider,t.version),p=!1,A=!1,$,D,C,z=!1,Q=!1,U,V=0;for(;;){const y=Xe(o,t.tool);if(u=`${o.id}:${y.group}`,le(e,o.id,y.group),o.transport.kind!=="machine"&&t.machine!==void 0)throw new l("invalid_argument",`provider "${o.id}" is not served by a device, so it takes no machine`,{param:"machine"});o.transport.kind==="machine"&&U===void 0&&(U=await Se(t.machine,void 0,{newest:re(o.major,t.tool)}),V=Date.now()+(o.commands.timeouts?.requestMs??Oe));let L=null;if(t.approval&&!z){const a=await je(e,t,ne(o,y));C=a.option,L=a.prepared,k=t.approval,d=a.requestId,z=!0}const M={params:y.params.filter(a=>"type"in a&&a.type==="file").map(a=>a.name),output:y.output.find(a=>a.type==="file")?.name??null},K=JSON.stringify(M);if(D!==void 0&&D!==K)throw new l("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});D=K,L&&($=await Y(e,M,t.arguments,L,{signal:i.signal})),$??=await Ae(e,M,t.arguments,{signal:i.signal});const{sub:R,grantee:S}=await Ke(e);if(o.transport.kind==="machine"){const a=await er(e,o,t,y,$,{sub:R,requestId:d,deadlineAt:V,signal:i.signal,machine:U});return w=a.responder,g=a.cleanup,{status:200,body:a.result}}const W=o.transport.endpoint;let E;try{E=await Pe({audience:W,sub:R,grantee:S,initiating_app:S,scope:We(o,y)},{signal:i.signal})}catch(a){if(!(a instanceof l)||a.details?.directory_error!=="invalid_target"||A)throw a;A=!0,await X(o.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}});const q=await O(o.id,t.version);if(q.transport.kind==="machine"||q.transport.endpoint===W)throw a;o=q;continue}_=E.jti;const J=e.class==="person"?Qe:S,T=fe({call:"tool",tokenPayer:E.payer,sub:R}),H=await ce({scope:"call",payer:T.payer,funding:T.funding,sub:R,grantee:J,provider:`app:${o.id}`});if(H!==null)throw await ge({request:d,kind:"tool",what:`${o.id}:${t.tool}`,usd:0,payer:T.payer,funding:T.funding,sub:R,grantee:J,node:e.node_id,run:e.run_id,provider:`app:${o.id}`,causedBy:n.causedBy??null,refused:ve(H)}),me(H);const ae={registration:{name:o.id,endpoint:o.transport.endpoint},...o.commands.baseUrl?{baseUrl:o.commands.baseUrl}:{},...o.commands.timeouts?{timeouts:o.commands.timeouts}:{},params:y.params,rest:y.rest,output:y.output,commandPath:[o.id,...t.tool.split(" ")]},se=Ce(ae,$.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:d,traceId:j,fileMode:"pass-through",extraHeaders:{...n.causedBy?{"Crtr-Caused-By":n.causedBy}:{},...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...await Ze(e),...C?{"Crtr-Approval":C}:{},...$.headers}});b??=Date.now()+(o.commands.timeouts?.requestMs??3e4);let f;try{if(f=await Re(se,{connectMs:o.commands.timeouts?.connectMs,requestMs:o.commands.timeouts?.requestMs,deadlineAt:b,signal:i.signal}),i.signal.aborted)throw i.signal.reason??new Error("provider call aborted")}catch(a){throw i.signal.aborted?i.signal.reason??a:a instanceof Te?new l("provider_unavailable",a.message,{origin:"provider",details:{status:null,code:a.kind}}):a}if(h??=await Ve({provider:o.id,tool:t.tool,request_id:d,run_id:e.run_id,sub:R,grantee:S,payer:E.payer,ledger_grantee:J,funding:T.funding,node_id:e.node_id,caused_by:n.causedBy??null,jti:E.jti,status:f.status,headers:f.headers}),w=f.headers["crtr-responder"],f.status===401&&$e(E.key),f.status>=200&&f.status<300){const a=f.body;if(!a||typeof a!="object"||Array.isArray(a))throw new l("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let q;try{q=await Z(e,$,a,{signal:i.signal})}catch(I){throw I instanceof l&&I.code==="provider_error"?new l("provider_bad_response",I.message,{origin:"provider",details:{status:f.status,message:I.message}}):I}g=q.cleanup;const N=x(y.output,q.result);if(N)throw new l("provider_bad_response",`provider result has an invalid output field "${N.field}"`,{origin:"provider",param:N.field,details:{status:f.status,...N}});if(i.signal.aborted)throw i.signal.reason??new Error("provider call aborted");return{status:200,body:q.result}}const B=G(f.body);if(f.status===403&&B.code==="approval_required"&&!C&&!Q){Q=!0;const a=await ke(e,t,d,B.question,i.signal,ne(o,y),$);if(k=a.questionId,C=a.option,$=await Y(e,M,t.arguments,$,{signal:i.signal}),b=void 0,i.signal.aborted)throw new l("user_denied","the approval wait was canceled");continue}if(f.status===403&&B.code==="approval_required"&&C)throw F(f.status,f.body,f.headers,!0);if(f.status!==401&&B.manifest_stale===!0&&!p){p=!0,await X(o.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}}),o=await O(o.id,t.version);continue}throw F(f.status,f.body,f.headers,!!C)}}catch(o){throw o instanceof l&&o.question_id&&(k=o.question_id),w===void 0&&o instanceof l&&typeof o.responder=="string"&&(w=o.responder),P=`refused:${o instanceof l?o.code:"server_error"}`,await g?.(),o}finally{try{await Me({request_id:d,caller:e,provider:t.provider,tool:t.tool,scope_checked:u,outcome:P,jti:_,receipt_id:h,responder:w,question_id:k})}finally{v()}n.signal.removeEventListener("abort",s)}}m(Er,"callProvider");async function er(e,r,n,c,t,i){const{machine:s}=i,d=await ze(e,i.sub),j={machine:s,envelope:d,signal:i.signal,deadlineAt:i.deadlineAt,mapFailure:F};if(re(r.major,n.tool)){const p=await De(j,t.arguments,r.major),A=x(c.output,p);if(A)throw new l("provider_bad_response",`the libraries result has an invalid output field "${A.field}"`,{origin:"provider",param:A.field,details:{status:200,...A}});return{result:p,responder:void 0,cleanup:void 0}}let v=t.arguments,g=null,b="";if(Fe(r.major,n.tool,t.arguments)){const p=await Ue(j,t.arguments,r.major);if(i.deadlineAt-Date.now()<=0)throw ee(s,"timeout","");p!==null&&(v=p.arguments,g=p.services,b=p.release)}const u=await Be(s,{tool:n.tool,arguments:v,version:r.major,requestId:i.requestId,runId:e.run_id,deadlineAt:i.deadlineAt,upload:t.upload===null?null:{id:t.upload.upload_id,link:t.upload.upload_link}},d,i.signal);if(i.signal.aborted)throw i.signal.reason??new Error("provider call aborted");if("unreachable"in u)throw ee(s,u.unreachable.reason,u.unreachable.message);const _=u.headers["crtr-responder"],h=G(u.body);if(u.status<200||u.status>=300){const p=Ne(s,u.status,typeof h.code=="string"?h.code:void 0,typeof h.message=="string"?h.message:void 0,u.body),A=p===null&&u.status>=500&&typeof h.code=="string"?new l("provider_error",typeof h.message=="string"?h.message:`the device answered HTTP ${u.status}`,{origin:"runtime",status:u.status,details:{code:h.code,status:u.status}}):null;throw Object.assign(p??A??F(u.status,u.body,u.headers),{responder:_})}const w=u.body&&typeof u.body=="object"&&!Array.isArray(u.body)?u.body.result:void 0;if(!w||typeof w!="object"||Array.isArray(w))throw Object.assign(new l("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:u.status,message:"the device result is not a JSON object"}}),{responder:_});let k=w;if(g!==null){const p=Le(k,g);if(Je(s,d,b,p.calls),"error"in p)throw Object.assign(p.error,{responder:_});k=p.result}let P;try{P=await Z(e,t,k,{signal:i.signal})}catch(p){throw p instanceof l&&p.code==="provider_error"?Object.assign(new l("provider_bad_response",p.message,{origin:"provider",details:{status:u.status,message:p.message}}),{responder:_}):p}const o=x(c.output,P.result);if(o)throw await P.cleanup?.(),Object.assign(new l("provider_bad_response",`the device result has an invalid output field "${o.field}"`,{origin:"provider",param:o.field,details:{status:u.status,...o}}),{responder:_});return{result:P.result,responder:_,cleanup:P.cleanup}}m(er,"callDevice");export{Er as callProvider,Ze as crtrCallerHeader,Ke as identity,F as mapProviderResponse,Ve as onProviderResponse,oe as providerLeaves,Cr as providerTools};
@@ -1 +1 @@
1
- var J=Object.defineProperty;var l=(e,r)=>J(e,"name",{value:r,configurable:!0});import{envHomeOverride as K,envRuntimeHomeOverride as X,INBOX_BETWEEN_TURNS_ENV as z,SCOPES_FILE_ENV as V,scopeAllowed as I}from"../../shared/env.js";import{isOwnMemoryScope as Y,validateScopes as Q}from"../scopes.js";import{activeGrant as F,terminalGrant as k}from"../grants/mirror.js";import{grantSnapshotMissing as Z}from"../../daemon/readiness.js";import{appendRunEvent as ee}from"../runs/events.js";import{personId as ne}from"../canvas/person.js";import{InputError as S}from"../io.js";import{ApiError as g}from"../../api/errors.js";import{assertGranteeRunsHere as re}from"./unsandboxed.js";import{randomBytes as te}from"node:crypto";import{existsSync as B}from"node:fs";import{resolve as ie,relative as oe,isAbsolute as L,join as ae,sep as le}from"node:path";import{appSpace as se}from"../layout.js";import{scopesFilePath as de}from"../canvas/paths.js";import{createNode as ce,assertRunNotDeleting as ue,conversationOwner as fe,getNode as u,getRow as pe}from"../canvas/index.js";import{ensureAppSpace as _e}from"../spaces/space.js";import{LAUNCHER_SOCKET as P}from"../../launcher/spec.js";import{blaxelEnvironmentOf as me}from"./blaxel-environment.js";import{resolveInstallId as we}from"../canvas/install-id.js";import{isAppRun as ge}from"../canvas/app-run.js";import{contextDir as he}from"../canvas/paths.js";import{conversationIdFromPath as ve}from"../conversation-store/index.js";import{usage as U}from"../errors.js";import{assertProfileAvailableForBirth as Re}from"../profiles/deletion-reservation.js";import{loadExactProfileManifest as ye}from"../profiles/manifest.js";import{admitNodeCreate as Ee}from"../command-hooks/lifecycle-create.js";import{getById as Ie,nodeObject as N,ownerForGrantee as Ne}from"../graph/objects.js";import{recordActedOn as Ce}from"../graph/edges.js";import{recordWatch as ke,copyWatchesForFork as Se}from"../graph/watches.js";import{publish as Te}from"../graph/events.js";import{canvasDatabaseUrl as De}from"../canvas/db.js";import{someAsync as Oe}from"../async-array.js";import{admitIntoManagedCheckout as xe}from"../worktree-mutation-async.js";let T;const H=new Map;function Rn(e){T=e}l(Rn,"setInstallId");async function be(){if(T!==void 0)return T;const e=await De(),r=H.get(e);if(r!==void 0)return r;const o=await we();return H.set(e,o),o}l(be,"installId");function Ae(){return`${Date.now().toString(36)}-${te(4).toString("hex")}`}l(Ae,"newJobId");async function Me(){return`${await be()}-${Ae()}`}l(Me,"newNodeId");const D=128,$e=/^[a-z0-9-]+$/;class j extends Error{static{l(this,"NodeIdConflictError")}nodeId;constructor(r){super(`node already exists: ${r}`),this.nodeId=r,this.name="NodeIdConflictError"}}function O(e){if(e.length===0||e.length>D)throw U(`invalid --node-id '${e}': must be 1-${D} characters`,{next:`Pass an id-safe string (lowercase letters, digits, hyphens) of at most ${D} characters.`});if(!$e.test(e))throw U(`invalid --node-id '${e}': must contain only lowercase letters, digits, and hyphens (e.g. 'remote42', 'my-node-1')`,{next:"Use only lowercase letters, digits, and hyphens."})}l(O,"validateNodeIdFormat");async function yn(e){if(O(e),await u(e)!==null)throw new j(e)}l(yn,"preflightNodeId");async function Fe(e){if(O(e),await u(e)!==null)throw new j(e);return e}l(Fe,"validatedExplicitNodeId");async function En(e){let r=e;const o=new Set;for(;;){if(o.has(r))return r;o.add(r);const s=await pe(r);if(s===null||s.parent==null)return r;r=s.parent}}l(En,"rootOfSpine");function In(){const e=process.env;return{nodeId:e.CRTR_NODE_ID??null,kind:e.CRTR_KIND??null,mode:e.CRTR_MODE??null}}l(In,"currentNodeContext");function Be(e){return ge(e)}l(Be,"inboxBetweenTurns");function Nn(e){const r={CRTR_NODE_ID:e.node_id,CRTR_KIND:e.kind,CRTR_MODE:e.mode,CRTR_LIFECYCLE:e.lifecycle,CRTR_NODE_CWD:e.cwd,CRTR_CONTEXT_DIR:he(e.node_id,e.grantee),...e.deadline_at?{CRTR_NODE_DEADLINE_AT:e.deadline_at}:{},CRTR_CYCLES:(e.cycles??0).toString(),CRTR_PROFILE_ID:e.profile_id??"",PI_CLAUDE_OAUTH_REINJECT_SCOPE:"never"};e.parent&&(r.CRTR_PARENT_NODE_ID=e.parent),e.run_id&&(r.CRTR_RUN_ID=e.run_id),Be(e)&&(r[z]="1"),e.scopes!==null&&e.scopes!==void 0&&(r[V]=de(e.node_id)),r.CRTR_GRANTEE=e.grantee??"app:terminal";const o=K();o!==void 0&&o!==""&&(r.CRTR_HOME=o);const s=X();s!==void 0&&s!==""&&(r.CRTR_RUNTIME_HOME=s);const f=process.env.CRTR_SUBTREE;return f!==void 0&&f!==""&&(r.CRTR_SUBTREE=f),{...r,...e.launch?.env??{}}}l(Nn,"nodeEnv");async function Le(e){const r=ve(e),o=r===null?null:await fe(r);return o===null?null:await u(o)}l(Le,"forkSourceNode");async function Cn(e){const r=e.nodeId!==void 0?(O(e.nodeId),e.nodeId):await Me(),o=e.parent??null,s=o===null,f=e.spawnedBy??o,_=e.creator??null,x=e.mode??"base",C=e.lifecycle??(s?"resident":"terminal"),h=e.run===void 0?await k():null,n={node_id:r,name:e.name??e.kind,description:e.description,cycles:0,created:new Date().toISOString(),cwd:e.cwd,host_kind:"broker",profile_id:e.profile_id??null,kind:e.kind,mode:x,lifecycle:C,persona_ack:{kind:e.kind,mode:x,lifecycle:C},status:"active",parent:o,spawned_by:f,creator:_,fork_from:e.forkFrom,fork_source_file:e.forkSourceFile,review_binding:e.reviewBinding,model_override:e.modelOverride??null,scopes:e.scopes??null,isolated:e.isolated===!0,grantee:e.run?.grantee??"app:terminal",sub:e.run?.sub??h?.sub??null,grant_id:e.run?.grant_id??h?.id??null,client_id:e.run?.client_id??null,metadata_json:s?e.run?.metadata_json??null:null,run_id:s?r:e.run?.run_id??null,event_cursor:s?e.run?.event_cursor??1:null,usage_json:s?e.run?.usage_json??JSON.stringify({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}):null,passive_default:e.passiveDefault??!1,caused_by:e.causedBy??null,...e.managedWorktree!==void 0?{managed_worktree:e.managedWorktree}:{},intent:null,deadline_at:e.deadlineAt??null,pi_session_id:null,pi_session_file:null,launch:e.launch},y=e.scopes===void 0?void 0:Q(e.scopes),v=o===null?null:await u(o);if(o!==null&&v===null)throw new Error(`cannot spawn under unknown parent node: ${o}`);if(v?.run_id&&(await u(v.run_id))?.deleting_at)throw new Error("run is deleting");const c=_===null?null:await u(_);if(_!==null&&c===null)throw new Error(`cannot spawn from unknown creator node: ${_}`);if(c?.run_id&&(await u(c.run_id))?.deleting_at)throw new Error("run is deleting");const b=e.forkSourceFile==null?null:await Le(e.forkSourceFile),A=e.replaces==null?null:await u(e.replaces),m=v??b??A??c??(f===null?null:await u(f)),q=[v,c,b,A,f===null?null:await u(f)],M=await Oe(q,async t=>t?.isolated===!0||t?.run_id!=null&&(await u(t.run_id))?.isolated===!0);if(M&&e.isolated===!1)throw new g(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});if(n.isolated=M||e.isolated===!0,m!==null){const t=c??m;if(c&&m.grantee&&c.grantee&&c.grantee!==m.grantee)throw new g(403,"other_app_run","cannot create under another app's node");const i=c&&c!==m?[m,c]:[m],d=[],w=new Set;for(const a of i){a.scopes!==null&&a.scopes!==void 0&&d.push(a.scopes);const p=a.grantee==="app:terminal"?await k():a.grant_id?await F(a.grant_id):null;if(p){const R=[...p.resolved];w.add(R),d.push(R)}else{const R=[];w.add(R),d.push(R)}}const E=l(a=>Y(a,t.grantee??"app:terminal"),"ownMemory"),$=l((a,p)=>E(p)&&w.has(a)||I(a,p),"allowed");if(y!==void 0&&!y.every(a=>d.every(p=>$(p,a))))throw new S({error:"unknown_scope",message:"child scopes must be covered by the creating node, its source and their current grants",received:y,field:"scopes",next:"Request only scopes the creating node currently holds."});if(n.scopes=y??(i.every(a=>a.scopes==null)&&i.every(a=>a.grant_id===t.grant_id&&a.sub===t.sub)?null:i.length===1?m.scopes??null:[...new Set(d.flat().filter(a=>d.every(p=>$(p,a))))]),n.scopes?.includes("crtr:act")&&!I(n.scopes,"crtr:llm"))throw new S({error:"invalid_scope",message:"crtr:act requires crtr:llm",received:n.scopes,field:"scopes",next:"Include crtr:llm or omit crtr:act."});n.grantee=t.grantee,n.sub=t.sub??(n.grantee==="app:terminal"?h?.sub:null)??null,n.grant_id=t.grant_id??(n.grantee==="app:terminal"?h?.id:null)??null,n.client_id=t.client_id,n.run_id=s?r:t.run_id??null}if(re(n.grantee),e.run===void 0&&n.grantee==="app:terminal"&&h===null)throw await Z()?new g(503,"runtime_starting","the first grant snapshot from the directory has not committed"):new g(403,"scope_missing","terminal grant is not active");if(n.isolated){if((process.platform!=="linux"||!B(P))&&await me(n.profile_id??null)===null)throw new g(400,"isolated_unavailable","isolated nodes require the Linux root launcher");if(e.managedWorktree!==void 0)throw new g(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const t=ae(se(n.grantee??"app:terminal").nodes,r),i=oe(t,ie(n.cwd));if(!L(n.cwd)||i===".."||i.startsWith(`..${le}`)||L(i))throw new g(400,"invalid_request","isolated node cwd must be inside its own folder",{param:"cwd"})}if(n.profile_id){const t=ye(n.profile_id).manifest.grantee??"app:terminal";if(t!==n.grantee){const i=`crtr:memory:rw:${t}:profile:${n.profile_id}`,d=n.grantee==="app:terminal"?await k():n.grant_id?await F(n.grant_id):null;if(!d||d.grantee!==n.grantee||!I(d.resolved,i)||!I(n.scopes,i))throw new S({error:"scope_missing",message:`missing scope ${i}`,received:n.profile_id,field:"profile",next:"Select a profile covered by this run and its current grant."})}}if(Re(n.profile_id??null),!n.grantee)throw new Error("node creation requires an app");const W=await Ee({kind:n.kind,mode:n.mode,cwd:n.cwd,profile:n.profile_id??null,root:s,parentId:o,requestingNodeId:_,lifecycle:C,replaces:e.replaces??null},n.grantee),G=l(async(t,i)=>{const d=await N(t,i.node_id),w=_??o??e.actor??f??null,E=w!==null&&(await Ie(t,w))?.type==="node"?await N(t,w):await Ne(t,e.run===void 0?ne():i.grantee);await Ce(t,E,d),o!==null&&e.subscribe!==!1&&await ke(t,await N(t,o),d,"spawn"),e.forkSourceNode&&await Se(t,await N(t,e.forkSourceNode),d),await Te(t,{event:"created",object:d,actor:E,attrs:{name:i.name,kind:i.kind,creator:_??null}})},"recordBirth");try{e.nodeId!==void 0&&await Fe(e.nodeId),await e.beforeCreate?.(),(process.platform!=="linux"||B(P))&&await _e(n.grantee??"app:terminal"),await xe(n.cwd,r,()=>ce(n,{outcomeDelivery:e.outcomeDelivery,withinCreate:l(async(t,i)=>{i.run_id&&i.run_id!==i.node_id&&await ue(t,i.run_id),s&&e.run===void 0&&await ee(t,r,r,"run.started",{scopes:i.scopes??null,payer:"user",usage:JSON.parse(i.usage_json)}),await e.withinCreate?.(t,i),await G(t,i)},"withinCreate")}))}finally{W()}return n}l(Cn,"spawnNode");export{j as NodeIdConflictError,In as currentNodeContext,Le as forkSourceNode,Be as inboxBetweenTurns,Ae as newJobId,Me as newNodeId,Nn as nodeEnv,yn as preflightNodeId,En as rootOfSpine,Rn as setInstallId,Cn as spawnNode};
1
+ var X=Object.defineProperty;var l=(e,r)=>X(e,"name",{value:r,configurable:!0});import{envHomeOverride as z,envRuntimeHomeOverride as V,INBOX_BETWEEN_TURNS_ENV as Y,SCOPES_FILE_ENV as Q,scopeAllowed as C}from"../../shared/env.js";import{isOwnMemoryScope as Z,validateScopes as ee}from"../scopes.js";import{activeGrant as F,terminalGrant as B}from"../grants/mirror.js";import{callerPerson as ne,nodeTerminalGrant as P}from"../grants/terminal.js";import{grantSnapshotMissing as re}from"../../daemon/readiness.js";import{appendRunEvent as te}from"../runs/events.js";import{isCloudRole as ie,personId as oe}from"../canvas/person.js";import{InputError as S}from"../io.js";import{ApiError as w}from"../../api/errors.js";import{assertGranteeRunsHere as ae}from"./unsandboxed.js";import{randomBytes as le}from"node:crypto";import{existsSync as L}from"node:fs";import{resolve as se,relative as de,isAbsolute as U,join as ce,sep as ue}from"node:path";import{appSpace as fe}from"../layout.js";import{scopesFilePath as pe}from"../canvas/paths.js";import{createNode as me,assertRunNotDeleting as _e,conversationOwner as we,getNode as u,getRow as ge}from"../canvas/index.js";import{ensureAppSpace as he}from"../spaces/space.js";import{LAUNCHER_SOCKET as H}from"../../launcher/spec.js";import{blaxelEnvironmentOf as Re}from"./blaxel-environment.js";import{resolveInstallId as ve}from"../canvas/install-id.js";import{isAppRun as ye}from"../canvas/app-run.js";import{contextDir as Ee}from"../canvas/paths.js";import{conversationIdFromPath as Ie}from"../conversation-store/index.js";import{usage as j}from"../errors.js";import{assertProfileAvailableForBirth as Ce}from"../profiles/deletion-reservation.js";import{loadExactProfileManifest as Ne}from"../profiles/manifest.js";import{admitNodeCreate as ke}from"../command-hooks/lifecycle-create.js";import{getById as Se,nodeObject as N,ownerForGrantee as Te}from"../graph/objects.js";import{recordActedOn as De}from"../graph/edges.js";import{recordWatch as Oe,copyWatchesForFork as xe}from"../graph/watches.js";import{publish as be}from"../graph/events.js";import{canvasDatabaseUrl as Ae}from"../canvas/db.js";import{someAsync as Me}from"../async-array.js";import{admitIntoManagedCheckout as $e}from"../worktree-mutation-async.js";let T;const q=new Map;function Nn(e){T=e}l(Nn,"setInstallId");async function Fe(){if(T!==void 0)return T;const e=await Ae(),r=q.get(e);if(r!==void 0)return r;const o=await ve();return q.set(e,o),o}l(Fe,"installId");function Be(){return`${Date.now().toString(36)}-${le(4).toString("hex")}`}l(Be,"newJobId");async function Pe(){return`${await Fe()}-${Be()}`}l(Pe,"newNodeId");const D=128,Le=/^[a-z0-9-]+$/;class W extends Error{static{l(this,"NodeIdConflictError")}nodeId;constructor(r){super(`node already exists: ${r}`),this.nodeId=r,this.name="NodeIdConflictError"}}function O(e){if(e.length===0||e.length>D)throw j(`invalid --node-id '${e}': must be 1-${D} characters`,{next:`Pass an id-safe string (lowercase letters, digits, hyphens) of at most ${D} characters.`});if(!Le.test(e))throw j(`invalid --node-id '${e}': must contain only lowercase letters, digits, and hyphens (e.g. 'remote42', 'my-node-1')`,{next:"Use only lowercase letters, digits, and hyphens."})}l(O,"validateNodeIdFormat");async function kn(e){if(O(e),await u(e)!==null)throw new W(e)}l(kn,"preflightNodeId");async function Ue(e){if(O(e),await u(e)!==null)throw new W(e);return e}l(Ue,"validatedExplicitNodeId");async function Sn(e){let r=e;const o=new Set;for(;;){if(o.has(r))return r;o.add(r);const s=await ge(r);if(s===null||s.parent==null)return r;r=s.parent}}l(Sn,"rootOfSpine");function Tn(){const e=process.env;return{nodeId:e.CRTR_NODE_ID??null,kind:e.CRTR_KIND??null,mode:e.CRTR_MODE??null}}l(Tn,"currentNodeContext");function He(e){return ye(e)}l(He,"inboxBetweenTurns");function Dn(e){const r={CRTR_NODE_ID:e.node_id,CRTR_KIND:e.kind,CRTR_MODE:e.mode,CRTR_LIFECYCLE:e.lifecycle,CRTR_NODE_CWD:e.cwd,CRTR_CONTEXT_DIR:Ee(e.node_id,e.grantee),...e.deadline_at?{CRTR_NODE_DEADLINE_AT:e.deadline_at}:{},CRTR_CYCLES:(e.cycles??0).toString(),CRTR_PROFILE_ID:e.profile_id??"",PI_CLAUDE_OAUTH_REINJECT_SCOPE:"never"};e.parent&&(r.CRTR_PARENT_NODE_ID=e.parent),e.run_id&&(r.CRTR_RUN_ID=e.run_id),He(e)&&(r[Y]="1"),e.scopes!==null&&e.scopes!==void 0&&(r[Q]=pe(e.node_id)),r.CRTR_GRANTEE=e.grantee??"app:terminal";const o=z();o!==void 0&&o!==""&&(r.CRTR_HOME=o);const s=V();s!==void 0&&s!==""&&(r.CRTR_RUNTIME_HOME=s);const f=process.env.CRTR_SUBTREE;return f!==void 0&&f!==""&&(r.CRTR_SUBTREE=f),{...r,...e.launch?.env??{}}}l(Dn,"nodeEnv");async function je(e){const r=Ie(e),o=r===null?null:await we(r);return o===null?null:await u(o)}l(je,"forkSourceNode");async function On(e){const r=e.nodeId!==void 0?(O(e.nodeId),e.nodeId):await Pe(),o=e.parent??null,s=o===null,f=e.spawnedBy??o,m=e.creator??null,x=e.mode??"base",k=e.lifecycle??(s?"resident":"terminal"),y=e.run===void 0?await ne():null;let h=y===null?null:await B(y);const n={node_id:r,name:e.name??e.kind,description:e.description,cycles:0,created:new Date().toISOString(),cwd:e.cwd,host_kind:"broker",profile_id:e.profile_id??null,kind:e.kind,mode:x,lifecycle:k,persona_ack:{kind:e.kind,mode:x,lifecycle:k},status:"active",parent:o,spawned_by:f,creator:m,fork_from:e.forkFrom,fork_source_file:e.forkSourceFile,review_binding:e.reviewBinding,model_override:e.modelOverride??null,scopes:e.scopes??null,isolated:e.isolated===!0,grantee:e.run?.grantee??"app:terminal",sub:e.run?.sub??h?.sub??null,grant_id:e.run?.grant_id??h?.id??null,client_id:e.run?.client_id??null,metadata_json:s?e.run?.metadata_json??null:null,run_id:s?r:e.run?.run_id??null,event_cursor:s?e.run?.event_cursor??1:null,usage_json:s?e.run?.usage_json??JSON.stringify({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}):null,passive_default:e.passiveDefault??!1,caused_by:e.causedBy??null,...e.managedWorktree!==void 0?{managed_worktree:e.managedWorktree}:{},intent:null,deadline_at:e.deadlineAt??null,pi_session_id:null,pi_session_file:null,launch:e.launch},E=e.scopes===void 0?void 0:ee(e.scopes),R=o===null?null:await u(o);if(o!==null&&R===null)throw new Error(`cannot spawn under unknown parent node: ${o}`);if(R?.run_id&&(await u(R.run_id))?.deleting_at)throw new Error("run is deleting");const c=m===null?null:await u(m);if(m!==null&&c===null)throw new Error(`cannot spawn from unknown creator node: ${m}`);if(c?.run_id&&(await u(c.run_id))?.deleting_at)throw new Error("run is deleting");const b=e.forkSourceFile==null?null:await je(e.forkSourceFile),A=e.replaces==null?null:await u(e.replaces),_=R??b??A??c??(f===null?null:await u(f)),G=[R,c,b,A,f===null?null:await u(f)],M=await Me(G,async t=>t?.isolated===!0||t?.run_id!=null&&(await u(t.run_id))?.isolated===!0);if(M&&e.isolated===!1)throw new w(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});if(n.isolated=M||e.isolated===!0,_!==null){const t=c??_;if(c&&_.grantee&&c.grantee&&c.grantee!==_.grantee)throw new w(403,"other_app_run","cannot create under another app's node");const i=c&&c!==_?[_,c]:[_];if(ie()&&y!==null&&i.some(a=>a.sub&&a.sub!==y))throw new w(403,"other_person_run","cannot create under another person's node");const d=[],g=new Set;for(const a of i){a.scopes!==null&&a.scopes!==void 0&&d.push(a.scopes);const p=a.grantee==="app:terminal"?await P(a):a.grant_id?await F(a.grant_id):null;if(p){const v=[...p.resolved];g.add(v),d.push(v)}else{const v=[];g.add(v),d.push(v)}}const I=l(a=>Z(a,t.grantee??"app:terminal"),"ownMemory"),$=l((a,p)=>I(p)&&g.has(a)||C(a,p),"allowed");if(E!==void 0&&!E.every(a=>d.every(p=>$(p,a))))throw new S({error:"unknown_scope",message:"child scopes must be covered by the creating node, its source and their current grants",received:E,field:"scopes",next:"Request only scopes the creating node currently holds."});if(n.scopes=E??(i.every(a=>a.scopes==null)&&i.every(a=>a.grant_id===t.grant_id&&a.sub===t.sub)?null:i.length===1?_.scopes??null:[...new Set(d.flat().filter(a=>d.every(p=>$(p,a))))]),n.scopes?.includes("crtr:act")&&!C(n.scopes,"crtr:llm"))throw new S({error:"invalid_scope",message:"crtr:act requires crtr:llm",received:n.scopes,field:"scopes",next:"Include crtr:llm or omit crtr:act."});n.grantee=t.grantee,n.grantee==="app:terminal"&&t.sub&&(h=await B(t.sub)),n.sub=t.sub??(n.grantee==="app:terminal"?h?.sub:null)??null,n.grant_id=t.grant_id??(n.grantee==="app:terminal"?h?.id:null)??null,n.client_id=t.client_id,n.run_id=s?r:t.run_id??null}if(ae(n.grantee),e.run===void 0&&n.grantee==="app:terminal"&&h===null)throw await re()?new w(503,"runtime_starting","the first grant snapshot from the directory has not committed"):new w(403,"scope_missing","terminal grant is not active");if(n.isolated){if((process.platform!=="linux"||!L(H))&&await Re(n.profile_id??null)===null)throw new w(400,"isolated_unavailable","isolated nodes require the Linux root launcher");if(e.managedWorktree!==void 0)throw new w(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const t=ce(fe(n.grantee??"app:terminal").nodes,r),i=de(t,se(n.cwd));if(!U(n.cwd)||i===".."||i.startsWith(`..${ue}`)||U(i))throw new w(400,"invalid_request","isolated node cwd must be inside its own folder",{param:"cwd"})}if(n.profile_id){const t=Ne(n.profile_id).manifest.grantee??"app:terminal";if(t!==n.grantee){const i=`crtr:memory:rw:${t}:profile:${n.profile_id}`,d=n.grantee==="app:terminal"?await P(n):n.grant_id?await F(n.grant_id):null;if(!d||d.grantee!==n.grantee||!C(d.resolved,i)||!C(n.scopes,i))throw new S({error:"scope_missing",message:`missing scope ${i}`,received:n.profile_id,field:"profile",next:"Select a profile covered by this run and its current grant."})}}if(Ce(n.profile_id??null),!n.grantee)throw new Error("node creation requires an app");const J=await ke({kind:n.kind,mode:n.mode,cwd:n.cwd,profile:n.profile_id??null,root:s,parentId:o,requestingNodeId:m,lifecycle:k,replaces:e.replaces??null},n.grantee),K=l(async(t,i)=>{const d=await N(t,i.node_id),g=m??o??e.actor??f??null,I=g!==null&&(await Se(t,g))?.type==="node"?await N(t,g):await Te(t,e.run===void 0?oe():i.grantee);await De(t,I,d),o!==null&&e.subscribe!==!1&&await Oe(t,await N(t,o),d,"spawn"),e.forkSourceNode&&await xe(t,await N(t,e.forkSourceNode),d),await be(t,{event:"created",object:d,actor:I,attrs:{name:i.name,kind:i.kind,creator:m??null}})},"recordBirth");try{e.nodeId!==void 0&&await Ue(e.nodeId),await e.beforeCreate?.(),(process.platform!=="linux"||L(H))&&await he(n.grantee??"app:terminal"),await $e(n.cwd,r,()=>me(n,{outcomeDelivery:e.outcomeDelivery,withinCreate:l(async(t,i)=>{i.run_id&&i.run_id!==i.node_id&&await _e(t,i.run_id),s&&e.run===void 0&&await te(t,r,r,"run.started",{scopes:i.scopes??null,payer:"user",usage:JSON.parse(i.usage_json)}),await e.withinCreate?.(t,i),await K(t,i)},"withinCreate")}))}finally{J()}return n}l(On,"spawnNode");export{W as NodeIdConflictError,Tn as currentNodeContext,je as forkSourceNode,He as inboxBetweenTurns,Be as newJobId,Pe as newNodeId,Dn as nodeEnv,kn as preflightNodeId,Sn as rootOfSpine,Nn as setInstallId,On as spawnNode};
@@ -1,3 +1,3 @@
1
- var ie=Object.defineProperty;var l=(e,r)=>ie(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as te}from"./unsandboxed.js";import{existsSync as H}from"node:fs";import{conversationIdFromPath as v,conversationStore as p,ConversationNotFoundError as M}from"../conversation-store/index.js";import{isAbsolute as ne}from"node:path";import{findNodeBySessionFile as ae,getNode as S,updateNode as $,beginExecution as oe,ExecutionAlreadyLive as se,recordExecutionPid as le,abortExecution as N,endLiveExecution as ce,unbindSession as U,recordLaunch as ue,setFrozen as fe,clearFrozen as z,fullName as we}from"../canvas/index.js";import{emitEvent as K}from"../events/emit.js";import{consumeDeadlineOnWake as de}from"../feed/messages.js";import{isParked as G,transition as C}from"./lifecycle.js";import{publishNotice as he}from"./close.js";import{InputError as F}from"../io.js";import{buildPiArgv as _}from"./launch.js";import{buildReviveKickoff as pe,drainBearings as me,hasRetainedReports as ve,readGoal as g,readYieldMessage as _e,RUNTIME_RESTART_CONTINUATION as ye}from"./kickoff.js";import{readRoadmap as J}from"./roadmap.js";import{encodeKickoffOrigin as ke,KICKOFF_ORIGIN_ENV as ge}from"./stamp/protocol.js";import{headlessBrokerHost as be,brokerLaunchJson as Re}from"./host.js";import{processRunner as xe}from"./runner/process.js";import{runnerForEnvironment as Ee}from"./runner/index.js";import{blaxelEnvironmentOf as W}from"./blaxel-environment.js";import{recordLaunchFailure as Se}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as $e,checkBilledWake as Ne,EnvironmentImagePending as Ce,imageForWake as Fe}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as Ae,liveExecutionForNode as Pe,setExecutionLaunchJson as Be}from"../canvas/executions.js";import{appMemoryFull as Oe,appMemoryTooSmall as De,boundFleet as j,brokerCapReached as V,brokerThresholdsForDaemon as A}from"./fleet.js";import{admitBrokerMemory as Te}from"./memory-admission.js";import{beginBootFaultAttempt as Le,clearFault as He,hasAdmittedTurn as Me}from"../canvas/recovery.js";import{clearNodeLedger as X,forkNodeLedger as Ue,noticeChangedWhileDone as ze}from"./ledger.js";import{forkSourceNode as Ke,rootOfSpine as Ge}from"./nodes.js";import{checkStart as Je}from"./start-check.js";import{isReviewCompanionBound as We}from"../review/companion.js";import{assertProfileActive as je}from"../profiles/manifest.js";import{isPidAlive as Ve}from"../canvas/pid.js";import{crtrHome as Xe,jobDir as Ye}from"../canvas/paths.js";import{contextDir as qe,ensureNodeDirs as Qe,nodeAgentDir as Ze}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as Ie}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as er}from"../command-hooks/transport/exec-lifecycle.js";async function rr(e,r){const i=e.pi_session_file,t=i?v(i):null;if(t===null)return{};try{const a=await p().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof M)return{};throw a}}l(rr,"resumeArgs");async function Y(e){const r=v(e);if(r===null)return!1;try{return await p().header(r),!0}catch(i){if(i instanceof M)return!1;throw i}}l(Y,"conversationExists");async function ir(e){const r=v(e);return r===null?!0:(await p().read(r)).entries.some(i=>i.type==="message")}l(ir,"sessionHasConversation");async function tr(e){const r=v(e);if(r===null)return!0;const i=(await p().header(r)).header.cwd;return typeof i!="string"||H(i)}l(tr,"sessionCwdExists");async function nr(e,r,i,t){const a=r.pi_session_file;if(!a||!await Y(a))return{};const f=await ae(a);return f!==null&&f.node_id!==e&&Ve(f.pi_pid)?{}:t&&!await tr(a)?{}:await rr(r,i)}l(nr,"resumableSessionArgs");async function ar(e){return await J(e)===null&&await _e(e)===null&&!await ve(e)}l(ar,"isUnstartedBirth");async function Gr(e){const r=await g(e);if(r!==null&&r.trim()!=="")return!0;const i=await J(e);return i!==null&&i.trim()!==""}l(Gr,"hasFreshGroundState");async function Jr(e,r){const i=await q(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}l(Jr,"reviveNode");async function Wr(e,r,i){return q(e,{...r,viewerAttach:!0},i)}l(Wr,"decideAttachRevive");async function q(e,r,i){let t=await S(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await S(e)??t,t.run_id&&(await S(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);je(t.profile_id),te(t.grantee);const a=j();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&G(t))&&(await Je(t),await W(t.profile_id??null)!==null&&await Ne(t)),await a.has(e))return await C(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&G(t))return{parked:!0};const f=await a.reserveSlot(e);let o=null;if(f){try{o=await Te(e)}catch(w){throw a.releaseReservation(e),w}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw De(o);if(!f||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?Oe(o):V(A().automaticReviveCap,await a.occupancy());return t.frozen_at==null&&(await fe(e,new Date().toISOString()),await ce(e,{reason:"frozen"}),K({level:"info",event:"broker.frozen",node_id:e,fields:o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:A().automaticReviveCap}})),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await or(e,t,r)}}finally{a.releaseReservation(e)}}l(q,"decideRevive");async function or(e,r,i){const t=await W(r.profile_id??null);if(t===null&&!H(r.cwd)){const s=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new F({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:s})}r.host_kind!=="broker"&&(await $(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await We(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);const f=r.fork_from!=null&&r.pi_session_id==null,o=r.fork_source_file;if(f&&(typeof o!="string"||!ne(o)))throw new F({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const w=f&&typeof o=="string";if(w&&!await Y(o))throw new F({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${o}`,next:`Restore ${o}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:Q}=A();if(t!==null){const s=await Fe(t);if(s.state==="pending")throw new Ce(t,s.error)}const Z=t===null?xe:Ee("blaxel");let u;try{u=await oe(e,{daemonEpoch:j().epoch(),cap:Q,...t===null?{}:{runner:"blaxel",release:$e()}})}catch(s){if(s instanceof Ae)throw V(s.cap,s.live);if(s instanceof se)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw s}try{r.cycles=(r.cycles??0)+1,await $(e,{cycles:r.cycles});const b=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,h=w?{}:await nr(e,r,b,t===null),y=h.resumeSessionPath!==void 0,P=y&&h.newCycle===!0,m=y&&!P,B=!y&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await ar(e),R=m&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await Me(e)&&!await ir(h.resumeSessionPath)?(await g(e))?.trim():void 0,O=R!==void 0&&R!=="",I=w||B||O,D=r.cycle_pending===!0||P;await $(e,{cycle_pending:D}),r.cycle_pending=D,!w&&!y&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);let c,d=null;const x=l(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(w){const n=await g(e),E=v(o);if(E===null)throw new Error(`invalid fork source ${o}`);const re=(await p().header(await p().fork(E,e))).path;c=_(r,{resumeSessionPath:re,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(d=x()),await Ue(e,(await Ke(o))?.node_id??null)}else if(O)c=_(r,{...h,prompt:R}),d=x();else if(m)c=_(r,{...h,...i.wakeReason==="runtime-restart-abort"?{prompt:ye}:{}}),i.wakeReason==="runtime-restart-abort"&&(d={type:"runtime",kind:"restart-continuation"});else if(B){const n=(await g(e))?.trim();c=_(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(d=x()),await X(e)}else{const n=await me(r,u);c=_(r,{...h,prompt:await pe(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),d=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await X(e)}c.env={...c.env,CRTR_SUBTREE:await Ge(e)},d!==null&&(c.env[ge]=ke(d)),r.status==="done"&&r.last_settled_at!=null&&await ze(e,r.last_settled_at),await C(e,r.intent==="refresh"?"refresh-launch":"revive"),await He(e,{preserveEpisode:!0});const T=await Le(e);i.recovery!==!0&&i.viewerAttach!==!0&&await de(e),await Qe(e,r.grantee);const ee=Ie(r.cwd,r.profile_id).plans.get("node:start")??[];await er(ee,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await Ze(e),contextDir:await qe(e),jobDir:Ye(e)},runtime:{isBirth:I,canvasHome:Xe(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:u};const L=await Re(e,c,r.cwd);await Be(u,L);let k;try{const n=await Pe(e);if(k=await Z.start({executionId:u,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:L,cwd:r.cwd,resuming:m}),t===null&&k.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){try{await C(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await he(e,"notice.crash",`Child crashed \u2014 ${we(r)} (${e}) failed to relaunch and is now dead: ${n.message}
1
+ var te=Object.defineProperty;var l=(e,r)=>te(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as ne}from"./unsandboxed.js";import{existsSync as M}from"node:fs";import{conversationIdFromPath as v,conversationStore as p,ConversationNotFoundError as H}from"../conversation-store/index.js";import{isAbsolute as ae}from"node:path";import{findNodeBySessionFile as oe,getNode as $,updateNode as N,beginExecution as se,ExecutionAlreadyLive as le,recordExecutionPid as ce,abortExecution as C,endLiveExecution as ue,unbindSession as U,recordLaunch as fe,setFrozen as we,clearFrozen as G,fullName as de}from"../canvas/index.js";import{emitEvent as K}from"../events/emit.js";import{consumeDeadlineOnWake as he}from"../feed/messages.js";import{isParked as W,transition as F}from"./lifecycle.js";import{publishNotice as pe}from"./close.js";import{InputError as A}from"../io.js";import{buildPiArgv as _}from"./launch.js";import{buildReviveKickoff as me,drainBearings as ve,hasRetainedReports as _e,readGoal as g,readYieldMessage as ye,RUNTIME_RESTART_CONTINUATION as ke}from"./kickoff.js";import{readRoadmap as J}from"./roadmap.js";import{encodeKickoffOrigin as ge,KICKOFF_ORIGIN_ENV as be}from"./stamp/protocol.js";import{headlessBrokerHost as Re,brokerLaunchJson as xe}from"./host.js";import{processRunner as Ee}from"./runner/process.js";import{runnerForEnvironment as Se}from"./runner/index.js";import{blaxelEnvironmentOf as j}from"./blaxel-environment.js";import{recordLaunchFailure as $e}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as Ne,checkBilledWake as Ce,EnvironmentImagePending as Fe,imageForWake as Ae}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as Pe,liveExecutionForNode as Be,setExecutionLaunchJson as Oe}from"../canvas/executions.js";import{appMemoryFull as De,appMemoryTooSmall as Te,boundFleet as V,brokerCapReached as X,brokerThresholdsForDaemon as P}from"./fleet.js";import{admitBrokerMemory as Le}from"./memory-admission.js";import{beginBootFaultAttempt as ze,clearFault as Me,hasAdmittedTurn as He}from"../canvas/recovery.js";import{clearNodeLedger as Y,forkNodeLedger as Ue,noticeChangedWhileDone as Ge}from"./ledger.js";import{forkSourceNode as Ke,rootOfSpine as We}from"./nodes.js";import{ApiError as Je}from"../../api/errors.js";import{grantSnapshotMissing as je}from"../../daemon/readiness.js";import{checkStart as Ve}from"./start-check.js";import{isReviewCompanionBound as Xe}from"../review/companion.js";import{assertProfileActive as Ye}from"../profiles/manifest.js";import{isPidAlive as qe}from"../canvas/pid.js";import{crtrHome as Qe,jobDir as Ze}from"../canvas/paths.js";import{contextDir as Ie,ensureNodeDirs as er,nodeAgentDir as rr}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as ir}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as tr}from"../command-hooks/transport/exec-lifecycle.js";async function nr(e,r){const i=e.pi_session_file,t=i?v(i):null;if(t===null)return{};try{const a=await p().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof H)return{};throw a}}l(nr,"resumeArgs");async function q(e){const r=v(e);if(r===null)return!1;try{return await p().header(r),!0}catch(i){if(i instanceof H)return!1;throw i}}l(q,"conversationExists");async function ar(e){const r=v(e);return r===null?!0:(await p().read(r)).entries.some(i=>i.type==="message")}l(ar,"sessionHasConversation");async function or(e){const r=v(e);if(r===null)return!0;const i=(await p().header(r)).header.cwd;return typeof i!="string"||M(i)}l(or,"sessionCwdExists");async function sr(e,r,i,t){const a=r.pi_session_file;if(!a||!await q(a))return{};const w=await oe(a);return w!==null&&w.node_id!==e&&qe(w.pi_pid)?{}:t&&!await or(a)?{}:await nr(r,i)}l(sr,"resumableSessionArgs");async function lr(e){return await J(e)===null&&await ye(e)===null&&!await _e(e)}l(lr,"isUnstartedBirth");async function Xr(e){const r=await g(e);if(r!==null&&r.trim()!=="")return!0;const i=await J(e);return i!==null&&i.trim()!==""}l(Xr,"hasFreshGroundState");async function Yr(e,r){const i=await Q(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}l(Yr,"reviveNode");async function qr(e,r,i){return Q(e,{...r,viewerAttach:!0},i)}l(qr,"decideAttachRevive");async function Q(e,r,i){let t=await $(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await $(e)??t,t.run_id&&(await $(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);Ye(t.profile_id),ne(t.grantee);const a=V(),w=t.grantee==="app:terminal"&&await je();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&W(t))&&(await Ve(t),await j(t.profile_id??null)!==null&&await Ce(t)),await a.has(e))return await F(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&W(t))return{parked:!0};if(w){if(r.capacity==="refuse")throw new Je(503,"runtime_starting","the first grant snapshot from the directory has not committed");return await Z(e,t,{reason:"grant_snapshot"}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}const c=await a.reserveSlot(e);let o=null;if(c){try{o=await Le(e)}catch(b){throw a.releaseReservation(e),b}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw Te(o);if(!c||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?De(o):X(P().automaticReviveCap,await a.occupancy());return await Z(e,t,o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:P().automaticReviveCap}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await cr(e,t,r)}}finally{a.releaseReservation(e)}}l(Q,"decideRevive");async function Z(e,r,i){r.frozen_at==null&&(await we(e,new Date().toISOString()),await ue(e,{reason:"frozen"}),K({level:"info",event:"broker.frozen",node_id:e,fields:i}))}l(Z,"freezeForWait");async function cr(e,r,i){const t=await j(r.profile_id??null);if(t===null&&!M(r.cwd)){const s=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new A({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:s})}r.host_kind!=="broker"&&(await N(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await Xe(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);const w=r.fork_from!=null&&r.pi_session_id==null,c=r.fork_source_file;if(w&&(typeof c!="string"||!ae(c)))throw new A({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const o=w&&typeof c=="string";if(o&&!await q(c))throw new A({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${c}`,next:`Restore ${c}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:b}=P();if(t!==null){const s=await Ae(t);if(s.state==="pending")throw new Fe(t,s.error)}const I=t===null?Ee:Se("blaxel");let f;try{f=await se(e,{daemonEpoch:V().epoch(),cap:b,...t===null?{}:{runner:"blaxel",release:Ne()}})}catch(s){if(s instanceof Pe)throw X(s.cap,s.live);if(s instanceof le)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw s}try{r.cycles=(r.cycles??0)+1,await N(e,{cycles:r.cycles});const R=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,h=o?{}:await sr(e,r,R,t===null),y=h.resumeSessionPath!==void 0,B=y&&h.newCycle===!0,m=y&&!B,O=!y&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await lr(e),x=m&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await He(e)&&!await ar(h.resumeSessionPath)?(await g(e))?.trim():void 0,D=x!==void 0&&x!=="",ee=o||O||D,T=r.cycle_pending===!0||B;await N(e,{cycle_pending:T}),r.cycle_pending=T,!o&&!y&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);let u,d=null;const E=l(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(o){const n=await g(e),S=v(c);if(S===null)throw new Error(`invalid fork source ${c}`);const ie=(await p().header(await p().fork(S,e))).path;u=_(r,{resumeSessionPath:ie,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(d=E()),await Ue(e,(await Ke(c))?.node_id??null)}else if(D)u=_(r,{...h,prompt:x}),d=E();else if(m)u=_(r,{...h,...i.wakeReason==="runtime-restart-abort"?{prompt:ke}:{}}),i.wakeReason==="runtime-restart-abort"&&(d={type:"runtime",kind:"restart-continuation"});else if(O){const n=(await g(e))?.trim();u=_(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(d=E()),await Y(e)}else{const n=await ve(r,f);u=_(r,{...h,prompt:await me(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(u.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),d=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await Y(e)}u.env={...u.env,CRTR_SUBTREE:await We(e)},d!==null&&(u.env[be]=ge(d)),r.status==="done"&&r.last_settled_at!=null&&await Ge(e,r.last_settled_at),await F(e,r.intent==="refresh"?"refresh-launch":"revive"),await Me(e,{preserveEpisode:!0});const L=await ze(e);i.recovery!==!0&&i.viewerAttach!==!0&&await he(e),await er(e,r.grantee);const re=ir(r.cwd,r.profile_id).plans.get("node:start")??[];await tr(re,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await rr(e),contextDir:await Ie(e),jobDir:Ze(e)},runtime:{isBirth:ee,canvasHome:Qe(),profile:r.profile_id??null}}),u.env={...u.env,CRTR_EXECUTION_ID:f};const z=await xe(e,u,r.cwd);await Oe(f,z);let k;try{const n=await Be(e);if(k=await I.start({executionId:f,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:z,cwd:r.cwd,resuming:m}),t===null&&k.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){try{await F(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await pe(e,"notice.crash",`Child crashed \u2014 ${de(r)} (${e}) failed to relaunch and is now dead: ${n.message}
2
2
 
3
- It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await N(u,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await ue(e,new Date().toISOString()),t!==null)return await z(e),{window:null,session:r.tmux_session??null,resumed:m,faultExplanation:T.explanation,outcome:"launched"};if(await le(u,e,k.pid)==="shutdown")throw await N(u,{reason:"superseded"}),be.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await z(e),{window:null,session:r.tmux_session??null,resumed:m,launch:k,faultExplanation:T.explanation,outcome:"launched"}}catch(s){throw await N(u,{reason:"launch_failed"}),await Se(u,s).catch(b=>{K({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:b})}),s}}l(or,"launchRevive");export{Wr as decideAttachRevive,Gr as hasFreshGroundState,rr as resumeArgs,Jr as reviveNode};
3
+ It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await C(f,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await fe(e,new Date().toISOString()),t!==null)return await G(e),{window:null,session:r.tmux_session??null,resumed:m,faultExplanation:L.explanation,outcome:"launched"};if(await ce(f,e,k.pid)==="shutdown")throw await C(f,{reason:"superseded"}),Re.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await G(e),{window:null,session:r.tmux_session??null,resumed:m,launch:k,faultExplanation:L.explanation,outcome:"launched"}}catch(s){throw await C(f,{reason:"launch_failed"}),await $e(f,s).catch(R=>{K({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:R})}),s}}l(cr,"launchRevive");export{qr as decideAttachRevive,Xr as hasFreshGroundState,nr as resumeArgs,Yr as reviveNode};
@@ -1 +1 @@
1
- var c=Object.defineProperty;var n=(a,t)=>c(a,"name",{value:t,configurable:!0});import{check as s,findRefusal as u,payerOf as o}from"../ledger/index.js";import{RunError as l}from"../runs/operations.js";import{payerOfGrant as p,terminalGrant as f}from"../grants/mirror.js";async function e(a){const t=a.grantee??"app:terminal",r=a.sub??(t==="app:terminal"?(await f())?.sub:void 0)??null;if(r===null)return null;const i=await p(a.grant_id);return{scope:"start",payer:o({call:"start",grantPayer:i,sub:r,grantee:t}).payer,sub:r,grantee:t}}n(e,"startCandidateOf");async function w(a){const t=await e(a);t!==null&&await s(t)}n(w,"checkStart");async function R(a){const t=await e(a);return t===null?null:await u(t)}n(R,"startRefusal");function b(a){return a instanceof l&&a.code==="cap_exhausted"}n(b,"isStartRefusal");export{w as checkStart,b as isStartRefusal,e as startCandidateOf,R as startRefusal};
1
+ var c=Object.defineProperty;var n=(a,t)=>c(a,"name",{value:t,configurable:!0});import{check as o,findRefusal as s,payerOf as u}from"../ledger/index.js";import{RunError as l}from"../runs/operations.js";import{payerOfGrant as p}from"../grants/mirror.js";import{callerTerminalGrant as f}from"../grants/terminal.js";async function e(a){const t=a.grantee??"app:terminal",r=a.sub??(t==="app:terminal"?(await f())?.sub:void 0)??null;if(r===null)return null;const i=await p(a.grant_id);return{scope:"start",payer:u({call:"start",grantPayer:i,sub:r,grantee:t}).payer,sub:r,grantee:t}}n(e,"startCandidateOf");async function R(a){const t=await e(a);t!==null&&await o(t)}n(R,"checkStart");async function b(a){const t=await e(a);return t===null?null:await s(t)}n(b,"startRefusal");function O(a){return a instanceof l&&a.code==="cap_exhausted"}n(O,"isStartRefusal");export{R as checkStart,O as isStartRefusal,e as startCandidateOf,b as startRefusal};
@@ -1 +1 @@
1
- var I=Object.defineProperty;var o=(e,t)=>I(e,"name",{value:t,configurable:!0});import{canonicalSessionFile as q,findNodeBySessionFile as M,getNode as h,isWarmSpare as $,recordRespawnAttempt as j,recordSettledTurn as D,setPresence as k,subscriptionsOf as L,updateNode as v,updateNodeTelemetry as U}from"../../../core/canvas/canvas.js";import{liveExecutionForNode as x,recordExecutionIdle as W,recordExecutionPid as H}from"../../../core/canvas/executions.js";import{bindSession as G,readLiveSession as V,transferSession as J}from"../../../core/canvas/sessions.js";import{isSafeNodeId as Y}from"../../../core/canvas/paths.js";import{appForNode as z}from"../../../core/canvas/node-agent-paths.js";import{isPidAlive as K}from"../../../core/canvas/pid.js";import{editorLabel as S}from"../../../core/canvas/labels.js";import{assembleNodeSubject as Q}from"../../../core/substrate/subject.js";import{openDb as X,withCanvasWrite as p}from"../../../core/canvas/db.js";import{runningJobs as Z}from"../../../core/graph/jobs.js";import{notFound as g,usage as u}from"../../../core/errors.js";import{hasUnseenWakeMail as P}from"../../../core/canvas/deliveries.js";import{clearYieldMessage as ee}from"../../../core/runtime/kickoff.js";import{readRoadmap as te}from"../../../core/runtime/roadmap.js";import{forkSourceNode as ne}from"../../../core/runtime/nodes.js";import{transition as _}from"../../../core/runtime/lifecycle.js";import{confirmViewer as R,probeViewer as O,tearDownNode as F}from"../../../core/runtime/placement.js";import{hasAttachedBrokerViewers as ie}from"../viewer-presence.js";import{evaluateStop as re}from"../../../core/runtime/stop-guard.js";import{readOutputRequest as oe}from"../../../core/runtime/output-result.js";import{clearFault as w,readFault as ae,recordFault as se}from"../../../core/canvas/recovery.js";import{updateModelRecipe as de}from"../../../core/runtime/model-swap.js";import{completeCompanionRequestedSubmit as ce}from"../../review/finish.js";import{emitEvent as A}from"../../../core/events/emit.js";import{consumePendingPark as ue,recordPendingParkActivity as le}from"../../park-pending.js";import{shouldParkAfterSummary as pe}from"../../park-activity.js";import{ownerOnly as fe}from"../router.js";import{situationalContextEnvelope as me}from"../../../core/runtime/situational-context.js";import{requireCurrentExecution as f}from"./broker-recovery.js";import{ensureRunObserver as we}from"../../runs/observer.js";import{parseNodeTelemetryRecord as ye,writeNodeTelemetryRecord as he}from"./telemetry-record.js";import{mapAsync as E}from"../../../core/async-array.js";function l(e,t){if(typeof e!="object"||e===null||Array.isArray(e))throw u(`${t} requires a JSON body`);return e}o(l,"objectBody");function _e(e){const t=l(e,"broker session-bound");if(typeof t.executionId!="string"||t.executionId==="")throw u("executionId must be a non-empty string");if(typeof t.piSessionId!="string"||t.piSessionId==="")throw u("piSessionId must be a non-empty string");if(t.sessionFile!==null&&typeof t.sessionFile!="string")throw u("sessionFile must be a string or null");if(!Number.isSafeInteger(t.pid)||t.pid<=0)throw u("pid must be a positive safe integer");if(t.reason!==null&&typeof t.reason!="string")throw u("reason must be a string or null");if(!Array.isArray(t.reviewBoundaryIds)||!t.reviewBoundaryIds.every(n=>typeof n=="string"))throw u("reviewBoundaryIds must be an array of strings");return t}o(_e,"parseSessionBoundBody");function be(e){const t=l(e,"broker settle");if(typeof t.stopReason!="string")throw u("stopReason must be a string");for(const n of["pushedFinal"])if(typeof t[n]!="boolean")throw u(`${n} must be a boolean`);return t}o(be,"parseSettleBody");function ke(e){const t=l(e,"broker park-complete");if(t.outcome!=="completed"&&t.outcome!=="failed")throw u("outcome must be completed or failed");return t}o(ke,"parseParkCompleteBody");function ve(e){const t=l(e,"broker telemetry");if(typeof t.expected_execution_id!="string"||t.expected_execution_id==="")throw u("expected_execution_id must be a non-empty string");const n=ye(t);return{...n,expected_execution_id:t.expected_execution_id,updated_at:new Date(n.updated_at).toISOString()}}o(ve,"parseTelemetryBody");function ge(e){const t=l(e,"broker model");if(typeof t.spec!="string"||t.spec.trim()==="")throw u("spec must be a non-empty string");const n=t.intent;if(n!==void 0){const i=n!==null&&typeof n=="object"?n:void 0;if(i===void 0||typeof i.family!="string"||i.family===""||!["ultra","strong","medium","light"].includes(i.strength))throw u("intent must be { family, strength } with strength ultra|strong|medium|light when present")}if(typeof t.userSelected!="boolean")throw u("userSelected must be a boolean");return t}o(ge,"parseModelCommitBody");async function m(e){const t=await h(e);if(t===null)throw g(`unknown node: ${e}`,{received:e});return t}o(m,"requireNode");async function b(e){return{node_id:e.node_id,grantee:await z(e.node_id),name:e.name,...e.description===void 0?{}:{description:e.description},...e.title===void 0?{}:{title:e.title},...e.icon===void 0?{}:{icon:e.icon},kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,parent:e.parent??null,fork_from:e.fork_from??null,profile_id:e.profile_id??null,...e.managed_worktree===void 0?{}:{managed_worktree:e.managed_worktree===null?null:{state:e.managed_worktree.state,...e.managed_worktree.cleanup===void 0?{}:{cleanup:e.managed_worktree.cleanup},path:e.managed_worktree.path,branch:e.managed_worktree.branch,base_ref:e.managed_worktree.base_ref,base_sha:e.managed_worktree.base_sha}},...e.review_binding===void 0?{}:{review_binding:e.review_binding??null},intent:e.intent??null,...e.persona_ack===void 0?{}:{persona_ack:e.persona_ack},created:e.created}}o(b,"extensionNode");async function xe(e){const t=await m(e),n=[],i=new Set([e]);let r=t.parent??null;for(;r!==null&&r!==""&&!i.has(r);){i.add(r);const s=await h(r);if(s===null)break;n.unshift(await b(s)),r=s.parent??null}const a=await E((await E(await L(e),async s=>await h(s.node_id))).filter(s=>s!==null&&s.kind!=="human").sort((s,y)=>s.status===y.status?0:s.status==="active"?-1:y.status==="active"?1:0),b),d=typeof t.fork_source_file=="string"?await ne(t.fork_source_file):null,c=await Q(e);if(c===null)throw g(`unknown node: ${e}`,{received:e});return{node:await b(t),warm_spare:await $(e),situational_card:await me(e),ancestors:n,children:a,fork_source:d===null?null:await b(d),subject:c,roadmap:await te(e)}}o(xe,"extensionState");function T(e,t){return e?.kind===t.kind&&e?.mode===t.mode&&e?.lifecycle===t.lifecycle}o(T,"samePersona");function Se(e){const t=l(e,"broker generated-name");if(t.kind!=="initial"&&t.kind!=="recap")throw u("kind must be initial or recap");if(typeof t.description!="string"||typeof t.title!="string"||typeof t.icon!="string")throw u("description, title, and icon must be strings");if(t.kind==="initial")return t;const n=t.expected;if(typeof n!="object"||n===null||Array.isArray(n)||["name","description","title","icon","kind"].some(i=>typeof n[i]!="string"))throw u("recap expected must contain name, description, title, icon, and kind strings");return t}o(Se,"parseGeneratedNameBody");function Pe(e){const t=l(e,"broker persona-ack"),n=o(i=>{const r=t[i];if(typeof r!="object"||r===null||Array.isArray(r)||typeof r.kind!="string"||r.mode!=="base"&&r.mode!=="orchestrator"||r.lifecycle!=="terminal"&&r.lifecycle!=="resident")throw u(`${i} must be a persona state`);return r},"parse");return{expected_execution_id:t.expected_execution_id,from:n("from"),to:n("to")}}o(Pe,"parsePersonaAckBody");async function Re(e){const t=e.params.id,n=_e(e.body),i=n.sessionFile===null?null:q(n.sessionFile);return{status:200,body:await p(async a=>{await a.prepareUncached("SELECT 1 FROM nodes WHERE node_id = ? FOR UPDATE").get(t);const d=await m(t);if(await H(n.executionId,t,n.pid)==="shutdown")return{action:"shutdown"};const c=d.review_binding?.review_id;if(c!==void 0&&!n.reviewBoundaryIds.includes(c))return{action:"shutdown"};if(n.reason==="new"&&d.parent==null)return{action:"relaunch_root"};if(n.reason!=="new"&&d.fork_from!=null&&d.pi_session_id==null&&i===null)return{action:"shutdown"};const s=i===null?null:await M(i);if(s!==null&&s.node_id!==t){if(K(s.pi_pid))return await se(t,{link:"daemon\u2192node",op:"session-file-ownership",kind:"protocol",retry:{disposition:"fatal"},message:`refusing to bind ${i}: it is owned by live node ${s.node_id}`}),{action:"shutdown"};const y=await V(s.node_id);if(y===null||await J({fromNodeId:s.node_id,fromBindingId:y.bindingId,toNodeId:t,executionId:n.executionId,sessionId:n.piSessionId,transcriptPath:i})!=="transferred")return{action:"shutdown"}}else if(await G(t,{executionId:n.executionId,sessionId:n.piSessionId,transcriptPath:i})==="shutdown")return{action:"shutdown"};return n.reason!=="new"&&(await ee(t),d.intent==="refresh"&&await _(t,"revive")),{action:"none"}})}}o(Re,"handleSessionBound");async function B(e,t){return t.live!==null||await ie(e)||await P(e)}o(B,"isHeldAwake");async function N(e,t){return(await x(e))?.runner==="blaxel"&&!await B(e,t)&&(await Z(await X(),e)).length===0}o(N,"residentBlaxelCanIdle");async function Oe(e,t,n){const i=await h(e);if(i===null){if(t.pushedFinal)return{directive:{action:"shutdown"},closePane:!1};throw g(`unknown node: ${e}`,{received:e})}if(await D(e,new Date().toISOString()),(t.stopReason==="stop"||t.stopReason==="length")&&await j(e,{failures:0,notBefore:null}),t.stopReason==="quit"){let c=!1;return(i.status==="active"||i.status==="idle")&&i.intent===null&&(await _(e,"finish"),await w(e),await k(e,{pane:null,tmux_session:null,window:null}),c=!0),{directive:{action:"shutdown"},closePane:c}}if(t.stopReason!=="stop"&&t.stopReason!=="length")return await w(e,{link:"daemon\u2192node"}),i.intent==="refresh"?{directive:{action:"shutdown"},closePane:!1}:t.stopReason==="error"&&t.faultSettled===!0&&(await ae(e))?.retry.disposition==="fatal"&&await oe(e)===null&&await N(e,n)?{directive:{action:"stay_resident"},closePane:!1}:{directive:{action:"stay_dormant"},closePane:!1};const r={pushedFinal:t.pushedFinal};if(r.pushedFinal||i.status==="done")return await w(e),await k(e,{pane:null,tmux_session:null,window:null}),{directive:{action:"shutdown"},closePane:!0};if(i.intent==="refresh")return await w(e),{directive:{action:"shutdown"},closePane:!1};const a=await re(e,r);if(await w(e),a.action==="reprompt")return{directive:{action:"reprompt",prompt:a.message},closePane:!1};if(a.reason==="finished")return{directive:await P(e)?{action:"stay_dormant"}:{action:"shutdown"},closePane:!1};const d=await B(e,n);return(await x(e))?.runner==="blaxel"?{directive:await N(e,n)?{action:"stay_resident"}:{action:"stay_dormant"},closePane:!1}:a.reason!=="awaiting"&&a.reason!=="scheduled"?{directive:{action:"stay_dormant"},closePane:!1}:d?{directive:{action:"stay_dormant"},closePane:!1}:(await _(e,"release"),{directive:{action:"shutdown"},closePane:!1})}o(Oe,"settleBroker");async function Fe(e){const t=e.params.id,n=l(e.body,"broker idle");return await f(t,n.expected_execution_id),await W(n.expected_execution_id),{status:204}}o(Fe,"handleIdle");async function Ae(e){const t=e.params.id,n=be(e.body);await f(t,n.expected_execution_id);const i=await O(t),{directive:r,closePane:a}=await p(async()=>await Oe(t,n,await R(i)));return a&&await F(t),r.action!=="reprompt"&&ce(t).catch(d=>A({level:"error",event:"api.broker_ops.companion_submit_failed",node_id:t,error:d})),{status:200,body:r}}o(Ae,"handleSettle");async function Ee(e){const t=e.params.id,n=ke(e.body);await f(t,n.expected_execution_id);const i=await O(t),r=await p(async()=>{const a=await ue(t);if(a===void 0)return{action:"stay_dormant"};const d=await m(t);return await pe(t,d,a,await R(i))?(await w(t),await _(t,"park"),await k(t,{pane:null,tmux_session:null,window:null}),A({level:n.outcome==="completed"?"info":"warn",event:"broker.park.complete",...Y(t)?{node_id:t}:{},outcome:n.outcome==="completed"?"succeeded":"failed"}),{action:"shutdown"}):{action:"stay_dormant"}});return r.action==="shutdown"&&await F(t),{status:200,body:r}}o(Ee,"handleParkComplete");async function Te(e){const t=e.params.id;return await f(t,l(e.body,"broker park-activity").expected_execution_id),{status:200,body:await p(async()=>({activity:await le(t)?"recorded":"none"}))}}o(Te,"handleParkActivity");async function Be(e){const t=e.params.id,n=ve(e.body);return await f(t,n.expected_execution_id),await p(async()=>{await U(t,{tokensIn:n.tokens_in,contextTokens:n.context_tokens,lastActivity:n.last_activity,updatedAt:n.updated_at}),he(t,n)}),{status:204}}o(Be,"handleTelemetry");async function Ne(e){const t=e.params.id,n=ge(e.body);return await f(t,n.expected_execution_id),{status:200,body:{modelOverride:(await p(async()=>await de(await m(t),n.spec,n.intent,n.userSelected))).model_override}}}o(Ne,"handleModelCommit");let C=!0;function yt(e){C=e}o(yt,"setRequestsStartRunObservers");async function Ce(e){const t=e.params.id,n=await m(t);return n.run_id&&C&&await we(t,n.run_id),{status:200,body:await xe(t)}}o(Ce,"handleExtensionState");async function Ie(e){const t=e.params.id,n=Se(e.body);return await f(t,n.expected_execution_id),{status:200,body:await p(async r=>{await r.prepareUncached("SELECT 1 FROM nodes WHERE node_id = ? FOR UPDATE").get(t);const a=await m(t);if(n.kind==="initial"){if((a.description??"").trim()!=="")return{applied:!1};const s=await v(t,{description:n.description,...n.title.trim()===""?{}:{title:n.title},...n.icon.trim()===""?{}:{icon:n.icon}});return{applied:!0,editorLabel:S(s),description:s.description??"",title:s.title??"",icon:s.icon??""}}const d=n.expected;if(a.name!==d.name||(a.description??"")!==d.description||(a.title??"")!==d.title||(a.icon??"")!==d.icon||a.kind!==d.kind)return{applied:!1};const c=await v(t,{description:n.description,title:n.title.trim()===""?void 0:n.title,icon:n.icon.trim()===""?void 0:n.icon});return{applied:!0,editorLabel:S(c),description:c.description??"",title:c.title??"",icon:c.icon??""}})}}o(Ie,"handleGeneratedName");async function qe(e){const t=e.params.id,n=Pe(e.body);return await f(t,n.expected_execution_id),{status:200,body:await p(async r=>{await r.prepareUncached("SELECT 1 FROM nodes WHERE node_id = ? FOR UPDATE").get(t);const a=await m(t),d={kind:a.kind,mode:a.mode,lifecycle:a.lifecycle},c=a.persona_ack,s=c===void 0?d:{kind:c.kind??a.kind,mode:c.mode,lifecycle:c.lifecycle};return!T(d,n.to)||!T(s,n.from)?{applied:!1}:(await v(t,{persona_ack:n.to}),{applied:!0})})}}o(qe,"handlePersonaAck");const ht=fe([{method:"POST",pattern:"/v1/nodes/:id/broker/session-bound",handler:Re},{method:"POST",pattern:"/v1/nodes/:id/broker/settle",handler:Ae},{method:"POST",pattern:"/v1/nodes/:id/broker/idle",handler:Fe},{method:"POST",pattern:"/v1/nodes/:id/broker/park-complete",handler:Ee},{method:"POST",pattern:"/v1/nodes/:id/broker/park-activity",handler:Te},{method:"POST",pattern:"/v1/nodes/:id/broker/telemetry",handler:Be},{method:"POST",pattern:"/v1/nodes/:id/broker/model",handler:Ne},{method:"GET",pattern:"/v1/nodes/:id/broker/extension-state",handler:Ce},{method:"POST",pattern:"/v1/nodes/:id/broker/generated-name",handler:Ie},{method:"POST",pattern:"/v1/nodes/:id/broker/persona-ack",handler:qe}]);export{ht as brokerOperationRoutes,b as extensionNode,yt as setRequestsStartRunObservers};
1
+ var C=Object.defineProperty;var o=(e,t)=>C(e,"name",{value:t,configurable:!0});import{canonicalSessionFile as I,findNodeBySessionFile as q,getNode as b,getNodes as M,isWarmSpare as $,recordRespawnAttempt as j,recordSettledTurn as D,setPresence as k,subscriptionsOf as L,updateNode as g,updateNodeTelemetry as U}from"../../../core/canvas/canvas.js";import{liveExecutionForNode as x,recordExecutionIdle as W,recordExecutionPid as H}from"../../../core/canvas/executions.js";import{bindSession as G,readLiveSession as V,transferSession as J}from"../../../core/canvas/sessions.js";import{isSafeNodeId as Y}from"../../../core/canvas/paths.js";import{isPidAlive as z}from"../../../core/canvas/pid.js";import{editorLabel as S}from"../../../core/canvas/labels.js";import{assembleNodeSubject as K}from"../../../core/substrate/subject.js";import{openDb as Q,withCanvasWrite as p}from"../../../core/canvas/db.js";import{runningJobs as X}from"../../../core/graph/jobs.js";import{notFound as v,usage as u}from"../../../core/errors.js";import{hasUnseenWakeMail as P}from"../../../core/canvas/deliveries.js";import{clearYieldMessage as Z}from"../../../core/runtime/kickoff.js";import{readRoadmap as ee}from"../../../core/runtime/roadmap.js";import{forkSourceNode as te}from"../../../core/runtime/nodes.js";import{transition as h}from"../../../core/runtime/lifecycle.js";import{confirmViewer as R,probeViewer as O,tearDownNode as F}from"../../../core/runtime/placement.js";import{hasAttachedBrokerViewers as ne}from"../viewer-presence.js";import{evaluateStop as ie}from"../../../core/runtime/stop-guard.js";import{readOutputRequest as re}from"../../../core/runtime/output-result.js";import{clearFault as w,readFault as oe,recordFault as ae}from"../../../core/canvas/recovery.js";import{updateModelRecipe as se}from"../../../core/runtime/model-swap.js";import{completeCompanionRequestedSubmit as de}from"../../review/finish.js";import{emitEvent as A}from"../../../core/events/emit.js";import{consumePendingPark as ce,recordPendingParkActivity as ue}from"../../park-pending.js";import{shouldParkAfterSummary as le}from"../../park-activity.js";import{ownerOnly as pe}from"../router.js";import{situationalContextEnvelope as fe}from"../../../core/runtime/situational-context.js";import{requireCurrentExecution as f}from"./broker-recovery.js";import{ensureRunObserver as me}from"../../runs/observer.js";import{parseNodeTelemetryRecord as we,writeNodeTelemetryRecord as ye}from"./telemetry-record.js";import{mapAsync as he}from"../../../core/async-array.js";function l(e,t){if(typeof e!="object"||e===null||Array.isArray(e))throw u(`${t} requires a JSON body`);return e}o(l,"objectBody");function _e(e){const t=l(e,"broker session-bound");if(typeof t.executionId!="string"||t.executionId==="")throw u("executionId must be a non-empty string");if(typeof t.piSessionId!="string"||t.piSessionId==="")throw u("piSessionId must be a non-empty string");if(t.sessionFile!==null&&typeof t.sessionFile!="string")throw u("sessionFile must be a string or null");if(!Number.isSafeInteger(t.pid)||t.pid<=0)throw u("pid must be a positive safe integer");if(t.reason!==null&&typeof t.reason!="string")throw u("reason must be a string or null");if(!Array.isArray(t.reviewBoundaryIds)||!t.reviewBoundaryIds.every(n=>typeof n=="string"))throw u("reviewBoundaryIds must be an array of strings");return t}o(_e,"parseSessionBoundBody");function be(e){const t=l(e,"broker settle");if(typeof t.stopReason!="string")throw u("stopReason must be a string");for(const n of["pushedFinal"])if(typeof t[n]!="boolean")throw u(`${n} must be a boolean`);return t}o(be,"parseSettleBody");function ke(e){const t=l(e,"broker park-complete");if(t.outcome!=="completed"&&t.outcome!=="failed")throw u("outcome must be completed or failed");return t}o(ke,"parseParkCompleteBody");function ge(e){const t=l(e,"broker telemetry");if(typeof t.expected_execution_id!="string"||t.expected_execution_id==="")throw u("expected_execution_id must be a non-empty string");const n=we(t);return{...n,expected_execution_id:t.expected_execution_id,updated_at:new Date(n.updated_at).toISOString()}}o(ge,"parseTelemetryBody");function ve(e){const t=l(e,"broker model");if(typeof t.spec!="string"||t.spec.trim()==="")throw u("spec must be a non-empty string");const n=t.intent;if(n!==void 0){const i=n!==null&&typeof n=="object"?n:void 0;if(i===void 0||typeof i.family!="string"||i.family===""||!["ultra","strong","medium","light"].includes(i.strength))throw u("intent must be { family, strength } with strength ultra|strong|medium|light when present")}if(typeof t.userSelected!="boolean")throw u("userSelected must be a boolean");return t}o(ve,"parseModelCommitBody");async function m(e){const t=await b(e);if(t===null)throw v(`unknown node: ${e}`,{received:e});return t}o(m,"requireNode");async function _(e){return{node_id:e.node_id,grantee:e.grantee,name:e.name,...e.description===void 0?{}:{description:e.description},...e.title===void 0?{}:{title:e.title},...e.icon===void 0?{}:{icon:e.icon},kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,parent:e.parent??null,fork_from:e.fork_from??null,profile_id:e.profile_id??null,...e.managed_worktree===void 0?{}:{managed_worktree:e.managed_worktree===null?null:{state:e.managed_worktree.state,...e.managed_worktree.cleanup===void 0?{}:{cleanup:e.managed_worktree.cleanup},path:e.managed_worktree.path,branch:e.managed_worktree.branch,base_ref:e.managed_worktree.base_ref,base_sha:e.managed_worktree.base_sha}},...e.review_binding===void 0?{}:{review_binding:e.review_binding??null},intent:e.intent??null,...e.persona_ack===void 0?{}:{persona_ack:e.persona_ack},created:e.created}}o(_,"extensionNode");async function xe(e){const t=await m(e),n=[],i=new Set([e]);let r=t.parent??null;for(;r!==null&&r!==""&&!i.has(r);){i.add(r);const s=await b(r);if(s===null)break;n.unshift(await _(s)),r=s.parent??null}const a=await he((await M((await L(e)).map(s=>s.node_id))).filter(s=>s.kind!=="human").sort((s,y)=>s.status===y.status?0:s.status==="active"?-1:y.status==="active"?1:0),_),d=typeof t.fork_source_file=="string"?await te(t.fork_source_file):null,c=await K(e);if(c===null)throw v(`unknown node: ${e}`,{received:e});return{node:await _(t),warm_spare:await $(e),situational_card:await fe(e),ancestors:n,children:a,fork_source:d===null?null:await _(d),subject:c,roadmap:await ee(e)}}o(xe,"extensionState");function E(e,t){return e?.kind===t.kind&&e?.mode===t.mode&&e?.lifecycle===t.lifecycle}o(E,"samePersona");function Se(e){const t=l(e,"broker generated-name");if(t.kind!=="initial"&&t.kind!=="recap")throw u("kind must be initial or recap");if(typeof t.description!="string"||typeof t.title!="string"||typeof t.icon!="string")throw u("description, title, and icon must be strings");if(t.kind==="initial")return t;const n=t.expected;if(typeof n!="object"||n===null||Array.isArray(n)||["name","description","title","icon","kind"].some(i=>typeof n[i]!="string"))throw u("recap expected must contain name, description, title, icon, and kind strings");return t}o(Se,"parseGeneratedNameBody");function Pe(e){const t=l(e,"broker persona-ack"),n=o(i=>{const r=t[i];if(typeof r!="object"||r===null||Array.isArray(r)||typeof r.kind!="string"||r.mode!=="base"&&r.mode!=="orchestrator"||r.lifecycle!=="terminal"&&r.lifecycle!=="resident")throw u(`${i} must be a persona state`);return r},"parse");return{expected_execution_id:t.expected_execution_id,from:n("from"),to:n("to")}}o(Pe,"parsePersonaAckBody");async function Re(e){const t=e.params.id,n=_e(e.body),i=n.sessionFile===null?null:I(n.sessionFile);return{status:200,body:await p(async a=>{await a.prepareUncached("SELECT 1 FROM nodes WHERE node_id = ? FOR UPDATE").get(t);const d=await m(t);if(await H(n.executionId,t,n.pid)==="shutdown")return{action:"shutdown"};const c=d.review_binding?.review_id;if(c!==void 0&&!n.reviewBoundaryIds.includes(c))return{action:"shutdown"};if(n.reason==="new"&&d.parent==null)return{action:"relaunch_root"};if(n.reason!=="new"&&d.fork_from!=null&&d.pi_session_id==null&&i===null)return{action:"shutdown"};const s=i===null?null:await q(i);if(s!==null&&s.node_id!==t){if(z(s.pi_pid))return await ae(t,{link:"daemon\u2192node",op:"session-file-ownership",kind:"protocol",retry:{disposition:"fatal"},message:`refusing to bind ${i}: it is owned by live node ${s.node_id}`}),{action:"shutdown"};const y=await V(s.node_id);if(y===null||await J({fromNodeId:s.node_id,fromBindingId:y.bindingId,toNodeId:t,executionId:n.executionId,sessionId:n.piSessionId,transcriptPath:i})!=="transferred")return{action:"shutdown"}}else if(await G(t,{executionId:n.executionId,sessionId:n.piSessionId,transcriptPath:i})==="shutdown")return{action:"shutdown"};return n.reason!=="new"&&(await Z(t),d.intent==="refresh"&&await h(t,"revive")),{action:"none"}})}}o(Re,"handleSessionBound");async function T(e,t){return t.live!==null||await ne(e)||await P(e)}o(T,"isHeldAwake");async function B(e,t){return(await x(e))?.runner==="blaxel"&&!await T(e,t)&&(await X(await Q(),e)).length===0}o(B,"residentBlaxelCanIdle");async function Oe(e,t,n){const i=await b(e);if(i===null){if(t.pushedFinal)return{directive:{action:"shutdown"},closePane:!1};throw v(`unknown node: ${e}`,{received:e})}if(await D(e,new Date().toISOString()),(t.stopReason==="stop"||t.stopReason==="length")&&await j(e,{failures:0,notBefore:null}),t.stopReason==="quit"){let c=!1;return(i.status==="active"||i.status==="idle")&&i.intent===null&&(await h(e,"finish"),await w(e),await k(e,{pane:null,tmux_session:null,window:null}),c=!0),{directive:{action:"shutdown"},closePane:c}}if(t.stopReason!=="stop"&&t.stopReason!=="length")return await w(e,{link:"daemon\u2192node"}),i.intent==="refresh"?{directive:{action:"shutdown"},closePane:!1}:t.stopReason==="error"&&t.faultSettled===!0&&(await oe(e))?.retry.disposition==="fatal"&&await re(e)===null&&await B(e,n)?{directive:{action:"stay_resident"},closePane:!1}:{directive:{action:"stay_dormant"},closePane:!1};const r={pushedFinal:t.pushedFinal};if(r.pushedFinal||i.status==="done")return await w(e),await k(e,{pane:null,tmux_session:null,window:null}),{directive:{action:"shutdown"},closePane:!0};if(i.intent==="refresh")return await w(e),{directive:{action:"shutdown"},closePane:!1};const a=await ie(e,r);if(await w(e),a.action==="reprompt")return{directive:{action:"reprompt",prompt:a.message},closePane:!1};if(a.reason==="finished")return{directive:await P(e)?{action:"stay_dormant"}:{action:"shutdown"},closePane:!1};const d=await T(e,n);return(await x(e))?.runner==="blaxel"?{directive:await B(e,n)?{action:"stay_resident"}:{action:"stay_dormant"},closePane:!1}:a.reason!=="awaiting"&&a.reason!=="scheduled"?{directive:{action:"stay_dormant"},closePane:!1}:d?{directive:{action:"stay_dormant"},closePane:!1}:(await h(e,"release"),{directive:{action:"shutdown"},closePane:!1})}o(Oe,"settleBroker");async function Fe(e){const t=e.params.id,n=l(e.body,"broker idle");return await f(t,n.expected_execution_id),await W(n.expected_execution_id),{status:204}}o(Fe,"handleIdle");async function Ae(e){const t=e.params.id,n=be(e.body);await f(t,n.expected_execution_id);const i=await O(t),{directive:r,closePane:a}=await p(async()=>await Oe(t,n,await R(i)));return a&&await F(t),r.action!=="reprompt"&&de(t).catch(d=>A({level:"error",event:"api.broker_ops.companion_submit_failed",node_id:t,error:d})),{status:200,body:r}}o(Ae,"handleSettle");async function Ee(e){const t=e.params.id,n=ke(e.body);await f(t,n.expected_execution_id);const i=await O(t),r=await p(async()=>{const a=await ce(t);if(a===void 0)return{action:"stay_dormant"};const d=await m(t);return await le(t,d,a,await R(i))?(await w(t),await h(t,"park"),await k(t,{pane:null,tmux_session:null,window:null}),A({level:n.outcome==="completed"?"info":"warn",event:"broker.park.complete",...Y(t)?{node_id:t}:{},outcome:n.outcome==="completed"?"succeeded":"failed"}),{action:"shutdown"}):{action:"stay_dormant"}});return r.action==="shutdown"&&await F(t),{status:200,body:r}}o(Ee,"handleParkComplete");async function Te(e){const t=e.params.id;return await f(t,l(e.body,"broker park-activity").expected_execution_id),{status:200,body:await p(async()=>({activity:await ue(t)?"recorded":"none"}))}}o(Te,"handleParkActivity");async function Be(e){const t=e.params.id,n=ge(e.body);return await f(t,n.expected_execution_id),await p(async()=>{await U(t,{tokensIn:n.tokens_in,contextTokens:n.context_tokens,lastActivity:n.last_activity,updatedAt:n.updated_at}),ye(t,n)}),{status:204}}o(Be,"handleTelemetry");async function Ne(e){const t=e.params.id,n=ve(e.body);return await f(t,n.expected_execution_id),{status:200,body:{modelOverride:(await p(async()=>await se(await m(t),n.spec,n.intent,n.userSelected))).model_override}}}o(Ne,"handleModelCommit");let N=!0;function wt(e){N=e}o(wt,"setRequestsStartRunObservers");async function Ce(e){const t=e.params.id,n=await m(t);return n.run_id&&N&&await me(t,n.run_id),{status:200,body:await xe(t)}}o(Ce,"handleExtensionState");async function Ie(e){const t=e.params.id,n=Se(e.body);return await f(t,n.expected_execution_id),{status:200,body:await p(async r=>{await r.prepareUncached("SELECT 1 FROM nodes WHERE node_id = ? FOR UPDATE").get(t);const a=await m(t);if(n.kind==="initial"){if((a.description??"").trim()!=="")return{applied:!1};const s=await g(t,{description:n.description,...n.title.trim()===""?{}:{title:n.title},...n.icon.trim()===""?{}:{icon:n.icon}});return{applied:!0,editorLabel:S(s),description:s.description??"",title:s.title??"",icon:s.icon??""}}const d=n.expected;if(a.name!==d.name||(a.description??"")!==d.description||(a.title??"")!==d.title||(a.icon??"")!==d.icon||a.kind!==d.kind)return{applied:!1};const c=await g(t,{description:n.description,title:n.title.trim()===""?void 0:n.title,icon:n.icon.trim()===""?void 0:n.icon});return{applied:!0,editorLabel:S(c),description:c.description??"",title:c.title??"",icon:c.icon??""}})}}o(Ie,"handleGeneratedName");async function qe(e){const t=e.params.id,n=Pe(e.body);return await f(t,n.expected_execution_id),{status:200,body:await p(async r=>{await r.prepareUncached("SELECT 1 FROM nodes WHERE node_id = ? FOR UPDATE").get(t);const a=await m(t),d={kind:a.kind,mode:a.mode,lifecycle:a.lifecycle},c=a.persona_ack,s=c===void 0?d:{kind:c.kind??a.kind,mode:c.mode,lifecycle:c.lifecycle};return!E(d,n.to)||!E(s,n.from)?{applied:!1}:(await g(t,{persona_ack:n.to}),{applied:!0})})}}o(qe,"handlePersonaAck");const yt=pe([{method:"POST",pattern:"/v1/nodes/:id/broker/session-bound",handler:Re},{method:"POST",pattern:"/v1/nodes/:id/broker/settle",handler:Ae},{method:"POST",pattern:"/v1/nodes/:id/broker/idle",handler:Fe},{method:"POST",pattern:"/v1/nodes/:id/broker/park-complete",handler:Ee},{method:"POST",pattern:"/v1/nodes/:id/broker/park-activity",handler:Te},{method:"POST",pattern:"/v1/nodes/:id/broker/telemetry",handler:Be},{method:"POST",pattern:"/v1/nodes/:id/broker/model",handler:Ne},{method:"GET",pattern:"/v1/nodes/:id/broker/extension-state",handler:Ce},{method:"POST",pattern:"/v1/nodes/:id/broker/generated-name",handler:Ie},{method:"POST",pattern:"/v1/nodes/:id/broker/persona-ack",handler:qe}]);export{yt as brokerOperationRoutes,_ as extensionNode,wt as setRequestsStartRunObservers};
@@ -1 +1 @@
1
- var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as G,sep as F}from"node:path";import{ApiError as s}from"../../../api/errors.js";import{armCron as rn,parseCronSink as on,getCron as v,listCrons as P,listCronRuns as L,pokeHeldCrons as tn,setCronState as Z}from"../../../core/canvas/crons.js";import{getNode as _}from"../../../core/canvas/canvas.js";import{appSpace as an,cronFolder as sn}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn,terminalGrant as fn}from"../../../core/grants/mirror.js";import{assertScope as H}from"../../../core/scopes.js";import{InputError as c}from"../../../core/io.js";import{nextSlotAfter as wn}from"../../../core/wake.js";import{executeCron as _n,markSelfCanceledCronRun as hn}from"../../cron-run.js";import{toCronDTO as k,toCronRunDTO as M}from"../map.js";import{appOf as T,assertCeiling as mn,canReadNode as gn}from"../router.js";import{checkCronBirth as yn,checkCronFire as bn,cronRefusalError as vn}from"../../cron/authority.js";import{endCron as kn}from"../../cron/ended.js";import{requireUsableProfile as Sn}from"./profiles.js";import{filterAsync as $n,mapAsync as On}from"../../../core/async-array.js";import{personId as Cn}from"../../../core/canvas/person.js";function E(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new c({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(E,"requireString");function O(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new c({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(O,"requireEnum");async function In(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new c({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(await _(n)===null)throw new c({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 c({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 c({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(In,"parseSinkSpec");const qn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function Y(e,n){if(!qn.test(e)||Number.isNaN(Date.parse(e)))throw new c({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(Y,"requireUtcIso");function Nn(e){try{wn(e,new Date)}catch(n){throw new c({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(Nn,"requireRecur");function jn(e){let n;try{n=JSON.parse(e)}catch{throw new c({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 c({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 c({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(jn,"requireEnvJson");function U(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(U,"callerProfile");function K(e,n){return n===null||e.scope==="global"||e.profile===n}t(K,"inScope");function J(e){if(e.principal.class==="person"&&e.principal.sub!==void 0)throw new s(403,"permission_error","schedules are managed by the install's person, a node or an app");return e.principal.class==="person"?null:T(e.principal)}t(J,"callerApp");async function R(e,n){return await gn(e.principal,n.grantee,n.created_by??void 0)}t(R,"visibleTo");function An(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(An,"anchorNodeOf");async function Pn(e,n,r,o,i,d){const u=typeof e.cwd=="string"&&e.cwd.trim()!==""?G(e.cwd):null;if(!cn()){if(u===null)throw new c({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return u}const h=o!==null&&await _(o)!==null?o:void 0,l=t(async q=>await ln(n,q,h,i,void 0,d),"reaches");if(u!==null&&await l(u))return u;const a=an(n).files;if(u!==null&&e.cwd_is_default!==!0)throw new s(400,"invalid_request",`cwd ${u} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${a}`,{cwd:u,default:a},void 0,{param:"cwd"});const p=r===null?null:(await _(r))?.cwd??null;return p!==null&&await l(p)?p:a}t(Pn,"resolveArmedCwd");async function Tn(e,n,r){const o=e.principal;if(o.class==="person"){if(o.sub!==void 0)throw new s(403,"permission_error","schedules are managed by the install's person, a node or an app");const a=await fn();return{sub:a?.sub??Cn(),grant_id:a?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const a=await un(n);if(a===null)throw new s(401,"grant_revoked",`no active grant for ${n}`);const p=e.ceiling??o.scopes;return{sub:a.sub,grant_id:a.id,scopes:p===null?null:[...p],origin_run_id:null}}if(o.class==="cron"){const a=await bn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!a.allowed)throw new s(a.reason==="scope_missing"?403:401,a.reason==="scope_missing"?"scope_missing":"grant_revoked",a.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?null:[...o.scopes],origin_run_id:o.origin_run,armed_from_exec:o.from_exec}}if(o.class!=="broker")throw new s(401,"unauthorized","this caller cannot arm a cron");const i=r??await _(o.node),d=i?.grant_id??o.grant,u=i?.sub??o.sub,h=d!=null?await dn(d):await pn(n,u);if(h===null)throw new s(401,"grant_revoked","the node grant is not active");const l=i?.scopes??o.scopes;return{sub:h.sub,grant_id:h.id,scopes:l===null?null:[...l],origin_run_id:i?.run_id??o.run}}t(Tn,"armPrincipal");async function En(e){const n=e.body??{},r=E(n,"name"),o=E(n,"command"),i=Y(E(n,"fire_at"),"fire_at"),d=typeof n.recur=="string"&&n.recur!==""?Nn(n.recur):null,u=typeof n.expires_at=="string"&&n.expires_at!==""?Y(n.expires_at,"expires_at"):null,h=typeof n.env_json=="string"&&n.env_json!==""?jn(n.env_json):null,l=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,a=O(n.on_output,"on_output",["silent","on-failure","always","on-change"],l?.startsWith("node:")?"on-change":"on-failure"),p=O(n.overlap,"overlap",["skip","queue","replace"],"skip"),q=O(n.tier,"tier",["normal","urgent","critical"],"normal"),W=a==="always"||a==="on-change";if(W&&l===null)throw new c({error:"sink_required",message:`on_output "${a}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!W&&l!==null)throw new c({error:"sink_unused",message:`on_output "${a}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=l!==null?await In(l):"{}";if(l?.startsWith("spawn:")&&(mn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const y=await yn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,from_exec:e.principal.from_exec},{kind:l.slice(6)});if(!y.allowed)throw vn(y)}const g=T(e.principal)??"app:terminal",b=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==b)throw new s(403,"permission_error","a broker may only arm a cron as its own node");const m=b===null?null:await _(b);if(m!==null){if(m.grantee!==g)throw new c({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});H(m,"crtr:schedule"),l?.startsWith("spawn:")&&H(m,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new s(400,"invalid_request","isolated must be a boolean");if(m?.isolated&&n.isolated===!1)throw new s(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const x=m?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(x&&n.isolated===!1)throw new s(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const N=x||n.isolated===!0,f=await Tn(e,g,m),j=nn(),S=N?sn(g,j):null;if(S!==null&&n.cwd_is_default!==!0){const y=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?G(n.cwd):S);if(y===".."||y.startsWith(`..${F}`)||y.startsWith(F))throw new s(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??await Pn(n,g,b,An(n),N,{sub:f.sub,grantId:f.grant_id,scopes:f.scopes}),w=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(w!==null&&await _(w)!==null&&(await _(w)).grantee!==g)throw new c({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(w!==null&&await _(w)===null)throw new c({error:"anchor_not_found",message:`anchor node ${w} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const B=n.cancel_on_wake===!0;if(B&&w===null)throw new c({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new c({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const A=typeof n.profile=="string"&&n.profile!==""?(await Sn(e,n.profile)).profileId:null,z=O(n.scope,"scope",["profile","global"],A!==null?"profile":"global");if(z==="profile"&&A===null)throw new c({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return await rn({cron_id:j,name:r,created_by:b,grantee:g,sub:f.sub,grant_id:f.grant_id,scopes:f.scopes,origin_run_id:f.origin_run_id,armed_from_exec:f.armed_from_exec===!0,isolated:N,command:o,fire_at:i,recur:d,tz:typeof n.tz=="string"?n.tz:null,expires_at:u,anchor_node:w,cancel_on_wake:B,cwd:V,env_json:h,profile:A,scope:z,run_timeout_s:$,overlap:p,on_output:a,sink:Q,tier:q}),{status:201,body:k(await v(j),null)}}t(En,"handleArm");async function Un(e){J(e);const n=U(e),r=e.query.get("created_by");if(r==="")throw new s(400,"invalid_request","created_by must be nonempty when present");const o=await $n((n===null?await P({all:!0}):[...await P({profile:n}),...await P({global:!0})].sort((i,d)=>i.fire_at<d.fire_at?-1:i.fire_at>d.fire_at?1:0)).filter(i=>r===null||i.created_by===r),async i=>await R(e,i));return{status:200,body:await On(o,async i=>k(i,await D(i)))}}t(Un,"handleList");async function D(e){return(await L(e.cron_id))[0]??null}t(D,"lastRunOf");async function C(e){const n=e.params.cronId,r=await v(n);if(r===null||!await R(e,r))throw new s(404,"cron_not_found",`no cron ${n}`);if(!K(r,U(e)))throw new s(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(C,"requireCron");function I(e,n){const r=J(e);if(r!==null&&n.grantee!==r)throw new s(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");async function Jn(e){const n=await C(e),r=await L(n.cron_id);return{status:200,body:{cron:k(n,r[0]??null),runs:r.map(M)}}}t(Jn,"handleShow");async function Rn(e){const n=I(e,await C(e));await Z(n.cron_id,"paused");const r=await v(n.cron_id);return{status:200,body:k(r,await D(r))}}t(Rn,"handlePause");async function Dn(e){const n=I(e,await C(e));await Z(n.cron_id,"active");const r=await v(n.cron_id);return{status:200,body:k(r,await D(r))}}t(Dn,"handleResume");async function Wn(e){const n=I(e,await C(e));if(n.run_state==="running")throw new s(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 s(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:M(r)}}t(Wn,"handleRun");async function xn(e){const n=new Date().toISOString();return{status:200,body:{unparked:await tn(n,J(e)),at:n}}}t(xn,"handlePoke");function Bn(e,n){const r=e.principal;if(r.class==="cron")return r.kind==="exec"?{kind:"canceled",by:r.app}:r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const i=on(n.sink);return i?.kind==="node"&&i.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=T(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Bn,"cancelNotice");async function zn(e){const n=e.params.cronId,r=await v(n);if(r!==null&&!await R(e,r))throw new s(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!K(r,U(e)))throw new s(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),i=e.principal.class==="cron"?e.principal.kind==="cron"&&o===e.principal.execution&&n===e.principal.cron:!0,d=o!==null&&i&&hn(n,o);return r!==null&&await kn(r,d?null:Bn(e,r),{preserveRunningProcess:d}),{status:204}}t(zn,"handleCancel");const le=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:En},{method:"GET",pattern:"/v1/crons",handler:Un},{method:"GET",pattern:"/v1/crons/:cronId",handler:Jn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:Rn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:Dn},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:xn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Wn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:zn}];export{le as cronRoutes};
1
+ var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as G,sep as F}from"node:path";import{ApiError as s}from"../../../api/errors.js";import{armCron as rn,parseCronSink as on,getCron as v,listCrons as A,listCronRuns as L,pokeHeldCrons as tn,setCronState as Z}from"../../../core/canvas/crons.js";import{getNode as _}from"../../../core/canvas/canvas.js";import{appSpace as an,cronFolder as sn}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn}from"../../../core/grants/mirror.js";import{callerTerminalGrant as fn}from"../../../core/grants/terminal.js";import{assertScope as H}from"../../../core/scopes.js";import{InputError as c}from"../../../core/io.js";import{nextSlotAfter as wn}from"../../../core/wake.js";import{executeCron as _n,markSelfCanceledCronRun as hn}from"../../cron-run.js";import{toCronDTO as k,toCronRunDTO as M}from"../map.js";import{appOf as P,assertCeiling as mn,canReadNode as gn}from"../router.js";import{checkCronBirth as yn,checkCronFire as bn,cronRefusalError as vn}from"../../cron/authority.js";import{endCron as kn}from"../../cron/ended.js";import{requireUsableProfile as Sn}from"./profiles.js";import{filterAsync as $n,mapAsync as On}from"../../../core/async-array.js";import{personId as Cn}from"../../../core/canvas/person.js";function E(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new c({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(E,"requireString");function O(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new c({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(O,"requireEnum");async function In(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new c({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(await _(n)===null)throw new c({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 c({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 c({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(In,"parseSinkSpec");const qn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function Y(e,n){if(!qn.test(e)||Number.isNaN(Date.parse(e)))throw new c({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(Y,"requireUtcIso");function Nn(e){try{wn(e,new Date)}catch(n){throw new c({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(Nn,"requireRecur");function jn(e){let n;try{n=JSON.parse(e)}catch{throw new c({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 c({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 c({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(jn,"requireEnvJson");function U(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(U,"callerProfile");function K(e,n){return n===null||e.scope==="global"||e.profile===n}t(K,"inScope");function J(e){if(e.principal.class==="person"&&e.principal.sub!==void 0)throw new s(403,"permission_error","schedules are managed by the install's person, a node or an app");return e.principal.class==="person"?null:P(e.principal)}t(J,"callerApp");async function R(e,n){return await gn(e.principal,n.grantee,n.created_by??void 0)}t(R,"visibleTo");function Tn(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(Tn,"anchorNodeOf");async function An(e,n,r,o,i,d){const u=typeof e.cwd=="string"&&e.cwd.trim()!==""?G(e.cwd):null;if(!cn()){if(u===null)throw new c({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return u}const h=o!==null&&await _(o)!==null?o:void 0,l=t(async q=>await ln(n,q,h,i,void 0,d),"reaches");if(u!==null&&await l(u))return u;const a=an(n).files;if(u!==null&&e.cwd_is_default!==!0)throw new s(400,"invalid_request",`cwd ${u} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${a}`,{cwd:u,default:a},void 0,{param:"cwd"});const p=r===null?null:(await _(r))?.cwd??null;return p!==null&&await l(p)?p:a}t(An,"resolveArmedCwd");async function Pn(e,n,r){const o=e.principal;if(o.class==="person"){if(o.sub!==void 0)throw new s(403,"permission_error","schedules are managed by the install's person, a node or an app");const a=await fn();return{sub:a?.sub??Cn(),grant_id:a?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const a=await un(n);if(a===null)throw new s(401,"grant_revoked",`no active grant for ${n}`);const p=e.ceiling??o.scopes;return{sub:a.sub,grant_id:a.id,scopes:p===null?null:[...p],origin_run_id:null}}if(o.class==="cron"){const a=await bn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!a.allowed)throw new s(a.reason==="scope_missing"?403:401,a.reason==="scope_missing"?"scope_missing":"grant_revoked",a.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?null:[...o.scopes],origin_run_id:o.origin_run,armed_from_exec:o.from_exec}}if(o.class!=="broker")throw new s(401,"unauthorized","this caller cannot arm a cron");const i=r??await _(o.node),d=i?.grant_id??o.grant,u=i?.sub??o.sub,h=d!=null?await dn(d):await pn(n,u);if(h===null)throw new s(401,"grant_revoked","the node grant is not active");const l=i?.scopes??o.scopes;return{sub:h.sub,grant_id:h.id,scopes:l===null?null:[...l],origin_run_id:i?.run_id??o.run}}t(Pn,"armPrincipal");async function En(e){const n=e.body??{},r=E(n,"name"),o=E(n,"command"),i=Y(E(n,"fire_at"),"fire_at"),d=typeof n.recur=="string"&&n.recur!==""?Nn(n.recur):null,u=typeof n.expires_at=="string"&&n.expires_at!==""?Y(n.expires_at,"expires_at"):null,h=typeof n.env_json=="string"&&n.env_json!==""?jn(n.env_json):null,l=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,a=O(n.on_output,"on_output",["silent","on-failure","always","on-change"],l?.startsWith("node:")?"on-change":"on-failure"),p=O(n.overlap,"overlap",["skip","queue","replace"],"skip"),q=O(n.tier,"tier",["normal","urgent","critical"],"normal"),W=a==="always"||a==="on-change";if(W&&l===null)throw new c({error:"sink_required",message:`on_output "${a}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!W&&l!==null)throw new c({error:"sink_unused",message:`on_output "${a}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=l!==null?await In(l):"{}";if(l?.startsWith("spawn:")&&(mn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const y=await yn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,from_exec:e.principal.from_exec},{kind:l.slice(6)});if(!y.allowed)throw vn(y)}const g=P(e.principal)??"app:terminal",b=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==b)throw new s(403,"permission_error","a broker may only arm a cron as its own node");const m=b===null?null:await _(b);if(m!==null){if(m.grantee!==g)throw new c({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});H(m,"crtr:schedule"),l?.startsWith("spawn:")&&H(m,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new s(400,"invalid_request","isolated must be a boolean");if(m?.isolated&&n.isolated===!1)throw new s(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const x=m?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(x&&n.isolated===!1)throw new s(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const N=x||n.isolated===!0,f=await Pn(e,g,m),j=nn(),S=N?sn(g,j):null;if(S!==null&&n.cwd_is_default!==!0){const y=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?G(n.cwd):S);if(y===".."||y.startsWith(`..${F}`)||y.startsWith(F))throw new s(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??await An(n,g,b,Tn(n),N,{sub:f.sub,grantId:f.grant_id,scopes:f.scopes}),w=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(w!==null&&await _(w)!==null&&(await _(w)).grantee!==g)throw new c({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(w!==null&&await _(w)===null)throw new c({error:"anchor_not_found",message:`anchor node ${w} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const B=n.cancel_on_wake===!0;if(B&&w===null)throw new c({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new c({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 T=typeof n.profile=="string"&&n.profile!==""?(await Sn(e,n.profile)).profileId:null,z=O(n.scope,"scope",["profile","global"],T!==null?"profile":"global");if(z==="profile"&&T===null)throw new c({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return await rn({cron_id:j,name:r,created_by:b,grantee:g,sub:f.sub,grant_id:f.grant_id,scopes:f.scopes,origin_run_id:f.origin_run_id,armed_from_exec:f.armed_from_exec===!0,isolated:N,command:o,fire_at:i,recur:d,tz:typeof n.tz=="string"?n.tz:null,expires_at:u,anchor_node:w,cancel_on_wake:B,cwd:V,env_json:h,profile:T,scope:z,run_timeout_s:$,overlap:p,on_output:a,sink:Q,tier:q}),{status:201,body:k(await v(j),null)}}t(En,"handleArm");async function Un(e){J(e);const n=U(e),r=e.query.get("created_by");if(r==="")throw new s(400,"invalid_request","created_by must be nonempty when present");const o=await $n((n===null?await A({all:!0}):[...await A({profile:n}),...await A({global:!0})].sort((i,d)=>i.fire_at<d.fire_at?-1:i.fire_at>d.fire_at?1:0)).filter(i=>r===null||i.created_by===r),async i=>await R(e,i));return{status:200,body:await On(o,async i=>k(i,await D(i)))}}t(Un,"handleList");async function D(e){return(await L(e.cron_id))[0]??null}t(D,"lastRunOf");async function C(e){const n=e.params.cronId,r=await v(n);if(r===null||!await R(e,r))throw new s(404,"cron_not_found",`no cron ${n}`);if(!K(r,U(e)))throw new s(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(C,"requireCron");function I(e,n){const r=J(e);if(r!==null&&n.grantee!==r)throw new s(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");async function Jn(e){const n=await C(e),r=await L(n.cron_id);return{status:200,body:{cron:k(n,r[0]??null),runs:r.map(M)}}}t(Jn,"handleShow");async function Rn(e){const n=I(e,await C(e));await Z(n.cron_id,"paused");const r=await v(n.cron_id);return{status:200,body:k(r,await D(r))}}t(Rn,"handlePause");async function Dn(e){const n=I(e,await C(e));await Z(n.cron_id,"active");const r=await v(n.cron_id);return{status:200,body:k(r,await D(r))}}t(Dn,"handleResume");async function Wn(e){const n=I(e,await C(e));if(n.run_state==="running")throw new s(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 s(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:M(r)}}t(Wn,"handleRun");async function xn(e){const n=new Date().toISOString();return{status:200,body:{unparked:await tn(n,J(e)),at:n}}}t(xn,"handlePoke");function Bn(e,n){const r=e.principal;if(r.class==="cron")return r.kind==="exec"?{kind:"canceled",by:r.app}:r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const i=on(n.sink);return i?.kind==="node"&&i.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=P(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Bn,"cancelNotice");async function zn(e){const n=e.params.cronId,r=await v(n);if(r!==null&&!await R(e,r))throw new s(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!K(r,U(e)))throw new s(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),i=e.principal.class==="cron"?e.principal.kind==="cron"&&o===e.principal.execution&&n===e.principal.cron:!0,d=o!==null&&i&&hn(n,o);return r!==null&&await kn(r,d?null:Bn(e,r),{preserveRunningProcess:d}),{status:204}}t(zn,"handleCancel");const de=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:En},{method:"GET",pattern:"/v1/crons",handler:Un},{method:"GET",pattern:"/v1/crons/:cronId",handler:Jn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:Rn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:Dn},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:xn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Wn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:zn}];export{de as cronRoutes};
@@ -1 +1 @@
1
- var ie=Object.defineProperty;var u=(n,e)=>ie(n,"name",{value:e,configurable:!0});import{ApiError as k}from"../../../api/errors.js";import{DOC_KINDS as x,DOC_UNSET_FIELDS as U}from"../../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as V}from"../../../api/dto/objects.js";import{withCanvasWrite as A}from"../../../core/canvas/db.js";import{CrtrError as re}from"../../../core/errors.js";import{emitEvent as ae}from"../../../core/events/emit.js";import{canChange as se,spaceOf as ce}from"../../../core/graph/access.js";import{get as j}from"../../../core/graph/bodies.js";import{deleteDocument as de,documentRecord as L,editDocument as ue,heads as W,history as fe,linksIn as K,moveDocument as pe,readBase as le,setDocumentRecord as me,withCurrentNames as we,writeDocument as he}from"../../../core/graph/documents.js";import{resolveName as G}from"../../../core/graph/names.js";import{getById as M}from"../../../core/graph/objects.js";import{noteRepoDocumentsChanged as ye}from"../../../core/graph/repo-sync/index.js";import{checkoutsForCwd as ge}from"../../../core/graph/repo-sync/repos.js";import{lintRules as ve,rulesOf as _e}from"../../../core/graph/rules.js";import{unifiedDiff as be}from"../../../core/graph/diff.js";import{ownerForGrantee as ke}from"../../../core/graph/objects.js";import{ownerFromFlag as q,readerFor as E,resolveRef as X,warmReaderRepos as O}from"../reader.js";import{appCaller as z,asApiError as Ae,requireOwnNamespace as Ee}from"./schemas.js";import{filterAsync as Oe,flatMapAsync as $e}from"../../../core/async-array.js";function g(n,e,t,o,a={}){return new k(n,e,t,{...a,error:e,message:t,next:o})}u(g,"refuse");const b=u((n,e)=>new k(400,"invalid_request",n,e),"invalid");function C(n){const e=n.body;if(e===void 0)return{};if(typeof e!="object"||e===null||Array.isArray(e))throw b("the request body must be a JSON object");return e}u(C,"bodyOf");const N=u(async n=>async e=>await ge(n,e),"reposFnFor");function S(n,e){if(n instanceof re){const t=n.details;if(n.code==="read_first")throw g(409,"read_first",n.message,`Read it first with \`crtr canvas read ${e?.id??"<ref>"}\`, then rerun the edit.`,t??{});if(n.code==="conflict")throw new k(409,"name_taken",n.message,t);if(n.code==="forbidden")throw typeof t?.next=="string"?g(403,"forbidden",n.message,t.next,t):new k(403,"forbidden",n.message,t);if(n.code==="scope_missing")throw g(403,"scope_missing",n.message,`Launch the run with \`${String(t?.scope)}\` in its scopes list.`,t??{});if(t?.rule==="repo-public-only")throw g(422,"repo_documents_are_public",n.message,'Own it as `user` and add a rule with `if: {"repo": "<handle>"}` so it loads only in that repo.')}throw Ae(n)}u(S,"mapGraphError");async function B(n,e,t){const o=await X(n,e,decodeURIComponent(t));if("remote"in o)throw g(404,"not_found",Y(o.remote),"Check the name with `crtr canvas search`.");if("deleted"in o)throw new k(404,"not_found",`${o.deleted.name} is deleted`,{id:o.deleted.id,deleted:!0});if(o.type!=="document")throw b(`${t} is a ${o.type}, not a document`);return o}u(B,"refDocument");function Y(n){return`no object named ${n} on this runtime; it may be on another runtime, or the name may be misspelled`}u(Y,"remoteMessage");async function je(n,e){const t=e.owner===null?null:await M(n,e.owner);return t?t.type==="node"?t.id:t.name:""}u(je,"ownerHandle");async function R(n,e){const t=await je(n,e);return t?`${t}/${e.name}`:e.name}u(R,"canonicalName");async function Re(n,e){const t=e.owner===null?null:await M(n,e.owner),o=t?{id:t.id,type:t.type,handle:t.type==="node"?t.id:t.name}:null;return{id:e.id,type:e.type,kind:e.kind,owner:o,privacy:e.privacy,name:e.name,preview:e.preview??"",created_at:e.created_at,deleted_at:e.deleted_at,...e.type==="document"?{schema_type:e.schema_type??null}:{}}}u(Re,"objectDTO");async function De(n,e){const t=e.owner===null?null:await M(n,e.owner);return t?.type==="repo"?t:null}u(De,"repoOwner");async function T(n,e,t,o){const a=[...new Map(await $e(t,async f=>{const i=await De(n,f);return i?[[i.id,i]]:[]})).values()],c=e.class==="node"?e.cwd:o,s=e.class==="node"?[...e.checkouts,...e.projectCheckouts].map(f=>f.root):[];return{repos:a,dirs:[...new Set([...c!==void 0?[c]:[],...s])]}}u(T,"repoChange");async function F(n){for(const e of n.repos)try{await ye(e,n.dirs)}catch(t){ae({level:"warn",event:"repo_sync.failed",outcome:"failed",error:t,fields:{repo:e.name,step:"export",reason:t instanceof Error?t.message:String(t)}})}}u(F,"syncChanged");const Z=/^[A-Za-z0-9_-]+(\/[A-Za-z0-9_-]+)*$/;function Ce(n,e){if(n===void 0)return;if(!Array.isArray(n)){e.push("`rules` must be a list");return}const t=n.map(a=>{if(typeof a!="object"||a===null||Array.isArray(a))return a;const{match_frontmatter:c,...s}=a;return"matchFrontmatter"in s?{...s,match_frontmatter:s.matchFrontmatter}:c===void 0?s:{...s,matchFrontmatter:c}}),o=ve(t);if(o){e.push(o.replaceAll("matchFrontmatter","match_frontmatter"));return}return t}u(Ce,"rulesFromDTO");function J(n,e){const t={},o=u(i=>{const d=n[i];if(d!==void 0){if(typeof d!="string"){e.push(`\`${i}\` must be a string`);return}return d}},"str"),a=o("preview");a!==void 0&&(a.trim()===""||/[\r\n]/.test(a)?e.push("`preview` must be one non-empty line"):t.preview=a);const c=o("summary");c!==void 0&&(t.summary=c);const s=o("why_it_exists");if(s!==void 0&&(t.whyItExists=s),n.privacy!==void 0&&(V.includes(n.privacy)?t.privacy=n.privacy:e.push(`\`privacy\` must be one of ${V.join(", ")}`)),n.unlisted!==void 0&&(typeof n.unlisted!="boolean"?e.push("`unlisted` must be a boolean"):t.unlisted=n.unlisted),n.lint_ignore!==void 0){const i=n.lint_ignore;!Array.isArray(i)||i.some(d=>typeof d!="string")?e.push("`lint_ignore` must be a list of strings"):t.lintIgnore=i}if(n.extensions!==void 0){const i=n.extensions;typeof i!="object"||i===null||Array.isArray(i)||Object.values(i).some(d=>typeof d!="object"||d===null||Array.isArray(d))?e.push("`extensions` must map each plugin to an object of fields"):t.extensions=i}const f=Ce(n.rules,e);return f!==void 0&&(t.rules=f),t}u(J,"fieldsFrom");function Q(n){if(n.length>0)throw b(n.join("; "),{errors:n})}u(Q,"failOn");async function ee(n,e,t,o){const a=await Oe(K(t),async c=>"remote"in await G(n,e,c));return{...o.length>0?{unresolved_links:o}:{},...a.length>0?{remote_links:a}:{}}}u(ee,"linkReport");async function Ne(n){const e=C(n),t=[],o=e.name;(typeof o!="string"||!Z.test(o))&&t.push("`name` must be segments of letters, digits, `_` or `-` joined by `/`"),typeof e.body!="string"&&t.push("`body` must be a string"),typeof e.preview!="string"&&t.push("`preview` is required");const a=e.kind??"knowledge";x.includes(a)||t.push(a==="report"?"reports are written only by `crtr push`":`\`kind\` must be one of ${x.join(", ")}`);const c=J(e,t);e.cwd!==void 0&&typeof e.cwd!="string"&&t.push("`cwd` must be a string");const s=e.schema_type;s!==void 0&&typeof s!="string"&&t.push("`schema_type` must be a string");const f=e.relations;f!==void 0&&(!Array.isArray(f)||f.some(r=>typeof r!="object"||r===null||typeof r.relation!="string"||typeof r.subject!="string"))&&t.push("`relations` must be a list of {relation, subject} strings"),Q(t);const i=e;await O(n.principal,i.cwd);const{dto:d,change:h}=await A(async r=>{const p=await E(r,n);i.schema_type!==void 0?await Ee(r,z(n).app,i.schema_type):i.relations!==void 0&&z(n);const l=i.owner==="user"&&p.class==="app"&&p.sub!==void 0?await ke(r,p.sub.startsWith("user:")?p.sub:`user:${p.sub}`):await q(r,p,i.owner,i.cwd);try{const{object:w,unresolved:_}=await he(r,p,{...c,owner:l,name:i.name,kind:a,preview:c.preview,privacy:c.privacy??"public",...i.schema_type!==void 0?{schemaType:i.schema_type}:{},...i.relations!==void 0?{relations:i.relations}:{}},i.body,await N(r));return{dto:{id:w.id,name:await R(r,w),...await ee(r,p,i.body,_)},change:await T(r,p,[w],i.cwd)}}catch(w){S(w)}});return await F(h),d}u(Ne,"write");function Se(n,e){let t=n;e.forEach(({old:o,new:a},c)=>{const s=o===""?-1:t.indexOf(o);if(s<0)throw g(422,"replace_no_match",`replace[${c}]: old text not found in the revision you read`,"Copy the old text exactly from your last read and rerun.",{index:c});if(t.indexOf(o,s+1)>=0)throw g(422,"replace_ambiguous",`replace[${c}]: old text occurs more than once`,"Include more surrounding text so the old text matches once, and rerun.",{index:c});t=t.slice(0,s)+a+t.slice(s+o.length)})}u(Se,"checkReplacements");async function Te(n){const e=C(n),t=[],o=e.body!==void 0,a=e.replace!==void 0;o&&a&&t.push("give `body` or `replace`, not both"),o&&typeof e.body!="string"&&t.push("`body` must be a string");const c=e.replace;a&&(!Array.isArray(c)||c.length===0||c.some(r=>typeof r!="object"||r===null||typeof r.old!="string"||typeof r.new!="string"))&&t.push("`replace` must be a non-empty list of {old, new} strings"),(typeof e.rationale!="string"||e.rationale.trim()==="")&&t.push("`rationale` is required"),e.verbatim!==void 0&&typeof e.verbatim!="boolean"&&t.push("`verbatim` must be a boolean"),e.base!==void 0&&(typeof e.base!="string"||e.base==="")&&t.push("`base` must be a non-empty heads key"),e.base!==void 0&&!o&&!a&&t.push("`base` applies only to a `body` or `replace` edit");const s=J(e,t),f=e.unset??[];if(!Array.isArray(f)||f.some(r=>!U.includes(r)))t.push(`\`unset\` takes only ${U.join(", ")}`);else for(const r of f)r==="summary"&&(s.summary=null),r==="why_it_exists"&&(s.whyItExists=null),r==="unlisted"&&(s.unlisted=!1),r==="lint_ignore"&&(s.lintIgnore=null),r==="extensions"&&(s.extensions=null),r==="rules"&&(s.rules=[]);t.length===0&&!o&&!a&&Object.keys(s).length===0&&t.push("nothing to change: give `body`, `replace`, or a field"),Q(t);const i=e;await O(n.principal);const{dto:d,synced:h}=await A(async r=>{const p=await E(r,n),l=await B(r,p,n.params.ref),w=await R(r,l),_=await T(r,p,[l]);try{if(!o&&!a)return await me(r,p,l,s,i.rationale),{dto:{id:l.id,name:w,revision:(await L(r,l)).revision},synced:_};const v=i.base??await le(r,p,l);if(a&&v!==null){const P=await L(r,l),H=await W(r,l);P.source_path===null&&P.heads_key===v&&H.length===1&&Se(await we(r,l,j(H[0].body_hash)),i.replace)}const D=i.verbatim===!0,m=o?{body:i.body,verbatim:D}:{replace:i.replace,verbatim:D},y=await ue(r,p,l,m,i.rationale,v,s,await N(r));if("stale"in y)throw g(409,"stale_revision",`${w} changed after your read; it is now at revision ${y.stale.revision}`,`Read it again with \`crtr canvas read ${w}\`, then redo your change on the current text.`,{stale:y.stale});const I=j((await W(r,l))[0].body_hash);return{dto:{id:l.id,name:w,revision:y.revision,...await ee(r,p,I,y.unresolved)},synced:_}}catch(v){S(v,l)}});return await F(h),d}u(Te,"edit");async function Fe(n){const e=C(n);if(e.new_name===void 0&&e.owner===void 0)throw b("give a new name, an owner, or both");if(e.new_name!==void 0&&(typeof e.new_name!="string"||!Z.test(e.new_name)))throw b("`new_name` must be segments of letters, digits, `_` or `-` joined by `/`");await O(n.principal,e.cwd);const{dto:t,change:o}=await A(async a=>{const c=await E(a,n),s=await B(a,c,n.params.ref),f=e.owner===void 0?void 0:await q(a,c,e.owner,e.cwd);try{const i=await pe(a,c,s,{...e.new_name!==void 0?{name:e.new_name}:{},...f?{owner:f}:{}},await N(a));return{dto:{id:i.id,name:await R(a,i)},change:await T(a,c,[s,i],e.cwd)}}catch(i){S(i,s)}});return await F(o),t}u(Fe,"move");async function Ie(n){await O(n.principal);const{dto:e,change:t}=await A(async o=>{const a=await E(o,n),c=await B(o,a,n.params.ref),s=await R(o,c),f=await T(o,a,[c]);try{await de(o,a,c,await N(o))}catch(i){S(i,c)}return{dto:{id:c.id,name:s},change:f}});return await F(t),e}u(Ie,"remove");async function Le(n){const e=n.query.get("diff"),t=e===null?void 0:Number(e);if(t!==void 0&&!(Number.isInteger(t)&&t>0))throw b("`diff` must be a revision number");return await O(n.principal),await A(async o=>{const a=await E(o,n),c=await X(o,a,decodeURIComponent(n.params.ref));if("remote"in c)throw g(404,"not_found",Y(c.remote),"Check the name with `crtr canvas search`.");const s="deleted"in c?c.deleted:c;if(s.type!=="document")throw b(`${s.name} is a ${s.type}, not a document`);const f=await fe(o,a,s),i={object:await Re(o,s),revisions:f.map(d=>({rev_id:d.rev_id,number:d.number,author_id:d.author,rationale:d.rationale,verbatim:d.verbatim,created_at:d.created_at,parents:d.parents,acting_app:d.acting_app,acting_for:d.acting_for}))};if(t!==void 0){const d=f.find(r=>r.number===t);if(!d)throw new k(404,"not_found",`${s.name} has no revision ${t}`);const h=d.parents[0]===void 0?void 0:f.find(r=>r.rev_id===d.parents[0]);i.diff=be(h?j(h.body_hash):"",j(d.body_hash))}return i})}u(Le,"revisionHistory");const te=1e3,ne=1e3,oe=3e3,We=/^When\b.+\bbecause\b.+/,Me=u(n=>{const e=n.trim();return e===""?0:e.split(/\s+/).length},"words");function qe(n,e){if(n.some(o=>o.on==="boot"&&o.deliver==="content")&&e>te)return`body is ${e} words, over the ${te}-word cap for a boot rule at content (the whole body enters every node's context). Keep the core here and move separate subjects into [[linked]] documents.`;const t=[...new Set(n.filter(o=>o.on!=="boot"&&o.deliver==="content").map(o=>o.on))];return t.length>0&&e>ne?`body is ${e} words, over the ${ne}-word cap for a content rule on ${t.join("/")}. Keep the core here and move separate subjects into [[linked]] documents.`:n.some(o=>o.on==="boot"&&o.deliver==="preview")&&e>oe?`body is ${e} words, over the ${oe}-word cap for a boot rule at preview. Keep the core here and move separate subjects into [[linked]] documents.`:null}u(qe,"lengthFinding");async function Be(n){const e=C(n);return await O(n.principal,e.cwd),await A(async t=>{const o=await E(t,n),a=e.owner===void 0?null:await q(t,o,e.owner,e.cwd),c=await t.prepare("SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND home_machine IS NULL AND kind != 'report' ORDER BY name").all(),s=[],f=new Map;for(const i of c){if(a&&i.owner!==a.id||!await se(t,o,i))continue;const d=await L(t,i),h=new Set(d.lint_ignore??[]),r=await R(t,i),p=u((m,y,I)=>{h.has(m)||s.push({name:r,severity:y,rule:m,message:I})},"add"),l=await _e(t,i),w=(await W(t,i))[0],_=w?j(w.body_hash):"",v=qe(l,Me(_));v&&p("length","error",v),l.some(m=>m.deliver==="preview")&&!We.test(i.preview??"")&&p("preview-shape","error",`a rule delivers this document at preview, so the preview must read "When <situation in the reader's work>, read this because <what the reader gains>"`);const D=new Set((await t.prepare("SELECT written_as FROM edges WHERE type = 'links-to' AND src = ?").all(i.id)).map(m=>m.written_as));for(const m of K(_)){if(D.has(m))continue;const y=await G(t,o,m);"remote"in y||"object"in y||"deleted"in y||p("links","warning",`[[${m}]] names no object in your view`)}if(l.some(m=>m.on==="workspace-open"&&m.deliver==="content")){const m=await ce(t,i);m.type==="repo"&&!h.has("workspace-open")&&f.set(m.name,[...f.get(m.name)??[],r])}}for(const[i,d]of f)if(!(d.length<2))for(const h of d)s.push({name:h,severity:"warning",rule:"workspace-open",message:`repo ${i} has ${d.length} documents delivered at content on workspace-open (${d.join(", ")}); keep one and link the rest from it`});return{findings:s}})}u(Be,"lint");const $=u((n,e=200)=>async t=>({status:e,body:await n(t)}),"ok"),st=[{method:"POST",pattern:"/v1/docs",handler:$(Ne,201)},{method:"PATCH",pattern:"/v1/docs/:ref",handler:$(Te)},{method:"POST",pattern:"/v1/docs/:ref/move",handler:$(Fe)},{method:"DELETE",pattern:"/v1/docs/:ref",handler:$(Ie)},{method:"GET",pattern:"/v1/docs/:ref/history",handler:$(Le)},{method:"POST",pattern:"/v1/docs/lint",handler:$(Be)}];export{R as canonicalName,st as docRoutes,T as repoChange,F as syncChanged};
1
+ var re=Object.defineProperty;var u=(n,e)=>re(n,"name",{value:e,configurable:!0});import{ApiError as k}from"../../../api/errors.js";import{DOC_KINDS as U,DOC_UNSET_FIELDS as V}from"../../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as K}from"../../../api/dto/objects.js";import{openDb as ae,withCanvasWrite as A}from"../../../core/canvas/db.js";import{CrtrError as se}from"../../../core/errors.js";import{emitEvent as ce}from"../../../core/events/emit.js";import{canChange as de,spaceOf as ue}from"../../../core/graph/access.js";import{get as j}from"../../../core/graph/bodies.js";import{deleteDocument as fe,documentRecord as L,editDocument as me,heads as W,history as pe,linksIn as G,moveDocument as le,readBase as we,setDocumentRecord as he,withCurrentNames as ye,writeDocument as ge}from"../../../core/graph/documents.js";import{resolveName as X}from"../../../core/graph/names.js";import{getById as M}from"../../../core/graph/objects.js";import{noteRepoDocumentsChanged as ve}from"../../../core/graph/repo-sync/index.js";import{checkoutsForCwd as _e}from"../../../core/graph/repo-sync/repos.js";import{lintRules as be,rulesOf as ke}from"../../../core/graph/rules.js";import{unifiedDiff as Ae}from"../../../core/graph/diff.js";import{ownerForGrantee as Ee}from"../../../core/graph/objects.js";import{stubOf as $e}from"../../../core/peer/stubs.js";import{forwarding as Oe}from"../../peer/forward.js";import{resolvedLocally as je}from"./objects.js";import{ownerFromFlag as q,readerFor as E,resolveRef as x,warmReaderRepos as $}from"../reader.js";import{appCaller as z,asApiError as Re,requireOwnNamespace as De}from"./schemas.js";import{filterAsync as Ce,flatMapAsync as Ne}from"../../../core/async-array.js";function g(n,e,t,o,a={}){return new k(n,e,t,{...a,error:e,message:t,next:o})}u(g,"refuse");const b=u((n,e)=>new k(400,"invalid_request",n,e),"invalid");function C(n){const e=n.body;if(e===void 0)return{};if(typeof e!="object"||e===null||Array.isArray(e))throw b("the request body must be a JSON object");return e}u(C,"bodyOf");const N=u(async n=>async e=>await _e(n,e),"reposFnFor");function S(n,e){if(n instanceof se){const t=n.details;if(n.code==="read_first")throw g(409,"read_first",n.message,`Read it first with \`crtr canvas read ${e?.id??"<ref>"}\`, then rerun the edit.`,t??{});if(n.code==="conflict")throw new k(409,"name_taken",n.message,t);if(n.code==="forbidden")throw typeof t?.next=="string"?g(403,"forbidden",n.message,t.next,t):new k(403,"forbidden",n.message,t);if(n.code==="scope_missing")throw g(403,"scope_missing",n.message,`Launch the run with \`${String(t?.scope)}\` in its scopes list.`,t??{});if(t?.rule==="repo-public-only")throw g(422,"repo_documents_are_public",n.message,'Own it as `user` and add a rule with `if: {"repo": "<handle>"}` so it loads only in that repo.')}throw Re(n)}u(S,"mapGraphError");async function B(n,e,t){const o=await x(n,e,decodeURIComponent(t));if("remote"in o)throw g(404,"not_found",Z(o.remote),"Check the name with `crtr canvas search`.");if("deleted"in o)throw new k(404,"not_found",`${o.deleted.name} is deleted`,{id:o.deleted.id,deleted:!0});if(o.type!=="document")throw b(`${t} is a ${o.type}, not a document`);return o}u(B,"refDocument");async function Y(n){const{stub:e}=await je(decodeURIComponent(n.params.ref),n);if(e===null)return;const t=await $e(await ae(),e);if(t.object.type!=="document")return;const o=await Oe.current.label(t.home_machine);throw g(404,"not_here",`this document lives on runtime ${o.name}; edit it (or read its history) from a node there`,`Run the command from a node on ${o.name}.`,{id:t.object.id,home_machine:o.id})}u(Y,"refuseRemoteDocument");function Z(n){return`no object named ${n} on this runtime; it may be on another runtime, or the name may be misspelled`}u(Z,"remoteMessage");async function Se(n,e){const t=e.owner===null?null:await M(n,e.owner);return t?t.type==="node"?t.id:t.name:""}u(Se,"ownerHandle");async function R(n,e){const t=await Se(n,e);return t?`${t}/${e.name}`:e.name}u(R,"canonicalName");async function Te(n,e){const t=e.owner===null?null:await M(n,e.owner),o=t?{id:t.id,type:t.type,handle:t.type==="node"?t.id:t.name}:null;return{id:e.id,type:e.type,kind:e.kind,owner:o,privacy:e.privacy,name:e.name,preview:e.preview??"",created_at:e.created_at,deleted_at:e.deleted_at,...e.type==="document"?{schema_type:e.schema_type??null}:{}}}u(Te,"objectDTO");async function Ie(n,e){const t=e.owner===null?null:await M(n,e.owner);return t?.type==="repo"?t:null}u(Ie,"repoOwner");async function T(n,e,t,o){const a=[...new Map(await Ne(t,async f=>{const i=await Ie(n,f);return i?[[i.id,i]]:[]})).values()],c=e.class==="node"?e.cwd:o,s=e.class==="node"?[...e.checkouts,...e.projectCheckouts].map(f=>f.root):[];return{repos:a,dirs:[...new Set([...c!==void 0?[c]:[],...s])]}}u(T,"repoChange");async function I(n){for(const e of n.repos)try{await ve(e,n.dirs)}catch(t){ce({level:"warn",event:"repo_sync.failed",outcome:"failed",error:t,fields:{repo:e.name,step:"export",reason:t instanceof Error?t.message:String(t)}})}}u(I,"syncChanged");const J=/^[A-Za-z0-9_-]+(\/[A-Za-z0-9_-]+)*$/;function Fe(n,e){if(n===void 0)return;if(!Array.isArray(n)){e.push("`rules` must be a list");return}const t=n.map(a=>{if(typeof a!="object"||a===null||Array.isArray(a))return a;const{match_frontmatter:c,...s}=a;return"matchFrontmatter"in s?{...s,match_frontmatter:s.matchFrontmatter}:c===void 0?s:{...s,matchFrontmatter:c}}),o=be(t);if(o){e.push(o.replaceAll("matchFrontmatter","match_frontmatter"));return}return t}u(Fe,"rulesFromDTO");function Q(n,e){const t={},o=u(i=>{const d=n[i];if(d!==void 0){if(typeof d!="string"){e.push(`\`${i}\` must be a string`);return}return d}},"str"),a=o("preview");a!==void 0&&(a.trim()===""||/[\r\n]/.test(a)?e.push("`preview` must be one non-empty line"):t.preview=a);const c=o("summary");c!==void 0&&(t.summary=c);const s=o("why_it_exists");if(s!==void 0&&(t.whyItExists=s),n.privacy!==void 0&&(K.includes(n.privacy)?t.privacy=n.privacy:e.push(`\`privacy\` must be one of ${K.join(", ")}`)),n.unlisted!==void 0&&(typeof n.unlisted!="boolean"?e.push("`unlisted` must be a boolean"):t.unlisted=n.unlisted),n.lint_ignore!==void 0){const i=n.lint_ignore;!Array.isArray(i)||i.some(d=>typeof d!="string")?e.push("`lint_ignore` must be a list of strings"):t.lintIgnore=i}if(n.extensions!==void 0){const i=n.extensions;typeof i!="object"||i===null||Array.isArray(i)||Object.values(i).some(d=>typeof d!="object"||d===null||Array.isArray(d))?e.push("`extensions` must map each plugin to an object of fields"):t.extensions=i}const f=Fe(n.rules,e);return f!==void 0&&(t.rules=f),t}u(Q,"fieldsFrom");function ee(n){if(n.length>0)throw b(n.join("; "),{errors:n})}u(ee,"failOn");async function te(n,e,t,o){const a=await Ce(G(t),async c=>"remote"in await X(n,e,c));return{...o.length>0?{unresolved_links:o}:{},...a.length>0?{remote_links:a}:{}}}u(te,"linkReport");async function Le(n){const e=C(n),t=[],o=e.name;(typeof o!="string"||!J.test(o))&&t.push("`name` must be segments of letters, digits, `_` or `-` joined by `/`"),typeof e.body!="string"&&t.push("`body` must be a string"),typeof e.preview!="string"&&t.push("`preview` is required");const a=e.kind??"knowledge";U.includes(a)||t.push(a==="report"?"reports are written only by `crtr push`":`\`kind\` must be one of ${U.join(", ")}`);const c=Q(e,t);e.cwd!==void 0&&typeof e.cwd!="string"&&t.push("`cwd` must be a string");const s=e.schema_type;s!==void 0&&typeof s!="string"&&t.push("`schema_type` must be a string");const f=e.relations;f!==void 0&&(!Array.isArray(f)||f.some(r=>typeof r!="object"||r===null||typeof r.relation!="string"||typeof r.subject!="string"))&&t.push("`relations` must be a list of {relation, subject} strings"),ee(t);const i=e;await $(n.principal,i.cwd);const{dto:d,change:h}=await A(async r=>{const m=await E(r,n);i.schema_type!==void 0?await De(r,z(n).app,i.schema_type):i.relations!==void 0&&z(n);const p=i.owner==="user"&&m.class==="app"&&m.sub!==void 0?await Ee(r,m.sub.startsWith("user:")?m.sub:`user:${m.sub}`):await q(r,m,i.owner,i.cwd);try{const{object:w,unresolved:_}=await ge(r,m,{...c,owner:p,name:i.name,kind:a,preview:c.preview,privacy:c.privacy??"public",...i.schema_type!==void 0?{schemaType:i.schema_type}:{},...i.relations!==void 0?{relations:i.relations}:{}},i.body,await N(r));return{dto:{id:w.id,name:await R(r,w),...await te(r,m,i.body,_)},change:await T(r,m,[w],i.cwd)}}catch(w){S(w)}});return await I(h),d}u(Le,"write");function We(n,e){let t=n;e.forEach(({old:o,new:a},c)=>{const s=o===""?-1:t.indexOf(o);if(s<0)throw g(422,"replace_no_match",`replace[${c}]: old text not found in the revision you read`,"Copy the old text exactly from your last read and rerun.",{index:c});if(t.indexOf(o,s+1)>=0)throw g(422,"replace_ambiguous",`replace[${c}]: old text occurs more than once`,"Include more surrounding text so the old text matches once, and rerun.",{index:c});t=t.slice(0,s)+a+t.slice(s+o.length)})}u(We,"checkReplacements");async function Me(n){const e=C(n),t=[],o=e.body!==void 0,a=e.replace!==void 0;o&&a&&t.push("give `body` or `replace`, not both"),o&&typeof e.body!="string"&&t.push("`body` must be a string");const c=e.replace;a&&(!Array.isArray(c)||c.length===0||c.some(r=>typeof r!="object"||r===null||typeof r.old!="string"||typeof r.new!="string"))&&t.push("`replace` must be a non-empty list of {old, new} strings"),(typeof e.rationale!="string"||e.rationale.trim()==="")&&t.push("`rationale` is required"),e.verbatim!==void 0&&typeof e.verbatim!="boolean"&&t.push("`verbatim` must be a boolean"),e.base!==void 0&&(typeof e.base!="string"||e.base==="")&&t.push("`base` must be a non-empty heads key"),e.base!==void 0&&!o&&!a&&t.push("`base` applies only to a `body` or `replace` edit");const s=Q(e,t),f=e.unset??[];if(!Array.isArray(f)||f.some(r=>!V.includes(r)))t.push(`\`unset\` takes only ${V.join(", ")}`);else for(const r of f)r==="summary"&&(s.summary=null),r==="why_it_exists"&&(s.whyItExists=null),r==="unlisted"&&(s.unlisted=!1),r==="lint_ignore"&&(s.lintIgnore=null),r==="extensions"&&(s.extensions=null),r==="rules"&&(s.rules=[]);t.length===0&&!o&&!a&&Object.keys(s).length===0&&t.push("nothing to change: give `body`, `replace`, or a field"),ee(t);const i=e;await Y(n),await $(n.principal);const{dto:d,synced:h}=await A(async r=>{const m=await E(r,n),p=await B(r,m,n.params.ref),w=await R(r,p),_=await T(r,m,[p]);try{if(!o&&!a)return await he(r,m,p,s,i.rationale),{dto:{id:p.id,name:w,revision:(await L(r,p)).revision},synced:_};const v=i.base??await we(r,m,p);if(a&&v!==null){const P=await L(r,p),H=await W(r,p);P.source_path===null&&P.heads_key===v&&H.length===1&&We(await ye(r,p,j(H[0].body_hash)),i.replace)}const D=i.verbatim===!0,l=o?{body:i.body,verbatim:D}:{replace:i.replace,verbatim:D},y=await me(r,m,p,l,i.rationale,v,s,await N(r));if("stale"in y)throw g(409,"stale_revision",`${w} changed after your read; it is now at revision ${y.stale.revision}`,`Read it again with \`crtr canvas read ${w}\`, then redo your change on the current text.`,{stale:y.stale});const F=j((await W(r,p))[0].body_hash);return{dto:{id:p.id,name:w,revision:y.revision,...await te(r,m,F,y.unresolved)},synced:_}}catch(v){S(v,p)}});return await I(h),d}u(Me,"edit");async function qe(n){const e=C(n);if(e.new_name===void 0&&e.owner===void 0)throw b("give a new name, an owner, or both");if(e.new_name!==void 0&&(typeof e.new_name!="string"||!J.test(e.new_name)))throw b("`new_name` must be segments of letters, digits, `_` or `-` joined by `/`");await $(n.principal,e.cwd);const{dto:t,change:o}=await A(async a=>{const c=await E(a,n),s=await B(a,c,n.params.ref),f=e.owner===void 0?void 0:await q(a,c,e.owner,e.cwd);try{const i=await le(a,c,s,{...e.new_name!==void 0?{name:e.new_name}:{},...f?{owner:f}:{}},await N(a));return{dto:{id:i.id,name:await R(a,i)},change:await T(a,c,[s,i],e.cwd)}}catch(i){S(i,s)}});return await I(o),t}u(qe,"move");async function Be(n){await $(n.principal);const{dto:e,change:t}=await A(async o=>{const a=await E(o,n),c=await B(o,a,n.params.ref),s=await R(o,c),f=await T(o,a,[c]);try{await fe(o,a,c,await N(o))}catch(i){S(i,c)}return{dto:{id:c.id,name:s},change:f}});return await I(t),e}u(Be,"remove");async function Pe(n){const e=n.query.get("diff"),t=e===null?void 0:Number(e);if(t!==void 0&&!(Number.isInteger(t)&&t>0))throw b("`diff` must be a revision number");return await Y(n),await $(n.principal),await A(async o=>{const a=await E(o,n),c=await x(o,a,decodeURIComponent(n.params.ref));if("remote"in c)throw g(404,"not_found",Z(c.remote),"Check the name with `crtr canvas search`.");const s="deleted"in c?c.deleted:c;if(s.type!=="document")throw b(`${s.name} is a ${s.type}, not a document`);const f=await pe(o,a,s),i={object:await Te(o,s),revisions:f.map(d=>({rev_id:d.rev_id,number:d.number,author_id:d.author,rationale:d.rationale,verbatim:d.verbatim,created_at:d.created_at,parents:d.parents,acting_app:d.acting_app,acting_for:d.acting_for}))};if(t!==void 0){const d=f.find(r=>r.number===t);if(!d)throw new k(404,"not_found",`${s.name} has no revision ${t}`);const h=d.parents[0]===void 0?void 0:f.find(r=>r.rev_id===d.parents[0]);i.diff=Ae(h?j(h.body_hash):"",j(d.body_hash))}return i})}u(Pe,"revisionHistory");const ne=1e3,oe=1e3,ie=3e3,He=/^When\b.+\bbecause\b.+/,Ue=u(n=>{const e=n.trim();return e===""?0:e.split(/\s+/).length},"words");function Ve(n,e){if(n.some(o=>o.on==="boot"&&o.deliver==="content")&&e>ne)return`body is ${e} words, over the ${ne}-word cap for a boot rule at content (the whole body enters every node's context). Keep the core here and move separate subjects into [[linked]] documents.`;const t=[...new Set(n.filter(o=>o.on!=="boot"&&o.deliver==="content").map(o=>o.on))];return t.length>0&&e>oe?`body is ${e} words, over the ${oe}-word cap for a content rule on ${t.join("/")}. Keep the core here and move separate subjects into [[linked]] documents.`:n.some(o=>o.on==="boot"&&o.deliver==="preview")&&e>ie?`body is ${e} words, over the ${ie}-word cap for a boot rule at preview. Keep the core here and move separate subjects into [[linked]] documents.`:null}u(Ve,"lengthFinding");async function Ke(n){const e=C(n);return await $(n.principal,e.cwd),await A(async t=>{const o=await E(t,n),a=e.owner===void 0?null:await q(t,o,e.owner,e.cwd),c=await t.prepare("SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND home_machine IS NULL AND kind != 'report' ORDER BY name").all(),s=[],f=new Map;for(const i of c){if(a&&i.owner!==a.id||!await de(t,o,i))continue;const d=await L(t,i),h=new Set(d.lint_ignore??[]),r=await R(t,i),m=u((l,y,F)=>{h.has(l)||s.push({name:r,severity:y,rule:l,message:F})},"add"),p=await ke(t,i),w=(await W(t,i))[0],_=w?j(w.body_hash):"",v=Ve(p,Ue(_));v&&m("length","error",v),p.some(l=>l.deliver==="preview")&&!He.test(i.preview??"")&&m("preview-shape","error",`a rule delivers this document at preview, so the preview must read "When <situation in the reader's work>, read this because <what the reader gains>"`);const D=new Set((await t.prepare("SELECT written_as FROM edges WHERE type = 'links-to' AND src = ?").all(i.id)).map(l=>l.written_as));for(const l of G(_)){if(D.has(l))continue;const y=await X(t,o,l);"remote"in y||"object"in y||"deleted"in y||m("links","warning",`[[${l}]] names no object in your view`)}if(p.some(l=>l.on==="workspace-open"&&l.deliver==="content")){const l=await ue(t,i);l.type==="repo"&&!h.has("workspace-open")&&f.set(l.name,[...f.get(l.name)??[],r])}}for(const[i,d]of f)if(!(d.length<2))for(const h of d)s.push({name:h,severity:"warning",rule:"workspace-open",message:`repo ${i} has ${d.length} documents delivered at content on workspace-open (${d.join(", ")}); keep one and link the rest from it`});return{findings:s}})}u(Ke,"lint");const O=u((n,e=200)=>async t=>({status:e,body:await n(t)}),"ok"),wt=[{method:"POST",pattern:"/v1/docs",handler:O(Le,201)},{method:"PATCH",pattern:"/v1/docs/:ref",handler:O(Me)},{method:"POST",pattern:"/v1/docs/:ref/move",handler:O(qe)},{method:"DELETE",pattern:"/v1/docs/:ref",handler:O(Be)},{method:"GET",pattern:"/v1/docs/:ref/history",handler:O(Pe)},{method:"POST",pattern:"/v1/docs/lint",handler:O(Ke)}];export{R as canonicalName,wt as docRoutes,T as repoChange,I as syncChanged};