@north-light/crouter 0.3.320 → 0.3.322

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/bin/runtime-selector.mjs +52 -3
  2. package/dist/api/client.d.ts +31 -17
  3. package/dist/api/client.js +2 -2
  4. package/dist/api/dto/broker-ops.d.ts +0 -1
  5. package/dist/api/dto/modelauth.d.ts +15 -0
  6. package/dist/api/dto/node-outcomes.d.ts +6 -0
  7. package/dist/api/dto/nodes.d.ts +4 -3
  8. package/dist/api/dto/review-comments.d.ts +8 -0
  9. package/dist/api/errors.d.ts +6 -3
  10. package/dist/api/errors.js +1 -1
  11. package/dist/api/index.d.ts +2 -2
  12. package/dist/api/index.js +1 -1
  13. package/dist/api/node-transport.d.ts +18 -0
  14. package/dist/api/node-transport.js +1 -0
  15. package/dist/api/routes.d.ts +2 -0
  16. package/dist/api/routes.js +1 -1
  17. package/dist/builtin-memory/crouter-sdk.md +86 -0
  18. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +1 -1
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +1 -1
  20. package/dist/cli.js +8 -2
  21. package/dist/clients/attach/render/chat-view.d.ts +5 -1
  22. package/dist/clients/attach/render/chat-view.js +1 -1
  23. package/dist/clients/attach/session/frames.d.ts +1 -0
  24. package/dist/clients/attach/session/frames.js +1 -1
  25. package/dist/clients/attach/viewer.js +606 -606
  26. package/dist/clients/inbox/review/comments-client.d.ts +8 -1
  27. package/dist/clients/inbox/review/comments-client.js +1 -1
  28. package/dist/clients/inbox/review/companion-pane.d.ts +17 -0
  29. package/dist/clients/inbox/review/companion-pane.js +1 -1
  30. package/dist/clients/inbox/review/document-surface.d.ts +10 -0
  31. package/dist/clients/inbox/review/document-surface.js +2 -2
  32. package/dist/clients/inbox/review/frame.js +1 -1
  33. package/dist/clients/inbox/review/keys.d.ts +2 -0
  34. package/dist/clients/inbox/review/keys.js +1 -1
  35. package/dist/commands/api-client.js +3 -3
  36. package/dist/commands/human/prompts.js +1 -1
  37. package/dist/commands/memory/read.js +2 -2
  38. package/dist/commands/sys/branch.js +1 -1
  39. package/dist/commands/sys/connect.d.ts +1 -0
  40. package/dist/commands/sys/connect.js +3 -0
  41. package/dist/commands/sys/daemon.js +1 -1
  42. package/dist/commands/sys/panels/provider-panel.js +1 -1
  43. package/dist/commands/sys/provider-login.d.ts +8 -0
  44. package/dist/commands/sys/provider-login.js +9 -0
  45. package/dist/commands/sys/setup-core.d.ts +4 -0
  46. package/dist/commands/sys/setup-core.js +4 -4
  47. package/dist/commands/sys.js +1 -1
  48. package/dist/core/canvas/browse/app.js +3 -3
  49. package/dist/core/canvas/canvas.d.ts +3 -3
  50. package/dist/core/canvas/canvas.js +17 -14
  51. package/dist/core/canvas/migrations.js +34 -27
  52. package/dist/core/canvas/node-sql-migration.d.ts +1 -0
  53. package/dist/core/canvas/node-sql-migration.js +28 -2
  54. package/dist/core/canvas/paths.d.ts +8 -4
  55. package/dist/core/canvas/paths.js +1 -1
  56. package/dist/core/canvas/recovery.d.ts +1 -0
  57. package/dist/core/canvas/recovery.js +1 -1
  58. package/dist/core/canvas/worktrees.d.ts +1 -0
  59. package/dist/core/canvas/worktrees.js +1 -1
  60. package/dist/core/config.js +1 -1
  61. package/dist/core/human/requests.d.ts +1 -0
  62. package/dist/core/human/requests.js +17 -13
  63. package/dist/core/review/fork.d.ts +8 -0
  64. package/dist/core/review/fork.js +5 -0
  65. package/dist/core/runtime/broker/daemon-ops.js +1 -1
  66. package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
  67. package/dist/core/runtime/broker/extension-abort.js +1 -1
  68. package/dist/core/runtime/broker.js +1 -1
  69. package/dist/core/runtime/spawn.d.ts +5 -1
  70. package/dist/core/runtime/spawn.js +2 -2
  71. package/dist/core/runtime/stop-guard.d.ts +1 -3
  72. package/dist/core/runtime/stop-guard.js +2 -2
  73. package/dist/core/runtime/stop-signals.d.ts +0 -1
  74. package/dist/core/runtime/stop-signals.js +1 -1
  75. package/dist/core/secrets.d.ts +3 -0
  76. package/dist/core/secrets.js +2 -2
  77. package/dist/core/subscription-state.d.ts +1 -1
  78. package/dist/core/subscription-state.js +3 -3
  79. package/dist/core/termrender/termrender.d.ts +3 -1
  80. package/dist/core/termrender/termrender.js +13 -13
  81. package/dist/core/user-settings.d.ts +4 -0
  82. package/dist/core/user-settings.js +1 -1
  83. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  84. package/dist/daemon/api/handlers/health.d.ts +1 -1
  85. package/dist/daemon/api/handlers/health.js +14 -1
  86. package/dist/daemon/api/handlers/inbox.js +1 -1
  87. package/dist/daemon/api/handlers/modelauth.js +1 -1
  88. package/dist/daemon/api/handlers/review-comments.js +1 -1
  89. package/dist/daemon/api/map.d.ts +4 -2
  90. package/dist/daemon/api/map.js +2 -2
  91. package/dist/daemon/api/server.d.ts +5 -1
  92. package/dist/daemon/api/server.js +4 -4
  93. package/dist/daemon/companion-retire.d.ts +3 -1
  94. package/dist/daemon/companion-retire.js +1 -1
  95. package/dist/daemon/crtrd-cli.js +1 -1
  96. package/dist/daemon/crtrd.js +7 -7
  97. package/dist/daemon/human/finish.d.ts +4 -1
  98. package/dist/daemon/human/finish.js +7 -7
  99. package/dist/daemon/human/settle.d.ts +8 -6
  100. package/dist/daemon/human/settle.js +1 -1
  101. package/dist/daemon/manage.d.ts +1 -0
  102. package/dist/daemon/manage.js +2 -2
  103. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  104. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  105. package/dist/pi-extensions/canvas-stophook.js +1 -1
  106. package/dist/types.d.ts +7 -0
  107. package/dist/types.js +1 -1
  108. package/docs/sdk/README.md +54 -0
  109. package/docs/sdk/client.md +92 -0
  110. package/docs/sdk/docker.md +63 -0
  111. package/docs/sdk/errors.md +85 -0
  112. package/docs/sdk/getting-started.md +160 -0
  113. package/docs/sdk/memory.md +107 -0
  114. package/docs/sdk/migration.md +108 -0
  115. package/docs/sdk/nodes.md +193 -0
  116. package/docs/sdk/resources.md +71 -0
  117. package/docs/sdk/streaming.md +124 -0
  118. package/package.json +1 -1
  119. package/runtime.lock.json +6 -6
@@ -1,4 +1,4 @@
1
- var J=Object.defineProperty;var i=(e,n)=>J(e,"name",{value:n,configurable:!0});import{importNodeSqlAuthority as Q}from"./node-sql-migration.js";import{migrateRecoveryWitnesses as V}from"./recovery.js";import{migrateMessagingState as Z}from"./deliveries.js";import{migrateHumanRequestsState as ee}from"../human/requests.js";import{existsSync as A,readFileSync as h,readdirSync as X,realpathSync as ne}from"node:fs";import{basename as te,join as y,resolve as H}from"node:path";import{crtrHome as oe,isSafeNodeId as ie,nodesRoot as S,nodeMetaPath as I}from"./paths.js";import{STATE_MIGRATIONS as re}from"../../migrations/registry.js";import{customExtensionPaths as se}from"./extensions.js";import{readMetaObject as P,updateJsonFileDurably as b,withMetaLock as Te,writeMetaObjectLocked as Ee}from"./meta-file.js";function ae(e){e.exec(`
1
+ var J=Object.defineProperty;var i=(e,n)=>J(e,"name",{value:n,configurable:!0});import{importNodeSqlAuthority as Q,recreateNodeRowsView as V}from"./node-sql-migration.js";import{migrateRecoveryWitnesses as Z}from"./recovery.js";import{migrateMessagingState as ee}from"./deliveries.js";import{migrateHumanRequestsState as ne}from"../human/requests.js";import{existsSync as A,readFileSync as h,readdirSync as X,realpathSync as te}from"node:fs";import{basename as oe,join as y,resolve as H}from"node:path";import{crtrHome as ie,isSafeNodeId as re,nodesRoot as S,nodeMetaPath as m}from"./paths.js";import{STATE_MIGRATIONS as se}from"../../migrations/registry.js";import{customExtensionPaths as Te}from"./extensions.js";import{readMetaObject as P,updateJsonFileDurably as b,withMetaLock as Ee,writeMetaObjectLocked as ae}from"./meta-file.js";function de(e){e.exec(`
2
2
  CREATE TABLE IF NOT EXISTS nodes (
3
3
  node_id TEXT PRIMARY KEY,
4
4
  name TEXT NOT NULL,
@@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS edges (
23
23
  CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(type, to_id);
24
24
  CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(type, from_id);
25
25
  CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);
26
- `)}i(ae,"baselineSchema");function w(e,n){return new Set(e.prepare(`PRAGMA table_info(${n})`).all().map(t=>t.name))}i(w,"tableColumns");function Y(e,n,t,o){n.has(t)||(e.exec(o),n.add(t))}i(Y,"addColumnIfMissing");function l(e){return w(e,"nodes")}i(l,"nodeColumns");const a=Y;function de(e){const n=l(e);a(e,n,"intent","ALTER TABLE nodes ADD COLUMN intent TEXT;"),a(e,n,"pi_pid","ALTER TABLE nodes ADD COLUMN pi_pid INTEGER;"),a(e,n,"window","ALTER TABLE nodes ADD COLUMN window TEXT;"),a(e,n,"tmux_session","ALTER TABLE nodes ADD COLUMN tmux_session TEXT;")}i(de,"addRuntimeColumns");function _e(e){const n=S();if(!A(n))return;const t=e.prepare('UPDATE nodes SET intent = ?, pi_pid = ?, "window" = ?, tmux_session = ? WHERE node_id = ?');for(const o of X(n)){const s=I(o);if(!A(s))continue;let T;try{T=JSON.parse(h(s,"utf8"))}catch{continue}t.run(T.intent??null,T.pi_pid??null,T.window??null,T.tmux_session??null,o)}}i(_e,"backfillRuntime");function ce(e){if(!(e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'edges'").get()!==void 0)){e.exec(`
26
+ `)}i(de,"baselineSchema");function w(e,n){return new Set(e.prepare(`PRAGMA table_info(${n})`).all().map(t=>t.name))}i(w,"tableColumns");function Y(e,n,t,o){n.has(t)||(e.exec(o),n.add(t))}i(Y,"addColumnIfMissing");function l(e){return w(e,"nodes")}i(l,"nodeColumns");const a=Y;function _e(e){const n=l(e);a(e,n,"intent","ALTER TABLE nodes ADD COLUMN intent TEXT;"),a(e,n,"pi_pid","ALTER TABLE nodes ADD COLUMN pi_pid INTEGER;"),a(e,n,"window","ALTER TABLE nodes ADD COLUMN window TEXT;"),a(e,n,"tmux_session","ALTER TABLE nodes ADD COLUMN tmux_session TEXT;")}i(_e,"addRuntimeColumns");function ce(e){const n=S();if(!A(n))return;const t=e.prepare('UPDATE nodes SET intent = ?, pi_pid = ?, "window" = ?, tmux_session = ? WHERE node_id = ?');for(const o of X(n)){const s=m(o);if(!A(s))continue;let T;try{T=JSON.parse(h(s,"utf8"))}catch{continue}t.run(T.intent??null,T.pi_pid??null,T.window??null,T.tmux_session??null,o)}}i(ce,"backfillRuntime");function Ne(e){if(!(e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'edges'").get()!==void 0)){e.exec(`
27
27
  CREATE TABLE edges (
28
28
  type TEXT NOT NULL,
29
29
  from_id TEXT NOT NULL,
@@ -57,14 +57,14 @@ DROP TABLE edges;
57
57
  ALTER TABLE edges_new RENAME TO edges;
58
58
  CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(type, to_id);
59
59
  CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(type, from_id);
60
- `);const o=e.prepare("SELECT COUNT(*) AS n FROM edges").get().n;if(o!==t)throw new Error(`edges FK rebuild lost rows: expected ${t} non-orphan edge(s), got ${o}`);const s=e.prepare("PRAGMA foreign_key_check").all();if(s.length>0)throw new Error(`edges FK rebuild left ${s.length} foreign-key violation(s)`);e.exec("COMMIT")}catch(o){throw e.exec("ROLLBACK"),o}}finally{e.exec("PRAGMA foreign_keys = ON;")}}i(ce,"edgesForeignKeyCascade");function Ne(e){const n=l(e);a(e,n,"pane","ALTER TABLE nodes ADD COLUMN pane TEXT;")}i(Ne,"addPaneColumn");function ue(e){e.exec(`
60
+ `);const o=e.prepare("SELECT COUNT(*) AS n FROM edges").get().n;if(o!==t)throw new Error(`edges FK rebuild lost rows: expected ${t} non-orphan edge(s), got ${o}`);const s=e.prepare("PRAGMA foreign_key_check").all();if(s.length>0)throw new Error(`edges FK rebuild left ${s.length} foreign-key violation(s)`);e.exec("COMMIT")}catch(o){throw e.exec("ROLLBACK"),o}}finally{e.exec("PRAGMA foreign_keys = ON;")}}i(Ne,"edgesForeignKeyCascade");function ue(e){const n=l(e);a(e,n,"pane","ALTER TABLE nodes ADD COLUMN pane TEXT;")}i(ue,"addPaneColumn");function le(e){e.exec(`
61
61
  CREATE TABLE IF NOT EXISTS focuses (
62
62
  focus_id TEXT PRIMARY KEY, -- stable internal id for the viewport
63
63
  pane TEXT, -- the durable %pane_id realizing the focus
64
64
  session TEXT, -- derived cache of the user session (reconciled from pane)
65
65
  node_id TEXT NOT NULL UNIQUE -- the node shown; UNIQUE \u2192 a node occupies <=1 focus
66
66
  );
67
- `)}i(ue,"addFocusesTable");function le(e){e.exec(`
67
+ `)}i(le,"addFocusesTable");function Le(e){e.exec(`
68
68
  CREATE TABLE IF NOT EXISTS wakeups (
69
69
  wakeup_id TEXT PRIMARY KEY,
70
70
  node_id TEXT, -- TARGET + node-anchored cancel anchor; NULL = canvas-detached
@@ -80,7 +80,7 @@ CREATE INDEX IF NOT EXISTS idx_wakeups_fire ON wakeups(fire_at);
80
80
  CREATE INDEX IF NOT EXISTS idx_wakeups_node ON wakeups(node_id); -- by-node cancel / list
81
81
  CREATE INDEX IF NOT EXISTS idx_wakeups_owner ON wakeups(owner_id); -- by-owner reap (ruling A)
82
82
  CREATE UNIQUE INDEX IF NOT EXISTS idx_wakeups_deadline ON wakeups(node_id) WHERE kind = 'deadline';
83
- `)}i(le,"appendWakeupsTable");function Le(e){e.exec(`
83
+ `)}i(Le,"appendWakeupsTable");function pe(e){e.exec(`
84
84
  CREATE TABLE IF NOT EXISTS triggers (
85
85
  trigger_id TEXT PRIMARY KEY,
86
86
  owner_id TEXT NOT NULL,
@@ -131,12 +131,12 @@ SELECT
131
131
  FROM wakeups;
132
132
 
133
133
  DROP TABLE wakeups;
134
- `)}i(Le,"migrateWakeupsToTriggers");function pe(e){const n=l(e);a(e,n,"host_kind","ALTER TABLE nodes ADD COLUMN host_kind TEXT;")}i(pe,"addHostKindColumn");function Oe(e){e.exec(`
134
+ `)}i(pe,"migrateWakeupsToTriggers");function Oe(e){const n=l(e);a(e,n,"host_kind","ALTER TABLE nodes ADD COLUMN host_kind TEXT;")}i(Oe,"addHostKindColumn");function Ae(e){e.exec(`
135
135
  CREATE TABLE IF NOT EXISTS canvas_meta (
136
136
  key TEXT PRIMARY KEY,
137
137
  value TEXT NOT NULL
138
138
  );
139
- `)}i(Oe,"addCanvasMetaTable");function Ae(e){const n=l(e);a(e,n,"profile_id","ALTER TABLE nodes ADD COLUMN profile_id TEXT;")}i(Ae,"addProfileIdColumn");function fe(e){const n=l(e);a(e,n,"pi_pid_identity","ALTER TABLE nodes ADD COLUMN pi_pid_identity TEXT;")}i(fe,"addPidIdentityColumn");function Re(e){const n=l(e);a(e,n,"waiting_for","ALTER TABLE nodes ADD COLUMN waiting_for TEXT;")}i(Re,"addMessageWaitColumn");function $(e){const n=l(e);if(n.size===0)return;a(e,n,"final_report","ALTER TABLE nodes ADD COLUMN final_report TEXT;"),a(e,n,"finalized_at","ALTER TABLE nodes ADD COLUMN finalized_at TEXT;");const t=e.prepare("SELECT node_id FROM nodes WHERE status = 'done' AND final_report IS NULL").all(),o=e.prepare("UPDATE nodes SET final_report = ?, finalized_at = ? WHERE node_id = ? AND final_report IS NULL");for(const{node_id:s}of t){const T=y(S(),s,"reports");if(!A(T))continue;let r=null;for(const E of X(T))if(E.endsWith(".md"))try{const d=h(y(T,E),"utf8"),_=/^---\nnode: [^\n]+\nkind: final\nts: ([^\n]+)\n---\n/.exec(d);if(_===null||Number.isNaN(Date.parse(_[1])))continue;const c={name:E,ts:_[1]};(r===null||c.ts>r.ts||c.ts===r.ts&&c.name>r.name)&&(r=c)}catch{}r!==null&&o.run(r.name,r.ts,s)}}i($,"addCanonicalFinalColumns");function me(e){const n=S();if(A(n))for(const t of X(n)){const o=I(t);A(o)&&b(o,s=>{const T=s.launch;if(typeof T!="object"||T===null||Array.isArray(T))return!1;const r=T.extensions;if(!Array.isArray(r)||!r.every(d=>typeof d=="string"))return!1;const E=se(r);return E.length===r.length?!1:(T.extensions=E,!0)})}}i(me,"normalizePersistedLaunchExtensions");function Ie(e){const n=l(e);a(e,n,"waiting_for_generation","ALTER TABLE nodes ADD COLUMN waiting_for_generation INTEGER NOT NULL DEFAULT 0;")}i(Ie,"addMessageWaitGenerationColumn");function Xe(e){e.exec(`
139
+ `)}i(Ae,"addCanvasMetaTable");function fe(e){const n=l(e);a(e,n,"profile_id","ALTER TABLE nodes ADD COLUMN profile_id TEXT;")}i(fe,"addProfileIdColumn");function Ie(e){const n=l(e);a(e,n,"pi_pid_identity","ALTER TABLE nodes ADD COLUMN pi_pid_identity TEXT;")}i(Ie,"addPidIdentityColumn");function Re(e){const n=l(e);a(e,n,"waiting_for","ALTER TABLE nodes ADD COLUMN waiting_for TEXT;")}i(Re,"addMessageWaitColumn");function $(e){const n=l(e);if(n.size===0)return;a(e,n,"final_report","ALTER TABLE nodes ADD COLUMN final_report TEXT;"),a(e,n,"finalized_at","ALTER TABLE nodes ADD COLUMN finalized_at TEXT;");const t=e.prepare("SELECT node_id FROM nodes WHERE status = 'done' AND final_report IS NULL").all(),o=e.prepare("UPDATE nodes SET final_report = ?, finalized_at = ? WHERE node_id = ? AND final_report IS NULL");for(const{node_id:s}of t){const T=y(S(),s,"reports");if(!A(T))continue;let r=null;for(const E of X(T))if(E.endsWith(".md"))try{const d=h(y(T,E),"utf8"),_=/^---\nnode: [^\n]+\nkind: final\nts: ([^\n]+)\n---\n/.exec(d);if(_===null||Number.isNaN(Date.parse(_[1])))continue;const c={name:E,ts:_[1]};(r===null||c.ts>r.ts||c.ts===r.ts&&c.name>r.name)&&(r=c)}catch{}r!==null&&o.run(r.name,r.ts,s)}}i($,"addCanonicalFinalColumns");function me(e){const n=S();if(A(n))for(const t of X(n)){const o=m(t);A(o)&&b(o,s=>{const T=s.launch;if(typeof T!="object"||T===null||Array.isArray(T))return!1;const r=T.extensions;if(!Array.isArray(r)||!r.every(d=>typeof d=="string"))return!1;const E=Te(r);return E.length===r.length?!1:(T.extensions=E,!0)})}}i(me,"normalizePersistedLaunchExtensions");function Xe(e){const n=l(e);a(e,n,"waiting_for_generation","ALTER TABLE nodes ADD COLUMN waiting_for_generation INTEGER NOT NULL DEFAULT 0;")}i(Xe,"addMessageWaitGenerationColumn");function Se(e){e.exec(`
140
140
  CREATE TABLE IF NOT EXISTS consult_outbox (
141
141
  node_id TEXT PRIMARY KEY, -- legacy work node id
142
142
  dir TEXT NOT NULL, -- legacy ticket directory
@@ -150,15 +150,15 @@ CREATE INDEX IF NOT EXISTS idx_consult_outbox_status ON consult_outbox(status);
150
150
  CREATE INDEX IF NOT EXISTS idx_consult_outbox_dir ON consult_outbox(dir);
151
151
  CREATE INDEX IF NOT EXISTS idx_consult_outbox_dir_request_status ON consult_outbox(dir, request_id, status);
152
152
  CREATE INDEX IF NOT EXISTS idx_consult_outbox_dir_status ON consult_outbox(dir, status);
153
- `)}i(Xe,"addHumanWorkOutboxTable");function Se(e){const n=w(e,"consult_outbox"),t=i((o,s)=>Y(e,n,o,s),"add");t("work_kind","ALTER TABLE consult_outbox ADD COLUMN work_kind TEXT NOT NULL DEFAULT 'follow_up';"),t("interaction_id","ALTER TABLE consult_outbox ADD COLUMN interaction_id TEXT;"),t("generation_id","ALTER TABLE consult_outbox ADD COLUMN generation_id TEXT;"),t("interaction_snapshot","ALTER TABLE consult_outbox ADD COLUMN interaction_snapshot TEXT;"),t("handler_command","ALTER TABLE consult_outbox ADD COLUMN handler_command TEXT;"),t("handler_args_json","ALTER TABLE consult_outbox ADD COLUMN handler_args_json TEXT;"),t("claim_token","ALTER TABLE consult_outbox ADD COLUMN claim_token TEXT;"),e.exec(`
153
+ `)}i(Se,"addHumanWorkOutboxTable");function Ue(e){const n=w(e,"consult_outbox"),t=i((o,s)=>Y(e,n,o,s),"add");t("work_kind","ALTER TABLE consult_outbox ADD COLUMN work_kind TEXT NOT NULL DEFAULT 'follow_up';"),t("interaction_id","ALTER TABLE consult_outbox ADD COLUMN interaction_id TEXT;"),t("generation_id","ALTER TABLE consult_outbox ADD COLUMN generation_id TEXT;"),t("interaction_snapshot","ALTER TABLE consult_outbox ADD COLUMN interaction_snapshot TEXT;"),t("handler_command","ALTER TABLE consult_outbox ADD COLUMN handler_command TEXT;"),t("handler_args_json","ALTER TABLE consult_outbox ADD COLUMN handler_args_json TEXT;"),t("claim_token","ALTER TABLE consult_outbox ADD COLUMN claim_token TEXT;"),e.exec(`
154
154
  CREATE INDEX IF NOT EXISTS idx_consult_outbox_human_work_status ON consult_outbox(work_kind, status);
155
155
  CREATE INDEX IF NOT EXISTS idx_consult_outbox_human_work_dir_status ON consult_outbox(dir, work_kind, status);
156
156
  CREATE INDEX IF NOT EXISTS idx_consult_outbox_human_work_request_status ON consult_outbox(dir, request_id, work_kind, status);
157
157
  CREATE UNIQUE INDEX IF NOT EXISTS idx_consult_outbox_visual_request ON consult_outbox(dir, request_id, work_kind) WHERE work_kind = 'visual';
158
- `)}i(Se,"addHumanWorkColumns");function Ue(e){e.exec(`
158
+ `)}i(Ue,"addHumanWorkColumns");function ge(e){e.exec(`
159
159
  CREATE INDEX IF NOT EXISTS idx_nodes_parent ON nodes(parent);
160
160
  CREATE INDEX IF NOT EXISTS idx_nodes_created ON nodes(created);
161
- `)}i(Ue,"addNodeParentCreatedIndexes");function ge(e){e.exec(`
161
+ `)}i(ge,"addNodeParentCreatedIndexes");function Ce(e){e.exec(`
162
162
  CREATE TABLE IF NOT EXISTS crons (
163
163
  cron_id TEXT PRIMARY KEY,
164
164
  name TEXT NOT NULL, -- agent-legible label; how it shows in list
@@ -213,22 +213,22 @@ CREATE TABLE IF NOT EXISTS cron_runs ( -- bounded ring, last 20 p
213
213
  FOREIGN KEY (cron_id) REFERENCES crons(cron_id) ON DELETE CASCADE
214
214
  );
215
215
  CREATE INDEX IF NOT EXISTS idx_cron_runs_cron ON cron_runs(cron_id, started);
216
- `)}i(ge,"addCronsTables");function f(e){return`'${e.replace(/'/g,`'"'"'`)}'`}i(f,"shellSingleQuote");function Ce(e,n){let t=0;for(;;){const o=`${n}_${t}`;if(!e.split(`
217
- `).includes(o))return o;t++}}i(Ce,"uniqueHeredocDelimiter");function W(e,n,t){const o=Ce(n,t);return`${e} <<'${o}'
216
+ `)}i(Ce,"addCronsTables");function f(e){return`'${e.replace(/'/g,`'"'"'`)}'`}i(f,"shellSingleQuote");function De(e,n){let t=0;for(;;){const o=`${n}_${t}`;if(!e.split(`
217
+ `).includes(o))return o;t++}}i(De,"uniqueHeredocDelimiter");function W(e,n,t){const o=De(n,t);return`${e} <<'${o}'
218
218
  ${n}
219
- ${o}`}i(W,"heredocCommand");function De(e){if(typeof e!="string")return{};try{const n=JSON.parse(e);return typeof n!="object"||n===null||Array.isArray(n)?{}:n}catch{return{}}}i(De,"migrationPayload");function L(e,n,t){const o=e[n];if(typeof o!="string"||o.trim()==="")throw new Error(`trigger ${t} payload requires non-empty ${n}`);return o}i(L,"requiredString");function U(e,n){const t=e[n];return typeof t=="string"&&t.trim()!==""?t:void 0}i(U,"optionalString");function R(e,n){const t=e[n];return typeof t=="string"&&t.trim()!==""?t:void 0}i(R,"optionalColumn");function K(e,n,t,o){return W(`crtr node message send --to ${f(e)} --tier ${f(n)}`,t,`CRTR_MIGRATED_${o.replace(/[^A-Za-z0-9]/g,"_")}`)}i(K,"migratedMessageCommand");function he(e,n){return`printf '%s\\n' "$CRTR_GATE_STDOUT" | crtr node message send --to ${f(e)} --tier ${f(n)}`}i(he,"migratedGateStdoutMessageCommand");function we(e,n){const t=[`crtr node new --kind ${f(L(e,"kind",n))}`],o=[["mode","mode"],["cwd","cwd"],["name","name"],["parent","parent"],["worktreeBase","worktree"],["forkFrom","fork-from"],["model","model"],["profile","profile"],["situationalContext","situational-context"]];for(const[T,r]of o){const E=U(e,T);E!==void 0&&t.push(`--${r} ${f(E)}`)}e.root===!0&&t.push("--root");const s=L(e,"prompt",n);return W(t.join(" "),s,`CRTR_MIGRATED_${n.replace(/[^A-Za-z0-9]/g,"_")}`)}i(we,"migratedSpawnCommand");const q="migration.v22.skipped";function xe(e){const n=e.prepare("SELECT cwd FROM nodes WHERE parent IS NULL ORDER BY created LIMIT 1").get();return(n!==void 0?R(n,"cwd"):void 0)??process.cwd()}i(xe,"ambientMigrationCwd");function ve(e){let n;try{n=e.prepare("SELECT eval_pid FROM triggers WHERE eval_state = 'running' AND eval_pid IS NOT NULL").all()}catch{return}for(const t of n){const o=t.eval_pid;if(!(typeof o!="number"||!Number.isInteger(o)||o<=1))try{process.kill(-o,"SIGTERM")}catch{}}}i(ve,"reapRunningPredicateEvaluations");function Fe(e){const n=[],t=e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'triggers'").get()!==void 0;t&&ve(e);let o=[];if(t)try{o=e.prepare("SELECT * FROM triggers ORDER BY created, trigger_id").all()}catch(r){n.push({trigger_id:"<unreadable table>",reason:`triggers table could not be read: ${r instanceof Error?r.message:String(r)}`})}const s=xe(e),T=e.prepare(`
219
+ ${o}`}i(W,"heredocCommand");function he(e){if(typeof e!="string")return{};try{const n=JSON.parse(e);return typeof n!="object"||n===null||Array.isArray(n)?{}:n}catch{return{}}}i(he,"migrationPayload");function L(e,n,t){const o=e[n];if(typeof o!="string"||o.trim()==="")throw new Error(`trigger ${t} payload requires non-empty ${n}`);return o}i(L,"requiredString");function U(e,n){const t=e[n];return typeof t=="string"&&t.trim()!==""?t:void 0}i(U,"optionalString");function I(e,n){const t=e[n];return typeof t=="string"&&t.trim()!==""?t:void 0}i(I,"optionalColumn");function K(e,n,t,o){return W(`crtr node message send --to ${f(e)} --tier ${f(n)}`,t,`CRTR_MIGRATED_${o.replace(/[^A-Za-z0-9]/g,"_")}`)}i(K,"migratedMessageCommand");function we(e,n){return`printf '%s\\n' "$CRTR_GATE_STDOUT" | crtr node message send --to ${f(e)} --tier ${f(n)}`}i(we,"migratedGateStdoutMessageCommand");function xe(e,n){const t=[`crtr node new --kind ${f(L(e,"kind",n))}`],o=[["mode","mode"],["cwd","cwd"],["name","name"],["parent","parent"],["worktreeBase","worktree"],["forkFrom","fork-from"],["model","model"],["profile","profile"],["situationalContext","situational-context"]];for(const[T,r]of o){const E=U(e,T);E!==void 0&&t.push(`--${r} ${f(E)}`)}e.root===!0&&t.push("--root");const s=L(e,"prompt",n);return W(t.join(" "),s,`CRTR_MIGRATED_${n.replace(/[^A-Za-z0-9]/g,"_")}`)}i(xe,"migratedSpawnCommand");const q="migration.v22.skipped";function ve(e){const n=e.prepare("SELECT cwd FROM nodes WHERE parent IS NULL ORDER BY created LIMIT 1").get();return(n!==void 0?I(n,"cwd"):void 0)??process.cwd()}i(ve,"ambientMigrationCwd");function Fe(e){let n;try{n=e.prepare("SELECT eval_pid FROM triggers WHERE eval_state = 'running' AND eval_pid IS NOT NULL").all()}catch{return}for(const t of n){const o=t.eval_pid;if(!(typeof o!="number"||!Number.isInteger(o)||o<=1))try{process.kill(-o,"SIGTERM")}catch{}}}i(Fe,"reapRunningPredicateEvaluations");function ye(e){const n=[],t=e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'triggers'").get()!==void 0;t&&Fe(e);let o=[];if(t)try{o=e.prepare("SELECT * FROM triggers ORDER BY created, trigger_id").all()}catch(r){n.push({trigger_id:"<unreadable table>",reason:`triggers table could not be read: ${r instanceof Error?r.message:String(r)}`})}const s=ve(e),T=e.prepare(`
220
220
  INSERT OR REPLACE INTO crons (
221
221
  cron_id, name, created_by, command, fire_at, recur, tz, expires_at,
222
222
  anchor_node, cancel_on_wake, cwd, env_json, profile, scope,
223
223
  run_timeout_s, overlap, on_output, sink, tier, created, updated
224
- ) VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, NULL, ?, 'profile', ?, 'skip', 'on-failure', '{}', ?, ?, ?)`);for(const r of o){const E=R(r,"trigger_id");if(E===void 0){n.push({trigger_id:"<unidentified row>",reason:"row carries no trigger_id"});continue}try{ye(e,T,r,E,s)}catch(d){n.push({trigger_id:E,reason:d instanceof Error?d.message:String(d)})}}n.length>0&&(e.prepare("INSERT INTO canvas_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(q,JSON.stringify(n)),console.warn(`[canvas migration v22] ${n.length} retired schedule(s) could not be converted to crons and were dropped; see canvas_meta['${q}']: `+n.map(r=>`${r.trigger_id} (${r.reason})`).join("; "))),e.exec(`
224
+ ) VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, NULL, ?, 'profile', ?, 'skip', 'on-failure', '{}', ?, ?, ?)`);for(const r of o){const E=I(r,"trigger_id");if(E===void 0){n.push({trigger_id:"<unidentified row>",reason:"row carries no trigger_id"});continue}try{ke(e,T,r,E,s)}catch(d){n.push({trigger_id:E,reason:d instanceof Error?d.message:String(d)})}}n.length>0&&(e.prepare("INSERT INTO canvas_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(q,JSON.stringify(n)),console.warn(`[canvas migration v22] ${n.length} retired schedule(s) could not be converted to crons and were dropped; see canvas_meta['${q}']: `+n.map(r=>`${r.trigger_id} (${r.reason})`).join("; "))),e.exec(`
225
225
  DROP INDEX IF EXISTS idx_triggers_fire;
226
226
  DROP INDEX IF EXISTS idx_triggers_node;
227
227
  DROP INDEX IF EXISTS idx_triggers_owner;
228
228
  DROP INDEX IF EXISTS idx_triggers_eval;
229
229
  DROP INDEX IF EXISTS idx_triggers_deadline;
230
230
  DROP TABLE IF EXISTS triggers;
231
- `)}i(Fe,"migrateTriggersToCrons");function ye(e,n,t,o,s){const T=L(t,"owner_id",o),r=e.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(T),E=(r===void 0?void 0:R(r,"cwd"))??s,d=r===void 0?null:R(r,"profile_id")??null,_=R(t,"node_id")??null,c=L(t,"action",o),N=L(t,"action_mode",o),u=L(t,"trigger_type",o),m=De(t.payload),M=R(t,"created")??new Date().toISOString(),C=N==="deadline_message"?"urgent":U(m,"tier")??"normal",x=U(m,"label");let p,D,G=!1;if(c==="msg"&&(N==="inbox_message"||N==="deadline_message")){if(_===null)throw new Error("has no message target");const O=U(m,"body");O!==void 0?p=K(_,C,O,o):u==="when"?(G=!0,p=he(_,C)):p=K(_,C,x??"Scheduled message (no body was recorded when this schedule was armed).",o),D=x??(N==="deadline_message"?"deadline":"scheduled message")}else if(c==="msg"&&N==="fresh_revive"){if(_===null)throw new Error("has no revive target");p=`crtr node lifecycle revive ${f(_)} --fresh`,D=x??"scheduled fresh revive"}else if(c==="new"&&N==="node_birth")p=we(m,o),D=U(m,"name")??`scheduled ${L(m,"kind",o)} node`;else throw new Error(`has unsupported action ${c}/${N}`);let v,F,B=null;if(u==="when"){v=L(t,"next_eval_at",o),F=L(t,"eval_every",o),B=R(t,"timeout_at")??null;const O=L(t,"when_script",o);p=`${G?`if ! CRTR_GATE_STDOUT="$(
231
+ `)}i(ye,"migrateTriggersToCrons");function ke(e,n,t,o,s){const T=L(t,"owner_id",o),r=e.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(T),E=(r===void 0?void 0:I(r,"cwd"))??s,d=r===void 0?null:I(r,"profile_id")??null,_=I(t,"node_id")??null,c=L(t,"action",o),N=L(t,"action_mode",o),u=L(t,"trigger_type",o),R=he(t.payload),M=I(t,"created")??new Date().toISOString(),C=N==="deadline_message"?"urgent":U(R,"tier")??"normal",x=U(R,"label");let p,D,G=!1;if(c==="msg"&&(N==="inbox_message"||N==="deadline_message")){if(_===null)throw new Error("has no message target");const O=U(R,"body");O!==void 0?p=K(_,C,O,o):u==="when"?(G=!0,p=we(_,C)):p=K(_,C,x??"Scheduled message (no body was recorded when this schedule was armed).",o),D=x??(N==="deadline_message"?"deadline":"scheduled message")}else if(c==="msg"&&N==="fresh_revive"){if(_===null)throw new Error("has no revive target");p=`crtr node lifecycle revive ${f(_)} --fresh`,D=x??"scheduled fresh revive"}else if(c==="new"&&N==="node_birth")p=xe(R,o),D=U(R,"name")??`scheduled ${L(R,"kind",o)} node`;else throw new Error(`has unsupported action ${c}/${N}`);let v,F,B=null;if(u==="when"){v=L(t,"next_eval_at",o),F=L(t,"eval_every",o),B=I(t,"timeout_at")??null;const O=L(t,"when_script",o);p=`${G?`if ! CRTR_GATE_STDOUT="$(
232
232
  ${O}
233
233
  )"; then`:`if ! (
234
234
  ${O}
@@ -237,14 +237,14 @@ ${O}
237
237
  fi
238
238
  {
239
239
  ${p}
240
- } && crtr cron cancel "$CRTR_CRON_ID"`}else if(u==="at"||u==="every"||u==="until")v=L(t,"fire_at",o),F=u==="every"?L(t,"recur",o):null;else throw new Error(`has unknown timing type ${u}`);if(N==="deadline_message"&&_!==null){const O=e.prepare("SELECT cron_id FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1 AND cron_id <> ?").get(_,o);if(O!==void 0)throw new Error(`node ${_} already has a migrated deadline (${String(O.cron_id)}); only one is allowed`)}n.run(o,D,T,p,v,F,B,_,N==="deadline_message"?1:0,E,d,300,C,M,M)}i(ye,"convertTriggerRow");function ke(e){const n=w(e,"crons");n.size===0||n.has("run_pid_identity")||e.exec("ALTER TABLE crons ADD COLUMN run_pid_identity TEXT;")}i(ke,"addCronRunPidIdentityColumn");function Me(e){const n=w(e,"crons");n.size!==0&&(n.has("run_out_of_band")||e.exec("ALTER TABLE crons ADD COLUMN run_out_of_band INTEGER NOT NULL DEFAULT 0;"),n.has("queued")&&e.exec("ALTER TABLE crons DROP COLUMN queued;"))}i(Me,"addCronOutOfBandColumn");function Ge(e){e.exec(`
240
+ } && crtr cron cancel "$CRTR_CRON_ID"`}else if(u==="at"||u==="every"||u==="until")v=L(t,"fire_at",o),F=u==="every"?L(t,"recur",o):null;else throw new Error(`has unknown timing type ${u}`);if(N==="deadline_message"&&_!==null){const O=e.prepare("SELECT cron_id FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1 AND cron_id <> ?").get(_,o);if(O!==void 0)throw new Error(`node ${_} already has a migrated deadline (${String(O.cron_id)}); only one is allowed`)}n.run(o,D,T,p,v,F,B,_,N==="deadline_message"?1:0,E,d,300,C,M,M)}i(ke,"convertTriggerRow");function Me(e){const n=w(e,"crons");n.size===0||n.has("run_pid_identity")||e.exec("ALTER TABLE crons ADD COLUMN run_pid_identity TEXT;")}i(Me,"addCronRunPidIdentityColumn");function Ge(e){const n=w(e,"crons");n.size!==0&&(n.has("run_out_of_band")||e.exec("ALTER TABLE crons ADD COLUMN run_out_of_band INTEGER NOT NULL DEFAULT 0;"),n.has("queued")&&e.exec("ALTER TABLE crons DROP COLUMN queued;"))}i(Ge,"addCronOutOfBandColumn");function Be(e){e.exec(`
241
241
  CREATE TABLE IF NOT EXISTS warm_pool (
242
242
  node_id TEXT PRIMARY KEY REFERENCES nodes(node_id) ON DELETE CASCADE,
243
243
  recipe_key TEXT NOT NULL, -- frozen launch tuple this spare was minted for
244
244
  created TEXT NOT NULL -- ISO 8601, mint time (oldest spare claimed first)
245
245
  );
246
246
  CREATE INDEX IF NOT EXISTS idx_warm_pool_recipe ON warm_pool(recipe_key, created);
247
- `)}i(Ge,"addWarmPoolTable");function Be(e){const n=e.prepare("SELECT node_id, dir, root, request_id, work_kind, status, final_report, updated_at FROM consult_outbox").all();e.exec(`
247
+ `)}i(Be,"addWarmPoolTable");function He(e){const n=e.prepare("SELECT node_id, dir, root, request_id, work_kind, status, final_report, updated_at FROM consult_outbox").all();e.exec(`
248
248
  CREATE TABLE consult_outbox_new (
249
249
  node_id TEXT PRIMARY KEY,
250
250
  ticket_node_id TEXT NOT NULL,
@@ -256,7 +256,7 @@ CREATE TABLE consult_outbox_new (
256
256
  );
257
257
  `);const t=e.prepare(`
258
258
  INSERT INTO consult_outbox_new (node_id, ticket_node_id, request_id, work_kind, status, final_report, updated_at)
259
- VALUES (?, ?, ?, ?, ?, ?, ?)`);let o=0;for(const s of n){const T=te(s.dir);if(!(s.root===H(s.root)&&s.dir===y(s.root,T))||!ie(T)){o+=1;continue}t.run(s.node_id,T,s.request_id,s.work_kind,s.status,s.final_report,s.updated_at)}o>0&&console.warn(`[canvas migration v26] dropped ${o} consult_outbox row(s) whose legacy ticket path cannot safely map to a ticket node id`),e.exec(`
259
+ VALUES (?, ?, ?, ?, ?, ?, ?)`);let o=0;for(const s of n){const T=oe(s.dir);if(!(s.root===H(s.root)&&s.dir===y(s.root,T))||!re(T)){o+=1;continue}t.run(s.node_id,T,s.request_id,s.work_kind,s.status,s.final_report,s.updated_at)}o>0&&console.warn(`[canvas migration v26] dropped ${o} consult_outbox row(s) whose legacy ticket path cannot safely map to a ticket node id`),e.exec(`
260
260
  DROP TABLE consult_outbox;
261
261
  ALTER TABLE consult_outbox_new RENAME TO consult_outbox;
262
262
 
@@ -265,7 +265,7 @@ CREATE INDEX IF NOT EXISTS idx_consult_outbox_human_work_status ON consult_outbo
265
265
  CREATE INDEX IF NOT EXISTS idx_consult_outbox_ticket_status ON consult_outbox(ticket_node_id, work_kind, status);
266
266
  CREATE INDEX IF NOT EXISTS idx_consult_outbox_ticket_request_status ON consult_outbox(ticket_node_id, request_id, work_kind, status);
267
267
  CREATE UNIQUE INDEX IF NOT EXISTS idx_consult_outbox_visual_request ON consult_outbox(ticket_node_id, request_id, work_kind) WHERE work_kind = 'visual';
268
- `)}i(Be,"narrowHumanWorkOutbox");function He(e){e.exec(`
268
+ `)}i(He,"narrowHumanWorkOutbox");function Pe(e){e.exec(`
269
269
  CREATE TABLE IF NOT EXISTS reviews (
270
270
  review_id TEXT PRIMARY KEY,
271
271
  idempotency_key TEXT NOT NULL UNIQUE,
@@ -309,7 +309,7 @@ CREATE INDEX IF NOT EXISTS idx_reviews_state ON reviews(state);
309
309
  CREATE INDEX IF NOT EXISTS idx_reviews_origin ON reviews(origin_node_id);
310
310
  CREATE INDEX IF NOT EXISTS idx_reviews_file ON reviews(file);
311
311
  CREATE INDEX IF NOT EXISTS idx_reviews_bridge ON reviews(bridge_node_id);
312
- `)}i(He,"addReviewsTable");function Pe(e){e.exec(`
312
+ `)}i(Pe,"addReviewsTable");function be(e){e.exec(`
313
313
  CREATE TABLE IF NOT EXISTS review_comments (
314
314
  comment_id TEXT PRIMARY KEY,
315
315
  review_id TEXT NOT NULL,
@@ -354,13 +354,13 @@ CREATE INDEX IF NOT EXISTS idx_review_comment_events_review_seq
354
354
  ON review_comment_events(review_id, seq);
355
355
  CREATE INDEX IF NOT EXISTS idx_review_comment_events_comment_seq
356
356
  ON review_comment_events(comment_id, seq);
357
- `)}i(Pe,"addReviewCommentTables");function be(e){e.exec(`
357
+ `)}i(be,"addReviewCommentTables");function Ye(e){e.exec(`
358
358
  ALTER TABLE review_comments
359
359
  ADD COLUMN anchor_state TEXT NOT NULL DEFAULT 'live' CHECK (anchor_state IN ('live', 'detached'));
360
- `)}i(be,"addReviewCommentAnchorState");function Ye(e){e.exec("DROP TABLE IF EXISTS consult_outbox")}i(Ye,"dropConsultOutbox");function $e(e){e.exec("ALTER TABLE reviews ADD COLUMN submit_requested_at TEXT")}i($e,"addReviewSubmitRequested");function We(e){e.exec(`
360
+ `)}i(Ye,"addReviewCommentAnchorState");function $e(e){e.exec("DROP TABLE IF EXISTS consult_outbox")}i($e,"dropConsultOutbox");function We(e){e.exec("ALTER TABLE reviews ADD COLUMN submit_requested_at TEXT")}i(We,"addReviewSubmitRequested");function Ke(e){e.exec(`
361
361
  ALTER TABLE crons ADD COLUMN held INTEGER NOT NULL DEFAULT 0;
362
362
  ALTER TABLE crons ADD COLUMN last_poke_at TEXT;
363
- `)}i(We,"addCronHeldColumns");function Ke(e){e.exec(`
363
+ `)}i(Ke,"addCronHeldColumns");function qe(e){e.exec(`
364
364
  CREATE TABLE human_action_deliveries (
365
365
  request_id TEXT PRIMARY KEY,
366
366
  state TEXT NOT NULL,
@@ -378,7 +378,7 @@ CREATE TABLE human_action_deliveries (
378
378
  updated_at INTEGER NOT NULL
379
379
  );
380
380
  CREATE INDEX idx_human_action_deliveries_due ON human_action_deliveries(state, next_attempt_at);
381
- `)}i(Ke,"addHumanActionDeliveries");function qe(e){const n=l(e);a(e,n,"pi_session_file","ALTER TABLE nodes ADD COLUMN pi_session_file TEXT;");for(const t of X(S(),{withFileTypes:!0}))if(t.isDirectory())try{const o=JSON.parse(h(I(t.name),"utf8"));typeof o.pi_session_file=="string"&&e.prepare("UPDATE nodes SET pi_session_file = ? WHERE node_id = ?").run(o.pi_session_file,t.name)}catch{}e.exec("CREATE INDEX IF NOT EXISTS idx_nodes_pi_session_file ON nodes(pi_session_file);")}i(qe,"addNodeSessionFileIndex");function j(e,n){const t=i(E=>{const d=H(E);return A(d)?ne(d):d},"canonical"),o=e.prepare("UPDATE nodes SET pi_session_file = ? WHERE node_id = ?");e.exec("DROP INDEX IF EXISTS idx_nodes_pi_session_file;");const s=e.prepare("SELECT node_id FROM nodes ORDER BY created ASC, node_id ASC").all();for(const{node_id:E}of s){const d=I(E);if(P(d)===null){o.run(null,E);continue}const _=Te(d,()=>{const c=P(d);if(c===null)throw new Error(`node ${E} session metadata changed during ownership migration`);if(typeof c.pi_session_file!="string")return null;const N=c.pi_session_file,u=t(N);return u!==N&&(c.pi_session_file=u,Ee(d,c)),u});o.run(_,E)}const T=e.prepare(`
381
+ `)}i(qe,"addHumanActionDeliveries");function je(e){const n=l(e);a(e,n,"pi_session_file","ALTER TABLE nodes ADD COLUMN pi_session_file TEXT;");for(const t of X(S(),{withFileTypes:!0}))if(t.isDirectory())try{const o=JSON.parse(h(m(t.name),"utf8"));typeof o.pi_session_file=="string"&&e.prepare("UPDATE nodes SET pi_session_file = ? WHERE node_id = ?").run(o.pi_session_file,t.name)}catch{}e.exec("CREATE INDEX IF NOT EXISTS idx_nodes_pi_session_file ON nodes(pi_session_file);")}i(je,"addNodeSessionFileIndex");function j(e,n){const t=i(E=>{const d=H(E);return A(d)?te(d):d},"canonical"),o=e.prepare("UPDATE nodes SET pi_session_file = ? WHERE node_id = ?");e.exec("DROP INDEX IF EXISTS idx_nodes_pi_session_file;");const s=e.prepare("SELECT node_id FROM nodes ORDER BY created ASC, node_id ASC").all();for(const{node_id:E}of s){const d=m(E);if(P(d)===null){o.run(null,E);continue}const _=Ee(d,()=>{const c=P(d);if(c===null)throw new Error(`node ${E} session metadata changed during ownership migration`);if(typeof c.pi_session_file!="string")return null;const N=c.pi_session_file,u=t(N);return u!==N&&(c.pi_session_file=u,ae(d,c)),u});o.run(_,E)}const T=e.prepare(`
382
382
  SELECT pi_session_file
383
383
  FROM nodes
384
384
  WHERE pi_session_file IS NOT NULL
@@ -389,7 +389,7 @@ SELECT node_id
389
389
  FROM nodes
390
390
  WHERE pi_session_file = ?
391
391
  ORDER BY created ASC, node_id ASC
392
- `).all(E);for(const{node_id:_}of d.slice(1)){if(!b(I(_),N=>{const u=N.pi_session_file;if(typeof u!="string"||t(u)!==E)throw new Error(`node ${_} session metadata changed during ownership migration`);return N.pi_session_id=null,N.pi_session_file=null,!0}))throw new Error(`node ${_} session metadata changed during ownership migration`);r.run(_)}}e.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_nodes_pi_session_file ON nodes(pi_session_file);")}i(j,"reconcileNodeSessionFileOwners");function je(e){j(e,!0)}i(je,"makeNodeSessionFileUnique");function ze(e){j(e,!1)}i(ze,"repairNodeSessionFileUniqueIndex");function Je(e){const n=l(e);a(e,n,"frozen_at","ALTER TABLE nodes ADD COLUMN frozen_at TEXT;"),a(e,n,"respawn_failures","ALTER TABLE nodes ADD COLUMN respawn_failures INTEGER NOT NULL DEFAULT 0;"),a(e,n,"respawn_not_before","ALTER TABLE nodes ADD COLUMN respawn_not_before TEXT;"),a(e,n,"launched_at","ALTER TABLE nodes ADD COLUMN launched_at TEXT;"),a(e,n,"last_settled_at","ALTER TABLE nodes ADD COLUMN last_settled_at TEXT;"),a(e,n,"terminal_reason","ALTER TABLE nodes ADD COLUMN terminal_reason TEXT;"),e.exec(`
392
+ `).all(E);for(const{node_id:_}of d.slice(1)){if(!b(m(_),N=>{const u=N.pi_session_file;if(typeof u!="string"||t(u)!==E)throw new Error(`node ${_} session metadata changed during ownership migration`);return N.pi_session_id=null,N.pi_session_file=null,!0}))throw new Error(`node ${_} session metadata changed during ownership migration`);r.run(_)}}e.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_nodes_pi_session_file ON nodes(pi_session_file);")}i(j,"reconcileNodeSessionFileOwners");function ze(e){j(e,!0)}i(ze,"makeNodeSessionFileUnique");function Je(e){j(e,!1)}i(Je,"repairNodeSessionFileUniqueIndex");function Qe(e){const n=l(e);a(e,n,"frozen_at","ALTER TABLE nodes ADD COLUMN frozen_at TEXT;"),a(e,n,"respawn_failures","ALTER TABLE nodes ADD COLUMN respawn_failures INTEGER NOT NULL DEFAULT 0;"),a(e,n,"respawn_not_before","ALTER TABLE nodes ADD COLUMN respawn_not_before TEXT;"),a(e,n,"launched_at","ALTER TABLE nodes ADD COLUMN launched_at TEXT;"),a(e,n,"last_settled_at","ALTER TABLE nodes ADD COLUMN last_settled_at TEXT;"),a(e,n,"terminal_reason","ALTER TABLE nodes ADD COLUMN terminal_reason TEXT;"),e.exec(`
393
393
  UPDATE nodes SET terminal_reason = CASE
394
394
  WHEN status = 'done' AND final_report IS NOT NULL THEN 'finalized'
395
395
  WHEN status = 'done' AND intent = 'parked' THEN 'parked'
@@ -398,7 +398,7 @@ UPDATE nodes SET terminal_reason = CASE
398
398
  WHEN status = 'dead' THEN 'crashed'
399
399
  END
400
400
  WHERE status IN ('done','canceled','dead') AND terminal_reason IS NULL;
401
- `)}i(Je,"addNodeLifecycleColumns");function Qe(e){const n=l(e);a(e,n,"description","ALTER TABLE nodes ADD COLUMN description TEXT;"),a(e,n,"cycles","ALTER TABLE nodes ADD COLUMN cycles INTEGER;");const t=S();if(!A(t))return;const o=e.prepare("UPDATE nodes SET description = ?, cycles = ? WHERE node_id = ?");for(const s of X(t)){const T=I(s);if(!A(T))continue;let r;try{r=JSON.parse(h(T,"utf8"))}catch{continue}const E=typeof r.description=="string"?r.description:null,d=typeof r.cycles=="number"?r.cycles:null;o.run(E,d,s)}}i(Qe,"addNodeLabelColumns");function Ve(e){const n=l(e);a(e,n,"outcome_kind","ALTER TABLE nodes ADD COLUMN outcome_kind TEXT;"),a(e,n,"outcome_reason","ALTER TABLE nodes ADD COLUMN outcome_reason TEXT;"),a(e,n,"outcome_at","ALTER TABLE nodes ADD COLUMN outcome_at TEXT;"),a(e,n,"outcome_revision","ALTER TABLE nodes ADD COLUMN outcome_revision INTEGER NOT NULL DEFAULT 0;"),a(e,n,"outcome_detail_json","ALTER TABLE nodes ADD COLUMN outcome_detail_json TEXT;"),a(e,n,"deadline_at","ALTER TABLE nodes ADD COLUMN deadline_at TEXT;"),e.exec(`
401
+ `)}i(Qe,"addNodeLifecycleColumns");function Ve(e){const n=l(e);a(e,n,"description","ALTER TABLE nodes ADD COLUMN description TEXT;"),a(e,n,"cycles","ALTER TABLE nodes ADD COLUMN cycles INTEGER;");const t=S();if(!A(t))return;const o=e.prepare("UPDATE nodes SET description = ?, cycles = ? WHERE node_id = ?");for(const s of X(t)){const T=m(s);if(!A(T))continue;let r;try{r=JSON.parse(h(T,"utf8"))}catch{continue}const E=typeof r.description=="string"?r.description:null,d=typeof r.cycles=="number"?r.cycles:null;o.run(E,d,s)}}i(Ve,"addNodeLabelColumns");function Ze(e){const n=l(e);a(e,n,"outcome_kind","ALTER TABLE nodes ADD COLUMN outcome_kind TEXT;"),a(e,n,"outcome_reason","ALTER TABLE nodes ADD COLUMN outcome_reason TEXT;"),a(e,n,"outcome_at","ALTER TABLE nodes ADD COLUMN outcome_at TEXT;"),a(e,n,"outcome_revision","ALTER TABLE nodes ADD COLUMN outcome_revision INTEGER NOT NULL DEFAULT 0;"),a(e,n,"outcome_detail_json","ALTER TABLE nodes ADD COLUMN outcome_detail_json TEXT;"),a(e,n,"deadline_at","ALTER TABLE nodes ADD COLUMN deadline_at TEXT;"),e.exec(`
402
402
  CREATE TABLE IF NOT EXISTS node_outcome_deliveries (
403
403
  node_id TEXT PRIMARY KEY,
404
404
  state TEXT NOT NULL,
@@ -431,4 +431,11 @@ UPDATE nodes SET outcome_kind='failure',
431
431
  outcome_reason=COALESCE(terminal_reason,'crashed'),
432
432
  outcome_at=COALESCE(finalized_at, created), outcome_revision=1
433
433
  WHERE final_report IS NULL AND status IN ('done','dead','canceled') AND outcome_at IS NULL;
434
- `)}i(Ve,"addNodeOutcomeAndDeadline");const Ze=re.filter(e=>e.lane==="journaled").map(e=>n=>e.apply(n,oe())),g=[ae,de,_e,ce,Ne,ue,le,pe,Le,Oe,Ae,fe,Re,$,me,$,Ie,Xe,Se,Ue,ge,Fe,ke,Me,Ge,Be,He,Pe,be,Ye,$e,We,Ke,qe,Je,je,ze,Qe,Ve,...Ze,e=>{Q(e),V(e),Z(e),ee(e)}],en=new Set([3]);function k(e){return e.prepare("PRAGMA user_version").get().user_version}i(k,"currentUserVersion");function z(e,n){g[n](e),e.exec(`PRAGMA user_version = ${n+1}`)}i(z,"applyMigration");function ln(e){const n=k(e);if(n>g.length)throw new Error(`unsupported canvas schema version ${n} (latest supported ${g.length})`);if(n===g.length)return;const t=e.prepare("PRAGMA busy_timeout").get().timeout;e.exec("PRAGMA busy_timeout = 60000;");try{nn(e)}finally{e.exec(`PRAGMA busy_timeout = ${t};`)}}i(ln,"migrate");function nn(e){for(;;){const n=k(e);if(n>=g.length)return;if(e.isTransaction||en.has(n)){z(e,n);continue}e.exec("BEGIN IMMEDIATE");try{k(e)===n&&z(e,n),e.exec("COMMIT")}catch(t){try{e.exec("ROLLBACK")}catch{}throw t}}}i(nn,"runPendingMigrations");export{g as MIGRATIONS,ln as migrate};
434
+ `)}i(Ze,"addNodeOutcomeAndDeadline");function en(e){e.exec(`
435
+ CREATE INDEX IF NOT EXISTS deliveries_external_due_order
436
+ ON deliveries(state, next_attempt_at, created_at, delivery_id)
437
+ WHERE destination_kind = 'external_action' AND canceled_at IS NULL;
438
+ DROP INDEX IF EXISTS deliveries_external_due;
439
+ CREATE INDEX IF NOT EXISTS idx_nodes_parent_created ON nodes(parent, created);
440
+ CREATE INDEX IF NOT EXISTS idx_edges_type_created ON edges(type, created);
441
+ `)}i(en,"addRecurringLaneIndexes");const nn=se.filter(e=>e.lane==="journaled").map(e=>n=>e.apply(n,ie()));function tn(e){V(e)}i(tn,"projectLiveNodeState");const g=[de,_e,ce,Ne,ue,le,Le,Oe,pe,Ae,fe,Ie,Re,$,me,$,Xe,Se,Ue,ge,Ce,ye,Me,Ge,Be,He,Pe,be,Ye,$e,We,Ke,qe,je,Qe,ze,Je,Ve,Ze,...nn,e=>{Q(e),Z(e),ee(e),ne(e)},en,tn],on=new Set([3]);function k(e){return e.prepare("PRAGMA user_version").get().user_version}i(k,"currentUserVersion");function z(e,n){g[n](e),e.exec(`PRAGMA user_version = ${n+1}`)}i(z,"applyMigration");function On(e){const n=k(e);if(n>g.length)throw new Error(`unsupported canvas schema version ${n} (latest supported ${g.length})`);if(n===g.length)return;const t=e.prepare("PRAGMA busy_timeout").get().timeout;e.exec("PRAGMA busy_timeout = 60000;");try{rn(e)}finally{e.exec(`PRAGMA busy_timeout = ${t};`)}}i(On,"migrate");function rn(e){for(;;){const n=k(e);if(n>=g.length)return;if(e.isTransaction||on.has(n)){z(e,n);continue}e.exec("BEGIN IMMEDIATE");try{k(e)===n&&z(e,n),e.exec("COMMIT")}catch(t){try{e.exec("ROLLBACK")}catch{}throw t}}}i(rn,"runPendingMigrations");export{g as MIGRATIONS,On as migrate};
@@ -1,3 +1,4 @@
1
1
  import { DatabaseSync } from 'node:sqlite';
2
+ export declare function recreateNodeRowsView(db: DatabaseSync): void;
2
3
  /** Imported by the shared appended consolidation transaction. The caller owns the transaction and user_version. */
3
4
  export declare function importNodeSqlAuthority(db: DatabaseSync): void;
@@ -1,4 +1,4 @@
1
- var X=Object.defineProperty;var r=(e,s)=>X(e,"name",{value:s,configurable:!0});import{randomUUID as N}from"node:crypto";import{existsSync as m,readFileSync as g,readdirSync as x,realpathSync as y}from"node:fs";import{join as A,resolve as R}from"node:path";import{nodesRoot as L}from"./paths.js";const C=["node_id","name","created","cwd","kind","mode","lifecycle"];function u(e,s){return new Set(e.prepare(`PRAGMA table_info(${s})`).all().map(({name:i})=>i))}r(u,"columns");function U(e){const s=u(e,"nodes"),i=[["title","TEXT"],["icon","TEXT"],["spawned_by","TEXT"],["creator","TEXT"],["fork_from","TEXT"],["fork_source_file","TEXT"],["passive_default","INTEGER NOT NULL DEFAULT 0 CHECK (passive_default IN (0,1))"],["cycle_pending","INTEGER NOT NULL DEFAULT 0 CHECK (cycle_pending IN (0,1))"],["model_override","TEXT"],["persona_ack_json","TEXT"],["review_binding_json","TEXT"],["launch_config_json","TEXT"]];for(const[n,t]of i)s.has(n)||e.exec(`ALTER TABLE nodes ADD COLUMN ${n} ${t}`);e.exec("CREATE INDEX IF NOT EXISTS idx_nodes_cycle_pending ON nodes(cycle_pending) WHERE cycle_pending = 1")}r(U,"addNodeColumns");function f(e){const s=R(e);return m(s)?y(s):s}r(f,"canonical");function l(e,s,i){if(e===void 0)return null;const n=JSON.stringify(e);if(n===void 0)throw new Error(`cannot serialize ${s} in ${i}`);return n}r(l,"json");function c(e,s,i){const n=e[s];if(typeof n!="string")throw new Error(`cannot import node metadata ${i}: ${s} must be a string`);return n}r(c,"requiredString");function a(e,s,i){const n=e[s];if(n==null)return null;if(typeof n!="string")throw new Error(`cannot import node metadata ${i}: ${s} must be a string or null`);return n}r(a,"optionalString");function O(e,s,i){const n=e[s];if(n==null)return null;if(typeof n!="number"||!Number.isFinite(n))throw new Error(`cannot import node metadata ${i}: ${s} must be a finite number or null`);return n}r(O,"optionalNumber");function h(e,s,i){if(e!=null){if(typeof e!="object"||Array.isArray(e))throw new Error(`cannot import ${s} from ${i}: expected an object`);return e}}r(h,"optionalObject");function k(){if(!m(L()))return[];const e=[];for(const s of x(L(),{withFileTypes:!0})){if(!s.isDirectory())continue;const i=A(L(),s.name,"meta.json");if(!m(i))continue;let n;try{n=JSON.parse(g(i,"utf8"))}catch(t){throw new Error(`cannot import node metadata ${i}: ${t.message}`)}for(const t of C)if(n[t]===void 0||n[t]===null)throw new Error(`cannot import node metadata ${i}: missing required ${t}`);if(typeof n.node_id!="string"||n.node_id!==s.name)throw new Error(`cannot import node metadata ${i}: node_id does not match its directory`);e.push({path:i,meta:n})}return e}r(k,"readMetas");function D(e){return u(e,"nodes").has("pi_session_file")?new Map(e.prepare("SELECT node_id, pi_session_file FROM nodes WHERE pi_session_file IS NOT NULL").all().map(({node_id:i,pi_session_file:n})=>[i,n])):new Map}r(D,"sqlOnlyPaths");function F(e){e.exec(`
1
+ var A=Object.defineProperty;var d=(e,r)=>A(e,"name",{value:r,configurable:!0});import{randomUUID as N}from"node:crypto";import{existsSync as m,readFileSync as h,readdirSync as x,realpathSync as g}from"node:fs";import{join as X,resolve as R}from"node:path";import{nodesRoot as L}from"./paths.js";const C=["node_id","name","created","cwd","kind","mode","lifecycle"];function f(e,r){return new Set(e.prepare(`PRAGMA table_info(${r})`).all().map(({name:i})=>i))}d(f,"columns");function U(e){const r=f(e,"nodes"),i=[["title","TEXT"],["icon","TEXT"],["spawned_by","TEXT"],["creator","TEXT"],["fork_from","TEXT"],["fork_source_file","TEXT"],["passive_default","INTEGER NOT NULL DEFAULT 0 CHECK (passive_default IN (0,1))"],["cycle_pending","INTEGER NOT NULL DEFAULT 0 CHECK (cycle_pending IN (0,1))"],["model_override","TEXT"],["persona_ack_json","TEXT"],["review_binding_json","TEXT"],["launch_config_json","TEXT"]];for(const[n,t]of i)r.has(n)||e.exec(`ALTER TABLE nodes ADD COLUMN ${n} ${t}`);e.exec("CREATE INDEX IF NOT EXISTS idx_nodes_cycle_pending ON nodes(cycle_pending) WHERE cycle_pending = 1")}d(U,"addNodeColumns");function T(e){const r=R(e);return m(r)?g(r):r}d(T,"canonical");function l(e,r,i){if(e===void 0)return null;const n=JSON.stringify(e);if(n===void 0)throw new Error(`cannot serialize ${r} in ${i}`);return n}d(l,"json");function _(e,r,i){const n=e[r];if(typeof n!="string")throw new Error(`cannot import node metadata ${i}: ${r} must be a string`);return n}d(_,"requiredString");function a(e,r,i){const n=e[r];if(n==null)return null;if(typeof n!="string")throw new Error(`cannot import node metadata ${i}: ${r} must be a string or null`);return n}d(a,"optionalString");function O(e,r,i){const n=e[r];if(n==null)return null;if(typeof n!="number"||!Number.isFinite(n))throw new Error(`cannot import node metadata ${i}: ${r} must be a finite number or null`);return n}d(O,"optionalNumber");function y(e,r,i){if(e!=null){if(typeof e!="object"||Array.isArray(e))throw new Error(`cannot import ${r} from ${i}: expected an object`);return e}}d(y,"optionalObject");function k(){if(!m(L()))return[];const e=[];for(const r of x(L(),{withFileTypes:!0})){if(!r.isDirectory())continue;const i=X(L(),r.name,"meta.json");if(!m(i))continue;let n;try{n=JSON.parse(h(i,"utf8"))}catch(t){throw new Error(`cannot import node metadata ${i}: ${t.message}`)}for(const t of C)if(n[t]===void 0||n[t]===null)throw new Error(`cannot import node metadata ${i}: missing required ${t}`);if(typeof n.node_id!="string"||n.node_id!==r.name)throw new Error(`cannot import node metadata ${i}: node_id does not match its directory`);e.push({path:i,meta:n})}return e}d(k,"readMetas");function D(e){return f(e,"nodes").has("pi_session_file")?new Map(e.prepare("SELECT node_id, pi_session_file FROM nodes WHERE pi_session_file IS NOT NULL").all().map(({node_id:i,pi_session_file:n})=>[i,n])):new Map}d(D,"sqlOnlyPaths");function F(e){e.exec(`
2
2
  CREATE TABLE IF NOT EXISTS sessions (
3
3
  binding_id TEXT PRIMARY KEY,
4
4
  session_id TEXT,
@@ -51,4 +51,30 @@ CREATE TABLE IF NOT EXISTS worktrees (
51
51
  disposal_json TEXT
52
52
  );
53
53
  CREATE INDEX IF NOT EXISTS idx_worktrees_sweep ON worktrees(state, cleanup_state, next_attempt_at);
54
- `)}r(F,"createTables");function b(e,s,i){const n=e.prepare("INSERT INTO nodes (node_id, name, description, cycles, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created, title, icon, spawned_by, creator, fork_from, fork_source_file, passive_default, cycle_pending, model_override, persona_ack_json, review_binding_json, launch_config_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(node_id) DO UPDATE SET name=excluded.name, description=excluded.description, cycles=excluded.cycles, kind=excluded.kind, mode=excluded.mode, lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind, profile_id=excluded.profile_id, parent=excluded.parent, title=excluded.title, icon=excluded.icon, spawned_by=excluded.spawned_by, creator=excluded.creator, fork_from=excluded.fork_from, fork_source_file=excluded.fork_source_file, passive_default=excluded.passive_default, cycle_pending=excluded.cycle_pending, model_override=excluded.model_override, persona_ack_json=excluded.persona_ack_json, review_binding_json=excluded.review_binding_json, launch_config_json=excluded.launch_config_json");for(const{path:t,meta:o}of s){const d=c(o,"node_id",t),_=i.get(d),E=a(o,"pi_session_file",t);if(_!==void 0&&E!==null&&f(_)!==f(E))throw new Error(`cannot import node metadata ${t}: pi_session_file conflicts with indexed path ${_}`);n.run(d,c(o,"name",t),a(o,"description",t),O(o,"cycles",t),c(o,"kind",t),c(o,"mode",t),c(o,"lifecycle",t),c(o,"cwd",t),a(o,"host_kind",t),a(o,"profile_id",t),a(o,"parent",t),c(o,"created",t),a(o,"title",t),a(o,"icon",t),a(o,"spawned_by",t),a(o,"creator",t),a(o,"fork_from",t),a(o,"fork_source_file",t),o.passive_default===!0?1:0,o.cycle_pending===!0?1:0,a(o,"model_override",t),l(o.persona_ack,"persona_ack",t),l(o.review_binding,"review_binding",t),l(o.launch,"launch",t)),E!==null&&i.delete(d)}}r(b,"importMetas");function v(e,s){const i=new Set(e.prepare("SELECT node_id FROM nodes").all().map(({node_id:t})=>t)),n=e.prepare("INSERT INTO edges (type, from_id, to_id, active, created) VALUES ('spawned_by', ?, ?, 1, ?) ON CONFLICT(type, from_id, to_id) DO NOTHING");for(const{meta:t}of s){const o=t.node_id,d=typeof t.spawned_by=="string"?t.spawned_by:typeof t.parent=="string"?t.parent:null;d!==null&&i.has(o)&&i.has(d)&&n.run(o,d,new Date().toISOString())}}r(v,"importEdges");function $(e,s,i){const n=e.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, ?, ?, ?, NULL, NULL, ?, ?, NULL)"),t=new Map;for(const{path:o,meta:d}of s){const _=c(d,"node_id",o),E=i.get(_),p=typeof d.pi_session_file=="string"?d.pi_session_file:E,w=typeof d.pi_session_id=="string"?d.pi_session_id:null;if(p===void 0&&w===null)continue;const T=p===void 0?null:f(p);if(T!==null){const S=t.get(T);if(S!==void 0)throw new Error(`cannot import duplicate live transcript ${T}: ${S}, ${_}`);t.set(T,_)}const I=c(d,"created",o);n.run(N(),w,_,T,I,I),E!==void 0&&i.delete(_)}for(const[o,d]of i){const _=f(d),E=t.get(_);if(E!==void 0)throw new Error(`cannot import duplicate live transcript ${_}: ${E}, ${o}`);t.set(_,o);const p=e.prepare("SELECT created FROM nodes WHERE node_id = ?").get(o)?.created;p!==void 0&&n.run(N(),null,o,_,p,p)}}r($,"importSessions");function j(e,s){const i=e.prepare("INSERT INTO worktrees (node_id, 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) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");for(const{path:n,meta:t}of s){const o=h(t.managed_worktree,"managed worktree",n);if(o===void 0)continue;const d=h(o.sweep,"managed_worktree.sweep",n);i.run(c(t,"node_id",n),c(o,"state",n),a(o,"cleanup",n),c(o,"path",n),c(o,"branch",n),c(o,"repo_root",n),c(o,"base_ref",n),c(o,"base_sha",n),c(o,"created",n),a(o,"closed",n),d===void 0?null:a(d,"next_attempt",n),d===void 0?0:O(d,"attempts",n)??0,d?.quarantined===!0?1:0,d===void 0?null:a(d,"reason",n),d===void 0?null:a(d,"detail",n),d===void 0?null:a(d,"last_attempt",n),l(d?.observed,"managed_worktree.sweep.observed",n),l(o.abandoned,"managed_worktree.abandoned",n),l(o.land_intent,"managed_worktree.land_intent",n),l(o.disposal,"managed_worktree.disposal",n))}}r(j,"importWorktrees");function H(e){if(!u(e,"nodes").has("pi_pid"))return;const i=e.prepare("SELECT node_id, pi_pid, pi_pid_identity, launched_at, created FROM nodes WHERE pi_pid IS NOT NULL").all(),n=e.prepare("INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, ?, ?, 'running', ?, NULL, NULL)");for(const t of i)n.run(N(),t.node_id,t.pi_pid,t.pi_pid_identity,t.launched_at??t.created)}r(H,"importExecutions");function M(e,s){if(e.prepare("SELECT COUNT(*) AS count FROM nodes").get().count<s)throw new Error("node import removed an existing SQL row");if(e.prepare("PRAGMA foreign_key_check").all().length>0)throw new Error("node import foreign key check failed")}r(M,"validate");function K(e){e.exec("DROP INDEX IF EXISTS idx_nodes_pi_session_file");const s=u(e,"nodes");for(const i of["pi_session_file","pi_pid","pi_pid_identity"])s.has(i)&&e.exec(`ALTER TABLE nodes DROP COLUMN ${i}`);e.exec("DROP VIEW IF EXISTS node_rows"),e.exec("CREATE VIEW node_rows AS SELECT n.*, CASE WHEN e.state = 'running' THEN e.pid ELSE NULL END AS pi_pid, CASE WHEN e.state = 'running' THEN e.process_identity ELSE NULL END AS pi_pid_identity, s.session_id AS pi_session_id, s.transcript_path AS pi_session_file FROM nodes n LEFT JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited' LEFT JOIN sessions s ON s.node_id = n.node_id AND s.unbound_at IS NULL")}r(K,"dropLegacyColumnsAndCreateView");function Y(e){const s=e.prepare("SELECT COUNT(*) AS count FROM nodes").get().count,i=D(e),n=k();U(e),F(e),b(e,n,i),v(e,n),$(e,n,i),j(e,n),H(e),M(e,s),K(e)}r(Y,"importNodeSqlAuthority");export{Y as importNodeSqlAuthority};
54
+ `)}d(F,"createTables");function b(e,r,i){const n=e.prepare("INSERT INTO nodes (node_id, name, description, cycles, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created, title, icon, spawned_by, creator, fork_from, fork_source_file, passive_default, cycle_pending, model_override, persona_ack_json, review_binding_json, launch_config_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(node_id) DO UPDATE SET name=excluded.name, description=excluded.description, cycles=excluded.cycles, kind=excluded.kind, mode=excluded.mode, lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind, profile_id=excluded.profile_id, parent=excluded.parent, title=excluded.title, icon=excluded.icon, spawned_by=excluded.spawned_by, creator=excluded.creator, fork_from=excluded.fork_from, fork_source_file=excluded.fork_source_file, passive_default=excluded.passive_default, cycle_pending=excluded.cycle_pending, model_override=excluded.model_override, persona_ack_json=excluded.persona_ack_json, review_binding_json=excluded.review_binding_json, launch_config_json=excluded.launch_config_json");for(const{path:t,meta:o}of r){const s=_(o,"node_id",t),c=i.get(s),p=a(o,"pi_session_file",t);if(c!==void 0&&p!==null&&T(c)!==T(p))throw new Error(`cannot import node metadata ${t}: pi_session_file conflicts with indexed path ${c}`);n.run(s,_(o,"name",t),a(o,"description",t),O(o,"cycles",t),_(o,"kind",t),_(o,"mode",t),_(o,"lifecycle",t),_(o,"cwd",t),a(o,"host_kind",t),a(o,"profile_id",t),a(o,"parent",t),_(o,"created",t),a(o,"title",t),a(o,"icon",t),a(o,"spawned_by",t),a(o,"creator",t),a(o,"fork_from",t),a(o,"fork_source_file",t),o.passive_default===!0?1:0,o.cycle_pending===!0?1:0,a(o,"model_override",t),l(o.persona_ack,"persona_ack",t),l(o.review_binding,"review_binding",t),l(o.launch,"launch",t)),p!==null&&i.delete(s)}}d(b,"importMetas");function v(e,r){const i=new Set(e.prepare("SELECT node_id FROM nodes").all().map(({node_id:t})=>t)),n=e.prepare("INSERT INTO edges (type, from_id, to_id, active, created) VALUES ('spawned_by', ?, ?, 1, ?) ON CONFLICT(type, from_id, to_id) DO NOTHING");for(const{meta:t}of r){const o=t.node_id,s=typeof t.spawned_by=="string"?t.spawned_by:typeof t.parent=="string"?t.parent:null;s!==null&&i.has(o)&&i.has(s)&&n.run(o,s,new Date().toISOString())}}d(v,"importEdges");function j(e,r,i){const n=e.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, ?, ?, ?, NULL, NULL, ?, ?, NULL)"),t=new Map;for(const{path:o,meta:s}of r){const c=_(s,"node_id",o),p=i.get(c),E=typeof s.pi_session_file=="string"?s.pi_session_file:p,S=typeof s.pi_session_id=="string"?s.pi_session_id:null;if(E===void 0&&S===null)continue;const u=E===void 0?null:T(E);if(u!==null){const I=t.get(u);if(I!==void 0)throw new Error(`cannot import duplicate live transcript ${u}: ${I}, ${c}`);t.set(u,c)}const w=_(s,"created",o);n.run(N(),S,c,u,w,w),p!==void 0&&i.delete(c)}for(const[o,s]of i){const c=T(s),p=t.get(c);if(p!==void 0)throw new Error(`cannot import duplicate live transcript ${c}: ${p}, ${o}`);t.set(c,o);const E=e.prepare("SELECT created FROM nodes WHERE node_id = ?").get(o)?.created;E!==void 0&&n.run(N(),null,o,c,E,E)}}d(j,"importSessions");function $(e,r){const i=e.prepare("INSERT INTO worktrees (node_id, 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) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");for(const{path:n,meta:t}of r){const o=y(t.managed_worktree,"managed worktree",n);if(o===void 0)continue;const s=y(o.sweep,"managed_worktree.sweep",n);i.run(_(t,"node_id",n),_(o,"state",n),a(o,"cleanup",n),_(o,"path",n),_(o,"branch",n),_(o,"repo_root",n),_(o,"base_ref",n),_(o,"base_sha",n),_(o,"created",n),a(o,"closed",n),s===void 0?null:a(s,"next_attempt",n),s===void 0?0:O(s,"attempts",n)??0,s?.quarantined===!0?1:0,s===void 0?null:a(s,"reason",n),s===void 0?null:a(s,"detail",n),s===void 0?null:a(s,"last_attempt",n),l(s?.observed,"managed_worktree.sweep.observed",n),l(o.abandoned,"managed_worktree.abandoned",n),l(o.land_intent,"managed_worktree.land_intent",n),l(o.disposal,"managed_worktree.disposal",n))}}d($,"importWorktrees");function H(e){if(!f(e,"nodes").has("pi_pid"))return;const i=e.prepare("SELECT node_id, pi_pid, pi_pid_identity, launched_at, created FROM nodes WHERE pi_pid IS NOT NULL").all(),n=e.prepare("INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, ?, ?, 'running', ?, NULL, NULL)");for(const t of i)n.run(N(),t.node_id,t.pi_pid,t.pi_pid_identity,t.launched_at??t.created)}d(H,"importExecutions");function M(e,r){if(e.prepare("SELECT COUNT(*) AS count FROM nodes").get().count<r)throw new Error("node import removed an existing SQL row");if(e.prepare("PRAGMA foreign_key_check").all().length>0)throw new Error("node import foreign key check failed")}d(M,"validate");const W=`
55
+ CREATE VIEW node_rows AS
56
+ SELECT n.*,
57
+ CASE WHEN e.state = 'running' THEN e.pid ELSE NULL END AS pi_pid,
58
+ CASE WHEN e.state = 'running' THEN e.process_identity ELSE NULL END AS pi_pid_identity,
59
+ e.turn_open_at,
60
+ e.turn_closed_at,
61
+ s.session_id AS pi_session_id,
62
+ s.transcript_path AS pi_session_file,
63
+ f.fault_id,
64
+ f.link AS fault_link,
65
+ f.op AS fault_op,
66
+ f.kind AS fault_kind,
67
+ f.message AS fault_message,
68
+ f.since AS fault_since,
69
+ f.operation_id AS fault_operation_id,
70
+ f.anchor_entry_id AS fault_anchor_entry_id,
71
+ f.retry_disposition AS fault_retry_disposition,
72
+ f.retry_by AS fault_retry_by,
73
+ f.retry_attempt AS fault_retry_attempt,
74
+ f.retry_max AS fault_retry_max,
75
+ f.retry_next_at AS fault_retry_next_at,
76
+ f.provider_error_json AS fault_provider_error_json
77
+ FROM nodes n
78
+ LEFT JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited'
79
+ LEFT JOIN sessions s ON s.node_id = n.node_id AND s.unbound_at IS NULL
80
+ LEFT JOIN node_faults f ON f.node_id = n.node_id AND f.active = 1`;function K(e){e.exec("DROP VIEW IF EXISTS node_rows"),e.exec(W)}d(K,"recreateNodeRowsView");function P(e){e.exec("DROP INDEX IF EXISTS idx_nodes_pi_session_file");const r=f(e,"nodes");for(const i of["pi_session_file","pi_pid","pi_pid_identity"])r.has(i)&&e.exec(`ALTER TABLE nodes DROP COLUMN ${i}`);K(e)}d(P,"dropLegacyColumnsAndCreateView");function B(e){const r=e.prepare("SELECT COUNT(*) AS count FROM nodes").get().count,i=D(e),n=k();U(e),F(e),b(e,n,i),v(e,n),j(e,n,i),$(e,n),H(e),M(e,r),P(e)}d(B,"importNodeSqlAuthority");export{B as importNodeSqlAuthority,K as recreateNodeRowsView};
@@ -16,16 +16,20 @@ export declare function nodesRoot(): string;
16
16
  export declare function reviewsRoot(): string;
17
17
  export declare function reviewDir(reviewId: string): string;
18
18
  export declare function reviewBranchPath(reviewId: string): string;
19
+ /** The immutable session branch a single comment's fork is born from. Each
20
+ * comment gets its own branch file so concurrent forks never share a source. */
21
+ export declare function reviewForkBranchPath(reviewId: string, commentId: string): string;
22
+ export declare function ensureReviewForkDir(reviewId: string): void;
19
23
  export declare function reviewResultPath(reviewId: string): string;
20
24
  export declare function ensureReviewDir(reviewId: string): void;
21
25
  /** Absolute path to crtrd's API listener unix socket, a sibling of
22
26
  * `canvasDbPath()` under the canvas home (`~/.crouter/canvas/crtrd.sock` unless
23
27
  * `CRTR_HOME` is set). Pure path builder — no db reach — so both the daemon
24
28
  * (which binds it) and a CLI-side resolver can import it freely. `src/api`'s
25
- * `CrtrClient.forLocalSocket()` deliberately re-derives this same path from a
26
- * Node-builtin `join(crtrHome-equivalent, 'crtrd.sock')` rather than importing
27
- * here, to keep the exported `/api` contract dependency-light; the two must
28
- * agree. */
29
+ * `defaultSocketPath()` (in `api/node-transport.ts`) deliberately re-derives
30
+ * this same path from a Node-builtin `join(crtrHome-equivalent, 'crtrd.sock')`
31
+ * rather than importing here, to keep the exported `/api` contract
32
+ * dependency-light; the two must agree. */
29
33
  export declare function apiSocketPath(): string;
30
34
  /** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
31
35
  * absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
@@ -1 +1 @@
1
- var f=Object.defineProperty;var r=(t,i)=>f(t,"name",{value:i,configurable:!0});import{homedir as x}from"node:os";import{join as e}from"node:path";import{mkdirSync as s}from"node:fs";import{CRTR_DIR_NAME as h}from"../../types.js";function o(){const t=process.env.CRTR_HOME;return t!==void 0&&t!==""?t:e(x(),h,"canvas")}r(o,"crtrHome");function g(){return e(o(),"canvas.db")}r(g,"canvasDbPath");function w(){return e(o(),"on-disk-migration.lock")}r(w,"onDiskMigrationLockPath");function T(){return e(o(),"on-disk-migrations.json")}r(T,"onDiskMigrationMarkerPath");function N(t){return c(t),e(n(t),"view.sock")}r(N,"viewSocketPath");function a(){return e(o(),"nodes")}r(a,"nodesRoot");function m(){return e(o(),"reviews")}r(m,"reviewsRoot");function u(t){return c(t),e(m(),t)}r(u,"reviewDir");function C(t){return e(u(t),"branch-point.jsonl")}r(C,"reviewBranchPath");function E(t){return e(u(t),"result.json")}r(E,"reviewResultPath");function M(t){s(u(t),{recursive:!0,mode:448})}r(M,"ensureReviewDir");function d(){return e(o(),"crtrd.sock")}r(d,"apiSocketPath");function y(){return e(o(),"session-list-cache.json")}r(y,"sessionListCachePath");const D=128;function l(t){return typeof t=="string"&&t!==""&&t!=="."&&t!==".."&&!/[\\/\0]/u.test(t)&&Buffer.byteLength(t,"utf8")<=D}r(l,"isSafeNodeId");function c(t){if(!l(t))throw new TypeError("node_id must be a non-empty path segment of at most 128 UTF-8 bytes")}r(c,"assertSafeNodeId");function n(t){return c(t),e(a(),t)}r(n,"nodeDir");function p(t){return e(n(t),"context")}r(p,"contextDir");function S(t,i){return t.replaceAll("$CRTR_CONTEXT_DIR",p(i)).replaceAll("$CRTR_NODE_ID",i)}r(S,"interpolateNodePaths");function v(t){return e(n(t),"job")}r(v,"jobDir");function P(t){return e(n(t),"reports")}r(P,"reportsDir");function A(t){return e(n(t),"messages")}r(A,"messagesDir");function O(t){return e(n(t),"meta.json")}r(O,"nodeMetaPath");function B(t){return e(n(t),"inbox.jsonl")}r(B,"inboxPath");function H(t){return e(n(t),"situational-context.md")}r(H,"situationalContextPath");function L(t){return e(n(t),"passive.jsonl")}r(L,"passivePath");function X(t){return e(n(t),"transcript.jsonl")}r(X,"transcriptPath");function $(t){return e(n(t),"session.ptr")}r($,"sessionPtrPath");function F(t){return e(n(t),"injected-docs.json")}r(F,"injectedDocsPath");function U(t){for(const i of[p(t),v(t),P(t)])s(i,{recursive:!0})}r(U,"ensureNodeDirs");function Y(){s(a(),{recursive:!0})}r(Y,"ensureHome");export{d as apiSocketPath,g as canvasDbPath,p as contextDir,o as crtrHome,Y as ensureHome,U as ensureNodeDirs,M as ensureReviewDir,B as inboxPath,F as injectedDocsPath,S as interpolateNodePaths,l as isSafeNodeId,v as jobDir,A as messagesDir,n as nodeDir,O as nodeMetaPath,a as nodesRoot,w as onDiskMigrationLockPath,T as onDiskMigrationMarkerPath,L as passivePath,P as reportsDir,C as reviewBranchPath,u as reviewDir,E as reviewResultPath,m as reviewsRoot,y as sessionListCachePath,$ as sessionPtrPath,H as situationalContextPath,X as transcriptPath,N as viewSocketPath};
1
+ var f=Object.defineProperty;var r=(t,o)=>f(t,"name",{value:o,configurable:!0});import{homedir as x}from"node:os";import{join as e}from"node:path";import{mkdirSync as u}from"node:fs";import{CRTR_DIR_NAME as h}from"../../types.js";function i(){const t=process.env.CRTR_HOME;return t!==void 0&&t!==""?t:e(x(),h,"canvas")}r(i,"crtrHome");function b(){return e(i(),"canvas.db")}r(b,"canvasDbPath");function g(){return e(i(),"on-disk-migration.lock")}r(g,"onDiskMigrationLockPath");function T(){return e(i(),"on-disk-migrations.json")}r(T,"onDiskMigrationMarkerPath");function N(t){return c(t),e(n(t),"view.sock")}r(N,"viewSocketPath");function a(){return e(i(),"nodes")}r(a,"nodesRoot");function m(){return e(i(),"reviews")}r(m,"reviewsRoot");function s(t){return c(t),e(m(),t)}r(s,"reviewDir");function C(t){return e(s(t),"branch-point.jsonl")}r(C,"reviewBranchPath");function E(t,o){return c(o),e(s(t),"forks",`${o}.jsonl`)}r(E,"reviewForkBranchPath");function M(t){u(e(s(t),"forks"),{recursive:!0,mode:448})}r(M,"ensureReviewForkDir");function d(t){return e(s(t),"result.json")}r(d,"reviewResultPath");function y(t){u(s(t),{recursive:!0,mode:448})}r(y,"ensureReviewDir");function S(){return e(i(),"crtrd.sock")}r(S,"apiSocketPath");function A(){return e(i(),"session-list-cache.json")}r(A,"sessionListCachePath");const v=128;function D(t){return typeof t=="string"&&t!==""&&t!=="."&&t!==".."&&!/[\\/\0]/u.test(t)&&Buffer.byteLength(t,"utf8")<=v}r(D,"isSafeNodeId");function c(t){if(!D(t))throw new TypeError("node_id must be a non-empty path segment of at most 128 UTF-8 bytes")}r(c,"assertSafeNodeId");function n(t){return c(t),e(a(),t)}r(n,"nodeDir");function p(t){return e(n(t),"context")}r(p,"contextDir");function B(t,o){return t.replaceAll("$CRTR_CONTEXT_DIR",p(o)).replaceAll("$CRTR_NODE_ID",o)}r(B,"interpolateNodePaths");function l(t){return e(n(t),"job")}r(l,"jobDir");function P(t){return e(n(t),"reports")}r(P,"reportsDir");function O(t){return e(n(t),"messages")}r(O,"messagesDir");function F(t){return e(n(t),"meta.json")}r(F,"nodeMetaPath");function H(t){return e(n(t),"inbox.jsonl")}r(H,"inboxPath");function L(t){return e(n(t),"situational-context.md")}r(L,"situationalContextPath");function $(t){return e(n(t),"passive.jsonl")}r($,"passivePath");function X(t){return e(n(t),"transcript.jsonl")}r(X,"transcriptPath");function U(t){return e(n(t),"session.ptr")}r(U,"sessionPtrPath");function Y(t){return e(n(t),"injected-docs.json")}r(Y,"injectedDocsPath");function q(t){for(const o of[p(t),l(t),P(t)])u(o,{recursive:!0})}r(q,"ensureNodeDirs");function z(){u(a(),{recursive:!0})}r(z,"ensureHome");export{S as apiSocketPath,b as canvasDbPath,p as contextDir,i as crtrHome,z as ensureHome,q as ensureNodeDirs,y as ensureReviewDir,M as ensureReviewForkDir,H as inboxPath,Y as injectedDocsPath,B as interpolateNodePaths,D as isSafeNodeId,l as jobDir,O as messagesDir,n as nodeDir,F as nodeMetaPath,a as nodesRoot,g as onDiskMigrationLockPath,T as onDiskMigrationMarkerPath,$ as passivePath,P as reportsDir,C as reviewBranchPath,s as reviewDir,E as reviewForkBranchPath,d as reviewResultPath,m as reviewsRoot,A as sessionListCachePath,U as sessionPtrPath,L as situationalContextPath,X as transcriptPath,N as viewSocketPath};
@@ -29,6 +29,7 @@ export interface BootFaultAttempt {
29
29
  explanation(): string | null;
30
30
  }
31
31
  export declare function parseProviderError(value: unknown): FaultProviderError | undefined;
32
+ export declare function hydrateFault(row: Record<string, unknown>): StoredFault;
32
33
  export declare function openTurn(executionId: string, _opts: {
33
34
  reason: 'agent-start' | 'prompt-unsettled';
34
35
  }): boolean;
@@ -23,4 +23,4 @@ CREATE TABLE IF NOT EXISTS node_faults (
23
23
  );
24
24
  CREATE UNIQUE INDEX IF NOT EXISTS idx_node_faults_active ON node_faults(node_id) WHERE active = 1;
25
25
  CREATE UNIQUE INDEX IF NOT EXISTS idx_node_faults_episode ON node_faults(node_id) WHERE episode_state IS NOT NULL;
26
- `)}o(ye,"createFaultTable");function y(e,t){return typeof t=="string"&&e.includes(t)}o(y,"contains");function b(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(b,"isRecord");function B(e){if(!b(e)||typeof e.provider!="string"||e.provider===""||typeof e.model!="string"||e.model==="")return;const t=typeof e.status=="number"&&Number.isFinite(e.status)?e.status:void 0,r=typeof e.code=="string"||typeof e.code=="number"?e.code:void 0,n=typeof e.errno=="string"?e.errno:void 0,i=typeof e.body=="string"?e.body:void 0,d=Array.isArray(e.causes)?e.causes.flatMap(s=>{if(!b(s))return[];const c={...typeof s.message=="string"?{message:s.message}:{},...typeof s.status=="number"&&Number.isFinite(s.status)?{status:s.status}:{},...typeof s.code=="string"||typeof s.code=="number"?{code:s.code}:{},...typeof s.errno=="string"?{errno:s.errno}:{},...typeof s.body=="string"?{body:s.body}:{}};return Object.keys(c).length===0?[]:[c]}):void 0;return{provider:e.provider,model:e.model,...t===void 0?{}:{status:t},...r===void 0?{}:{code:r},...n===void 0?{}:{errno:n},...i===void 0?{}:{body:i},...d===void 0||d.length===0?{}:{causes:d}}}o(B,"parseProviderError");function K(e){try{const t=JSON.parse(e);if(!y(ce,t.link)||typeof t.op!="string"||t.op===""||!y(pe,t.kind)||typeof t.message!="string"||typeof t.since!="string")return null;const r=X(t.operation_id);if(!b(t.retry)||!y(_e,t.retry.disposition)||t.retry.by!==void 0&&!y(le,t.retry.by)||t.retry.attempt!==void 0&&typeof t.retry.attempt!="number"||t.retry.max!==void 0&&typeof t.retry.max!="number"||t.retry.nextAt!==void 0&&typeof t.retry.nextAt!="string"||t.anchorEntryId!==void 0&&(typeof t.anchorEntryId!="string"||t.anchorEntryId===""))return null;const n=t.providerError===void 0?void 0:B(t.providerError);return t.providerError!==void 0&&n===void 0?null:{link:t.link,op:t.op,kind:t.kind,retry:{disposition:t.retry.disposition,...t.retry.by===void 0?{}:{by:t.retry.by},...t.retry.attempt===void 0?{}:{attempt:t.retry.attempt},...t.retry.max===void 0?{}:{max:t.retry.max},...t.retry.nextAt===void 0?{}:{nextAt:t.retry.nextAt}},message:t.message,since:t.since,operation_id:r,...t.anchorEntryId===void 0?{}:{anchorEntryId:t.anchorEntryId},...n===void 0?{}:{providerError:n}}}catch{return null}}o(K,"parseLegacyFault");function Te(e){const t=de(e);try{return oe(t)}catch{return t}}o(Te,"canonicalPath");function ge(e){try{const t=JSON.parse(e);if(!y(Ee,t.state)||typeof t.sessionFile!="string"||t.sessionFile==="")return null;const r=JSON.stringify(t.fault),n=K(r);if(n===null||n.link!=="pi\u2192provider"||n.retry.disposition!=="auto"||n.retry.by!=="daemon")return null;const i=t.anchorEntryId;return i!==null&&typeof i!="string"||(n.anchorEntryId??null)!==i?null:{state:t.state,sessionFile:Te(t.sessionFile),fault:n}}catch{return null}}o(ge,"parseLegacyEpisode");function J(e){return e!==void 0?X(e):M.current()??M.fresh(r=>r)}o(J,"resolveOperationId");function Ne(e){return e.slice(0,ue)}o(Ne,"clampMessage");function A(e){return e.link==="pi\u2192provider"&&e.retry.disposition==="auto"&&e.retry.by==="daemon"?3:e.retry.disposition==="fatal"?2:1}o(A,"faultPriority");function Y(e){return{link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operationId:e.operation_id,anchorEntryId:e.anchorEntryId??null,retryDisposition:e.retry.disposition,retryBy:e.retry.by??null,retryAttempt:e.retry.attempt??null,retryMax:e.retry.max??null,retryNextAt:e.retry.nextAt??null,providerError:e.providerError===void 0?null:JSON.stringify(e.providerError)}}o(Y,"encodeFault");function G(e){const t=e.provider_error_json,r=typeof t=="string"?B(JSON.parse(t)):void 0;if(typeof t=="string"&&r===void 0)throw new Error(`invalid provider error on stored fault ${e.fault_id}`);const n={disposition:e.retry_disposition,...e.retry_by===null?{}:{by:e.retry_by},...e.retry_attempt===null?{}:{attempt:e.retry_attempt},...e.retry_max===null?{}:{max:e.retry_max},...e.retry_next_at===null?{}:{nextAt:e.retry_next_at}};return{faultId:e.fault_id,link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operation_id:e.operation_id,retry:n,...e.anchor_entry_id===null?{}:{anchorEntryId:e.anchor_entry_id},...r===void 0?{}:{providerError:r}}}o(G,"hydrateFault");function I(e,t){const r=e.prepare("SELECT * FROM node_faults WHERE node_id = ? AND active = 1").get(t);return r===void 0?null:G(r)}o(I,"activeFault");function V(e){if(!y(j,e.link))throw new Error(`fault link is not durable: ${e.link}`);return{link:e.link,op:e.op,kind:e.kind,retry:e.retry,message:Ne(e.message),since:e.since??a(),operation_id:J(e.operation_id),...typeof e.anchorEntryId=="string"&&e.anchorEntryId!==""?{anchorEntryId:e.anchorEntryId}:{},...e.providerError===void 0?{}:{providerError:e.providerError}}}o(V,"storedFault");function Q(e,t){w({level:t.retry.disposition==="fatal"?"error":"warn",event:"fault.recorded",operation_id:t.operation_id,node_id:e,error_class:P(t),fields:{link:t.link,op:t.op,kind:t.kind,retry:t.retry,message:t.message,...t.anchorEntryId===void 0?{}:{anchor_entry_id:t.anchorEntryId},...t.providerError===void 0?{}:{provider_error:t.providerError}}})}o(Q,"emitRecordedFault");function F(e,t,r,n,i){const d=k(),s=Y(r),c=a();return e.prepare("INSERT INTO node_faults (fault_id, node_id, link, op, kind, message, since, operation_id, anchor_entry_id, retry_disposition, retry_by, retry_attempt, retry_max, retry_next_at, provider_error_json, active, episode_state, episode_binding_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(d,t,s.link,s.op,s.kind,s.message,s.since,s.operationId,s.anchorEntryId,s.retryDisposition,s.retryBy,s.retryAttempt,s.retryMax,s.retryNextAt,s.providerError,n?1:0,i?.state??null,i?.bindingId??null,c,c),{...r,faultId:d}}o(F,"insertFault");function q(e,t,r,n){const i=Y(r);return e.prepare("UPDATE node_faults SET link = ?, op = ?, kind = ?, message = ?, since = ?, operation_id = ?, anchor_entry_id = ?, retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, provider_error_json = ?, episode_state = COALESCE(?, episode_state), episode_binding_id = COALESCE(?, episode_binding_id), updated_at = ? WHERE fault_id = ?").run(i.link,i.op,i.kind,i.message,i.since,i.operationId,i.anchorEntryId,i.retryDisposition,i.retryBy,i.retryAttempt,i.retryMax,i.retryNextAt,i.providerError,n?.state??null,n?.bindingId??null,a(),t),{...r,faultId:t}}o(q,"updateFault");function R(e,t){e.prepare("DELETE FROM node_faults WHERE fault_id = ? AND active = 0 AND episode_state IS NULL").run(t)}o(R,"deleteIfUnretained");function z(e,t,r,n){const i=I(e,t);if(i===null){const d=e.prepare("SELECT fault_id, link, kind, since, operation_id FROM node_faults WHERE node_id = ? AND active = 0 AND episode_state IS NOT NULL").get(t);return d!==void 0&&d.link===r.link&&d.kind===r.kind&&d.since===r.since&&d.operation_id===r.operation_id?(e.prepare("UPDATE node_faults SET active = 1, updated_at = ? WHERE fault_id = ?").run(a(),d.fault_id),q(e,d.fault_id,r)):F(e,t,r,!0,n)}if(i.link===r.link&&i.kind===r.kind){const d={...i.retry,...r.retry};return r.retry.disposition==="fatal"&&!("nextAt"in r.retry)&&delete d.nextAt,q(e,i.faultId,{...r,since:i.since,anchorEntryId:i.anchorEntryId??r.anchorEntryId,retry:d},n)}return A(i)>A(r)?null:(e.prepare("UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(a(),i.faultId),R(e,i.faultId),F(e,t,r,!0,n))}o(z,"replaceActiveFault");function g(e,t,r){return e.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? AND unbound_at IS NULL").get(t,r)?.binding_id??null}o(g,"liveBinding");function Ce(e,t){return p(r=>{const n=a();return r.prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(n,e).changes>0?!0:r.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ? AND state != 'exited'").run(n,n,e).changes>0})}o(Ce,"openTurn");function be(e){return p(t=>t.prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(a(),e).changes>0)}o(be,"recordTurnProgress");function Fe(e,t){return p(r=>r.prepare("UPDATE executions SET turn_closed_at = ?, turn_end_reason = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(a(),t.endReason,e).changes>0)}o(Fe,"closeTurn");function He(e,t){if(!Number.isInteger(t.consecutivePasses)||t.consecutivePasses<1||t.routes.length===0||!t.routes.every(r=>r!==""))throw new Error("invalid transport-poison verdict");return p(r=>r.prepare("UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ? AND state != 'exited'").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:t.consecutivePasses,routes:t.routes}}),e).changes>0)}o(He,"recordTransportPoisonVerdict");function We(e){const t=T().prepare("SELECT exit_diagnostic_json FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(e);if(t===void 0||t.exit_diagnostic_json===null)return null;try{const r=JSON.parse(t.exit_diagnostic_json),n=r.transport_poison;return r.schema!=="crtr.execution-exit-diagnostic/v1"||n===void 0||!Number.isInteger(n.consecutive_passes)||n.consecutive_passes<1||!Array.isArray(n.routes)||n.routes.length===0||!n.routes.every(i=>typeof i=="string"&&i!=="")?null:{consecutivePasses:n.consecutive_passes,routes:n.routes}}catch{return null}}o(We,"readTransportPoisonVerdict");function Pe(e){const t=T().prepare("SELECT turn_open_at, turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND state != 'exited'").get(e);return t===void 0?null:{busy:t.turn_open_at!==null&&t.turn_closed_at===null,progressAt:t.turn_progress_at}}o(Pe,"readLiveTurn");function we(e){const t=T(),r=t.prepare("SELECT turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL ORDER BY rowid DESC LIMIT 1").get(e);if(r===void 0)return null;const n=t.prepare("SELECT turn_end_reason FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(e);return{interrupted:r.turn_closed_at===null||n?.turn_end_reason==="teardown-aborted",progressAt:r.turn_progress_at}}o(we,"readInterruptedTurn");function Me(e,t){const r=V(t);return Q(e,r),p(n=>z(n,e,r))}o(Me,"recordFault");function Xe(e,t){return p(r=>{const n=I(r,e);if(n===null||t?.link!==void 0&&n.link!==t.link)return!1;const i=r.prepare("SELECT episode_state FROM node_faults WHERE fault_id = ?").get(n.faultId);r.prepare("UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(a(),n.faultId),i.episode_state==="pending"&&t?.preserveEpisode!==!0&&r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?").run(a(),n.faultId),R(r,n.faultId);const d=J();return w({level:"info",event:"fault.cleared",operation_id:d,node_id:e,error_class:P(n),fields:{cleared_fault_operation_id:n.operation_id,link:n.link,op:n.op,kind:n.kind,retry:n.retry,message:n.message,since:n.since,...n.anchorEntryId===void 0?{}:{anchor_entry_id:n.anchorEntryId}}}),!0})}o(Xe,"clearFault");function Z(e){return I(T(),e)}o(Z,"readFault");function je(e,t){const r=V(t.input);if(r.link!=="pi\u2192provider"||r.retry.disposition!=="auto"||r.retry.by!=="daemon")throw new Error("provider retry episode requires an automatic daemon provider fault");return Q(e,r),p(n=>{const i=g(n,e,t.sessionId);if(i===null)return null;const d=I(n,e);if(d!==null&&(d.link!==r.link||d.kind!==r.kind)&&A(d)>A(r))return null;const s=n.prepare("SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_state IS NOT NULL").all(e);n.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_state IS NOT NULL").run(a(),e);for(const N of s)R(n,N.fault_id);const c=z(n,e,r,{state:"pending",bindingId:i});return c===null?null:(n.prepare("UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run("pending",i,a(),c.faultId),c)})}o(je,"recordPendingEpisode");function $e(e,t){return p(r=>{const n=g(r,e,t.sessionId);return n===null?!1:r.prepare("UPDATE node_faults SET episode_state = 'admitted', retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, updated_at = ? WHERE node_id = ? AND fault_id = ? AND episode_state = 'pending' AND episode_binding_id = ?").run(t.retry.disposition,t.retry.by??null,t.retry.attempt??null,t.retry.max??null,t.retry.nextAt??null,a(),e,t.faultId,n).changes>0})}o($e,"admitEpisode");function Be(e,t){return p(r=>{const n=g(r,e,t.sessionId);return n===null?!1:r.prepare("UPDATE node_faults SET episode_state = 'pending', updated_at = ? WHERE node_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").run(a(),e,n).changes>0})}o(Be,"reopenEpisode");function Ke(e,t){return p(r=>{const n=g(r,e,t.sessionId);return n===null?!1:r.prepare("UPDATE node_faults SET episode_state = 'invalidated', updated_at = ? WHERE node_id = ? AND episode_state != 'invalidated' AND episode_state IS NOT NULL AND episode_binding_id = ?").run(a(),e,n).changes>0})}o(Ke,"invalidateEpisode");function Je(e,t){return p(r=>{const n=g(r,e,t.sessionId);if(n===null)return!1;const i=r.prepare("SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").all(e,n);if(i.length===0)return!1;r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").run(a(),e,n);for(const d of i)R(r,d.fault_id);return!0})}o(Je,"clearEpisode");function Ye(e){const t=T().prepare("SELECT f.*, s.session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.node_id = ? AND f.episode_state IS NOT NULL").get(e);return t===void 0?null:{state:t.episode_state,sessionId:t.session_id,fault:G(t)}}o(Ye,"readEpisode");function Ge(e){const t=Z(e)===null;return{explanation(){if(!t)return null;const r=Z(e);if(r===null||r.link!=="crtr\u2192pi"||r.retry.disposition!=="fatal")return null;const n=r.op.trim(),i=r.message.trim();return n===""||i===""?null:`${n}: ${i}`}}}o(Ge,"beginBootFaultAttempt");function ee(e,t){try{return se(e).mtime.toISOString()}catch{return t}}o(ee,"markerMtime");function H(e,t){if(!_(e))return null;try{return ie(e,"utf8")}catch(r){if(!t)return null;throw new Error(`cannot import recovery input ${e}: ${r.message}`)}}o(H,"legacyFile");function Le(e,t){return e.prepare("SELECT execution_id, started_at FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(t)??null}o(Le,"latestExecution");function me(e,t){const n=$(e,"nodes").has("launched_at")?e.prepare("SELECT COALESCE(launched_at, created) AS started_at FROM nodes WHERE node_id = ?").get(t):e.prepare("SELECT created AS started_at FROM nodes WHERE node_id = ?").get(t);if(n===void 0)throw new Error(`cannot import recovery witness for unknown node ${t}`);const i=k();return e.prepare("INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, NULL, NULL, 'exited', ?, ?, 'pre-consolidation')").run(i,t,n.started_at,n.started_at),{execution_id:i,started_at:n.started_at}}o(me,"syntheticExecution");function te(e,t,r,n,i){return F(e,t,r,n,i)}o(te,"insertMigratedFault");function xe(e,t,r,n){const i=e.prepare("SELECT binding_id, node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL").get(r);if(i!==void 0){if(i.node_id!==t)throw new Error(`cannot import provider retry ${n}: transcript is live-owned by ${i.node_id}`);return i.binding_id}const d=e.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND transcript_path = ? ORDER BY rowid DESC LIMIT 1").get(t,r);if(d!==void 0)return d.binding_id;const s=e.prepare("SELECT created FROM nodes WHERE node_id = ?").get(t);if(s===void 0)throw new Error(`cannot import provider retry ${n}: unknown node ${t}`);const c=k();return e.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, NULL, ?, ?, NULL, NULL, ?, ?, ?)").run(c,t,r,s.created,s.created,a()),c}o(xe,"migrationBinding");function Ve(e){if(fe(e),ye(e),!_(C()))return;const t=ne(C(),{withFileTypes:!0}).filter(n=>n.isDirectory()).sort((n,i)=>n.name.localeCompare(i.name)),r=e.prepare("SELECT 1 FROM nodes WHERE node_id = ?");for(const n of t){const i=n.name,d=ae(i),s=f(d,"busy"),c=f(d,"clean-abort"),N=f(d,"transport-poison.json"),h=f(d,"fault"),L=f(d,"provider-retry"),D=_(s),U=_(c);if(!D&&!U&&!_(N)&&!_(h)&&!_(L)||!_(f(C(),i,"meta.json"))&&r.get(i)===void 0)continue;const O=H(N,!1),m=H(h,!0),x=H(L,!0);if(!D&&!U&&O===null&&m===null&&x===null)continue;let l=Le(e,i);if(l===null&&(l=me(e,i)),D){const u=ee(s,l.started_at);e.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ?").run(u,u,l.execution_id)}else if(U){const u=ee(c,l.started_at);e.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = ?, turn_end_reason = 'teardown-aborted' WHERE execution_id = ?").run(u,u,u,l.execution_id)}if(O!==null)try{const u=JSON.parse(O);Number.isInteger(u.consecutivePasses)&&u.consecutivePasses>=1&&Array.isArray(u.routes)&&u.routes.length>0&&u.routes.every(W=>typeof W=="string"&&W!=="")&&e.prepare("UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ?").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:u.consecutivePasses,routes:u.routes}}),l.execution_id)}catch{}const S=m===null?null:K(m);if(m!==null&&S===null)throw new Error(`cannot import recovery fault ${h}`);const E=x===null?null:ge(x);if(x!==null&&E===null)throw new Error(`cannot import provider retry ${L}`);let v=S===null||S.link==="broker\u2194crtrd"?null:te(e,i,S,!0);if(E!==null){const u=xe(e,i,E.sessionFile,L);v!==null&&v.since===E.fault.since?e.prepare("UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run(E.state,u,a(),v.faultId):te(e,i,E.fault,!1,{state:E.state,bindingId:u})}}}o(Ve,"migrateRecoveryWitnesses");export{ke as FAULT_QUIET_MS,$e as admitEpisode,Ge as beginBootFaultAttempt,Je as clearEpisode,Xe as clearFault,Fe as closeTurn,Ke as invalidateEpisode,Ve as migrateRecoveryWitnesses,Ce as openTurn,B as parseProviderError,Ye as readEpisode,Z as readFault,we as readInterruptedTurn,Pe as readLiveTurn,We as readTransportPoisonVerdict,Me as recordFault,je as recordPendingEpisode,He as recordTransportPoisonVerdict,be as recordTurnProgress,Be as reopenEpisode};
26
+ `)}o(ye,"createFaultTable");function y(e,t){return typeof t=="string"&&e.includes(t)}o(y,"contains");function b(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(b,"isRecord");function B(e){if(!b(e)||typeof e.provider!="string"||e.provider===""||typeof e.model!="string"||e.model==="")return;const t=typeof e.status=="number"&&Number.isFinite(e.status)?e.status:void 0,r=typeof e.code=="string"||typeof e.code=="number"?e.code:void 0,n=typeof e.errno=="string"?e.errno:void 0,i=typeof e.body=="string"?e.body:void 0,d=Array.isArray(e.causes)?e.causes.flatMap(s=>{if(!b(s))return[];const c={...typeof s.message=="string"?{message:s.message}:{},...typeof s.status=="number"&&Number.isFinite(s.status)?{status:s.status}:{},...typeof s.code=="string"||typeof s.code=="number"?{code:s.code}:{},...typeof s.errno=="string"?{errno:s.errno}:{},...typeof s.body=="string"?{body:s.body}:{}};return Object.keys(c).length===0?[]:[c]}):void 0;return{provider:e.provider,model:e.model,...t===void 0?{}:{status:t},...r===void 0?{}:{code:r},...n===void 0?{}:{errno:n},...i===void 0?{}:{body:i},...d===void 0||d.length===0?{}:{causes:d}}}o(B,"parseProviderError");function K(e){try{const t=JSON.parse(e);if(!y(ce,t.link)||typeof t.op!="string"||t.op===""||!y(pe,t.kind)||typeof t.message!="string"||typeof t.since!="string")return null;const r=X(t.operation_id);if(!b(t.retry)||!y(_e,t.retry.disposition)||t.retry.by!==void 0&&!y(le,t.retry.by)||t.retry.attempt!==void 0&&typeof t.retry.attempt!="number"||t.retry.max!==void 0&&typeof t.retry.max!="number"||t.retry.nextAt!==void 0&&typeof t.retry.nextAt!="string"||t.anchorEntryId!==void 0&&(typeof t.anchorEntryId!="string"||t.anchorEntryId===""))return null;const n=t.providerError===void 0?void 0:B(t.providerError);return t.providerError!==void 0&&n===void 0?null:{link:t.link,op:t.op,kind:t.kind,retry:{disposition:t.retry.disposition,...t.retry.by===void 0?{}:{by:t.retry.by},...t.retry.attempt===void 0?{}:{attempt:t.retry.attempt},...t.retry.max===void 0?{}:{max:t.retry.max},...t.retry.nextAt===void 0?{}:{nextAt:t.retry.nextAt}},message:t.message,since:t.since,operation_id:r,...t.anchorEntryId===void 0?{}:{anchorEntryId:t.anchorEntryId},...n===void 0?{}:{providerError:n}}}catch{return null}}o(K,"parseLegacyFault");function Te(e){const t=de(e);try{return oe(t)}catch{return t}}o(Te,"canonicalPath");function ge(e){try{const t=JSON.parse(e);if(!y(Ee,t.state)||typeof t.sessionFile!="string"||t.sessionFile==="")return null;const r=JSON.stringify(t.fault),n=K(r);if(n===null||n.link!=="pi\u2192provider"||n.retry.disposition!=="auto"||n.retry.by!=="daemon")return null;const i=t.anchorEntryId;return i!==null&&typeof i!="string"||(n.anchorEntryId??null)!==i?null:{state:t.state,sessionFile:Te(t.sessionFile),fault:n}}catch{return null}}o(ge,"parseLegacyEpisode");function J(e){return e!==void 0?X(e):M.current()??M.fresh(r=>r)}o(J,"resolveOperationId");function Ne(e){return e.slice(0,ue)}o(Ne,"clampMessage");function A(e){return e.link==="pi\u2192provider"&&e.retry.disposition==="auto"&&e.retry.by==="daemon"?3:e.retry.disposition==="fatal"?2:1}o(A,"faultPriority");function Y(e){return{link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operationId:e.operation_id,anchorEntryId:e.anchorEntryId??null,retryDisposition:e.retry.disposition,retryBy:e.retry.by??null,retryAttempt:e.retry.attempt??null,retryMax:e.retry.max??null,retryNextAt:e.retry.nextAt??null,providerError:e.providerError===void 0?null:JSON.stringify(e.providerError)}}o(Y,"encodeFault");function G(e){const t=e.provider_error_json,r=typeof t=="string"?B(JSON.parse(t)):void 0;if(typeof t=="string"&&r===void 0)throw new Error(`invalid provider error on stored fault ${e.fault_id}`);const n={disposition:e.retry_disposition,...e.retry_by===null?{}:{by:e.retry_by},...e.retry_attempt===null?{}:{attempt:e.retry_attempt},...e.retry_max===null?{}:{max:e.retry_max},...e.retry_next_at===null?{}:{nextAt:e.retry_next_at}};return{faultId:e.fault_id,link:e.link,op:e.op,kind:e.kind,message:e.message,since:e.since,operation_id:e.operation_id,retry:n,...e.anchor_entry_id===null?{}:{anchorEntryId:e.anchor_entry_id},...r===void 0?{}:{providerError:r}}}o(G,"hydrateFault");function I(e,t){const r=e.prepare("SELECT * FROM node_faults WHERE node_id = ? AND active = 1").get(t);return r===void 0?null:G(r)}o(I,"activeFault");function V(e){if(!y(j,e.link))throw new Error(`fault link is not durable: ${e.link}`);return{link:e.link,op:e.op,kind:e.kind,retry:e.retry,message:Ne(e.message),since:e.since??a(),operation_id:J(e.operation_id),...typeof e.anchorEntryId=="string"&&e.anchorEntryId!==""?{anchorEntryId:e.anchorEntryId}:{},...e.providerError===void 0?{}:{providerError:e.providerError}}}o(V,"storedFault");function Q(e,t){w({level:t.retry.disposition==="fatal"?"error":"warn",event:"fault.recorded",operation_id:t.operation_id,node_id:e,error_class:P(t),fields:{link:t.link,op:t.op,kind:t.kind,retry:t.retry,message:t.message,...t.anchorEntryId===void 0?{}:{anchor_entry_id:t.anchorEntryId},...t.providerError===void 0?{}:{provider_error:t.providerError}}})}o(Q,"emitRecordedFault");function F(e,t,r,n,i){const d=k(),s=Y(r),c=a();return e.prepare("INSERT INTO node_faults (fault_id, node_id, link, op, kind, message, since, operation_id, anchor_entry_id, retry_disposition, retry_by, retry_attempt, retry_max, retry_next_at, provider_error_json, active, episode_state, episode_binding_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(d,t,s.link,s.op,s.kind,s.message,s.since,s.operationId,s.anchorEntryId,s.retryDisposition,s.retryBy,s.retryAttempt,s.retryMax,s.retryNextAt,s.providerError,n?1:0,i?.state??null,i?.bindingId??null,c,c),{...r,faultId:d}}o(F,"insertFault");function q(e,t,r,n){const i=Y(r);return e.prepare("UPDATE node_faults SET link = ?, op = ?, kind = ?, message = ?, since = ?, operation_id = ?, anchor_entry_id = ?, retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, provider_error_json = ?, episode_state = COALESCE(?, episode_state), episode_binding_id = COALESCE(?, episode_binding_id), updated_at = ? WHERE fault_id = ?").run(i.link,i.op,i.kind,i.message,i.since,i.operationId,i.anchorEntryId,i.retryDisposition,i.retryBy,i.retryAttempt,i.retryMax,i.retryNextAt,i.providerError,n?.state??null,n?.bindingId??null,a(),t),{...r,faultId:t}}o(q,"updateFault");function R(e,t){e.prepare("DELETE FROM node_faults WHERE fault_id = ? AND active = 0 AND episode_state IS NULL").run(t)}o(R,"deleteIfUnretained");function z(e,t,r,n){const i=I(e,t);if(i===null){const d=e.prepare("SELECT fault_id, link, kind, since, operation_id FROM node_faults WHERE node_id = ? AND active = 0 AND episode_state IS NOT NULL").get(t);return d!==void 0&&d.link===r.link&&d.kind===r.kind&&d.since===r.since&&d.operation_id===r.operation_id?(e.prepare("UPDATE node_faults SET active = 1, updated_at = ? WHERE fault_id = ?").run(a(),d.fault_id),q(e,d.fault_id,r)):F(e,t,r,!0,n)}if(i.link===r.link&&i.kind===r.kind){const d={...i.retry,...r.retry};return r.retry.disposition==="fatal"&&!("nextAt"in r.retry)&&delete d.nextAt,q(e,i.faultId,{...r,since:i.since,anchorEntryId:i.anchorEntryId??r.anchorEntryId,retry:d},n)}return A(i)>A(r)?null:(e.prepare("UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(a(),i.faultId),R(e,i.faultId),F(e,t,r,!0,n))}o(z,"replaceActiveFault");function g(e,t,r){return e.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND session_id = ? AND unbound_at IS NULL").get(t,r)?.binding_id??null}o(g,"liveBinding");function Ce(e,t){return p(r=>{const n=a();return r.prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(n,e).changes>0?!0:r.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ? AND state != 'exited'").run(n,n,e).changes>0})}o(Ce,"openTurn");function be(e){return p(t=>t.prepare("UPDATE executions SET turn_progress_at = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(a(),e).changes>0)}o(be,"recordTurnProgress");function Fe(e,t){return p(r=>r.prepare("UPDATE executions SET turn_closed_at = ?, turn_end_reason = ? WHERE execution_id = ? AND state != 'exited' AND turn_open_at IS NOT NULL AND turn_closed_at IS NULL").run(a(),t.endReason,e).changes>0)}o(Fe,"closeTurn");function He(e,t){if(!Number.isInteger(t.consecutivePasses)||t.consecutivePasses<1||t.routes.length===0||!t.routes.every(r=>r!==""))throw new Error("invalid transport-poison verdict");return p(r=>r.prepare("UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ? AND state != 'exited'").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:t.consecutivePasses,routes:t.routes}}),e).changes>0)}o(He,"recordTransportPoisonVerdict");function We(e){const t=T().prepare("SELECT exit_diagnostic_json FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(e);if(t===void 0||t.exit_diagnostic_json===null)return null;try{const r=JSON.parse(t.exit_diagnostic_json),n=r.transport_poison;return r.schema!=="crtr.execution-exit-diagnostic/v1"||n===void 0||!Number.isInteger(n.consecutive_passes)||n.consecutive_passes<1||!Array.isArray(n.routes)||n.routes.length===0||!n.routes.every(i=>typeof i=="string"&&i!=="")?null:{consecutivePasses:n.consecutive_passes,routes:n.routes}}catch{return null}}o(We,"readTransportPoisonVerdict");function Pe(e){const t=T().prepare("SELECT turn_open_at, turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND state != 'exited'").get(e);return t===void 0?null:{busy:t.turn_open_at!==null&&t.turn_closed_at===null,progressAt:t.turn_progress_at}}o(Pe,"readLiveTurn");function we(e){const t=T(),r=t.prepare("SELECT turn_closed_at, turn_progress_at FROM executions WHERE node_id = ? AND turn_open_at IS NOT NULL ORDER BY rowid DESC LIMIT 1").get(e);if(r===void 0)return null;const n=t.prepare("SELECT turn_end_reason FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(e);return{interrupted:r.turn_closed_at===null||n?.turn_end_reason==="teardown-aborted",progressAt:r.turn_progress_at}}o(we,"readInterruptedTurn");function Me(e,t){const r=V(t);return Q(e,r),p(n=>z(n,e,r))}o(Me,"recordFault");function Xe(e,t){return p(r=>{const n=I(r,e);if(n===null||t?.link!==void 0&&n.link!==t.link)return!1;const i=r.prepare("SELECT episode_state FROM node_faults WHERE fault_id = ?").get(n.faultId);r.prepare("UPDATE node_faults SET active = 0, updated_at = ? WHERE fault_id = ?").run(a(),n.faultId),i.episode_state==="pending"&&t?.preserveEpisode!==!0&&r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE fault_id = ?").run(a(),n.faultId),R(r,n.faultId);const d=J();return w({level:"info",event:"fault.cleared",operation_id:d,node_id:e,error_class:P(n),fields:{cleared_fault_operation_id:n.operation_id,link:n.link,op:n.op,kind:n.kind,retry:n.retry,message:n.message,since:n.since,...n.anchorEntryId===void 0?{}:{anchor_entry_id:n.anchorEntryId}}}),!0})}o(Xe,"clearFault");function Z(e){return I(T(),e)}o(Z,"readFault");function je(e,t){const r=V(t.input);if(r.link!=="pi\u2192provider"||r.retry.disposition!=="auto"||r.retry.by!=="daemon")throw new Error("provider retry episode requires an automatic daemon provider fault");return Q(e,r),p(n=>{const i=g(n,e,t.sessionId);if(i===null)return null;const d=I(n,e);if(d!==null&&(d.link!==r.link||d.kind!==r.kind)&&A(d)>A(r))return null;const s=n.prepare("SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_state IS NOT NULL").all(e);n.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_state IS NOT NULL").run(a(),e);for(const N of s)R(n,N.fault_id);const c=z(n,e,r,{state:"pending",bindingId:i});return c===null?null:(n.prepare("UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run("pending",i,a(),c.faultId),c)})}o(je,"recordPendingEpisode");function $e(e,t){return p(r=>{const n=g(r,e,t.sessionId);return n===null?!1:r.prepare("UPDATE node_faults SET episode_state = 'admitted', retry_disposition = ?, retry_by = ?, retry_attempt = ?, retry_max = ?, retry_next_at = ?, updated_at = ? WHERE node_id = ? AND fault_id = ? AND episode_state = 'pending' AND episode_binding_id = ?").run(t.retry.disposition,t.retry.by??null,t.retry.attempt??null,t.retry.max??null,t.retry.nextAt??null,a(),e,t.faultId,n).changes>0})}o($e,"admitEpisode");function Be(e,t){return p(r=>{const n=g(r,e,t.sessionId);return n===null?!1:r.prepare("UPDATE node_faults SET episode_state = 'pending', updated_at = ? WHERE node_id = ? AND episode_state = 'admitted' AND episode_binding_id = ?").run(a(),e,n).changes>0})}o(Be,"reopenEpisode");function Ke(e,t){return p(r=>{const n=g(r,e,t.sessionId);return n===null?!1:r.prepare("UPDATE node_faults SET episode_state = 'invalidated', updated_at = ? WHERE node_id = ? AND episode_state != 'invalidated' AND episode_state IS NOT NULL AND episode_binding_id = ?").run(a(),e,n).changes>0})}o(Ke,"invalidateEpisode");function Je(e,t){return p(r=>{const n=g(r,e,t.sessionId);if(n===null)return!1;const i=r.prepare("SELECT fault_id FROM node_faults WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").all(e,n);if(i.length===0)return!1;r.prepare("UPDATE node_faults SET episode_state = NULL, episode_binding_id = NULL, updated_at = ? WHERE node_id = ? AND episode_binding_id = ? AND episode_state IS NOT NULL").run(a(),e,n);for(const d of i)R(r,d.fault_id);return!0})}o(Je,"clearEpisode");function Ye(e){const t=T().prepare("SELECT f.*, s.session_id FROM node_faults f JOIN sessions s ON s.binding_id = f.episode_binding_id WHERE f.node_id = ? AND f.episode_state IS NOT NULL").get(e);return t===void 0?null:{state:t.episode_state,sessionId:t.session_id,fault:G(t)}}o(Ye,"readEpisode");function Ge(e){const t=Z(e)===null;return{explanation(){if(!t)return null;const r=Z(e);if(r===null||r.link!=="crtr\u2192pi"||r.retry.disposition!=="fatal")return null;const n=r.op.trim(),i=r.message.trim();return n===""||i===""?null:`${n}: ${i}`}}}o(Ge,"beginBootFaultAttempt");function ee(e,t){try{return se(e).mtime.toISOString()}catch{return t}}o(ee,"markerMtime");function H(e,t){if(!_(e))return null;try{return ie(e,"utf8")}catch(r){if(!t)return null;throw new Error(`cannot import recovery input ${e}: ${r.message}`)}}o(H,"legacyFile");function Le(e,t){return e.prepare("SELECT execution_id, started_at FROM executions WHERE node_id = ? ORDER BY rowid DESC LIMIT 1").get(t)??null}o(Le,"latestExecution");function me(e,t){const n=$(e,"nodes").has("launched_at")?e.prepare("SELECT COALESCE(launched_at, created) AS started_at FROM nodes WHERE node_id = ?").get(t):e.prepare("SELECT created AS started_at FROM nodes WHERE node_id = ?").get(t);if(n===void 0)throw new Error(`cannot import recovery witness for unknown node ${t}`);const i=k();return e.prepare("INSERT INTO executions (execution_id, node_id, daemon_epoch, pid, process_identity, state, started_at, ended_at, exit_reason) VALUES (?, ?, NULL, NULL, NULL, 'exited', ?, ?, 'pre-consolidation')").run(i,t,n.started_at,n.started_at),{execution_id:i,started_at:n.started_at}}o(me,"syntheticExecution");function te(e,t,r,n,i){return F(e,t,r,n,i)}o(te,"insertMigratedFault");function xe(e,t,r,n){const i=e.prepare("SELECT binding_id, node_id FROM sessions WHERE transcript_path = ? AND unbound_at IS NULL").get(r);if(i!==void 0){if(i.node_id!==t)throw new Error(`cannot import provider retry ${n}: transcript is live-owned by ${i.node_id}`);return i.binding_id}const d=e.prepare("SELECT binding_id FROM sessions WHERE node_id = ? AND transcript_path = ? ORDER BY rowid DESC LIMIT 1").get(t,r);if(d!==void 0)return d.binding_id;const s=e.prepare("SELECT created FROM nodes WHERE node_id = ?").get(t);if(s===void 0)throw new Error(`cannot import provider retry ${n}: unknown node ${t}`);const c=k();return e.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES (?, NULL, ?, ?, NULL, NULL, ?, ?, ?)").run(c,t,r,s.created,s.created,a()),c}o(xe,"migrationBinding");function Ve(e){if(fe(e),ye(e),!_(C()))return;const t=ne(C(),{withFileTypes:!0}).filter(n=>n.isDirectory()).sort((n,i)=>n.name.localeCompare(i.name)),r=e.prepare("SELECT 1 FROM nodes WHERE node_id = ?");for(const n of t){const i=n.name,d=ae(i),s=f(d,"busy"),c=f(d,"clean-abort"),N=f(d,"transport-poison.json"),h=f(d,"fault"),L=f(d,"provider-retry"),D=_(s),U=_(c);if(!D&&!U&&!_(N)&&!_(h)&&!_(L)||!_(f(C(),i,"meta.json"))&&r.get(i)===void 0)continue;const O=H(N,!1),m=H(h,!0),x=H(L,!0);if(!D&&!U&&O===null&&m===null&&x===null)continue;let l=Le(e,i);if(l===null&&(l=me(e,i)),D){const u=ee(s,l.started_at);e.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = NULL, turn_end_reason = NULL WHERE execution_id = ?").run(u,u,l.execution_id)}else if(U){const u=ee(c,l.started_at);e.prepare("UPDATE executions SET turn_open_at = ?, turn_progress_at = ?, turn_closed_at = ?, turn_end_reason = 'teardown-aborted' WHERE execution_id = ?").run(u,u,u,l.execution_id)}if(O!==null)try{const u=JSON.parse(O);Number.isInteger(u.consecutivePasses)&&u.consecutivePasses>=1&&Array.isArray(u.routes)&&u.routes.length>0&&u.routes.every(W=>typeof W=="string"&&W!=="")&&e.prepare("UPDATE executions SET exit_diagnostic_json = ? WHERE execution_id = ?").run(JSON.stringify({schema:"crtr.execution-exit-diagnostic/v1",transport_poison:{consecutive_passes:u.consecutivePasses,routes:u.routes}}),l.execution_id)}catch{}const S=m===null?null:K(m);if(m!==null&&S===null)throw new Error(`cannot import recovery fault ${h}`);const E=x===null?null:ge(x);if(x!==null&&E===null)throw new Error(`cannot import provider retry ${L}`);let v=S===null||S.link==="broker\u2194crtrd"?null:te(e,i,S,!0);if(E!==null){const u=xe(e,i,E.sessionFile,L);v!==null&&v.since===E.fault.since?e.prepare("UPDATE node_faults SET episode_state = ?, episode_binding_id = ?, updated_at = ? WHERE fault_id = ?").run(E.state,u,a(),v.faultId):te(e,i,E.fault,!1,{state:E.state,bindingId:u})}}}o(Ve,"migrateRecoveryWitnesses");export{ke as FAULT_QUIET_MS,$e as admitEpisode,Ge as beginBootFaultAttempt,Je as clearEpisode,Xe as clearFault,Fe as closeTurn,G as hydrateFault,Ke as invalidateEpisode,Ve as migrateRecoveryWitnesses,Ce as openTurn,B as parseProviderError,Ye as readEpisode,Z as readFault,we as readInterruptedTurn,Pe as readLiveTurn,We as readTransportPoisonVerdict,Me as recordFault,je as recordPendingEpisode,He as recordTransportPoisonVerdict,be as recordTurnProgress,Be as reopenEpisode};
@@ -1,5 +1,6 @@
1
1
  import { DatabaseSync } from 'node:sqlite';
2
2
  import type { ManagedWorktree, ManagedWorktreeObservation } from './types.js';
3
+ export declare function worktreeFromRow(row: Record<string, unknown> | undefined): ManagedWorktree | null;
3
4
  export declare function readWorktree(nodeId: string): ManagedWorktree | null;
4
5
  export declare function openWorktree(nodeId: string, worktree: ManagedWorktree, db?: DatabaseSync): void;
5
6
  export declare function closeWorktree(nodeId: string, input: {
@@ -1 +1 @@
1
- var l=Object.defineProperty;var n=(t,e)=>l(t,"name",{value:e,configurable:!0});import{openDb as s,withCanvasWrite as _}from"./db.js";function r(t){const e=JSON.stringify(t);if(e===void 0)throw new TypeError("worktree value must be JSON-serializable");return e}n(r,"json");function o(t){if(typeof t=="string")return JSON.parse(t)}n(o,"parse");function i(t){const e=s().prepare("SELECT * FROM worktrees WHERE node_id = ?").get(t);if(e===void 0)return null;const a=o(e.sweep_observed_json),p=e.attempts,d=a===void 0?void 0:{reason:e.sweep_reason,...e.sweep_detail===null?{}:{detail:e.sweep_detail},observed:a,attempts:p,last_attempt:e.sweep_last_at,next_attempt:e.next_attempt_at,...e.quarantined===1?{quarantined:!0}:{}};return{state:e.state,...e.cleanup_state===null?{}:{cleanup:e.cleanup_state},path:e.path,branch:e.branch,repo_root:e.repo_root,base_ref:e.base_ref,base_sha:e.base_sha,created:e.created_at,...e.closed_at===null?{}:{closed:e.closed_at},...o(e.abandoned_json)===void 0?{}:{abandoned:o(e.abandoned_json)},...o(e.land_intent_json)===void 0?{}:{land_intent:o(e.land_intent_json)},...d===void 0?{}:{sweep:d},...o(e.disposal_json)===void 0?{}:{disposal:o(e.disposal_json)}}}n(i,"readWorktree");function L(t,e,a=s()){a.prepare("INSERT INTO worktrees (node_id, 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) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL)").run(t,e.state,e.cleanup??null,e.path,e.branch,e.repo_root,e.base_ref,e.base_sha,e.created,e.closed??null)}n(L,"openWorktree");function E(t,e){s().prepare("UPDATE worktrees SET state = 'closed', cleanup_state = ?, closed_at = ?, land_intent_json = NULL, sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(e.cleanup,e.closedAt,t)}n(E,"closeWorktree");function N(t,e){_(a=>{a.prepare("UPDATE nodes SET cwd = ? WHERE node_id = ?").run(e.cwd,t),e.unbindSession&&a.prepare("UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL").run(e.closedAt,t),a.prepare("UPDATE worktrees SET state = 'closed', cleanup_state = ?, closed_at = ?, land_intent_json = NULL, sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(e.cleanup,e.closedAt,t)})}n(N,"closeWorktreeAndRepoint");function w(t,e){s().prepare("UPDATE worktrees SET state = 'abandoned', abandoned_json = ? WHERE node_id = ? AND abandoned_json IS NULL").run(r(e),t)}n(w,"abandonWorktree");function U(t,e){_(a=>{a.prepare("UPDATE nodes SET cwd = ? WHERE node_id = ?").run(e.cwd,t),a.prepare("UPDATE worktrees SET state = 'abandoned', abandoned_json = ? WHERE node_id = ? AND abandoned_json IS NULL").run(r(e.abandoned),t)})}n(U,"abandonWorktreeAndRepoint");function f(t,e){s().prepare("UPDATE worktrees SET land_intent_json = ? WHERE node_id = ? AND land_intent_json IS NULL").run(r(e),t)}n(f,"recordLandIntent");function b(t,e){s().prepare("UPDATE worktrees SET disposal_json = ? WHERE node_id = ? AND disposal_json IS NULL").run(r(e),t)}n(b,"recordDisposalGrant");function A(t,e){s().prepare("UPDATE worktrees SET sweep_reason = ?, sweep_detail = ?, sweep_observed_json = ?, attempts = ?, sweep_last_at = ?, next_attempt_at = ?, quarantined = ? WHERE node_id = ?").run(e.reason,e.detail??null,r(e.observed),e.attempts,e.lastAttemptAt,e.nextAttemptAt,e.quarantined?1:0,t)}n(A,"recordSweepAttempt");function R(t){s().prepare("UPDATE worktrees SET state = 'closed', cleanup_state = 'complete', sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(t)}n(R,"completeCleanup");function S(t){return s().prepare("SELECT 1 FROM worktrees WHERE node_id = ? AND state != 'abandoned' AND (state = 'open' OR cleanup_state IS NULL OR cleanup_state != 'complete')").get(t)!==void 0}n(S,"hasUnreconciledWorktree");function m(t){return s().prepare("SELECT w.node_id FROM worktrees w JOIN nodes n ON n.node_id = w.node_id WHERE n.status IN ('done', 'dead', 'canceled') AND n.frozen_at IS NULL AND w.state IN ('open', 'closed') AND (w.state = 'open' OR w.cleanup_state IS NULL OR w.cleanup_state != 'complete') AND (w.state = 'closed' OR (n.status = 'done' AND n.final_report IS NOT NULL)) AND (w.next_attempt_at IS NULL OR w.next_attempt_at <= ?) ORDER BY w.next_attempt_at, w.node_id").all(t).map(({node_id:e})=>e)}n(m,"sweepCandidates");function T(){return s().prepare("SELECT node_id FROM worktrees WHERE quarantined = 1 ORDER BY sweep_last_at").all().flatMap(({node_id:t})=>{const e=i(t);return e===null?[]:[{nodeId:t,worktree:e}]})}n(T,"quarantinedWorktrees");export{w as abandonWorktree,U as abandonWorktreeAndRepoint,E as closeWorktree,N as closeWorktreeAndRepoint,R as completeCleanup,S as hasUnreconciledWorktree,L as openWorktree,T as quarantinedWorktrees,i as readWorktree,b as recordDisposalGrant,f as recordLandIntent,A as recordSweepAttempt,m as sweepCandidates};
1
+ var p=Object.defineProperty;var n=(e,t)=>p(e,"name",{value:t,configurable:!0});import{openDb as a,withCanvasWrite as _}from"./db.js";function d(e){const t=JSON.stringify(e);if(t===void 0)throw new TypeError("worktree value must be JSON-serializable");return t}n(d,"json");function o(e){if(typeof e=="string")return JSON.parse(e)}n(o,"parse");function l(e){if(e===void 0)return null;const t=o(e.sweep_observed_json),s=e.attempts,r=t===void 0?void 0:{reason:e.sweep_reason,...e.sweep_detail===null?{}:{detail:e.sweep_detail},observed:t,attempts:s,last_attempt:e.sweep_last_at,next_attempt:e.next_attempt_at,...e.quarantined===1?{quarantined:!0}:{}};return{state:e.state,...e.cleanup_state===null?{}:{cleanup:e.cleanup_state},path:e.path,branch:e.branch,repo_root:e.repo_root,base_ref:e.base_ref,base_sha:e.base_sha,created:e.created_at,...e.closed_at===null?{}:{closed:e.closed_at},...o(e.abandoned_json)===void 0?{}:{abandoned:o(e.abandoned_json)},...o(e.land_intent_json)===void 0?{}:{land_intent:o(e.land_intent_json)},...r===void 0?{}:{sweep:r},...o(e.disposal_json)===void 0?{}:{disposal:o(e.disposal_json)}}}n(l,"worktreeFromRow");function i(e){return l(a().prepare("SELECT * FROM worktrees WHERE node_id = ?").get(e))}n(i,"readWorktree");function L(e,t,s=a()){s.prepare("INSERT INTO worktrees (node_id, 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) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL)").run(e,t.state,t.cleanup??null,t.path,t.branch,t.repo_root,t.base_ref,t.base_sha,t.created,t.closed??null)}n(L,"openWorktree");function E(e,t){a().prepare("UPDATE worktrees SET state = 'closed', cleanup_state = ?, closed_at = ?, land_intent_json = NULL, sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(t.cleanup,t.closedAt,e)}n(E,"closeWorktree");function N(e,t){_(s=>{s.prepare("UPDATE nodes SET cwd = ? WHERE node_id = ?").run(t.cwd,e),t.unbindSession&&s.prepare("UPDATE sessions SET unbound_at = ? WHERE node_id = ? AND unbound_at IS NULL").run(t.closedAt,e),s.prepare("UPDATE worktrees SET state = 'closed', cleanup_state = ?, closed_at = ?, land_intent_json = NULL, sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(t.cleanup,t.closedAt,e)})}n(N,"closeWorktreeAndRepoint");function U(e,t){a().prepare("UPDATE worktrees SET state = 'abandoned', abandoned_json = ? WHERE node_id = ? AND abandoned_json IS NULL").run(d(t),e)}n(U,"abandonWorktree");function f(e,t){_(s=>{s.prepare("UPDATE nodes SET cwd = ? WHERE node_id = ?").run(t.cwd,e),s.prepare("UPDATE worktrees SET state = 'abandoned', abandoned_json = ? WHERE node_id = ? AND abandoned_json IS NULL").run(d(t.abandoned),e)})}n(f,"abandonWorktreeAndRepoint");function b(e,t){a().prepare("UPDATE worktrees SET land_intent_json = ? WHERE node_id = ? AND land_intent_json IS NULL").run(d(t),e)}n(b,"recordLandIntent");function A(e,t){a().prepare("UPDATE worktrees SET disposal_json = ? WHERE node_id = ? AND disposal_json IS NULL").run(d(t),e)}n(A,"recordDisposalGrant");function w(e,t){a().prepare("UPDATE worktrees SET sweep_reason = ?, sweep_detail = ?, sweep_observed_json = ?, attempts = ?, sweep_last_at = ?, next_attempt_at = ?, quarantined = ? WHERE node_id = ?").run(t.reason,t.detail??null,d(t.observed),t.attempts,t.lastAttemptAt,t.nextAttemptAt,t.quarantined?1:0,e)}n(w,"recordSweepAttempt");function R(e){a().prepare("UPDATE worktrees SET state = 'closed', cleanup_state = 'complete', sweep_reason = NULL, sweep_detail = NULL, sweep_last_at = NULL, sweep_observed_json = NULL, next_attempt_at = NULL, attempts = 0, quarantined = 0 WHERE node_id = ?").run(e)}n(R,"completeCleanup");function S(e){return a().prepare("SELECT 1 FROM worktrees WHERE node_id = ? AND state != 'abandoned' AND (state = 'open' OR cleanup_state IS NULL OR cleanup_state != 'complete')").get(e)!==void 0}n(S,"hasUnreconciledWorktree");function m(e){return a().prepare("SELECT w.node_id FROM worktrees w JOIN nodes n ON n.node_id = w.node_id WHERE n.status IN ('done', 'dead', 'canceled') AND n.frozen_at IS NULL AND w.state IN ('open', 'closed') AND (w.state = 'open' OR w.cleanup_state IS NULL OR w.cleanup_state != 'complete') AND (w.state = 'closed' OR (n.status = 'done' AND n.final_report IS NOT NULL)) AND (w.next_attempt_at IS NULL OR w.next_attempt_at <= ?) ORDER BY w.next_attempt_at, w.node_id").all(e).map(({node_id:t})=>t)}n(m,"sweepCandidates");function T(){return a().prepare("SELECT node_id FROM worktrees WHERE quarantined = 1 ORDER BY sweep_last_at").all().flatMap(({node_id:e})=>{const t=i(e);return t===null?[]:[{nodeId:e,worktree:t}]})}n(T,"quarantinedWorktrees");export{U as abandonWorktree,f as abandonWorktreeAndRepoint,E as closeWorktree,N as closeWorktreeAndRepoint,R as completeCleanup,S as hasUnreconciledWorktree,L as openWorktree,T as quarantinedWorktrees,i as readWorktree,A as recordDisposalGrant,b as recordLandIntent,w as recordSweepAttempt,m as sweepCandidates,l as worktreeFromRow};