@north-light/cli 0.3.528 → 0.3.530

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.
@@ -1 +1 @@
1
- var Oe=Object.defineProperty;var a=(e,t)=>Oe(e,"name",{value:t,configurable:!0});import{prepared as Z}from"../../../core/sqlite-statements.js";import{envProfileId as Ie}from"../../../shared/env.js";import{pausedCanvasProfiles as Ae}from"../../../core/profiles/manifest.js";import{existsSync as N,realpathSync as q,statSync as ee}from"node:fs";import{isAbsolute as F,resolve as T}from"node:path";import{appendSituationalContext as $e,formatSituationalProse as Ce}from"../../../core/runtime/situational-context.js";import{closeNode as Ne}from"../../../core/runtime/close.js";import{reviveNode as Te}from"../../../core/runtime/revive.js";import{nowIso as B}from"../../../core/fs-utils.js";import{appPaidLaunch as Ee,assertLaunchModelRegistered as K,forkNode as Pe,resolveForkSource as De,spawnChildPrepared as Le}from"../../../core/runtime/spawn.js";import{forkSourceNode as Me,newNodeId as ne}from"../../../core/runtime/nodes.js";import{abandonCreateRequest as te,childrenOf as je,completeCreateRequest as oe,findCreateRequest as Ge,getNode as R,recordCreateRequestNode as Fe,reserveCreateRequest as re,reserveCreateRequestNode as Be,listNodes as ie,recordRespawnAttempt as Ke,subscribersOf as We,subscriptionsOf as de,updateNode as C}from"../../../core/canvas/canvas.js";import{armControllerWait as xe}from"../../../core/canvas/waits.js";import{subtreeIds as Ve}from"../../../core/canvas/nav-model.js";import{withCanvasWrite as W}from"../../../core/canvas/db.js";import{markNodeRead as ze}from"../../../core/canvas/recovery.js";import{nodeObject as Je}from"../../../core/graph/objects.js";import{recordActedOn as Ue}from"../../../core/graph/edges.js";import{publish as Ye}from"../../../core/graph/events.js";import{readerFor as He}from"../reader.js";import{relocateSessionCwd as Qe,unbindSession as Xe}from"../../../core/canvas/sessions.js";import{ticketCountsForNodes as Ze}from"../../../core/canvas/attention.js";import{isSafeNodeId as qe}from"../../../core/canvas/paths.js";import{contextDir as se,nodeAgentDir as x}from"../../../core/canvas/node-agent-paths.js";import{checkCronBirth as en}from"../../cron/authority.js";import{cronBirthPlacement as nn}from"../../cron/armed-context.js";import{existsBeneath as tn,isAbsentOrRefused as on,isBeneathRefusal as rn,readdirBeneath as dn}from"../../../core/spaces/open-beneath.js";import{readNodeMessagesPage as sn,readNodeSession as an,readNodeSnapshot as ln,transcriptMarkdown as cn}from"../../../core/runtime/node-read.js";import{assembleNodeSubject as fn}from"../../../core/substrate/subject.js";import{reviveAll as un}from"../../../core/runtime/revive-all.js";import{promoteAsync as pn,requestYieldAsync as mn,reshapeNodeAsync as V}from"../../../core/runtime/promote.js";import{recycleNode as hn}from"../../../core/runtime/recycle.js";import{relaunchRoot as wn}from"../../../core/runtime/reset.js";import{assertReopenGate as yn}from"../../../core/runtime/reopen.js";import{writeYieldMessage as _n,readGoal as gn}from"../../../core/runtime/kickoff.js";import{ensureRoadmap as bn,hasRoadmap as vn}from"../../../core/runtime/roadmap.js";import{renameWindow as kn}from"../../../core/runtime/placement.js";import{buildLaunchSpecAsync as Sn,explicitModelIntent as Rn,normalizeModel as On,readMergedLaunchConfigAsync as ae}from"../../../core/runtime/launch.js";import{modelRequestFromConfig as In}from"../../../core/model-routes.js";import{persistDormantModel as An,setModelLive as $n}from"../../../core/runtime/model-swap.js";import{waitForBrokerViewSocket as le}from"../../../core/runtime/view-socket.js";import{BrokerUnreachableError as Cn}from"../../../core/runtime/broker-request.js";import{boundFleet as E}from"../../../core/runtime/fleet.js";import{admitLegacyStructuredOutput as Nn}from"../../structured-requests/sidecar.js";import{assertOutputSchemaAccepted as Tn}from"../../../core/runtime/structured-output.js";import{OUTCOME_PAYLOAD_MAX_BYTES as ce}from"../../../core/canvas/types.js";import{HUMAN_ACTION_NAME_PATTERN as En}from"../../../core/human/action-binding.js";import{resolveHumanAction as Pn}from"../../../core/human-actions.js";import{parseDurationMs as Dn}from"../../../core/wake.js";import{claimWarmNode as Ln,refillWarmPool as fe,resumeWarmClaim as Mn,warmPoolEnabled as jn}from"../../../core/runtime/warm-pool.js";import{assertLaunchKind as z,isLaunchKind as Gn,resolveLaunchTarget as Fn}from"../../../core/runtime/launch-target.js";import{fullName as Bn}from"../../../core/canvas/labels.js";import{reportsForNode as ue}from"./reports.js";import{requireUsableProfile as Kn}from"./profiles.js";import{assertProfileActive as Wn,loadExactProfileManifest as xn}from"../../../core/profiles/manifest.js";import{getCron as pe}from"../../../core/canvas/crons.js";import{cronWakeOrigin as Vn}from"../../../core/runtime/bearings.js";import{boundDaemonControl as zn}from"../../control.js";import{notFound as P,usage as h}from"../../../core/errors.js";import{assertScope as Jn,validateScopes as me}from"../../../core/scopes.js";import{scopeAllowed as I}from"../../../shared/env.js";import{toCloseResultDTO as Un,toNodeDetailDTO as J,toNodeSummaryDTO as Yn,toReviveResultDTO as Hn}from"../map.js";import{ApiError as u}from"../../../api/index.js";import{InputError as y}from"../../../core/io.js";import{actsAsPerson as Qn,brokerMayReach as U,canReadNode as he,resolvedScopes as Xn}from"../router.js";import{forwardVerb as Zn,queryWithoutMachine as qn,relayError as et,unknownError as nt,withMachine as tt}from"../../peer/forward.js";import{fileRoot as ot}from"../../../core/spaces/file-root.js";import{activeGrant as rt,payerOfGrant as it,terminalGrant as dt}from"../../../core/grants/mirror.js";import{appRungForGrant as st}from"../../../core/llm/app-rungs.js";import{APP_FAMILY as at}from"../../../core/model-routes.js";import{readConfig as lt}from"../../../core/config.js";import{hasConfiguredProviderCredential as ct,NO_PROVIDER_CREDENTIAL_MESSAGE as we}from"../../../core/runtime/provider-credential-gate.js";function w(e){const t=R(e);if(t===null)throw P(`unknown node: ${e}`,{received:e});return t}a(w,"requireMeta");function Y(e){return{subscribesTo:de(e),subscribers:We(e),children:je(e)}}a(Y,"edgesFor");function g(e){const t=w(e);return J(t,Y(e))}a(g,"detailOf");function ft(e){let t=ue(e).length;try{t+=dn(x(e),se(e)).length}catch(n){if(!on(n))throw n}return t}a(ft,"countFiles");function ut(e){if(typeof e!="object"||e===null)throw h("a node create requires a JSON body");const t=e;if(t.kind!==void 0&&(typeof t.kind!="string"||t.kind.trim()===""))throw h("kind must be a non-empty string when provided");if(t.description!==void 0&&(typeof t.description!="string"||t.description.trim()===""))throw h("description must be a non-empty string when provided");if(t.creator!==void 0&&(typeof t.creator!="string"||t.creator.trim()===""))throw h("creator must be a non-empty node id when provided");if(t.scopes!==void 0&&me(t.scopes),t.isolated!==void 0&&typeof t.isolated!="boolean")throw new u(400,"invalid_request","isolated must be a boolean",{param:"isolated"});const n=t.root_lifecycle;if(n!==void 0&&n!=="resident"&&n!=="terminal")throw h("root_lifecycle must be 'resident' or 'terminal'");if(t.request_key!==void 0&&(typeof t.request_key!="string"||t.request_key.trim()===""||t.request_key.length>128))throw h("request_key must be a non-empty string of at most 128 characters when provided");return t}a(ut,"parseCreateBody");function pt(e){if(e===void 0)return;const t=Dn(e),n=t===null?null:new Date(Date.now()+t);if(t===null||!Number.isFinite(t)||t<=0||n===null||!Number.isFinite(n.getTime()))throw new u(422,"invalid_deadline","`deadline` must be a positive representable duration");return n.toISOString()}a(pt,"parseDeadline");function mt(e){if(e!==void 0){if(typeof e!="object"||e===null||Array.isArray(e))throw new u(400,"invalid_request","`outcome_delivery` must be an object");if(typeof e.action!="string"||!En.test(e.action))throw new u(400,"invalid_request","`outcome_delivery.action` must be a declared action name");if(e.payload!==void 0){let t;try{t=JSON.stringify(e.payload)}catch{throw new u(400,"invalid_request","`outcome_delivery.payload` must be JSON-serializable")}if(t===void 0)throw new u(400,"invalid_request","`outcome_delivery.payload` must be JSON-serializable");if(Buffer.byteLength(t,"utf8")>ce)throw new u(413,"payload_too_large",`serialized payload exceeds ${ce} bytes`)}return e}}a(mt,"parseOutcomeDelivery");function ht(e,t){const n=Pn(e.action,t);if(!("kind"in n))return{actionName:e.action,argv:n.argv,cwd:n.cwd,configOrigin:n.configOrigin,...e.payload===void 0?{}:{payload:e.payload}};throw n.kind==="unknown_action"?new u(422,"unknown_action",`no action named ${e.action} is declared from ${t}`):new u(422,"invalid_action",`${e.action} is declared in ${n.configOrigin} but unusable: ${n.reason}`,{reason:n.reason,configOrigin:n.configOrigin})}a(ht,"resolveOutcomeDelivery");function wt(e,t,n){const r={kind:e.kind,cwd:n,prompt:e.prompt??""};return e.mode!==void 0&&(r.mode=e.mode),e.name!==void 0&&(r.name=e.name),e.description!==void 0&&(r.description=e.description),t!==void 0&&(r.parent=t),e.creator!==void 0&&(r.creator=e.creator),e.scopes!==void 0&&(r.scopes=e.scopes),e.isolated!==void 0&&(r.isolated=e.isolated),e.root===!0&&(r.root=!0),e.root_lifecycle!==void 0&&(r.rootLifecycle=e.root_lifecycle),(e.worktree===!0||typeof e.worktree=="string")&&(r.worktree=!0),typeof e.worktree=="string"&&(r.worktreeBase=e.worktree),e.worktree!==void 0&&(r.worktreeCwd=T(e.pin_cwd??e.cwd??n)),e.fork_from!==void 0&&(r.forkFrom=e.fork_from),e.model!==void 0&&(r.model=e.model),e.profile!==void 0&&(r.profile=e.profile),e.situational_context!==void 0&&(r.situationalContext=e.situational_context),e.node_id!==void 0&&(r.nodeId=e.node_id),r}a(wt,"buildRecipe");function yt(e,t,n){return!jn()||e.prefer_warm!==!0||e.root!==!0||e.parent!=null||e.root_lifecycle==="terminal"||(e.prompt??"")!==""||e.model!==void 0||e.fork_from!==void 0||e.worktree!==void 0||e.node_id!==void 0||e.scopes!==void 0||e.isolated!==void 0?null:{kind:e.kind,mode:e.mode??"base",cwd:t,profileId:n,model:e.model??null,situationalContext:e.situational_context??null,name:e.name??null,creator:e.creator??null}}a(yt,"warmRequestFor");import{resolveCreateKind as yr}from"../../../core/runtime/launch-target.js";function ye(e){const t=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return In(e.model,t,e.modelExact===!0)}a(ye,"requestFromLaunch");function _t(e,t){if(t===null)return e;if(e.scopes===void 0)return{...e,scopes:[...t]};const n=e.scopes.filter(r=>!I(t,r));if(n.length===0)return e;throw new u(403,"scope_denied",`scopes exceed the bearer token's ceiling: ${n.join(", ")}`,{scopes:n,ceiling:t})}a(_t,"withinCeiling");async function _e(e,t,n){const r=(await Sn(t.kind,t.mode,{lifecycle:t.lifecycle,hasManager:e.parent!==null,model:t.model,modelOverride:!0,cwd:e.cwd,profileId:e.profile_id??null})).launch;await K(ye(r),{cwd:e.cwd,profileId:e.profile_id??null},n)}a(_e,"assertNodeModelChangeRegistered");function ge(e,t){const n=e.principal;if(n.class!=="broker"&&n.class!=="cron")return;const r=ot({app:n.app,resolved:Xn(n),launched:n.scopes},t,"read");if("root"in r)return;const o="missing"in r?r.missing:null;throw new u(403,"scope_missing",o?`the child's directory ${t} needs missing scope ${o}`:`no permission of this run names the child's directory ${t}`,void 0,void 0,o?{scope:o}:void 0)}a(ge,"assertCallerReaches");const H=new Map;async function gt(e,t={},n={}){const r=_t(ut(e.body),e.ceiling);if(e.principal.class==="broker"){const c=e.principal;if(r.prefer_warm===!0&&c.app!=="app:terminal"&&(r.prefer_warm=!1),r.parent!=null){const i=w(r.parent);if(i.grantee!==c.app)throw new u(403,"other_app_run","cannot create a child under another app's node");if(!U(c,r.parent,i.grantee??"app:terminal"))throw P(`unknown node: ${r.parent}`,{received:r.parent})}if(r.creator!==void 0&&r.creator!==c.node)throw new u(403,"other_app_run","cannot create for another node");if(r.fork_from!==void 0){let i=null;try{i=Me(De(r.fork_from))}catch{}if(i?.grantee!==c.app||!U(c,i.node_id,i.grantee))throw new u(403,"other_app_run","cannot fork another app's conversation")}r.creator=c.node}if(e.principal.class==="cron"){const c=e.principal;for(const l of["parent","creator","fork_from","node_id"])if(r[l]!=null)throw new u(403,"permission_error",`a cron cannot set ${l} on a node it starts`);r.prefer_warm=!1;const i=en({grantee:c.app,sub:c.sub,grant_id:c.grant,scopes:c.scopes});if(!i.allowed)throw new u(i.reason==="scope_missing"?403:401,i.reason==="scope_missing"?"scope_missing":"grant_revoked",i.message);if(r.scopes!==void 0){const l=r.scopes.find(p=>!I(i.effectiveScopes,p)||!I(c.scopes,p));if(l!==void 0)throw new u(403,"scope_missing",`scope ${l} is not held by this cron`,void 0,void 0,{scope:l})}else c.scopes!==null&&(r.scopes=[...c.scopes]);r.node_id=ne(),c.isolated&&(r.isolated=!0,r.cwd=x(r.node_id,c.app))}r.creator!==void 0&&Jn(w(r.creator),"crtr:act");const o=r.request_key?.trim();if(o===void 0||r.node_id!==void 0&&qe(r.node_id)&&R(r.node_id)!==null&&Ge(o)?.nodeId!==r.node_id)return D(e,r,t,n);const s=H.get(o);if(s!==void 0)return s;const d=bt(e,r,o,t,n);H.set(o,d);try{return await d}finally{H.delete(o)}}a(gt,"handleCreate");async function bt(e,t,n,r,o){const s=re(n);if(s.nodeId!==null&&R(s.nodeId)!==null){if(s.state!=="warm_claimed"){const d=R(s.nodeId);if(e.principal.class!=="person"&&!he(e.principal,d.grantee??"app:terminal",s.nodeId))throw new u(409,"create_request_conflict","request_key belongs to another caller");return{status:201,body:g(s.nodeId)}}return D(e,t,r,o,s)}try{return await D(e,t,r,o,s)}catch(d){const c=re(n);throw(c.nodeId===null||R(c.nodeId)===null)&&te(n,c.nodeId),d}}a(bt,"handleKeyedCreateRequest");async function D(e,t,n,r,o=void 0){const s=r.deadlineAt??pt(t.deadline),d=mt(t.outcome_delivery),c=e.principal.class==="cron"?nn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,cron_id:e.principal.cron,origin_run_id:e.principal.origin_run,isolated:e.principal.isolated,creator:pe(e.principal.cron)?.created_by??null},t.node_id):null;c!==null&&(t.root=c.root===!0);const i=c?.parent??t.parent??void 0;if(i==null&&t.root!==!0)throw h("a node create requires a parent (or root: true for an independent root)");let l=null;if(t.output_schema!==void 0){try{l=JSON.parse(t.output_schema)}catch{throw h("output_schema must be a JSON schema string")}if(l===null||typeof l!="object"||Array.isArray(l))throw h("output_schema must be a JSON schema object");await Tn(l)}const{profileId:p,kind:m,cwd:f,launch:b}=await Fn(t,{validateKind:Re});ge(e,f),t.worktree!==void 0&&ge(e,T(t.pin_cwd??t.cwd??f));const v={...t,kind:m},_=d===void 0?void 0:ht(d,f),Q=("grant"in e.principal?e.principal.grant:null)??(i==null?null:R(i)?.grant_id??null);if(await K(ye(Ee(b,Q,t.model)),{cwd:f,profileId:p},n.modelRegistry),it(Q)!=="app"&&!await ct(p))throw new u(412,"no_provider_credential",we,{error:"no_provider_credential",message:we,next:"The person running this runtime logs a provider in with `crtr sys setup`, or stores a provider key on the profile."});let M=null;{const A=o===void 0||o.nodeId===null||o.state==="warm_claimed"?yt(v,f,p):null,j=l===null&&r.beforeBrokerLaunch===void 0?void 0:S=>{l!==null&&Nn(S,"terminal",l),r.beforeBrokerLaunch?.(S)};if(o?.state==="warm_claimed"&&o.nodeId!==null){const S=A===null?null:await Mn(A,o.nodeId,j);if(S===null)throw te(o.requestKey,o.nodeId),new u(503,"create_resume_failed",`could not resume warm claim ${o.nodeId}; retry the create`);return oe(o.requestKey,S),{status:201,body:g(S)}}if(A!==null){const S=await Ln(A,{...s===void 0?{}:{deadlineAt:s},..._===void 0?{}:{outcomeDelivery:_},...o===void 0?{}:{requestKey:o.requestKey}},j);if(S!==null)return o!==void 0&&oe(o.requestKey,S),fe(A),{status:201,body:g(S)};M=A}const G=o?.nodeId??t.node_id??(o===void 0?void 0:ne());o!==void 0&&o.nodeId===null&&Be(o.requestKey,G);const O=wt(G===void 0?v:{...v,node_id:G},i,f);c!==null&&(c.rootLifecycle!==void 0&&(O.rootLifecycle=c.rootLifecycle),c.scopes!==void 0&&v.scopes===void 0&&(O.scopes=c.scopes),c.run!==void 0&&(O.run=c.run),c.withinCreate!==void 0&&(O.withinCreate=c.withinCreate)),s!==void 0&&(O.deadlineAt=s),_!==void 0&&(O.outcomeDelivery=_),O.readiness=t.root===!0?"await":"launch";const X=await Le(O,j,()=>{M!==null&&fe(M)});return o!==void 0&&Fe(o.requestKey,X.node.node_id),{status:201,body:g(X.node.node_id)}}}a(D,"handleCreateRequest");function k(e,t){const n=e.query.getAll(t);if(n.length>1)throw h(`${t} must appear at most once`);return n[0]}a(k,"singleQueryValue");function be(e,t){if(e!==void 0){if(e==="1"||e==="true")return!0;if(e==="0"||e==="false")return!1;throw h(`${t} must be true or false`)}}a(be,"optionalBoolean");function L(e,t,n){if(e!==void 0){if(n.includes(e))return e;throw h(`${t} must be one of: ${n.join(", ")}`)}}a(L,"oneOf");function vt(e){const t=new Set(["status","lifecycle","kind","mode","profile_id","profile_prefix","parent","top_level","under","hanging","include"]);for(const[b]of e.query)if(!t.has(b))throw h(`unknown nodes query parameter: ${b}`);const n=L(k(e,"status"),"status",["active","idle","done","dead","canceled"]),r=L(k(e,"lifecycle"),"lifecycle",["terminal","resident"]),o=k(e,"kind"),s=L(k(e,"mode"),"mode",["base","orchestrator"]),d=k(e,"profile_id"),c=k(e,"profile_prefix"),i=k(e,"parent"),l=be(k(e,"top_level"),"top_level"),p=k(e,"under"),m=be(k(e,"hanging"),"hanging"),f=L(k(e,"include"),"include",["activity"]);if(i!==void 0&&l===!0)throw h("parent and top_level=true cannot be combined");return{...n!==void 0?{status:n}:{},...r!==void 0?{lifecycle:r}:{},...o!==void 0?{kind:o}:{},...s!==void 0?{mode:s}:{},...d!==void 0?{profile_id:d}:{},...c!==void 0?{profile_prefix:c}:{},...i!==void 0?{parent:i}:{},...l!==void 0?{top_level:l}:{},...p!==void 0?{under:p}:{},...m!==void 0?{hanging:m}:{},...f!==void 0?{include:f}:{}}}a(vt,"parseListQuery");function kt(e){const t=vt(e);let n=t.status!==void 0?ie({status:t.status}):ie();if(e.principal.class!=="person"&&(n=n.filter(d=>he(e.principal,d.grantee??"app:terminal",d.node_id))),t.lifecycle!==void 0&&(n=n.filter(d=>d.lifecycle===t.lifecycle)),t.kind!==void 0&&(n=n.filter(d=>d.kind===t.kind)),t.mode!==void 0&&(n=n.filter(d=>d.mode===t.mode)),t.profile_id!==void 0&&(n=n.filter(d=>d.profile_id===t.profile_id)),t.profile_prefix!==void 0&&(n=n.filter(d=>d.profile_id?.startsWith(t.profile_prefix))),t.parent!==void 0&&(n=n.filter(d=>d.parent===t.parent)),t.top_level===!0&&(n=n.filter(d=>d.parent===null)),t.under!==void 0){const d=new Set(Ve(t.under));n=n.filter(c=>d.has(c.node_id))}t.hanging===!0&&(n=n.filter(d=>d.parent!==null&&R(d.parent)===null));const r=t.include==="activity"?Ze(n.map(d=>d.node_id)):null,o=n.length>0?Ae():new Set;return{status:200,body:n.map(d=>{const c=Yn(d,d.profile_id!==null&&o.has(d.profile_id));if(r!==null){const i=ue(d.node_id);c.activity={latest_report:i[0]??null,final_report:d.final_report===null?null:i.find(l=>l.name===d.final_report)??null,pending_human_count:r[d.node_id]??0}}return c})}}a(kt,"handleList");async function St(e){const t=e.params.id,n=await Zn(e,t,{method:"GET",path:`/v1/nodes/${encodeURIComponent(t)}`,query:qn(e)});if(n.kind==="unknown")throw nt(t,n.unreachable);if(n.kind==="remote"){if(n.status>=400)throw et(n.status,n.body);return{status:n.status,body:tt(n.body,n.machine)}}return w(t),{status:200,body:g(t)}}a(St,"handleDetail");async function Rt(e){const t=e.params.id;w(t);const n=await ln(t);return{status:200,body:{node_id:t,snapshot:{messages:n.snapshot.messages,...n.snapshot.messageIds===void 0?{}:{messageIds:n.snapshot.messageIds},messageVisibility:n.snapshot.messageVisibility,turnVisibility:n.snapshot.turnVisibility,stats:n.snapshot.stats,state:n.snapshot.state,display:n.snapshot.display,...n.snapshot.queued===void 0?{}:{queued:n.snapshot.queued},...n.snapshot.toolGroupSummaries===void 0?{}:{toolGroupSummaries:n.snapshot.toolGroupSummaries},...n.snapshot.turnReplies===void 0?{}:{turnReplies:n.snapshot.turnReplies},...n.snapshot.workingActivity===void 0?{}:{workingActivity:n.snapshot.workingActivity}},commands:n.commands.map(o=>({name:o.name,description:o.description,source:o.source})),captured_at:B()}}}a(Rt,"handleSnapshot");async function Ot(e){const t=e.params.id,n=fn(t);if(n===null)throw P(`unknown node: ${t}`,{received:t});return{status:200,body:n}}a(Ot,"handleSubject");function It(e){for(const[s]of e.query)if(s!=="cursor"&&s!=="limit")throw h(`unknown messages query parameter: ${s}`);const t=e.query.getAll("cursor"),n=e.query.getAll("limit");if(t.length>1)throw h("cursor must appear at most once");if(n.length>1)throw h("limit must appear at most once");if(n.length===0)return t.length===0?{}:{cursor:t[0]};const r=n[0],o=Number(r);if(!Number.isSafeInteger(o)||r==="")throw h("limit must be an integer from 1 through 500");return t.length===0?{limit:o}:{cursor:t[0],limit:o}}a(It,"parseMessagesQuery");async function At(e){const t=e.params.id,n=await sn(t,It(e));return{status:200,body:{node_id:t,messages:n.messages,...n.messageIds===void 0?{}:{message_ids:n.messageIds},message_visibility:n.messageVisibility,next_cursor:n.nextCursor,captured_at:B()}}}a(At,"handleMessages");function $t(e){const t=e.params.id;w(t);const n=an(t);return{status:200,body:{node_id:t,session_file:n.sessionFile,session_jsonl:n.sessionJsonl,system_prompt:n.systemPrompt,captured_at:B()}}}a($t,"handleSession");async function Ct(e){const t=e.params.id;return w(t),{status:200,body:{node_id:t,markdown:await cn(t)}}}a(Ct,"handleTranscript");function Nt(e){const t=e.params.id;w(t);const n=a((o,s)=>({node_id:o,label:R(o)?.name??o,dir:se(o),self:s,files:ft(o)}),"root"),r=[n(t,!0),...de(t).map(o=>n(o.node_id,!1))].filter(o=>Tt(o.node_id,o.dir)||o.files>0);return{status:200,body:{node_id:t,roots:r}}}a(Nt,"handleContext");function Tt(e,t){try{return tn(x(e),t)}catch(n){if(rn(n))return!1;throw n}}a(Tt,"hasDir");async function Et(e){const t=w(e.params.id),n=e.principal.class==="broker"?e.principal.scopes:e.ceiling,r=n===null?void 0:t.scopes==null?[...n]:[...new Set([...t.scopes.filter(s=>I(n,s)),...n.filter(s=>I(t.scopes,s))])],o=await Pe(t.node_id,r,e.principal.class==="broker"?e.principal.node:void 0);return{status:201,body:g(o.node.node_id)}}a(Et,"handleFork");const Pt=1e4;async function Dt(e){const t=e.params.id;w(t);const n=e.body,r=n?.resume??!0,o=n?.reopen??!1;yn(t,o);const s=!r&&n?.cron_id!==void 0?pe(n.cron_id):null,d=await Te(t,{resume:r,capacity:"refuse",...s!==null?{wakeReason:Vn(s,"schedule_revive")}:{}});d.outcome!=="already-live"&&Ke(t,{failures:0,notBefore:null});const c=R(t)?.status??"active",i=(d.launch??E().get(t)?.handle)?.exited,l=d.outcome!=="frozen"&&await le(t,i,Pt);return{status:200,body:Hn(t,d,c,l)}}a(Dt,"handleRevive");async function Lt(e){const t=e.params.id;return{status:200,body:{newNodeId:(await wn(t))?.newNodeId??null}}}a(Lt,"handleRelaunchRoot");function Mt(e){const t=e.params.id;w(t);const n=e.body;if(n?.caller_pid!==void 0&&(!Number.isInteger(n.caller_pid)||n.caller_pid<=0))throw h("caller_pid must be a positive integer when provided");const r=zn(),o=Ne(t,{rootEvent:n?.finish===!0?"finish":"cancel",callerPid:n?.caller_pid,...e.principal.class==="broker"?{closer:e.principal.node}:{},...r===null?{}:{registerDetached:r.registerDetached}});return{status:200,body:Un(o)}}a(Mt,"handleClose");function jt(e){if(!Qn(e.principal))throw new u(403,"permission_error","only the person marks a conversation read");const t=e.params.id;return w(t),ze(t),{status:204}}a(jt,"handleMarkRead");async function Gt(e){const t=e.params.id;w(t);const n=await hn(t);return{status:200,body:{recycled:n.recycled,finalized:n.finalized,new_root:n.newRoot,delivered:n.delivered}}}a(Gt,"handleRecycle");async function Ft(e){const t=e.params.id;return w(t),await V(t,{lifecycle:"terminal"}),$(e,t,["lifecycle"]),{status:200,body:g(t)}}a(Ft,"handleDemote");async function Bt(e,t={}){const n=e.params.id,r=w(n),o=e.body??{};o.kind!==void 0&&await z(o.kind,r.cwd,r.profile_id??null),o.model!==void 0&&await _e(r,{kind:o.kind??r.kind,mode:"orchestrator",lifecycle:o.resident===!0?"resident":r.lifecycle,model:o.model},t.modelRegistry);const s=await pn(n,{...o.kind!==void 0?{kind:o.kind}:{},...o.resident===!0?{resident:!0}:{},...o.model!==void 0?{model:o.model}:{}});$(e,n,["mode",...o.kind!==void 0?["kind"]:[],...o.resident===!0?["lifecycle"]:[],...o.model!==void 0?["model"]:[]]);const d=J(s.meta,Y(n));return d.roadmap_written=s.roadmapWritten,d.roadmap_ref=s.roadmapRef,d.goal_path=s.goalPath,{status:200,body:d}}a(Bt,"handlePromote");async function Kt(e,t={}){const n=e.params.id,r=w(n),o=e.body??{};o.kind!==void 0&&await z(o.kind,r.cwd,r.profile_id??null),o.model!==void 0&&await _e(r,{kind:o.kind??r.kind,mode:o.promote===!0?"orchestrator":r.mode,lifecycle:r.lifecycle,model:o.model},t.modelRegistry);const s=o.note??"Refresh: continue against your roadmap or goal.";_n(n,s);const d=await mn(n,{...o.promote===!0?{promote:!0}:{},...o.kind!==void 0?{kind:o.kind}:{},...o.model!==void 0?{model:o.model}:{}});return{status:200,body:J(d.meta,Y(n))}}a(Kt,"handleYield");function Wt(e){const t=e.params.id;w(t);const n=e.body;if(n===void 0||typeof n.controller!="string"||n.controller.trim()==="")throw h("a wait requires a controller node id");const r=R(n.controller);if(r===null)throw P(`unknown controller: ${n.controller}`,{received:n.controller});if(r.status!=="active"&&r.status!=="idle")throw new u(422,"unprocessable",`controller ${n.controller} is ${r.status}`);return xe(t,n.controller),{status:200,body:g(t)}}a(Wt,"handleWait");async function xt(e){const t=e.principal,n=await un(t.class==="broker"?o=>o.grantee===t.app&&U(t,o.node_id,o.grantee):void 0);return{status:200,body:{revived:[...n.revived],failed:[...n.failed],frozen:[...n.frozen]}}}a(xt,"handleReviveAll");const Vt=new Set(["interrupt","steer","after-turn","next-turn"]);function zt(e){if(e===null)return null;const t=a(r=>{throw new y({error:"invalid_listen",message:r,received:e,field:"listen",next:'Send an object mapping levels "1".."10" to interrupt|steer|after-turn|next-turn (e.g. {"3":"next-turn"}), or null to reset to the global default.'})},"bad");if(typeof e!="object"||Array.isArray(e))return t("listen must be an object or null");const n=Object.entries(e);if(n.length===0)return t("listen must name at least one level");for(const[r,o]of n)/^(?:[1-9]|10)$/.test(r)||t(`listen level must be 1..10: ${r}`),(typeof o!="string"||!Vt.has(o))&&t(`listen delivery must be interrupt|steer|after-turn|next-turn: ${String(o)}`);return JSON.stringify(Object.fromEntries(n))}a(zt,"listenJson");function $(e,t,n,r){n.length===0&&r===void 0||W(o=>{if(r?.(o),n.length===0)return;const s=Je(o,t),d=He(o,e),c=d.class==="person"?d.person:d.class==="app"?d.app:d.node;Ue(o,c,s),Ye(o,{event:"configured",object:s,actor:c,attrs:{changed:n.join(",")}})})}a($,"recordConfigured");function ve(e){const t=E().get(e);if(t===void 0)return!1;try{return process.kill(t.pid,"SIGTERM"),!0}catch{return!1}}a(ve,"restartLiveBroker");function ke(e,t){try{return q(e)===q(t)}catch{return!1}}a(ke,"sameDirectory");async function Jt(e,t={}){const n=e.params.id,r=e.body;let o;if(r===void 0)o={};else if(typeof r=="object"&&r!==null&&!Array.isArray(r))o=r;else throw new y({error:"cwd_invalid",message:"cwd repair requires a non-null object with a non-empty cwd string",received:r,field:"cwd",next:"Send a config patch with a non-empty absolute cwd string."});if(o.profile!==void 0){if(typeof o.profile!="string"||o.profile==="")throw new y({error:"profile_invalid",message:"profile must be a non-empty profile id or name",received:o.profile,field:"profile",next:"Send a profile id or exact name."});if(Object.keys(o).some(m=>m!=="profile"&&m!=="restart"&&o[m]!==void 0))throw new y({error:"profile_not_exclusive",message:"profile can only be combined with restart",field:"profile",next:"Send profile (and optionally restart) alone."});const i=w(n),l=Kn(e,o.profile);Wn(l.profileId);const p=xn(l.profileId).manifest.grantee??"app:terminal";if(p!==(i.grantee??"app:terminal"))throw new y({error:"profile_grantee_mismatch",message:`profile ${l.profileId} belongs to ${p}, node ${n} to ${i.grantee}`,received:o.profile,field:"profile",next:"Pick a profile owned by the same app as the node."});return(i.profile_id??null)!==l.profileId&&(Gn(i.kind)?(await z(i.kind,i.cwd,l.profileId),await V(n,{profileId:l.profileId})):C(n,{profile_id:l.profileId}),$(e,n,["profile"])),o.restart===!0&&ve(n),{status:200,body:g(n)}}if(Object.prototype.hasOwnProperty.call(o,"cwd")){const i=o.cwd;if(typeof i!="string"||i==="")throw new y({error:"cwd_invalid",message:"cwd must be a non-empty string",received:i,field:"cwd",next:"Send a non-empty absolute cwd string."});if(Object.keys(o).some(f=>f!=="cwd"&&f!=="keep_session"&&f!=="restart"&&o[f]!==void 0))throw new y({error:"cwd_not_exclusive",message:"cwd repair cannot be combined with another config field",received:o,field:"cwd",next:"Send cwd as the only config field."});const l=w(n),p=N(l.cwd);if(o.keep_session===!0||p&&F(i)&&N(i)&&ke(l.cwd,i)){if(!F(i))throw new y({error:"cwd_not_absolute",message:`cwd must be an absolute path: ${i}`,received:i,field:"cwd",next:"Send an absolute path."});const f=T(i);let b=!1;try{b=ee(f).isDirectory()}catch{}if(!b)throw new y({error:"cwd_missing",message:`cwd is not an existing directory: ${f}`,received:i,field:"cwd",next:"Send an existing absolute directory."});if(p&&!ke(l.cwd,f))throw new y({error:"cwd_not_same_directory",message:`${f} is not the same directory as the recorded ${l.cwd}; --keep-session only relocates an alias`,received:i,field:"cwd",next:"Drop --keep-session to repair a gone cwd, or pass the real path of the recorded directory."});if(l.managed_worktree?.state==="open")throw new y({error:"cwd_node_managed_worktree_open",message:`${n} owns an open managed worktree and cannot relocate its cwd`,received:i,field:"cwd",next:"Reconcile the managed worktree first."});const v=l.cwd;return v!==f&&(W(_=>{C(n,{cwd:f}),Z(_,"UPDATE crons SET cwd = ? WHERE anchor_node = ? AND cwd = ?").run(f,n,v),Qe(n,v,f)}),$(e,n,["cwd"])),o.restart===!0&&ve(n),{status:200,body:g(n)}}if(E().has(n)){const f=`${n} cannot repair its cwd while its broker is live`;throw new u(409,"cwd_node_live",f,{error:"cwd_node_live",message:f,received:i,field:"cwd",next:"Let the node become dormant, then retry the cwd repair."})}if(l.fork_from!=null)throw new y({error:"cwd_node_forked",message:`${n} is a fork and cannot repair its cwd; recorded cwd: ${l.cwd}`,received:i,field:"cwd",next:`Restore ${l.cwd}, then revive the fork normally.`});if(l.managed_worktree?.state==="open")throw new y({error:"cwd_node_managed_worktree_open",message:`${n} owns an open managed worktree and cannot repair its cwd`,received:i,field:"cwd",next:"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon flow."});if(N(l.cwd))throw new y({error:"cwd_still_present",message:`${n} recorded cwd still exists: ${l.cwd}; cwd repair is not a relocation command`,received:i,field:"cwd",next:"This command only repairs a node whose recorded cwd is gone."});if(!F(i))throw new y({error:"cwd_not_absolute",message:`cwd must be an absolute path: ${i}`,received:i,field:"cwd",next:"Send an absolute path."});const m=T(i);if(!N(m))throw new y({error:"cwd_missing",message:`cwd does not exist: ${m}`,received:i,field:"cwd",next:"Create the replacement directory or send an existing absolute directory."});try{if(!ee(m).isDirectory())throw new y({error:"cwd_not_a_directory",message:`cwd is not a directory: ${m}`,received:i,field:"cwd",next:"Send an existing absolute directory."})}catch(f){throw f instanceof y?f:new y({error:"cwd_missing",message:`cwd does not exist: ${m}`,received:i,field:"cwd",next:"Create the replacement directory or send an existing absolute directory."})}return W(()=>{C(n,{cwd:m}),Xe(n)}),$(e,n,["cwd"]),{status:200,body:g(n)}}let s=w(n);const d=o.listen===void 0?void 0:zt(o.listen);if(o.scopes!==void 0){const i=me(o.scopes),l=s.grantee==="app:terminal"?dt():s.grant_id?rt(s.grant_id):null,p=s.grantee==="app:terminal"&&!lt("user").runtime?.issuer;if(!i.every(m=>I(s.scopes,m)&&(l?I(l.resolved,m):p)))throw new y({error:"unknown_scope",message:"scopes may only narrow the node's current effective set",received:i,field:"scopes",next:"Send only scopes the node currently holds."});if(i.includes("crtr:act")&&!I(i,"crtr:llm"))throw new u(400,"invalid_scope","crtr:act requires crtr:llm");s=C(n,{scopes:i})}if(o.situational_context!==void 0&&$e(n,Ce(o.situational_context)),o.model!==void 0){const i=(await ae(s.cwd,s.profile_id??null)).modelLadders,l=Rn(o.model,i),p=st(s.grant_id,o.model,l===void 0),m=p?{family:at,strength:p.strength}:l,f=p?p.model:On(o.model,i);await K({kind:"concrete",spec:f},{cwd:s.cwd,profileId:s.profile_id??null},t.modelRegistry);const b=E();let v=!1;if(b.isClaimed(n)&&await le(n,b.get(n)?.handle.exited)&&(v=await $n(n,f,m).then(()=>!0,_=>{if(_ instanceof Cn)return!1;throw _})),!v){if(b.isClaimed(n)){const _=`${n}'s broker did not accept the model switch; the model was not changed`;throw new u(409,"broker_not_ready",_,{error:"broker_not_ready",message:_,received:o.model,field:"model",next:"Check the node with `crtr node show`, then retry the model change."})}if(!f.includes("/"))throw h(`model must resolve to a provider/id spec: ${o.model}`);An(n,f,m)}s=w(n)}if(o.kind!==void 0||o.mode!==void 0||o.lifecycle!==void 0){o.kind!==void 0&&await Re(o.kind);const i=o.kind??s.kind,l=o.mode??s.mode,p=o.lifecycle??s.lifecycle;let m;l==="orchestrator"&&!vn(n)&&(m=a(()=>{const f=gn(n);bn(n,f!==null&&f.trim()!==""?{goal:f.trim()}:{})},"beforePersist")),s=await V(n,{...o.kind!==void 0?{kind:i}:{},...o.mode!==void 0?{mode:l}:{},...o.lifecycle!==void 0?{lifecycle:p}:{}},m)}o.name!==void 0&&(s=C(n,{name:o.name}),s.window&&kn(s.window,Bn(s)));const c=["scopes","situational_context","model","kind","mode","lifecycle","name","listen"].filter(i=>o[i]!==void 0);return $(e,n,c,d===void 0?void 0:i=>{Z(i,"UPDATE objects SET listen_json = ? WHERE id = ? AND type = 'node'").run(d,n)}),{status:200,body:g(n)}}a(Jt,"handleConfig");async function Se(){return(await ae(process.cwd(),Ie()||null)).kinds}a(Se,"daemonKinds");async function Re(e){const t=Object.keys(await Se());if(!t.includes(e))throw h(`unknown kind: ${e}`,{received:e,next:`Valid kinds: ${t.sort().join(", ")}.`})}a(Re,"assertKindServer");async function Ut(){const e=await Se();return{status:200,body:{kinds:Object.keys(e).sort().map(n=>({kind:n,when_to_use:e[n]?.whenToUse??null,available_to:e[n]?.availableTo??null}))}}}a(Ut,"handleListKinds");const mr=[{method:"POST",pattern:"/v1/nodes",scope:"crtr:act",handler:gt},{method:"GET",pattern:"/v1/nodes",handler:kt},{method:"GET",pattern:"/v1/kinds",handler:a(()=>Ut(),"handler")},{method:"POST",pattern:"/v1/nodes/revive-all",scope:"crtr:act",handler:xt},{method:"GET",pattern:"/v1/nodes/:id",handler:St},{method:"GET",pattern:"/v1/nodes/:id/snapshot",handler:Rt},{method:"GET",pattern:"/v1/nodes/:id/subject",handler:Ot},{method:"GET",pattern:"/v1/nodes/:id/messages",handler:At},{method:"GET",pattern:"/v1/nodes/:id/session",handler:$t},{method:"GET",pattern:"/v1/nodes/:id/transcript",handler:Ct},{method:"GET",pattern:"/v1/nodes/:id/context",handler:Nt},{method:"POST",pattern:"/v1/nodes/:id/fork",scope:"crtr:act",handler:Et},{method:"POST",pattern:"/v1/nodes/:id/revive",scope:"crtr:act",handler:Dt},{method:"POST",pattern:"/v1/nodes/:id/relaunch-root",scope:"crtr:act",handler:Lt},{method:"POST",pattern:"/v1/nodes/:id/close",handler:Mt},{method:"POST",pattern:"/v1/nodes/:id/read",handler:jt},{method:"POST",pattern:"/v1/nodes/:id/recycle",scope:"crtr:act",handler:Gt},{method:"POST",pattern:"/v1/nodes/:id/demote",scope:"crtr:act",handler:Ft},{method:"POST",pattern:"/v1/nodes/:id/promote",scope:"crtr:act",handler:Bt},{method:"POST",pattern:"/v1/nodes/:id/yield",scope:"crtr:act",handler:Kt},{method:"POST",pattern:"/v1/nodes/:id/wait",handler:Wt},{method:"PATCH",pattern:"/v1/nodes/:id/config",handler:Jt}];export{Jt as handleConfig,gt as handleCreate,Et as handleFork,kt as handleList,Bt as handlePromote,Kt as handleYield,mr as nodeRoutes,yr as resolveCreateKind};
1
+ var Oe=Object.defineProperty;var a=(e,t)=>Oe(e,"name",{value:t,configurable:!0});import{prepared as Z}from"../../../core/sqlite-statements.js";import{envProfileId as Ie}from"../../../shared/env.js";import{pausedCanvasProfiles as Ae}from"../../../core/profiles/manifest.js";import{existsSync as N,realpathSync as q,statSync as ee}from"node:fs";import{isAbsolute as F,resolve as T}from"node:path";import{appendSituationalContext as $e,formatSituationalProse as Ce}from"../../../core/runtime/situational-context.js";import{closeNode as Ne}from"../../../core/runtime/close.js";import{reviveNode as Te}from"../../../core/runtime/revive.js";import{nowIso as B}from"../../../core/fs-utils.js";import{appPaidLaunch as Ee,assertLaunchModelRegistered as K,forkNode as Pe,resolveForkSource as De,spawnChildPrepared as Le}from"../../../core/runtime/spawn.js";import{forkSourceNode as Me,newNodeId as ne}from"../../../core/runtime/nodes.js";import{abandonCreateRequest as te,childrenOf as je,completeCreateRequest as oe,findCreateRequest as Ge,getNode as R,recordCreateRequestNode as Fe,reserveCreateRequest as re,reserveCreateRequestNode as Be,listNodes as ie,recordRespawnAttempt as Ke,subscribersOf as We,subscriptionsOf as de,updateNode as C}from"../../../core/canvas/canvas.js";import{armControllerWait as Ve}from"../../../core/canvas/waits.js";import{subtreeIds as xe}from"../../../core/canvas/nav-model.js";import{withCanvasWrite as W}from"../../../core/canvas/db.js";import{markNodeRead as ze}from"../../../core/canvas/recovery.js";import{nodeObject as Je}from"../../../core/graph/objects.js";import{recordActedOn as Ue}from"../../../core/graph/edges.js";import{publish as Ye}from"../../../core/graph/events.js";import{readerFor as He}from"../reader.js";import{relocateSessionCwd as Qe,unbindSession as Xe}from"../../../core/canvas/sessions.js";import{ticketCountsForNodes as Ze}from"../../../core/canvas/attention.js";import{isSafeNodeId as qe}from"../../../core/canvas/paths.js";import{contextDir as se,nodeAgentDir as V}from"../../../core/canvas/node-agent-paths.js";import{checkCronBirth as en}from"../../cron/authority.js";import{cronBirthPlacement as nn}from"../../cron/armed-context.js";import{existsBeneath as tn,isAbsentOrRefused as on,isBeneathRefusal as rn,readdirBeneath as dn}from"../../../core/spaces/open-beneath.js";import{readNodeMessagesPage as sn,readNodeSession as an,readNodeSnapshot as ln,transcriptMarkdown as cn}from"../../../core/runtime/node-read.js";import{assembleNodeSubject as fn}from"../../../core/substrate/subject.js";import{reviveAll as un}from"../../../core/runtime/revive-all.js";import{promoteAsync as pn,requestYieldAsync as mn,reshapeNodeAsync as x}from"../../../core/runtime/promote.js";import{recycleNode as hn}from"../../../core/runtime/recycle.js";import{relaunchRoot as wn}from"../../../core/runtime/reset.js";import{assertReopenGate as yn}from"../../../core/runtime/reopen.js";import{writeYieldMessage as _n,readGoal as gn}from"../../../core/runtime/kickoff.js";import{ensureRoadmap as bn,hasRoadmap as vn}from"../../../core/runtime/roadmap.js";import{renameWindow as kn}from"../../../core/runtime/placement.js";import{buildLaunchSpecAsync as Sn,explicitModelIntent as Rn,normalizeModel as On,readMergedLaunchConfigAsync as ae}from"../../../core/runtime/launch.js";import{modelRequestFromConfig as In}from"../../../core/model-routes.js";import{persistDormantModel as An,setModelLive as $n}from"../../../core/runtime/model-swap.js";import{waitForBrokerViewSocket as le}from"../../../core/runtime/view-socket.js";import{BrokerUnreachableError as Cn}from"../../../core/runtime/broker-request.js";import{boundFleet as E}from"../../../core/runtime/fleet.js";import{admitLegacyStructuredOutput as Nn}from"../../structured-requests/sidecar.js";import{assertOutputSchemaAccepted as Tn}from"../../../core/runtime/structured-output.js";import{OUTCOME_PAYLOAD_MAX_BYTES as ce}from"../../../core/canvas/types.js";import{HUMAN_ACTION_NAME_PATTERN as En}from"../../../core/human/action-binding.js";import{resolveHumanAction as Pn}from"../../../core/human-actions.js";import{parseDurationMs as Dn}from"../../../core/wake.js";import{claimWarmNode as Ln,refillWarmPool as fe,resumeWarmClaim as Mn,warmPoolEnabled as jn}from"../../../core/runtime/warm-pool.js";import{assertLaunchKind as z,isLaunchKind as Gn,resolveLaunchTarget as Fn}from"../../../core/runtime/launch-target.js";import{fullName as Bn}from"../../../core/canvas/labels.js";import{reportsForNode as ue}from"./reports.js";import{requireUsableProfile as Kn}from"./profiles.js";import{assertProfileActive as Wn,loadExactProfileManifest as Vn}from"../../../core/profiles/manifest.js";import{getCron as pe}from"../../../core/canvas/crons.js";import{cronWakeOrigin as xn}from"../../../core/runtime/bearings.js";import{boundDaemonControl as zn}from"../../control.js";import{notFound as P,usage as h}from"../../../core/errors.js";import{assertScope as Jn,validateScopes as me}from"../../../core/scopes.js";import{scopeAllowed as I}from"../../../shared/env.js";import{toCloseResultDTO as Un,toNodeDetailDTO as J,toNodeSummaryDTO as Yn,toReviveResultDTO as Hn}from"../map.js";import{ApiError as u}from"../../../api/index.js";import{InputError as y}from"../../../core/io.js";import{actsAsPerson as Qn,brokerMayReach as U,canReadNode as he,resolvedScopes as Xn}from"../router.js";import{forwardVerb as Zn,queryWithoutMachine as qn,relayError as et,unknownError as nt,withMachine as tt}from"../../peer/forward.js";import{fileRoot as ot}from"../../../core/spaces/file-root.js";import{activeGrant as rt,payerOfGrant as it,terminalGrant as dt}from"../../../core/grants/mirror.js";import{appRungForGrant as st}from"../../../core/llm/app-rungs.js";import{APP_FAMILY as at}from"../../../core/model-routes.js";import{readConfig as lt}from"../../../core/config.js";import{hasConfiguredProviderCredential as ct,NO_PROVIDER_CREDENTIAL_MESSAGE as we}from"../../../core/runtime/provider-credential-gate.js";function w(e){const t=R(e);if(t===null)throw P(`unknown node: ${e}`,{received:e});return t}a(w,"requireMeta");function Y(e){return{subscribesTo:de(e),subscribers:We(e),children:je(e)}}a(Y,"edgesFor");function g(e){const t=w(e);return J(t,Y(e))}a(g,"detailOf");function ft(e){let t=ue(e).length;try{t+=dn(V(e),se(e)).length}catch(n){if(!on(n))throw n}return t}a(ft,"countFiles");function ut(e){if(typeof e!="object"||e===null)throw h("a node create requires a JSON body");const t=e;if(t.kind!==void 0&&(typeof t.kind!="string"||t.kind.trim()===""))throw h("kind must be a non-empty string when provided");if(t.description!==void 0&&(typeof t.description!="string"||t.description.trim()===""))throw h("description must be a non-empty string when provided");if(t.creator!==void 0&&(typeof t.creator!="string"||t.creator.trim()===""))throw h("creator must be a non-empty node id when provided");if(t.scopes!==void 0&&me(t.scopes),t.isolated!==void 0&&typeof t.isolated!="boolean")throw new u(400,"invalid_request","isolated must be a boolean",{param:"isolated"});const n=t.root_lifecycle;if(n!==void 0&&n!=="resident"&&n!=="terminal")throw h("root_lifecycle must be 'resident' or 'terminal'");if(t.request_key!==void 0&&(typeof t.request_key!="string"||t.request_key.trim()===""||t.request_key.length>128))throw h("request_key must be a non-empty string of at most 128 characters when provided");return t}a(ut,"parseCreateBody");function pt(e){if(e===void 0)return;const t=Dn(e),n=t===null?null:new Date(Date.now()+t);if(t===null||!Number.isFinite(t)||t<=0||n===null||!Number.isFinite(n.getTime()))throw new u(422,"invalid_deadline","`deadline` must be a positive representable duration");return n.toISOString()}a(pt,"parseDeadline");function mt(e){if(e!==void 0){if(typeof e!="object"||e===null||Array.isArray(e))throw new u(400,"invalid_request","`outcome_delivery` must be an object");if(typeof e.action!="string"||!En.test(e.action))throw new u(400,"invalid_request","`outcome_delivery.action` must be a declared action name");if(e.payload!==void 0){let t;try{t=JSON.stringify(e.payload)}catch{throw new u(400,"invalid_request","`outcome_delivery.payload` must be JSON-serializable")}if(t===void 0)throw new u(400,"invalid_request","`outcome_delivery.payload` must be JSON-serializable");if(Buffer.byteLength(t,"utf8")>ce)throw new u(413,"payload_too_large",`serialized payload exceeds ${ce} bytes`)}return e}}a(mt,"parseOutcomeDelivery");function ht(e,t){const n=Pn(e.action,t);if(!("kind"in n))return{actionName:e.action,argv:n.argv,cwd:n.cwd,configOrigin:n.configOrigin,...e.payload===void 0?{}:{payload:e.payload}};throw n.kind==="unknown_action"?new u(422,"unknown_action",`no action named ${e.action} is declared from ${t}`):new u(422,"invalid_action",`${e.action} is declared in ${n.configOrigin} but unusable: ${n.reason}`,{reason:n.reason,configOrigin:n.configOrigin})}a(ht,"resolveOutcomeDelivery");function wt(e,t,n){const r={kind:e.kind,cwd:n,prompt:e.prompt??""};return e.mode!==void 0&&(r.mode=e.mode),e.name!==void 0&&(r.name=e.name),e.description!==void 0&&(r.description=e.description),t!==void 0&&(r.parent=t),e.creator!==void 0&&(r.creator=e.creator),e.scopes!==void 0&&(r.scopes=e.scopes),e.isolated!==void 0&&(r.isolated=e.isolated),e.root===!0&&(r.root=!0),e.root_lifecycle!==void 0&&(r.rootLifecycle=e.root_lifecycle),(e.worktree===!0||typeof e.worktree=="string")&&(r.worktree=!0),typeof e.worktree=="string"&&(r.worktreeBase=e.worktree),e.worktree!==void 0&&(r.worktreeCwd=T(e.pin_cwd??e.cwd??n)),e.fork_from!==void 0&&(r.forkFrom=e.fork_from),e.model!==void 0&&(r.model=e.model),e.profile!==void 0&&(r.profile=e.profile),e.situational_context!==void 0&&(r.situationalContext=e.situational_context),e.node_id!==void 0&&(r.nodeId=e.node_id),r}a(wt,"buildRecipe");function yt(e,t,n){return!jn()||e.prefer_warm!==!0||e.root!==!0||e.parent!=null||e.root_lifecycle==="terminal"||(e.prompt??"")!==""||e.model!==void 0||e.fork_from!==void 0||e.worktree!==void 0||e.node_id!==void 0||e.scopes!==void 0||e.isolated!==void 0?null:{kind:e.kind,mode:e.mode??"base",cwd:t,profileId:n,model:e.model??null,situationalContext:e.situational_context??null,name:e.name??null,creator:e.creator??null}}a(yt,"warmRequestFor");import{resolveCreateKind as yr}from"../../../core/runtime/launch-target.js";function ye(e){const t=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return In(e.model,t,e.modelExact===!0)}a(ye,"requestFromLaunch");function _t(e,t){if(t===null)return e;if(e.scopes===void 0)return{...e,scopes:[...t]};const n=e.scopes.filter(r=>!I(t,r));if(n.length===0)return e;throw new u(403,"scope_denied",`scopes exceed the bearer token's ceiling: ${n.join(", ")}`,{scopes:n,ceiling:t})}a(_t,"withinCeiling");async function _e(e,t,n){const r=(await Sn(t.kind,t.mode,{lifecycle:t.lifecycle,hasManager:e.parent!==null,model:t.model,modelOverride:!0,cwd:e.cwd,profileId:e.profile_id??null})).launch;await K(ye(r),{cwd:e.cwd,profileId:e.profile_id??null},n)}a(_e,"assertNodeModelChangeRegistered");function ge(e,t){const n=e.principal;if(n.class!=="broker"&&n.class!=="cron")return;const r=ot({app:n.app,resolved:Xn(n),launched:n.scopes},t,"read");if("root"in r)return;const o="missing"in r?r.missing:null;throw new u(403,"scope_missing",o?`the child's directory ${t} needs missing scope ${o}`:`no permission of this run names the child's directory ${t}`,void 0,void 0,o?{scope:o}:void 0)}a(ge,"assertCallerReaches");const H=new Map;async function gt(e,t={},n={}){const r=_t(ut(e.body),e.ceiling);if(e.principal.class==="broker"){const c=e.principal;if(r.prefer_warm===!0&&c.app!=="app:terminal"&&(r.prefer_warm=!1),r.parent!=null){const i=w(r.parent);if(i.grantee!==c.app)throw new u(403,"other_app_run","cannot create a child under another app's node");if(!U(c,r.parent,i.grantee??"app:terminal"))throw P(`unknown node: ${r.parent}`,{received:r.parent})}if(r.creator!==void 0&&r.creator!==c.node)throw new u(403,"other_app_run","cannot create for another node");if(r.fork_from!==void 0){let i=null;try{i=Me(De(r.fork_from))}catch{}if(i?.grantee!==c.app||!U(c,i.node_id,i.grantee))throw new u(403,"other_app_run","cannot fork another app's conversation")}r.creator=c.node}if(e.principal.class==="cron"){const c=e.principal;for(const l of["parent","creator","fork_from","node_id"])if(r[l]!=null)throw new u(403,"permission_error",`a cron cannot set ${l} on a node it starts`);r.prefer_warm=!1;const i=en({grantee:c.app,sub:c.sub,grant_id:c.grant,scopes:c.scopes});if(!i.allowed)throw new u(i.reason==="scope_missing"?403:401,i.reason==="scope_missing"?"scope_missing":"grant_revoked",i.message);if(r.scopes!==void 0){const l=r.scopes.find(p=>!I(i.effectiveScopes,p)||!I(c.scopes,p));if(l!==void 0)throw new u(403,"scope_missing",`scope ${l} is not held by this cron`,void 0,void 0,{scope:l})}else c.scopes!==null&&(r.scopes=[...c.scopes]);r.node_id=ne(),c.isolated&&(r.isolated=!0,r.cwd=V(r.node_id,c.app))}r.creator!==void 0&&Jn(w(r.creator),"crtr:act");const o=r.request_key?.trim();if(o===void 0||r.node_id!==void 0&&qe(r.node_id)&&R(r.node_id)!==null&&Ge(o)?.nodeId!==r.node_id)return D(e,r,t,n);const s=H.get(o);if(s!==void 0)return s;const d=bt(e,r,o,t,n);H.set(o,d);try{return await d}finally{H.delete(o)}}a(gt,"handleCreate");async function bt(e,t,n,r,o){const s=re(n);if(s.nodeId!==null&&R(s.nodeId)!==null){if(s.state!=="warm_claimed"){const d=R(s.nodeId);if(e.principal.class!=="person"&&!he(e.principal,d.grantee??"app:terminal",s.nodeId))throw new u(409,"create_request_conflict","request_key belongs to another caller");return{status:201,body:g(s.nodeId)}}return D(e,t,r,o,s)}try{return await D(e,t,r,o,s)}catch(d){const c=re(n);throw(c.nodeId===null||R(c.nodeId)===null)&&te(n,c.nodeId),d}}a(bt,"handleKeyedCreateRequest");async function D(e,t,n,r,o=void 0){const s=r.deadlineAt??pt(t.deadline),d=mt(t.outcome_delivery),c=e.principal.class==="cron"?nn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,cron_id:e.principal.cron,origin_run_id:e.principal.origin_run,isolated:e.principal.isolated,creator:pe(e.principal.cron)?.created_by??null},t.node_id):null;c!==null&&(t.root=c.root===!0);const i=c?.parent??t.parent??void 0;if(i==null&&t.root!==!0)throw h("a node create requires a parent (or root: true for an independent root)");let l=null;if(t.output_schema!==void 0){try{l=JSON.parse(t.output_schema)}catch{throw h("output_schema must be a JSON schema string")}if(l===null||typeof l!="object"||Array.isArray(l))throw h("output_schema must be a JSON schema object");await Tn(l)}const{profileId:p,kind:m,cwd:f,launch:b}=await Fn(t,{validateKind:Re});ge(e,f),t.worktree!==void 0&&ge(e,T(t.pin_cwd??t.cwd??f));const v={...t,kind:m},_=d===void 0?void 0:ht(d,f),Q=("grant"in e.principal?e.principal.grant:null)??(i==null?null:R(i)?.grant_id??null);if(await K(ye(Ee(b,Q,t.model)),{cwd:f,profileId:p},n.modelRegistry),it(Q)!=="app"&&!await ct(p))throw new u(412,"no_provider_credential",we,{error:"no_provider_credential",message:we,next:"The person running this runtime logs a provider in with `crtr sys setup`, or stores a provider key on the profile."});let M=null;{const A=o===void 0||o.nodeId===null||o.state==="warm_claimed"?yt(v,f,p):null,j=l===null&&r.beforeBrokerLaunch===void 0?void 0:S=>{l!==null&&Nn(S,"terminal",l),r.beforeBrokerLaunch?.(S)};if(o?.state==="warm_claimed"&&o.nodeId!==null){const S=A===null?null:await Mn(A,o.nodeId,j);if(S===null)throw te(o.requestKey,o.nodeId),new u(503,"create_resume_failed",`could not resume warm claim ${o.nodeId}; retry the create`);return oe(o.requestKey,S),{status:201,body:g(S)}}if(A!==null){const S=await Ln(A,{...s===void 0?{}:{deadlineAt:s},..._===void 0?{}:{outcomeDelivery:_},...o===void 0?{}:{requestKey:o.requestKey}},j);if(S!==null)return o!==void 0&&oe(o.requestKey,S),fe(A),{status:201,body:g(S)};M=A}const G=o?.nodeId??t.node_id??(o===void 0?void 0:ne());o!==void 0&&o.nodeId===null&&Be(o.requestKey,G);const O=wt(G===void 0?v:{...v,node_id:G},i,f);c!==null&&(c.rootLifecycle!==void 0&&(O.rootLifecycle=c.rootLifecycle),c.scopes!==void 0&&v.scopes===void 0&&(O.scopes=c.scopes),c.run!==void 0&&(O.run=c.run),c.withinCreate!==void 0&&(O.withinCreate=c.withinCreate)),s!==void 0&&(O.deadlineAt=s),_!==void 0&&(O.outcomeDelivery=_),O.readiness=t.root===!0?"await":"launch";const X=await Le(O,j,()=>{M!==null&&fe(M)});return o!==void 0&&Fe(o.requestKey,X.node.node_id),{status:201,body:g(X.node.node_id)}}}a(D,"handleCreateRequest");function k(e,t){const n=e.query.getAll(t);if(n.length>1)throw h(`${t} must appear at most once`);return n[0]}a(k,"singleQueryValue");function be(e,t){if(e!==void 0){if(e==="1"||e==="true")return!0;if(e==="0"||e==="false")return!1;throw h(`${t} must be true or false`)}}a(be,"optionalBoolean");function L(e,t,n){if(e!==void 0){if(n.includes(e))return e;throw h(`${t} must be one of: ${n.join(", ")}`)}}a(L,"oneOf");function vt(e){const t=new Set(["status","lifecycle","kind","mode","profile_id","profile_prefix","parent","top_level","under","hanging","include"]);for(const[b]of e.query)if(!t.has(b))throw h(`unknown nodes query parameter: ${b}`);const n=L(k(e,"status"),"status",["active","idle","done","dead","canceled"]),r=L(k(e,"lifecycle"),"lifecycle",["terminal","resident"]),o=k(e,"kind"),s=L(k(e,"mode"),"mode",["base","orchestrator"]),d=k(e,"profile_id"),c=k(e,"profile_prefix"),i=k(e,"parent"),l=be(k(e,"top_level"),"top_level"),p=k(e,"under"),m=be(k(e,"hanging"),"hanging"),f=L(k(e,"include"),"include",["activity"]);if(i!==void 0&&l===!0)throw h("parent and top_level=true cannot be combined");return{...n!==void 0?{status:n}:{},...r!==void 0?{lifecycle:r}:{},...o!==void 0?{kind:o}:{},...s!==void 0?{mode:s}:{},...d!==void 0?{profile_id:d}:{},...c!==void 0?{profile_prefix:c}:{},...i!==void 0?{parent:i}:{},...l!==void 0?{top_level:l}:{},...p!==void 0?{under:p}:{},...m!==void 0?{hanging:m}:{},...f!==void 0?{include:f}:{}}}a(vt,"parseListQuery");function kt(e){const t=vt(e);let n=t.status!==void 0?ie({status:t.status}):ie();if(e.principal.class!=="person"&&(n=n.filter(d=>he(e.principal,d.grantee??"app:terminal",d.node_id))),t.lifecycle!==void 0&&(n=n.filter(d=>d.lifecycle===t.lifecycle)),t.kind!==void 0&&(n=n.filter(d=>d.kind===t.kind)),t.mode!==void 0&&(n=n.filter(d=>d.mode===t.mode)),t.profile_id!==void 0&&(n=n.filter(d=>d.profile_id===t.profile_id)),t.profile_prefix!==void 0&&(n=n.filter(d=>d.profile_id?.startsWith(t.profile_prefix))),t.parent!==void 0&&(n=n.filter(d=>d.parent===t.parent)),t.top_level===!0&&(n=n.filter(d=>d.parent===null)),t.under!==void 0){const d=new Set(xe(t.under));n=n.filter(c=>d.has(c.node_id))}t.hanging===!0&&(n=n.filter(d=>d.parent!==null&&R(d.parent)===null));const r=t.include==="activity"?Ze(n.map(d=>d.node_id)):null,o=n.length>0?Ae():new Set;return{status:200,body:n.map(d=>{const c=Yn(d,d.profile_id!==null&&o.has(d.profile_id));if(r!==null){const i=ue(d.node_id);c.activity={latest_report:i[0]??null,final_report:d.final_report===null?null:i.find(l=>l.name===d.final_report)??null,pending_human_count:r[d.node_id]??0}}return c})}}a(kt,"handleList");async function St(e){const t=e.params.id,n=await Zn(e,t,{method:"GET",path:`/v1/nodes/${encodeURIComponent(t)}`,query:qn(e)});if(n.kind==="unknown")throw nt(t,n.unreachable);if(n.kind==="remote"){if(n.status>=400)throw et(n.status,n.body);return{status:n.status,body:tt(n.body,n.machine)}}return w(t),{status:200,body:g(t)}}a(St,"handleDetail");async function Rt(e){const t=e.params.id;w(t);const n=await ln(t);return{status:200,body:{node_id:t,snapshot:{messages:n.snapshot.messages,...n.snapshot.messageIds===void 0?{}:{messageIds:n.snapshot.messageIds},messageVisibility:n.snapshot.messageVisibility,turnVisibility:n.snapshot.turnVisibility,stats:n.snapshot.stats,state:n.snapshot.state,display:n.snapshot.display,...n.snapshot.queued===void 0?{}:{queued:n.snapshot.queued},...n.snapshot.toolGroupSummaries===void 0?{}:{toolGroupSummaries:n.snapshot.toolGroupSummaries},...n.snapshot.turnReplies===void 0?{}:{turnReplies:n.snapshot.turnReplies},...n.snapshot.workingActivity===void 0?{}:{workingActivity:n.snapshot.workingActivity}},commands:n.commands.map(o=>({name:o.name,description:o.description,source:o.source})),captured_at:B()}}}a(Rt,"handleSnapshot");async function Ot(e){const t=e.params.id,n=fn(t);if(n===null)throw P(`unknown node: ${t}`,{received:t});return{status:200,body:n}}a(Ot,"handleSubject");function It(e){for(const[s]of e.query)if(s!=="cursor"&&s!=="limit")throw h(`unknown messages query parameter: ${s}`);const t=e.query.getAll("cursor"),n=e.query.getAll("limit");if(t.length>1)throw h("cursor must appear at most once");if(n.length>1)throw h("limit must appear at most once");if(n.length===0)return t.length===0?{}:{cursor:t[0]};const r=n[0],o=Number(r);if(!Number.isSafeInteger(o)||r==="")throw h("limit must be an integer from 1 through 500");return t.length===0?{limit:o}:{cursor:t[0],limit:o}}a(It,"parseMessagesQuery");async function At(e){const t=e.params.id,n=await sn(t,It(e));return{status:200,body:{node_id:t,messages:n.messages,...n.messageIds===void 0?{}:{message_ids:n.messageIds},message_visibility:n.messageVisibility,next_cursor:n.nextCursor,captured_at:B()}}}a(At,"handleMessages");function $t(e){const t=e.params.id;w(t);const n=an(t);return{status:200,body:{node_id:t,session_file:n.sessionFile,session_jsonl:n.sessionJsonl,system_prompt:n.systemPrompt,captured_at:B()}}}a($t,"handleSession");async function Ct(e){const t=e.params.id;return w(t),{status:200,body:{node_id:t,markdown:await cn(t)}}}a(Ct,"handleTranscript");function Nt(e){const t=e.params.id;w(t);const n=a((o,s)=>({node_id:o,label:R(o)?.name??o,dir:se(o),self:s,files:ft(o)}),"root"),r=[n(t,!0),...de(t).map(o=>n(o.node_id,!1))].filter(o=>Tt(o.node_id,o.dir)||o.files>0);return{status:200,body:{node_id:t,roots:r}}}a(Nt,"handleContext");function Tt(e,t){try{return tn(V(e),t)}catch(n){if(rn(n))return!1;throw n}}a(Tt,"hasDir");async function Et(e){const t=w(e.params.id),n=e.principal.class==="broker"?e.principal.scopes:e.ceiling,r=n===null?void 0:t.scopes==null?[...n]:[...new Set([...t.scopes.filter(s=>I(n,s)),...n.filter(s=>I(t.scopes,s))])],o=await Pe(t.node_id,r,e.principal.class==="broker"?e.principal.node:void 0);return{status:201,body:g(o.node.node_id)}}a(Et,"handleFork");const Pt=1e4;async function Dt(e){const t=e.params.id;w(t);const n=e.body,r=n?.resume??!0,o=n?.reopen??!1;yn(t,o,e.principal.class==="broker"?e.principal.node:null);const s=!r&&n?.cron_id!==void 0?pe(n.cron_id):null,d=await Te(t,{resume:r,capacity:"refuse",...s!==null?{wakeReason:xn(s,"schedule_revive")}:{}});d.outcome!=="already-live"&&Ke(t,{failures:0,notBefore:null});const c=R(t)?.status??"active",i=(d.launch??E().get(t)?.handle)?.exited,l=d.outcome!=="frozen"&&await le(t,i,Pt);return{status:200,body:Hn(t,d,c,l)}}a(Dt,"handleRevive");async function Lt(e){const t=e.params.id;return{status:200,body:{newNodeId:(await wn(t))?.newNodeId??null}}}a(Lt,"handleRelaunchRoot");function Mt(e){const t=e.params.id;w(t);const n=e.body;if(n?.caller_pid!==void 0&&(!Number.isInteger(n.caller_pid)||n.caller_pid<=0))throw h("caller_pid must be a positive integer when provided");const r=zn(),o=Ne(t,{rootEvent:n?.finish===!0?"finish":"cancel",callerPid:n?.caller_pid,...e.principal.class==="broker"?{closer:e.principal.node}:{},...r===null?{}:{registerDetached:r.registerDetached}});return{status:200,body:Un(o)}}a(Mt,"handleClose");function jt(e){if(!Qn(e.principal))throw new u(403,"permission_error","only the person marks a conversation read");const t=e.params.id;return w(t),ze(t),{status:204}}a(jt,"handleMarkRead");async function Gt(e){const t=e.params.id;w(t);const n=await hn(t);return{status:200,body:{recycled:n.recycled,finalized:n.finalized,new_root:n.newRoot,delivered:n.delivered}}}a(Gt,"handleRecycle");async function Ft(e){const t=e.params.id;return w(t),await x(t,{lifecycle:"terminal"}),$(e,t,["lifecycle"]),{status:200,body:g(t)}}a(Ft,"handleDemote");async function Bt(e,t={}){const n=e.params.id,r=w(n),o=e.body??{};o.kind!==void 0&&await z(o.kind,r.cwd,r.profile_id??null),o.model!==void 0&&await _e(r,{kind:o.kind??r.kind,mode:"orchestrator",lifecycle:o.resident===!0?"resident":r.lifecycle,model:o.model},t.modelRegistry);const s=await pn(n,{...o.kind!==void 0?{kind:o.kind}:{},...o.resident===!0?{resident:!0}:{},...o.model!==void 0?{model:o.model}:{}});$(e,n,["mode",...o.kind!==void 0?["kind"]:[],...o.resident===!0?["lifecycle"]:[],...o.model!==void 0?["model"]:[]]);const d=J(s.meta,Y(n));return d.roadmap_written=s.roadmapWritten,d.roadmap_ref=s.roadmapRef,d.goal_path=s.goalPath,{status:200,body:d}}a(Bt,"handlePromote");async function Kt(e,t={}){const n=e.params.id,r=w(n),o=e.body??{};o.kind!==void 0&&await z(o.kind,r.cwd,r.profile_id??null),o.model!==void 0&&await _e(r,{kind:o.kind??r.kind,mode:o.promote===!0?"orchestrator":r.mode,lifecycle:r.lifecycle,model:o.model},t.modelRegistry);const s=o.note??"Refresh: continue against your roadmap or goal.";_n(n,s);const d=await mn(n,{...o.promote===!0?{promote:!0}:{},...o.kind!==void 0?{kind:o.kind}:{},...o.model!==void 0?{model:o.model}:{}});return{status:200,body:J(d.meta,Y(n))}}a(Kt,"handleYield");function Wt(e){const t=e.params.id;w(t);const n=e.body;if(n===void 0||typeof n.controller!="string"||n.controller.trim()==="")throw h("a wait requires a controller node id");const r=R(n.controller);if(r===null)throw P(`unknown controller: ${n.controller}`,{received:n.controller});if(r.status!=="active"&&r.status!=="idle")throw new u(422,"unprocessable",`controller ${n.controller} is ${r.status}`);return Ve(t,n.controller),{status:200,body:g(t)}}a(Wt,"handleWait");async function Vt(e){const t=e.principal,n=await un(t.class==="broker"?o=>o.grantee===t.app&&U(t,o.node_id,o.grantee):void 0);return{status:200,body:{revived:[...n.revived],failed:[...n.failed],frozen:[...n.frozen]}}}a(Vt,"handleReviveAll");const xt=new Set(["interrupt","steer","after-turn","next-turn"]);function zt(e){if(e===null)return null;const t=a(r=>{throw new y({error:"invalid_listen",message:r,received:e,field:"listen",next:'Send an object mapping levels "1".."10" to interrupt|steer|after-turn|next-turn (e.g. {"3":"next-turn"}), or null to reset to the global default.'})},"bad");if(typeof e!="object"||Array.isArray(e))return t("listen must be an object or null");const n=Object.entries(e);if(n.length===0)return t("listen must name at least one level");for(const[r,o]of n)/^(?:[1-9]|10)$/.test(r)||t(`listen level must be 1..10: ${r}`),(typeof o!="string"||!xt.has(o))&&t(`listen delivery must be interrupt|steer|after-turn|next-turn: ${String(o)}`);return JSON.stringify(Object.fromEntries(n))}a(zt,"listenJson");function $(e,t,n,r){n.length===0&&r===void 0||W(o=>{if(r?.(o),n.length===0)return;const s=Je(o,t),d=He(o,e),c=d.class==="person"?d.person:d.class==="app"?d.app:d.node;Ue(o,c,s),Ye(o,{event:"configured",object:s,actor:c,attrs:{changed:n.join(",")}})})}a($,"recordConfigured");function ve(e){const t=E().get(e);if(t===void 0)return!1;try{return process.kill(t.pid,"SIGTERM"),!0}catch{return!1}}a(ve,"restartLiveBroker");function ke(e,t){try{return q(e)===q(t)}catch{return!1}}a(ke,"sameDirectory");async function Jt(e,t={}){const n=e.params.id,r=e.body;let o;if(r===void 0)o={};else if(typeof r=="object"&&r!==null&&!Array.isArray(r))o=r;else throw new y({error:"cwd_invalid",message:"cwd repair requires a non-null object with a non-empty cwd string",received:r,field:"cwd",next:"Send a config patch with a non-empty absolute cwd string."});if(o.profile!==void 0){if(typeof o.profile!="string"||o.profile==="")throw new y({error:"profile_invalid",message:"profile must be a non-empty profile id or name",received:o.profile,field:"profile",next:"Send a profile id or exact name."});if(Object.keys(o).some(m=>m!=="profile"&&m!=="restart"&&o[m]!==void 0))throw new y({error:"profile_not_exclusive",message:"profile can only be combined with restart",field:"profile",next:"Send profile (and optionally restart) alone."});const i=w(n),l=Kn(e,o.profile);Wn(l.profileId);const p=Vn(l.profileId).manifest.grantee??"app:terminal";if(p!==(i.grantee??"app:terminal"))throw new y({error:"profile_grantee_mismatch",message:`profile ${l.profileId} belongs to ${p}, node ${n} to ${i.grantee}`,received:o.profile,field:"profile",next:"Pick a profile owned by the same app as the node."});return(i.profile_id??null)!==l.profileId&&(Gn(i.kind)?(await z(i.kind,i.cwd,l.profileId),await x(n,{profileId:l.profileId})):C(n,{profile_id:l.profileId}),$(e,n,["profile"])),o.restart===!0&&ve(n),{status:200,body:g(n)}}if(Object.prototype.hasOwnProperty.call(o,"cwd")){const i=o.cwd;if(typeof i!="string"||i==="")throw new y({error:"cwd_invalid",message:"cwd must be a non-empty string",received:i,field:"cwd",next:"Send a non-empty absolute cwd string."});if(Object.keys(o).some(f=>f!=="cwd"&&f!=="keep_session"&&f!=="restart"&&o[f]!==void 0))throw new y({error:"cwd_not_exclusive",message:"cwd repair cannot be combined with another config field",received:o,field:"cwd",next:"Send cwd as the only config field."});const l=w(n),p=N(l.cwd);if(o.keep_session===!0||p&&F(i)&&N(i)&&ke(l.cwd,i)){if(!F(i))throw new y({error:"cwd_not_absolute",message:`cwd must be an absolute path: ${i}`,received:i,field:"cwd",next:"Send an absolute path."});const f=T(i);let b=!1;try{b=ee(f).isDirectory()}catch{}if(!b)throw new y({error:"cwd_missing",message:`cwd is not an existing directory: ${f}`,received:i,field:"cwd",next:"Send an existing absolute directory."});if(p&&!ke(l.cwd,f))throw new y({error:"cwd_not_same_directory",message:`${f} is not the same directory as the recorded ${l.cwd}; --keep-session only relocates an alias`,received:i,field:"cwd",next:"Drop --keep-session to repair a gone cwd, or pass the real path of the recorded directory."});if(l.managed_worktree?.state==="open")throw new y({error:"cwd_node_managed_worktree_open",message:`${n} owns an open managed worktree and cannot relocate its cwd`,received:i,field:"cwd",next:"Reconcile the managed worktree first."});const v=l.cwd;return v!==f&&(W(_=>{C(n,{cwd:f}),Z(_,"UPDATE crons SET cwd = ? WHERE anchor_node = ? AND cwd = ?").run(f,n,v),Qe(n,v,f)}),$(e,n,["cwd"])),o.restart===!0&&ve(n),{status:200,body:g(n)}}if(E().has(n)){const f=`${n} cannot repair its cwd while its broker is live`;throw new u(409,"cwd_node_live",f,{error:"cwd_node_live",message:f,received:i,field:"cwd",next:"Let the node become dormant, then retry the cwd repair."})}if(l.fork_from!=null)throw new y({error:"cwd_node_forked",message:`${n} is a fork and cannot repair its cwd; recorded cwd: ${l.cwd}`,received:i,field:"cwd",next:`Restore ${l.cwd}, then revive the fork normally.`});if(l.managed_worktree?.state==="open")throw new y({error:"cwd_node_managed_worktree_open",message:`${n} owns an open managed worktree and cannot repair its cwd`,received:i,field:"cwd",next:"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon flow."});if(N(l.cwd))throw new y({error:"cwd_still_present",message:`${n} recorded cwd still exists: ${l.cwd}; cwd repair is not a relocation command`,received:i,field:"cwd",next:"This command only repairs a node whose recorded cwd is gone."});if(!F(i))throw new y({error:"cwd_not_absolute",message:`cwd must be an absolute path: ${i}`,received:i,field:"cwd",next:"Send an absolute path."});const m=T(i);if(!N(m))throw new y({error:"cwd_missing",message:`cwd does not exist: ${m}`,received:i,field:"cwd",next:"Create the replacement directory or send an existing absolute directory."});try{if(!ee(m).isDirectory())throw new y({error:"cwd_not_a_directory",message:`cwd is not a directory: ${m}`,received:i,field:"cwd",next:"Send an existing absolute directory."})}catch(f){throw f instanceof y?f:new y({error:"cwd_missing",message:`cwd does not exist: ${m}`,received:i,field:"cwd",next:"Create the replacement directory or send an existing absolute directory."})}return W(()=>{C(n,{cwd:m}),Xe(n)}),$(e,n,["cwd"]),{status:200,body:g(n)}}let s=w(n);const d=o.listen===void 0?void 0:zt(o.listen);if(o.scopes!==void 0){const i=me(o.scopes),l=s.grantee==="app:terminal"?dt():s.grant_id?rt(s.grant_id):null,p=s.grantee==="app:terminal"&&!lt("user").runtime?.issuer;if(!i.every(m=>I(s.scopes,m)&&(l?I(l.resolved,m):p)))throw new y({error:"unknown_scope",message:"scopes may only narrow the node's current effective set",received:i,field:"scopes",next:"Send only scopes the node currently holds."});if(i.includes("crtr:act")&&!I(i,"crtr:llm"))throw new u(400,"invalid_scope","crtr:act requires crtr:llm");s=C(n,{scopes:i})}if(o.situational_context!==void 0&&$e(n,Ce(o.situational_context)),o.model!==void 0){const i=(await ae(s.cwd,s.profile_id??null)).modelLadders,l=Rn(o.model,i),p=st(s.grant_id,o.model,l===void 0),m=p?{family:at,strength:p.strength}:l,f=p?p.model:On(o.model,i);await K({kind:"concrete",spec:f},{cwd:s.cwd,profileId:s.profile_id??null},t.modelRegistry);const b=E();let v=!1;if(b.isClaimed(n)&&await le(n,b.get(n)?.handle.exited)&&(v=await $n(n,f,m).then(()=>!0,_=>{if(_ instanceof Cn)return!1;throw _})),!v){if(b.isClaimed(n)){const _=`${n}'s broker did not accept the model switch; the model was not changed`;throw new u(409,"broker_not_ready",_,{error:"broker_not_ready",message:_,received:o.model,field:"model",next:"Check the node with `crtr node show`, then retry the model change."})}if(!f.includes("/"))throw h(`model must resolve to a provider/id spec: ${o.model}`);An(n,f,m)}s=w(n)}if(o.kind!==void 0||o.mode!==void 0||o.lifecycle!==void 0){o.kind!==void 0&&await Re(o.kind);const i=o.kind??s.kind,l=o.mode??s.mode,p=o.lifecycle??s.lifecycle;let m;l==="orchestrator"&&!vn(n)&&(m=a(()=>{const f=gn(n);bn(n,f!==null&&f.trim()!==""?{goal:f.trim()}:{})},"beforePersist")),s=await x(n,{...o.kind!==void 0?{kind:i}:{},...o.mode!==void 0?{mode:l}:{},...o.lifecycle!==void 0?{lifecycle:p}:{}},m)}o.name!==void 0&&(s=C(n,{name:o.name}),s.window&&kn(s.window,Bn(s)));const c=["scopes","situational_context","model","kind","mode","lifecycle","name","listen"].filter(i=>o[i]!==void 0);return $(e,n,c,d===void 0?void 0:i=>{Z(i,"UPDATE objects SET listen_json = ? WHERE id = ? AND type = 'node'").run(d,n)}),{status:200,body:g(n)}}a(Jt,"handleConfig");async function Se(){return(await ae(process.cwd(),Ie()||null)).kinds}a(Se,"daemonKinds");async function Re(e){const t=Object.keys(await Se());if(!t.includes(e))throw h(`unknown kind: ${e}`,{received:e,next:`Valid kinds: ${t.sort().join(", ")}.`})}a(Re,"assertKindServer");async function Ut(){const e=await Se();return{status:200,body:{kinds:Object.keys(e).sort().map(n=>({kind:n,when_to_use:e[n]?.whenToUse??null,available_to:e[n]?.availableTo??null}))}}}a(Ut,"handleListKinds");const mr=[{method:"POST",pattern:"/v1/nodes",scope:"crtr:act",handler:gt},{method:"GET",pattern:"/v1/nodes",handler:kt},{method:"GET",pattern:"/v1/kinds",handler:a(()=>Ut(),"handler")},{method:"POST",pattern:"/v1/nodes/revive-all",scope:"crtr:act",handler:Vt},{method:"GET",pattern:"/v1/nodes/:id",handler:St},{method:"GET",pattern:"/v1/nodes/:id/snapshot",handler:Rt},{method:"GET",pattern:"/v1/nodes/:id/subject",handler:Ot},{method:"GET",pattern:"/v1/nodes/:id/messages",handler:At},{method:"GET",pattern:"/v1/nodes/:id/session",handler:$t},{method:"GET",pattern:"/v1/nodes/:id/transcript",handler:Ct},{method:"GET",pattern:"/v1/nodes/:id/context",handler:Nt},{method:"POST",pattern:"/v1/nodes/:id/fork",scope:"crtr:act",handler:Et},{method:"POST",pattern:"/v1/nodes/:id/revive",scope:"crtr:act",handler:Dt},{method:"POST",pattern:"/v1/nodes/:id/relaunch-root",scope:"crtr:act",handler:Lt},{method:"POST",pattern:"/v1/nodes/:id/close",handler:Mt},{method:"POST",pattern:"/v1/nodes/:id/read",handler:jt},{method:"POST",pattern:"/v1/nodes/:id/recycle",scope:"crtr:act",handler:Gt},{method:"POST",pattern:"/v1/nodes/:id/demote",scope:"crtr:act",handler:Ft},{method:"POST",pattern:"/v1/nodes/:id/promote",scope:"crtr:act",handler:Bt},{method:"POST",pattern:"/v1/nodes/:id/yield",scope:"crtr:act",handler:Kt},{method:"POST",pattern:"/v1/nodes/:id/wait",handler:Wt},{method:"PATCH",pattern:"/v1/nodes/:id/config",handler:Jt}];export{Jt as handleConfig,gt as handleCreate,Et as handleFork,kt as handleList,Bt as handlePromote,Kt as handleYield,mr as nodeRoutes,yr as resolveCreateKind};
@@ -1,19 +1,21 @@
1
- var g=Object.defineProperty;var l=(s,e)=>g(s,"name",{value:e,configurable:!0});import{getNode as v,getRow as k}from"../core/canvas/index.js";import{revokeExitedNodeToken as $}from"../core/canvas/node-tokens.js";import{isProfilePaused as T}from"../core/profiles/manifest.js";import{fullName as p}from"../core/canvas/labels.js";import{isSafeNodeId as o}from"../core/canvas/paths.js";import{hasIdleReleaseIntent as R,transition as _}from"../core/runtime/lifecycle.js";import{publishNotice as E}from"../core/runtime/close.js";import{readInterruptedTurn as P,readTransportPoisonVerdict as N}from"../core/canvas/recovery.js";import{hasPendingWake as x}from"./reconcilers/live-obligation.js";import{clearRespawnPolicy as h,deferRespawnWhileOffline as C,MAX_RESPAWN_ATTEMPTS as F,OFFLINE_RESPAWN_RETRY_MS as M,recordRespawnPolicy as S,respawnBackoffMs as O}from"./reconcilers/node-lifecycle/respawn-policy.js";import{probeOnline as z}from"../core/runtime/connectivity.js";import{reviveNode as A}from"../core/runtime/revive.js";import{pushUrgent as m}from"../core/feed/feed.js";import{emitEvent as c}from"../core/events/emit.js";import{brokerThresholdsForDaemon as B}from"../core/runtime/fleet.js";import{isRecoveryHeld as D}from"../core/runtime/recovery-hold.js";function w(s,e){const r=e===void 0?"The daemon did not observe an exit status.":`The observed exit status was code ${e.code??"null"}, signal ${e.signal??"null"}.`;return`\u26A0 Spawn failed \u2014 \`${s.name}\` (${s.kind}) never started.
1
+ var g=Object.defineProperty;var l=(s,e)=>g(s,"name",{value:e,configurable:!0});import{getNode as v,getRow as k}from"../core/canvas/index.js";import{revokeExitedNodeToken as $}from"../core/canvas/node-tokens.js";import{isProfilePaused as T}from"../core/profiles/manifest.js";import{fullName as p}from"../core/canvas/labels.js";import{isSafeNodeId as o}from"../core/canvas/paths.js";import{hasIdleReleaseIntent as R,transition as _}from"../core/runtime/lifecycle.js";import{publishNotice as E}from"../core/runtime/close.js";import{readFault as P,readInterruptedTurn as N,readTransportPoisonVerdict as x}from"../core/canvas/recovery.js";import{hasPendingWake as C}from"./reconcilers/live-obligation.js";import{clearRespawnPolicy as h,deferRespawnWhileOffline as F,MAX_RESPAWN_ATTEMPTS as M,OFFLINE_RESPAWN_RETRY_MS as S,recordRespawnPolicy as O,respawnBackoffMs as z}from"./reconcilers/node-lifecycle/respawn-policy.js";import{probeOnline as A}from"../core/runtime/connectivity.js";import{reviveNode as B}from"../core/runtime/revive.js";import{pushUrgent as m}from"../core/feed/feed.js";import{emitEvent as c}from"../core/events/emit.js";import{brokerThresholdsForDaemon as D}from"../core/runtime/fleet.js";import{isRecoveryHeld as L}from"../core/runtime/recovery-hold.js";function w(s,e){const r=P(s.node_id),t=r===null?"":`The broker reported a ${r.kind} fault on ${r.link} (${r.op}) before it exited: ${r.message}
2
2
 
3
- Its pi vehicle exited before the session came up (no pi_session_id was ever recorded), so the node produced no output. ${r}
3
+ `,i=e===void 0?"The daemon did not observe an exit status.":`The observed exit status was code ${e.code??"null"}, signal ${e.signal??"null"}.`;return`\u26A0 Spawn failed \u2014 \`${s.name}\` (${s.kind}) never started.
4
4
 
5
- Inspect canonical event diagnostics with \`crtr sys logs --node ${s.node_id}\`.
5
+ Its pi vehicle exited before the session came up (no pi_session_id was ever recorded), so the node produced no output. ${i}
6
+
7
+ `+t+`Inspect canonical event diagnostics with \`crtr sys logs --node ${s.node_id}\`.
6
8
 
7
9
  If the work still needs doing, re-spawn it.`}l(w,"bootFailureBody");async function y(s,e){await m(s.node_id,w(s,e),{from:s.node_id})}l(y,"surfaceBootFailure");function b(s){return`\u26A0 Out of memory \u2014 \`${s.name}\` (${s.kind}) was killed because its app's processes together passed the app's memory limit.
8
10
 
9
- Every process of the run was stopped. The daemon does not restart it, because it would reach the same limit. Reduce what the run holds in memory, or ask the person to raise the app's limit (\`crtr sys limits set --app ${s.grantee??"app:terminal"} --memory <size>\`), then revive the node.`}l(b,"outOfMemoryBody");async function L(s){const e=N(s.node_id),r=e===null?"":`
11
+ Every process of the run was stopped. The daemon does not restart it, because it would reach the same limit. Reduce what the run holds in memory, or ask the person to raise the app's limit (\`crtr sys limits set --app ${s.grantee??"app:terminal"} --memory <size>\`), then revive the node.`}l(b,"outOfMemoryBody");async function W(s){const e=x(s.node_id),r=e===null?"":`
10
12
 
11
13
  Provider rotation observed ${e.consecutivePasses} consecutive route-queue passes with no HTTP response (tried ${e.routes.join(", ")}). Check network and provider transport before reviving.`,t=`\u26A0 Crash loop \u2014 \`${s.name}\` (${s.kind}) kept dying without settling a turn.
12
14
 
13
15
  Its session started successfully, but six consecutive relaunches made no progress, so the daemon stopped retrying.`+r+`
14
16
 
15
- Fix the crash cause, then revive the node if the work still needs doing.`;await m(s.node_id,t,{from:s.node_id})}l(L,"surfaceCrashLoop");async function W(s,e){const r=e instanceof Error?e.message:String(e),t=`\u26A0 Recovery failed \u2014 \`${s.name}\` (${s.kind}) could not be relaunched.
17
+ Fix the crash cause, then revive the node if the work still needs doing.`;await m(s.node_id,t,{from:s.node_id})}l(W,"surfaceCrashLoop");async function q(s,e){const r=e instanceof Error?e.message:String(e),t=`\u26A0 Recovery failed \u2014 \`${s.name}\` (${s.kind}) could not be relaunched.
16
18
 
17
19
  The daemon refused this recovery before starting a broker: ${r}
18
20
 
19
- The node is now dead. Fix the recorded launch configuration, then revive it if the work still needs doing.`;await m(s.node_id,t,{from:s.node_id})}l(W,"surfaceRecoveryFailure");function q(s){return s?.cycle_pending!==!0}l(q,"retryResumeMode");function G(s,e,r=!0){return s===null||s.status==="done"||s.status==="canceled"||s.status==="dead"?"forget":s.intent==="refresh"?"respawn-fresh":R(s)?"dormant":s.lifecycle==="resident"&&!e&&!r&&s.pi_session_id!=null?"release-idle":s.pi_session_id!=null?q(s)?"respawn-resume":"respawn-cycle":e||s.fork_from!=null?"respawn-fresh":"boot-failure"}l(G,"classifyDeadNode");const H=3e4;class oe{static{l(this,"DaemonFleet")}#n;#o;#s;#l;#e=new Map;#r=new Set;#a=new Set;#t=new Map;#c=Number.NEGATIVE_INFINITY;constructor(e){this.#n=e.epoch,this.#o=e.enqueue,this.#s=e.now??Date.now,this.#l=e.probeOnline??(()=>z())}epoch(){return this.#n}register(e,r){if(this.#e.has(e))throw new Error(`fleet: ${e} already has a live broker entry \u2014 refusing a second launch (programming error)`);if(r.pid===null)throw new Error(`fleet: refusing to register ${e} with no pid`);this.#r.delete(e),this.#t.delete(e),this.#e.set(e,{pid:r.pid,handle:r})}has(e){return this.#e.has(e)}isClaimed(e){return this.#e.has(e)||this.#r.has(e)}get(e){return this.#e.get(e)}size(){return this.#e.size}occupancy(){return this.#e.size+this.#r.size}reserveSlot(e){if(this.#r.has(e)||this.#e.has(e))return!0;const{automaticReviveCap:r}=B();if(this.occupancy()<r)return this.#r.add(e),!0;const t=this.#s();return t-this.#c>=H&&(this.#c=t,c({level:"warn",event:"broker.capacity.revive_deferred",fields:{live_count:this.occupancy(),hard_cap:r}})),!1}releaseReservation(e){this.#r.delete(e)}entries(){return this.#e.entries()}interruptForProfilePause(e,r=this.#e.get(e)?.pid){if(r!==void 0){this.#a.add(e);try{process.kill(r,"SIGTERM")}catch{}}}hasProfilePauseInterruption(e){return this.#a.has(e)}forget(e){this.#e.delete(e),this.#r.delete(e),this.#t.delete(e)}onChildExit(e,r,t){t&&$(t),this.#e.delete(e),this.#o(()=>{const i=v(e),a=i?.launched_at==null?Number.NaN:Date.parse(i.launched_at),n=Number.isFinite(a)?Math.max(0,this.#s()-a):null,d=i?.respawn_failures??0;i?.status==="done"||i?.status==="canceled"||i?.status==="dead"?this.#t.delete(e):this.#t.set(e,r),this.#u(e,r,n,d)})}deliverExit(e,r){this.onChildExit(e,r)}async applyDeadRowPolicy(e){const r=this.#t.get(e);try{await this.#f(e,r)}finally{this.#t.get(e)===r&&this.#t.delete(e)}}async terminalizeIfStillNonTerminal(e,r,t,i,a){const n=v(e);return n===null||n.status==="done"||n.status==="canceled"||n.status==="dead"?!1:(await this.#i(e,n,{event:r,reason:i,detail:a,wake:`Child crashed \u2014 ${p(n)} (${e}) could not be relaunched and is now dead. It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,surface:l(()=>W(n,t),"surface")}),!0)}dispose(){}#u(e,r,t,i){c({level:"info",event:"broker.exit.observed",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e},code:r.code,signal:r.signal,uptime_ms:t,consecutive_failures:i}})}async#f(e,r){return this.#h(e,r)}async#h(e,r){const t=v(e);if(t!==null&&T(t.profile_id))return h(e),"forget";if(this.#a.delete(e))return h(e),"forget";if(D(e))return"dormant";const i=P(e)?.interrupted===!0;if(r?.oomKilled===!0&&t!==null&&t.status!=="done"&&t.status!=="canceled"&&t.status!=="dead")return h(e),await this.#i(e,t,{event:"broker.out_of_memory",reason:"out_of_memory",detail:{schema:"crtr.node-outcome-detail/v1",message:b(t)},wake:`Child out of memory \u2014 ${p(t)} (${e}) used more memory than its app is allowed and is now dead. It is not restarted automatically. Revive it with \`crtr node lifecycle revive ${e}\` once the work needs less memory or the cap is raised (\`crtr sys limits set\`).`,surface:l(async()=>{await m(e,b(t),{from:e})},"surface")}),"forget";const a=G(t,i,x(e));switch(a){case"forget":h(e);break;case"dormant":h(e);break;case"release-idle":h(e),_(e,"release"),c({level:"info",event:"broker.recovery.released",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e}}});break;case"boot-failure":await this.#i(e,t,{event:"broker.boot.failed",reason:"boot_failed",detail:{schema:"crtr.node-outcome-detail/v1",message:w(t,r)},wake:`Child crashed \u2014 ${p(t)} (${e}) exited before its session ever came up and is now dead. It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,surface:l(()=>y(t,r),"surface")});break;case"respawn-fresh":case"respawn-cycle":case"respawn-resume":await this.#d(e,t,a,i);break}return a}async#d(e,r,t,i){const a=k(e);if(a===null)return;const n=a.respawn_failures??0;if(!await this.#l()){C(e,n,this.#s()),c({level:"info",event:"broker.respawn.offline_deferred",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e},action:t,retry_in_ms:M,consecutive_failures:n}});return}const d=a.respawn_not_before==null,f=d?S(a,this.#s()):{failures:n,notBefore:a.respawn_not_before,exhausted:n>=F};if(f.exhausted){const u=`Child crashed \u2014 ${p(r)} (${e}) kept dying without settling a turn (${f.failures} consecutive launches) and is now dead. It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`;await this.#i(e,r,{event:"broker.respawn.exhausted",reason:"crash_looped",detail:{schema:"crtr.node-outcome-detail/v1",message:u,respawn_failures:f.failures},wake:u,...r.pi_session_id!=null?{surface:l(()=>L(r),"surface")}:{}});return}if(d&&f.notBefore!==null){c({level:"info",event:"broker.respawn.deferred",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e},action:t,backoff_ms:O(f.failures),consecutive_failures:f.failures}});return}c({level:"info",event:"broker.respawn.started",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e},action:t,consecutive_failures:f.failures}});try{await A(e,{capacity:"freeze",resume:t==="respawn-resume",wakeReason:t==="respawn-resume"&&i?"runtime-restart-abort":void 0,recovery:!0})}catch(u){c({level:"error",event:"broker.respawn.failed",...o(e)?{node_id:e}:{},error:u,fields:{...o(e)?{}:{affected_node_id:e},action:t}}),await this.terminalizeIfStillNonTerminal(e,"broker.respawn.refused",u,"launch_failed",{schema:"crtr.node-outcome-detail/v1",message:u instanceof Error?u.message:String(u)})}}async#i(e,r,t){c({level:"error",event:t.event,...o(e)?{node_id:e}:{fields:{affected_node_id:e}}}),_(e,"crash",{reason:t.reason,outcome:{detail:t.detail}});try{await(t.surface??(()=>y(r)))()}catch(i){c({level:"error",event:"broker.boot_failure_surface.failed",...o(e)?{node_id:e}:{fields:{affected_node_id:e}},error:i})}E(e,"notice.crash",t.wake,{reason:"child-crashed",child:e})}}export{oe as DaemonFleet,G as classifyDeadNode,q as retryResumeMode,y as surfaceBootFailure};
21
+ The node is now dead. Fix the recorded launch configuration, then revive it if the work still needs doing.`;await m(s.node_id,t,{from:s.node_id})}l(q,"surfaceRecoveryFailure");function G(s){return s?.cycle_pending!==!0}l(G,"retryResumeMode");function H(s,e,r=!0){return s===null||s.status==="done"||s.status==="canceled"||s.status==="dead"?"forget":s.intent==="refresh"?"respawn-fresh":R(s)?"dormant":s.lifecycle==="resident"&&!e&&!r&&s.pi_session_id!=null?"release-idle":s.pi_session_id!=null?G(s)?"respawn-resume":"respawn-cycle":e||s.fork_from!=null?"respawn-fresh":"boot-failure"}l(H,"classifyDeadNode");const Y=3e4;class le{static{l(this,"DaemonFleet")}#n;#o;#s;#l;#e=new Map;#r=new Set;#a=new Set;#t=new Map;#c=Number.NEGATIVE_INFINITY;constructor(e){this.#n=e.epoch,this.#o=e.enqueue,this.#s=e.now??Date.now,this.#l=e.probeOnline??(()=>A())}epoch(){return this.#n}register(e,r){if(this.#e.has(e))throw new Error(`fleet: ${e} already has a live broker entry \u2014 refusing a second launch (programming error)`);if(r.pid===null)throw new Error(`fleet: refusing to register ${e} with no pid`);this.#r.delete(e),this.#t.delete(e),this.#e.set(e,{pid:r.pid,handle:r})}has(e){return this.#e.has(e)}isClaimed(e){return this.#e.has(e)||this.#r.has(e)}get(e){return this.#e.get(e)}size(){return this.#e.size}occupancy(){return this.#e.size+this.#r.size}reserveSlot(e){if(this.#r.has(e)||this.#e.has(e))return!0;const{automaticReviveCap:r}=D();if(this.occupancy()<r)return this.#r.add(e),!0;const t=this.#s();return t-this.#c>=Y&&(this.#c=t,c({level:"warn",event:"broker.capacity.revive_deferred",fields:{live_count:this.occupancy(),hard_cap:r}})),!1}releaseReservation(e){this.#r.delete(e)}entries(){return this.#e.entries()}interruptForProfilePause(e,r=this.#e.get(e)?.pid){if(r!==void 0){this.#a.add(e);try{process.kill(r,"SIGTERM")}catch{}}}hasProfilePauseInterruption(e){return this.#a.has(e)}forget(e){this.#e.delete(e),this.#r.delete(e),this.#t.delete(e)}onChildExit(e,r,t){t&&$(t),this.#e.delete(e),this.#o(()=>{const i=v(e),a=i?.launched_at==null?Number.NaN:Date.parse(i.launched_at),n=Number.isFinite(a)?Math.max(0,this.#s()-a):null,d=i?.respawn_failures??0;i?.status==="done"||i?.status==="canceled"||i?.status==="dead"?this.#t.delete(e):this.#t.set(e,r),this.#u(e,r,n,d)})}deliverExit(e,r){this.onChildExit(e,r)}async applyDeadRowPolicy(e){const r=this.#t.get(e);try{await this.#f(e,r)}finally{this.#t.get(e)===r&&this.#t.delete(e)}}async terminalizeIfStillNonTerminal(e,r,t,i,a){const n=v(e);return n===null||n.status==="done"||n.status==="canceled"||n.status==="dead"?!1:(await this.#i(e,n,{event:r,reason:i,detail:a,wake:`Child crashed \u2014 ${p(n)} (${e}) could not be relaunched and is now dead. It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,surface:l(()=>q(n,t),"surface")}),!0)}dispose(){}#u(e,r,t,i){c({level:"info",event:"broker.exit.observed",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e},code:r.code,signal:r.signal,uptime_ms:t,consecutive_failures:i}})}async#f(e,r){return this.#h(e,r)}async#h(e,r){const t=v(e);if(t!==null&&T(t.profile_id))return h(e),"forget";if(this.#a.delete(e))return h(e),"forget";if(L(e))return"dormant";const i=N(e)?.interrupted===!0;if(r?.oomKilled===!0&&t!==null&&t.status!=="done"&&t.status!=="canceled"&&t.status!=="dead")return h(e),await this.#i(e,t,{event:"broker.out_of_memory",reason:"out_of_memory",detail:{schema:"crtr.node-outcome-detail/v1",message:b(t)},wake:`Child out of memory \u2014 ${p(t)} (${e}) used more memory than its app is allowed and is now dead. It is not restarted automatically. Revive it with \`crtr node lifecycle revive ${e}\` once the work needs less memory or the cap is raised (\`crtr sys limits set\`).`,surface:l(async()=>{await m(e,b(t),{from:e})},"surface")}),"forget";const a=H(t,i,C(e));switch(a){case"forget":h(e);break;case"dormant":h(e);break;case"release-idle":h(e),_(e,"release"),c({level:"info",event:"broker.recovery.released",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e}}});break;case"boot-failure":await this.#i(e,t,{event:"broker.boot.failed",reason:"boot_failed",detail:{schema:"crtr.node-outcome-detail/v1",message:w(t,r)},wake:`Child crashed \u2014 ${p(t)} (${e}) exited before its session ever came up and is now dead. It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,surface:l(()=>y(t,r),"surface")});break;case"respawn-fresh":case"respawn-cycle":case"respawn-resume":await this.#d(e,t,a,i);break}return a}async#d(e,r,t,i){const a=k(e);if(a===null)return;const n=a.respawn_failures??0;if(!await this.#l()){F(e,n,this.#s()),c({level:"info",event:"broker.respawn.offline_deferred",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e},action:t,retry_in_ms:S,consecutive_failures:n}});return}const d=a.respawn_not_before==null,f=d?O(a,this.#s()):{failures:n,notBefore:a.respawn_not_before,exhausted:n>=M};if(f.exhausted){const u=`Child crashed \u2014 ${p(r)} (${e}) kept dying without settling a turn (${f.failures} consecutive launches) and is now dead. It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`;await this.#i(e,r,{event:"broker.respawn.exhausted",reason:"crash_looped",detail:{schema:"crtr.node-outcome-detail/v1",message:u,respawn_failures:f.failures},wake:u,...r.pi_session_id!=null?{surface:l(()=>W(r),"surface")}:{}});return}if(d&&f.notBefore!==null){c({level:"info",event:"broker.respawn.deferred",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e},action:t,backoff_ms:z(f.failures),consecutive_failures:f.failures}});return}c({level:"info",event:"broker.respawn.started",...o(e)?{node_id:e}:{},fields:{...o(e)?{}:{affected_node_id:e},action:t,consecutive_failures:f.failures}});try{await B(e,{capacity:"freeze",resume:t==="respawn-resume",wakeReason:t==="respawn-resume"&&i?"runtime-restart-abort":void 0,recovery:!0})}catch(u){c({level:"error",event:"broker.respawn.failed",...o(e)?{node_id:e}:{},error:u,fields:{...o(e)?{}:{affected_node_id:e},action:t}}),await this.terminalizeIfStillNonTerminal(e,"broker.respawn.refused",u,"launch_failed",{schema:"crtr.node-outcome-detail/v1",message:u instanceof Error?u.message:String(u)})}}async#i(e,r,t){c({level:"error",event:t.event,...o(e)?{node_id:e}:{fields:{affected_node_id:e}}}),_(e,"crash",{reason:t.reason,outcome:{detail:t.detail}});try{await(t.surface??(()=>y(r)))()}catch(i){c({level:"error",event:"broker.boot_failure_surface.failed",...o(e)?{node_id:e}:{fields:{affected_node_id:e}},error:i})}E(e,"notice.crash",t.wake,{reason:"child-crashed",child:e})}}export{le as DaemonFleet,H as classifyDeadNode,G as retryResumeMode,y as surfaceBootFailure};
@@ -1,4 +1,4 @@
1
- /** Rows changed, or null when this runtime has not joined a directory. */
1
+ /** Rows changed, or null when this runtime has not joined a directory or the rewrite already ran for its owner. */
2
2
  export declare function applyJoinedOwner(): {
3
3
  owner: string;
4
4
  rows: number;
@@ -1 +1 @@
1
- var i=Object.defineProperty;var t=(r,e)=>i(r,"name",{value:e,configurable:!0});import{withCanvasWrite as m}from"../core/canvas/db.js";import{rewriteLocalNodeIdentity as p}from"../core/canvas/canvas.js";import{rewriteLocalNodeTokenIdentity as a}from"../core/canvas/node-tokens.js";import{joinedOwner as f}from"../core/directory/join.js";import{LOCAL_TERMINAL_GRANT_ID as n}from"../core/grants/local.js";import{dropLocalGrantMirror as l}from"../core/grants/mirror.js";import{rewriteLocalRunEventGrantee as c}from"../core/runs/events.js";import{rewriteLocalIdempotencyGrantee as w}from"../core/runs/operations.js";function O(){const r=f();if(r===null)return null;const e=m(o=>p(o,r,n)+a(o,r,n)+l(o)+c(o)+w(o));return{owner:r,rows:e}}t(O,"applyJoinedOwner");export{O as applyJoinedOwner};
1
+ var m=Object.defineProperty;var o=(r,e)=>m(r,"name",{value:e,configurable:!0});import{withCanvasWrite as p}from"../core/canvas/db.js";import{rewriteLocalNodeIdentity as l}from"../core/canvas/canvas.js";import{rewriteLocalNodeTokenIdentity as c}from"../core/canvas/node-tokens.js";import{joinedOwner as u}from"../core/directory/join.js";import{LOCAL_TERMINAL_GRANT_ID as t}from"../core/grants/local.js";import{dropLocalGrantMirror as E}from"../core/grants/mirror.js";import{rewriteLocalRunEventGrantee as f}from"../core/runs/events.js";import{rewriteLocalIdempotencyGrantee as L}from"../core/runs/operations.js";import{prepared as n}from"../core/sqlite-statements.js";const a="joined_owner_applied";function A(){const r=u();return r===null?null:p(e=>{if(n(e,"SELECT value FROM canvas_meta WHERE key = ?").get(a)?.value===r)return null;const i=l(e,r,t)+c(e,r,t)+E(e)+f(e)+L(e);return n(e,"INSERT INTO canvas_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(a,r),{owner:r,rows:i}})}o(A,"applyJoinedOwner");export{A as applyJoinedOwner};
@@ -1,4 +1,4 @@
1
- var Y=Object.defineProperty;var p=(s,e)=>Y(s,"name",{value:e,configurable:!0});import{existsSync as H}from"node:fs";import{LAUNCHER_SOCKET as K}from"../../launcher/spec.js";import{launcherRequest as V}from"../../launcher/client.js";import{execFile as z}from"node:child_process";import{promisify as j}from"node:util";import{getNode as T,isWarmSpare as J,subscribersOf as Q}from"../../core/canvas/index.js";import{isSafeNodeId as c}from"../../core/canvas/paths.js";import{contextDir as X}from"../../core/canvas/node-agent-paths.js";import{fullName as C}from"../../core/canvas/labels.js";import{recordedPidLiveness as D}from"../../core/canvas/pid.js";import{transition as Z}from"../../core/runtime/lifecycle.js";import{listUnnotifiedFatalFaultNodes as ee,markFaultNotified as te,readFault as O,readLiveTurn as E,recordFault as ne}from"../../core/canvas/recovery.js";import{errorClassFromFault as re}from"../../core/fault-classifier.js";import{nextFaultRetry as ie}from"../../core/runtime/fault-recovery.js";import{publishNotice as R}from"../../core/runtime/close.js";import{openDb as L,withCanvasWrite as ae}from"../../core/canvas/db.js";import{getById as se}from"../../core/graph/objects.js";import{watcherIdsOf as oe}from"../../core/graph/watches.js";import{headlessBrokerHost as S}from"../../core/runtime/host.js";import{emitEvent as u}from"../../core/events/emit.js";import{operationIdContext as F}from"../../core/events/operation-id.js";import{hasUnseenWakeMail as le,resetStaleExecutionMail as de}from"../../core/canvas/deliveries.js";import{activeBackgroundBashJobs as ce}from"../../core/bash-jobs.js";import{focusOf as ue}from"../../core/runtime/placement.js";import{hasAttachedBrokerViewers as fe}from"../api/viewer-presence.js";import{deliverLive as pe}from"../../core/runtime/interactive-deliver.js";import{BrokerUnreachableError as me}from"../../core/runtime/broker-request.js";import{formatCard as he,PARK_SUMMARY_PROMPT as _e}from"../../shared/generated-context.js";import{brokerThresholdsForDaemon as ge}from"../../core/runtime/fleet.js";import{readConfig as ke}from"../../core/config.js";import{beginPendingPark as ve,consumePendingPark as N,pendingParkIds as ye,pendingParkStartedAt as be}from"../park-pending.js";import{shouldParkAfterSummary as we}from"../park-activity.js";import{applyParkEvent as P,parkEvent as Se}from"./live-obligation.js";import{memoryDeniedRecently as Ne}from"../../core/runtime/memory-admission.js";import{envNoDaemonAutostart as Pe,envTestUnattendedParkMs as $e,parkSummaryGraceMs as Te,envUnattendedParkMs as Me}from"../../shared/env.js";const M=j(z);function Ae(s){return E(s)?.busy===!0}p(Ae,"liveBusy");function yt(s){const e=E(s)?.progressAt;if(e==null)return null;const t=Date.parse(e);return Number.isNaN(t)?null:t}p(yt,"liveTurnProgressMs");function G(s,e){return e===null?null:`${s}:${e}`}p(G,"incarnationOf");function I(s){const e=E(s.node_id),t=e?.busy===!0&&e.progressAt!==null?Date.parse(e.progressAt):Number.NaN,r=s.launched_at===null||s.launched_at===void 0?Number.NaN:Date.parse(s.launched_at);return!Number.isFinite(t)||!Number.isFinite(r)||t<r?null:t}p(I,"busySinceForIncarnation");const W=3*6e4,U=2e4,x=20*6e4,Ee=2,Fe=5*6e4;function Ie(s,e){return s===null||s.parent!=null?!1:s.lifecycle==="terminal"?!0:e==="cap_exhausted"&&s.run_id===s.node_id}p(Ie,"settlesOnFatalProviderFault");function xe(){const s=Me();if(s!==void 0)return s;const e=ke("user").lifecycle.unattendedParkMs;return Pe()?$e()??e:e}p(xe,"unattendedParkMsForDaemon");function B(){return Te()}p(B,"parkSummaryGraceMsForDaemon");function Ce(s,e,t,r){return s!==!0||e!=="refresh"||t?"leave":r===null||r<W?"pending":"kill"}p(Ce,"yieldStallVerdict");function De(s){return!s.busy||s.quietForMs===null||s.quietForMs<x?"leave":s.cpuPercent===null||s.hasDescendants!==!1&&s.cpuPercent>Ee?"pending":"wedged"}p(De,"wedgeVerdict");function q(s){const e=new Map,t=new Map;for(const r of s.split(`
2
- `)){const i=r.trim();if(i==="")continue;const n=i.split(/\s+/);if(n.length<3)continue;const l=Number(n[0]),o=Number(n[1]),a=Number(n[2]);if(!Number.isFinite(l)||!Number.isFinite(o)||!Number.isFinite(a))continue;e.set(l,a);const m=t.get(o);m===void 0?t.set(o,[l]):m.push(l)}return{byPid:e,childrenOf:t}}p(q,"parsePsTree");function Oe(s,e){const{byPid:t,childrenOf:r}=q(s);if(!t.has(e))return null;let i=0;const n=[e],l=new Set;for(;n.length>0;){const o=n.pop();if(l.has(o))continue;l.add(o),i+=t.get(o)??0;const a=r.get(o);a!==void 0&&n.push(...a)}return i}p(Oe,"sumTreeCpu");function Re(s,e){const{byPid:t,childrenOf:r}=q(s);return t.has(e)?(r.get(e)??[]).length>0:null}p(Re,"treeHasDescendants");class bt{static{p(this,"BrokerSupervisionReconciler")}yieldStalls=new Map;wedgeNotifiedAt=new Map;unattendedSince=new Map;sampleTree;signal;lastOverCapWarnAt=Number.NEGATIVE_INFINITY;constructor(e={}){this.sampleTree=e.readTreeSample??(t=>this.readTreeSample(t)),this.signal=e.signal??((t,r)=>{if(process.platform==="linux"&&H(K)){if(r!=="SIGTERM"&&r!=="SIGKILL")throw new Error(`unsupported launcher signal ${r}`);return V({operation:"stop",pid:t,signal:r}).then(()=>{})}process.kill(t,r)})}async runFleetPreamble(e){return ge()}warnIfSaturated(e){if(e.fleet.size()<e.thresholds.warning||!e.lifecycle.acceptsDetachedWork())return;const t=F.fresh(()=>this.warnOverCapInTmux(e.fleet.size(),e.thresholds,e.frozen));e.lifecycle.registerDetached(t)}async run(e,t){const r=xe();for(const i of ee())await F.fresh(async()=>{try{this.handleFatalFault(i)}catch(n){u({level:"error",event:"broker.supervision.failed",...c(i)?{node_id:i}:{fields:{affected_node_id:i}},error:n})}});for(const i of t.rows){const n=i.node_id,l=t.fleet.get(n);if(l===void 0||i.status!=="active"&&i.status!=="idle"){this.unattendedSince.delete(n);continue}await F.fresh(async()=>{try{await this.handleYieldStall(i,l,t.fleet,e),await this.handleWedgeDetection(i,l,t.fleet,e),this.handleUnattendedParking(n,T(n),l.pid,e,t,r)}catch(o){this.unattendedSince.delete(n),u({level:"error",event:"broker.supervision.failed",...c(n)?{node_id:n}:{fields:{affected_node_id:n}},error:o})}})}for(const i of this.unattendedSince.keys())t.fleet.has(i)||this.unattendedSince.delete(i);for(const i of ye())t.fleet.has(i)||N(i);de(e)}parkResetCondition(e,t,r,i){if(t.pi_session_id==null||t.pi_pid!==r||t.status==="done"||t.status==="canceled"||t.status==="dead"||t.intent!==null||ce(X(e)).length>0||ue(e)!==null||fe(e))return!0;const n=O(e);return n!==null&&n.retry.disposition==="auto"&&n.retry.by==="daemon"&&(n.kind==="connection"||n.kind==="rate-limit"||n.kind==="overloaded")&&(n.retry.nextAt!==void 0||ie(n,i)!==null)?!0:le(e)}handleUnattendedParking(e,t,r,i,n,l){if(J(e)){this.unattendedSince.delete(e),N(e);return}if(t===null){this.unattendedSince.delete(e),N(e);return}const o=this.parkResetCondition(e,t,r,i),a=be(e);if(a!==void 0){if(i-a<B())return;const _=N(e);if(_===void 0||(this.unattendedSince.delete(e),!we(e,t,_)))return;u({level:"warn",event:"broker.park.summary_expired",...c(e)?{node_id:e}:{},fields:{...c(e)?{}:{affected_node_id:e},grace_ms:B()}}),P(e,"park"),S.teardown(e);return}if(o||Ae(e)){this.unattendedSince.delete(e);return}const m=this.unattendedSince.get(e);if(m===void 0){this.unattendedSince.set(e,i);return}const d=Se(e,t.lifecycle),h=d!=="cancel"&&Ne(t.grantee??"app:terminal",i)?0:l;if(!(i-m<h)){if(this.unattendedSince.delete(e),d==="release"){P(e,d),S.teardown(e);return}if(d==="cancel"){P(e,d),S.teardown(e);return}if(t.grantee!==void 0&&t.grantee!=="app:terminal"){P(e,"park"),S.teardown(e);return}n.lifecycle.acceptsDetachedWork()&&(ve(e,i),n.lifecycle.registerDetached(this.requestParkSummary(e)))}}async requestParkSummary(e){try{const t=await pe(e,he("park",{},_e),{origin:{type:"runtime",kind:"park"}});u({level:"info",event:"broker.park.summary_requested",...c(e)?{node_id:e}:{},fields:{...c(e)?{}:{affected_node_id:e},route:t}})}catch(t){if(N(e)===void 0)return;if(t instanceof me){P(e,"park"),S.teardown(e);return}u({level:"error",event:"broker.park.summary_failed",...c(e)?{node_id:e}:{fields:{affected_node_id:e}},error:t})}}async warnOverCapInTmux(e,t,r){const i=Date.now();if(i-this.lastOverCapWarnAt<Fe)return;this.lastOverCapWarnAt=i;const{warning:n,automaticReviveCap:l}=t,o=r.stalled+r.waitingOnMail;u({level:"warn",event:"broker.capacity.warning",fields:{live_count:e,warning_threshold:n,hard_cap:l,frozen_stalled:r.stalled,frozen_waiting_on_mail:r.waitingOnMail}});const a=o===0?"":` \u2014 ${o} frozen (${r.stalled} stalled, ${r.waitingOnMail} waiting on mail)`,m=`\u26A0 crtrd: ${e} live brokers (warning ${n}; hard cap ${l})${a} \u2014 possible broker leak. ps -ax | grep broker-cli.js`;try{const d=await M("tmux",["list-clients","-F","#{client_name}"],{encoding:"utf8",timeout:2e3});if(typeof d.stdout!="string")return;await Promise.allSettled(d.stdout.split(`
3
- `).map(async h=>{if(h!=="")try{await M("tmux",["display-message","-C","-d","10000","-c",h,m],{encoding:"utf8",timeout:2e3})}catch{try{await M("tmux",["display-message","-c",h,m],{encoding:"utf8",timeout:2e3})}catch(_){u({level:"error",event:"broker.capacity.tmux_display_failed",error:_,fields:{client:h}})}}}))}catch(d){u({level:"error",event:"broker.capacity.tmux_list_clients_failed",error:d})}}async handleYieldStall(e,t,r,i){const n=e.node_id,l=G(t.pid,e.pi_pid_identity);if(e.intent!=="refresh"||I(e)!==null||l===null){l!==null&&this.yieldStalls.delete(l);return}let o=this.yieldStalls.get(l);if(o===void 0){o={since:i},this.yieldStalls.set(l,o);return}if(Ce(!0,e.intent,!1,i-o.since)!=="kill")return;const a=T(n);if(!(r.get(n)!==t||a?.pi_pid!==t.pid||a.pi_pid_identity!==e.pi_pid_identity||D(t.pid,e.pi_pid_identity)!=="alive"))if(o.termedAt===void 0){if(!await this.signalYieldStall(n,t.pid,"SIGTERM"))return;o.termedAt=i,u({level:"warn",event:"broker.yield_stall.sigterm",...c(n)?{node_id:n}:{},fields:{...c(n)?{}:{affected_node_id:n},pid:t.pid,elapsed_ms:i-o.since,grace_ms:W}})}else i-o.termedAt>=U&&await this.signalYieldStall(n,t.pid,"SIGKILL")&&u({level:"warn",event:"broker.yield_stall.sigkill",...c(n)?{node_id:n}:{},fields:{...c(n)?{}:{affected_node_id:n},pid:t.pid,elapsed_ms:i-o.termedAt,grace_ms:U,stall_elapsed_ms:i-o.since}})}async signalYieldStall(e,t,r){try{return await this.signal(t,r),!0}catch(i){return u({level:"error",event:"broker.yield_stall.signal_failed",...c(e)?{node_id:e}:{},error:i,fields:{...c(e)?{}:{affected_node_id:e},pid:t,signal:r}}),!1}}async readTreeSample(e){try{const{stdout:t}=await M("ps",["-ax","-o","pid=,ppid=,pcpu="],{encoding:"utf8",timeout:2e3});if(typeof t!="string")return null;const r=Oe(t,e),i=Re(t,e);return r===null||i===null?null:{cpuPercent:r,hasDescendants:i}}catch{return null}}async handleWedgeDetection(e,t,r,i){const n=e.node_id,l=G(t.pid,e.pi_pid_identity),o=I(e);if(o===null||l===null){l!==null&&this.wedgeNotifiedAt.delete(l);return}const a=i-o,m=a>=x?await this.sampleTree(t.pid):null,d=T(n),h=d===null?null:I(d);if(r.get(n)!==t||d===null||d.pi_pid!==t.pid||d.pi_pid_identity!==e.pi_pid_identity||h!==o)return;const _=m?.cpuPercent??null,$=m?.hasDescendants??null;if(De({busy:!0,quietForMs:a,cpuPercent:_,hasDescendants:$})!=="wedged"||this.wedgeNotifiedAt.get(l)===o)return;this.wedgeNotifiedAt.set(l,o);const v=C(d),g=Math.round((a??x)/6e4),y=$??!0,k=!y&&D(t.pid,e.pi_pid_identity)!=="alive"?"pid_identity_unproven":null;let f=!1,b=!1,w;if(y)w=`Child wedged \u2014 ${v} (${n}) is alive and mid-turn but has shown NO engine progress for ${g}+ minutes and its process is near-idle. It will NOT recover on its own \u2014 read \`crtr canvas read wedged-child-on-runaway-bash\` and kill the runaway SUBPROCESS (not this node) so its turn can resume with no lost context.`;else if(k!==null)w=`Child wedged \u2014 ${v} (${n}) is alive and mid-turn but has shown NO engine progress for ${g}+ minutes and it has no subprocess to kill (the engine itself stalled). The daemon did not SIGTERM the broker because its PID identity cannot be confirmed alive.`;else{f=!0;try{await this.signal(t.pid,"SIGTERM"),b=!0,w=`Child wedged \u2014 ${v} (${n}) is alive and mid-turn but has shown NO engine progress for ${g}+ minutes and it has no subprocess to kill (the engine itself stalled). The daemon sent SIGTERM to the broker and will resume a cleanly aborted session with a continuation, or start a recovery cycle if the abort cannot settle.`}catch(A){u({level:"error",event:"broker.wedge.signal_failed",...c(n)?{node_id:n}:{},error:A,fields:{...c(n)?{}:{affected_node_id:n},pid:t.pid,signal:"SIGTERM"}}),w=`Child wedged \u2014 ${v} (${n}) is alive and mid-turn but has shown NO engine progress for ${g}+ minutes and it has no subprocess to kill (the engine itself stalled). The daemon attempted SIGTERM to the broker, but the signal call failed.`}}u({level:"error",event:"broker.wedge.detected",...c(n)?{node_id:n}:{},fields:{...c(n)?{}:{affected_node_id:n},pid:t.pid,quiet_ms:a,cpu_percent:_,has_descendants:y,kick_attempted:f,kicked:b,...k===null?{}:{kick_suppression_reason:k}}});try{ne(n,{link:"daemon\u2192node",op:"engine wedge",kind:"wedged",retry:{disposition:"manual"},message:w})}catch(A){u({level:"error",event:"broker.wedge.fault_record_failed",...c(n)?{node_id:n}:{fields:{affected_node_id:n}},error:A})}R(n,"notice.crash",w,{reason:"child-wedged",child:n,quiet_ms:a,cpu_percent:_,kick_attempted:f,kicked:b,...k===null?{}:{kick_suppression_reason:k}})}handleFatalFault(e,t=!0){const r=O(e);if(r===null||r.retry.disposition!=="fatal"||r.link!=="pi\u2192provider"||r.notifiedSince===r.since)return;const i=T(e),n=i!==null?C(i):e,l=r.kind==="auth",o=(r.message.split(`
4
- `)[0]??"").slice(0,200),a=r.providerError,m=a===void 0?"":` Provider: ${a.provider}/${a.model}.`+(a.status===void 0?"":` HTTP status: ${a.status}.`)+(a.errno===void 0?"":` Errno: ${a.errno}.`)+(a.code===void 0?"":` Code: ${a.code}.`)+(a.body===void 0?"":` Body: ${a.body.slice(0,200)}.`)+(a.ratelimit===void 0?"":` Rate limit: ${a.ratelimit.scope??"account"}${a.ratelimit.claim===void 0?"":` (${a.ratelimit.claim})`}${a.ratelimit.model===void 0?"":` on ${a.ratelimit.model}`}.`),d=r.kind==="context-overflow",h=Ie(i,typeof a?.code=="string"?a.code:void 0),_=d||h,$=d?`exceeded its model context window and is now dead. Shrink the task or loaded context, or choose a larger-context model before spawning it again. Error: ${o}`:`got a non-retryable ${r.kind} error and will NOT recover on its own; `+(h?"its terminal run has failed. ":"it stays alive, and waking it clears the fault as soon as one turn completes \u2014 a wake that hits the same cause fails again. "+(l?"Its provider credential is dead \u2014 re-authenticate (`/login`, or re-auth crtr); it recovers on its next wake once the auth reloads. ":""))+`Error: ${o}.${m}`,v=d?"failed":"hit a fatal provider fault",g=new Set(Q(e).map(f=>f.node_id)),y=oe(L(),se(L(),e)),k=[{relation:"Child",recipients:y.filter(f=>g.has(f))},{relation:"Watched node",recipients:y.filter(f=>!g.has(f))}];u({level:"error",event:"fault.fatal.fanout",...c(e)?{node_id:e}:{},fields:{...c(e)?{}:{affected_node_id:e},kind:r.kind,link:r.link,terminal_overflow:d,terminal_provider_fault:h}}),ae(()=>{for(const{relation:f,recipients:b}of k)b.length!==0&&R(e,"notice.crash",`${f} ${v} \u2014 ${n} (${e}) ${$}`,{reason:d?"child-context-overflow":"child-fault-fatal",child:e,kind:r.kind,message:o,...a===void 0?{}:{provider_error:a}},"normal",null,b);te(r.faultId),t&&_&&Z(e,"crash",{reason:d?"context_overflow":a?.code==="cap_exhausted"?"cap_exhausted":"provider_fatal",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:r.message,fault_kind:r.kind,error_class:re(r).class}}})}),t&&_&&S.teardown(e).then(f=>{f.confirmed||u({level:"error",event:"fault.fatal.teardown_failed",node_id:e,error:f.error??new Error("Broker teardown not confirmed")})},f=>u({level:"error",event:"fault.fatal.teardown_failed",node_id:e,error:f}))}}export{bt as BrokerSupervisionReconciler,Ee as WEDGE_CPU_MAX_PERCENT,x as WEDGE_QUIET_MS,W as YIELD_STALL_GRACE_MS,B as parkSummaryGraceMsForDaemon,Ie as settlesOnFatalProviderFault,Oe as sumTreeCpu,Re as treeHasDescendants,xe as unattendedParkMsForDaemon,De as wedgeVerdict,Ce as yieldStallVerdict};
1
+ var Y=Object.defineProperty;var p=(s,e)=>Y(s,"name",{value:e,configurable:!0});import{existsSync as H}from"node:fs";import{LAUNCHER_SOCKET as K}from"../../launcher/spec.js";import{launcherRequest as V}from"../../launcher/client.js";import{execFile as z}from"node:child_process";import{promisify as j}from"node:util";import{getNode as T,isWarmSpare as J,subscribersOf as Q}from"../../core/canvas/index.js";import{isSafeNodeId as c}from"../../core/canvas/paths.js";import{contextDir as X}from"../../core/canvas/node-agent-paths.js";import{fullName as C}from"../../core/canvas/labels.js";import{recordedPidLiveness as D}from"../../core/canvas/pid.js";import{transition as Z}from"../../core/runtime/lifecycle.js";import{hasAdmittedTurn as ee,listUnnotifiedFatalFaultNodes as te,markFaultNotified as ne,readFault as O,readLiveTurn as E,recordFault as re}from"../../core/canvas/recovery.js";import{errorClassFromFault as ie}from"../../core/fault-classifier.js";import{nextFaultRetry as ae}from"../../core/runtime/fault-recovery.js";import{publishNotice as R}from"../../core/runtime/close.js";import{openDb as G,withCanvasWrite as se}from"../../core/canvas/db.js";import{getById as oe}from"../../core/graph/objects.js";import{watcherIdsOf as le}from"../../core/graph/watches.js";import{headlessBrokerHost as S}from"../../core/runtime/host.js";import{emitEvent as u}from"../../core/events/emit.js";import{operationIdContext as F}from"../../core/events/operation-id.js";import{hasUnseenWakeMail as de,resetStaleExecutionMail as ce}from"../../core/canvas/deliveries.js";import{activeBackgroundBashJobs as ue}from"../../core/bash-jobs.js";import{focusOf as fe}from"../../core/runtime/placement.js";import{hasAttachedBrokerViewers as pe}from"../api/viewer-presence.js";import{deliverLive as me}from"../../core/runtime/interactive-deliver.js";import{BrokerUnreachableError as he}from"../../core/runtime/broker-request.js";import{formatCard as _e,PARK_SUMMARY_PROMPT as ge}from"../../shared/generated-context.js";import{brokerThresholdsForDaemon as ke}from"../../core/runtime/fleet.js";import{readConfig as ve}from"../../core/config.js";import{beginPendingPark as ye,consumePendingPark as N,pendingParkIds as be,pendingParkStartedAt as we}from"../park-pending.js";import{shouldParkAfterSummary as Se}from"../park-activity.js";import{applyParkEvent as P,parkEvent as Ne}from"./live-obligation.js";import{readGoal as Pe}from"../../core/runtime/kickoff.js";import{memoryDeniedRecently as $e}from"../../core/runtime/memory-admission.js";import{envNoDaemonAutostart as Te,envTestUnattendedParkMs as Ae,parkSummaryGraceMs as Me,envUnattendedParkMs as Ee}from"../../shared/env.js";const A=j(z);function Fe(s){return!ee(s)&&(Pe(s)?.trim()??"")!==""}p(Fe,"awaitsBirthTurn");function xe(s){return E(s)?.busy===!0}p(xe,"liveBusy");function Nt(s){const e=E(s)?.progressAt;if(e==null)return null;const t=Date.parse(e);return Number.isNaN(t)?null:t}p(Nt,"liveTurnProgressMs");function L(s,e){return e===null?null:`${s}:${e}`}p(L,"incarnationOf");function x(s){const e=E(s.node_id),t=e?.busy===!0&&e.progressAt!==null?Date.parse(e.progressAt):Number.NaN,r=s.launched_at===null||s.launched_at===void 0?Number.NaN:Date.parse(s.launched_at);return!Number.isFinite(t)||!Number.isFinite(r)||t<r?null:t}p(x,"busySinceForIncarnation");const W=3*6e4,B=2e4,I=20*6e4,Ie=2,Ce=5*6e4;function De(s,e){return s===null||s.parent!=null?!1:s.lifecycle==="terminal"?!0:e==="cap_exhausted"&&s.run_id===s.node_id}p(De,"settlesOnFatalProviderFault");function Oe(){const s=Ee();if(s!==void 0)return s;const e=ve("user").lifecycle.unattendedParkMs;return Te()?Ae()??e:e}p(Oe,"unattendedParkMsForDaemon");function U(){return Me()}p(U,"parkSummaryGraceMsForDaemon");function Re(s,e,t,r){return s!==!0||e!=="refresh"||t?"leave":r===null||r<W?"pending":"kill"}p(Re,"yieldStallVerdict");function Ge(s){return!s.busy||s.quietForMs===null||s.quietForMs<I?"leave":s.cpuPercent===null||s.hasDescendants!==!1&&s.cpuPercent>Ie?"pending":"wedged"}p(Ge,"wedgeVerdict");function q(s){const e=new Map,t=new Map;for(const r of s.split(`
2
+ `)){const i=r.trim();if(i==="")continue;const n=i.split(/\s+/);if(n.length<3)continue;const l=Number(n[0]),o=Number(n[1]),a=Number(n[2]);if(!Number.isFinite(l)||!Number.isFinite(o)||!Number.isFinite(a))continue;e.set(l,a);const m=t.get(o);m===void 0?t.set(o,[l]):m.push(l)}return{byPid:e,childrenOf:t}}p(q,"parsePsTree");function Le(s,e){const{byPid:t,childrenOf:r}=q(s);if(!t.has(e))return null;let i=0;const n=[e],l=new Set;for(;n.length>0;){const o=n.pop();if(l.has(o))continue;l.add(o),i+=t.get(o)??0;const a=r.get(o);a!==void 0&&n.push(...a)}return i}p(Le,"sumTreeCpu");function We(s,e){const{byPid:t,childrenOf:r}=q(s);return t.has(e)?(r.get(e)??[]).length>0:null}p(We,"treeHasDescendants");class Pt{static{p(this,"BrokerSupervisionReconciler")}yieldStalls=new Map;wedgeNotifiedAt=new Map;unattendedSince=new Map;sampleTree;signal;lastOverCapWarnAt=Number.NEGATIVE_INFINITY;constructor(e={}){this.sampleTree=e.readTreeSample??(t=>this.readTreeSample(t)),this.signal=e.signal??((t,r)=>{if(process.platform==="linux"&&H(K)){if(r!=="SIGTERM"&&r!=="SIGKILL")throw new Error(`unsupported launcher signal ${r}`);return V({operation:"stop",pid:t,signal:r}).then(()=>{})}process.kill(t,r)})}async runFleetPreamble(e){return ke()}warnIfSaturated(e){if(e.fleet.size()<e.thresholds.warning||!e.lifecycle.acceptsDetachedWork())return;const t=F.fresh(()=>this.warnOverCapInTmux(e.fleet.size(),e.thresholds,e.frozen));e.lifecycle.registerDetached(t)}async run(e,t){const r=Oe();for(const i of te())await F.fresh(async()=>{try{this.handleFatalFault(i)}catch(n){u({level:"error",event:"broker.supervision.failed",...c(i)?{node_id:i}:{fields:{affected_node_id:i}},error:n})}});for(const i of t.rows){const n=i.node_id,l=t.fleet.get(n);if(l===void 0||i.status!=="active"&&i.status!=="idle"){this.unattendedSince.delete(n);continue}await F.fresh(async()=>{try{await this.handleYieldStall(i,l,t.fleet,e),await this.handleWedgeDetection(i,l,t.fleet,e),this.handleUnattendedParking(n,T(n),l.pid,e,t,r)}catch(o){this.unattendedSince.delete(n),u({level:"error",event:"broker.supervision.failed",...c(n)?{node_id:n}:{fields:{affected_node_id:n}},error:o})}})}for(const i of this.unattendedSince.keys())t.fleet.has(i)||this.unattendedSince.delete(i);for(const i of be())t.fleet.has(i)||N(i);ce(e)}parkResetCondition(e,t,r,i){if(t.pi_session_id==null||t.pi_pid!==r||t.status==="done"||t.status==="canceled"||t.status==="dead"||t.intent!==null||ue(X(e)).length>0||fe(e)!==null||pe(e))return!0;const n=O(e);return n!==null&&n.retry.disposition==="auto"&&n.retry.by==="daemon"&&(n.kind==="connection"||n.kind==="rate-limit"||n.kind==="overloaded")&&(n.retry.nextAt!==void 0||ae(n,i)!==null)?!0:de(e)}handleUnattendedParking(e,t,r,i,n,l){if(J(e)){this.unattendedSince.delete(e),N(e);return}if(t===null){this.unattendedSince.delete(e),N(e);return}const o=this.parkResetCondition(e,t,r,i),a=we(e);if(a!==void 0){if(i-a<U())return;const _=N(e);if(_===void 0||(this.unattendedSince.delete(e),!Se(e,t,_)))return;u({level:"warn",event:"broker.park.summary_expired",...c(e)?{node_id:e}:{},fields:{...c(e)?{}:{affected_node_id:e},grace_ms:U()}}),P(e,"park"),S.teardown(e);return}if(o||xe(e)||Fe(e)){this.unattendedSince.delete(e);return}const m=this.unattendedSince.get(e);if(m===void 0){this.unattendedSince.set(e,i);return}const d=Ne(e,t.lifecycle),h=d!=="cancel"&&$e(t.grantee??"app:terminal",i)?0:l;if(!(i-m<h)){if(this.unattendedSince.delete(e),d==="release"){P(e,d),S.teardown(e);return}if(d==="cancel"){P(e,d),S.teardown(e);return}if(t.grantee!==void 0&&t.grantee!=="app:terminal"){P(e,"park"),S.teardown(e);return}n.lifecycle.acceptsDetachedWork()&&(ye(e,i),n.lifecycle.registerDetached(this.requestParkSummary(e)))}}async requestParkSummary(e){try{const t=await me(e,_e("park",{},ge),{origin:{type:"runtime",kind:"park"}});u({level:"info",event:"broker.park.summary_requested",...c(e)?{node_id:e}:{},fields:{...c(e)?{}:{affected_node_id:e},route:t}})}catch(t){if(N(e)===void 0)return;if(t instanceof he){P(e,"park"),S.teardown(e);return}u({level:"error",event:"broker.park.summary_failed",...c(e)?{node_id:e}:{fields:{affected_node_id:e}},error:t})}}async warnOverCapInTmux(e,t,r){const i=Date.now();if(i-this.lastOverCapWarnAt<Ce)return;this.lastOverCapWarnAt=i;const{warning:n,automaticReviveCap:l}=t,o=r.stalled+r.waitingOnMail;u({level:"warn",event:"broker.capacity.warning",fields:{live_count:e,warning_threshold:n,hard_cap:l,frozen_stalled:r.stalled,frozen_waiting_on_mail:r.waitingOnMail}});const a=o===0?"":` \u2014 ${o} frozen (${r.stalled} stalled, ${r.waitingOnMail} waiting on mail)`,m=`\u26A0 crtrd: ${e} live brokers (warning ${n}; hard cap ${l})${a} \u2014 possible broker leak. ps -ax | grep broker-cli.js`;try{const d=await A("tmux",["list-clients","-F","#{client_name}"],{encoding:"utf8",timeout:2e3});if(typeof d.stdout!="string")return;await Promise.allSettled(d.stdout.split(`
3
+ `).map(async h=>{if(h!=="")try{await A("tmux",["display-message","-C","-d","10000","-c",h,m],{encoding:"utf8",timeout:2e3})}catch{try{await A("tmux",["display-message","-c",h,m],{encoding:"utf8",timeout:2e3})}catch(_){u({level:"error",event:"broker.capacity.tmux_display_failed",error:_,fields:{client:h}})}}}))}catch(d){u({level:"error",event:"broker.capacity.tmux_list_clients_failed",error:d})}}async handleYieldStall(e,t,r,i){const n=e.node_id,l=L(t.pid,e.pi_pid_identity);if(e.intent!=="refresh"||x(e)!==null||l===null){l!==null&&this.yieldStalls.delete(l);return}let o=this.yieldStalls.get(l);if(o===void 0){o={since:i},this.yieldStalls.set(l,o);return}if(Re(!0,e.intent,!1,i-o.since)!=="kill")return;const a=T(n);if(!(r.get(n)!==t||a?.pi_pid!==t.pid||a.pi_pid_identity!==e.pi_pid_identity||D(t.pid,e.pi_pid_identity)!=="alive"))if(o.termedAt===void 0){if(!await this.signalYieldStall(n,t.pid,"SIGTERM"))return;o.termedAt=i,u({level:"warn",event:"broker.yield_stall.sigterm",...c(n)?{node_id:n}:{},fields:{...c(n)?{}:{affected_node_id:n},pid:t.pid,elapsed_ms:i-o.since,grace_ms:W}})}else i-o.termedAt>=B&&await this.signalYieldStall(n,t.pid,"SIGKILL")&&u({level:"warn",event:"broker.yield_stall.sigkill",...c(n)?{node_id:n}:{},fields:{...c(n)?{}:{affected_node_id:n},pid:t.pid,elapsed_ms:i-o.termedAt,grace_ms:B,stall_elapsed_ms:i-o.since}})}async signalYieldStall(e,t,r){try{return await this.signal(t,r),!0}catch(i){return u({level:"error",event:"broker.yield_stall.signal_failed",...c(e)?{node_id:e}:{},error:i,fields:{...c(e)?{}:{affected_node_id:e},pid:t,signal:r}}),!1}}async readTreeSample(e){try{const{stdout:t}=await A("ps",["-ax","-o","pid=,ppid=,pcpu="],{encoding:"utf8",timeout:2e3});if(typeof t!="string")return null;const r=Le(t,e),i=We(t,e);return r===null||i===null?null:{cpuPercent:r,hasDescendants:i}}catch{return null}}async handleWedgeDetection(e,t,r,i){const n=e.node_id,l=L(t.pid,e.pi_pid_identity),o=x(e);if(o===null||l===null){l!==null&&this.wedgeNotifiedAt.delete(l);return}const a=i-o,m=a>=I?await this.sampleTree(t.pid):null,d=T(n),h=d===null?null:x(d);if(r.get(n)!==t||d===null||d.pi_pid!==t.pid||d.pi_pid_identity!==e.pi_pid_identity||h!==o)return;const _=m?.cpuPercent??null,$=m?.hasDescendants??null;if(Ge({busy:!0,quietForMs:a,cpuPercent:_,hasDescendants:$})!=="wedged"||this.wedgeNotifiedAt.get(l)===o)return;this.wedgeNotifiedAt.set(l,o);const v=C(d),g=Math.round((a??I)/6e4),y=$??!0,k=!y&&D(t.pid,e.pi_pid_identity)!=="alive"?"pid_identity_unproven":null;let f=!1,b=!1,w;if(y)w=`Child wedged \u2014 ${v} (${n}) is alive and mid-turn but has shown NO engine progress for ${g}+ minutes and its process is near-idle. It will NOT recover on its own \u2014 read \`crtr canvas read wedged-child-on-runaway-bash\` and kill the runaway SUBPROCESS (not this node) so its turn can resume with no lost context.`;else if(k!==null)w=`Child wedged \u2014 ${v} (${n}) is alive and mid-turn but has shown NO engine progress for ${g}+ minutes and it has no subprocess to kill (the engine itself stalled). The daemon did not SIGTERM the broker because its PID identity cannot be confirmed alive.`;else{f=!0;try{await this.signal(t.pid,"SIGTERM"),b=!0,w=`Child wedged \u2014 ${v} (${n}) is alive and mid-turn but has shown NO engine progress for ${g}+ minutes and it has no subprocess to kill (the engine itself stalled). The daemon sent SIGTERM to the broker and will resume a cleanly aborted session with a continuation, or start a recovery cycle if the abort cannot settle.`}catch(M){u({level:"error",event:"broker.wedge.signal_failed",...c(n)?{node_id:n}:{},error:M,fields:{...c(n)?{}:{affected_node_id:n},pid:t.pid,signal:"SIGTERM"}}),w=`Child wedged \u2014 ${v} (${n}) is alive and mid-turn but has shown NO engine progress for ${g}+ minutes and it has no subprocess to kill (the engine itself stalled). The daemon attempted SIGTERM to the broker, but the signal call failed.`}}u({level:"error",event:"broker.wedge.detected",...c(n)?{node_id:n}:{},fields:{...c(n)?{}:{affected_node_id:n},pid:t.pid,quiet_ms:a,cpu_percent:_,has_descendants:y,kick_attempted:f,kicked:b,...k===null?{}:{kick_suppression_reason:k}}});try{re(n,{link:"daemon\u2192node",op:"engine wedge",kind:"wedged",retry:{disposition:"manual"},message:w})}catch(M){u({level:"error",event:"broker.wedge.fault_record_failed",...c(n)?{node_id:n}:{fields:{affected_node_id:n}},error:M})}R(n,"notice.crash",w,{reason:"child-wedged",child:n,quiet_ms:a,cpu_percent:_,kick_attempted:f,kicked:b,...k===null?{}:{kick_suppression_reason:k}})}handleFatalFault(e,t=!0){const r=O(e);if(r===null||r.retry.disposition!=="fatal"||r.link!=="pi\u2192provider"||r.notifiedSince===r.since)return;const i=T(e),n=i!==null?C(i):e,l=r.kind==="auth",o=(r.message.split(`
4
+ `)[0]??"").slice(0,200),a=r.providerError,m=a===void 0?"":` Provider: ${a.provider}/${a.model}.`+(a.status===void 0?"":` HTTP status: ${a.status}.`)+(a.errno===void 0?"":` Errno: ${a.errno}.`)+(a.code===void 0?"":` Code: ${a.code}.`)+(a.body===void 0?"":` Body: ${a.body.slice(0,200)}.`)+(a.ratelimit===void 0?"":` Rate limit: ${a.ratelimit.scope??"account"}${a.ratelimit.claim===void 0?"":` (${a.ratelimit.claim})`}${a.ratelimit.model===void 0?"":` on ${a.ratelimit.model}`}.`),d=r.kind==="context-overflow",h=De(i,typeof a?.code=="string"?a.code:void 0),_=d||h,$=d?`exceeded its model context window and is now dead. Shrink the task or loaded context, or choose a larger-context model before spawning it again. Error: ${o}`:`got a non-retryable ${r.kind} error and will NOT recover on its own; `+(h?"its terminal run has failed. ":"it stays alive, and waking it clears the fault as soon as one turn completes \u2014 a wake that hits the same cause fails again. "+(l?"Its provider credential is dead \u2014 re-authenticate (`/login`, or re-auth crtr); it recovers on its next wake once the auth reloads. ":""))+`Error: ${o}.${m}`,v=d?"failed":"hit a fatal provider fault",g=new Set(Q(e).map(f=>f.node_id)),y=le(G(),oe(G(),e)),k=[{relation:"Child",recipients:y.filter(f=>g.has(f))},{relation:"Watched node",recipients:y.filter(f=>!g.has(f))}];u({level:"error",event:"fault.fatal.fanout",...c(e)?{node_id:e}:{},fields:{...c(e)?{}:{affected_node_id:e},kind:r.kind,link:r.link,terminal_overflow:d,terminal_provider_fault:h}}),se(()=>{for(const{relation:f,recipients:b}of k)b.length!==0&&R(e,"notice.crash",`${f} ${v} \u2014 ${n} (${e}) ${$}`,{reason:d?"child-context-overflow":"child-fault-fatal",child:e,kind:r.kind,message:o,...a===void 0?{}:{provider_error:a}},"normal",null,b);ne(r.faultId),t&&_&&Z(e,"crash",{reason:d?"context_overflow":a?.code==="cap_exhausted"?"cap_exhausted":"provider_fatal",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:r.message,fault_kind:r.kind,error_class:ie(r).class}}})}),t&&_&&S.teardown(e).then(f=>{f.confirmed||u({level:"error",event:"fault.fatal.teardown_failed",node_id:e,error:f.error??new Error("Broker teardown not confirmed")})},f=>u({level:"error",event:"fault.fatal.teardown_failed",node_id:e,error:f}))}}export{Pt as BrokerSupervisionReconciler,Ie as WEDGE_CPU_MAX_PERCENT,I as WEDGE_QUIET_MS,W as YIELD_STALL_GRACE_MS,U as parkSummaryGraceMsForDaemon,De as settlesOnFatalProviderFault,Le as sumTreeCpu,We as treeHasDescendants,Oe as unattendedParkMsForDaemon,Ge as wedgeVerdict,Re as yieldStallVerdict};
@@ -1 +1 @@
1
- var c=Object.defineProperty;var d=(r,i)=>c(r,"name",{value:i,configurable:!0});import{clearFrozen as m,endLiveExecution as l,getNode as h}from"../../../core/canvas/index.js";import{isSafeNodeId as _}from"../../../core/canvas/paths.js";import{recordedPidLiveness as o}from"../../../core/canvas/pid.js";import{emitEvent as p}from"../../../core/events/emit.js";import{operationIdContext as y}from"../../../core/events/operation-id.js";import{readInterruptedTurn as P}from"../../../core/canvas/recovery.js";import{isProfilePaused as u}from"../../../core/profiles/manifest.js";import{isRecoveryHeld as v}from"../../../core/runtime/recovery-hold.js";import{hasUnseenWakeMail as k}from"../../../core/canvas/deliveries.js";import{hasIdleReleaseIntent as L}from"../../../core/runtime/lifecycle.js";import{classifyDeadNode as z}from"../../fleet.js";import{applyParkEvent as I,hasPendingWake as R,parkEvent as E}from"../live-obligation.js";import{FreezeLaneReconciler as M}from"./freeze-lane.js";import{isInBackoff as g}from"./respawn-policy.js";import{handleTerminatingRow as N}from"./terminating.js";import{wantsExecution as b}from"./wants-execution.js";function C(r){return r.status==="done"||r.status==="canceled"||r.status==="dead"}d(C,"isTerminal");const D=12e4;function F(r,i){const e=Math.max(...[r.created,r.launched_at].map(n=>n==null?0:Date.parse(n)||0));return e>0&&i-e<D}d(F,"isLaunchInFlight");class Y{static{d(this,"NodeLifecycleReconciler")}freezeLane=new M;async run(i,e){const n=[];for(const a of e.rows)await y.fresh(async()=>{try{await this.classifyRow(i,a,e,n)}catch(t){const s=a.node_id;p({level:"error",event:"node.lifecycle.classify_failed",..._(s)?{node_id:s}:{fields:{affected_node_id:s}},error:t})}});return this.freezeLane.run(i,{fleet:e.fleet,candidates:n})}async classifyRow(i,e,n,a){const t=e.node_id;if(C(e)){if(N(e,n.fleet.has(t)))return;e.pi_pid!=null&&o(e.pi_pid,e.pi_pid_identity)==="dead"&&l(t,{reason:"observed-dead"}),e.frozen_at!=null&&a.push({row:e,enact:"thaw"});return}if(n.fleet.has(t)){u(e.profile_id)?n.fleet.interruptForProfilePause(t):this.thawMark(e);return}if(u(e.profile_id)){this.freezeLane.endEpisode(t);return}if(v(t))return;const s=e.pi_pid!=null&&o(e.pi_pid,e.pi_pid_identity)==="dead";if(s&&!n.fleet.hasProfilePauseInterruption(t)&&l(t,{reason:"observed-dead"}),!b(e)){this.thawMark(e),this.settleParked(e);return}if(g(e,i))return;if(e.status==="idle"&&L(e)&&k(t)){a.push({row:e,enact:"idle-release"});return}if(e.pi_pid==null){if(e.respawn_not_before!=null){a.push({row:e,enact:"policy"});return}if(e.frozen_at!=null){a.push({row:e,enact:"thaw"});return}if(n.fleet.isClaimed(t)||F(e,i))return;p({level:"warn",event:"node.lifecycle.stranded_launch",node_id:t}),a.push({row:e,enact:"thaw"});return}if(!s)return;const f=z(h(t),P(t)?.interrupted===!0,R(t));if(f==="respawn-fresh"||f==="respawn-cycle"||f==="respawn-resume"){a.push({row:e,enact:"policy"});return}await n.fleet.applyDeadRowPolicy(t),this.thawMark(e)}thawMark(i){i.frozen_at!=null&&m(i.node_id)}settleParked(i){if(i.status!=="idle")return;this.freezeLane.endEpisode(i.node_id);const e=E(i.node_id,i.lifecycle);e!=="release"&&I(i.node_id,e)}}export{D as LAUNCH_GRACE_MS,Y as NodeLifecycleReconciler,F as isLaunchInFlight};
1
+ var m=Object.defineProperty;var f=(r,i)=>m(r,"name",{value:i,configurable:!0});import{clearFrozen as l,endLiveExecution as o,getNode as h}from"../../../core/canvas/index.js";import{isSafeNodeId as _}from"../../../core/canvas/paths.js";import{recordedPidLiveness as p}from"../../../core/canvas/pid.js";import{emitEvent as u}from"../../../core/events/emit.js";import{operationIdContext as y}from"../../../core/events/operation-id.js";import{readInterruptedTurn as P}from"../../../core/canvas/recovery.js";import{isProfilePaused as c}from"../../../core/profiles/manifest.js";import{isRecoveryHeld as v}from"../../../core/runtime/recovery-hold.js";import{hasUnseenWakeMail as k}from"../../../core/canvas/deliveries.js";import{hasIdleReleaseIntent as L}from"../../../core/runtime/lifecycle.js";import{classifyDeadNode as z}from"../../fleet.js";import{applyParkEvent as I,hasPendingWake as R,parkEvent as E}from"../live-obligation.js";import{FreezeLaneReconciler as M}from"./freeze-lane.js";import{isInBackoff as g}from"./respawn-policy.js";import{handleTerminatingRow as N}from"./terminating.js";import{wantsExecution as b}from"./wants-execution.js";function C(r){return r.status==="done"||r.status==="canceled"||r.status==="dead"}f(C,"isTerminal");const D=12e4;function F(r,i){const e=Math.max(...[r.created,r.launched_at].map(n=>n==null?0:Date.parse(n)||0));return e>0&&i-e<D}f(F,"isLaunchInFlight");class Y{static{f(this,"NodeLifecycleReconciler")}freezeLane=new M;async run(i,e){const n=[];for(const a of e.rows)await y.fresh(async()=>{try{await this.classifyRow(i,a,e,n)}catch(t){const s=a.node_id;u({level:"error",event:"node.lifecycle.classify_failed",..._(s)?{node_id:s}:{fields:{affected_node_id:s}},error:t})}});return this.freezeLane.run(i,{fleet:e.fleet,candidates:n})}async classifyRow(i,e,n,a){const t=e.node_id;if(C(e)){if(N(e,n.fleet.has(t)))return;e.pi_pid!=null&&p(e.pi_pid,e.pi_pid_identity)==="dead"&&o(t,{reason:"observed-dead"}),e.frozen_at!=null&&(e.final_report!=null?l(t):a.push({row:e,enact:"thaw"}));return}if(n.fleet.has(t)){c(e.profile_id)?n.fleet.interruptForProfilePause(t):this.thawMark(e);return}if(c(e.profile_id)){this.freezeLane.endEpisode(t);return}if(v(t))return;const s=e.pi_pid!=null&&p(e.pi_pid,e.pi_pid_identity)==="dead";if(s&&!n.fleet.hasProfilePauseInterruption(t)&&o(t,{reason:"observed-dead"}),!b(e)){this.thawMark(e),this.settleParked(e);return}if(g(e,i))return;if(e.status==="idle"&&L(e)&&k(t)){a.push({row:e,enact:"idle-release"});return}if(e.pi_pid==null){if(e.respawn_not_before!=null){a.push({row:e,enact:"policy"});return}if(e.frozen_at!=null){a.push({row:e,enact:"thaw"});return}if(n.fleet.isClaimed(t)||F(e,i))return;u({level:"warn",event:"node.lifecycle.stranded_launch",node_id:t}),a.push({row:e,enact:"thaw"});return}if(!s)return;const d=z(h(t),P(t)?.interrupted===!0,R(t));if(d==="respawn-fresh"||d==="respawn-cycle"||d==="respawn-resume"){a.push({row:e,enact:"policy"});return}await n.fleet.applyDeadRowPolicy(t),this.thawMark(e)}thawMark(i){i.frozen_at!=null&&l(i.node_id)}settleParked(i){if(i.status!=="idle")return;this.freezeLane.endEpisode(i.node_id);const e=E(i.node_id,i.lifecycle);e!=="release"&&I(i.node_id,e)}}export{D as LAUNCH_GRACE_MS,Y as NodeLifecycleReconciler,F as isLaunchInFlight};
@@ -1,3 +1,3 @@
1
- var Q=Object.defineProperty;var h=(_,k)=>Q(_,"name",{value:k,configurable:!0});import{spawn as W}from"node:child_process";import{createHash as X}from"node:crypto";import{chownSync as K,chmodSync as H,closeSync as Y,existsSync as Z,mkdirSync as $,readFileSync as ee,rmSync as te,statSync as re}from"node:fs";import{createServer as ne}from"node:net";import{dirname as F,join as E}from"node:path";import{fileURLToPath as oe}from"node:url";import{loadLinuxNative as se}from"../native/linux.js";import{envHomeOverride as ie}from"../shared/env.js";import{launchSandbox as ae}from"./bwrap.js";import{LaunchGate as de}from"./launch-gate.js";import{serveAccepted as ce}from"./accepted-socket.js";import{installNetworkRule as ue}from"./netrule.js";import{LAUNCHER_SOCKET as S,LaunchRefused as u}from"./spec.js";import{systemIdentity as pe,AppUsers as le}from"./users.js";import{SourceValidator as fe}from"./validate.js";import{prepareDaemonScopeDirectories as me}from"./scope-directories.js";import{LimitsManager as he}from"./limits/manager.js";import{CRTRD_OOM_SCORE_ADJ as we}from"./limits/policy.js";import{writeLimitsLog as ge}from"./limits/log.js";const P=se();function J(_){const k=_._handle?.fd;if(k===void 0||k<0)throw new Error("launcher socket fd unavailable");return k}h(J,"socketFd");async function Pe(_={}){const k=_.superviseDaemon??!0;if(process.platform!=="linux"||process.getuid?.()!==0||!P)throw new Error("launcher requires Linux root and its native addon");const{daemonUid:L,daemonGid:x,dataGid:M}=pe(),T=F(S);if($(T,{recursive:!0,mode:493}),(re(T).mode&511)!==493)throw new Error(`${T} must be mode 0755`);K(T,L,x),process.umask(7);const N=E(T,"nodes");$(N,{recursive:!0,mode:504}),K(N,0,x),H(N,504);const v=new fe(ie()||"/",x);me(E(v.root,"runtime"),L,x);const b=new le(M);ue();const d=new he({runtimeRoot:v.runtime,appsRoot:E(v.root,"apps"),stateDir:E(v.runtime,"limits-state"),dataGid:M,users:b});d.init();const G=new Map,j=h(r=>{const p=d.takeEvents();for(const o of p)ge(o);return p.length===0?r:{...r,events:p}},"reply_"),B=new de,I=new Map,R=new Map,q=h((r,p)=>{const o=p.capture,c=X("sha256"),w=h((l,i)=>{let f=0,a=!1;const m=[];return{take(y){i?.update(y);const D=Math.max(0,Math.min(y.length,l-f));D&&(m.push(y.subarray(0,D)),f+=D),D!==y.length&&(a=!0)},result:h(()=>({text:Buffer.concat(m).toString("utf8"),truncated:a}),"result")}},"chunks"),e=w(o.stdoutBytes,c),s=w(o.stderrBytes);let t=Buffer.alloc(0),n=0;return r.stdout?.on("data",l=>e.take(l)),r.stderr?.on("data",l=>{const i=l;o.stderrTail?(n+=i.length,t=i.length>=o.stderrBytes?i.subarray(i.length-o.stderrBytes):Buffer.concat([t,i]).subarray(Math.max(0,t.length+i.length-o.stderrBytes))):s.take(i)}),new Promise(l=>r.once("close",(i,f)=>{const a=e.result(),m=o.stderrTail?{text:t.toString("utf8"),truncated:n>o.stderrBytes}:s.result();l({exitCode:i,signal:f,stdout:a.text,stderr:m.text,stdoutTruncated:a.truncated,stderrTruncated:m.truncated,stdoutSha256:c.digest("hex")})}))},"captureExit"),V=h(r=>{const p=new Set;for(;r>1&&!p.has(r);){const o=I.get(r);if(o)return o;p.add(r);try{const c=ee(`/proc/${r}/status`,"utf8");r=Number(/^PPid:\s*(\d+)/m.exec(c)?.[1]??0)}catch{return null}}return null},"sandboxForPid");let g,C=!1;const z=E(F(oe(import.meta.url)),"../daemon/crtrd-cli.js"),U=h(()=>{C||(g=W("/bin/setpriv",["--reuid",String(L),"--regid",String(x),"--groups",String(M),"--",process.execPath,z,...process.argv.slice(2)],{stdio:"inherit",env:{...process.env,CRTR_LAUNCHER_SOCKET:S,CRTR_HOME:v.root}}),g.pid&&d.protectDaemon(g.pid,we),g.once("exit",()=>{g=void 0,C||U()}),g.once("error",r=>{console.error("crtrd spawn failed:",r),process.exitCode=1,C=!0}))},"startDaemon");Z(S)&&te(S);const O=ne({pauseOnConnect:!0},r=>ce(r,p=>{const o=[];return(async()=>{try{const c=P.peerCred(J(r)).uid;if(c!==L&&c!==0)throw new u("invalid_spec","launcher peer is not crtrd or root");let w;for(;;)try{w=P.recvFds(J(r));break}catch(t){if(t.code!=="EINTR")throw t}p(),o.push(...w.fds);const e=JSON.parse(w.data);let s;switch(e.operation){case"launch":{if(o.length>1)throw new u("invalid_uid","app uid is not registered");let t,n,l;try{n=await B.launch(async()=>{if(!b.has(e.spec.uid))throw new u("invalid_uid","app uid is not registered");const i=l=d.prepareRun(e.spec.uid,e.spec.nodeId,e.spec.broker===!0);return ae(e.spec,o[0],v,M,f=>{if(!f.pid)throw new u("launch_failed","bwrap has no pid");t=f.pid;const a=f.pid;if(I.set(a,{child:f,uid:e.spec.uid,nodeId:e.spec.nodeId,mounts:e.spec.mounts,plan:i}),f.once("exit",()=>{I.delete(a);const m=d.finishRun(i);G.set(a,m.oomKilled),setTimeout(()=>G.delete(a),5*6e4).unref()}),e.spec.capture){const m=q(f,e.spec).then(y=>({...y,oomKilled:G.get(a)===!0}));R.set(a,m),m.then(()=>{setTimeout(()=>{R.get(a)===m&&R.delete(a)},3e5).unref()})}},i)})}catch(i){throw t!==void 0?R.delete(t):l&&d.finishRun(l),i}if(!n.pid)throw new u("launch_failed","bwrap has no pid");d.confirmStarted(l,n.pid),s={pid:n.pid};break}case"wait":{const t=R.get(e.pid);if(!t)throw new u("invalid_spec","unknown captured sandbox pid");const n=await t;R.delete(e.pid),s={result:n};break}case"stop":{const t=Number.isInteger(e.pid)?V(e.pid):null;if(!t||e.nodeId!==void 0&&t.nodeId!==e.nodeId)throw new u("invalid_spec","unknown sandbox pid");if(e.signal!==void 0&&e.signal!=="SIGTERM"&&e.signal!=="SIGKILL")throw new u("invalid_spec","invalid stop signal");const n=e.signal??"SIGKILL";t.child.kill(n),n==="SIGKILL"&&d.killRun(t.plan),s={ok:!0};break}case"user.create":s={uid:await B.write(()=>b.create(e.app,e.uid))};break;case"user.delete":{await B.write(()=>{if([...I.values()].some(t=>t.uid===e.uid))throw new u("invalid_spec","stop this app\u2019s sandboxes before deleting its user");b.delete(e.uid)}),s={ok:!0};break}case"users.sync":await B.write(()=>b.sync(e.users)),s={ok:!0};break;case"list":s={sandboxes:[...I].map(([t,n])=>({pid:t,uid:n.uid,nodeId:n.nodeId,mounts:n.mounts}))};break;case"limits.apply":{const t=b.entries().find(n=>n.app===e.app.replace(/^app:/,""));if(!t)throw new u("invalid_uid","app is not registered");d.applyApp(t.app,t.uid,!0),s={ok:!0};break}case"memory.admit":{const t=b.entries().find(n=>n.app===e.app.replace(/^app:/,""));if(!t)throw new u("invalid_uid","app is not registered");s=d.admitBroker(t.app,e.nodeId);break}case"limits.show":s={limits:d.show()};break;case"disk.usage":s={disk:await d.diskUsageReport()};break;case"cache.purge":s={purged:await d.cachePass()};break;case"exit.status":s={oomKilled:G.get(e.pid)===!0};break;case"daemon.handover":g?.kill("SIGTERM"),s={ok:!0};break;default:throw new u("invalid_spec","unknown launcher operation")}return`${JSON.stringify(j(s))}
1
+ var Q=Object.defineProperty;var h=(_,k)=>Q(_,"name",{value:k,configurable:!0});import{spawn as W}from"node:child_process";import{createHash as X}from"node:crypto";import{chownSync as K,chmodSync as H,closeSync as Y,existsSync as Z,mkdirSync as $,readFileSync as ee,rmSync as te,statSync as re}from"node:fs";import{createServer as ne}from"node:net";import{dirname as F,join as E}from"node:path";import{fileURLToPath as oe}from"node:url";import{loadLinuxNative as se}from"../native/linux.js";import{envHomeOverride as ie}from"../shared/env.js";import{launchSandbox as ae}from"./bwrap.js";import{LaunchGate as de}from"./launch-gate.js";import{serveAccepted as ce}from"./accepted-socket.js";import{installNetworkRule as ue}from"./netrule.js";import{LAUNCHER_SOCKET as S,LaunchRefused as u}from"./spec.js";import{systemIdentity as pe,AppUsers as le}from"./users.js";import{SourceValidator as fe}from"./validate.js";import{prepareDaemonScopeDirectories as me}from"./scope-directories.js";import{LimitsManager as he}from"./limits/manager.js";import{CRTRD_OOM_SCORE_ADJ as we}from"./limits/policy.js";import{writeLimitsLog as ge}from"./limits/log.js";const P=se();function J(_){const k=_._handle?.fd;if(k===void 0||k<0)throw new Error("launcher socket fd unavailable");return k}h(J,"socketFd");async function Pe(_={}){const k=_.superviseDaemon??!0;if(process.platform!=="linux"||process.getuid?.()!==0||!P)throw new Error("launcher requires Linux root and its native addon");const{daemonUid:L,daemonGid:x,dataGid:M}=pe(),T=F(S);if($(T,{recursive:!0,mode:493}),(re(T).mode&511)!==493)throw new Error(`${T} must be mode 0755`);K(T,L,x),process.umask(7);const N=E(T,"nodes");$(N,{recursive:!0,mode:504}),K(N,0,x),H(N,504);const v=new fe(ie()||"/",x);me(E(v.root,"runtime"),L,x);const b=new le(M);ue();const d=new he({runtimeRoot:v.runtime,appsRoot:E(v.root,"apps"),stateDir:E(v.runtime,"limits-state"),dataGid:M,users:b});d.init();const G=new Map,j=h(r=>{const p=d.takeEvents();for(const o of p)ge(o);return p.length===0?r:{...r,events:p}},"reply_"),B=new de,I=new Map,R=new Map,q=h((r,p)=>{const o=p.capture,c=X("sha256"),w=h((l,i)=>{let f=0,a=!1;const m=[];return{take(y){i?.update(y);const D=Math.max(0,Math.min(y.length,l-f));D&&(m.push(y.subarray(0,D)),f+=D),D!==y.length&&(a=!0)},result:h(()=>({text:Buffer.concat(m).toString("utf8"),truncated:a}),"result")}},"chunks"),e=w(o.stdoutBytes,c),s=w(o.stderrBytes);let t=Buffer.alloc(0),n=0;return r.stdout?.on("data",l=>e.take(l)),r.stderr?.on("data",l=>{const i=l;o.stderrTail?(n+=i.length,t=i.length>=o.stderrBytes?i.subarray(i.length-o.stderrBytes):Buffer.concat([t,i]).subarray(Math.max(0,t.length+i.length-o.stderrBytes))):s.take(i)}),new Promise(l=>r.once("close",(i,f)=>{const a=e.result(),m=o.stderrTail?{text:t.toString("utf8"),truncated:n>o.stderrBytes}:s.result();l({exitCode:i,signal:f,stdout:a.text,stderr:m.text,stdoutTruncated:a.truncated,stderrTruncated:m.truncated,stdoutSha256:c.digest("hex")})}))},"captureExit"),V=h(r=>{const p=new Set;for(;r>1&&!p.has(r);){const o=I.get(r);if(o)return o;p.add(r);try{const c=ee(`/proc/${r}/status`,"utf8");r=Number(/^PPid:\s*(\d+)/m.exec(c)?.[1]??0)}catch{return null}}return null},"sandboxForPid");let g,C=!1;const z=E(F(oe(import.meta.url)),"../daemon/crtrd-cli.js"),U=h(()=>{C||(g=W("/bin/setpriv",["--reuid",String(L),"--regid",String(x),"--groups",String(M),"--",process.execPath,z,...process.argv.slice(2)],{stdio:"inherit",env:{...process.env,CRTR_LAUNCHER_SOCKET:S,CRTR_HOME:v.root}}),g.pid&&d.protectDaemon(g.pid,we),g.once("exit",()=>{g=void 0,C||U()}),g.once("error",r=>{console.error("crtrd spawn failed:",r),process.exitCode=1,C=!0}))},"startDaemon");Z(S)&&te(S);const O=ne({pauseOnConnect:!0},r=>ce(r,p=>{const o=[];return(async()=>{try{const c=P.peerCred(J(r)).uid;if(c!==L&&c!==0)throw new u("invalid_spec","launcher peer is not crtrd or root");let w;for(;;)try{w=P.recvFds(J(r));break}catch(t){if(t.code!=="EINTR")throw t}p(),o.push(...w.fds);const e=JSON.parse(w.data);let s;switch(e.operation){case"launch":{if(o.length>1)throw new u("invalid_uid","app uid is not registered");let t,n,l;try{n=await B.launch(async()=>{if(!b.has(e.spec.uid))throw new u("invalid_uid","app uid is not registered");const i=l=d.prepareRun(e.spec.uid,e.spec.nodeId,e.spec.broker===!0);return ae(e.spec,o[0],v,M,f=>{if(!f.pid)throw new u("launch_failed","bwrap has no pid");t=f.pid;const a=f.pid;if(I.set(a,{child:f,uid:e.spec.uid,nodeId:e.spec.nodeId,mounts:e.spec.mounts,plan:i}),f.once("exit",()=>{I.delete(a);const m=d.finishRun(i);G.set(a,m.oomKilled),setTimeout(()=>G.delete(a),5*6e4).unref()}),e.spec.capture){const m=q(f,e.spec).then(y=>({...y,oomKilled:G.get(a)===!0}));R.set(a,m),m.then(()=>{setTimeout(()=>{R.get(a)===m&&R.delete(a)},3e5).unref()})}},i)})}catch(i){throw t!==void 0?R.delete(t):l&&d.finishRun(l),i}if(!n.pid)throw new u("launch_failed","bwrap has no pid");d.confirmStarted(l,n.pid),s={pid:n.pid};break}case"wait":{const t=R.get(e.pid);if(!t)throw new u("invalid_spec","unknown captured sandbox pid");const n=await t;r.writable&&R.delete(e.pid),s={result:n};break}case"stop":{const t=Number.isInteger(e.pid)?V(e.pid):null;if(!t||e.nodeId!==void 0&&t.nodeId!==e.nodeId)throw new u("invalid_spec","unknown sandbox pid");if(e.signal!==void 0&&e.signal!=="SIGTERM"&&e.signal!=="SIGKILL")throw new u("invalid_spec","invalid stop signal");const n=e.signal??"SIGKILL";t.child.kill(n),n==="SIGKILL"&&d.killRun(t.plan),s={ok:!0};break}case"user.create":s={uid:await B.write(()=>b.create(e.app,e.uid))};break;case"user.delete":{await B.write(()=>{if([...I.values()].some(t=>t.uid===e.uid))throw new u("invalid_spec","stop this app\u2019s sandboxes before deleting its user");b.delete(e.uid)}),s={ok:!0};break}case"users.sync":await B.write(()=>b.sync(e.users)),s={ok:!0};break;case"list":s={sandboxes:[...I].map(([t,n])=>({pid:t,uid:n.uid,nodeId:n.nodeId,mounts:n.mounts}))};break;case"limits.apply":{const t=b.entries().find(n=>n.app===e.app.replace(/^app:/,""));if(!t)throw new u("invalid_uid","app is not registered");d.applyApp(t.app,t.uid,!0),s={ok:!0};break}case"memory.admit":{const t=b.entries().find(n=>n.app===e.app.replace(/^app:/,""));if(!t)throw new u("invalid_uid","app is not registered");s=d.admitBroker(t.app,e.nodeId);break}case"limits.show":s={limits:d.show()};break;case"disk.usage":s={disk:await d.diskUsageReport()};break;case"cache.purge":s={purged:await d.cachePass()};break;case"exit.status":s={oomKilled:G.get(e.pid)===!0};break;case"daemon.handover":g?.kill("SIGTERM"),s={ok:!0};break;default:throw new u("invalid_spec","unknown launcher operation")}return`${JSON.stringify(j(s))}
2
2
  `}catch(c){const w=c instanceof u?c.refusal:"launch_failed";return console.error(`launcher refusal ${w}:`,c),`${JSON.stringify({refusal:w})}
3
3
  `}finally{for(const c of o)Y(c)}})()}));await new Promise((r,p)=>{O.once("error",p),O.listen(S,r)}),K(S,L,x),H(S,384);const A=h(()=>{C=!0,g?.kill("SIGTERM");for(const{child:r}of I.values())r.kill("SIGKILL");O.close(),v.close()},"shutdown");process.once("SIGTERM",A),process.once("SIGINT",A),k&&U()}h(Pe,"runLauncher");export{Pe as runLauncher};
@@ -19,7 +19,7 @@ await writeFile('acme.tar', bundle.tar);
19
19
 
20
20
  `buildBundle` returns `commandsJson`, `bundleJson`, generated `memory/` members, uncompressed tar bytes, and an ETag. It runs the manifest validator and throws `ManifestInvalidError` when the generated command manifest is invalid. `buildCommandManifest(plugin, { mountPath })` returns the manifest object when archive bytes are not needed.
21
21
 
22
- The tar has exactly `bundle.json`, `commands.json`, and `memory/<name>.md` members for the documents declared in the `memory` array of `definePlugin`. `bundle.json` carries `bundleVersion`, plus `version` when the plugin declares one, `object_kinds` when it declares object kinds, and `groups` when a provider declares its tool groups (the directory refuses a provider archive without them). `groups` is keyed by group id, each value `{"consent_line": string, "who"?: "own" | "user" | "allowlist" | "any", "allowlist"?: string[]}`, with `who` and `allowlist` written only when declared:
22
+ The tar has exactly `bundle.json`, `commands.json`, and `memory/<name>.md` members for the documents declared in the `memory` array of `definePlugin`. `bundle.json` carries `bundleVersion`, plus `version` when the plugin declares one, `object_kinds` when it declares object kinds, and `groups` when a provider declares its tool groups (the directory refuses a provider archive without them). `groups` is keyed by group id, each value `{"consent_line": string, "who"?: "own" | "user" | "allowlist" | "any" | "team:<id>", "allowlist"?: string[]}`, with `who` and `allowlist` written only when declared:
23
23
 
24
24
  ```json
25
25
  {"bundleVersion":1,"version":1,"groups":{"read":{"consent_line":"Read your Acme apps and deployments."},"deploy":{"consent_line":"Start deployments on your Acme apps.","who":"allowlist","allowlist":["app:k3x9"]}}}
@@ -30,7 +30,7 @@ definePlugin({
30
30
  });
31
31
  ```
32
32
 
33
- Each group has `consentLine`, the line the directory's consent page shows, and optionally `who` (`own`, `user`, `allowlist` or `any`; the directory treats an omitted `who` as `user`) and `allowlist` (app principals `app:<id>`, required with `who: 'allowlist'` and refused otherwise). `buildBundle` writes the block into `bundle.json` as `groups`, and the directory applies it when it refreshes the provider's manifest. The directory refuses a provider archive whose `bundle.json` has no `groups` (`bundle.json groups: is required`), and a provider's groups come only from this declaration: the directory's console and CLI do not edit groups. `definePlugin` itself accepts a provider without `groups`, but once declared every leaf's `group` must name a declared group and every declared group must be used by at least one leaf; `definePlugin` throws `PluginDefinitionError` otherwise. A plugin that is not `provider: true` must not declare `groups`.
33
+ Each group has `consentLine`, the line the directory's consent page shows, and optionally `who` (`own`, `user`, `allowlist`, `any` or `team:<id>`; the directory treats an omitted `who` as `user`) and `allowlist` (app principals `app:<id>`, required with `who: 'allowlist'` and refused otherwise). `buildBundle` writes the block into `bundle.json` as `groups`, and the directory applies it when it refreshes the provider's manifest. The directory refuses a provider archive whose `bundle.json` has no `groups` (`bundle.json groups: is required`), and a provider's groups come only from this declaration: the directory's console and CLI do not edit groups. `definePlugin` itself accepts a provider without `groups`, but once declared every leaf's `group` must name a declared group and every declared group must be used by at least one leaf; `definePlugin` throws `PluginDefinitionError` otherwise. A plugin that is not `provider: true` must not declare `groups`.
34
34
 
35
35
  A branch or leaf may set `tier` to `common` or `important` to be promoted into its parent's `-h`. A command with more than 40 children lists only its promoted children in its own `-h`, followed by the count of the rest; the others still run and answer `-h` by name. Use this when a plugin mounts one command per entry of a large catalog. On such a command only promoted leaves reach the top-level `crtr -h`; a promoted branch shows in the command's own `-h`.
36
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.528",
3
+ "version": "0.3.530",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -116,7 +116,7 @@
116
116
  "link": "npm link",
117
117
  "prepublishOnly": "npm run build",
118
118
  "test": "CRTR_NO_DAEMON_AUTOSTART=1 node --conditions=crtr-src --import tsx/esm --import ./scripts/test-env-scrub.mjs --test --test-concurrency=$(node -p 'Math.max(4, require(`node:os`).availableParallelism() - 1)') --test-timeout=10000 ${CRTR_TEST_SHARD:+--test-shard=$CRTR_TEST_SHARD} 'src/**/__tests__/*.test.ts'",
119
- "test:integration": "npm run build:api && CRTR_NO_DAEMON_AUTOSTART=1 node --conditions=crtr-src --import tsx/esm --import ./scripts/test-env-scrub.mjs --test --test-concurrency=$(node -p 'Math.min(8, Math.max(2, require(`node:os`).availableParallelism() - 1))') --test-timeout=60000 ${CRTR_TEST_SHARD:+--test-shard=$CRTR_TEST_SHARD} 'src/**/__tests__/integration/*.test.ts' 'packages/crouter-sdk/src/**/__tests__/integration/*.test.ts' 'packages/crouter-plugin/src/**/__tests__/integration/*.test.ts'",
119
+ "test:integration": "npm run build:sdk && CRTR_NO_DAEMON_AUTOSTART=1 node --conditions=crtr-src --import tsx/esm --import ./scripts/test-env-scrub.mjs --test --test-concurrency=$(node -p 'Math.min(8, Math.max(2, require(`node:os`).availableParallelism() - 1))') --test-timeout=60000 ${CRTR_TEST_SHARD:+--test-shard=$CRTR_TEST_SHARD} 'src/**/__tests__/integration/*.test.ts' 'packages/crouter-sdk/src/**/__tests__/integration/*.test.ts' 'packages/crouter-plugin/src/**/__tests__/integration/*.test.ts'",
120
120
  "test:seam": "npm run build && npm run test:seam:built",
121
121
  "test:sdk": "npm run build:api && npm run build:identity && node --conditions=crtr-src --import tsx/esm --test --test-timeout=60000 'packages/crouter-sdk/src/__tests__/*.test.ts' 'packages/crouter-sdk/src/__tests__/integration/*.test.ts'",
122
122
  "test:sdk:pg": "node -e \"if (!process.env.TEST_DATABASE_URL) { console.error('test:sdk:pg needs TEST_DATABASE_URL (a disposable PostgreSQL), e.g. docker run --rm -d -e POSTGRES_PASSWORD=pw -p 127.0.0.1:55432:5432 postgres:18-alpine; TEST_DATABASE_URL=postgres://postgres:pw@127.0.0.1:55432/postgres'); process.exit(1) }\" && node --conditions=crtr-src --import tsx/esm --test --test-timeout=60000 'packages/crouter-sdk/src/**/__tests__/**/*.pg.test.ts'",
@@ -139,7 +139,7 @@
139
139
  },
140
140
  "license": "UNLICENSED",
141
141
  "dependencies": {
142
- "@north-light/identity": "0.3.528",
142
+ "@north-light/identity": "0.3.530",
143
143
  "@earendil-works/pi-agent-core": "1.1.0",
144
144
  "@earendil-works/pi-ai": "1.1.0",
145
145
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.528",
3
+ "version": "0.3.530",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",