@north-light/cli 0.3.512 → 0.3.513
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/dist/api/command-manifest/object-kinds.js +1 -1
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/canvas.d.ts +12 -0
- package/dist/api/dto/custom-objects.d.ts +1 -0
- package/dist/api/dto/objects.d.ts +1 -0
- package/dist/builtin-memory/crouter-sdk/release-notes.md +5 -0
- package/dist/clients/attach/chrome/canvas-panels.d.ts +5 -0
- package/dist/clients/attach/chrome/canvas-panels.js +1 -1
- package/dist/clients/attach/chrome/object-wait.d.ts +4 -0
- package/dist/clients/attach/chrome/object-wait.js +1 -0
- package/dist/clients/attach/chrome/roster.d.ts +3 -0
- package/dist/clients/attach/chrome/roster.js +4 -4
- package/dist/clients/attach/session/editor-inventory.js +2 -2
- package/dist/clients/attach/session/readonly-popup.d.ts +15 -0
- package/dist/clients/attach/session/readonly-popup.js +1 -0
- package/dist/clients/attach/viewer.js +603 -603
- package/dist/commands/custom-object/register.js +1 -1
- package/dist/core/canvas/canvas.d.ts +0 -13
- package/dist/core/canvas/canvas.js +16 -18
- package/dist/core/canvas/migrations.js +40 -22
- package/dist/core/canvas/remote-canvas-source.js +1 -1
- package/dist/core/canvas/render-source.d.ts +1 -1
- package/dist/core/canvas/types.d.ts +0 -4
- package/dist/core/canvas/waits.d.ts +23 -2
- package/dist/core/canvas/waits.js +9 -4
- package/dist/core/graph/custom.d.ts +1 -0
- package/dist/core/graph/custom.js +2 -2
- package/dist/core/graph/events.js +6 -6
- package/dist/core/graph/kinds/custom.d.ts +1 -0
- package/dist/core/graph/kinds/custom.js +1 -1
- package/dist/core/graph/kinds/kind.d.ts +3 -0
- package/dist/core/graph/object-waits.d.ts +11 -0
- package/dist/core/graph/object-waits.js +1 -0
- package/dist/core/runtime/lifecycle.js +2 -2
- package/dist/core/runtime/stop-guard.js +3 -3
- package/dist/daemon/api/handlers/canvas.js +5 -5
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/objects.js +3 -3
- package/dist/daemon/api/map.js +2 -2
- package/dist/daemon/reconcilers/live-obligation.js +1 -1
- package/dist/types.d.ts +1 -0
- package/docs/sdk/release-notes.md +5 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
var y=Object.defineProperty;var u=(s,e)=>y(s,"name",{value:e,configurable:!0});import{createRemoteExec as h}from"./remote-transport.js";const d=1e3;function r(s){return typeof s=="object"&&s!==null&&!Array.isArray(s)}u(r,"isPlainObject");function w(s){if(!r(s))return!1;const e=s.node;if(!r(e)||typeof e.node_id!="string"||!Array.isArray(s.reports)||!Array.isArray(s.managers))return!1;const t=s.artifacts;if(!r(t)||typeof t.report!="number"||typeof t.doc!="number"||typeof t.roadmap!="number"||typeof s.pending_crons!="number")return!1;const o=s.paths;if(!r(o)||typeof o.context_dir!="string"||"session_file"in o&&o.session_file!==null&&typeof o.session_file!="string"||typeof s.follow_up!="string")return!1;const n=s.fault;if(n!=null){if(!r(n)||typeof n.link!="string"||typeof n.kind!="string"||typeof n.since!="string")return!1;const i=n.retry;if(!r(i)||typeof i.disposition!="string")return!1}return!0}u(w,"isValidShowResponse");function p(s){return r(s)&&typeof s.node_id=="string"&&typeof s.name=="string"&&typeof s.kind=="string"&&typeof s.status=="string"&&typeof s.active=="boolean"}u(p,"isValidNeighborEntry");function g(s){return r(s)&&typeof s.from_id=="string"&&typeof s.to_id=="string"&&typeof s.active=="boolean"&&typeof s.created=="string"}u(g,"isValidRosterEdge");function _(s){return r(s)?typeof s.node_id=="string"&&typeof s.name=="string"&&typeof s.kind=="string"&&typeof s.mode=="string"&&typeof s.lifecycle=="string"&&typeof s.status=="string"&&typeof s.cwd=="string"&&typeof s.created=="string":!1}u(_,"isValidNodeRow");class v{static{u(this,"RemoteCanvasSource")}previewEndpoint;relayToken;exec;nodeCache=new Map;rosterCache=new Map;topologyCache=null;viewCache=new Map;constructor(e,t){this.previewEndpoint=e,this.relayToken=t,this.exec=h(e,t)}async execJson(e){let t;try{t=await this.exec("crtr",["--json",...e])}catch{return null}if(!t.ok||t.exitCode!==void 0&&t.exitCode!==0)return null;try{return JSON.parse(t.stdout.trim())}catch{return null}}async fetchShow(e){const t=this.nodeCache.get(e);if(t&&Date.now()-t.at<d)return t.value;const o=await this.execJson(["node","inspect","show",e]),n=w(o)?{...o,fault:o.fault??null,reports:o.reports.filter(p),managers:o.managers.filter(p)}:null;return this.nodeCache.set(e,{value:n,at:Date.now()}),n}topology(){if(this.topologyCache!==null&&Date.now()-this.topologyCache.at<d)return this.topologyCache.value;const e=this.loadTopology();return this.topologyCache={value:e,at:Date.now()},e}async loadTopology(){const e=await this.exec("crtr",["--json","canvas","roster"]);if(!e.ok||e.exitCode!==void 0&&e.exitCode!==0)throw new Error("remote canvas topology is unavailable");let t;try{t=JSON.parse(e.stdout.trim())}catch{throw new Error("remote canvas topology is invalid")}if(!r(t)||!Array.isArray(t.edges)||!t.edges.every(g))throw new Error("remote canvas topology is invalid");const o=t.edges,n={subscriptions:new Map,subscribers:new Map};for(const i of o){const a={node_id:i.to_id,active:i.active,created:i.created},c={node_id:i.from_id,active:i.active,created:i.created},l=n.subscriptions.get(i.from_id);l===void 0?n.subscriptions.set(i.from_id,[a]):l.push(a);const f=n.subscribers.get(i.to_id);f===void 0?n.subscribers.set(i.to_id,[c]):f.push(c)}return n}async rawShow(e){return this.fetchShow(e)}async getNode(e){return(await this.fetchShow(e))?.node??null}async getRow(e){const t=(await this.fetchShow(e))?.node;return t?{node_id:t.node_id,name:t.name,kind:t.kind,mode:t.mode,lifecycle:t.lifecycle,status:t.status,cwd:t.cwd,host_kind:t.host_kind??null,profile_id:t.profile_id??null,scopes:t.scopes??null,isolated:t.isolated===!0,parent:t.parent??null,created:t.created,intent:t.intent??null,
|
|
1
|
+
var y=Object.defineProperty;var u=(s,e)=>y(s,"name",{value:e,configurable:!0});import{createRemoteExec as h}from"./remote-transport.js";const d=1e3;function r(s){return typeof s=="object"&&s!==null&&!Array.isArray(s)}u(r,"isPlainObject");function w(s){if(!r(s))return!1;const e=s.node;if(!r(e)||typeof e.node_id!="string"||!Array.isArray(s.reports)||!Array.isArray(s.managers))return!1;const t=s.artifacts;if(!r(t)||typeof t.report!="number"||typeof t.doc!="number"||typeof t.roadmap!="number"||typeof s.pending_crons!="number")return!1;const o=s.paths;if(!r(o)||typeof o.context_dir!="string"||"session_file"in o&&o.session_file!==null&&typeof o.session_file!="string"||typeof s.follow_up!="string")return!1;const n=s.fault;if(n!=null){if(!r(n)||typeof n.link!="string"||typeof n.kind!="string"||typeof n.since!="string")return!1;const i=n.retry;if(!r(i)||typeof i.disposition!="string")return!1}return!0}u(w,"isValidShowResponse");function p(s){return r(s)&&typeof s.node_id=="string"&&typeof s.name=="string"&&typeof s.kind=="string"&&typeof s.status=="string"&&typeof s.active=="boolean"}u(p,"isValidNeighborEntry");function g(s){return r(s)&&typeof s.from_id=="string"&&typeof s.to_id=="string"&&typeof s.active=="boolean"&&typeof s.created=="string"}u(g,"isValidRosterEdge");function _(s){return r(s)?typeof s.node_id=="string"&&typeof s.name=="string"&&typeof s.kind=="string"&&typeof s.mode=="string"&&typeof s.lifecycle=="string"&&typeof s.status=="string"&&typeof s.cwd=="string"&&typeof s.created=="string":!1}u(_,"isValidNodeRow");class v{static{u(this,"RemoteCanvasSource")}previewEndpoint;relayToken;exec;nodeCache=new Map;rosterCache=new Map;topologyCache=null;viewCache=new Map;constructor(e,t){this.previewEndpoint=e,this.relayToken=t,this.exec=h(e,t)}async execJson(e){let t;try{t=await this.exec("crtr",["--json",...e])}catch{return null}if(!t.ok||t.exitCode!==void 0&&t.exitCode!==0)return null;try{return JSON.parse(t.stdout.trim())}catch{return null}}async fetchShow(e){const t=this.nodeCache.get(e);if(t&&Date.now()-t.at<d)return t.value;const o=await this.execJson(["node","inspect","show",e]),n=w(o)?{...o,fault:o.fault??null,reports:o.reports.filter(p),managers:o.managers.filter(p)}:null;return this.nodeCache.set(e,{value:n,at:Date.now()}),n}topology(){if(this.topologyCache!==null&&Date.now()-this.topologyCache.at<d)return this.topologyCache.value;const e=this.loadTopology();return this.topologyCache={value:e,at:Date.now()},e}async loadTopology(){const e=await this.exec("crtr",["--json","canvas","roster"]);if(!e.ok||e.exitCode!==void 0&&e.exitCode!==0)throw new Error("remote canvas topology is unavailable");let t;try{t=JSON.parse(e.stdout.trim())}catch{throw new Error("remote canvas topology is invalid")}if(!r(t)||!Array.isArray(t.edges)||!t.edges.every(g))throw new Error("remote canvas topology is invalid");const o=t.edges,n={subscriptions:new Map,subscribers:new Map};for(const i of o){const a={node_id:i.to_id,active:i.active,created:i.created},c={node_id:i.from_id,active:i.active,created:i.created},l=n.subscriptions.get(i.from_id);l===void 0?n.subscriptions.set(i.from_id,[a]):l.push(a);const f=n.subscribers.get(i.to_id);f===void 0?n.subscribers.set(i.to_id,[c]):f.push(c)}return n}async rawShow(e){return this.fetchShow(e)}async getNode(e){return(await this.fetchShow(e))?.node??null}async getRow(e){const t=(await this.fetchShow(e))?.node;return t?{node_id:t.node_id,name:t.name,kind:t.kind,mode:t.mode,lifecycle:t.lifecycle,status:t.status,cwd:t.cwd,host_kind:t.host_kind??null,profile_id:t.profile_id??null,scopes:t.scopes??null,isolated:t.isolated===!0,parent:t.parent??null,created:t.created,intent:t.intent??null,frozen_at:t.frozen_at??null,terminal_reason:t.terminal_reason??null,pi_pid:t.pi_pid??null,pi_pid_identity:t.pi_pid_identity??null,window:t.window??null,tmux_session:t.tmux_session??null,pane:t.pane??null,fault:t.fault??null,streaming:t.streaming===!0}:null}async listNodes(e){const t=JSON.stringify(e?.status??null),o=this.rosterCache.get(t);if(o&&Date.now()-o.at<d)return o.value;const n=["node","inspect","list"];e?.status&&n.push("--status",(Array.isArray(e.status)?e.status:[e.status]).join(","));const i=await this.execJson(n),c=(r(i)&&Array.isArray(i.nodes)?i.nodes:[]).filter(_).map(l=>({...l,isolated:l.isolated===!0}));return this.rosterCache.set(t,{value:c,at:Date.now()}),c}async subscriptionsOf(e){return(await this.topology()).subscriptions.get(e)??[]}async subscribersOf(e){return(await this.topology()).subscribers.get(e)??[]}async view(e){const t=this.viewCache.get(e);if(t&&Date.now()-t.at<d)return t.value;const o=new Set([e]),n=[],i=(await this.subscriptionsOf(e)).map(a=>a.node_id);for(;i.length>0;){const a=i.shift();if(!o.has(a)){o.add(a),n.push(a);for(const c of await this.subscriptionsOf(a))o.has(c.node_id)||i.push(c.node_id)}}return this.viewCache.set(e,{value:n,at:Date.now()}),n}async ticketCountsForNodes(e){if(e.length===0)return{};const t=await this.execJson(["canvas","attention","map","--nodes",e.join(",")]);return r(t)&&r(t.counts)?t.counts:{}}async ticketCountsForView(e){const t=await this.execJson(["canvas","attention","map","--view",e]);return r(t)&&r(t.counts)?t.counts:{}}async hasActiveLiveSubscription(e){const t=(await this.subscriptionsOf(e)).filter(o=>o.active);for(const o of t){const n=await this.getRow(o.node_id);if(n&&(n.status==="active"||n.status==="idle"))return!0}return!1}}export{v as RemoteCanvasSource};
|
|
@@ -104,7 +104,7 @@ export interface DashboardRow {
|
|
|
104
104
|
transcriptLoaded?: boolean;
|
|
105
105
|
/** The source `NodeRow` this row was built from (dashboardRowFromNodeRow only).
|
|
106
106
|
* Carries every column listNodes already fetched — the runtime fields
|
|
107
|
-
* (host_kind/parent/
|
|
107
|
+
* (host_kind/parent/final_report/finalized_at/pi_pid) plus the
|
|
108
108
|
* mirrored identity fields (description/cycles) — so the snapshot handler
|
|
109
109
|
* reads them here instead of opening meta.json.
|
|
110
110
|
* Undefined for rows built by other paths (dashboardRowsFromSource). */
|
|
@@ -283,9 +283,6 @@ export interface NodeRuntime {
|
|
|
283
283
|
* Optional on the hydrated view (a fresh construction omits it); the row
|
|
284
284
|
* column is always present, defaulting null. */
|
|
285
285
|
intent?: ExitIntent;
|
|
286
|
-
/** Controller whose next inbox delivery permits this terminal node to sleep.
|
|
287
|
-
* Mutable durable runtime state: ancestry never implies a wait. */
|
|
288
|
-
waiting_for?: string | null;
|
|
289
286
|
frozen_at?: string | null;
|
|
290
287
|
respawn_failures?: number;
|
|
291
288
|
respawn_not_before?: string | null;
|
|
@@ -394,7 +391,6 @@ export interface NodeRow {
|
|
|
394
391
|
created: string;
|
|
395
392
|
/** Authoritative runtime columns (see NodeRuntime). */
|
|
396
393
|
intent: ExitIntent;
|
|
397
|
-
waiting_for: string | null;
|
|
398
394
|
frozen_at?: string | null;
|
|
399
395
|
respawn_failures?: number;
|
|
400
396
|
respawn_not_before?: string | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
1
2
|
export interface ControllerWait {
|
|
2
3
|
waitId: string;
|
|
3
4
|
nodeId: string;
|
|
@@ -10,9 +11,29 @@ export interface ControllerWait {
|
|
|
10
11
|
export declare function armControllerWait(nodeId: string, controllerId: string, now?: number): ControllerWait;
|
|
11
12
|
export declare function readControllerWait(nodeId: string): ControllerWait | null;
|
|
12
13
|
/** Clear only the wait that was observed for this sender; a rearm wins a race. */
|
|
13
|
-
export declare function settleMatchingControllerWait(nodeId: string, senderId: string | null, now: number, db?:
|
|
14
|
+
export declare function settleMatchingControllerWait(nodeId: string, senderId: string | null, now: number, db?: DatabaseSync): boolean;
|
|
14
15
|
/** Clear a matching wait only if its controller is no longer active or idle. */
|
|
15
|
-
export declare function settleDeadControllerWait(nodeId: string, controllerId: string, now: number, db?:
|
|
16
|
+
export declare function settleDeadControllerWait(nodeId: string, controllerId: string, now: number, db?: DatabaseSync): boolean;
|
|
16
17
|
/** Live rows drive controller-death reconciliation without relying on the retired node columns. */
|
|
17
18
|
export declare function liveControllerWaits(): ControllerWait[];
|
|
19
|
+
/** A node's live wait on a watched object, ended by the event its kind declares `endsWait` (`waits`, kind `object`). */
|
|
20
|
+
export interface ObjectWait {
|
|
21
|
+
waitId: string;
|
|
22
|
+
nodeId: string;
|
|
23
|
+
objectId: string;
|
|
24
|
+
createdAt: number;
|
|
25
|
+
}
|
|
26
|
+
/** Arm a node's wait on an object, inside the caller's write transaction; an existing live wait on the same object stands. */
|
|
27
|
+
export declare function armObjectWait(db: DatabaseSync, nodeId: string, objectId: string, now?: number): void;
|
|
28
|
+
/** Settle every live wait on `objectId` (its ending event was published), or only `nodeId`'s (it stopped watching). */
|
|
29
|
+
export declare function settleObjectWaits(db: DatabaseSync, objectId: string, nodeId?: string | null, now?: number): number;
|
|
30
|
+
/** A node that ended waits on nothing: settle all of its live object waits. */
|
|
31
|
+
export declare function settleNodeObjectWaits(db: DatabaseSync, nodeId: string, now?: number): number;
|
|
32
|
+
/** Every node's live object waits with the watched object's kind and name, for the roster. A deleted object
|
|
33
|
+
* settles its waits on publish, so none should dangle; a wait whose object row is gone is skipped rather than shown nameless. */
|
|
34
|
+
export declare function liveObjectWaitsNamed(db?: DatabaseSync): Array<ObjectWait & {
|
|
35
|
+
objectType: string;
|
|
36
|
+
objectName: string;
|
|
37
|
+
}>;
|
|
38
|
+
export declare function hasLiveObjectWait(nodeId: string): boolean;
|
|
18
39
|
export declare function hasLiveControllerWait(nodeId: string): boolean;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
var
|
|
2
|
-
VALUES (?, ?, 'controller', ?, ?, ?, NULL)`).run(
|
|
1
|
+
var c=Object.defineProperty;var r=(t,e)=>c(t,"name",{value:e,configurable:!0});import{prepared as i}from"../sqlite-statements.js";import{randomBytes as l}from"node:crypto";import{openDb as o,withCanvasWrite as N}from"./db.js";function _(t){if(t.kind!=="controller")throw new Error(`wait ${t.wait_id} is not a controller wait`);return{waitId:t.wait_id,nodeId:t.node_id,controllerId:t.target_id,generation:t.generation,createdAt:t.created_at,satisfiedAt:t.satisfied_at}}r(_,"waitFrom");function E(){let t;do t=l(16).toString("hex");while(t==="00000000000000000000000000000000");return t}r(E,"newWaitId");function g(t,e,a=Date.now()){return N(n=>{i(n,"UPDATE waits SET satisfied_at = ? WHERE node_id = ? AND kind = 'controller' AND satisfied_at IS NULL").run(a,t);const d=Number(i(n,"SELECT COALESCE(MAX(generation), 0) AS generation FROM waits WHERE node_id = ? AND kind = 'controller'").get(t).generation)+1,s={wait_id:E(),node_id:t,kind:"controller",target_id:e,generation:d,created_at:a,satisfied_at:null};return i(n,`INSERT INTO waits (wait_id, node_id, kind, target_id, generation, created_at, satisfied_at)
|
|
2
|
+
VALUES (?, ?, 'controller', ?, ?, ?, NULL)`).run(s.wait_id,s.node_id,s.target_id,s.generation,s.created_at),_(s)})}r(g,"armControllerWait");function A(t){const e=i(o(),"SELECT * FROM waits WHERE node_id = ? AND kind = 'controller' AND satisfied_at IS NULL").get(t);return e===void 0?null:_(e)}r(A,"readControllerWait");function D(t,e,a,n=o()){if(e===null)return!1;const d=i(n,"SELECT wait_id, generation FROM waits WHERE node_id = ? AND kind = 'controller' AND target_id = ? AND satisfied_at IS NULL").get(t,e);return d===void 0?!1:Number(i(n,"UPDATE waits SET satisfied_at = ? WHERE wait_id = ? AND generation = ? AND satisfied_at IS NULL").run(a,d.wait_id,d.generation).changes)===1}r(D,"settleMatchingControllerWait");function S(t,e,a,n=o()){const d=i(n,`SELECT w.wait_id, w.generation, target.status AS controller_status
|
|
3
3
|
FROM waits w LEFT JOIN nodes target ON target.node_id = w.target_id
|
|
4
|
-
WHERE w.node_id = ? AND w.kind = 'controller' AND w.target_id = ? AND w.satisfied_at IS NULL`).get(t,e);return
|
|
4
|
+
WHERE w.node_id = ? AND w.kind = 'controller' AND w.target_id = ? AND w.satisfied_at IS NULL`).get(t,e);return d===void 0||d.controller_status==="active"||d.controller_status==="idle"?!1:Number(i(n,"UPDATE waits SET satisfied_at = ? WHERE wait_id = ? AND generation = ? AND satisfied_at IS NULL").run(a,d.wait_id,d.generation).changes)===1}r(S,"settleDeadControllerWait");function T(){return i(o(),"SELECT * FROM waits WHERE kind = 'controller' AND satisfied_at IS NULL").all().map(_)}r(T,"liveControllerWaits");function R(t,e,a,n=Date.now()){i(t,`INSERT INTO waits (wait_id, node_id, kind, target_id, generation, created_at, satisfied_at)
|
|
5
|
+
SELECT ?, ?, 'object', ?, 1, ?, NULL
|
|
6
|
+
WHERE NOT EXISTS (SELECT 1 FROM waits WHERE node_id = ? AND kind = 'object' AND target_id = ? AND satisfied_at IS NULL)`).run(E(),e,a,n,e,a)}r(R,"armObjectWait");function W(t,e,a=null,n=Date.now()){return Number(a===null?i(t,"UPDATE waits SET satisfied_at = ? WHERE kind = 'object' AND target_id = ? AND satisfied_at IS NULL").run(n,e).changes:i(t,"UPDATE waits SET satisfied_at = ? WHERE kind = 'object' AND target_id = ? AND node_id = ? AND satisfied_at IS NULL").run(n,e,a).changes)}r(W,"settleObjectWaits");function I(t,e,a=Date.now()){return Number(i(t,"UPDATE waits SET satisfied_at = ? WHERE kind = 'object' AND node_id = ? AND satisfied_at IS NULL").run(a,e).changes)}r(I,"settleNodeObjectWaits");function O(t=o()){return i(t,`SELECT w.wait_id, w.node_id, w.target_id, w.created_at, o.type AS object_type, o.name AS object_name
|
|
7
|
+
FROM waits w JOIN objects o ON o.id = w.target_id
|
|
8
|
+
WHERE w.kind = 'object' AND w.satisfied_at IS NULL AND o.deleted_at IS NULL
|
|
9
|
+
ORDER BY w.node_id, w.created_at, w.rowid`).all().map(e=>({waitId:e.wait_id,nodeId:e.node_id,objectId:e.target_id,createdAt:e.created_at,objectType:e.object_type,objectName:e.object_name}))}r(O,"liveObjectWaitsNamed");function U(t){return i(o(),"SELECT 1 FROM waits WHERE node_id = ? AND kind = 'object' AND satisfied_at IS NULL LIMIT 1").get(t)!==void 0}r(U,"hasLiveObjectWait");function m(t){return i(o(),`SELECT 1 FROM waits w JOIN nodes target ON target.node_id = w.target_id
|
|
5
10
|
WHERE w.node_id = ? AND w.kind = 'controller' AND w.satisfied_at IS NULL
|
|
6
|
-
AND target.status IN ('active', 'idle') LIMIT 1`).get(t)!==void 0}r(
|
|
11
|
+
AND target.status IN ('active', 'idle') LIMIT 1`).get(t)!==void 0}r(m,"hasLiveControllerWait");export{g as armControllerWait,R as armObjectWait,m as hasLiveControllerWait,U as hasLiveObjectWait,T as liveControllerWaits,O as liveObjectWaitsNamed,A as readControllerWait,S as settleDeadControllerWait,D as settleMatchingControllerWait,I as settleNodeObjectWaits,W as settleObjectWaits};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var x=Object.defineProperty;var m=(e,t)=>x(e,"name",{value:t,configurable:!0});import{prepared as a}from"../sqlite-statements.js";import{createHash as J,randomBytes as P,timingSafeEqual as q}from"node:crypto";import{CrtrError as j,usage as p}from"../errors.js";import{ExitCode as O}from"../../types.js";import{createObject as F,liveDocumentNamed as M}from"./objects.js";import{invalidateCustomKind as D}from"./kinds/registry.js";import{customReachable as k,mayActAs as L}from"./access.js";import{publish as N,HOP_LIMIT as B,HopLimitError as K}from"./events.js";import{checkoutsForCwd as T}from"./repo-sync/repos.js";import{onQueueSignal as V,signalQueue as R}from"./receivers.js";import{receiverEndpoint as G,isPublicReceiverAddress as Q}from"./receiver-url.js";import{isIP as Y}from"node:net";import{KIND as H,LOCAL as I,MAX_KIND_EVENTS as z,RESERVED_KIND_ROOTS as X,eventCatalogReasons as Z}from"../plugin-objects-manifest.js";const ee=["public","friends","private"];function w(e,t){throw new j(e,t,O.GENERAL)}m(w,"conflict");function A(e){return J("sha256").update(e).digest("hex")}m(A,"hash");function W(){return`crtr_pub_${P(32).toString("base64url")}`}m(W,"issueToken");function te(e){if(e==null)return null;if(e==="queue")return JSON.stringify(e);if(typeof e!="object"||Array.isArray(e))throw p("invalid custom receiver; expected queue, an https url, or argv and absolute cwd");if("url"in e&&typeof e.url=="string")try{const s=G(e.url).hostname.replace(/^\[|\]$/g,"");if(Y(s)&&!Q(s))throw new Error("non-public IP address");return JSON.stringify({url:e.url})}catch(t){throw p(`invalid custom receiver: ${String(t)}`)}if("argv"in e&&Array.isArray(e.argv)&&e.argv.length>0&&e.argv.every(t=>typeof t=="string")&&typeof e.cwd=="string"&&e.cwd.startsWith("/"))return JSON.stringify(e);throw p("invalid custom receiver; url must be https and argv requires a command and absolute cwd")}m(te,"receiverJson");function ge(e,t,s){const{kind:n,name:i,owner:o,description:d,events:l,receiver:c,rotateToken:h,privacy:f}=s;if(f!==void 0&&!ee.includes(f))throw p(`invalid privacy ${JSON.stringify(f)}`);if(L(e,t,o)||w("forbidden",`cannot act as ${o.id}`),!H.test(n)||X.includes(n.split(".")[0]))throw p(`invalid or reserved kind ${n}`);if(!i.trim()||i!==i.trim()||i.includes("\0"))throw p(`invalid object name ${JSON.stringify(i)}`);const E=te(c);if(c!=="queue"&&c!==null&&c!==void 0&&"argv"in c&&t.class!=="person"&&t.class!=="node"&&w("argv_forbidden","argv receiver requires a person or node principal"),l===void 0){const u=a(e,"SELECT source FROM object_kinds WHERE name = ?").get(n);if(u?.source!=="manifest"&&u?.source!=="peer")throw p("events is required unless the kind is declared by an installed plugin")}else if(Object.keys(l).length>z||Object.entries(l).some(([u,g])=>!I.test(u)||!Number.isInteger(g?.level)||g.level<1||g.level>10||g.supersedes!==void 0&&typeof g.supersedes!="boolean"))throw p("invalid custom event catalog");const r=M(e,o.rowid,i);if(r&&!k(e,t,r)&&w("forbidden",`cannot act as ${o.id}`),r&&t.class!=="person"&&t.class!=="node"){const u=a(e,"SELECT receiver_json FROM custom_objects WHERE object = ?").get(r.rowid)?.receiver_json;u&&typeof JSON.parse(u)=="object"&&"argv"in JSON.parse(u)&&w("argv_forbidden","argv receiver requires a person or node principal")}r&&r.type!==n&&w("name_taken",`${o.name}/${i} is already held by ${r.id}`);const v=l===void 0?null:ne(e,{name:n,owner:o,description:d,events:l,read:null,source:"register"});if(!r){const u=F(e,{type:n,kind:null,owner:o.rowid,privacy:f??"public",name:i,preview:""}),g=W();return a(e,`INSERT INTO custom_objects (object, description, receiver_json, token_hash)
|
|
1
|
+
var x=Object.defineProperty;var m=(e,t)=>x(e,"name",{value:t,configurable:!0});import{prepared as a}from"../sqlite-statements.js";import{createHash as J,randomBytes as P,timingSafeEqual as q}from"node:crypto";import{CrtrError as j,usage as p}from"../errors.js";import{ExitCode as O}from"../../types.js";import{createObject as F,liveDocumentNamed as M}from"./objects.js";import{invalidateCustomKind as D}from"./kinds/registry.js";import{customReachable as k,mayActAs as L}from"./access.js";import{publish as N,HOP_LIMIT as B,HopLimitError as K}from"./events.js";import{checkoutsForCwd as T}from"./repo-sync/repos.js";import{onQueueSignal as V,signalQueue as R}from"./receivers.js";import{receiverEndpoint as G,isPublicReceiverAddress as Q}from"./receiver-url.js";import{isIP as Y}from"node:net";import{KIND as H,LOCAL as I,MAX_KIND_EVENTS as z,RESERVED_KIND_ROOTS as X,eventCatalogReasons as Z}from"../plugin-objects-manifest.js";const ee=["public","friends","private"];function w(e,t){throw new j(e,t,O.GENERAL)}m(w,"conflict");function A(e){return J("sha256").update(e).digest("hex")}m(A,"hash");function W(){return`crtr_pub_${P(32).toString("base64url")}`}m(W,"issueToken");function te(e){if(e==null)return null;if(e==="queue")return JSON.stringify(e);if(typeof e!="object"||Array.isArray(e))throw p("invalid custom receiver; expected queue, an https url, or argv and absolute cwd");if("url"in e&&typeof e.url=="string")try{const s=G(e.url).hostname.replace(/^\[|\]$/g,"");if(Y(s)&&!Q(s))throw new Error("non-public IP address");return JSON.stringify({url:e.url})}catch(t){throw p(`invalid custom receiver: ${String(t)}`)}if("argv"in e&&Array.isArray(e.argv)&&e.argv.length>0&&e.argv.every(t=>typeof t=="string")&&typeof e.cwd=="string"&&e.cwd.startsWith("/"))return JSON.stringify(e);throw p("invalid custom receiver; url must be https and argv requires a command and absolute cwd")}m(te,"receiverJson");function ge(e,t,s){const{kind:n,name:i,owner:o,description:d,events:l,receiver:c,rotateToken:h,privacy:f}=s;if(f!==void 0&&!ee.includes(f))throw p(`invalid privacy ${JSON.stringify(f)}`);if(L(e,t,o)||w("forbidden",`cannot act as ${o.id}`),!H.test(n)||X.includes(n.split(".")[0]))throw p(`invalid or reserved kind ${n}`);if(!i.trim()||i!==i.trim()||i.includes("\0"))throw p(`invalid object name ${JSON.stringify(i)}`);const E=te(c);if(c!=="queue"&&c!==null&&c!==void 0&&"argv"in c&&t.class!=="person"&&t.class!=="node"&&w("argv_forbidden","argv receiver requires a person or node principal"),l===void 0){const u=a(e,"SELECT source FROM object_kinds WHERE name = ?").get(n);if(u?.source!=="manifest"&&u?.source!=="peer")throw p("events is required unless the kind is declared by an installed plugin")}else if(Object.keys(l).length>z||Object.entries(l).some(([u,g])=>!I.test(u)||!Number.isInteger(g?.level)||g.level<1||g.level>10||g.supersedes!==void 0&&typeof g.supersedes!="boolean"||g.ends_wait!==void 0&&typeof g.ends_wait!="boolean"))throw p("invalid custom event catalog");const r=M(e,o.rowid,i);if(r&&!k(e,t,r)&&w("forbidden",`cannot act as ${o.id}`),r&&t.class!=="person"&&t.class!=="node"){const u=a(e,"SELECT receiver_json FROM custom_objects WHERE object = ?").get(r.rowid)?.receiver_json;u&&typeof JSON.parse(u)=="object"&&"argv"in JSON.parse(u)&&w("argv_forbidden","argv receiver requires a person or node principal")}r&&r.type!==n&&w("name_taken",`${o.name}/${i} is already held by ${r.id}`);const v=l===void 0?null:ne(e,{name:n,owner:o,description:d,events:l,read:null,source:"register"});if(!r){const u=F(e,{type:n,kind:null,owner:o.rowid,privacy:f??"public",name:i,preview:""}),g=W();return a(e,`INSERT INTO custom_objects (object, description, receiver_json, token_hash)
|
|
2
2
|
VALUES (?, ?, ?, ?)`).run(u.rowid,d,E,A(g)),t.class==="node"&&a(e,"UPDATE custom_objects SET registered_by = ? WHERE object = ?").run(t.node.id,u.rowid),E!==null&&a(e,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(u.rowid),N(e,{event:"created",object:u,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{}},$=>T(e,$)),{object:u,token:g,created:!0,changed:[]}}const y=a(e,"SELECT description, receiver_json FROM custom_objects WHERE object = ?").get(r.rowid);y||w("name_taken",`${o.name}/${i} is not a custom object`);const _=[];if(y.description!==d&&_.push("description"),y.receiver_json!==E&&_.push("receiver"),v?.changed.includes("events")&&_.push("events"),v?.changed.includes("description")&&!_.includes("description")&&_.push("description"),f!==void 0&&r.privacy!==f&&_.push("privacy"),h&&_.push("token"),_.length===0)return{object:r,created:!1,changed:_};const S=h?W():void 0,b=_.includes("privacy")?{...r,privacy:f}:r;if(b!==r&&a(e,"UPDATE objects SET privacy = ? WHERE rowid = ?").run(f,r.rowid),a(e,"UPDATE custom_objects SET description = ?, receiver_json = ?, token_hash = COALESCE(?, token_hash) WHERE object = ?").run(d,E,S===void 0?null:A(S),r.rowid),a(e,"UPDATE objects SET reception = ? WHERE rowid = ?").run(E===null?0:2,r.rowid),y.receiver_json!==E){const u=Date.now();if(E===null){const g=a(e,`UPDATE deliveries SET canceled_at = ?, updated_at = ?
|
|
3
3
|
WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(u,u,r.id);a(e,"UPDATE custom_objects SET dropped = dropped + ? WHERE object = ?").run(g.changes,r.rowid)}else a(e,`UPDATE deliveries SET next_attempt_at = ?, updated_at = ?
|
|
4
4
|
WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(E==='"queue"'?null:u,u,r.id);R(r.id)}return h&&R(r.id),N(e,{event:"configured",object:b,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{changed:_.join(",")}},u=>T(e,u)),{object:b,...S===void 0?{}:{token:S},created:!1,changed:_}}m(ge,"registerCustomObject");function ne(e,t){const{name:s,owner:n,description:i,events:o,read:d,source:l}=t;if(!H.test(s))throw p(`invalid kind ${s}`);if(Z(o,s).length>0)throw p("invalid custom event catalog");const c=a(e,"SELECT owner, description, events_json, read_json, source, declared FROM object_kinds WHERE name = ?").get(s),h=U(o),f=l==="manifest"&&d!==null?JSON.stringify({command:d.command}):null,E=new Date().toISOString();if(!c)return a(e,`INSERT INTO object_kinds (name, owner, description, events_json, created_at, updated_at, read_json, source, declared)
|
|
5
5
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)`).run(s,n.rowid,i,h,E,E,f,l),D(e,s),{outcome:"inserted",changed:[]};const r=c.owner===n.rowid;if(l==="peer"){if(c.source!=="peer")return{outcome:"skipped",changed:[]}}else if(c.source!=="peer"&&(r||w("kind_owned",`${s} belongs to another owner`),l==="register"&&c.source==="manifest"))return{outcome:"skipped",changed:[]};const v=[];return U(JSON.parse(c.events_json))!==h&&v.push("events"),c.description!==i&&v.push("description"),(c.read_json??null)!==f&&v.push("read"),v.length===0&&r&&c.source===l&&c.declared===1?{outcome:"unchanged",changed:v}:(a(e,"UPDATE object_kinds SET owner = ?, description = ?, events_json = ?, read_json = ?, source = ?, declared = 1, updated_at = ? WHERE name = ?").run(n.rowid,i,h,f,l,E,s),D(e,s),{outcome:"updated",changed:v})}m(ne,"upsertKind");function he(e,t,s,n){const i=a(e,"SELECT events_json FROM object_kinds WHERE name = ?").get(t.type);if(!i||t.deleted_at!==null)throw p(`${t.id} is not a live custom object`);const o=JSON.parse(i.events_json);if(!I.test(n.event)||!Object.hasOwn(o,n.event))throw new j("unknown_event",`${t.type} declares ${Object.keys(o).join(", ")}, not ${n.event}`,O.USAGE,{declared:Object.keys(o),event:n.event});if(typeof n.summary!="string")throw p("summary must be text");const d=n.state===void 0?null:JSON.stringify(n.state);if(n.state!==void 0&&d===void 0)throw p("state must be JSON-serializable");if(Buffer.byteLength(JSON.stringify({summary:n.summary,...n.state===void 0?{}:{state:n.state}}))>64*1024)throw new j("body_too_large","custom event summary and state exceed 64 KiB",O.USAGE);if(d!==null&&Buffer.byteLength(d)>64*1024)throw p("state exceeds 64 KiB");const l=n.cause??null,c=l===null?null:a(e,`SELECT depth FROM messages WHERE message_id = ?
|
|
6
6
|
UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(l,l);if(l!==null&&c===void 0)throw new j("unknown_cause",`no message ${l}`,O.USAGE);if(c&&c.depth>=B)throw new K(c.depth+1);const h=new Date().toISOString();return a(e,"UPDATE custom_objects SET summary = ?, state_json = ?, published_at = ? WHERE object = ?").run(n.summary,d,h,t.rowid),a(e,"UPDATE objects SET preview = ? WHERE rowid = ?").run(n.summary.split(`
|
|
7
|
-
`)[0],t.rowid),N(e,{event:`${t.type}.${n.event}`,object:t,actor:s,attrs:{},messageBody:{summary:n.summary,...n.state===void 0?{}:{state:n.state}},...n.cause===void 0?{}:{cause:n.cause}},f=>T(e,f))}m(he,"publishCustom");function ve(e,t,s){if(t.deleted_at!==null)return;N(e,{event:"deleted",object:t,actor:s,attrs:{}},i=>T(e,i));const n=new Date().toISOString();a(e,"UPDATE objects SET deleted_at = ?, reception = 0 WHERE rowid = ?").run(n,t.rowid),a(e,"UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL").run(Date.now(),Date.now(),t.id),R(t.id)}m(ve,"unregisterCustomObject");function U(e){return JSON.stringify(Object.fromEntries(Object.entries(e).sort(([t],[s])=>t.localeCompare(s)).map(([t,s])=>[t,{level:s.level,...s.supersedes===void 0?{}:{supersedes:s.supersedes}}])))}m(U,"canonicalEvents");function we(e,t,s=0){return C(e,t.id,s,"pending")}m(we,"pendingFor");function ye(e,t){const s=a(e,"SELECT destination_id FROM deliveries WHERE delivery_id = ?").get(t);return s?C(e,s.destination_id,-1,"handed_off").find(n=>n.delivery_id===t)??null:null}m(ye,"pushLineFor");function C(e,t,s,n){return a(e,`SELECT d.delivery_id, m.message_id, m.seq, m.event, m.level, m.object_id, m.actor_id, m.body_json, m.body_ref, m.depth, m.created_at,
|
|
7
|
+
`)[0],t.rowid),N(e,{event:`${t.type}.${n.event}`,object:t,actor:s,attrs:{},messageBody:{summary:n.summary,...n.state===void 0?{}:{state:n.state}},...n.cause===void 0?{}:{cause:n.cause}},f=>T(e,f))}m(he,"publishCustom");function ve(e,t,s){if(t.deleted_at!==null)return;N(e,{event:"deleted",object:t,actor:s,attrs:{}},i=>T(e,i));const n=new Date().toISOString();a(e,"UPDATE objects SET deleted_at = ?, reception = 0 WHERE rowid = ?").run(n,t.rowid),a(e,"UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL").run(Date.now(),Date.now(),t.id),R(t.id)}m(ve,"unregisterCustomObject");function U(e){return JSON.stringify(Object.fromEntries(Object.entries(e).sort(([t],[s])=>t.localeCompare(s)).map(([t,s])=>[t,{level:s.level,...s.supersedes===void 0?{}:{supersedes:s.supersedes},...s.ends_wait===void 0?{}:{ends_wait:s.ends_wait}}])))}m(U,"canonicalEvents");function we(e,t,s=0){return C(e,t.id,s,"pending")}m(we,"pendingFor");function ye(e,t){const s=a(e,"SELECT destination_id FROM deliveries WHERE delivery_id = ?").get(t);return s?C(e,s.destination_id,-1,"handed_off").find(n=>n.delivery_id===t)??null:null}m(ye,"pushLineFor");function C(e,t,s,n){return a(e,`SELECT d.delivery_id, m.message_id, m.seq, m.event, m.level, m.object_id, m.actor_id, m.body_json, m.body_ref, m.depth, m.created_at,
|
|
8
8
|
o.type, o.name, owner.id AS owner_id
|
|
9
9
|
FROM deliveries d JOIN messages m ON m.message_id = d.message_id JOIN objects o ON o.id = m.object_id
|
|
10
10
|
LEFT JOIN objects owner ON owner.rowid = o.owner
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
var
|
|
2
|
-
AND name = ? AND rowid != ? LIMIT 1`).get(e.name,e.rowid)?`${e.type}:${e.name}`:e.name}l(
|
|
3
|
-
`)[0]:`${e.event} ${e.object.type} ${e.object.name}`),...a?.bodyRef===void 0?{}:{bodyRef:a.bodyRef},...a?.disposition===void 0?{}:{disposition:a.disposition},senderNodeId:e.actor?.type==="node"?e.actor.id:null,senderName:e.actor===null||e.actor===void 0?null:e.actor.home_machine?`${e.actor.name} on ${k(e.actor.home_machine)}`:e.actor.name,bodyJson:f}}l(
|
|
1
|
+
var x=Object.defineProperty;var l=(n,e)=>x(n,"name",{value:e,configurable:!0});import{prepared as h}from"../sqlite-statements.js";import{EventEmitter as $}from"node:events";const S=new $;S.setMaxListeners(0);function ue(n,e){const o=l(s=>e(s),"listener");return S.on(n,o),()=>S.off(n,o)}l(ue,"subscribeDocumentEvents");import{evalCondition as B}from"../predicate.js";import{finalizeNodeMail as J,insertMessage as K}from"../feed/messages.js";import{linkAuthorizes as U,ownerChain as G,readCheck as q}from"./access.js";import{getByRowid as z}from"./objects.js";import{appScopesNow as X,publishLookups as Y,readerForObject as P}from"./reader.js";import{configFromJson as C}from"./watches.js";import{CrtrError as L}from"../errors.js";import{ExitCode as M}from"../../types.js";import{eventSpec as V,kindOf as D}from"./kinds/registry.js";import{machineNameOf as k}from"../peer/names.js";import{settleObjectWaits as Q}from"../canvas/waits.js";import{RECEIVERS as F}from"./receivers.js";import{DEFAULT_LISTEN as W}from"./types.js";function Z(n,e){if(n.level===void 0||n.level===e.level||e.levels?.includes(n.level))return n.level??e.level;const o=[e.level,...e.levels??[]].filter((s,t,i)=>i.indexOf(s)===t).sort((s,t)=>s-t);throw new L("invalid_level",`${n.object.type} ${n.event} publishes at level ${o.join(", ")}, not ${n.level}`,M.USAGE,{kind:n.object.type,event:n.event,level:n.level,allowed:o})}l(Z,"resolveLevel");function ee(n,e){const o=n.split("."),s=e.split(".");for(let t=0;t<o.length;t++){if(o[t]===">")return t===o.length-1&&s.length>t;if(t>=s.length||o[t]!=="*"&&o[t]!==s[t])return!1}return o.length===s.length}l(ee,"matchSubject");const H={"next-turn":1,"after-turn":2,steer:3,interrupt:4};function te(n,e){let o=null;for(const s of n){let t=null;for(const i of s)i.threshold<=e&&(!t||i.threshold>t.threshold)&&(t=i);t&&(!o||H[t.delivery]>H[o])&&(o=t.delivery)}return o}l(te,"resolveDelivery");function ve(n,e,o){if(e.config)return e.config;const s=h(n,"SELECT listen_json FROM objects WHERE rowid = ?").get(o.rowid);return s?.listen_json?C(s.listen_json):W}l(ve,"listenConfigFor");function ne(n,e){return e.inboxKind??(n.level>=7?"urgent":"update")}l(ne,"inboxKind");function oe(n,e){return e.type==="node"?e.id:h(n,`SELECT 1 FROM objects WHERE type IN ('person','app','repo','profile')
|
|
2
|
+
AND name = ? AND rowid != ? LIMIT 1`).get(e.name,e.rowid)?`${e.type}:${e.name}`:e.name}l(oe,"ownerHandleOf");function se(n,e,o){const s=e.object.owner===null?null:h(n,"SELECT * FROM objects WHERE rowid = ?").get(e.object.owner),t=s?.id??null,i=s?oe(n,s):null;return{event:e.event,level:e.level,object:{id:e.object.id,type:e.object.type,kind:e.object.kind,owner:t,owner_handle:i,name:e.object.name,...e.object.home_machine?{machine:k(e.object.home_machine),machine_id:e.object.home_machine}:{}},actor:e.actor?.id??null,...e.actor?.home_machine?{actor_machine:k(e.actor.home_machine),actor_machine_id:e.actor.home_machine}:{},attrs:o,...e.pointer===void 0?{}:{pointer:e.pointer}}}l(se,"pushFields");function ie(n,e,o,s){const t=se(n,e,s),i=e.messageBody!==void 0&&o.carriesBody===!0,f=i?typeof e.messageBody=="object"&&e.messageBody!==null&&!Array.isArray(e.messageBody)?{...e.messageBody,push:t}:{body:e.messageBody,push:t}:{push:t},d=i?e.messageBody:null,a=d?.bodyRef===void 0?null:d;return{kind:ne(e,o),label:a?.label??(typeof d?.label=="string"?d.label:e.object.type.includes(".")&&typeof e.messageBody?.summary=="string"?e.messageBody.summary.split(`
|
|
3
|
+
`)[0]:`${e.event} ${e.object.type} ${e.object.name}`),...a?.bodyRef===void 0?{}:{bodyRef:a.bodyRef},...a?.disposition===void 0?{}:{disposition:a.disposition},senderNodeId:e.actor?.type==="node"?e.actor.id:null,senderName:e.actor===null||e.actor===void 0?null:e.actor.home_machine?`${e.actor.name} on ${k(e.actor.home_machine)}`:e.actor.name,bodyJson:f}}l(ie,"messageInput");function re(n,e,o,s,t){if(!o.supersedes)return[{attrs:e.attrs,destinations:[...s]}];const i=h(n,`SELECT d.delivery_id, d.message_id, d.state, m.body_json FROM deliveries d
|
|
4
4
|
JOIN messages m ON m.message_id = d.message_id
|
|
5
5
|
WHERE d.destination_id = ? AND d.state IN ('pending', 'handed_off')
|
|
6
6
|
AND d.canceled_at IS NULL AND m.event = ? AND m.object_id = ?
|
|
7
|
-
ORDER BY m.seq`),f=h(n,"DELETE FROM deliveries WHERE delivery_id = ?"),d=h(n,"SELECT * FROM objects WHERE id = ?"),a=new Map,_=new Set;for(const m of s){const w=t(m),O=d.get(w),I=O===void 0?void 0:D(n,O.type),j=i.all(w,e.event,e.object.id),b=new Set;for(const c of j)c.state==="handed_off"?I?.supersedeClaimed?.(n,c.delivery_id)&&b.add(c.delivery_id):(f.run(c.delivery_id),_.add(c.message_id));const y=j.filter(c=>c.state==="pending"||b.has(c.delivery_id)).at(-1),R=y?.message_id??null,r=a.get(R)??{prior:y?.body_json?JSON.parse(y.body_json).push?.attrs??null:null,destinations:[]};r.destinations.push(m),a.set(R,r)}const N=h(n,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)");for(const m of _)N.run(m,m);return[...a.values()].map(m=>({attrs:o.merge?o.merge(m.prior,e):e.attrs,destinations:m.destinations}))}l(
|
|
8
|
-
UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(i,i);if(i!==null&&f===void 0)throw new L("unknown_cause",`no message ${i}`,M.USAGE,{cause:i});const d=f===null?0:f.depth+1;if(d>T)throw new
|
|
7
|
+
ORDER BY m.seq`),f=h(n,"DELETE FROM deliveries WHERE delivery_id = ?"),d=h(n,"SELECT * FROM objects WHERE id = ?"),a=new Map,_=new Set;for(const m of s){const w=t(m),O=d.get(w),I=O===void 0?void 0:D(n,O.type),j=i.all(w,e.event,e.object.id),b=new Set;for(const c of j)c.state==="handed_off"?I?.supersedeClaimed?.(n,c.delivery_id)&&b.add(c.delivery_id):(f.run(c.delivery_id),_.add(c.message_id));const y=j.filter(c=>c.state==="pending"||b.has(c.delivery_id)).at(-1),R=y?.message_id??null,r=a.get(R)??{prior:y?.body_json?JSON.parse(y.body_json).push?.attrs??null:null,destinations:[]};r.destinations.push(m),a.set(R,r)}const N=h(n,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)");for(const m of _)N.run(m,m);return[...a.values()].map(m=>({attrs:o.merge?o.merge(m.prior,e):e.attrs,destinations:m.destinations}))}l(re,"supersede");function ce(n,e,o,s,t){const i=z(n,s);if(i===null||i.type!=="app")return!1;const f=h(n,"SELECT 1 FROM peer_stubs WHERE object = ?").get(e.rowid)!==void 0,d=f?null:X(n,i,t);return(f||d!==null)&&U(n,i,d,e,o)}l(ce,"linkDelivers");const T=8;class de extends L{static{l(this,"HopLimitError")}constructor(e){super("hop_limit",`event depth ${e} exceeds ${T}`,M.GENERAL,{depth:e,limit:T})}}function Se(n,e,o=()=>[]){const s=V(n,e.object.type,e.event),t={...e,level:Z(e,s)};t.object.type==="document"&&(t.event==="edited"||t.event==="deleted")&&queueMicrotask(()=>S.emit(t.object.id,t.event));const i=e.cause??null,f=i===null?null:h(n,`SELECT depth FROM messages WHERE message_id = ?
|
|
8
|
+
UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(i,i);if(i!==null&&f===void 0)throw new L("unknown_cause",`no message ${i}`,M.USAGE,{cause:i});const d=f===null?0:f.depth+1;if(d>T)throw new de(d);(s.endsWait===!0||t.event==="deleted")&&Q(n,t.object.id);const a=G(n,t.object),_=`w.watcher, o.id AS watcher_id, o.type AS watcher_type, o.reception, s.pattern, s.filter_json,
|
|
9
9
|
COALESCE(s.config_json, o.listen_json) AS config_json, w.link_app`,N=n.prepare(s.durable?`SELECT ${_} FROM watches w JOIN watch_subscriptions s ON s.watch = w.rowid
|
|
10
10
|
JOIN objects o ON o.rowid = w.watcher WHERE w.object = ? AND w.watcher != ? AND o.reception >= 1`:`SELECT ${_} FROM objects o INDEXED BY objects_receiving CROSS JOIN watches w CROSS JOIN watch_subscriptions s
|
|
11
|
-
WHERE o.reception = 2 AND w.watcher = o.rowid AND s.watch = w.rowid AND w.object = ? AND w.watcher != ?`).all(t.object.rowid,t.actor?.rowid??-1);if(N.length===0)return{delivered:0,message_id:null,depth:d};const m={event:t.event,level:t.level,id:t.object.id,type:t.object.type,kind:t.object.kind,actor:t.actor?.id??null,...t.attrs},w=new Map;for(const r of N){if(t.onlyWatchers!==void 0&&!t.onlyWatchers.includes(r.watcher_id)||!
|
|
11
|
+
WHERE o.reception = 2 AND w.watcher = o.rowid AND s.watch = w.rowid AND w.object = ? AND w.watcher != ?`).all(t.object.rowid,t.actor?.rowid??-1);if(N.length===0)return{delivered:0,message_id:null,depth:d};const m={event:t.event,level:t.level,id:t.object.id,type:t.object.type,kind:t.object.kind,actor:t.actor?.id??null,...t.attrs},w=new Map;for(const r of N){if(t.onlyWatchers!==void 0&&!t.onlyWatchers.includes(r.watcher_id)||!ee(r.pattern,t.event)||r.filter_json!==null&&!B(JSON.parse(r.filter_json),m))continue;const c=w.get(r.watcher)??{id:r.watcher_id,type:r.watcher_type,reception:r.reception,configs:[],linkApp:r.link_app};c.configs.push(r.config_json?C(r.config_json):W),w.set(r.watcher,c)}const O=(a.length?a[a.length-1]:t.object).type==="repo",I=l(()=>[],"noRepos"),j=q(n,t.object),b=Y(n),y=[];for(const r of w.values()){const c=h(n,"SELECT * FROM objects WHERE id = ?").get(r.id),u=D(n,c.type),v=u.receiverFor?u.receiverFor(n,c):u.receiver;if(v===void 0)continue;const g=te(r.configs,t.level);if(g===null)continue;const E=P(n,c,O?o:I,b);if(E.scopes===null&&(E.class==="node"||E.owner.type!=="person"))continue;const p=r.linkApp!==null&&!j(E)&&ce(n,t.object,c,r.linkApp,b);!p&&!j(E)||y.push({id:r.id,reception:r.reception,delivery:g,receiver:v,...p?{linked:p}:{}})}if(y.length===0)return{delivered:0,message_id:null,depth:d};let R=null;for(const r of re(n,t,s,y,c=>c.id)){const c=new Map;for(const p of r.destinations)c.set(p.receiver,[...c.get(p.receiver)??[],p]);const u=ie(n,t,s,r.attrs),v=[...c].map(([p,A])=>F[p].bodyOwner(A)).find(p=>p!==null)??null,g=J({...u,urgency:"normal"},v),E=K(n,{operationId:g.operationId,createdAt:g.createdAt,senderNodeId:u.senderNodeId,senderName:u.senderName,kind:u.kind,label:u.label,bodyRef:g.bodyRef,bodyJson:g.bodyJson,disposition:u.disposition??null,event:{event:t.event,objectId:t.object.id,level:t.level,actorId:t.actor?.id??null},causeMessageId:i,depth:d});R??=E;for(const[p,A]of c)F[p].enqueue(n,{messageId:E,event:t.event,level:t.level,senderNodeId:u.senderNodeId,elevatesTier:s.elevatesTier===!0,...t.idempotencyKey===void 0?{}:{idempotencyKey:t.idempotencyKey}},A)}return{delivered:y.length,message_id:R,depth:d}}l(Se,"publish");export{T as HOP_LIMIT,de as HopLimitError,ve as listenConfigFor,ee as matchSubject,Se as publish,te as resolveDelivery,Z as resolveLevel,ue as subscribeDocumentEvents,re as supersede};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var l=Object.defineProperty;var d=(s,
|
|
1
|
+
var l=Object.defineProperty;var d=(s,c)=>l(s,"name",{value:c,configurable:!0});import{prepared as m}from"../../sqlite-statements.js";import{recordLiveRead as u}from"./kind.js";import{header as v}from"./render.js";import{objectDTO as j}from"./dto.js";import{receiverForObject as b}from"../receivers.js";function S(s,c){const a=Object.fromEntries(Object.entries(c).map(([t,{ends_wait:i,...o}])=>[`${s}.${t}`,{...o,carriesBody:!0,...i===void 0?{}:{endsWait:i}}]));return{name:s,events:{created:{level:4},read:{level:1},watched:{level:1},deleted:{level:5},configured:{level:1},...a},addressable:"name",receiverFor:b,read(t,i,o,p){u(t,i,o,p.reposFn);const e=m(t,"SELECT description, summary, state_json, published_at, receiver_json, dropped FROM custom_objects WHERE object = ?").get(o.rowid),n=e?.receiver_json==null?null:JSON.parse(e.receiver_json),r={object:j(t,o),description:e?.description??"",summary:e?.summary??null,state:e?.state_json==null?null:JSON.parse(e.state_json),published_at:e?.published_at??null,receiver:typeof n=="string"?n:n&&typeof n=="object"?Object.keys(n)[0]:null,dropped:e?.dropped??0};return{data:r,text:`${v("object",r.object)}
|
|
2
2
|
Description: ${r.description}
|
|
3
3
|
Summary: ${r.summary??""}
|
|
4
4
|
State: ${JSON.stringify(r.state)}
|
|
@@ -27,6 +27,9 @@ export interface EventSpec {
|
|
|
27
27
|
elevatesTier?: boolean;
|
|
28
28
|
/** The inbox kind the message is filed under. Omitted: `urgent` at level 7 and above, else `update`. */
|
|
29
29
|
inboxKind?: 'final' | 'completed';
|
|
30
|
+
/** A node that watches an object of this kind is waiting on it until this event (a run's
|
|
31
|
+
* `completed`, a pull request's `merged`); `publish` settles that wait (`waits`, kind `object`). */
|
|
32
|
+
endsWait?: boolean;
|
|
30
33
|
}
|
|
31
34
|
export type EventCatalog = Readonly<Record<string, EventSpec>>;
|
|
32
35
|
/** What a read answers: the kind's structured fields and exactly the text `crtr canvas read` prints. */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A node's wait on a watched object: armed when a node starts watching an object whose kind declares an
|
|
2
|
+
* event with `endsWait` (a workflow run until `completed`), settled by `publish` when that event lands,
|
|
3
|
+
* by the node stopping its watch, or by the node ending. The rows live in `waits` (kind `object`). */
|
|
4
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
import type { ObjectRow } from './types.js';
|
|
6
|
+
/** Whether an object of this kind is something a node waits on: one of its events ends a wait. */
|
|
7
|
+
export declare function kindEndsWait(db: DatabaseSync, type: string): boolean;
|
|
8
|
+
/** `watcher` (a node) started watching `object`: arm its wait when the object's kind has an ending event. */
|
|
9
|
+
export declare function armObjectWaitForWatch(db: DatabaseSync, watcher: ObjectRow, object: ObjectRow): void;
|
|
10
|
+
/** `watcher` stopped watching `object` entirely: its wait on it is over. */
|
|
11
|
+
export declare function settleObjectWaitForUnwatch(db: DatabaseSync, watcher: ObjectRow, object: ObjectRow): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var r=Object.defineProperty;var n=(e,t)=>r(e,"name",{value:t,configurable:!0});import{armObjectWait as o,settleObjectWaits as a}from"../canvas/waits.js";import{kindOf as d}from"./kinds/registry.js";function f(e,t){return Object.values(d(e,t).events).some(i=>i.endsWait===!0)}n(f,"kindEndsWait");function c(e,t,i){t.type!=="node"||!f(e,i.type)||o(e,t.id,i.id)}n(c,"armObjectWaitForWatch");function m(e,t,i){t.type==="node"&&a(e,i.id,t.id)}n(m,"settleObjectWaitForUnwatch");export{c as armObjectWaitForWatch,f as kindEndsWait,m as settleObjectWaitForUnwatch};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var x=Object.defineProperty;var u=(t,e)=>x(t,"name",{value:e,configurable:!0});import{getNode as S,cancelCronsOnWake as J,transitionStatusIntent as b}from"../canvas/index.js";import{composeNodeOutcomeDocument as A}from"./outcome-document.js";import{withCanvasWrite as E}from"../canvas/db.js";import{nodeObject as D}from"../graph/objects.js";import{endMessageSubscriptions as L}from"../graph/watches.js";import{runStructuredRequestEffects as P,settlePendingStructuredRequestForNodeIn as $}from"../canvas/structured-requests.js";const m="*",
|
|
2
|
-
`)[0]??"";let n=0,c="";for(const d of r){const
|
|
1
|
+
var x=Object.defineProperty;var u=(t,e)=>x(t,"name",{value:e,configurable:!0});import{getNode as S,cancelCronsOnWake as J,transitionStatusIntent as b}from"../canvas/index.js";import{composeNodeOutcomeDocument as A}from"./outcome-document.js";import{withCanvasWrite as E}from"../canvas/db.js";import{nodeObject as D}from"../graph/objects.js";import{endMessageSubscriptions as L}from"../graph/watches.js";import{settleNodeObjectWaits as j}from"../canvas/waits.js";import{runStructuredRequestEffects as P,settlePendingStructuredRequestForNodeIn as $}from"../canvas/structured-requests.js";const m="*",q={finish:"finished",park:"parked",cancel:"stranded",crash:"crashed","refresh-launch":null,revive:null},l=["active","idle"],M={finish:{status:"done",intent:"done",from:l},park:{status:"done",intent:"parked",from:l},cancel:{status:"canceled",intent:null,from:m},crash:{status:"dead",from:l},yield:{intent:"refresh",from:l},release:{status:"idle",intent:"idle-release",from:[...l,"done"]},"refresh-launch":{status:"active",intent:"refresh",from:m},revive:{status:"active",intent:null,from:m}};function T(t,e){const r=t.split(`
|
|
2
|
+
`)[0]??"";let n=0,c="";for(const d of r){const s=Buffer.byteLength(d,"utf8");if(n+s>e)break;n+=s,c+=d}return c}u(T,"firstLineAtMost");function W(t){if(t===void 0)return null;const e=t.message===void 0?void 0:T(t.message,500),r={...t,...e===void 0?{}:{message:e}},n=JSON.stringify(r);return Buffer.byteLength(n,"utf8")<=4*1024?n:JSON.stringify({schema:"crtr.node-outcome-detail/v1",truncated:!0,...e===void 0?{}:{message:e}})}u(W,"detailJson");function B(t,e){let r,n;return e==="finished"||e==="declined"?(r="done",n="done"):e==="parked"?(r="done",n="parked"):e==="closed"||e==="retired"||e==="stranded"||e==="deadline_exceeded"?(r="canceled",n=null):(r="dead",n=null),{...t,status:r,intent:n,terminal_reason:e}}u(B,"nodeAtOutcome");function H(t,e,r){const n=M[e],c=Object.prototype.hasOwnProperty.call(n,"status"),d=Object.prototype.hasOwnProperty.call(n,"intent"),s=r?.reason??q[e],f={};c&&(f.status=n.status),d&&(f.intent=n.intent??null),s!==void 0&&(f.terminal_reason=s);const a=e==="finish"||e==="park"||e==="cancel"||e==="crash",o=a?S(t):null,p=a&&o?.kind!=="human",h=s,_=a?new Date().toISOString():null,w=W(r?.outcome?.detail),N=p&&o?.run_id!=null&&s!=="parked"&&o.outcome_at!==null&&(l.includes(o.status)||o.outcome_reason==="parked");let k;p&&o!==null&&(o.outcome_at===null||N)&&(k=JSON.stringify(A({node:B(o,h),outcome:{kind:"failure",reason:h,at:_,revision:(o.outcome_revision??0)+1,detailJson:w}})));const y=a?s==="deadline_exceeded"?{intended:"expired",failureReason:"node_deadline_exceeded",failureDetail:r?.outcome?.detail??null}:{intended:"failed",failureReason:s,failureDetail:r?.outcome?.detail??null}:null,O=E(i=>{const g=b(t,f,{fromStatuses:n.from===m?void 0:n.from,requireNoFinalReport:e==="cancel",...p?{outcome:{kind:"failure",reason:h,detailJson:w,at:_,...N?{supersede:!0}:{},...k===void 0?{}:{documentJson:k}}}:{}});a&&g>0&&(L(i,D(i,t),"done"),j(i,t));const R=g===1&&y!==null?$(i,t,y):null;return{changes:g,settlement:R}});if(O.changes===0){const i=S(t);if(i===null)throw new Error(`transition: unknown node ${t}`);if(e==="cancel"&&i.final_report!==null&&i.final_report!==void 0)return i;throw new Error(`illegal lifecycle transition: '${e}' from status='${i.status}' (node ${t})`)}return O.settlement?.changed&&P(O.settlement.row),(e==="finish"||e==="park"||e==="cancel")&&J(t),S(t)}u(H,"transition");function K(t){return t.status==="done"&&t.intent==="parked"}u(K,"isParked");function Q(t){return t?.intent==="idle-release"}u(Q,"hasIdleReleaseIntent");export{Q as hasIdleReleaseIntent,K as isParked,H as transition};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var _=Object.defineProperty;var r=(t,e)=>_(t,"name",{value:e,configurable:!0});import{hasActiveLiveSubscription as S,
|
|
2
|
-
WHERE d.destination_id = ? AND d.wake = 1 AND m.kind IN ('message', 'completed') AND d.created_at > ? LIMIT 1`).get(t,e.at)!==void 0?{count:0,at:e.at}:e}r(R,"effectiveSchemaReprompts");function
|
|
1
|
+
var _=Object.defineProperty;var r=(t,e)=>_(t,"name",{value:e,configurable:!0});import{hasActiveLiveSubscription as S,hasPendingCronWake as v,getNode as c}from"../canvas/index.js";import{hasLiveControllerWait as w,hasLiveObjectWait as O}from"../canvas/waits.js";import{contextDir as m}from"../canvas/node-agent-paths.js";import{activeBackgroundBashJobs as f}from"../bash-jobs.js";import{hasOpenReplyBearingRequestByRequester as L}from"../human/requests.js";import{formatCard as o,formatStructuredOutputReprompt as b,STALL_REPROMPT as A}from"../../shared/generated-context.js";import{readOutputRequest as d,readSchemaReprompts as N,writeSchemaReprompts as q}from"./structured-output.js";import{openDb as x}from"../canvas/db.js";import{prepared as y}from"../sqlite-statements.js";const h=3;function g(t){return t.parent==null&&t.run_id===t.node_id&&t.metadata_json!=null}r(g,"isAppRunRoot");function R(t){const e=N(t);return e.count===0?e:y(x(),`SELECT 1 FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
2
|
+
WHERE d.destination_id = ? AND d.wake = 1 AND m.kind IN ('message', 'completed') AND d.created_at > ? LIMIT 1`).get(t,e.at)!==void 0?{count:0,at:e.at}:e}r(R,"effectiveSchemaReprompts");function E(t){const{count:e}=R(t);return e>=h?!1:(q(t,{count:e+1,at:Date.now()}),!0)}r(E,"takeRunSchemaReprompt");function U(t){return t.pi_pid!=null&&g(t)&&d(t.node_id).state==="valid"&&R(t.node_id).count<h&&u(t.node_id,f(m(t.node_id)).length>0)===null}r(U,"runSchemaRepromptPending");import{STALL_REPROMPT as z}from"../../shared/generated-context.js";function P(t,e){return o("stop-guard",{},b(JSON.stringify(e,null,2),t))}r(P,"formatValidStructuredOutputReprompt");function T(t){return o("stop-guard",{},`Structured-output request file is invalid and cannot be used: ${t}
|
|
3
3
|
|
|
4
4
|
You have two options:
|
|
5
5
|
1. Remove the invalid file (the output-schema.json in your node directory has been corrupted)
|
|
6
6
|
2. Fix the corruption if you know the original request schema
|
|
7
7
|
|
|
8
|
-
After resolving the file, call the \`submit_result\` tool again (with \`decline\` when the schema cannot be honestly satisfied). If this is unexpected, escalate to the user using \`crtr human send\`.`)}r(
|
|
8
|
+
After resolving the file, call the \`submit_result\` tool again (with \`decline\` when the schema cannot be honestly satisfied). If this is unexpected, escalate to the user using \`crtr human send\`.`)}r(T,"formatInvalidStructuredOutputReprompt");function u(t,e){return v(t)?"scheduled":S(t)||w(t)||O(t)||L(t)||e?"awaiting":null}r(u,"wakeCapableWait");function V(t,e,i=f(m(t)).length>0){if(e.pushedFinal)return{action:"allow",reason:"finished"};const a=d(t);if(a.state==="valid"){const l=u(t,i);if(l!==null)return{action:"allow",reason:l};const p=c(t);return p!==null&&g(p)&&!E(t)?{action:"allow",reason:"dormant"}:{action:"reprompt",reason:"stalled",message:P(a.request.request_id,a.request.schema)}}if(a.state==="invalid")return{action:"reprompt",reason:"stalled",message:T(a.error)};const n=c(t);if(n!==null&&(n.final_report!=null||n.status==="done"||n.status==="dead"||n.status==="canceled"))return{action:"allow",reason:"finished"};if(n!==null&&n.lifecycle==="resident")return{action:"allow",reason:"dormant"};const s=u(t,i);return s!==null?{action:"allow",reason:s}:{action:"reprompt",reason:"stalled",message:o("stop-guard",{},A)}}r(V,"evaluateStop");export{h as RUN_SCHEMA_REPROMPT_LIMIT,z as STALL_REPROMPT,V as evaluateStop,g as isAppRunRoot,U as runSchemaRepromptPending};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var Y=Object.defineProperty;var l=(t,e)=>Y(t,"name",{value:e,configurable:!0});import{gzipSync as V}from"node:zlib";import{browseNodes as P,graphNodes as X,listNodes as F,pruneNodes as Z,pruneToLimit as ee,rosterEdges as w,rosterNodes as te}from"../../../core/canvas/canvas.js";import{
|
|
2
|
-
`);if(e.length>0){const
|
|
3
|
-
`).forEach((
|
|
1
|
+
var Y=Object.defineProperty;var l=(t,e)=>Y(t,"name",{value:e,configurable:!0});import{gzipSync as V}from"node:zlib";import{browseNodes as P,graphNodes as X,listNodes as F,pruneNodes as Z,pruneToLimit as ee,rosterEdges as w,rosterNodes as te}from"../../../core/canvas/canvas.js";import{liveObjectWaitsNamed as ne,readControllerWait as oe}from"../../../core/canvas/waits.js";import{activeBackgroundBashJobs as se}from"../../../core/bash-jobs.js";import{contextDir as re}from"../../../core/canvas/node-agent-paths.js";import{nowIso as I}from"../../../core/fs-utils.js";import{isPidPresent as ie}from"../../../core/canvas/pid.js";import{dashboardRowsAllFromSource as ae,enrichRowsFromSource as de}from"../../../core/canvas/render-source.js";import{localCanvasSource as G}from"../../../core/canvas/source.js";import{ticketCountsForNodes as D,ticketsAcrossCanvas as le}from"../../../core/canvas/attention.js";import{buildCorpus as ce,groupCorpus as ue,resolveRef as fe}from"../../../core/canvas/history.js";import{listFocuses as q,reapEmptyNodes as pe}from"../../../core/runtime/placement.js";import{boundFleet as me}from"../../../core/runtime/fleet.js";import{retentionHold as _e}from"../../reconcilers/live-obligation.js";import{notFound as he,usage as _}from"../../../core/errors.js";import{openDb as ye}from"../../../core/canvas/db.js";import{prepared as be}from"../../../core/sqlite-statements.js";import{view as ge}from"../../../core/canvas/tree.js";import{isLiveStatus as ve}from"../../../core/canvas/node-order.js";import{readNodeRecap as ke}from"../../../core/canvas/node-recap.js";import{readGoal as Se}from"../../../core/runtime/kickoff.js";import{faultSummary as we}from"../../../core/canvas/status-glyph.js";import{listPendingAsks as Ne}from"../../../core/human/requests.js";import{toGraphNodeDTO as Re}from"../map.js";import{actsAsPerson as Ce,canReadNode as T,canReadRun as je}from"../router.js";import{ApiError as $}from"../../../api/errors.js";function N(t){return t.principal.class==="person"?null:new Set(F().filter(e=>T(t.principal,e.grantee??"app:terminal",e.node_id)).map(e=>e.node_id))}l(N,"visibleNodes");function H(t){return t.principal.class==="person"?null:new Set(F().filter(e=>e.run_id?je(t.principal,e.grantee??"app:terminal",e.node_id):T(t.principal,e.grantee??"app:terminal",e.node_id)).map(e=>e.node_id))}l(H,"visibleHistoryNodes");function Ie(t){const e=N(t);return{status:200,body:{items:le().filter(n=>e===null||e.has(n.node_id)).map(n=>({node_id:n.node_id,name:n.name,cwd:n.cwd,count:n.count,subjects:n.subjects}))}}}l(Ie,"handleAttention");function Me(t){const e=t.body;if(e===null||typeof e!="object"||!Array.isArray(e.node_ids))throw _("attention count request requires node_ids: string[]");const r=e.node_ids;if(!r.every(s=>typeof s=="string"))throw _("attention count request node_ids must contain only strings");const n=N(t);return{status:200,body:{counts:D(r.filter(s=>n===null||n.has(s)))}}}l(Me,"handleAttentionCounts");function x(t){return Buffer.from(String(t),"utf8").toString("base64")}l(x,"encodeCursor");function z(t){const e=Number(Buffer.from(t,"base64").toString("utf8"));if(!Number.isInteger(e)||e<0)throw _(`invalid cursor: ${t}`);return e}l(z,"decodeCursor");function Le(t,e,r){const a=t.loadBody().split(`
|
|
2
|
+
`);if(e.length>0){const c=a.findIndex(p=>{const f=p.toLowerCase();return e.some(h=>f.includes(h))});if(c!==-1)return a.slice(c,c+r).map(p=>p.trim()).filter(p=>p!=="").join(" \u23CE ")}const s=a.map(c=>c.trim()).filter(c=>c!=="").slice(0,r).join(" \u23CE ");return s!==""?s:t.title}l(Le,"snippet");function Oe(t,e,r){const n=[...t];return e==="relevance"&&r!==void 0?n.sort((a,s)=>(r.get(s.ref)??0)-(r.get(a.ref)??0)||s.tsMs-a.tsMs||a.ref.localeCompare(s.ref)):e==="oldest"?n.sort((a,s)=>a.tsMs-s.tsMs||a.ref.localeCompare(s.ref)):n.sort((a,s)=>s.tsMs-a.tsMs||a.ref.localeCompare(s.ref)),n}l(Oe,"sortArtifacts");function Ee(t){if(t==null)return{};if(typeof t!="object")throw _("history search body must be a JSON object");return t}l(Ee,"parseSearchBody");function Te(t){if(t===null||typeof t!="object")throw _("history grep body must be a JSON object");const e=t;if(typeof e.pattern!="string"||e.pattern==="")throw _("history grep body requires a non-empty pattern");return e}l(Te,"parseGrepBody");function B(t,e){const r={cwd:t.cwd,allCwds:t.all_cwds===!0,under:t.under,nodes:t.nodes},n={types:t.types,kinds:t.kinds,statuses:t.statuses,sinceMs:t.since_ms,untilMs:t.until_ms,origins:t.origins,roles:t.roles,from:t.from},a=H(e);return ce(r,n).filter(s=>a===null||a.has(s.nodeId))}l(B,"buildCorpusFromQuery");function $e(t){if(t==null)return{};if(typeof t!="object")throw _("history stats body must be a JSON object");return t}l($e,"parseStatsBody");const J=["origin","node","kind","day","type","role"];function Be(t){const e=$e(t.body),r=e.group_by??"type";if(!J.includes(r))throw _(`--group-by must be one of: ${J.join(", ")}; received: ${String(e.group_by)}`);const n=B(e,t),a=ue(n,r);return{status:200,body:{group_by:r,groups:a,total:a.reduce((c,p)=>c+p.count,0),scanned:n.length}}}l(Be,"handleHistoryStats");function Ae(t){const e=Te(t.body),r=Math.min(100,Math.max(1,e.limit??20)),n=B(e,t),a=e.cursor!==void 0?z(e.cursor):0;let s;try{s=new RegExp(e.pattern,"i")}catch{throw _(`invalid regex pattern: ${e.pattern}`)}const c=[];for(const i of n)i.loadBody().split(`
|
|
3
|
+
`).forEach((u,g)=>{s.test(u)&&c.push({ref:i.ref,node:`${i.nodeName} (${i.nodeId})`,ts:i.ts,line:g+1,text:u.trim()})});c.sort((i,o)=>i.ref.localeCompare(o.ref)||i.line-o.line);const p=c.slice(a,a+r),f=a+r<c.length?x(a+r):null;return{status:200,body:{hits:p,next_cursor:f,total:c.length}}}l(Ae,"handleHistoryGrep");function Pe(t){const e=Ee(t.body),r=(e.query??"").trim(),n=e.weigh_body===!0,a=e.full===!0,s=Math.max(1,e.snippet_lines??1),c=Math.min(100,Math.max(1,e.limit??20)),p=e.per_node===!0;if(p&&(e.nodes===void 0||e.nodes.length===0))throw new $(400,"invalid_request","`per_node` requires a non-empty `nodes` list",{param:"nodes"});const f=B(e,t),h=e.cursor!==void 0?z(e.cursor):0,i=r.toLowerCase().split(/\s+/).filter(d=>d.length>0);let o=f,u;const g=e.sort??(i.length>0?"relevance":"recency");if(i.length>0&&g==="relevance"){u=new Map;const d=[];for(const m of f){const E=m.title.toLowerCase(),U=m.nodeName.toLowerCase(),Q=m.nodeDesc.toLowerCase(),A=n?m.loadBody().toLowerCase():null;let S=0;for(const j of i)E.includes(j)&&(S+=10),U.includes(j)&&(S+=5),Q.includes(j)&&(S+=3),A!==null&&A.includes(j)&&(S+=1);S>0&&(u.set(m.ref,S),d.push(m))}o=d}else i.length>0&&(o=f.filter(d=>{const m=`${d.title}
|
|
4
4
|
${d.nodeName}
|
|
5
|
-
${d.nodeDesc}${
|
|
6
|
-
`+d.loadBody():""}`.toLowerCase();return i.some(
|
|
5
|
+
${d.nodeDesc}${n?`
|
|
6
|
+
`+d.loadBody():""}`.toLowerCase();return i.some(E=>m.includes(E))}));let b=Oe(o,g,u);if(p){const d=new Set;b=b.filter(m=>!d.has(m.nodeId)&&d.add(m.nodeId))}const v=p?b:b.slice(h,h+c),y=!p&&h+c<b.length?x(h+c):null;return{status:200,body:{hits:v.map(d=>{const m={ref:d.ref,node:`${d.nodeName} (${d.nodeId})`,kind:d.nodeKind,source:d.source,ts:d.ts,...p?{node_id:d.nodeId}:{}};return g==="relevance"&&u!==void 0&&(m.score=u.get(d.ref)??0),a?m.body=d.loadBody():m.snippet=Le(d,i,s),m}),next_cursor:y,total:b.length}}}l(Pe,"handleHistorySearch");function Fe(t){const e=(t.query.get("ref")??"").trim();if(!e.includes(":"))throw _(`ref must be <node-id>:inbox/<n>, <node-id>:session, or <node-id>:session/<n>; received: ${e}`,{received:e,next:"Get a valid ref from `crtr canvas history search`. Reports and documents are `crtr canvas read <name>`."});const r=fe(e);if(r===null)throw he(`no inbox entry or message for ref: ${e}`,{ref:e,next:"Re-run `crtr canvas history search` for a current ref. Reports and documents are `crtr canvas read <name>`."});if(H(t)?.has(r.nodeId)===!1)throw new $(404,"not_found","artifact not found");const n=r.source,s=t.query.get("format")==="raw"&&r.raw!==null?r.raw:r.body;return{status:200,body:{ref:e,node:`${r.nodeName} (${r.nodeId})`,source:n,ts:r.ts,content:s}}}l(Fe,"handleHistoryRead");function Ge(t){for(const[n]of t.query)if(n!=="live")throw _(`unknown snapshot query parameter: ${n}`);const e=t.query.getAll("live");if(e.length>1)throw _("live must appear at most once");const r=e[0];if(r===void 0||r==="false"||r==="0")return!1;if(r==="true"||r==="1")return!0;throw _("live must be true or false")}l(Ge,"snapshotLiveOnly");async function De(t){const e=new Set(q().map(i=>i.node_id)),r=N(t),n=(await ae(G,e,Ge(t))).filter(i=>r===null||r.has(i.node_id)),a=D(n.map(i=>i.node_id));await de(G,n,a);const s=n.map(i=>{const o=i.srcRow,u=o?.host_kind??"tmux";return{node_id:i.node_id,name:i.name,title:i.handle??i.name,description:i.description??null,kind:i.kind,mode:i.mode,lifecycle:i.lifecycle??o?.lifecycle,status:i.status,cwd:i.cwd,profile_id:i.profile_id??null,parent:o?.parent??null,created:i.created,host_kind:u,enterable:u==="broker",attention_count:i.asks,...o?.cycles!=null?{cycles:o.cycles}:{},...i.mtimeMs!==void 0?{last_activity:new Date(i.mtimeMs).toISOString()}:{},ctx_tokens:i.ctx_tokens,streaming:i.streaming??!1,jobs_active:o!==void 0&&ie(o.pi_pid)?se(re(i.node_id)).length:0,waiting_for:oe(i.node_id)?.controllerId??null,hanging:i.hanging??null,viewed:i.viewed??!1,final_report:o?.final_report??null,finalized_at:o?.finalized_at??null}}),c=new Map;for(const i of w()){const o=c.get(i.from_id);o===void 0?c.set(i.from_id,[i.to_id]):o.push(i.to_id)}const p=new Set(n.map(i=>i.node_id)),f=Object.fromEntries(n.map(i=>[i.node_id,(c.get(i.node_id)??[]).filter(o=>p.has(o))]));return{status:200,body:{generated_at:I(),nodes:s,subscriptions:f}}}l(De,"handleSnapshot");function K(){const t=te().map(n=>{const a=n.host_kind??"tmux";return{node_id:n.node_id,name:n.name,kind:n.kind,mode:n.mode,lifecycle:n.lifecycle,status:n.status,cwd:n.cwd,host_kind:a,enterable:a==="broker",parent:n.parent,created:n.created}}),e=w().map(n=>({from_id:n.from_id,to_id:n.to_id,active:n.active,created:n.created})),r=ne().map(n=>({node_id:n.nodeId,object_id:n.objectId,object_type:n.objectType,object_name:n.objectName,created:new Date(n.createdAt).toISOString()}));return{generated_at:I(),nodes:t,edges:e,object_waits:r}}l(K,"buildRoster");let R=null,M=null;function qe(){return R===null?(R=Promise.resolve().then(()=>K()).finally(()=>{R=null}),R):(M===null&&(M=R.then(()=>K()).finally(()=>{M=null})),M)}l(qe,"rosterCoalesced");async function He(t){const e=await qe(),r=N(t);return r===null?{status:200,body:e}:{status:200,body:{...e,nodes:e.nodes.filter(n=>r.has(n.node_id)),edges:e.edges.filter(n=>r.has(n.from_id)&&r.has(n.to_id)),object_waits:(e.object_waits??[]).filter(n=>r.has(n.node_id))}}}l(He,"handleRoster");function W(t){if(t.name!==""&&t.name!==t.kind)return t.name;const e=(t.description??"").trim();return e!==""?e:t.name}l(W,"liveRootName");const xe=1440*6e4;function ze(){const t=P(),e=new Map(t.map(o=>[o.node_id,o])),r=new Map(be(ye(),"SELECT node_id, reply_at, read_at, intent FROM nodes WHERE parent IS NULL").all().map(o=>[o.node_id,o])),n=Ne(),a=new Map;for(const o of n)a.set(o.requester_node_id,(a.get(o.requester_node_id)??0)+1);const s=new Date(Date.now()-xe).toISOString(),c=l(o=>{const u=r.get(o.node_id);return u?.reply_at!=null&&(u.read_at==null||u.reply_at>u.read_at)||(a.get(o.node_id)??0)>0?!0:o.status!=="done"||u?.intent!=="parked"?!1:[o.created,o.telemetry_updated_at,u.reply_at].reduce((v,y)=>y!=null&&y>v?y:v,"")>=s},"keepFinished"),p=t.filter(o=>o.parent===null&&o.kind!=="human"&&(ve(o.status)||c(o))),f=new Map;for(const o of w())f.set(o.from_id,[...f.get(o.from_id)??[],o.to_id]);const h=l(o=>{const u=e.get(o)?.fault;return u&&u.link!=="broker\u2194crtrd"?we(u):null},"faultOf"),i=p.map(o=>{const u=[o.node_id,...ge(o.node_id)].filter(d=>e.get(d)?.kind!=="human"&&e.has(d)),g=new Set(u),b=n.filter(d=>g.has(d.requester_node_id)),v=r.get(o.node_id),y=v?.reply_at??null,O=v?.read_at??null;let k=o.created;for(const d of u){const m=e.get(d).telemetry_updated_at;m!=null&&m>k&&(k=m)}return y!==null&&y>k&&(k=y),{node_id:o.node_id,name:W(o),cwd:o.cwd,profile_id:o.profile_id,status:o.status,state_line:(()=>{const d=ke(o.node_id)?.state.trim();return d||null})(),goal:Se(o.node_id)?.trim()||null,asks:b.length,first_ask_ticket_id:b[0]?.inbox_ticket_id??null,fault:u.map(h).find(d=>d!==null)??null,running_count:u.filter(d=>e.get(d).streaming===!0).length,unread:y!==null&&(O===null||y>O),last_activity:k,children:(f.get(o.node_id)??[]).filter(d=>e.has(d)&&e.get(d).kind!=="human").map(d=>{const m=e.get(d);return{node_id:d,name:W(m),status:m.status,streaming:m.streaming===!0,asks:a.get(d)??0,fault:h(d)}})}});return{generated_at:I(),rows:i}}l(ze,"buildLiveRoots");let C=null,L=null;function Je(){const t=l(()=>Promise.resolve().then(ze),"run");return C===null?(C=t().finally(()=>{C=null}),C):(L===null&&(L=C.then(t).finally(()=>{L=null})),L)}l(Je,"liveRootsCoalesced");async function Ke(t){if(!Ce(t.principal))throw new $(403,"permission_error","only the person reads the dashboard");const e=await Je(),r=t.query.get("profile");return{status:200,body:r===null?e:{...e,rows:e.rows.filter(n=>n.profile_id===r)}}}l(Ke,"handleLiveRoots");function We(t){const e=P().filter(s=>t.principal.class==="person"||T(t.principal,s.grantee??"app:terminal",s.node_id)).map(s=>({node_id:s.node_id,name:s.name,description:s.description,kind:s.kind,mode:s.mode,lifecycle:s.lifecycle,status:s.status,cwd:s.cwd,profile_id:s.profile_id,grantee:s.grantee??"app:terminal",parent:s.parent,created:s.created,frozen_at:s.frozen_at??null,terminal_reason:s.terminal_reason??null,final_report:s.final_report??null,telemetry_tokens_in:s.telemetry_tokens_in??null,telemetry_updated_at:s.telemetry_updated_at??null,fault:s.fault&&s.fault.link!=="broker\u2194crtrd"?{...s.fault,link:s.fault.link}:null,streaming:s.streaming??!1})),r=new Set(e.map(s=>s.node_id)),n=w().filter(s=>r.has(s.from_id)&&r.has(s.to_id));return{status:200,rawBody:V(JSON.stringify({nodes:e,edges:n}),{level:1}),headers:{"content-type":"application/gzip"}}}l(We,"handleBrowse");function Ue(t){const e=N(t);return{status:200,body:{generated_at:I(),nodes:X().filter(n=>e===null||e.has(n.node_id)).map(Re),edges:w().filter(n=>e===null||e.has(n.from_id)&&e.has(n.to_id)).map(n=>({from_id:n.from_id,to_id:n.to_id,active:n.active,created:n.created})),focused_node_ids:q().map(n=>n.node_id).filter(n=>e===null||e.has(n))}}}l(Ue,"handleGraph");function Qe(t){if(t==null)return{};if(typeof t!="object")throw _("prune body must be a JSON object");const e=t,r=l(i=>{const o=e[i];if(o!==void 0){if(typeof o!="number"||!Number.isFinite(o))throw _(`${i} must be a number`);return o}},"num"),n=l(i=>{const o=e[i];if(o!==void 0){if(typeof o!="boolean")throw _(`${i} must be a boolean`);return o}},"bool"),a={},s=r("limit");s!==void 0&&(a.limit=s);const c=r("ttl_days");c!==void 0&&(a.ttl_days=c);const p=n("include_stale");p!==void 0&&(a.include_stale=p);const f=n("empty");f!==void 0&&(a.empty=f);const h=n("dry_run");return h!==void 0&&(a.dry_run=h),a}l(Qe,"parsePruneBody");function Ye(t){const e=Qe(t.body),r=e.ttl_days??14,n=e.dry_run??!1,a=_e(me());let s;return e.limit!==void 0?s=ee({maxNodes:e.limit,held:a,dryRun:n}).pruned.map(f=>({node_id:f.node_id,status:f.status,created:f.created})):e.empty===!0?s=pe({dryRun:n}).map(f=>({node_id:f,status:"empty",created:""})):s=Z({ttlDays:r,held:a,dryRun:n,includeStale:e.include_stale??!1}).pruned.map(f=>({node_id:f.node_id,status:f.status,created:f.created})),{status:200,body:{pruned:s,dry_run:n,ttl_days:r}}}l(Ye,"handlePrune");const Nt=[{method:"GET",pattern:"/v1/canvas/attention",handler:Ie},{method:"POST",pattern:"/v1/canvas/attention/counts",handler:Me},{method:"POST",pattern:"/v1/canvas/history/search",handler:Pe},{method:"POST",pattern:"/v1/canvas/history/grep",handler:Ae},{method:"GET",pattern:"/v1/canvas/history/read",handler:Fe},{method:"POST",pattern:"/v1/canvas/history/stats",handler:Be},{method:"GET",pattern:"/v1/canvas/snapshot",handler:De},{method:"GET",pattern:"/v1/canvas/roster",handler:He},{method:"GET",pattern:"/v1/canvas/live-roots",handler:Ke},{method:"GET",pattern:"/v1/canvas/browse",handler:We},{method:"GET",pattern:"/v1/canvas/graph",handler:Ue},{method:"POST",pattern:"/v1/canvas/prune",ownerOnly:!0,handler:Ye}];export{Nt as canvasRoutes,We as handleBrowse};
|