@north-light/cli 0.3.593 → 0.3.595

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.
@@ -64,4 +64,4 @@ var oe=Object.defineProperty;var o=(e,n)=>oe(e,"name",{value:n,configurable:!0})
64
64
  JOIN objects d ON d.id = w.object AND d.type = 'document' AND d.deleted_at IS NULL
65
65
  JOIN objects o ON o.id = w.watcher AND o.deleted_at IS NULL
66
66
  LEFT JOIN nodes n ON n.node_id = w.watcher
67
- WHERE w.watcher != d.owner AND d.owner IS NOT NULL AND (n.node_id IS NULL OR n.status IN ('active', 'idle'))`).all();return new Set(e.map(n=>n.owner))}o(Xe,"ownersOfWatchedDocuments");async function te(e){const t=(await s()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ?"),a=new Set(e.map(i=>i.node_id));return await m(e,async i=>(await t.all(i.node_id,i.node_id)).every(r=>a.has(r.node_id)))}o(te,"retainRunRootsWithMembers");async function ae(e,n){if(e.length===0)return;const t=await O(e,async i=>await I(i.node_id)),a=await U(e.map(i=>i.node_id));for(const i of e)await p(async r=>await j(r,i.node_id));if(await x(a),n!==void 0){for(const i of t)await F(i,async()=>{await Z(i),ne(i)});n(t.flatMap(ee));return}for(const i of t)await F(i,async()=>await W(i))}o(ae,"deletePrunedNodes");async function F(e,n){try{n()}catch(t){Ee({level:"error",event:"canvas.node_files.remove_failed",error:t,fields:{node_id:e.nodeId}})}}o(F,"eachNodeBestEffort");function ie(e){let n=0;for(const t of[e.created,e.launched_at,e.last_settled_at,e.finalized_at,e.outcome_at,e.last_activity_at]){if(t==null)continue;const a=Date.parse(t);Number.isFinite(a)&&a>n&&(n=a)}try{const t=ue(S(Ue(e.node_id),"telemetry.json"),{throwIfNoEntry:!1});t!==void 0&&t.mtimeMs>n&&(n=t.mtimeMs)}catch{}return n}o(ie,"lastActivityMs");async function ht(e){const n=e.dryRun??!1,t=await Ye(),a=await m(t,async u=>await X(u)&&!await e.held(u.node_id)),i=t.length-a.length,r=Math.max(0,e.maxNodes-i),d=a.map(u=>({row:u,recency:ie(u)})).sort((u,_)=>_.recency-u.recency).slice(r).map(({row:u})=>({node_id:u.node_id,status:u.status,created:u.created})),l=await te(d);return n||l.length===0?{pruned:l,dryRun:n}:(await ae(l),{pruned:l,dryRun:n})}o(ht,"pruneToLimit");export{K as PARKED_RESIDENT_SQL,Ke as RESUMABLE_NODE_SQL,Fn as abandonCreateRequest,gn as assertRunNotDeleting,at as browseNodes,bn as canonicalSessionFile,wt as childrenOf,ft as claimWarmSpare,Vn as clearFinalReportIfMatches,et as clearFrozen,Wn as completeCreateRequest,An as createNode,Rt as deleteNode,St as deleteNodes,Pn as dueNodeDeadlines,kn as findCreateRequest,Cn as findNodeBySessionFile,Qn as getFinalReport,h as getNode,vn as getNodes,Hn as getRow,Mn as getRowByPane,ot as graphNodes,X as isCollectible,pt as isWarmSpare,ie as lastActivityMs,Ye as listNodes,tt as listNodesByProfile,st as listNodesForSupervision,ut as listParkedResidentsWithWakeMail,mt as listResumableRoots,rt as listTerminalPlacementResidue,_t as listWarmSpares,I as nodeFiles,Ve as nodesForSupervisionSql,xn as pruneExpiredCreateRequests,Ot as pruneNodes,ht as pruneToLimit,jn as recordCreateRequestNode,Gn as recordLaunch,nt as recordRespawnAttempt,Xn as recordSettledTurn,lt as registerWarmSpare,W as removeNodeFiles,Un as reserveCreateRequest,In as reserveCreateRequestNode,dt as resumableNodeCwds,yt as rosterNodes,Zn as setFrozen,Jn as setIntent,$n as setNodeDeadline,Kn as setPresence,zn as setStatus,qn as shiftProfileNodeDeadlines,Qe as terminalPlacementResidueSql,Yn as transitionStatusIntent,ct as unregisterWarmSpare,Bn as updateNode,it as updateNodeTelemetry,Et as warmSpareCount,Nt as withFreshTerminalGuard};
67
+ WHERE w.watcher != d.owner AND d.owner IS NOT NULL AND (n.node_id IS NULL OR n.status IN ('active', 'idle'))`).all();return new Set(e.map(n=>n.owner))}o(Xe,"ownersOfWatchedDocuments");async function te(e){const t=(await s()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ?"),a=new Set(e.map(i=>i.node_id));return await m(e,async i=>(await t.all(i.node_id,i.node_id)).every(r=>a.has(r.node_id)))}o(te,"retainRunRootsWithMembers");async function ae(e,n){if(e.length===0)return;const t=await O(e,async i=>await I(i.node_id)),a=await U(e.map(i=>i.node_id));for(const i of e)await p(async r=>await j(r,i.node_id));if(await x(a),n!==void 0){for(const i of t)await F(i,async()=>{await Z(i),ne(i)});n(t.flatMap(ee));return}for(const i of t)await F(i,async()=>await W(i))}o(ae,"deletePrunedNodes");async function F(e,n){try{await n()}catch(t){Ee({level:"error",event:"canvas.node_files.remove_failed",error:t,fields:{node_id:e.nodeId}})}}o(F,"eachNodeBestEffort");function ie(e){let n=0;for(const t of[e.created,e.launched_at,e.last_settled_at,e.finalized_at,e.outcome_at,e.last_activity_at]){if(t==null)continue;const a=Date.parse(t);Number.isFinite(a)&&a>n&&(n=a)}try{const t=ue(S(Ue(e.node_id),"telemetry.json"),{throwIfNoEntry:!1});t!==void 0&&t.mtimeMs>n&&(n=t.mtimeMs)}catch{}return n}o(ie,"lastActivityMs");async function ht(e){const n=e.dryRun??!1,t=await Ye(),a=await m(t,async u=>await X(u)&&!await e.held(u.node_id)),i=t.length-a.length,r=Math.max(0,e.maxNodes-i),d=a.map(u=>({row:u,recency:ie(u)})).sort((u,_)=>_.recency-u.recency).slice(r).map(({row:u})=>({node_id:u.node_id,status:u.status,created:u.created})),l=await te(d);return n||l.length===0?{pruned:l,dryRun:n}:(await ae(l),{pruned:l,dryRun:n})}o(ht,"pruneToLimit");export{K as PARKED_RESIDENT_SQL,Ke as RESUMABLE_NODE_SQL,Fn as abandonCreateRequest,gn as assertRunNotDeleting,at as browseNodes,bn as canonicalSessionFile,wt as childrenOf,ft as claimWarmSpare,Vn as clearFinalReportIfMatches,et as clearFrozen,Wn as completeCreateRequest,An as createNode,Rt as deleteNode,St as deleteNodes,Pn as dueNodeDeadlines,kn as findCreateRequest,Cn as findNodeBySessionFile,Qn as getFinalReport,h as getNode,vn as getNodes,Hn as getRow,Mn as getRowByPane,ot as graphNodes,X as isCollectible,pt as isWarmSpare,ie as lastActivityMs,Ye as listNodes,tt as listNodesByProfile,st as listNodesForSupervision,ut as listParkedResidentsWithWakeMail,mt as listResumableRoots,rt as listTerminalPlacementResidue,_t as listWarmSpares,I as nodeFiles,Ve as nodesForSupervisionSql,xn as pruneExpiredCreateRequests,Ot as pruneNodes,ht as pruneToLimit,jn as recordCreateRequestNode,Gn as recordLaunch,nt as recordRespawnAttempt,Xn as recordSettledTurn,lt as registerWarmSpare,W as removeNodeFiles,Un as reserveCreateRequest,In as reserveCreateRequestNode,dt as resumableNodeCwds,yt as rosterNodes,Zn as setFrozen,Jn as setIntent,$n as setNodeDeadline,Kn as setPresence,zn as setStatus,qn as shiftProfileNodeDeadlines,Qe as terminalPlacementResidueSql,Yn as transitionStatusIntent,ct as unregisterWarmSpare,Bn as updateNode,it as updateNodeTelemetry,Et as warmSpareCount,Nt as withFreshTerminalGuard};
@@ -1,2 +1,6 @@
1
1
  /** Runtime spec §15 "On removal" (design flow 10). Audit and usage rows stay. */
2
2
  export declare function removeApp(app: string, sub?: string): Promise<void>;
3
+ /** What a fleet does when a person's grant of `app` is removed and the app itself stays: that person's nodes, crons, questions,
4
+ * sandboxes, mail, upload uses and node files go. Never the app's shared parts (shares, uploads, space, env, OS user), even for
5
+ * the last person. This is the one removal that accepts `app:terminal`, whose whole-app removal `removeApp` refuses. */
6
+ export declare function removePersonData(app: string, sub: string): Promise<void>;
@@ -1 +1 @@
1
- var _=Object.defineProperty;var E=(r,o)=>_(r,"name",{value:o,configurable:!0});import{rmSync as v}from"node:fs";import{join as S}from"node:path";import{deleteNodes as h}from"../canvas/canvas.js";import{withCanvasWrite as c,openDb as d}from"../canvas/db.js";import{cancelCron as O}from"../canvas/crons.js";import{emitEvent as p}from"../events/emit.js";import{appSpace as T,appsRoot as w,secretsDir as C}from"../layout.js";import{notifyRunDeleted as D}from"../runs/events.js";import{cancelOpenQuestions as L}from"../runs/questions.js";import{removeTreeBeneath as W}from"../spaces/open-beneath.js";import{markGranteeUploadsDeletePending as F,releaseRunUploads as H,turnSharesOffFor as N}from"../storage/tables.js";import{stopNodeSandboxes as M}from"../spaces/stop.js";import{activeAppUser as $,markAppUserRemoved as y}from"../spaces/users.js";import{launcherRequest as A}from"../../launcher/client.js";import{LaunchRefused as U}from"../../launcher/spec.js";import{localGrants as q}from"./mirror.js";import{launcherPresent as u,listSandboxes as I,stopSandboxPid as P}from"./sandboxes.js";import{filterAsync as B}from"../async-array.js";async function se(r,o){if(r==="app:terminal")throw new Error("app:terminal cannot be removed");const m=T(r),s=o===void 0||!(await q()).some(e=>e.grantee===r&&e.sub!==o&&e.status!=="removed"),i=s?await $(r):null;p({level:"info",event:"daemon.grants.removal_started",fields:{grantee:r,...o===void 0?{}:{sub:o},uid:i?.uid??null}});const t=o===void 0?{sql:"grantee = ?",args:[r]}:{sql:"grantee = ? AND sub = ?",args:[r,o]};for(const{node_id:e}of await(await d()).prepare(`SELECT node_id FROM node_rows WHERE ${t.sql} ORDER BY created DESC`).all(...t.args))await L({node_id:e},"the app was removed");const a=await c(async e=>(await e.prepare(`UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id IN (SELECT node_id FROM node_rows WHERE ${t.sql})`).run(new Date().toISOString(),...t.args),(await e.prepare(`SELECT node_id FROM node_rows WHERE ${t.sql} ORDER BY created DESC`).all(...t.args)).map(n=>n.node_id)));for(const{cron_id:e}of await(await d()).prepare(`SELECT cron_id FROM crons WHERE ${t.sql}`).all(...t.args))await O(e);const l=(await Promise.all(a.map(e=>M(e,"app_removed",{deleting:!0})))).filter(e=>!e.confirmed);if(l.length)throw new Error(`sandbox teardown could not be confirmed for ${l.length} node(s) of ${r}`);if(i&&u())for(const e of await I())e.uid===i.uid&&await P(e.pid);const R=await B(a,async e=>(await(await d()).prepare("SELECT run_id FROM nodes WHERE node_id = ?").get(e))?.run_id===e);await c(async e=>{if(s&&await N(r,e),await H(a,e),s&&await F(r,e),!a.length)return;const n=a.map(()=>"?").join(","),g=await e.prepareUncached(`SELECT message_id FROM messages WHERE sender_node_id IN (${n}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${n})`).all(...a,...a);await e.prepareUncached(`DELETE FROM deliveries WHERE destination_id IN (${n})`).run(...a);for(const{message_id:f}of g)await e.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(f,f)}),await h(a);for(const e of R)D(e);if(s&&(v(S(C(),"env",m.root.slice(w().length+1)),{recursive:!0,force:!0}),W(w(),m.root)),i&&u())try{await A({operation:"user.delete",uid:i.uid})}catch(e){if(!(e instanceof U&&e.refusal==="invalid_uid"))throw e}i&&await y(i.uid),p({level:"info",event:"daemon.grants.removal_completed",fields:{grantee:r,...o===void 0?{}:{sub:o},uid:i?.uid??null,nodes:a.length}})}E(se,"removeApp");export{se as removeApp};
1
+ var h=Object.defineProperty;var l=(r,o)=>h(r,"name",{value:o,configurable:!0});import{rmSync as O}from"node:fs";import{join as D}from"node:path";import{deleteNodes as T,nodeFiles as C,removeNodeFiles as F}from"../canvas/canvas.js";import{hasUnreconciledWorktree as W}from"../canvas/worktrees.js";import{withCanvasWrite as p,openDb as m}from"../canvas/db.js";import{cancelCron as L}from"../canvas/crons.js";import{emitEvent as w}from"../events/emit.js";import{appSpace as N,appsRoot as u,secretsDir as H}from"../layout.js";import{notifyRunDeleted as y}from"../runs/events.js";import{cancelOpenQuestions as M}from"../runs/questions.js";import{removeTreeBeneath as $}from"../spaces/open-beneath.js";import{markGranteeUploadsDeletePending as U,releaseRunUploads as A,turnSharesOffFor as q}from"../storage/tables.js";import{stopNodeSandboxes as I}from"../spaces/stop.js";import{activeAppUser as P,markAppUserRemoved as k}from"../spaces/users.js";import{launcherRequest as b}from"../../launcher/client.js";import{LaunchRefused as j}from"../../launcher/spec.js";import{localGrants as B}from"./mirror.js";import{launcherPresent as g,listSandboxes as x,stopSandboxPid as G}from"./sandboxes.js";import{filterAsync as R}from"../async-array.js";async function Ee(r,o){if(r==="app:terminal")throw new Error("app:terminal cannot be removed");const s=o===void 0||!(await B()).some(d=>d.grantee===r&&d.sub!==o&&d.status!=="removed");await _(r,o,s)}l(Ee,"removeApp");async function pe(r,o){await _(r,o,!1,!0)}l(pe,"removePersonData");async function _(r,o,s,d=!1){const f=N(r),t=s?await P(r):null;w({level:"info",event:"daemon.grants.removal_started",fields:{grantee:r,...o===void 0?{}:{sub:o},uid:t?.uid??null}});const i=o===void 0?{sql:"grantee = ?",args:[r]}:{sql:"grantee = ? AND sub = ?",args:[r,o]};for(const{node_id:e}of await(await m()).prepare(`SELECT node_id FROM node_rows WHERE ${i.sql} ORDER BY created DESC`).all(...i.args))await M({node_id:e},"the app was removed");const a=await p(async e=>(await e.prepare(`UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id IN (SELECT node_id FROM node_rows WHERE ${i.sql})`).run(new Date().toISOString(),...i.args),(await e.prepare(`SELECT node_id FROM node_rows WHERE ${i.sql} ORDER BY created DESC`).all(...i.args)).map(n=>n.node_id)));for(const{cron_id:e}of await(await m()).prepare(`SELECT cron_id FROM crons WHERE ${i.sql}`).all(...i.args))await L(e);const c=(await Promise.all(a.map(e=>I(e,"app_removed",{deleting:!0})))).filter(e=>!e.confirmed);if(c.length)throw new Error(`sandbox teardown could not be confirmed for ${c.length} node(s) of ${r}`);if(t&&g())for(const e of await x())e.uid===t.uid&&await G(e.pid);const v=await R(a,async e=>(await(await m()).prepare("SELECT run_id FROM nodes WHERE node_id = ?").get(e))?.run_id===e);if(await p(async e=>{if(s&&await q(r,e),await A(a,e),s&&await U(r,e),!a.length)return;const n=a.map(()=>"?").join(","),S=await e.prepareUncached(`SELECT message_id FROM messages WHERE sender_node_id IN (${n}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${n})`).all(...a,...a);await e.prepareUncached(`DELETE FROM deliveries WHERE destination_id IN (${n})`).run(...a);for(const{message_id:E}of S)await e.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(E,E)}),d){const e=await R(a,W);if(e.length)throw new Error(`refusing to delete nodes with unreconciled managed worktrees: ${e.join(", ")}`);for(const n of a)await F(await C(n))}await T(a);for(const e of v)y(e);if(s&&(O(D(H(),"env",f.root.slice(u().length+1)),{recursive:!0,force:!0}),$(u(),f.root)),t&&g())try{await b({operation:"user.delete",uid:t.uid})}catch(e){if(!(e instanceof j&&e.refusal==="invalid_uid"))throw e}t&&await k(t.uid),w({level:"info",event:"daemon.grants.removal_completed",fields:{grantee:r,...o===void 0?{}:{sub:o},uid:t?.uid??null,nodes:a.length}})}l(_,"cascade");export{Ee as removeApp,pe as removePersonData};
@@ -22,7 +22,7 @@ export interface HomeDeps {
22
22
  machines(): Promise<Machine[]>;
23
23
  self(): string;
24
24
  now(): number;
25
- /** Whether this daemon belongs to an account at all; an unjoined daemon has no peers, so nothing is ever "elsewhere". */
25
+ /** Whether this daemon has peer machines to ask; one without them has none, so nothing is ever "elsewhere". */
26
26
  joined(): boolean;
27
27
  }
28
28
  export declare const defaultHomeDeps: HomeDeps;
@@ -1,2 +1,2 @@
1
- var d=Object.defineProperty;var c=(e,n)=>d(e,"name",{value:n,configurable:!0});import{getById as p}from"../graph/objects.js";import{isUnreachable as _,peerCall as y}from"./client.js";import{listMachines as E,normalizeMachineId as w,ownMachineId as x}from"./directory.js";import{directoryIssuer as b}from"../directory/issuer.js";import{stubOf as O}from"./stubs.js";const l={call:c((e,n)=>y(e,n),"call"),machines:E,self:x,now:Date.now,joined:c(()=>b()!==null,"joined")};async function T(e,n){return(await e.prepare("SELECT machine_id FROM peer_locations WHERE id = ?").get(n))?.machine_id??null}c(T,"cachedLocation");async function I(e,n,r,i=Date.now()){await e.prepare(`INSERT INTO peer_locations (id, machine_id, learned_at) VALUES (?, ?, ?)
2
- ON CONFLICT(id) DO UPDATE SET machine_id = excluded.machine_id, learned_at = excluded.learned_at`).run(n,r,i)}c(I,"rememberLocation");async function L(e,n){await e.prepare("DELETE FROM peer_locations WHERE id = ?").run(n)}c(L,"dropLocation");async function g(e,n){if(!e.joined())return[];let r;try{r=await e.machines()}catch(a){return{where:"unknown",unreachable:[`directory (${a instanceof Error?a.message:String(a)})`]}}const i=e.self();return r.filter(a=>a.machine_id!==i&&a.machine_id!==n&&a.kind!=="device")}c(g,"peersToAsk");async function f(e,n,r=l,i){const a=await g(r,i);if("where"in a)return a;const o=await Promise.all(a.map(async t=>{const h=await r.call(t.machine_id,{method:"POST",path:"/v1/peer/locate",body:{id:n}});if(_(h))return{m:t,state:"unreachable"};const s=h.body;return h.status===200&&s?.answer==="here"?{m:t,state:"here",machine:t.machine_id}:h.status===200&&s?.answer==="moved"&&typeof s.machine=="string"?{m:t,state:"here",machine:w(s.machine)}:h.status===200&&s?.answer==="no"?{m:t,state:"no"}:{m:t,state:"unreachable"}})),u=o.find(t=>t.state==="here");if(u&&"machine"in u)return await I(e,n,u.machine,r.now()),{where:"machine",machine:u.machine,source:"locate"};const m=o.filter(t=>t.state==="unreachable").map(t=>t.m.name||t.m.machine_id);return m.length>0?{where:"unknown",unreachable:m}:{where:"nowhere"}}c(f,"locate");async function N(e,n,r={}){const i=await p(e,n);if(i!==null){const o=await O(e,i);return o===null?{where:"here"}:{where:"machine",machine:o.home_machine,source:"stub"}}if(!(r.deps??l).joined())return{where:"nowhere"};if(r.hint)return{where:"machine",machine:w(r.hint),source:"hint"};const a=await T(e,n);return a!==null?{where:"machine",machine:a,source:"cache"}:f(e,n,r.deps)}c(N,"homeOf");async function j(e,n,r,i=l){return await L(e,n),f(e,n,i,r)}c(j,"relocate");export{T as cachedLocation,l as defaultHomeDeps,L as dropLocation,N as homeOf,f as locate,g as peersToAsk,j as relocate,I as rememberLocation};
1
+ var d=Object.defineProperty;var o=(e,n)=>d(e,"name",{value:n,configurable:!0});import{getById as p}from"../graph/objects.js";import{isUnreachable as _,peerCall as y}from"./client.js";import{listMachines as E,normalizeMachineId as w,ownMachineId as x}from"./directory.js";import{directoryIssuer as b}from"../directory/issuer.js";import{isCloudRole as O}from"../canvas/person-state.js";import{stubOf as T}from"./stubs.js";const l={call:o((e,n)=>y(e,n),"call"),machines:E,self:x,now:Date.now,joined:o(()=>!O()&&b()!==null,"joined")};async function I(e,n){return(await e.prepare("SELECT machine_id FROM peer_locations WHERE id = ?").get(n))?.machine_id??null}o(I,"cachedLocation");async function L(e,n,r,i=Date.now()){await e.prepare(`INSERT INTO peer_locations (id, machine_id, learned_at) VALUES (?, ?, ?)
2
+ ON CONFLICT(id) DO UPDATE SET machine_id = excluded.machine_id, learned_at = excluded.learned_at`).run(n,r,i)}o(L,"rememberLocation");async function g(e,n){await e.prepare("DELETE FROM peer_locations WHERE id = ?").run(n)}o(g,"dropLocation");async function D(e,n){if(!e.joined())return[];let r;try{r=await e.machines()}catch(a){return{where:"unknown",unreachable:[`directory (${a instanceof Error?a.message:String(a)})`]}}const i=e.self();return r.filter(a=>a.machine_id!==i&&a.machine_id!==n&&a.kind!=="device")}o(D,"peersToAsk");async function f(e,n,r=l,i){const a=await D(r,i);if("where"in a)return a;const c=await Promise.all(a.map(async t=>{const h=await r.call(t.machine_id,{method:"POST",path:"/v1/peer/locate",body:{id:n}});if(_(h))return{m:t,state:"unreachable"};const s=h.body;return h.status===200&&s?.answer==="here"?{m:t,state:"here",machine:t.machine_id}:h.status===200&&s?.answer==="moved"&&typeof s.machine=="string"?{m:t,state:"here",machine:w(s.machine)}:h.status===200&&s?.answer==="no"?{m:t,state:"no"}:{m:t,state:"unreachable"}})),u=c.find(t=>t.state==="here");if(u&&"machine"in u)return await L(e,n,u.machine,r.now()),{where:"machine",machine:u.machine,source:"locate"};const m=c.filter(t=>t.state==="unreachable").map(t=>t.m.name||t.m.machine_id);return m.length>0?{where:"unknown",unreachable:m}:{where:"nowhere"}}o(f,"locate");async function v(e,n,r={}){const i=await p(e,n);if(i!==null){const c=await T(e,i);return c===null?{where:"here"}:{where:"machine",machine:c.home_machine,source:"stub"}}if(!(r.deps??l).joined())return{where:"nowhere"};if(r.hint)return{where:"machine",machine:w(r.hint),source:"hint"};const a=await I(e,n);return a!==null?{where:"machine",machine:a,source:"cache"}:f(e,n,r.deps)}o(v,"homeOf");async function A(e,n,r,i=l){return await g(e,n),f(e,n,i,r)}o(A,"relocate");export{I as cachedLocation,l as defaultHomeDeps,g as dropLocation,v as homeOf,f as locate,D as peersToAsk,A as relocate,L as rememberLocation};
@@ -0,0 +1,15 @@
1
+ /** The accepted-message id of an app run's launch prompt. Derived from the run id, so the launch paths can find it
2
+ * again without a second record and a replayed creation can never mint a second one. */
3
+ export declare function initialInputMessageId(runId: string): string;
4
+ /** Gives a run's launch prompt the durable message identity a `run.start` `message` already has, inside the run-creation
5
+ * transaction. The launch prompt reaches the model as the kickoff, so its delivery is written already acknowledged:
6
+ * the inbox never hands it over a second time. Its id is what the first turn's model and tool rows name as their cause. */
7
+ export declare function recordInitialInput(runId: string, grantee: string, prompt: string): Promise<void>;
8
+ /** Puts a run root's launch-prompt message id on its first kickoff's env, where the broker reads the ids of the turn's input.
9
+ * A node that is not a run root, or a run started with a `message` (which joins the first turn through the inbox), has none. */
10
+ export declare function carryInitialInput(inv: {
11
+ env: Record<string, string>;
12
+ }, node: {
13
+ node_id: string;
14
+ run_id?: string | null;
15
+ }): Promise<void>;
@@ -0,0 +1 @@
1
+ var d=Object.defineProperty;var n=(e,i)=>d(e,"name",{value:i,configurable:!0});import{createHash as o}from"node:crypto";import{openDb as u}from"../canvas/db.js";import{enqueueNodeMail as c}from"../feed/messages.js";function t(e){return o("sha256").update(`initial-input:${e}`).digest("hex").slice(0,32)}n(t,"initialInputMessageId");async function m(e,i,a){await c(e,{kind:"message",label:a.slice(0,120),senderNodeId:null,senderName:i,bodyJson:{body:a,start_turn:!1},urgency:"deferred",wake:0,messageId:t(e),withinEnqueue:n(async(r,s)=>{await r.prepare("UPDATE deliveries SET state = 'acknowledged', acknowledged_at = ? WHERE message_id = ?").run(Date.now(),s)},"withinEnqueue")})}n(m,"recordInitialInput");async function E(e,i){if(i.run_id!==i.node_id)return;const a=t(i.node_id);await(await u()).prepare("SELECT 1 FROM messages WHERE message_id = ?").get(a)!==void 0&&(e.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify([a]))}n(E,"carryInitialInput");export{E as carryInitialInput,t as initialInputMessageId,m as recordInitialInput};
@@ -1,14 +1,14 @@
1
- var Me=Object.defineProperty;var c=(e,t)=>Me(e,"name",{value:t,configurable:!0});import{coveredByAny as Ae}from"../../shared/env.js";import{createHash as Q}from"node:crypto";import{ApiError as Ie}from"../../api/errors.js";import{CrtrError as Te}from"../errors.js";import{isCloudRole as Le}from"../canvas/person.js";import{isAbsolute as V,relative as Ce,resolve as ke,sep as xe,join as We}from"node:path";import{SessionManager as Fe}from"@earendil-works/pi-coding-agent";import{covers as P}from"@north-light/identity";import{openDb as B,withCanvasWrite as F}from"../canvas/db.js";import{deleteNodeWithOwned as qe}from"../graph/deletion.js";import{getById as He}from"../graph/objects.js";import{repairRunConversations as Z}from"../canvas/sessions.js";import{getNode as O,nodeFiles as Ue,removeNodeFiles as $e,updateNode as Pe}from"../canvas/canvas.js";import{fullName as Be}from"../canvas/labels.js";import{renameWindow as je}from"../runtime/placement.js";import{liveExecutionForNode as K}from"../canvas/executions.js";import{conversationStore as ee,isConversationNotFound as Je}from"../conversation-store/index.js";import{PARKED_RESIDENT_SQL as Ye}from"../canvas/canvas.js";import{WAKE_MAIL_SQL as Ge}from"../canvas/deliveries.js";import{readNodeRecap as Xe}from"../canvas/node-recap.js";import{enqueueNodeMail as ze}from"../feed/messages.js";import{newestReportBody as Qe}from"../feed/reports.js";import{ensureAppProfile as te,loadExactProfileManifest as ne}from"../profiles/manifest.js";import{stopNodeSandboxes as se}from"../spaces/stop.js";import{BrokerUnreachableError as Ve}from"../runtime/broker-request.js";import{interruptLive as Ze}from"../runtime/interactive-deliver.js";import{isBrokerLive as ae}from"../runtime/model-swap.js";import{newNodeId as Ke}from"../runtime/nodes.js";import{reviveNode as j}from"../runtime/revive.js";import{spawnChildPrepared as et}from"../runtime/spawn.js";import{isPortableModelToken as tt}from"../runtime/launch.js";import{parseDurationMs as nt}from"../wake.js";import{expandModelCandidates as st,parseModelSpec as at}from"../model-routes.js";import{payerOfGrant as J}from"../grants/mirror.js";import{isolatedBrokerLauncherAvailable as it}from"../runtime/host.js";import{blaxelEnvironmentOf as rt}from"../runtime/blaxel-environment.js";import{resolveRunKind as ot,resolveRunModel as dt}from"../runtime/launch-target.js";import{cycleAwareMessages as ct}from"../runtime/session-cycles.js";import{originFor as ie}from"../runtime/stamp/protocol.js";import{readOutputRequest as ut,readOutputResult as re}from"../runtime/output-result.js";import{admitLegacyStructuredOutput as oe}from"../../daemon/structured-requests/sidecar.js";import{runSchemaRepromptPending as mt}from"../runtime/stop-guard.js";import{toRunOutcomeDTO as lt}from"../../daemon/api/map.js";import{openAssistantMessageStartSequence as ft}from"../../daemon/runs/observer.js";import{closeNode as de}from"../runtime/close.js";import{parseScope as pt}from"@north-light/identity";import{appendRunEvent as ce,lockRunRoot as _t,subscribeRunEvents as wt}from"./events.js";import{runActivity as gt}from"./activity.js";import{cancelOpenQuestions as ue,openNoticeIds as Et,openQuestionIds as me}from"./questions.js";import{appSpace as Y}from"../layout.js";import{ensureAppSpace as yt}from"../spaces/space.js";import{assertUploadsUsable as le,resolveStorageUrls as ht}from"../storage/attachments.js";import{storageClient as Rt,storageConfig as St}from"../storage/client.js";import{deleteUploadRow as Nt,insertUploadUses as fe,releaseRunUploads as vt,runUsesPath as bt}from"../storage/tables.js";import{abortProviderCalls as pe}from"../providers/inflight.js";import{filterAsync as _e,mapAsync as x}from"../async-array.js";const we=c((e,t,s,n)=>ht(t,e,s,n?{taken:n}:{}),"defaultResolver"),Ot=3e3;class R extends Error{static{c(this,"RunError")}status;code;param;details;scope;retryable;constructor(t,s,n,d,f,r,i=t===503){super(n),this.status=t,this.code=s,this.param=d,this.details=f,this.scope=r,this.retryable=i}}const N=c((e,t)=>new R(400,"invalid_request",e,t),"invalid"),G=c(()=>new R(404,"not_found","run not found"),"missing"),X=c(()=>({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}),"usage"),A=c(async()=>await B(),"db");async function q(e,t,s=!1){const n=await O(e);if(!n||n.parent!==null||n.run_id!==e||n.grantee!==t||!s&&n.deleting_at)throw G();return n}c(q,"ownedRun");const ge=["waiting_on_user","idle","settled"];async function C(e){if(e.status==="canceled"||e.status==="dead"||e.status==="done"&&e.intent!=="parked")return"settled";const t=await(await A()).prepare(`SELECT 1 FROM node_rows n WHERE n.run_id = ? AND n.status = 'active' AND
1
+ var Me=Object.defineProperty;var c=(e,t)=>Me(e,"name",{value:t,configurable:!0});import{coveredByAny as Ie}from"../../shared/env.js";import{createHash as Q}from"node:crypto";import{ApiError as Ae}from"../../api/errors.js";import{CrtrError as Te}from"../errors.js";import{isCloudRole as Le}from"../canvas/person.js";import{isAbsolute as V,relative as Ce,resolve as ke,sep as xe,join as We}from"node:path";import{SessionManager as Fe}from"@earendil-works/pi-coding-agent";import{covers as P}from"@north-light/identity";import{openDb as B,withCanvasWrite as F}from"../canvas/db.js";import{deleteNodeWithOwned as qe}from"../graph/deletion.js";import{getById as He}from"../graph/objects.js";import{repairRunConversations as Z}from"../canvas/sessions.js";import{getNode as O,nodeFiles as Ue,removeNodeFiles as $e,updateNode as Pe}from"../canvas/canvas.js";import{fullName as Be}from"../canvas/labels.js";import{renameWindow as je}from"../runtime/placement.js";import{liveExecutionForNode as K}from"../canvas/executions.js";import{conversationStore as ee,isConversationNotFound as Je}from"../conversation-store/index.js";import{PARKED_RESIDENT_SQL as Ye}from"../canvas/canvas.js";import{WAKE_MAIL_SQL as Ge}from"../canvas/deliveries.js";import{readNodeRecap as Xe}from"../canvas/node-recap.js";import{enqueueNodeMail as ze}from"../feed/messages.js";import{recordInitialInput as Qe}from"./initial-input.js";import{newestReportBody as Ve}from"../feed/reports.js";import{ensureAppProfile as te,loadExactProfileManifest as ne}from"../profiles/manifest.js";import{stopNodeSandboxes as se}from"../spaces/stop.js";import{BrokerUnreachableError as Ze}from"../runtime/broker-request.js";import{interruptLive as Ke}from"../runtime/interactive-deliver.js";import{isBrokerLive as ae}from"../runtime/model-swap.js";import{newNodeId as et}from"../runtime/nodes.js";import{reviveNode as j}from"../runtime/revive.js";import{spawnChildPrepared as tt}from"../runtime/spawn.js";import{isPortableModelToken as nt}from"../runtime/launch.js";import{parseDurationMs as st}from"../wake.js";import{expandModelCandidates as at,parseModelSpec as it}from"../model-routes.js";import{payerOfGrant as J}from"../grants/mirror.js";import{isolatedBrokerLauncherAvailable as rt}from"../runtime/host.js";import{blaxelEnvironmentOf as ot}from"../runtime/blaxel-environment.js";import{resolveRunKind as dt,resolveRunModel as ct}from"../runtime/launch-target.js";import{cycleAwareMessages as ut}from"../runtime/session-cycles.js";import{originFor as ie}from"../runtime/stamp/protocol.js";import{readOutputRequest as mt,readOutputResult as re}from"../runtime/output-result.js";import{admitLegacyStructuredOutput as oe}from"../../daemon/structured-requests/sidecar.js";import{runSchemaRepromptPending as lt}from"../runtime/stop-guard.js";import{toRunOutcomeDTO as ft}from"../../daemon/api/map.js";import{openAssistantMessageStartSequence as pt}from"../../daemon/runs/observer.js";import{closeNode as de}from"../runtime/close.js";import{parseScope as _t}from"@north-light/identity";import{appendRunEvent as ce,lockRunRoot as wt,subscribeRunEvents as gt}from"./events.js";import{runActivity as Et}from"./activity.js";import{cancelOpenQuestions as ue,openNoticeIds as yt,openQuestionIds as me}from"./questions.js";import{appSpace as Y}from"../layout.js";import{ensureAppSpace as ht}from"../spaces/space.js";import{assertUploadsUsable as le,resolveStorageUrls as Rt}from"../storage/attachments.js";import{storageClient as St,storageConfig as Nt}from"../storage/client.js";import{deleteUploadRow as vt,insertUploadUses as fe,releaseRunUploads as bt,runUsesPath as Ot}from"../storage/tables.js";import{abortProviderCalls as pe}from"../providers/inflight.js";import{filterAsync as _e,mapAsync as x}from"../async-array.js";const we=c((e,t,s,n)=>Rt(t,e,s,n?{taken:n}:{}),"defaultResolver"),Dt=3e3;class R extends Error{static{c(this,"RunError")}status;code;param;details;scope;retryable;constructor(t,s,n,d,f,r,i=t===503){super(n),this.status=t,this.code=s,this.param=d,this.details=f,this.scope=r,this.retryable=i}}const N=c((e,t)=>new R(400,"invalid_request",e,t),"invalid"),G=c(()=>new R(404,"not_found","run not found"),"missing"),X=c(()=>({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}),"usage"),I=c(async()=>await B(),"db");async function q(e,t,s=!1){const n=await O(e);if(!n||n.parent!==null||n.run_id!==e||n.grantee!==t||!s&&n.deleting_at)throw G();return n}c(q,"ownedRun");const ge=["waiting_on_user","idle","settled"];async function C(e){if(e.status==="canceled"||e.status==="dead"||e.status==="done"&&e.intent!=="parked")return"settled";const t=await(await I()).prepare(`SELECT 1 FROM node_rows n WHERE n.run_id = ? AND n.status = 'active' AND
2
2
  (n.last_settled_at IS NULL OR EXISTS (
3
3
  SELECT 1 FROM run_events e WHERE e.run_id = ? AND e.node_id = n.node_id AND e.type = 'node.turn.started'
4
4
  AND e.sequence_number > COALESCE((SELECT MAX(c.sequence_number) FROM run_events c
5
5
  WHERE c.run_id = e.run_id AND c.node_id = e.node_id AND c.type = 'node.turn.completed'), 0)
6
- )) LIMIT 1`).get(e.node_id,e.node_id),s=!e.last_settled_at&&e.status!=="done";return t||s||await mt(e)?"running":(await me(e.node_id)).length?"waiting_on_user":await(await A()).prepare(`SELECT 1 FROM deliveries d JOIN nodes n ON n.node_id = d.destination_id
6
+ )) LIMIT 1`).get(e.node_id,e.node_id),s=!e.last_settled_at&&e.status!=="done";return t||s||await lt(e)?"running":(await me(e.node_id)).length?"waiting_on_user":await(await I()).prepare(`SELECT 1 FROM deliveries d JOIN nodes n ON n.node_id = d.destination_id
7
7
  WHERE n.run_id = ? AND ${Ge("d","'armed', 'pending', 'handed_off'")}
8
- AND (n.status IN ('active', 'idle') OR ${Ye("n")}) LIMIT 1`).get(e.node_id)?"running":"idle"}c(C,"runStatus");async function Wn(e){const t=await O(e);return t!==null&&await C(t)==="running"}c(Wn,"runIsRunning");async function W(e){await Z(e.node_id),e=await O(e.node_id)??e;const t=await C(e),s=t==="settled"?[]:await me(e.node_id),n=await Qe(e.node_id)||void 0,d=t==="settled"?await lt(e):null,f=d?.kind==="result"?await re(e.node_id):void 0,r=Xe(e.node_id);return{run_id:e.node_id,name:e.name,status:t,...d?{outcome:d}:{},...f===void 0?{}:{result:f},...n!==void 0?{last_report:n}:{},...r?{recap:r}:{},pending_question_ids:s,open_notice_ids:t==="settled"?[]:await Et(e.node_id),...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(X())),payer:await J(e.grant_id),scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:await gt(e.node_id,t)}}c(W,"runObject");const z=864e5,Ee=255;function Fn(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>Ee)throw N(`Idempotency-Key must be 1-${Ee} characters`);return e}}c(Fn,"idempotencyKeyOf");function Dt(e){if(Le()){if(!e)throw new R(403,"permission_error","a cloud role serves this call only for a token that names a person");return e}}c(Dt,"cloudPersonScope");function k(e,t){const s=Dt(e);return s===void 0?t:`${s}:${t}`}c(k,"personKey");async function H(e,t,s,n){n&&await n.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(`idempotency:${e}:${t}`),n??=await B();const d=await n.prepare("SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!d||Date.now()-Date.parse(d.created_at)>=z)){if(d.body_hash!==s)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:d.run_id,response:JSON.parse(d.response_json)}}}c(H,"findIdempotent");async function ye(e,t,s,n,d,f,r){await e.prepare("DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,s,new Date(Date.now()-z).toISOString()),await e.prepare("INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,s,n,d,JSON.stringify(f),r)}c(ye,"recordIdempotent");async function qn(e=Date.now()){return Number((await(await B()).prepare("DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-z).toISOString())).changes)}c(qn,"pruneExpiredIdempotencyKeys");async function he(e,t,s,n,d,f=!1){const r=n.text;let i=0;return{messageId:(await ze(t,{kind:"message",label:r.slice(0,120),senderNodeId:"human",senderName:s,bodyJson:d?{body:r,...f?{steer:!0}:{}}:{body:r,start_turn:!1},urgency:d?"normal":"deferred",wake:d?1:0,withinEnqueue:c(async(m,h)=>{n.uses.length&&(await le(s,n.uses,m),await fe(n.uses.map(({upload_id:g,path:E,blob_key:o})=>({upload_id:g,path:E,blob_key:o,run_id:t})),m)),i=(await ce(m,t,t,"node.message.accepted",{message_id:h,start_turn:d})).sequence_number},"withinEnqueue")})).messageId,sequence:i}}c(he,"acceptRunMessage");class Re extends Error{static{c(this,"IdempotentReplay")}winner;constructor(t){super("idempotent replay"),this.winner=t}}const Mt=1440*60*1e3;function Se(e){const t=typeof e=="string"?nt(e):null;if(t===null||!Number.isSafeInteger(t))throw N(`deadline '${String(e)}' is not a duration; use one or more <int>[smhd] parts such as 10m or 1h30m`,"deadline");if(t<=0)throw N("deadline must be greater than zero","deadline");if(t>Mt)throw N("deadline must be at most 24h","deadline");return t}c(Se,"parseRunDeadlineMs");async function Hn(e,t,s,n=we){if(t.isolated&&!it()&&await rt((t.profile===void 0?await te(e.grantee):ne(t.profile)).profileId)===null)throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");t.deadline!==void 0&&Se(t.deadline);const{causedBy:d,...f}=t,r=Q("sha256").update(JSON.stringify(f)).digest("hex");if(s){const a=await H(e.grantee,k(e.sub,s),r);if(a){const l=await q(a.run_id,e.grantee);return!l.pi_pid&&await C(l)==="running"&&(t.output_schema!==void 0&&!l.outcome_at&&await ut(l.node_id)===null&&await re(l.node_id)===void 0&&await oe(l.node_id,"terminal",t.output_schema),await j(l.node_id,{resume:!0,capacity:"freeze"})),a.response}}await yt(e.grantee);const i=t.profile===void 0?await te(e.grantee):ne(t.profile),u=i.manifest.grantee??"app:terminal";if(u!==e.grantee){const a=`crtr:memory:rw:${u}:profile:${i.profileId}`;if(!e.resolved.some(l=>P(l,a))||t.scopes!==null&&!t.scopes.some(l=>P(l,a)))throw new R(403,"scope_missing",`missing scope ${a}`,"profile",void 0,a)}if(t.model!==void 0){const a=t.model;if(tt(a)){if(a.includes("/")&&await J(e.grant)!=="app"){const l=a.lastIndexOf("/"),D=a.slice(0,l),v=a.slice(l+1);if(["ultra","strong","medium","light"].includes(v)&&(await st({kind:"logical",family:D,strength:v,spec:a},Y(e.grantee).files,i.profileId)).every(S=>S.family!==D))throw N(`model '${a}' names a model family that is not configured in this daemon`,"model")}}else if(at(a)===void 0)throw N(`model '${a}' is not a strength (ultra, strong, medium, light), a family/strength, or a provider/model id`,"model")}const m=await Ke(),h=We(Y(e.grantee).nodes,m);if(t.isolated&&t.cwd!==void 0){const a=Ce(h,ke(t.cwd));if(!V(t.cwd)||a===".."||a.startsWith(`..${xe}`)||V(a))throw N("cwd must be inside the isolated root node folder","cwd")}const g=await n(m,e.grantee,t.prompt),E=g.text,o=t.message===void 0?void 0:await n(m,e.grantee,t.message,g.uses.map(a=>a.path)),p=await J(e.grant);let y;try{const a=dt(i,t.model);await et({kind:ot(i),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??h:Y(e.grantee).files,profile:i.profileId,prompt:E,nodeId:m,isolated:t.isolated??!1,...t.causedBy===void 0?{}:{causedBy:t.causedBy},...a===void 0?{}:{model:a},...t.deadline===void 0?{}:{deadlineAt:new Date(Date.now()+Se(t.deadline)).toISOString()},...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:m,event_cursor:1,usage_json:JSON.stringify(X())},withinCreate:c(async(l,D)=>{if(s){const S=await H(e.grantee,k(e.sub,s),r,l);if(S)throw new Re(S)}const v=g.uses;if(v.length&&(await le(e.grantee,v,l),await fe(v.map(({upload_id:S,path:I,blob_key:U})=>({upload_id:S,path:I,blob_key:U,run_id:m})),l)),y={run_id:m,node_id:m,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:p,status:"running",created_at:D.created,metadata:t.metadata,sequence_number:0},await ce(l,m,m,"run.started",{scopes:t.scopes,payer:p,usage:X()}),o){const S=await he(l,m,e.grantee,o,!1);y.message={run_id:m,message_id:S.messageId,sequence_number:S.sequence}}s&&await ye(l,e.grantee,k(e.sub,s),r,m,y,D.created)},"withinCreate")},t.output_schema===void 0?void 0:async l=>{await oe(l,"terminal",t.output_schema)})}catch(a){if(a instanceof Re)return a.winner.response;if(t.model!==void 0&&a instanceof Te&&a.code==="usage")throw N(a.message,"model");if(a instanceof Ie&&(a.code==="isolated_run"||a.code==="invalid_request")){const l=a.details&&typeof a.details=="object"&&"param"in a.details&&typeof a.details.param=="string"?a.details.param:void 0;throw new R(a.status,a.code,a.message,l)}throw a}if(!y)throw new Error("run creation did not commit");return y}c(Hn,"startRun");async function Un(e,t){let s;if(t.after)try{const i=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!i||typeof i!="object"||!("activity"in i)||typeof i.activity!="string"||!("id"in i)||typeof i.id!="string")throw new Error("invalid cursor");s={activity:i.activity,id:i.id}}catch{throw N("invalid list cursor","after")}const n=await(await A()).prepareUncached(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
8
+ AND (n.status IN ('active', 'idle') OR ${Ye("n")}) LIMIT 1`).get(e.node_id)?"running":"idle"}c(C,"runStatus");async function qn(e){const t=await O(e);return t!==null&&await C(t)==="running"}c(qn,"runIsRunning");async function W(e){await Z(e.node_id),e=await O(e.node_id)??e;const t=await C(e),s=t==="settled"?[]:await me(e.node_id),n=await Ve(e.node_id)||void 0,d=t==="settled"?await ft(e):null,f=d?.kind==="result"?await re(e.node_id):void 0,r=Xe(e.node_id);return{run_id:e.node_id,name:e.name,status:t,...d?{outcome:d}:{},...f===void 0?{}:{result:f},...n!==void 0?{last_report:n}:{},...r?{recap:r}:{},pending_question_ids:s,open_notice_ids:t==="settled"?[]:await yt(e.node_id),...e.last_turn_error_json?{last_turn_error:JSON.parse(e.last_turn_error_json)}:{},usage:JSON.parse(e.usage_json??JSON.stringify(X())),payer:await J(e.grant_id),scopes:e.scopes,metadata:e.metadata_json?JSON.parse(e.metadata_json):{},created_at:e.created,...t==="settled"&&e.outcome_at?{settled_at:e.outcome_at}:{},latest_sequence:(e.event_cursor??1)-1,last_activity_at:e.last_activity_at,activity:await Et(e.node_id,t)}}c(W,"runObject");const z=864e5,Ee=255;function Hn(e){if(e!==void 0){if(typeof e!="string"||!e.length||e.length>Ee)throw N(`Idempotency-Key must be 1-${Ee} characters`);return e}}c(Hn,"idempotencyKeyOf");function Mt(e){if(Le()){if(!e)throw new R(403,"permission_error","a cloud role serves this call only for a token that names a person");return e}}c(Mt,"cloudPersonScope");function k(e,t){const s=Mt(e);return s===void 0?t:`${s}:${t}`}c(k,"personKey");async function H(e,t,s,n){n&&await n.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(`idempotency:${e}:${t}`),n??=await B();const d=await n.prepare("SELECT body_hash, run_id, response_json, created_at FROM idempotency_keys WHERE grantee = ? AND key = ?").get(e,t);if(!(!d||Date.now()-Date.parse(d.created_at)>=z)){if(d.body_hash!==s)throw new R(409,"idempotency_conflict","key used with a different request");return{run_id:d.run_id,response:JSON.parse(d.response_json)}}}c(H,"findIdempotent");async function ye(e,t,s,n,d,f,r){await e.prepare("DELETE FROM idempotency_keys WHERE grantee = ? AND key = ? AND created_at <= ?").run(t,s,new Date(Date.now()-z).toISOString()),await e.prepare("INSERT INTO idempotency_keys (grantee, key, body_hash, run_id, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,s,n,d,JSON.stringify(f),r)}c(ye,"recordIdempotent");async function Un(e=Date.now()){return Number((await(await B()).prepare("DELETE FROM idempotency_keys WHERE created_at <= ?").run(new Date(e-z).toISOString())).changes)}c(Un,"pruneExpiredIdempotencyKeys");async function he(e,t,s,n,d,f=!1){const r=n.text;let i=0;return{messageId:(await ze(t,{kind:"message",label:r.slice(0,120),senderNodeId:"human",senderName:s,bodyJson:d?{body:r,...f?{steer:!0}:{}}:{body:r,start_turn:!1},urgency:d?"normal":"deferred",wake:d?1:0,withinEnqueue:c(async(m,h)=>{n.uses.length&&(await le(s,n.uses,m),await fe(n.uses.map(({upload_id:g,path:E,blob_key:o})=>({upload_id:g,path:E,blob_key:o,run_id:t})),m)),i=(await ce(m,t,t,"node.message.accepted",{message_id:h,start_turn:d})).sequence_number},"withinEnqueue")})).messageId,sequence:i}}c(he,"acceptRunMessage");class Re extends Error{static{c(this,"IdempotentReplay")}winner;constructor(t){super("idempotent replay"),this.winner=t}}const It=1440*60*1e3;function Se(e){const t=typeof e=="string"?st(e):null;if(t===null||!Number.isSafeInteger(t))throw N(`deadline '${String(e)}' is not a duration; use one or more <int>[smhd] parts such as 10m or 1h30m`,"deadline");if(t<=0)throw N("deadline must be greater than zero","deadline");if(t>It)throw N("deadline must be at most 24h","deadline");return t}c(Se,"parseRunDeadlineMs");async function $n(e,t,s,n=we){if(t.isolated&&!rt()&&await ot((t.profile===void 0?await te(e.grantee):ne(t.profile)).profileId)===null)throw new R(503,"isolated_unavailable","isolated runs require the Linux root launcher","isolated");t.deadline!==void 0&&Se(t.deadline);const{causedBy:d,...f}=t,r=Q("sha256").update(JSON.stringify(f)).digest("hex");if(s){const a=await H(e.grantee,k(e.sub,s),r);if(a){const l=await q(a.run_id,e.grantee);return!l.pi_pid&&await C(l)==="running"&&(t.output_schema!==void 0&&!l.outcome_at&&await mt(l.node_id)===null&&await re(l.node_id)===void 0&&await oe(l.node_id,"terminal",t.output_schema),await j(l.node_id,{resume:!0,capacity:"freeze"})),a.response}}await ht(e.grantee);const i=t.profile===void 0?await te(e.grantee):ne(t.profile),u=i.manifest.grantee??"app:terminal";if(u!==e.grantee){const a=`crtr:memory:rw:${u}:profile:${i.profileId}`;if(!e.resolved.some(l=>P(l,a))||t.scopes!==null&&!t.scopes.some(l=>P(l,a)))throw new R(403,"scope_missing",`missing scope ${a}`,"profile",void 0,a)}if(t.model!==void 0){const a=t.model;if(nt(a)){if(a.includes("/")&&await J(e.grant)!=="app"){const l=a.lastIndexOf("/"),D=a.slice(0,l),v=a.slice(l+1);if(["ultra","strong","medium","light"].includes(v)&&(await at({kind:"logical",family:D,strength:v,spec:a},Y(e.grantee).files,i.profileId)).every(S=>S.family!==D))throw N(`model '${a}' names a model family that is not configured in this daemon`,"model")}}else if(it(a)===void 0)throw N(`model '${a}' is not a strength (ultra, strong, medium, light), a family/strength, or a provider/model id`,"model")}const m=await et(),h=We(Y(e.grantee).nodes,m);if(t.isolated&&t.cwd!==void 0){const a=Ce(h,ke(t.cwd));if(!V(t.cwd)||a===".."||a.startsWith(`..${xe}`)||V(a))throw N("cwd must be inside the isolated root node folder","cwd")}const g=await n(m,e.grantee,t.prompt),E=g.text,o=t.message===void 0?void 0:await n(m,e.grantee,t.message,g.uses.map(a=>a.path)),p=await J(e.grant);let y;try{const a=ct(i,t.model);await tt({kind:dt(i),mode:"base",root:!0,rootLifecycle:"resident",readiness:"launch",cwd:t.isolated?t.cwd??h:Y(e.grantee).files,profile:i.profileId,prompt:E,nodeId:m,isolated:t.isolated??!1,...t.causedBy===void 0?{}:{causedBy:t.causedBy},...a===void 0?{}:{model:a},...t.deadline===void 0?{}:{deadlineAt:new Date(Date.now()+Se(t.deadline)).toISOString()},...t.scopes===null?{}:{scopes:t.scopes},run:{grantee:e.grantee,sub:e.sub,grant_id:e.grant,client_id:e.client_id,metadata_json:JSON.stringify(t.metadata),run_id:m,event_cursor:1,usage_json:JSON.stringify(X())},withinCreate:c(async(l,D)=>{if(s){const S=await H(e.grantee,k(e.sub,s),r,l);if(S)throw new Re(S)}const v=g.uses;if(v.length&&(await le(e.grantee,v,l),await fe(v.map(({upload_id:S,path:A,blob_key:U})=>({upload_id:S,path:A,blob_key:U,run_id:m})),l)),y={run_id:m,node_id:m,grantee:e.grantee,grant:e.grant,rev:e.rev,scopes:t.scopes,payer:p,status:"running",created_at:D.created,metadata:t.metadata,sequence_number:0},await ce(l,m,m,"run.started",{scopes:t.scopes,payer:p,usage:X()}),o){const S=await he(l,m,e.grantee,o,!1);y.message={run_id:m,message_id:S.messageId,sequence_number:S.sequence}}else await Qe(m,e.grantee,E);s&&await ye(l,e.grantee,k(e.sub,s),r,m,y,D.created)},"withinCreate")},t.output_schema===void 0?void 0:async l=>{await oe(l,"terminal",t.output_schema)})}catch(a){if(a instanceof Re)return a.winner.response;if(t.model!==void 0&&a instanceof Te&&a.code==="usage")throw N(a.message,"model");if(a instanceof Ae&&(a.code==="isolated_run"||a.code==="invalid_request")){const l=a.details&&typeof a.details=="object"&&"param"in a.details&&typeof a.details.param=="string"?a.details.param:void 0;throw new R(a.status,a.code,a.message,l)}throw a}if(!y)throw new Error("run creation did not commit");return y}c($n,"startRun");async function Pn(e,t){let s;if(t.after)try{const i=JSON.parse(Buffer.from(t.after,"base64url").toString());if(!i||typeof i!="object"||!("activity"in i)||typeof i.activity!="string"||!("id"in i)||typeof i.id!="string")throw new Error("invalid cursor");s={activity:i.activity,id:i.id}}catch{throw N("invalid list cursor","after")}const n=await(await I()).prepareUncached(`SELECT node_id, COALESCE(last_activity_at, created) AS activity FROM node_rows
9
9
  WHERE run_id = node_id AND deleting_at IS NULL ${e===null?"":"AND grantee = ?"}${t.sub===void 0?"":" AND sub = ?"}
10
- ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e],...t.sub===void 0?[]:[t.sub]),d=await _e(await x((await _e(n,async({node_id:i})=>!t.visible||await t.visible(i))).filter(({activity:i,node_id:u})=>!s||i<s.activity||i===s.activity&&u<s.id),async i=>({root:await O(i.node_id),activity:i.activity})),async({root:i})=>!t.status||await C(i)===t.status),f=d.slice(0,t.limit),r=f.at(-1);return{runs:await x(f,async({root:i})=>await W(i)),next:d.length>t.limit&&r?Buffer.from(JSON.stringify({activity:r.activity,id:r.root.node_id})).toString("base64url"):null}}c(Un,"listRuns");async function $n(e,t,s){const n=await q(e,t),d=c(async()=>await W(await q(e,t)),"read");return!s||ge.includes(await C(n))?await d():new Promise((f,r)=>{let i=c(()=>{},"unsubscribe");const u=c(()=>{clearTimeout(o),clearInterval(E),i()},"settle"),m=c(async()=>{u();try{f(await d())}catch(p){r(p)}},"finish"),h=c(async()=>{ge.includes(String((await d()).status))&&await m()},"check"),g=c(()=>{h().catch(p=>{u(),r(p)})},"run"),E=setInterval(g,250),o=setTimeout(()=>{m()},s*1e3);wt(e,g).then(p=>{i=p},p=>{u(),r(p)})})}c($n,"waitRun");const At=5;async function Pn(e,t,s=we,n=!0,d,f=!0){const r=e.status==="canceled"||e.outcome_reason==="closed",i=e.grantee??"app:terminal",u=Q("sha256").update(JSON.stringify(["run.message",e.node_id,t,n,...n&&!f?["steer:false"]:[]])).digest("hex"),m=c(async g=>{const E=await O(e.node_id),o=await(await A()).prepare("SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(g.message_id));return E&&!r&&n&&!E.pi_pid&&o&&await j(e.node_id,{resume:!0,capacity:"freeze"}),g},"replay");if(d){const g=await H(i,k(e.sub,d),u);if(g)return m(g.response)}if(r)throw new R(409,"run_settled","canceled run cannot receive a message");const h=n&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;for(let g=1;;g++){const E=await s(e.node_id,i,t),o=await F(async p=>{if(d){const l=await H(i,k(e.sub,d),u,p);if(l)return{replayed:l.response}}if(E.uses.length){await _t(p,e.node_id);for(const l of E.uses)if(await bt(e.node_id,l.path,p))return"path_taken"}await h?.(e.node_id);const y=await he(p,e.node_id,i,E,n,n&&f),a={run_id:e.node_id,message_id:y.messageId,sequence_number:y.sequence};return d&&await ye(p,i,k(e.sub,d),u,e.node_id,a,new Date().toISOString()),{result:a}});if(o==="path_taken"){if(g>=At)throw new R(503,"service_unavailable","attachments of concurrent messages kept taking each other's file names; retry");continue}return"replayed"in o?m(o.replayed):(n&&!e.pi_pid&&await j(e.node_id,{resume:!0,capacity:"freeze"}),o.result)}}c(Pn,"messageRun");async function It(e){const t=c(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let s=0;s<2;s++){const n=await O(e);if(!n)throw G();if(!ae(n)){if(n.streaming)throw t();return}const d=(await K(e))?.executionId;try{await Ze(e)==="turn"&&pe({nodes:[e]});return}catch(f){if(!(f instanceof Ve))throw f;const r=await O(e);if(!r)throw G();if(!r.streaming)return;if(s===0&&ae(r)&&(await K(e))?.executionId!==d)continue;throw t()}}throw t()}c(It,"interruptNode");async function Bn(e,t="stop"){const s=t==="stop"?(await(await A()).prepare("SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id)).map(({node_id:n})=>n):[e.node_id];return await Promise.all(s.map(n=>It(n))),await W(await O(e.node_id)??e)}c(Bn,"interruptRun");async function jn(e,t={}){const s=(await(await A()).prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(r=>r.node_id);pe({run:e.node_id});const n=[];if(await C(e)!=="settled")n.push(...(await de(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"})).closed);else for(const r of s){if(r===e.node_id)continue;const i=await O(r);i&&i.status!=="done"&&i.status!=="canceled"&&i.status!=="dead"&&n.push(...(await de(r,{preserveEmpty:!0,stopReason:"run_canceled"})).closed)}if(await ue({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...n])].map(r=>se(r,"run_canceled")))).some(r=>!r.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const f=await O(e.node_id);return await W(f??e)}c(jn,"cancelRun");const Ne=200;async function Jn(e,t){if(typeof t!="string"||!t.trim())throw N("name is required","name");const s=t.trim();if(s.length>Ne)throw N(`name exceeds ${Ne} characters`,"name");const n=await Pe(e.node_id,{name:s});return n.window&&je(n.window,Be(n)),await W(n)}c(Jn,"renameRun");async function Yn(e,t={config:St,client:Rt}){await ue({run_id:e.node_id},"the run was deleted");const s=await F(async u=>(await u.prepare("UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),(await u.prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(m=>m.node_id)));if((await Promise.all(s.map(u=>se(u,"run_deleted",{deleting:!0})))).some(u=>!u.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const d=await x(s,async u=>{const m=await Ue(u);if(m.app===null)throw new Error(`run node missing during deletion: ${u}`);return m}),f=await(await A()).prepare("SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:u}of f)try{await ee().delete(u)}catch(m){if(!Je(m))throw m}const r=await F(async u=>{for(const E of s){const o=await He(u,E);o!==null&&o.deleted_at===null&&await qe(u,o)}const m=await vt([e.node_id],u);await u.prepare("DELETE FROM run_events WHERE run_id = ?").run(e.node_id),await u.prepare("DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const h=s.map(()=>"?").join(","),g=await u.prepareUncached(`SELECT message_id FROM messages WHERE sender_node_id IN (${h}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${h})`).all(...s,...s);await u.prepareUncached(`DELETE FROM deliveries WHERE destination_id IN (${h})`).run(...s);for(const{message_id:E}of g)await u.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(E,E);return m});for(const u of d)await $e(u);const{notifyRunDeleted:i}=await import("./events.js");return i(e.node_id),{uploads_pending:await Tt(r,t)}}c(Yn,"deleteRun");async function Tt(e,t){if(!e.length||!t.config())return e.length;let s;try{s=t.client()}catch{return e.length}const n=new Set,d=e.map(r=>s.deleteUpload(r).then(async()=>{await Nt(r),n.add(r)},()=>{}));let f;return await Promise.race([Promise.all(d),new Promise(r=>{f=setTimeout(r,t.budgetMs??Ot),f.unref?.()})]),clearTimeout(f),e.length-n.size}c(Tt,"deleteReleasedUploads");async function Gn(e,t,s){await q(e,t),await Z(e);const n=await F(async o=>{const p=await o.prepare("SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e),y=new Map;for(const S of p)y.set(S.node_id,(await o.prepare("SELECT session_id FROM sessions WHERE node_id = ? GROUP BY session_id ORDER BY min(created_at)").all(S.node_id)).map(I=>I.session_id));const l=(await o.prepare("SELECT event_cursor FROM nodes WHERE node_id = ?").get(e)).event_cursor-1,D=await o.prepare(`SELECT node_id, sequence_number FROM run_events
10
+ ORDER BY activity DESC, node_id DESC`).all(...e===null?[]:[e],...t.sub===void 0?[]:[t.sub]),d=await _e(await x((await _e(n,async({node_id:i})=>!t.visible||await t.visible(i))).filter(({activity:i,node_id:u})=>!s||i<s.activity||i===s.activity&&u<s.id),async i=>({root:await O(i.node_id),activity:i.activity})),async({root:i})=>!t.status||await C(i)===t.status),f=d.slice(0,t.limit),r=f.at(-1);return{runs:await x(f,async({root:i})=>await W(i)),next:d.length>t.limit&&r?Buffer.from(JSON.stringify({activity:r.activity,id:r.root.node_id})).toString("base64url"):null}}c(Pn,"listRuns");async function Bn(e,t,s){const n=await q(e,t),d=c(async()=>await W(await q(e,t)),"read");return!s||ge.includes(await C(n))?await d():new Promise((f,r)=>{let i=c(()=>{},"unsubscribe");const u=c(()=>{clearTimeout(o),clearInterval(E),i()},"settle"),m=c(async()=>{u();try{f(await d())}catch(p){r(p)}},"finish"),h=c(async()=>{ge.includes(String((await d()).status))&&await m()},"check"),g=c(()=>{h().catch(p=>{u(),r(p)})},"run"),E=setInterval(g,250),o=setTimeout(()=>{m()},s*1e3);gt(e,g).then(p=>{i=p},p=>{u(),r(p)})})}c(Bn,"waitRun");const At=5;async function jn(e,t,s=we,n=!0,d,f=!0){const r=e.status==="canceled"||e.outcome_reason==="closed",i=e.grantee??"app:terminal",u=Q("sha256").update(JSON.stringify(["run.message",e.node_id,t,n,...n&&!f?["steer:false"]:[]])).digest("hex"),m=c(async g=>{const E=await O(e.node_id),o=await(await I()).prepare("SELECT 1 FROM deliveries WHERE message_id = ? AND state = 'pending' AND canceled_at IS NULL").get(String(g.message_id));return E&&!r&&n&&!E.pi_pid&&o&&await j(e.node_id,{resume:!0,capacity:"freeze"}),g},"replay");if(d){const g=await H(i,k(e.sub,d),u);if(g)return m(g.response)}if(r)throw new R(409,"run_settled","canceled run cannot receive a message");const h=n&&e.final_report&&!e.pi_pid?(await import("../runtime/reopen.js")).commitReopenResident:void 0;for(let g=1;;g++){const E=await s(e.node_id,i,t),o=await F(async p=>{if(d){const l=await H(i,k(e.sub,d),u,p);if(l)return{replayed:l.response}}if(E.uses.length){await wt(p,e.node_id);for(const l of E.uses)if(await Ot(e.node_id,l.path,p))return"path_taken"}await h?.(e.node_id);const y=await he(p,e.node_id,i,E,n,n&&f),a={run_id:e.node_id,message_id:y.messageId,sequence_number:y.sequence};return d&&await ye(p,i,k(e.sub,d),u,e.node_id,a,new Date().toISOString()),{result:a}});if(o==="path_taken"){if(g>=At)throw new R(503,"service_unavailable","attachments of concurrent messages kept taking each other's file names; retry");continue}return"replayed"in o?m(o.replayed):(n&&!e.pi_pid&&await j(e.node_id,{resume:!0,capacity:"freeze"}),o.result)}}c(jn,"messageRun");async function Tt(e){const t=c(()=>new R(503,"service_unavailable",`node ${e} broker was unavailable before the interrupt reached it`),"unavailable");for(let s=0;s<2;s++){const n=await O(e);if(!n)throw G();if(!ae(n)){if(n.streaming)throw t();return}const d=(await K(e))?.executionId;try{await Ke(e)==="turn"&&pe({nodes:[e]});return}catch(f){if(!(f instanceof Ze))throw f;const r=await O(e);if(!r)throw G();if(!r.streaming)return;if(s===0&&ae(r)&&(await K(e))?.executionId!==d)continue;throw t()}}throw t()}c(Tt,"interruptNode");async function Jn(e,t="stop"){const s=t==="stop"?(await(await I()).prepare("SELECT node_id FROM nodes WHERE run_id = ?").all(e.node_id)).map(({node_id:n})=>n):[e.node_id];return await Promise.all(s.map(n=>Tt(n))),await W(await O(e.node_id)??e)}c(Jn,"interruptRun");async function Yn(e,t={}){const s=(await(await I()).prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(r=>r.node_id);pe({run:e.node_id});const n=[];if(await C(e)!=="settled")n.push(...(await de(e.node_id,{preserveEmpty:!0,stopReason:"run_canceled"})).closed);else for(const r of s){if(r===e.node_id)continue;const i=await O(r);i&&i.status!=="done"&&i.status!=="canceled"&&i.status!=="dead"&&n.push(...(await de(r,{preserveEmpty:!0,stopReason:"run_canceled"})).closed)}if(await ue({run_id:e.node_id},"the run was canceled"),(await Promise.all([...new Set([e.node_id,...n])].map(r=>se(r,"run_canceled")))).some(r=>!r.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const f=await O(e.node_id);return await W(f??e)}c(Yn,"cancelRun");const Ne=200;async function Gn(e,t){if(typeof t!="string"||!t.trim())throw N("name is required","name");const s=t.trim();if(s.length>Ne)throw N(`name exceeds ${Ne} characters`,"name");const n=await Pe(e.node_id,{name:s});return n.window&&je(n.window,Be(n)),await W(n)}c(Gn,"renameRun");async function Xn(e,t={config:Nt,client:St}){await ue({run_id:e.node_id},"the run was deleted");const s=await F(async u=>(await u.prepare("UPDATE nodes SET deleting_at = COALESCE(deleting_at, ?) WHERE node_id = ?").run(new Date().toISOString(),e.node_id),(await u.prepare("SELECT node_id FROM nodes WHERE run_id = ? ORDER BY created DESC").all(e.node_id)).map(m=>m.node_id)));if((await Promise.all(s.map(u=>se(u,"run_deleted",{deleting:!0})))).some(u=>!u.confirmed))throw new R(500,"server_error","run process teardown could not be confirmed",void 0,void 0,void 0,!0);const d=await x(s,async u=>{const m=await Ue(u);if(m.app===null)throw new Error(`run node missing during deletion: ${u}`);return m}),f=await(await I()).prepare("SELECT DISTINCT session_id FROM sessions WHERE session_id IS NOT NULL AND node_id IN (SELECT node_id FROM nodes WHERE run_id = ?)").all(e.node_id);for(const{session_id:u}of f)try{await ee().delete(u)}catch(m){if(!Je(m))throw m}const r=await F(async u=>{for(const E of s){const o=await He(u,E);o!==null&&o.deleted_at===null&&await qe(u,o)}const m=await bt([e.node_id],u);await u.prepare("DELETE FROM run_events WHERE run_id = ?").run(e.node_id),await u.prepare("DELETE FROM idempotency_keys WHERE run_id = ?").run(e.node_id);const h=s.map(()=>"?").join(","),g=await u.prepareUncached(`SELECT message_id FROM messages WHERE sender_node_id IN (${h}) UNION SELECT message_id FROM deliveries WHERE destination_id IN (${h})`).all(...s,...s);await u.prepareUncached(`DELETE FROM deliveries WHERE destination_id IN (${h})`).run(...s);for(const{message_id:E}of g)await u.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)").run(E,E);return m});for(const u of d)await $e(u);const{notifyRunDeleted:i}=await import("./events.js");return i(e.node_id),{uploads_pending:await Lt(r,t)}}c(Xn,"deleteRun");async function Lt(e,t){if(!e.length||!t.config())return e.length;let s;try{s=t.client()}catch{return e.length}const n=new Set,d=e.map(r=>s.deleteUpload(r).then(async()=>{await vt(r),n.add(r)},()=>{}));let f;return await Promise.race([Promise.all(d),new Promise(r=>{f=setTimeout(r,t.budgetMs??Dt),f.unref?.()})]),clearTimeout(f),e.length-n.size}c(Lt,"deleteReleasedUploads");async function zn(e,t,s){await q(e,t),await Z(e);const n=await F(async o=>{const p=await o.prepare("SELECT node_id, parent FROM nodes WHERE run_id = ? ORDER BY CASE WHEN node_id = ? THEN 0 ELSE 1 END, created, node_id").all(e,e),y=new Map;for(const S of p)y.set(S.node_id,(await o.prepare("SELECT session_id FROM sessions WHERE node_id = ? GROUP BY session_id ORDER BY min(created_at)").all(S.node_id)).map(A=>A.session_id));const l=(await o.prepare("SELECT event_cursor FROM nodes WHERE node_id = ?").get(e)).event_cursor-1,D=await o.prepare(`SELECT node_id, sequence_number FROM run_events
11
11
  WHERE run_id = ? AND sequence_number <= ? AND type IN ('node.thinking.done', 'node.output_text.done', 'usage')
12
12
  ORDER BY sequence_number`).all(e,l),v=await o.prepare(`SELECT node_id, sequence_number, type, body_json FROM run_events
13
13
  WHERE run_id = ? AND sequence_number <= ? AND type IN ('node.thinking.delta', 'node.output_text.delta')
14
- ORDER BY sequence_number`).all(e,l);return{nodes:p,sessionIds:y,latestSequence:l,completed:D,deltas:v}}),{nodes:d,sessionIds:f,latestSequence:r,completed:i,deltas:u}=n,m=await x(d,async o=>({node_id:o.node_id,parent_node_id:o.parent,conversations:await x(f.get(o.node_id)??[],async p=>{const{header:y,entries:a}=await ee().read(p);if(s==="entries")return{conversation_id:p,header:y,entries:a};const l=Fe.inMemory(y.cwd,void 0,[y,...a]),D=ct(l)??[],v=new Map(a.map((w,b)=>[w.id,b])),S=a.filter(w=>w.type==="custom"&&w.customType==="crtr-system-prompt").map(w=>({id:w.id,message:{role:"custom",customType:"crtr-system-prompt",data:"data"in w?w.data:void 0,timestamp:w.timestamp}})),I=a.filter(w=>w.type==="custom_message"&&w.customType==="app_message").map(w=>({id:w.id,message:{role:"custom",customType:"app_message",content:"content"in w?w.content:"",timestamp:w.timestamp}})),U=o.node_id===e?a.find(w=>{if(w.type!=="message"||w.message.role!=="user")return!1;const b=ie(a,w.message.timestamp);return b?.type==="runtime"&&b.kind==="kickoff"})?.id:void 0,ve=[...D.filter(({id:w})=>!I.some(b=>b.id===w)),...S,...I].sort((w,b)=>(v.get(w.id)??Number.MAX_SAFE_INTEGER)-(v.get(b.id)??Number.MAX_SAFE_INTEGER)).map(({id:w,message:b})=>{const _=b,T=_.usage,$=_.role==="user"?ie(a,Number(_.timestamp)):null,be=$?.type==="human"||w===U,L=_.role==="toolResult"?"tool_result":_.role==="bashExecution"?"bash_execution":_.role==="compactionSummary"?"compaction_summary":_.role==="branchSummary"?"branch_summary":_.role==="custom"&&_.customType==="crtr-system-prompt"?"system":_.role==="user"?be?"user":"custom":_.role,Oe=L==="custom"?_.role==="custom"?_.customType:$?.type==="runtime"?$.kind:"unknown":void 0,De=L==="system"&&_.customType==="crtr-system-prompt"?[{type:"text",text:_.data?.text??""}]:typeof _.content=="string"?[{type:"text",text:_.content}]:Array.isArray(_.content)?_.content.map(M=>M.type==="toolCall"?{type:"tool_call",tool_call_id:M.id,name:M.name,arguments:M.arguments}:M.type==="image"?{type:"image",mime_type:M.mimeType}:M.type==="thinking"?{type:"thinking",text:M.thinking}:M):[];return{entry_id:w,role:L,...L==="custom"?{custom_type:Oe}:{},time:typeof _.timestamp=="string"?_.timestamp:new Date(Number(_.timestamp??Date.now())).toISOString(),content:De,...L==="assistant"?{model:_.model,provider:_.provider,stop_reason:_.stopReason,..._.errorMessage?{error_message:_.errorMessage}:{},...T?{usage:{input_tokens:T.input??0,output_tokens:T.output??0,cache_read_tokens:T.cacheRead??0,cache_write_tokens:T.cacheWrite??0,cost_usd:T.cost?.total??0}}:{}}:{},...L==="tool_result"?{tool_call_id:_.toolCallId,tool_name:_.toolName,is_error:_.isError}:{}}})??[];return{conversation_id:p,messages:ve}})})),h=new Map(await Promise.all(m.map(async o=>[o.node_id,await ft(o.node_id)])));for(const o of i){const p=h.get(o.node_id);p!=null&&o.sequence_number>=p&&h.set(o.node_id,null)}const g=new Map;for(const o of u){const p=h.get(o.node_id);if(p==null||o.sequence_number<p)continue;const y=JSON.parse(o.body_json),a=g.get(o.node_id)??{thinking:"",text:""};o.type==="node.thinking.delta"?a.thinking+=y.delta:a.text+=y.delta,g.set(o.node_id,a)}const E=m.map(o=>({...o,in_progress_message:h.get(o.node_id)==null?null:g.get(o.node_id)??{thinking:"",text:""}}));return{run_id:e,latest_sequence:r,nodes:E}}c(Gn,"traceRun");function Xn(e,t,s){if(t===void 0){if(!e.some(n=>P(n,"crtr:llm")))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return null}if(!Array.isArray(t)||t.some(n=>typeof n!="string"))throw N("scopes must be a list of scope strings","scopes");for(const n of t){try{pt(n)}catch{throw N(`invalid scope ${n}`,"scopes")}if(/^crtr:files:(?:read|rw):app:/.test(n)&&n.split(":")[4]===s.slice(4))throw N(`own-app files cannot narrow a run: ${n}`,"scopes");if(!(/^crtr:memory:(?:read|rw):app:/.test(n)&&n.split(":")[4]===s.slice(4))&&!Ae(e,n))throw new R(403,"scope_missing",`missing scope ${n}`,void 0,void 0,n)}if(!t.includes("crtr:llm"))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return t}c(Xn,"requestedScopes");export{z as IDEMPOTENCY_WINDOW_MS,Mt as RUN_DEADLINE_MAX_MS,Ot as RUN_DELETE_STORAGE_BUDGET_MS,Ne as RUN_NAME_MAX,R as RunError,jn as cancelRun,Dt as cloudPersonScope,Yn as deleteRun,Fn as idempotencyKeyOf,Bn as interruptRun,Un as listRuns,Pn as messageRun,q as ownedRun,Se as parseRunDeadlineMs,qn as pruneExpiredIdempotencyKeys,Jn as renameRun,Xn as requestedScopes,Wn as runIsRunning,W as runObject,Hn as startRun,Gn as traceRun,$n as waitRun};
14
+ ORDER BY sequence_number`).all(e,l);return{nodes:p,sessionIds:y,latestSequence:l,completed:D,deltas:v}}),{nodes:d,sessionIds:f,latestSequence:r,completed:i,deltas:u}=n,m=await x(d,async o=>({node_id:o.node_id,parent_node_id:o.parent,conversations:await x(f.get(o.node_id)??[],async p=>{const{header:y,entries:a}=await ee().read(p);if(s==="entries")return{conversation_id:p,header:y,entries:a};const l=Fe.inMemory(y.cwd,void 0,[y,...a]),D=ut(l)??[],v=new Map(a.map((w,b)=>[w.id,b])),S=a.filter(w=>w.type==="custom"&&w.customType==="crtr-system-prompt").map(w=>({id:w.id,message:{role:"custom",customType:"crtr-system-prompt",data:"data"in w?w.data:void 0,timestamp:w.timestamp}})),A=a.filter(w=>w.type==="custom_message"&&w.customType==="app_message").map(w=>({id:w.id,message:{role:"custom",customType:"app_message",content:"content"in w?w.content:"",timestamp:w.timestamp}})),U=o.node_id===e?a.find(w=>{if(w.type!=="message"||w.message.role!=="user")return!1;const b=ie(a,w.message.timestamp);return b?.type==="runtime"&&b.kind==="kickoff"})?.id:void 0,ve=[...D.filter(({id:w})=>!A.some(b=>b.id===w)),...S,...A].sort((w,b)=>(v.get(w.id)??Number.MAX_SAFE_INTEGER)-(v.get(b.id)??Number.MAX_SAFE_INTEGER)).map(({id:w,message:b})=>{const _=b,T=_.usage,$=_.role==="user"?ie(a,Number(_.timestamp)):null,be=$?.type==="human"||w===U,L=_.role==="toolResult"?"tool_result":_.role==="bashExecution"?"bash_execution":_.role==="compactionSummary"?"compaction_summary":_.role==="branchSummary"?"branch_summary":_.role==="custom"&&_.customType==="crtr-system-prompt"?"system":_.role==="user"?be?"user":"custom":_.role,Oe=L==="custom"?_.role==="custom"?_.customType:$?.type==="runtime"?$.kind:"unknown":void 0,De=L==="system"&&_.customType==="crtr-system-prompt"?[{type:"text",text:_.data?.text??""}]:typeof _.content=="string"?[{type:"text",text:_.content}]:Array.isArray(_.content)?_.content.map(M=>M.type==="toolCall"?{type:"tool_call",tool_call_id:M.id,name:M.name,arguments:M.arguments}:M.type==="image"?{type:"image",mime_type:M.mimeType}:M.type==="thinking"?{type:"thinking",text:M.thinking}:M):[];return{entry_id:w,role:L,...L==="custom"?{custom_type:Oe}:{},time:typeof _.timestamp=="string"?_.timestamp:new Date(Number(_.timestamp??Date.now())).toISOString(),content:De,...L==="assistant"?{model:_.model,provider:_.provider,stop_reason:_.stopReason,..._.errorMessage?{error_message:_.errorMessage}:{},...T?{usage:{input_tokens:T.input??0,output_tokens:T.output??0,cache_read_tokens:T.cacheRead??0,cache_write_tokens:T.cacheWrite??0,cost_usd:T.cost?.total??0}}:{}}:{},...L==="tool_result"?{tool_call_id:_.toolCallId,tool_name:_.toolName,is_error:_.isError}:{}}})??[];return{conversation_id:p,messages:ve}})})),h=new Map(await Promise.all(m.map(async o=>[o.node_id,await pt(o.node_id)])));for(const o of i){const p=h.get(o.node_id);p!=null&&o.sequence_number>=p&&h.set(o.node_id,null)}const g=new Map;for(const o of u){const p=h.get(o.node_id);if(p==null||o.sequence_number<p)continue;const y=JSON.parse(o.body_json),a=g.get(o.node_id)??{thinking:"",text:""};o.type==="node.thinking.delta"?a.thinking+=y.delta:a.text+=y.delta,g.set(o.node_id,a)}const E=m.map(o=>({...o,in_progress_message:h.get(o.node_id)==null?null:g.get(o.node_id)??{thinking:"",text:""}}));return{run_id:e,latest_sequence:r,nodes:E}}c(zn,"traceRun");function Qn(e,t,s){if(t===void 0){if(!e.some(n=>P(n,"crtr:llm")))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return null}if(!Array.isArray(t)||t.some(n=>typeof n!="string"))throw N("scopes must be a list of scope strings","scopes");for(const n of t){try{_t(n)}catch{throw N(`invalid scope ${n}`,"scopes")}if(/^crtr:files:(?:read|rw):app:/.test(n)&&n.split(":")[4]===s.slice(4))throw N(`own-app files cannot narrow a run: ${n}`,"scopes");if(!(/^crtr:memory:(?:read|rw):app:/.test(n)&&n.split(":")[4]===s.slice(4))&&!Ie(e,n))throw new R(403,"scope_missing",`missing scope ${n}`,void 0,void 0,n)}if(!t.includes("crtr:llm"))throw new R(403,"scope_missing","missing scope crtr:llm",void 0,void 0,"crtr:llm");return t}c(Qn,"requestedScopes");export{z as IDEMPOTENCY_WINDOW_MS,It as RUN_DEADLINE_MAX_MS,Dt as RUN_DELETE_STORAGE_BUDGET_MS,Ne as RUN_NAME_MAX,R as RunError,Yn as cancelRun,Mt as cloudPersonScope,Xn as deleteRun,Hn as idempotencyKeyOf,Jn as interruptRun,Pn as listRuns,jn as messageRun,q as ownedRun,Se as parseRunDeadlineMs,Un as pruneExpiredIdempotencyKeys,Gn as renameRun,Qn as requestedScopes,qn as runIsRunning,W as runObject,$n as startRun,zn as traceRun,Bn as waitRun};
@@ -2,7 +2,7 @@ import { type AssistantMessageEventStream, type Context, type Model, type Models
2
2
  import type { ModelRuntime } from '@earendil-works/pi-coding-agent';
3
3
  /** Pi uses availability and auth for its model controls; only the daemon can
4
4
  * decide whether a route has credentials. Keep the broker's metadata selectable. */
5
- export declare function configureBrokerModelRuntime(runtime: ModelRuntime, intent?: string): void;
5
+ export declare function configureBrokerModelRuntime(runtime: ModelRuntime, intent?: string, causedBy?: string): void;
6
6
  export declare const LLM_BROKER_RETRY_WINDOW_S = 300;
7
7
  export declare const LLM_REFUSAL_DIAGNOSTIC = "crtr:llm-refusal";
8
8
  /** The retry deadline (epoch ms) a final daemon refusal carried on its
@@ -23,5 +23,5 @@ export declare function requestBody(model: Model<any>, context: Context, options
23
23
  /** The real response under a deferred-head framing (`daemon/api/deferred-head.ts`): the magic line, keep-alive
24
24
  * blank lines, one JSON head line, then the body unchanged. Any other response is returned as it is. */
25
25
  export declare function unwrapDeferredHead(response: Response): Promise<Response>;
26
- /** One pi stream, backed exclusively by the authenticated daemon llm route. `intent` replaces the node's own `CRTR_MODEL_INTENT` for one stream: a background one-shot of an app-paid node asks for `app/light`, not the node's rung. */
27
- export declare function streamBrokerModel(model: Model<any>, context: Context, options?: ModelsSimpleStreamOptions, intent?: string): AssistantMessageEventStream;
26
+ /** One pi stream, backed exclusively by the authenticated daemon llm route. `intent` replaces the node's own `CRTR_MODEL_INTENT` for one stream: a background one-shot of an app-paid node asks for `app/light`, not the node's rung. `causedBy` is the cause a one-shot helper captured when it was dispatched; the main stream reads the live turn's at call time. */
27
+ export declare function streamBrokerModel(model: Model<any>, context: Context, options?: ModelsSimpleStreamOptions, intent?: string, causedBy?: string): AssistantMessageEventStream;
@@ -1,3 +1,3 @@
1
- var J=Object.defineProperty;var d=(e,n)=>J(e,"name",{value:n,configurable:!0});import{setTimeout as Y}from"node:timers/promises";import{createAssistantMessageEventStream as z,getCurrentSystemPrompt as G,getCurrentTools as X}from"@earendil-works/pi-ai";import{socketFetch as K}from"../../../api/node-transport.js";import{DEFER_HEAD_REQUEST_HEADER as Q,DEFERRED_HEAD_CONTENT_TYPE as V,DEFERRED_HEAD_MAGIC as Z}from"../../../daemon/api/deferred-head.js";import{apiSocketPath as ee}from"../../canvas/paths.js";import{northlightUnreachable as te}from"../../directory/reach.js";import{currentNodeToken as ne}from"./node-token.js";import{brokerApiUrl as A}from"./daemon-ops.js";import{currentCausedBy as re}from"./turn-cause.js";import{envModelExact as U,envModelIntent as oe}from"../../../shared/env.js";import{MODEL_CONTEXT_BODY_MAX_BYTES as S}from"../../llm/body-limits.js";import{modelRequestFromConfig as se}from"../../model-routes.js";import{parseProviderRateLimit as ie,providerFailureDiagnostic as T}from"../../provider-failure.js";import{UPSTREAM_MODEL_UNAVAILABLE as D}from"../../fault-classifier.js";function De(e,n){e.checkAuth=async t=>e.getProvider(t)?{type:"api_key"}:void 0,e.getAvailable=async t=>e.getModels(t),e.getAvailableSnapshot=()=>e.getModels(),e.streamSimple=(t,u,o)=>pe(t,u,o,n)}d(De,"configureBrokerModelRuntime");const ae=300,q="crtr:llm-refusal";class k extends Error{static{d(this,"LlmRefusalError")}retryAt;rateLimit;code;status;constructor(n,t,u,o,r){super(n),this.retryAt=t,this.rateLimit=u,this.code=o,this.status=r}}class R extends Error{static{d(this,"UpstreamModelUnavailableError")}}function B(e,n){const t=e===void 0?"the request body":`the request body (${e} bytes)`;return new Error(`request_too_large: ${t} exceeds the ${n}-byte model request limit; compact the conversation to shrink the context`)}d(B,"contextTooLargeError");function L(e){const n=e.details?.rate_limit;if(!n||typeof n!="object")return;const t=ie(n);if(t===void 0)return;const{provider:u,model:o}=n;return typeof u=="string"&&u!==""&&typeof o=="string"&&o!==""?{...t,model:`${u}/${o}`}:t}d(L,"refusalRateLimit");function Le(e){for(const n of e.diagnostics??[]){if(n.type!==q)continue;const t=n.details?.retryAt;if(typeof t=="number"&&Number.isFinite(t)&&t>0)return t}}d(Le,"refusalRetryAt");class F extends Error{static{d(this,"ContentFilteredError")}}function P(e){return typeof e=="string"?e:e.map(n=>n.type==="text"?{type:"text",text:n.text}:{type:"image_url",image_url:{url:`data:${n.mimeType};base64,${n.data}`}})}d(P,"wireContent");function ce(e){const n=d(o=>o.role==="assistant"&&(o.stopReason==="error"||o.stopReason==="aborted"),"incomplete"),t=e.some(n)?e.filter(o=>!n(o)):e;let u=t.length;for(;u>0&&t[u-1]?.role==="assistant";)u--;return u===t.length?t:t.slice(0,u)}d(ce,"wireHistory");function le(e,n,t,u){const o=[],r=G(n.messages),i=X(n.messages);r?o.push({role:"system",content:r}):n.systemPrompt&&o.push({role:"system",content:n.systemPrompt});for(const f of ce(n.messages))f.role==="user"?o.push({role:"user",content:P(f.content)}):f.role==="toolResult"?o.push({role:"tool",content:P(f.content),tool_call_id:f.toolCallId}):f.role==="assistant"&&o.push({role:"assistant",content:f.content.filter(c=>c.type==="text").map(c=>c.text).join("")||null,tool_calls:f.content.filter(c=>c.type==="toolCall").map(c=>({id:c.id,type:"function",function:{name:c.name,arguments:JSON.stringify(c.arguments)}}))});const _=`${e.provider}/${e.id}`,y=u??oe();return{model:se(_,y,U())?.kind==="logical"?y:_,model_policy:U()?"exact":"substitute",stream:!0,messages:o,...i.length?{tools:i.map(f=>({type:"function",function:{name:f.name,description:f.description,parameters:f.parameters}}))}:{},...t.reasoning?{reasoning_effort:t.reasoning}:{},...t.temperature!==void 0?{temperature:t.temperature}:{},...t.maxTokens!==void 0?{max_tokens:t.maxTokens}:{}}}d(le,"requestBody");function O(e,n){if(typeof n!="string")return!1;const t=n.indexOf("/");return t<1||t===n.length-1?!1:(e.provider=n.slice(0,t),e.model=n.slice(t+1),!0)}d(O,"applyServedModel");function H(e){if(e.details?.served_model!==void 0)return e.details.served_model;const n=e.details?.rate_limit;return typeof n?.provider=="string"&&typeof n.model=="string"?`${n.provider}/${n.model}`:void 0}d(H,"refusalRoute");function j(e){if(!e||typeof e!="object"||!("error"in e))throw new Error("llm returned an invalid response");const n=e.error;if(!n||typeof n.message!="string")throw new Error("llm returned an invalid refusal");return n}d(j,"parseRefusal");function W(){const e=A();return e===void 0?"http://localhost/v1/llm":new URL("/v1/llm",e).toString()}d(W,"llmUrl");function de(){return A()===void 0?K(ee()):globalThis.fetch}d(de,"llmFetch");async function ue(e){if(!e.headers.get("content-type")?.startsWith(V))return e;if(!e.body)throw new Error("deferred head response has no body");const n=e.body.getReader();let t=new Uint8Array(0),u=!1;for(;;){const o=t.indexOf(10);if(o<0){const{value:c,done:p}=await n.read();if(p)throw new Error("deferred head response ended before its head");const w=new Uint8Array(t.length+c.length);w.set(t),w.set(c,t.length),t=w;continue}const r=new TextDecoder().decode(t.subarray(0,o)).trim();if(t=t.subarray(o+1),!u){if(`${r}
2
- `!==Z)throw new Error("deferred head response has no magic line");u=!0;continue}if(r==="")continue;const i=JSON.parse(r),_=new Headers;for(const[c,p]of Object.entries(i.headers))for(const w of Array.isArray(p)?p:[p])_.append(c,w);const y=t,f=new ReadableStream({start(c){y.length>0&&c.enqueue(y)},async pull(c){const{value:p,done:w}=await n.read();w?c.close():c.enqueue(p)},cancel(c){return n.cancel(c)}});return new Response(i.status===204?null:f,{status:i.status,headers:_})}}d(ue,"unwrapDeferredHead");function fe(){const e=re();return e===void 0?{}:{"crtr-caused-by":e}}d(fe,"causedByHeader");function pe(e,n,t={},u){const o=z(),r={role:"assistant",content:[],api:e.api,provider:e.provider,model:e.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"pending",timestamp:Date.now()};return(async()=>{const i=ne();if(!i)throw new Error("llm requires a node token");const _=JSON.stringify(le(e,n,t,u)),y=Buffer.byteLength(_);if(y>S)throw B(y,S);let f=0;for(;;){t.signal?.throwIfAborted();const c=await de()(W(),{method:"POST",headers:{authorization:`Bearer ${i}`,"content-type":"application/json",[Q]:"1",...fe()},body:_,signal:t.signal}).catch(l=>{const s=W();throw A()===void 0||t.signal?.aborted?l:new R(te(l,s).message)}),p=await ue(c);if(!p.ok){const l=j(await p.json());if(p.status===413||l.code==="payload_too_large")throw B(y,S);if(O(r,H(l)),l.retryable!==!0)throw new k(l.message,void 0,L(l),l.code,p.status);const s=l.retry_after_s;if(typeof s!="number"||!Number.isFinite(s)||s<=0)throw l.code===D?new R(l.message):new Error(l.message);const h=ae-f;if(s>h){const m=Date.now()+s*1e3;throw new k(`${l.message} (retry after ${s}s, at ${new Date(m).toISOString()})`,m,L(l))}await Y(s*1e3,void 0,{signal:t.signal}),f+=s;continue}if(!p.body)throw new Error("llm returned no stream");o.push({type:"start",partial:r});const w=p.body.getReader(),I=new TextDecoder;let x="",b="stop",M,$=!1,C=!1;const N=d(l=>{const s=JSON.parse(l);if(s.error){const a=j(s);throw O(r,H(a)),a.retryable!==!0?new k(a.message,void 0,L(a),a.code):a.code===D?new R(a.message):new Error(a.message)}if(s.served_model){if(C=!0,s.served_model.indexOf("/")<1)throw new Error("llm returned an invalid served_model");O(r,s.served_model)}const h=s.choices?.[0],m=h?.delta;if(m?.reasoning_content){const a=r.content.findIndex(v=>v.type==="thinking"),g=a<0?{type:"thinking",thinking:""}:r.content[a],E=a<0?r.content.push(g)-1:a;if(a<0&&o.push({type:"thinking_start",contentIndex:E,partial:r}),g.type!=="thinking")throw new Error("invalid thinking block");g.thinking+=m.reasoning_content,o.push({type:"thinking_delta",contentIndex:E,delta:m.reasoning_content,partial:r})}if(m?.content){const a=r.content.findIndex(v=>v.type==="text"),g=a<0?{type:"text",text:""}:r.content[a],E=a<0?r.content.push(g)-1:a;if(a<0&&o.push({type:"text_start",contentIndex:E,partial:r}),g.type!=="text")throw new Error("invalid text block");g.text+=m.content,o.push({type:"text_delta",contentIndex:E,delta:m.content,partial:r})}for(const a of m?.tool_calls??[]){const g={type:"toolCall",id:a.id,name:a.function.name,arguments:JSON.parse(a.function.arguments)},E=r.content.push(g)-1;o.push({type:"toolcall_start",contentIndex:E,partial:r}),o.push({type:"toolcall_end",contentIndex:E,toolCall:g,partial:r})}h?.finish_reason&&(b=h.finish_reason),h?.message?.refusal&&(M=h.message.refusal),s.usage&&($=!0,r.usage={input:s.usage.prompt_tokens,output:s.usage.completion_tokens,cacheRead:s.usage.cache_read_tokens,cacheWrite:s.usage.cache_write_tokens,totalTokens:s.usage.total_tokens,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:typeof s.usage.usd_equivalent=="number"&&Number.isFinite(s.usage.usd_equivalent)?s.usage.usd_equivalent:0}})},"apply");for(;;){const{value:l,done:s}=await w.read();if(s)break;x+=I.decode(l,{stream:!0});let h;for(;(h=x.indexOf(`
3
- `))>=0;){const m=x.slice(0,h).trim();x=x.slice(h+1),m&&N(m)}}if(x+=I.decode(),x.trim()&&N(x.trim()),!$||!C)throw new Error("llm stream ended without usage or served_model");for(const[l,s]of r.content.entries())s.type==="text"&&o.push({type:"text_end",contentIndex:l,content:s.text,partial:r}),s.type==="thinking"&&o.push({type:"thinking_end",contentIndex:l,content:s.thinking,partial:r});if(b==="content_filter")throw new F(M??"Content filtered");if(!["stop","tool_calls","length"].includes(b))throw new Error(`llm returned unknown finish reason: ${b}`);r.stopReason=b==="tool_calls"?"toolUse":b==="length"?"length":"stop",o.push({type:"done",reason:r.stopReason,message:r}),o.end();return}})().catch(i=>{r.stopReason=t.signal?.aborted?"aborted":"error",r.errorMessage=i instanceof Error?i.message:String(i),i instanceof k&&(r.diagnostics=[{type:q,timestamp:Date.now(),...i.retryAt!==void 0?{details:{retryAt:i.retryAt}}:{}},...i.rateLimit!==void 0?[T({status:429,ratelimit:i.rateLimit})]:[],...i.rateLimit===void 0&&i.code==="cap_exhausted"?[T({status:i.status??402,code:i.code})]:[]]),i instanceof R&&(r.diagnostics=[T({status:503,code:D})]),i instanceof F&&r.stopReason==="error"&&(r.rawStopReason="content_filter"),o.push({type:"error",reason:r.stopReason,error:r}),o.end()}),o}d(pe,"streamBrokerModel");export{ae as LLM_BROKER_RETRY_WINDOW_S,q as LLM_REFUSAL_DIAGNOSTIC,De as configureBrokerModelRuntime,Le as refusalRetryAt,le as requestBody,pe as streamBrokerModel,ue as unwrapDeferredHead,ce as wireHistory};
1
+ var Y=Object.defineProperty;var f=(e,t)=>Y(e,"name",{value:t,configurable:!0});import{setTimeout as z}from"node:timers/promises";import{createAssistantMessageEventStream as G,getCurrentSystemPrompt as X,getCurrentTools as K}from"@earendil-works/pi-ai";import{socketFetch as Q}from"../../../api/node-transport.js";import{DEFER_HEAD_REQUEST_HEADER as V,DEFERRED_HEAD_CONTENT_TYPE as Z,DEFERRED_HEAD_MAGIC as ee}from"../../../daemon/api/deferred-head.js";import{apiSocketPath as te}from"../../canvas/paths.js";import{northlightUnreachable as ne}from"../../directory/reach.js";import{currentNodeToken as re}from"./node-token.js";import{brokerApiUrl as A}from"./daemon-ops.js";import{currentCausedBy as oe}from"./turn-cause.js";import{envModelExact as U,envModelIntent as se}from"../../../shared/env.js";import{MODEL_CONTEXT_BODY_MAX_BYTES as S}from"../../llm/body-limits.js";import{modelRequestFromConfig as ie}from"../../model-routes.js";import{parseProviderRateLimit as ae,providerFailureDiagnostic as T}from"../../provider-failure.js";import{UPSTREAM_MODEL_UNAVAILABLE as D}from"../../fault-classifier.js";function Le(e,t,n){e.checkAuth=async a=>e.getProvider(a)?{type:"api_key"}:void 0,e.getAvailable=async a=>e.getModels(a),e.getAvailableSnapshot=()=>e.getModels(),e.streamSimple=(a,s,i)=>me(a,s,i,t,n)}f(Le,"configureBrokerModelRuntime");const ce=300,q="crtr:llm-refusal";class k extends Error{static{f(this,"LlmRefusalError")}retryAt;rateLimit;code;status;constructor(t,n,a,s,i){super(t),this.retryAt=n,this.rateLimit=a,this.code=s,this.status=i}}class R extends Error{static{f(this,"UpstreamModelUnavailableError")}}function B(e,t){const n=e===void 0?"the request body":`the request body (${e} bytes)`;return new Error(`request_too_large: ${n} exceeds the ${t}-byte model request limit; compact the conversation to shrink the context`)}f(B,"contextTooLargeError");function L(e){const t=e.details?.rate_limit;if(!t||typeof t!="object")return;const n=ae(t);if(n===void 0)return;const{provider:a,model:s}=t;return typeof a=="string"&&a!==""&&typeof s=="string"&&s!==""?{...n,model:`${a}/${s}`}:n}f(L,"refusalRateLimit");function Oe(e){for(const t of e.diagnostics??[]){if(t.type!==q)continue;const n=t.details?.retryAt;if(typeof n=="number"&&Number.isFinite(n)&&n>0)return n}}f(Oe,"refusalRetryAt");class F extends Error{static{f(this,"ContentFilteredError")}}function P(e){return typeof e=="string"?e:e.map(t=>t.type==="text"?{type:"text",text:t.text}:{type:"image_url",image_url:{url:`data:${t.mimeType};base64,${t.data}`}})}f(P,"wireContent");function le(e){const t=f(s=>s.role==="assistant"&&(s.stopReason==="error"||s.stopReason==="aborted"),"incomplete"),n=e.some(t)?e.filter(s=>!t(s)):e;let a=n.length;for(;a>0&&n[a-1]?.role==="assistant";)a--;return a===n.length?n:n.slice(0,a)}f(le,"wireHistory");function de(e,t,n,a){const s=[],i=X(t.messages),r=K(t.messages);i?s.push({role:"system",content:i}):t.systemPrompt&&s.push({role:"system",content:t.systemPrompt});for(const p of le(t.messages))p.role==="user"?s.push({role:"user",content:P(p.content)}):p.role==="toolResult"?s.push({role:"tool",content:P(p.content),tool_call_id:p.toolCallId}):p.role==="assistant"&&s.push({role:"assistant",content:p.content.filter(c=>c.type==="text").map(c=>c.text).join("")||null,tool_calls:p.content.filter(c=>c.type==="toolCall").map(c=>({id:c.id,type:"function",function:{name:c.name,arguments:JSON.stringify(c.arguments)}}))});const d=`${e.provider}/${e.id}`,_=a??se();return{model:ie(d,_,U())?.kind==="logical"?_:d,model_policy:U()?"exact":"substitute",stream:!0,messages:s,...r.length?{tools:r.map(p=>({type:"function",function:{name:p.name,description:p.description,parameters:p.parameters}}))}:{},...n.reasoning?{reasoning_effort:n.reasoning}:{},...n.temperature!==void 0?{temperature:n.temperature}:{},...n.maxTokens!==void 0?{max_tokens:n.maxTokens}:{}}}f(de,"requestBody");function O(e,t){if(typeof t!="string")return!1;const n=t.indexOf("/");return n<1||n===t.length-1?!1:(e.provider=t.slice(0,n),e.model=t.slice(n+1),!0)}f(O,"applyServedModel");function H(e){if(e.details?.served_model!==void 0)return e.details.served_model;const t=e.details?.rate_limit;return typeof t?.provider=="string"&&typeof t.model=="string"?`${t.provider}/${t.model}`:void 0}f(H,"refusalRoute");function j(e){if(!e||typeof e!="object"||!("error"in e))throw new Error("llm returned an invalid response");const t=e.error;if(!t||typeof t.message!="string")throw new Error("llm returned an invalid refusal");return t}f(j,"parseRefusal");function W(){const e=A();return e===void 0?"http://localhost/v1/llm":new URL("/v1/llm",e).toString()}f(W,"llmUrl");function ue(){return A()===void 0?Q(te()):globalThis.fetch}f(ue,"llmFetch");async function fe(e){if(!e.headers.get("content-type")?.startsWith(Z))return e;if(!e.body)throw new Error("deferred head response has no body");const t=e.body.getReader();let n=new Uint8Array(0),a=!1;for(;;){const s=n.indexOf(10);if(s<0){const{value:c,done:w}=await t.read();if(w)throw new Error("deferred head response ended before its head");const m=new Uint8Array(n.length+c.length);m.set(n),m.set(c,n.length),n=m;continue}const i=new TextDecoder().decode(n.subarray(0,s)).trim();if(n=n.subarray(s+1),!a){if(`${i}
2
+ `!==ee)throw new Error("deferred head response has no magic line");a=!0;continue}if(i==="")continue;const r=JSON.parse(i),d=new Headers;for(const[c,w]of Object.entries(r.headers))for(const m of Array.isArray(w)?w:[w])d.append(c,m);const _=n,p=new ReadableStream({start(c){_.length>0&&c.enqueue(_)},async pull(c){const{value:w,done:m}=await t.read();m?c.close():c.enqueue(w)},cancel(c){return t.cancel(c)}});return new Response(r.status===204?null:p,{status:r.status,headers:d})}}f(fe,"unwrapDeferredHead");function pe(e){const t=e??oe();return t===void 0?{}:{"crtr-caused-by":t}}f(pe,"causedByHeader");function me(e,t,n={},a,s){const i=G(),r={role:"assistant",content:[],api:e.api,provider:e.provider,model:e.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"pending",timestamp:Date.now()};return(async()=>{const d=re();if(!d)throw new Error("llm requires a node token");const _=JSON.stringify(de(e,t,n,a)),p=Buffer.byteLength(_);if(p>S)throw B(p,S);let c=0;for(;;){n.signal?.throwIfAborted();const w=await ue()(W(),{method:"POST",headers:{authorization:`Bearer ${d}`,"content-type":"application/json",[V]:"1",...pe(s)},body:_,signal:n.signal}).catch(u=>{const o=W();throw A()===void 0||n.signal?.aborted?u:new R(ne(u,o).message)}),m=await fe(w);if(!m.ok){const u=j(await m.json());if(m.status===413||u.code==="payload_too_large")throw B(p,S);if(O(r,H(u)),u.retryable!==!0)throw new k(u.message,void 0,L(u),u.code,m.status);const o=u.retry_after_s;if(typeof o!="number"||!Number.isFinite(o)||o<=0)throw u.code===D?new R(u.message):new Error(u.message);const g=ce-c;if(o>g){const h=Date.now()+o*1e3;throw new k(`${u.message} (retry after ${o}s, at ${new Date(h).toISOString()})`,h,L(u))}await z(o*1e3,void 0,{signal:n.signal}),c+=o;continue}if(!m.body)throw new Error("llm returned no stream");i.push({type:"start",partial:r});const J=m.body.getReader(),I=new TextDecoder;let x="",b="stop",M,$=!1,C=!1;const N=f(u=>{const o=JSON.parse(u);if(o.error){const l=j(o);throw O(r,H(l)),l.retryable!==!0?new k(l.message,void 0,L(l),l.code):l.code===D?new R(l.message):new Error(l.message)}if(o.served_model){if(C=!0,o.served_model.indexOf("/")<1)throw new Error("llm returned an invalid served_model");O(r,o.served_model)}const g=o.choices?.[0],h=g?.delta;if(h?.reasoning_content){const l=r.content.findIndex(v=>v.type==="thinking"),y=l<0?{type:"thinking",thinking:""}:r.content[l],E=l<0?r.content.push(y)-1:l;if(l<0&&i.push({type:"thinking_start",contentIndex:E,partial:r}),y.type!=="thinking")throw new Error("invalid thinking block");y.thinking+=h.reasoning_content,i.push({type:"thinking_delta",contentIndex:E,delta:h.reasoning_content,partial:r})}if(h?.content){const l=r.content.findIndex(v=>v.type==="text"),y=l<0?{type:"text",text:""}:r.content[l],E=l<0?r.content.push(y)-1:l;if(l<0&&i.push({type:"text_start",contentIndex:E,partial:r}),y.type!=="text")throw new Error("invalid text block");y.text+=h.content,i.push({type:"text_delta",contentIndex:E,delta:h.content,partial:r})}for(const l of h?.tool_calls??[]){const y={type:"toolCall",id:l.id,name:l.function.name,arguments:JSON.parse(l.function.arguments)},E=r.content.push(y)-1;i.push({type:"toolcall_start",contentIndex:E,partial:r}),i.push({type:"toolcall_end",contentIndex:E,toolCall:y,partial:r})}g?.finish_reason&&(b=g.finish_reason),g?.message?.refusal&&(M=g.message.refusal),o.usage&&($=!0,r.usage={input:o.usage.prompt_tokens,output:o.usage.completion_tokens,cacheRead:o.usage.cache_read_tokens,cacheWrite:o.usage.cache_write_tokens,totalTokens:o.usage.total_tokens,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:typeof o.usage.usd_equivalent=="number"&&Number.isFinite(o.usage.usd_equivalent)?o.usage.usd_equivalent:0}})},"apply");for(;;){const{value:u,done:o}=await J.read();if(o)break;x+=I.decode(u,{stream:!0});let g;for(;(g=x.indexOf(`
3
+ `))>=0;){const h=x.slice(0,g).trim();x=x.slice(g+1),h&&N(h)}}if(x+=I.decode(),x.trim()&&N(x.trim()),!$||!C)throw new Error("llm stream ended without usage or served_model");for(const[u,o]of r.content.entries())o.type==="text"&&i.push({type:"text_end",contentIndex:u,content:o.text,partial:r}),o.type==="thinking"&&i.push({type:"thinking_end",contentIndex:u,content:o.thinking,partial:r});if(b==="content_filter")throw new F(M??"Content filtered");if(!["stop","tool_calls","length"].includes(b))throw new Error(`llm returned unknown finish reason: ${b}`);r.stopReason=b==="tool_calls"?"toolUse":b==="length"?"length":"stop",i.push({type:"done",reason:r.stopReason,message:r}),i.end();return}})().catch(d=>{r.stopReason=n.signal?.aborted?"aborted":"error",r.errorMessage=d instanceof Error?d.message:String(d),d instanceof k&&(r.diagnostics=[{type:q,timestamp:Date.now(),...d.retryAt!==void 0?{details:{retryAt:d.retryAt}}:{}},...d.rateLimit!==void 0?[T({status:429,ratelimit:d.rateLimit})]:[],...d.rateLimit===void 0&&d.code==="cap_exhausted"?[T({status:d.status??402,code:d.code})]:[]]),d instanceof R&&(r.diagnostics=[T({status:503,code:D})]),d instanceof F&&r.stopReason==="error"&&(r.rawStopReason="content_filter"),i.push({type:"error",reason:r.stopReason,error:r}),i.end()}),i}f(me,"streamBrokerModel");export{ce as LLM_BROKER_RETRY_WINDOW_S,q as LLM_REFUSAL_DIAGNOSTIC,Le as configureBrokerModelRuntime,Oe as refusalRetryAt,de as requestBody,me as streamBrokerModel,fe as unwrapDeferredHead,le as wireHistory};
@@ -4,4 +4,8 @@ type Branch = ReturnType<BrokerSession['sessionManager']['getBranch']>;
4
4
  export declare function turnCauseOf(branch: Branch): string | undefined;
5
5
  /** `message:<id>` of the turn running now, or undefined. */
6
6
  export declare function currentCausedBy(): string | undefined;
7
+ /** The cause a one-shot helper call dispatched now belongs to: the turn whose input entry is staged and whose prompt has not
8
+ * reached the session yet (a helper started by that prompt's own input event runs before its user entry exists), else the
9
+ * running or latest turn's. Read once at dispatch, so a helper that outlives its turn keeps that turn's cause. */
10
+ export declare function currentOrPendingCausedBy(): string | undefined;
7
11
  export {};
@@ -1 +1 @@
1
- var f=Object.defineProperty;var r=(s,e)=>f(s,"name",{value:e,configurable:!0});let u;function c(s){u=s}r(c,"setTurnCauseSource");function a(s){let e=[],t;for(const n of s)if(n.type==="custom"&&n.customType==="crtr-turn-input"){const i=n.data?.message_ids;Array.isArray(i)&&e.push(...i.filter(o=>typeof o=="string"))}else(n.type==="message"&&n.message.role==="user"||n.type==="custom_message")&&(e.length>0?(t=e[0],e=[]):t=void 0);return t===void 0?void 0:`message:${t}`}r(a,"turnCauseOf");function p(){return u===void 0?void 0:a(u().sessionManager.getBranch())}r(p,"currentCausedBy");export{p as currentCausedBy,c as setTurnCauseSource,a as turnCauseOf};
1
+ var a=Object.defineProperty;var r=(n,e)=>a(n,"name",{value:e,configurable:!0});let u;function g(n){u=n}r(g,"setTurnCauseSource");function d(n){let e=[],s;for(const t of n)if(t.type==="custom"&&t.customType==="crtr-turn-input"){const i=t.data?.message_ids;Array.isArray(i)&&e.push(...i.filter(o=>typeof o=="string"))}else(t.type==="message"&&t.message.role==="user"||t.type==="custom_message")&&(e.length>0?(s=e[0],e=[]):s=void 0);return{cause:s,pending:e}}r(d,"causeAndPending");function f(n){const{cause:e}=d(n);return e===void 0?void 0:`message:${e}`}r(f,"turnCauseOf");function p(){return u===void 0?void 0:f(u().sessionManager.getBranch())}r(p,"currentCausedBy");function y(){if(u===void 0)return;const{cause:n,pending:e}=d(u().sessionManager.getBranch()),s=e[0]??n;return s===void 0?void 0:`message:${s}`}r(y,"currentOrPendingCausedBy");export{p as currentCausedBy,y as currentOrPendingCausedBy,g as setTurnCauseSource,f as turnCauseOf};
@@ -20,6 +20,8 @@ export interface HeadlessRequest {
20
20
  seedMessages?: readonly AgentMessage[];
21
21
  /** Reuses a provider cache identity without binding or persisting a session. */
22
22
  sessionId?: string;
23
+ /** The turn cause the call belongs to (`message:<id>`), captured by the caller with the call's input; null names none. Omit to read the turn at dispatch. */
24
+ causedBy?: string | null;
23
25
  /** Omit `noTools` to load Pi's builtin tool suite. */
24
26
  toolPolicy?: 'builtin';
25
27
  }
@@ -1 +1 @@
1
- var j=Object.defineProperty;var s=(e,t)=>j(e,"name",{value:t,configurable:!0});import{envModelIntent as _,envNodeCwd as D,envProfileId as E}from"../../shared/env.js";import{join as N}from"node:path";import{InMemoryCredentialStore as O}from"@earendil-works/pi-ai";import{APP_FAMILY as c,expandModelCandidates as $,modelRequestFromConfig as I}from"../model-routes.js";import{defaultModelLaddersConfig as B}from"../../types.js";import{stripThinkingSuffix as H}from"./model-selection.js";import{configureBrokerModelRuntime as W}from"./broker/model-adapter.js";import{currentNodeToken as Y}from"./broker/node-token.js";function z(e){for(let t=e.length-1;t>=0;t--){const n=e[t];if(n?.role!=="assistant")continue;if(typeof n.content=="string")return n.content.trim();if(!Array.isArray(n.content))continue;const m=n.content.map(i=>{const u=i;return u.type==="text"&&typeof u.text=="string"?u.text:""}).join("").trim();if(m!=="")return m}return""}s(z,"lastAssistantText");function G(e,t){const n=_();if(!(n===void 0||!n.startsWith(`${c}/`)))return t?{modelRequest:I(e.modelSpec,n,!1),intent:n}:{modelRequest:{kind:"logical",family:c,strength:"light",spec:H(B().anthropic.light)},intent:`${c}/light`}}s(G,"appPaidRequest");async function f(e,t,n,m){let i,u=!1,d=!1,p;const g=s(o=>{i===void 0||u||(u=!0,o&&i.abort().catch(()=>{}),i.dispose())},"disposeSession"),R=new Promise(o=>{p=setTimeout(()=>{d=!0,g(!0),o(t)},e.timeoutMs)}),v=(async()=>{try{const o=await import("@earendil-works/pi-coding-agent");if(Y()===void 0||d)return t;const l=D()??process.cwd(),C=E(),y=o.getAgentDir(),x=await o.ModelRuntime.create({credentials:new O,modelsPath:N(y,"models.json")}),M=G(e,n);if(W(x,M?.intent),d)return t;const h=await o.createAgentSessionServices({cwd:l,agentDir:y,modelRuntime:x,settingsManager:o.SettingsManager.inMemory(),resourceLoaderOptions:{noExtensions:!0,noContextFiles:!0,noSkills:!0,noPromptTemplates:!0,noThemes:!0,systemPrompt:e.systemPrompt}});if(d)return t;const w=M?.modelRequest??I(e.modelSpec,e.modelIntent,!1);if(w===void 0)return t;const A=new o.ModelRegistry(h.modelRuntime),S=(await $(w,l,C)).map(a=>A.find(a.providerId,a.modelId)).find(a=>a!==void 0);if(S===void 0)return t;let T;const r=e.tool,F=r===void 0?void 0:o.defineTool({name:r.name,label:r.label,description:r.description,parameters:r.parameters,...r.constrainedSampling===void 0?{}:{constrainedSampling:r.constrainedSampling},async execute(a,L){return T=L,{content:[{type:"text",text:"Recorded."}],details:{recorded:!0},terminate:!0}}}),P=o.SessionManager.inMemory(l,e.sessionId===void 0?void 0:{id:e.sessionId});for(const a of e.seedMessages??[])P.appendMessage(a);return{session:i}=await o.createAgentSessionFromServices({services:h,sessionManager:P,model:S,thinkingLevel:"off",...r===void 0?e.toolPolicy==="builtin"?{}:{noTools:"all"}:{tools:[r.name],customTools:[F]}}),d||(await i.bindExtensions({}),d)?t:(await i.prompt(e.userPrompt),d?t:m(i,T))}catch{return t}finally{g(!1)}})();try{return await Promise.race([v,R])}finally{clearTimeout(p)}}s(f,"runHeadless");function b(e){return f(e,null,!1,(t,n)=>n)}s(b,"headlessToolCall");function ee(e){return f(e,"",!1,t=>z(t.messages))}s(ee,"headlessText");function te(e){return f(e,"failed",!0,()=>"completed")}s(te,"headlessTurn");export{ee as headlessText,b as headlessToolCall,te as headlessTurn};
1
+ var j=Object.defineProperty;var s=(e,t)=>j(e,"name",{value:t,configurable:!0});import{envModelIntent as O,envNodeCwd as _,envProfileId as D}from"../../shared/env.js";import{join as E}from"node:path";import{InMemoryCredentialStore as N}from"@earendil-works/pi-ai";import{APP_FAMILY as c,expandModelCandidates as $,modelRequestFromConfig as R}from"../model-routes.js";import{defaultModelLaddersConfig as H}from"../../types.js";import{stripThinkingSuffix as W}from"./model-selection.js";import{configureBrokerModelRuntime as Y}from"./broker/model-adapter.js";import{currentNodeToken as z}from"./broker/node-token.js";import{currentOrPendingCausedBy as G}from"./broker/turn-cause.js";function J(e){for(let t=e.length-1;t>=0;t--){const n=e[t];if(n?.role!=="assistant")continue;if(typeof n.content=="string")return n.content.trim();if(!Array.isArray(n.content))continue;const u=n.content.map(m=>{const i=m;return i.type==="text"&&typeof i.text=="string"?i.text:""}).join("").trim();if(u!=="")return u}return""}s(J,"lastAssistantText");function K(e,t){const n=O();if(!(n===void 0||!n.startsWith(`${c}/`)))return t?{modelRequest:R(e.modelSpec,n,!1),intent:n}:{modelRequest:{kind:"logical",family:c,strength:"light",spec:W(H().anthropic.light)},intent:`${c}/light`}}s(K,"appPaidRequest");async function f(e,t,n,u){const m=e.causedBy===void 0?G():e.causedBy??void 0;let i,p=!1,d=!1,g;const y=s(o=>{i===void 0||p||(p=!0,o&&i.abort().catch(()=>{}),i.dispose())},"disposeSession"),v=new Promise(o=>{g=setTimeout(()=>{d=!0,y(!0),o(t)},e.timeoutMs)}),C=(async()=>{try{const o=await import("@earendil-works/pi-coding-agent");if(z()===void 0||d)return t;const l=_()??process.cwd(),A=D(),x=o.getAgentDir(),M=await o.ModelRuntime.create({credentials:new N,modelsPath:E(x,"models.json")}),h=K(e,n);if(Y(M,h?.intent,m),d)return t;const w=await o.createAgentSessionServices({cwd:l,agentDir:x,modelRuntime:M,settingsManager:o.SettingsManager.inMemory(),resourceLoaderOptions:{noExtensions:!0,noContextFiles:!0,noSkills:!0,noPromptTemplates:!0,noThemes:!0,systemPrompt:e.systemPrompt}});if(d)return t;const S=h?.modelRequest??R(e.modelSpec,e.modelIntent,!1);if(S===void 0)return t;const B=new o.ModelRegistry(w.modelRuntime),P=(await $(S,l,A)).map(a=>B.find(a.providerId,a.modelId)).find(a=>a!==void 0);if(P===void 0)return t;let T;const r=e.tool,F=r===void 0?void 0:o.defineTool({name:r.name,label:r.label,description:r.description,parameters:r.parameters,...r.constrainedSampling===void 0?{}:{constrainedSampling:r.constrainedSampling},async execute(a,L){return T=L,{content:[{type:"text",text:"Recorded."}],details:{recorded:!0},terminate:!0}}}),I=o.SessionManager.inMemory(l,e.sessionId===void 0?void 0:{id:e.sessionId});for(const a of e.seedMessages??[])I.appendMessage(a);return{session:i}=await o.createAgentSessionFromServices({services:w,sessionManager:I,model:P,thinkingLevel:"off",...r===void 0?e.toolPolicy==="builtin"?{}:{noTools:"all"}:{tools:[r.name],customTools:[F]}}),d||(await i.bindExtensions({}),d)?t:(await i.prompt(e.userPrompt),d?t:u(i,T))}catch{return t}finally{y(!1)}})();try{return await Promise.race([C,v])}finally{clearTimeout(g)}}s(f,"runHeadless");function ne(e){return f(e,null,!1,(t,n)=>n)}s(ne,"headlessToolCall");function oe(e){return f(e,"",!1,t=>J(t.messages))}s(oe,"headlessText");function ie(e){return f(e,"failed",!0,()=>"completed")}s(ie,"headlessTurn");export{oe as headlessText,ne as headlessToolCall,ie as headlessTurn};
@@ -55,11 +55,11 @@ export declare function sanitizeIcon(raw: string): string;
55
55
  * This module stays canvas-free so CLI leaves can reach it. The canvas-coupled
56
56
  * commit path lives in pi-extensions/broker-local.ts, which imports this pure
57
57
  * helper and writes through crtrd's guarded generated-name operation. */
58
- export declare function headlessName(body: string, sourceCap?: number): Promise<SessionName>;
58
+ export declare function headlessName(body: string, sourceCap?: number, causedBy?: string | null | undefined): Promise<SessionName>;
59
59
  /** Generate a name from a bounded conversation source rather than a single
60
60
  * kickoff message. The caller owns selecting representative conversation text;
61
61
  * this cap keeps the one-shot prompt compact. */
62
- export declare function headlessNameFromConversation(conversation: string): Promise<SessionName>;
62
+ export declare function headlessNameFromConversation(conversation: string, causedBy?: string | null): Promise<SessionName>;
63
63
  /** A filesystem-safe slug for the `.todos/<slug>.md` fallback path: lowercase,
64
64
  * [a-z0-9] kept, everything else → single hyphen, clamped to 8 words. */
65
65
  export declare function titleSlug(title: string): string;
@@ -1,14 +1,14 @@
1
- var U=Object.defineProperty;var s=(e,t)=>U(e,"name",{value:t,configurable:!0});import{headlessText as k,headlessToolCall as p}from"./headless-pi.js";import{defaultProvider as h,modelLadders as m,stripThinkingSuffix as g}from"./model-selection.js";const a=2e3,f=45e3,x=["fixing/diagnosis: U+F188 bug, U+F0F1 stethoscope, U+F06D fire, U+F071 warning","building/changing code: U+F0AD wrench, U+F067 plus, U+F121 code, U+F085 cogs, U+F021 refresh","testing/verifying: U+F0C3 flask, U+F00C check, U+F24E balance","writing/docs/research: U+F02D book, U+F15C document, U+F002 magnifier, U+F0EB lightbulb","data/infra: U+F1C0 database, U+F0C2 cloud, U+F120 terminal, U+F013 gear, U+F1B3 packages","ship/perf/scale: U+F135 rocket, U+F0E7 bolt, U+F080 chart, U+F201 trend line","security/access: U+F132 shield, U+F023 lock, U+F084 key","interface/design: U+F1FC paintbrush, U+F03E image, U+F108 display","communication/coordination: U+F1D8 paper plane, U+F0E6 comments, U+F0E8 sitemap, U+F0C1 link"].join(`
2
- `),T="You label agent sessions. A session is whatever a person opened it for: a coding task, a question, a piece of research, an idea they are turning over, or an ordinary conversation. You are labelling that session, never taking part in it \u2014 do not answer the message, do not do the work it asks for, do not comment on it. Every session gets a label, however short, casual, misspelled, or open-ended its opening message is.\n\nCall the `name_session` tool exactly once with:\n- `title`: how a person would refer to this session in a list of their conversations \u2014 2-8 words, sentence case, written naturally with its punctuation intact (e.g. `What's your favorite color?`, `Payroll export mapping bug`, `Ideas for the launch email`). No quotes, no markdown, no trailing period.\n- `name`: the same subject as a concise 3-8 word kebab-case handle \u2014 lowercase words joined by single hyphens (e.g. `favorite-color-question`, `refactor-auth-token-flow`). No punctuation, quotes, or prose.\n- `icon`: ONE Nerd Font glyph, written as its codepoint in `U+XXXX` form (e.g. `U+F188`). Emit a codepoint, not a name like `nf-fa-bug`, and not an emoji.\n\nChoosing the icon \u2014 work through it in this order:\n1. Name the concrete SUBJECT of the task: the thing being acted on. A font, a trash can, an account, a browser tab, a clock, an invoice, a map, a robot, a specific technology's logo. If a glyph depicts that thing, use it. This is almost always the better icon, because it is what distinguishes this session from every other one.\n2. Only when the task has no depictable subject, fall back to the ACTIVITY (fixing, documenting, testing) using the generic set below.\n\nANY Nerd Font codepoint is allowed and the set is enormous \u2014 the whole Font Awesome range, Devicons and Seti technology logos, Octicons, Codicons, Weather, and the thousands of Material Design nouns above U+F0001. Reach into it. The examples below are the generic tier, NOT a menu: if your pick came from them and the task had a real subject, you picked wrong. Never reuse a generic gear, wrench, or bug for a task that is actually about something.\n\nGeneric fallback set, by kind of work:\n"+x;function C(e,t=a){return`<source>
1
+ var k=Object.defineProperty;var s=(e,t)=>k(e,"name",{value:t,configurable:!0});import{headlessText as x,headlessToolCall as h}from"./headless-pi.js";import{currentOrPendingCausedBy as T}from"./broker/turn-cause.js";import{defaultProvider as m,modelLadders as g,stripThinkingSuffix as f}from"./model-selection.js";const c=2e3,w=45e3,C=["fixing/diagnosis: U+F188 bug, U+F0F1 stethoscope, U+F06D fire, U+F071 warning","building/changing code: U+F0AD wrench, U+F067 plus, U+F121 code, U+F085 cogs, U+F021 refresh","testing/verifying: U+F0C3 flask, U+F00C check, U+F24E balance","writing/docs/research: U+F02D book, U+F15C document, U+F002 magnifier, U+F0EB lightbulb","data/infra: U+F1C0 database, U+F0C2 cloud, U+F120 terminal, U+F013 gear, U+F1B3 packages","ship/perf/scale: U+F135 rocket, U+F0E7 bolt, U+F080 chart, U+F201 trend line","security/access: U+F132 shield, U+F023 lock, U+F084 key","interface/design: U+F1FC paintbrush, U+F03E image, U+F108 display","communication/coordination: U+F1D8 paper plane, U+F0E6 comments, U+F0E8 sitemap, U+F0C1 link"].join(`
2
+ `),E="You label agent sessions. A session is whatever a person opened it for: a coding task, a question, a piece of research, an idea they are turning over, or an ordinary conversation. You are labelling that session, never taking part in it \u2014 do not answer the message, do not do the work it asks for, do not comment on it. Every session gets a label, however short, casual, misspelled, or open-ended its opening message is.\n\nCall the `name_session` tool exactly once with:\n- `title`: how a person would refer to this session in a list of their conversations \u2014 2-8 words, sentence case, written naturally with its punctuation intact (e.g. `What's your favorite color?`, `Payroll export mapping bug`, `Ideas for the launch email`). No quotes, no markdown, no trailing period.\n- `name`: the same subject as a concise 3-8 word kebab-case handle \u2014 lowercase words joined by single hyphens (e.g. `favorite-color-question`, `refactor-auth-token-flow`). No punctuation, quotes, or prose.\n- `icon`: ONE Nerd Font glyph, written as its codepoint in `U+XXXX` form (e.g. `U+F188`). Emit a codepoint, not a name like `nf-fa-bug`, and not an emoji.\n\nChoosing the icon \u2014 work through it in this order:\n1. Name the concrete SUBJECT of the task: the thing being acted on. A font, a trash can, an account, a browser tab, a clock, an invoice, a map, a robot, a specific technology's logo. If a glyph depicts that thing, use it. This is almost always the better icon, because it is what distinguishes this session from every other one.\n2. Only when the task has no depictable subject, fall back to the ACTIVITY (fixing, documenting, testing) using the generic set below.\n\nANY Nerd Font codepoint is allowed and the set is enormous \u2014 the whole Font Awesome range, Devicons and Seti technology logos, Octicons, Codicons, Weather, and the thousands of Material Design nouns above U+F0001. Reach into it. The examples below are the generic tier, NOT a menu: if your pick came from them and the task had a real subject, you picked wrong. Never reuse a generic gear, wrench, or bug for a task that is actually about something.\n\nGeneric fallback set, by kind of work:\n"+C;function N(e,t=c){return`<source>
3
3
  ${e.slice(0,t)}
4
4
  </source>
5
5
 
6
- Label the session this text is from: call \`name_session\` once with a title, a name, and an icon depicting it, then stop. Do not answer it.`}s(C,"nameUserPrompt");const c=72;function l(e){return((e??"").split(`
7
- `).map(o=>o.trim()).find(o=>o!=="")??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").split("-").filter(o=>o!=="").slice(0,8).join("-")}s(l,"sanitizeSessionName");function w(e){const n=((e??"").split(`
8
- `).map(i=>i.trim()).find(i=>i!=="")??"").replace(/\s+/g," ").replace(/^["'`]+|["'`]+$/g,"").trim();if(n==="")return"";let o=n;if(n.length>c){const i=n.slice(0,c),r=i.lastIndexOf(" ");o=`${(r>c/2?i.slice(0,r):i).trimEnd()}\u2026`}return o.charAt(0).toUpperCase()+o.slice(1)}s(w,"sanitizeSessionTitle");function E(e){return w((e??"").replace(/\s+/g," "))}s(E,"titleFromPrompt");function O(e){return l(E(e))||"session"}s(O,"slugFromPrompt");function j(e){const t=e===void 0?void 0:process.env[e];if(t!==void 0&&t.trim()!=="")return t.trim();const n=m();return g(n[h(n)].light)}s(j,"lightModel");function b(){const e=process.env.CRTR_NAME_MODEL;if(e!==void 0&&e.trim()!=="")return{modelSpec:e.trim()};const t=m(),n=h(t);return{modelSpec:g(t[n].light),modelIntent:`${n}/light`}}s(b,"nameModelRequest");const N={type:"object",properties:{title:{type:"string",description:"A 2-8 word sentence-case title a person would recognize this session by in a list, with its punctuation intact, e.g. Payroll export mapping bug. No quotes, markdown, or trailing period."},name:{type:"string",description:"A 3-8 word kebab-case session name: lowercase words joined by single hyphens, e.g. refactor-auth-token-flow. No punctuation, quotes, or prose."},icon:{type:"string",description:"The Nerd Font glyph depicting this session, as a codepoint in U+XXXX form (e.g. U+F188)."}},required:["title","name","icon"],additionalProperties:!1};function v(e){const t=(e??"").trim();if(t==="")return"";const n=/^(?:u\+|0x|\\u\{?|&#x)?([0-9a-f]{4,6})\}?;?$/i.exec(t);if(n!==null){const i=Number.parseInt(n[1],16);if(i>=8192&&i<=1114111)try{return String.fromCodePoint(i)}catch{return""}return""}const o=[...t];return o.length>2?"":o[0].codePointAt(0)>=8192?t:""}s(v,"sanitizeIcon");async function P(e,t=a){const n={name:"",title:"",icon:""},o={systemPrompt:T,userPrompt:C(e,t),...b(),timeoutMs:f,tool:{name:"name_session",label:"Name session",description:"Record the title, name, and icon for this session. Call exactly once, then stop.",parameters:N,constrainedSampling:{type:"json_schema",strict:"prefer"}}},i=await p(o)??await p(o);if(i===null||typeof i!="object")return n;const r=i,u=w(typeof r.title=="string"?r.title:""),d=l(typeof r.name=="string"?r.name:"")||l(u),F=v(typeof r.icon=="string"?r.icon:"");return d!==""?{name:d,title:u,icon:F}:n}s(P,"headlessName");function L(e){return P(e,3e4)}s(L,"headlessNameFromConversation");const S='You write GitHub issue titles. Given a free-text note (a todo, idea, or bug report), reply with ONLY a concise issue title: sentence case, 4-10 words, no trailing period, no quotes, no prefix like "Bug:" or "Idea:", no markdown. Capture the gist as an actionable title. Output JUST the title, nothing else.';function M(e){return`<note>
9
- ${e.slice(0,a)}
6
+ Label the session this text is from: call \`name_session\` once with a title, a name, and an icon depicting it, then stop. Do not answer it.`}s(N,"nameUserPrompt");const l=72;function u(e){return((e??"").split(`
7
+ `).map(o=>o.trim()).find(o=>o!=="")??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").split("-").filter(o=>o!=="").slice(0,8).join("-")}s(u,"sanitizeSessionName");function b(e){const n=((e??"").split(`
8
+ `).map(i=>i.trim()).find(i=>i!=="")??"").replace(/\s+/g," ").replace(/^["'`]+|["'`]+$/g,"").trim();if(n==="")return"";let o=n;if(n.length>l){const i=n.slice(0,l),r=i.lastIndexOf(" ");o=`${(r>l/2?i.slice(0,r):i).trimEnd()}\u2026`}return o.charAt(0).toUpperCase()+o.slice(1)}s(b,"sanitizeSessionTitle");function P(e){return b((e??"").replace(/\s+/g," "))}s(P,"titleFromPrompt");function q(e){return u(P(e))||"session"}s(q,"slugFromPrompt");function X(e){const t=e===void 0?void 0:process.env[e];if(t!==void 0&&t.trim()!=="")return t.trim();const n=g();return f(n[m(n)].light)}s(X,"lightModel");function y(){const e=process.env.CRTR_NAME_MODEL;if(e!==void 0&&e.trim()!=="")return{modelSpec:e.trim()};const t=g(),n=m(t);return{modelSpec:f(t[n].light),modelIntent:`${n}/light`}}s(y,"nameModelRequest");const v={type:"object",properties:{title:{type:"string",description:"A 2-8 word sentence-case title a person would recognize this session by in a list, with its punctuation intact, e.g. Payroll export mapping bug. No quotes, markdown, or trailing period."},name:{type:"string",description:"A 3-8 word kebab-case session name: lowercase words joined by single hyphens, e.g. refactor-auth-token-flow. No punctuation, quotes, or prose."},icon:{type:"string",description:"The Nerd Font glyph depicting this session, as a codepoint in U+XXXX form (e.g. U+F188)."}},required:["title","name","icon"],additionalProperties:!1};function S(e){const t=(e??"").trim();if(t==="")return"";const n=/^(?:u\+|0x|\\u\{?|&#x)?([0-9a-f]{4,6})\}?;?$/i.exec(t);if(n!==null){const i=Number.parseInt(n[1],16);if(i>=8192&&i<=1114111)try{return String.fromCodePoint(i)}catch{return""}return""}const o=[...t];return o.length>2?"":o[0].codePointAt(0)>=8192?t:""}s(S,"sanitizeIcon");async function M(e,t=c,n=T()??null){const o={name:"",title:"",icon:""},i={systemPrompt:E,userPrompt:N(e,t),...y(),timeoutMs:w,causedBy:n,tool:{name:"name_session",label:"Name session",description:"Record the title, name, and icon for this session. Call exactly once, then stop.",parameters:v,constrainedSampling:{type:"json_schema",strict:"prefer"}}},r=await h(i)??await h(i);if(r===null||typeof r!="object")return o;const a=r,d=b(typeof a.title=="string"?a.title:""),p=u(typeof a.name=="string"?a.name:"")||u(d),U=S(typeof a.icon=="string"?a.icon:"");return p!==""?{name:p,title:d,icon:U}:o}s(M,"headlessName");function $(e,t){return M(e,3e4,t)}s($,"headlessNameFromConversation");const _='You write GitHub issue titles. Given a free-text note (a todo, idea, or bug report), reply with ONLY a concise issue title: sentence case, 4-10 words, no trailing period, no quotes, no prefix like "Bug:" or "Idea:", no markdown. Capture the gist as an actionable title. Output JUST the title, nothing else.';function A(e){return`<note>
9
+ ${e.slice(0,c)}
10
10
  </note>
11
11
 
12
- Write a concise GitHub issue title for the note above. Output JUST the title, nothing else.`}s(M,"titleUserPrompt");function y(e){const t=(e??"").split(`
13
- `).map(o=>o.trim()).find(o=>o!=="")??"",n=t.length>80?`${t.slice(0,77).trimEnd()}\u2026`:t;return n!==""?n:"untitled"}s(y,"firstLineTitle");function q(e){return(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").split("-").filter(n=>n!=="").slice(0,8).join("-")||"untitled"}s(q,"titleSlug");async function X(e){const t=(e??"").trim();if(t==="")return"untitled";try{const o=(await k({systemPrompt:S,userPrompt:M(t),...b(),timeoutMs:f})).split(`
14
- `).map(i=>i.trim()).find(i=>i!=="")??"";return o!==""?o.replace(/^["']|["']$/g,"").replace(/\.$/,""):y(t)}catch{return y(t)}}s(X,"generateIssueTitle");export{X as generateIssueTitle,P as headlessName,L as headlessNameFromConversation,j as lightModel,v as sanitizeIcon,l as sanitizeSessionName,w as sanitizeSessionTitle,O as slugFromPrompt,E as titleFromPrompt,q as titleSlug};
12
+ Write a concise GitHub issue title for the note above. Output JUST the title, nothing else.`}s(A,"titleUserPrompt");function F(e){const t=(e??"").split(`
13
+ `).map(o=>o.trim()).find(o=>o!=="")??"",n=t.length>80?`${t.slice(0,77).trimEnd()}\u2026`:t;return n!==""?n:"untitled"}s(F,"firstLineTitle");function D(e){return(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").split("-").filter(n=>n!=="").slice(0,8).join("-")||"untitled"}s(D,"titleSlug");async function R(e){const t=(e??"").trim();if(t==="")return"untitled";try{const o=(await x({systemPrompt:_,userPrompt:A(t),...y(),timeoutMs:w})).split(`
14
+ `).map(i=>i.trim()).find(i=>i!=="")??"";return o!==""?o.replace(/^["']|["']$/g,"").replace(/\.$/,""):F(t)}catch{return F(t)}}s(R,"generateIssueTitle");export{R as generateIssueTitle,M as headlessName,$ as headlessNameFromConversation,X as lightModel,S as sanitizeIcon,u as sanitizeSessionName,b as sanitizeSessionTitle,q as slugFromPrompt,P as titleFromPrompt,D as titleSlug};
@@ -1,3 +1,3 @@
1
1
  /** Ask Pi for a concise briefing of `conversation`. Invokes `onRecap` with
2
2
  * the parsed string on success, or never on timeout, empty, or garbled output. */
3
- export declare function generateRecap(conversation: string, onRecap: (briefing: string) => void, liveModel?: string, roadmap?: string): void;
3
+ export declare function generateRecap(conversation: string, onRecap: (briefing: string) => void, liveModel?: string, roadmap?: string, causedBy?: string | null): void;
@@ -1,4 +1,4 @@
1
- var h=Object.defineProperty;var n=(t,e)=>h(t,"name",{value:e,configurable:!0});import{headlessText as l}from"./headless-pi.js";import{envRecapModelOverride as c}from"../../shared/env.js";const d=25e3,m="anthropic/claude-haiku-5-5",u=`You brief a developer returning to a paused coding-agent conversation. They run many agent sessions in parallel and have lost this one's thread: they no longer know what this conversation is about or what they originally asked for. The brief exists to solve exactly that \u2014 pinned above their input box, it must tell them at a glance what this conversation is FOR and where it actually stands. You are given the first and most recent messages of the literal user/agent back-and-forth (tool output omitted), and the agent's roadmap file when one exists.
1
+ var l=Object.defineProperty;var n=(t,e)=>l(t,"name",{value:e,configurable:!0});import{headlessText as c}from"./headless-pi.js";import{envRecapModelOverride as d}from"../../shared/env.js";const m=25e3,u="anthropic/claude-haiku-5-5",p=`You brief a developer returning to a paused coding-agent conversation. They run many agent sessions in parallel and have lost this one's thread: they no longer know what this conversation is about or what they originally asked for. The brief exists to solve exactly that \u2014 pinned above their input box, it must tell them at a glance what this conversation is FOR and where it actually stands. You are given the first and most recent messages of the literal user/agent back-and-forth (tool output omitted), and the agent's roadmap file when one exists.
2
2
 
3
3
  Output EXACTLY two plain-text lines, nothing else:
4
4
  Goal: <what was requested \u2014 the original ask / what this conversation is for>
@@ -14,7 +14,7 @@ Goal: Build the trimmed-v1 command-plugin seam (orchestrating phases).
14
14
  State: Phase 1 landed; waiting on the core-seam worker's final.
15
15
 
16
16
  Goal: Standing personal assistant \u2014 iMessage wakes, Reddit scans, small tasks.
17
- State: Idle \u2014 waiting on you: hook pick, map export, Omi timing.`;function p(t,e){return`${e!==void 0&&e.trim()!==""?`<roadmap>
17
+ State: Idle \u2014 waiting on you: hook pick, map export, Omi timing.`;function g(t,e){return`${e!==void 0&&e.trim()!==""?`<roadmap>
18
18
  ${e.trim()}
19
19
  </roadmap>
20
20
 
@@ -22,6 +22,6 @@ ${e.trim()}
22
22
  ${t}
23
23
  </conversation>
24
24
 
25
- Write the two-line Goal:/State: briefing for the conversation above. Output JUST the two lines.`}n(p,"recapUserPrompt");function g(t){const e=c();return e!==void 0?e:t!==void 0&&t.trim()!==""?t.trim():m}n(g,"recapModel");function f(t){return t.split(`
25
+ Write the two-line Goal:/State: briefing for the conversation above. Output JUST the two lines.`}n(g,"recapUserPrompt");function f(t){const e=d();return e!==void 0?e:t!==void 0&&t.trim()!==""?t.trim():u}n(f,"recapModel");function w(t){return t.split(`
26
26
  `).map(e=>e.replace(/\s+/g," ").trim()).filter(e=>e!=="").join(`
27
- `)}n(f,"parseRecap");function b(t,e,a,i){const o=(t??"").trim();o!==""&&l({systemPrompt:u,userPrompt:p(o,i),modelSpec:g(a),timeoutMs:d}).then(s=>{const r=f(s);if(r!=="")try{e(r)}catch{}}).catch(()=>{})}n(b,"generateRecap");export{b as generateRecap};
27
+ `)}n(w,"parseRecap");function k(t,e,a,s,o){const i=(t??"").trim();i!==""&&c({systemPrompt:p,userPrompt:g(i,s),modelSpec:f(a),timeoutMs:m,...o===void 0?{}:{causedBy:o}}).then(h=>{const r=w(h);if(r!=="")try{e(r)}catch{}}).catch(()=>{})}n(k,"generateRecap");export{k as generateRecap};
@@ -1,3 +1,3 @@
1
- var le=Object.defineProperty;var s=(e,r)=>le(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as ce}from"./unsandboxed.js";import{existsSync as U}from"node:fs";import{conversationIdFromPath as _,conversationStore as h,ConversationNotFoundError as H}from"../conversation-store/index.js";import{isAbsolute as ue}from"node:path";import{findNodeBySessionFile as fe,getNode as R,updateNode as N,beginExecution as we,ExecutionAlreadyLive as me,recordExecutionPid as de,abortExecution as F,endLiveExecution as he,unbindSession as G,recordLaunch as pe,setFrozen as ve,clearFrozen as K,fullName as _e}from"../canvas/index.js";import{emitEvent as W}from"../events/emit.js";import{consumeDeadlineOnWake as ye}from"../feed/messages.js";import{isParked as J,transition as A}from"./lifecycle.js";import{publishNotice as ke}from"./close.js";import{InputError as O}from"../io.js";import{buildPiArgv as y}from"./launch.js";import{buildReviveKickoff as ge,drainBearings as be,hasRetainedReports as Re,readGoal as E,readYieldMessage as Ee,RUNTIME_RESTART_CONTINUATION as xe}from"./kickoff.js";import{readRoadmap as j}from"./roadmap.js";import{encodeKickoffOrigin as Se,KICKOFF_ORIGIN_ENV as Ce}from"./stamp/protocol.js";import{headlessBrokerHost as $e,brokerLaunchJson as Ne}from"./host.js";import{isCloudRole as Fe}from"../canvas/person.js";import{asNodePerson as Ae}from"../../daemon/reconcilers/node-lifecycle/node-person.js";import{processRunner as Oe}from"./runner/process.js";import{runnerForEnvironment as V}from"./runner/index.js";import{MachineLaunchUncertainError as Pe,MachineOfflineError as Be}from"./runner/machine/ops.js";import{blaxelEnvironmentOf as X}from"./blaxel-environment.js";import{recordLaunchFailure as De}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as Y,remoteMachineEnvironmentOf as Me,checkBilledWake as Te,EnvironmentImagePending as Le,imageForWake as ze}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as Ue,liveExecutionForNode as He,setExecutionLaunchJson as Ge}from"../canvas/executions.js";import{isDrained as Ke}from"../../daemon/drain.js";import{appMemoryFull as We,appMemoryTooSmall as Je,boundFleet as q,brokerCapReached as Q,brokerThresholdsForDaemon as P}from"./fleet.js";import{admitBrokerMemory as je}from"./memory-admission.js";import{beginBootFaultAttempt as Ve,clearFault as Xe,hasAdmittedTurn as Ye,readFault as qe}from"../canvas/recovery.js";import{clearNodeLedger as Z,forkNodeLedger as Qe,noticeChangedWhileDone as Ze}from"./ledger.js";import{forkSourceNode as Ie,rootOfSpine as er}from"./nodes.js";import{ApiError as rr}from"../../api/errors.js";import{grantSnapshotMissing as ir}from"../../daemon/readiness.js";import{checkStart as tr}from"./start-check.js";import{isReviewCompanionBound as nr}from"../review/companion.js";import{assertProfileActive as ar}from"../profiles/manifest.js";import{isPidAlive as or}from"../canvas/pid.js";import{crtrHome as sr,jobDir as lr}from"../canvas/paths.js";import{contextDir as cr,ensureNodeDirs as ur,nodeAgentDir as fr}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as wr}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as mr}from"../command-hooks/transport/exec-lifecycle.js";async function dr(e,r){const i=e.pi_session_file,t=i?_(i):null;if(t===null)return{};try{const a=await h().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof H)return{};throw a}}s(dr,"resumeArgs");async function I(e){const r=_(e);if(r===null)return!1;try{return await h().header(r),!0}catch(i){if(i instanceof H)return!1;throw i}}s(I,"conversationExists");async function hr(e){const r=_(e);return r===null?!0:(await h().read(r)).entries.some(i=>i.type==="message")}s(hr,"sessionHasConversation");async function pr(e){const r=_(e);if(r===null)return!0;const i=(await h().header(r)).header.cwd;return typeof i!="string"||U(i)}s(pr,"sessionCwdExists");async function vr(e,r,i,t){const a=r.pi_session_file;if(!a||!await I(a))return{};const u=await fe(a);return u!==null&&u.node_id!==e&&or(u.pi_pid)?{}:t&&!await pr(a)?{}:await dr(r,i)}s(vr,"resumableSessionArgs");async function _r(e){return await j(e)===null&&await Ee(e)===null&&!await Re(e)}s(_r,"isUnstartedBirth");async function ci(e){const r=await E(e);if(r!==null&&r.trim()!=="")return!0;const i=await j(e);return i!==null&&i.trim()!==""}s(ci,"hasFreshGroundState");async function ui(e,r){const i=await ee(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(ui,"reviveNode");async function fi(e,r,i){return ee(e,{...r,viewerAttach:!0},i)}s(fi,"decideAttachRevive");async function ee(e,r,i){const t=Fe()?await R(e):null;return t===null?await re(e,r,i):await Ae({node_id:e,sub:t.sub??null,profile_id:t.profile_id??null},()=>re(e,r,i))}s(ee,"decideRevive");async function re(e,r,i){let t=await R(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await R(e)??t,t.run_id&&(await R(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);ar(t.profile_id),ce(t.grantee);const a=q(),u=t.grantee==="app:terminal"&&await ir();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&J(t))&&(await tr(t),await X(t.profile_id??null)!==null&&await Te(t)),await a.has(e))return await A(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&J(t))return{parked:!0};if(u){if(r.capacity==="refuse")throw new rr(503,"runtime_starting","the first grant snapshot from the directory has not committed");return await ie(e,t,{reason:"grant_snapshot"}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}const k=await Ke();if(k&&r.capacity==="refuse")throw new Error(`the daemon is drained for an import; ${e} cannot start until it ends`);const p=!k&&await a.reserveSlot(e);let o=null;if(p){try{o=await je(e)}catch(w){throw a.releaseReservation(e),w}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw Je(o);if(!p||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?We(o):Q(P().automaticReviveCap,await a.occupancy());return await ie(e,t,k?{reason:"drain"}:o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:P().automaticReviveCap}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await kr(e,t,r)}}finally{a.releaseReservation(e)}}s(re,"decideReviveAsCaller");async function ie(e,r,i){r.frozen_at==null&&(await ve(e,new Date().toISOString()),await he(e,{reason:"frozen"}),W({level:"info",event:"broker.frozen",node_id:e,fields:i}))}s(ie,"freezeForWait");async function yr(e,r){const i=await X(r.profile_id??null),t=await Me(r.profile_id??null)!==null;if(!(i!==null||t)&&!U(r.cwd)){const u=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new O({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:u})}return{blaxelEnvironment:i,remoteMachine:t}}s(yr,"assertRecordedCwdExists");async function kr(e,r,i){const{blaxelEnvironment:t,remoteMachine:a}=await yr(e,r),u=t!==null||a;r.host_kind!=="broker"&&(await N(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await nr(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);const p=r.fork_from!=null&&r.pi_session_id==null,o=r.fork_source_file;if(p&&(typeof o!="string"||!ue(o)))throw new O({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const w=p&&typeof o=="string";if(w&&!await I(o))throw new O({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${o}`,next:`Restore ${o}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:te}=P();if(t!==null){const l=await ze(t);if(l.state==="pending")throw new Le(t,l.error)}const ne=t!==null?V("blaxel"):a?V("machine"):Oe;let f;try{f=await we(e,{daemonEpoch:q().epoch(),cap:te,...t!==null?{runner:"blaxel",release:Y()}:a?{runner:"machine",release:Y()}:{}})}catch(l){if(l instanceof Ue)throw Q(l.cap,l.live);if(l instanceof me)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw l}try{r.cycles=(r.cycles??0)+1,await N(e,{cycles:r.cycles});const x=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,d=w?{}:await vr(e,r,x,!u),g=d.resumeSessionPath!==void 0,B=g&&d.newCycle===!0,v=g&&!B,D=!g&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await _r(e),S=v&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await Ye(e)&&!await hr(d.resumeSessionPath)?(await E(e))?.trim():void 0,M=S!==void 0&&S!=="",ae=w||D||M,T=r.cycle_pending===!0||B;await N(e,{cycle_pending:T}),r.cycle_pending=T,!w&&!g&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);let c,m=null;const C=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(w){const n=await E(e),$=_(o);if($===null)throw new Error(`invalid fork source ${o}`);const se=(await h().header(await h().fork($,e))).path;c=y(r,{resumeSessionPath:se,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(m=C()),await Qe(e,(await Ie(o))?.node_id??null)}else if(M)c=y(r,{...d,prompt:S}),m=C();else if(v)c=y(r,{...d,...i.wakeReason==="runtime-restart-abort"?{prompt:xe}:{}}),i.wakeReason==="runtime-restart-abort"&&(m={type:"runtime",kind:"restart-continuation"});else if(D){const n=(await E(e))?.trim();c=y(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(m=C()),await Z(e)}else{const n=await be(r,f);c=y(r,{...d,prompt:await ge(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),m=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await Z(e)}c.env={...c.env,CRTR_SUBTREE:await er(e)},m!==null&&(c.env[Ce]=Se(m)),r.status==="done"&&r.last_settled_at!=null&&await Ze(e,r.last_settled_at),await A(e,r.intent==="refresh"?"refresh-launch":"revive"),(await qe(e))?.op!=="conversation_write_failed"&&await Xe(e,{preserveEpisode:!0});const L=await Ve(e);i.recovery!==!0&&i.viewerAttach!==!0&&await ye(e),await ur(e,r.grantee);const oe=wr(r.cwd,r.profile_id).plans.get("node:start")??[];await mr(oe,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await fr(e),contextDir:await cr(e),jobDir:lr(e)},runtime:{isBirth:ae,canvasHome:sr(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:f};const z=await Ne(e,c,r.cwd);await Ge(f,z);let b;try{const n=await He(e);if(b=await ne.start({executionId:f,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:z,cwd:r.cwd,resuming:v}),!u&&b.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){if(n instanceof Be)throw n;try{await A(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await ke(e,"notice.crash",`Child crashed \u2014 ${_e(r)} (${e}) failed to relaunch and is now dead: ${n.message}
1
+ var ce=Object.defineProperty;var s=(e,r)=>ce(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as ue}from"./unsandboxed.js";import{existsSync as U}from"node:fs";import{conversationIdFromPath as _,conversationStore as h,ConversationNotFoundError as H}from"../conversation-store/index.js";import{isAbsolute as fe}from"node:path";import{findNodeBySessionFile as we,getNode as R,updateNode as N,beginExecution as me,ExecutionAlreadyLive as de,recordExecutionPid as he,abortExecution as F,endLiveExecution as pe,unbindSession as G,recordLaunch as ve,setFrozen as _e,clearFrozen as K,fullName as ye}from"../canvas/index.js";import{emitEvent as W}from"../events/emit.js";import{consumeDeadlineOnWake as ke}from"../feed/messages.js";import{isParked as J,transition as A}from"./lifecycle.js";import{publishNotice as ge}from"./close.js";import{InputError as O}from"../io.js";import{buildPiArgv as y}from"./launch.js";import{buildReviveKickoff as be,drainBearings as Re,hasRetainedReports as Ee,readGoal as E,readYieldMessage as xe,RUNTIME_RESTART_CONTINUATION as Se}from"./kickoff.js";import{readRoadmap as j}from"./roadmap.js";import{encodeKickoffOrigin as Ce,KICKOFF_ORIGIN_ENV as $e}from"./stamp/protocol.js";import{carryInitialInput as V}from"../runs/initial-input.js";import{headlessBrokerHost as Ne,brokerLaunchJson as Fe}from"./host.js";import{isCloudRole as Ae}from"../canvas/person.js";import{asNodePerson as Oe}from"../../daemon/reconcilers/node-lifecycle/node-person.js";import{processRunner as Pe}from"./runner/process.js";import{runnerForEnvironment as X}from"./runner/index.js";import{MachineLaunchUncertainError as Be,MachineOfflineError as De}from"./runner/machine/ops.js";import{blaxelEnvironmentOf as Y}from"./blaxel-environment.js";import{recordLaunchFailure as Me}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as q,remoteMachineEnvironmentOf as Te,checkBilledWake as Le,EnvironmentImagePending as ze,imageForWake as Ue}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as He,liveExecutionForNode as Ge,setExecutionLaunchJson as Ke}from"../canvas/executions.js";import{isDrained as We}from"../../daemon/drain.js";import{appMemoryFull as Je,appMemoryTooSmall as je,boundFleet as Q,brokerCapReached as Z,brokerThresholdsForDaemon as P}from"./fleet.js";import{admitBrokerMemory as Ve}from"./memory-admission.js";import{beginBootFaultAttempt as Xe,clearFault as Ye,hasAdmittedTurn as qe,readFault as Qe}from"../canvas/recovery.js";import{clearNodeLedger as I,forkNodeLedger as Ze,noticeChangedWhileDone as Ie}from"./ledger.js";import{forkSourceNode as er,rootOfSpine as rr}from"./nodes.js";import{ApiError as ir}from"../../api/errors.js";import{grantSnapshotMissing as tr}from"../../daemon/readiness.js";import{checkStart as nr}from"./start-check.js";import{isReviewCompanionBound as ar}from"../review/companion.js";import{assertProfileActive as or}from"../profiles/manifest.js";import{isPidAlive as sr}from"../canvas/pid.js";import{crtrHome as lr,jobDir as cr}from"../canvas/paths.js";import{contextDir as ur,ensureNodeDirs as fr,nodeAgentDir as wr}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as mr}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as dr}from"../command-hooks/transport/exec-lifecycle.js";async function hr(e,r){const i=e.pi_session_file,t=i?_(i):null;if(t===null)return{};try{const a=await h().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof H)return{};throw a}}s(hr,"resumeArgs");async function ee(e){const r=_(e);if(r===null)return!1;try{return await h().header(r),!0}catch(i){if(i instanceof H)return!1;throw i}}s(ee,"conversationExists");async function pr(e){const r=_(e);return r===null?!0:(await h().read(r)).entries.some(i=>i.type==="message")}s(pr,"sessionHasConversation");async function vr(e){const r=_(e);if(r===null)return!0;const i=(await h().header(r)).header.cwd;return typeof i!="string"||U(i)}s(vr,"sessionCwdExists");async function _r(e,r,i,t){const a=r.pi_session_file;if(!a||!await ee(a))return{};const u=await we(a);return u!==null&&u.node_id!==e&&sr(u.pi_pid)?{}:t&&!await vr(a)?{}:await hr(r,i)}s(_r,"resumableSessionArgs");async function yr(e){return await j(e)===null&&await xe(e)===null&&!await Ee(e)}s(yr,"isUnstartedBirth");async function fi(e){const r=await E(e);if(r!==null&&r.trim()!=="")return!0;const i=await j(e);return i!==null&&i.trim()!==""}s(fi,"hasFreshGroundState");async function wi(e,r){const i=await re(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(wi,"reviveNode");async function mi(e,r,i){return re(e,{...r,viewerAttach:!0},i)}s(mi,"decideAttachRevive");async function re(e,r,i){const t=Ae()?await R(e):null;return t===null?await ie(e,r,i):await Oe({node_id:e,sub:t.sub??null,profile_id:t.profile_id??null},()=>ie(e,r,i))}s(re,"decideRevive");async function ie(e,r,i){let t=await R(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await R(e)??t,t.run_id&&(await R(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);or(t.profile_id),ue(t.grantee);const a=Q(),u=t.grantee==="app:terminal"&&await tr();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&J(t))&&(await nr(t),await Y(t.profile_id??null)!==null&&await Le(t)),await a.has(e))return await A(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&J(t))return{parked:!0};if(u){if(r.capacity==="refuse")throw new ir(503,"runtime_starting","the first grant snapshot from the directory has not committed");return await te(e,t,{reason:"grant_snapshot"}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}const k=await We();if(k&&r.capacity==="refuse")throw new Error(`the daemon is drained for an import; ${e} cannot start until it ends`);const p=!k&&await a.reserveSlot(e);let o=null;if(p){try{o=await Ve(e)}catch(w){throw a.releaseReservation(e),w}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw je(o);if(!p||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?Je(o):Z(P().automaticReviveCap,await a.occupancy());return await te(e,t,k?{reason:"drain"}:o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:P().automaticReviveCap}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await gr(e,t,r)}}finally{a.releaseReservation(e)}}s(ie,"decideReviveAsCaller");async function te(e,r,i){r.frozen_at==null&&(await _e(e,new Date().toISOString()),await pe(e,{reason:"frozen"}),W({level:"info",event:"broker.frozen",node_id:e,fields:i}))}s(te,"freezeForWait");async function kr(e,r){const i=await Y(r.profile_id??null),t=await Te(r.profile_id??null)!==null;if(!(i!==null||t)&&!U(r.cwd)){const u=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new O({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:u})}return{blaxelEnvironment:i,remoteMachine:t}}s(kr,"assertRecordedCwdExists");async function gr(e,r,i){const{blaxelEnvironment:t,remoteMachine:a}=await kr(e,r),u=t!==null||a;r.host_kind!=="broker"&&(await N(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await ar(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);const p=r.fork_from!=null&&r.pi_session_id==null,o=r.fork_source_file;if(p&&(typeof o!="string"||!fe(o)))throw new O({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const w=p&&typeof o=="string";if(w&&!await ee(o))throw new O({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${o}`,next:`Restore ${o}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:ne}=P();if(t!==null){const l=await Ue(t);if(l.state==="pending")throw new ze(t,l.error)}const ae=t!==null?X("blaxel"):a?X("machine"):Pe;let f;try{f=await me(e,{daemonEpoch:Q().epoch(),cap:ne,...t!==null?{runner:"blaxel",release:q()}:a?{runner:"machine",release:q()}:{}})}catch(l){if(l instanceof He)throw Z(l.cap,l.live);if(l instanceof de)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw l}try{r.cycles=(r.cycles??0)+1,await N(e,{cycles:r.cycles});const x=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,d=w?{}:await _r(e,r,x,!u),g=d.resumeSessionPath!==void 0,B=g&&d.newCycle===!0,v=g&&!B,D=!g&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await yr(e),S=v&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await qe(e)&&!await pr(d.resumeSessionPath)?(await E(e))?.trim():void 0,M=S!==void 0&&S!=="",oe=w||D||M,T=r.cycle_pending===!0||B;await N(e,{cycle_pending:T}),r.cycle_pending=T,!w&&!g&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);let c,m=null;const C=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(w){const n=await E(e),$=_(o);if($===null)throw new Error(`invalid fork source ${o}`);const le=(await h().header(await h().fork($,e))).path;c=y(r,{resumeSessionPath:le,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(m=C()),await Ze(e,(await er(o))?.node_id??null)}else if(M)c=y(r,{...d,prompt:S}),m=C(),await V(c,r);else if(v)c=y(r,{...d,...i.wakeReason==="runtime-restart-abort"?{prompt:Se}:{}}),i.wakeReason==="runtime-restart-abort"&&(m={type:"runtime",kind:"restart-continuation"});else if(D){const n=(await E(e))?.trim();c=y(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(m=C(),await V(c,r)),await I(e)}else{const n=await Re(r,f);c=y(r,{...d,prompt:await be(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),m=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await I(e)}c.env={...c.env,CRTR_SUBTREE:await rr(e)},m!==null&&(c.env[$e]=Ce(m)),r.status==="done"&&r.last_settled_at!=null&&await Ie(e,r.last_settled_at),await A(e,r.intent==="refresh"?"refresh-launch":"revive"),(await Qe(e))?.op!=="conversation_write_failed"&&await Ye(e,{preserveEpisode:!0});const L=await Xe(e);i.recovery!==!0&&i.viewerAttach!==!0&&await ke(e),await fr(e,r.grantee);const se=mr(r.cwd,r.profile_id).plans.get("node:start")??[];await dr(se,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await wr(e),contextDir:await ur(e),jobDir:cr(e)},runtime:{isBirth:oe,canvasHome:lr(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:f};const z=await Fe(e,c,r.cwd);await Ke(f,z);let b;try{const n=await Ge(e);if(b=await ae.start({executionId:f,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:z,cwd:r.cwd,resuming:v}),!u&&b.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){if(n instanceof De)throw n;try{await A(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await ge(e,"notice.crash",`Child crashed \u2014 ${ye(r)} (${e}) failed to relaunch and is now dead: ${n.message}
2
2
 
3
- It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await F(f,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await pe(e,new Date().toISOString()),u)return await K(e),{window:null,session:r.tmux_session??null,resumed:v,faultExplanation:L.explanation,outcome:"launched"};if(await de(f,e,b.pid)==="shutdown")throw await F(f,{reason:"superseded"}),$e.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await K(e),{window:null,session:r.tmux_session??null,resumed:v,launch:b,faultExplanation:L.explanation,outcome:"launched"}}catch(l){throw l instanceof Pe||(await F(f,{reason:"launch_failed"}),await De(f,l).catch(x=>{W({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:x})})),l}}s(kr,"launchRevive");export{yr as assertRecordedCwdExists,fi as decideAttachRevive,ci as hasFreshGroundState,dr as resumeArgs,ui as reviveNode};
3
+ It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await F(f,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await ve(e,new Date().toISOString()),u)return await K(e),{window:null,session:r.tmux_session??null,resumed:v,faultExplanation:L.explanation,outcome:"launched"};if(await he(f,e,b.pid)==="shutdown")throw await F(f,{reason:"superseded"}),Ne.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await K(e),{window:null,session:r.tmux_session??null,resumed:v,launch:b,faultExplanation:L.explanation,outcome:"launched"}}catch(l){throw l instanceof Be||(await F(f,{reason:"launch_failed"}),await Me(f,l).catch(x=>{W({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:x})})),l}}s(gr,"launchRevive");export{kr as assertRecordedCwdExists,mi as decideAttachRevive,fi as hasFreshGroundState,hr as resumeArgs,wi as reviveNode};
@@ -1,3 +1,3 @@
1
- var R=Object.defineProperty;var i=(n,r)=>R(n,"name",{value:r,configurable:!0});import{openDb as k}from"../../../canvas/db.js";import{mintExecutionToken as I}from"../../../canvas/node-tokens.js";import{BlaxelApiError as T,isWorkloadUnavailable as x}from"./client.js";const f=4180,d="view",C="node /opt/crtr/current/scripts/sandbox-start.mjs --wake",S=3,N=/^crtr-sandbox-start: failed (\S+)/m,m=i(n=>`broker-${n}`,"brokerProcessName"),E=i(n=>n.sandboxName??`${n.nodeId}-${n.attempt}`,"sandboxNameFor");async function y(n){return(await(await k()).prepare("SELECT DISTINCT sandbox_name FROM executions WHERE node_id = ? AND sandbox_name IS NOT NULL").all(n)).map(l=>l.sandbox_name)}i(y,"defaultSandboxNames");function P(n){const r=`${n.logs}
2
- ${n.stderr}
3
- ${n.stdout}`;return{state:"failed",reason:N.exec(r)?.[1]??"start_failed",code:n.exitCode}}i(P,"failureOf");function h(n){const{client:r}=n,l=(n.stopGraceS??10)*1e3,b=new Map;async function u(t){return await r.getSandbox(E(t))}return i(u,"sandboxOf"),{kind:"blaxel",async start(t){const a=await n.launchContext(t),e=E(t),o=await r.createSandbox({name:e,image:a.image,memoryMb:a.memoryMb,ports:[{name:d,target:f}]}),s=m(t.executionId);if((await r.getProcess(o,s))?.status==="running")return{pid:null,exited:null};await r.ensurePreview(e,d,f);const w=await(n.mintToken??(p=>I(p.executionId,p.nodeId)))(t),g={...a.secrets,CRTR_API_URL:a.apiUrl,CRTR_EXECUTION_ID:t.executionId,CRTR_NODE_TOKEN:w,CRTR_RELEASE:t.release,CRTR_RELEASE_URL:a.releaseUrl,CRTR_RELEASE_SHA256:a.releaseSha256};return await r.startProcess(o,{name:s,command:n.bootstrap(),env:g,waitForCompletion:!1}),{pid:null,exited:null}},async wake(t){const a=await u(t);if(a===null)return"no_broker";try{const e=await r.exec(a,`wake-${t.executionId}-${Date.now()}`,`${C} ${t.executionId}`,30);if(e.exitCode===0)return"signalled";if(e.exitCode===S)return"no_broker";throw new Error(`wake of ${t.executionId} failed (exit ${e.exitCode}): ${(e.stderr||e.logs).slice(0,300)}`)}catch(e){if(x(e))return"no_broker";throw e}},async stop(t,a){try{const e=await u(t);if(e===null)return{confirmed:!0};const o=m(t.executionId),s=i(async()=>(await r.getProcess(e,o))?.status==="running","running");if(!await s())return{confirmed:!0};await r.stopProcess(e,o);const c=Date.now()+l;for(;Date.now()<c;){if(!await s())return{confirmed:!0};await new Promise(w=>setTimeout(w,250))}return await r.killProcess(e,o),{confirmed:!await s()}}catch(e){return x(e)?{confirmed:!0}:{confirmed:!1,error:e instanceof Error?e:new Error(String(e))}}},async status(t,a){try{const e=await u(t);if(e===null)return{state:"gone"};if(a?.idle===!0)return{state:"running",pid:null};const o=await r.getProcess(e,m(t.executionId));return o===null?{state:"gone"}:o.status==="running"?{state:"running",pid:null}:o.status==="failed"||o.status==="completed"&&o.exitCode!==0?P(o):{state:"gone"}}catch(e){return x(e)?{state:"gone"}:{state:"unknown",error:e instanceof Error?e.message:String(e)}}},async delete(t){for(const a of await(n.sandboxNamesForNode??y)(t))await r.deleteSandbox(a)},async viewerAddress(t){const a=E(t),e=b.get(a);if(e&&e.expiresAt-Date.now()>5*6e4)return _(e.url,e.token);const o=await r.ensurePreview(a,d,f),s=new Date(Date.now()+60*6e4),{token:c}=await r.createPreviewToken(a,d,s);return b.set(a,{url:o,token:c,expiresAt:s.getTime()}),_(o,c)},async sandboxState(t){return(await u(t))?.state??null}}}i(h,"createBlaxelRunner");function _(n,r){return{kind:"wss",url:`${n.replace(/^https:/,"wss:")}/view`,headers:{"x-blaxel-preview-token":r}}}i(_,"viewer");export{f as BROKER_VIEW_PORT,T as BlaxelApiError,S as EXIT_NO_BROKER,N as FAILURE_LINE,C as WAKE_COMMAND,m as brokerProcessName,h as createBlaxelRunner,E as sandboxNameFor};
1
+ var $=Object.defineProperty;var i=(r,a)=>$(r,"name",{value:a,configurable:!0});import{openDb as k}from"../../../canvas/db.js";import{mintExecutionToken as I}from"../../../canvas/node-tokens.js";import{BlaxelApiError as T,isWorkloadUnavailable as f}from"./client.js";const x=4180,u="view",S="node /opt/crtr/current/scripts/sandbox-start.mjs --wake",C=3,y=/^crtr-sandbox-start: failed (\S+)/m,m=i(r=>`broker-${r}`,"brokerProcessName"),p=i(r=>r.sandboxName??`${r.nodeId}-${r.attempt}`,"sandboxNameFor");async function N(r){return(await(await k()).prepare("SELECT DISTINCT sandbox_name FROM executions WHERE node_id = ? AND sandbox_name IS NOT NULL").all(r)).map(l=>l.sandbox_name)}i(N,"defaultSandboxNames");function h(r){const a=`${r.logs}
2
+ ${r.stderr}
3
+ ${r.stdout}`;return{state:"failed",reason:y.exec(a)?.[1]??"start_failed",code:r.exitCode}}i(h,"failureOf");function v(r){const{client:a}=r,l=(r.stopGraceS??10)*1e3,E=new Map;async function d(t){return await a.getSandbox(p(t))}i(d,"sandboxOf");async function _(t,n){if(!/^[A-Za-z0-9_-]+$/.test(n))throw new Error(`execution id ${JSON.stringify(n)} is not safe to name in a command`);const e=Math.max(1,Math.ceil(l/1e3)),o=`p='[b]roker-cli\\.js .*--epoch ${n}( |$)'; pkill -TERM -f "$p"; i=0; while pgrep -f "$p" >/dev/null && [ $i -lt ${e*5} ]; do sleep 0.2; i=$((i+1)); done; pkill -KILL -f "$p"; sleep 0.3; ! pgrep -f "$p"`;return(await a.exec(t,`stop-${n}-${Date.now()}`,o,e+20)).exitCode===0}return i(_,"brokerGone"),{kind:"blaxel",async start(t){const n=await r.launchContext(t),e=p(t),o=await a.createSandbox({name:e,image:n.image,memoryMb:n.memoryMb,ports:[{name:u,target:x}]}),s=m(t.executionId);if((await a.getProcess(o,s))?.status==="running")return{pid:null,exited:null};await a.ensurePreview(e,u,x);const w=await(r.mintToken??(b=>I(b.executionId,b.nodeId)))(t),R={...n.secrets,CRTR_API_URL:n.apiUrl,CRTR_EXECUTION_ID:t.executionId,CRTR_NODE_TOKEN:w,CRTR_RELEASE:t.release,CRTR_RELEASE_URL:n.releaseUrl,CRTR_RELEASE_SHA256:n.releaseSha256};return await a.startProcess(o,{name:s,command:r.bootstrap(),env:R,waitForCompletion:!1}),{pid:null,exited:null}},async wake(t){const n=await d(t);if(n===null)return"no_broker";try{const e=await a.exec(n,`wake-${t.executionId}-${Date.now()}`,`${S} ${t.executionId}`,30);if(e.exitCode===0)return"signalled";if(e.exitCode===C)return"no_broker";throw new Error(`wake of ${t.executionId} failed (exit ${e.exitCode}): ${(e.stderr||e.logs).slice(0,300)}`)}catch(e){if(f(e))return"no_broker";throw e}},async stop(t,n){try{const e=await d(t);if(e===null)return{confirmed:!0};const o=m(t.executionId),s=i(async()=>(await a.getProcess(e,o))?.status==="running","running");if(await s()){await a.stopProcess(e,o);const c=Date.now()+l;for(;Date.now()<c&&await s();)await new Promise(w=>setTimeout(w,250));if(await s()&&await a.killProcess(e,o),await s())return{confirmed:!1}}return{confirmed:await _(e,t.executionId)}}catch(e){return f(e)?{confirmed:!0}:{confirmed:!1,error:e instanceof Error?e:new Error(String(e))}}},async status(t,n){try{const e=await d(t);if(e===null)return{state:"gone"};if(n?.idle===!0)return{state:"running",pid:null};const o=await a.getProcess(e,m(t.executionId));return o===null?{state:"gone"}:o.status==="running"?{state:"running",pid:null}:o.status==="failed"||o.status==="completed"&&o.exitCode!==0?h(o):{state:"gone"}}catch(e){return f(e)?{state:"gone"}:{state:"unknown",error:e instanceof Error?e.message:String(e)}}},async delete(t){for(const n of await(r.sandboxNamesForNode??N)(t))await a.deleteSandbox(n)},async viewerAddress(t){const n=p(t),e=E.get(n);if(e&&e.expiresAt-Date.now()>5*6e4)return g(e.url,e.token);const o=await a.ensurePreview(n,u,x),s=new Date(Date.now()+60*6e4),{token:c}=await a.createPreviewToken(n,u,s);return E.set(n,{url:o,token:c,expiresAt:s.getTime()}),g(o,c)},async sandboxState(t){return(await d(t))?.state??null}}}i(v,"createBlaxelRunner");function g(r,a){return{kind:"wss",url:`${r.replace(/^https:/,"wss:")}/view`,headers:{"x-blaxel-preview-token":a}}}i(g,"viewer");export{x as BROKER_VIEW_PORT,T as BlaxelApiError,C as EXIT_NO_BROKER,y as FAILURE_LINE,S as WAKE_COMMAND,m as brokerProcessName,v as createBlaxelRunner,p as sandboxNameFor};
@@ -1,3 +1,3 @@
1
- var me=Object.defineProperty;var d=(e,o)=>me(e,"name",{value:o,configurable:!0});import{accessSync as he,constants as pe}from"node:fs";import{isAbsolute as _e,resolve as ke,join as ve}from"node:path";import{appSpace as ge}from"../layout.js";import{ApiError as ne}from"../../api/errors.js";import{spawnNode as ye,forkSourceNode as be,currentNodeContext as xe,rootOfSpine as $e,newNodeId as Se,preflightNodeId as Ce}from"./nodes.js";import{resolveProfileOperand as Ee}from"../profiles/manifest.js";import{selectProfileForCwd as Re,selectProfileForCwdReadOnly as Oe}from"../profiles/select.js";import{buildLaunchSpecAsync as oe,buildPiArgv as Fe}from"./launch.js";import{runsOnBlaxel as Ie,sandboxWorktreeRequest as Pe}from"../../daemon/api/environment-of.js";import{createManagedWorktreeAsync as Be,rollbackManagedWorktreeAsync as De}from"../worktree-mutation-async.js";import{usage as w,brokerLaunchFailed as D}from"../errors.js";import{writeGoal as Ne}from"./kickoff.js";import{appendSituationalContext as Le,formatSituationalProse as Me}from"./situational-context.js";import{ensureRoadmap as Te}from"./roadmap.js";import{buildWakeBearings as Ae}from"./bearings.js";import{encodeKickoffOrigin as We,KICKOFF_ORIGIN_ENV as je}from"./stamp/protocol.js";import{ExecutionCapReached as ze,beginExecution as He,crtrHome as Ke,getNode as u,fullName as re,jobDir as Ge,recordExecutionPid as Ue,abortExecution as j,setFrozen as z}from"../canvas/index.js";import{contextDir as H,nodeAgentDir as K}from"../canvas/node-agent-paths.js";import{copyTreeBeneath as Ve}from"../spaces/open-beneath.js";import{conversationIdFromPath as N,conversationStore as S}from"../conversation-store/index.js";import{appMemoryTooSmall as Xe,boundFleet as qe,brokerThresholdsForDaemon as te}from"./fleet.js";import{admitBrokerMemory as Ye}from"./memory-admission.js";import{emitEvent as L}from"../events/emit.js";import{blaxelEnvironmentOf as Je}from"./blaxel-environment.js";import{openViewerWindow as Qe,focusOf as Ze,windowOfPane as en}from"./placement.js";import{awaitBrokerViewSocket as nn}from"./view-socket.js";import{readLogSnapshot as on}from"../events/read.js";import{transition as k}from"./lifecycle.js";import{beginBootFaultAttempt as rn}from"../canvas/recovery.js";import{headlessBrokerHost as G}from"./host.js";import{ensureDaemon as tn}from"../../daemon/manage.js";import{readConfig as an}from"../config.js";import{modelRequestFromConfig as dn,unregisteredConcreteModel as sn,unroutableConcreteModel as ln}from"../model-routes.js";import{openModelRegistry as cn}from"./model-registry.js";import{MODEL_SPEC_NEXT as un}from"./model-selection.js";import{forkNodeLedger as fn}from"./ledger.js";import{appRungForGrant as wn}from"../llm/app-rungs.js";import{APP_FAMILY as U}from"../model-routes.js";import{discoverLifecycleHookRegistry as mn}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as hn}from"../command-hooks/transport/exec-lifecycle.js";import{someAsync as pn}from"../async-array.js";import{checkStart as _n}from"./start-check.js";function v(e){return{detail:{schema:"crtr.node-outcome-detail/v1",message:e instanceof Error?e.message:String(e)}}}d(v,"launchFailureOutcome");async function kn(e,o,a){try{return await G.launch(e,o,a)}catch(r){try{await k(e,"crash",{reason:"launch_failed",outcome:v(r)})}catch{}const t=r.message;throw D(t.startsWith("[broker]")?t:`[broker] refusing to launch ${e}: ${t}`,"Inspect the cause above, fix the launch recipe or environment, then retry.")}}d(kn,"launchBrokerOrCrash");function vn(e){return on({nodeId:e,component:"broker",pattern:"broker.startup."},!1).items.flatMap(a=>a.kind==="event"&&a.envelope.event.startsWith("broker.startup.")&&a.envelope.event!=="broker.startup.warning"?[a.envelope.event.slice(15)]:[]).at(-1)??null}d(vn,"lastStartupStep");function gn(e,o){const a=vn(e),r=a===null?"it had logged no startup step":`the last startup step it logged was \`${a}\``;if(o.reason==="exited"){const{code:t,signal:s}=o.status;return{text:`exited (code ${t??"null"}, signal ${s??"null"}) before binding its view socket; ${r}`,fields:{reason:"exited",code:t,signal:s,last_startup_step:a}}}return{text:`was still starting after ${Math.round(o.waitedMs/1e3)} s without binding its view socket, so the launcher stopped it; ${r}`,fields:{reason:"timeout",waited_ms:o.waitedMs,last_startup_step:a}}}d(gn,"notReadyDescription");async function yn(e,o){try{await k(e,"crash",{reason:"launch_failed",outcome:v(o)})}catch{}try{G.teardown(e)}catch{}}d(yn,"crashAndTearDown");async function bn(e){const o=(await S().list()).map(({conversation_id:t,path:s})=>({id:t,path:s})),a=o.filter(t=>t.id===e),r=a.length>0?a:o.filter(t=>t.id.startsWith(e));if(r.length===0)throw w(`--fork-from '${e}': no pi session with that id found.`,{next:"Pass a live node id, a real session uuid/prefix, or an absolute .jsonl session path."});if(r.length>1)throw w(`--fork-from '${e}': ambiguous \u2014 ${r.length} sessions match that id prefix; pass the full id or the .jsonl path.`,{next:"Pass the full session uuid or an absolute .jsonl path to disambiguate."});return r[0].path}d(bn,"resolveSessionUuid");async function ie(e){const o=e.trim();if(o==="")throw w("--fork-from requires a node id, session file, or session uuid.",{next:"Pass a live node id, an absolute .jsonl session path, or a session uuid/prefix."});if(o.includes("/")||o.endsWith(".jsonl")){const r=N(_e(o)?o:ke(process.cwd(),o));if(r===null)throw w(`--fork-from '${o}': invalid conversation path.`,{next:"Pass a conversation file path or id."});return(await S().header(r)).path}const a=await u(o);if(a!==null){const r=a.pi_session_file;if(r==null||r==="")throw w(`node ${o} has no pi session yet \u2014 it has not captured a session FILE to fork from (the broker forks a .jsonl path, not a bare id).`,{next:"Pick a node that has captured a session, or drop --fork-from."});const t=N(r);if(t===null)throw w(`node ${o} has no valid conversation id.`,{next:"Pick a node with a captured conversation."});return(await S().header(t)).path}return await bn(o)}d(ie,"resolveForkSource");async function xn(e,o,a,r={}){return e===null?null:e!==void 0&&e!==""?Ee(e).profileId:o!==null?(await u(o))?.profile_id??null:r.readOnlySelection===!0?Oe(a):Re(a)}d(xn,"resolveProfileId");function ae(e){const o=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return dn(e.model,o,e.modelExact===!0)}d(ae,"requestFromLaunch");async function de(e,o,a){const r=a??(e.modelIntent?`${e.modelIntent.family}/${e.modelIntent.strength}`:e.model),t=await wn(o,r,e.modelExact===!0);if(t===null)return e;const{CRTR_MODEL_INTENT:s,...l}=e.env??{};return{...e,model:t.model,modelIntent:{family:U,strength:t.strength},modelExact:void 0,env:{...l,CRTR_MODEL_INTENT:`${U}/${t.strength}`}}}d(de,"appPaidLaunch");async function se(e,o,a){if(e===void 0||e.kind==="logical"&&e.family!==U)return;const r=await sn(e.kind==="logical"?{kind:"concrete",spec:e.spec}:e,a??await cn(),o);if(r!==void 0)throw w(`model '${r.request.spec}' is not registered in this daemon.`,{received:r.request.spec,next:un});const t=await ln(e,o);if(t!==void 0)throw w(`model '${t.spec}' is registered but no configured model route offers it, so no turn could be served.`,{received:t.spec,next:"Pick a model from your configured routes (`crtr sys config get modelLadders`), or add this one to a ladder or route."})}d(se,"assertLaunchModelRegistered");function $n(e,o,a){const r=e??o??null;if(!a&&r===null)throw new Error("spawnChild requires a calling node (CRTR_NODE_ID) or an explicit parent");return r}d($n,"resolveSpawner");async function le(e,o,a){try{tn()}catch{}const r=xe(),t=e.root===!0,s=$n(e.parent,r.nodeId??null,t),l=e.mode??"base",g=e.rootLifecycle??(t?"resident":"terminal"),ce=t&&g==="resident",R=s===null?null:await u(s),V=e.creator===void 0||e.creator===null?null:await u(e.creator);e.nodeId!==void 0&&await Ce(e.nodeId);const y=e.forkFrom!==void 0?await ie(e.forkFrom):void 0,M=y===void 0?null:await be(y),T=await pn([R,V,M],async f=>f?.isolated===!0||f?.run_id!=null&&(await u(f.run_id))?.isolated===!0);if(T&&e.isolated===!1)throw new ne(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});const O=T||e.isolated===!0;if(O&&e.worktree===!0)throw new ne(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const C=e.worktree===!0,m=O||C?e.nodeId??await Se():e.nodeId,F=O?e.run!==void 0&&t&&!T?e.cwd:ve(ge(e.run?.grantee??R?.grantee??"app:terminal").nodes,m):e.cwd,h=await xn(e.profile,s,F),X=e.run?.grant_id??R?.grant_id??null,E=e.run??V??R??M,q=await de((await oe(e.kind,l,{lifecycle:g,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:F,profileId:h,app:E?.grantee??"app:terminal"})).launch,X,e.model);await se(ae(q),{cwd:F,profileId:h}),await _n({grantee:E?.grantee??"app:terminal",sub:E?.sub,grant_id:E?.grant_id});const Y=M?.node_id??null;let b,I=F;const A=C&&await Ie(h)?await Pe(h,e.worktreeCwd??e.cwd,m,e.worktreeBase):void 0;try{C&&A===void 0&&(b=await Be(e.worktreeCwd??e.cwd,m,e.worktreeBase),I=b.path);const f=C?await de((await oe(e.kind,l,{lifecycle:g,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:I,profileId:h,app:E?.grantee??"app:terminal"})).launch,X,e.model):q;A!==void 0&&(f.worktree=A),C&&await se(ae(f),{cwd:I,profileId:h});const n=await ye({kind:e.kind,mode:l,lifecycle:g,cwd:I,name:e.name??e.kind,description:e.description,parent:t?null:s,creator:e.creator,...e.actor===void 0?{}:{actor:e.actor},forkSourceNode:Y,spawnedBy:t?s??void 0:void 0,profile_id:h,...e.scopes===void 0?{}:{scopes:e.scopes},isolated:O,...e.run===void 0?{}:{run:e.run},...e.causedBy===void 0?{}:{causedBy:e.causedBy},...e.withinCreate===void 0?{}:{withinCreate:e.withinCreate},forkFrom:e.forkFrom,forkSourceFile:y,modelOverride:e.model===void 0?void 0:f.model,...b!==void 0?{managedWorktree:b}:{},...m!==void 0?{nodeId:m}:{},...e.reviewBinding===void 0?{}:{reviewBinding:e.reviewBinding},...e.deadlineAt===void 0?{}:{deadlineAt:e.deadlineAt},...e.outcomeDelivery===void 0?{}:{outcomeDelivery:e.outcomeDelivery},launch:f});if(a?.(n.node_id),e.forkFrom!==void 0)try{await fn(n.node_id,Y??null)}catch(i){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),i}if(await Ne(n.node_id,e.prompt),e.situationalContext!==void 0&&await Le(n.node_id,Me(e.situationalContext)),o!==void 0)try{o(n.node_id)}catch(i){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),i}const W=e.prompt!=="",ue=`${W&&e.wakeOrigin!==void 0?`${Ae(e.wakeOrigin)}
1
+ var me=Object.defineProperty;var d=(e,o)=>me(e,"name",{value:o,configurable:!0});import{accessSync as he,constants as pe}from"node:fs";import{isAbsolute as _e,resolve as ke,join as ve}from"node:path";import{appSpace as ge}from"../layout.js";import{ApiError as ne}from"../../api/errors.js";import{spawnNode as ye,forkSourceNode as be,currentNodeContext as xe,rootOfSpine as $e,newNodeId as Se,preflightNodeId as Ce}from"./nodes.js";import{resolveProfileOperand as Ee}from"../profiles/manifest.js";import{selectProfileForCwd as Re,selectProfileForCwdReadOnly as Oe}from"../profiles/select.js";import{buildLaunchSpecAsync as oe,buildPiArgv as Ie}from"./launch.js";import{runsOnBlaxel as Fe,sandboxWorktreeRequest as Pe}from"../../daemon/api/environment-of.js";import{createManagedWorktreeAsync as Be,rollbackManagedWorktreeAsync as De}from"../worktree-mutation-async.js";import{usage as w,brokerLaunchFailed as D}from"../errors.js";import{writeGoal as Ne}from"./kickoff.js";import{appendSituationalContext as Le,formatSituationalProse as Me}from"./situational-context.js";import{ensureRoadmap as Te}from"./roadmap.js";import{buildWakeBearings as Ae}from"./bearings.js";import{encodeKickoffOrigin as We,KICKOFF_ORIGIN_ENV as je}from"./stamp/protocol.js";import{carryInitialInput as ze}from"../runs/initial-input.js";import{ExecutionCapReached as He,beginExecution as Ke,crtrHome as Ge,getNode as u,fullName as re,jobDir as Ue,recordExecutionPid as Ve,abortExecution as j,setFrozen as z}from"../canvas/index.js";import{contextDir as H,nodeAgentDir as K}from"../canvas/node-agent-paths.js";import{copyTreeBeneath as Xe}from"../spaces/open-beneath.js";import{conversationIdFromPath as N,conversationStore as S}from"../conversation-store/index.js";import{appMemoryTooSmall as qe,boundFleet as Ye,brokerThresholdsForDaemon as te}from"./fleet.js";import{admitBrokerMemory as Je}from"./memory-admission.js";import{emitEvent as L}from"../events/emit.js";import{blaxelEnvironmentOf as Qe}from"./blaxel-environment.js";import{openViewerWindow as Ze,focusOf as en,windowOfPane as nn}from"./placement.js";import{awaitBrokerViewSocket as on}from"./view-socket.js";import{readLogSnapshot as rn}from"../events/read.js";import{transition as v}from"./lifecycle.js";import{beginBootFaultAttempt as tn}from"../canvas/recovery.js";import{headlessBrokerHost as G}from"./host.js";import{ensureDaemon as an}from"../../daemon/manage.js";import{readConfig as dn}from"../config.js";import{modelRequestFromConfig as sn,unregisteredConcreteModel as ln,unroutableConcreteModel as cn}from"../model-routes.js";import{openModelRegistry as un}from"./model-registry.js";import{MODEL_SPEC_NEXT as fn}from"./model-selection.js";import{forkNodeLedger as wn}from"./ledger.js";import{appRungForGrant as mn}from"../llm/app-rungs.js";import{APP_FAMILY as U}from"../model-routes.js";import{discoverLifecycleHookRegistry as hn}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as pn}from"../command-hooks/transport/exec-lifecycle.js";import{someAsync as _n}from"../async-array.js";import{checkStart as kn}from"./start-check.js";function g(e){return{detail:{schema:"crtr.node-outcome-detail/v1",message:e instanceof Error?e.message:String(e)}}}d(g,"launchFailureOutcome");async function vn(e,o,a){try{return await G.launch(e,o,a)}catch(r){try{await v(e,"crash",{reason:"launch_failed",outcome:g(r)})}catch{}const t=r.message;throw D(t.startsWith("[broker]")?t:`[broker] refusing to launch ${e}: ${t}`,"Inspect the cause above, fix the launch recipe or environment, then retry.")}}d(vn,"launchBrokerOrCrash");function gn(e){return rn({nodeId:e,component:"broker",pattern:"broker.startup."},!1).items.flatMap(a=>a.kind==="event"&&a.envelope.event.startsWith("broker.startup.")&&a.envelope.event!=="broker.startup.warning"?[a.envelope.event.slice(15)]:[]).at(-1)??null}d(gn,"lastStartupStep");function yn(e,o){const a=gn(e),r=a===null?"it had logged no startup step":`the last startup step it logged was \`${a}\``;if(o.reason==="exited"){const{code:t,signal:s}=o.status;return{text:`exited (code ${t??"null"}, signal ${s??"null"}) before binding its view socket; ${r}`,fields:{reason:"exited",code:t,signal:s,last_startup_step:a}}}return{text:`was still starting after ${Math.round(o.waitedMs/1e3)} s without binding its view socket, so the launcher stopped it; ${r}`,fields:{reason:"timeout",waited_ms:o.waitedMs,last_startup_step:a}}}d(yn,"notReadyDescription");async function bn(e,o){try{await v(e,"crash",{reason:"launch_failed",outcome:g(o)})}catch{}try{G.teardown(e)}catch{}}d(bn,"crashAndTearDown");async function xn(e){const o=(await S().list()).map(({conversation_id:t,path:s})=>({id:t,path:s})),a=o.filter(t=>t.id===e),r=a.length>0?a:o.filter(t=>t.id.startsWith(e));if(r.length===0)throw w(`--fork-from '${e}': no pi session with that id found.`,{next:"Pass a live node id, a real session uuid/prefix, or an absolute .jsonl session path."});if(r.length>1)throw w(`--fork-from '${e}': ambiguous \u2014 ${r.length} sessions match that id prefix; pass the full id or the .jsonl path.`,{next:"Pass the full session uuid or an absolute .jsonl path to disambiguate."});return r[0].path}d(xn,"resolveSessionUuid");async function ie(e){const o=e.trim();if(o==="")throw w("--fork-from requires a node id, session file, or session uuid.",{next:"Pass a live node id, an absolute .jsonl session path, or a session uuid/prefix."});if(o.includes("/")||o.endsWith(".jsonl")){const r=N(_e(o)?o:ke(process.cwd(),o));if(r===null)throw w(`--fork-from '${o}': invalid conversation path.`,{next:"Pass a conversation file path or id."});return(await S().header(r)).path}const a=await u(o);if(a!==null){const r=a.pi_session_file;if(r==null||r==="")throw w(`node ${o} has no pi session yet \u2014 it has not captured a session FILE to fork from (the broker forks a .jsonl path, not a bare id).`,{next:"Pick a node that has captured a session, or drop --fork-from."});const t=N(r);if(t===null)throw w(`node ${o} has no valid conversation id.`,{next:"Pick a node with a captured conversation."});return(await S().header(t)).path}return await xn(o)}d(ie,"resolveForkSource");async function $n(e,o,a,r={}){return e===null?null:e!==void 0&&e!==""?Ee(e).profileId:o!==null?(await u(o))?.profile_id??null:r.readOnlySelection===!0?Oe(a):Re(a)}d($n,"resolveProfileId");function ae(e){const o=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return sn(e.model,o,e.modelExact===!0)}d(ae,"requestFromLaunch");async function de(e,o,a){const r=a??(e.modelIntent?`${e.modelIntent.family}/${e.modelIntent.strength}`:e.model),t=await mn(o,r,e.modelExact===!0);if(t===null)return e;const{CRTR_MODEL_INTENT:s,...l}=e.env??{};return{...e,model:t.model,modelIntent:{family:U,strength:t.strength},modelExact:void 0,env:{...l,CRTR_MODEL_INTENT:`${U}/${t.strength}`}}}d(de,"appPaidLaunch");async function se(e,o,a){if(e===void 0||e.kind==="logical"&&e.family!==U)return;const r=await ln(e.kind==="logical"?{kind:"concrete",spec:e.spec}:e,a??await un(),o);if(r!==void 0)throw w(`model '${r.request.spec}' is not registered in this daemon.`,{received:r.request.spec,next:fn});const t=await cn(e,o);if(t!==void 0)throw w(`model '${t.spec}' is registered but no configured model route offers it, so no turn could be served.`,{received:t.spec,next:"Pick a model from your configured routes (`crtr sys config get modelLadders`), or add this one to a ladder or route."})}d(se,"assertLaunchModelRegistered");function Sn(e,o,a){const r=e??o??null;if(!a&&r===null)throw new Error("spawnChild requires a calling node (CRTR_NODE_ID) or an explicit parent");return r}d(Sn,"resolveSpawner");async function le(e,o,a){try{an()}catch{}const r=xe(),t=e.root===!0,s=Sn(e.parent,r.nodeId??null,t),l=e.mode??"base",y=e.rootLifecycle??(t?"resident":"terminal"),ce=t&&y==="resident",R=s===null?null:await u(s),V=e.creator===void 0||e.creator===null?null:await u(e.creator);e.nodeId!==void 0&&await Ce(e.nodeId);const b=e.forkFrom!==void 0?await ie(e.forkFrom):void 0,M=b===void 0?null:await be(b),T=await _n([R,V,M],async f=>f?.isolated===!0||f?.run_id!=null&&(await u(f.run_id))?.isolated===!0);if(T&&e.isolated===!1)throw new ne(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});const O=T||e.isolated===!0;if(O&&e.worktree===!0)throw new ne(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const C=e.worktree===!0,m=O||C?e.nodeId??await Se():e.nodeId,I=O?e.run!==void 0&&t&&!T?e.cwd:ve(ge(e.run?.grantee??R?.grantee??"app:terminal").nodes,m):e.cwd,h=await $n(e.profile,s,I),X=e.run?.grant_id??R?.grant_id??null,E=e.run??V??R??M,q=await de((await oe(e.kind,l,{lifecycle:y,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:I,profileId:h,app:E?.grantee??"app:terminal"})).launch,X,e.model);await se(ae(q),{cwd:I,profileId:h}),await kn({grantee:E?.grantee??"app:terminal",sub:E?.sub,grant_id:E?.grant_id});const Y=M?.node_id??null;let x,F=I;const A=C&&await Fe(h)?await Pe(h,e.worktreeCwd??e.cwd,m,e.worktreeBase):void 0;try{C&&A===void 0&&(x=await Be(e.worktreeCwd??e.cwd,m,e.worktreeBase),F=x.path);const f=C?await de((await oe(e.kind,l,{lifecycle:y,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:F,profileId:h,app:E?.grantee??"app:terminal"})).launch,X,e.model):q;A!==void 0&&(f.worktree=A),C&&await se(ae(f),{cwd:F,profileId:h});const n=await ye({kind:e.kind,mode:l,lifecycle:y,cwd:F,name:e.name??e.kind,description:e.description,parent:t?null:s,creator:e.creator,...e.actor===void 0?{}:{actor:e.actor},forkSourceNode:Y,spawnedBy:t?s??void 0:void 0,profile_id:h,...e.scopes===void 0?{}:{scopes:e.scopes},isolated:O,...e.run===void 0?{}:{run:e.run},...e.causedBy===void 0?{}:{causedBy:e.causedBy},...e.withinCreate===void 0?{}:{withinCreate:e.withinCreate},forkFrom:e.forkFrom,forkSourceFile:b,modelOverride:e.model===void 0?void 0:f.model,...x!==void 0?{managedWorktree:x}:{},...m!==void 0?{nodeId:m}:{},...e.reviewBinding===void 0?{}:{reviewBinding:e.reviewBinding},...e.deadlineAt===void 0?{}:{deadlineAt:e.deadlineAt},...e.outcomeDelivery===void 0?{}:{outcomeDelivery:e.outcomeDelivery},launch:f});if(a?.(n.node_id),e.forkFrom!==void 0)try{await wn(n.node_id,Y??null)}catch(i){throw await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),i}if(await Ne(n.node_id,e.prompt),e.situationalContext!==void 0&&await Le(n.node_id,Me(e.situationalContext)),o!==void 0)try{o(n.node_id)}catch(i){throw await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),i}const W=e.prompt!=="",ue=`${W&&e.wakeOrigin!==void 0?`${Ae(e.wakeOrigin)}
2
2
 
3
- `:""}${e.prompt}`;l==="orchestrator"&&await Te(n.node_id,{goal:e.prompt.trim()}),await Sn(n);let J;if(y!==void 0)try{const i=N(y);if(i===null)throw new Error(`invalid fork source ${y}`);const c=await S().fork(i,n.node_id);J=(await S().header(c)).path}catch(i){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),i}const x=Fe(n,{prompt:ue,resumeSessionPath:J});if(x.env={...x.env,CRTR_SUBTREE:await $e(n.node_id)},W){const i=e.wakeOrigin!==void 0?{type:"runtime",kind:"wake",wake:e.wakeOrigin}:{type:"runtime",kind:"kickoff",...n.spawned_by!=null&&n.spawned_by!==""?{spawnedBy:n.spawned_by}:{}};x.env[je]=We(i)}const p=qe();if(await Je(n.profile_id??null)!==null)return await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"remote_environment"}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const Q=await p.reserveSlot(n.node_id),_=Q?await Ye(n.node_id):null;if(_?.verdict==="too_small"){p.releaseReservation(n.node_id);const i=Xe(_);throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),i}if(!Q||_?.verdict==="denied")return _?.verdict==="denied"&&p.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:_?.verdict==="denied"?{reason:"app_memory",free_bytes:_.free_bytes,needed_bytes:_.needed_bytes}:{reason:"broker_cap",live_count:await p.occupancy(),hard_cap:te().automaticReviveCap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const fe=await rn(n.node_id),{automaticReviveCap:we}=te();let $;try{$=await He(n.node_id,{daemonEpoch:p.epoch(),cap:we})}catch(i){if(!(i instanceof ze))throw i;return p.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"broker_cap",live_count:i.live,hard_cap:i.cap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null}}x.env={...x.env,CRTR_EXECUTION_ID:$};let P;try{P=await kn(n.node_id,x,{cwd:n.cwd,name:re(n),resuming:!1})}catch(i){throw await j($,{reason:"launch_failed"}),i}finally{p.releaseReservation(n.node_id)}if(P.pid===null){const i=new Error(`broker host returned no pid for ${n.node_id}`);throw await j($,{reason:"launch_failed"}),await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),D(`failed to launch the broker engine for ${n.node_id} (${n.name}) \u2014 the node was not started.`,`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then verify the pi engine can start in this environment.`)}if(await Ue($,n.node_id,P.pid)==="shutdown")throw await j($,{reason:"superseded"}),G.teardown(n.node_id),D(`broker execution was superseded for ${n.node_id}`,"Retry spawning the node.");if(ce&&e.readiness==="await"){const i=await nn(n.node_id,P.exited);if(!i.ready){const c=gn(n.node_id,i);L({level:"error",event:"broker.launch.not_ready",node_id:n.node_id,fields:c.fields});const B=await fe.explanation();throw await yn(n.node_id,new Error(B??c.text)),D(B!==null?`the root broker ${n.node_id} could not start \u2014 ${B}`:`the root broker ${n.node_id} ${c.text} \u2014 it was not started.`,B!==null?"Resolve the cause named above, then retry.":`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then retry.`)}}let Z=null,ee=null;if(!t&&s!==null&&an("user").auto_open_child_viewers)try{const i=await Ze(s);if(i!==null&&i.pane!==null&&i.pane!==""){const c=await Qe(n.node_id,i.session??"",{name:re(n),cwd:e.cwd,besidePane:i.pane,waitForBroker:!0,initialWhip:W});c!==null&&c.pane!==null&&c.pane!==""&&(ee=c.session,Z=en(c.pane))}}catch{}return{node:await u(n.node_id),frozen:!1,window:Z,session:ee}}catch(f){throw b!==void 0&&m!==void 0&&await u(m)===null&&await De(b),f}}d(le,"spawnChildPrepared");async function vo(e){return le(e)}d(vo,"spawnChild");async function Sn(e){const o=mn(e.cwd,e.profile_id).plans.get("node:start")??[];await hn(o,{node:{id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,cwd:e.cwd,nodeDir:await K(e.node_id),contextDir:await H(e.node_id),jobDir:Ge(e.node_id)},runtime:{isBirth:!0,canvasHome:Ke(),profile:e.profile_id??null}})}d(Sn,"invokeBirthLifecycleHooks");const Cn=new Set(["initial-prompt.md","yield-message.md","result.json","jobs"]);async function go(e,o,a){const r=await u(e);if(r===null)throw w(`unknown node: ${e}`,{received:e,next:"List nodes and pass the id of a node with a captured pi session."});const t=await ie(e),s=await H(e);try{await S().header(N(t)),he(s,pe.R_OK)}catch(l){throw new Error(`cannot fork ${e}: its conversation or context is unreadable \u2014 ${l.message}`)}return le({kind:r.kind,mode:"base",cwd:r.cwd,prompt:"",root:!0,readiness:"await",forkFrom:e,...a===void 0?{}:{actor:a},profile:r.profile_id,model:r.model_override??void 0,...o===void 0?{}:{scopes:o}},async l=>{const g=await H(l);Ve(await K(e),s,await K(l),g,Cn)})}d(go,"forkNode");export{de as appPaidLaunch,se as assertLaunchModelRegistered,go as forkNode,Sn as invokeBirthLifecycleHooks,ie as resolveForkSource,xn as resolveProfileId,$n as resolveSpawner,vo as spawnChild,le as spawnChildPrepared};
3
+ `:""}${e.prompt}`;l==="orchestrator"&&await Te(n.node_id,{goal:e.prompt.trim()}),await Cn(n);let J;if(b!==void 0)try{const i=N(b);if(i===null)throw new Error(`invalid fork source ${b}`);const c=await S().fork(i,n.node_id);J=(await S().header(c)).path}catch(i){throw await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),i}const p=Ie(n,{prompt:ue,resumeSessionPath:J});if(p.env={...p.env,CRTR_SUBTREE:await $e(n.node_id)},W){const i=e.wakeOrigin!==void 0?{type:"runtime",kind:"wake",wake:e.wakeOrigin}:{type:"runtime",kind:"kickoff",...n.spawned_by!=null&&n.spawned_by!==""?{spawnedBy:n.spawned_by}:{}};p.env[je]=We(i),await ze(p,n)}const _=Ye();if(await Qe(n.profile_id??null)!==null)return await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"remote_environment"}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const Q=await _.reserveSlot(n.node_id),k=Q?await Je(n.node_id):null;if(k?.verdict==="too_small"){_.releaseReservation(n.node_id);const i=qe(k);throw await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),i}if(!Q||k?.verdict==="denied")return k?.verdict==="denied"&&_.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:k?.verdict==="denied"?{reason:"app_memory",free_bytes:k.free_bytes,needed_bytes:k.needed_bytes}:{reason:"broker_cap",live_count:await _.occupancy(),hard_cap:te().automaticReviveCap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const fe=await tn(n.node_id),{automaticReviveCap:we}=te();let $;try{$=await Ke(n.node_id,{daemonEpoch:_.epoch(),cap:we})}catch(i){if(!(i instanceof He))throw i;return _.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"broker_cap",live_count:i.live,hard_cap:i.cap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null}}p.env={...p.env,CRTR_EXECUTION_ID:$};let P;try{P=await vn(n.node_id,p,{cwd:n.cwd,name:re(n),resuming:!1})}catch(i){throw await j($,{reason:"launch_failed"}),i}finally{_.releaseReservation(n.node_id)}if(P.pid===null){const i=new Error(`broker host returned no pid for ${n.node_id}`);throw await j($,{reason:"launch_failed"}),await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),D(`failed to launch the broker engine for ${n.node_id} (${n.name}) \u2014 the node was not started.`,`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then verify the pi engine can start in this environment.`)}if(await Ve($,n.node_id,P.pid)==="shutdown")throw await j($,{reason:"superseded"}),G.teardown(n.node_id),D(`broker execution was superseded for ${n.node_id}`,"Retry spawning the node.");if(ce&&e.readiness==="await"){const i=await on(n.node_id,P.exited);if(!i.ready){const c=yn(n.node_id,i);L({level:"error",event:"broker.launch.not_ready",node_id:n.node_id,fields:c.fields});const B=await fe.explanation();throw await bn(n.node_id,new Error(B??c.text)),D(B!==null?`the root broker ${n.node_id} could not start \u2014 ${B}`:`the root broker ${n.node_id} ${c.text} \u2014 it was not started.`,B!==null?"Resolve the cause named above, then retry.":`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then retry.`)}}let Z=null,ee=null;if(!t&&s!==null&&dn("user").auto_open_child_viewers)try{const i=await en(s);if(i!==null&&i.pane!==null&&i.pane!==""){const c=await Ze(n.node_id,i.session??"",{name:re(n),cwd:e.cwd,besidePane:i.pane,waitForBroker:!0,initialWhip:W});c!==null&&c.pane!==null&&c.pane!==""&&(ee=c.session,Z=nn(c.pane))}}catch{}return{node:await u(n.node_id),frozen:!1,window:Z,session:ee}}catch(f){throw x!==void 0&&m!==void 0&&await u(m)===null&&await De(x),f}}d(le,"spawnChildPrepared");async function yo(e){return le(e)}d(yo,"spawnChild");async function Cn(e){const o=hn(e.cwd,e.profile_id).plans.get("node:start")??[];await pn(o,{node:{id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,cwd:e.cwd,nodeDir:await K(e.node_id),contextDir:await H(e.node_id),jobDir:Ue(e.node_id)},runtime:{isBirth:!0,canvasHome:Ge(),profile:e.profile_id??null}})}d(Cn,"invokeBirthLifecycleHooks");const En=new Set(["initial-prompt.md","yield-message.md","result.json","jobs"]);async function bo(e,o,a){const r=await u(e);if(r===null)throw w(`unknown node: ${e}`,{received:e,next:"List nodes and pass the id of a node with a captured pi session."});const t=await ie(e),s=await H(e);try{await S().header(N(t)),he(s,pe.R_OK)}catch(l){throw new Error(`cannot fork ${e}: its conversation or context is unreadable \u2014 ${l.message}`)}return le({kind:r.kind,mode:"base",cwd:r.cwd,prompt:"",root:!0,readiness:"await",forkFrom:e,...a===void 0?{}:{actor:a},profile:r.profile_id,model:r.model_override??void 0,...o===void 0?{}:{scopes:o}},async l=>{const y=await H(l);Xe(await K(e),s,await K(l),y,En)})}d(bo,"forkNode");export{de as appPaidLaunch,se as assertLaunchModelRegistered,bo as forkNode,Cn as invokeBirthLifecycleHooks,ie as resolveForkSource,$n as resolveProfileId,Sn as resolveSpawner,yo as spawnChild,le as spawnChildPrepared};
@@ -1 +1 @@
1
- var v=Object.defineProperty;var a=(t,e)=>v(t,"name",{value:e,configurable:!0});import{openDb as d}from"../../core/canvas/db.js";import{endLiveExecution as _,liveExecutionForNode as E}from"../../core/canvas/executions.js";import{emitEvent as c}from"../../core/events/emit.js";import{afterRevocationStop as y}from"../../core/grants/on-sync.js";import{allGrants as u,grantIdsWhere as S,grantStanding as m,mirrorOnly as h}from"../../core/grants/mirror.js";import{removeApp as g}from"../../core/grants/remove.js";import{wakeAllGrantWaiters as k,wakeGrantWaiters as x}from"../../core/grants/watch.js";import{discardOrphanedTokens as w,discardTokensForGrant as N}from"../../core/providers/token-cache.js";import{runnerForExecution as O}from"../../core/runtime/runner/index.js";import{closeGrantStreams as T,heldGrantIds as b}from"../../core/runs/events.js";import{stopNodeSandboxes as L}from"../../core/spaces/stop.js";import{brokerSignalBus as R}from"../broker-signals/bus.js";import{withRowClaim as D}from"./row-claim.js";const G=6e4;function f(t,e,n){c({level:"error",event:`daemon.grants.${t}`,error:e,fields:n})}a(f,"failed");function A(t,e,n){try{const o=e.sub===null||e.grantee===null?w(n):N({sub:e.sub,grantee:e.grantee,active:e.standing==="active"},n);o.tokens>0&&c({level:"info",event:"daemon.grants.outbound_tokens_discarded",fields:{grant:t,tokens:o.tokens}})}catch(o){f("outbound_tokens_discard_failed",o,{grant:t})}}a(A,"discardTokens");async function p(){try{await(await import("../../core/providers/bundles.js")).syncProviderBundles()}catch(t){c({level:"error",event:"daemon.providers.bundle_sync_failed",error:t})}}a(p,"reconcileProviderBundles");async function P(t,e){if(x(t),e.standing==="inactive"){const n=T(t);n>0&&c({level:"info",event:"daemon.grants.streams_closed",fields:{grant:t,grantee:e.grantee,streams:n}})}await p()}a(P,"serveEffects");async function C(t,e){const n=await D("nodes",t,G,async o=>{const r=await E(t);if(r===null)return;await o.assertOwned(),c({level:"info",event:"daemon.grants.node_stop",node_id:t,fields:{grant:e,execution_id:r.executionId}});const i=a(async()=>{await o.assertOwned(),await _(t,{reason:"permission_revoked"}),await y(t)},"finish");if(r.runner==="process"){const l=await L(t,"permission_revoked",{holdRecovery:!0,after:i});if(!l.confirmed)throw l.error??new Error(`stop of ${t} was not confirmed`);return}const s=await O(r.runner).stop(r,{reason:"permission_revoked"});if(!s.confirmed)throw s.error??new Error(`stop of ${t} was not confirmed`);await i()})}a(C,"stopRevokedNode");async function W(t,e){if(!(e.grantee!==null&&h({grantee:e.grantee}))){if(e.standing==="inactive"){const n=await(await d()).prepare("SELECT DISTINCT n.node_id FROM nodes n JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited' WHERE n.grant_id = ?").all(t);for(const{node_id:o}of n)try{await C(o,t)}catch(r){f("node_stop_failed",r,{grant:t,node_id:o})}}if(e.status==="removed"&&e.removal_done_at===null&&e.grantee!==null&&e.sub!==null)try{await g(e.grantee,e.sub),await(await d()).prepare("UPDATE grants SET removal_done_at = ? WHERE id = ? AND removal_done_at IS NULL").run(new Date().toISOString(),t)}catch(n){f("removal_failed",n,{grant:t,grantee:e.grantee})}}}a(W,"laneEffects");async function F(t,e){const n=await m(e);A(e,n,await u()),await Promise.all([t.serves?P(e,n):void 0,t.runsLanes&&n.standing!=="lagging"?W(e,n):void 0])}a(F,"applyGrantEffects");async function M(t){const e=new Set;if(t.serves)for(const o of b())e.add(o);if(t.runsLanes){const o=await(await d()).prepare("SELECT DISTINCT n.grant_id FROM nodes n JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited' WHERE n.grant_id IS NOT NULL").all();for(const{grant_id:r}of o)e.add(r)}const n=[];for(const o of e)(await m(o)).status===null&&n.push(o);return n}a(M,"goneGrantIds");async function I(t){const e=new Map,n=a(r=>{const i=(e.get(r)??Promise.resolve()).then(()=>F(t,r)).catch(s=>f("effects_failed",s,{grant:r})).finally(()=>{e.get(r)===i&&e.delete(r)});return e.set(r,i),i},"run"),o=a(async()=>{const r=t.serves?p():void 0;try{t.serves&&k(),w(await u());const i=new Set(await S(`status <> 'active' OR expires_at <= '${new Date().toISOString()}'`));for(const s of await M(t))i.add(s);await Promise.all([...i].map(n))}catch(i){f("catch_up_failed",i,{})}await r},"catchUp");return await R.subscribe(r=>{n(r)},{kinds:["grant"],onListenStart:a(()=>{o()},"onListenStart")})}a(I,"startGrantEffects");export{F as applyGrantEffects,I as startGrantEffects};
1
+ var v=Object.defineProperty;var a=(t,e)=>v(t,"name",{value:e,configurable:!0});import{openDb as l}from"../../core/canvas/db.js";import{isCloudRole as _}from"../../core/canvas/person.js";import{endLiveExecution as E,liveExecutionForNode as y}from"../../core/canvas/executions.js";import{emitEvent as c}from"../../core/events/emit.js";import{afterRevocationStop as S}from"../../core/grants/on-sync.js";import{allGrants as u,grantIdsWhere as h,grantStanding as m,mirrorOnly as g}from"../../core/grants/mirror.js";import{removeApp as b,removePersonData as k}from"../../core/grants/remove.js";import{wakeAllGrantWaiters as x,wakeGrantWaiters as N}from"../../core/grants/watch.js";import{discardOrphanedTokens as p,discardTokensForGrant as O}from"../../core/providers/token-cache.js";import{runnerForExecution as T}from"../../core/runtime/runner/index.js";import{closeGrantStreams as L,heldGrantIds as R}from"../../core/runs/events.js";import{stopNodeSandboxes as D}from"../../core/spaces/stop.js";import{brokerSignalBus as G}from"../broker-signals/bus.js";import{withRowClaim as P}from"./row-claim.js";const A=6e4;function f(t,e,r){c({level:"error",event:`daemon.grants.${t}`,error:e,fields:r})}a(f,"failed");function C(t,e,r){try{const o=e.sub===null||e.grantee===null?p(r):O({sub:e.sub,grantee:e.grantee,active:e.standing==="active"},r);o.tokens>0&&c({level:"info",event:"daemon.grants.outbound_tokens_discarded",fields:{grant:t,tokens:o.tokens}})}catch(o){f("outbound_tokens_discard_failed",o,{grant:t})}}a(C,"discardTokens");async function w(){try{await(await import("../../core/providers/bundles.js")).syncProviderBundles()}catch(t){c({level:"error",event:"daemon.providers.bundle_sync_failed",error:t})}}a(w,"reconcileProviderBundles");async function W(t,e){if(N(t),e.standing==="inactive"){const r=L(t);r>0&&c({level:"info",event:"daemon.grants.streams_closed",fields:{grant:t,grantee:e.grantee,streams:r}})}await w()}a(W,"serveEffects");async function F(t,e){const r=await P("nodes",t,A,async o=>{const n=await y(t);if(n===null)return;await o.assertOwned(),c({level:"info",event:"daemon.grants.node_stop",node_id:t,fields:{grant:e,execution_id:n.executionId}});const i=a(async()=>{await o.assertOwned(),await E(t,{reason:"permission_revoked"}),await S(t)},"finish");if(n.runner==="process"){const d=await D(t,"permission_revoked",{holdRecovery:!0,after:i});if(!d.confirmed)throw d.error??new Error(`stop of ${t} was not confirmed`);return}const s=await T(n.runner).stop(n,{reason:"permission_revoked"});if(!s.confirmed)throw s.error??new Error(`stop of ${t} was not confirmed`);await i()})}a(F,"stopRevokedNode");async function M(t,e){if(!(e.grantee!==null&&g({grantee:e.grantee}))){if(e.standing==="inactive"){const r=await(await l()).prepare("SELECT DISTINCT n.node_id FROM nodes n JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited' WHERE n.grant_id = ?").all(t);for(const{node_id:o}of r)try{await F(o,t)}catch(n){f("node_stop_failed",n,{grant:t,node_id:o})}}if(e.status==="removed"&&e.removal_done_at===null&&e.grantee!==null&&e.sub!==null)try{e.grantee!=="app:terminal"?await b(e.grantee,e.sub):_()?await k(e.grantee,e.sub):c({level:"info",event:"daemon.grants.removal_settled_without_cleanup",fields:{grant:t,grantee:e.grantee,sub:e.sub}}),await(await l()).prepare("UPDATE grants SET removal_done_at = ? WHERE id = ? AND removal_done_at IS NULL").run(new Date().toISOString(),t)}catch(r){f("removal_failed",r,{grant:t,grantee:e.grantee})}}}a(M,"laneEffects");async function U(t,e){const r=await m(e);C(e,r,await u()),await Promise.all([t.serves?W(e,r):void 0,t.runsLanes&&r.standing!=="lagging"?M(e,r):void 0])}a(U,"applyGrantEffects");async function $(t){const e=new Set;if(t.serves)for(const o of R())e.add(o);if(t.runsLanes){const o=await(await l()).prepare("SELECT DISTINCT n.grant_id FROM nodes n JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited' WHERE n.grant_id IS NOT NULL").all();for(const{grant_id:n}of o)e.add(n)}const r=[];for(const o of e)(await m(o)).status===null&&r.push(o);return r}a($,"goneGrantIds");async function re(t){const e=new Map,r=a(n=>{const i=(e.get(n)??Promise.resolve()).then(()=>U(t,n)).catch(s=>f("effects_failed",s,{grant:n})).finally(()=>{e.get(n)===i&&e.delete(n)});return e.set(n,i),i},"run"),o=a(async()=>{const n=t.serves?w():void 0;try{t.serves&&x(),p(await u());const i=new Set(await h(`status <> 'active' OR expires_at <= '${new Date().toISOString()}'`));for(const s of await $(t))i.add(s);await Promise.all([...i].map(r))}catch(i){f("catch_up_failed",i,{})}await n},"catchUp");return await G.subscribe(n=>{r(n)},{kinds:["grant"],onListenStart:a(()=>{o()},"onListenStart")})}a(re,"startGrantEffects");export{U as applyGrantEffects,re as startGrantEffects};
@@ -16,4 +16,6 @@ export declare function generateAndCommitRecapName(nodeId: string, conversation:
16
16
  title: string;
17
17
  icon: string;
18
18
  kind: string;
19
- }, isStillAutomatic: () => boolean, onNamed?: (editorLabel: string) => void): void;
19
+ }, isStillAutomatic: () => boolean,
20
+ /** The turn cause the caller took with `conversation`, before any await; null names none. */
21
+ causedBy: string | null, onNamed?: (editorLabel: string) => void): void;
@@ -1 +1 @@
1
- var f=Object.defineProperty;var r=(e,t)=>f(e,"name",{value:t,configurable:!0});import{localClient as l}from"../api/node-transport.js";import{brokerExecutionId as s,brokerExtensionState as m,commitBrokerGeneratedName as c}from"../core/runtime/broker/daemon-ops.js";import{publishNodeNamed as u}from"../core/runtime/broker/node-named.js";import{headlessName as b,headlessNameFromConversation as h,sanitizeSessionName as x,sanitizeSessionTitle as N,slugFromPrompt as L,titleFromPrompt as k}from"../core/runtime/naming.js";async function G(e,t){const n=t.trim();return n===""?!1:(await l({autostart:!1}).captureBrokerGoal(e,{text:n,expected_execution_id:s()})).captured}r(G,"captureGoalIfAbsent");async function S(e){return(await m(e)).roadmap}r(S,"readRoadmap");function p(e,t){return{description:x(t.name)||L(e),title:N(t.title)||k(e),icon:t.icon}}r(p,"generatedLabel");function g(e,t,n){const o=t.trim();o!==""&&m(e).then(i=>{if((i.node.description??"").trim()==="")return b(o).then(a=>c(e,{kind:"initial",...p(o,a)}))}).then(i=>{!i?.applied||i.editorLabel===void 0||(u({description:i.description??"",title:i.title??"",icon:i.icon??"",editorLabel:i.editorLabel}),n?.(i.editorLabel))}).catch(()=>{})}r(g,"generateAndCommitInitialName");function w(e,t,n,o,i){const a=t.trim();a!==""&&h(a).then(d=>{if(o())return c(e,{kind:"recap",...p(a,d),expected:n})}).then(d=>{d?.applied&&d.editorLabel!==void 0&&i?.(d.editorLabel)}).catch(()=>{})}r(w,"generateAndCommitRecapName");export{G as captureGoalIfAbsent,g as generateAndCommitInitialName,w as generateAndCommitRecapName,S as readRoadmap};
1
+ var f=Object.defineProperty;var o=(e,t)=>f(e,"name",{value:t,configurable:!0});import{localClient as l}from"../api/node-transport.js";import{brokerExecutionId as s,brokerExtensionState as m,commitBrokerGeneratedName as p}from"../core/runtime/broker/daemon-ops.js";import{publishNodeNamed as b}from"../core/runtime/broker/node-named.js";import{currentOrPendingCausedBy as h}from"../core/runtime/broker/turn-cause.js";import{headlessName as x,headlessNameFromConversation as N,sanitizeSessionName as y,sanitizeSessionTitle as L,slugFromPrompt as k,titleFromPrompt as C}from"../core/runtime/naming.js";async function P(e,t){const n=t.trim();return n===""?!1:(await l({autostart:!1}).captureBrokerGoal(e,{text:n,expected_execution_id:s()})).captured}o(P,"captureGoalIfAbsent");async function S(e){return(await m(e)).roadmap}o(S,"readRoadmap");function u(e,t){return{description:y(t.name)||k(e),title:L(t.title)||C(e),icon:t.icon}}o(u,"generatedLabel");function w(e,t,n){const r=t.trim();if(r==="")return;const c=h()??null;m(e).then(i=>{if((i.node.description??"").trim()==="")return x(r,void 0,c).then(a=>p(e,{kind:"initial",...u(r,a)}))}).then(i=>{!i?.applied||i.editorLabel===void 0||(b({description:i.description??"",title:i.title??"",icon:i.icon??"",editorLabel:i.editorLabel}),n?.(i.editorLabel))}).catch(()=>{})}o(w,"generateAndCommitInitialName");function z(e,t,n,r,c,i){const a=t.trim();a!==""&&N(a,c).then(d=>{if(r())return p(e,{kind:"recap",...u(a,d),expected:n})}).then(d=>{d?.applied&&d.editorLabel!==void 0&&i?.(d.editorLabel)}).catch(()=>{})}o(z,"generateAndCommitRecapName");export{P as captureGoalIfAbsent,w as generateAndCommitInitialName,z as generateAndCommitRecapName,S as readRoadmap};
@@ -1,11 +1,11 @@
1
- var B=Object.defineProperty;var a=(n,s)=>B(n,"name",{value:s,configurable:!0});import{envNodeId as O,envRecapIdleMs as U}from"../shared/env.js";import{brokerExtensionState as $}from"../core/runtime/broker/daemon-ops.js";import{generateRecap as V}from"../core/runtime/recap.js";import{putBrokerRecap as q}from"../core/runtime/broker/daemon-ops.js";import{isReviewBoundary as F}from"../core/runtime/session-visibility.js";import{editorLabelForBrokerNode as H}from"../core/runtime/broker-extension-render.js";import{generateAndCommitRecapName as K,readRoadmap as Y}from"./broker-local.js";function z(){return U()}a(z,"idleMs");const J=7e3,b=5,Q=1200,C="crtr-recap";let A;function X(n){const s=n.content;return typeof s=="string"?s.trim():Array.isArray(s)?s.filter(r=>r!==null&&typeof r=="object"&&r.type==="text"&&typeof r.text=="string").map(r=>(r.text??"").trim()).filter(r=>r!=="").join(`
2
- `).trim():""}a(X,"messageText");function Z(n,s){let r;try{r=n.getEntries()}catch{return{entries:[],boundaryPresent:s===void 0}}if(s===void 0)return{entries:r,boundaryPresent:!0};const t=r.findIndex(o=>F(o,s));return t===-1?{entries:[],boundaryPresent:!1}:{entries:r.slice(t+1),boundaryPresent:!0}}a(Z,"recapEntries");function D(n,s,r,t){const{entries:o,boundaryPresent:u}=Z(n,t),l=[],m=a(d=>{const p=d.role;if(p!=="user"&&p!=="assistant")return;let f=X(d);if(f==="")return;f.length>s&&(f=`${f.slice(0,s)} \u2026[truncated]`);const g=`${p==="user"?"User":"Agent"}: ${f}`;l.at(-1)!==g&&l.push(g)},"append");for(const d of o)d.type!=="message"||d.message===void 0||m(d.message);return u&&r?.role==="assistant"&&m(r),l}a(D,"conversationParts");function ee(n,s,r){const t=D(n,Q,s,r);if(t.length===0)return"";if(t.length<=b*2)return t.join(`
1
+ var U=Object.defineProperty;var c=(t,o)=>U(t,"name",{value:o,configurable:!0});import{envNodeId as V,envRecapIdleMs as q}from"../shared/env.js";import{brokerExtensionState as $}from"../core/runtime/broker/daemon-ops.js";import{generateRecap as F}from"../core/runtime/recap.js";import{currentOrPendingCausedBy as D}from"../core/runtime/broker/turn-cause.js";import{putBrokerRecap as H}from"../core/runtime/broker/daemon-ops.js";import{isReviewBoundary as K}from"../core/runtime/session-visibility.js";import{editorLabelForBrokerNode as Y}from"../core/runtime/broker-extension-render.js";import{generateAndCommitRecapName as z,readRoadmap as J}from"./broker-local.js";function Q(){return q()}c(Q,"idleMs");const X=7e3,b=5,Z=1200,j="crtr-recap";let M;function ee(t){const o=t.content;return typeof o=="string"?o.trim():Array.isArray(o)?o.filter(r=>r!==null&&typeof r=="object"&&r.type==="text"&&typeof r.text=="string").map(r=>(r.text??"").trim()).filter(r=>r!=="").join(`
2
+ `).trim():""}c(ee,"messageText");function te(t,o){let r;try{r=t.getEntries()}catch{return{entries:[],boundaryPresent:o===void 0}}if(o===void 0)return{entries:r,boundaryPresent:!0};const e=r.findIndex(a=>K(a,o));return e===-1?{entries:[],boundaryPresent:!1}:{entries:r.slice(e+1),boundaryPresent:!0}}c(te,"recapEntries");function B(t,o,r,e){const{entries:a,boundaryPresent:u}=te(t,e),l=[],m=c(d=>{const p=d.role;if(p!=="user"&&p!=="assistant")return;let f=ee(d);if(f==="")return;f.length>o&&(f=`${f.slice(0,o)} \u2026[truncated]`);const g=`${p==="user"?"User":"Agent"}: ${f}`;l.at(-1)!==g&&l.push(g)},"append");for(const d of a)d.type!=="message"||d.message===void 0||m(d.message);return u&&r?.role==="assistant"&&m(r),l}c(B,"conversationParts");function ne(t,o,r){const e=B(t,Z,o,r);if(e.length===0)return"";if(e.length<=b*2)return e.join(`
3
3
 
4
- `);const o=t.length-b*2;return[...t.slice(0,b),`[\u2026 ${o} message${o===1?"":"s"} omitted \u2026]`,...t.slice(t.length-b)].join(`
4
+ `);const a=e.length-b*2;return[...e.slice(0,b),`[\u2026 ${a} message${a===1?"":"s"} omitted \u2026]`,...e.slice(e.length-b)].join(`
5
5
 
6
- `)}a(ee,"buildConversation");function te(n,s,r){const t=D(n,600,s,r);if(t.length===0)return"";const o=48;return t.length<=o?t.join(`
6
+ `)}c(ne,"buildConversation");function re(t,o,r){const e=B(t,600,o,r);if(e.length===0)return"";const a=48;return e.length<=a?e.join(`
7
7
 
8
- `):Array.from({length:o},(l,m)=>{const d=Math.round(m*(t.length-1)/(o-1));return t[d]}).join(`
8
+ `):Array.from({length:a},(l,m)=>{const d=Math.round(m*(e.length-1)/(a-1));return e[d]}).join(`
9
9
 
10
- `)}a(te,"buildNamingConversation");function ne(n,s=V,r){const t=O();if(t===void 0||t.trim()==="")return;let o,u,l,m;const d=a(i=>{const e=i.model?.provider?.trim(),c=i.model?.id?.trim();e&&c&&(m=`${e}/${c}`)},"captureLiveModel");let p=Date.now(),f=0,g,y=!1,_=!1,S=!1,h=!1,I;const v=a(()=>o!==void 0&&typeof o.setWidget=="function","canPaint"),w=a(i=>{try{o?.notify?.(i,"info")}catch{}},"notify"),x=a(()=>{if(v())try{o.setWidget(C,void 0,{placement:"aboveEditor"})}catch{}},"clearRecap"),j=a(i=>{if(!v()||i==="")return;const e=i.split(`
11
- `).filter(c=>c.trim()!=="");if(e.length!==0)try{o.setWidget(C,e,{placement:"aboveEditor"})}catch{}},"showRecap"),P=a(()=>{p=Date.now(),f+=1,S=!1,y&&(_=!0)},"onMessage"),T=a(i=>{if(!(n.getSessionName===void 0||n.setSessionName===void 0)){if(r!==void 0){const e=n.getSessionName();if(e===void 0)return;r(t,i,()=>n.getSessionName?.()===e,c=>{try{n.setSessionName?.(c)}catch{}});return}$(t).then(({node:e})=>{if(e.name.trim()!==""&&e.name!==e.kind)return;const c=H(e);n.getSessionName?.()===c&&K(t,i,{name:e.name,description:e.description??"",title:e.title??"",icon:e.icon??"",kind:e.kind},()=>n.getSessionName?.()===c,M=>{try{n.setSessionName?.(M)}catch{}})}).catch(()=>{})}},"refreshAutomaticName"),k=a(i=>{if(!v()||u===void 0||h)return;const e=g,c=l?.review_binding?.review_id??u.getEntries().map(N=>N.details?.reviewId).find(N=>typeof N=="string"),M=ee(u,e,c);if(M===""){S=!0,y=!1,_=!1,x();return}S=!1;const L=i?te(u,e,c):"",R=f;i||(I=R),h=!0,i&&(w("Generating recap\u2026"),T(L)),Y(t).catch(()=>null).then(N=>s(M,G=>{!i&&f!==R||!v()||q(t,G).then(()=>{!i&&f!==R||!v()||(y=!0,_=f!==R,j(G),i&&w("Recap ready"))}).catch(W=>w(`Recap could not be saved: ${String(W)}`)).finally(()=>{h=!1})},m,N??void 0)),setTimeout(()=>{h=!1},3e4).unref?.()},"requestRecap");n.registerCommand?.("recap",{description:"Generate a Goal/State recap and refresh the original generated session title",handler:a((i,e)=>{o=e.ui,u=e.sessionManager,d(e),k(!0)},"handler")}),n.on("session_start",(i,e)=>{o=e.ui,u=e.sessionManager,d(e),l=void 0,$(t).then(c=>{l=c.node}).catch(()=>{}),p=Date.now(),f=0,g=void 0,y=!1,_=!1,S=!1,h=!1,I=void 0,x()}),n.on("input",(i,e)=>{P()}),n.on("message_update",(i,e)=>{const c=i?.message;c?.role==="assistant"&&(g=c),P()}),n.on("turn_end",(i,e)=>{g=void 0,d(e),P()}),A!==void 0&&clearInterval(A);const E=setInterval(()=>{try{if(!v()||u===void 0||S||y&&!_||h||I===f||Date.now()-p<z())return;k(!1)}catch{}},J);typeof E.unref=="function"&&E.unref(),A=E,n.on("session_shutdown",()=>{clearInterval(E),A===E&&(A=void 0)})}a(ne,"registerCanvasRecap");var ue=ne;export{ue as default,ne as registerCanvasRecap};
10
+ `)}c(re,"buildNamingConversation");function ie(t,o=F,r){const e=V();if(e===void 0||e.trim()==="")return;let a,u,l,m;const d=c(i=>{const s=i.model?.provider?.trim(),n=i.model?.id?.trim();s&&n&&(m=`${s}/${n}`)},"captureLiveModel");let p=Date.now(),f=0,g,_=!1,S=!1,E=!1,y=!1,I;const v=c(()=>a!==void 0&&typeof a.setWidget=="function","canPaint"),P=c(i=>{try{a?.notify?.(i,"info")}catch{}},"notify"),k=c(()=>{if(v())try{a.setWidget(j,void 0,{placement:"aboveEditor"})}catch{}},"clearRecap"),T=c(i=>{if(!v()||i==="")return;const s=i.split(`
11
+ `).filter(n=>n.trim()!=="");if(s.length!==0)try{a.setWidget(j,s,{placement:"aboveEditor"})}catch{}},"showRecap"),w=c(()=>{p=Date.now(),f+=1,E=!1,_&&(S=!0)},"onMessage"),L=c(i=>{if(t.getSessionName===void 0||t.setSessionName===void 0)return;if(r!==void 0){const n=t.getSessionName();if(n===void 0)return;r(e,i,()=>t.getSessionName?.()===n,h=>{try{t.setSessionName?.(h)}catch{}});return}const s=D()??null;$(e).then(({node:n})=>{if(n.name.trim()!==""&&n.name!==n.kind)return;const h=Y(n);t.getSessionName?.()===h&&z(e,i,{name:n.name,description:n.description??"",title:n.title??"",icon:n.icon??"",kind:n.kind},()=>t.getSessionName?.()===h,s,x=>{try{t.setSessionName?.(x)}catch{}})}).catch(()=>{})},"refreshAutomaticName"),C=c(i=>{if(!v()||u===void 0||y)return;const s=g,n=l?.review_binding?.review_id??u.getEntries().map(A=>A.details?.reviewId).find(A=>typeof A=="string"),h=ne(u,s,n);if(h===""){E=!0,_=!1,S=!1,k();return}E=!1;const x=i?re(u,s,n):"",R=f;i||(I=R),y=!0,i&&(P("Generating recap\u2026"),L(x));const O=D()??null;J(e).catch(()=>null).then(A=>o(h,G=>{!i&&f!==R||!v()||H(e,G).then(()=>{!i&&f!==R||!v()||(_=!0,S=f!==R,T(G),i&&P("Recap ready"))}).catch(W=>P(`Recap could not be saved: ${String(W)}`)).finally(()=>{y=!1})},m,A??void 0,O)),setTimeout(()=>{y=!1},3e4).unref?.()},"requestRecap");t.registerCommand?.("recap",{description:"Generate a Goal/State recap and refresh the original generated session title",handler:c((i,s)=>{a=s.ui,u=s.sessionManager,d(s),C(!0)},"handler")}),t.on("session_start",(i,s)=>{a=s.ui,u=s.sessionManager,d(s),l=void 0,$(e).then(n=>{l=n.node}).catch(()=>{}),p=Date.now(),f=0,g=void 0,_=!1,S=!1,E=!1,y=!1,I=void 0,k()}),t.on("input",(i,s)=>{w()}),t.on("message_update",(i,s)=>{const n=i?.message;n?.role==="assistant"&&(g=n),w()}),t.on("turn_end",(i,s)=>{g=void 0,d(s),w()}),M!==void 0&&clearInterval(M);const N=setInterval(()=>{try{if(!v()||u===void 0||E||_&&!S||y||I===f||Date.now()-p<Q())return;C(!1)}catch{}},X);typeof N.unref=="function"&&N.unref(),M=N,t.on("session_shutdown",()=>{clearInterval(N),M===N&&(M=void 0)})}c(ie,"registerCanvasRecap");var pe=ie;export{pe as default,ie as registerCanvasRecap};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.593",
3
+ "version": "0.3.595",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -145,7 +145,7 @@
145
145
  },
146
146
  "license": "UNLICENSED",
147
147
  "dependencies": {
148
- "@north-light/identity": "0.3.593",
148
+ "@north-light/identity": "0.3.595",
149
149
  "@earendil-works/chord": "1.1.0",
150
150
  "@earendil-works/pi-agent-core": "1.1.0",
151
151
  "@earendil-works/pi-ai": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.593",
3
+ "version": "0.3.595",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.593",
3
+ "version": "0.3.595",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.593",
9
+ "version": "0.3.595",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -28,7 +28,7 @@
28
28
  "@milkdown/preset-gfm": "^7.22.3",
29
29
  "@milkdown/prose": "^7.22.3",
30
30
  "@milkdown/utils": "^7.22.3",
31
- "@north-light/identity": "0.3.593",
31
+ "@north-light/identity": "0.3.595",
32
32
  "cron-parser": "^5.6.0",
33
33
  "esbuild": "^0.27.7",
34
34
  "jose": "^6.2.12",
@@ -11956,17 +11956,17 @@
11956
11956
  },
11957
11957
  "packages/crouter-api": {
11958
11958
  "name": "@north-light/api",
11959
- "version": "0.3.593",
11959
+ "version": "0.3.595",
11960
11960
  "license": "UNLICENSED"
11961
11961
  },
11962
11962
  "packages/crouter-env-docker": {
11963
11963
  "name": "@north-light/env-docker",
11964
- "version": "0.3.593",
11964
+ "version": "0.3.595",
11965
11965
  "license": "UNLICENSED"
11966
11966
  },
11967
11967
  "packages/crouter-identity": {
11968
11968
  "name": "@north-light/identity",
11969
- "version": "0.3.593",
11969
+ "version": "0.3.595",
11970
11970
  "license": "UNLICENSED",
11971
11971
  "dependencies": {
11972
11972
  "jose": "^6.2.1"
@@ -11974,21 +11974,21 @@
11974
11974
  },
11975
11975
  "packages/crouter-plugin": {
11976
11976
  "name": "@north-light/plugin",
11977
- "version": "0.3.593",
11977
+ "version": "0.3.595",
11978
11978
  "license": "UNLICENSED",
11979
11979
  "dependencies": {
11980
- "@north-light/api": "0.3.593",
11981
- "@north-light/identity": "0.3.593"
11980
+ "@north-light/api": "0.3.595",
11981
+ "@north-light/identity": "0.3.595"
11982
11982
  }
11983
11983
  },
11984
11984
  "packages/crouter-sdk": {
11985
11985
  "name": "@north-light/sdk",
11986
- "version": "0.3.593",
11986
+ "version": "0.3.595",
11987
11987
  "license": "UNLICENSED",
11988
11988
  "dependencies": {
11989
- "@north-light/api": "0.3.593",
11990
- "@north-light/identity": "0.3.593",
11991
- "@north-light/plugin": "0.3.593",
11989
+ "@north-light/api": "0.3.595",
11990
+ "@north-light/identity": "0.3.595",
11991
+ "@north-light/plugin": "0.3.595",
11992
11992
  "jose": "^6.2.1"
11993
11993
  },
11994
11994
  "bin": {
@@ -5,8 +5,11 @@
5
5
  # CRTR_RELEASE_URL, CRTR_RELEASE_SHA256 and the rest the runner set. Once per sandbox it creates the `crtr` user
6
6
  # (`crtrd` refuses root) and its directories; it installs the release if it is absent (download, sha256 check,
7
7
  # unpack into a temp dir, rename into place, keep three), then hands over to `crtr-sandbox-start` as `crtr`.
8
- # The node token reaches the start script on stdin, never in its environment, so no process of user `crtr` can
9
- # read it from /proc. A failed download or checksum exits non-zero and prints `crtr-sandbox-start: failed <reason>`
8
+ # The node token reaches the start script on stdin, never in its environment, so it is in no process's
9
+ # /proc/<pid>/environ. It is not hidden from user `crtr` otherwise: the broker holds it in memory, and the agent's
10
+ # shell runs as the same user (design-fleet D13/D78 scope the token to this execution's node, nothing more). The final `exec` chain forks nothing (busybox `nsenter -S/-G` execs, and the token goes in as a here-document, not a pipe, which would
11
+ # put a forking shell in front of it), so the process Blaxel tracks as `broker-<execution id>` is the start script itself: stopping it reaches
12
+ # the node and the broker, and its exit means the broker is gone. A failed download or checksum exits non-zero and prints `crtr-sandbox-start: failed <reason>`
10
13
  # as its last line (the runner's `status` reports that reason).
11
14
  set -eu
12
15
 
@@ -51,5 +54,8 @@ done
51
54
 
52
55
  token=$CRTR_NODE_TOKEN
53
56
  unset CRTR_NODE_TOKEN
54
- # `su -p` keeps the rest of the environment; the start script derives HOME and the layout itself.
55
- printf '%s' "$token" | exec su -p -s /bin/sh "$RUN_USER" -c "exec node '$RELEASE_DIR/scripts/sandbox-start.mjs'"
57
+ # busybox `su` forks, which leaves the tracked process a wrapper whose children outlive a stop; `nsenter` with no namespace flag only sets the
58
+ # uid and gid, then execs. It keeps the environment (as `su -p` did); the start script derives HOME and the layout itself.
59
+ exec nsenter -S "$(id -u "$RUN_USER")" -G "$(id -g "$RUN_USER")" node "$RELEASE_DIR/scripts/sandbox-start.mjs" <<EOT
60
+ $token
61
+ EOT
@@ -344,34 +344,71 @@ async function start(env) {
344
344
  gitConfig('credential.https://github.com.helper', join(releaseDir, 'scripts', 'git-credential-crtr'));
345
345
  if (bundle.git_identity) { gitConfig('user.name', bundle.git_identity.name); gitConfig('user.email', bundle.git_identity.email); }
346
346
 
347
- // 5.5 plugins, once per revision.
347
+ // 5.5 plugins, once per revision. The person's install rows are already authoritative on the control plane, and no broker or daemon exists yet, so
348
+ // this materializes each plugin's code the way `pkg plugin install` lays it out in the user scope (`<runtime>/marketplaces/<name>`, `<runtime>/plugins/<plugin>`)
349
+ // with git alone; the release CLI would load the person's settings from a daemon this start cannot reach.
348
350
  const marker = join(work, '.crtr', 'plugins-revision');
349
351
  const have = existsSync(marker) ? readFileSync(marker, 'utf8').trim() : null;
350
- if (have !== String(bundle.plugins_revision) && bundle.plugins.length > 0) {
351
- const crtr = join(releaseDir, 'bin', 'crtr');
352
- const pluginEnv = { ...gitEnv, NPM_CONFIG_PREFIX: join(home, '.npm-global'), CRTR_HOME: root, CRTR_NO_AUTO_INIT: '1' };
353
- const crtrRun = (args) => run(process.execPath, [crtr, ...args], { env: pluginEnv, timeout: 300_000 });
352
+ if (have !== String(bundle.plugins_revision)) {
353
+ const pluginsDir = join(root, 'runtime', 'plugins');
354
+ const marketplacesDir = join(root, 'runtime', 'marketplaces');
355
+ const cloneEnv = { ...gitEnv, GIT_TERMINAL_PROMPT: '0' };
354
356
  const detail = (r) => (r.stderr || r.stdout || '').slice(0, 300);
355
- // A plugin of a marketplace is installed as `<marketplace name>/<plugin>`; the bundle names only the marketplace's url and ref, so the name is what
356
- // `pkg market add` reports. Adding re-clones, so a marketplace added once per start is current. A plugin already installed (an earlier revision) is
357
- // removed first, since install refuses an installed name.
357
+ const cloneInto = (url, ref, dest) => {
358
+ const r = run('git', ['clone', '--depth', '1', ...(ref ? ['--branch', ref] : []), url, dest], { env: cloneEnv, timeout: 300_000 });
359
+ return r.status === 0 ? null : detail(r) || `git clone exited ${r.status}`;
360
+ };
361
+ // The directory holds exactly the bundle's plugins: one an earlier revision installed and this one no longer lists is removed.
362
+ let current = null;
363
+ try {
364
+ const wanted = new Set(bundle.plugins.map((plugin) => plugin.name));
365
+ if (existsSync(pluginsDir)) for (const name of readdirSync(pluginsDir)) if (!wanted.has(name)) rmSync(join(pluginsDir, name), { recursive: true, force: true });
366
+ const readJson = (path) => { try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; } };
367
+ // A plugin of a marketplace is installed as `<plugin>` from the marketplace the bundle's url and ref name. Cloning again each start keeps a marketplace
368
+ // current; a plugin installed by an earlier revision is replaced.
358
369
  const marketplaces = new Map();
359
370
  for (const plugin of bundle.plugins) {
371
+ current = plugin.name;
360
372
  if ('path' in plugin.source) throw new StartFailure('plugin_source_unreachable', `${plugin.name}: its source is ${plugin.source.path} on the machine that registered it, which a sandbox cannot read`);
361
373
  const { url, ref } = plugin.source.marketplace;
362
374
  if (!url) throw new StartFailure('plugin_source_unreachable', `${plugin.name}: its marketplace has no url`);
375
+ const fail = (message) => new StartFailure('plugin_install_failed', `${plugin.name}: ${message}`);
376
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(plugin.name)) throw fail('invalid plugin name');
363
377
  const key = `${url}\0${ref ?? ''}`;
364
378
  if (!marketplaces.has(key)) {
365
- const added = crtrRun(['--json', 'pkg', 'market', 'add', url, ...(ref ? ['--ref', ref] : []), '--scope', 'user']);
366
- let name;
367
- try { name = JSON.parse(added.stdout).name; } catch { /* reported below */ }
368
- if (added.status !== 0 || typeof name !== 'string') throw new StartFailure('plugin_install_failed', `${plugin.name}: marketplace ${url}: ${detail(added)}`);
369
- marketplaces.set(key, name);
379
+ mkdirSync(marketplacesDir, { recursive: true });
380
+ const staging = join(marketplacesDir, `.clone-${process.pid}-${marketplaces.size}`);
381
+ rmSync(staging, { recursive: true, force: true });
382
+ const cloneError = cloneInto(url, ref, staging);
383
+ if (cloneError !== null) throw fail(`marketplace ${url}: ${cloneError}`);
384
+ const manifest = readJson(join(staging, '.crouter-marketplace', 'marketplace.json'));
385
+ if (typeof manifest?.name !== 'string' || !Array.isArray(manifest.plugins)) { rmSync(staging, { recursive: true, force: true }); throw fail(`marketplace ${url}: not a valid marketplace`); }
386
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(manifest.name)) { rmSync(staging, { recursive: true, force: true }); throw fail(`marketplace ${url}: invalid marketplace name ${JSON.stringify(manifest.name)}`); }
387
+ const dest = join(marketplacesDir, manifest.name);
388
+ rmSync(dest, { recursive: true, force: true });
389
+ renameSync(staging, dest);
390
+ marketplaces.set(key, { dest, manifest });
370
391
  }
371
- const removed = crtrRun(['pkg', 'plugin', 'remove', plugin.name, '--scope', 'user']);
372
- if (removed.status !== 0 && !/not found/i.test(removed.stderr + removed.stdout)) throw new StartFailure('plugin_install_failed', `${plugin.name}: remove: ${detail(removed)}`);
373
- const installed = crtrRun(['pkg', 'plugin', 'install', `${marketplaces.get(key)}/${plugin.name}`, '--scope', 'user']);
374
- if (installed.status !== 0) throw new StartFailure('plugin_install_failed', `${plugin.name}: ${detail(installed)}`);
392
+ const { dest: marketplaceDir, manifest } = marketplaces.get(key);
393
+ const entry = manifest.plugins.find((candidate) => candidate?.name === plugin.name);
394
+ if (!entry || typeof entry.source !== 'string') throw fail(`not found in marketplace ${manifest.name}`);
395
+ const target = join(pluginsDir, plugin.name);
396
+ mkdirSync(pluginsDir, { recursive: true });
397
+ rmSync(target, { recursive: true, force: true });
398
+ if (/^(https?:\/\/|git@|ssh:\/\/|file:\/\/)/.test(entry.source) || entry.source.endsWith('.git')) {
399
+ const cloneError = cloneInto(entry.source, undefined, target);
400
+ if (cloneError !== null) throw fail(cloneError);
401
+ } else {
402
+ const source = resolve(marketplaceDir, entry.source);
403
+ if (!existsSync(source) || !within(realpathSync(marketplaceDir), realpathSync(source))) throw fail(`plugin source ${entry.source} is not in marketplace ${manifest.name}`);
404
+ symlinkSync(source, target, 'dir');
405
+ }
406
+ if (readJson(join(target, '.crouter-plugin', 'plugin.json')) === null) { rmSync(target, { recursive: true, force: true }); throw fail('its .crouter-plugin/plugin.json is missing or invalid'); }
407
+ }
408
+ } catch (error) {
409
+ // A filesystem error (mkdir, remove, rename, symlink) is an install failure of the plugin being installed, not a generic start failure.
410
+ if (error instanceof StartFailure) throw error;
411
+ throw new StartFailure('plugin_install_failed', `${current ?? 'plugins'}: ${error.message}`.slice(0, 300));
375
412
  }
376
413
  }
377
414
  if (have !== String(bundle.plugins_revision)) { mkdirSync(dirname(marker), { recursive: true }); writeFileSync(marker, String(bundle.plugins_revision)); }