@north-light/cli 0.3.535 → 0.3.536

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/api/client.d.ts +3 -0
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/recovery.d.ts +2 -1
  6. package/dist/clients/attach/viewer.js +406 -406
  7. package/dist/commands/api-client.js +3 -3
  8. package/dist/core/canvas/canvas.d.ts +10 -0
  9. package/dist/core/canvas/canvas.js +20 -20
  10. package/dist/core/canvas/crons.d.ts +4 -2
  11. package/dist/core/canvas/crons.js +1 -1
  12. package/dist/core/canvas/paths.d.ts +5 -0
  13. package/dist/core/canvas/paths.js +1 -1
  14. package/dist/core/canvas/recovery.d.ts +7 -0
  15. package/dist/core/canvas/recovery.js +2 -2
  16. package/dist/core/feed/feed.js +3 -3
  17. package/dist/core/feed/messages.d.ts +6 -0
  18. package/dist/core/feed/messages.js +5 -5
  19. package/dist/core/graph/kinds/node.js +5 -5
  20. package/dist/core/providers/mint.d.ts +2 -0
  21. package/dist/core/providers/mint.js +1 -1
  22. package/dist/core/runtime/broker/cli-relay.js +1 -1
  23. package/dist/core/runtime/broker/fault-retry.d.ts +12 -0
  24. package/dist/core/runtime/broker/fault-retry.js +1 -1
  25. package/dist/core/runtime/broker/run-turn-end.d.ts +3 -2
  26. package/dist/core/runtime/broker/run-turn-end.js +1 -1
  27. package/dist/core/runtime/lifecycle.d.ts +4 -0
  28. package/dist/core/runtime/lifecycle.js +2 -2
  29. package/dist/core/runtime/nodes.js +1 -1
  30. package/dist/core/runtime/revive.d.ts +1 -1
  31. package/dist/core/runtime/revive.js +1 -1
  32. package/dist/core/worktree-mutation-async.d.ts +10 -0
  33. package/dist/core/worktree-mutation-async.js +5 -5
  34. package/dist/core/worktree-sweep.js +6 -6
  35. package/dist/core/worktree.d.ts +1 -0
  36. package/dist/core/worktree.js +1 -1
  37. package/dist/daemon/api/handlers/broker-recovery.js +1 -1
  38. package/dist/daemon/api/handlers/messages.js +2 -2
  39. package/dist/daemon/messaging/node-message.js +1 -1
  40. package/dist/daemon/peer/forward.js +1 -1
  41. package/package.json +2 -2
  42. package/packages/crouter-identity/package.json +1 -1
  43. package/runtime.lock.json +12 -12
@@ -1,8 +1,8 @@
1
- var p=Object.defineProperty;var s=(r,e)=>p(r,"name",{value:e,configurable:!0});import{existsSync as _,mkdirSync as E,renameSync as N,writeFileSync as I}from"node:fs";import{randomBytes as v,randomUUID as b}from"node:crypto";import{join as k}from"node:path";import{cancelCronsOnWake as u}from"../canvas/crons.js";import{withCanvasWrite as c,openDb as h}from"../canvas/db.js";import{getRow as S}from"../canvas/canvas.js";import{messagesDir as R}from"../canvas/paths.js";import{syncDirectory as D,syncFile as T}from"../fs-utils.js";import{settleDeadControllerWait as A,settleMatchingControllerWait as O}from"../canvas/waits.js";import{generateOperationId as x,operationIdContext as L,validateOperationId as C}from"../events/operation-id.js";import{clipBody as J}from"../../shared/inbox-entry-body.js";function y(){let r;do r=v(16).toString("hex");while(r==="00000000000000000000000000000000");return r}s(y,"newId");function M(r,e){const t=JSON.stringify(r);if(t===void 0)throw new TypeError(`${e} must be JSON-serializable`);return t}s(M,"stringify");async function U(r,e){return e!=="normal"&&e!=="deferred"?e:(await S(r))?.lifecycle==="terminal"?"urgent":e}s(U,"effectiveTier");function W(r,e,t){const n=R(r);_(n)||E(n,{recursive:!0});const a=e.replace(/[^0-9]/g,""),d=k(n,`${a}-${b().slice(0,6)}.md`),i=`${d}.tmp`;return I(i,t,"utf8"),T(i),N(i,d),D(n),d}s(W,"writeMessageBody");function q(r){if(typeof r!="object"||r===null)return"";const e=r.body;return typeof e=="string"?e.trim():""}s(q,"inlineBody");async function m(r,e){const t=e.messageId??y(),n=Number((await r.prepare("UPDATE message_sequence SET seq = seq + 1 WHERE id = 1 RETURNING seq").get()).seq);return await r.prepare(`INSERT INTO messages
1
+ var _=Object.defineProperty;var o=(r,e)=>_(r,"name",{value:e,configurable:!0});import{existsSync as N,mkdirSync as E,renameSync as I,writeFileSync as b}from"node:fs";import{randomBytes as v,randomUUID as k}from"node:crypto";import{join as h}from"node:path";import{cancelCronsOnWake as R}from"../canvas/crons.js";import{withCanvasWrite as l,openDb as S}from"../canvas/db.js";import{getRow as D}from"../canvas/canvas.js";import{messagesDir as T}from"../canvas/paths.js";import{syncDirectory as A,syncFile as O}from"../fs-utils.js";import{settleDeadControllerWait as x,settleMatchingControllerWait as L}from"../canvas/waits.js";import{generateOperationId as C,operationIdContext as J,validateOperationId as M}from"../events/operation-id.js";import{clipBody as U}from"../../shared/inbox-entry-body.js";function u(){let r;do r=v(16).toString("hex");while(r==="00000000000000000000000000000000");return r}o(u,"newId");function W(r,e){const t=JSON.stringify(r);if(t===void 0)throw new TypeError(`${e} must be JSON-serializable`);return t}o(W,"stringify");async function q(r,e){return e!=="normal"&&e!=="deferred"?e:(await D(r))?.lifecycle==="terminal"?"urgent":e}o(q,"effectiveTier");function $(r,e,t){const n=T(r);N(n)||E(n,{recursive:!0});const a=e.replace(/[^0-9]/g,""),d=h(n,`${a}-${k().slice(0,6)}.md`),i=`${d}.tmp`;return b(i,t,"utf8"),O(i),I(i,d),A(n),d}o($,"writeMessageBody");function F(r){if(typeof r!="object"||r===null)return"";const e=r.body;return typeof e=="string"?e.trim():""}o(F,"inlineBody");async function y(r,e){const t=e.messageId??u(),n=Number((await r.prepare("UPDATE message_sequence SET seq = seq + 1 WHERE id = 1 RETURNING seq").get()).seq);return await r.prepare(`INSERT INTO messages
2
2
  (message_id, seq, operation_id, created_at, sender_node_id, sender_name, kind, label, body_ref, body_json, disposition,
3
3
  event, object_id, level, actor_id, cause_message_id, depth)
4
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(t,n,e.operationId,e.createdAt,e.senderNodeId,e.senderName,e.kind,e.label,e.bodyRef,e.bodyJson===void 0?null:M(e.bodyJson,"message body"),e.disposition,e.event?.event??null,e.event?.objectId??null,e.event?.level??null,e.event?.actorId??null,e.causeMessageId??null,e.depth??0),t}s(m,"insertMessage");async function f(r,e,t,n,a,d,i){const o=y();return await r.prepare(`INSERT INTO deliveries
4
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(t,n,e.operationId,e.createdAt,e.senderNodeId,e.senderName,e.kind,e.label,e.bodyRef,e.bodyJson===void 0?null:W(e.bodyJson,"message body"),e.disposition,e.event?.event??null,e.event?.objectId??null,e.event?.level??null,e.event?.actorId??null,e.causeMessageId??null,e.depth??0),t}o(y,"insertMessage");async function m(r,e,t,n,a,d,i){const s=u();return await r.prepare(`INSERT INTO deliveries
5
5
  (delivery_id, message_id, destination_id, urgency, wake, state, idempotency_key, created_at, updated_at)
6
- VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?)`).run(o,e,t,n,a,i??null,d,d),o}s(f,"insertNodeDelivery");function g(r,e){const t=new Date().toISOString(),n=r.operationId===void 0?L.current()??x():C(r.operationId);let a=r.bodyRef??null,d=r.bodyJson;if(a===null&&e!==null&&r.senderNodeId!=="human"){const i=q(d),o=J(i);o.clipped&&(a=W(e,t,i),d={...d,body:o.text})}return{createdAt:t,operationId:n,bodyRef:a,bodyJson:d}}s(g,"finalizeNodeMail");async function F(r,e){const t=g(e,r);return await c(async n=>{const a=await n.prepare("SELECT run_id FROM nodes WHERE node_id = ?").get(r);if(a?.run_id&&(await n.prepare("SELECT deleting_at FROM nodes WHERE node_id = ?").get(a.run_id))?.deleting_at)throw new Error("run is deleting");if(e.idempotencyKey!==void 0){const l=await n.prepare(`SELECT d.delivery_id, d.message_id, d.urgency, m.created_at
7
- FROM deliveries d JOIN messages m ON m.message_id = d.message_id WHERE d.idempotency_key = ?`).get(e.idempotencyKey);if(l!==void 0)return{messageId:l.message_id,deliveryId:l.delivery_id,urgency:l.urgency,createdAt:l.created_at}}const d=Date.now(),i=await U(r,e.urgency),o=await m(n,{messageId:e.messageId,operationId:t.operationId,createdAt:t.createdAt,senderNodeId:e.senderNodeId??null,senderName:e.senderName??null,kind:e.kind,label:e.label,bodyRef:t.bodyRef,bodyJson:t.bodyJson,disposition:e.disposition??null}),w=await f(n,o,r,i,e.wake,d,e.idempotencyKey);return await O(r,e.senderNodeId??null,d,n),await e.withinEnqueue?.(n,o),e.wake===1&&i!=="deferred"&&await u(r),{messageId:o,deliveryId:w,urgency:i,createdAt:t.createdAt}})}s(F,"enqueueNodeMail");function Z(r){switch(r){case"interrupt":return{urgency:"critical",wake:1};case"steer":return{urgency:"urgent",wake:1};case"after-turn":return{urgency:"normal",wake:1};case"next-turn":return{urgency:"deferred",wake:0}}}s(Z,"listenDeliveryTier");async function ee(r,e,t){const n={kind:"update",label:t.label??"notice",bodyJson:{body:t.body},urgency:t.tier,wake:t.wake},a=g(n,e),d=Date.now(),i=await m(r,{operationId:a.operationId,createdAt:a.createdAt,senderNodeId:null,senderName:null,kind:n.kind,label:n.label,bodyRef:a.bodyRef,bodyJson:a.bodyJson,disposition:null}),o=await f(r,i,e,t.tier,t.wake,d);return t.wake===1&&t.tier!=="deferred"&&await u(e),{messageId:i,deliveryId:o,urgency:t.tier,createdAt:a.createdAt}}s(ee,"queueNodeNotice");async function re(r,e=Date.now()){return Number((await(await h()).prepare("UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE delivery_id = ? AND canceled_at IS NULL").run(e,e,r)).changes)===1}s(re,"cancelDelivery");async function te(r,e={}){return await c(async t=>{const n=["d.destination_id = ?","d.canceled_at IS NULL","d.state IN ('pending', 'handed_off')"],a=[r];e.senderNodeId!==void 0&&(n.push("m.sender_node_id IS NOT DISTINCT FROM ?"),a.push(e.senderNodeId)),e.kind!==void 0&&(n.push("m.kind = ?"),a.push(e.kind));const d=await t.prepareUncached(`SELECT d.delivery_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id WHERE ${n.join(" AND ")}`).all(...a);if(d.length===0)return[];const i=Date.now();return await t.prepareUncached(`UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE delivery_id IN (${d.map(()=>"?").join(",")})`).run(i,i,...d.map(o=>o.delivery_id)),d.map(o=>o.delivery_id)})}s(te,"cancelPendingNodeMail");async function ne(r,e){return await c(async t=>{const n=Date.now(),a=await t.prepare(`SELECT target.status AS controller_status FROM waits w LEFT JOIN nodes target ON target.node_id = w.target_id
8
- WHERE w.node_id = ? AND w.kind = 'controller' AND w.target_id = ? AND w.satisfied_at IS NULL`).get(r,e);if(a===void 0||a.controller_status==="active"||a.controller_status==="idle")return null;const d=await F(r,{kind:"urgent",label:"declared controller is no longer live",bodyJson:{body:`Controller ${e} is no longer live; the declared message wait was cleared.`},urgency:"urgent",wake:1});if(!await A(r,e,n,t))throw new Error(`controller wait changed while notifying ${r}`);return d})}s(ne,"enqueueControllerDeathNotice");export{re as cancelDelivery,te as cancelPendingNodeMail,U as effectiveTier,ne as enqueueControllerDeathNotice,F as enqueueNodeMail,g as finalizeNodeMail,m as insertMessage,Z as listenDeliveryTier,y as newId,ee as queueNodeNotice};
6
+ VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?)`).run(s,e,t,n,a,i??null,d,d),s}o(m,"insertNodeDelivery");function f(r,e){const t=new Date().toISOString(),n=r.operationId===void 0?J.current()??C():M(r.operationId);let a=r.bodyRef??null,d=r.bodyJson;if(a===null&&e!==null&&r.senderNodeId!=="human"){const i=F(d),s=U(i);s.clipped&&(a=$(e,t,i),d={...d,body:s.text})}return{createdAt:t,operationId:n,bodyRef:a,bodyJson:d}}o(f,"finalizeNodeMail");async function j(r,e){const t=f(e,r);return await l(async n=>{const a=await n.prepare("SELECT run_id FROM nodes WHERE node_id = ?").get(r);if(a?.run_id&&(await n.prepare("SELECT deleting_at FROM nodes WHERE node_id = ?").get(a.run_id))?.deleting_at)throw new Error("run is deleting");if(e.idempotencyKey!==void 0){const c=await n.prepare(`SELECT d.delivery_id, d.message_id, d.urgency, m.created_at
7
+ FROM deliveries d JOIN messages m ON m.message_id = d.message_id WHERE d.idempotency_key = ?`).get(e.idempotencyKey);if(c!==void 0)return{messageId:c.message_id,deliveryId:c.delivery_id,urgency:c.urgency,createdAt:c.created_at}}const d=Date.now(),i=await q(r,e.urgency),s=await y(n,{messageId:e.messageId,operationId:t.operationId,createdAt:t.createdAt,senderNodeId:e.senderNodeId??null,senderName:e.senderName??null,kind:e.kind,label:e.label,bodyRef:t.bodyRef,bodyJson:t.bodyJson,disposition:e.disposition??null}),p=await m(n,s,r,i,e.wake,d,e.idempotencyKey);return await L(r,e.senderNodeId??null,d,n),await e.withinEnqueue?.(n,s),e.wake===1&&i!=="deferred"&&await g(r),{messageId:s,deliveryId:p,urgency:i,createdAt:t.createdAt}})}o(j,"enqueueNodeMail");function re(r){switch(r){case"interrupt":return{urgency:"critical",wake:1};case"steer":return{urgency:"urgent",wake:1};case"after-turn":return{urgency:"normal",wake:1};case"next-turn":return{urgency:"deferred",wake:0}}}o(re,"listenDeliveryTier");async function w(r,e,t){const n={kind:"update",label:t.label??"notice",bodyJson:{body:t.body},urgency:t.tier,wake:t.wake},a=f(n,e),d=Date.now(),i=await y(r,{operationId:a.operationId,createdAt:a.createdAt,senderNodeId:null,senderName:null,kind:n.kind,label:n.label,bodyRef:a.bodyRef,bodyJson:a.bodyJson,disposition:null}),s=await m(r,i,e,t.tier,t.wake,d);return{messageId:i,deliveryId:s,urgency:t.tier,createdAt:a.createdAt}}o(w,"insertNodeNotice");async function g(r){await l(async e=>{for(const t of await R(r))await w(e,r,{body:`Your cron ${t.name}, next scheduled for ${t.fire_at}, was canceled by this wake. Re-create it if you still need it.`,label:"cron canceled",tier:"normal",wake:1})})}o(g,"consumeDeadlineOnWake");async function te(r,e,t){const n=await w(r,e,t);return t.wake===1&&t.tier!=="deferred"&&await g(e),n}o(te,"queueNodeNotice");async function ne(r,e=Date.now()){return Number((await(await S()).prepare("UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE delivery_id = ? AND canceled_at IS NULL").run(e,e,r)).changes)===1}o(ne,"cancelDelivery");async function ae(r,e={}){return await l(async t=>{const n=["d.destination_id = ?","d.canceled_at IS NULL","d.state IN ('pending', 'handed_off')"],a=[r];e.senderNodeId!==void 0&&(n.push("m.sender_node_id IS NOT DISTINCT FROM ?"),a.push(e.senderNodeId)),e.kind!==void 0&&(n.push("m.kind = ?"),a.push(e.kind));const d=await t.prepareUncached(`SELECT d.delivery_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id WHERE ${n.join(" AND ")}`).all(...a);if(d.length===0)return[];const i=Date.now();return await t.prepareUncached(`UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE delivery_id IN (${d.map(()=>"?").join(",")})`).run(i,i,...d.map(s=>s.delivery_id)),d.map(s=>s.delivery_id)})}o(ae,"cancelPendingNodeMail");async function de(r,e){return await l(async t=>{const n=Date.now(),a=await t.prepare(`SELECT target.status AS controller_status FROM waits w LEFT JOIN nodes target ON target.node_id = w.target_id
8
+ WHERE w.node_id = ? AND w.kind = 'controller' AND w.target_id = ? AND w.satisfied_at IS NULL`).get(r,e);if(a===void 0||a.controller_status==="active"||a.controller_status==="idle")return null;const d=await j(r,{kind:"urgent",label:"declared controller is no longer live",bodyJson:{body:`Controller ${e} is no longer live; the declared message wait was cleared.`},urgency:"urgent",wake:1});if(!await x(r,e,n,t))throw new Error(`controller wait changed while notifying ${r}`);return d})}o(de,"enqueueControllerDeathNotice");export{ne as cancelDelivery,ae as cancelPendingNodeMail,g as consumeDeadlineOnWake,q as effectiveTier,de as enqueueControllerDeathNotice,j as enqueueNodeMail,f as finalizeNodeMail,y as insertMessage,re as listenDeliveryTier,u as newId,te as queueNodeNotice};
@@ -1,5 +1,5 @@
1
- var E=Object.defineProperty;var l=(t,r)=>E(t,"name",{value:r,configurable:!0});import{cancelCronsOnWake as _}from"../../canvas/crons.js";import{settleMatchingControllerWait as T}from"../../canvas/waits.js";import{effectiveTier as b,listenDeliveryTier as R,newId as I}from"../../feed/messages.js";import{readGoal as N}from"../../runtime/kickoff.js";import{canRead as h}from"../access.js";import{objectDTO as y}from"./dto.js";import{withBase as S}from"./kind.js";import{recordLiveRead as L}from"./live-read.js";import{attr as O,header as D,section as m}from"./render.js";import{filterAsync as j,mapAsync as C}from"../../async-array.js";const p=[3,5,7];function g(t){return t?.split(`
2
- `).map(r=>r.trim()).find(r=>r!=="")??""}l(g,"firstLine");const q={bodyOwner:l(t=>t[0]?.id??null,"bodyOwner"),async enqueue(t,r,a){const d=Date.now(),s=r.senderNodeId,n=a.map(e=>e.id),c=`(${n.map(()=>"?").join(",")})`,u=s===null?new Set:new Set((await t.prepareUncached(`SELECT DISTINCT node_id FROM waits WHERE kind = 'controller' AND target_id = ? AND satisfied_at IS NULL AND node_id IN ${c}`).all(s,...n)).map(e=>e.node_id)),i=new Set((await t.prepareUncached(`SELECT DISTINCT anchor_node FROM crons WHERE cancel_on_wake = 1 AND anchor_node IN ${c}`).all(...n)).map(e=>e.anchor_node)),w=t.prepare(`INSERT INTO deliveries (delivery_id, message_id, destination_id, urgency, wake, state, created_at, updated_at, idempotency_key)
3
- VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?)`);for(const e of a){const o=R(e.delivery),f=e.reception===2?o.wake:0,v=r.elevatesTier?await b(e.id,o.urgency):o.urgency;await w.run(I(),r.messageId,e.id,v,f,d,d,r.idempotencyKey===void 0?null:`${r.idempotencyKey}:${e.id}`),u.has(e.id)&&await T(e.id,s,d,t),f===1&&v!=="deferred"&&i.has(e.id)&&await _(e.id)}}},J={name:"node",addressable:"id",receiver:"inbox",receiverFor:l(async(t,r)=>await t.prepare("SELECT 1 FROM peer_stubs WHERE object = ?").get(r.id)===void 0?"inbox":"peer","receiverFor"),events:S({configured:{level:1,durable:!0},messaged:{level:2,durable:!0},sent:{level:1,durable:!0},"report.update":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"report.final":{level:7,durable:!0,carriesBody:!0,elevatesTier:!0,inboxKind:"final"},outcome:{level:1,durable:!0,supersedes:!0,carriesBody:!0},"report.result":{level:5,durable:!0,carriesBody:!0,elevatesTier:!0,inboxKind:"completed"},"notice.crash":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"notice.deadline":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"notice.close":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0}}),async read(t,r,a,d){const s=await t.prepare("SELECT status FROM nodes WHERE node_id = ?").get(a.id),n=l(async e=>await C(await j(e,async o=>await h(t,r,o)),async o=>await y(t,o)),"visible"),c=await t.prepare(`SELECT o.* FROM nodes n JOIN objects o ON o.id = n.node_id
4
- WHERE n.parent = ? AND o.deleted_at IS NULL ORDER BY o.seq`).all(a.id),u=await t.prepare("SELECT * FROM objects WHERE owner = ? AND deleted_at IS NULL ORDER BY seq").all(a.id);await L(t,r,a,d.reposFn);const i={status:s?.status??"deleted",mandate_first_line:g(await N(a.id)),children:await n(c),owned:await n(u.filter(e=>e.type!=="job")),jobs:await n(u.filter(e=>e.type==="job"))},w=[D("object",await y(t,a),O("status",i.status)),`Mandate: ${i.mandate_first_line}`,...m("Children",i.children),...m("Owns",i.owned),...m("Jobs",i.jobs)].join(`
5
- `);return{data:i,text:w}}};export{q as nodeInbox,J as nodeKind};
1
+ var E=Object.defineProperty;var l=(t,r)=>E(t,"name",{value:r,configurable:!0});import{settleMatchingControllerWait as _}from"../../canvas/waits.js";import{consumeDeadlineOnWake as T,effectiveTier as b,listenDeliveryTier as R,newId as I}from"../../feed/messages.js";import{readGoal as N}from"../../runtime/kickoff.js";import{canRead as h}from"../access.js";import{objectDTO as f}from"./dto.js";import{withBase as S}from"./kind.js";import{recordLiveRead as D}from"./live-read.js";import{attr as L,header as O,section as m}from"./render.js";import{filterAsync as j,mapAsync as g}from"../../async-array.js";const p=[3,5,7];function A(t){return t?.split(`
2
+ `).map(r=>r.trim()).find(r=>r!=="")??""}l(A,"firstLine");const K={bodyOwner:l(t=>t[0]?.id??null,"bodyOwner"),async enqueue(t,r,a){const d=Date.now(),s=r.senderNodeId,n=a.map(e=>e.id),c=`(${n.map(()=>"?").join(",")})`,u=s===null?new Set:new Set((await t.prepareUncached(`SELECT DISTINCT node_id FROM waits WHERE kind = 'controller' AND target_id = ? AND satisfied_at IS NULL AND node_id IN ${c}`).all(s,...n)).map(e=>e.node_id)),i=new Set((await t.prepareUncached(`SELECT DISTINCT anchor_node FROM crons WHERE cancel_on_wake = 1 AND anchor_node IN ${c}`).all(...n)).map(e=>e.anchor_node)),w=t.prepare(`INSERT INTO deliveries (delivery_id, message_id, destination_id, urgency, wake, state, created_at, updated_at, idempotency_key)
3
+ VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?)`);for(const e of a){const o=R(e.delivery),v=e.reception===2?o.wake:0,y=r.elevatesTier?await b(e.id,o.urgency):o.urgency;await w.run(I(),r.messageId,e.id,y,v,d,d,r.idempotencyKey===void 0?null:`${r.idempotencyKey}:${e.id}`),u.has(e.id)&&await _(e.id,s,d,t),v===1&&y!=="deferred"&&i.has(e.id)&&await T(e.id)}}},q={name:"node",addressable:"id",receiver:"inbox",receiverFor:l(async(t,r)=>await t.prepare("SELECT 1 FROM peer_stubs WHERE object = ?").get(r.id)===void 0?"inbox":"peer","receiverFor"),events:S({configured:{level:1,durable:!0},messaged:{level:2,durable:!0},sent:{level:1,durable:!0},"report.update":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"report.final":{level:7,durable:!0,carriesBody:!0,elevatesTier:!0,inboxKind:"final"},outcome:{level:1,durable:!0,supersedes:!0,carriesBody:!0},"report.result":{level:5,durable:!0,carriesBody:!0,elevatesTier:!0,inboxKind:"completed"},"notice.crash":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"notice.deadline":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0},"notice.close":{level:5,levels:p,durable:!0,carriesBody:!0,elevatesTier:!0}}),async read(t,r,a,d){const s=await t.prepare("SELECT status FROM nodes WHERE node_id = ?").get(a.id),n=l(async e=>await g(await j(e,async o=>await h(t,r,o)),async o=>await f(t,o)),"visible"),c=await t.prepare(`SELECT o.* FROM nodes n JOIN objects o ON o.id = n.node_id
4
+ WHERE n.parent = ? AND o.deleted_at IS NULL ORDER BY o.seq`).all(a.id),u=await t.prepare("SELECT * FROM objects WHERE owner = ? AND deleted_at IS NULL ORDER BY seq").all(a.id);await D(t,r,a,d.reposFn);const i={status:s?.status??"deleted",mandate_first_line:A(await N(a.id)),children:await n(c),owned:await n(u.filter(e=>e.type!=="job")),jobs:await n(u.filter(e=>e.type==="job"))},w=[O("object",await f(t,a),L("status",i.status)),`Mandate: ${i.mandate_first_line}`,...m("Children",i.children),...m("Owns",i.owned),...m("Jobs",i.jobs)].join(`
5
+ `);return{data:i,text:w}}};export{K as nodeInbox,q as nodeKind};
@@ -4,6 +4,8 @@ export type { OutboundTokenRequest } from './token-cache.js';
4
4
  export { discardFile } from './token-cache.js';
5
5
  /** How long the token endpoint has to answer. */
6
6
  export declare const MINT_TIMEOUT_MS = 10000;
7
+ /** Waits before the second and third attempt at a token endpoint that answered 502, 503 or 504. */
8
+ export declare const MINT_RETRY_DELAYS_MS: number[];
7
9
  export interface MintOptions {
8
10
  /** The directory issuer; default the configured `runtime.issuer`. */
9
11
  issuer?: string;
@@ -1 +1 @@
1
- var T=Object.defineProperty;var a=(r,e)=>T(r,"name",{value:e,configurable:!0});import{createHash as M}from"node:crypto";import{readConfig as E}from"../config.js";import{emitEvent as $}from"../events/emit.js";import{grantSyncStateFor as A}from"../grants/mirror.js";import{assertion as N,machineRevokedMessage as O,requestGrantSync as P,runtimeClient as I}from"../grants/sync.js";import{isUnsandboxedMachine as F}from"../runtime/unsandboxed.js";import{ProviderError as h}from"./errors.js";import{cacheKey as G,cachedToken as L,discardEpoch as U,storeToken as C}from"./token-cache.js";import{discardFile as ce}from"./token-cache.js";const D=1e4;function J(r){const e=r.issuer??E("user").runtime?.issuer;if(!e)throw new h("not_found","provider calls need a directory-connected runtime");return e.replace(/\/$/,"")}a(J,"issuerOf");function f(r,e,t){return new h(r,e,{origin:"directory",details:t})}a(f,"directoryError");function B(r,e,t,i){const n=typeof e.error=="string"?e.error:null,o=typeof e.error_description=="string"?e.error_description:void 0,c={directory_status:r,...n?{directory_error:n}:{},...o?{directory_description:o}:{}},u=o?`: ${o}`:"";switch(n){case"invalid_client":return F()?{error:f("machine_revoked",O(i),c),grantRefusal:!1}:{error:f("server_error",`the directory refused the outbound token request (${n})${u}`,c),grantRefusal:!1};case"invalid_request":return{error:f("server_error",`the directory refused the outbound token request (${n})${u}`,c),grantRefusal:!1};case"invalid_target":return{error:f("not_found",`the directory knows no provider at ${t.audience}${u}`,c),grantRefusal:!1};case"invalid_grant":return{error:f("grant_revoked",`the directory has no active grant for ${t.grantee}${u}`,c),grantRefusal:!0};case"provider_not_connected":return{error:f("provider_not_connected",o??"The user has not connected this provider.",c),grantRefusal:!1};case"invalid_scope":{const s=typeof e.scope=="string"&&e.scope?e.scope:t.scope.length===1?t.scope[0]:void 0,m=s?`missing scope ${s}`:`the grant does not cover ${t.scope.join(" ")}`;return{error:new h("scope_missing",m,{origin:"directory",details:c,...s?{scope:s}:{}}),grantRefusal:!0}}default:return{error:f("server_error",`the directory token endpoint answered ${r}${n?` ${n}`:""}${u}`,c),grantRefusal:!1}}}a(B,"refusal");function H(r){try{return JSON.parse(Buffer.from(r.split(".")[1]??"","base64url").toString("utf8"))}catch{return{}}}a(H,"claims");function K(r){const e=r.grant_rev,t=r.grant_status;return typeof e!="number"||!Number.isInteger(e)||e<0||typeof t!="string"?null:{rev:e,status:t}}a(K,"refusedGrantState");function z(r,e){const t=a(i=>i.status==="active"?0:1,"phase");return r.rev>e.rev||r.rev===e.rev&&t(r)>t(e)}a(z,"newer");async function Q(r,e,t){if(!e)return;const i=await A(r.grantee,r.sub)??{rev:0,status:"removed"};if(!z(e,i))return;const n=t.syncGrants??P;Promise.resolve().then(()=>n({fresh:!0})).catch(()=>{})}a(Q,"syncIfNewer");async function V(r,e={}){const t=J(e),i=`${t}/token`,n=I(),o=new URLSearchParams({grant_type:"client_credentials",client_id:n.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:N(n,i),audience:r.audience,sub:r.sub,grantee:r.grantee,scope:[...new Set(r.scope)].sort().join(" "),initiating_app:r.initiating_app}),c=AbortSignal.timeout(D),u=e.signal?AbortSignal.any([e.signal,c]):c;let s,m;try{s=await(e.fetch??fetch)(i,{method:"POST",body:o,signal:u,headers:{Accept:"application/json"}}),m=await s.text()}catch(l){throw e.signal?.aborted?e.signal.reason:new h("server_error",`the directory token endpoint could not be reached: ${l instanceof Error?l.message:String(l)}`,{origin:"directory"})}let p=null;try{p=m?JSON.parse(m):null}catch{p=null}const g=p&&typeof p=="object"&&!Array.isArray(p)?p:{};if(!s.ok){const{error:l,grantRefusal:j}=B(s.status,g,r,t);throw l.code==="machine_revoked"&&$({level:"error",event:"daemon.grants.machine_revoked",fields:{audience:r.audience}}),$({level:"info",event:"daemon.providers.mint_refused",fields:{audience:r.audience,grantee:r.grantee,code:l.code,directory_error:typeof g.error=="string"?g.error:null,status:s.status}}),j&&await Q(r,K(g),e),l}const y=g.access_token;if(typeof y!="string"||!y)throw new h("server_error","the directory token response has no access_token",{origin:"directory"});const d=H(y),S=Math.floor((e.now?.()??Date.now())/1e3),_=g.expires_in,v=typeof _=="number"&&Number.isFinite(_)&&_>0?S+Math.floor(_):null,w=typeof d.exp=="number"?d.exp:null,b=v!==null&&w!==null?Math.min(v,w):v??w;if(b===null)throw new h("server_error","the directory token response names no expiry",{origin:"directory"});const x=typeof d.jti=="string"&&d.jti?d.jti:M("sha256").update(y).digest("hex"),R=typeof d.payer=="string"&&d.payer?d.payer:r.sub;return{access_token:y,jti:x,exp:b,payer:R}}a(V,"mintOutboundToken");const k=new Map;async function ie(r,e={}){const t=G(r),i=e.now??Date.now,n=L(t,i());if(n)return{...n,key:t};let o=k.get(t);if(!o){const u=U(t);o=V(r,{...e,signal:void 0}).then(s=>(C(t,s,u),s)).finally(()=>{k.delete(t)}),o.catch(()=>{}),k.set(t,o)}return{...e.signal?await W(o,e.signal):await o,key:t}}a(ie,"outboundToken");function W(r,e){return e.aborted?Promise.reject(e.reason):new Promise((t,i)=>{const n=a(()=>i(e.reason),"onAbort");e.addEventListener("abort",n,{once:!0}),r.then(t,i).finally(()=>e.removeEventListener("abort",n))})}a(W,"abortable");export{D as MINT_TIMEOUT_MS,ce as discardFile,V as mintOutboundToken,ie as outboundToken};
1
+ var M=Object.defineProperty;var a=(r,e)=>M(r,"name",{value:e,configurable:!0});import{createHash as j}from"node:crypto";import{readConfig as E}from"../config.js";import{emitEvent as S}from"../events/emit.js";import{grantSyncStateFor as A}from"../grants/mirror.js";import{assertion as N,machineRevokedMessage as P,requestGrantSync as I,runtimeClient as O}from"../grants/sync.js";import{isUnsandboxedMachine as L}from"../runtime/unsandboxed.js";import{ProviderError as h}from"./errors.js";import{cacheKey as D,cachedToken as F,discardEpoch as G,storeToken as U}from"./token-cache.js";import{discardFile as ue}from"./token-cache.js";const C=1e4,J=[300,900];function Y(r){const e=r.issuer??E("user").runtime?.issuer;if(!e)throw new h("not_found","provider calls need a directory-connected runtime");return e.replace(/\/$/,"")}a(Y,"issuerOf");function g(r,e,t){return new h(r,e,{origin:"directory",details:t})}a(g,"directoryError");function B(r,e,t,c){const n=typeof e.error=="string"?e.error:null,i=typeof e.error_description=="string"?e.error_description:void 0,o={directory_status:r,...n?{directory_error:n}:{},...i?{directory_description:i}:{}},d=i?`: ${i}`:"";switch(n){case"invalid_client":return L()?{error:g("machine_revoked",P(c),o),grantRefusal:!1}:{error:g("server_error",`the directory refused the outbound token request (${n})${d}`,o),grantRefusal:!1};case"invalid_request":return{error:g("server_error",`the directory refused the outbound token request (${n})${d}`,o),grantRefusal:!1};case"invalid_target":return{error:g("not_found",`the directory knows no provider at ${t.audience}${d}`,o),grantRefusal:!1};case"invalid_grant":return{error:g("grant_revoked",`the directory has no active grant for ${t.grantee}${d}`,o),grantRefusal:!0};case"provider_not_connected":return{error:g("provider_not_connected",i??"The user has not connected this provider.",o),grantRefusal:!1};case"invalid_scope":{const s=typeof e.scope=="string"&&e.scope?e.scope:t.scope.length===1?t.scope[0]:void 0,l=s?`missing scope ${s}`:`the grant does not cover ${t.scope.join(" ")}`;return{error:new h("scope_missing",l,{origin:"directory",details:o,...s?{scope:s}:{}}),grantRefusal:!0}}default:return{error:g("server_error",`the directory token endpoint answered ${r}${n?` ${n}`:""}${d}`,o),grantRefusal:!1}}}a(B,"refusal");function H(r){try{return JSON.parse(Buffer.from(r.split(".")[1]??"","base64url").toString("utf8"))}catch{return{}}}a(H,"claims");function K(r){const e=r.grant_rev,t=r.grant_status;return typeof e!="number"||!Number.isInteger(e)||e<0||typeof t!="string"?null:{rev:e,status:t}}a(K,"refusedGrantState");function z(r,e){const t=a(c=>c.status==="active"?0:1,"phase");return r.rev>e.rev||r.rev===e.rev&&t(r)>t(e)}a(z,"newer");async function Q(r,e,t){if(!e)return;const c=await A(r.grantee,r.sub)??{rev:0,status:"removed"};if(!z(e,c))return;const n=t.syncGrants??I;Promise.resolve().then(()=>n({fresh:!0})).catch(()=>{})}a(Q,"syncIfNewer");async function V(r,e={}){const t=Y(e),c=`${t}/token`,n=O(),i=a(async()=>{const u=new URLSearchParams({grant_type:"client_credentials",client_id:n.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:N(n,c),audience:r.audience,sub:r.sub,grantee:r.grantee,scope:[...new Set(r.scope)].sort().join(" "),initiating_app:r.initiating_app}),p=AbortSignal.timeout(C),w=e.signal?AbortSignal.any([e.signal,p]):p,$=await(e.fetch??fetch)(c,{method:"POST",body:u,signal:w,headers:{Accept:"application/json"}});return{response:$,text:await $.text()}},"attempt");let o,d;try{for(let u=0;;u++){({response:o,text:d}=await i());const p=J[u];if(p===void 0||![502,503,504].includes(o.status))break;await new Promise(w=>setTimeout(w,p))}}catch(u){throw e.signal?.aborted?e.signal.reason:new h("server_error",`the directory token endpoint could not be reached: ${u instanceof Error?u.message:String(u)}`,{origin:"directory"})}let s=null;try{s=d?JSON.parse(d):null}catch{s=null}const l=s&&typeof s=="object"&&!Array.isArray(s)?s:{};if(!o.ok){const{error:u,grantRefusal:p}=B(o.status,l,r,t);throw u.code==="machine_revoked"&&S({level:"error",event:"daemon.grants.machine_revoked",fields:{audience:r.audience}}),S({level:"info",event:"daemon.providers.mint_refused",fields:{audience:r.audience,grantee:r.grantee,code:u.code,directory_error:typeof l.error=="string"?l.error:null,status:o.status}}),p&&await Q(r,K(l),e),u}const m=l.access_token;if(typeof m!="string"||!m)throw new h("server_error","the directory token response has no access_token",{origin:"directory"});const f=H(m),x=Math.floor((e.now?.()??Date.now())/1e3),y=l.expires_in,_=typeof y=="number"&&Number.isFinite(y)&&y>0?x+Math.floor(y):null,v=typeof f.exp=="number"?f.exp:null,b=_!==null&&v!==null?Math.min(_,v):_??v;if(b===null)throw new h("server_error","the directory token response names no expiry",{origin:"directory"});const R=typeof f.jti=="string"&&f.jti?f.jti:j("sha256").update(m).digest("hex"),T=typeof f.payer=="string"&&f.payer?f.payer:r.sub;return{access_token:m,jti:R,exp:b,payer:T}}a(V,"mintOutboundToken");const k=new Map;async function se(r,e={}){const t=D(r),c=e.now??Date.now,n=F(t,c());if(n)return{...n,key:t};let i=k.get(t);if(!i){const d=G(t);i=V(r,{...e,signal:void 0}).then(s=>(U(t,s,d),s)).finally(()=>{k.delete(t)}),i.catch(()=>{}),k.set(t,i)}return{...e.signal?await W(i,e.signal):await i,key:t}}a(se,"outboundToken");function W(r,e){return e.aborted?Promise.reject(e.reason):new Promise((t,c)=>{const n=a(()=>c(e.reason),"onAbort");e.addEventListener("abort",n,{once:!0}),r.then(t,c).finally(()=>e.removeEventListener("abort",n))})}a(W,"abortable");export{J as MINT_RETRY_DELAYS_MS,C as MINT_TIMEOUT_MS,ue as discardFile,V as mintOutboundToken,se as outboundToken};
@@ -1 +1 @@
1
- var m=Object.defineProperty;var d=(s,n)=>m(s,"name",{value:n,configurable:!0});import{createServer as h,request as f}from"node:http";import{chmodSync as y,existsSync as l,unlinkSync as p}from"node:fs";import{join as S}from"node:path";import{apiSocketPath as u,nodeRunDir as k}from"../../canvas/paths.js";async function H(s,n){const r=S(k(s),"cli.sock"),o=h((t,e)=>{const i={...t.headers,authorization:`Bearer ${n}`};delete i.connection;const c=f({socketPath:u(),path:t.url,method:t.method,headers:i},a=>{e.writeHead(a.statusCode??502,a.headers),a.pipe(e)});c.on("error",a=>{e.headersSent||e.writeHead(503,{"content-type":"application/json"}),e.end(JSON.stringify({error:{code:"daemon_unavailable",message:a.message}}))}),t.pipe(c),e.on("close",()=>c.destroy())});return l(r)&&p(r),await new Promise((t,e)=>{o.once("error",e),o.listen(r,()=>{o.off("error",e),t()})}),y(r,384),{path:r,close(){o.closeAllConnections(),o.close(),l(r)&&p(r)}}}d(H,"serveCliRelay");export{H as serveCliRelay};
1
+ var E=Object.defineProperty;var p=(a,n)=>E(a,"name",{value:n,configurable:!0});import{createServer as y,request as u}from"node:http";import{chmodSync as S,existsSync as h,rmSync as w,unlinkSync as f}from"node:fs";import{join as l}from"node:path";import{apiSocketPath as _,BROKER_RELEASED_MARKER as k,BROKER_RELAY_GENERATION as v,nodeRunDir as R}from"../../canvas/paths.js";import{writeFileBeneath as A}from"../../spaces/open-beneath.js";async function I(a,n){const r=l(R(a),"cli.sock"),t=y((o,e)=>{const m={...o.headers,authorization:`Bearer ${n}`};delete m.connection;const i=u({socketPath:_(),path:o.url,method:o.method,headers:m},s=>{e.writeHead(s.statusCode??502,s.headers),s.pipe(e)});i.on("error",s=>{e.headersSent||e.writeHead(503,{"content-type":"application/json"}),e.end(JSON.stringify({error:{code:"daemon_unavailable",message:s.message}}))}),o.pipe(i),e.on("close",()=>i.destroy())});h(r)&&f(r),await new Promise((o,e)=>{t.once("error",e),t.listen(r,()=>{t.off("error",e),o()})}),S(r,384);const c=R(a),d=process.env.CRTR_EXECUTION_ID;if(!d)throw new Error("broker relay has no execution id");return A(c,l(c,v),d,{atomic:!0}),w(l(c,k),{force:!0}),{path:r,close(){t.closeAllConnections(),t.close(),h(r)&&f(r)}}}p(I,"serveCliRelay");export{I as serveCliRelay};
@@ -83,6 +83,9 @@ export declare class FaultRetry {
83
83
  * this generation, since the replacement owns recovery from then on, and when
84
84
  * another turn starts, since a stale fault would re-drive from the old anchor. */
85
85
  publishSettledProviderFault(generation: SubscriptionGeneration): Promise<void>;
86
+ /** Run a daemon write until the daemon answers: a transport failure waits and
87
+ * tries again, and `assertCurrent` throws once the write has gone stale. */
88
+ private retryWhileDaemonUnreachable;
86
89
  private recordStagedOutcome;
87
90
  /** Where the next retry of this episode rewinds to. The episode keeps its
88
91
  * original anchor while every attempt since failed without doing anything, so
@@ -135,5 +138,14 @@ export declare class FaultRetry {
135
138
  /** One retry attempt. Every outcome but a re-armed timer or a prompted recovery turn means no recovery will answer the settled turn. */
136
139
  private runFaultRetry;
137
140
  private attemptFaultRetry;
141
+ /** The admitted recovery prompt rejected, so unless a turn started nothing
142
+ * will settle the durable `admitted` episode. While this generation, session
143
+ * and episode still own the attempt and no turn has started since, a
144
+ * transient rejection reopens the episode for the existing retry policy with
145
+ * the attempt it consumed and the original anchor; a rejection that would
146
+ * fail the same way again (missing credentials, a refused request) ends the
147
+ * episode with a fatal fault, and so does a run node's, whose turn the app
148
+ * has already seen end. */
149
+ private settleRejectedRecoveryPrompt;
138
150
  }
139
151
  export {};
@@ -1 +1 @@
1
- var ae=Object.defineProperty;var m=(r,e)=>ae(r,"name",{value:e,configurable:!0});import{envModelIntent as N,envNodeCwd as G,envProfileId as U}from"../../../shared/env.js";import{isContextOverflow as Y}from"@earendil-works/pi-ai";import{classify as B}from"../../fault-classifier.js";import{isDaemonTransportApiError as de}from"../../../api/errors.js";import{isModelNotFoundError as le,isProviderUnconfiguredError as ce}from"../fault.js";import{brokerRecovery as ue,mutateBrokerFault as W,mutateBrokerProviderRetry as _}from"./daemon-ops.js";import{extractCoolingDeadline as pe}from"../managed-provider-cooling.js";import{extractProviderFailureDetails as fe}from"../../provider-failure.js";import{expandModelCandidates as q,modelRequestFromConfig as V,probeRouteAvailability as he}from"../../model-routes.js";import{operationIdContext as j}from"../../events/operation-id.js";import{emitEvent as w}from"../../events/emit.js";import{CONNECTION_FAULT_RECOVERY_BODY as me,PROVIDER_FAULT_RECOVERY_BODY as ge,RUN_CONNECTION_FAULT_RETRY_BODY as ye,formatCard as X,formatModelFallbackRecovery as ve}from"../../../shared/generated-context.js";import{markFatalOnExhaust as J,nextFaultRetry as z,policyFor as H}from"../fault-recovery.js";import{parseModelSpec as K}from"../model-selection.js";import{endedByAbort as Q}from"../session-cycles.js";import{resolveProviderCandidates as Fe}from"../../subscription-state.js";import{announceMessageOrigin as Z}from"../stamp/channel.js";import{LLM_REFUSAL_DIAGNOSTIC as we,refusalRetryAt as ke}from"./model-adapter.js";import{isSafetyRefusal as ee}from"../../llm/refusal.js";const be=2147483647,te=3e4;function _e(r,e,t){const n=Date.parse(r);return!Number.isFinite(n)||t+e-n>te}m(_e,"runTurnRetryExpired");class ne extends Error{static{m(this,"StaleOutcome")}}const Ee=1e3,Ie=3e4;function Re(r,e){return r!=="connection"?ge:e?ye:me}m(Re,"retryCardBody");function E(r){return{link:r.link,op:r.op,kind:r.kind,retry:r.retry,message:r.message,since:r.since,operation_id:r.operation_id,...r.anchor_entry_id===void 0?{}:{anchorEntryId:r.anchor_entry_id},...r.provider_error===void 0?{}:{providerError:r.provider_error}}}m(E,"policyFault");function x(r){return{link:r.link,op:r.op,kind:r.kind,retry:r.retry,message:r.message,...r.operation_id===void 0?{}:{operation_id:r.operation_id},...r.since===void 0?{}:{since:r.since},...r.anchorEntryId===void 0?{}:{anchor_entry_id:r.anchorEntryId},...r.providerError===void 0?{}:{provider_error:r.providerError}}}m(x,"faultInput");function ie(r,e){const t=m(n=>`${n.providerId}/${n.modelId}`===e.modelSpec,"onLiveModel");return(r.find(n=>t(n)&&n.thinkingLevel===e.thinkingLevel)??r.find(t))?.rank}m(ie,"liveRouteRank");function re(r){const e=B("pi\u2192provider",r),t=r.diagnostics?.some(n=>n.type===we)??!1;return{classified:e,disposition:t?"fatal":e.disposition}}m(re,"settledProviderFault");function He(r,e){if(r.stopReason!=="error"||Q(r)||ee(r)||Y(r,e))return!1;const{classified:t,disposition:n}=re(r);return n==="auto"&&(t.kind==="connection"||t.kind==="rate-limit"||t.kind==="overloaded")}m(He,"willDaemonRetry");const Se=/does not support assistant message prefill|conversation must end with a user message/i;function Ae(r,e){return e&&Se.test(r)}m(Ae,"isPrefillRejectionDuringEpisode");class Ke{static{m(this,"FaultRetry")}deps;timer=null;failedRetryRoutes=new Set;notFoundModels=new Set;providerFallbackInFlight=null;notFoundFallbackInFlight=null;stagedCapacityFailure=null;agentStarts=0;constructor(e){this.deps=e}clearTimer(){this.timer!==null&&(clearTimeout(this.timer),this.timer=null)}reset(){this.clearTimer(),this.deps.turnEnds?.abandon(),this.providerFallbackInFlight=null,this.notFoundFallbackInFlight=null,this.stagedCapacityFailure=null}abandonTurnEnd(){this.deps.turnEnds?.abandon()}state(){return ue(this.deps.nodeId)}async recordFault(e){return(await W(this.deps.nodeId,{op:"record",input:e})).fault}async clearFault(e,t=!1){await W(this.deps.nodeId,{op:"clear",...e===void 0?{}:{link:e},...t?{preserve_episode:!0}:{}})}sessionId(e){const t=e.sessionManager.getSessionId?.();return typeof t=="string"&&t!==""?t:null}async clearEpisode(e){const t=this.sessionId(e);t!==null&&await _(this.deps.nodeId,{op:"clear",session_id:t})}onEvent(e){e.type==="agent_start"&&this.agentStarts++,this.maybeSwitchProviderForRetry(e),this.maybeFallbackOnUnusableModel(e)}isActiveAutoFault(e){return e===null||e.retry.disposition!=="auto"||e.retry.by!=="daemon"?!1:e.kind==="connection"||e.kind==="rate-limit"||e.kind==="overloaded"}async publishSettledProviderFault(e){const t={agentEnd:e.stagedProviderAgentEnd,watchdogAbort:e.stagedWatchdogAbort,refreshAbort:e.stagedRefreshAbort,overflowFailure:e.stagedOverflowFailure,capacityFailure:this.stagedCapacityFailure?.generation===e?this.stagedCapacityFailure:null};this.stagedCapacityFailure=null,e.stagedProviderAgentEnd=null,e.stagedWatchdogAbort=!1,e.stagedRefreshAbort=!1,e.stagedOverflowFailure=null;const n=this.agentStarts,i=m(()=>{if(this.deps.installedGeneration()!==e||this.agentStarts!==n)throw new ne},"assertCurrent");let a=Ee;try{for(;;){try{await this.recordStagedOutcome(e,t,i);return}catch(s){if(!de(s))throw s;w({level:"warn",event:"broker.runtime.diagnostic",fields:{message:`settled turn fault write waiting for the daemon: ${s.message}`}})}await new Promise(s=>setTimeout(s,a)),a=Math.min(a*2,Ie),i()}}catch(s){if(!(s instanceof ne))throw s}}async recordStagedOutcome(e,t,n){const{agentEnd:i,watchdogAbort:a,refreshAbort:s,overflowFailure:d,capacityFailure:f}=t,o=e.session,l=m(async F=>{n(),await this.clearEpisode(F)},"clearEpisode"),g=m(async F=>{n(),await this.recordFault(F)},"recordFault"),p=m(async(F,oe)=>{n(),await this.recordPendingProviderFault(F,oe)},"recordPendingProviderFault"),v=await this.state(),h=v.fault,c=v.episode,I=this.sessionId(o),k=this.isActiveAutoFault(h)&&h.link==="pi\u2192provider"?h:c?.state==="admitted"&&c.session_id===I&&this.isActiveAutoFault(c.fault)?c.fault:null,y=k===null?null:{since:k.since,anchorEntryId:k.anchor_entry_id,attempt:k.retry.attempt},u=[...Array.isArray(i?.messages)?i.messages:[]].reverse().find(F=>typeof F=="object"&&F!==null&&F.role==="assistant");if(f!==null||d!==null){await l(o),await g({link:"pi\u2192provider",op:"context overflow recovery",kind:"context-overflow",retry:{disposition:"fatal"},message:f?.errorMessage??d.errorMessage,anchor_entry_id:o.sessionManager.getLeafId?.()??void 0});return}if(a&&u?.stopReason==="aborted"){await p(o,{link:"pi\u2192provider",op:"generation turn",kind:"connection",retry:{disposition:"auto",by:"daemon",...y?.attempt!==void 0?{attempt:y.attempt}:{}},message:typeof u.errorMessage=="string"?u.errorMessage:"engine error (no errorMessage recorded)",...y!==null?{since:y.since}:{},anchor_entry_id:this.retryAnchor(o,y?.anchorEntryId)});return}if(u?.stopReason!=="error"){u!==void 0&&await l(o);return}if(s){await l(o);return}if(ee(u)){await l(o);return}const S=typeof u.errorMessage=="string"?u.errorMessage:"engine error (no errorMessage recorded)";if(Q({stopReason:"error",errorMessage:S})){await l(o);return}const b=Y(u,o.model?.contextWindow),A=re(u),T=y!==null&&A.disposition!=="auto"&&Ae(S,!0),se=T?{kind:"connection",disposition:"auto"}:A.classified,R=T?"auto":A.disposition,C=pe(u),O=ke(u),P=fe(u),$=typeof u.provider=="string"&&u.provider!==""?u.provider:o.model?.provider,L=typeof u.model=="string"&&u.model!==""?u.model:o.model?.id,D=P===void 0||$===void 0||L===void 0?void 0:{provider:$,model:L,...P};b&&await l(o);const M={link:"pi\u2192provider",op:"generation turn",kind:b?"context-overflow":se.kind,retry:b?{disposition:"fatal"}:R==="auto"?{disposition:"auto",by:"daemon",...C!==void 0?{nextAt:new Date(C).toISOString()}:{},...y?.attempt!==void 0?{attempt:y.attempt}:{}}:{disposition:R,...O!==void 0?{nextAt:new Date(O).toISOString()}:{}},message:S,...!b&&R==="auto"&&y!==null?{since:y.since}:{},anchor_entry_id:!b&&R==="auto"?this.retryAnchor(o,y?.anchorEntryId):o.sessionManager.getLeafId?.()??void 0,...D===void 0?{}:{provider_error:D}};M.retry.disposition==="auto"?await p(o,M):(await l(o),await g(M))}retryAnchor(e,t){const n=e.sessionManager.getLeafId?.()??void 0;return t!==void 0&&!this.hasProgressPastAnchor(e,t)?t:n}async recoverOnStartup(){const e=this.deps.installedGeneration();if(e===void 0)return"none";const t=(await this.state()).episode,n=this.sessionId(e.session);if(this.deps.cfg.newCycle===!0)return t!==null&&n!==null&&t.session_id===n&&await _(this.deps.nodeId,{op:"invalidate",session_id:n}),"none";if(t===null||n===null||t.session_id!==n)return"none";if(t.state==="admitted")if(t.fault.anchor_entry_id!==void 0&&!this.hasProgressPastAnchor(e.session,t.fault.anchor_entry_id)){if(!(await _(this.deps.nodeId,{op:"reopen",session_id:n})).applied)return"none"}else return await this.clearEpisode(e.session),"none";else if(t.state!=="pending")return"none";return await this.recordFault(x(E(t.fault))),await this.schedule(),"retry-armed"}hasProgressPastAnchor(e,t){const n=e.sessionManager.getBranch(),i=n.findIndex(s=>s.id===t);return(i===-1?n:n.slice(i+1)).some(s=>{if(s.type!=="message")return!1;const{message:d}=s;return d.role==="toolResult"?!0:d.role==="assistant"&&Array.isArray(d.content)&&d.content.length>0})}async schedule(){const e=this.deps.installedGeneration(),t=this.deps.currentSession();if(t.isStreaming||this.timer!==null)return;if(e===void 0){this.abandonTurnEnd();return}const n=await this.state(),i=n.fault;if(!this.isActiveAutoFault(i)||this.sessionId(t)!==null&&!this.pendingEpisodeMatches(t,i,n)){this.clearTimer(),this.abandonTurnEnd();return}const a=Date.now(),s=this.faultRetryDelayMs(i,a);if(s!==null&&this.deps.turnEnds?.holding()===!0&&_e(i.since,s,a)){await this.endRunTurnOnFault(t,i,s);return}s!==null?this.clearAndArmFaultRetry(s,e):this.abandonTurnEnd()}async endRunTurnOnFault(e,t,n){this.clearTimer(),this.abandonTurnEnd(),w({level:"info",event:"broker.runtime.diagnostic",fields:{message:`run turn completed on provider fault: ${t.kind} retry in ${n}ms is past the ${te}ms turn window (since ${t.since}, attempt ${t.retry.attempt??0})`}}),await this.clearEpisode(e),await this.clearFault("pi\u2192provider")}async rearmIfStranded(){if(this.timer!==null||this.deps.installedGeneration()===void 0)return;const e=this.deps.currentSession();if(e.isStreaming)return;const t=await this.state();!this.isActiveAutoFault(t.fault)||!this.pendingEpisodeMatches(e,t.fault,t)||await this.schedule()}maybeSwitchProviderForRetry(e){if(e.type!=="auto_retry_start")return;const t=B("pi\u2192provider",e.errorMessage??"").kind;if(t!=="rate-limit"&&t!=="connection"||this.providerFallbackInFlight!==null||this.deps.cfg.modelExact===!0)return;const n=this.deps.installedGeneration();if(n===void 0)return;const i=this.deps.currentSession(),a=this.deps.currentServices(),s=this.deps.formatModelSpec(i.model,i.thinkingLevel);if(s===null)return;const d=V(this.deps.cfg.model,N(),!1);if(d===void 0)return;const f=q(d,G()??this.deps.cfg.cwd,U()),o=K(s),l=ie(f,o),g=l===void 0?{route:void 0,capacityExhausted:!1}:this.nextEligibleFallbackRoute(f.slice(l+1),this.deps.registryOf(a),i.getContextUsage()?.tokens,h=>!this.failedRetryRoutes.has(h.routeId));if(g.route===void 0){g.capacityExhausted&&(this.stageCapacityFailure(n,i),queueMicrotask(()=>{this.deps.installedGeneration()===n&&this.deps.currentSession()===i&&i.abortRetry()}));return}const p=g.route,v=(async()=>{const h=this.deps.registryOf(a).find(p.providerId,p.modelId);if(!h||(await i.setModel(h),this.deps.installedGeneration()!==n||this.deps.currentSession()!==i))return;const c=`${p.providerId}/${p.modelId}`===o.modelSpec?o.thinkingLevel:p.thinkingLevel;i.setThinkingLevel(c??"off"),this.failedRetryRoutes.add(p.routeId),await this.deps.broadcastModelChanged(!1)})().catch(h=>{w({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`provider route fallback failed: ${h instanceof Error?h.message:String(h)}`}})}).finally(()=>{this.deps.installedGeneration()===n&&(this.providerFallbackInFlight=null)});this.providerFallbackInFlight=v}maybeFallbackOnUnusableModel(e){if(e.type!=="message_end"||e.message.role!=="assistant"||e.message.stopReason!=="error")return;const t=ce(e.message.errorMessage??"");if(!t&&!le(e.message.errorMessage??"")||this.deps.cfg.modelExact===!0||this.notFoundFallbackInFlight!==null)return;const n=this.deps.installedGeneration();if(n===void 0)return;const i=this.deps.currentSession(),a=this.deps.currentServices(),s=this.deps.formatModelSpec(i.model,i.thinkingLevel);if(s===null)return;const d=K(s);this.notFoundModels.add(d.modelSpec);const f=V(this.deps.cfg.model,N(),!1),o=f?q(f,G()??this.deps.cfg.cwd,U()):[],l=ie(o,d),g=l===void 0?{route:void 0,capacityExhausted:!1}:this.nextEligibleFallbackRoute(o.slice(l+1),this.deps.registryOf(a),i.getContextUsage()?.tokens,c=>!this.notFoundModels.has(`${c.providerId}/${c.modelId}`));if(g.route===void 0){g.capacityExhausted&&this.stageCapacityFailure(n,i);return}const p=g.route,v=`${p.providerId}/${p.modelId}${p.thinkingLevel?`:${p.thinkingLevel}`:""}`,h=(async()=>{const c=this.deps.registryOf(a).find(p.providerId,p.modelId);if(c===void 0){w({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`${t?"unconfigured-provider":"not_found"} fallback skipped: ${v} is not registered`}});return}if(await i.setModel(c),this.deps.installedGeneration()!==n||this.deps.currentSession()!==i)return;i.setThinkingLevel(p.thinkingLevel??"off"),await this.deps.broadcastModelChanged();const I=this.deps.formatModelSpec(i.model,i.thinkingLevel)??v;if(w({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`${t?"unconfigured-provider":"not_found"} fallback: ${s} unavailable \u2192 ${I}; re-driving turn`}}),await i.agent.waitForIdle(),this.deps.installedGeneration()!==n||this.deps.currentSession()!==i)return;t&&await this.clearFault("pi\u2192provider");const k=X("recovery",{},ve(s,I,t?"credential":"not-found"));Z(k,{type:"runtime",kind:"recovery"}),await this.deps.turnAdmission.admit(y=>j.fresh(()=>i.prompt(k,{preflightResult:y})))})().catch(c=>{w({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`${t?"unconfigured-provider":"not_found"} fallback failed: ${c instanceof Error?c.message:String(c)}`}})}).finally(()=>{this.deps.installedGeneration()===n&&(this.notFoundFallbackInFlight=null)});this.notFoundFallbackInFlight=h}nextEligibleFallbackRoute(e,t,n,i){let a=0,s=0;for(const d of e){if(!i(d)||Fe([{...d,...he(d,t),automaticFallback:!0}]).candidates.length===0)continue;const f=t.find(d.providerId,d.modelId);if(f!==void 0){if(a++,this.targetCannotFitContext(f.contextWindow,n)){s++;continue}return{route:d,capacityExhausted:!1}}}return{route:void 0,capacityExhausted:a>0&&a===s}}targetCannotFitContext(e,t){return Number.isFinite(e)&&Number.isFinite(t)&&e<t}stageCapacityFailure(e,t){const n=t.getContextUsage()?.tokens;this.stagedCapacityFailure={generation:e,errorMessage:Number.isFinite(n)?`All available fallback models have context windows smaller than the current conversation (${n} tokens).`:"All available fallback models have context windows smaller than the current conversation."}}async recordPendingProviderFault(e,t){const n=this.sessionId(e);if(n===null){await this.recordFault(t);return}await _(this.deps.nodeId,{op:"record-pending",session_id:n,input:t})}pendingEpisodeMatches(e,t,n){const i=this.sessionId(e),a=n.episode;return i!==null&&a!==null&&a.state==="pending"&&a.session_id===i&&a.fault.fault_id===t.fault_id}faultRetryDelayMs(e,t){if(e.retry.nextAt!==void 0){const n=Date.parse(e.retry.nextAt)-t;return Number.isFinite(n)?Math.max(0,n):null}return z(E(e),t)?.delayMs??null}clearAndArmFaultRetry(e,t){if(this.deps.installedGeneration()!==t)return;this.clearTimer();const n=Math.min(Math.max(0,e),be),i=Math.max(0,e-n);this.timer=setTimeout(()=>{if(this.timer=null,this.deps.installedGeneration()===t){if(i>0){this.clearAndArmFaultRetry(i,t);return}this.runFaultRetry().catch(a=>{this.abandonTurnEnd(),w({level:"warn",event:"broker.runtime.diagnostic",fields:{message:`fault retry fire failed: ${a instanceof Error?a.message:String(a)}`}})})}},n),typeof this.timer.unref=="function"&&this.timer.unref()}async runFaultRetry(){await this.attemptFaultRetry()===void 0&&this.abandonTurnEnd()}async attemptFaultRetry(){const e=this.deps.installedGeneration();if(e===void 0)return;const t=e.session;if(t.isStreaming)return;const n=(await this.state()).fault;if(!this.isActiveAutoFault(n))return;let i=n;if(n.kind==="connection"&&this.deps.turnEnds===void 0){const o=await this.deps.retryProbe();if(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t||t.isStreaming)return;const l=(await this.state()).fault;if(!this.isActiveAutoFault(l)||l.since!==n.since)return;if(!o)return this.clearAndArmFaultRetry(H(l.kind).baseDelayMs,e),"rearmed";i=l}const a=this.deps.turnEnds!==void 0,s=i.retry.attempt??0,d=H(i.kind);if(s>=d.max){await this.recordFault(x(J(E(i)))),await this.clearEpisode(t);return}const f=z(E(i),Date.now());if(f===null){await this.recordFault(x(J(E(i)))),await this.clearEpisode(t);return}if(!(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t))return this.deps.turnAdmission.admit(async o=>{const l=(await this.state()).fault;if(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t||t.isStreaming||!this.isActiveAutoFault(l)||l.since!==i.since){o();return}const g=this.sessionId(t);if(g===null){o();return}if(!(await _(this.deps.nodeId,{op:"admit",session_id:g,fault_id:l.fault_id,retry:f.fault.retry})).applied){o();return}const v=i.anchor_entry_id;if(v!==void 0&&(t.sessionManager.getLeafId?.()??null)!==v&&!this.hasProgressPastAnchor(t,v))try{const c=await t.navigateTree(v);if(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t){o();return}c.cancelled||this.deps.reWelcomeAll()}catch(c){w({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`fault retry rewind skipped: ${c instanceof Error?c.message:String(c)}`}})}const h=X("recovery",a?{attempt:f.attempt}:{attempt:f.attempt,max:f.max},Re(i.kind,a));Z(h,{type:"runtime",kind:"recovery"}),this.deps.turnEnds?.beginRecovery();try{await j.fresh(()=>t.prompt(h,{preflightResult:o}))}finally{this.deps.turnEnds?.recoveryReturned()}return"prompted"})}}export{Ke as FaultRetry,te as RUN_TURN_RETRY_WINDOW_MS,Ae as isPrefillRejectionDuringEpisode,Re as retryCardBody,_e as runTurnRetryExpired,re as settledProviderFault,He as willDaemonRetry};
1
+ var ue=Object.defineProperty;var m=(i,e)=>ue(i,"name",{value:e,configurable:!0});import{envModelIntent as j,envNodeCwd as Y,envProfileId as q}from"../../../shared/env.js";import{isContextOverflow as V}from"@earendil-works/pi-ai";import{classify as C}from"../../fault-classifier.js";import{isDaemonTransportApiError as X}from"../../../api/errors.js";import{isModelNotFoundError as fe,isProviderUnconfiguredError as pe}from"../fault.js";import{brokerRecovery as he,mutateBrokerFault as J,mutateBrokerProviderRetry as k}from"./daemon-ops.js";import{extractCoolingDeadline as me}from"../managed-provider-cooling.js";import{extractProviderFailureDetails as ge}from"../../provider-failure.js";import{expandModelCandidates as z,modelRequestFromConfig as H,probeRouteAvailability as ye}from"../../model-routes.js";import{operationIdContext as K}from"../../events/operation-id.js";import{emitEvent as F}from"../../events/emit.js";import{CONNECTION_FAULT_RECOVERY_BODY as ve,PROVIDER_FAULT_RECOVERY_BODY as we,RUN_CONNECTION_FAULT_RETRY_BODY as Fe,formatCard as Q,formatModelFallbackRecovery as be}from"../../../shared/generated-context.js";import{markFatalOnExhaust as O,nextFaultRetry as P,policyFor as Z}from"../fault-recovery.js";import{parseModelSpec as ee}from"../model-selection.js";import{endedByAbort as te}from"../session-cycles.js";import{resolveProviderCandidates as ke}from"../../subscription-state.js";import{announceMessageOrigin as ne}from"../stamp/channel.js";import{LLM_REFUSAL_DIAGNOSTIC as _e,refusalRetryAt as Ie}from"./model-adapter.js";import{isSafetyRefusal as re}from"../../llm/refusal.js";const Ee=2147483647,ie=3e4;function Re(i,e,t){const n=Date.parse(i);return!Number.isFinite(n)||t+e-n>ie}m(Re,"runTurnRetryExpired");class _ extends Error{static{m(this,"StaleOutcome")}}const Se=1e3,Ae=3e4;function Me(i,e){return i!=="connection"?we:e?Fe:ve}m(Me,"retryCardBody");function I(i){return{link:i.link,op:i.op,kind:i.kind,retry:i.retry,message:i.message,since:i.since,operation_id:i.operation_id,...i.anchor_entry_id===void 0?{}:{anchorEntryId:i.anchor_entry_id},...i.provider_error===void 0?{}:{providerError:i.provider_error}}}m(I,"policyFault");function se(i){return i instanceof Error?i.message:String(i)}m(se,"errorText");function oe(i){return X(i)||C("pi\u2192provider",i).disposition==="auto"}m(oe,"isTransientRejection");function A(i){return{link:i.link,op:i.op,kind:i.kind,retry:i.retry,message:i.message,...i.operation_id===void 0?{}:{operation_id:i.operation_id},...i.since===void 0?{}:{since:i.since},...i.anchorEntryId===void 0?{}:{anchor_entry_id:i.anchorEntryId},...i.providerError===void 0?{}:{provider_error:i.providerError}}}m(A,"faultInput");function ae(i,e){const t=m(n=>`${n.providerId}/${n.modelId}`===e.modelSpec,"onLiveModel");return(i.find(n=>t(n)&&n.thinkingLevel===e.thinkingLevel)??i.find(t))?.rank}m(ae,"liveRouteRank");function de(i){const e=C("pi\u2192provider",i),t=i.diagnostics?.some(n=>n.type===_e)??!1;return{classified:e,disposition:t?"fatal":e.disposition}}m(de,"settledProviderFault");function Qe(i,e){if(i.stopReason!=="error"||te(i)||re(i)||V(i,e))return!1;const{classified:t,disposition:n}=de(i);return n==="auto"&&(t.kind==="connection"||t.kind==="rate-limit"||t.kind==="overloaded")}m(Qe,"willDaemonRetry");const xe=/does not support assistant message prefill|conversation must end with a user message/i;function Te(i,e){return e&&xe.test(i)}m(Te,"isPrefillRejectionDuringEpisode");class Ze{static{m(this,"FaultRetry")}deps;timer=null;failedRetryRoutes=new Set;notFoundModels=new Set;providerFallbackInFlight=null;notFoundFallbackInFlight=null;stagedCapacityFailure=null;agentStarts=0;constructor(e){this.deps=e}clearTimer(){this.timer!==null&&(clearTimeout(this.timer),this.timer=null)}reset(){this.clearTimer(),this.deps.turnEnds?.abandon(),this.providerFallbackInFlight=null,this.notFoundFallbackInFlight=null,this.stagedCapacityFailure=null}abandonTurnEnd(){this.deps.turnEnds?.abandon()}state(){return he(this.deps.nodeId)}async recordFault(e){return(await J(this.deps.nodeId,{op:"record",input:e})).fault}async clearFault(e,t=!1){await J(this.deps.nodeId,{op:"clear",...e===void 0?{}:{link:e},...t?{preserve_episode:!0}:{}})}sessionId(e){const t=e.sessionManager.getSessionId?.();return typeof t=="string"&&t!==""?t:null}async clearEpisode(e){const t=this.sessionId(e);t!==null&&await k(this.deps.nodeId,{op:"clear",session_id:t})}onEvent(e){e.type==="agent_start"&&this.agentStarts++,this.maybeSwitchProviderForRetry(e),this.maybeFallbackOnUnusableModel(e)}isActiveAutoFault(e){return e===null||e.retry.disposition!=="auto"||e.retry.by!=="daemon"?!1:e.kind==="connection"||e.kind==="rate-limit"||e.kind==="overloaded"}async publishSettledProviderFault(e){const t={agentEnd:e.stagedProviderAgentEnd,watchdogAbort:e.stagedWatchdogAbort,refreshAbort:e.stagedRefreshAbort,overflowFailure:e.stagedOverflowFailure,capacityFailure:this.stagedCapacityFailure?.generation===e?this.stagedCapacityFailure:null};this.stagedCapacityFailure=null,e.stagedProviderAgentEnd=null,e.stagedWatchdogAbort=!1,e.stagedRefreshAbort=!1,e.stagedOverflowFailure=null;const n=this.agentStarts,r=m(()=>{if(this.deps.installedGeneration()!==e||this.agentStarts!==n)throw new _},"assertCurrent");try{await this.retryWhileDaemonUnreachable("settled turn fault write",()=>this.recordStagedOutcome(e,t,r),r)}catch(s){if(!(s instanceof _))throw s}}async retryWhileDaemonUnreachable(e,t,n){let r=Se;for(;;){try{await t();return}catch(s){if(!X(s))throw s;F({level:"warn",event:"broker.runtime.diagnostic",fields:{message:`${e} waiting for the daemon: ${s.message}`}})}await new Promise(s=>setTimeout(s,r)),r=Math.min(r*2,Ae),n()}}async recordStagedOutcome(e,t,n){const{agentEnd:r,watchdogAbort:s,refreshAbort:a,overflowFailure:l,capacityFailure:c}=t,o=e.session,f=m(async b=>{n(),await this.clearEpisode(b)},"clearEpisode"),g=m(async b=>{n(),await this.recordFault(b)},"recordFault"),p=m(async(b,ce)=>{n(),await this.recordPendingProviderFault(b,ce)},"recordPendingProviderFault"),y=await this.state(),u=y.fault,h=y.episode,E=this.sessionId(o),w=this.isActiveAutoFault(u)&&u.link==="pi\u2192provider"?u:h?.state==="admitted"&&h.session_id===E&&this.isActiveAutoFault(h.fault)?h.fault:null,v=w===null?null:{since:w.since,anchorEntryId:w.anchor_entry_id,attempt:w.retry.attempt},d=[...Array.isArray(r?.messages)?r.messages:[]].reverse().find(b=>typeof b=="object"&&b!==null&&b.role==="assistant");if(c!==null||l!==null){await f(o),await g({link:"pi\u2192provider",op:"context overflow recovery",kind:"context-overflow",retry:{disposition:"fatal"},message:c?.errorMessage??l.errorMessage,anchor_entry_id:o.sessionManager.getLeafId?.()??void 0});return}if(s&&d?.stopReason==="aborted"){await p(o,{link:"pi\u2192provider",op:"generation turn",kind:"connection",retry:{disposition:"auto",by:"daemon",...v?.attempt!==void 0?{attempt:v.attempt}:{}},message:typeof d.errorMessage=="string"?d.errorMessage:"engine error (no errorMessage recorded)",...v!==null?{since:v.since}:{},anchor_entry_id:this.retryAnchor(o,v?.anchorEntryId)});return}if(d?.stopReason!=="error"){d!==void 0&&await f(o);return}if(a){await f(o);return}if(re(d)){await f(o);return}const M=typeof d.errorMessage=="string"?d.errorMessage:"engine error (no errorMessage recorded)";if(te({stopReason:"error",errorMessage:M})){await f(o);return}const R=V(d,o.model?.contextWindow),x=de(d),D=v!==null&&x.disposition!=="auto"&&Te(M,!0),le=D?{kind:"connection",disposition:"auto"}:x.classified,S=D?"auto":x.disposition,L=me(d),N=Ie(d),G=ge(d),U=typeof d.provider=="string"&&d.provider!==""?d.provider:o.model?.provider,W=typeof d.model=="string"&&d.model!==""?d.model:o.model?.id,B=G===void 0||U===void 0||W===void 0?void 0:{provider:U,model:W,...G};R&&await f(o);const T={link:"pi\u2192provider",op:"generation turn",kind:R?"context-overflow":le.kind,retry:R?{disposition:"fatal"}:S==="auto"?{disposition:"auto",by:"daemon",...L!==void 0?{nextAt:new Date(L).toISOString()}:{},...v?.attempt!==void 0?{attempt:v.attempt}:{}}:{disposition:S,...N!==void 0?{nextAt:new Date(N).toISOString()}:{}},message:M,...!R&&S==="auto"&&v!==null?{since:v.since}:{},anchor_entry_id:!R&&S==="auto"?this.retryAnchor(o,v?.anchorEntryId):o.sessionManager.getLeafId?.()??void 0,...B===void 0?{}:{provider_error:B}};T.retry.disposition==="auto"?await p(o,T):(await f(o),await g(T))}retryAnchor(e,t){const n=e.sessionManager.getLeafId?.()??void 0;return t!==void 0&&!this.hasProgressPastAnchor(e,t)?t:n}async recoverOnStartup(){const e=this.deps.installedGeneration();if(e===void 0)return"none";const t=(await this.state()).episode,n=this.sessionId(e.session);if(this.deps.cfg.newCycle===!0)return t!==null&&n!==null&&t.session_id===n&&await k(this.deps.nodeId,{op:"invalidate",session_id:n}),"none";if(t===null||n===null||t.session_id!==n)return"none";if(t.state==="admitted")if(t.fault.anchor_entry_id!==void 0&&!this.hasProgressPastAnchor(e.session,t.fault.anchor_entry_id)){if(!(await k(this.deps.nodeId,{op:"reopen",session_id:n})).applied)return"none"}else return await this.clearEpisode(e.session),"none";else if(t.state!=="pending")return"none";return await this.recordFault(A(I(t.fault))),await this.schedule(),"retry-armed"}hasProgressPastAnchor(e,t){const n=e.sessionManager.getBranch(),r=n.findIndex(a=>a.id===t);return(r===-1?n:n.slice(r+1)).some(a=>{if(a.type!=="message")return!1;const{message:l}=a;return l.role==="toolResult"?!0:l.role==="assistant"&&Array.isArray(l.content)&&l.content.length>0})}async schedule(){const e=this.deps.installedGeneration(),t=this.deps.currentSession();if(t.isStreaming||this.timer!==null)return;if(e===void 0){this.abandonTurnEnd();return}const n=await this.state(),r=n.fault;if(!this.isActiveAutoFault(r)||this.sessionId(t)!==null&&!this.pendingEpisodeMatches(t,r,n)){this.clearTimer(),this.abandonTurnEnd();return}const s=Date.now(),a=this.faultRetryDelayMs(r,s);if(a!==null&&this.deps.turnEnds?.holding()===!0&&Re(r.since,a,s)){await this.endRunTurnOnFault(t,r,a);return}a!==null?this.clearAndArmFaultRetry(a,e):this.abandonTurnEnd()}async endRunTurnOnFault(e,t,n){this.clearTimer(),this.abandonTurnEnd(),F({level:"info",event:"broker.runtime.diagnostic",fields:{message:`run turn completed on provider fault: ${t.kind} retry in ${n}ms is past the ${ie}ms turn window (since ${t.since}, attempt ${t.retry.attempt??0})`}}),await this.clearEpisode(e),await this.clearFault("pi\u2192provider")}async rearmIfStranded(){if(this.timer!==null||this.deps.installedGeneration()===void 0)return;const e=this.deps.currentSession();if(e.isStreaming)return;const t=await this.state();!this.isActiveAutoFault(t.fault)||!this.pendingEpisodeMatches(e,t.fault,t)||await this.schedule()}maybeSwitchProviderForRetry(e){if(e.type!=="auto_retry_start")return;const t=C("pi\u2192provider",e.errorMessage??"").kind;if(t!=="rate-limit"&&t!=="connection"||this.providerFallbackInFlight!==null||this.deps.cfg.modelExact===!0)return;const n=this.deps.installedGeneration();if(n===void 0)return;const r=this.deps.currentSession(),s=this.deps.currentServices(),a=this.deps.formatModelSpec(r.model,r.thinkingLevel);if(a===null)return;const l=H(this.deps.cfg.model,j(),!1);if(l===void 0)return;const c=z(l,Y()??this.deps.cfg.cwd,q()),o=ee(a),f=ae(c,o),g=f===void 0?{route:void 0,capacityExhausted:!1}:this.nextEligibleFallbackRoute(c.slice(f+1),this.deps.registryOf(s),r.getContextUsage()?.tokens,u=>!this.failedRetryRoutes.has(u.routeId));if(g.route===void 0){g.capacityExhausted&&(this.stageCapacityFailure(n,r),queueMicrotask(()=>{this.deps.installedGeneration()===n&&this.deps.currentSession()===r&&r.abortRetry()}));return}const p=g.route,y=(async()=>{const u=this.deps.registryOf(s).find(p.providerId,p.modelId);if(!u||(await r.setModel(u),this.deps.installedGeneration()!==n||this.deps.currentSession()!==r))return;const h=`${p.providerId}/${p.modelId}`===o.modelSpec?o.thinkingLevel:p.thinkingLevel;r.setThinkingLevel(h??"off"),this.failedRetryRoutes.add(p.routeId),await this.deps.broadcastModelChanged(!1)})().catch(u=>{F({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`provider route fallback failed: ${u instanceof Error?u.message:String(u)}`}})}).finally(()=>{this.deps.installedGeneration()===n&&(this.providerFallbackInFlight=null)});this.providerFallbackInFlight=y}maybeFallbackOnUnusableModel(e){if(e.type!=="message_end"||e.message.role!=="assistant"||e.message.stopReason!=="error")return;const t=pe(e.message.errorMessage??"");if(!t&&!fe(e.message.errorMessage??"")||this.deps.cfg.modelExact===!0||this.notFoundFallbackInFlight!==null)return;const n=this.deps.installedGeneration();if(n===void 0)return;const r=this.deps.currentSession(),s=this.deps.currentServices(),a=this.deps.formatModelSpec(r.model,r.thinkingLevel);if(a===null)return;const l=ee(a);this.notFoundModels.add(l.modelSpec);const c=H(this.deps.cfg.model,j(),!1),o=c?z(c,Y()??this.deps.cfg.cwd,q()):[],f=ae(o,l),g=f===void 0?{route:void 0,capacityExhausted:!1}:this.nextEligibleFallbackRoute(o.slice(f+1),this.deps.registryOf(s),r.getContextUsage()?.tokens,h=>!this.notFoundModels.has(`${h.providerId}/${h.modelId}`));if(g.route===void 0){g.capacityExhausted&&this.stageCapacityFailure(n,r);return}const p=g.route,y=`${p.providerId}/${p.modelId}${p.thinkingLevel?`:${p.thinkingLevel}`:""}`,u=(async()=>{const h=this.deps.registryOf(s).find(p.providerId,p.modelId);if(h===void 0){F({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`${t?"unconfigured-provider":"not_found"} fallback skipped: ${y} is not registered`}});return}if(await r.setModel(h),this.deps.installedGeneration()!==n||this.deps.currentSession()!==r)return;r.setThinkingLevel(p.thinkingLevel??"off"),await this.deps.broadcastModelChanged();const E=this.deps.formatModelSpec(r.model,r.thinkingLevel)??y;if(F({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`${t?"unconfigured-provider":"not_found"} fallback: ${a} unavailable \u2192 ${E}; re-driving turn`}}),await r.agent.waitForIdle(),this.deps.installedGeneration()!==n||this.deps.currentSession()!==r)return;t&&await this.clearFault("pi\u2192provider");const w=Q("recovery",{},be(a,E,t?"credential":"not-found"));ne(w,{type:"runtime",kind:"recovery"}),await this.deps.turnAdmission.admit(v=>K.fresh(()=>r.prompt(w,{preflightResult:v})))})().catch(h=>{F({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`${t?"unconfigured-provider":"not_found"} fallback failed: ${h instanceof Error?h.message:String(h)}`}})}).finally(()=>{this.deps.installedGeneration()===n&&(this.notFoundFallbackInFlight=null)});this.notFoundFallbackInFlight=u}nextEligibleFallbackRoute(e,t,n,r){let s=0,a=0;for(const l of e){if(!r(l)||ke([{...l,...ye(l,t),automaticFallback:!0}]).candidates.length===0)continue;const c=t.find(l.providerId,l.modelId);if(c!==void 0){if(s++,this.targetCannotFitContext(c.contextWindow,n)){a++;continue}return{route:l,capacityExhausted:!1}}}return{route:void 0,capacityExhausted:s>0&&s===a}}targetCannotFitContext(e,t){return Number.isFinite(e)&&Number.isFinite(t)&&e<t}stageCapacityFailure(e,t){const n=t.getContextUsage()?.tokens;this.stagedCapacityFailure={generation:e,errorMessage:Number.isFinite(n)?`All available fallback models have context windows smaller than the current conversation (${n} tokens).`:"All available fallback models have context windows smaller than the current conversation."}}async recordPendingProviderFault(e,t){const n=this.sessionId(e);if(n===null){await this.recordFault(t);return}await k(this.deps.nodeId,{op:"record-pending",session_id:n,input:t})}pendingEpisodeMatches(e,t,n){const r=this.sessionId(e),s=n.episode;return r!==null&&s!==null&&s.state==="pending"&&s.session_id===r&&s.fault.fault_id===t.fault_id}faultRetryDelayMs(e,t){if(e.retry.nextAt!==void 0){const n=Date.parse(e.retry.nextAt)-t;return Number.isFinite(n)?Math.max(0,n):null}return P(I(e),t)?.delayMs??null}clearAndArmFaultRetry(e,t){if(this.deps.installedGeneration()!==t)return;this.clearTimer();const n=Math.min(Math.max(0,e),Ee),r=Math.max(0,e-n);this.timer=setTimeout(()=>{if(this.timer=null,this.deps.installedGeneration()===t){if(r>0){this.clearAndArmFaultRetry(r,t);return}this.runFaultRetry().catch(s=>{this.abandonTurnEnd(),F({level:"warn",event:"broker.runtime.diagnostic",fields:{message:`fault retry fire failed: ${s instanceof Error?s.message:String(s)}`}})})}},n),typeof this.timer.unref=="function"&&this.timer.unref()}async runFaultRetry(){await this.attemptFaultRetry()===void 0&&this.abandonTurnEnd()}async attemptFaultRetry(){const e=this.deps.installedGeneration();if(e===void 0)return;const t=e.session;if(t.isStreaming)return;const n=(await this.state()).fault;if(!this.isActiveAutoFault(n))return;let r=n;if(n.kind==="connection"&&this.deps.turnEnds===void 0){const y=await this.deps.retryProbe();if(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t||t.isStreaming)return;const u=(await this.state()).fault;if(!this.isActiveAutoFault(u)||u.since!==n.since)return;if(!y)return this.clearAndArmFaultRetry(Z(u.kind).baseDelayMs,e),"rearmed";r=u}const s=this.deps.turnEnds!==void 0,a=r.retry.attempt??0,l=Z(r.kind);if(a>=l.max){await this.recordFault(A(O(I(r)))),await this.clearEpisode(t);return}const c=P(I(r),Date.now());if(c===null){await this.recordFault(A(O(I(r)))),await this.clearEpisode(t);return}if(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t)return;let o,f;const g=await this.deps.turnAdmission.admit(async y=>{const u=(await this.state()).fault;if(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t||t.isStreaming||!this.isActiveAutoFault(u)||u.since!==r.since){y();return}const h=this.sessionId(t);if(h===null){y();return}if(!(await k(this.deps.nodeId,{op:"admit",session_id:h,fault_id:u.fault_id,retry:c.fault.retry})).applied){y();return}const w=r.anchor_entry_id;if(w!==void 0&&(t.sessionManager.getLeafId?.()??null)!==w&&!this.hasProgressPastAnchor(t,w))try{const d=await t.navigateTree(w);if(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t){y();return}d.cancelled||this.deps.reWelcomeAll()}catch(d){F({level:"debug",event:"broker.runtime.diagnostic",fields:{message:`fault retry rewind skipped: ${d instanceof Error?d.message:String(d)}`}})}const v=Q("recovery",s?{attempt:c.attempt}:{attempt:c.attempt,max:c.max},Me(r.kind,s));ne(v,{type:"runtime",kind:"recovery"}),this.deps.turnEnds?.beginRecovery();const $=this.agentStarts;try{await K.fresh(()=>t.prompt(v,{preflightResult:y}))}catch(d){o={error:d,faultId:u.fault_id,startsBefore:$},oe(d)||(f={code:"model_error",message:`recovery prompt rejected: ${se(d)}`});return}finally{this.deps.turnEnds?.recoveryReturned(f)}return"prompted"});if(o===void 0)return g;const p=await this.settleRejectedRecoveryPrompt(e,t,o);if(p==="stale")throw o.error;return p==="rearmed"?"rearmed":void 0}async settleRejectedRecoveryPrompt(e,t,n){const r=this.sessionId(t);if(r===null)return"stale";const s=m(()=>{if(this.deps.installedGeneration()!==e||this.deps.currentSession()!==t||t.isStreaming||this.agentStarts!==n.startsBefore)throw new _},"assertCurrent"),a=se(n.error),l=oe(n.error);try{let c;return await this.retryWhileDaemonUnreachable("rejected recovery prompt settlement",async()=>{s();const{episode:o}=await this.state();if(s(),o===null||o.state!=="admitted"||o.session_id!==r||o.fault.fault_id!==n.faultId)throw new _;const f=I(o.fault),g=l&&this.deps.turnEnds===void 0?P(f,Date.now()):null,p=g===null?O(l?f:{...f,kind:"other",message:`recovery prompt rejected: ${a}`}):{...f,retry:{...f.retry,nextAt:g.nextAt}};if(s(),!(await k(this.deps.nodeId,{op:"settle-rejected",session_id:r,fault_id:n.faultId,reopen:g!==null,input:A(p)})).applied)throw new _;c=g?.nextAt},s),c===void 0?(F({level:"warn",event:"broker.runtime.diagnostic",fields:{message:`recovery prompt rejected, ending the retry episode: ${a}`}}),"ended"):(F({level:"warn",event:"broker.runtime.diagnostic",fields:{message:`recovery prompt rejected before any turn started, retrying at ${c}: ${a}`}}),await this.schedule(),"rearmed")}catch(c){if(!(c instanceof _))throw c;return"stale"}}}export{Ze as FaultRetry,ie as RUN_TURN_RETRY_WINDOW_MS,Te as isPrefillRejectionDuringEpisode,Me as retryCardBody,Re as runTurnRetryExpired,de as settledProviderFault,Qe as willDaemonRetry};
@@ -27,8 +27,9 @@ export declare class RunTurnEnds {
27
27
  settled(writer: TurnEndWriter, end: TurnEnd, retryPending: boolean): void;
28
28
  /** The daemon's fault retry is about to prompt its recovery turn. */
29
29
  beginRecovery(): void;
30
- /** The recovery prompt returned. If its turn never started (preflight failed or an input handler consumed it), no recovery runs. */
31
- recoveryReturned(): void;
30
+ /** The recovery prompt returned. If its turn never started (preflight failed or an input handler consumed it), no recovery runs and
31
+ * the held end is written, with `rejected` as its error when the prompt rejected for a reason a retry cannot cure. */
32
+ recoveryReturned(rejected?: TurnError): void;
32
33
  /** No recovery turn will run (retries exhausted or abandoned, the prompt failed, the session was replaced): end the turn now. */
33
34
  abandon(): void;
34
35
  /** Whether a turn end is held for a recovery turn. */
@@ -1 +1 @@
1
- var h=Object.defineProperty;var n=(t,e)=>h(t,"name",{value:e,configurable:!0});const s="crtr-turn-end";class l{static{n(this,"RunTurnEnds")}held;recoveryStarting=!1;started(){if(this.recoveryStarting){this.recoveryStarting=!1,this.held=void 0;return}this.flush()}settled(e,r,i){this.flush(),i?this.held={writer:e,end:r}:e.appendCustomEntry(s,r)}beginRecovery(){this.recoveryStarting=!0}recoveryReturned(){this.recoveryStarting&&this.abandon()}abandon(){this.recoveryStarting=!1,this.flush()}holding(){return this.held!==void 0}flush(){const e=this.held;e!==void 0&&(this.held=void 0,e.writer.appendCustomEntry(s,e.end))}}export{l as RunTurnEnds,s as TURN_END_ENTRY};
1
+ var d=Object.defineProperty;var r=(t,e)=>d(t,"name",{value:e,configurable:!0});const i="crtr-turn-end";class l{static{r(this,"RunTurnEnds")}held;recoveryStarting=!1;started(){if(this.recoveryStarting){this.recoveryStarting=!1,this.held=void 0;return}this.flush()}settled(e,n,s){this.flush(),s?this.held={writer:e,end:n}:e.appendCustomEntry(i,n)}beginRecovery(){this.recoveryStarting=!0}recoveryReturned(e){this.recoveryStarting&&(e!==void 0&&this.held!==void 0&&(this.held={...this.held,end:{...this.held.end,error:e}}),this.abandon())}abandon(){this.recoveryStarting=!1,this.flush()}holding(){return this.held!==void 0}flush(){const e=this.held;e!==void 0&&(this.held=void 0,e.writer.appendCustomEntry(i,e.end))}}export{l as RunTurnEnds,i as TURN_END_ENTRY};
@@ -17,6 +17,10 @@ export declare function transition(nodeId: string, event: LifecycleEvent, opts?:
17
17
  reason?: TerminalReason;
18
18
  outcome?: TerminalOutcomeInput;
19
19
  }): Promise<NodeMeta>;
20
+ /** The last relay generation published when it bound. Missing means no replacement has bound yet. */
21
+ export declare function brokerRelayGeneration(nodeId: string): string | null;
22
+ /** The marker names the relay generation the committed disposition released. */
23
+ export declare function recordBrokerRelease(nodeId: string, relay: string | null): void;
20
24
  /** Whether this row is a PARKED node — terminalized by the unattended clock
21
25
  * rather than by finishing its own work. The one read of the park marker:
22
26
  * `done` qualified by `intent='parked'`, both written in the same atomic
@@ -1,2 +1,2 @@
1
- var x=Object.defineProperty;var a=(e,t)=>x(e,"name",{value:t,configurable:!0});import{getNode as O,cancelCronsOnWake as J,transitionStatusIntent as b}from"../canvas/index.js";import{composeNodeOutcomeDocument as A}from"./outcome-document.js";import{withCanvasWrite as E}from"../canvas/db.js";import{nodeObject as D}from"../graph/objects.js";import{endMessageSubscriptions as L}from"../graph/watches.js";import{settleNodeObjectWaits as P}from"../canvas/waits.js";import{runStructuredRequestEffects as j,settlePendingStructuredRequestForNodeIn as $}from"../canvas/structured-requests.js";const m="*",q={finish:"finished",park:"parked",cancel:"stranded",crash:"crashed","refresh-launch":null,revive:null},l=["active","idle"],M={finish:{status:"done",intent:"done",from:l},park:{status:"done",intent:"parked",from:l},cancel:{status:"canceled",intent:null,from:m},crash:{status:"dead",from:l},yield:{intent:"refresh",from:l},release:{status:"idle",intent:"idle-release",from:[...l,"done"]},"refresh-launch":{status:"active",intent:"refresh",from:m},revive:{status:"active",intent:null,from:m}};function T(e,t){const r=e.split(`
2
- `)[0]??"";let n=0,c="";for(const d of r){const s=Buffer.byteLength(d,"utf8");if(n+s>t)break;n+=s,c+=d}return c}a(T,"firstLineAtMost");function W(e){if(e===void 0)return null;const t=e.message===void 0?void 0:T(e.message,500),r={...e,...t===void 0?{}:{message:t}},n=JSON.stringify(r);return Buffer.byteLength(n,"utf8")<=4*1024?n:JSON.stringify({schema:"crtr.node-outcome-detail/v1",truncated:!0,...t===void 0?{}:{message:t}})}a(W,"detailJson");function B(e,t){let r,n;return t==="finished"||t==="declined"?(r="done",n="done"):t==="parked"?(r="done",n="parked"):t==="closed"||t==="retired"||t==="stranded"||t==="deadline_exceeded"?(r="canceled",n=null):(r="dead",n=null),{...e,status:r,intent:n,terminal_reason:t}}a(B,"nodeAtOutcome");async function K(e,t,r){const n=M[t],c=Object.prototype.hasOwnProperty.call(n,"status"),d=Object.prototype.hasOwnProperty.call(n,"intent"),s=r?.reason??q[t],f={};c&&(f.status=n.status),d&&(f.intent=n.intent??null),s!==void 0&&(f.terminal_reason=s);const u=t==="finish"||t==="park"||t==="cancel"||t==="crash",_=u?new Date().toISOString():null,p=s,y=W(r?.outcome?.detail),S=u?s==="deadline_exceeded"?{intended:"expired",failureReason:"node_deadline_exceeded",failureDetail:r?.outcome?.detail??null}:{intended:"failed",failureReason:s,failureDetail:r?.outcome?.detail??null}:null,h=await E(async i=>{const o=u?await O(e):null,w=u&&o?.kind!=="human",N=w&&o?.run_id!=null&&s!=="parked"&&o.outcome_at!==null&&(l.includes(o.status)||o.outcome_reason==="parked");let k;w&&o!==null&&(o.outcome_at===null||N)&&(k=JSON.stringify(await A({node:B(o,p),outcome:{kind:"failure",reason:p,at:_,revision:(o.outcome_revision??0)+1,detailJson:y}})));const g=await b(e,f,{fromStatuses:n.from===m?void 0:n.from,requireNoFinalReport:t==="cancel",...w?{outcome:{kind:"failure",reason:p,detailJson:y,at:_,...N?{supersede:!0}:{},...k===void 0?{}:{documentJson:k}}}:{}});u&&g>0&&(await L(i,await D(i,e),"done"),await P(i,e));const R=g===1&&S!==null?await $(i,e,S):null;return{changes:g,settlement:R}});if(h.changes===0){const i=await O(e);if(i===null)throw new Error(`transition: unknown node ${e}`);if(t==="cancel"&&i.final_report!==null&&i.final_report!==void 0)return i;throw new Error(`illegal lifecycle transition: '${t}' from status='${i.status}' (node ${e})`)}return h.settlement?.changed&&j(h.settlement.row),(t==="finish"||t==="park"||t==="cancel")&&await J(e),await O(e)}a(K,"transition");function C(e){return e.status==="done"&&e.intent==="parked"}a(C,"isParked");function Q(e){return C(e)&&e.lifecycle==="resident"&&e.final_report==null&&e.deleting_at==null}a(Q,"isParkedResident");function U(e){return e?.intent==="idle-release"}a(U,"hasIdleReleaseIntent");export{U as hasIdleReleaseIntent,C as isParked,Q as isParkedResident,K as transition};
1
+ var L=Object.defineProperty;var o=(t,e)=>L(t,"name",{value:e,configurable:!0});import{getNode as y,cancelCronsOnWake as B,transitionStatusIntent as D}from"../canvas/index.js";import{closeSync as J,constants as N,existsSync as j,fstatSync as F,readSync as P}from"node:fs";import{join as E}from"node:path";import{BROKER_RELEASED_MARKER as $,BROKER_RELAY_GENERATION as M,nodeRunDir as x}from"../canvas/paths.js";import{openBeneath as T,writeFileBeneath as W,isAbsentOrRefused as q}from"../spaces/open-beneath.js";import{emitEvent as C}from"../events/emit.js";import{composeNodeOutcomeDocument as K}from"./outcome-document.js";import{afterCommit as Y,withCanvasWrite as G}from"../canvas/db.js";import{nodeObject as z}from"../graph/objects.js";import{endMessageSubscriptions as V}from"../graph/watches.js";import{settleNodeObjectWaits as v}from"../canvas/waits.js";import{runStructuredRequestEffects as H,settlePendingStructuredRequestForNodeIn as I}from"../canvas/structured-requests.js";const p="*",Q={finish:"finished",park:"parked",cancel:"stranded",crash:"crashed","refresh-launch":null,revive:null},u=["active","idle"],U={finish:{status:"done",intent:"done",from:u},park:{status:"done",intent:"parked",from:u},cancel:{status:"canceled",intent:null,from:p},crash:{status:"dead",from:u},yield:{intent:"refresh",from:u},release:{status:"idle",intent:"idle-release",from:[...u,"done"]},"refresh-launch":{status:"active",intent:"refresh",from:p},revive:{status:"active",intent:null,from:p}};function X(t,e){const r=t.split(`
2
+ `)[0]??"";let n=0,l="";for(const c of r){const s=Buffer.byteLength(c,"utf8");if(n+s>e)break;n+=s,l+=c}return l}o(X,"firstLineAtMost");function Z(t){if(t===void 0)return null;const e=t.message===void 0?void 0:X(t.message,500),r={...t,...e===void 0?{}:{message:e}},n=JSON.stringify(r);return Buffer.byteLength(n,"utf8")<=4*1024?n:JSON.stringify({schema:"crtr.node-outcome-detail/v1",truncated:!0,...e===void 0?{}:{message:e}})}o(Z,"detailJson");function ee(t,e){let r,n;return e==="finished"||e==="declined"?(r="done",n="done"):e==="parked"?(r="done",n="parked"):e==="closed"||e==="retired"||e==="stranded"||e==="deadline_exceeded"?(r="canceled",n=null):(r="dead",n=null),{...t,status:r,intent:n,terminal_reason:e}}o(ee,"nodeAtOutcome");async function Oe(t,e,r){const n=U[e],l=Object.prototype.hasOwnProperty.call(n,"status"),c=Object.prototype.hasOwnProperty.call(n,"intent"),s=r?.reason??Q[e],d={};l&&(d.status=n.status),c&&(d.intent=n.intent??null),s!==void 0&&(d.terminal_reason=s);const f=e==="finish"||e==="park"||e==="cancel"||e==="crash",_=f?new Date().toISOString():null,h=s,k=Z(r?.outcome?.detail),S=f?s==="deadline_exceeded"?{intended:"expired",failureReason:"node_deadline_exceeded",failureDetail:r?.outcome?.detail??null}:{intended:"failed",failureReason:s,failureDetail:r?.outcome?.detail??null}:null,w=await G(async i=>{const a=f?await y(t):null,O=f&&a?.kind!=="human",g=O&&a?.run_id!=null&&s!=="parked"&&a.outcome_at!==null&&(u.includes(a.status)||a.outcome_reason==="parked");let R;O&&a!==null&&(a.outcome_at===null||g)&&(R=JSON.stringify(await K({node:ee(a,h),outcome:{kind:"failure",reason:h,at:_,revision:(a.outcome_revision??0)+1,detailJson:k}})));const m=await D(t,d,{fromStatuses:n.from===p?void 0:n.from,requireNoFinalReport:e==="cancel",...O?{outcome:{kind:"failure",reason:h,detailJson:k,at:_,...g?{supersede:!0}:{},...R===void 0?{}:{documentJson:R}}}:{}});f&&m>0&&(await V(i,await z(i,t),"done"),await v(i,t));const A=m===1&&S!==null?await I(i,t,S):null;if(m===1&&(e==="release"||e==="park"||e==="finish"||e==="cancel")){const b=te(t);Y(()=>ne(t,b))}return{changes:m,settlement:A}});if(w.changes===0){const i=await y(t);if(i===null)throw new Error(`transition: unknown node ${t}`);if(e==="cancel"&&i.final_report!==null&&i.final_report!==void 0)return i;throw new Error(`illegal lifecycle transition: '${e}' from status='${i.status}' (node ${t})`)}return w.settlement?.changed&&H(w.settlement.row),(e==="finish"||e==="park"||e==="cancel")&&await B(t),await y(t)}o(Oe,"transition");function te(t){const e=x(t);let r;try{r=T(e,E(e,M),N.O_RDONLY|N.O_NOFOLLOW)}catch(n){if(q(n))return null;throw n}try{if(!F(r).isFile())return null;const n=Buffer.alloc(37),l=P(r,n,0,n.length,0),c=n.toString("utf8",0,l);return l===36&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(c)?c:null}finally{J(r)}}o(te,"brokerRelayGeneration");function ne(t,e){const r=x(t);if(!(e===null||!j(r)))try{W(r,E(r,$),e,{atomic:!0})}catch(n){C({level:"warn",event:"node.lifecycle.release_marker_failed",node_id:t,error:n})}}o(ne,"recordBrokerRelease");function re(t){return t.status==="done"&&t.intent==="parked"}o(re,"isParked");function Re(t){return re(t)&&t.lifecycle==="resident"&&t.final_report==null&&t.deleting_at==null}o(Re,"isParkedResident");function ye(t){return t?.intent==="idle-release"}o(ye,"hasIdleReleaseIntent");export{te as brokerRelayGeneration,ye as hasIdleReleaseIntent,re as isParked,Re as isParkedResident,ne as recordBrokerRelease,Oe as transition};
@@ -1 +1 @@
1
- var K=Object.defineProperty;var l=(e,t)=>K(e,"name",{value:t,configurable:!0});import{envHomeOverride as X,envRuntimeHomeOverride as z,INBOX_BETWEEN_TURNS_ENV as V,SCOPES_FILE_ENV as Y,scopeAllowed as C}from"../../shared/env.js";import{isOwnMemoryScope as Q,validateScopes as Z}from"../scopes.js";import{activeGrant as B,terminalGrant as k}from"../grants/mirror.js";import{grantSnapshotMissing as ee}from"../../daemon/readiness.js";import{appendRunEvent as ne}from"../runs/events.js";import{readConfig as S}from"../config.js";import{InputError as T}from"../io.js";import{ApiError as g}from"../../api/errors.js";import{assertGranteeRunsHere as re}from"./unsandboxed.js";import{randomBytes as te}from"node:crypto";import{existsSync as P}from"node:fs";import{resolve as ie,relative as ae,isAbsolute as U,join as oe,sep as le}from"node:path";import{appSpace as se}from"../layout.js";import{scopesFilePath as de}from"../canvas/paths.js";import{createNode as ce,assertRunNotDeleting as ue,conversationOwner as fe,getNode as u,getRow as pe}from"../canvas/index.js";import{ensureAppSpace as me}from"../spaces/space.js";import{LAUNCHER_SOCKET as H}from"../../launcher/spec.js";import{resolveInstallId as _e}from"../canvas/install-id.js";import{isAppRun as we}from"../canvas/app-run.js";import{contextDir as ge}from"../canvas/paths.js";import{conversationIdFromPath as he}from"../conversation-store/index.js";import{usage as L}from"../errors.js";import{assertProfileAvailableForBirth as ve}from"../profiles/deletion-reservation.js";import{loadExactProfileManifest as Re}from"../profiles/manifest.js";import{admitNodeCreate as ye}from"../command-hooks/lifecycle-create.js";import{getById as Ee,nodeObject as I,ownerForGrantee as Ce}from"../graph/objects.js";import{recordActedOn as Ie}from"../graph/edges.js";import{recordWatch as Ne,copyWatchesForFork as ke}from"../graph/watches.js";import{publish as Se}from"../graph/events.js";import{canvasDatabaseUrl as Te}from"../canvas/db.js";import{someAsync as Oe}from"../async-array.js";let O;const j=new Map;function wn(e){O=e}l(wn,"setInstallId");async function xe(){if(O!==void 0)return O;const e=await Te(),t=j.get(e);if(t!==void 0)return t;const a=await _e();return j.set(e,a),a}l(xe,"installId");function De(){return`${Date.now().toString(36)}-${te(4).toString("hex")}`}l(De,"newJobId");async function be(){return`${await xe()}-${De()}`}l(be,"newNodeId");const x=128,Ae=/^[a-z0-9-]+$/;class q extends Error{static{l(this,"NodeIdConflictError")}nodeId;constructor(t){super(`node already exists: ${t}`),this.nodeId=t,this.name="NodeIdConflictError"}}function D(e){if(e.length===0||e.length>x)throw L(`invalid --node-id '${e}': must be 1-${x} characters`,{next:`Pass an id-safe string (lowercase letters, digits, hyphens) of at most ${x} characters.`});if(!Ae.test(e))throw L(`invalid --node-id '${e}': must contain only lowercase letters, digits, and hyphens (e.g. 'remote42', 'my-node-1')`,{next:"Use only lowercase letters, digits, and hyphens."})}l(D,"validateNodeIdFormat");async function gn(e){if(D(e),await u(e)!==null)throw new q(e)}l(gn,"preflightNodeId");async function Me(e){if(D(e),await u(e)!==null)throw new q(e);return e}l(Me,"validatedExplicitNodeId");async function hn(e){let t=e;const a=new Set;for(;;){if(a.has(t))return t;a.add(t);const s=await pe(t);if(s===null||s.parent==null)return t;t=s.parent}}l(hn,"rootOfSpine");function vn(){const e=process.env;return{nodeId:e.CRTR_NODE_ID??null,kind:e.CRTR_KIND??null,mode:e.CRTR_MODE??null}}l(vn,"currentNodeContext");function $e(e){return we(e)}l($e,"inboxBetweenTurns");function Rn(e){const t={CRTR_NODE_ID:e.node_id,CRTR_KIND:e.kind,CRTR_MODE:e.mode,CRTR_LIFECYCLE:e.lifecycle,CRTR_NODE_CWD:e.cwd,CRTR_CONTEXT_DIR:ge(e.node_id,e.grantee),CRTR_CYCLES:(e.cycles??0).toString(),CRTR_PROFILE_ID:e.profile_id??"",PI_CLAUDE_OAUTH_REINJECT_SCOPE:"never"};e.parent&&(t.CRTR_PARENT_NODE_ID=e.parent),e.run_id&&(t.CRTR_RUN_ID=e.run_id),$e(e)&&(t[V]="1"),e.scopes!==null&&e.scopes!==void 0&&(t[Y]=de(e.node_id)),t.CRTR_GRANTEE=e.grantee??"app:terminal";const a=X();a!==void 0&&a!==""&&(t.CRTR_HOME=a);const s=z();s!==void 0&&s!==""&&(t.CRTR_RUNTIME_HOME=s);const f=process.env.CRTR_SUBTREE;return f!==void 0&&f!==""&&(t.CRTR_SUBTREE=f),{...t,...e.launch?.env??{}}}l(Rn,"nodeEnv");async function Fe(e){const t=he(e),a=t===null?null:await fe(t);return a===null?null:await u(a)}l(Fe,"forkSourceNode");async function yn(e){const t=e.nodeId!==void 0?(D(e.nodeId),e.nodeId):await be(),a=e.parent??null,s=a===null,f=e.spawnedBy??a,m=e.creator??null,b=e.mode??"base",N=e.lifecycle??(s?"resident":"terminal"),h=e.run===void 0?await k():null,n={node_id:t,name:e.name??e.kind,description:e.description,cycles:0,created:new Date().toISOString(),cwd:e.cwd,host_kind:"broker",profile_id:e.profile_id??null,kind:e.kind,mode:b,lifecycle:N,persona_ack:{kind:e.kind,mode:b,lifecycle:N},status:"active",parent:a,spawned_by:f,creator:m,fork_from:e.forkFrom,fork_source_file:e.forkSourceFile,review_binding:e.reviewBinding,model_override:e.modelOverride??null,scopes:e.scopes??null,isolated:e.isolated===!0,grantee:e.run?.grantee??"app:terminal",sub:e.run?.sub??h?.sub??null,grant_id:e.run?.grant_id??h?.id??null,client_id:e.run?.client_id??null,metadata_json:s?e.run?.metadata_json??null:null,run_id:s?t:e.run?.run_id??null,event_cursor:s?e.run?.event_cursor??1:null,usage_json:s?e.run?.usage_json??JSON.stringify({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}):null,passive_default:e.passiveDefault??!1,...e.managedWorktree!==void 0?{managed_worktree:e.managedWorktree}:{},intent:null,deadline_at:e.deadlineAt??null,pi_session_id:null,pi_session_file:null,launch:e.launch},y=e.scopes===void 0?void 0:Z(e.scopes),v=a===null?null:await u(a);if(a!==null&&v===null)throw new Error(`cannot spawn under unknown parent node: ${a}`);if(v?.run_id&&(await u(v.run_id))?.deleting_at)throw new Error("run is deleting");const c=m===null?null:await u(m);if(m!==null&&c===null)throw new Error(`cannot spawn from unknown creator node: ${m}`);if(c?.run_id&&(await u(c.run_id))?.deleting_at)throw new Error("run is deleting");const A=e.forkSourceFile==null?null:await Fe(e.forkSourceFile),M=e.replaces==null?null:await u(e.replaces),_=v??A??M??c??(f===null?null:await u(f)),W=[v,c,A,M,f===null?null:await u(f)],$=await Oe(W,async r=>r?.isolated===!0||r?.run_id!=null&&(await u(r.run_id))?.isolated===!0);if($&&e.isolated===!1)throw new g(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});if(n.isolated=$||e.isolated===!0,_!==null){const r=c??_;if(c&&_.grantee&&c.grantee&&c.grantee!==_.grantee)throw new g(403,"other_app_run","cannot create under another app's node");const i=c&&c!==_?[_,c]:[_],d=[],w=new Set;for(const o of i){o.scopes!==null&&o.scopes!==void 0&&d.push(o.scopes);const p=o.grantee==="app:terminal"?await k():o.grant_id?await B(o.grant_id):null;if(p){const R=[...p.resolved];w.add(R),d.push(R)}else if(!(o.grantee==="app:terminal"&&!S("user").runtime?.issuer)){const R=[];w.add(R),d.push(R)}}const E=l(o=>Q(o,r.grantee==="user:local"?"app:terminal":r.grantee??"app:terminal"),"ownMemory"),F=l((o,p)=>E(p)&&w.has(o)||C(o,p),"allowed");if(y!==void 0&&!y.every(o=>d.every(p=>F(p,o))))throw new T({error:"unknown_scope",message:"child scopes must be covered by the creating node, its source and their current grants",received:y,field:"scopes",next:"Request only scopes the creating node currently holds."});if(n.scopes=y??(i.every(o=>o.scopes==null)&&i.every(o=>o.grant_id===r.grant_id&&o.sub===r.sub)?null:i.length===1?_.scopes??null:[...new Set(d.flat().filter(o=>d.every(p=>F(p,o))))]),n.scopes?.includes("crtr:act")&&!C(n.scopes,"crtr:llm"))throw new T({error:"invalid_scope",message:"crtr:act requires crtr:llm",received:n.scopes,field:"scopes",next:"Include crtr:llm or omit crtr:act."});n.grantee=r.grantee==="user:local"?"app:terminal":r.grantee,n.sub=r.sub??(n.grantee==="app:terminal"?h?.sub:null)??null,n.grant_id=r.grant_id??(n.grantee==="app:terminal"?h?.id:null)??null,n.client_id=r.client_id,n.run_id=s?t:r.run_id??null}if(re(n.grantee),e.run===void 0&&n.grantee==="app:terminal"&&S("user").runtime?.issuer&&h===null)throw await ee()?new g(503,"runtime_starting","the first grant snapshot from the directory has not committed"):new g(403,"scope_missing","terminal grant is not active");if(n.isolated){if(process.platform!=="linux"||!P(H))throw new g(400,"isolated_unavailable","isolated nodes require the Linux root launcher");if(e.managedWorktree!==void 0)throw new g(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const r=oe(se(n.grantee??"app:terminal").nodes,t),i=ae(r,ie(n.cwd));if(!U(n.cwd)||i===".."||i.startsWith(`..${le}`)||U(i))throw new g(400,"invalid_request","isolated node cwd must be inside its own folder",{param:"cwd"})}if(n.profile_id){const r=Re(n.profile_id).manifest.grantee??"app:terminal";if(r!==n.grantee){const i=`crtr:memory:rw:${r}:profile:${n.profile_id}`,d=n.grantee==="app:terminal"?await k():n.grant_id?await B(n.grant_id):null;if(!(n.grantee==="app:terminal"&&!S("user").runtime?.issuer)&&(!d||d.grantee!==n.grantee||!C(d.resolved,i))||!C(n.scopes,i))throw new T({error:"scope_missing",message:`missing scope ${i}`,received:n.profile_id,field:"profile",next:"Select a profile covered by this run and its current grant."})}}if(ve(n.profile_id??null),!n.grantee)throw new Error("node creation requires an app");const G=await ye({kind:n.kind,mode:n.mode,cwd:n.cwd,profile:n.profile_id??null,root:s,parentId:a,requestingNodeId:m,lifecycle:N,replaces:e.replaces??null},n.grantee),J=l(async(r,i)=>{const d=await I(r,i.node_id),w=m??a??e.actor??f??null,E=w!==null&&(await Ee(r,w))?.type==="node"?await I(r,w):await Ce(r,e.run===void 0?"user:local":i.grantee);await Ie(r,E,d),a!==null&&e.subscribe!==!1&&await Ne(r,await I(r,a),d,"spawn"),e.forkSourceNode&&await ke(r,await I(r,e.forkSourceNode),d),await Se(r,{event:"created",object:d,actor:E,attrs:{name:i.name,kind:i.kind,creator:m??null}})},"recordBirth");try{e.nodeId!==void 0&&await Me(e.nodeId),await e.beforeCreate?.(),(process.platform!=="linux"||P(H))&&await me(n.grantee??"app:terminal"),await ce(n,{outcomeDelivery:e.outcomeDelivery,withinCreate:l(async(r,i)=>{i.run_id&&i.run_id!==i.node_id&&await ue(r,i.run_id),s&&e.run===void 0&&await ne(r,t,t,"run.started",{scopes:i.scopes??null,payer:"user",usage:JSON.parse(i.usage_json)}),await e.withinCreate?.(r,i),await J(r,i)},"withinCreate")})}finally{G()}return n}l(yn,"spawnNode");export{q as NodeIdConflictError,vn as currentNodeContext,Fe as forkSourceNode,$e as inboxBetweenTurns,De as newJobId,be as newNodeId,Rn as nodeEnv,gn as preflightNodeId,hn as rootOfSpine,wn as setInstallId,yn as spawnNode};
1
+ var K=Object.defineProperty;var l=(e,r)=>K(e,"name",{value:r,configurable:!0});import{envHomeOverride as X,envRuntimeHomeOverride as z,INBOX_BETWEEN_TURNS_ENV as V,SCOPES_FILE_ENV as Y,scopeAllowed as C}from"../../shared/env.js";import{isOwnMemoryScope as Q,validateScopes as Z}from"../scopes.js";import{activeGrant as B,terminalGrant as k}from"../grants/mirror.js";import{grantSnapshotMissing as ee}from"../../daemon/readiness.js";import{appendRunEvent as ne}from"../runs/events.js";import{readConfig as S}from"../config.js";import{InputError as T}from"../io.js";import{ApiError as g}from"../../api/errors.js";import{assertGranteeRunsHere as re}from"./unsandboxed.js";import{randomBytes as te}from"node:crypto";import{existsSync as P}from"node:fs";import{resolve as ie,relative as ae,isAbsolute as U,join as oe,sep as le}from"node:path";import{appSpace as se}from"../layout.js";import{scopesFilePath as de}from"../canvas/paths.js";import{createNode as ce,assertRunNotDeleting as ue,conversationOwner as fe,getNode as u,getRow as me}from"../canvas/index.js";import{ensureAppSpace as pe}from"../spaces/space.js";import{LAUNCHER_SOCKET as H}from"../../launcher/spec.js";import{resolveInstallId as _e}from"../canvas/install-id.js";import{isAppRun as we}from"../canvas/app-run.js";import{contextDir as ge}from"../canvas/paths.js";import{conversationIdFromPath as he}from"../conversation-store/index.js";import{usage as L}from"../errors.js";import{assertProfileAvailableForBirth as ve}from"../profiles/deletion-reservation.js";import{loadExactProfileManifest as Re}from"../profiles/manifest.js";import{admitNodeCreate as ye}from"../command-hooks/lifecycle-create.js";import{getById as Ee,nodeObject as I,ownerForGrantee as Ce}from"../graph/objects.js";import{recordActedOn as Ie}from"../graph/edges.js";import{recordWatch as Ne,copyWatchesForFork as ke}from"../graph/watches.js";import{publish as Se}from"../graph/events.js";import{canvasDatabaseUrl as Te}from"../canvas/db.js";import{someAsync as Oe}from"../async-array.js";import{admitIntoManagedCheckout as xe}from"../worktree-mutation-async.js";let O;const j=new Map;function hn(e){O=e}l(hn,"setInstallId");async function De(){if(O!==void 0)return O;const e=await Te(),r=j.get(e);if(r!==void 0)return r;const a=await _e();return j.set(e,a),a}l(De,"installId");function be(){return`${Date.now().toString(36)}-${te(4).toString("hex")}`}l(be,"newJobId");async function Ae(){return`${await De()}-${be()}`}l(Ae,"newNodeId");const x=128,Me=/^[a-z0-9-]+$/;class q extends Error{static{l(this,"NodeIdConflictError")}nodeId;constructor(r){super(`node already exists: ${r}`),this.nodeId=r,this.name="NodeIdConflictError"}}function D(e){if(e.length===0||e.length>x)throw L(`invalid --node-id '${e}': must be 1-${x} characters`,{next:`Pass an id-safe string (lowercase letters, digits, hyphens) of at most ${x} characters.`});if(!Me.test(e))throw L(`invalid --node-id '${e}': must contain only lowercase letters, digits, and hyphens (e.g. 'remote42', 'my-node-1')`,{next:"Use only lowercase letters, digits, and hyphens."})}l(D,"validateNodeIdFormat");async function vn(e){if(D(e),await u(e)!==null)throw new q(e)}l(vn,"preflightNodeId");async function $e(e){if(D(e),await u(e)!==null)throw new q(e);return e}l($e,"validatedExplicitNodeId");async function Rn(e){let r=e;const a=new Set;for(;;){if(a.has(r))return r;a.add(r);const s=await me(r);if(s===null||s.parent==null)return r;r=s.parent}}l(Rn,"rootOfSpine");function yn(){const e=process.env;return{nodeId:e.CRTR_NODE_ID??null,kind:e.CRTR_KIND??null,mode:e.CRTR_MODE??null}}l(yn,"currentNodeContext");function Fe(e){return we(e)}l(Fe,"inboxBetweenTurns");function En(e){const r={CRTR_NODE_ID:e.node_id,CRTR_KIND:e.kind,CRTR_MODE:e.mode,CRTR_LIFECYCLE:e.lifecycle,CRTR_NODE_CWD:e.cwd,CRTR_CONTEXT_DIR:ge(e.node_id,e.grantee),CRTR_CYCLES:(e.cycles??0).toString(),CRTR_PROFILE_ID:e.profile_id??"",PI_CLAUDE_OAUTH_REINJECT_SCOPE:"never"};e.parent&&(r.CRTR_PARENT_NODE_ID=e.parent),e.run_id&&(r.CRTR_RUN_ID=e.run_id),Fe(e)&&(r[V]="1"),e.scopes!==null&&e.scopes!==void 0&&(r[Y]=de(e.node_id)),r.CRTR_GRANTEE=e.grantee??"app:terminal";const a=X();a!==void 0&&a!==""&&(r.CRTR_HOME=a);const s=z();s!==void 0&&s!==""&&(r.CRTR_RUNTIME_HOME=s);const f=process.env.CRTR_SUBTREE;return f!==void 0&&f!==""&&(r.CRTR_SUBTREE=f),{...r,...e.launch?.env??{}}}l(En,"nodeEnv");async function Be(e){const r=he(e),a=r===null?null:await fe(r);return a===null?null:await u(a)}l(Be,"forkSourceNode");async function Cn(e){const r=e.nodeId!==void 0?(D(e.nodeId),e.nodeId):await Ae(),a=e.parent??null,s=a===null,f=e.spawnedBy??a,p=e.creator??null,b=e.mode??"base",N=e.lifecycle??(s?"resident":"terminal"),h=e.run===void 0?await k():null,n={node_id:r,name:e.name??e.kind,description:e.description,cycles:0,created:new Date().toISOString(),cwd:e.cwd,host_kind:"broker",profile_id:e.profile_id??null,kind:e.kind,mode:b,lifecycle:N,persona_ack:{kind:e.kind,mode:b,lifecycle:N},status:"active",parent:a,spawned_by:f,creator:p,fork_from:e.forkFrom,fork_source_file:e.forkSourceFile,review_binding:e.reviewBinding,model_override:e.modelOverride??null,scopes:e.scopes??null,isolated:e.isolated===!0,grantee:e.run?.grantee??"app:terminal",sub:e.run?.sub??h?.sub??null,grant_id:e.run?.grant_id??h?.id??null,client_id:e.run?.client_id??null,metadata_json:s?e.run?.metadata_json??null:null,run_id:s?r:e.run?.run_id??null,event_cursor:s?e.run?.event_cursor??1:null,usage_json:s?e.run?.usage_json??JSON.stringify({input_tokens:0,output_tokens:0,cache_read_tokens:0,cache_write_tokens:0,cost_usd:0}):null,passive_default:e.passiveDefault??!1,...e.managedWorktree!==void 0?{managed_worktree:e.managedWorktree}:{},intent:null,deadline_at:e.deadlineAt??null,pi_session_id:null,pi_session_file:null,launch:e.launch},y=e.scopes===void 0?void 0:Z(e.scopes),v=a===null?null:await u(a);if(a!==null&&v===null)throw new Error(`cannot spawn under unknown parent node: ${a}`);if(v?.run_id&&(await u(v.run_id))?.deleting_at)throw new Error("run is deleting");const c=p===null?null:await u(p);if(p!==null&&c===null)throw new Error(`cannot spawn from unknown creator node: ${p}`);if(c?.run_id&&(await u(c.run_id))?.deleting_at)throw new Error("run is deleting");const A=e.forkSourceFile==null?null:await Be(e.forkSourceFile),M=e.replaces==null?null:await u(e.replaces),_=v??A??M??c??(f===null?null:await u(f)),W=[v,c,A,M,f===null?null:await u(f)],$=await Oe(W,async t=>t?.isolated===!0||t?.run_id!=null&&(await u(t.run_id))?.isolated===!0);if($&&e.isolated===!1)throw new g(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});if(n.isolated=$||e.isolated===!0,_!==null){const t=c??_;if(c&&_.grantee&&c.grantee&&c.grantee!==_.grantee)throw new g(403,"other_app_run","cannot create under another app's node");const i=c&&c!==_?[_,c]:[_],d=[],w=new Set;for(const o of i){o.scopes!==null&&o.scopes!==void 0&&d.push(o.scopes);const m=o.grantee==="app:terminal"?await k():o.grant_id?await B(o.grant_id):null;if(m){const R=[...m.resolved];w.add(R),d.push(R)}else if(!(o.grantee==="app:terminal"&&!S("user").runtime?.issuer)){const R=[];w.add(R),d.push(R)}}const E=l(o=>Q(o,t.grantee==="user:local"?"app:terminal":t.grantee??"app:terminal"),"ownMemory"),F=l((o,m)=>E(m)&&w.has(o)||C(o,m),"allowed");if(y!==void 0&&!y.every(o=>d.every(m=>F(m,o))))throw new T({error:"unknown_scope",message:"child scopes must be covered by the creating node, its source and their current grants",received:y,field:"scopes",next:"Request only scopes the creating node currently holds."});if(n.scopes=y??(i.every(o=>o.scopes==null)&&i.every(o=>o.grant_id===t.grant_id&&o.sub===t.sub)?null:i.length===1?_.scopes??null:[...new Set(d.flat().filter(o=>d.every(m=>F(m,o))))]),n.scopes?.includes("crtr:act")&&!C(n.scopes,"crtr:llm"))throw new T({error:"invalid_scope",message:"crtr:act requires crtr:llm",received:n.scopes,field:"scopes",next:"Include crtr:llm or omit crtr:act."});n.grantee=t.grantee==="user:local"?"app:terminal":t.grantee,n.sub=t.sub??(n.grantee==="app:terminal"?h?.sub:null)??null,n.grant_id=t.grant_id??(n.grantee==="app:terminal"?h?.id:null)??null,n.client_id=t.client_id,n.run_id=s?r:t.run_id??null}if(re(n.grantee),e.run===void 0&&n.grantee==="app:terminal"&&S("user").runtime?.issuer&&h===null)throw await ee()?new g(503,"runtime_starting","the first grant snapshot from the directory has not committed"):new g(403,"scope_missing","terminal grant is not active");if(n.isolated){if(process.platform!=="linux"||!P(H))throw new g(400,"isolated_unavailable","isolated nodes require the Linux root launcher");if(e.managedWorktree!==void 0)throw new g(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const t=oe(se(n.grantee??"app:terminal").nodes,r),i=ae(t,ie(n.cwd));if(!U(n.cwd)||i===".."||i.startsWith(`..${le}`)||U(i))throw new g(400,"invalid_request","isolated node cwd must be inside its own folder",{param:"cwd"})}if(n.profile_id){const t=Re(n.profile_id).manifest.grantee??"app:terminal";if(t!==n.grantee){const i=`crtr:memory:rw:${t}:profile:${n.profile_id}`,d=n.grantee==="app:terminal"?await k():n.grant_id?await B(n.grant_id):null;if(!(n.grantee==="app:terminal"&&!S("user").runtime?.issuer)&&(!d||d.grantee!==n.grantee||!C(d.resolved,i))||!C(n.scopes,i))throw new T({error:"scope_missing",message:`missing scope ${i}`,received:n.profile_id,field:"profile",next:"Select a profile covered by this run and its current grant."})}}if(ve(n.profile_id??null),!n.grantee)throw new Error("node creation requires an app");const G=await ye({kind:n.kind,mode:n.mode,cwd:n.cwd,profile:n.profile_id??null,root:s,parentId:a,requestingNodeId:p,lifecycle:N,replaces:e.replaces??null},n.grantee),J=l(async(t,i)=>{const d=await I(t,i.node_id),w=p??a??e.actor??f??null,E=w!==null&&(await Ee(t,w))?.type==="node"?await I(t,w):await Ce(t,e.run===void 0?"user:local":i.grantee);await Ie(t,E,d),a!==null&&e.subscribe!==!1&&await Ne(t,await I(t,a),d,"spawn"),e.forkSourceNode&&await ke(t,await I(t,e.forkSourceNode),d),await Se(t,{event:"created",object:d,actor:E,attrs:{name:i.name,kind:i.kind,creator:p??null}})},"recordBirth");try{e.nodeId!==void 0&&await $e(e.nodeId),await e.beforeCreate?.(),(process.platform!=="linux"||P(H))&&await pe(n.grantee??"app:terminal"),await xe(n.cwd,r,()=>ce(n,{outcomeDelivery:e.outcomeDelivery,withinCreate:l(async(t,i)=>{i.run_id&&i.run_id!==i.node_id&&await ue(t,i.run_id),s&&e.run===void 0&&await ne(t,r,r,"run.started",{scopes:i.scopes??null,payer:"user",usage:JSON.parse(i.usage_json)}),await e.withinCreate?.(t,i),await J(t,i)},"withinCreate")}))}finally{G()}return n}l(Cn,"spawnNode");export{q as NodeIdConflictError,yn as currentNodeContext,Be as forkSourceNode,Fe as inboxBetweenTurns,be as newJobId,Ae as newNodeId,En as nodeEnv,vn as preflightNodeId,Rn as rootOfSpine,hn as setInstallId,Cn as spawnNode};
@@ -85,7 +85,7 @@ export type ReviveCapacityPolicy = 'freeze' | 'refuse';
85
85
  * died or was torn down — the daemon's respawn after a broker exit, and the
86
86
  * `reviveAll` sweep over disconnected nodes. Nothing external arrived for the
87
87
  * node, so such a launch is NOT a wake and does not consume the node's armed
88
- * deadline (see the cancelCronsOnWake call below). A viewer attach (the
88
+ * deadline (see the consumeDeadlineOnWake call below). A viewer attach (the
89
89
  * `viewerAttach` flag {@link decideAttachRevive} sets) is not a wake either.
90
90
  *
91
91
  * Throws if the node does not exist. All other failures propagate as-is —
@@ -1,3 +1,3 @@
1
- var Y=Object.defineProperty;var a=(e,r)=>Y(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as q}from"./unsandboxed.js";import{existsSync as B}from"node:fs";import{conversationIdFromPath as h,conversationStore as d,ConversationNotFoundError as O}from"../conversation-store/index.js";import{isAbsolute as Q}from"node:path";import{findNodeBySessionFile as Z,getNode as R,updateNode as E,beginExecution as I,recordExecutionPid as ee,abortExecution as S,endLiveExecution as re,unbindSession as T,recordLaunch as ie,setFrozen as te,clearFrozen as ne,fullName as D}from"../canvas/index.js";import{emitEvent as oe}from"../events/emit.js";import{cancelCronsOnWake as se}from"../canvas/crons.js";import{isParked as ae,transition as x}from"./lifecycle.js";import{publishNotice as le}from"./close.js";import{InputError as $}from"../io.js";import{buildPiArgv as p}from"./launch.js";import{buildReviveKickoff as ce,drainBearings as ue,hasRetainedReports as fe,readGoal as k,readYieldMessage as we,RUNTIME_RESTART_CONTINUATION as de}from"./kickoff.js";import{readRoadmap as z}from"./roadmap.js";import{encodeKickoffOrigin as he,KICKOFF_ORIGIN_ENV as pe}from"./stamp/protocol.js";import{headlessBrokerHost as H}from"./host.js";import{appMemoryFull as ve,appMemoryTooSmall as me,boundFleet as M,brokerCapReached as _e,brokerThresholdsForDaemon as U}from"./fleet.js";import{admitBrokerMemory as ye}from"./memory-admission.js";import{beginBootFaultAttempt as ke,clearFault as ge,hasAdmittedTurn as be}from"../canvas/recovery.js";import{clearNodeLedger as K,forkNodeLedger as Re,noticeChangedWhileDone as Ee}from"./ledger.js";import{forkSourceNode as Se,rootOfSpine as xe}from"./nodes.js";import{isReviewCompanionBound as $e}from"../review/companion.js";import{assertProfileActive as Ce}from"../profiles/manifest.js";import{isPidAlive as Ne}from"../canvas/pid.js";import{crtrHome as Fe,jobDir as Pe}from"../canvas/paths.js";import{contextDir as Ae,ensureNodeDirs as Be,nodeAgentDir as Oe}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as Te}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as De}from"../command-hooks/transport/exec-lifecycle.js";async function ze(e,r){const i=e.pi_session_file,t=i?h(i):null;if(t===null)return{};try{const n=await d().header(t);return r?{resumeSessionPath:n.path}:{resumeSessionPath:n.path,newCycle:!0}}catch(n){if(n instanceof O)return{};throw n}}a(ze,"resumeArgs");async function G(e){const r=h(e);if(r===null)return!1;try{return await d().header(r),!0}catch(i){if(i instanceof O)return!1;throw i}}a(G,"conversationExists");async function He(e){const r=h(e);return r===null?!0:(await d().read(r)).entries.some(i=>i.type==="message")}a(He,"sessionHasConversation");async function Me(e){const r=h(e);if(r===null)return!0;const i=(await d().header(r)).header.cwd;return typeof i!="string"||B(i)}a(Me,"sessionCwdExists");async function Ue(e,r,i){const t=r.pi_session_file;if(!t||!await G(t))return{};const n=await Z(t);return n!==null&&n.node_id!==e&&Ne(n.pi_pid)?{}:await Me(t)?await ze(r,i):{}}a(Ue,"resumableSessionArgs");async function Ke(e){return await z(e)===null&&await we(e)===null&&!await fe(e)}a(Ke,"isUnstartedBirth");async function _r(e){const r=await k(e);if(r!==null&&r.trim()!=="")return!0;const i=await z(e);return i!==null&&i.trim()!==""}a(_r,"hasFreshGroundState");async function yr(e,r){const i=await L(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}a(yr,"reviveNode");async function kr(e,r,i){return L(e,{...r,viewerAttach:!0},i)}a(kr,"decideAttachRevive");async function L(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.`);Ce(t.profile_id),q(t.grantee);const n=M();if(n.has(e))return await x(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(n.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&ae(t))return{parked:!0};const l=n.reserveSlot(e);let s=null;if(l){try{s=await ye(e)}catch(u){throw n.releaseReservation(e),u}s.verdict!=="admitted"&&n.releaseReservation(e)}if(s?.verdict==="too_small")throw me(s);if(!l||s?.verdict==="denied"){if(r.capacity==="refuse")throw s?.verdict==="denied"?ve(s):_e(U().automaticReviveCap,n.size());return t.frozen_at==null&&(await te(e,new Date().toISOString()),await re(e,{reason:"frozen"}),oe({level:"info",event:"broker.frozen",node_id:e,fields:s?.verdict==="denied"?{reason:"app_memory",free_bytes:s.free_bytes,needed_bytes:s.needed_bytes}:{reason:"broker_cap",live_count:n.size(),hard_cap:U().automaticReviveCap}})),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await Ge(e,t,r)}}finally{n.releaseReservation(e)}}a(L,"decideRevive");async function Ge(e,r,i){if(!B(r.cwd)){const v=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 $({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:v})}r.host_kind!=="broker"&&(await E(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await $e(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await T(e),r.pi_session_id=null,r.pi_session_file=null);const n=r.fork_from!=null&&r.pi_session_id==null,l=r.fork_source_file;if(n&&(typeof l!="string"||!Q(l)))throw new $({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 s=n&&typeof l=="string";if(s&&!await G(l))throw new $({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${l}`,next:`Restore ${l}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const u=await I(e,{daemonEpoch:M().epoch()});try{r.cycles=(r.cycles??0)+1,await E(e,{cycles:r.cycles});const j=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,w=s?{}:await Ue(e,r,j),m=w.resumeSessionPath!==void 0,C=m&&w.newCycle===!0,_=m&&!C,N=!m&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await Ke(e),g=_&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await be(e)&&!await He(w.resumeSessionPath)?(await k(e))?.trim():void 0,F=g!==void 0&&g!=="",W=s||N||F,P=r.cycle_pending===!0||C;await E(e,{cycle_pending:P}),r.cycle_pending=P,!s&&!m&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await T(e),r.pi_session_id=null,r.pi_session_file=null);let c,f=null;const b=a(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(s){const o=await k(e),A=h(l);if(A===null)throw new Error(`invalid fork source ${l}`);const X=(await d().header(await d().fork(A,e))).path;c=p(r,{resumeSessionPath:X,...o!=null&&o!==""?{prompt:o}:{}}),o!=null&&o!==""&&(f=b()),await Re(e,(await Se(l))?.node_id??null)}else if(F)c=p(r,{...w,prompt:g}),f=b();else if(_)c=p(r,{...w,...i.wakeReason==="runtime-restart-abort"?{prompt:de}:{}}),i.wakeReason==="runtime-restart-abort"&&(f={type:"runtime",kind:"restart-continuation"});else if(N){const o=(await k(e))?.trim();c=p(r,o!==void 0&&o!==""?{prompt:o}:{}),o!==void 0&&o!==""&&(f=b()),await K(e)}else{const o=await ue(r,u);c=p(r,{...w,prompt:await ce(r,o,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&o.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(o.unreadMessageIds)),f=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 K(e)}c.env={...c.env,CRTR_SUBTREE:await xe(e)},f!==null&&(c.env[pe]=he(f)),r.status==="done"&&r.last_settled_at!=null&&await Ee(e,r.last_settled_at),await x(e,r.intent==="refresh"?"refresh-launch":"revive"),await ge(e,{preserveEpisode:!0});const J=await ke(e);i.recovery!==!0&&i.viewerAttach!==!0&&await se(e),await Be(e,r.grantee);const V=Te(r.cwd,r.profile_id).plans.get("node:start")??[];await De(V,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await Oe(e),contextDir:await Ae(e),jobDir:Pe(e)},runtime:{isBirth:W,canvasHome:Fe(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:u};let y;try{if(y=await H.launch(e,c,{cwd:r.cwd,name:D(r),resuming:_}),y.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(o){try{await x(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:o instanceof Error?o.message:String(o)}}}),await le(e,"notice.crash",`Child crashed \u2014 ${D(r)} (${e}) failed to relaunch and is now dead: ${o.message}
1
+ var Y=Object.defineProperty;var a=(e,r)=>Y(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as q}from"./unsandboxed.js";import{existsSync as B}from"node:fs";import{conversationIdFromPath as h,conversationStore as d,ConversationNotFoundError as D}from"../conversation-store/index.js";import{isAbsolute as Q}from"node:path";import{findNodeBySessionFile as Z,getNode as R,updateNode as E,beginExecution as I,recordExecutionPid as ee,abortExecution as S,endLiveExecution as re,unbindSession as O,recordLaunch as ie,setFrozen as te,clearFrozen as ne,fullName as T}from"../canvas/index.js";import{emitEvent as oe}from"../events/emit.js";import{consumeDeadlineOnWake as se}from"../feed/messages.js";import{isParked as ae,transition as x}from"./lifecycle.js";import{publishNotice as le}from"./close.js";import{InputError as $}from"../io.js";import{buildPiArgv as p}from"./launch.js";import{buildReviveKickoff as ce,drainBearings as ue,hasRetainedReports as fe,readGoal as k,readYieldMessage as we,RUNTIME_RESTART_CONTINUATION as de}from"./kickoff.js";import{readRoadmap as z}from"./roadmap.js";import{encodeKickoffOrigin as he,KICKOFF_ORIGIN_ENV as pe}from"./stamp/protocol.js";import{headlessBrokerHost as H}from"./host.js";import{appMemoryFull as ve,appMemoryTooSmall as me,boundFleet as M,brokerCapReached as _e,brokerThresholdsForDaemon as U}from"./fleet.js";import{admitBrokerMemory as ye}from"./memory-admission.js";import{beginBootFaultAttempt as ke,clearFault as ge,hasAdmittedTurn as be}from"../canvas/recovery.js";import{clearNodeLedger as K,forkNodeLedger as Re,noticeChangedWhileDone as Ee}from"./ledger.js";import{forkSourceNode as Se,rootOfSpine as xe}from"./nodes.js";import{isReviewCompanionBound as $e}from"../review/companion.js";import{assertProfileActive as Ne}from"../profiles/manifest.js";import{isPidAlive as Ce}from"../canvas/pid.js";import{crtrHome as Fe,jobDir as Pe}from"../canvas/paths.js";import{contextDir as Ae,ensureNodeDirs as Be,nodeAgentDir as De}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as Oe}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as Te}from"../command-hooks/transport/exec-lifecycle.js";async function ze(e,r){const i=e.pi_session_file,t=i?h(i):null;if(t===null)return{};try{const n=await d().header(t);return r?{resumeSessionPath:n.path}:{resumeSessionPath:n.path,newCycle:!0}}catch(n){if(n instanceof D)return{};throw n}}a(ze,"resumeArgs");async function G(e){const r=h(e);if(r===null)return!1;try{return await d().header(r),!0}catch(i){if(i instanceof D)return!1;throw i}}a(G,"conversationExists");async function He(e){const r=h(e);return r===null?!0:(await d().read(r)).entries.some(i=>i.type==="message")}a(He,"sessionHasConversation");async function Me(e){const r=h(e);if(r===null)return!0;const i=(await d().header(r)).header.cwd;return typeof i!="string"||B(i)}a(Me,"sessionCwdExists");async function Ue(e,r,i){const t=r.pi_session_file;if(!t||!await G(t))return{};const n=await Z(t);return n!==null&&n.node_id!==e&&Ce(n.pi_pid)?{}:await Me(t)?await ze(r,i):{}}a(Ue,"resumableSessionArgs");async function Ke(e){return await z(e)===null&&await we(e)===null&&!await fe(e)}a(Ke,"isUnstartedBirth");async function _r(e){const r=await k(e);if(r!==null&&r.trim()!=="")return!0;const i=await z(e);return i!==null&&i.trim()!==""}a(_r,"hasFreshGroundState");async function yr(e,r){const i=await L(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}a(yr,"reviveNode");async function kr(e,r,i){return L(e,{...r,viewerAttach:!0},i)}a(kr,"decideAttachRevive");async function L(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.`);Ne(t.profile_id),q(t.grantee);const n=M();if(n.has(e))return await x(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(n.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&ae(t))return{parked:!0};const l=n.reserveSlot(e);let s=null;if(l){try{s=await ye(e)}catch(u){throw n.releaseReservation(e),u}s.verdict!=="admitted"&&n.releaseReservation(e)}if(s?.verdict==="too_small")throw me(s);if(!l||s?.verdict==="denied"){if(r.capacity==="refuse")throw s?.verdict==="denied"?ve(s):_e(U().automaticReviveCap,n.size());return t.frozen_at==null&&(await te(e,new Date().toISOString()),await re(e,{reason:"frozen"}),oe({level:"info",event:"broker.frozen",node_id:e,fields:s?.verdict==="denied"?{reason:"app_memory",free_bytes:s.free_bytes,needed_bytes:s.needed_bytes}:{reason:"broker_cap",live_count:n.size(),hard_cap:U().automaticReviveCap}})),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await Ge(e,t,r)}}finally{n.releaseReservation(e)}}a(L,"decideRevive");async function Ge(e,r,i){if(!B(r.cwd)){const v=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 $({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:v})}r.host_kind!=="broker"&&(await E(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await $e(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await O(e),r.pi_session_id=null,r.pi_session_file=null);const n=r.fork_from!=null&&r.pi_session_id==null,l=r.fork_source_file;if(n&&(typeof l!="string"||!Q(l)))throw new $({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 s=n&&typeof l=="string";if(s&&!await G(l))throw new $({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${l}`,next:`Restore ${l}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const u=await I(e,{daemonEpoch:M().epoch()});try{r.cycles=(r.cycles??0)+1,await E(e,{cycles:r.cycles});const j=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,w=s?{}:await Ue(e,r,j),m=w.resumeSessionPath!==void 0,N=m&&w.newCycle===!0,_=m&&!N,C=!m&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await Ke(e),g=_&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await be(e)&&!await He(w.resumeSessionPath)?(await k(e))?.trim():void 0,F=g!==void 0&&g!=="",W=s||C||F,P=r.cycle_pending===!0||N;await E(e,{cycle_pending:P}),r.cycle_pending=P,!s&&!m&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await O(e),r.pi_session_id=null,r.pi_session_file=null);let c,f=null;const b=a(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(s){const o=await k(e),A=h(l);if(A===null)throw new Error(`invalid fork source ${l}`);const X=(await d().header(await d().fork(A,e))).path;c=p(r,{resumeSessionPath:X,...o!=null&&o!==""?{prompt:o}:{}}),o!=null&&o!==""&&(f=b()),await Re(e,(await Se(l))?.node_id??null)}else if(F)c=p(r,{...w,prompt:g}),f=b();else if(_)c=p(r,{...w,...i.wakeReason==="runtime-restart-abort"?{prompt:de}:{}}),i.wakeReason==="runtime-restart-abort"&&(f={type:"runtime",kind:"restart-continuation"});else if(C){const o=(await k(e))?.trim();c=p(r,o!==void 0&&o!==""?{prompt:o}:{}),o!==void 0&&o!==""&&(f=b()),await K(e)}else{const o=await ue(r,u);c=p(r,{...w,prompt:await ce(r,o,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&o.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(o.unreadMessageIds)),f=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 K(e)}c.env={...c.env,CRTR_SUBTREE:await xe(e)},f!==null&&(c.env[pe]=he(f)),r.status==="done"&&r.last_settled_at!=null&&await Ee(e,r.last_settled_at),await x(e,r.intent==="refresh"?"refresh-launch":"revive"),await ge(e,{preserveEpisode:!0});const J=await ke(e);i.recovery!==!0&&i.viewerAttach!==!0&&await se(e),await Be(e,r.grantee);const V=Oe(r.cwd,r.profile_id).plans.get("node:start")??[];await Te(V,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await De(e),contextDir:await Ae(e),jobDir:Pe(e)},runtime:{isBirth:W,canvasHome:Fe(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:u};let y;try{if(y=await H.launch(e,c,{cwd:r.cwd,name:T(r),resuming:_}),y.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(o){try{await x(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:o instanceof Error?o.message:String(o)}}}),await le(e,"notice.crash",`Child crashed \u2014 ${T(r)} (${e}) failed to relaunch and is now dead: ${o.message}
2
2
 
3
3
  It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await S(u,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${o.message}`)}if(await ie(e,new Date().toISOString()),await ee(u,e,y.pid)==="shutdown")throw await S(u,{reason:"superseded"}),H.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await ne(e),{window:null,session:r.tmux_session??null,resumed:_,launch:y,faultExplanation:J.explanation,outcome:"launched"}}catch(v){throw await S(u,{reason:"launch_failed"}),v}}a(Ge,"launchRevive");export{kr as decideAttachRevive,_r as hasFreshGroundState,ze as resumeArgs,yr as reviveNode};
@@ -1,5 +1,15 @@
1
1
  import { type ManagedWorktree } from './canvas/index.js';
2
2
  import { type AbandonManagedWorktreeResult, type AutoDroppedWorktree } from './worktree.js';
3
+ /** Run `admit` — the step that persists a node whose cwd is `cwd` — under the
4
+ * repository lock the cleanup sweep removes checkouts under, when `cwd` is in
5
+ * another node's managed checkout. The repository comes from that owner's
6
+ * recorded managed worktree, not from the checkout on disk, so the lock is
7
+ * still taken after the sweep has removed the checkout. The sweep decides to
8
+ * remove a checkout and removes it inside that lock, and a node admitted inside
9
+ * the same lock is either seen by that decision or finds the checkout already
10
+ * gone and is refused here, instead of being persisted with a cwd that no
11
+ * longer exists. */
12
+ export declare function admitIntoManagedCheckout<T>(cwd: string, nodeId: string, admit: () => Promise<T>): Promise<T>;
3
13
  export declare function rollbackManagedWorktreeAsync(wt: ManagedWorktree): Promise<void>;
4
14
  export declare function createManagedWorktreeAsync(cwd: string, nodeId: string, baseRef?: string): Promise<ManagedWorktree>;
5
15
  export interface AbsentWorktreeConclusion {