@north-light/cli 0.3.545 → 0.3.547

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
2
- export declare const CONTRACT_LEVEL = "1.29";
2
+ export declare const CONTRACT_LEVEL = "1.30";
3
3
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
4
4
  export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
5
5
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -1 +1 @@
1
- var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.29",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
1
+ var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.30",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
@@ -117,14 +117,6 @@ export interface BrokerExtensionNodeDTO {
117
117
  created: string;
118
118
  }
119
119
  export type BrokerExtensionSubjectDTO = NodeSubjectDTO;
120
- /** One resolved report sender. Report contents stay broker-local filesystem
121
- * data; this daemon projection supplies only existence and display metadata. */
122
- export interface BrokerReportNodeDTO {
123
- node_id: string;
124
- grantee: string;
125
- name: string;
126
- created: string;
127
- }
128
120
  /** `GET /v1/nodes/{id}/broker/extension-state`. This is deliberately a fixed
129
121
  * extension rendering projection, not a generic node/state read API. */
130
122
  export interface BrokerExtensionStateDTO {
@@ -134,7 +126,6 @@ export interface BrokerExtensionStateDTO {
134
126
  children: BrokerExtensionNodeDTO[];
135
127
  fork_source: BrokerExtensionNodeDTO | null;
136
128
  subject: BrokerExtensionSubjectDTO;
137
- report_nodes: BrokerReportNodeDTO[];
138
129
  /** The head body of the node's `roadmap` document, or null when it has none. */
139
130
  roadmap: string | null;
140
131
  }
@@ -9,6 +9,10 @@ when-and-why-to-read: When you need Release notes, this knowledge should be read
9
9
 
10
10
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
11
11
 
12
+ ## 0.3.546
13
+
14
+ `GET /v1/nodes/:id/broker/extension-state` no longer returns `report_nodes` (every node's id, name, created and grantee). Nothing has read it since 0.3.350, and returning it cost a query over every node on each model request of every broker. No SDK method reads it.
15
+
12
16
  ## 0.3.545
13
17
 
14
18
  `@north-light/sdk/stores/postgres` gains an object kit: `PostgresObjectStore` and `objectKitSchemaSql`. Additive: no existing export, request or response changes.
@@ -1 +1 @@
1
- var u=Object.defineProperty;var o=(e,a)=>u(e,"name",{value:a,configurable:!0});import{defineLeaf as p}from"../core/command.js";import{cliClient as h,rethrowAsCliError as m}from"./api-client.js";const r=14,v=p({name:"prune",description:"remove terminal nodes (dead/done/canceled) last active more than a TTL ago",whenToUse:"you want to bound the canvas\u2019s on-disk growth \u2014 sweep away nodes that are finished (done), crashed (dead), or closed (canceled) and last active more than a retention window ago, reclaiming their rows, edges (cascade-deleted by the schema), and `nodes/<id>/` dirs. crtrd already runs the default 14-day sweep of terminal nodes every 5 minutes, so run this by hand for a shorter window, a count cap, --include-stale, or --empty; live nodes (active/idle) and nodes with an open managed worktree are never touched, and you can preview the sweep before it deletes anything",help:{name:"canvas prune",summary:"delete terminal nodes last active more than a TTL ago (edges cascade, dirs removed); --max N for a count cap; --dry-run to preview",params:[{kind:"flag",name:"max",type:"int",required:!1,constraint:"COUNT mode (instead of the TTL sweep): keep every protected node, plus the most recently active terminal nodes up to N in total, deleting the rest. Active/idle nodes regardless of process state and every node listed under effects as kept are NEVER deleted, so the real floor is max(N, protectedCount). Recency = the latest lifecycle timestamp or telemetry.json mtime. Combine with --dry-run to preview."},{kind:"flag",name:"ttl",type:"int",required:!1,default:r,constraint:`Retention window in days: only dead/done/canceled nodes last active more than this many days ago are pruned (last active = the latest lifecycle timestamp or telemetry.json mtime), except the nodes listed under effects as kept. Default: ${r}.`},{kind:"flag",name:"include-stale",type:"bool",required:!1,default:!1,constraint:"ALSO prune stale active/idle nodes last active before the TTL whose process is gone (pi_pid null or dead) \u2014 reaps abandoned roots the daemon never reconciled. Skipped: a live pi_pid, a node sleeping until it is woken, a node with unseen wake mail, a frozen node, a node in respawn backoff, and every node listed under effects as kept."},{kind:"flag",name:"empty",type:"bool",required:!1,default:!1,constraint:"Instead of the TTL sweep, reap EMPTY nodes \u2014 nodes (of any age or status) whose engine never produced an assistant message, the useless shells that just take up graph + disk space. A node mid-first-turn, the caller, and a node with an open managed worktree are spared. Combine with --dry-run to preview."},{kind:"flag",name:"dry-run",type:"bool",required:!1,default:!1,constraint:"Report what WOULD be pruned without deleting anything."}],output:[{name:"pruned",type:"integer",required:!0,constraint:"How many nodes were pruned (0 under --dry-run since nothing is deleted; the candidate count is in `nodes`)."},{name:"dryRun",type:"boolean",required:!0,constraint:"True when nothing was deleted (preview only)."},{name:"ttlDays",type:"integer",required:!0,constraint:"The retention window used."},{name:"nodes",type:"object[]",required:!0,constraint:"One per pruned (or, under --dry-run, prunable) node: {node_id,status,created}."}],outputKind:"object",effects:["With --max N: COUNT mode \u2014 keep every protected node plus the most recently active terminal nodes up to N in total, deleting the rest; active/idle nodes regardless of process state are never deleted. Takes precedence over the TTL/--empty sweeps.","Deletes matching `nodes` rows; their edges cascade-delete via the FK; each node\u2019s `nodes/<id>/` dir is removed.","Always kept, in every mode except --empty: the caller, a node with a live or launching engine, a node with durable work that must wake it (subscription, message wait, cron wake, open ticket, output request), a node a person is viewing, a frozen node, an app run (deleted only through the runs API), and a node with an open managed worktree.","No-op on live nodes (active/idle) and terminal nodes active within the TTL. With --include-stale: also deletes active/idle nodes last active before the TTL whose process is gone and that nothing will wake.","crtrd runs the default sweep (terminal nodes, 14 days, no --include-stale) automatically every 5 minutes.","Under --dry-run: read-only, deletes nothing."]},run:o(async e=>{const a=e.ttl??r,i=e.dryRun??!1,l=e.includeStale??!1,c=e.empty??!1,s=e.max,n={dry_run:i,ttl_days:a};s!==void 0?n.limit=s:c?n.empty=!0:n.include_stale=l;try{const t=await h().prune(n);return{pruned:t.dry_run?0:t.pruned.length,dryRun:t.dry_run,ttlDays:t.ttl_days,nodes:t.pruned.map(d=>({node_id:d.node_id,status:d.status,created:d.created}))}}catch(t){m(t)}},"run")});export{v as canvasPruneLeaf};
1
+ var u=Object.defineProperty;var o=(e,a)=>u(e,"name",{value:a,configurable:!0});import{defineLeaf as p}from"../core/command.js";import{cliClient as h,rethrowAsCliError as m}from"./api-client.js";const r=2,v=p({name:"prune",description:"remove terminal nodes (dead/done/canceled) last active more than a TTL ago",whenToUse:"you want to bound the canvas\u2019s on-disk growth \u2014 sweep away nodes that are finished (done), crashed (dead), or closed (canceled) and last active more than a retention window ago, reclaiming their rows, edges (cascade-deleted by the schema), and `nodes/<id>/` dirs. crtrd already runs the default 2-day sweep of terminal nodes every 5 minutes, so run this by hand for a shorter window, a count cap, --include-stale, or --empty; live nodes (active/idle) and nodes with an open managed worktree are never touched, and you can preview the sweep before it deletes anything",help:{name:"canvas prune",summary:"delete terminal nodes last active more than a TTL ago (edges cascade, dirs removed); --max N for a count cap; --dry-run to preview",params:[{kind:"flag",name:"max",type:"int",required:!1,constraint:"COUNT mode (instead of the TTL sweep): keep every protected node, plus the most recently active terminal nodes up to N in total, deleting the rest. Active/idle nodes regardless of process state and every node listed under effects as kept are NEVER deleted, so the real floor is max(N, protectedCount). Recency = the latest lifecycle timestamp or telemetry.json mtime. Combine with --dry-run to preview."},{kind:"flag",name:"ttl",type:"int",required:!1,default:r,constraint:`Retention window in days: only dead/done/canceled nodes last active more than this many days ago are pruned (last active = the latest lifecycle timestamp or telemetry.json mtime), except the nodes listed under effects as kept. Default: ${r}.`},{kind:"flag",name:"include-stale",type:"bool",required:!1,default:!1,constraint:"ALSO prune stale active/idle nodes last active before the TTL whose process is gone (pi_pid null or dead) \u2014 reaps abandoned roots the daemon never reconciled. Skipped: a live pi_pid, a node sleeping until it is woken, a node with unseen wake mail, a frozen node, a node in respawn backoff, and every node listed under effects as kept."},{kind:"flag",name:"empty",type:"bool",required:!1,default:!1,constraint:"Instead of the TTL sweep, reap EMPTY nodes \u2014 nodes (of any age or status) whose engine never produced an assistant message, the useless shells that just take up graph + disk space. A node mid-first-turn, the caller, and a node with an open managed worktree are spared. Combine with --dry-run to preview."},{kind:"flag",name:"dry-run",type:"bool",required:!1,default:!1,constraint:"Report what WOULD be pruned without deleting anything."}],output:[{name:"pruned",type:"integer",required:!0,constraint:"How many nodes were pruned (0 under --dry-run since nothing is deleted; the candidate count is in `nodes`)."},{name:"dryRun",type:"boolean",required:!0,constraint:"True when nothing was deleted (preview only)."},{name:"ttlDays",type:"integer",required:!0,constraint:"The retention window used."},{name:"nodes",type:"object[]",required:!0,constraint:"One per pruned (or, under --dry-run, prunable) node: {node_id,status,created}."}],outputKind:"object",effects:["With --max N: COUNT mode \u2014 keep every protected node plus the most recently active terminal nodes up to N in total, deleting the rest; active/idle nodes regardless of process state are never deleted. Takes precedence over the TTL/--empty sweeps.","Deletes matching `nodes` rows; their edges cascade-delete via the FK; each node\u2019s `nodes/<id>/` dir is removed.","Always kept, in every mode except --empty: the caller, a node with a live or launching engine, a node with durable work that must wake it (subscription, message wait, cron wake, open ticket, output request), a node a person is viewing, a frozen node, an app run (deleted only through the runs API), and a node with an open managed worktree.","No-op on live nodes (active/idle) and terminal nodes active within the TTL. With --include-stale: also deletes active/idle nodes last active before the TTL whose process is gone and that nothing will wake.","crtrd runs the default sweep (terminal nodes, 2 days, no --include-stale) automatically every 5 minutes.","Under --dry-run: read-only, deletes nothing."]},run:o(async e=>{const a=e.ttl??r,i=e.dryRun??!1,l=e.includeStale??!1,c=e.empty??!1,s=e.max,n={dry_run:i,ttl_days:a};s!==void 0?n.limit=s:c?n.empty=!0:n.include_stale=l;try{const t=await h().prune(n);return{pruned:t.dry_run?0:t.pruned.length,dryRun:t.dry_run,ttlDays:t.ttl_days,nodes:t.pruned.map(d=>({node_id:d.node_id,status:d.status,created:d.created}))}}catch(t){m(t)}},"run")});export{v as canvasPruneLeaf};
@@ -191,13 +191,6 @@ export declare function resumableNodeCwds(ownerId: string): Promise<Array<{
191
191
  * launches: the lifecycle tick reopens them. Each parked resident seeks its own mail
192
192
  * through `deliveries_destination`. */
193
193
  export declare function listParkedResidentsWithWakeMail(): Promise<NodeRow[]>;
194
- /** Identity-only projection of every user-visible row: the broker's report
195
- * resolver needs only "does this sender exist, and what is it called", and a
196
- * three-column read stays cheap against a canvas of thousands of rows where a
197
- * full `listNodes()` hydration does not. */
198
- export declare function listNodeIdentities(): Promise<Array<Pick<NodeRow, 'node_id' | 'name' | 'created'> & {
199
- grantee: string;
200
- }>>;
201
194
  /** Mark an already-spawned node as an unclaimed spare for `recipeKey`. Hides it
202
195
  * from every listing until it is claimed. */
203
196
  export declare function registerWarmSpare(nodeId: string, recipeKey: string): Promise<void>;
@@ -1,8 +1,8 @@
1
- var Z=Object.defineProperty;var a=(e,n)=>Z(e,"name",{value:n,configurable:!0});import{envNodeId as x}from"../../shared/env.js";import{rmdirSync as ee,rmSync as ne,statSync as te}from"node:fs";import{dirname as ae,join as U,resolve as ie}from"node:path";import{removeTreeBeneath as W}from"../spaces/open-beneath.js";import{emitEvent as oe}from"../events/emit.js";import{withCanvasWrite as p,openDb as r}from"./db.js";import{conversationStore as re,ConversationNotFoundError as se}from"../conversation-store/index.js";import{hasUnseenWakeMail as de,UNSEEN_WAKE_MAIL_SQL as ue}from"./deliveries.js";import{isAppRun as j}from"./app-run.js";import{registerOutcomeAction as I}from"./outcome-actions.js";import{publish as le}from"../graph/events.js";import{nodeObject as ce}from"../graph/objects.js";import{findNodeBySessionFileSql as _e}from"./sessions.js";import{hydrateFault as pe}from"./recovery.js";import{hasUnreconciledWorktree as fe,openWorktree as Ee,readWorktree as we,worktreeFromRow as ye}from"./worktrees.js";import{isPidAlive as me,isPidPresent as F,recordedPidLiveness as Ne}from"./pid.js";import{ensureHome as Re,jobDir as Se,nodeDir as he,nodeRunDir as C}from"./paths.js";import{appForNode as Oe,ensureNodeDirs as Te,nodeAgentDir as Le}from"./node-agent-paths.js";import{appSpace as b}from"../layout.js";import{createObject as De,getById as H,ownerForGrantee as ge,setObjectKind as ve,setObjectName as ke}from"../graph/objects.js";import{deleteNodeWithOwned as Ae}from"../graph/deletion.js";import{SUBTREE_ALL_IDS_SQL as xe,SUBTREE_IDS_SQL as Ue}from"./tree.js";import{filterAsync as y,mapAsync as h}from"../async-array.js";function E(e,n){if(e==null)return null;const t=JSON.stringify(e);if(t===void 0)throw new TypeError(`${n} must be JSON-serializable`);return t}a(E,"jsonValue");async function cn(e,n){if((await e.prepare("SELECT deleting_at FROM nodes WHERE node_id = ?").get(n))?.deleting_at)throw new Error("run is deleting")}a(cn,"assertRunNotDeleting");async function We(e,n){const t=await ge(n,e.grantee??"app:terminal");await De(n,{id:e.node_id,type:"node",kind:e.kind,owner:t.id,privacy:"public",name:e.name,preview:""}),await n.prepare(`INSERT INTO nodes (node_id, description, title, icon, cycles, mode, lifecycle, cwd, host_kind, profile_id, parent, spawned_by, creator, fork_from, fork_source_file, created, passive_default, cycle_pending, model_override, persona_ack_json, review_binding_json, launch_config_json, scopes_json, isolated, status, intent, final_report, finalized_at, deadline_at, "window", tmux_session, pane, sub, grant_id, client_id, metadata_json, run_id, event_cursor, usage_json, deleting_at) VALUES (${Array(40).fill("?").join(", ")})`).run(e.node_id,e.description??null,e.title??null,e.icon??null,e.cycles??null,e.mode,e.lifecycle,e.cwd,e.host_kind??null,e.profile_id??null,e.parent??null,e.spawned_by??null,e.creator??null,e.fork_from??null,e.fork_source_file??null,e.created,e.passive_default===!0?1:0,e.cycle_pending===!0?1:0,e.model_override??null,E(e.persona_ack,"persona_ack"),E(e.review_binding,"review_binding"),E(e.launch,"launch"),E(e.scopes,"scopes"),e.isolated===!0?1:0,e.status??"active",e.intent??null,e.final_report??null,e.finalized_at??null,e.deadline_at??null,e.window??null,e.tmux_session??null,e.pane??null,e.sub??null,e.grant_id??null,e.client_id??null,e.metadata_json??null,e.run_id??null,e.event_cursor??null,e.usage_json??null,e.deleting_at??null)}a(We,"seedRow");function M(e){if(e.fault_id==null)return null;const n=Object.entries(e).filter(([t])=>t.startsWith("fault_")).map(([t,i])=>[t==="fault_id"?t:t.slice(6),i]);return pe(Object.fromEntries(n))}a(M,"faultFrom");function f(e){const n={node_id:e.node_id,name:e.name,description:e.description??void 0,cycles:e.cycles??void 0,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,profile_id:e.profile_id??null,scopes:e.scopes_json==null?null:JSON.parse(e.scopes_json),isolated:e.isolated===1,grantee:e.grantee,sub:e.sub??null,grant_id:e.grant_id??null,client_id:e.client_id??null,metadata_json:e.metadata_json??null,run_id:e.run_id??null,event_cursor:e.event_cursor??null,last_activity_at:e.last_activity_at??null,usage_json:e.usage_json??null,last_turn_error_json:e.last_turn_error_json??null,deleting_at:e.deleting_at??null,parent:e.parent??null,created:e.created,intent:e.intent??null,frozen_at:e.frozen_at??null,respawn_failures:e.respawn_failures??0,respawn_not_before:e.respawn_not_before??null,launched_at:e.launched_at??null,last_settled_at:e.last_settled_at??null,terminal_reason:e.terminal_reason??null,final_report:e.final_report??null,finalized_at:e.finalized_at??null,outcome_kind:e.outcome_kind??null,outcome_reason:e.outcome_reason??null,outcome_at:e.outcome_at??null,outcome_revision:e.outcome_revision??0,outcome_detail_json:e.outcome_detail_json??null,deadline_at:e.deadline_at??null,telemetry_tokens_in:e.telemetry_tokens_in??null,telemetry_context_tokens:e.telemetry_context_tokens??null,telemetry_last_activity:e.telemetry_last_activity??null,telemetry_updated_at:e.telemetry_updated_at??null,pi_pid:e.pi_pid??null,pi_pid_identity:e.pi_pid_identity??null,window:e.window??null,tmux_session:e.tmux_session??null,pane:e.pane??null,fault:null,streaming:!1},t=F(n.pi_pid),i=M(e);return n.fault=i?.link==="pi\u2192provider"&&i.retry.by==="daemon"&&i.retry.disposition==="auto"&&!t?null:i,n.streaming=e.turn_open_at!==null&&e.turn_closed_at===null&&t,n}a(f,"rowFrom");async function P(e,n){return n??=await we(e.node_id),{...f(e),title:e.title??void 0,icon:e.icon??void 0,spawned_by:e.spawned_by??null,creator:e.creator??null,fork_from:e.fork_from??null,fork_source_file:e.fork_source_file??null,passive_default:e.passive_default===1,cycle_pending:e.cycle_pending===1,model_override:e.model_override??null,scopes:e.scopes_json==null?null:JSON.parse(e.scopes_json),persona_ack:e.persona_ack_json==null?void 0:JSON.parse(e.persona_ack_json),review_binding:e.review_binding_json==null?void 0:JSON.parse(e.review_binding_json),launch:e.launch_config_json==null?void 0:JSON.parse(e.launch_config_json),pi_session_id:e.pi_session_id??null,pi_session_file:e.pi_session_file??null,...n===null?{}:{managed_worktree:n}}}a(P,"metaFrom");async function _n(e,n={}){return Re(),await p(async t=>{await We(e,t),e.managed_worktree!==void 0&&e.managed_worktree!==null&&await Ee(e.node_id,e.managed_worktree,t),n.outcomeDelivery!==void 0&&await I(t,e.node_id,n.outcomeDelivery),await n.withinCreate?.(t,e)}),await Te(e.node_id),await N(e.node_id)}a(_n,"createNode");async function N(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE node_id = ?").get(e);return n===void 0?null:await P(n)}a(N,"getNode");const je=1440*60*1e3;function $(e){return{requestKey:e.request_key,nodeId:e.node_id,state:e.state}}a($,"createRequestRecord");async function pn(e){const n=await(await r()).prepareUncached("SELECT request_key, node_id, state FROM create_requests WHERE request_key = ?").get(e);return n===void 0?null:$(n)}a(pn,"findCreateRequest");async function fn(e){return await p(async n=>{const t=Date.now();await n.prepareUncached("DELETE FROM create_requests WHERE expires_at <= ?").run(t);const i=await n.prepareUncached("SELECT request_key, node_id, state FROM create_requests WHERE request_key = ?").get(e);return i!==void 0?$(i):(await n.prepareUncached("INSERT INTO create_requests (request_key, node_id, expires_at, state) VALUES (?, NULL, ?, 'reserved')").run(e,t+je),{requestKey:e,nodeId:null,state:"reserved"})})}a(fn,"reserveCreateRequest");async function En(e=Date.now()){return Number((await(await r()).prepareUncached("DELETE FROM create_requests WHERE expires_at <= ?").run(e)).changes)}a(En,"pruneExpiredCreateRequests");async function wn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET node_id = ?, state = 'complete' WHERE request_key = ?").run(n,e)}a(wn,"recordCreateRequestNode");async function yn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET node_id = ?, state = 'reserved' WHERE request_key = ?").run(n,e)}a(yn,"reserveCreateRequestNode");async function mn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET state = 'complete' WHERE request_key = ? AND node_id = ?").run(e,n)}a(mn,"completeCreateRequest");async function Nn(e,n){await p(async t=>{if(n===null){await t.prepareUncached("DELETE FROM create_requests WHERE request_key = ? AND node_id IS NULL").run(e);return}await t.prepareUncached("DELETE FROM create_requests WHERE request_key = ? AND node_id = ?").run(e,n)})}a(Nn,"abandonCreateRequest");function Rn(e){return ie(e)}a(Rn,"canonicalSessionFile");async function Sn(e){const n=await _e(e);return n===null?null:await N(n)}a(Sn,"findNodeBySessionFile");async function hn(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE node_id = ?").get(e);return n?f(n):null}a(hn,"getRow");async function On(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE pane = ?").get(e);return n?f(n):null}a(On,"getRowByPane");async function Tn(e){return(await(await r()).prepare(`SELECT * FROM node_rows
1
+ var te=Object.defineProperty;var i=(e,n)=>te(e,"name",{value:n,configurable:!0});import{envNodeId as j}from"../../shared/env.js";import{lstatSync as ae,realpathSync as W,rmdirSync as ie,rmSync as oe,statSync as re}from"node:fs";import{basename as se,dirname as F,isAbsolute as de,join as N,relative as ue,resolve as le,sep as ce}from"node:path";import{removeTreeBeneath as b}from"../spaces/open-beneath.js";import{emitEvent as _e}from"../events/emit.js";import{withCanvasWrite as p,openDb as r}from"./db.js";import{conversationStore as pe,ConversationNotFoundError as fe}from"../conversation-store/index.js";import{hasUnseenWakeMail as Ee,UNSEEN_WAKE_MAIL_SQL as we}from"./deliveries.js";import{isAppRun as C}from"./app-run.js";import{registerOutcomeAction as H}from"./outcome-actions.js";import{publish as ye}from"../graph/events.js";import{nodeObject as me}from"../graph/objects.js";import{findNodeBySessionFileSql as Ne}from"./sessions.js";import{hydrateFault as Re}from"./recovery.js";import{hasUnreconciledWorktree as Se,openWorktree as he,readWorktree as Oe,worktreeFromRow as Le}from"./worktrees.js";import{isPidAlive as Te,isPidPresent as M,recordedPidLiveness as De}from"./pid.js";import{ensureHome as ve,jobDir as ge,nodeDir as Ae,nodeRunDir as P}from"./paths.js";import{appForNode as ke,ensureNodeDirs as xe,nodeAgentDir as Ue}from"./node-agent-paths.js";import{appSpace as O}from"../layout.js";import{createObject as Ie,getById as $,ownerForGrantee as je,setObjectKind as We,setObjectName as Fe}from"../graph/objects.js";import{deleteNodeWithOwned as be}from"../graph/deletion.js";import{SUBTREE_ALL_IDS_SQL as Ce,SUBTREE_IDS_SQL as He}from"./tree.js";import{filterAsync as y,mapAsync as L}from"../async-array.js";function E(e,n){if(e==null)return null;const t=JSON.stringify(e);if(t===void 0)throw new TypeError(`${n} must be JSON-serializable`);return t}i(E,"jsonValue");async function Rn(e,n){if((await e.prepare("SELECT deleting_at FROM nodes WHERE node_id = ?").get(n))?.deleting_at)throw new Error("run is deleting")}i(Rn,"assertRunNotDeleting");async function Me(e,n){const t=await je(n,e.grantee??"app:terminal");await Ie(n,{id:e.node_id,type:"node",kind:e.kind,owner:t.id,privacy:"public",name:e.name,preview:""}),await n.prepare(`INSERT INTO nodes (node_id, description, title, icon, cycles, mode, lifecycle, cwd, host_kind, profile_id, parent, spawned_by, creator, fork_from, fork_source_file, created, passive_default, cycle_pending, model_override, persona_ack_json, review_binding_json, launch_config_json, scopes_json, isolated, status, intent, final_report, finalized_at, deadline_at, "window", tmux_session, pane, sub, grant_id, client_id, metadata_json, run_id, event_cursor, usage_json, deleting_at) VALUES (${Array(40).fill("?").join(", ")})`).run(e.node_id,e.description??null,e.title??null,e.icon??null,e.cycles??null,e.mode,e.lifecycle,e.cwd,e.host_kind??null,e.profile_id??null,e.parent??null,e.spawned_by??null,e.creator??null,e.fork_from??null,e.fork_source_file??null,e.created,e.passive_default===!0?1:0,e.cycle_pending===!0?1:0,e.model_override??null,E(e.persona_ack,"persona_ack"),E(e.review_binding,"review_binding"),E(e.launch,"launch"),E(e.scopes,"scopes"),e.isolated===!0?1:0,e.status??"active",e.intent??null,e.final_report??null,e.finalized_at??null,e.deadline_at??null,e.window??null,e.tmux_session??null,e.pane??null,e.sub??null,e.grant_id??null,e.client_id??null,e.metadata_json??null,e.run_id??null,e.event_cursor??null,e.usage_json??null,e.deleting_at??null)}i(Me,"seedRow");function q(e){if(e.fault_id==null)return null;const n=Object.entries(e).filter(([t])=>t.startsWith("fault_")).map(([t,a])=>[t==="fault_id"?t:t.slice(6),a]);return Re(Object.fromEntries(n))}i(q,"faultFrom");function f(e){const n={node_id:e.node_id,name:e.name,description:e.description??void 0,cycles:e.cycles??void 0,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,profile_id:e.profile_id??null,scopes:e.scopes_json==null?null:JSON.parse(e.scopes_json),isolated:e.isolated===1,grantee:e.grantee,sub:e.sub??null,grant_id:e.grant_id??null,client_id:e.client_id??null,metadata_json:e.metadata_json??null,run_id:e.run_id??null,event_cursor:e.event_cursor??null,last_activity_at:e.last_activity_at??null,usage_json:e.usage_json??null,last_turn_error_json:e.last_turn_error_json??null,deleting_at:e.deleting_at??null,parent:e.parent??null,created:e.created,intent:e.intent??null,frozen_at:e.frozen_at??null,respawn_failures:e.respawn_failures??0,respawn_not_before:e.respawn_not_before??null,launched_at:e.launched_at??null,last_settled_at:e.last_settled_at??null,terminal_reason:e.terminal_reason??null,final_report:e.final_report??null,finalized_at:e.finalized_at??null,outcome_kind:e.outcome_kind??null,outcome_reason:e.outcome_reason??null,outcome_at:e.outcome_at??null,outcome_revision:e.outcome_revision??0,outcome_detail_json:e.outcome_detail_json??null,deadline_at:e.deadline_at??null,telemetry_tokens_in:e.telemetry_tokens_in??null,telemetry_context_tokens:e.telemetry_context_tokens??null,telemetry_last_activity:e.telemetry_last_activity??null,telemetry_updated_at:e.telemetry_updated_at??null,pi_pid:e.pi_pid??null,pi_pid_identity:e.pi_pid_identity??null,window:e.window??null,tmux_session:e.tmux_session??null,pane:e.pane??null,fault:null,streaming:!1},t=M(n.pi_pid),a=q(e);return n.fault=a?.link==="pi\u2192provider"&&a.retry.by==="daemon"&&a.retry.disposition==="auto"&&!t?null:a,n.streaming=e.turn_open_at!==null&&e.turn_closed_at===null&&t,n}i(f,"rowFrom");async function B(e,n){return n??=await Oe(e.node_id),{...f(e),title:e.title??void 0,icon:e.icon??void 0,spawned_by:e.spawned_by??null,creator:e.creator??null,fork_from:e.fork_from??null,fork_source_file:e.fork_source_file??null,passive_default:e.passive_default===1,cycle_pending:e.cycle_pending===1,model_override:e.model_override??null,scopes:e.scopes_json==null?null:JSON.parse(e.scopes_json),persona_ack:e.persona_ack_json==null?void 0:JSON.parse(e.persona_ack_json),review_binding:e.review_binding_json==null?void 0:JSON.parse(e.review_binding_json),launch:e.launch_config_json==null?void 0:JSON.parse(e.launch_config_json),pi_session_id:e.pi_session_id??null,pi_session_file:e.pi_session_file??null,...n===null?{}:{managed_worktree:n}}}i(B,"metaFrom");async function Sn(e,n={}){return ve(),await p(async t=>{await Me(e,t),e.managed_worktree!==void 0&&e.managed_worktree!==null&&await he(e.node_id,e.managed_worktree,t),n.outcomeDelivery!==void 0&&await H(t,e.node_id,n.outcomeDelivery),await n.withinCreate?.(t,e)}),await xe(e.node_id),await R(e.node_id)}i(Sn,"createNode");async function R(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE node_id = ?").get(e);return n===void 0?null:await B(n)}i(R,"getNode");const Pe=1440*60*1e3;function z(e){return{requestKey:e.request_key,nodeId:e.node_id,state:e.state}}i(z,"createRequestRecord");async function hn(e){const n=await(await r()).prepareUncached("SELECT request_key, node_id, state FROM create_requests WHERE request_key = ?").get(e);return n===void 0?null:z(n)}i(hn,"findCreateRequest");async function On(e){return await p(async n=>{const t=Date.now();await n.prepareUncached("DELETE FROM create_requests WHERE expires_at <= ?").run(t);const a=await n.prepareUncached("SELECT request_key, node_id, state FROM create_requests WHERE request_key = ?").get(e);return a!==void 0?z(a):(await n.prepareUncached("INSERT INTO create_requests (request_key, node_id, expires_at, state) VALUES (?, NULL, ?, 'reserved')").run(e,t+Pe),{requestKey:e,nodeId:null,state:"reserved"})})}i(On,"reserveCreateRequest");async function Ln(e=Date.now()){return Number((await(await r()).prepareUncached("DELETE FROM create_requests WHERE expires_at <= ?").run(e)).changes)}i(Ln,"pruneExpiredCreateRequests");async function Tn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET node_id = ?, state = 'complete' WHERE request_key = ?").run(n,e)}i(Tn,"recordCreateRequestNode");async function Dn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET node_id = ?, state = 'reserved' WHERE request_key = ?").run(n,e)}i(Dn,"reserveCreateRequestNode");async function vn(e,n){await(await r()).prepareUncached("UPDATE create_requests SET state = 'complete' WHERE request_key = ? AND node_id = ?").run(e,n)}i(vn,"completeCreateRequest");async function gn(e,n){await p(async t=>{if(n===null){await t.prepareUncached("DELETE FROM create_requests WHERE request_key = ? AND node_id IS NULL").run(e);return}await t.prepareUncached("DELETE FROM create_requests WHERE request_key = ? AND node_id = ?").run(e,n)})}i(gn,"abandonCreateRequest");function An(e){return le(e)}i(An,"canonicalSessionFile");async function kn(e){const n=await Ne(e);return n===null?null:await R(n)}i(kn,"findNodeBySessionFile");async function xn(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE node_id = ?").get(e);return n?f(n):null}i(xn,"getRow");async function Un(e){const n=await(await r()).prepare("SELECT * FROM node_rows WHERE pane = ?").get(e);return n?f(n):null}i(Un,"getRowByPane");async function In(e){return(await(await r()).prepare(`SELECT * FROM node_rows
2
2
  WHERE deadline_at IS NOT NULL AND outcome_at IS NULL
3
3
  AND deadline_at <= ? AND status IN ('active','idle') AND kind != 'human'
4
- ORDER BY deadline_at, created, node_id`).all(e)).map(f)}a(Tn,"dueNodeDeadlines");async function Ln(e,n){return Number((await(await r()).prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ?").run(n,e)).changes)===1}a(Ln,"setNodeDeadline");async function Dn(e,n){return!Number.isFinite(n)||n<=0?0:await p(async t=>{const i=await t.prepare(`SELECT node_id, deadline_at FROM nodes
5
- WHERE profile_id = ? AND deadline_at IS NOT NULL AND status IN ('active', 'idle')`).all(e),o=t.prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ? AND deadline_at = ?");let s=0;for(const d of i){const c=Date.parse(d.deadline_at);Number.isNaN(c)||(s+=Number((await o.run(new Date(c+n).toISOString(),d.node_id,d.deadline_at)).changes))}return s})}a(Dn,"shiftProfileNodeDeadlines");function O(e,n){if(e==null)return null;if(typeof e=="string"||typeof e=="number"||typeof e=="bigint"||e instanceof Uint8Array)return e;throw new TypeError(`${n} must be a SQLite scalar`)}a(O,"sqlInput");async function gn(e,n){const t=[],i=a((s,d=s)=>{Object.prototype.hasOwnProperty.call(n,s)&&t.push([d,O(n[s],s)])},"add");if(i("description"),i("title"),i("icon"),i("cycles"),i("mode"),i("lifecycle"),i("cwd"),i("host_kind"),i("profile_id"),i("parent"),i("spawned_by"),i("creator"),i("fork_from"),i("fork_source_file"),i("model_override"),Object.prototype.hasOwnProperty.call(n,"passive_default")&&t.push(["passive_default",n.passive_default===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"cycle_pending")&&t.push(["cycle_pending",n.cycle_pending===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"persona_ack")&&t.push(["persona_ack_json",E(n.persona_ack,"persona_ack")]),Object.prototype.hasOwnProperty.call(n,"review_binding")&&t.push(["review_binding_json",E(n.review_binding,"review_binding")]),Object.prototype.hasOwnProperty.call(n,"isolated"))throw new TypeError("isolated cannot change after node creation");Object.prototype.hasOwnProperty.call(n,"scopes")&&t.push(["scopes_json",E(n.scopes,"scopes")]),Object.prototype.hasOwnProperty.call(n,"launch")&&t.push(["launch_config_json",E(n.launch,"launch")]);const o=a(s=>Object.prototype.hasOwnProperty.call(n,s),"has");return await p(async s=>{const d=await H(s,e);if(d===null||d.type!=="node")throw new Error(`unknown node: ${e}`);if(o("name")&&await ke(s,d,O(n.name,"name")),o("kind")&&await ve(s,d,O(n.kind,"kind")),t.length===0)return;if((await s.prepareUncached(`UPDATE nodes SET ${t.map(([u])=>`${u} = ?`).join(", ")} WHERE node_id = ?`).run(...t.map(([,u])=>u),e)).changes!==1)throw new Error(`unknown node: ${e}`)}),await N(e)}a(gn,"updateNode");async function vn(e,n){await(await r()).prepare("UPDATE nodes SET status = ? WHERE node_id = ?").run(n,e)}a(vn,"setStatus");async function kn(e,n){await(await r()).prepare("UPDATE nodes SET intent = ? WHERE node_id = ?").run(n??null,e)}a(kn,"setIntent");async function An(e,n,t={}){if(t.outcome===void 0){const o=[],s=[];Object.prototype.hasOwnProperty.call(n,"status")&&(o.push("status = ?"),s.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(o.push("intent = ?"),s.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(o.push("terminal_reason = ?"),s.push(n.terminal_reason??null));const d=["node_id = ?"];s.push(e),t.fromStatuses!==void 0&&(d.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),s.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&d.push("final_report IS NULL");const c=await(await r()).prepareUncached(`UPDATE nodes SET ${o.join(", ")} WHERE ${d.join(" AND ")}`).run(...s);return Number(c.changes)}const i=t.outcome;return await p(async o=>{const s=[],d=[];Object.prototype.hasOwnProperty.call(n,"status")&&(s.push("status = ?"),d.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(s.push("intent = ?"),d.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(s.push("terminal_reason = ?"),d.push(n.terminal_reason??null));const c=a((m,l)=>i.supersede===!0?"?":`CASE WHEN outcome_at IS NULL THEN ? ELSE ${l} END`,"outcomeValue");s.push(`outcome_kind = ${c(i.kind,"outcome_kind")}`,`outcome_reason = ${c(i.reason,"outcome_reason")}`,`outcome_detail_json = ${c(i.detailJson,"outcome_detail_json")}`,`outcome_revision = ${i.supersede===!0?"outcome_revision + 1":"CASE WHEN outcome_at IS NULL THEN outcome_revision + 1 ELSE outcome_revision END"}`,`outcome_at = ${c(i.at,"outcome_at")}`),d.push(i.kind,i.reason,i.detailJson,i.at);const u=["node_id = ?"];d.push(e),t.fromStatuses!==void 0&&(u.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),d.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&u.push("final_report IS NULL");const _=await o.prepareUncached(`UPDATE nodes SET ${s.join(", ")} WHERE ${u.join(" AND ")}`).run(...d);if(Number(_.changes)===0)return 0;const S=await o.prepare("SELECT outcome_at FROM nodes WHERE node_id = ?").get(e);return(i.supersede===!0||S.outcome_at===i.at)&&i.documentJson!==null&&i.documentJson!==void 0&&await le(o,{event:"outcome",object:await ce(o,e),actor:null,attrs:{revision:Number((await o.prepare("SELECT outcome_revision FROM nodes WHERE node_id = ?").get(e)).outcome_revision)},messageBody:JSON.parse(i.documentJson)}),Number(_.changes)})}a(An,"transitionStatusIntent");async function xn(e){return(await(await r()).prepare("SELECT final_report FROM nodes WHERE node_id = ?").get(e))?.final_report??null}a(xn,"getFinalReport");async function Un(e,n){return(await(await r()).prepare("UPDATE nodes SET final_report = NULL, finalized_at = NULL WHERE node_id = ? AND final_report = ?").run(e,n)).changes>0}a(Un,"clearFinalReportIfMatches");async function Wn(e,n){await(await r()).prepare('UPDATE nodes SET tmux_session = ?, "window" = ?, pane = ? WHERE node_id = ?').run(n.tmux_session??null,n.window??null,n.pane??null,e)}a(Wn,"setPresence");async function jn(e,n){await(await r()).prepare("UPDATE nodes SET launched_at = ?, frozen_at = NULL, respawn_not_before = NULL WHERE node_id = ?").run(n,e)}a(jn,"recordLaunch");async function In(e,n){await(await r()).prepare("UPDATE nodes SET last_settled_at = ? WHERE node_id = ?").run(n,e)}a(In,"recordSettledTurn");async function Fn(e,n){await(await r()).prepare("UPDATE nodes SET frozen_at = ? WHERE node_id = ?").run(n,e)}a(Fn,"setFrozen");async function Cn(e){await(await r()).prepare("UPDATE nodes SET frozen_at = NULL WHERE node_id = ?").run(e)}a(Cn,"clearFrozen");async function bn(e,n){await(await r()).prepare("UPDATE nodes SET respawn_failures = ?, respawn_not_before = ? WHERE node_id = ?").run(n.failures,n.notBefore,e)}a(bn,"recordRespawnAttempt");const w="node_id NOT IN (SELECT node_id FROM warm_pool)",q=["state","cleanup_state","path","branch","repo_root","base_ref","base_sha","created_at","closed_at","next_attempt_at","attempts","quarantined","sweep_reason","sweep_detail","sweep_last_at","sweep_observed_json","abandoned_json","land_intent_json","disposal_json"];function Ie(e){return e.worktree_state===null?null:ye(Object.fromEntries(q.map(n=>[n,e[`worktree_${n}`]])))}a(Ie,"worktreeFromProjection");async function Hn(e){const n=q.map(i=>`w.${i} AS worktree_${i}`).join(", "),t=await(await r()).prepareUncached(`SELECT n.*, ${n} FROM node_rows n LEFT JOIN worktrees w ON w.node_id = n.node_id WHERE n.profile_id = ? ORDER BY n.created`).all(e);return await h(t,async i=>await P(i,Ie(i)))}a(Hn,"listNodesByProfile");async function Mn(){return(await(await r()).prepare(`SELECT
4
+ ORDER BY deadline_at, created, node_id`).all(e)).map(f)}i(In,"dueNodeDeadlines");async function jn(e,n){return Number((await(await r()).prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ?").run(n,e)).changes)===1}i(jn,"setNodeDeadline");async function Wn(e,n){return!Number.isFinite(n)||n<=0?0:await p(async t=>{const a=await t.prepare(`SELECT node_id, deadline_at FROM nodes
5
+ WHERE profile_id = ? AND deadline_at IS NOT NULL AND status IN ('active', 'idle')`).all(e),o=t.prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ? AND deadline_at = ?");let s=0;for(const d of a){const l=Date.parse(d.deadline_at);Number.isNaN(l)||(s+=Number((await o.run(new Date(l+n).toISOString(),d.node_id,d.deadline_at)).changes))}return s})}i(Wn,"shiftProfileNodeDeadlines");function T(e,n){if(e==null)return null;if(typeof e=="string"||typeof e=="number"||typeof e=="bigint"||e instanceof Uint8Array)return e;throw new TypeError(`${n} must be a SQLite scalar`)}i(T,"sqlInput");async function Fn(e,n){const t=[],a=i((s,d=s)=>{Object.prototype.hasOwnProperty.call(n,s)&&t.push([d,T(n[s],s)])},"add");if(a("description"),a("title"),a("icon"),a("cycles"),a("mode"),a("lifecycle"),a("cwd"),a("host_kind"),a("profile_id"),a("parent"),a("spawned_by"),a("creator"),a("fork_from"),a("fork_source_file"),a("model_override"),Object.prototype.hasOwnProperty.call(n,"passive_default")&&t.push(["passive_default",n.passive_default===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"cycle_pending")&&t.push(["cycle_pending",n.cycle_pending===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"persona_ack")&&t.push(["persona_ack_json",E(n.persona_ack,"persona_ack")]),Object.prototype.hasOwnProperty.call(n,"review_binding")&&t.push(["review_binding_json",E(n.review_binding,"review_binding")]),Object.prototype.hasOwnProperty.call(n,"isolated"))throw new TypeError("isolated cannot change after node creation");Object.prototype.hasOwnProperty.call(n,"scopes")&&t.push(["scopes_json",E(n.scopes,"scopes")]),Object.prototype.hasOwnProperty.call(n,"launch")&&t.push(["launch_config_json",E(n.launch,"launch")]);const o=i(s=>Object.prototype.hasOwnProperty.call(n,s),"has");return await p(async s=>{const d=await $(s,e);if(d===null||d.type!=="node")throw new Error(`unknown node: ${e}`);if(o("name")&&await Fe(s,d,T(n.name,"name")),o("kind")&&await We(s,d,T(n.kind,"kind")),t.length===0)return;if((await s.prepareUncached(`UPDATE nodes SET ${t.map(([u])=>`${u} = ?`).join(", ")} WHERE node_id = ?`).run(...t.map(([,u])=>u),e)).changes!==1)throw new Error(`unknown node: ${e}`)}),await R(e)}i(Fn,"updateNode");async function bn(e,n){await(await r()).prepare("UPDATE nodes SET status = ? WHERE node_id = ?").run(n,e)}i(bn,"setStatus");async function Cn(e,n){await(await r()).prepare("UPDATE nodes SET intent = ? WHERE node_id = ?").run(n??null,e)}i(Cn,"setIntent");async function Hn(e,n,t={}){if(t.outcome===void 0){const o=[],s=[];Object.prototype.hasOwnProperty.call(n,"status")&&(o.push("status = ?"),s.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(o.push("intent = ?"),s.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(o.push("terminal_reason = ?"),s.push(n.terminal_reason??null));const d=["node_id = ?"];s.push(e),t.fromStatuses!==void 0&&(d.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),s.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&d.push("final_report IS NULL");const l=await(await r()).prepareUncached(`UPDATE nodes SET ${o.join(", ")} WHERE ${d.join(" AND ")}`).run(...s);return Number(l.changes)}const a=t.outcome;return await p(async o=>{const s=[],d=[];Object.prototype.hasOwnProperty.call(n,"status")&&(s.push("status = ?"),d.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(s.push("intent = ?"),d.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(s.push("terminal_reason = ?"),d.push(n.terminal_reason??null));const l=i((I,w)=>a.supersede===!0?"?":`CASE WHEN outcome_at IS NULL THEN ? ELSE ${w} END`,"outcomeValue");s.push(`outcome_kind = ${l(a.kind,"outcome_kind")}`,`outcome_reason = ${l(a.reason,"outcome_reason")}`,`outcome_detail_json = ${l(a.detailJson,"outcome_detail_json")}`,`outcome_revision = ${a.supersede===!0?"outcome_revision + 1":"CASE WHEN outcome_at IS NULL THEN outcome_revision + 1 ELSE outcome_revision END"}`,`outcome_at = ${l(a.at,"outcome_at")}`),d.push(a.kind,a.reason,a.detailJson,a.at);const u=["node_id = ?"];d.push(e),t.fromStatuses!==void 0&&(u.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),d.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&u.push("final_report IS NULL");const _=await o.prepareUncached(`UPDATE nodes SET ${s.join(", ")} WHERE ${u.join(" AND ")}`).run(...d);if(Number(_.changes)===0)return 0;const h=await o.prepare("SELECT outcome_at FROM nodes WHERE node_id = ?").get(e);return(a.supersede===!0||h.outcome_at===a.at)&&a.documentJson!==null&&a.documentJson!==void 0&&await ye(o,{event:"outcome",object:await me(o,e),actor:null,attrs:{revision:Number((await o.prepare("SELECT outcome_revision FROM nodes WHERE node_id = ?").get(e)).outcome_revision)},messageBody:JSON.parse(a.documentJson)}),Number(_.changes)})}i(Hn,"transitionStatusIntent");async function Mn(e){return(await(await r()).prepare("SELECT final_report FROM nodes WHERE node_id = ?").get(e))?.final_report??null}i(Mn,"getFinalReport");async function Pn(e,n){return(await(await r()).prepare("UPDATE nodes SET final_report = NULL, finalized_at = NULL WHERE node_id = ? AND final_report = ?").run(e,n)).changes>0}i(Pn,"clearFinalReportIfMatches");async function $n(e,n){await(await r()).prepare('UPDATE nodes SET tmux_session = ?, "window" = ?, pane = ? WHERE node_id = ?').run(n.tmux_session??null,n.window??null,n.pane??null,e)}i($n,"setPresence");async function qn(e,n){await(await r()).prepare("UPDATE nodes SET launched_at = ?, frozen_at = NULL, respawn_not_before = NULL WHERE node_id = ?").run(n,e)}i(qn,"recordLaunch");async function Bn(e,n){await(await r()).prepare("UPDATE nodes SET last_settled_at = ? WHERE node_id = ?").run(n,e)}i(Bn,"recordSettledTurn");async function zn(e,n){await(await r()).prepare("UPDATE nodes SET frozen_at = ? WHERE node_id = ?").run(n,e)}i(zn,"setFrozen");async function Jn(e){await(await r()).prepare("UPDATE nodes SET frozen_at = NULL WHERE node_id = ?").run(e)}i(Jn,"clearFrozen");async function Yn(e,n){await(await r()).prepare("UPDATE nodes SET respawn_failures = ?, respawn_not_before = ? WHERE node_id = ?").run(n.failures,n.notBefore,e)}i(Yn,"recordRespawnAttempt");const m="node_id NOT IN (SELECT node_id FROM warm_pool)",J=["state","cleanup_state","path","branch","repo_root","base_ref","base_sha","created_at","closed_at","next_attempt_at","attempts","quarantined","sweep_reason","sweep_detail","sweep_last_at","sweep_observed_json","abandoned_json","land_intent_json","disposal_json"];function $e(e){return e.worktree_state===null?null:Le(Object.fromEntries(J.map(n=>[n,e[`worktree_${n}`]])))}i($e,"worktreeFromProjection");async function Qn(e){const n=J.map(a=>`w.${a} AS worktree_${a}`).join(", "),t=await(await r()).prepareUncached(`SELECT n.*, ${n} FROM node_rows n LEFT JOIN worktrees w ON w.node_id = n.node_id WHERE n.profile_id = ? ORDER BY n.created`).all(e);return await L(t,async a=>await B(a,$e(a)))}i(Qn,"listNodesByProfile");async function Vn(){return(await(await r()).prepare(`SELECT
6
6
  node_id, name, description, kind, mode, lifecycle, status, cwd, profile_id,
7
7
  grantee, parent, created, frozen_at, terminal_reason, final_report,
8
8
  telemetry_tokens_in, telemetry_updated_at, pi_pid, turn_open_at, turn_closed_at,
@@ -10,13 +10,13 @@ var Z=Object.defineProperty;var a=(e,n)=>Z(e,"name",{value:n,configurable:!0});i
10
10
  fault_operation_id, fault_anchor_entry_id, fault_retry_disposition,
11
11
  fault_retry_by, fault_retry_attempt, fault_retry_max, fault_retry_next_at,
12
12
  fault_provider_error_json
13
- FROM node_rows WHERE ${w} ORDER BY created`).all()).map(f)}a(Mn,"browseNodes");async function Pn(e,n){await(await r()).prepare(`UPDATE nodes
13
+ FROM node_rows WHERE ${m} ORDER BY created`).all()).map(f)}i(Vn,"browseNodes");async function Kn(e,n){await(await r()).prepare(`UPDATE nodes
14
14
  SET telemetry_tokens_in = ?,
15
15
  telemetry_context_tokens = COALESCE(?, telemetry_context_tokens),
16
16
  telemetry_last_activity = COALESCE(?, telemetry_last_activity),
17
17
  telemetry_updated_at = ?
18
18
  WHERE node_id = ?
19
- AND (telemetry_updated_at IS NULL OR telemetry_updated_at < ?)`).run(n.tokensIn,n.contextTokens,n.lastActivity,n.updatedAt,e,n.updatedAt)}a(Pn,"updateNodeTelemetry");async function $n(){return(await(await r()).prepare(`SELECT
19
+ AND (telemetry_updated_at IS NULL OR telemetry_updated_at < ?)`).run(n.tokensIn,n.contextTokens,n.lastActivity,n.updatedAt,e,n.updatedAt)}i(Kn,"updateNodeTelemetry");async function Gn(){return(await(await r()).prepare(`SELECT
20
20
  node_id, name, description, cycles, kind, status, frozen_at,
21
21
  telemetry_context_tokens, telemetry_last_activity,
22
22
  pi_pid, turn_open_at, turn_closed_at,
@@ -24,14 +24,14 @@ var Z=Object.defineProperty;var a=(e,n)=>Z(e,"name",{value:n,configurable:!0});i
24
24
  fault_operation_id, fault_anchor_entry_id, fault_retry_disposition,
25
25
  fault_retry_by, fault_retry_attempt, fault_retry_max, fault_retry_next_at,
26
26
  fault_provider_error_json
27
- FROM node_rows WHERE ${w} ORDER BY created`).all()).map(n=>{const t=n.pi_pid??null,i=F(t),o=M(n);return{node_id:n.node_id,name:n.name,description:n.description??null,cycles:n.cycles??null,kind:n.kind,status:n.status,frozen_at:n.frozen_at??null,pi_pid:t,telemetry_context_tokens:n.telemetry_context_tokens??null,telemetry_last_activity:n.telemetry_last_activity??null,fault:o?.link==="pi\u2192provider"&&o.retry.by==="daemon"&&o.retry.disposition==="auto"&&!i?null:o,streaming:n.turn_open_at!==null&&n.turn_closed_at===null&&i}})}a($n,"graphNodes");async function Fe(e){const n=[w],t=[];if(e?.under!==void 0&&(e.underInclusive===!0?(n.push(`(node_id IN (${xe}) OR node_id = ?)`),t.push(e.under,e.under,e.under)):(n.push(`node_id IN (${Ue})`),t.push(e.under,e.under))),e?.lifecycle!==void 0&&(n.push("lifecycle = ?"),t.push(e.lifecycle)),e?.kind!==void 0&&(n.push("kind = ?"),t.push(e.kind)),e?.mode!==void 0&&(n.push("mode = ?"),t.push(e.mode)),e?.profileId!==void 0&&(n.push("profile_id = ?"),t.push(e.profileId)),e?.profilePrefix!==void 0&&(n.push("profile_id LIKE ? ESCAPE '\\'"),t.push(`${e.profilePrefix.replace(/[\\%_]/g,o=>`\\${o}`)}%`)),e?.parent!==void 0&&(n.push("parent = ?"),t.push(e.parent)),e?.topLevel===!0&&n.push("parent IS NULL"),e?.hanging===!0&&n.push("parent IS NOT NULL AND parent NOT IN (SELECT node_id FROM node_rows)"),e?.status!==void 0){const o=Array.isArray(e.status)?e.status:[e.status];n.push(`status IN (${o.map(()=>"?").join(",")})`),t.push(...o)}return e?.withRecordedPid===!0&&n.push("pi_pid IS NOT NULL"),e?.withRecordedPidOrFrozen===!0&&n.push("(pi_pid IS NOT NULL OR frozen_at IS NOT NULL)"),(await(await r()).prepareUncached(`SELECT * FROM node_rows WHERE ${n.join(" AND ")} ORDER BY created`).all(...t)).map(f)}a(Fe,"listNodes");const Ce=`SELECT
27
+ FROM node_rows WHERE ${m} ORDER BY created`).all()).map(n=>{const t=n.pi_pid??null,a=M(t),o=q(n);return{node_id:n.node_id,name:n.name,description:n.description??null,cycles:n.cycles??null,kind:n.kind,status:n.status,frozen_at:n.frozen_at??null,pi_pid:t,telemetry_context_tokens:n.telemetry_context_tokens??null,telemetry_last_activity:n.telemetry_last_activity??null,fault:o?.link==="pi\u2192provider"&&o.retry.by==="daemon"&&o.retry.disposition==="auto"&&!a?null:o,streaming:n.turn_open_at!==null&&n.turn_closed_at===null&&a}})}i(Gn,"graphNodes");async function qe(e){const n=[m],t=[];if(e?.under!==void 0&&(e.underInclusive===!0?(n.push(`(node_id IN (${Ce}) OR node_id = ?)`),t.push(e.under,e.under,e.under)):(n.push(`node_id IN (${He})`),t.push(e.under,e.under))),e?.lifecycle!==void 0&&(n.push("lifecycle = ?"),t.push(e.lifecycle)),e?.kind!==void 0&&(n.push("kind = ?"),t.push(e.kind)),e?.mode!==void 0&&(n.push("mode = ?"),t.push(e.mode)),e?.profileId!==void 0&&(n.push("profile_id = ?"),t.push(e.profileId)),e?.profilePrefix!==void 0&&(n.push("profile_id LIKE ? ESCAPE '\\'"),t.push(`${e.profilePrefix.replace(/[\\%_]/g,o=>`\\${o}`)}%`)),e?.parent!==void 0&&(n.push("parent = ?"),t.push(e.parent)),e?.topLevel===!0&&n.push("parent IS NULL"),e?.hanging===!0&&n.push("parent IS NOT NULL AND parent NOT IN (SELECT node_id FROM node_rows)"),e?.status!==void 0){const o=Array.isArray(e.status)?e.status:[e.status];n.push(`status IN (${o.map(()=>"?").join(",")})`),t.push(...o)}return e?.withRecordedPid===!0&&n.push("pi_pid IS NOT NULL"),e?.withRecordedPidOrFrozen===!0&&n.push("(pi_pid IS NOT NULL OR frozen_at IS NOT NULL)"),(await(await r()).prepareUncached(`SELECT * FROM node_rows WHERE ${n.join(" AND ")} ORDER BY created`).all(...t)).map(f)}i(qe,"listNodes");const Be=`SELECT
28
28
  node_id, created, launched_at, last_settled_at, finalized_at,
29
29
  outcome_at, last_activity_at, pane, "window", tmux_session
30
30
  FROM nodes
31
- WHERE ${w}
31
+ WHERE ${m}
32
32
  AND status IN ('done', 'canceled', 'dead')
33
33
  AND (pane IS NOT NULL OR "window" IS NOT NULL OR tmux_session IS NOT NULL)
34
- ORDER BY created`;async function qn(){return await(await r()).prepare(Ce).all()}a(qn,"listTerminalPlacementResidue");const be=`WITH candidates AS MATERIALIZED (
34
+ ORDER BY created`;async function Xn(){return await(await r()).prepare(Be).all()}i(Xn,"listTerminalPlacementResidue");const ze=`WITH candidates AS MATERIALIZED (
35
35
  SELECT node_id FROM nodes WHERE status IN ('active', 'idle')
36
36
  UNION
37
37
  SELECT node_id FROM nodes
@@ -45,19 +45,23 @@ var Z=Object.defineProperty;var a=(e,n)=>Z(e,"name",{value:n,configurable:!0});i
45
45
  AND v.node_id NOT IN (SELECT node_id FROM warm_pool)
46
46
  AND v.kind != 'human'
47
47
  AND (v.status IN ('active', 'idle') OR (v.status IN ('done', 'canceled', 'dead') AND (v.pi_pid IS NOT NULL OR v.frozen_at IS NOT NULL)))
48
- ORDER BY v.created`;async function Bn(){return(await(await r()).prepare(be).all()).map(f)}a(Bn,"listNodesForSupervision");const B=a(e=>`(${e}.status = 'done' AND ${e}.intent = 'parked' AND ${e}.lifecycle = 'resident' AND ${e}.final_report IS NULL AND ${e}.deleting_at IS NULL)`,"PARKED_RESIDENT_SQL"),He=a(e=>`(${e}.status IN ('active', 'idle') OR ${B(e)} OR (${e}.status IN ('done', 'canceled', 'dead') AND ${e}.frozen_at IS NOT NULL AND ${e}.final_report IS NULL))`,"RESUMABLE_NODE_SQL");async function zn(e){return await(await r()).prepare(`SELECT node_id, cwd FROM nodes WHERE ${He("nodes")} AND node_id != ?`).all(e)}a(zn,"resumableNodeCwds");async function Yn(){return(await(await r()).prepare(`SELECT v.* FROM node_rows v
49
- WHERE ${B("v")}
50
- AND EXISTS (SELECT 1 FROM deliveries d WHERE d.destination_id = v.node_id AND ${ue("d")}) AND v.kind != 'human' AND v.node_id NOT IN (SELECT node_id FROM warm_pool)
51
- ORDER BY v.created`).all()).map(f)}a(Yn,"listParkedResidentsWithWakeMail");async function Jn(){return await(await r()).prepare(`SELECT node_id, name, created, grantee FROM node_rows WHERE ${w} ORDER BY created`).all()}a(Jn,"listNodeIdentities");async function Qn(e,n){await(await r()).prepare("INSERT INTO warm_pool (node_id, recipe_key, created) VALUES (?, ?, ?) ON CONFLICT (node_id) DO UPDATE SET recipe_key = excluded.recipe_key, created = excluded.created").run(e,n,new Date().toISOString())}a(Qn,"registerWarmSpare");async function Vn(e){return(await(await r()).prepare("DELETE FROM warm_pool WHERE node_id = ?").run(e)).changes>0}a(Vn,"unregisterWarmSpare");async function T(e,n){const t=`SELECT w.node_id AS node_id, w.recipe_key AS recipe_key, w.created AS created,
48
+ ORDER BY v.created`;async function Zn(){return(await(await r()).prepare(ze).all()).map(f)}i(Zn,"listNodesForSupervision");const Y=i(e=>`(${e}.status = 'done' AND ${e}.intent = 'parked' AND ${e}.lifecycle = 'resident' AND ${e}.final_report IS NULL AND ${e}.deleting_at IS NULL)`,"PARKED_RESIDENT_SQL"),Je=i(e=>`(${e}.status IN ('active', 'idle') OR ${Y(e)} OR (${e}.status IN ('done', 'canceled', 'dead') AND ${e}.frozen_at IS NOT NULL AND ${e}.final_report IS NULL))`,"RESUMABLE_NODE_SQL");async function et(e){return await(await r()).prepare(`SELECT node_id, cwd FROM nodes WHERE ${Je("nodes")} AND node_id != ?`).all(e)}i(et,"resumableNodeCwds");async function nt(){return(await(await r()).prepare(`SELECT v.* FROM node_rows v
49
+ WHERE ${Y("v")}
50
+ AND EXISTS (SELECT 1 FROM deliveries d WHERE d.destination_id = v.node_id AND ${we("d")}) AND v.kind != 'human' AND v.node_id NOT IN (SELECT node_id FROM warm_pool)
51
+ ORDER BY v.created`).all()).map(f)}i(nt,"listParkedResidentsWithWakeMail");async function tt(e,n){await(await r()).prepare("INSERT INTO warm_pool (node_id, recipe_key, created) VALUES (?, ?, ?) ON CONFLICT (node_id) DO UPDATE SET recipe_key = excluded.recipe_key, created = excluded.created").run(e,n,new Date().toISOString())}i(tt,"registerWarmSpare");async function at(e){return(await(await r()).prepare("DELETE FROM warm_pool WHERE node_id = ?").run(e)).changes>0}i(at,"unregisterWarmSpare");async function D(e,n){const t=`SELECT w.node_id AS node_id, w.recipe_key AS recipe_key, w.created AS created,
52
52
  n.status AS status, n.pi_pid AS pi_pid, n.pi_pid_identity AS pi_pid_identity
53
- FROM warm_pool w JOIN node_rows n ON n.node_id = w.node_id`+(n===void 0?"":" WHERE w.recipe_key = ?")+" ORDER BY w.created";return(n===void 0?await e.prepareUncached(t).all():await e.prepareUncached(t).all(n)).map(o=>({node_id:o.node_id,recipe_key:o.recipe_key,created:o.created,status:o.status,pi_pid:o.pi_pid??null,pi_pid_identity:o.pi_pid_identity??null}))}a(T,"warmSpareRowsFor");async function Kn(){return await T(await r())}a(Kn,"listWarmSpares");async function Gn(e){return await(await r()).prepare("SELECT 1 FROM warm_pool WHERE node_id = ?").get(e)!==void 0}a(Gn,"isWarmSpare");function z(e){return e.status!=="active"&&e.status!=="idle"?!1:Ne(e.pi_pid,e.pi_pid_identity)==="alive"}a(z,"spareIsClaimable");async function Xn(e,n,t={}){return await p(async i=>{const o=(await T(i,e)).find(z);return o===void 0||t.requestKey!==void 0&&(await i.prepareUncached("UPDATE create_requests SET node_id = ?, state = 'warm_claimed' WHERE request_key = ? AND node_id IS NULL").run(o.node_id,t.requestKey)).changes===0||(await i.prepare("DELETE FROM warm_pool WHERE node_id = ?").run(o.node_id)).changes===0?null:(await i.prepare("UPDATE nodes SET created = ?, deadline_at = ? WHERE node_id = ?").run(n,t.deadlineAt??null,o.node_id),t.outcomeDelivery!==void 0&&await I(i,o.node_id,t.outcomeDelivery),o.node_id)})}a(Xn,"claimWarmSpare");async function Zn(e){return(await T(await r(),e)).filter(z).length}a(Zn,"warmSpareCount");async function et(e){return(await(await r()).prepare("SELECT node_id FROM nodes WHERE parent = ? ORDER BY created").all(e)).map(n=>n.node_id)}a(et,"childrenOf");async function nt(){return(await(await r()).prepareUncached(`SELECT node_id, name, kind, mode, lifecycle, status, cwd, host_kind, parent, created, description
54
- FROM node_rows WHERE ${w} ORDER BY created`).all()).map(e=>({node_id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,parent:e.parent??null,created:e.created,description:e.description??null}))}a(nt,"rosterNodes");async function tt(e){return(await(await r()).prepareUncached(`SELECT * FROM node_rows
53
+ FROM warm_pool w JOIN node_rows n ON n.node_id = w.node_id`+(n===void 0?"":" WHERE w.recipe_key = ?")+" ORDER BY w.created";return(n===void 0?await e.prepareUncached(t).all():await e.prepareUncached(t).all(n)).map(o=>({node_id:o.node_id,recipe_key:o.recipe_key,created:o.created,status:o.status,pi_pid:o.pi_pid??null,pi_pid_identity:o.pi_pid_identity??null}))}i(D,"warmSpareRowsFor");async function it(){return await D(await r())}i(it,"listWarmSpares");async function ot(e){return await(await r()).prepare("SELECT 1 FROM warm_pool WHERE node_id = ?").get(e)!==void 0}i(ot,"isWarmSpare");function Q(e){return e.status!=="active"&&e.status!=="idle"?!1:De(e.pi_pid,e.pi_pid_identity)==="alive"}i(Q,"spareIsClaimable");async function rt(e,n,t={}){return await p(async a=>{const o=(await D(a,e)).find(Q);return o===void 0||t.requestKey!==void 0&&(await a.prepareUncached("UPDATE create_requests SET node_id = ?, state = 'warm_claimed' WHERE request_key = ? AND node_id IS NULL").run(o.node_id,t.requestKey)).changes===0||(await a.prepare("DELETE FROM warm_pool WHERE node_id = ?").run(o.node_id)).changes===0?null:(await a.prepare("UPDATE nodes SET created = ?, deadline_at = ? WHERE node_id = ?").run(n,t.deadlineAt??null,o.node_id),t.outcomeDelivery!==void 0&&await H(a,o.node_id,t.outcomeDelivery),o.node_id)})}i(rt,"claimWarmSpare");async function st(e){return(await D(await r(),e)).filter(Q).length}i(st,"warmSpareCount");async function dt(e){return(await(await r()).prepare("SELECT node_id FROM nodes WHERE parent = ? ORDER BY created").all(e)).map(n=>n.node_id)}i(dt,"childrenOf");async function ut(){return(await(await r()).prepareUncached(`SELECT node_id, name, kind, mode, lifecycle, status, cwd, host_kind, parent, created, description
54
+ FROM node_rows WHERE ${m} ORDER BY created`).all()).map(e=>({node_id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,parent:e.parent??null,created:e.created,description:e.description??null}))}i(ut,"rosterNodes");async function lt(e){return(await(await r()).prepareUncached(`SELECT * FROM node_rows
55
55
  WHERE parent IS NULL AND kind != 'human'
56
56
  AND status IN ('active','idle') AND cwd = ?
57
- AND ${w}
58
- ORDER BY created DESC`).all(e)).map(f)}a(tt,"listResumableRoots");export*from"./tree.js";async function at(e,n,t){return await p(async i=>{const o=await i.prepare("SELECT status, final_report, frozen_at FROM nodes WHERE node_id = ?").get(e);return o===void 0?{kind:"missing"}:n(o)?{kind:"terminal",status:o.status,final_report:o.final_report}:{kind:"ran",result:await t()}})}a(at,"withFreshTerminalGuard");async function R(e){return await fe(e)}a(R,"hasUnreconciledManagedWorktree");async function Y(e,n={}){return!["done","dead","canceled"].includes(e.status)||e.pi_pid!==null||e.frozen_at!==null||await R(e.node_id)||e.node_id===x()||j(e)?!1:n.pastTtl===!0||!["crashed","stranded",null].includes(e.terminal_reason??null)}a(Y,"isCollectible");async function L(e){if(e.length===0)return[];const n=e.map(()=>"?").join(",");return(await(await r()).prepareUncached(`SELECT DISTINCT session_id FROM sessions WHERE node_id IN (${n}) AND session_id IS NOT NULL`).all(...e)).map(({session_id:i})=>i)}a(L,"nodeConversationIds");async function D(e){const n=await r(),t=n.prepare("SELECT 1 FROM sessions WHERE session_id = ? LIMIT 1"),i=n.prepare(`SELECT 1 FROM nodes n WHERE n.fork_source_file LIKE '%\\_' || ? || '.jsonl' ESCAPE '\\'
59
- AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.node_id = n.node_id) LIMIT 1`);for(const o of e)if(!(await t.get(o)!==void 0||await i.get(o)!==void 0))try{await re().delete(o)}catch(s){if(!(s instanceof se))throw s}}a(D,"deleteNodeConversations");async function g(e,n){const t=await H(e,n);return t===null||t.deleted_at!==null?!1:(await Ae(e,t),!0)}a(g,"deleteNodeObject");async function v(e){return{nodeId:e,app:await N(e)===null?null:await Oe(e)}}a(v,"nodeFiles");async function J({nodeId:e,app:n}){n!==null&&W(b(n).nodes,await Le(e,n)),W(ae(C(e)),C(e))}a(J,"removeNodeAppTrees");function Q({nodeId:e}){return[he(e)]}a(Q,"nodeOwnedDirs");function V({nodeId:e,app:n}){if(n!==null)try{ee(U(b(n).conversations,e))}catch(t){if(!["ENOENT","ENOTEMPTY"].includes(t.code??""))throw t}}a(V,"removeNodeConversationDir");async function k(e){await J(e);for(const n of Q(e))ne(n,{recursive:!0,force:!0});V(e)}a(k,"removeNodeFiles");async function it(e){if(await R(e))return{deleted:!1};const n=await v(e),t=await L([e]),i=await p(async o=>(await g(o,e),{deleted:!0}));return await D(t),await k(n),i}a(it,"deleteNode");async function ot(e){const n=[...new Set(e)];if(n.length===0)return[];const t=await y(n,R);if(t.length>0)throw new Error(`refusing to delete nodes with unreconciled managed worktrees: ${t.join(", ")}`);const i=await h(n,v),o=await L(n),s=n.map(()=>"?").join(","),d=(await(await r()).prepareUncached(`SELECT node_id FROM nodes WHERE node_id IN (${s}) ORDER BY created`).all(...n)).map(({node_id:u})=>u),c=await y(d,async u=>await p(async _=>await g(_,u)));await D(o);for(const u of c){const _=i[n.indexOf(u)];await A(_,async()=>await k(_))}return c}a(ot,"deleteNodes");async function rt(e){const n=e.dryRun??!1,t=e.includeStale??!1,i=Date.now()-e.ttlDays*864e5,o=new Date(i).toISOString(),s=x()??"",d=await r(),c=a(l=>X(l)<i,"quiet"),u=a(l=>({node_id:l.node_id,status:l.status,created:l.created}),"toPruned"),_=(await y((await d.prepare(`SELECT * FROM node_rows
57
+ AND ${m}
58
+ ORDER BY created DESC`).all(e)).map(f)}i(lt,"listResumableRoots");export*from"./tree.js";async function ct(e,n,t){return await p(async a=>{const o=await a.prepare("SELECT status, final_report, frozen_at FROM nodes WHERE node_id = ?").get(e);return o===void 0?{kind:"missing"}:n(o)?{kind:"terminal",status:o.status,final_report:o.final_report}:{kind:"ran",result:await t()}})}i(ct,"withFreshTerminalGuard");async function S(e){return await Se(e)}i(S,"hasUnreconciledManagedWorktree");async function V(e,n={}){return!["done","dead","canceled"].includes(e.status)||e.pi_pid!==null||e.frozen_at!==null||await S(e.node_id)||e.node_id===j()||C(e)?!1:n.pastTtl===!0||!["crashed","stranded",null].includes(e.terminal_reason??null)}i(V,"isCollectible");async function v(e){if(e.length===0)return[];const n=e.map(()=>"?").join(","),t=await(await r()).prepareUncached(`SELECT s.session_id, s.transcript_path, s.node_id, n.grantee FROM sessions s JOIN node_rows n ON n.node_id = s.node_id WHERE s.node_id IN (${n}) AND s.session_id IS NOT NULL AND s.transcript_path IS NOT NULL ORDER BY s.created_at, s.seq`).all(...e);return[...new Map(t.map(a=>[a.session_id,{id:a.session_id,path:a.transcript_path,dir:N(O(a.grantee).conversations,a.node_id)}])).values()]}i(v,"nodeConversationIds");async function g(e){const n=await r(),t=n.prepare("SELECT 1 FROM sessions WHERE session_id = ? LIMIT 1"),a=n.prepare(`SELECT 1 FROM nodes n WHERE n.fork_source_file LIKE '%\\_' || ? || '.jsonl' ESCAPE '\\'
59
+ AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.node_id = n.node_id) LIMIT 1`);for(const{id:o,path:s,dir:d}of e)if(!(await t.get(o)!==void 0||await a.get(o)!==void 0)&&ae(s,{throwIfNoEntry:!1})!==void 0){if(!Ye(d,s)){console.error(`[canvas] not deleting transcript outside its node's conversations directory: ${s}`);continue}try{await pe().delete(o,s)}catch(l){if(!(l instanceof fe))throw l}}}i(g,"deleteNodeConversations");function Ye(e,n){try{const t=ue(W(e),N(W(F(n)),se(n)));return t!==""&&t!==".."&&!t.startsWith(`..${ce}`)&&!de(t)}catch(t){if(t.code==="ENOENT")return!1;throw t}}i(Ye,"isBeneath");async function A(e,n){const t=await $(e,n);return t===null||t.deleted_at!==null?!1:(await be(e,t),!0)}i(A,"deleteNodeObject");async function k(e){return{nodeId:e,app:await R(e)===null?null:await ke(e)}}i(k,"nodeFiles");async function K({nodeId:e,app:n}){n!==null&&b(O(n).nodes,await Ue(e,n)),b(F(P(e)),P(e))}i(K,"removeNodeAppTrees");function G({nodeId:e}){return[Ae(e)]}i(G,"nodeOwnedDirs");function X({nodeId:e,app:n}){if(n!==null)try{ie(N(O(n).conversations,e))}catch(t){if(!["ENOENT","ENOTEMPTY"].includes(t.code??""))throw t}}i(X,"removeNodeConversationDir");async function x(e){await K(e);for(const n of G(e))oe(n,{recursive:!0,force:!0});X(e)}i(x,"removeNodeFiles");async function _t(e){if(await S(e))return{deleted:!1};const n=await k(e),t=await v([e]),a=await p(async o=>(await A(o,e),{deleted:!0}));return await g(t),await x(n),a}i(_t,"deleteNode");async function pt(e){const n=[...new Set(e)];if(n.length===0)return[];const t=await y(n,S);if(t.length>0)throw new Error(`refusing to delete nodes with unreconciled managed worktrees: ${t.join(", ")}`);const a=await L(n,k),o=await v(n),s=n.map(()=>"?").join(","),d=(await(await r()).prepareUncached(`SELECT node_id FROM nodes WHERE node_id IN (${s}) ORDER BY created`).all(...n)).map(({node_id:u})=>u),l=await y(d,async u=>await p(async _=>await A(_,u)));await g(o);for(const u of l){const _=a[n.indexOf(u)];await U(_,async()=>await x(_))}return l}i(pt,"deleteNodes");async function ft(e){const n=e.dryRun??!1,t=e.includeStale??!1,a=Date.now()-e.ttlDays*864e5,o=new Date(a).toISOString(),s=j()??"",d=await r(),l=i(c=>ne(c)<a,"quiet"),u=i(c=>({node_id:c.node_id,status:c.status,created:c.created}),"toPruned"),_=await Qe(),h=(await y((await d.prepare(`SELECT * FROM node_rows
60
60
  WHERE status IN ('dead', 'done', 'canceled') AND created < ?
61
- ORDER BY created`).all(o)).map(f),async l=>await Y(l,{pastTtl:!0})&&c(l)&&!await e.held(l.node_id))).map(u),S=t?(await y((await d.prepare(`SELECT * FROM node_rows
61
+ ORDER BY created`).all(o)).map(f),async c=>await V(c,{pastTtl:!0})&&l(c)&&!_.has(c.node_id)&&!await e.held(c.node_id))).map(u),I=t?(await y((await d.prepare(`SELECT * FROM node_rows
62
62
  WHERE status IN ('active', 'idle') AND created < ?
63
- ORDER BY created`).all(o)).map(f),async l=>l.node_id!==s&&!me(l.pi_pid??null)&&(l.frozen_at??null)===null&&(l.respawn_not_before??null)===null&&l.intent!=="idle-release"&&!j(l)&&!await de(l.node_id)&&!await R(l.node_id)&&c(l)&&!await e.held(l.node_id))).map(u):[],m=await K([..._,...S]);return n||m.length===0?{pruned:m,dryRun:n}:(await G(m,e.removeDirs),{pruned:m,dryRun:n})}a(rt,"pruneNodes");async function K(e){const t=(await r()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ?"),i=new Set(e.map(o=>o.node_id));return await y(e,async o=>(await t.all(o.node_id,o.node_id)).every(s=>i.has(s.node_id)))}a(K,"retainRunRootsWithMembers");async function G(e,n){if(e.length===0)return;const t=await h(e,async o=>await v(o.node_id)),i=await L(e.map(o=>o.node_id));for(const o of e)await p(async s=>await g(s,o.node_id));if(await D(i),n!==void 0){for(const o of t)await A(o,async()=>{await J(o),V(o)});n(t.flatMap(Q));return}for(const o of t)await A(o,async()=>await k(o))}a(G,"deletePrunedNodes");async function A(e,n){try{n()}catch(t){oe({level:"error",event:"canvas.node_files.remove_failed",error:t,fields:{node_id:e.nodeId}})}}a(A,"eachNodeBestEffort");function X(e){let n=0;for(const t of[e.created,e.launched_at,e.last_settled_at,e.finalized_at,e.outcome_at,e.last_activity_at]){if(t==null)continue;const i=Date.parse(t);Number.isFinite(i)&&i>n&&(n=i)}try{const t=te(U(Se(e.node_id),"telemetry.json"),{throwIfNoEntry:!1});t!==void 0&&t.mtimeMs>n&&(n=t.mtimeMs)}catch{}return n}a(X,"lastActivityMs");async function st(e){const n=e.dryRun??!1,t=await Fe(),i=await y(t,async u=>await Y(u)&&!await e.held(u.node_id)),o=t.length-i.length,s=Math.max(0,e.maxNodes-o),d=i.map(u=>({row:u,recency:X(u)})).sort((u,_)=>_.recency-u.recency).slice(s).map(({row:u})=>({node_id:u.node_id,status:u.status,created:u.created})),c=await K(d);return n||c.length===0?{pruned:c,dryRun:n}:(await G(c),{pruned:c,dryRun:n})}a(st,"pruneToLimit");export{B as PARKED_RESIDENT_SQL,He as RESUMABLE_NODE_SQL,Nn as abandonCreateRequest,cn as assertRunNotDeleting,Mn as browseNodes,Rn as canonicalSessionFile,et as childrenOf,Xn as claimWarmSpare,Un as clearFinalReportIfMatches,Cn as clearFrozen,mn as completeCreateRequest,_n as createNode,it as deleteNode,ot as deleteNodes,Tn as dueNodeDeadlines,pn as findCreateRequest,Sn as findNodeBySessionFile,xn as getFinalReport,N as getNode,hn as getRow,On as getRowByPane,$n as graphNodes,Y as isCollectible,Gn as isWarmSpare,X as lastActivityMs,Jn as listNodeIdentities,Fe as listNodes,Hn as listNodesByProfile,Bn as listNodesForSupervision,Yn as listParkedResidentsWithWakeMail,tt as listResumableRoots,qn as listTerminalPlacementResidue,Kn as listWarmSpares,v as nodeFiles,be as nodesForSupervisionSql,En as pruneExpiredCreateRequests,rt as pruneNodes,st as pruneToLimit,wn as recordCreateRequestNode,jn as recordLaunch,bn as recordRespawnAttempt,In as recordSettledTurn,Qn as registerWarmSpare,k as removeNodeFiles,fn as reserveCreateRequest,yn as reserveCreateRequestNode,zn as resumableNodeCwds,nt as rosterNodes,Fn as setFrozen,kn as setIntent,Ln as setNodeDeadline,Wn as setPresence,vn as setStatus,Dn as shiftProfileNodeDeadlines,Ce as terminalPlacementResidueSql,An as transitionStatusIntent,Vn as unregisterWarmSpare,gn as updateNode,Pn as updateNodeTelemetry,Zn as warmSpareCount,at as withFreshTerminalGuard};
63
+ ORDER BY created`).all(o)).map(f),async c=>c.node_id!==s&&!Te(c.pi_pid??null)&&(c.frozen_at??null)===null&&(c.respawn_not_before??null)===null&&c.intent!=="idle-release"&&!C(c)&&!await Ee(c.node_id)&&!await S(c.node_id)&&l(c)&&!await e.held(c.node_id))).map(u):[],w=await Z([...h,...I]);return n||w.length===0?{pruned:w,dryRun:n}:(await ee(w,e.removeDirs),{pruned:w,dryRun:n})}i(ft,"pruneNodes");async function Qe(){const e=await(await r()).prepare(`SELECT DISTINCT d.owner AS owner FROM watches w
64
+ JOIN objects d ON d.id = w.object AND d.type = 'document' AND d.deleted_at IS NULL
65
+ JOIN objects o ON o.id = w.watcher AND o.deleted_at IS NULL
66
+ LEFT JOIN nodes n ON n.node_id = w.watcher
67
+ WHERE w.watcher != d.owner AND d.owner IS NOT NULL AND (n.node_id IS NULL OR n.status IN ('active', 'idle'))`).all();return new Set(e.map(n=>n.owner))}i(Qe,"ownersOfWatchedDocuments");async function Z(e){const t=(await r()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ?"),a=new Set(e.map(o=>o.node_id));return await y(e,async o=>(await t.all(o.node_id,o.node_id)).every(s=>a.has(s.node_id)))}i(Z,"retainRunRootsWithMembers");async function ee(e,n){if(e.length===0)return;const t=await L(e,async o=>await k(o.node_id)),a=await v(e.map(o=>o.node_id));for(const o of e)await p(async s=>await A(s,o.node_id));if(await g(a),n!==void 0){for(const o of t)await U(o,async()=>{await K(o),X(o)});n(t.flatMap(G));return}for(const o of t)await U(o,async()=>await x(o))}i(ee,"deletePrunedNodes");async function U(e,n){try{n()}catch(t){_e({level:"error",event:"canvas.node_files.remove_failed",error:t,fields:{node_id:e.nodeId}})}}i(U,"eachNodeBestEffort");function ne(e){let n=0;for(const t of[e.created,e.launched_at,e.last_settled_at,e.finalized_at,e.outcome_at,e.last_activity_at]){if(t==null)continue;const a=Date.parse(t);Number.isFinite(a)&&a>n&&(n=a)}try{const t=re(N(ge(e.node_id),"telemetry.json"),{throwIfNoEntry:!1});t!==void 0&&t.mtimeMs>n&&(n=t.mtimeMs)}catch{}return n}i(ne,"lastActivityMs");async function Et(e){const n=e.dryRun??!1,t=await qe(),a=await y(t,async u=>await V(u)&&!await e.held(u.node_id)),o=t.length-a.length,s=Math.max(0,e.maxNodes-o),d=a.map(u=>({row:u,recency:ne(u)})).sort((u,_)=>_.recency-u.recency).slice(s).map(({row:u})=>({node_id:u.node_id,status:u.status,created:u.created})),l=await Z(d);return n||l.length===0?{pruned:l,dryRun:n}:(await ee(l),{pruned:l,dryRun:n})}i(Et,"pruneToLimit");export{Y as PARKED_RESIDENT_SQL,Je as RESUMABLE_NODE_SQL,gn as abandonCreateRequest,Rn as assertRunNotDeleting,Vn as browseNodes,An as canonicalSessionFile,dt as childrenOf,rt as claimWarmSpare,Pn as clearFinalReportIfMatches,Jn as clearFrozen,vn as completeCreateRequest,Sn as createNode,_t as deleteNode,pt as deleteNodes,In as dueNodeDeadlines,hn as findCreateRequest,kn as findNodeBySessionFile,Mn as getFinalReport,R as getNode,xn as getRow,Un as getRowByPane,Gn as graphNodes,V as isCollectible,ot as isWarmSpare,ne as lastActivityMs,qe as listNodes,Qn as listNodesByProfile,Zn as listNodesForSupervision,nt as listParkedResidentsWithWakeMail,lt as listResumableRoots,Xn as listTerminalPlacementResidue,it as listWarmSpares,k as nodeFiles,ze as nodesForSupervisionSql,Ln as pruneExpiredCreateRequests,ft as pruneNodes,Et as pruneToLimit,Tn as recordCreateRequestNode,qn as recordLaunch,Yn as recordRespawnAttempt,Bn as recordSettledTurn,tt as registerWarmSpare,x as removeNodeFiles,On as reserveCreateRequest,Dn as reserveCreateRequestNode,et as resumableNodeCwds,ut as rosterNodes,zn as setFrozen,Cn as setIntent,jn as setNodeDeadline,$n as setPresence,bn as setStatus,Wn as shiftProfileNodeDeadlines,Be as terminalPlacementResidueSql,Hn as transitionStatusIntent,at as unregisterWarmSpare,Fn as updateNode,Kn as updateNodeTelemetry,st as warmSpareCount,ct as withFreshTerminalGuard};
@@ -27,6 +27,9 @@ export interface CanvasDb {
27
27
  /** True when the calling async context is inside a `withCanvasWrite` body (its own open transaction). */
28
28
  readonly inTransaction: boolean;
29
29
  }
30
+ /** True only for a statement that starts with SELECT and cannot write or lock. Anything else — WITH, VALUES, SHOW,
31
+ * a comment-led statement, an unrecognised shape — is not a read, so a wrong guess lands on the writer. */
32
+ export declare function isReadStatement(sql: string): boolean;
30
33
  /** The URL of the database for the current `CRTR_HOME`. In a test it creates the home's database on first use. */
31
34
  export declare function canvasDatabaseUrl(): Promise<string>;
32
35
  /** Open (or reuse) the canvas db handle for the current `CRTR_HOME`. Keyed by database URL, so tests with distinct
@@ -1,4 +1,4 @@
1
- var R=Object.defineProperty;var a=(e,t)=>R(e,"name",{value:t,configurable:!0});import{AsyncLocalStorage as L}from"node:async_hooks";import{randomBytes as x}from"node:crypto";import{appendFileSync as k,mkdirSync as M,readFileSync as y}from"node:fs";import{dirname as N,join as T}from"node:path";import{fileURLToPath as P}from"node:url";import E from"pg";import{resolveDatabaseUrl as D}from"../../daemon/postgres/supervisor.js";import{publishFileExclusive as q}from"../fs-utils.js";import{initializeEmptyLayout as B,runtimeRoot as l}from"../layout.js";import{translatePlaceholders as I,translateOnce as U}from"../statements.js";import{ensureHome as H}from"./paths.js";import{CANVAS_SCHEMA_VERSION as h}from"./schema-version.js";const F="crtr_test_template";class K extends Error{static{a(this,"CanvasConstraintError")}constraint;code="23505";constructor(t,n,r){super(t,r),this.name="CanvasConstraintError",this.constraint=n}}function V(e){const t=process.env.CRTR_DB_OPEN_LEDGER;if(t)try{const n=`${new Date().toISOString()} ${process.pid} ${process.argv[1]??"<unknown>"} ${e.replace(/\/\/([^:@/]*):[^@/]*@/,"//$1@")}
2
- `;k(t,n)}catch{}}a(V,"recordOpenLedger");const G=1e3,d=new L;class W{static{a(this,"FifoLock")}tail=Promise.resolve();pending=0;get busy(){return this.pending>0}async acquire(){this.pending++;const t=this.tail;let n;return this.tail=new Promise(r=>{n=r}),await t,()=>{this.pending--,n()}}}const J=new Set([114,3802]),Y={getTypeParser:a(((e,t)=>J.has(e)?n=>n:e===20||e===1700?n=>{const r=Number(n);if(e===20&&!Number.isSafeInteger(r))throw new Error(`canvas db: bigint ${n} does not fit a JS number`);return r}:E.types.getTypeParser(e,t)),"getTypeParser")};class j{static{a(this,"Handle")}url;lock=new W;db;client=null;connecting=null;schemaChecked=!1;active=0;constructor(t){this.url=t;const n=this;this.db={prepare:a(r=>this.statement(r,!0),"prepare"),prepareUncached:a(r=>this.statement(r,!1),"prepareUncached"),exec:a(r=>this.gated(async()=>{await this.send({text:r})}),"exec"),get inTransaction(){return g(n)}}}async connected(){return this.client!==null?this.client:(this.connecting??=this.connect().finally(()=>{this.connecting=null}),this.connecting)}async connect(){V(this.url);const t=new E.Client({connectionString:this.url,types:Y,application_name:`crtr-${process.pid}`,connectionTimeoutMillis:1e4});t.on("error",n=>{console.warn(`[canvas db] connection error: ${n.message}`),this.drop(t)}),t.on("end",()=>this.drop(t)),await t.connect();try{this.schemaChecked||(await Q(t),this.schemaChecked=!0)}catch(n){throw await t.end().catch(()=>{}),n}return t.connection.stream.unref(),this.client=t,t}drop(t){this.client===t&&(this.client=null)}async send(t){const n=await this.connected(),r=d.getStore();if(r!==void 0&&r.open&&r.handle===this&&r.client!==n)throw new Error("canvas db: the connection was lost during this transaction; it was rolled back");return this.sendOn(n,t)}async sendOn(t,n){const r=t.connection.stream;this.active++===0&&r.ref();try{return await t.query(n)}catch(o){throw this.mapError(o,t)}finally{--this.active===0&&r.unref()}}mapError(t,n){const r=t;return r.code==="23505"?new K(r.message??"unique constraint violation",r.constraint,{cause:t}):((r.code==="57P01"||r.code==="ECONNRESET"||r.code==="EPIPE"||/Connection terminated|connection error|Client was closed/i.test(r.message??""))&&(this.drop(n),n.end().catch(()=>{})),t)}async gated(t){if(g(this)){const r=d.getStore(),o=performance.now();try{return await t()}finally{r.statementMs+=performance.now()-o}}if(!this.lock.busy)return t();const n=await this.lock.acquire();try{return await t()}finally{n()}}statement(t,n){const r=n?(()=>{const s=I(t);return{text:s.text,name:s.name}})():{text:U(t).text},o=a(s=>this.gated(()=>this.send({...r,values:s.map(z)})),"query");return{get:a(async(...s)=>(await o(s)).rows[0],"get"),all:a(async(...s)=>(await o(s)).rows,"all"),run:a(async(...s)=>({changes:(await o(s)).rowCount??0}),"run")}}async close(){const t=this.client;this.client=null,t!==null&&(t.connection.stream.ref(),await t.end().catch(()=>{}))}}function z(e){return ArrayBuffer.isView(e)&&!Buffer.isBuffer(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):e}a(z,"bindable");function g(e){const t=d.getStore();return t!==void 0&&t.open&&t.handle===e}a(g,"ownsTransaction");const X=N(P(import.meta.url)),S=T(X,"schema-pg.sql"),v=7427001;async function A(e){if((await e.query("SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'canvas_meta'")).rowCount===0)return;const n=await e.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'");return n.rows[0]===void 0?void 0:Number(n.rows[0].value)}a(A,"storedSchemaVersion");async function Q(e){await e.query("SELECT pg_advisory_lock($1)",[v]);try{let t=await A(e);if(t===void 0)await e.query(y(S,"utf8")),t=await A(e);else if(t===h){const n=/-- environments:begin\n([\s\S]*?)-- environments:end/.exec(y(S,"utf8"));if(n===null)throw new Error(`${S} has no environments block`);await e.query(n[1])}if(t!==h)throw new Error(t!==void 0&&t>h?`unsupported canvas schema version ${t} (this build supports ${h}); this build is older than the database. Select a newer runtime generation.`:`canvas schema version ${String(t)} does not match this build's ${h}`)}finally{await e.query("SELECT pg_advisory_unlock($1)",[v]).catch(()=>{})}}a(Q,"ensureSchema");function Z(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}a(Z,"runningUnderNodeTest");function w(e,t){const n=/^(postgres(?:ql)?:\/\/[^/?#]*)(\/[^?#]*)?(.*)$/s.exec(e);if(n===null)throw new Error(`not a postgres URL: ${e.replace(/\/\/[^@/]*@/,"//")}`);return`${n[1]}/${t}${n[3]}`}a(w,"withDatabase");async function tt(e){const t=T(l(),"test-db"),n=a(()=>{try{return y(t,"utf8").trim()||void 0}catch(m){if(m.code==="ENOENT")return;throw m}},"recorded"),r=n();if(r!==void 0)return w(e,r);M(l(),{recursive:!0});const o=`t_${x(8).toString("hex")}`,s=new E.Client({connectionString:w(e,"postgres")});await s.connect();try{if(await s.query(`CREATE DATABASE ${o} TEMPLATE ${F} STRATEGY file_copy`),!q(t,`${o}
3
- `,384))return await s.query(`DROP DATABASE ${o}`),w(e,n())}finally{await s.end()}return w(e,o)}a(tt,"testDatabaseUrl");async function et(){const e=process.env.CRTR_TEST_PG_URL;if(e)return tt(e);if(Z())throw new Error("CRTR_TEST_PG_URL is not set: a test must reach its own Postgres (run the suite through the lane runner); refusing to open a real canvas database");return D(l())}a(et,"canvasDatabaseUrl");const f=new Map,p=new Map;async function $(){B(),H();const e=await et();p.set(l(),e);let t=f.get(e);return t===void 0&&f.set(e,t=new j(e)),await t.connected(),t}a($,"openHandle");async function pt(){return(await $()).db}a(pt,"openDb");async function yt(){const e=p.get(l());if(e===void 0)return;p.delete(l());const t=f.get(e);t!==void 0&&(f.delete(e),await t.close())}a(yt,"closeDb");async function Et(){const e=[...f.values()];f.clear(),p.clear(),await Promise.all(e.map(t=>t.close()))}a(Et,"closeAllDbs");function gt(e){const t=d.getStore();if(t===void 0||!t.open){queueMicrotask(e);return}t.afterCommit.push(e)}a(gt,"afterCommit");let nt=0;async function St(e,t={}){const n=await $(),r=n.db;if(g(n)){if(!t.savepoint&&!t.rollback)return e(r);const i=`canvas_write_${++nt}`;await n.send({text:`SAVEPOINT ${i}`});const c=d.getStore()?.afterCommit,_=c?.length??0;let C;try{C=await e(r)}catch(O){throw await n.send({text:`ROLLBACK TO ${i}; RELEASE ${i}`}),c?.splice(_),O}return await n.send({text:t.rollback?`ROLLBACK TO ${i}; RELEASE ${i}`:`RELEASE ${i}`}),t.rollback&&c?.splice(_),C}const o=await n.lock.acquire(),s=performance.now(),m=new Error("canvas write transaction entered here");let b=!1;const u={handle:n,client:void 0,open:!0,afterCommit:[],statementMs:0};try{const i=await n.connected();await n.sendOn(i,{text:"BEGIN"}),u.client=i;try{const c=await d.run(u,()=>e(r));return await n.sendOn(i,{text:t.rollback?"ROLLBACK":"COMMIT"}),b=!t.rollback,c}catch(c){try{await n.sendOn(i,{text:"ROLLBACK"})}catch{}throw c}}finally{u.open=!1;const i=performance.now()-s;if(i-u.statementMs>G&&console.warn(`[canvas db] write transaction held ${Math.round(i)}ms (${Math.round(u.statementMs)}ms in its statements); its body did non-db work while every other db caller waited
4
- ${m.stack}`),o(),b)for(const c of u.afterCommit)queueMicrotask(c)}}a(St,"withCanvasWrite");export{K as CanvasConstraintError,gt as afterCommit,et as canvasDatabaseUrl,Et as closeAllDbs,yt as closeDb,pt as openDb,St as withCanvasWrite};
1
+ var L=Object.defineProperty;var s=(e,t)=>L(e,"name",{value:t,configurable:!0});import{AsyncLocalStorage as k}from"node:async_hooks";import{randomBytes as P}from"node:crypto";import{appendFileSync as M,mkdirSync as N,readFileSync as S}from"node:fs";import{dirname as D,join as v}from"node:path";import{fileURLToPath as q}from"node:url";import p from"pg";import{resolveDatabaseUrl as B}from"../../daemon/postgres/supervisor.js";import{publishFileExclusive as F}from"../fs-utils.js";import{initializeEmptyLayout as I,runtimeRoot as d}from"../layout.js";import{translatePlaceholders as U,translateOnce as H}from"../statements.js";import{ensureHome as K}from"./paths.js";import{CANVAS_SCHEMA_VERSION as w}from"./schema-version.js";const V="crtr_test_template";class G extends Error{static{s(this,"CanvasConstraintError")}constraint;code="23505";constructor(t,n,r){super(t,r),this.name="CanvasConstraintError",this.constraint=n}}function C(e){const t=process.env.CRTR_DB_OPEN_LEDGER;if(t)try{const n=`${new Date().toISOString()} ${process.pid} ${process.argv[1]??"<unknown>"} ${e.replace(/\/\/([^:@/]*):[^@/]*@/,"//$1@")}
2
+ `;M(t,n)}catch{}}s(C,"recordOpenLedger");const W=4,Y=/\b(?:FOR\s+(?:NO\s+KEY\s+)?(?:UPDATE|SHARE)|FOR\s+KEY\s+SHARE|INTO|nextval|setval|set_config|pg_notify|pg_advisory\w*|pg_try_advisory\w*|lo_\w+)\b/i;function z(e){return/^\s*SELECT\b/i.test(e)&&!Y.test(e)}s(z,"isReadStatement");const J=1e3,h=new k;class j{static{s(this,"FifoLock")}tail=Promise.resolve();pending=0;get busy(){return this.pending>0}drained(){return this.tail}async acquire(){this.pending++;const t=this.tail;let n;return this.tail=new Promise(r=>{n=r}),await t,()=>{this.pending--,n()}}}const X=new Set([114,3802]),A={getTypeParser:s(((e,t)=>X.has(e)?n=>n:e===20||e===1700?n=>{const r=Number(n);if(e===20&&!Number.isSafeInteger(r))throw new Error(`canvas db: bigint ${n} does not fit a JS number`);return r}:p.types.getTypeParser(e,t)),"getTypeParser")};class Z{static{s(this,"Handle")}url;lock=new j;db;client=null;pool=null;connecting=null;schemaChecked=!1;active=0;inFlight=new Set;constructor(t){this.url=t;const n=this;this.db={prepare:s(r=>this.statement(r,!0),"prepare"),prepareUncached:s(r=>this.statement(r,!1),"prepareUncached"),exec:s(r=>this.gated(async()=>{await this.send({text:r})}),"exec"),get inTransaction(){return E(n)}}}async connected(){return this.client!==null?this.client:(this.connecting??=this.connect().finally(()=>{this.connecting=null}),this.connecting)}async connect(){C(this.url);const t=new p.Client({connectionString:this.url,types:A,application_name:`crtr-${process.pid}`,connectionTimeoutMillis:1e4});t.on("error",n=>{console.warn(`[canvas db] connection error: ${n.message}`),this.drop(t)}),t.on("end",()=>this.drop(t)),await t.connect();try{this.schemaChecked||(await et(t),this.schemaChecked=!0)}catch(n){throw await t.end().catch(()=>{}),n}return t.connection.stream.unref(),this.client=t,t}readPool(){if(this.pool!==null)return this.pool;C(this.url);const t=new p.Pool({connectionString:this.url,types:A,application_name:`crtr-${process.pid}`,connectionTimeoutMillis:1e4,max:W,idleTimeoutMillis:1e4,allowExitOnIdle:!0});return t.on("error",n=>console.warn(`[canvas db] read connection error: ${n.message}`)),this.pool=t}async read(t){await this.lock.drained(),this.inFlight.size>0&&await Promise.all([...this.inFlight]);try{return await this.readPool().query(t)}catch(n){throw this.mapError(n,null)}}drop(t){this.client===t&&(this.client=null)}async send(t){const n=await this.connected(),r=h.getStore();if(r!==void 0&&r.open&&r.handle===this&&r.client!==n)throw new Error("canvas db: the connection was lost during this transaction; it was rolled back");return this.sendOn(n,t)}async sendOn(t,n){const r=t.connection.stream;this.active++===0&&r.ref();const o=t.query(n),i=o.then(()=>{},()=>{});this.inFlight.add(i),i.then(()=>this.inFlight.delete(i));try{return await o}catch(a){throw this.mapError(a,t)}finally{--this.active===0&&r.unref()}}mapError(t,n){const r=t;return r.code==="23505"?new G(r.message??"unique constraint violation",r.constraint,{cause:t}):((r.code==="57P01"||r.code==="ECONNRESET"||r.code==="EPIPE"||/Connection terminated|connection error|Client was closed/i.test(r.message??""))&&n!==null&&(this.drop(n),n.end().catch(()=>{})),t)}async gated(t){if(E(this)){const r=h.getStore(),o=performance.now();try{return await t()}finally{r.statementMs+=performance.now()-o}}if(!this.lock.busy)return t();const n=await this.lock.acquire();try{return await t()}finally{n()}}statement(t,n){const r=n?(()=>{const a=U(t);return{text:a.text,name:a.name}})():{text:H(t).text},o=z(r.text),i=s(a=>{const m={...r,values:a.map(Q)};return o&&!E(this)?this.read(m):this.gated(()=>this.send(m))},"query");return{get:s(async(...a)=>(await i(a)).rows[0],"get"),all:s(async(...a)=>(await i(a)).rows,"all"),run:s(async(...a)=>({changes:(await i(a)).rowCount??0}),"run")}}async close(){const t=this.pool;this.pool=null,await t?.end().catch(()=>{}),(await this.lock.acquire())();const n=this.client;this.client=null,n!==null&&(n.connection.stream.ref(),await n.end().catch(()=>{}))}}function Q(e){return ArrayBuffer.isView(e)&&!Buffer.isBuffer(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):e}s(Q,"bindable");function E(e){const t=h.getStore();return t!==void 0&&t.open&&t.handle===e}s(E,"ownsTransaction");const tt=D(q(import.meta.url)),_=v(tt,"schema-pg.sql"),O=7427001;async function R(e){if((await e.query("SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'canvas_meta'")).rowCount===0)return;const n=await e.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'");return n.rows[0]===void 0?void 0:Number(n.rows[0].value)}s(R,"storedSchemaVersion");async function et(e){await e.query("SELECT pg_advisory_lock($1)",[O]);try{let t=await R(e);if(t===void 0)await e.query(S(_,"utf8")),t=await R(e);else if(t===w){const n=/-- environments:begin\n([\s\S]*?)-- environments:end/.exec(S(_,"utf8"));if(n===null)throw new Error(`${_} has no environments block`);await e.query(n[1])}if(t!==w)throw new Error(t!==void 0&&t>w?`unsupported canvas schema version ${t} (this build supports ${w}); this build is older than the database. Select a newer runtime generation.`:`canvas schema version ${String(t)} does not match this build's ${w}`)}finally{await e.query("SELECT pg_advisory_unlock($1)",[O]).catch(()=>{})}}s(et,"ensureSchema");function nt(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}s(nt,"runningUnderNodeTest");function y(e,t){const n=/^(postgres(?:ql)?:\/\/[^/?#]*)(\/[^?#]*)?(.*)$/s.exec(e);if(n===null)throw new Error(`not a postgres URL: ${e.replace(/\/\/[^@/]*@/,"//")}`);return`${n[1]}/${t}${n[3]}`}s(y,"withDatabase");async function rt(e){const t=v(d(),"test-db"),n=s(()=>{try{return S(t,"utf8").trim()||void 0}catch(a){if(a.code==="ENOENT")return;throw a}},"recorded"),r=n();if(r!==void 0)return y(e,r);N(d(),{recursive:!0});const o=`t_${P(8).toString("hex")}`,i=new p.Client({connectionString:y(e,"postgres")});await i.connect();try{if(await i.query(`CREATE DATABASE ${o} TEMPLATE ${V} STRATEGY file_copy`),!F(t,`${o}
3
+ `,384))return await i.query(`DROP DATABASE ${o}`),y(e,n())}finally{await i.end()}return y(e,o)}s(rt,"testDatabaseUrl");async function st(){const e=process.env.CRTR_TEST_PG_URL;if(e)return rt(e);if(nt())throw new Error("CRTR_TEST_PG_URL is not set: a test must reach its own Postgres (run the suite through the lane runner); refusing to open a real canvas database");return B(d())}s(st,"canvasDatabaseUrl");const f=new Map,g=new Map;async function $(){I(),K();const e=await st();g.set(d(),e);let t=f.get(e);return t===void 0&&f.set(e,t=new Z(e)),await t.connected(),t}s($,"openHandle");async function gt(){return(await $()).db}s(gt,"openDb");async function St(){const e=g.get(d());if(e===void 0)return;g.delete(d());const t=f.get(e);t!==void 0&&(f.delete(e),await t.close())}s(St,"closeDb");async function _t(){const e=[...f.values()];f.clear(),g.clear(),await Promise.all(e.map(t=>t.close()))}s(_t,"closeAllDbs");function bt(e){const t=h.getStore();if(t===void 0||!t.open){queueMicrotask(e);return}t.afterCommit.push(e)}s(bt,"afterCommit");let at=0;async function Tt(e,t={}){const n=await $(),r=n.db;if(E(n)){if(!t.savepoint&&!t.rollback)return e(r);const c=`canvas_write_${++at}`;await n.send({text:`SAVEPOINT ${c}`});const l=h.getStore()?.afterCommit,b=l?.length??0;let T;try{T=await e(r)}catch(x){throw await n.send({text:`ROLLBACK TO ${c}; RELEASE ${c}`}),l?.splice(b),x}return await n.send({text:t.rollback?`ROLLBACK TO ${c}; RELEASE ${c}`:`RELEASE ${c}`}),t.rollback&&l?.splice(b),T}const o=await n.lock.acquire(),i=performance.now(),a=new Error("canvas write transaction entered here");let m=!1;const u={handle:n,client:void 0,open:!0,afterCommit:[],statementMs:0};try{const c=await n.connected();await n.sendOn(c,{text:"BEGIN"}),u.client=c;try{const l=await h.run(u,()=>e(r));return await n.sendOn(c,{text:t.rollback?"ROLLBACK":"COMMIT"}),m=!t.rollback,l}catch(l){try{await n.sendOn(c,{text:"ROLLBACK"})}catch{}throw l}}finally{u.open=!1;const c=performance.now()-i;if(c-u.statementMs>J&&console.warn(`[canvas db] write transaction held ${Math.round(c)}ms (${Math.round(u.statementMs)}ms in its statements); its body did non-db work while every other db caller waited
4
+ ${a.stack}`),o(),m)for(const l of u.afterCommit)queueMicrotask(l)}}s(Tt,"withCanvasWrite");export{G as CanvasConstraintError,bt as afterCommit,st as canvasDatabaseUrl,_t as closeAllDbs,St as closeDb,z as isReadStatement,gt as openDb,Tt as withCanvasWrite};
@@ -1,9 +1,9 @@
1
- var G=Object.defineProperty;var u=(l,o)=>G(l,"name",{value:o,configurable:!0});import{randomUUID as J}from"node:crypto";import{closeSync as v,createReadStream as K,existsSync as $,fsyncSync as k,mkdirSync as L,openSync as z,readFileSync as P,readSync as M,renameSync as Q,statSync as S,unlinkSync as O,writeFileSync as j}from"node:fs";import{createInterface as T}from"node:readline";import{dirname as V,join as F,relative as X,resolve as b}from"node:path";import{SessionManager as Y}from"@earendil-works/pi-coding-agent";import{ConversationNotFoundError as Z}from"./index.js";function E(l){const o=[];for(const w of l.split(`
2
- `))if(w.trim())try{o.push(JSON.parse(w))}catch{}return o}u(E,"parseLines");function I(l){const o=z(l,"r");try{const w=Buffer.allocUnsafe(4096);let f="";for(;;){const h=M(o,w,0,w.length,null);f+=w.toString("utf8",0,h);let y=f.indexOf(`
3
- `);for(;y!==-1||h===0;){const m=y===-1?f:f.slice(0,y),[d]=E(m);if(d!==void 0)return d;if(y===-1)return;f=f.slice(y+1),y=f.indexOf(`
4
- `)}}}finally{v(o)}}u(I,"firstLine");function ee(l,o){const w=z(l,"r");try{const f=Buffer.alloc(1);return M(w,f,0,1,o-1),f[0]}finally{v(w)}}u(ee,"lastByte");function C(l,o){return[l,...o].map(w=>`${JSON.stringify(w)}
5
- `).join("")}u(C,"lines");function W(l,o){const w=`--${b(o).replace(/^[/\\]/,"").replace(/[/\\:]/g,"-")}--`;return F(l,w)}u(W,"sessionDirectory");function A(l){return`${l.timestamp.replace(/[:.]/g,"-")}_${l.id}.jsonl`}u(A,"fileName");function ce(l,o){const{appendSection:w}=o,f=new Map,h=new Map;L(l,{recursive:!0,mode:448});const y=new Set;async function m(e){if(y.has(e))return;const t=await d(e);if(await o.repair?.(e,t),!$(t)){f.delete(e),h.delete(e);return}y.add(e)}u(m,"repair");async function d(e){const t=f.get(e)??await o.pathFor?.(e);if(!t)throw new Z(e);return f.set(e,t),t}u(d,"pathFor");async function x(e){await m(e);const t=await d(e),r=E(P(t,"utf8")),n=r.shift();if(n?.type!=="session"||n.id!==e)throw new Error(`invalid conversation: ${e}`);return{header:n,entries:r,path:t}}u(x,"read");function B(e,t){const r=I(t);return r?.type==="session"&&r.id===e?r:void 0}u(B,"validHeader");async function N(e){const t=await d(e),r=B(e,t);if(!r)throw new Error(`invalid conversation: ${e}`);return{header:r,path:t}}u(N,"headerOf");async function H(e,t){await m(e);const{header:r,path:n}=await N(e),c=S(n).size;if(c<=t.head+t.tail)return await x(e);const i=z(n,"r");try{const a=u((R,_)=>{if(_<=0)return[];const U=Buffer.allocUnsafe(_),q=M(i,U,0,_,R);return U.subarray(0,q).toString("utf8").split(`
6
- `)},"slice"),s=a(0,t.head),p=a(c-t.tail,t.tail);s.pop(),p.shift();const g=E(s.join(`
1
+ var G=Object.defineProperty;var u=(l,o)=>G(l,"name",{value:o,configurable:!0});import{randomUUID as J}from"node:crypto";import{closeSync as v,createReadStream as K,existsSync as $,fsyncSync as M,mkdirSync as L,openSync as z,readFileSync as C,readSync as k,renameSync as Q,rmSync as T,statSync as S,unlinkSync as O,writeFileSync as j}from"node:fs";import{createInterface as V}from"node:readline";import{dirname as X,join as F,relative as Y,resolve as b}from"node:path";import{SessionManager as Z}from"@earendil-works/pi-coding-agent";import{ConversationNotFoundError as I}from"./index.js";function E(l){const o=[];for(const w of l.split(`
2
+ `))if(w.trim())try{o.push(JSON.parse(w))}catch{}return o}u(E,"parseLines");function ee(l){const o=z(l,"r");try{const w=Buffer.allocUnsafe(4096);let f="";for(;;){const h=k(o,w,0,w.length,null);f+=w.toString("utf8",0,h);let d=f.indexOf(`
3
+ `);for(;d!==-1||h===0;){const m=d===-1?f:f.slice(0,d),[y]=E(m);if(y!==void 0)return y;if(d===-1)return;f=f.slice(d+1),d=f.indexOf(`
4
+ `)}}}finally{v(o)}}u(ee,"firstLine");function te(l,o){const w=z(l,"r");try{const f=Buffer.alloc(1);return k(w,f,0,1,o-1),f[0]}finally{v(w)}}u(te,"lastByte");function P(l,o){return[l,...o].map(w=>`${JSON.stringify(w)}
5
+ `).join("")}u(P,"lines");function W(l,o){const w=`--${b(o).replace(/^[/\\]/,"").replace(/[/\\:]/g,"-")}--`;return F(l,w)}u(W,"sessionDirectory");function A(l){return`${l.timestamp.replace(/[:.]/g,"-")}_${l.id}.jsonl`}u(A,"fileName");function fe(l,o){const{appendSection:w}=o,f=new Map,h=new Map;L(l,{recursive:!0,mode:448});const d=new Set;async function m(t){if(d.has(t))return;const e=await y(t);if(await o.repair?.(t,e),!$(e)){f.delete(t),h.delete(t);return}d.add(t)}u(m,"repair");async function y(t){const e=f.get(t)??await o.pathFor?.(t);if(!e)throw new I(t);return f.set(t,e),e}u(y,"pathFor");async function x(t){await m(t);const e=await y(t),r=E(C(e,"utf8")),n=r.shift();if(n?.type!=="session"||n.id!==t)throw new Error(`invalid conversation: ${t}`);return{header:n,entries:r,path:e}}u(x,"read");function B(t,e){const r=ee(e);return r?.type==="session"&&r.id===t?r:void 0}u(B,"validHeader");async function N(t){const e=await y(t),r=B(t,e);if(!r)throw new Error(`invalid conversation: ${t}`);return{header:r,path:e}}u(N,"headerOf");async function H(t,e){await m(t);const{header:r,path:n}=await N(t),c=S(n).size;if(c<=e.head+e.tail)return await x(t);const i=z(n,"r");try{const a=u((R,_)=>{if(_<=0)return[];const U=Buffer.allocUnsafe(_),q=k(i,U,0,_,R);return U.subarray(0,q).toString("utf8").split(`
6
+ `)},"slice"),s=a(0,e.head),p=a(c-e.tail,e.tail);s.pop(),p.shift();const g=E(s.join(`
7
7
  `));return g[0]?.type==="session"&&g.shift(),{header:r,entries:g,tail:E(p.join(`
8
- `)),path:n}}finally{v(i)}}u(H,"readWindow");async function D(e,t,r,n,c){if(f.has(t.id))throw new Error(`conversation already exists: ${t.id}`);const i=X(l,n);if(i.startsWith("..")||i===""||b(n)===b(l))throw new Error("conversation path outside store root");if(L(V(n),{recursive:!0,mode:448}),$(n))throw new Error(`conversation file already exists: ${n}`);let a=!1;try{const s=u(()=>{const p=z(n,"wx",384);a=!0;try{return j(p,C(t,r)),k(p),a=!1,S(n).size}finally{v(p)}},"write");return e===void 0?s():await w(t.id,{kind:"create",nodeId:e,path:n,entries:r,...c?{sourceId:c}:{}},s),f.set(t.id,n),t.id}catch(s){throw a||!$(n)?($(n)&&O(n),await o.abortCreate?.(t.id)):f.set(t.id,n),s}}return u(D,"createAt"),{recover(e){for(const t of e){if(!t.transcript_path)continue;const r=f.get(t.session_id);if(r&&r!==t.transcript_path)throw new Error(`conflicting conversation paths: ${t.session_id}`);f.set(t.session_id,t.transcript_path)}},repair:m,async readLines(e){await m(e);const t=await d(e),r=T({input:K(t),crlfDelay:1/0});return{path:t,lines:r}},async create(e,t,r,n){return await D(e,t,r,await o.createPath?.(e,t)??n??F(W(l,t.cwd),A(t)))},async append(e,t,r){if(t.length===0)return;await m(e);const n=await d(e);if(r?.dedupe){const i=new Set((await x(e)).entries.map(a=>a.id));if(t=t.filter(a=>!i.has(a.id)),t.length===0)return}const c=S(n).size;if(c>0&&ee(n,c)!==10)throw new Error(`incomplete conversation tail: ${e}`);try{await w(e,{kind:"append",entries:t},()=>{const i=z(n,"a");try{return j(i,t.map(a=>`${JSON.stringify(a)}
9
- `).join("")),k(i),S(n).size}finally{v(i)}})}catch(i){throw y.delete(e),i}},async replace(e,t,r){await m(e);const n=await d(e);if(t.id!==e)throw new Error("replacement header id differs from conversation id");const c=P(n),i=u(s=>{const p=`${n}.${J()}.tmp`;try{j(p,s,{flag:"wx",mode:384});const g=z(p,"r");try{k(g)}finally{v(g)}Q(p,n)}finally{$(p)&&O(p)}},"swap");let a=!1;try{await w(e,{kind:"replace"},()=>(i(C(t,r)),a=!0,S(n).size))}catch(s){throw a&&i(c),s}finally{h.delete(e)}},async read(e,t){return t?await H(e,t):await x(e)},async header(e){await m(e);const{header:t,path:r}=await N(e),n=S(r);return{header:t,path:r,mtime:n.mtimeMs,size:n.size}},async fork(e,t,r,n){const c=await x(e);let i=c.entries;if(r!==void 0){const p=Y.inMemory(c.header.cwd,void 0,[c.header,...i]);p.createBranchedSession(r),i=p.getEntries()}const a=new Date().toISOString(),s={...c.header,id:J(),timestamp:a,cwd:n??c.header.cwd,parentSession:c.path};return await D(t,s,i,t&&o.createPath?await o.createPath(t,s):F(W(l,s.cwd),A(s)),e)},async list(e){const t=[],r=await o.owners();for(const n of new Set([...f.keys(),...r.keys()])){const c=r.get(n);if(e!==void 0&&c!==e||(await m(n),!f.has(n)))continue;const i=await d(n),a=S(i);let s=h.get(n);(!s||s.mtime!==a.mtimeMs||s.size!==a.size)&&(s={mtime:a.mtimeMs,size:a.size,header:B(n,i)},h.set(n,s)),s.header&&t.push({conversation_id:n,...c?{node_id:c}:{},header:s.header,mtime:a.mtimeMs,size:a.size,path:i})}return t.sort((n,c)=>c.mtime-n.mtime)},async delete(e){O(await d(e)),await o.delete?.(e),f.delete(e),y.delete(e),h.delete(e)}}}u(ce,"createFileStore");export{ce as createFileStore,W as sessionDirectory};
8
+ `)),path:n}}finally{v(i)}}u(H,"readWindow");async function D(t,e,r,n,c){if(f.has(e.id))throw new Error(`conversation already exists: ${e.id}`);const i=Y(l,n);if(i.startsWith("..")||i===""||b(n)===b(l))throw new Error("conversation path outside store root");if(L(X(n),{recursive:!0,mode:448}),$(n))throw new Error(`conversation file already exists: ${n}`);let a=!1;try{const s=u(()=>{const p=z(n,"wx",384);a=!0;try{return j(p,P(e,r)),M(p),a=!1,S(n).size}finally{v(p)}},"write");return t===void 0?s():await w(e.id,{kind:"create",nodeId:t,path:n,entries:r,...c?{sourceId:c}:{}},s),f.set(e.id,n),e.id}catch(s){throw a||!$(n)?($(n)&&O(n),await o.abortCreate?.(e.id)):f.set(e.id,n),s}}return u(D,"createAt"),{recover(t){for(const e of t){if(!e.transcript_path)continue;const r=f.get(e.session_id);if(r&&r!==e.transcript_path)throw new Error(`conflicting conversation paths: ${e.session_id}`);f.set(e.session_id,e.transcript_path)}},repair:m,async readLines(t){await m(t);const e=await y(t),r=V({input:K(e),crlfDelay:1/0});return{path:e,lines:r}},async create(t,e,r,n){return await D(t,e,r,await o.createPath?.(t,e)??n??F(W(l,e.cwd),A(e)))},async append(t,e,r){if(e.length===0)return;await m(t);const n=await y(t);if(r?.dedupe){const i=new Set((await x(t)).entries.map(a=>a.id));if(e=e.filter(a=>!i.has(a.id)),e.length===0)return}const c=S(n).size;if(c>0&&te(n,c)!==10)throw new Error(`incomplete conversation tail: ${t}`);try{await w(t,{kind:"append",entries:e},()=>{const i=z(n,"a");try{return j(i,e.map(a=>`${JSON.stringify(a)}
9
+ `).join("")),M(i),S(n).size}finally{v(i)}})}catch(i){throw d.delete(t),i}},async replace(t,e,r){await m(t);const n=await y(t);if(e.id!==t)throw new Error("replacement header id differs from conversation id");const c=C(n),i=u(s=>{const p=`${n}.${J()}.tmp`;try{j(p,s,{flag:"wx",mode:384});const g=z(p,"r");try{M(g)}finally{v(g)}Q(p,n)}finally{$(p)&&O(p)}},"swap");let a=!1;try{await w(t,{kind:"replace"},()=>(i(P(e,r)),a=!0,S(n).size))}catch(s){throw a&&i(c),s}finally{h.delete(t)}},async read(t,e){return e?await H(t,e):await x(t)},async header(t){await m(t);const{header:e,path:r}=await N(t),n=S(r);return{header:e,path:r,mtime:n.mtimeMs,size:n.size}},async fork(t,e,r,n){const c=await x(t);let i=c.entries;if(r!==void 0){const p=Z.inMemory(c.header.cwd,void 0,[c.header,...i]);p.createBranchedSession(r),i=p.getEntries()}const a=new Date().toISOString(),s={...c.header,id:J(),timestamp:a,cwd:n??c.header.cwd,parentSession:c.path};return await D(e,s,i,e&&o.createPath?await o.createPath(e,s):F(W(l,s.cwd),A(s)),t)},async list(t){const e=[],r=await o.owners();for(const n of new Set([...f.keys(),...r.keys()])){const c=r.get(n);if(t!==void 0&&c!==t||(await m(n),!f.has(n)))continue;const i=await y(n),a=S(i);let s=h.get(n);(!s||s.mtime!==a.mtimeMs||s.size!==a.size)&&(s={mtime:a.mtimeMs,size:a.size,header:B(n,i)},h.set(n,s)),s.header&&e.push({conversation_id:n,...c?{node_id:c}:{},header:s.header,mtime:a.mtimeMs,size:a.size,path:i})}return e.sort((n,c)=>c.mtime-n.mtime)},async delete(t,e){e===void 0?O(await y(t)):T(e,{force:!0}),await o.delete?.(t),f.delete(t),d.delete(t),h.delete(t)}}}u(fe,"createFileStore");export{fe as createFileStore,W as sessionDirectory};
@@ -78,7 +78,9 @@ export interface ConversationStore {
78
78
  }>;
79
79
  fork(id: string, nodeId?: string, leafId?: string, cwd?: string): Promise<string>;
80
80
  list(nodeId?: string): Promise<ConversationInfo[]>;
81
- delete(id: string): Promise<void>;
81
+ /** `path` names the transcript when its sessions row is already gone (the in-process store only;
82
+ * the socket client resolves from the row and ignores it). A file already missing there is not an error. */
83
+ delete(id: string, path?: string): Promise<void>;
82
84
  }
83
85
  export declare function conversationIdFromPath(path: string): string | null;
84
86
  export declare function defaultConversationRoot(): string;
@@ -1 +1 @@
1
- var c=Object.defineProperty;var o=(e,r)=>c(e,"name",{value:r,configurable:!0});import{appsRoot as i,socketDir as p}from"../layout.js";import{join as d}from"node:path";import{createStoreClient as f}from"./client.js";import{currentNodeToken as u}from"../runtime/broker/node-token.js";class l extends Error{static{o(this,"ConversationNotFoundError")}code="not_found";constructor(r){super(`conversation not found: ${r}`)}}function h(e){return e instanceof l}o(h,"isConversationNotFound");function k(e){return/(?:^|\/)[^/]+_([0-9a-f-]{36})\.jsonl$/i.exec(e)?.[1]??null}o(k,"conversationIdFromPath");function R(){return i()}o(R,"defaultConversationRoot");function m(){return process.env.CRTR_STORE_ADDRESS||d(p(),"store.sock")}o(m,"storeAddress");let t,s;function A(e){t=e}o(A,"installConversationStore");function F(){if(t)return t;const e=a();return{create:o(async(...r)=>e.create(...r),"create"),append:o(async(...r)=>e.append(...r),"append"),replace:o(async(...r)=>e.replace(...r),"replace"),read:o(async(...r)=>e.read(...r),"read"),header:o(async(...r)=>e.header(...r),"header"),fork:o(async(...r)=>e.fork(...r),"fork"),list:o(async(...r)=>e.list(...r),"list"),delete:o(async(...r)=>e.delete(...r),"delete")}}o(F,"conversationStore");function a(){const e=m();return s?.address!==e&&(s={address:e,store:f(e,u())}),s.store}o(a,"conversationClient");async function D(e,r){if(t)return t.read(e,r);const n=a();return n.readAsync?n.readAsync(e,r):n.read(e,r)}o(D,"readConversationAsync");import{createFileStore as _,sessionDirectory as N}from"./file-store.js";import{installSessionManagerStorePatch as T}from"./patch.js";import{findOrphanedParents as $,relinkOrphanedParents as g,repairConversationFile as I}from"./repair.js";export{l as ConversationNotFoundError,a as conversationClient,k as conversationIdFromPath,F as conversationStore,_ as createFileStore,R as defaultConversationRoot,$ as findOrphanedParents,A as installConversationStore,T as installSessionManagerStorePatch,h as isConversationNotFound,D as readConversationAsync,g as relinkOrphanedParents,I as repairConversationFile,N as sessionDirectory,m as storeAddress};
1
+ var c=Object.defineProperty;var o=(e,r)=>c(e,"name",{value:r,configurable:!0});import{appsRoot as i,socketDir as p}from"../layout.js";import{join as d}from"node:path";import{createStoreClient as f}from"./client.js";import{currentNodeToken as u}from"../runtime/broker/node-token.js";class l extends Error{static{o(this,"ConversationNotFoundError")}code="not_found";constructor(r){super(`conversation not found: ${r}`)}}function h(e){return e instanceof l}o(h,"isConversationNotFound");function k(e){return/(?:^|\/)[^/]+_([0-9a-f-]{36})\.jsonl$/i.exec(e)?.[1]??null}o(k,"conversationIdFromPath");function R(){return i()}o(R,"defaultConversationRoot");function m(){return process.env.CRTR_STORE_ADDRESS||d(p(),"store.sock")}o(m,"storeAddress");let t,s;function A(e){t=e}o(A,"installConversationStore");function F(){if(t)return t;const e=a();return{create:o(async(...r)=>e.create(...r),"create"),append:o(async(...r)=>e.append(...r),"append"),replace:o(async(...r)=>e.replace(...r),"replace"),read:o(async(...r)=>e.read(...r),"read"),header:o(async(...r)=>e.header(...r),"header"),fork:o(async(...r)=>e.fork(...r),"fork"),list:o(async(...r)=>e.list(...r),"list"),delete:o(async r=>e.delete(r),"delete")}}o(F,"conversationStore");function a(){const e=m();return s?.address!==e&&(s={address:e,store:f(e,u())}),s.store}o(a,"conversationClient");async function D(e,r){if(t)return t.read(e,r);const n=a();return n.readAsync?n.readAsync(e,r):n.read(e,r)}o(D,"readConversationAsync");import{createFileStore as _,sessionDirectory as N}from"./file-store.js";import{installSessionManagerStorePatch as T}from"./patch.js";import{findOrphanedParents as $,relinkOrphanedParents as g,repairConversationFile as I}from"./repair.js";export{l as ConversationNotFoundError,a as conversationClient,k as conversationIdFromPath,F as conversationStore,_ as createFileStore,R as defaultConversationRoot,$ as findOrphanedParents,A as installConversationStore,T as installSessionManagerStorePatch,h as isConversationNotFound,D as readConversationAsync,g as relinkOrphanedParents,I as repairConversationFile,N as sessionDirectory,m as storeAddress};
@@ -1 +1 @@
1
- var O=Object.defineProperty;var r=(e,t)=>O(e,"name",{value:t,configurable:!0});import{canonicalSessionFile as R,findNodeBySessionFile as T,getNode as y,isWarmSpare as I,listNodeIdentities as $,recordRespawnAttempt as j,recordSettledTurn as C,setPresence as b,subscriptionsOf as q,updateNode as g,updateNodeTelemetry as E}from"../../../core/canvas/canvas.js";import{recordExecutionPid as M}from"../../../core/canvas/executions.js";import{bindSession as L,readLiveSession as D,transferSession as G}from"../../../core/canvas/sessions.js";import{isSafeNodeId as J}from"../../../core/canvas/paths.js";import{appForNode as W}from"../../../core/canvas/node-agent-paths.js";import{isPidAlive as U}from"../../../core/canvas/pid.js";import{editorLabel as P}from"../../../core/canvas/labels.js";import{assembleNodeSubject as V}from"../../../core/substrate/subject.js";import{withCanvasWrite as l}from"../../../core/canvas/db.js";import{notFound as v,usage as s}from"../../../core/errors.js";import{hasUnseenWakeMail as N}from"../../../core/canvas/deliveries.js";import{clearYieldMessage as Y}from"../../../core/runtime/kickoff.js";import{readRoadmap as z}from"../../../core/runtime/roadmap.js";import{forkSourceNode as H}from"../../../core/runtime/nodes.js";import{transition as h}from"../../../core/runtime/lifecycle.js";import{focusOf as _,tearDownNode as x}from"../../../core/runtime/placement.js";import{hasAttachedBrokerViewers as K}from"../viewer-presence.js";import{evaluateStop as Q}from"../../../core/runtime/stop-guard.js";import{clearFault as w,recordFault as X}from"../../../core/canvas/recovery.js";import{updateModelRecipe as Z}from"../../../core/runtime/model-swap.js";import{completeCompanionRequestedSubmit as ee}from"../../review/finish.js";import{emitEvent as A}from"../../../core/events/emit.js";import{consumePendingPark as te,recordPendingParkActivity as ne}from"../../park-pending.js";import{shouldParkAfterSummary as ie}from"../../park-activity.js";import{ownerOnly as oe}from"../router.js";import{requireCurrentExecution as m}from"./broker-recovery.js";import{ensureRunObserver as re}from"../../runs/observer.js";import{parseNodeTelemetryRecord as ae,writeNodeTelemetryRecord as se}from"./telemetry-record.js";import{mapAsync as B}from"../../../core/async-array.js";function p(e,t){if(typeof e!="object"||e===null||Array.isArray(e))throw s(`${t} requires a JSON body`);return e}r(p,"objectBody");function de(e){const t=p(e,"broker session-bound");if(typeof t.executionId!="string"||t.executionId==="")throw s("executionId must be a non-empty string");if(typeof t.piSessionId!="string"||t.piSessionId==="")throw s("piSessionId must be a non-empty string");if(t.sessionFile!==null&&typeof t.sessionFile!="string")throw s("sessionFile must be a string or null");if(!Number.isSafeInteger(t.pid)||t.pid<=0)throw s("pid must be a positive safe integer");if(t.reason!==null&&typeof t.reason!="string")throw s("reason must be a string or null");if(!Array.isArray(t.reviewBoundaryIds)||!t.reviewBoundaryIds.every(n=>typeof n=="string"))throw s("reviewBoundaryIds must be an array of strings");return t}r(de,"parseSessionBoundBody");function ce(e){const t=p(e,"broker settle");if(typeof t.stopReason!="string")throw s("stopReason must be a string");for(const n of["backgroundJobsRunning","pushedFinal"])if(typeof t[n]!="boolean")throw s(`${n} must be a boolean`);return t}r(ce,"parseSettleBody");function ue(e){const t=p(e,"broker park-complete");if(t.outcome!=="completed"&&t.outcome!=="failed")throw s("outcome must be completed or failed");return t}r(ue,"parseParkCompleteBody");function le(e){const t=p(e,"broker telemetry");if(typeof t.expected_execution_id!="string"||t.expected_execution_id==="")throw s("expected_execution_id must be a non-empty string");const n=ae(t);return{...n,expected_execution_id:t.expected_execution_id,updated_at:new Date(n.updated_at).toISOString()}}r(le,"parseTelemetryBody");function pe(e){const t=p(e,"broker model");if(typeof t.spec!="string"||t.spec.trim()==="")throw s("spec must be a non-empty string");const n=t.intent;if(n!==void 0){const o=n!==null&&typeof n=="object"?n:void 0;if(o===void 0||typeof o.family!="string"||o.family===""||!["ultra","strong","medium","light"].includes(o.strength))throw s("intent must be { family, strength } with strength ultra|strong|medium|light when present")}if(typeof t.userSelected!="boolean")throw s("userSelected must be a boolean");return t}r(pe,"parseModelCommitBody");async function f(e){const t=await y(e);if(t===null)throw v(`unknown node: ${e}`,{received:e});return t}r(f,"requireNode");async function k(e){return{node_id:e.node_id,grantee:await W(e.node_id),name:e.name,...e.description===void 0?{}:{description:e.description},...e.title===void 0?{}:{title:e.title},...e.icon===void 0?{}:{icon:e.icon},kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,parent:e.parent??null,fork_from:e.fork_from??null,profile_id:e.profile_id??null,...e.managed_worktree===void 0?{}:{managed_worktree:e.managed_worktree===null?null:{state:e.managed_worktree.state,...e.managed_worktree.cleanup===void 0?{}:{cleanup:e.managed_worktree.cleanup},path:e.managed_worktree.path,branch:e.managed_worktree.branch,base_ref:e.managed_worktree.base_ref,base_sha:e.managed_worktree.base_sha}},...e.review_binding===void 0?{}:{review_binding:e.review_binding??null},intent:e.intent??null,...e.persona_ack===void 0?{}:{persona_ack:e.persona_ack},created:e.created}}r(k,"extensionNode");async function me(e){const t=await f(e),n=[],o=new Set([e]);let i=t.parent??null;for(;i!==null&&i!==""&&!o.has(i);){o.add(i);const u=await y(i);if(u===null)break;n.unshift(await k(u)),i=u.parent??null}const a=await B((await B(await q(e),async u=>await y(u.node_id))).filter(u=>u!==null&&u.kind!=="human").sort((u,S)=>u.status===S.status?0:u.status==="active"?-1:S.status==="active"?1:0),k),c=typeof t.fork_source_file=="string"?await H(t.fork_source_file):null,d=await V(e);if(d===null)throw v(`unknown node: ${e}`,{received:e});return{node:await k(t),warm_spare:await I(e),ancestors:n,children:a,fork_source:c===null?null:await k(c),subject:d,report_nodes:await $(),roadmap:await z(e)}}r(me,"extensionState");function F(e,t){return e?.kind===t.kind&&e?.mode===t.mode&&e?.lifecycle===t.lifecycle}r(F,"samePersona");function fe(e){const t=p(e,"broker generated-name");if(t.kind!=="initial"&&t.kind!=="recap")throw s("kind must be initial or recap");if(typeof t.description!="string"||typeof t.title!="string"||typeof t.icon!="string")throw s("description, title, and icon must be strings");if(t.kind==="initial")return t;const n=t.expected;if(typeof n!="object"||n===null||Array.isArray(n)||["name","description","title","icon","kind"].some(o=>typeof n[o]!="string"))throw s("recap expected must contain name, description, title, icon, and kind strings");return t}r(fe,"parseGeneratedNameBody");function we(e){const t=p(e,"broker persona-ack"),n=r(o=>{const i=t[o];if(typeof i!="object"||i===null||Array.isArray(i)||typeof i.kind!="string"||i.mode!=="base"&&i.mode!=="orchestrator"||i.lifecycle!=="terminal"&&i.lifecycle!=="resident")throw s(`${o} must be a persona state`);return i},"parse");return{expected_execution_id:t.expected_execution_id,from:n("from"),to:n("to")}}r(we,"parsePersonaAckBody");async function ye(e){const t=e.params.id,n=de(e.body),o=n.sessionFile===null?null:R(n.sessionFile);return{status:200,body:await l(async()=>{const a=await f(t);if(await M(n.executionId,t,n.pid)==="shutdown")return{action:"shutdown"};const c=a.review_binding?.review_id;if(c!==void 0&&!n.reviewBoundaryIds.includes(c))return{action:"shutdown"};if(n.reason==="new"&&a.parent==null)return{action:"relaunch_root"};if(n.reason!=="new"&&a.fork_from!=null&&a.pi_session_id==null&&o===null)return{action:"shutdown"};const d=o===null?null:await T(o);if(d!==null&&d.node_id!==t){if(U(d.pi_pid))return await X(t,{link:"daemon\u2192node",op:"session-file-ownership",kind:"protocol",retry:{disposition:"fatal"},message:`refusing to bind ${o}: it is owned by live node ${d.node_id}`}),{action:"shutdown"};const u=await D(d.node_id);if(u===null||await G({fromNodeId:d.node_id,fromBindingId:u.bindingId,toNodeId:t,executionId:n.executionId,sessionId:n.piSessionId,transcriptPath:o})!=="transferred")return{action:"shutdown"}}else if(await L(t,{executionId:n.executionId,sessionId:n.piSessionId,transcriptPath:o})==="shutdown")return{action:"shutdown"};return n.reason!=="new"&&(await Y(t),a.intent==="refresh"&&await h(t,"revive")),{action:"none"}})}}r(ye,"handleSessionBound");async function he(e,t){const n=await y(e);if(n===null){if(t.pushedFinal)return{action:"shutdown"};throw v(`unknown node: ${e}`,{received:e})}if(await C(e,new Date().toISOString()),(t.stopReason==="stop"||t.stopReason==="length")&&await j(e,{failures:0,notBefore:null}),t.stopReason==="quit")return(n.status==="active"||n.status==="idle")&&n.intent===null&&(await h(e,"finish"),await w(e),await _(e)!==null&&await x(e),await b(e,{pane:null,tmux_session:null,window:null})),{action:"shutdown"};if(t.stopReason!=="stop"&&t.stopReason!=="length")return await w(e,{link:"daemon\u2192node"}),n.intent==="refresh"?{action:"shutdown"}:{action:"stay_dormant"};const o={pushedFinal:t.pushedFinal};if(o.pushedFinal||n.status==="done")return await w(e),await _(e)!==null&&await x(e),await b(e,{pane:null,tmux_session:null,window:null}),{action:"shutdown"};if(n.intent==="refresh")return await w(e),{action:"shutdown"};const i=await Q(e,o,t.backgroundJobsRunning);return await w(e),i.action==="reprompt"?{action:"reprompt",prompt:i.message}:i.reason==="finished"?await N(e)?{action:"stay_dormant"}:{action:"shutdown"}:i.reason!=="awaiting"&&i.reason!=="scheduled"?{action:"stay_dormant"}:await _(e)!==null||K(e)||await N(e)?{action:"stay_dormant"}:(await h(e,"release"),{action:"shutdown"})}r(he,"settleBroker");async function _e(e){const t=e.params.id,n=ce(e.body);await m(t,n.expected_execution_id);const o=await l(async()=>await he(t,n));return o.action!=="reprompt"&&ee(t).catch(i=>A({level:"error",event:"api.broker_ops.companion_submit_failed",node_id:t,error:i})),{status:200,body:o}}r(_e,"handleSettle");async function ke(e){const t=e.params.id,n=ue(e.body);return await m(t,n.expected_execution_id),{status:200,body:await l(async()=>{const i=te(t);if(i===void 0)return{action:"stay_dormant"};const a=await f(t);return await ie(t,a,i)?(await w(t),await h(t,"park"),await _(t)!==null&&await x(t),await b(t,{pane:null,tmux_session:null,window:null}),A({level:n.outcome==="completed"?"info":"warn",event:"broker.park.complete",...J(t)?{node_id:t}:{},outcome:n.outcome==="completed"?"succeeded":"failed"}),{action:"shutdown"}):{action:"stay_dormant"}})}}r(ke,"handleParkComplete");async function be(e){const t=e.params.id;return await m(t,p(e.body,"broker park-activity").expected_execution_id),{status:200,body:await l(async()=>({activity:ne(t)?"recorded":"none"}))}}r(be,"handleParkActivity");async function ge(e){const t=e.params.id,n=le(e.body);return await m(t,n.expected_execution_id),await l(async()=>{await E(t,{tokensIn:n.tokens_in,contextTokens:n.context_tokens,lastActivity:n.last_activity,updatedAt:n.updated_at}),se(t,n)}),{status:204}}r(ge,"handleTelemetry");async function ve(e){const t=e.params.id,n=pe(e.body);return await m(t,n.expected_execution_id),{status:200,body:{modelOverride:(await l(async()=>await Z(await f(t),n.spec,n.intent,n.userSelected))).model_override}}}r(ve,"handleModelCommit");async function xe(e){const t=e.params.id,n=await f(t);return n.run_id&&await re(t,n.run_id),{status:200,body:await me(t)}}r(xe,"handleExtensionState");async function Se(e){const t=e.params.id,n=fe(e.body);return await m(t,n.expected_execution_id),{status:200,body:await l(async()=>{const i=await f(t);if(n.kind==="initial"){if((i.description??"").trim()!=="")return{applied:!1};const d=await g(t,{description:n.description,...n.title.trim()===""?{}:{title:n.title},...n.icon.trim()===""?{}:{icon:n.icon}});return{applied:!0,editorLabel:P(d),description:d.description??"",title:d.title??"",icon:d.icon??""}}const a=n.expected;if(i.name!==a.name||(i.description??"")!==a.description||(i.title??"")!==a.title||(i.icon??"")!==a.icon||i.kind!==a.kind)return{applied:!1};const c=await g(t,{description:n.description,title:n.title.trim()===""?void 0:n.title,icon:n.icon.trim()===""?void 0:n.icon});return{applied:!0,editorLabel:P(c),description:c.description??"",title:c.title??"",icon:c.icon??""}})}}r(Se,"handleGeneratedName");async function Pe(e){const t=e.params.id,n=we(e.body);return await m(t,n.expected_execution_id),{status:200,body:await l(async()=>{const i=await f(t),a={kind:i.kind,mode:i.mode,lifecycle:i.lifecycle},c=i.persona_ack,d=c===void 0?a:{kind:c.kind??i.kind,mode:c.mode,lifecycle:c.lifecycle};return!F(a,n.to)||!F(d,n.from)?{applied:!1}:(await g(t,{persona_ack:n.to}),{applied:!0})})}}r(Pe,"handlePersonaAck");const nt=oe([{method:"POST",pattern:"/v1/nodes/:id/broker/session-bound",handler:ye},{method:"POST",pattern:"/v1/nodes/:id/broker/settle",handler:_e},{method:"POST",pattern:"/v1/nodes/:id/broker/park-complete",handler:ke},{method:"POST",pattern:"/v1/nodes/:id/broker/park-activity",handler:be},{method:"POST",pattern:"/v1/nodes/:id/broker/telemetry",handler:ge},{method:"POST",pattern:"/v1/nodes/:id/broker/model",handler:ve},{method:"GET",pattern:"/v1/nodes/:id/broker/extension-state",handler:xe},{method:"POST",pattern:"/v1/nodes/:id/broker/generated-name",handler:Se},{method:"POST",pattern:"/v1/nodes/:id/broker/persona-ack",handler:Pe}]);export{nt as brokerOperationRoutes,k as extensionNode};
1
+ var O=Object.defineProperty;var r=(e,t)=>O(e,"name",{value:t,configurable:!0});import{canonicalSessionFile as R,findNodeBySessionFile as T,getNode as y,isWarmSpare as I,recordRespawnAttempt as $,recordSettledTurn as j,setPresence as b,subscriptionsOf as C,updateNode as g,updateNodeTelemetry as q}from"../../../core/canvas/canvas.js";import{recordExecutionPid as E}from"../../../core/canvas/executions.js";import{bindSession as M,readLiveSession as L,transferSession as D}from"../../../core/canvas/sessions.js";import{isSafeNodeId as G}from"../../../core/canvas/paths.js";import{appForNode as J}from"../../../core/canvas/node-agent-paths.js";import{isPidAlive as W}from"../../../core/canvas/pid.js";import{editorLabel as P}from"../../../core/canvas/labels.js";import{assembleNodeSubject as U}from"../../../core/substrate/subject.js";import{withCanvasWrite as l}from"../../../core/canvas/db.js";import{notFound as v,usage as s}from"../../../core/errors.js";import{hasUnseenWakeMail as A}from"../../../core/canvas/deliveries.js";import{clearYieldMessage as V}from"../../../core/runtime/kickoff.js";import{readRoadmap as Y}from"../../../core/runtime/roadmap.js";import{forkSourceNode as z}from"../../../core/runtime/nodes.js";import{transition as h}from"../../../core/runtime/lifecycle.js";import{focusOf as _,tearDownNode as x}from"../../../core/runtime/placement.js";import{hasAttachedBrokerViewers as H}from"../viewer-presence.js";import{evaluateStop as K}from"../../../core/runtime/stop-guard.js";import{clearFault as w,recordFault as Q}from"../../../core/canvas/recovery.js";import{updateModelRecipe as X}from"../../../core/runtime/model-swap.js";import{completeCompanionRequestedSubmit as Z}from"../../review/finish.js";import{emitEvent as B}from"../../../core/events/emit.js";import{consumePendingPark as ee,recordPendingParkActivity as te}from"../../park-pending.js";import{shouldParkAfterSummary as ne}from"../../park-activity.js";import{ownerOnly as ie}from"../router.js";import{requireCurrentExecution as m}from"./broker-recovery.js";import{ensureRunObserver as oe}from"../../runs/observer.js";import{parseNodeTelemetryRecord as re,writeNodeTelemetryRecord as ae}from"./telemetry-record.js";import{mapAsync as N}from"../../../core/async-array.js";function p(e,t){if(typeof e!="object"||e===null||Array.isArray(e))throw s(`${t} requires a JSON body`);return e}r(p,"objectBody");function se(e){const t=p(e,"broker session-bound");if(typeof t.executionId!="string"||t.executionId==="")throw s("executionId must be a non-empty string");if(typeof t.piSessionId!="string"||t.piSessionId==="")throw s("piSessionId must be a non-empty string");if(t.sessionFile!==null&&typeof t.sessionFile!="string")throw s("sessionFile must be a string or null");if(!Number.isSafeInteger(t.pid)||t.pid<=0)throw s("pid must be a positive safe integer");if(t.reason!==null&&typeof t.reason!="string")throw s("reason must be a string or null");if(!Array.isArray(t.reviewBoundaryIds)||!t.reviewBoundaryIds.every(n=>typeof n=="string"))throw s("reviewBoundaryIds must be an array of strings");return t}r(se,"parseSessionBoundBody");function de(e){const t=p(e,"broker settle");if(typeof t.stopReason!="string")throw s("stopReason must be a string");for(const n of["backgroundJobsRunning","pushedFinal"])if(typeof t[n]!="boolean")throw s(`${n} must be a boolean`);return t}r(de,"parseSettleBody");function ce(e){const t=p(e,"broker park-complete");if(t.outcome!=="completed"&&t.outcome!=="failed")throw s("outcome must be completed or failed");return t}r(ce,"parseParkCompleteBody");function ue(e){const t=p(e,"broker telemetry");if(typeof t.expected_execution_id!="string"||t.expected_execution_id==="")throw s("expected_execution_id must be a non-empty string");const n=re(t);return{...n,expected_execution_id:t.expected_execution_id,updated_at:new Date(n.updated_at).toISOString()}}r(ue,"parseTelemetryBody");function le(e){const t=p(e,"broker model");if(typeof t.spec!="string"||t.spec.trim()==="")throw s("spec must be a non-empty string");const n=t.intent;if(n!==void 0){const o=n!==null&&typeof n=="object"?n:void 0;if(o===void 0||typeof o.family!="string"||o.family===""||!["ultra","strong","medium","light"].includes(o.strength))throw s("intent must be { family, strength } with strength ultra|strong|medium|light when present")}if(typeof t.userSelected!="boolean")throw s("userSelected must be a boolean");return t}r(le,"parseModelCommitBody");async function f(e){const t=await y(e);if(t===null)throw v(`unknown node: ${e}`,{received:e});return t}r(f,"requireNode");async function k(e){return{node_id:e.node_id,grantee:await J(e.node_id),name:e.name,...e.description===void 0?{}:{description:e.description},...e.title===void 0?{}:{title:e.title},...e.icon===void 0?{}:{icon:e.icon},kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,parent:e.parent??null,fork_from:e.fork_from??null,profile_id:e.profile_id??null,...e.managed_worktree===void 0?{}:{managed_worktree:e.managed_worktree===null?null:{state:e.managed_worktree.state,...e.managed_worktree.cleanup===void 0?{}:{cleanup:e.managed_worktree.cleanup},path:e.managed_worktree.path,branch:e.managed_worktree.branch,base_ref:e.managed_worktree.base_ref,base_sha:e.managed_worktree.base_sha}},...e.review_binding===void 0?{}:{review_binding:e.review_binding??null},intent:e.intent??null,...e.persona_ack===void 0?{}:{persona_ack:e.persona_ack},created:e.created}}r(k,"extensionNode");async function pe(e){const t=await f(e),n=[],o=new Set([e]);let i=t.parent??null;for(;i!==null&&i!==""&&!o.has(i);){o.add(i);const u=await y(i);if(u===null)break;n.unshift(await k(u)),i=u.parent??null}const a=await N((await N(await C(e),async u=>await y(u.node_id))).filter(u=>u!==null&&u.kind!=="human").sort((u,S)=>u.status===S.status?0:u.status==="active"?-1:S.status==="active"?1:0),k),c=typeof t.fork_source_file=="string"?await z(t.fork_source_file):null,d=await U(e);if(d===null)throw v(`unknown node: ${e}`,{received:e});return{node:await k(t),warm_spare:await I(e),ancestors:n,children:a,fork_source:c===null?null:await k(c),subject:d,roadmap:await Y(e)}}r(pe,"extensionState");function F(e,t){return e?.kind===t.kind&&e?.mode===t.mode&&e?.lifecycle===t.lifecycle}r(F,"samePersona");function me(e){const t=p(e,"broker generated-name");if(t.kind!=="initial"&&t.kind!=="recap")throw s("kind must be initial or recap");if(typeof t.description!="string"||typeof t.title!="string"||typeof t.icon!="string")throw s("description, title, and icon must be strings");if(t.kind==="initial")return t;const n=t.expected;if(typeof n!="object"||n===null||Array.isArray(n)||["name","description","title","icon","kind"].some(o=>typeof n[o]!="string"))throw s("recap expected must contain name, description, title, icon, and kind strings");return t}r(me,"parseGeneratedNameBody");function fe(e){const t=p(e,"broker persona-ack"),n=r(o=>{const i=t[o];if(typeof i!="object"||i===null||Array.isArray(i)||typeof i.kind!="string"||i.mode!=="base"&&i.mode!=="orchestrator"||i.lifecycle!=="terminal"&&i.lifecycle!=="resident")throw s(`${o} must be a persona state`);return i},"parse");return{expected_execution_id:t.expected_execution_id,from:n("from"),to:n("to")}}r(fe,"parsePersonaAckBody");async function we(e){const t=e.params.id,n=se(e.body),o=n.sessionFile===null?null:R(n.sessionFile);return{status:200,body:await l(async()=>{const a=await f(t);if(await E(n.executionId,t,n.pid)==="shutdown")return{action:"shutdown"};const c=a.review_binding?.review_id;if(c!==void 0&&!n.reviewBoundaryIds.includes(c))return{action:"shutdown"};if(n.reason==="new"&&a.parent==null)return{action:"relaunch_root"};if(n.reason!=="new"&&a.fork_from!=null&&a.pi_session_id==null&&o===null)return{action:"shutdown"};const d=o===null?null:await T(o);if(d!==null&&d.node_id!==t){if(W(d.pi_pid))return await Q(t,{link:"daemon\u2192node",op:"session-file-ownership",kind:"protocol",retry:{disposition:"fatal"},message:`refusing to bind ${o}: it is owned by live node ${d.node_id}`}),{action:"shutdown"};const u=await L(d.node_id);if(u===null||await D({fromNodeId:d.node_id,fromBindingId:u.bindingId,toNodeId:t,executionId:n.executionId,sessionId:n.piSessionId,transcriptPath:o})!=="transferred")return{action:"shutdown"}}else if(await M(t,{executionId:n.executionId,sessionId:n.piSessionId,transcriptPath:o})==="shutdown")return{action:"shutdown"};return n.reason!=="new"&&(await V(t),a.intent==="refresh"&&await h(t,"revive")),{action:"none"}})}}r(we,"handleSessionBound");async function ye(e,t){const n=await y(e);if(n===null){if(t.pushedFinal)return{action:"shutdown"};throw v(`unknown node: ${e}`,{received:e})}if(await j(e,new Date().toISOString()),(t.stopReason==="stop"||t.stopReason==="length")&&await $(e,{failures:0,notBefore:null}),t.stopReason==="quit")return(n.status==="active"||n.status==="idle")&&n.intent===null&&(await h(e,"finish"),await w(e),await _(e)!==null&&await x(e),await b(e,{pane:null,tmux_session:null,window:null})),{action:"shutdown"};if(t.stopReason!=="stop"&&t.stopReason!=="length")return await w(e,{link:"daemon\u2192node"}),n.intent==="refresh"?{action:"shutdown"}:{action:"stay_dormant"};const o={pushedFinal:t.pushedFinal};if(o.pushedFinal||n.status==="done")return await w(e),await _(e)!==null&&await x(e),await b(e,{pane:null,tmux_session:null,window:null}),{action:"shutdown"};if(n.intent==="refresh")return await w(e),{action:"shutdown"};const i=await K(e,o,t.backgroundJobsRunning);return await w(e),i.action==="reprompt"?{action:"reprompt",prompt:i.message}:i.reason==="finished"?await A(e)?{action:"stay_dormant"}:{action:"shutdown"}:i.reason!=="awaiting"&&i.reason!=="scheduled"?{action:"stay_dormant"}:await _(e)!==null||H(e)||await A(e)?{action:"stay_dormant"}:(await h(e,"release"),{action:"shutdown"})}r(ye,"settleBroker");async function he(e){const t=e.params.id,n=de(e.body);await m(t,n.expected_execution_id);const o=await l(async()=>await ye(t,n));return o.action!=="reprompt"&&Z(t).catch(i=>B({level:"error",event:"api.broker_ops.companion_submit_failed",node_id:t,error:i})),{status:200,body:o}}r(he,"handleSettle");async function _e(e){const t=e.params.id,n=ce(e.body);return await m(t,n.expected_execution_id),{status:200,body:await l(async()=>{const i=ee(t);if(i===void 0)return{action:"stay_dormant"};const a=await f(t);return await ne(t,a,i)?(await w(t),await h(t,"park"),await _(t)!==null&&await x(t),await b(t,{pane:null,tmux_session:null,window:null}),B({level:n.outcome==="completed"?"info":"warn",event:"broker.park.complete",...G(t)?{node_id:t}:{},outcome:n.outcome==="completed"?"succeeded":"failed"}),{action:"shutdown"}):{action:"stay_dormant"}})}}r(_e,"handleParkComplete");async function ke(e){const t=e.params.id;return await m(t,p(e.body,"broker park-activity").expected_execution_id),{status:200,body:await l(async()=>({activity:te(t)?"recorded":"none"}))}}r(ke,"handleParkActivity");async function be(e){const t=e.params.id,n=ue(e.body);return await m(t,n.expected_execution_id),await l(async()=>{await q(t,{tokensIn:n.tokens_in,contextTokens:n.context_tokens,lastActivity:n.last_activity,updatedAt:n.updated_at}),ae(t,n)}),{status:204}}r(be,"handleTelemetry");async function ge(e){const t=e.params.id,n=le(e.body);return await m(t,n.expected_execution_id),{status:200,body:{modelOverride:(await l(async()=>await X(await f(t),n.spec,n.intent,n.userSelected))).model_override}}}r(ge,"handleModelCommit");async function ve(e){const t=e.params.id,n=await f(t);return n.run_id&&await oe(t,n.run_id),{status:200,body:await pe(t)}}r(ve,"handleExtensionState");async function xe(e){const t=e.params.id,n=me(e.body);return await m(t,n.expected_execution_id),{status:200,body:await l(async()=>{const i=await f(t);if(n.kind==="initial"){if((i.description??"").trim()!=="")return{applied:!1};const d=await g(t,{description:n.description,...n.title.trim()===""?{}:{title:n.title},...n.icon.trim()===""?{}:{icon:n.icon}});return{applied:!0,editorLabel:P(d),description:d.description??"",title:d.title??"",icon:d.icon??""}}const a=n.expected;if(i.name!==a.name||(i.description??"")!==a.description||(i.title??"")!==a.title||(i.icon??"")!==a.icon||i.kind!==a.kind)return{applied:!1};const c=await g(t,{description:n.description,title:n.title.trim()===""?void 0:n.title,icon:n.icon.trim()===""?void 0:n.icon});return{applied:!0,editorLabel:P(c),description:c.description??"",title:c.title??"",icon:c.icon??""}})}}r(xe,"handleGeneratedName");async function Se(e){const t=e.params.id,n=fe(e.body);return await m(t,n.expected_execution_id),{status:200,body:await l(async()=>{const i=await f(t),a={kind:i.kind,mode:i.mode,lifecycle:i.lifecycle},c=i.persona_ack,d=c===void 0?a:{kind:c.kind??i.kind,mode:c.mode,lifecycle:c.lifecycle};return!F(a,n.to)||!F(d,n.from)?{applied:!1}:(await g(t,{persona_ack:n.to}),{applied:!0})})}}r(Se,"handlePersonaAck");const tt=ie([{method:"POST",pattern:"/v1/nodes/:id/broker/session-bound",handler:we},{method:"POST",pattern:"/v1/nodes/:id/broker/settle",handler:he},{method:"POST",pattern:"/v1/nodes/:id/broker/park-complete",handler:_e},{method:"POST",pattern:"/v1/nodes/:id/broker/park-activity",handler:ke},{method:"POST",pattern:"/v1/nodes/:id/broker/telemetry",handler:be},{method:"POST",pattern:"/v1/nodes/:id/broker/model",handler:ge},{method:"GET",pattern:"/v1/nodes/:id/broker/extension-state",handler:ve},{method:"POST",pattern:"/v1/nodes/:id/broker/generated-name",handler:xe},{method:"POST",pattern:"/v1/nodes/:id/broker/persona-ack",handler:Se}]);export{tt as brokerOperationRoutes,k as extensionNode};
@@ -2,7 +2,7 @@ import type { FleetRegistry } from '../../core/runtime/fleet.js';
2
2
  import type { DetachedWorkLifecycle } from './broker-supervision.js';
3
3
  export declare const DEAD_REAP_GRACE_MS: number;
4
4
  export declare const RETENTION_SWEEP_INTERVAL_MS: number;
5
- export declare const RETENTION_TTL_DAYS = 14;
5
+ export declare const RETENTION_TTL_DAYS = 2;
6
6
  export declare const CONTEXT_STRIP_GRACE_MS: number;
7
7
  export interface StorageMaintenanceContext {
8
8
  /** Carries the managed-worktree sweep's detached Git work off this tick. */
@@ -1 +1 @@
1
- var I=Object.defineProperty;var l=(f,t)=>I(f,"name",{value:t,configurable:!0});import{rm as y}from"node:fs/promises";import{closeFocusRow as A,getFocusById as S,listFocuses as N,listNodes as w}from"../../core/canvas/index.js";import{pruneExpiredIdempotencyKeys as R}from"../../core/runs/operations.js";import{getNode as g,lastActivityMs as u,listTerminalPlacementResidue as T,pruneExpiredCreateRequests as C,pruneNodes as F}from"../../core/canvas/canvas.js";import{lastContextStripMs as x,stripContextDir as P}from"../../core/canvas/context-strip.js";import{isAppRun as D}from"../../core/canvas/app-run.js";import{isSafeNodeId as c}from"../../core/canvas/paths.js";import{emitEvent as i}from"../../core/events/emit.js";import{operationIdContext as s}from"../../core/events/operation-id.js";import{listLivePanesAsync as G,tearDownNode as V}from"../../core/runtime/placement.js";import{reapStaleSpares as b}from"../../core/runtime/warm-pool.js";import{GroveInstanceSweepReconciler as M}from"./grove-instance-sweep.js";import{ManagedWorktreeSweepReconciler as Y}from"./managed-worktree-sweep.js";import{compactRunEvents as W,expireRunEvents as k}from"../../core/runs/events.js";import{openDb as q}from"../../core/canvas/db.js";import{touchMachineEnvironment as L}from"../../core/environments.js";import{retentionHold as _}from"./live-obligation.js";import{pruneMemoryReads as O}from"../../core/canvas/memory-reads.js";import{existsSync as z}from"node:fs";import{launcherRequest as U}from"../../launcher/client.js";import{LAUNCHER_SOCKET as H}from"../../launcher/spec.js";import{cachePassDue as K}from"../../launcher/limits/policy.js";const $=10*6e4,m=60*1e3,B=60*1e3,v=5*6e4,Q=14,X=1440*6e4,Z=2e3;class Ae{static{l(this,"StorageMaintenanceReconciler")}lastSpareSweepAt=Number.NEGATIVE_INFINITY;lastCreateRequestSweepAt=Number.NEGATIVE_INFINITY;lastEnvironmentTouchAt=Number.NEGATIVE_INFINITY;lastRunExpiryAt=Number.NEGATIVE_INFINITY;lastRunEventPassAt=Number.NEGATIVE_INFINITY;runEventCursor=0;lastAnalyzeAt=Number.NEGATIVE_INFINITY;lastRetentionSweepAt=Number.NEGATIVE_INFINITY;lastContextStripAt=Number.NEGATIVE_INFINITY;contextStripInFlight=!1;focusGcInFlight=!1;lastCachePassAt=null;cachePassInFlight=!1;worktreeSweep=new Y;groveInstanceSweep=new M;async run(t,r){if(await this.reapDeadResidue(t),this.scheduleFocusGc(r),await this.gcWarmPool(t),await this.touchEnvironment(t),await this.gcCreateRequests(t),t-this.lastRunEventPassAt>=Z){this.lastRunEventPassAt=t;try{await k(t);const e=await W(t,this.runEventCursor);this.runEventCursor=e.cursor}catch(e){console.warn(`run event maintenance failed: ${String(e)}`)}}if(t-this.lastRunExpiryAt>=60*6e4){try{await O(t)}catch(e){console.warn(`memory-read log prune failed: ${String(e)}`)}this.lastRunExpiryAt=t}t-this.lastAnalyzeAt>=1440*6e4&&(await(await q()).exec("ANALYZE"),this.lastAnalyzeAt=t),await this.sweepRetention(t,r),this.scheduleCachePass(t,r),this.scheduleContextStrip(t,r),this.worktreeSweep.run(t,r),this.groveInstanceSweep.run(t,r)}scheduleCachePass(t,r){this.cachePassInFlight||!K(this.lastCachePassAt,t)||process.platform!=="linux"||!z(H)||r.lifecycle.acceptsDetachedWork()&&(this.lastCachePassAt=t,this.cachePassInFlight=!0,r.lifecycle.registerDetached(U({operation:"cache.purge"}).then(()=>{}).catch(e=>{s.fresh(()=>{i({level:"warn",event:"app.cache.pass_failed",error:e})})}).then(()=>{this.cachePassInFlight=!1})))}msSinceTerminalActivity(t,r){const e=u(t);return e===0?Number.POSITIVE_INFINITY:r-e}async sweepRetention(t,r){t-this.lastRetentionSweepAt<v||r.lifecycle.acceptsDetachedWork()&&(this.lastRetentionSweepAt=t,await s.fresh(async()=>{try{const e=await F({ttlDays:Q,held:await _(r.fleet),removeDirs:l(a=>{r.lifecycle.registerDetached(Promise.all(a.map(o=>y(o,{recursive:!0,force:!0}))).then(()=>{}).catch(o=>{s.fresh(()=>{i({level:"error",event:"canvas.retention.failed",error:o})})}))},"removeDirs")});e.pruned.length>0&&i({level:"info",event:"canvas.retention.pruned",outcome:"succeeded",fields:{pruned_count:e.pruned.length}})}catch(e){i({level:"error",event:"canvas.retention.failed",error:e})}}))}scheduleContextStrip(t,r){if(this.contextStripInFlight||t-this.lastContextStripAt<v||!r.lifecycle.acceptsDetachedWork())return;this.lastContextStripAt=t,this.contextStripInFlight=!0;const e=this.stripFinishedContexts(t,r).finally(()=>{this.contextStripInFlight=!1});r.lifecycle.registerDetached(e)}async stripFinishedContexts(t,r){const e=await _(r.fleet),a=l(async n=>n!==null&&["done","canceled","dead"].includes(n.status)&&(n.pi_pid??null)===null&&(n.frozen_at??null)===null&&!D(n)&&u(n)<t-X&&!await e(n.node_id),"eligible");let o=0,p=0;try{for(const n of await w({status:["done","canceled","dead"]})){if(!c(n.node_id)||!await a(n))continue;const h=await x(n.node_id);if(h!==null&&h>=u(n)||!await a(await g(n.node_id)))continue;let d;try{d=await P(n.node_id)}catch(E){s.fresh(()=>{i({level:"warn",event:"canvas.context_strip.node_failed",error:E,fields:{node_id:n.node_id}})});continue}d.removed.length>0&&(o+=1,p+=d.bytes)}}catch(n){s.fresh(()=>{i({level:"error",event:"canvas.context_strip.failed",error:n})})}o>0&&s.fresh(()=>{i({level:"info",event:"canvas.context_strip.completed",outcome:"succeeded",fields:{node_count:o,bytes:p}})})}async reapDeadResidue(t){let r;try{r=await T()}catch(e){s.fresh(()=>{i({level:"error",event:"residue.reap_scan.failed",error:e})});return}for(const e of r){try{if(this.msSinceTerminalActivity(e,t)<$)continue}catch(a){s.fresh(()=>{i({level:"error",event:"residue.reap.failed",...c(e.node_id)?{node_id:e.node_id}:{},error:a,fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}})});continue}await s.fresh(async()=>{try{i({level:"info",event:"residue.reaped",...c(e.node_id)?{node_id:e.node_id}:{},fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}}),await V(e.node_id)}catch(a){i({level:"error",event:"residue.reap.failed",...c(e.node_id)?{node_id:e.node_id}:{},error:a,fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}})}})}}scheduleFocusGc(t){if(this.focusGcInFlight||!t.lifecycle.acceptsDetachedWork())return;this.focusGcInFlight=!0;const r=this.gcStaleFocuses().finally(()=>{this.focusGcInFlight=!1});t.lifecycle.registerDetached(r)}async gcStaleFocuses(){let t;try{t=await N()}catch(e){s.fresh(()=>{i({level:"error",event:"focus.gc.failed",error:e})});return}if(t.length===0)return;let r;try{r=await G()}catch(e){s.fresh(()=>{i({level:"error",event:"focus.gc.failed",error:e})});return}if(r!==null)for(const e of t){if(e.pane===null||r.has(e.pane))continue;if(!await s.fresh(async()=>{try{return(await S(e.focus_id))?.pane!==e.pane||(i({level:"info",event:"focus.gc.removed",...c(e.node_id)?{node_id:e.node_id}:{},fields:{focus_id:e.focus_id,focus_node_id:e.node_id,pane:e.pane}}),await A(e.focus_id)),!0}catch(o){return i({level:"error",event:"focus.gc.failed",error:o}),!1}}))return}}async gcCreateRequests(t){t-this.lastCreateRequestSweepAt<B||(this.lastCreateRequestSweepAt=t,await s.fresh(async()=>{const r=await C(t),e=await R(t);e>0&&i({level:"info",event:"canvas.idempotency_keys.reaped",outcome:"succeeded",fields:{reaped_count:e}}),r>0&&i({level:"info",event:"canvas.create_requests.reaped",outcome:"succeeded",fields:{reaped_count:r}})}))}async touchEnvironment(t){t-this.lastEnvironmentTouchAt<m||(this.lastEnvironmentTouchAt=t,await s.fresh(async()=>{try{await L(new Date(t).toISOString())}catch(r){i({level:"warn",event:"environment.touch.failed",error:r})}}))}async gcWarmPool(t){t-this.lastSpareSweepAt<m||(this.lastSpareSweepAt=t,await s.fresh(async()=>{const r=await b();r.length>0&&i({level:"info",event:"warm_pool.spares_reaped",outcome:"succeeded",fields:{reaped_count:r.length}})}))}}export{X as CONTEXT_STRIP_GRACE_MS,$ as DEAD_REAP_GRACE_MS,v as RETENTION_SWEEP_INTERVAL_MS,Q as RETENTION_TTL_DAYS,Ae as StorageMaintenanceReconciler};
1
+ var I=Object.defineProperty;var l=(f,t)=>I(f,"name",{value:t,configurable:!0});import{rm as y}from"node:fs/promises";import{closeFocusRow as A,getFocusById as S,listFocuses as N,listNodes as w}from"../../core/canvas/index.js";import{pruneExpiredIdempotencyKeys as R}from"../../core/runs/operations.js";import{getNode as g,lastActivityMs as u,listTerminalPlacementResidue as T,pruneExpiredCreateRequests as C,pruneNodes as F}from"../../core/canvas/canvas.js";import{lastContextStripMs as x,stripContextDir as P}from"../../core/canvas/context-strip.js";import{isAppRun as D}from"../../core/canvas/app-run.js";import{isSafeNodeId as c}from"../../core/canvas/paths.js";import{emitEvent as i}from"../../core/events/emit.js";import{operationIdContext as s}from"../../core/events/operation-id.js";import{listLivePanesAsync as G,tearDownNode as V}from"../../core/runtime/placement.js";import{reapStaleSpares as b}from"../../core/runtime/warm-pool.js";import{GroveInstanceSweepReconciler as M}from"./grove-instance-sweep.js";import{ManagedWorktreeSweepReconciler as Y}from"./managed-worktree-sweep.js";import{compactRunEvents as W,expireRunEvents as k}from"../../core/runs/events.js";import{openDb as q}from"../../core/canvas/db.js";import{touchMachineEnvironment as L}from"../../core/environments.js";import{retentionHold as _}from"./live-obligation.js";import{pruneMemoryReads as O}from"../../core/canvas/memory-reads.js";import{existsSync as z}from"node:fs";import{launcherRequest as U}from"../../launcher/client.js";import{LAUNCHER_SOCKET as H}from"../../launcher/spec.js";import{cachePassDue as K}from"../../launcher/limits/policy.js";const $=10*6e4,m=60*1e3,B=60*1e3,v=5*6e4,Q=2,X=1440*6e4,Z=2e3;class Ae{static{l(this,"StorageMaintenanceReconciler")}lastSpareSweepAt=Number.NEGATIVE_INFINITY;lastCreateRequestSweepAt=Number.NEGATIVE_INFINITY;lastEnvironmentTouchAt=Number.NEGATIVE_INFINITY;lastRunExpiryAt=Number.NEGATIVE_INFINITY;lastRunEventPassAt=Number.NEGATIVE_INFINITY;runEventCursor=0;lastAnalyzeAt=Number.NEGATIVE_INFINITY;lastRetentionSweepAt=Number.NEGATIVE_INFINITY;lastContextStripAt=Number.NEGATIVE_INFINITY;contextStripInFlight=!1;focusGcInFlight=!1;lastCachePassAt=null;cachePassInFlight=!1;worktreeSweep=new Y;groveInstanceSweep=new M;async run(t,r){if(await this.reapDeadResidue(t),this.scheduleFocusGc(r),await this.gcWarmPool(t),await this.touchEnvironment(t),await this.gcCreateRequests(t),t-this.lastRunEventPassAt>=Z){this.lastRunEventPassAt=t;try{await k(t);const e=await W(t,this.runEventCursor);this.runEventCursor=e.cursor}catch(e){console.warn(`run event maintenance failed: ${String(e)}`)}}if(t-this.lastRunExpiryAt>=60*6e4){try{await O(t)}catch(e){console.warn(`memory-read log prune failed: ${String(e)}`)}this.lastRunExpiryAt=t}t-this.lastAnalyzeAt>=1440*6e4&&(await(await q()).exec("ANALYZE"),this.lastAnalyzeAt=t),await this.sweepRetention(t,r),this.scheduleCachePass(t,r),this.scheduleContextStrip(t,r),this.worktreeSweep.run(t,r),this.groveInstanceSweep.run(t,r)}scheduleCachePass(t,r){this.cachePassInFlight||!K(this.lastCachePassAt,t)||process.platform!=="linux"||!z(H)||r.lifecycle.acceptsDetachedWork()&&(this.lastCachePassAt=t,this.cachePassInFlight=!0,r.lifecycle.registerDetached(U({operation:"cache.purge"}).then(()=>{}).catch(e=>{s.fresh(()=>{i({level:"warn",event:"app.cache.pass_failed",error:e})})}).then(()=>{this.cachePassInFlight=!1})))}msSinceTerminalActivity(t,r){const e=u(t);return e===0?Number.POSITIVE_INFINITY:r-e}async sweepRetention(t,r){t-this.lastRetentionSweepAt<v||r.lifecycle.acceptsDetachedWork()&&(this.lastRetentionSweepAt=t,await s.fresh(async()=>{try{const e=await F({ttlDays:Q,held:await _(r.fleet),removeDirs:l(a=>{r.lifecycle.registerDetached(Promise.all(a.map(o=>y(o,{recursive:!0,force:!0}))).then(()=>{}).catch(o=>{s.fresh(()=>{i({level:"error",event:"canvas.retention.failed",error:o})})}))},"removeDirs")});e.pruned.length>0&&i({level:"info",event:"canvas.retention.pruned",outcome:"succeeded",fields:{pruned_count:e.pruned.length}})}catch(e){i({level:"error",event:"canvas.retention.failed",error:e})}}))}scheduleContextStrip(t,r){if(this.contextStripInFlight||t-this.lastContextStripAt<v||!r.lifecycle.acceptsDetachedWork())return;this.lastContextStripAt=t,this.contextStripInFlight=!0;const e=this.stripFinishedContexts(t,r).finally(()=>{this.contextStripInFlight=!1});r.lifecycle.registerDetached(e)}async stripFinishedContexts(t,r){const e=await _(r.fleet),a=l(async n=>n!==null&&["done","canceled","dead"].includes(n.status)&&(n.pi_pid??null)===null&&(n.frozen_at??null)===null&&!D(n)&&u(n)<t-X&&!await e(n.node_id),"eligible");let o=0,p=0;try{for(const n of await w({status:["done","canceled","dead"]})){if(!c(n.node_id)||!await a(n))continue;const h=await x(n.node_id);if(h!==null&&h>=u(n)||!await a(await g(n.node_id)))continue;let d;try{d=await P(n.node_id)}catch(E){s.fresh(()=>{i({level:"warn",event:"canvas.context_strip.node_failed",error:E,fields:{node_id:n.node_id}})});continue}d.removed.length>0&&(o+=1,p+=d.bytes)}}catch(n){s.fresh(()=>{i({level:"error",event:"canvas.context_strip.failed",error:n})})}o>0&&s.fresh(()=>{i({level:"info",event:"canvas.context_strip.completed",outcome:"succeeded",fields:{node_count:o,bytes:p}})})}async reapDeadResidue(t){let r;try{r=await T()}catch(e){s.fresh(()=>{i({level:"error",event:"residue.reap_scan.failed",error:e})});return}for(const e of r){try{if(this.msSinceTerminalActivity(e,t)<$)continue}catch(a){s.fresh(()=>{i({level:"error",event:"residue.reap.failed",...c(e.node_id)?{node_id:e.node_id}:{},error:a,fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}})});continue}await s.fresh(async()=>{try{i({level:"info",event:"residue.reaped",...c(e.node_id)?{node_id:e.node_id}:{},fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}}),await V(e.node_id)}catch(a){i({level:"error",event:"residue.reap.failed",...c(e.node_id)?{node_id:e.node_id}:{},error:a,fields:{...c(e.node_id)?{}:{affected_node_id:e.node_id},pane:e.pane,window:e.window,tmux_session:e.tmux_session}})}})}}scheduleFocusGc(t){if(this.focusGcInFlight||!t.lifecycle.acceptsDetachedWork())return;this.focusGcInFlight=!0;const r=this.gcStaleFocuses().finally(()=>{this.focusGcInFlight=!1});t.lifecycle.registerDetached(r)}async gcStaleFocuses(){let t;try{t=await N()}catch(e){s.fresh(()=>{i({level:"error",event:"focus.gc.failed",error:e})});return}if(t.length===0)return;let r;try{r=await G()}catch(e){s.fresh(()=>{i({level:"error",event:"focus.gc.failed",error:e})});return}if(r!==null)for(const e of t){if(e.pane===null||r.has(e.pane))continue;if(!await s.fresh(async()=>{try{return(await S(e.focus_id))?.pane!==e.pane||(i({level:"info",event:"focus.gc.removed",...c(e.node_id)?{node_id:e.node_id}:{},fields:{focus_id:e.focus_id,focus_node_id:e.node_id,pane:e.pane}}),await A(e.focus_id)),!0}catch(o){return i({level:"error",event:"focus.gc.failed",error:o}),!1}}))return}}async gcCreateRequests(t){t-this.lastCreateRequestSweepAt<B||(this.lastCreateRequestSweepAt=t,await s.fresh(async()=>{const r=await C(t),e=await R(t);e>0&&i({level:"info",event:"canvas.idempotency_keys.reaped",outcome:"succeeded",fields:{reaped_count:e}}),r>0&&i({level:"info",event:"canvas.create_requests.reaped",outcome:"succeeded",fields:{reaped_count:r}})}))}async touchEnvironment(t){t-this.lastEnvironmentTouchAt<m||(this.lastEnvironmentTouchAt=t,await s.fresh(async()=>{try{await L(new Date(t).toISOString())}catch(r){i({level:"warn",event:"environment.touch.failed",error:r})}}))}async gcWarmPool(t){t-this.lastSpareSweepAt<m||(this.lastSpareSweepAt=t,await s.fresh(async()=>{const r=await b();r.length>0&&i({level:"info",event:"warm_pool.spares_reaped",outcome:"succeeded",fields:{reaped_count:r.length}})}))}}export{X as CONTEXT_STRIP_GRACE_MS,$ as DEAD_REAP_GRACE_MS,v as RETENTION_SWEEP_INTERVAL_MS,Q as RETENTION_TTL_DAYS,Ae as StorageMaintenanceReconciler};
@@ -7,6 +7,10 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
7
7
 
8
8
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
9
9
 
10
+ ## 0.3.546
11
+
12
+ `GET /v1/nodes/:id/broker/extension-state` no longer returns `report_nodes` (every node's id, name, created and grantee). Nothing has read it since 0.3.350, and returning it cost a query over every node on each model request of every broker. No SDK method reads it.
13
+
10
14
  ## 0.3.545
11
15
 
12
16
  `@north-light/sdk/stores/postgres` gains an object kit: `PostgresObjectStore` and `objectKitSchemaSql`. Additive: no existing export, request or response changes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.545",
3
+ "version": "0.3.547",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -140,7 +140,7 @@
140
140
  },
141
141
  "license": "UNLICENSED",
142
142
  "dependencies": {
143
- "@north-light/identity": "0.3.545",
143
+ "@north-light/identity": "0.3.547",
144
144
  "@earendil-works/pi-agent-core": "1.1.0",
145
145
  "@earendil-works/pi-ai": "1.1.0",
146
146
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.545",
3
+ "version": "0.3.547",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.545",
3
+ "version": "0.3.547",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.545",
9
+ "version": "0.3.547",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -24,7 +24,7 @@
24
24
  "@milkdown/preset-gfm": "^7.22.3",
25
25
  "@milkdown/prose": "^7.22.3",
26
26
  "@milkdown/utils": "^7.22.3",
27
- "@north-light/identity": "0.3.545",
27
+ "@north-light/identity": "0.3.547",
28
28
  "cron-parser": "^5.6.0",
29
29
  "esbuild": "^0.27.7",
30
30
  "jose": "^6.2.12",
@@ -12455,17 +12455,17 @@
12455
12455
  },
12456
12456
  "packages/crouter-api": {
12457
12457
  "name": "@north-light/api",
12458
- "version": "0.3.545",
12458
+ "version": "0.3.547",
12459
12459
  "license": "UNLICENSED"
12460
12460
  },
12461
12461
  "packages/crouter-env-docker": {
12462
12462
  "name": "@north-light/env-docker",
12463
- "version": "0.3.545",
12463
+ "version": "0.3.547",
12464
12464
  "license": "UNLICENSED"
12465
12465
  },
12466
12466
  "packages/crouter-identity": {
12467
12467
  "name": "@north-light/identity",
12468
- "version": "0.3.545",
12468
+ "version": "0.3.547",
12469
12469
  "license": "UNLICENSED",
12470
12470
  "dependencies": {
12471
12471
  "jose": "^6.2.1"
@@ -12473,21 +12473,21 @@
12473
12473
  },
12474
12474
  "packages/crouter-plugin": {
12475
12475
  "name": "@north-light/plugin",
12476
- "version": "0.3.545",
12476
+ "version": "0.3.547",
12477
12477
  "license": "UNLICENSED",
12478
12478
  "dependencies": {
12479
- "@north-light/api": "0.3.545",
12480
- "@north-light/identity": "0.3.545"
12479
+ "@north-light/api": "0.3.547",
12480
+ "@north-light/identity": "0.3.547"
12481
12481
  }
12482
12482
  },
12483
12483
  "packages/crouter-sdk": {
12484
12484
  "name": "@north-light/sdk",
12485
- "version": "0.3.545",
12485
+ "version": "0.3.547",
12486
12486
  "license": "UNLICENSED",
12487
12487
  "dependencies": {
12488
- "@north-light/api": "0.3.545",
12489
- "@north-light/identity": "0.3.545",
12490
- "@north-light/plugin": "0.3.545",
12488
+ "@north-light/api": "0.3.547",
12489
+ "@north-light/identity": "0.3.547",
12490
+ "@north-light/plugin": "0.3.547",
12491
12491
  "jose": "^6.2.1"
12492
12492
  },
12493
12493
  "bin": {