@north-light/crouter 0.3.322 → 0.3.324
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.
- package/bin/progress-painter.mjs +89 -0
- package/bin/runtime-selector.mjs +79 -9
- package/dist/api/client.d.ts +6 -2
- package/dist/api/client.js +2 -2
- package/dist/api/dto/broker-ops.d.ts +9 -0
- package/dist/api/dto/canvas.d.ts +23 -1
- package/dist/api/dto/nodes.d.ts +8 -0
- package/dist/api/node-transport.js +1 -1
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +1 -1
- package/dist/clients/attach/chrome/canvas-panels.js +1 -1
- package/dist/clients/attach/overlays/graph.d.ts +6 -5
- package/dist/clients/attach/overlays/graph.js +1 -1
- package/dist/clients/attach/viewer.js +536 -536
- package/dist/commands/node/create.js +3 -3
- package/dist/commands/sys/panels/plugins-panel.js +1 -1
- package/dist/commands/sys/panels/provider-panel.d.ts +4 -0
- package/dist/commands/sys/panels/provider-panel.js +1 -1
- package/dist/commands/sys/setup-core.js +7 -4
- package/dist/commands/sys/setup-wizard.d.ts +1 -1
- package/dist/commands/sys/setup-wizard.js +1 -1
- package/dist/core/canvas/canvas.d.ts +24 -0
- package/dist/core/canvas/canvas.js +29 -15
- package/dist/core/canvas/migrations.js +35 -32
- package/dist/core/canvas/nav-model.d.ts +1 -1
- package/dist/core/canvas/nav-model.js +1 -1
- package/dist/core/canvas/nav-render.d.ts +31 -25
- package/dist/core/canvas/nav-render.js +1 -1
- package/dist/core/canvas/render-source.d.ts +6 -16
- package/dist/core/canvas/render-source.js +9 -9
- package/dist/core/canvas/types.d.ts +16 -0
- package/dist/core/provider-credential.d.ts +6 -0
- package/dist/core/provider-credential.js +1 -0
- package/dist/core/runtime/boot-root.d.ts +2 -0
- package/dist/core/runtime/boot-root.js +5 -1
- package/dist/core/runtime/broker/daemon-ops.d.ts +2 -1
- package/dist/core/runtime/broker/daemon-ops.js +1 -1
- package/dist/core/runtime/front-door-login.d.ts +2 -0
- package/dist/core/runtime/front-door-login.js +1 -0
- package/dist/core/runtime/front-door.js +7 -5
- package/dist/core/runtime/nerd-font.d.ts +19 -1
- package/dist/core/runtime/nerd-font.js +2 -2
- package/dist/core/runtime/progress-bar.d.ts +13 -0
- package/dist/core/runtime/progress-bar.js +2 -0
- package/dist/core/runtime/provider-credential-gate.d.ts +4 -0
- package/dist/core/runtime/provider-credential-gate.js +2 -0
- package/dist/core/runtime/spawn-env.d.ts +7 -0
- package/dist/core/runtime/spawn-env.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +1 -1
- package/dist/daemon/api/handlers/canvas.js +4 -4
- package/dist/daemon/api/map.d.ts +15 -1
- package/dist/daemon/api/map.js +2 -2
- package/dist/daemon/fleet.js +1 -1
- package/dist/pi-extensions/canvas-stophook.js +1 -1
- package/package.json +1 -1
- package/runtime.lock.json +5 -5
- package/scripts/install-runtime.mjs +7 -3
|
@@ -1,4 +1,4 @@
|
|
|
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
|
|
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 p,readFileSync as S,readdirSync as U,realpathSync as te,statSync as oe}from"node:fs";import{basename as ie,join as w,resolve as H}from"node:path";import{crtrHome as re,isSafeNodeId as se,nodesRoot as I,nodeMetaPath as X}from"./paths.js";import{STATE_MIGRATIONS as Te}from"../../migrations/registry.js";import{customExtensionPaths as Ee}from"./extensions.js";import{readMetaObject as P,updateJsonFileDurably as b,withMetaLock as ae,writeMetaObjectLocked as de}from"./meta-file.js";function _e(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(
|
|
26
|
+
`)}i(_e,"baselineSchema");function x(e,n){return new Set(e.prepare(`PRAGMA table_info(${n})`).all().map(t=>t.name))}i(x,"tableColumns");function Y(e,n,t,o){n.has(t)||(e.exec(o),n.add(t))}i(Y,"addColumnIfMissing");function l(e){return x(e,"nodes")}i(l,"nodeColumns");const a=Y;function ce(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(ce,"addRuntimeColumns");function Ne(e){const n=I();if(!p(n))return;const t=e.prepare('UPDATE nodes SET intent = ?, pi_pid = ?, "window" = ?, tmux_session = ? WHERE node_id = ?');for(const o of U(n)){const T=X(o);if(!p(T))continue;let r;try{r=JSON.parse(S(T,"utf8"))}catch{continue}t.run(r.intent??null,r.pi_pid??null,r.window??null,r.tmux_session??null,o)}}i(Ne,"backfillRuntime");function ue(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
|
|
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 T=e.prepare("PRAGMA foreign_key_check").all();if(T.length>0)throw new Error(`edges FK rebuild left ${T.length} foreign-key violation(s)`);e.exec("COMMIT")}catch(o){throw e.exec("ROLLBACK"),o}}finally{e.exec("PRAGMA foreign_keys = ON;")}}i(ue,"edgesForeignKeyCascade");function le(e){const n=l(e);a(e,n,"pane","ALTER TABLE nodes ADD COLUMN pane TEXT;")}i(le,"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(
|
|
67
|
+
`)}i(Le,"addFocusesTable");function pe(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(
|
|
83
|
+
`)}i(pe,"appendWakeupsTable");function Oe(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(
|
|
134
|
+
`)}i(Oe,"migrateWakeupsToTriggers");function Ae(e){const n=l(e);a(e,n,"host_kind","ALTER TABLE nodes ADD COLUMN host_kind TEXT;")}i(Ae,"addHostKindColumn");function fe(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(
|
|
139
|
+
`)}i(fe,"addCanvasMetaTable");function me(e){const n=l(e);a(e,n,"profile_id","ALTER TABLE nodes ADD COLUMN profile_id TEXT;")}i(me,"addProfileIdColumn");function Re(e){const n=l(e);a(e,n,"pi_pid_identity","ALTER TABLE nodes ADD COLUMN pi_pid_identity TEXT;")}i(Re,"addPidIdentityColumn");function Ie(e){const n=l(e);a(e,n,"waiting_for","ALTER TABLE nodes ADD COLUMN waiting_for TEXT;")}i(Ie,"addMessageWaitColumn");function W(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:T}of t){const r=w(I(),T,"reports");if(!p(r))continue;let s=null;for(const E of U(r))if(E.endsWith(".md"))try{const d=S(w(r,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]};(s===null||c.ts>s.ts||c.ts===s.ts&&c.name>s.name)&&(s=c)}catch{}s!==null&&o.run(s.name,s.ts,T)}}i(W,"addCanonicalFinalColumns");function Xe(e){const n=I();if(p(n))for(const t of U(n)){const o=X(t);p(o)&&b(o,T=>{const r=T.launch;if(typeof r!="object"||r===null||Array.isArray(r))return!1;const s=r.extensions;if(!Array.isArray(s)||!s.every(d=>typeof d=="string"))return!1;const E=Ee(s);return E.length===s.length?!1:(r.extensions=E,!0)})}}i(Xe,"normalizePersistedLaunchExtensions");function Se(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(Se,"addMessageWaitGenerationColumn");function Ue(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(
|
|
153
|
+
`)}i(Ue,"addHumanWorkOutboxTable");function ge(e){const n=x(e,"consult_outbox"),t=i((o,T)=>Y(e,n,o,T),"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(
|
|
158
|
+
`)}i(ge,"addHumanWorkColumns");function De(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(
|
|
161
|
+
`)}i(De,"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,38 +213,38 @@ 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(Ce,"addCronsTables");function f(e){return`'${e.replace(/'/g,`'"'"'`)}'`}i(f,"shellSingleQuote");function
|
|
217
|
-
`).includes(o))return o;t++}}i(
|
|
216
|
+
`)}i(Ce,"addCronsTables");function f(e){return`'${e.replace(/'/g,`'"'"'`)}'`}i(f,"shellSingleQuote");function he(e,n){let t=0;for(;;){const o=`${n}_${t}`;if(!e.split(`
|
|
217
|
+
`).includes(o))return o;t++}}i(he,"uniqueHeredocDelimiter");function $(e,n,t){const o=he(n,t);return`${e} <<'${o}'
|
|
218
218
|
${n}
|
|
219
|
-
${o}`}i(
|
|
219
|
+
${o}`}i($,"heredocCommand");function we(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(we,"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 g(e,n){const t=e[n];return typeof t=="string"&&t.trim()!==""?t:void 0}i(g,"optionalString");function m(e,n){const t=e[n];return typeof t=="string"&&t.trim()!==""?t:void 0}i(m,"optionalColumn");function K(e,n,t,o){return $(`crtr node message send --to ${f(e)} --tier ${f(n)}`,t,`CRTR_MIGRATED_${o.replace(/[^A-Za-z0-9]/g,"_")}`)}i(K,"migratedMessageCommand");function xe(e,n){return`printf '%s\\n' "$CRTR_GATE_STDOUT" | crtr node message send --to ${f(e)} --tier ${f(n)}`}i(xe,"migratedGateStdoutMessageCommand");function ye(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[r,s]of o){const E=g(e,r);E!==void 0&&t.push(`--${s} ${f(E)}`)}e.root===!0&&t.push("--root");const T=L(e,"prompt",n);return $(t.join(" "),T,`CRTR_MIGRATED_${n.replace(/[^A-Za-z0-9]/g,"_")}`)}i(ye,"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?m(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 ke(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(s){n.push({trigger_id:"<unreadable table>",reason:`triggers table could not be read: ${s instanceof Error?s.message:String(s)}`})}const T=ve(e),r=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
|
|
224
|
+
) VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, NULL, ?, 'profile', ?, 'skip', 'on-failure', '{}', ?, ?, ?)`);for(const s of o){const E=m(s,"trigger_id");if(E===void 0){n.push({trigger_id:"<unidentified row>",reason:"row carries no trigger_id"});continue}try{Me(e,r,s,E,T)}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(s=>`${s.trigger_id} (${s.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(
|
|
232
|
-
${
|
|
231
|
+
`)}i(ke,"migrateTriggersToCrons");function Me(e,n,t,o,T){const r=L(t,"owner_id",o),s=e.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(r),E=(s===void 0?void 0:m(s,"cwd"))??T,d=s===void 0?null:m(s,"profile_id")??null,_=m(t,"node_id")??null,c=L(t,"action",o),N=L(t,"action_mode",o),u=L(t,"trigger_type",o),R=we(t.payload),M=m(t,"created")??new Date().toISOString(),C=N==="deadline_message"?"urgent":g(R,"tier")??"normal",y=g(R,"label");let O,h,B=!1;if(c==="msg"&&(N==="inbox_message"||N==="deadline_message")){if(_===null)throw new Error("has no message target");const A=g(R,"body");A!==void 0?O=K(_,C,A,o):u==="when"?(B=!0,O=xe(_,C)):O=K(_,C,y??"Scheduled message (no body was recorded when this schedule was armed).",o),h=y??(N==="deadline_message"?"deadline":"scheduled message")}else if(c==="msg"&&N==="fresh_revive"){if(_===null)throw new Error("has no revive target");O=`crtr node lifecycle revive ${f(_)} --fresh`,h=y??"scheduled fresh revive"}else if(c==="new"&&N==="node_birth")O=ye(R,o),h=g(R,"name")??`scheduled ${L(R,"kind",o)} node`;else throw new Error(`has unsupported action ${c}/${N}`);let v,F,G=null;if(u==="when"){v=L(t,"next_eval_at",o),F=L(t,"eval_every",o),G=m(t,"timeout_at")??null;const A=L(t,"when_script",o);O=`${B?`if ! CRTR_GATE_STDOUT="$(
|
|
232
|
+
${A}
|
|
233
233
|
)"; then`:`if ! (
|
|
234
|
-
${
|
|
234
|
+
${A}
|
|
235
235
|
); then`}
|
|
236
236
|
exit 0
|
|
237
237
|
fi
|
|
238
238
|
{
|
|
239
|
-
${
|
|
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
|
|
239
|
+
${O}
|
|
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 A=e.prepare("SELECT cron_id FROM crons WHERE anchor_node = ? AND cancel_on_wake = 1 AND cron_id <> ?").get(_,o);if(A!==void 0)throw new Error(`node ${_} already has a migrated deadline (${String(A.cron_id)}); only one is allowed`)}n.run(o,h,r,O,v,F,G,_,N==="deadline_message"?1:0,E,d,300,C,M,M)}i(Me,"convertTriggerRow");function Be(e){const n=x(e,"crons");n.size===0||n.has("run_pid_identity")||e.exec("ALTER TABLE crons ADD COLUMN run_pid_identity TEXT;")}i(Be,"addCronRunPidIdentityColumn");function Ge(e){const n=x(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 He(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(
|
|
247
|
+
`)}i(He,"addWarmPoolTable");function Pe(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
|
|
259
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`);let o=0;for(const T of n){const r=ie(T.dir);if(!(T.root===H(T.root)&&T.dir===w(T.root,r))||!se(r)){o+=1;continue}t.run(T.node_id,r,T.request_id,T.work_kind,T.status,T.final_report,T.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(
|
|
268
|
+
`)}i(Pe,"narrowHumanWorkOutbox");function be(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(
|
|
312
|
+
`)}i(be,"addReviewsTable");function Ye(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(
|
|
357
|
+
`)}i(Ye,"addReviewCommentTables");function We(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(
|
|
360
|
+
`)}i(We,"addReviewCommentAnchorState");function $e(e){e.exec("DROP TABLE IF EXISTS consult_outbox")}i($e,"dropConsultOutbox");function Ke(e){e.exec("ALTER TABLE reviews ADD COLUMN submit_requested_at TEXT")}i(Ke,"addReviewSubmitRequested");function qe(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(
|
|
363
|
+
`)}i(qe,"addCronHeldColumns");function je(e){e.exec(`
|
|
364
364
|
CREATE TABLE human_action_deliveries (
|
|
365
365
|
request_id TEXT PRIMARY KEY,
|
|
366
366
|
state TEXT NOT NULL,
|
|
@@ -378,18 +378,18 @@ 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(
|
|
381
|
+
`)}i(je,"addHumanActionDeliveries");function ze(e){const n=l(e);a(e,n,"pi_session_file","ALTER TABLE nodes ADD COLUMN pi_session_file TEXT;");for(const t of U(I(),{withFileTypes:!0}))if(t.isDirectory())try{const o=JSON.parse(S(X(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(ze,"addNodeSessionFileIndex");function j(e,n){const t=i(E=>{const d=H(E);return p(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 T=e.prepare("SELECT node_id FROM nodes ORDER BY created ASC, node_id ASC").all();for(const{node_id:E}of T){const d=X(E);if(P(d)===null){o.run(null,E);continue}const _=ae(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,de(d,c)),u});o.run(_,E)}const r=e.prepare(`
|
|
382
382
|
SELECT pi_session_file
|
|
383
383
|
FROM nodes
|
|
384
384
|
WHERE pi_session_file IS NOT NULL
|
|
385
385
|
GROUP BY pi_session_file
|
|
386
386
|
HAVING COUNT(*) > 1
|
|
387
|
-
`).all();if(!n&&
|
|
387
|
+
`).all();if(!n&&r.length>0){const E=r.map(({pi_session_file:d})=>d).join(", ");throw new Error(`cannot restore unique Pi session ownership: authoritative metadata has concurrent claimants for ${E}`)}const s=e.prepare("UPDATE nodes SET pi_session_file = NULL WHERE node_id = ?");for(const{pi_session_file:E}of r){const d=e.prepare(`
|
|
388
388
|
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(
|
|
392
|
+
`).all(E);for(const{node_id:_}of d.slice(1)){if(!b(X(_),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`);s.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 Qe(e){j(e,!1)}i(Qe,"repairNodeSessionFileUniqueIndex");function Ve(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(
|
|
401
|
+
`)}i(Ve,"addNodeLifecycleColumns");function Ze(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=I();if(!p(t))return;const o=e.prepare("UPDATE nodes SET description = ?, cycles = ? WHERE node_id = ?");for(const T of U(t)){const r=X(T);if(!p(r))continue;let s;try{s=JSON.parse(S(r,"utf8"))}catch{continue}const E=typeof s.description=="string"?s.description:null,d=typeof s.cycles=="number"?s.cycles:null;o.run(E,d,T)}}i(Ze,"addNodeLabelColumns");function en(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,11 +431,14 @@ 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(
|
|
434
|
+
`)}i(en,"addNodeOutcomeAndDeadline");function nn(e){const n=l(e);a(e,n,"telemetry_tokens_in","ALTER TABLE nodes ADD COLUMN telemetry_tokens_in INTEGER;"),a(e,n,"telemetry_context_tokens","ALTER TABLE nodes ADD COLUMN telemetry_context_tokens INTEGER;"),a(e,n,"telemetry_last_activity","ALTER TABLE nodes ADD COLUMN telemetry_last_activity TEXT;"),a(e,n,"telemetry_updated_at","ALTER TABLE nodes ADD COLUMN telemetry_updated_at TEXT;");const t=e.prepare(`UPDATE nodes
|
|
435
|
+
SET telemetry_tokens_in = ?, telemetry_context_tokens = ?,
|
|
436
|
+
telemetry_last_activity = ?, telemetry_updated_at = ?
|
|
437
|
+
WHERE node_id = ?`);for(const{node_id:o}of e.prepare("SELECT node_id FROM nodes").all()){const T=w(I(),o,"job","telemetry.json");if(p(T))try{const r=JSON.parse(S(T,"utf8")),s=typeof r.tokens_in=="number"&&Number.isFinite(r.tokens_in)?r.tokens_in:null,E=typeof r.context_tokens=="number"&&Number.isFinite(r.context_tokens)?r.context_tokens:null,d=typeof r.last_activity=="string"?r.last_activity:null,_=oe(T).mtime.toISOString();t.run(s,E,d,_,o)}catch{}}}i(nn,"addNodeTelemetryColumns");function tn(e){e.exec(`
|
|
435
438
|
CREATE INDEX IF NOT EXISTS deliveries_external_due_order
|
|
436
439
|
ON deliveries(state, next_attempt_at, created_at, delivery_id)
|
|
437
440
|
WHERE destination_kind = 'external_action' AND canceled_at IS NULL;
|
|
438
441
|
DROP INDEX IF EXISTS deliveries_external_due;
|
|
439
442
|
CREATE INDEX IF NOT EXISTS idx_nodes_parent_created ON nodes(parent, created);
|
|
440
443
|
CREATE INDEX IF NOT EXISTS idx_edges_type_created ON edges(type, created);
|
|
441
|
-
`)}i(
|
|
444
|
+
`)}i(tn,"addRecurringLaneIndexes");const on=Te.filter(e=>e.lane==="journaled").map(e=>n=>e.apply(n,re()));function rn(e){V(e)}i(rn,"projectLiveNodeState");const D=[_e,ce,Ne,ue,le,Le,pe,Ae,Oe,fe,me,Re,Ie,W,Xe,W,Se,Ue,ge,De,Ce,ke,Be,Ge,He,Pe,be,Ye,We,$e,Ke,qe,je,ze,Ve,Je,Qe,Ze,en,...on,e=>{Q(e),Z(e),ee(e),ne(e)},tn,rn,nn],sn=new Set([3]);function k(e){return e.prepare("PRAGMA user_version").get().user_version}i(k,"currentUserVersion");function z(e,n){D[n](e),e.exec(`PRAGMA user_version = ${n+1}`)}i(z,"applyMigration");function fn(e){const n=k(e);if(n>D.length)throw new Error(`unsupported canvas schema version ${n} (latest supported ${D.length})`);if(n===D.length)return;const t=e.prepare("PRAGMA busy_timeout").get().timeout;e.exec("PRAGMA busy_timeout = 60000;");try{Tn(e)}finally{e.exec(`PRAGMA busy_timeout = ${t};`)}}i(fn,"migrate");function Tn(e){for(;;){const n=k(e);if(n>=D.length)return;if(e.isTransaction||sn.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(Tn,"runPendingMigrations");export{D as MIGRATIONS,fn as migrate};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FlatRow, type FoldState, type SubtreeActivity } from './nav-render.js';
|
|
2
2
|
import type { NodeMeta } from './index.js';
|
|
3
|
-
export { PI_MAX_WIDGET_LINES, VIEWPORT_FALLBACK_ROWS, ESC, RESET, BOLD, DIM, REVERSE, BG_ATTACHED, GREEN, RED, YELLOW, CYAN, GRAY, nodeGlyph, visibleWidth, truncate, fillWidth, fillBar,
|
|
3
|
+
export { PI_MAX_WIDGET_LINES, VIEWPORT_FALLBACK_ROWS, ESC, RESET, BOLD, DIM, REVERSE, BG_ATTACHED, GREEN, RED, YELLOW, CYAN, GRAY, nodeGlyph, visibleWidth, truncate, fillWidth, fillBar, fmtTokens, tokensCell, activityCell, cycleBadge, navLabel, shortId, askBadge, liveBelowBadge, isAttached, hasBrokerViewers, fetchAsksMap, graphWidgetBudget, GRAPH_HINT, type FlatRow, type FoldState, type SubtreeActivity, } from './nav-render.js';
|
|
4
4
|
/** First manager (by created) — the UP step for the ancestry spine. */
|
|
5
5
|
export declare function managerOf(id: string): string | undefined;
|
|
6
6
|
/** A kind:'human' node is a control-plane ASK (a page on the human's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var R=Object.defineProperty;var o=(t,e)=>R(t,"name",{value:e,configurable:!0});import{getNode as $,subscribersOf as y,subscriptionsOf as I}from"./index.js";import{faultSummary as C}from"./status-glyph.js";import{statusRank as S}from"./node-order.js";import{nodeGlyph as
|
|
1
|
+
var R=Object.defineProperty;var o=(t,e)=>R(t,"name",{value:e,configurable:!0});import{getNode as $,subscribersOf as y,subscriptionsOf as I}from"./index.js";import{faultSummary as C}from"./status-glyph.js";import{statusRank as S}from"./node-order.js";import{nodeGlyph as _,navLabel as G,shortId as O,tokensCell as T,activityCell as D,cycleBadge as W,askBadge as L,liveBelowBadge as M,truncate as N,fillBar as w,fillWidth as k,isAttached as H,BOLD as K,DIM as g,RESET as m,REVERSE as P,BG_ATTACHED as V}from"./nav-render.js";import{PI_MAX_WIDGET_LINES as dt,VIEWPORT_FALLBACK_ROWS as ft,ESC as ht,RESET as pt,BOLD as $t,DIM as mt,REVERSE as vt,BG_ATTACHED as Et,GREEN as xt,RED as gt,YELLOW as Bt,CYAN as bt,GRAY as St,nodeGlyph as wt,visibleWidth as kt,truncate as At,fillWidth as Rt,fillBar as yt,fmtTokens as It,tokensCell as Ct,activityCell as _t,cycleBadge as Gt,navLabel as Ot,shortId as Tt,askBadge as Dt,liveBelowBadge as Wt,isAttached as Lt,hasBrokerViewers as Mt,fetchAsksMap as Nt,graphWidgetBudget as Ht,GRAPH_HINT as Kt}from"./nav-render.js";function Y(t){try{return I(t)}catch{return[]}}o(Y,"subsOf");function F(t){try{return y(t)}catch{return[]}}o(F,"mgrsOf");function q(t){return F(t)[0]?.node_id}o(q,"managerOf");function X(t){return $(t)?.kind==="human"}o(X,"isHumanAsk");function v(t){return Y(t).map(e=>e.node_id).filter(e=>!X(e))}o(v,"convoChildIds");function rt(t){return v(t).filter(e=>{const s=$(e)?.status;return s==="active"||s==="idle"})}o(rt,"liveReports");function j(t){return v(t).length}o(j,"childCount");function z(t){let e=t;const s=new Set([e]);for(;;){const c=q(e);if(c===void 0||s.has(c))break;s.add(c),e=c}return e}o(z,"climbRoot");function ct(t){const e=[],s=new Set([t]),c=v(t);for(;c.length>0;){const l=c.shift();if(!s.has(l)){s.add(l),e.push(l);for(const u of v(l))s.has(u)||c.push(u)}}return e}o(ct,"subtreeIds");function J(t){if(t===null)return"";const e=j(t.node_id);return e>0?` ${g}\u2933${e}${m}`:""}o(J,"childBadge");function Q(t){return v(t).sort((e,s)=>S($(e)?.status)-S($(s)?.status))}o(Q,"sortedChildIds");function U(t,e){const s=new Set,c=new Map,l=new Set,u=o(n=>{const i=$(n)?.status==="active";if(l.has(n))return{active:i?1:0,reveal:i||n===e};l.add(n);let d=0,r=!1;for(const h of v(n)){const f=u(h);d+=f.active,f.reveal&&(r=!0)}return c.set(n,d),r&&s.add(n),{active:d+(i?1:0),reveal:r||i||n===e}},"visit");return u(t),{expand:s,activeBelow:c}}o(U,"computeSubtreeActivity");function Z(t,e){return U(t,e).expand}o(Z,"computeDefaultExpanded");function at(t,e,s){const c=z(t),l=s??Z(c,t),u=o(r=>e.userExpanded.has(r)?!1:e.userCollapsed.has(r)?!0:!l.has(r),"isFolded"),n=[],i=new Set,d=o((r,h,f,E)=>{if(i.has(r)){const a=f?"":E?"\u2514\u2500 ":"\u251C\u2500 ";n.push({id:r,hasKids:!1,isSelf:r===t,branch:h+a,cycle:!0,collapsed:!1});return}i.add(r);const p=Q(r),x=u(r),B=f?"":E?"\u2514\u2500 ":"\u251C\u2500 ";if(n.push({id:r,hasKids:p.length>0,isSelf:r===t,branch:h+B,cycle:!1,collapsed:x}),x)return;const b=f?"":h+(E?" ":"\u2502 ");for(let a=0;a<p.length;a++)d(p[a],b,!1,a===p.length-1)},"walk");return d(c,"",!0,!0),n}o(at,"buildGraphModel");function lt(t,e,s,c,l){const u=o((a,A)=>e?w(a,k(),P):A?w(a,k(),V):N(a),"wrap");if(t.cycle){const a=`${t.branch} ${g}\u21BA ${O(t.id)}${m}`;return u(a,!1)}const n=$(t.id),i=_(n),d=G(n,t.id),r=t.isSelf?`${K}${d}${m}`:d,h=`${g}${n?.kind??""}${m}`,f=`${g}${T(n)}${m}`,E=t.hasKids&&t.collapsed,p=!e&&E?`${g}\u25B8${m} `:" ",x=n?.fault??null,B=x!==null?` \xB7 ${C(x)}`:"",b=`${t.branch}${p}${i} ${r} ${h} ${f}${W(n)}${J(n)}${M(n,c)}${L(t.id,l)}${D(n)}${B}`;return u(b,H(t.id,n,s))}o(lt,"renderGraphRow");export{Et as BG_ATTACHED,$t as BOLD,bt as CYAN,mt as DIM,ht as ESC,Kt as GRAPH_HINT,St as GRAY,xt as GREEN,dt as PI_MAX_WIDGET_LINES,gt as RED,pt as RESET,vt as REVERSE,ft as VIEWPORT_FALLBACK_ROWS,Bt as YELLOW,_t as activityCell,Dt as askBadge,at as buildGraphModel,J as childBadge,j as childCount,z as climbRoot,Z as computeDefaultExpanded,U as computeSubtreeActivity,v as convoChildIds,Gt as cycleBadge,Nt as fetchAsksMap,yt as fillBar,Rt as fillWidth,It as fmtTokens,Ht as graphWidgetBudget,Mt as hasBrokerViewers,Lt as isAttached,X as isHumanAsk,Wt as liveBelowBadge,rt as liveReports,q as managerOf,Ot as navLabel,wt as nodeGlyph,lt as renderGraphRow,Tt as shortId,Q as sortedChildIds,ct as subtreeIds,Ct as tokensCell,At as truncate,kt as visibleWidth};
|
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Fault } from '../fault-classifier.js';
|
|
2
|
+
import type { NodeStatus } from './types.js';
|
|
3
|
+
/** The node fields these helpers display — satisfied by `NodeMeta`, by
|
|
4
|
+
* `NodeRow`, and by the lean `GET /v1/canvas/graph` row the attach graph reads.
|
|
5
|
+
* Declared narrow so a caller holding only the display projection needs no full
|
|
6
|
+
* node record. */
|
|
7
|
+
export interface NavNode {
|
|
8
|
+
node_id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
kind: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
cycles?: number;
|
|
13
|
+
status: NodeStatus;
|
|
14
|
+
frozen_at?: string | null;
|
|
15
|
+
pi_pid?: number | null;
|
|
16
|
+
streaming?: boolean;
|
|
17
|
+
telemetry_context_tokens?: number | null;
|
|
18
|
+
telemetry_last_activity?: string | null;
|
|
19
|
+
fault?: Fault | null;
|
|
20
|
+
}
|
|
2
21
|
/** pi's InteractiveMode.MAX_WIDGET_LINES — the hard cap on lines in a string
|
|
3
22
|
* array widget; anything beyond it pi truncates with its own "... (widget
|
|
4
23
|
* truncated)". Our GRAPH viewport stays at/under this and scrolls internally. */
|
|
@@ -22,7 +41,7 @@ export declare const GRAY = "\u001B[90m";
|
|
|
22
41
|
* outranks the base status glyph. Every input is a projected row field — the
|
|
23
42
|
* owning daemon computed `fault`/`streaming` — so this holds for a remote node
|
|
24
43
|
* too, with no local read keyed on a node id that may collide. */
|
|
25
|
-
export declare function nodeGlyph(node:
|
|
44
|
+
export declare function nodeGlyph(node: NavNode | null): string;
|
|
26
45
|
/** Drop every SGR sequence, keeping the visible text. The selection bar uses
|
|
27
46
|
* this: reverse-video INVERTS a coloured cell into a coloured block, so a row
|
|
28
47
|
* built from tinted spans highlights as a patchwork of background swatches
|
|
@@ -50,52 +69,39 @@ export declare function fillWidth(): number;
|
|
|
50
69
|
* bar; the visible width is padded out to `width`; the line closes with a real
|
|
51
70
|
* RESET so the style never bleeds into the editor below. */
|
|
52
71
|
export declare function fillBar(content: string, width: number, open: string): string;
|
|
53
|
-
/** Clear the per-frame telemetry cache. Every render pass calls this first, so
|
|
54
|
-
* one keystroke re-reads each node's telemetry exactly once. */
|
|
55
|
-
export declare function beginFrameTelem(): void;
|
|
56
|
-
export interface Telemetry {
|
|
57
|
-
/** Live context-window size (pi's getContextUsage) — the figure pi's own footer
|
|
58
|
-
* shows. This is the node's window fill, NOT a per-turn delta. */
|
|
59
|
-
context_tokens?: number;
|
|
60
|
-
/** One-line "what is it doing" cue (`tool: detail`), written on every tool start. */
|
|
61
|
-
last_activity?: string;
|
|
62
|
-
}
|
|
63
|
-
export declare function readTelemetry(nodeId: string): Telemetry;
|
|
64
72
|
export declare function fmtTokens(n: number): string;
|
|
65
73
|
/** The context-window cell — live window fill rounded to the nearest 1k (the same
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
export declare function tokensCell(id: string, remote?: boolean): string;
|
|
74
|
+
* figure pi's footer shows), NOT a per-turn delta. Remote canvases retain the
|
|
75
|
+
* existing safe 0 display rather than treating a local node id as remote state. */
|
|
76
|
+
export declare function tokensCell(node: NavNode | null, remote?: boolean): string;
|
|
70
77
|
/** Dimmed live "what is it doing" cue for an ACTIVE node — the last tool it ran.
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
export declare function activityCell(id: string, node: NodeMeta | null, remote?: boolean): string;
|
|
78
|
+
* Empty for non-active rows and when none is recorded. */
|
|
79
|
+
export declare function activityCell(node: NavNode | null, remote?: boolean): string;
|
|
74
80
|
/** Revive-count badge (meta.cycles). Hidden on the first cycle (0) to cut noise. */
|
|
75
|
-
export declare function cycleBadge(node:
|
|
81
|
+
export declare function cycleBadge(node: NavNode | null): string;
|
|
76
82
|
/** Short on-screen label: the explicit handle when set, else the pi-generated
|
|
77
83
|
* description, else the bare name. fullName joins BOTH (`handle description`);
|
|
78
84
|
* the nav chrome shows just the first so rows stay compact. */
|
|
79
|
-
export declare function navLabel(node:
|
|
85
|
+
export declare function navLabel(node: NavNode | null, id: string): string;
|
|
80
86
|
export declare function shortId(id: string): string;
|
|
81
87
|
/** True when a human is watching `id` right now via the broker viewer count
|
|
82
88
|
* (attach.json). The tmux-focus axis (a `focuses` row) is a canvas-db read and
|
|
83
89
|
* stays in nav-model's `isAttached`, which ORs this signal with the focus set.
|
|
84
90
|
* `remote` skips the local `attach.json` read entirely and reports false (a
|
|
85
91
|
* remote node id may collide with a local one). */
|
|
86
|
-
export declare function hasBrokerViewers(id: string, node:
|
|
92
|
+
export declare function hasBrokerViewers(id: string, node: NavNode | null, remote?: boolean): boolean;
|
|
87
93
|
/** True when a human is watching `id` right now (tmux focus or broker viewer).
|
|
88
94
|
* `focused` is the tmux-focus set (a canvas-db read the caller supplies). The
|
|
89
95
|
* broker-viewer half is a pure `attach.json` read. `remote` skips the local read
|
|
90
96
|
* entirely and reports false (a remote node id may collide with a local one). */
|
|
91
|
-
export declare function isAttached(id: string, node:
|
|
97
|
+
export declare function isAttached(id: string, node: NavNode | null, focused: ReadonlySet<string>, remote?: boolean): boolean;
|
|
92
98
|
/** ⚑K pending-asks badge for a node, read from the supplied per-node ask map. */
|
|
93
99
|
export declare function askBadge(id: string, asks: Record<string, number>): string;
|
|
94
100
|
/** "Live work below" badge — green ⇣N when this node is NOT itself active but has
|
|
95
101
|
* N active descendants. The one signal that an idle/parked node isn't dead: real
|
|
96
102
|
* work is still running beneath it. Count comes from the single subtree-activity
|
|
97
103
|
* pass (no per-row walk). Suppressed on active rows (their own ● already says so). */
|
|
98
|
-
export declare function liveBelowBadge(node:
|
|
104
|
+
export declare function liveBelowBadge(node: NavNode | null, activeBelow: ReadonlyMap<string, number>): string;
|
|
99
105
|
export declare function fetchAsksMap(rootId: string): Record<string, number>;
|
|
100
106
|
export interface FlatRow {
|
|
101
107
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var p=Object.defineProperty;var n=(t,e)=>p(t,"name",{value:e,configurable:!0});import{execFileSync as m}from"node:child_process";import{existsSync as x,readFileSync as $}from"node:fs";import{join as h}from"node:path";import{jobDir as g}from"./paths.js";import{isFrozenRow as v,resolveNodeVisual as _}from"./status-glyph.js";const y=10,E=30,c="\x1B[",i=`${c}0m`,C=`${c}1m`,a=`${c}2m`,D=`${c}7m`,G=`${c}48;5;22m`,d=`${c}32m`,O=`${c}31m`,S=`${c}33m`,j=`${c}36m`,F=`${c}90m`;function k(t){return t.status!=="active"&&t.status!=="idle"?!1:t.streaming===!0}n(k,"streamingFor");function V(t){if(t===null)return"?";const e=_(t.status,{streaming:k(t),frozen:v(t),hanging:t.fault??null});return`${c}${e.color}m${e.glyph}${i}`}n(V,"nodeGlyph");const l=/\x1b\[[0-9;]*m/g;function H(t){return t.replace(l,"")}n(H,"stripSgr");function f(t){return t.replace(l,"").length}n(f,"visibleWidth");function A(t,e=R()){if(f(t)<=e)return t;let r="",o=0,s=0;for(;s<t.length&&o<e-1;){if(t[s]==="\x1B"){const u=/^\x1b\[[0-9;]*m/.exec(t.slice(s));if(u){r+=u[0],s+=u[0].length;continue}}r+=t[s],o++,s++}return`${r}\u2026${i}`}n(A,"truncate");function R(){return Math.max(20,Math.min((process.stdout.columns??80)-2,180))}n(R,"fillWidth");function P(t,e,r){const o=A(t,e),s=o.replace(/\x1b\[0m/g,`${i}${r}`),u=Math.max(0,e-f(o));return`${r}${s}${" ".repeat(u)}${i}`}n(P,"fillBar");function w(t){return t<1e3?`${t}`:`${Math.round(t/1e3)}k`}n(w,"fmtTokens");function Y(t,e=!1){return w(e?0:t?.telemetry_context_tokens??0)}n(Y,"tokensCell");function z(t,e=!1){if(e||t?.status!=="active")return"";const r=(t.telemetry_last_activity??"").trim();return r===""?"":` ${a}\xB7 ${r}${i}`}n(z,"activityCell");function J(t){const e=t?.cycles??0;return e>0?` ${a}\u27F3${e}${i}`:""}n(J,"cycleBadge");function K(t,e){if(t===null)return B(e);const r=t.name&&t.name!==t.kind?t.name:"";if(r!=="")return r;const o=(t.description??"").trim();return o!==""?o:t.name}n(K,"navLabel");function B(t){return t.slice(0,8)}n(B,"shortId");function b(t,e,r=!1){if(r||e?.status!=="active")return!1;try{const o=h(g(t),"attach.json");if(!x(o))return!1;const s=JSON.parse($(o,"utf8"));return typeof s.viewers=="number"&&s.viewers>0}catch{return!1}}n(b,"hasBrokerViewers");function X(t,e,r,o=!1){return o?!1:r.has(t)?!0:b(t,e,o)}n(X,"isAttached");function q(t,e){const r=e[t]??0;return r>0?` ${S}\u2691${r}${i}`:""}n(q,"askBadge");function Q(t,e){if(t===null||t.status==="active")return"";const r=e.get(t.node_id)??0;return r>0?` ${d}\u21E3${r}${i}`:""}n(Q,"liveBelowBadge");function U(t){try{const e=m("crtr",["canvas","attention","map","--view",t,"--json"],{timeout:2500,encoding:"utf8"});return JSON.parse(e.trim()).counts??{}}catch{return{}}}n(U,"fetchAsksMap");function Z(){const t=process.stdout.rows??E;return Math.max(4,Math.min(y,t-4))}n(Z,"graphWidgetBudget");const tt=`${a}jk move \xB7 hl fold \xB7 \u21B5 focus \xB7 e expand \xB7 x kill \xB7 m mgr \xB7 esc${i}`;export{G as BG_ATTACHED,C as BOLD,j as CYAN,a as DIM,c as ESC,tt as GRAPH_HINT,F as GRAY,d as GREEN,y as PI_MAX_WIDGET_LINES,O as RED,i as RESET,D as REVERSE,E as VIEWPORT_FALLBACK_ROWS,S as YELLOW,z as activityCell,q as askBadge,J as cycleBadge,U as fetchAsksMap,P as fillBar,R as fillWidth,w as fmtTokens,Z as graphWidgetBudget,b as hasBrokerViewers,X as isAttached,Q as liveBelowBadge,K as navLabel,V as nodeGlyph,B as shortId,H as stripSgr,Y as tokensCell,A as truncate,f as visibleWidth};
|
|
@@ -2,10 +2,6 @@ import type { CanvasSource } from './source.js';
|
|
|
2
2
|
import { type NodeRecap } from './node-recap.js';
|
|
3
3
|
import type { Fault } from '../runtime/fault.js';
|
|
4
4
|
import type { NodeStatus, Lifecycle, NodeRow, TerminalReason } from './types.js';
|
|
5
|
-
export interface NodeTelemetry {
|
|
6
|
-
tokens_in?: number;
|
|
7
|
-
}
|
|
8
|
-
export declare function readNodeTelemetry(nodeId: string): NodeTelemetry;
|
|
9
5
|
/** Format a token count as `Nk` (rounded down to nearest 1 k). */
|
|
10
6
|
export declare function fmtCtx(tokensIn: number | undefined): string;
|
|
11
7
|
export declare function renderTreeFromSource(source: CanvasSource, rootId: string, snapshot?: readonly NodeRow[], ticketSnapshot?: Record<string, number>, ticketUnavailable?: string): Promise<string>;
|
|
@@ -43,14 +39,11 @@ export interface DashboardRow {
|
|
|
43
39
|
* status alone cannot (see `isFinishedWork` in node-order.ts). Only populated
|
|
44
40
|
* by dashboardRowsAll (the browser snapshot). */
|
|
45
41
|
finalized?: boolean;
|
|
46
|
-
/** "Most recent activity" sort key — epoch ms
|
|
47
|
-
*
|
|
48
|
-
* no meta read), falling back to `created`'s epoch when a node never ran a turn.
|
|
42
|
+
/** "Most recent activity" sort key — epoch ms from the daemon's telemetry
|
|
43
|
+
* projection, falling back to `created` when a node never reported telemetry.
|
|
49
44
|
* This is the cheap proxy for last-message recency the attention sort tie-breaks
|
|
50
45
|
* on. Always set by dashboardRowsAll; optional only so synthetic test rows + the
|
|
51
|
-
* scoped dashboardRows can omit it.
|
|
52
|
-
* but its path lives in meta.json at a non-deterministic location — reading it
|
|
53
|
-
* per node would defeat the cheap boot, so telemetry mtime is used instead.) */
|
|
46
|
+
* scoped dashboardRows can omit it. */
|
|
54
47
|
mtimeMs?: number;
|
|
55
48
|
/** The node's spawn prompt (context/initial-prompt.md), trimmed + capped. Populated
|
|
56
49
|
* LAZILY by loadPreview (selected-row preview) — NOT on the cheap boot path.
|
|
@@ -156,12 +149,9 @@ export declare function readTranscript(sessionFile: string | null | undefined):
|
|
|
156
149
|
* — a tool-call-only turn (no final text) still counts. Drives reap-on-close/
|
|
157
150
|
* detach. One file read with an early exit on the first substantive turn. */
|
|
158
151
|
export declare function nodeHasAssistantMessage(sessionFile: string | null | undefined): boolean;
|
|
159
|
-
/** "Most recent activity" sort key
|
|
160
|
-
*
|
|
161
|
-
|
|
162
|
-
* read. Falls back to `created`'s epoch when a node never ran a turn (no telemetry).
|
|
163
|
-
* Never throws. */
|
|
164
|
-
export declare function sessionMtime(nodeId: string, created: string, remote?: boolean): number;
|
|
152
|
+
/** "Most recent activity" sort key from the daemon's telemetry projection. It
|
|
153
|
+
* falls back to `created` when the broker has never reported telemetry. */
|
|
154
|
+
export declare function sessionMtime(created: string, telemetryUpdatedAt: string | null | undefined, remote?: boolean): number;
|
|
165
155
|
/** Build one cheap-boot row from a raw db/source row. `fault`/`streaming` are
|
|
166
156
|
* projected row fields, so they hold for a remote row too; `remote` suppresses the
|
|
167
157
|
* LOCAL disk reads (telemetry-mtime/viewers) that are meaningless for a truly
|