@north-light/crouter 0.3.355 → 0.3.356

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/api/dto/delivery.d.ts +13 -3
  2. package/dist/api/dto/objects.d.ts +24 -22
  3. package/dist/api/dto/objects.js +1 -1
  4. package/dist/clients/attach/session/document-links.js +1 -1
  5. package/dist/clients/attach/viewer.js +829 -755
  6. package/dist/commands/canvas/list.js +3 -3
  7. package/dist/commands/canvas/read.d.ts +2 -4
  8. package/dist/commands/canvas/read.js +3 -5
  9. package/dist/commands/canvas/search.js +1 -1
  10. package/dist/commands/sys/context/admin/actions.js +1 -1
  11. package/dist/commands/sys/sync-deps.js +1 -1
  12. package/dist/commands/sys/sync-shared.js +1 -1
  13. package/dist/core/canvas/daemon-actions.d.ts +43 -0
  14. package/dist/core/canvas/daemon-actions.js +26 -0
  15. package/dist/core/canvas/deliveries.d.ts +1 -0
  16. package/dist/core/canvas/deliveries.js +1 -1
  17. package/dist/core/canvas/migrations.js +106 -46
  18. package/dist/core/feed/messages.d.ts +2 -20
  19. package/dist/core/feed/messages.js +6 -10
  20. package/dist/core/graph/events.d.ts +10 -5
  21. package/dist/core/graph/events.js +5 -5
  22. package/dist/core/graph/jobs.js +2 -2
  23. package/dist/core/graph/kinds/document.d.ts +19 -0
  24. package/dist/core/graph/kinds/document.js +5 -0
  25. package/dist/core/graph/kinds/dto.d.ts +5 -0
  26. package/dist/core/graph/kinds/dto.js +1 -0
  27. package/dist/core/graph/kinds/job.d.ts +2 -0
  28. package/dist/core/graph/kinds/job.js +2 -0
  29. package/dist/core/graph/kinds/kind.d.ts +39 -0
  30. package/dist/core/graph/kinds/kind.js +1 -0
  31. package/dist/core/graph/kinds/node.d.ts +2 -0
  32. package/dist/core/graph/kinds/node.js +4 -0
  33. package/dist/core/graph/kinds/owner.d.ts +2 -0
  34. package/dist/core/graph/kinds/owner.js +2 -0
  35. package/dist/core/graph/kinds/registry.d.ts +8 -0
  36. package/dist/core/graph/kinds/registry.js +1 -0
  37. package/dist/core/graph/kinds/render.d.ts +9 -0
  38. package/dist/core/graph/kinds/render.js +1 -0
  39. package/dist/core/graph/objects.d.ts +5 -4
  40. package/dist/core/graph/objects.js +5 -5
  41. package/dist/core/graph/types.d.ts +6 -5
  42. package/dist/core/runtime/broker-protocol.d.ts +2 -7
  43. package/dist/daemon/api/handlers/human-requests.js +1 -1
  44. package/dist/daemon/api/handlers/objects.js +2 -5
  45. package/dist/daemon/cron/settle.js +1 -1
  46. package/dist/daemon/cron/sinks.d.ts +3 -3
  47. package/dist/daemon/cron/sinks.js +2 -2
  48. package/dist/daemon/crtrd.js +6 -6
  49. package/dist/daemon/human/finish.js +6 -6
  50. package/dist/daemon/human/settle.d.ts +2 -3
  51. package/dist/daemon/human/settle.js +1 -1
  52. package/dist/daemon/reconcilers/action-lane.d.ts +8 -0
  53. package/dist/daemon/reconcilers/action-lane.js +1 -0
  54. package/dist/daemon/reconcilers/delivery-lane.d.ts +1 -1
  55. package/dist/daemon/reconcilers/delivery-lane.js +1 -1
  56. package/package.json +2 -2
  57. package/packages/crouter-identity/package.json +1 -1
  58. package/runtime.lock.json +11 -11
@@ -3,9 +3,14 @@
3
3
  import type { DatabaseSync } from 'node:sqlite';
4
4
  import { type ReposFn } from './reader.js';
5
5
  import { type Subscription } from './watches.js';
6
- import { type CanvasEvent, type EventName, type Level, type ListenConfig, type ListenDelivery, type ObjectRow, type ObjectType } from './types.js';
7
- /** The event's default level, or the caller's. Notices have no default: they carry the caller's tier. */
8
- export declare function levelFor(event: EventName, override?: Level, objectType?: ObjectType): Level;
6
+ import { type CanvasEvent, type Level, type ListenConfig, type ListenDelivery, type ObjectRow } from './types.js';
7
+ /** The event's default level, or the caller's. Notices have no default: they carry the caller's tier.
8
+ * Jobs no longer use it: their events publish at the level their kind declares. */
9
+ export declare function levelFor(event: string, override?: Level, objectType?: string): Level;
10
+ /** The level `ev` publishes at: the declared level when none is supplied; a supplied level
11
+ * must equal it or be one of the event's declared `levels`. Throws `unknown_event` for an
12
+ * event the object's kind does not declare, `invalid_level` for any other level. */
13
+ export declare function resolveLevel(ev: Pick<CanvasEvent, 'event' | 'object' | 'level'>): Level;
9
14
  /** NATS-style match over dot-separated event names: `*` is one part, `>` the rest (at least one part). */
10
15
  export declare function matchSubject(pattern: string, event: string): boolean;
11
16
  /** Per config, the delivery of the highest threshold the level reaches; across configs, the
@@ -14,13 +19,13 @@ export declare function resolveDelivery(configs: readonly ListenConfig[], level:
14
19
  /** A subscription's own config, else the watcher node's default, else the global default. */
15
20
  export declare function listenConfigFor(db: DatabaseSync, subscription: Pick<Subscription, 'config'>, watcher: ObjectRow): ListenConfig;
16
21
  /**
17
- * Push `ev` to every node watching the object itself whose subscriptions receive it — a watch on
22
+ * Validate the event against its object's kind (`resolveLevel`), then push it to every node watching the object itself whose subscriptions receive it — a watch on
18
23
  * an owner hears that owner's own events, not those of the objects it owns — inside the caller's write transaction. Document and job events reach
19
24
  * only live (active|idle) watchers; node events also reach done ones without waking them. The
20
25
  * actor is never pushed its own act, access is re-checked per watcher, and apps, persons and
21
26
  * other non-node watchers get nothing. Writes a `messages` row only when a delivery results.
22
27
  * `reposFn` resolves a watcher's repos when the object lives in a repo space.
23
28
  */
24
- export declare function publish(db: DatabaseSync, ev: CanvasEvent, reposFn?: ReposFn): {
29
+ export declare function publish(db: DatabaseSync, input: CanvasEvent, reposFn?: ReposFn): {
25
30
  delivered: number;
26
31
  };
@@ -1,7 +1,7 @@
1
- var C=Object.defineProperty;var i=(t,e)=>C(t,"name",{value:e,configurable:!0});import{evalCondition as M}from"../predicate.js";import{insertEventMail as j,listenDeliveryTier as W}from"../feed/messages.js";import{ownerChain as k,readCheck as H}from"./access.js";import{publishLookups as J,readerForNode as $}from"./reader.js";import{configFromJson as g}from"./watches.js";import{DEFAULT_LISTEN as N}from"./types.js";const x={created:4,read:1,watched:1,edited:8,messaged:2,sent:1,configured:1,"report.update":5,"report.final":7,"report.result":5,exited:7,killed:7};function ne(t,e,o){if(e!==void 0)return e;if(t==="deleted")return o==="document"?8:5;if(t.startsWith("notice."))throw new Error(`${t} has no default level; pass the notice's tier as a level`);return x[t]}i(ne,"levelFor");function B(t,e){const o=t.split("."),r=e.split(".");for(let s=0;s<o.length;s++){if(o[s]===">")return s===o.length-1&&r.length>s;if(s>=r.length||o[s]!=="*"&&o[s]!==r[s])return!1}return o.length===r.length}i(B,"matchSubject");const b={"next-turn":1,"after-turn":2,steer:3,interrupt:4};function U(t,e){let o=null;for(const r of t){let s=null;for(const d of r)d.threshold<=e&&(!s||d.threshold>s.threshold)&&(s=d);s&&(!o||b[s.delivery]>b[o])&&(o=s.delivery)}return o}i(U,"resolveDelivery");function oe(t,e,o){if(e.config)return e.config;const r=t.prepare("SELECT listen_default_json FROM nodes WHERE node_id = ?").get(o.id);return r?.listen_default_json?g(r.listen_default_json):N}i(oe,"listenConfigFor");const V=new Set(["messaged","sent","configured","report.update","report.final","report.result","notice.crash","notice.deadline","notice.close"]);function G(t){return t.event==="report.final"?"final":t.event==="report.result"?"completed":t.level>=7?"urgent":"update"}i(G,"inboxKind");function K(t,e){return e.type==="node"?e.id:t.prepare(`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}i(K,"ownerHandleOf");function X(t,e,o){const r=e.object.owner===null?null:t.prepare("SELECT * FROM objects WHERE rowid = ?").get(e.object.owner),s=r?.id??null,d=r?K(t,r):null;return{event:e.event,level:e.level,object:{id:e.object.id,type:e.object.type,kind:e.object.kind,owner:s,owner_handle:d,name:e.object.name},actor:e.actor?.id??null,attrs:o,...e.pointer===void 0?{}:{pointer:e.pointer}}}i(X,"pushFields");function O(t,e,o){const r=X(t,e,o),s=e.messageBody!==void 0&&(e.event.startsWith("report.")||e.event.startsWith("notice.")),d=s?typeof e.messageBody=="object"&&e.messageBody!==null&&!Array.isArray(e.messageBody)?{...e.messageBody,push:r}:{body:e.messageBody,push:r}:{push:r},l=s&&e.event.startsWith("report.")?e.messageBody:null;return{kind:G(e),label:l?.label??`${e.event} ${e.object.type} ${e.object.name}`,...l?.bodyRef===void 0?{}:{bodyRef:l.bodyRef},...l?.disposition===void 0?{}:{disposition:l.disposition},senderNodeId:e.actor?.type==="node"?e.actor.id:null,senderName:e.actor?.name??null,bodyJson:d,event:e.event,objectId:e.object.id,level:e.level,actorId:e.actor?.id??null}}i(O,"mailInput");function q(t){const e=t.attrs.revision,o=t.attrs.from_revision??(typeof e=="number"?e-1:null),r=t.attrs.rationale;return{...t.attrs,from_revision:o,revision:e,authors:t.actor?[t.actor.id]:[],rationales:typeof r=="string"?[r]:[]}}i(q,"editAttrs");function z(t,e){const o=[...t.authors??[],...e.authors.filter(r=>!(t.authors??[]).includes(r))];return{...e,from_revision:t.from_revision??null,authors:o,rationales:[...t.rationales??[],...e.rationales]}}i(z,"mergeEdits");function re(t,e,o=()=>[]){const r=k(t,e.object),s=V.has(e.event),d="w.object = ? AND w.watcher != ?",l="w.watcher, o.id AS watcher_id, n.status, n.listen_default_json, s.pattern, s.filter_json, s.config_json",m=t.prepare(s?`SELECT ${l} FROM watches w JOIN watch_subscriptions s ON s.watch = w.rowid
3
- JOIN objects o ON o.rowid = w.watcher AND o.type = 'node' JOIN nodes n ON n.node_id = o.id WHERE ${d}`:`SELECT ${l} FROM nodes n CROSS JOIN objects o CROSS JOIN watches w CROSS JOIN watch_subscriptions s
4
- WHERE n.status IN ('active','idle') AND o.id = n.node_id AND w.watcher = o.rowid AND s.watch = w.rowid AND ${d}`).all(e.object.rowid,e.actor?.rowid??-1);if(m.length===0)return{delivered:0};const R={event:e.event,level:e.level,id:e.object.id,type:e.object.type,kind:e.object.kind,actor:e.actor?.id??null,...e.attrs},p=new Map;for(const n of m){if(!B(n.pattern,e.event)||n.filter_json!==null&&!M(JSON.parse(n.filter_json),R))continue;const c=n.config_json?g(n.config_json):n.listen_default_json?g(n.listen_default_json):N,a=p.get(n.watcher)??{id:n.watcher_id,status:n.status,configs:[]};a.configs.push(c),p.set(n.watcher,a)}const S=(r.length?r[r.length-1]:e.object).type==="repo",D=i(()=>[],"noRepos"),A=H(t,e.object),I=J(t),u=[];for(const n of p.values()){const c=U(n.configs,e.level);if(c===null||!A($(t,n.id,S?o:D,I)))continue;const a=W(c),f=n.status==="active"||n.status==="idle";u.push({nodeId:n.id,urgency:a.urgency,wake:f?a.wake:0})}if(u.length===0)return{delivered:0};if(e.event!=="edited")return j(t,O(t,e,e.attrs),u),{delivered:u.length};const E=q(e),T=t.prepare(`SELECT d.delivery_id, d.message_id, m.body_json FROM deliveries d
1
+ var F=Object.defineProperty;var i=(t,e)=>F(t,"name",{value:e,configurable:!0});import{evalCondition as $}from"../predicate.js";import{insertEventMail as b,listenDeliveryTier as M}from"../feed/messages.js";import{ownerChain as W,readCheck as k}from"./access.js";import{publishLookups as x,readerForNode as H}from"./reader.js";import{configFromJson as m}from"./watches.js";import{CrtrError as J}from"../errors.js";import{ExitCode as B}from"../../types.js";import{eventSpec as U}from"./kinds/registry.js";import{DEFAULT_LISTEN as N}from"./types.js";const G={created:4,read:1,watched:1,edited:8,messaged:2,sent:1,configured:1,"report.update":5,"report.final":7,"report.result":5};function ce(t,e,o){if(e!==void 0)return e;if(t==="deleted")return o==="document"?8:5;if(t.startsWith("notice."))throw new Error(`${t} has no default level; pass the notice's tier as a level`);const n=G[t];if(n===void 0)throw new Error(`${t} has no default level`);return n}i(ce,"levelFor");function V(t){const e=U(t.object.type,t.event);if(t.level===void 0||t.level===e.level||e.levels?.includes(t.level))return t.level??e.level;const o=[e.level,...e.levels??[]].filter((n,r,l)=>l.indexOf(n)===r).sort((n,r)=>n-r);throw new J("invalid_level",`${t.object.type} ${t.event} publishes at level ${o.join(", ")}, not ${t.level}`,B.USAGE,{kind:t.object.type,event:t.event,level:t.level,allowed:o})}i(V,"resolveLevel");function K(t,e){const o=t.split("."),n=e.split(".");for(let r=0;r<o.length;r++){if(o[r]===">")return r===o.length-1&&n.length>r;if(r>=n.length||o[r]!=="*"&&o[r]!==n[r])return!1}return o.length===n.length}i(K,"matchSubject");const v={"next-turn":1,"after-turn":2,steer:3,interrupt:4};function X(t,e){let o=null;for(const n of t){let r=null;for(const l of n)l.threshold<=e&&(!r||l.threshold>r.threshold)&&(r=l);r&&(!o||v[r.delivery]>v[o])&&(o=r.delivery)}return o}i(X,"resolveDelivery");function ue(t,e,o){if(e.config)return e.config;const n=t.prepare("SELECT listen_default_json FROM nodes WHERE node_id = ?").get(o.id);return n?.listen_default_json?m(n.listen_default_json):N}i(ue,"listenConfigFor");const q=new Set(["messaged","sent","configured","report.update","report.final","report.result","notice.crash","notice.deadline","notice.close"]);function z(t){return t.event==="report.final"?"final":t.event==="report.result"?"completed":t.level>=7?"urgent":"update"}i(z,"inboxKind");function P(t,e){return e.type==="node"?e.id:t.prepare(`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}i(P,"ownerHandleOf");function Q(t,e,o){const n=e.object.owner===null?null:t.prepare("SELECT * FROM objects WHERE rowid = ?").get(e.object.owner),r=n?.id??null,l=n?P(t,n):null;return{event:e.event,level:e.level,object:{id:e.object.id,type:e.object.type,kind:e.object.kind,owner:r,owner_handle:l,name:e.object.name},actor:e.actor?.id??null,attrs:o,...e.pointer===void 0?{}:{pointer:e.pointer}}}i(Q,"pushFields");function O(t,e,o){const n=Q(t,e,o),r=e.messageBody!==void 0&&(e.event.startsWith("report.")||e.event.startsWith("notice.")),l=r?typeof e.messageBody=="object"&&e.messageBody!==null&&!Array.isArray(e.messageBody)?{...e.messageBody,push:n}:{body:e.messageBody,push:n}:{push:n},a=r&&e.event.startsWith("report.")?e.messageBody:null;return{kind:z(e),label:a?.label??`${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?.name??null,bodyJson:l,event:e.event,objectId:e.object.id,level:e.level,actorId:e.actor?.id??null}}i(O,"mailInput");function Y(t){const e=t.attrs.revision,o=t.attrs.from_revision??(typeof e=="number"?e-1:null),n=t.attrs.rationale;return{...t.attrs,from_revision:o,revision:e,authors:t.actor?[t.actor.id]:[],rationales:typeof n=="string"?[n]:[]}}i(Y,"editAttrs");function Z(t,e){const o=[...t.authors??[],...e.authors.filter(n=>!(t.authors??[]).includes(n))];return{...e,from_revision:t.from_revision??null,authors:o,rationales:[...t.rationales??[],...e.rationales]}}i(Z,"mergeEdits");function fe(t,e,o=()=>[]){const n={...e,level:V(e)},r=W(t,n.object),l=q.has(n.event),a="w.object = ? AND w.watcher != ?",g="w.watcher, o.id AS watcher_id, n.status, n.listen_default_json, s.pattern, s.filter_json, s.config_json",E=t.prepare(l?`SELECT ${g} FROM watches w JOIN watch_subscriptions s ON s.watch = w.rowid
3
+ JOIN objects o ON o.rowid = w.watcher AND o.type = 'node' JOIN nodes n ON n.node_id = o.id WHERE ${a}`:`SELECT ${g} FROM nodes n CROSS JOIN objects o CROSS JOIN watches w CROSS JOIN watch_subscriptions s
4
+ WHERE n.status IN ('active','idle') AND o.id = n.node_id AND w.watcher = o.rowid AND s.watch = w.rowid AND ${a}`).all(n.object.rowid,n.actor?.rowid??-1);if(E.length===0)return{delivered:0};const S={event:n.event,level:n.level,id:n.object.id,type:n.object.type,kind:n.object.kind,actor:n.actor?.id??null,...n.attrs},p=new Map;for(const s of E){if(!K(s.pattern,n.event)||s.filter_json!==null&&!$(JSON.parse(s.filter_json),S))continue;const c=s.config_json?m(s.config_json):s.listen_default_json?m(s.listen_default_json):N,d=p.get(s.watcher)??{id:s.watcher_id,status:s.status,configs:[]};d.configs.push(c),p.set(s.watcher,d)}const R=(r.length?r[r.length-1]:n.object).type==="repo",D=i(()=>[],"noRepos"),A=k(t,n.object),I=x(t),u=[];for(const s of p.values()){const c=X(s.configs,n.level);if(c===null||!A(H(t,s.id,R?o:D,I)))continue;const d=M(c),f=s.status==="active"||s.status==="idle";u.push({nodeId:s.id,urgency:d.urgency,wake:f?d.wake:0})}if(u.length===0)return{delivered:0};if(n.event!=="edited")return b(t,O(t,n,n.attrs),u),{delivered:u.length};const _=Y(n),L=t.prepare(`SELECT d.delivery_id, d.message_id, m.body_json FROM deliveries d
5
5
  JOIN messages m ON m.message_id = d.message_id
6
6
  WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND d.state = 'pending' AND d.canceled_at IS NULL
7
- AND m.event = 'edited' AND m.object_id = ?`),h=new Map,_=new Set,L=t.prepare("DELETE FROM deliveries WHERE delivery_id = ?");for(const n of u){const c=T.all(n.nodeId,e.object.id),a=c.at(-1);for(const w of c)L.run(w.delivery_id),_.add(w.message_id);const f=a?.message_id??null,y=h.get(f)??{prior:a?.body_json?JSON.parse(a.body_json).push?.attrs??null:null,destinations:[]};y.destinations.push(n),h.set(f,y)}for(const n of h.values())j(t,O(t,e,n.prior?z(n.prior,E):E),n.destinations);const F=t.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)");for(const n of _)F.run(n,n);return{delivered:u.length}}i(re,"publish");export{ne as levelFor,oe as listenConfigFor,B as matchSubject,re as publish,U as resolveDelivery};
7
+ AND m.event = 'edited' AND m.object_id = ?`),h=new Map,w=new Set,T=t.prepare("DELETE FROM deliveries WHERE delivery_id = ?");for(const s of u){const c=L.all(s.nodeId,n.object.id),d=c.at(-1);for(const j of c)T.run(j.delivery_id),w.add(j.message_id);const f=d?.message_id??null,y=h.get(f)??{prior:d?.body_json?JSON.parse(d.body_json).push?.attrs??null:null,destinations:[]};y.destinations.push(s),h.set(f,y)}for(const s of h.values())b(t,O(t,n,s.prior?Z(s.prior,_):_),s.destinations);const C=t.prepare("DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)");for(const s of w)C.run(s,s);return{delivered:u.length}}i(fe,"publish");export{ce as levelFor,ue as listenConfigFor,K as matchSubject,fe as publish,X as resolveDelivery,V as resolveLevel};
@@ -1,2 +1,2 @@
1
- var w=Object.defineProperty;var n=(t,r)=>w(t,"name",{value:r,configurable:!0});import{recordActedOn as s}from"./edges.js";import{levelFor as u,publish as d}from"./events.js";import{createObject as E,getByRowid as h}from"./objects.js";import{recordWatch as b}from"./watches.js";function p(t){return t.split(`
2
- `,1)[0].trim()}n(p,"firstLine");function m(t,r){return`${p(t)} (${r})`}n(m,"previewOf");function j(t,r){return t.prepare("SELECT * FROM jobs WHERE object = ?").get(r.rowid)??null}n(j,"jobRow");function R(t,r,o){if(r.type!=="node")throw new Error(`startJob: ${r.id} is a ${r.type}, not a node`);const e=E(t,{...o.id===void 0?{}:{id:o.id},type:"job",kind:"bash",owner:r.rowid,privacy:"public",name:p(o.command)||"bash",preview:m(o.command,"running")});return t.prepare("INSERT INTO jobs (object, command, status, exit_code, job_dir, log_path) VALUES (?, ?, 'running', NULL, ?, ?)").run(e.rowid,o.command,o.jobDir,o.logPath),s(t,r,e),b(t,r,e,"job-start"),d(t,{event:"created",object:e,actor:r,level:u("created"),attrs:{}}),e}n(R,"startJob");function T(t,r,o){const e=j(t,r);if(e===null)throw new Error(`finishJob: ${r.id} has no jobs row`);if(e.status!=="running")return r;const i="killedBy"in o,a=i?"killed":"exited",l=i?null:o.exitCode;t.prepare("UPDATE jobs SET status = ?, exit_code = ? WHERE object = ?").run(a,l,r.rowid);const f=i?"killed":`exited ${l}`;t.prepare("UPDATE objects SET preview = ? WHERE rowid = ?").run(m(e.command,f),r.rowid);const c=h(t,r.rowid);return i&&o.killedBy!==null&&s(t,o.killedBy,c),d(t,{event:a,object:c,actor:i?o.killedBy:null,level:u(a),attrs:i?{"log-path":e.log_path,...o.reason===void 0?{}:{reason:o.reason}}:{"exit-code":l,"log-path":e.log_path}}),c}n(T,"finishJob");export{T as finishJob,j as jobRow,R as startJob};
1
+ var f=Object.defineProperty;var n=(t,r)=>f(t,"name",{value:r,configurable:!0});import{recordActedOn as s}from"./edges.js";import{publish as u}from"./events.js";import{createObject as w,getByRowid as E}from"./objects.js";import{recordWatch as h}from"./watches.js";function d(t){return t.split(`
2
+ `,1)[0].trim()}n(d,"firstLine");function p(t,r){return`${d(t)} (${r})`}n(p,"previewOf");function b(t,r){return t.prepare("SELECT * FROM jobs WHERE object = ?").get(r.rowid)??null}n(b,"jobRow");function R(t,r,o){if(r.type!=="node")throw new Error(`startJob: ${r.id} is a ${r.type}, not a node`);const e=w(t,{...o.id===void 0?{}:{id:o.id},type:"job",kind:"bash",owner:r.rowid,privacy:"public",name:d(o.command)||"bash",preview:p(o.command,"running")});return t.prepare("INSERT INTO jobs (object, command, status, exit_code, job_dir, log_path) VALUES (?, ?, 'running', NULL, ?, ?)").run(e.rowid,o.command,o.jobDir,o.logPath),s(t,r,e),h(t,r,e,"job-start"),u(t,{event:"created",object:e,actor:r,attrs:{}}),e}n(R,"startJob");function T(t,r,o){const e=b(t,r);if(e===null)throw new Error(`finishJob: ${r.id} has no jobs row`);if(e.status!=="running")return r;const i="killedBy"in o,c=i?"killed":"exited",a=i?null:o.exitCode;t.prepare("UPDATE jobs SET status = ?, exit_code = ? WHERE object = ?").run(c,a,r.rowid);const m=i?"killed":`exited ${a}`;t.prepare("UPDATE objects SET preview = ? WHERE rowid = ?").run(p(e.command,m),r.rowid);const l=E(t,r.rowid);return i&&o.killedBy!==null&&s(t,o.killedBy,l),u(t,{event:c,object:l,actor:i?o.killedBy:null,attrs:i?{"log-path":e.log_path,...o.reason===void 0?{}:{reason:o.reason}}:{"exit-code":a,"log-path":e.log_path}}),l}n(T,"finishJob");export{T as finishJob,b as jobRow,R as startJob};
@@ -0,0 +1,19 @@
1
+ import type { DocFieldsDTO, ObjectDTO } from '../../../api/dto/objects.js';
2
+ import { type ObjectKind } from './kind.js';
3
+ export interface DocumentReadData {
4
+ summary: string | null;
5
+ revision: number;
6
+ heads: Array<{
7
+ rev_id: string;
8
+ number: number;
9
+ author_id: string | null;
10
+ created_at: string;
11
+ }>;
12
+ /** One body per head, in `heads` order. */
13
+ bodies: string[];
14
+ needs_merge: boolean;
15
+ listing?: ObjectDTO[];
16
+ attachments?: string;
17
+ fields?: DocFieldsDTO;
18
+ }
19
+ export declare const documentKind: ObjectKind;
@@ -0,0 +1,5 @@
1
+ var y=Object.defineProperty;var m=(n,e)=>y(n,"name",{value:e,configurable:!0});import{recordMemoryReadsIn as v}from"../../canvas/memory-reads.js";import{deliveryDocOf as _,nodeSubject as x,ownerHandle as $}from"../../substrate/delivery/corpus.js";import{recordContentDelivery as R}from"../../substrate/delivery/deliver.js";import{renderExplicitRead as D}from"../../substrate/delivery/render-event.js";import{canRead as S}from"../access.js";import{documentRecord as j,readDocument as k}from"../documents.js";import{loadLedger as E,saveLedger as N}from"../exposures.js";import{getByRowid as O}from"../objects.js";import{rulesOf as f}from"../rules.js";import{objectDTO as d}from"./dto.js";import{withBase as M}from"./kind.js";import{attr as l,header as I,section as L}from"./render.js";function A(n,e,t){const i=`${t.name}/`;return n.prepare(`SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND owner IS ?
2
+ AND substr(name, 1, ?) = ? ORDER BY name`).all(t.owner,i.length,i).filter(s=>!s.name.slice(i.length).includes("/")&&S(n,e,s)).map(s=>d(n,s))}m(A,"listingOf");function b(n,e,t){const i=e.class==="node",o=i?E(n,e.node.rowid):{node:0,preferenceSnapshot:null,objects:new Map,listed:new Map},s={db:n,reader:e,subject:i?x(n,e):null,ledger:o,channel:"transcript"},a=D(s,_(n,e,t));return i&&(R(n,e,a.delivered),N(n,o)),{attachments:a.attachments,linked:a.linked.map(u=>u.object)}}m(b,"explicitReadAttachments");function B(n,e,t){if(e.class!=="node"||t.kind!=="knowledge"&&t.kind!=="preference"||t.owner===null)return;const i=O(n,t.owner);i!==null&&v(n,e.node.id,[{name:t.name,scope:$(i),at:new Date().toISOString(),type:"read"}])}m(B,"recordExplicitMemoryRead");function T(n,e){const t=[];if(e.attachments&&t.push(e.attachments,""),t.push(I("object",n,l("privacy",n.privacy)+l("revision",e.revision))),t.push(`Preview: ${n.preview}`),e.summary&&t.push(`Summary: ${e.summary}`),e.fields!==void 0&&t.push(`<fields>
3
+ ${JSON.stringify(e.fields,null,2)}
4
+ </fields>`),e.needs_merge?(e.heads.forEach((i,o)=>{t.push(`<head${l("rev",i.rev_id)}${l("number",i.number)}${l("author",i.author_id)}>`,e.bodies[o]??"","</head>")}),t.push(`This document has ${e.heads.length} heads. Your next \`crtr doc edit\` writes the merged body and joins them into one revision.`)):t.push("",e.bodies[0]??""),e.listing&&e.listing.length>0){const i=e.listing.some(a=>a.owner?.id===n.owner?.id&&a.name.startsWith(`${n.name}/`)),o=e.listing.some(a=>a.owner?.id!==n.owner?.id||!a.name.startsWith(`${n.name}/`)),s=i&&o?`Names under ${n.name}/ and documents it links to`:i?`Names under ${n.name}/`:"Documents it links to";t.push("",...L(s,e.listing))}return t.join(`
5
+ `)}m(T,"render");const V={name:"document",addressable:"name",events:M({edited:{level:8},deleted:{level:8}}),listItem(n,e){return{summary:j(n,e).summary??null,rules:f(n,e).length}},read(n,e,t,i){const o=k(n,e,t,i.reposFn);B(n,e,t);const s=o.record,a=A(n,e,t),{attachments:u,linked:h}=b(n,e,t),g=new Set(a.map(r=>r.id)),p=[...a,...h.filter(r=>!g.has(r.id)).map(r=>d(n,r))],c={summary:s.summary??null,revision:s.revision,heads:o.heads.map(r=>({rev_id:r.rev_id,number:r.number,author_id:r.author,created_at:r.created_at})),bodies:o.heads.map(r=>r.body),needs_merge:o.needsMerge,...p.length>0?{listing:p}:{},...u===""?{}:{attachments:u}};return i.fields&&(c.fields={why_it_exists:s.why_it_exists??null,unlisted:s.unlisted,lint_ignore:s.lint_ignore??[],extensions:s.extensions??{},rules:f(n,t).map(({matchFrontmatter:r,...w})=>({...w,...r===void 0?{}:{match_frontmatter:r}}))}),{data:c,text:T(d(n,t),c)}}};export{V as documentKind};
@@ -0,0 +1,5 @@
1
+ /** An object row as the `/v1/objects` wire shape. */
2
+ import type { DatabaseSync } from 'node:sqlite';
3
+ import type { ObjectDTO } from '../../../api/dto/objects.js';
4
+ import type { ObjectRow } from '../types.js';
5
+ export declare function objectDTO(db: DatabaseSync, o: ObjectRow): ObjectDTO;
@@ -0,0 +1 @@
1
+ var i=Object.defineProperty;var d=(n,e)=>i(n,"name",{value:e,configurable:!0});import{getByRowid as a}from"../objects.js";function c(n,e){const t=e.owner===null?null:a(n,e.owner),r=t?{id:t.id,type:t.type,handle:t.type==="node"?t.id:t.name}:null;return{id:e.id,type:e.type,kind:e.kind,owner:r,privacy:e.privacy,name:e.name,preview:e.preview??"",created_at:e.created_at,deleted_at:e.deleted_at}}d(c,"objectDTO");export{c as objectDTO};
@@ -0,0 +1,2 @@
1
+ import { type ObjectKind } from './kind.js';
2
+ export declare const jobKind: ObjectKind;
@@ -0,0 +1,2 @@
1
+ import{jobRow as m}from"../jobs.js";import{objectDTO as c}from"./dto.js";import{recordLiveRead as l,withBase as p}from"./kind.js";import{attr as a,header as h}from"./render.js";const f={name:"job",addressable:"id",events:p({exited:{level:7},killed:{level:7}}),read(r,s,e,d){const o=m(r,e);if(!o)throw new Error(`job object ${e.id} has no jobs row`);l(r,s,e,d.reposFn);const n=c(r,e),t={command:o.command,status:o.status,exit_code:o.exit_code,log_path:o.log_path},i=[h("object",n,a("status",t.status)+a("exit_code",t.exit_code)),`Command: ${t.command}`,`Log: ${t.log_path}`].join(`
2
+ `);return{data:t,text:i}}};export{f as jobKind};
@@ -0,0 +1,39 @@
1
+ /** The one interface every canvas object kind implements: its declared events, how it is
2
+ * addressed, and how it reads. The kind name is what `objects.type` stores. */
3
+ import type { DatabaseSync } from 'node:sqlite';
4
+ import type { ReposFn } from '../reader.js';
5
+ import type { Level, ObjectRow, Reader } from '../types.js';
6
+ /** One declared event: its default level and, only where a sender may choose, the levels it may pick. */
7
+ export interface EventSpec {
8
+ level: Level;
9
+ levels?: readonly Level[];
10
+ }
11
+ export type EventCatalog = Readonly<Record<string, EventSpec>>;
12
+ /** What a read answers: the kind's structured fields and exactly the text `crtr canvas read` prints. */
13
+ export interface KindRead {
14
+ data: Record<string, unknown>;
15
+ text: string;
16
+ }
17
+ export interface ReadOpts {
18
+ /** A document read also returns its optional fields. */
19
+ fields: boolean;
20
+ reposFn: ReposFn;
21
+ }
22
+ export interface ObjectKind {
23
+ /** Stored in `objects.type`. */
24
+ name: string;
25
+ /** The base events merged with the kind's own. */
26
+ events: EventCatalog;
27
+ /** `name`: found by `<owner>/<name>`; `id`: by id only; `handle`: an owner's handle. */
28
+ addressable: 'name' | 'id' | 'handle';
29
+ /** Records the read (a watch and a `read` event) and renders the object. */
30
+ read(db: DatabaseSync, reader: Reader, object: ObjectRow, opts: ReadOpts): KindRead;
31
+ /** Extra fields a list or search row carries. */
32
+ listItem?(db: DatabaseSync, object: ObjectRow): Record<string, unknown>;
33
+ }
34
+ /** Events every kind inherits. */
35
+ export declare const BASE_EVENTS: EventCatalog;
36
+ /** The base events with a kind's own merged over them. */
37
+ export declare function withBase(own?: EventCatalog): EventCatalog;
38
+ /** A live read of a non-document object: the reader watches it and its other watchers hear `read`. */
39
+ export declare function recordLiveRead(db: DatabaseSync, reader: Reader, object: ObjectRow, reposFn: ReposFn): void;
@@ -0,0 +1 @@
1
+ var l=Object.defineProperty;var d=(e,r)=>l(e,"name",{value:r,configurable:!0});import{actorOf as a}from"../documents.js";import{publish as c}from"../events.js";import{recordWatch as n}from"../watches.js";const f={created:{level:4},read:{level:1},watched:{level:1},deleted:{level:5},configured:{level:1}};function s(e={}){return{...f,...e}}d(s,"withBase");function h(e,r,t,i){const o=a(r);o.rowid!==t.rowid&&(n(e,o,t,"read"),c(e,{event:"read",object:t,actor:o,attrs:{}},i))}d(h,"recordLiveRead");export{f as BASE_EVENTS,h as recordLiveRead,s as withBase};
@@ -0,0 +1,2 @@
1
+ import { type ObjectKind } from './kind.js';
2
+ export declare const nodeKind: ObjectKind;
@@ -0,0 +1,4 @@
1
+ var R=Object.defineProperty;var a=(e,t)=>R(e,"name",{value:t,configurable:!0});import{readGoal as u}from"../../runtime/kickoff.js";import{canRead as w}from"../access.js";import{objectDTO as p}from"./dto.js";import{recordLiveRead as j,withBase as L}from"./kind.js";import{attr as O,header as _,section as d}from"./render.js";const n=[3,5,7];function h(e){return e?.split(`
2
+ `).map(t=>t.trim()).find(t=>t!=="")??""}a(h,"firstLine");const F={name:"node",addressable:"id",events:L({messaged:{level:2},sent:{level:1},"report.update":{level:5,levels:n},"report.final":{level:7},"report.result":{level:5},"notice.crash":{level:5,levels:n},"notice.deadline":{level:5,levels:n},"notice.close":{level:5,levels:n}}),read(e,t,o,m){const E=e.prepare("SELECT status FROM nodes WHERE node_id = ?").get(o.id),s=a(l=>l.filter(i=>w(e,t,i)).map(i=>p(e,i)),"visible"),v=e.prepare(`SELECT o.* FROM nodes n JOIN objects o ON o.id = n.node_id
3
+ WHERE n.parent = ? AND o.deleted_at IS NULL ORDER BY o.rowid`).all(o.id),c=e.prepare("SELECT * FROM objects WHERE owner = ? AND deleted_at IS NULL ORDER BY rowid").all(o.rowid);j(e,t,o,m.reposFn);const r={status:E?.status??"deleted",mandate_first_line:h(u(o.id)),children:s(v),owned:s(c.filter(l=>l.type!=="job")),jobs:s(c.filter(l=>l.type==="job"))},f=[_("object",p(e,o),O("status",r.status)),`Mandate: ${r.mandate_first_line}`,...d("Children",r.children),...d("Owns",r.owned),...d("Jobs",r.jobs)].join(`
4
+ `);return{data:r,text:f}}};export{F as nodeKind};
@@ -0,0 +1,2 @@
1
+ import { type ObjectKind } from './kind.js';
2
+ export declare const ownerKinds: ObjectKind[];
@@ -0,0 +1,2 @@
1
+ var m=Object.defineProperty;var d=(a,t)=>m(a,"name",{value:t,configurable:!0});import{objectDTO as c}from"./dto.js";import{recordLiveRead as l,withBase as h}from"./kind.js";import{header as y}from"./render.js";function n(a){return{name:a,addressable:"handle",events:h(),read(t,o,e,s){const r=t.prepare("SELECT scheme, external_id FROM identities WHERE object = ?").get(e.rowid);l(t,o,e,s.reposFn);const i={handle:e.name,display_name:e.preview||e.name,identity:{scheme:r?.scheme??"",external_id:r?.external_id??""}},p=[y("object",c(t,e)),`Handle: ${i.handle}`,`Display name: ${i.display_name}`,`Identity: ${i.identity.scheme}:${i.identity.external_id}`].join(`
2
+ `);return{data:i,text:p}}}}d(n,"ownerKind");const E=[n("person"),n("app"),n("repo"),n("profile")];export{E as ownerKinds};
@@ -0,0 +1,8 @@
1
+ import type { EventSpec, ObjectKind } from './kind.js';
2
+ /** The names of every declared kind. */
3
+ export declare function kindNames(): string[];
4
+ export declare function isKind(name: string): boolean;
5
+ /** The kind named `name`, or throws `unknown_kind`. */
6
+ export declare function kindOf(name: string): ObjectKind;
7
+ /** The declared spec of `event` on `kind`, or throws `unknown_event` naming the declared events. */
8
+ export declare function eventSpec(kind: string, event: string): EventSpec;
@@ -0,0 +1 @@
1
+ var s=Object.defineProperty;var t=(n,e)=>s(n,"name",{value:e,configurable:!0});import{CrtrError as d}from"../../errors.js";import{ExitCode as c}from"../../../types.js";import{documentKind as u}from"./document.js";import{jobKind as m}from"./job.js";import{nodeKind as p}from"./node.js";import{ownerKinds as a}from"./owner.js";let f=null;function r(){return f??=new Map([u,p,m,...a].map(n=>[n.name,n])),f}t(r,"kinds");function k(){return[...r().keys()]}t(k,"kindNames");function K(n){return r().has(n)}t(K,"isKind");function w(n){const e=r().get(n);if(e===void 0)throw new d("unknown_kind",`no object kind named ${JSON.stringify(n)}; the kinds are ${k().join(", ")}`,c.USAGE,{kind:n});return e}t(w,"kindOf");function S(n,e){const o=w(n).events,i=Object.hasOwn(o,e)?o[e]:void 0;if(i===void 0)throw new d("unknown_event",`${n} declares no event ${JSON.stringify(e)}; it declares ${Object.keys(o).join(", ")}`,c.USAGE,{kind:n,event:e,declared:Object.keys(o)});return i}t(S,"eventSpec");export{S as eventSpec,K as isKind,k as kindNames,w as kindOf};
@@ -0,0 +1,9 @@
1
+ /** Text helpers every kind's `read` renders with, and the CLI renders deleted answers with.
2
+ * Pure: imports only DTO types, so the CLI can load it without the canvas. */
3
+ import type { ObjectDTO } from '../../../api/dto/objects.js';
4
+ export declare function attr(name: string, value: string | number | null | undefined): string;
5
+ /** One line per object: id, name, type/kind, owner handle and preview. */
6
+ export declare function objectRow(o: ObjectDTO): string;
7
+ /** `<tag type kind id name owner …extra>` */
8
+ export declare function header(tag: string, o: ObjectDTO, extra?: string): string;
9
+ export declare function section(title: string, rows: ObjectDTO[]): string[];
@@ -0,0 +1 @@
1
+ var i=Object.defineProperty;var t=(e,n)=>i(e,"name",{value:n,configurable:!0});function r(e,n){return n==null||n===""?"":` ${e}="${String(n).replace(/"/g,"&quot;")}"`}t(r,"attr");function p(e){const n=e.kind?`${e.type}/${e.kind}`:e.type,d=e.owner?` owner=${e.owner.handle}`:"",$=e.deleted_at?" (deleted)":"";return`- ${e.id} [[${e.name}]] ${n}${d}${$} \u2014 ${e.preview}`}t(p,"objectRow");function u(e,n,d=""){return`<${e}${r("type",n.type)}${r("kind",n.kind)}${r("id",n.id)}${r("name",n.name)}${r("owner",n.owner?.handle)}${d}>`}t(u,"header");function a(e,n){return n.length===0?[]:[`${e}:`,...n.map(p)]}t(a,"section");export{r as attr,u as header,p as objectRow,a as section};
@@ -1,10 +1,11 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
- import type { ObjectRow, ObjectType, Privacy, Reader } from './types.js';
2
+ import type { ObjectRow, Privacy, Reader } from './types.js';
3
+ export type OwnerType = 'person' | 'app' | 'repo' | 'profile';
3
4
  export declare function getById(db: DatabaseSync, id: string): ObjectRow | null;
4
5
  export declare function getByRowid(db: DatabaseSync, rowid: number): ObjectRow | null;
5
6
  export declare function createObject(db: DatabaseSync, spec: {
6
7
  id?: string;
7
- type: ObjectType;
8
+ type: string;
8
9
  kind: string | null;
9
10
  owner: number | null;
10
11
  privacy: Privacy;
@@ -15,7 +16,7 @@ export declare function createObject(db: DatabaseSync, spec: {
15
16
  * (scheme, externalId). Handles are unique per owner type: a handle already held
16
17
  * by a different identity of the same type throws `conflict` naming both; it is
17
18
  * never renamed silently. */
18
- export declare function ensureOwner(db: DatabaseSync, type: ObjectType, scheme: string, externalId: string, handle: string, owner?: ObjectRow): ObjectRow;
19
+ export declare function ensureOwner(db: DatabaseSync, type: OwnerType, scheme: string, externalId: string, handle: string, owner?: ObjectRow): ObjectRow;
19
20
  /** The owner object of a node grantee: `user:local` → person `user`; `app:<x>` → app `<x>`. */
20
21
  export declare function ownerForGrantee(db: DatabaseSync, grantee: string): ObjectRow;
21
22
  /** The owner's live document named `name`, or null. */
@@ -24,7 +25,7 @@ export declare function nodeObject(db: DatabaseSync, nodeId: string): ObjectRow;
24
25
  export declare function setObjectName(db: DatabaseSync, object: ObjectRow, name: string): void;
25
26
  export declare function setObjectKind(db: DatabaseSync, object: ObjectRow, kind: string | null): void;
26
27
  export interface ObjectFilter {
27
- type?: ObjectType;
28
+ type?: string;
28
29
  kind?: string;
29
30
  owner?: ObjectRow;
30
31
  privacy?: Privacy;
@@ -1,9 +1,9 @@
1
- var S=Object.defineProperty;var s=(o,e)=>S(o,"name",{value:e,configurable:!0});import{CrtrError as L,notFound as j}from"../errors.js";import{ExitCode as b}from"../../types.js";import{newNodeId as T}from"../runtime/nodes.js";import{canRead as l}from"./access.js";import{bodyOf as x}from"./bodies.js";const y=new Set(["person","app","repo","profile"]);function h(o){return o||null}s(h,"rowFrom");function I(o,e){return h(o.prepare("SELECT * FROM objects WHERE id = ?").get(e))}s(I,"getById");function W(o,e){return h(o.prepare("SELECT * FROM objects WHERE rowid = ?").get(e))}s(W,"getByRowid");function _(o,e){return o.prepare(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at)
2
- VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(e.id??T(),e.type,e.kind,e.owner,e.privacy,e.name,e.preview,new Date().toISOString())}s(_,"createObject");function R(o,e,t,p,r,n){if(!y.has(e))throw new Error(`ensureOwner: ${e} is not an owner type`);const a=o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
1
+ var S=Object.defineProperty;var s=(o,e)=>S(o,"name",{value:e,configurable:!0});import{CrtrError as L,notFound as j}from"../errors.js";import{ExitCode as b}from"../../types.js";import{newNodeId as T}from"../runtime/nodes.js";import{kindOf as x}from"./kinds/registry.js";import{canRead as l}from"./access.js";import{bodyOf as y}from"./bodies.js";const I=new Set(["person","app","repo","profile"]);function h(o){return o||null}s(h,"rowFrom");function _(o,e){return h(o.prepare("SELECT * FROM objects WHERE id = ?").get(e))}s(_,"getById");function M(o,e){return h(o.prepare("SELECT * FROM objects WHERE rowid = ?").get(e))}s(M,"getByRowid");function g(o,e){return x(e.type),o.prepare(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at)
2
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(e.id??T(),e.type,e.kind,e.owner,e.privacy,e.name,e.preview,new Date().toISOString())}s(g,"createObject");function R(o,e,t,p,r,n){if(!I.has(e))throw new Error(`ensureOwner: ${e} is not an owner type`);const a=o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
3
3
  WHERE i.scheme = ? AND i.external_id = ?`).get(t,p);if(a)return a;const c=o.prepare(`SELECT o.id, o.type, i.scheme, i.external_id FROM objects o
4
4
  LEFT JOIN identities i ON i.object = o.rowid
5
- WHERE o.name = ? AND o.type = ?`).get(r,e);if(c)throw new L("conflict",`handle "${r}" is held by ${c.type} ${c.id} (${c.scheme}:${c.external_id}); cannot give it to ${e} ${t}:${p}`,b.GENERAL,{handle:r,holder:c.id});const d=_(o,{type:e,kind:null,owner:e==="profile"?n?.rowid??null:null,privacy:"public",name:r,preview:""});return o.prepare("INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(d.rowid,t,p),d}s(R,"ensureOwner");function H(o,e){if(e==="user:local")return R(o,"person","local-user",e,"user");if(/^app:[a-z0-9][a-z0-9._-]*$/.test(e))return R(o,"app","app",e,e.slice(4));throw new Error(`node grantee ${JSON.stringify(e)} is neither app:<name> nor user:local`)}s(H,"ownerForGrantee");function M(o,e,t){return h(o.prepare("SELECT * FROM objects WHERE type = 'document' AND owner = ? AND name = ? AND deleted_at IS NULL").get(e,t))}s(M,"liveDocumentNamed");function U(o,e){const t=I(o,e);if(!t||t.type!=="node")throw j(`no node object ${e}`);return t}s(U,"nodeObject");function k(o,e,t){o.prepare("UPDATE objects SET name = ? WHERE rowid = ?").run(t,e.rowid)}s(k,"setObjectName");function P(o,e,t){o.prepare("UPDATE objects SET kind = ? WHERE rowid = ?").run(t,e.rowid)}s(P,"setObjectKind");function B(o,e,t,p={}){const r=["rowid > ?"],n=[p.after_rowid??0];return t.type!==void 0&&(r.push("type = ?"),n.push(t.type)),t.kind!==void 0&&(r.push("kind = ?"),n.push(t.kind)),t.owner!==void 0&&(r.push("owner = ?"),n.push(t.owner.rowid)),t.privacy!==void 0&&(r.push("privacy = ?"),n.push(t.privacy)),t.name!==void 0&&(r.push("name = ?"),n.push(t.name)),r.push(t.deleted?"deleted_at IS NOT NULL":"deleted_at IS NULL"),g(o,e,`SELECT * FROM objects WHERE ${r.join(" AND ")}`,n,Math.min(p.limit??100,100))}s(B,"listObjects");function J(o,e,t){const r=[],n=`%${t.text.replace(/[\\%_]/g,i=>`\\${i}`)}%`;if(!t.grep){const i=o.prepare(`SELECT o.*, d.summary AS _summary FROM objects o LEFT JOIN documents d ON d.object = o.rowid
5
+ WHERE o.name = ? AND o.type = ?`).get(r,e);if(c)throw new L("conflict",`handle "${r}" is held by ${c.type} ${c.id} (${c.scheme}:${c.external_id}); cannot give it to ${e} ${t}:${p}`,b.GENERAL,{handle:r,holder:c.id});const d=g(o,{type:e,kind:null,owner:e==="profile"?n?.rowid??null:null,privacy:"public",name:r,preview:""});return o.prepare("INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(d.rowid,t,p),d}s(R,"ensureOwner");function k(o,e){if(e==="user:local")return R(o,"person","local-user",e,"user");if(/^app:[a-z0-9][a-z0-9._-]*$/.test(e))return R(o,"app","app",e,e.slice(4));throw new Error(`node grantee ${JSON.stringify(e)} is neither app:<name> nor user:local`)}s(k,"ownerForGrantee");function U(o,e,t){return h(o.prepare("SELECT * FROM objects WHERE type = 'document' AND owner = ? AND name = ? AND deleted_at IS NULL").get(e,t))}s(U,"liveDocumentNamed");function P(o,e){const t=_(o,e);if(!t||t.type!=="node")throw j(`no node object ${e}`);return t}s(P,"nodeObject");function B(o,e,t){o.prepare("UPDATE objects SET name = ? WHERE rowid = ?").run(t,e.rowid)}s(B,"setObjectName");function J(o,e,t){o.prepare("UPDATE objects SET kind = ? WHERE rowid = ?").run(t,e.rowid)}s(J,"setObjectKind");function K(o,e,t,p={}){const r=["rowid > ?"],n=[p.after_rowid??0];return t.type!==void 0&&(r.push("type = ?"),n.push(t.type)),t.kind!==void 0&&(r.push("kind = ?"),n.push(t.kind)),t.owner!==void 0&&(r.push("owner = ?"),n.push(t.owner.rowid)),t.privacy!==void 0&&(r.push("privacy = ?"),n.push(t.privacy)),t.name!==void 0&&(r.push("name = ?"),n.push(t.name)),r.push(t.deleted?"deleted_at IS NOT NULL":"deleted_at IS NULL"),A(o,e,`SELECT * FROM objects WHERE ${r.join(" AND ")}`,n,Math.min(p.limit??100,100))}s(K,"listObjects");function Y(o,e,t){const r=[],n=`%${t.text.replace(/[\\%_]/g,i=>`\\${i}`)}%`;if(!t.grep){const i=o.prepare(`SELECT o.*, d.summary AS _summary FROM objects o LEFT JOIN documents d ON d.object = o.rowid
6
6
  WHERE o.deleted_at IS NULL AND (o.name LIKE ?1 ESCAPE '\\' OR o.preview LIKE ?1 ESCAPE '\\' OR d.summary LIKE ?1 ESCAPE '\\')
7
7
  ORDER BY o.rowid`).all(n),u=t.text.toLowerCase();for(const m of i){const{_summary:w,...E}=m,f=E;if(!l(o,e,f))continue;const O=f.name.toLowerCase().includes(u)?"name":f.preview.toLowerCase().includes(u)?"preview":"summary";if(r.push({object:f,field:O,...O==="summary"?{text:String(w)}:{}}),r.length>=100)return r}}if(!t.body&&!t.grep)return r;const a=new Set(r.map(i=>i.object.rowid)),c=t.grep?(i=>u=>i.test(u))(new RegExp(t.text)):i=>i.includes(t.text),d=o.prepare("SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL ORDER BY rowid").all(),N=o.prepare(`SELECT r.body_hash, d.source_path FROM revisions r JOIN documents d ON d.object = r.document WHERE r.document = ?
8
- AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rowid)`);for(const i of d)if(!(a.has(i.rowid)||!l(o,e,i)))for(const{body_hash:u,source_path:m}of N.all(i.rowid)){const w=x(u,m).split(`
9
- `),E=w.findIndex(c);if(!(E<0)){if(r.push({object:i,field:"body",line:E+1,text:w[E]}),r.length>=100)return r;break}}return r}s(J,"searchObjects");function g(o,e,t,p,r){const n=[],a=o.prepare(`${t} ORDER BY rowid LIMIT ${r*4}`);for(;;){const c=a.all(...p);for(const d of c)if(l(o,e,d)&&n.push(d),n.length===r)return n;if(c.length<r*4)return n;p[0]=c[c.length-1].rowid}}s(g,"readablePages");export{_ as createObject,R as ensureOwner,I as getById,W as getByRowid,B as listObjects,M as liveDocumentNamed,U as nodeObject,H as ownerForGrantee,J as searchObjects,P as setObjectKind,k as setObjectName};
8
+ AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rowid)`);for(const i of d)if(!(a.has(i.rowid)||!l(o,e,i)))for(const{body_hash:u,source_path:m}of N.all(i.rowid)){const w=y(u,m).split(`
9
+ `),E=w.findIndex(c);if(!(E<0)){if(r.push({object:i,field:"body",line:E+1,text:w[E]}),r.length>=100)return r;break}}return r}s(Y,"searchObjects");function A(o,e,t,p,r){const n=[],a=o.prepare(`${t} ORDER BY rowid LIMIT ${r*4}`);for(;;){const c=a.all(...p);for(const d of c)if(l(o,e,d)&&n.push(d),n.length===r)return n;if(c.length<r*4)return n;p[0]=c[c.length-1].rowid}}s(A,"readablePages");export{g as createObject,R as ensureOwner,_ as getById,M as getByRowid,K as listObjects,U as liveDocumentNamed,P as nodeObject,k as ownerForGrantee,Y as searchObjects,J as setObjectKind,B as setObjectName};
@@ -1,10 +1,10 @@
1
1
  /** Shared types of the canvas graph: documents, nodes, jobs and their owners as rows of `objects`. */
2
- export type ObjectType = 'document' | 'node' | 'job' | 'person' | 'app' | 'repo' | 'profile';
3
2
  export type Privacy = 'public' | 'friends' | 'private';
4
3
  export interface ObjectRow {
5
4
  rowid: number;
6
5
  id: string;
7
- type: ObjectType;
6
+ /** The kind name (`node`, `document`, `job`, an owner kind); the kind registry validates it on create. */
7
+ type: string;
8
8
  kind: string | null;
9
9
  owner: number | null;
10
10
  privacy: Privacy;
@@ -13,7 +13,6 @@ export interface ObjectRow {
13
13
  created_at: string;
14
14
  deleted_at: string | null;
15
15
  }
16
- export type EventName = 'created' | 'read' | 'watched' | 'deleted' | 'edited' | 'messaged' | 'sent' | 'configured' | 'report.update' | 'report.final' | 'report.result' | 'notice.crash' | 'notice.deadline' | 'notice.close' | 'exited' | 'killed';
17
16
  export type Level = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
18
17
  export type ListenDelivery = 'interrupt' | 'steer' | 'after-turn' | 'next-turn';
19
18
  /** Stored as `{"10":"interrupt",...}`. */
@@ -24,10 +23,12 @@ export type ListenConfig = Array<{
24
23
  export declare const DEFAULT_LISTEN: ListenConfig;
25
24
  export type WatchStart = 'spawn' | 'job-start' | 'ask' | 'read' | 'write' | 'act' | 'message' | 'deliver' | 'fork';
26
25
  export interface CanvasEvent {
27
- event: EventName;
26
+ /** An event the object's kind declares; `publish` validates it against the registry. */
27
+ event: string;
28
28
  object: ObjectRow;
29
29
  actor: ObjectRow | null;
30
- level: Level;
30
+ /** Omitted: the declared level. Supplied: must equal it or be one of the event's declared `levels`. */
31
+ level?: Level;
31
32
  /** revision, rationale, exit-code, log-path, request id …; never content */
32
33
  attrs: Record<string, string | number | boolean | null>;
33
34
  /** the act's pointer (inbox entry id, report doc id, revision rev_id) */
@@ -1,6 +1,7 @@
1
1
  import type { AgentSession, AgentSessionEvent, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcSessionState, SessionEntry, SessionInfo, SettingsConfig, SessionStats } from '@earendil-works/pi-coding-agent';
2
2
  import type { Api, ImageContent, Model } from '@earendil-works/pi-ai';
3
3
  import type { BrokerErrorFrame, BrokerNodeNamedFrame } from '../../api/dto/broker.js';
4
+ import type { DeliveryRefDTO } from '../../api/dto/delivery.js';
4
5
  import type { ToolGroupSummary } from '../../shared/tool-groups.js';
5
6
  import type { ModelIntent } from '../../types.js';
6
7
  import type { MessageOrigin, MessageVisibility } from './stamp/protocol.js';
@@ -524,13 +525,7 @@ export interface WireSettings extends SettingsConfig {
524
525
  * resolution is by `name` alone). No body/source path: this is a metadata-only
525
526
  * inventory, never the document content. `gatewayVisible` is broker-computed
526
527
  * chat-surface eligibility; terminal viewers retain every row. */
527
- export interface RefMeta {
528
- name: string;
529
- kind: 'knowledge' | 'preference';
530
- scope: 'node' | 'project' | 'profile' | 'user' | 'builtin';
531
- shortForm: string;
532
- gatewayVisible: boolean;
533
- }
528
+ export type RefMeta = DeliveryRefDTO;
534
529
  export interface ListModelsData {
535
530
  type: 'data';
536
531
  id: string;
@@ -1 +1 @@
1
- var N=Object.defineProperty;var o=(e,t)=>N(e,"name",{value:t,configurable:!0});import{statSync as P}from"node:fs";import{isAbsolute as T}from"node:path";import{ApiError as s}from"../../../api/index.js";import{readExternalActionDelivery as C}from"../../../core/canvas/deliveries.js";import{isSafeNodeId as I}from"../../../core/canvas/paths.js";import{getNode as b}from"../../../core/canvas/canvas.js";import{assertScope as $}from"../../../core/scopes.js";import{resolvePageComponents as g}from"../../../core/config.js";import{resolveHumanAction as x}from"../../../core/human-actions.js";import{HUMAN_ACTION_NAME_PATTERN as D}from"../../../core/human/action-binding.js";import{createPageRequest as J,getRequest as F}from"../../../core/human/requests.js";import{preparePageSource as M}from"../../../core/human/page-authoring.js";import{publishPage as G,validatePageTicketResponses as H}from"../../../core/human/tickets.js";import{cancelHumanTicket as L,resolvePageTicket as U}from"../../human/finish.js";import{inboxPageProjection as B}from"./inbox-page.js";const K=1e3,q=200,m={placement:"inline",inbox:!0,reply:!1},v=["sessionName","askedBy","emittedAt","profileName","nodeId"];function c(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(c,"isPlainObject");function l(e,t,n){for(const r of Object.keys(e))if(!t.includes(r))throw new s(400,"invalid_request",`unexpected field in ${n}: ${r}`)}o(l,"requireNoExtraKeys");function i(e){return new s(400,"invalid_request",e)}o(i,"invalid");function w(e){if(!c(e))throw i("`page` must be an object");if(l(e,["dialect","source"],"`page`"),e.dialect!=="jsx"&&e.dialect!=="html")throw i('`page.dialect` must be "jsx" or "html"');if(typeof e.source!="string"||e.source.trim()==="")throw i("`page.source` must be the authored page text");return{dialect:e.dialect,source:e.source}}o(w,"validatePage");function S(e,t){const n=e??t;if(n!=="inline"&&n!=="panel")throw i('`delivery.placement` must be "inline" or "panel"');return n}o(S,"validatePlacement");function X(e,t){if(e===void 0)return m;if(!c(e))throw i("`delivery` must be an object");l(e,["placement","inbox","reply"],"`delivery`");const n=e.inbox??m.inbox,r=e.reply??m.reply;if(typeof n!="boolean")throw i("`delivery.inbox` must be boolean");if(typeof r!="boolean")throw i("`delivery.reply` must be boolean");if(r&&t===null)throw i("`delivery.reply` requires `requester_node_id`");return{placement:S(e.placement,m.placement),inbox:n,reply:r}}o(X,"validateCreateDelivery");function z(e,t){if(e===void 0)return t;if(!c(e))throw i("`delivery` must be an object");return l(e,["placement"],"`delivery`"),{...t,placement:S(e.placement,t.placement)}}o(z,"validateReplaceDelivery");function V(e){if(e===void 0)return;if(!c(e))throw i("`source` must be an object");l(e,v,"`source`");const t={};for(const n of v){const r=e[n];if(r!==void 0){if(typeof r!="string"||r==="")throw i(`\`source.${n}\` must be a nonempty string`);t[n]=r}}return t}o(V,"validateSource");function Y(e){if(typeof e!="string"||e==="")throw i("`creator_cwd` is required");if(!T(e))throw i("`creator_cwd` must be an absolute path");try{if(!P(e).isDirectory())throw i(`\`creator_cwd\` is not a directory: ${e}`)}catch(t){throw t instanceof s?t:i(`\`creator_cwd\` does not exist: ${e}`)}return e}o(Y,"validateCreatorCwd");function Q(e){if(e!==void 0){if(!c(e))throw i("`action` must be an object");if(l(e,["name","payload"],"`action`"),typeof e.name!="string"||!D.test(e.name))throw i("`action.name` must be a declared action name");return{name:e.name,payload:e.payload??null}}}o(Q,"validateActionSelection");function W(e){if(e==null)return null;if(typeof e!="string"||!I(e))throw i("`requester_node_id` must be a safe node id or null");return e}o(W,"validateRequesterNodeId");function Z(e){if(!c(e))throw i("request body is required");l(e,["page","delivery","source","requester_node_id","creator_cwd","action"],"the request body");const t=V(e.source),n=W(e.requester_node_id),r=Q(e.action);return{page:w(e.page),delivery:X(e.delivery,n),...t===void 0?{}:{source:t},requesterNodeId:n,creatorCwd:Y(e.creator_cwd),...r===void 0?{}:{action:r}}}o(Z,"validateCreateRequest");function ee(e,t){if(!c(e))throw i("request body is required");return l(e,["page","delivery"],"the request body"),{page:w(e.page),delivery:z(e.delivery,t)}}o(ee,"validateReplaceRequest");function _(e,t,n){if(e!==void 0){if(typeof e!="string"||e.trim()===""||e.length>n)throw i(`\`${t}\` must be nonempty after trim and at most ${n} characters`);return e}}o(_,"optionalBoundedString");function te(e){if(!c(e))throw i("request body is required");if(l(e,["responses","actor"],"the request body"),!c(e.responses))throw i("`responses` must be an object");const t=_(e.actor,"actor",q);return{responses:e.responses,...t===void 0?{}:{actor:t}}}o(te,"validateRespondRequest");function ne(e){if(e===void 0)return{};if(!c(e))throw i("request body must be an object");l(e,["reason","actor"],"the request body");const t=_(e.reason,"reason",K),n=_(e.actor,"actor",q);return{...t===void 0?{}:{reason:t},...n===void 0?{}:{actor:n}}}o(ne,"validateSettleRequest");function h(e){const t=e.params.request_id;if(typeof t!="string"||t==="")throw i("request id is required");return t}o(h,"requestId");function f(e){const t=F(e);if(t===null||t.kind!=="page")throw new s(404,"not_found","no request with that id");return t}o(f,"requirePageRequest");function A(e){try{const t=JSON.parse(e.specification_json);if(!c(t))throw new Error("not an object");return t}catch{throw new s(500,"internal","the request specification could not be read")}}o(A,"manifest");function re(e){if(e.result_json===null)return null;try{return JSON.parse(e.result_json)}catch{throw new s(500,"internal","the request result could not be read")}}o(re,"terminalResult");function oe(e){if(e.action_json===null)return null;try{const t=JSON.parse(e.action_json);if(t.schema!=="crtr.human-action-binding/v1"||typeof t.name!="string")throw new Error("invalid action");return t}catch{throw new s(500,"internal","the request action could not be read")}}o(oe,"frozenAction");function ie(e){return e.state!=="settled"?"pending":e.outcome==="answered"?"answered":e.outcome==="dismissed"?"dismissed":"canceled"}o(ie,"requestState");function ae(e){if(e.action_delivery_id===null)return{state:"none",attempt:0};const t=C(`human-action:${e.inbox_ticket_id}`);if(t===null)return{state:"none",attempt:0};const n=t.state==="acknowledged"?"accepted":t.state==="permanent_failed"?"permanent_failed":t.state==="handed_off"?"running":"pending",r={state:n,attempt:t.attempts};return n==="pending"&&t.nextAttemptAt!==null&&(r.next_attempt_at=new Date(t.nextAttemptAt).toISOString()),t.acknowledgedAt!==null&&(r.accepted_at=new Date(t.acknowledgedAt).toISOString()),t.failedAt!==null&&(r.permanent_failed_at=new Date(t.failedAt).toISOString()),t.lastFailure!==null&&(r.last_failure=t.lastFailure),r}o(ae,"deliveryDetail");function y(e){const t=A(e),n=re(e),r=ie(e),a={request_id:e.request_id,inbox_ticket_id:e.inbox_ticket_id,request_dir:e.request_dir,kind:e.kind,state:r,title:e.title,source:t.source??{},emitted_at:t.source?.emittedAt??e.created_at};e.subtitle!==null&&(a.subtitle=e.subtitle),e.kind==="page"&&(a.page=B(e)),n!==null&&(a.settled_at=e.settled_at??e.updated_at,n.kind==="page"&&r==="answered"&&(a.responses=n.responses),n.kind==="canceled"&&n.reason!==void 0&&(a.reason=n.reason),n.kind==="page"&&n.actor!==void 0&&(a.actor=n.actor),n.kind==="canceled"&&n.actor!==void 0&&(a.actor=n.actor));const u=oe(e);return u!==null&&(a.action={name:u.name,payload:u.payload},a.delivery=ae(e)),a}o(y,"requestToDTO");function k(e,t,n,r){try{return M({dialect:e,document:t,source:n,delivery:r,productKinds:g()})}catch(a){throw i(a instanceof Error?a.message:"the page could not be prepared")}}o(k,"prepareOrReject");function se(e){const t=Z(e.body),n=t.requesterNodeId??t.source?.nodeId;if(n!=null){const d=b(n);d!==null&&$(d,"crtr:act")}let r;if(t.action!==void 0){const d=x(t.action.name,t.creatorCwd);if("kind"in d){const E=d.kind==="unknown_action"?`no action named ${d.name} is declared from ${t.creatorCwd}`:`${d.name} is declared in ${d.configOrigin} but unusable: ${d.reason}`;throw new s(400,d.kind,E)}const p=n==null?null:b(n);if(n&&!p)throw new s(404,"not_found","action requester node not found");if(p&&e.principal.class!=="person"&&p.grantee!==e.principal.app)throw new s(403,"permission_error","action requester belongs to another app");r=JSON.stringify({schema:"crtr.human-action-binding/v1",name:t.action.name,payload:t.action.payload,creatorCwd:t.creatorCwd,configOrigin:d.configOrigin,argv:d.argv,cwd:d.cwd,app:p?.grantee??(e.principal.class==="broker"?e.principal.app:"app:terminal")})}const a=k(t.page.dialect,t.page.source,t.source,t.delivery),u=J({prepared:a,requester_node_id:t.requesterNodeId,...r===void 0?{}:{action_json:r}});return{status:201,body:{request_id:u.request_id,request_dir:u.request_dir,state:"pending",...t.action===void 0?{}:{action:{name:t.action.name}},delivery_state:"none"}}}o(se,"handleCreate");function de(e){return{status:200,body:y(f(h(e)))}}o(de,"handleGet");function ce(e){const t=f(h(e));if(t.state==="settled")throw new s(409,"already_settled","the request already has a terminal result");const n=A(t),r=ee(e.body,n.delivery),a=k(r.page.dialect,r.page.source,n.source,r.delivery);try{G(t.request_dir,a)}catch(u){throw u instanceof Error&&u.message===`page request is not open: ${t.request_id}`?new s(409,"already_settled","the request already has a terminal result"):u}return{status:200,body:y(f(t.request_id))}}o(ce,"handleReplace");function O(e){return e instanceof s?e:e instanceof Error&&e.message.includes("unknown page request")?new s(404,"not_found","no request with that id"):e instanceof Error&&e.message==="page_has_no_response_lifecycle"?new s(409,"not_settleable","this request carries no response lifecycle"):new s(500,"internal","the request could not be settled")}o(O,"settlementError");function R(e){if(e.state==="settled")throw new s(409,"already_settled","the request already has a terminal result");if(!e.settleable)throw new s(409,"not_settleable","this request carries no response lifecycle")}o(R,"requireSettleable");async function ue(e){const t=f(h(e)),n=te(e.body);R(t);try{H(t.request_dir,n.responses)}catch(r){throw i(r instanceof Error?r.message:"the response map is invalid")}try{await U(t.request_id,n.responses,n.actor===void 0?{}:{actor:n.actor})}catch(r){throw O(r)}return{status:200,body:y(f(t.request_id))}}o(ue,"handleRespond");async function j(e,t){const n=f(h(e)),r=ne(e.body);R(n);try{await L(n.request_id,{...r,disposition:t})}catch(a){throw O(a)}return{status:200,body:y(f(n.request_id))}}o(j,"settle");function le(){return{status:200,body:{components:[...g()]}}}o(le,"handleComponents");const je=[{method:"GET",pattern:"/v1/human/components",handler:o(()=>le(),"handler")},{method:"POST",pattern:"/v1/human/requests",scope:"crtr:act",handler:se},{method:"GET",pattern:"/v1/human/requests/:request_id",handler:de},{method:"POST",pattern:"/v1/human/requests/:request_id/replace",handler:ce},{method:"POST",pattern:"/v1/human/requests/:request_id/respond",handler:ue},{method:"POST",pattern:"/v1/human/requests/:request_id/dismiss",handler:o(e=>j(e,"dismissed"),"handler")},{method:"POST",pattern:"/v1/human/requests/:request_id/cancel",handler:o(e=>j(e,"canceled"),"handler")}];export{je as humanRequestRoutes};
1
+ var N=Object.defineProperty;var o=(e,t)=>N(e,"name",{value:t,configurable:!0});import{statSync as P}from"node:fs";import{isAbsolute as T}from"node:path";import{ApiError as s}from"../../../api/index.js";import{readDaemonActionById as C}from"../../../core/canvas/daemon-actions.js";import{isSafeNodeId as I}from"../../../core/canvas/paths.js";import{getNode as b}from"../../../core/canvas/canvas.js";import{assertScope as $}from"../../../core/scopes.js";import{resolvePageComponents as g}from"../../../core/config.js";import{resolveHumanAction as D}from"../../../core/human-actions.js";import{HUMAN_ACTION_NAME_PATTERN as x}from"../../../core/human/action-binding.js";import{createPageRequest as J,getRequest as F}from"../../../core/human/requests.js";import{preparePageSource as M}from"../../../core/human/page-authoring.js";import{publishPage as B,validatePageTicketResponses as G}from"../../../core/human/tickets.js";import{cancelHumanTicket as H,resolvePageTicket as L}from"../../human/finish.js";import{inboxPageProjection as U}from"./inbox-page.js";const K=1e3,q=200,m={placement:"inline",inbox:!0,reply:!1},v=["sessionName","askedBy","emittedAt","profileName","nodeId"];function c(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o(c,"isPlainObject");function l(e,t,n){for(const r of Object.keys(e))if(!t.includes(r))throw new s(400,"invalid_request",`unexpected field in ${n}: ${r}`)}o(l,"requireNoExtraKeys");function i(e){return new s(400,"invalid_request",e)}o(i,"invalid");function w(e){if(!c(e))throw i("`page` must be an object");if(l(e,["dialect","source"],"`page`"),e.dialect!=="jsx"&&e.dialect!=="html")throw i('`page.dialect` must be "jsx" or "html"');if(typeof e.source!="string"||e.source.trim()==="")throw i("`page.source` must be the authored page text");return{dialect:e.dialect,source:e.source}}o(w,"validatePage");function S(e,t){const n=e??t;if(n!=="inline"&&n!=="panel")throw i('`delivery.placement` must be "inline" or "panel"');return n}o(S,"validatePlacement");function X(e,t){if(e===void 0)return m;if(!c(e))throw i("`delivery` must be an object");l(e,["placement","inbox","reply"],"`delivery`");const n=e.inbox??m.inbox,r=e.reply??m.reply;if(typeof n!="boolean")throw i("`delivery.inbox` must be boolean");if(typeof r!="boolean")throw i("`delivery.reply` must be boolean");if(r&&t===null)throw i("`delivery.reply` requires `requester_node_id`");return{placement:S(e.placement,m.placement),inbox:n,reply:r}}o(X,"validateCreateDelivery");function z(e,t){if(e===void 0)return t;if(!c(e))throw i("`delivery` must be an object");return l(e,["placement"],"`delivery`"),{...t,placement:S(e.placement,t.placement)}}o(z,"validateReplaceDelivery");function V(e){if(e===void 0)return;if(!c(e))throw i("`source` must be an object");l(e,v,"`source`");const t={};for(const n of v){const r=e[n];if(r!==void 0){if(typeof r!="string"||r==="")throw i(`\`source.${n}\` must be a nonempty string`);t[n]=r}}return t}o(V,"validateSource");function Y(e){if(typeof e!="string"||e==="")throw i("`creator_cwd` is required");if(!T(e))throw i("`creator_cwd` must be an absolute path");try{if(!P(e).isDirectory())throw i(`\`creator_cwd\` is not a directory: ${e}`)}catch(t){throw t instanceof s?t:i(`\`creator_cwd\` does not exist: ${e}`)}return e}o(Y,"validateCreatorCwd");function Q(e){if(e!==void 0){if(!c(e))throw i("`action` must be an object");if(l(e,["name","payload"],"`action`"),typeof e.name!="string"||!x.test(e.name))throw i("`action.name` must be a declared action name");return{name:e.name,payload:e.payload??null}}}o(Q,"validateActionSelection");function W(e){if(e==null)return null;if(typeof e!="string"||!I(e))throw i("`requester_node_id` must be a safe node id or null");return e}o(W,"validateRequesterNodeId");function Z(e){if(!c(e))throw i("request body is required");l(e,["page","delivery","source","requester_node_id","creator_cwd","action"],"the request body");const t=V(e.source),n=W(e.requester_node_id),r=Q(e.action);return{page:w(e.page),delivery:X(e.delivery,n),...t===void 0?{}:{source:t},requesterNodeId:n,creatorCwd:Y(e.creator_cwd),...r===void 0?{}:{action:r}}}o(Z,"validateCreateRequest");function ee(e,t){if(!c(e))throw i("request body is required");return l(e,["page","delivery"],"the request body"),{page:w(e.page),delivery:z(e.delivery,t)}}o(ee,"validateReplaceRequest");function _(e,t,n){if(e!==void 0){if(typeof e!="string"||e.trim()===""||e.length>n)throw i(`\`${t}\` must be nonempty after trim and at most ${n} characters`);return e}}o(_,"optionalBoundedString");function te(e){if(!c(e))throw i("request body is required");if(l(e,["responses","actor"],"the request body"),!c(e.responses))throw i("`responses` must be an object");const t=_(e.actor,"actor",q);return{responses:e.responses,...t===void 0?{}:{actor:t}}}o(te,"validateRespondRequest");function ne(e){if(e===void 0)return{};if(!c(e))throw i("request body must be an object");l(e,["reason","actor"],"the request body");const t=_(e.reason,"reason",K),n=_(e.actor,"actor",q);return{...t===void 0?{}:{reason:t},...n===void 0?{}:{actor:n}}}o(ne,"validateSettleRequest");function h(e){const t=e.params.request_id;if(typeof t!="string"||t==="")throw i("request id is required");return t}o(h,"requestId");function f(e){const t=F(e);if(t===null||t.kind!=="page")throw new s(404,"not_found","no request with that id");return t}o(f,"requirePageRequest");function A(e){try{const t=JSON.parse(e.specification_json);if(!c(t))throw new Error("not an object");return t}catch{throw new s(500,"internal","the request specification could not be read")}}o(A,"manifest");function re(e){if(e.result_json===null)return null;try{return JSON.parse(e.result_json)}catch{throw new s(500,"internal","the request result could not be read")}}o(re,"terminalResult");function oe(e){if(e.action_json===null)return null;try{const t=JSON.parse(e.action_json);if(t.schema!=="crtr.human-action-binding/v1"||typeof t.name!="string")throw new Error("invalid action");return t}catch{throw new s(500,"internal","the request action could not be read")}}o(oe,"frozenAction");function ie(e){return e.state!=="settled"?"pending":e.outcome==="answered"?"answered":e.outcome==="dismissed"?"dismissed":"canceled"}o(ie,"requestState");function ae(e){if(e.action_delivery_id===null)return{state:"none",attempt:0};const t=C(e.action_delivery_id);if(t===null)return{state:"none",attempt:0};const n=t.state==="acknowledged"?"accepted":t.state==="permanent_failed"?"permanent_failed":t.state==="handed_off"?"running":"pending",r={state:n,attempt:t.attempts};return n==="pending"&&t.nextAttemptAt!==null&&(r.next_attempt_at=new Date(t.nextAttemptAt).toISOString()),t.acknowledgedAt!==null&&(r.accepted_at=new Date(t.acknowledgedAt).toISOString()),t.failedAt!==null&&(r.permanent_failed_at=new Date(t.failedAt).toISOString()),t.lastFailure!==null&&(r.last_failure=t.lastFailure),r}o(ae,"deliveryDetail");function y(e){const t=A(e),n=re(e),r=ie(e),a={request_id:e.request_id,inbox_ticket_id:e.inbox_ticket_id,request_dir:e.request_dir,kind:e.kind,state:r,title:e.title,source:t.source??{},emitted_at:t.source?.emittedAt??e.created_at};e.subtitle!==null&&(a.subtitle=e.subtitle),e.kind==="page"&&(a.page=U(e)),n!==null&&(a.settled_at=e.settled_at??e.updated_at,n.kind==="page"&&r==="answered"&&(a.responses=n.responses),n.kind==="canceled"&&n.reason!==void 0&&(a.reason=n.reason),n.kind==="page"&&n.actor!==void 0&&(a.actor=n.actor),n.kind==="canceled"&&n.actor!==void 0&&(a.actor=n.actor));const u=oe(e);return u!==null&&(a.action={name:u.name,payload:u.payload},a.delivery=ae(e)),a}o(y,"requestToDTO");function k(e,t,n,r){try{return M({dialect:e,document:t,source:n,delivery:r,productKinds:g()})}catch(a){throw i(a instanceof Error?a.message:"the page could not be prepared")}}o(k,"prepareOrReject");function se(e){const t=Z(e.body),n=t.requesterNodeId??t.source?.nodeId;if(n!=null){const d=b(n);d!==null&&$(d,"crtr:act")}let r;if(t.action!==void 0){const d=D(t.action.name,t.creatorCwd);if("kind"in d){const E=d.kind==="unknown_action"?`no action named ${d.name} is declared from ${t.creatorCwd}`:`${d.name} is declared in ${d.configOrigin} but unusable: ${d.reason}`;throw new s(400,d.kind,E)}const p=n==null?null:b(n);if(n&&!p)throw new s(404,"not_found","action requester node not found");if(p&&e.principal.class!=="person"&&p.grantee!==e.principal.app)throw new s(403,"permission_error","action requester belongs to another app");r=JSON.stringify({schema:"crtr.human-action-binding/v1",name:t.action.name,payload:t.action.payload,creatorCwd:t.creatorCwd,configOrigin:d.configOrigin,argv:d.argv,cwd:d.cwd,app:p?.grantee??(e.principal.class==="broker"?e.principal.app:"app:terminal")})}const a=k(t.page.dialect,t.page.source,t.source,t.delivery),u=J({prepared:a,requester_node_id:t.requesterNodeId,...r===void 0?{}:{action_json:r}});return{status:201,body:{request_id:u.request_id,request_dir:u.request_dir,state:"pending",...t.action===void 0?{}:{action:{name:t.action.name}},delivery_state:"none"}}}o(se,"handleCreate");function de(e){return{status:200,body:y(f(h(e)))}}o(de,"handleGet");function ce(e){const t=f(h(e));if(t.state==="settled")throw new s(409,"already_settled","the request already has a terminal result");const n=A(t),r=ee(e.body,n.delivery),a=k(r.page.dialect,r.page.source,n.source,r.delivery);try{B(t.request_dir,a)}catch(u){throw u instanceof Error&&u.message===`page request is not open: ${t.request_id}`?new s(409,"already_settled","the request already has a terminal result"):u}return{status:200,body:y(f(t.request_id))}}o(ce,"handleReplace");function O(e){return e instanceof s?e:e instanceof Error&&e.message.includes("unknown page request")?new s(404,"not_found","no request with that id"):e instanceof Error&&e.message==="page_has_no_response_lifecycle"?new s(409,"not_settleable","this request carries no response lifecycle"):new s(500,"internal","the request could not be settled")}o(O,"settlementError");function R(e){if(e.state==="settled")throw new s(409,"already_settled","the request already has a terminal result");if(!e.settleable)throw new s(409,"not_settleable","this request carries no response lifecycle")}o(R,"requireSettleable");async function ue(e){const t=f(h(e)),n=te(e.body);R(t);try{G(t.request_dir,n.responses)}catch(r){throw i(r instanceof Error?r.message:"the response map is invalid")}try{await L(t.request_id,n.responses,n.actor===void 0?{}:{actor:n.actor})}catch(r){throw O(r)}return{status:200,body:y(f(t.request_id))}}o(ue,"handleRespond");async function j(e,t){const n=f(h(e)),r=ne(e.body);R(n);try{await H(n.request_id,{...r,disposition:t})}catch(a){throw O(a)}return{status:200,body:y(f(n.request_id))}}o(j,"settle");function le(){return{status:200,body:{components:[...g()]}}}o(le,"handleComponents");const je=[{method:"GET",pattern:"/v1/human/components",handler:o(()=>le(),"handler")},{method:"POST",pattern:"/v1/human/requests",scope:"crtr:act",handler:se},{method:"GET",pattern:"/v1/human/requests/:request_id",handler:de},{method:"POST",pattern:"/v1/human/requests/:request_id/replace",handler:ce},{method:"POST",pattern:"/v1/human/requests/:request_id/respond",handler:ue},{method:"POST",pattern:"/v1/human/requests/:request_id/dismiss",handler:o(e=>j(e,"dismissed"),"handler")},{method:"POST",pattern:"/v1/human/requests/:request_id/cancel",handler:o(e=>j(e,"canceled"),"handler")}];export{je as humanRequestRoutes};
@@ -1,5 +1,2 @@
1
- var G=Object.defineProperty;var c=(t,n)=>G(t,"name",{value:n,configurable:!0});import{ApiError as S}from"../../../api/errors.js";import{OBJECT_TYPES as A,PRIVACY_LEVELS_DTO as J}from"../../../api/dto/objects.js";import{withCanvasWrite as g}from"../../../core/canvas/db.js";import{recordMemoryReadsIn as U}from"../../../core/canvas/memory-reads.js";import{canRead as I}from"../../../core/graph/access.js";import{actorOf as N,documentRecord as V,readDocument as z}from"../../../core/graph/documents.js";import{edgesOf as Z}from"../../../core/graph/edges.js";import{publish as C}from"../../../core/graph/events.js";import{jobRow as K}from"../../../core/graph/jobs.js";import{getByRowid as q,listObjects as Q,searchObjects as M}from"../../../core/graph/objects.js";import{reposForCwd as X}from"../../../core/graph/repo-sync/repos.js";import{rulesOf as F}from"../../../core/graph/rules.js";import{recordWatch as $,unwatch as ee,watchersOf as te,watchesOf as ne}from"../../../core/graph/watches.js";import{loadLedger as re,saveLedger as oe}from"../../../core/graph/exposures.js";import{deliveryDocOf as ie,nodeSubject as se,ownerHandle as ce}from"../../../core/substrate/delivery/corpus.js";import{recordContentDelivery as de}from"../../../core/substrate/delivery/deliver.js";import{renderExplicitRead as le}from"../../../core/substrate/delivery/render-event.js";import{readGoal as ae}from"../../../core/runtime/kickoff.js";import{readerFor as b,resolveRef as W}from"../reader.js";import{invalidRequest as E,isRecord as _}from"./validate.js";const L=c(t=>n=>X(t,n),"reposFnFor");function u(t,n){const e=n.owner===null?null:q(t,n.owner),r=e?{id:e.id,type:e.type,handle:e.type==="node"?e.id:e.name}:null;return{id:n.id,type:n.type,kind:n.kind,owner:r,privacy:n.privacy,name:n.name,preview:n.preview??"",created_at:n.created_at,deleted_at:n.deleted_at}}c(u,"objectDTO");function H(t,n){return n.type!=="document"?u(t,n):{...u(t,n),summary:V(t,n).summary??null,rules:F(t,n).length}}c(H,"listItem");function ue(t){return`no object named ${t} on this runtime; it may be on another runtime, or the name may be misspelled`}c(ue,"remoteMessage");function P(t,n,e){try{return R(t,n,e)}catch(r){if(r instanceof S&&r.code==="not_found"&&!r.details?.deleted)return null;throw r}}c(P,"ownerFilter");function R(t,n,e){const r=W(t,n,e);if("remote"in r)throw new S(404,"not_found",ue(r.remote),{next:"Check the name with `crtr canvas search`."});if("deleted"in r)throw new S(404,"not_found",`${r.deleted.name} is deleted`,{id:r.deleted.id,deleted:!0});return r}c(R,"liveRef");const O=c(t=>decodeURIComponent(t.params.ref),"refParam");function fe(t,n,e){const r=`${e.name}/`;return t.prepare(`SELECT * FROM objects WHERE type = 'document' AND deleted_at IS NULL AND owner IS ?
2
- AND substr(name, 1, ?) = ? ORDER BY name`).all(e.owner,r.length,r).filter(i=>!i.name.slice(r.length).includes("/")&&I(t,n,i)).map(i=>u(t,i))}c(fe,"listingOf");function D(t,n,e){const r=N(n);r.rowid!==e.rowid&&($(t,r,e,"read"),C(t,{event:"read",object:e,actor:r,level:1,attrs:{}},L(t)))}c(D,"recordLiveRead");function pe(t,n,e){const r=n.class==="node",o=r?re(t,n.node.rowid):{node:0,preferenceSnapshot:null,objects:new Map,listed:new Map},i={db:t,reader:n,subject:r?se(t,n):null,ledger:o,channel:"transcript"},d=le(i,ie(t,n,e));return r&&(de(t,n,d.delivered),oe(t,o)),{attachments:d.attachments,linked:d.linked.map(s=>s.object)}}c(pe,"explicitReadAttachments");function me(t,n,e){if(n.class!=="node"||e.kind!=="knowledge"&&e.kind!=="preference"||e.owner===null)return;const r=q(t,e.owner);r!==null&&U(t,n.node.id,[{name:e.name,scope:ce(r),at:new Date().toISOString(),type:"read"}])}c(me,"recordExplicitMemoryRead");function he(t,n,e,r){const o=z(t,n,e,L(t));me(t,n,e);const i=o.record,d=fe(t,n,e),{attachments:s,linked:l}=pe(t,n,e),p=new Set(d.map(a=>a.id)),h=[...d,...l.filter(a=>!p.has(a.id)).map(a=>u(t,a))],w={state:"object",type:"document",object:u(t,e),summary:i.summary??null,revision:i.revision,heads:o.heads.map(a=>({rev_id:a.rev_id,number:a.number,author_id:a.author,created_at:a.created_at})),bodies:o.heads.map(a=>a.body),needs_merge:o.needsMerge,...h.length>0?{listing:h}:{},...s===""?{}:{attachments:s}};return r&&(w.fields={why_it_exists:i.why_it_exists??null,unlisted:i.unlisted,lint_ignore:i.lint_ignore??[],extensions:i.extensions??{},rules:F(t,e).map(({matchFrontmatter:a,...m})=>({...m,...a===void 0?{}:{match_frontmatter:a}}))}),w}c(he,"readDoc");function we(t){return t?.split(`
3
- `).map(n=>n.trim()).find(n=>n!=="")??""}c(we,"firstLine");function ye(t,n,e){const r=t.prepare("SELECT status FROM nodes WHERE node_id = ?").get(e.id),o=c(s=>s.filter(l=>I(t,n,l)).map(l=>u(t,l)),"visible"),i=t.prepare(`SELECT o.* FROM nodes n JOIN objects o ON o.id = n.node_id
4
- WHERE n.parent = ? AND o.deleted_at IS NULL ORDER BY o.rowid`).all(e.id),d=t.prepare("SELECT * FROM objects WHERE owner = ? AND deleted_at IS NULL ORDER BY rowid").all(e.rowid);return D(t,n,e),{state:"object",type:"node",object:u(t,e),status:r?.status??"deleted",mandate_first_line:we(ae(e.id)),children:o(i),owned:o(d.filter(s=>s.type!=="job")),jobs:o(d.filter(s=>s.type==="job"))}}c(ye,"readNode");function ge(t,n,e){const r=K(t,e);if(!r)throw new Error(`job object ${e.id} has no jobs row`);return D(t,n,e),{state:"object",type:"job",object:u(t,e),command:r.command,status:r.status,exit_code:r.exit_code,log_path:r.log_path}}c(ge,"readJob");function be(t,n,e){const r=t.prepare("SELECT scheme, external_id FROM identities WHERE object = ?").get(e.rowid);return D(t,n,e),{state:"object",type:e.type,object:u(t,e),handle:e.name,display_name:e.preview||e.name,identity:{scheme:r?.scheme??"",external_id:r?.external_id??""}}}c(be,"readOwner");function je(t){const n=["1","true"].includes(t.query.get("fields")??"");return g(e=>{const r=b(e,t),o=W(e,r,O(t));return"remote"in o?{state:"remote",remote:o.remote}:"deleted"in o?{state:"deleted",deleted:u(e,o.deleted)}:o.type==="document"?he(e,r,o,n):o.type==="node"?ye(e,r,o):o.type==="job"?ge(e,r,o):be(e,r,o)})}c(je,"read");function v(t,n,e,r){if(t!=null){if(!e.includes(t)){r.push({field:n,problem:`must be one of ${e.join(", ")}`,received:t});return}return t}}c(v,"oneOf");function B(t,n,e,r){if(t==null)return r;const o=Number(t);return!Number.isInteger(o)||o<1||o>100?(e.push({field:n,problem:"must be an integer from 1 to 100",received:t}),r):o}c(B,"limitOf");function _e(t){const n=t.query,e=[],r=v(n.get("type"),"type",A,e),o=v(n.get("privacy"),"privacy",J,e),i=B(n.get("limit"),"limit",e,50),d=n.get("cursor");d!==null&&!/^\d+$/.test(d)&&e.push({field:"cursor",problem:"is not a cursor from a previous page",received:d});const s=n.get("deleted");return s!==null&&!["0","1","true","false"].includes(s)&&e.push({field:"deleted",problem:"must be true or false",received:s}),e.length>0&&E("object list request",e),g(l=>{const p=b(l,t),h={...r?{type:r}:{},...o?{privacy:o}:{},...n.get("kind")!==null?{kind:n.get("kind")}:{},...s==="1"||s==="true"?{deleted:!0}:{}},w=n.get("owner");if(w!==null){const y=P(l,p,w);if(y===null)return{items:[],next_cursor:null};h.owner=y}const a=n.get("name"),m=[];let f=d===null?0:Number(d),T=!0;for(;m.length<i&&T;){const y=Q(l,p,h,{limit:100,after_rowid:f});T=y.length===100;for(const k of y)if(f=k.rowid,(a===null||k.name.startsWith(a))&&m.push(k),m.length===i)break}const x=m.at(-1);return{items:m.map(y=>H(l,y)),next_cursor:m.length===i&&x?String(x.rowid):null}})}c(_e,"list");function ve(t){const n=[],e=_(t.body)?t.body:{};_(t.body)||n.push({field:"body",problem:"must be an object",received:t.body});const r=e.text;(typeof r!="string"||r.trim()==="")&&n.push({field:"text",problem:"is required",received:r});const o=e.grep;if(o!==void 0)if(typeof o!="string")n.push({field:"grep",problem:"must be a regular expression string",received:o});else try{new RegExp(o)}catch(s){n.push({field:"grep",problem:`is not a valid regular expression: ${s.message}`,received:o})}e.body!==void 0&&typeof e.body!="boolean"&&n.push({field:"body",problem:"must be a boolean",received:e.body});const i=v(e.type,"type",A,n);e.kind!==void 0&&typeof e.kind!="string"&&n.push({field:"kind",problem:"must be a string",received:e.kind}),e.owner!==void 0&&typeof e.owner!="string"&&n.push({field:"owner",problem:"must be a string",received:e.owner});const d=B(e.limit,"limit",n,50);return n.length>0&&E("object search request",n),g(s=>{const l=b(s,t),p=typeof e.owner=="string"?P(s,l,e.owner):null;if(typeof e.owner=="string"&&p===null)return{items:[]};const h=M(s,l,{text:r,body:e.body===!0||typeof o=="string"}),w=new Set(h.map(f=>f.object.rowid));return{items:(typeof o=="string"?M(s,l,{text:o,grep:!0}).filter(f=>w.has(f.object.rowid)):h).filter(f=>(!i||f.object.type===i)&&(e.kind===void 0||f.object.kind===e.kind)&&(!p||f.object.owner===p.rowid)).slice(0,d).map(f=>({object:H(s,f.object),...f.line!==void 0?{matches:[{line:f.line,text:f.text??""}]}:{}}))}})}c(ve,"search");const Y=["interrupt","steer","after-turn","next-turn"],Ee=/^(\*|>|[A-Za-z0-9_-]+)(\.(\*|>|[A-Za-z0-9_-]+))*$/;function Re(t,n){if(t!==void 0){if(!Array.isArray(t)||t.length===0||t.some(e=>typeof e!="string"||!Ee.test(e)||e.includes(">")&&!e.endsWith(">"))){n.push({field:"patterns",problem:"must be a non-empty list of event patterns (dot-separated words, `*` for one word, a final `>` for the rest)",received:t});return}return t}}c(Re,"patternsOf");function Oe(t,n){if(t===void 0)return;if(!_(t)||Object.keys(t).length===0){n.push({field:"config",problem:"must map levels 1-10 to a delivery",received:t});return}const e=[];for(const[r,o]of Object.entries(t)){const i=Number(r);!Number.isInteger(i)||i<1||i>10?n.push({field:`config.${r}`,problem:"level must be an integer from 1 to 10"}):Y.includes(o)?e.push({threshold:i,delivery:o}):n.push({field:`config.${r}`,problem:`delivery must be one of ${Y.join(", ")}`,received:o})}return e.sort((r,o)=>o.threshold-r.threshold)}c(Oe,"configOf");function ke(t){const n=[],e=t.body===void 0?{}:_(t.body)?t.body:(n.push({field:"body",problem:"must be an object"}),{}),r=Re(e.patterns,n),o=Oe(e.config,n);return e.filter!==void 0&&!_(e.filter)&&n.push({field:"filter",problem:"must be an object",received:e.filter}),n.length>0&&E("watch request",n),g(i=>{const d=b(i,t),s=R(i,d,O(t)),l=N(d),p=r??[">"];return $(i,l,s,"ask",{patterns:p,...o?{config:o}:{},...e.filter!==void 0?{filter:e.filter}:{}}),C(i,{event:"watched",object:s,actor:l,level:1,attrs:{}},L(i)),{object:u(i,s),patterns:p}})}c(ke,"watch");function Se(t){const n=t.query.get("pattern");return g(e=>{const r=b(e,t),o=R(e,r,O(t)),i=N(r),d=c(()=>e.prepare(`SELECT count(*) AS n FROM watch_subscriptions s JOIN watches w ON w.rowid = s.watch
5
- WHERE w.watcher = ? AND w.object = ?`).get(i.rowid,o.rowid).n,"count"),s=d();return ee(e,i,o,n??void 0),{object:u(e,o),removed:s-d()}})}c(Se,"unwatchRoute");function Ne(t){const n=[],e=v(t.query.get("type"),"type",["acted-on","links-to","watches"],n),r=v(t.query.get("direction"),"direction",["out","in"],n);return n.length>0&&E("edges request",n),g(o=>{const i=b(o,t),d=R(o,i,O(t)),s=[];if(e!=="watches")for(const l of Z(o,i,d,{...e?{type:e}:{},direction:r??"both"}))s.push({type:l.type,src:u(o,l.src),dst:u(o,l.dst),...l.written_as?{written_as:l.written_as}:{}});if(e===void 0||e==="watches"){const l=[...r!=="in"?ne(o,i,d):[],...r!=="out"?te(o,i,d):[]];for(const p of l)s.push({type:"watches",src:u(o,p.watcher),dst:u(o,p.object)})}return{items:s}})}c(Ne,"edges");const j=c(t=>n=>({status:200,body:t(n)}),"ok"),Ze=[{method:"POST",pattern:"/v1/objects/search",handler:j(ve)},{method:"GET",pattern:"/v1/objects/:ref/edges",handler:j(Ne)},{method:"POST",pattern:"/v1/objects/:ref/watch",handler:j(ke)},{method:"DELETE",pattern:"/v1/objects/:ref/watch",handler:j(Se)},{method:"GET",pattern:"/v1/objects/:ref",handler:j(je)},{method:"GET",pattern:"/v1/objects",handler:j(_e)}];export{Ze as objectRoutes};
1
+ var G=Object.defineProperty;var d=(e,r)=>G(e,"name",{value:r,configurable:!0});import{ApiError as R}from"../../../api/errors.js";import{PRIVACY_LEVELS_DTO as V}from"../../../api/dto/objects.js";import{withCanvasWrite as h}from"../../../core/canvas/db.js";import{actorOf as $}from"../../../core/graph/documents.js";import{edgesOf as x}from"../../../core/graph/edges.js";import{publish as z}from"../../../core/graph/events.js";import{objectDTO as a}from"../../../core/graph/kinds/dto.js";import{isKind as H,kindNames as M,kindOf as A}from"../../../core/graph/kinds/registry.js";import{listObjects as Z,searchObjects as I}from"../../../core/graph/objects.js";import{reposForCwd as J}from"../../../core/graph/repo-sync/repos.js";import{recordWatch as K,unwatch as U,watchersOf as Y,watchesOf as B}from"../../../core/graph/watches.js";import{readerFor as b,resolveRef as F}from"../reader.js";import{invalidRequest as v,isRecord as w}from"./validate.js";const S=d(e=>r=>J(e,r),"reposFnFor");function C(e,r){return{...a(e,r),...A(r.type).listItem?.(e,r)}}d(C,"listItem");function Q(e){return`no object named ${e} on this runtime; it may be on another runtime, or the name may be misspelled`}d(Q,"remoteMessage");function D(e,r,t){try{return k(e,r,t)}catch(o){if(o instanceof R&&o.code==="not_found"&&!o.details?.deleted)return null;throw o}}d(D,"ownerFilter");function k(e,r,t){const o=F(e,r,t);if("remote"in o)throw new R(404,"not_found",Q(o.remote),{next:"Check the name with `crtr canvas search`."});if("deleted"in o)throw new R(404,"not_found",`${o.deleted.name} is deleted`,{id:o.deleted.id,deleted:!0});return o}d(k,"liveRef");const O=d(e=>decodeURIComponent(e.params.ref),"refParam");function X(e){const r=["1","true"].includes(e.query.get("fields")??"");return h(t=>{const o=b(t,e),n=F(t,o,O(e));if("remote"in n)return{state:"remote",remote:n.remote};if("deleted"in n)return{state:"deleted",deleted:a(t,n.deleted)};const{data:i,text:c}=A(n.type).read(t,o,n,{fields:r,reposFn:S(t)});return{state:"object",type:n.type,object:a(t,n),text:c,data:i}})}d(X,"read");function q(e,r,t,o){if(e!=null){if(!t.includes(e)){o.push({field:r,problem:`must be one of ${t.join(", ")}`,received:e});return}return e}}d(q,"oneOf");function L(e,r){if(e!=null){if(typeof e!="string"||!H(e)){r.push({field:"type",problem:`must be a kind: one of ${M().join(", ")}`,received:e});return}return e}}d(L,"kindName");function P(e,r,t,o){if(e==null)return o;const n=Number(e);return!Number.isInteger(n)||n<1||n>100?(t.push({field:r,problem:"must be an integer from 1 to 100",received:e}),o):n}d(P,"limitOf");function ee(e){const r=e.query,t=[],o=L(r.get("type"),t),n=q(r.get("privacy"),"privacy",V,t),i=P(r.get("limit"),"limit",t,50),c=r.get("cursor");c!==null&&!/^\d+$/.test(c)&&t.push({field:"cursor",problem:"is not a cursor from a previous page",received:c});const s=r.get("deleted");return s!==null&&!["0","1","true","false"].includes(s)&&t.push({field:"deleted",problem:"must be true or false",received:s}),t.length>0&&v("object list request",t),h(u=>{const l=b(u,e),g={...o?{type:o}:{},...n?{privacy:n}:{},...r.get("kind")!==null?{kind:r.get("kind")}:{},...s==="1"||s==="true"?{deleted:!0}:{}},j=r.get("owner");if(j!==null){const m=D(u,l,j);if(m===null)return{items:[],next_cursor:null};g.owner=m}const E=r.get("name"),p=[];let f=c===null?0:Number(c),N=!0;for(;p.length<i&&N;){const m=Z(u,l,g,{limit:100,after_rowid:f});N=m.length===100;for(const _ of m)if(f=_.rowid,(E===null||_.name.startsWith(E))&&p.push(_),p.length===i)break}const T=p.at(-1);return{items:p.map(m=>C(u,m)),next_cursor:p.length===i&&T?String(T.rowid):null}})}d(ee,"list");function te(e){const r=[],t=w(e.body)?e.body:{};w(e.body)||r.push({field:"body",problem:"must be an object",received:e.body});const o=t.text;(typeof o!="string"||o.trim()==="")&&r.push({field:"text",problem:"is required",received:o});const n=t.grep;if(n!==void 0)if(typeof n!="string")r.push({field:"grep",problem:"must be a regular expression string",received:n});else try{new RegExp(n)}catch(s){r.push({field:"grep",problem:`is not a valid regular expression: ${s.message}`,received:n})}t.body!==void 0&&typeof t.body!="boolean"&&r.push({field:"body",problem:"must be a boolean",received:t.body});const i=L(t.type,r);t.kind!==void 0&&typeof t.kind!="string"&&r.push({field:"kind",problem:"must be a string",received:t.kind}),t.owner!==void 0&&typeof t.owner!="string"&&r.push({field:"owner",problem:"must be a string",received:t.owner});const c=P(t.limit,"limit",r,50);return r.length>0&&v("object search request",r),h(s=>{const u=b(s,e),l=typeof t.owner=="string"?D(s,u,t.owner):null;if(typeof t.owner=="string"&&l===null)return{items:[]};const g=I(s,u,{text:o,body:t.body===!0||typeof n=="string"}),j=new Set(g.map(f=>f.object.rowid));return{items:(typeof n=="string"?I(s,u,{text:n,grep:!0}).filter(f=>j.has(f.object.rowid)):g).filter(f=>(!i||f.object.type===i)&&(t.kind===void 0||f.object.kind===t.kind)&&(!l||f.object.owner===l.rowid)).slice(0,c).map(f=>({object:C(s,f.object),...f.line!==void 0?{matches:[{line:f.line,text:f.text??""}]}:{}}))}})}d(te,"search");const W=["interrupt","steer","after-turn","next-turn"],re=/^(\*|>|[A-Za-z0-9_-]+)(\.(\*|>|[A-Za-z0-9_-]+))*$/;function ne(e,r){if(e!==void 0){if(!Array.isArray(e)||e.length===0||e.some(t=>typeof t!="string"||!re.test(t)||t.includes(">")&&!t.endsWith(">"))){r.push({field:"patterns",problem:"must be a non-empty list of event patterns (dot-separated words, `*` for one word, a final `>` for the rest)",received:e});return}return e}}d(ne,"patternsOf");function oe(e,r){if(e===void 0)return;if(!w(e)||Object.keys(e).length===0){r.push({field:"config",problem:"must map levels 1-10 to a delivery",received:e});return}const t=[];for(const[o,n]of Object.entries(e)){const i=Number(o);!Number.isInteger(i)||i<1||i>10?r.push({field:`config.${o}`,problem:"level must be an integer from 1 to 10"}):W.includes(n)?t.push({threshold:i,delivery:n}):r.push({field:`config.${o}`,problem:`delivery must be one of ${W.join(", ")}`,received:n})}return t.sort((o,n)=>n.threshold-o.threshold)}d(oe,"configOf");function ie(e){const r=[],t=e.body===void 0?{}:w(e.body)?e.body:(r.push({field:"body",problem:"must be an object"}),{}),o=ne(t.patterns,r),n=oe(t.config,r);return t.filter!==void 0&&!w(t.filter)&&r.push({field:"filter",problem:"must be an object",received:t.filter}),r.length>0&&v("watch request",r),h(i=>{const c=b(i,e),s=k(i,c,O(e)),u=$(c),l=o??[">"];return K(i,u,s,"ask",{patterns:l,...n?{config:n}:{},...t.filter!==void 0?{filter:t.filter}:{}}),z(i,{event:"watched",object:s,actor:u,level:1,attrs:{}},S(i)),{object:a(i,s),patterns:l}})}d(ie,"watch");function se(e){const r=e.query.get("pattern");return h(t=>{const o=b(t,e),n=k(t,o,O(e)),i=$(o),c=d(()=>t.prepare(`SELECT count(*) AS n FROM watch_subscriptions s JOIN watches w ON w.rowid = s.watch
2
+ WHERE w.watcher = ? AND w.object = ?`).get(i.rowid,n.rowid).n,"count"),s=c();return U(t,i,n,r??void 0),{object:a(t,n),removed:s-c()}})}d(se,"unwatchRoute");function de(e){const r=[],t=q(e.query.get("type"),"type",["acted-on","links-to","watches"],r),o=q(e.query.get("direction"),"direction",["out","in"],r);return r.length>0&&v("edges request",r),h(n=>{const i=b(n,e),c=k(n,i,O(e)),s=[];if(t!=="watches")for(const u of x(n,i,c,{...t?{type:t}:{},direction:o??"both"}))s.push({type:u.type,src:a(n,u.src),dst:a(n,u.dst),...u.written_as?{written_as:u.written_as}:{}});if(t===void 0||t==="watches"){const u=[...o!=="in"?B(n,i,c):[],...o!=="out"?Y(n,i,c):[]];for(const l of u)s.push({type:"watches",src:a(n,l.watcher),dst:a(n,l.object)})}return{items:s}})}d(de,"edges");const y=d(e=>r=>({status:200,body:e(r)}),"ok"),ke=[{method:"POST",pattern:"/v1/objects/search",handler:y(te)},{method:"GET",pattern:"/v1/objects/:ref/edges",handler:y(de)},{method:"POST",pattern:"/v1/objects/:ref/watch",handler:y(ie)},{method:"DELETE",pattern:"/v1/objects/:ref/watch",handler:y(se)},{method:"GET",pattern:"/v1/objects/:ref",handler:y(X)},{method:"GET",pattern:"/v1/objects",handler:y(ee)}];export{ke as objectRoutes};
@@ -1 +1 @@
1
- var y=Object.defineProperty;var a=(d,s)=>y(d,"name",{value:s,configurable:!0});import{consumeCron as m,recordCronRunLocked as I,releaseCronRunLease as b}from"../../core/canvas/crons.js";import{withCanvasWrite as k}from"../../core/canvas/db.js";import{enqueueExternalAction as _,enqueueNodeMail as v}from"../../core/feed/messages.js";import{disposeSettledRun as g}from"./disposition.js";function q(d){return typeof d=="object"&&(d.kind==="park"||d.kind==="redue")}a(q,"occurrenceStaysOwed");async function R(d,s){const{outOfBand:l,outcome:t,record:n,lease:i}=s;let o,u,e;if(t.kind==="disposed"){const r=await g(d,t.ctx,l,t.selfCanceled??!1);o=r.delivered,u=r.paused,e=r.obligation}else o=t.delivered,u=t.paused??!1;const f=k(r=>{e!==void 0&&(e.kind==="node"?o=`delivered: ${v(e.destinationNodeId,{kind:"message",label:e.label,senderNodeId:e.senderNodeId,bodyJson:{body:e.stdout},urgency:e.urgency,wake:1,idempotencyKey:n.runId}).deliveryId}`:e.kind==="external"&&(o=`delivered: ${_({destinationId:e.destinationId,adapter:e.adapter,bodyJson:e.bodyJson,idempotencyKey:n.runId}).deliveryId}`));const c={run_id:n.runId,cron_id:d.cron_id,started:n.startedAtIso,finished:new Date(n.finishedAtMs).toISOString(),duration_ms:n.durationMs,exit_code:n.exitCode,stdout_head:n.stdoutHead,stderr_head:n.stderrHead,delivered:o};return I(r,c),c});return i!=="none"&&b(d.cron_id,i==="release"?void 0:i),!l&&d.recur==null&&!u&&!q(i)&&m(d.cron_id),f}a(R,"settleRun");export{R as settleRun};
1
+ var m=Object.defineProperty;var u=(e,s)=>m(e,"name",{value:s,configurable:!0});import{consumeCron as y,recordCronRunLocked as I,releaseCronRunLease as b}from"../../core/canvas/crons.js";import{withCanvasWrite as k}from"../../core/canvas/db.js";import{enqueueDaemonAction as _}from"../../core/canvas/daemon-actions.js";import{enqueueNodeMail as v}from"../../core/feed/messages.js";import{disposeSettledRun as g}from"./disposition.js";function w(e){return typeof e=="object"&&(e.kind==="park"||e.kind==="redue")}u(w,"occurrenceStaysOwed");async function S(e,s){const{outOfBand:c,outcome:o,record:n,lease:i}=s;let t,a,d;if(o.kind==="disposed"){const r=await g(e,o.ctx,c,o.selfCanceled??!1);t=r.delivered,a=r.paused,d=r.obligation}else t=o.delivered,a=o.paused??!1;const f=k(r=>{d!==void 0&&(d.kind==="node"?t=`delivered: ${v(d.destinationNodeId,{kind:"message",label:d.label,senderNodeId:d.senderNodeId,bodyJson:{body:d.stdout},urgency:d.urgency,wake:1,idempotencyKey:n.runId}).deliveryId}`:d.kind==="action"&&(t=`delivered: ${_({adapter:d.adapter,bodyJson:d.bodyJson,idempotencyKey:n.runId})}`));const l={run_id:n.runId,cron_id:e.cron_id,started:n.startedAtIso,finished:new Date(n.finishedAtMs).toISOString(),duration_ms:n.durationMs,exit_code:n.exitCode,stdout_head:n.stdoutHead,stderr_head:n.stderrHead,delivered:t};return I(r,l),l});return i!=="none"&&b(e.cron_id,i==="release"?void 0:i),!c&&e.recur==null&&!a&&!w(i)&&y(e.cron_id),f}u(S,"settleRun");export{S as settleRun};
@@ -1,4 +1,5 @@
1
1
  import { type Cron } from '../../core/canvas/crons.js';
2
+ import type { DaemonActionAdapter } from '../../core/canvas/daemon-actions.js';
2
3
  export type CronSinkResolution = {
3
4
  kind: 'none';
4
5
  delivered: string;
@@ -10,9 +11,8 @@ export type CronSinkResolution = {
10
11
  stdout: string;
11
12
  urgency: 'critical' | 'urgent' | 'normal' | 'deferred';
12
13
  } | {
13
- kind: 'external';
14
- destinationId: string;
15
- adapter: Record<string, unknown>;
14
+ kind: 'action';
15
+ adapter: DaemonActionAdapter;
16
16
  bodyJson: unknown;
17
17
  };
18
18
  /** Resolve a completed run into a durable obligation. No sink effect happens here. */
@@ -1,2 +1,2 @@
1
- var m=Object.defineProperty;var d=(n,o)=>m(n,"name",{value:o,configurable:!0});import{newNodeId as t}from"../../core/runtime/nodes.js";import{getNode as a}from"../../core/canvas/canvas.js";import{parseCronSink as s}from"../../core/canvas/crons.js";import{hasNoNaturalCycle as u}from"../../core/runtime/revive-all.js";import{isParked as p}from"../../core/runtime/lifecycle.js";import{cronWakeOrigin as f}from"../../core/runtime/bearings.js";import{cronBirthParent as k,resolveLiveProfile as c,resolveLiveProfileName as y}from"./armed-context.js";function g(n,o,i){const e=s(n.sink);if(e===null)return{kind:"none",delivered:"no sink armed \u2014 nothing delivered"};if(e.kind==="node"){const r=a(e.node);if(r===null)throw new Error(`node sink ${e.node} is gone`);if(u(r)&&!p(r))throw new Error(`node sink ${e.node} has no natural cycle ahead (done or finalized)`);return{kind:"node",destinationNodeId:e.node,senderNodeId:n.created_by!==null&&a(n.created_by)!==null?n.created_by:null,label:`\u23F0 cron ${n.name}`,stdout:i,urgency:n.tier}}if(e.kind==="spawn"){const r=k(n),l=[`Scheduled cron "${n.name}" (${n.cron_id}) fired and delivered its output to you as your kickoff.`,"","## Command output","",i].join(`
2
- `);return{kind:"external",destinationId:"cron_spawn",adapter:{adapter:"cron_spawn",cronId:n.cron_id,nodeId:t(),kind:e.spawn,cwd:n.cwd,...r!==null?{parent:r,root:!1}:{parent:null,root:!0,rootLifecycle:"terminal"},profile:c(n),wakeOrigin:f(n,"node_birth"),prompt:l},bodyJson:{runId:o,stdout:i}}}return{kind:"external",destinationId:"cron_human",adapter:{adapter:"cron_human",cronId:n.cron_id,requestId:t(),title:`\u23F0 cron ${n.name}`,summary:`Scheduled cron "${n.name}" delivered output for your attention.`,sessionName:`cron ${n.name} (${n.cron_id})`,profileName:y(n)??null},bodyJson:{runId:o,stdout:i}}}d(g,"resolveCronSink");export{g as resolveCronSink};
1
+ var m=Object.defineProperty;var d=(e,r)=>m(e,"name",{value:r,configurable:!0});import{newNodeId as t}from"../../core/runtime/nodes.js";import{getNode as a}from"../../core/canvas/canvas.js";import{parseCronSink as s}from"../../core/canvas/crons.js";import{hasNoNaturalCycle as u}from"../../core/runtime/revive-all.js";import{isParked as p}from"../../core/runtime/lifecycle.js";import{cronWakeOrigin as f}from"../../core/runtime/bearings.js";import{cronBirthParent as k,resolveLiveProfile as c,resolveLiveProfileName as y}from"./armed-context.js";function I(e,r,i){const n=s(e.sink);if(n===null)return{kind:"none",delivered:"no sink armed \u2014 nothing delivered"};if(n.kind==="node"){const o=a(n.node);if(o===null)throw new Error(`node sink ${n.node} is gone`);if(u(o)&&!p(o))throw new Error(`node sink ${n.node} has no natural cycle ahead (done or finalized)`);return{kind:"node",destinationNodeId:n.node,senderNodeId:e.created_by!==null&&a(e.created_by)!==null?e.created_by:null,label:`\u23F0 cron ${e.name}`,stdout:i,urgency:e.tier}}if(n.kind==="spawn"){const o=k(e),l=[`Scheduled cron "${e.name}" (${e.cron_id}) fired and delivered its output to you as your kickoff.`,"","## Command output","",i].join(`
2
+ `);return{kind:"action",adapter:{adapter:"cron_spawn",cronId:e.cron_id,nodeId:t(),kind:n.spawn,cwd:e.cwd,...o!==null?{parent:o,root:!1}:{parent:null,root:!0,rootLifecycle:"terminal"},profile:c(e),wakeOrigin:f(e,"node_birth"),prompt:l},bodyJson:{runId:r,stdout:i}}}return{kind:"action",adapter:{adapter:"cron_human",cronId:e.cron_id,requestId:t(),title:`\u23F0 cron ${e.name}`,summary:`Scheduled cron "${e.name}" delivered output for your attention.`,sessionName:`cron ${e.name} (${e.cron_id})`,profileName:y(e)??null},bodyJson:{runId:r,stdout:i}}}d(I,"resolveCronSink");export{I as resolveCronSink};