@north-light/cli 0.3.447 → 0.3.448

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 (42) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/nodes.d.ts +5 -0
  4. package/dist/api/dto/objects.d.ts +33 -2
  5. package/dist/api/error-codes.d.ts +15 -0
  6. package/dist/api/error-codes.js +1 -1
  7. package/dist/clients/attach/viewer.js +513 -513
  8. package/dist/commands/canvas/list.js +3 -3
  9. package/dist/commands/canvas/read.js +4 -4
  10. package/dist/commands/node/inspect.js +8 -8
  11. package/dist/commands/sys/sync-shared.js +1 -1
  12. package/dist/commands/sys/update.js +1 -1
  13. package/dist/core/feed/inbox.js +1 -1
  14. package/dist/core/graph/events.js +8 -8
  15. package/dist/core/peer/client.d.ts +2 -0
  16. package/dist/core/peer/client.js +1 -1
  17. package/dist/core/peer/directory.js +1 -1
  18. package/dist/core/peer/home.d.ts +40 -0
  19. package/dist/core/peer/home.js +2 -0
  20. package/dist/core/peer/names.d.ts +3 -0
  21. package/dist/core/peer/names.js +1 -0
  22. package/dist/core/peer/stubs.d.ts +2 -0
  23. package/dist/core/peer/stubs.js +1 -1
  24. package/dist/core/peer/version.d.ts +2 -2
  25. package/dist/core/peer/version.js +1 -1
  26. package/dist/core/runtime/broker/inbox.js +1 -1
  27. package/dist/core/scoped-state/settings.js +1 -1
  28. package/dist/daemon/api/handlers/messages.js +2 -2
  29. package/dist/daemon/api/handlers/nodes.js +1 -1
  30. package/dist/daemon/api/handlers/objects.d.ts +4 -0
  31. package/dist/daemon/api/handlers/objects.js +2 -2
  32. package/dist/daemon/peer/forward.d.ts +69 -0
  33. package/dist/daemon/peer/forward.js +1 -0
  34. package/dist/daemon/peer/handlers.d.ts +2 -0
  35. package/dist/daemon/peer/handlers.js +1 -1
  36. package/dist/shared/generated-context.d.ts +8 -0
  37. package/dist/shared/generated-context.js +4 -4
  38. package/dist/shared/inbox-entry-body.js +5 -5
  39. package/package.json +2 -2
  40. package/packages/crouter-identity/package.json +1 -1
  41. package/runtime.lock.json +12 -12
  42. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/tree-cache.json +0 -728
@@ -1,3 +1,3 @@
1
- var s=Object.defineProperty;var t=(e,r)=>s(e,"name",{value:r,configurable:!0});import{defineLeaf as a}from"../../core/command.js";import{PRIVACY_LEVELS_DTO as i}from"../../api/dto/objects.js";import{cliClient as c,rethrowAsCliError as d}from"../api-client.js";import{objectRow as l}from"./read.js";function m(e){const r=l(e);if(e.type!=="document")return r;const n=[`privacy=${e.privacy}`,e.rules!==void 0?`rules=${e.rules}`:""].filter(Boolean).join(" ");return`${r}
2
- ${n}${e.summary?` \u2014 ${e.summary}`:""}`}t(m,"listItemRow");const g=a({name:"list",description:"list objects by type, kind, owner, privacy or name prefix",whenToUse:"browsing what exists without reading it \u2014 the documents an owner holds, a node\u2019s reports, every job of a node. Records nothing, so listing does not make you a watcher.",help:{name:"canvas list",summary:"list the objects you can read, filtered; records nothing",params:[{kind:"flag",name:"type",type:"string",required:!1,constraint:"Only objects of this kind: document, node, job, person, app, repo or profile. The daemon rejects a name no kind declares and lists the kinds."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Only objects of this kind: for documents knowledge, preference or report; for nodes the node kind."},{kind:"flag",name:"owner",type:"string",required:!1,constraint:"Only objects this owner owns directly: a node id, a handle, or any name `crtr canvas read` accepts."},{kind:"flag",name:"privacy",type:"enum",choices:[...i],required:!1,constraint:"Only objects at this privacy level."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Only objects whose name starts with this prefix."},{kind:"flag",name:"deleted",type:"bool",required:!1,constraint:"When present, lists deleted objects instead of live ones."},{kind:"flag",name:"limit",type:"int",required:!1,default:50,constraint:"Page size, 1 to 100."},{kind:"flag",name:"cursor",type:"string",required:!1,constraint:"The `next_cursor` of the previous page."}],output:[{name:"items",type:"string[]",required:!0,constraint:"One row per object: id, `[[name]]`, type/kind, owner handle and preview. A document row adds its privacy, its rule count and its summary."},{name:"next_cursor",type:"string",required:!1,constraint:"Pass as `--cursor` for the next page; absent on the last page."}],outputKind:"object",effects:["None. Listing records no watch."]},run:t(async e=>{const r={...e.type?{type:e.type}:{},...e.kind?{kind:e.kind}:{},...e.owner?{owner:e.owner}:{},...e.privacy?{privacy:e.privacy}:{},...e.name?{name:e.name}:{},...e.deleted===!0?{deleted:!0}:{},limit:e.limit??50,...e.cursor?{cursor:e.cursor}:{}};try{return{...await c().objects.list(r)}}catch(n){d(n)}},"run"),render:t(e=>{const{items:r,next_cursor:n}=e;if(r.length===0)return"No objects match.";const o=r.map(m);return n&&o.push("",`More: \`crtr canvas list \u2026 --cursor ${n}\`.`),o.join(`
3
- `)},"render")});export{m as listItemRow,g as listLeaf};
1
+ var m=Object.defineProperty;var s=(e,t)=>m(e,"name",{value:t,configurable:!0});import{defineLeaf as h}from"../../core/command.js";import{PRIVACY_LEVELS_DTO as u}from"../../api/dto/objects.js";import{cliClient as f,rethrowAsCliError as p}from"../api-client.js";import{objectRow as l}from"./read.js";function y(e){const t=e.machine?`${l(e)} machine=${e.machine.name}`:l(e);if(e.type!=="document")return t;const r=[`privacy=${e.privacy}`,e.rules!==void 0?`rules=${e.rules}`:""].filter(Boolean).join(" ");return`${t}
2
+ ${r}${e.summary?` \u2014 ${e.summary}`:""}`}s(y,"listItemRow");const v=h({name:"list",description:"list objects by type, kind, owner, privacy or name prefix",whenToUse:"browsing what exists without reading it \u2014 the documents an owner holds, a node\u2019s reports, every job of a node. Records nothing, so listing does not make you a watcher.",help:{name:"canvas list",summary:"list the objects you can read, filtered; records nothing",params:[{kind:"flag",name:"type",type:"string",required:!1,constraint:"Only objects of this kind: document, node, job, person, app, repo or profile. The daemon rejects a name no kind declares and lists the kinds."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Only objects of this kind: for documents knowledge, preference or report; for nodes the node kind."},{kind:"flag",name:"owner",type:"string",required:!1,constraint:"Only objects this owner owns directly: a node id, a handle, or any name `crtr canvas read` accepts."},{kind:"flag",name:"privacy",type:"enum",choices:[...u],required:!1,constraint:"Only objects at this privacy level."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Only objects whose name starts with this prefix."},{kind:"flag",name:"deleted",type:"bool",required:!1,constraint:"When present, lists deleted objects instead of live ones."},{kind:"flag",name:"machine",type:"string",required:!1,constraint:"List exactly one machine of your account (`runtime:<id>` or its bare id), paging through its own cursor. Without it a joined runtime lists its own objects plus the first page of every other machine, each row labelled `machine=<name>`."},{kind:"flag",name:"limit",type:"int",required:!1,default:50,constraint:"Page size, 1 to 100."},{kind:"flag",name:"cursor",type:"string",required:!1,constraint:"The `next_cursor` of the previous page."}],output:[{name:"items",type:"string[]",required:!0,constraint:"One row per object: id, `[[name]]`, type/kind, owner handle and preview. A document row adds its privacy, its rule count and its summary."},{name:"machines",type:"object[]",required:!1,constraint:"On a joined runtime: one entry per machine, `{machine_id, name, state: ok|unreachable|too_old, more}`. `more` means that machine has further rows: page it with `--machine`."},{name:"next_cursor",type:"string",required:!1,constraint:"Pass as `--cursor` for the next page of THIS machine (or of `--machine`); absent on the last page."}],outputKind:"object",effects:["None. Listing records no watch."]},run:s(async e=>{const t={...e.type?{type:e.type}:{},...e.kind?{kind:e.kind}:{},...e.owner?{owner:e.owner}:{},...e.privacy?{privacy:e.privacy}:{},...e.name?{name:e.name}:{},...e.deleted===!0?{deleted:!0}:{},...e.machine?{machine:e.machine}:{},limit:e.limit??50,...e.cursor?{cursor:e.cursor}:{}};try{return{...await f().objects.list(t)}}catch(r){p(r)}},"run"),render:s(e=>{const{items:t,next_cursor:r,machines:o}=e,i=o?.length===1?o[0].machine_id:null,a=t.length===0?["No objects match."]:t.map(y),c=(o??[]).filter(n=>n.state!=="ok"),d=(o??[]).filter(n=>n.state==="ok"&&n.more&&n.machine_id!==o[0].machine_id&&i===null);(c.length>0||d.length>0)&&a.push("");for(const n of c)a.push(n.state==="too_old"?`Not listed: ${n.name} (${n.machine_id}) runs a crtr too old to take part; update it with \`crtr sys update\` there.`:`Not listed: ${n.name} (${n.machine_id}) is unreachable, so its objects are missing from this list.`);for(const n of d)a.push(`${n.name} has more: \`crtr canvas list --machine ${n.machine_id} \u2026\`.`);return r&&a.push("",`More: \`crtr canvas list \u2026${i===null?"":` --machine ${i}`} --cursor ${r}\`.`),a.join(`
3
+ `)},"render")});export{y as listItemRow,v as listLeaf};
@@ -1,10 +1,10 @@
1
- var l=Object.defineProperty;var o=(t,e)=>l(t,"name",{value:e,configurable:!0});import{defineLeaf as p}from"../../core/command.js";import{attr as i,header as m,section as f}from"../../core/graph/kinds/render.js";import{PREVIEW_RESULT_PATH_ENV as c}from"../../core/preview-result-path.js";import{isJsonOutput as b}from"../../core/io.js";import{cliClient as w,rethrowAsCliError as y}from"../api-client.js";const j="A name (the `[[ ]]` brackets are optional) or a raw object id. A name is `<owner-handle>/<path>`; a node or other owner is also named by its id or handle.";import{attr as q,objectRow as O}from"../../core/graph/kinds/render.js";function h(t){if(t.state==="remote")return`No object named ${t.remote} on this runtime. It may be on another runtime, or the name may be misspelled.`;if(t.state==="deleted"){const e=t.deleted;return[`${m("deleted",e,i("deleted_at",e.deleted_at))}`,`Last preview: ${e.preview}`,"</deleted>"].join(`
1
+ var l=Object.defineProperty;var o=(t,e)=>l(t,"name",{value:e,configurable:!0});import{defineLeaf as p}from"../../core/command.js";import{attr as i,header as u,section as f}from"../../core/graph/kinds/render.js";import{PREVIEW_RESULT_PATH_ENV as c}from"../../core/preview-result-path.js";import{isJsonOutput as b}from"../../core/io.js";import{cliClient as w,rethrowAsCliError as y}from"../api-client.js";const j="A name (the `[[ ]]` brackets are optional) or a raw object id. A name is `<owner-handle>/<path>`; a node or other owner is also named by its id or handle.";import{attr as q,objectRow as I}from"../../core/graph/kinds/render.js";function h(t){if(t.state==="remote")return`No object named ${t.remote} on this runtime. It may be on another runtime, or the name may be misspelled.`;if(t.state==="unknown")return`No machine of yours answers for ${t.ref} (unreachable: ${t.unreachable.join(", ")||"none named"}). It may be on one of them; try again when they are back.`;if(t.state==="deleted"){const e=t.deleted;return[`${u("deleted",e,i("deleted_at",e.deleted_at))}`,`Last preview: ${e.preview}`,"</deleted>"].join(`
2
2
  `)}return`${t.text}
3
- </object>`}o(h,"renderRead");function u(t){if(t.state!=="object"||t.type!=="document")return null;const e=t.data,n=t.object,r=[m("object",n,i("privacy",n.privacy)+i("revision",e.revision)),`Preview: ${n.preview}`];if(e.summary&&r.push(`Summary: ${e.summary}`),e.fields!==void 0&&r.push(`<fields>
3
+ </object>`}o(h,"renderRead");function m(t){if(t.state!=="object"||t.type!=="document")return null;const e=t.data,n=t.object,r=[u("object",n,i("privacy",n.privacy)+i("revision",e.revision)),`Preview: ${n.preview}`];if(e.summary&&r.push(`Summary: ${e.summary}`),e.fields!==void 0&&r.push(`<fields>
4
4
  ${JSON.stringify(e.fields,null,2)}
5
5
  </fields>`),e.needs_merge&&(e.heads.forEach((a,d)=>r.push(`<head${i("rev",a.rev_id)}${i("number",a.number)}${i("author",a.author_id)}>
6
6
  ${e.bodies[d]??""}
7
7
  </head>`)),r.push(`This document has ${e.heads.length} heads. Your next \`crtr doc edit\` writes the merged body and joins them into one revision.`)),e.listing?.length){const a=e.listing.some(s=>s.owner?.id===n.owner?.id&&s.name.startsWith(`${n.name}/`)),d=e.listing.some(s=>s.owner?.id!==n.owner?.id||!s.name.startsWith(`${n.name}/`));r.push("",...f(a&&d?`Names under ${n.name}/ and documents it links to`:a?`Names under ${n.name}/`:"Documents it links to",e.listing))}return r.push("</object>"),{body:e.needs_merge?"":e.bodies[0]??"",metadata:r.join(`
8
- `),context:e.attachments??""}}o(u,"documentReadParts");function v(t){const e=u(t);return e===null||t.state!=="object"||b()?t:{state:"object",type:"document",object:t.object,readMetadata:e.metadata,readContext:e.context}}o(v,"previewRead");function g(t){const e=u(t);if(e===null||process.env[c]===void 0&&process.stdout.isTTY)return h(t);const n=`${e.metadata}
8
+ `),context:e.attachments??""}}o(m,"documentReadParts");function v(t){const e=m(t);return e===null||t.state!=="object"||b()?t:{state:"object",type:"document",object:t.object,readMetadata:e.metadata,readContext:e.context}}o(v,"previewRead");function g(t){const e=m(t);if(e===null||process.env[c]===void 0&&process.stdout.isTTY)return h(t);const n=`${e.metadata}
9
9
  ${e.context?`${e.context}
10
- `:""}`;return process.env[c]||process.stderr.write(n),e.body}o(g,"renderReadOutput");const k=p({name:"read",description:"read one object \u2014 a document, node, job or owner \u2014 by name or id",whenToUse:"you need the content or state of an object you can name: a document\u2019s body, a node\u2019s status and what it owns, a job\u2019s result. Reading makes you a watcher of the object. Find the name first with `crtr canvas search` or `crtr canvas list` when you do not know it.",help:{name:"canvas read",summary:"read one object by name or id and become a watcher of it",params:[{kind:"positional",name:"ref",required:!0,constraint:j},{kind:"flag",name:"fields",type:"bool",required:!1,constraint:"When present, a document read also returns its delivery rules, why-it-exists, unlisted, lint-ignore and plugin extension fields."}],output:[{name:"object",type:"string",required:!0,constraint:"A document prints only its exact body to stdout (including when piped or redirected). Its attributed `<object>` header, preview, summary, revision, listing, linked previews, and `<auto-loaded-context>` arrive AFTER command output in a node\u2019s tool result. Several heads print no stdout body; each head with its text appears in metadata, and the next `crtr doc edit` merges them. Outside a node, metadata goes to stderr when stdout is not a TTY; a TTY shows the full rendering as before. `--json` is unchanged. Non-document objects retain their full rendered stdout."},{name:"deleted",type:"string",required:!1,constraint:"`<deleted \u2026>` with the last preview when the object was deleted."},{name:"remote",type:"string",required:!1,constraint:"A line saying no object has that name on this runtime: it may be on another runtime, or the name may be misspelled."}],outputKind:"object",effects:["A live read records a watch on the object for you (every event subject) and publishes a read event to its other watchers. A deleted or remote answer records nothing."]},run:o(async t=>{try{return{...await w().objects.read(t.ref,t.fields===!0?{fields:!0}:void 0)}}catch(e){y(e)}},"run"),render:o(t=>g(t),"render"),preview:o(t=>v(t),"preview"),stdoutExact:o(t=>u(t)!==null&&!(process.env[c]===void 0&&process.stdout.isTTY),"stdoutExact")});export{j as REF_CONSTRAINT,q as attr,u as documentReadParts,O as objectRow,v as previewRead,k as readLeaf,h as renderRead,g as renderReadOutput};
10
+ `:""}`;return process.env[c]||process.stderr.write(n),e.body}o(g,"renderReadOutput");const _=p({name:"read",description:"read one object \u2014 a document, node, job or owner \u2014 by name or id",whenToUse:"you need the content or state of an object you can name: a document\u2019s body, a node\u2019s status and what it owns, a job\u2019s result. Reading makes you a watcher of the object. Find the name first with `crtr canvas search` or `crtr canvas list` when you do not know it.",help:{name:"canvas read",summary:"read one object by name or id and become a watcher of it",params:[{kind:"positional",name:"ref",required:!0,constraint:j},{kind:"flag",name:"fields",type:"bool",required:!1,constraint:"When present, a document read also returns its delivery rules, why-it-exists, unlisted, lint-ignore and plugin extension fields."}],output:[{name:"object",type:"string",required:!0,constraint:"A document prints only its exact body to stdout (including when piped or redirected). Its attributed `<object>` header, preview, summary, revision, listing, linked previews, and `<auto-loaded-context>` arrive AFTER command output in a node\u2019s tool result. Several heads print no stdout body; each head with its text appears in metadata, and the next `crtr doc edit` merges them. Outside a node, metadata goes to stderr when stdout is not a TTY; a TTY shows the full rendering as before. `--json` is unchanged. Non-document objects retain their full rendered stdout."},{name:"deleted",type:"string",required:!1,constraint:"`<deleted \u2026>` with the last preview when the object was deleted."},{name:"remote",type:"string",required:!1,constraint:"A line saying no object has that name on this runtime: it may be on another runtime, or the name may be misspelled."}],outputKind:"object",effects:["A live read records a watch on the object for you (every event subject) and publishes a read event to its other watchers. A deleted or remote answer records nothing."]},run:o(async t=>{try{return{...await w().objects.read(t.ref,t.fields===!0?{fields:!0}:void 0)}}catch(e){y(e)}},"run"),render:o(t=>g(t),"render"),preview:o(t=>v(t),"preview"),stdoutExact:o(t=>m(t)!==null&&!(process.env[c]===void 0&&process.stdout.isTTY),"stdoutExact")});export{j as REF_CONSTRAINT,q as attr,m as documentReadParts,I as objectRow,v as previewRead,_ as readLeaf,h as renderRead,g as renderReadOutput};
@@ -1,12 +1,12 @@
1
- var N=Object.defineProperty;var h=(n,e)=>N(n,"name",{value:e,configurable:!0});import{defineBranch as R,defineLeaf as b}from"../../core/command.js";import{InputError as y}from"../../core/io.js";import{requireScope as P}from"../../core/errors.js";import{isPidAlive as I}from"../../core/canvas/pid.js";import{fullName as k}from"../../core/canvas/labels.js";import{fuzzyMatch as O}from"../../core/tui/fuzzy.js";import{nodeSnapshotLeaf as F}from"../node-snapshot.js";import{nodeTranscriptLeaf as L}from"../node-transcript.js";import{nodeContextLeaf as A}from"../node-context.js";import{cliClient as $,cliCanvasSource as x,rethrowAsCliError as w}from"../api-client.js";import{RemoteCanvasSource as q}from"../../core/canvas/remote-canvas-source.js";import{kindsStateBlock as C,profilesStateBlock as U,resolveProfileViaDaemon as D}from"../profile/daemon-profiles.js";import{focusViewer as z,nodeInPane as E}from"../surface/node/placement.js";const B=b({name:"list",description:"list nodes on the canvas",whenToUse:"you want a flat roster of the nodes on the canvas, sliced by displayed name, status, kind, mode, lifecycle, profile, cwd, and graph scope (--under a node, for that node plus its subscription descendants). The filters AND together. Use `node inspect show` instead to drill into one node and its spine neighbors, `canvas dashboard` for the tree shape, and `canvas attention` to find nodes blocked on the user",help:{name:"node inspect list",summary:"list nodes on the canvas, filtered by any combination of attribute slices and a graph-scope (--under) that AND together",params:[{kind:"flag",name:"status",type:"string",required:!1,constraint:"Filter status (comma-separated for several; accepted values: active, idle, done, dead, canceled). NOTE: `active` means the engine process is live (never closed), NOT that the node is generating right now \u2014 an active node is usually dormant between turns. To tell working-vs-idle, check the pi session-file mtime or CPU, not status."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Filter by persona kind (comma-separated for several). The <kinds> list below names every top-level installable kind; a registered sub-kind filters too by exact path even though it is not listed."},{kind:"flag",name:"mode",type:"string",required:!1,constraint:"Filter by mode (comma-separated for several; accepted values: base, orchestrator)."},{kind:"flag",name:"lifecycle",type:"string",required:!1,constraint:"Filter by lifecycle (comma-separated for several; accepted values: terminal, resident)."},{kind:"flag",name:"profile",type:"string",required:!1,constraint:"Filter by profile. On the local canvas, accepts a profile id or manifest name from the <profiles> list below, or a close-enough name that resolves to the nearest profile. Against a remote canvas, pass the exact profile id carried by its node rows."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Filter by the displayed node label (handle plus generated description) with the canvas browser\u2019s case-insensitive fuzzy subsequence match."},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Filter to nodes pinned to this exact cwd (absolute path). Roster is canvas-wide (all cwds) by default."},{kind:"flag",name:"under",type:"string",required:!1,constraint:'Graph scope: restrict to this node and its subscription descendants (the sub-DAG reachable downward \u2014 its reports, theirs, \u2026). This is "one initiative / my subtree". ANDs with every attribute filter above, narrowing any of them to this subtree.'},{kind:"flag",name:"canvas",type:"string",required:!1,constraint:"Read from the named remote canvas target (see `crtr canvas config list`) instead of the local canvas. Overrides `crtr canvas use`."}],output:[{name:"nodes",type:"object[]",required:!0,constraint:"Rows include {node_id, name, kind, mode, lifecycle, status, cwd, profile_id, parent, created}. `name` is the displayed node label searched by --name and shown in canvas views. `profile_id` is the stable profile-directory id (null only for historical no-profile nodes)."}],outputKind:"object",effects:["Read-only: lists nodes through the daemon API."]},helpState:{leaf:h(async()=>(await Promise.all([C(),U()])).join(`
2
- `),"leaf")},run:h(async n=>{const e=h(r=>{const g=n[r];return g!==void 0&&g!==""?g.split(",").map(v=>v.trim()).filter(v=>v!==""):void 0},"csv"),o=e("status"),s=e("kind"),u=e("mode"),d=e("lifecycle"),f=n.profile,c=n.name,l=n.cwd,i=n.under,p=x({canvasName:n.canvas}),a=p instanceof q?f:f===void 0?void 0:(await D(f)).id;let t;if(i!==void 0&&i!==""){if(await p.getNode(i).catch(w)===null)throw new y({error:"not_found",message:`no node: ${i}`,next:"List nodes with `crtr node inspect list`, then pass a real id to --under."});t=new Set([i,...await p.view(i)])}return{nodes:(await p.listNodes(o!==void 0?{status:o}:void 0).catch(w)).filter(r=>!(t!==void 0&&!t.has(r.node_id)||s!==void 0&&!s.includes(r.kind)||u!==void 0&&!u.includes(r.mode)||d!==void 0&&!d.includes(r.lifecycle)||a!==void 0&&r.profile_id!==a||l!==void 0&&l!==""&&r.cwd!==l)).map(r=>({...r,name:k(r)})).filter(r=>c===void 0||c===""||O(c,r.name))}},"run")}),S=new WeakSet;async function j(n,e){const o=await n.getNode(e.node_id).catch(w);return{node_id:e.node_id,name:o?k(o):"(missing)",kind:o?.kind??"?",status:o?o.intent?`${o.status}/${o.intent}`:o.status:"?",active:e.active}}h(j,"enrichNeighbor");const K=b({name:"show",description:"show a node + its spine neighbors and drill-in pointers",whenToUse:"you want one node in depth \u2014 the debugging entrypoint: a glanceable meta slice, named spine neighbors, a pending-cron count, and exact drill-in paths. Use `node inspect list` for the flat roster or `canvas dashboard` for the whole-tree shape",help:{name:"node inspect show",summary:"show a node in depth \u2014 meta slice, named neighbors, pending-cron count, and drill-in pointers",params:[{kind:"positional",name:"node",required:!0,constraint:"Node id."},{kind:"flag",name:"canvas",type:"string",required:!1,constraint:"Read from the named remote canvas target (see `crtr canvas config list`) instead of the local canvas. Overrides `crtr canvas use`."}],output:[{name:"node",type:"object",required:!0,constraint:"The full node meta (system prompt + every telemetry field). The render surfaces a glanceable slice; `--json` carries the complete dump."},{name:"fault",type:"object",required:!0,constraint:"The active runtime fault projection, or null: {link, kind, retry: {disposition}, since}."},{name:"reports",type:"object[]",required:!0,constraint:"Who this node subscribes to (its reports/down), each enriched with {node_id, name, kind, status, active} \u2014 these are typically its spawned children."},{name:"managers",type:"object[]",required:!0,constraint:"Who subscribes to this node (its managers/up), same enriched shape."},{name:"pending_crons",type:"number",required:!0,constraint:"How many pending crons were created by this node. Inspect with `crtr cron list`."},{name:"paths",type:"object",required:!0,constraint:"Resolved on-disk locations: {context_dir, session_file} \u2014 read these directly to debug. Reports are documents: `crtr canvas read <node>/reports/<name>`."},{name:"follow_up",type:"string",required:!0,constraint:"The concrete next commands to drill deeper."}],outputKind:"object",effects:["Read-only: reads the node, its edges, and pending crons through crtrd."],preview:{suppressOutput:!0}},run:h(async n=>{const e=n.node,o=n.canvas,s=x({canvasName:o});if(s instanceof q){const t=await s.rawShow(e);if(t===null)throw new y({error:"not_found",message:`no node: ${e}`,next:"List nodes with `crtr node inspect list --canvas <name>`."});const m={...t};return S.add(m),m}const u=await s.getNode(e).catch(t=>w(t,"List nodes with `crtr node inspect list`."));if(u===null)throw new y({error:"not_found",message:`no node: ${e}`,next:"List nodes with `crtr node inspect list`."});const d=u,f=$(),c=await Promise.all((await s.subscriptionsOf(e)).map(t=>j(s,t))),l=await Promise.all((await s.subscribersOf(e)).map(t=>j(s,t))),i=(await f.listCrons().catch(w)).filter(t=>t.created_by===e).length,p={context_dir:d.paths.context_dir,...d.pi_session_file!==void 0?{session_file:d.pi_session_file}:{}},a=[];return a.push(`List the documents it owns (reports, plans, notes): \`crtr canvas list --owner ${e}\`.`),a.push(`Browse its context dir (and its subscriptions') in nvim: \`crtr node inspect context ${e}\`.`),p.session_file&&a.push(`Read the raw conversation transcript: ${p.session_file}`),a.push(`On-disk context dir (ls/read directly): ${p.context_dir}`),i>0&&a.push(`It created ${i} pending cron(s): \`crtr cron list\`.`),c.length>0&&a.push(`Drill into a child/report: \`crtr node inspect show <node_id>\` (${c.map(t=>t.node_id).join(", ")}).`),a.push(`Full machine dump (system prompt + all telemetry): \`crtr --json node inspect show ${e}\`.`),{node:d,fault:d.fault??null,reports:c,managers:l,pending_crons:i,paths:p,follow_up:a.join(`
3
- `)}},"run"),render:h(n=>{const e=n.node,o=String(e.node_id),s=e.pi_pid,d=S.has(n)?"remote (liveness not checked locally)":I(s)?"alive":"dead",f=e.intent?`/${String(e.intent)}`:"",c=n.fault,l=[];l.push(`# ${k(e)} (${o})`);const i=[`- kind/mode/lifecycle: ${e.kind} / ${e.mode} / ${e.lifecycle}`,`- status: ${e.status}${f}`,...c==null?[]:[`- fault: ${String(c.kind)} / ${String(c.retry?.disposition)} since ${String(c.since)}`],`- broker pid: ${s??"(none)"} \u2014 ${d}`,`- cwd: ${e.cwd}`,`- host: ${e.host_kind}`+(e.profile_id?` \xB7 profile ${e.profile_id}`:""),`- cycles: ${e.cycles} \xB7 created ${e.created}`];e.parent&&i.push(`- parent: ${e.parent}`),i.push(`- creator: ${e.creator??"an external process"}`),e.waiting_for&&i.push(`- waiting for message: ${e.waiting_for}`),l.push(i.join(`
4
- `));const p=h((a,t)=>{if(t.length===0)return`${a}: none`;const m=["node_id","name","kind","status","active"],_=`| ${m.join(" | ")} |`,r=`| ${m.map(()=>"---").join(" | ")} |`,g=t.map(v=>`| ${m.map(T=>String(v[T]??"").replace(/\|/g,"\\|").replace(/\n/g," ")).join(" | ")} |`).join(`
5
- `);return`${t.length} ${a}:
1
+ var R=Object.defineProperty;var h=(n,e)=>R(n,"name",{value:e,configurable:!0});import{defineBranch as P,defineLeaf as k}from"../../core/command.js";import{InputError as y}from"../../core/io.js";import{requireScope as I}from"../../core/errors.js";import{isPidAlive as O}from"../../core/canvas/pid.js";import{fullName as $}from"../../core/canvas/labels.js";import{fuzzyMatch as F}from"../../core/tui/fuzzy.js";import{nodeSnapshotLeaf as L}from"../node-snapshot.js";import{nodeTranscriptLeaf as A}from"../node-transcript.js";import{nodeContextLeaf as C}from"../node-context.js";import{cliClient as _,cliCanvasSource as x,rethrowAsCliError as w}from"../api-client.js";import{RemoteCanvasSource as q}from"../../core/canvas/remote-canvas-source.js";import{kindsStateBlock as U,profilesStateBlock as D,resolveProfileViaDaemon as z}from"../profile/daemon-profiles.js";import{focusViewer as E,nodeInPane as B}from"../surface/node/placement.js";const M=k({name:"list",description:"list nodes on the canvas",whenToUse:"you want a flat roster of the nodes on the canvas, sliced by displayed name, status, kind, mode, lifecycle, profile, cwd, and graph scope (--under a node, for that node plus its subscription descendants). The filters AND together. Use `node inspect show` instead to drill into one node and its spine neighbors, `canvas dashboard` for the tree shape, and `canvas attention` to find nodes blocked on the user",help:{name:"node inspect list",summary:"list nodes on the canvas, filtered by any combination of attribute slices and a graph-scope (--under) that AND together",params:[{kind:"flag",name:"status",type:"string",required:!1,constraint:"Filter status (comma-separated for several; accepted values: active, idle, done, dead, canceled). NOTE: `active` means the engine process is live (never closed), NOT that the node is generating right now \u2014 an active node is usually dormant between turns. To tell working-vs-idle, check the pi session-file mtime or CPU, not status."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Filter by persona kind (comma-separated for several). The <kinds> list below names every top-level installable kind; a registered sub-kind filters too by exact path even though it is not listed."},{kind:"flag",name:"mode",type:"string",required:!1,constraint:"Filter by mode (comma-separated for several; accepted values: base, orchestrator)."},{kind:"flag",name:"lifecycle",type:"string",required:!1,constraint:"Filter by lifecycle (comma-separated for several; accepted values: terminal, resident)."},{kind:"flag",name:"profile",type:"string",required:!1,constraint:"Filter by profile. On the local canvas, accepts a profile id or manifest name from the <profiles> list below, or a close-enough name that resolves to the nearest profile. Against a remote canvas, pass the exact profile id carried by its node rows."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Filter by the displayed node label (handle plus generated description) with the canvas browser\u2019s case-insensitive fuzzy subsequence match."},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Filter to nodes pinned to this exact cwd (absolute path). Roster is canvas-wide (all cwds) by default."},{kind:"flag",name:"under",type:"string",required:!1,constraint:'Graph scope: restrict to this node and its subscription descendants (the sub-DAG reachable downward \u2014 its reports, theirs, \u2026). This is "one initiative / my subtree". ANDs with every attribute filter above, narrowing any of them to this subtree.'},{kind:"flag",name:"canvas",type:"string",required:!1,constraint:"Read from the named remote canvas target (see `crtr canvas config list`) instead of the local canvas. Overrides `crtr canvas use`."}],output:[{name:"nodes",type:"object[]",required:!0,constraint:"Rows include {node_id, name, kind, mode, lifecycle, status, cwd, profile_id, parent, created}. `name` is the displayed node label searched by --name and shown in canvas views. `profile_id` is the stable profile-directory id (null only for historical no-profile nodes)."}],outputKind:"object",effects:["Read-only: lists nodes through the daemon API."]},helpState:{leaf:h(async()=>(await Promise.all([U(),D()])).join(`
2
+ `),"leaf")},run:h(async n=>{const e=h(r=>{const g=n[r];return g!==void 0&&g!==""?g.split(",").map(v=>v.trim()).filter(v=>v!==""):void 0},"csv"),o=e("status"),t=e("kind"),u=e("mode"),d=e("lifecycle"),m=n.profile,c=n.name,l=n.cwd,i=n.under,p=x({canvasName:n.canvas}),a=p instanceof q?m:m===void 0?void 0:(await z(m)).id;let s;if(i!==void 0&&i!==""){if(await p.getNode(i).catch(w)===null)throw new y({error:"not_found",message:`no node: ${i}`,next:"List nodes with `crtr node inspect list`, then pass a real id to --under."});s=new Set([i,...await p.view(i)])}return{nodes:(await p.listNodes(o!==void 0?{status:o}:void 0).catch(w)).filter(r=>!(s!==void 0&&!s.has(r.node_id)||t!==void 0&&!t.includes(r.kind)||u!==void 0&&!u.includes(r.mode)||d!==void 0&&!d.includes(r.lifecycle)||a!==void 0&&r.profile_id!==a||l!==void 0&&l!==""&&r.cwd!==l)).map(r=>({...r,name:$(r)})).filter(r=>c===void 0||c===""||F(c,r.name))}},"run")}),S=new WeakSet;async function j(n,e){const o=await n.getNode(e.node_id).catch(w);return{node_id:e.node_id,name:o?$(o):"(missing)",kind:o?.kind??"?",status:o?o.intent?`${o.status}/${o.intent}`:o.status:"?",active:e.active}}h(j,"enrichNeighbor");const K=k({name:"show",description:"show a node + its spine neighbors and drill-in pointers",whenToUse:"you want one node in depth \u2014 the debugging entrypoint: a glanceable meta slice, named spine neighbors, a pending-cron count, and exact drill-in paths. Use `node inspect list` for the flat roster or `canvas dashboard` for the whole-tree shape",help:{name:"node inspect show",summary:"show a node in depth \u2014 meta slice, named neighbors, pending-cron count, and drill-in pointers",params:[{kind:"positional",name:"node",required:!0,constraint:"Node id."},{kind:"flag",name:"canvas",type:"string",required:!1,constraint:"Read from the named remote canvas target (see `crtr canvas config list`) instead of the local canvas. Overrides `crtr canvas use`."}],output:[{name:"node",type:"object",required:!0,constraint:"The full node meta (system prompt + every telemetry field). The render surfaces a glanceable slice; `--json` carries the complete dump."},{name:"fault",type:"object",required:!0,constraint:"The active runtime fault projection, or null: {link, kind, retry: {disposition}, since}."},{name:"reports",type:"object[]",required:!0,constraint:"Who this node subscribes to (its reports/down), each enriched with {node_id, name, kind, status, active} \u2014 these are typically its spawned children."},{name:"managers",type:"object[]",required:!0,constraint:"Who subscribes to this node (its managers/up), same enriched shape."},{name:"pending_crons",type:"number",required:!0,constraint:"How many pending crons were created by this node. Inspect with `crtr cron list`."},{name:"paths",type:"object",required:!0,constraint:"Resolved on-disk locations: {context_dir, session_file} \u2014 read these directly to debug. Reports are documents: `crtr canvas read <node>/reports/<name>`."},{name:"follow_up",type:"string",required:!0,constraint:"The concrete next commands to drill deeper."}],outputKind:"object",effects:["Read-only: reads the node, its edges, and pending crons through crtrd."],preview:{suppressOutput:!0}},run:h(async n=>{const e=n.node,o=n.canvas,t=x({canvasName:o});if(t instanceof q){const s=await t.rawShow(e);if(s===null)throw new y({error:"not_found",message:`no node: ${e}`,next:"List nodes with `crtr node inspect list --canvas <name>`."});const f={...s};return S.add(f),f}const u=await t.getNode(e).catch(s=>w(s,"List nodes with `crtr node inspect list`."));if(u===null)throw new y({error:"not_found",message:`no node: ${e}`,next:"List nodes with `crtr node inspect list`."});const d=u,m=_(),c=await Promise.all((await t.subscriptionsOf(e)).map(s=>j(t,s))),l=await Promise.all((await t.subscribersOf(e)).map(s=>j(t,s))),i=(await m.listCrons().catch(w)).filter(s=>s.created_by===e).length,p={context_dir:d.paths.context_dir,...d.pi_session_file!==void 0?{session_file:d.pi_session_file}:{}},a=[];return a.push(`List the documents it owns (reports, plans, notes): \`crtr canvas list --owner ${e}\`.`),a.push(`Browse its context dir (and its subscriptions') in nvim: \`crtr node inspect context ${e}\`.`),p.session_file&&a.push(`Read the raw conversation transcript: ${p.session_file}`),a.push(`On-disk context dir (ls/read directly): ${p.context_dir}`),i>0&&a.push(`It created ${i} pending cron(s): \`crtr cron list\`.`),c.length>0&&a.push(`Drill into a child/report: \`crtr node inspect show <node_id>\` (${c.map(s=>s.node_id).join(", ")}).`),a.push(`Full machine dump (system prompt + all telemetry): \`crtr --json node inspect show ${e}\`.`),{node:d,fault:d.fault??null,reports:c,managers:l,pending_crons:i,paths:p,follow_up:a.join(`
3
+ `)}},"run"),render:h(n=>{const e=n.node,o=String(e.node_id),t=e.pi_pid,d=S.has(n)?"remote (liveness not checked locally)":O(t)?"alive":"dead",m=e.intent?`/${String(e.intent)}`:"",c=n.fault,l=[];l.push(`# ${$(e)} (${o})`);const i=[`- kind/mode/lifecycle: ${e.kind} / ${e.mode} / ${e.lifecycle}`,`- status: ${e.status}${m}`,...c==null?[]:[`- fault: ${String(c.kind)} / ${String(c.retry?.disposition)} since ${String(c.since)}`],`- broker pid: ${t??"(none)"} \u2014 ${d}`,`- cwd: ${e.cwd}`,`- host: ${e.host_kind}`+(e.profile_id?` \xB7 profile ${e.profile_id}`:""),`- cycles: ${e.cycles} \xB7 created ${e.created}`],p=e.machine;p&&i.push(`- machine: ${p.name??p.id}`),e.parent&&i.push(`- parent: ${e.parent}`),i.push(`- creator: ${e.creator??"an external process"}`),e.waiting_for&&i.push(`- waiting for message: ${e.waiting_for}`),l.push(i.join(`
4
+ `));const a=h((s,f)=>{if(f.length===0)return`${s}: none`;const b=["node_id","name","kind","status","active"],r=`| ${b.join(" | ")} |`,g=`| ${b.map(()=>"---").join(" | ")} |`,v=f.map(T=>`| ${b.map(N=>String(T[N]??"").replace(/\|/g,"\\|").replace(/\n/g," ")).join(" | ")} |`).join(`
5
+ `);return`${f.length} ${s}:
6
6
 
7
- ${_}
8
7
  ${r}
9
- ${g}`},"table");return l.push(p("reports (down \u2014 subscriptions, usually children)",n.reports)),l.push(p("managers (up \u2014 subscribers)",n.managers)),l.push(`${n.pending_crons} pending cron(s)`),l.push(`Drill deeper:
8
+ ${g}
9
+ ${v}`},"table");return l.push(a("reports (down \u2014 subscriptions, usually children)",n.reports)),l.push(a("managers (up \u2014 subscribers)",n.managers)),l.push(`${n.pending_crons} pending cron(s)`),l.push(`Drill deeper:
10
10
  ${String(n.follow_up)}`),l.join(`
11
11
 
12
- `)},"render")}),oe=b({name:"fork",description:"fork a node into an independent idle root with the same conversation and durable context",whenToUse:"a conversation has already accumulated the context you want, and you want a new user-driven branch beside it without sending a prompt or inheriting any graph-management responsibility. This copies the conversation and durable context, then waits for the fork\u2019s first real user message to supply its mandate. Use `node new --fork-from` instead when spawning a managed worker with an immediate task",help:{name:"node fork",summary:"create an independent resident root from a node\u2019s conversation and durable context, with no kickoff prompt or model turn",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Source node id. Mutually exclusive with --pane; one of them is required."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the source node. Mutually exclusive with --node; one of them is required. The prefix menu passes this for you and the new viewer splits beside this pane."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The new independent root id."},{name:"focused",type:"boolean",required:!0,constraint:"True when a new viewer pane opened beside the caller; false outside tmux."},{name:"session",type:"string",required:!1,constraint:"The tmux session containing the new viewer, when one opened."}],outputKind:"object",effects:["Creates a resident base-mode root with the source node\u2019s copied pi conversation, cwd, profile, kind, model override, and durable context files. Top-level initial-prompt.md, yield-message.md, result.json, and the jobs/ folder of background bash jobs are omitted.","Copies no parent, subscriptions, subscribers, children, crons, worktree, situational context, or lifecycle responsibility. Sends no prompt and consumes no model turn; the first real user message becomes the fork\u2019s mandate.","Inside tmux, opens and focuses a surface-attach viewer as an even-horizontal split in the source pane\u2019s window. Outside tmux, the root is still created but no interactive fallback opens."]},run:h(async n=>{P("crtr:act","crtr node fork");const e=n.pane,o=n.node;if(o!==void 0&&e!==void 0)throw new y({error:"conflicting_target",message:"--node and --pane are mutually exclusive",next:"Pass the source node id or its viewer pane, not both."});let s=o;if(s===void 0&&e!==void 0&&(s=E(e)),s===void 0||s==="")throw new y({error:"no_node",message:"no source node was provided or found in the pane",next:"Pass --node <id>, or --pane <pane-id> for a pane showing a node."});const u=await $().forkNode(s).catch(w),d=await z(u.node_id,{cwd:u.cwd,name:u.name,...e!==void 0?{pane:e}:{},newPane:!0});return{node_id:u.node_id,focused:d.focused,session:d.session}},"run"),render:h(n=>{const e=n.focused===!0?` Viewer opened beside the source${n.session?` in session ${n.session}`:""}.`:" No viewer opened because no tmux pane was available.";return`Forked independent idle root ${n.node_id}.${e} It will make no model call until its first real user message.`},"render")}),ie=R({name:"inspect",description:"read one node \u2014 its graph position, conversation, and context dir",whenToUse:"reading everything about ONE node you can name: enumerate the roster (`list`), inspect a node and its spine neighbors (`show`), or browse its on-disk context dir (`context`). The documents a node owns are `crtr canvas list --owner <id>`. Use `canvas dashboard` instead to render the whole tree SHAPE, or `canvas attention` to find which nodes are blocked on the user",help:{name:"node inspect",summary:"read one node (or the roster) \u2014 topology, conversation, and context dir",model:"Inspection is read-only and never revives a node. The roster is the selection surface; one-node metadata points to progressively heavier views: a parsed broker snapshot, rendered conversation, or context browser."},children:[B,K,F,L,A]});export{oe as nodeFork,ie as nodeInspect};
12
+ `)},"render")}),ie=k({name:"fork",description:"fork a node into an independent idle root with the same conversation and durable context",whenToUse:"a conversation has already accumulated the context you want, and you want a new user-driven branch beside it without sending a prompt or inheriting any graph-management responsibility. This copies the conversation and durable context, then waits for the fork\u2019s first real user message to supply its mandate. Use `node new --fork-from` instead when spawning a managed worker with an immediate task",help:{name:"node fork",summary:"create an independent resident root from a node\u2019s conversation and durable context, with no kickoff prompt or model turn",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Source node id. Mutually exclusive with --pane; one of them is required."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the source node. Mutually exclusive with --node; one of them is required. The prefix menu passes this for you and the new viewer splits beside this pane."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The new independent root id."},{name:"focused",type:"boolean",required:!0,constraint:"True when a new viewer pane opened beside the caller; false outside tmux."},{name:"session",type:"string",required:!1,constraint:"The tmux session containing the new viewer, when one opened."}],outputKind:"object",effects:["Creates a resident base-mode root with the source node\u2019s copied pi conversation, cwd, profile, kind, model override, and durable context files. Top-level initial-prompt.md, yield-message.md, result.json, and the jobs/ folder of background bash jobs are omitted.","Copies no parent, subscriptions, subscribers, children, crons, worktree, situational context, or lifecycle responsibility. Sends no prompt and consumes no model turn; the first real user message becomes the fork\u2019s mandate.","Inside tmux, opens and focuses a surface-attach viewer as an even-horizontal split in the source pane\u2019s window. Outside tmux, the root is still created but no interactive fallback opens."]},run:h(async n=>{I("crtr:act","crtr node fork");const e=n.pane,o=n.node;if(o!==void 0&&e!==void 0)throw new y({error:"conflicting_target",message:"--node and --pane are mutually exclusive",next:"Pass the source node id or its viewer pane, not both."});let t=o;if(t===void 0&&e!==void 0&&(t=B(e)),t===void 0||t==="")throw new y({error:"no_node",message:"no source node was provided or found in the pane",next:"Pass --node <id>, or --pane <pane-id> for a pane showing a node."});const u=await _().forkNode(t).catch(w),d=await E(u.node_id,{cwd:u.cwd,name:u.name,...e!==void 0?{pane:e}:{},newPane:!0});return{node_id:u.node_id,focused:d.focused,session:d.session}},"run"),render:h(n=>{const e=n.focused===!0?` Viewer opened beside the source${n.session?` in session ${n.session}`:""}.`:" No viewer opened because no tmux pane was available.";return`Forked independent idle root ${n.node_id}.${e} It will make no model call until its first real user message.`},"render")}),re=P({name:"inspect",description:"read one node \u2014 its graph position, conversation, and context dir",whenToUse:"reading everything about ONE node you can name: enumerate the roster (`list`), inspect a node and its spine neighbors (`show`), or browse its on-disk context dir (`context`). The documents a node owns are `crtr canvas list --owner <id>`. Use `canvas dashboard` instead to render the whole tree SHAPE, or `canvas attention` to find which nodes are blocked on the user",help:{name:"node inspect",summary:"read one node (or the roster) \u2014 topology, conversation, and context dir",model:"Inspection is read-only and never revives a node. The roster is the selection surface; one-node metadata points to progressively heavier views: a parsed broker snapshot, rendered conversation, or context browser."},children:[M,K,L,A,C]});export{ie as nodeFork,re as nodeInspect};
@@ -1 +1 @@
1
- var f=Object.defineProperty;var i=(n,e)=>f(n,"name",{value:e,configurable:!0});import{ApiError as h}from"../../api/index.js";import{identifyCheckout as p}from"../../core/graph/repo-sync/identity.js";import{rulesFromFrontmatter as w}from"../../core/graph/rules.js";import{usage as g}from"../../core/errors.js";function S(n){if(typeof n=="string")return n;if(typeof n=="number"||typeof n=="boolean")return String(n)}i(S,"scalarString");function m(n){let e=n.replace(/\s+/g," ").trim();return e=e.replace(/^[,;:.\s]+/,"").replace(/[,;:.\s]+$/,""),/^[A-Z][a-z]/.test(e)&&(e=e.charAt(0).toLowerCase()+e.slice(1)),e}i(m,"cleanClause");const y=/\b(?:use\s+this\s+skill\s+when|use\s+this\s+when|this\s+skill\s+should\s+be\s+used\s+when|used\s+when|use\s+when)\b\s*/i;function v(n,e){const t=n.replace(/\s+/g," ").trim();if(t==="")return`When this migrated ${e} applies, this ${e} should be read.`;const r=t.match(y);if(r&&r.index!==void 0){const a=m(t.slice(0,r.index)),o=m(t.slice(r.index+r[0].length));if(o!==""){let s=`When ${o}, this ${e} should be read`;return a!==""&&(s+=` because ${a}`),`${s}.`}}return`When ${m(t)}, this ${e} should be read.`}i(v,"routeFromDescription");function C(n){return n.replace(/\\/g,"/").replace(/\.md$/i,"").split("/").map(t=>t.trim().toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"")).filter(Boolean).join("/")}i(C,"normalizedName");function E(n){return w(n).map(e=>({on:e.on,deliver:e.deliver,...e.match!==void 0?{match:e.match}:{},...e.matchFrontmatter!==void 0?{match_frontmatter:e.matchFrontmatter}:{},...e.if!==void 0?{if:e.if}:{}}))}i(E,"rulesFromLegacyFrontmatter");async function d(n,e,t){let r;do{const a=await n.objects.list({type:e,limit:100,...r?{cursor:r}:{}});for(const o of a.items){const s=await n.objects.read(o.id);if(s.state!=="object"||s.type!==e)continue;const u=s.data;if(u.identity.external_id===t)return u.handle}r=a.next_cursor??void 0}while(r);return null}i(d,"ownerByIdentity");async function l(n,e){const t=new Set;let r;do{const a=await n.objects.list({type:"document",owner:e,limit:100,...r?{cursor:r}:{}});for(const o of a.items)t.add(o.name);r=a.next_cursor??void 0}while(r);return t}i(l,"documentNames");async function W(n,e){const t=p(e);if(!t.ok)throw g(`${e} is not inside a repo: ${t.reason}`);const r=await d(n,"repo",t.externalId);return{owner:"repo",cwd:t.root,handle:r??t.handleHint,existing:r?await l(n,r):new Set}}i(W,"repoOwner");async function k(n){const e=await d(n,"person","user:local");return{owner:"user",cwd:process.cwd(),handle:e??"user",existing:e?await l(n,e):new Set}}i(k,"userOwner");async function z(n,e,t,r){const a={preview:t.preview,...t.summary!==void 0&&t.summary!==""?{summary:t.summary}:{},...t.privacy!==void 0?{privacy:t.privacy}:{},rules:t.rules};if(!e.existing.has(t.name)){const c=await n.docs.write({name:t.name,body:t.body,kind:t.kind,owner:e.owner,cwd:e.cwd,...a});return e.handle=c.name.slice(0,c.name.length-t.name.length-1),e.existing.add(t.name),{name:c.name,changed:!0}}const o=`${e.handle}/${t.name}`,s=await n.objects.read(o),u=s.state==="object"&&s.type==="document"?s.data.bodies:null;return s.state==="object"&&u!==null&&u.length===1&&u[0]===t.body&&s.object.preview===t.preview?{name:o,changed:!1}:{name:(await n.docs.edit(o,{body:t.body,rationale:r,...a})).name,changed:!0}}i(z,"putDoc");function A(n){if(n instanceof h)return n.message;throw n}i(A,"writeFailure");export{C as normalizedName,z as putDoc,W as repoOwner,v as routeFromDescription,E as rulesFromLegacyFrontmatter,S as scalarString,k as userOwner,A as writeFailure};
1
+ var f=Object.defineProperty;var i=(n,e)=>f(n,"name",{value:e,configurable:!0});import{ApiError as h}from"../../api/index.js";import{identifyCheckout as p}from"../../core/graph/repo-sync/identity.js";import{rulesFromFrontmatter as w}from"../../core/graph/rules.js";import{usage as g}from"../../core/errors.js";function S(n){if(typeof n=="string")return n;if(typeof n=="number"||typeof n=="boolean")return String(n)}i(S,"scalarString");function m(n){let e=n.replace(/\s+/g," ").trim();return e=e.replace(/^[,;:.\s]+/,"").replace(/[,;:.\s]+$/,""),/^[A-Z][a-z]/.test(e)&&(e=e.charAt(0).toLowerCase()+e.slice(1)),e}i(m,"cleanClause");const y=/\b(?:use\s+this\s+skill\s+when|use\s+this\s+when|this\s+skill\s+should\s+be\s+used\s+when|used\s+when|use\s+when)\b\s*/i;function v(n,e){const t=n.replace(/\s+/g," ").trim();if(t==="")return`When this migrated ${e} applies, this ${e} should be read.`;const r=t.match(y);if(r&&r.index!==void 0){const a=m(t.slice(0,r.index)),o=m(t.slice(r.index+r[0].length));if(o!==""){let s=`When ${o}, this ${e} should be read`;return a!==""&&(s+=` because ${a}`),`${s}.`}}return`When ${m(t)}, this ${e} should be read.`}i(v,"routeFromDescription");function C(n){return n.replace(/\\/g,"/").replace(/\.md$/i,"").split("/").map(t=>t.trim().toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"")).filter(Boolean).join("/")}i(C,"normalizedName");function E(n){return w(n).map(e=>({on:e.on,deliver:e.deliver,...e.match!==void 0?{match:e.match}:{},...e.matchFrontmatter!==void 0?{match_frontmatter:e.matchFrontmatter}:{},...e.if!==void 0?{if:e.if}:{}}))}i(E,"rulesFromLegacyFrontmatter");async function d(n,e,t){let r;do{const a=await n.objects.list({type:e,machine:"self",limit:100,...r?{cursor:r}:{}});for(const o of a.items){const s=await n.objects.read(o.id);if(s.state!=="object"||s.type!==e)continue;const c=s.data;if(c.identity.external_id===t)return c.handle}r=a.next_cursor??void 0}while(r);return null}i(d,"ownerByIdentity");async function l(n,e){const t=new Set;let r;do{const a=await n.objects.list({type:"document",owner:e,machine:"self",limit:100,...r?{cursor:r}:{}});for(const o of a.items)t.add(o.name);r=a.next_cursor??void 0}while(r);return t}i(l,"documentNames");async function W(n,e){const t=p(e);if(!t.ok)throw g(`${e} is not inside a repo: ${t.reason}`);const r=await d(n,"repo",t.externalId);return{owner:"repo",cwd:t.root,handle:r??t.handleHint,existing:r?await l(n,r):new Set}}i(W,"repoOwner");async function k(n){const e=await d(n,"person","user:local");return{owner:"user",cwd:process.cwd(),handle:e??"user",existing:e?await l(n,e):new Set}}i(k,"userOwner");async function z(n,e,t,r){const a={preview:t.preview,...t.summary!==void 0&&t.summary!==""?{summary:t.summary}:{},...t.privacy!==void 0?{privacy:t.privacy}:{},rules:t.rules};if(!e.existing.has(t.name)){const u=await n.docs.write({name:t.name,body:t.body,kind:t.kind,owner:e.owner,cwd:e.cwd,...a});return e.handle=u.name.slice(0,u.name.length-t.name.length-1),e.existing.add(t.name),{name:u.name,changed:!0}}const o=`${e.handle}/${t.name}`,s=await n.objects.read(o),c=s.state==="object"&&s.type==="document"?s.data.bodies:null;return s.state==="object"&&c!==null&&c.length===1&&c[0]===t.body&&s.object.preview===t.preview?{name:o,changed:!1}:{name:(await n.docs.edit(o,{body:t.body,rationale:r,...a})).name,changed:!0}}i(z,"putDoc");function A(n){if(n instanceof h)return n.message;throw n}i(A,"writeFailure");export{C as normalizedName,z as putDoc,W as repoOwner,v as routeFromDescription,E as rulesFromLegacyFrontmatter,S as scalarString,k as userOwner,A as writeFailure};
@@ -1 +1 @@
1
- var u=Object.defineProperty;var c=(o,s)=>u(o,"name",{value:s,configurable:!0});import{defineLeaf as l}from"../../core/command.js";import{updateState as d}from"../../core/config.js";import{projectScopeRoot as p}from"../../core/scope.js";import{selfCheck as f,selfUpdate as m,contentCheck as h}from"../../core/self-update.js";import{marketUpdate as g}from"../pkg/market-manage.js";import{pluginUpdate as y}from"../pkg/plugin-manage.js";import{nowIso as k}from"../../core/fs-utils.js";import{processLocatedGeneration as b,readPackageVersion as _,runtimeManifest as v,selectedGeneration as w}from"./shared.js";const x=l({name:"update",description:"update binary and content",whenToUse:"upgrading crtr or its installed plugins and marketplaces \u2014 pull the latest binary, refresh installed content, or both, or just check what is out of date without applying anything.",help:{name:"sys update",summary:"update the crtr binary and/or installed plugins and marketplaces",params:[{kind:"flag",name:"target",type:"enum",choices:["self","content","all"],required:!1,constraint:"What to update. Default: all."},{kind:"flag",name:"check",type:"bool",required:!1,constraint:"Check for updates without applying them (bounded, blocking)."}],output:[{name:"target",type:"string",required:!1,constraint:"Present when applying updates: self | content | all."},{name:"status",type:"string",required:!1,constraint:"done | failed when applying updates."},{name:"error",type:"string",required:!1,constraint:"Failure message when status is failed."},{name:"updates",type:"object[]",required:!1,constraint:"Present when --check. Each: {name, current, latest, up_to_date, unreachable, kind}."},{name:"up_to_date",type:"boolean",required:!1,constraint:"Present when --check. True when all items are up to date."}],outputKind:"object",effects:["--check \u2014 read-only, bounded network calls.","Default (no --check) \u2014 launches a background job; returns job handle immediately.","Content update replaces each marketplace and source-installed plugin checkout with a freshly staged revision, discarding local modifications in those directories."]},run:c(async o=>{const s=o.target,i=o.check,n=s!==void 0?s:"all";if(i){const t=[];if(n==="self"||n==="all"){const e=f();e!==null?t.push({name:e.package,kind:"self",current:e.current,latest:e.latest,up_to_date:!e.migrating&&e.current===e.latest,unreachable:!1}):t.push({name:"@north-light/cli",kind:"self",current:null,latest:null,up_to_date:!0,unreachable:!0})}if(n==="content"||n==="all"){const e=h();for(const r of e)t.push({name:r.name,kind:r.kind,current:r.current,latest:r.latest,up_to_date:r.up_to_date,unreachable:r.unreachable})}const a=t.every(e=>e.up_to_date||e.unreachable);return{updates:t,up_to_date:a}}try{if(n==="self"||n==="all"){m();const t=["user"];p()&&t.unshift("project");for(const a of t)d(a,e=>{e.last_self_check=k()})}return(n==="content"||n==="all")&&(await g.run({}),await y.run({})),{target:n,status:"done"}}catch(t){const a=t instanceof Error?t.message:String(t);return{target:n,status:"failed",error:a}}},"run")}),G=l({name:"version",description:"print installed version",whenToUse:"you need the installed crtr version \u2014 to report it in a bug or confirm an upgrade landed.",help:{name:"sys version",summary:"report selected and process runtime generations",params:[],output:[{name:"version",type:"string",required:!0,constraint:"Version from this process generation manifest; package.json version in a development checkout."},{name:"selected_generation",type:"object",required:!0,constraint:"Selected runtime generation: {id, version, status}; id/version are null when no selection exists."},{name:"process_generation",type:"object",required:!0,constraint:"Generation containing this loaded module: {id, version, status}; id/version are null in a development checkout."},{name:"drift",type:"boolean",required:!0,constraint:"True when selected and process generation ids differ."}],outputKind:"object",effects:["None. Read-only."]},run:c(async o=>{const s=c(t=>{if(t.generation===null)return{id:null,version:null,status:t.error??"no runtime generation"};const{id:a,manifest:e}=t.generation,r=v.validateManifestShape(e,{id:a,entryName:"cli"});return{id:a,version:typeof e.packageVersion=="string"?e.packageVersion:null,status:r.ok?"valid":r.error}},"describe"),i=s(w()),n=s(b());return{version:n.version??_(),selected_generation:i,process_generation:n,drift:i.id!==null&&n.id!==null&&i.id!==n.id}},"run")});export{x as sysUpdateLeaf,G as sysVersionLeaf};
1
+ var d=Object.defineProperty;var l=(i,a)=>d(i,"name",{value:a,configurable:!0});import{defineLeaf as u}from"../../core/command.js";import{updateState as p}from"../../core/config.js";import{projectScopeRoot as f}from"../../core/scope.js";import{selfCheck as h,selfUpdate as m,contentCheck as g}from"../../core/self-update.js";import{marketUpdate as y}from"../pkg/market-manage.js";import{pluginUpdate as k}from"../pkg/plugin-manage.js";import{nowIso as b}from"../../core/fs-utils.js";import{processLocatedGeneration as w,readPackageVersion as _,runtimeManifest as v,selectedGeneration as j}from"./shared.js";const E=u({name:"update",description:"update binary and content",whenToUse:"upgrading crtr or its installed plugins and marketplaces \u2014 pull the latest binary, refresh installed content, or both, or just check what is out of date without applying anything.",help:{name:"sys update",summary:"update the crtr binary and/or installed plugins and marketplaces",params:[{kind:"flag",name:"target",type:"enum",choices:["self","content","all"],required:!1,constraint:"What to update. Default: all."},{kind:"flag",name:"check",type:"bool",required:!1,constraint:"Check for updates without applying them (bounded, blocking)."}],output:[{name:"target",type:"string",required:!1,constraint:"Present when applying updates: self | content | all."},{name:"status",type:"string",required:!1,constraint:"done | failed when applying updates."},{name:"error",type:"string",required:!1,constraint:"Failure message when status is failed."},{name:"warning",type:"string",required:!1,constraint:"Present when the update was applied but recording the self-check time failed; status stays done."},{name:"updates",type:"object[]",required:!1,constraint:"Present when --check. Each: {name, current, latest, up_to_date, unreachable, kind}."},{name:"up_to_date",type:"boolean",required:!1,constraint:"Present when --check. True when all items are up to date."}],outputKind:"object",effects:["--check \u2014 read-only, bounded network calls.","Default (no --check) \u2014 launches a background job; returns job handle immediately.","Content update replaces each marketplace and source-installed plugin checkout with a freshly staged revision, discarding local modifications in those directories."]},run:l(async i=>{const a=i.target,c=i.check,t=a!==void 0?a:"all";if(c){const n=[];if(t==="self"||t==="all"){const e=h();e!==null?n.push({name:e.package,kind:"self",current:e.current,latest:e.latest,up_to_date:!e.migrating&&e.current===e.latest,unreachable:!1}):n.push({name:"@north-light/cli",kind:"self",current:null,latest:null,up_to_date:!0,unreachable:!0})}if(t==="content"||t==="all"){const e=g();for(const o of e)n.push({name:o.name,kind:o.kind,current:o.current,latest:o.latest,up_to_date:o.up_to_date,unreachable:o.unreachable})}const r=n.every(e=>e.up_to_date||e.unreachable);return{updates:n,up_to_date:r}}const s=[];try{if(t==="self"||t==="all"){m();const n=["user"];f()&&n.unshift("project");for(const r of n)try{p(r,e=>{e.last_self_check=b()})}catch(e){s.push(`recording the ${r}-scope self-check failed: ${e instanceof Error?e.message:String(e)}`)}}return(t==="content"||t==="all")&&(await y.run({}),await k.run({})),s.length===0?{target:t,status:"done"}:{target:t,status:"done",warning:`update installed, but ${s.join("; ")}`}}catch(n){const r=n instanceof Error?n.message:String(n);return{target:t,status:"failed",error:r}}},"run")}),G=u({name:"version",description:"print installed version",whenToUse:"you need the installed crtr version \u2014 to report it in a bug or confirm an upgrade landed.",help:{name:"sys version",summary:"report selected and process runtime generations",params:[],output:[{name:"version",type:"string",required:!0,constraint:"Version from this process generation manifest; package.json version in a development checkout."},{name:"selected_generation",type:"object",required:!0,constraint:"Selected runtime generation: {id, version, status}; id/version are null when no selection exists."},{name:"process_generation",type:"object",required:!0,constraint:"Generation containing this loaded module: {id, version, status}; id/version are null in a development checkout."},{name:"drift",type:"boolean",required:!0,constraint:"True when selected and process generation ids differ."}],outputKind:"object",effects:["None. Read-only."]},run:l(async i=>{const a=l(s=>{if(s.generation===null)return{id:null,version:null,status:s.error??"no runtime generation"};const{id:n,manifest:r}=s.generation,e=v.validateManifestShape(r,{id:n,entryName:"cli"});return{id:n,version:typeof r.packageVersion=="string"?r.packageVersion:null,status:e.ok?"valid":e.error}},"describe"),c=a(j()),t=a(w());return{version:t.version??_(),selected_generation:c,process_generation:t,drift:c.id!==null&&t.id!==null&&c.id!==t.id}},"run")});export{E as sysUpdateLeaf,G as sysVersionLeaf};
@@ -1 +1 @@
1
- var c=Object.defineProperty;var r=(o,e)=>c(o,"name",{value:e,configurable:!0});import{prepared as u}from"../sqlite-statements.js";import{openDb as m}from"../canvas/db.js";import{heads as p}from"../graph/documents.js";import{getById as l}from"../graph/objects.js";import{bodyOf as h}from"../graph/bodies.js";import{validateOperationId as y}from"../events/operation-id.js";import{formatInboxCard as b}from"../../shared/generated-context.js";import{inlineBody as I,renderEntryBody as g,reportRefNodeId as E}from"../../shared/inbox-entry-body.js";const _=/^(?!0{32}$)[0-9a-f]{32}$/;function x(o){if(typeof o!="string"||!_.test(o))throw new TypeError("entry_id must be 32 lowercase non-zero hexadecimal characters");return o}r(x,"validateInboxEntryId");function J(o){const e=o.message;return{entry_id:x(e.messageId),operation_id:y(e.operationId),ts:e.createdAt,from:e.senderNodeId,...e.senderName===null?{}:{from_name:e.senderName},tier:o.urgency,kind:e.kind,...e.bodyRef===null?{}:{ref:e.bodyRef},label:e.label,...e.bodyJson===null?{}:{data:e.bodyJson},...e.disposition===null?{}:{disposition:e.disposition}}}r(J,"nodeMailEntry");function k(o){const e=m(),n=l(e,o);if(n===null||n.type!=="document"||n.deleted_at!==null)return null;const s=p(e,n)[0];if(s===void 0)return null;const d=u(e,"SELECT source_path FROM documents WHERE object = ?").get(n.rowid);return h(s.body_hash,d?.source_path??null)}r(k,"reportDocumentBody");function B(o){const e=o.data?.push?.object?.id,n=E(o.ref)!==void 0&&e!==void 0?k(e):null;return g(o,n)}r(B,"entryBody");function N(o){return{kind:o.kind,body:B(o),...o.ref===void 0?{}:{ref:o.ref},...o.disposition===void 0?{}:{disposition:o.disposition}}}r(N,"cardEntry");function $(o){if(o.length===0)return[];const e=new Map;for(const t of o){const i=t.from??"system";e.has(i)||e.set(i,[]),e.get(i).push(t)}const n=[],s=[];let d=-1;for(const[t,i]of e){if(t==="human"){for(const f of i){const a=I(f);n.push({kind:"human",text:a===""?f.label:a,entries:[f]})}continue}d<0&&(d=n.length,n.push({kind:"card",text:"",entries:[]})),s.push({id:t,entries:i.map(N)})}return d>=0&&(n[d]={kind:"card",text:b(s),entries:o.filter(t=>(t.from??"system")!=="human")}),n}r($,"coalesce");export{$ as coalesce,J as nodeMailEntry,k as reportDocumentBody};
1
+ var c=Object.defineProperty;var d=(o,e)=>c(o,"name",{value:e,configurable:!0});import{prepared as u}from"../sqlite-statements.js";import{openDb as m}from"../canvas/db.js";import{heads as p}from"../graph/documents.js";import{getById as l}from"../graph/objects.js";import{bodyOf as h}from"../graph/bodies.js";import{validateOperationId as y}from"../events/operation-id.js";import{formatInboxCard as b,machineOfEntries as I}from"../../shared/generated-context.js";import{inlineBody as g,renderEntryBody as E,reportRefNodeId as _}from"../../shared/inbox-entry-body.js";const x=/^(?!0{32}$)[0-9a-f]{32}$/;function k(o){if(typeof o!="string"||!x.test(o))throw new TypeError("entry_id must be 32 lowercase non-zero hexadecimal characters");return o}d(k,"validateInboxEntryId");function $(o){const e=o.message;return{entry_id:k(e.messageId),operation_id:y(e.operationId),ts:e.createdAt,from:e.senderNodeId,...e.senderName===null?{}:{from_name:e.senderName},tier:o.urgency,kind:e.kind,...e.bodyRef===null?{}:{ref:e.bodyRef},label:e.label,...e.bodyJson===null?{}:{data:e.bodyJson},...e.disposition===null?{}:{disposition:e.disposition}}}d($,"nodeMailEntry");function B(o){const e=m(),n=l(e,o);if(n===null||n.type!=="document"||n.deleted_at!==null)return null;const s=p(e,n)[0];if(s===void 0)return null;const i=u(e,"SELECT source_path FROM documents WHERE object = ?").get(n.rowid);return h(s.body_hash,i?.source_path??null)}d(B,"reportDocumentBody");function N(o){const e=o.data?.push?.object?.id,n=_(o.ref)!==void 0&&e!==void 0?B(e):null;return E(o,n)}d(N,"entryBody");function R(o){return{kind:o.kind,body:N(o),...o.ref===void 0?{}:{ref:o.ref},...o.disposition===void 0?{}:{disposition:o.disposition}}}d(R,"cardEntry");function z(o){if(o.length===0)return[];const e=new Map;for(const t of o){const r=t.from??"system";e.has(r)||e.set(r,[]),e.get(r).push(t)}const n=[],s=[];let i=-1;for(const[t,r]of e){if(t==="human"){for(const f of r){const a=g(f);n.push({kind:"human",text:a===""?f.label:a,entries:[f]})}continue}i<0&&(i=n.length,n.push({kind:"card",text:"",entries:[]})),s.push({id:t,...I(r),entries:r.map(R)})}return i>=0&&(n[i]={kind:"card",text:b(s),entries:o.filter(t=>(t.from??"system")!=="human")}),n}d(z,"coalesce");export{z as coalesce,$ as nodeMailEntry,B as reportDocumentBody};
@@ -1,11 +1,11 @@
1
- var k=Object.defineProperty;var a=(n,e)=>k(n,"name",{value:e,configurable:!0});import{prepared as m}from"../sqlite-statements.js";import{evalCondition as x}from"../predicate.js";import{finalizeNodeMail as W,insertMessage as $}from"../feed/messages.js";import{ownerChain as B,readCheck as J}from"./access.js";import{publishLookups as K,readerForObject as U}from"./reader.js";import{configFromJson as T}from"./watches.js";import{CrtrError as L}from"../errors.js";import{ExitCode as A}from"../../types.js";import{eventSpec as G,kindOf as C}from"./kinds/registry.js";import{RECEIVERS as D}from"./receivers.js";import{DEFAULT_LISTEN as F}from"./types.js";function q(n,e){if(n.level===void 0||n.level===e.level||e.levels?.includes(n.level))return n.level??e.level;const o=[e.level,...e.levels??[]].filter((s,t,d)=>d.indexOf(s)===t).sort((s,t)=>s-t);throw new L("invalid_level",`${n.object.type} ${n.event} publishes at level ${o.join(", ")}, not ${n.level}`,A.USAGE,{kind:n.object.type,event:n.event,level:n.level,allowed:o})}a(q,"resolveLevel");function X(n,e){const o=n.split("."),s=e.split(".");for(let t=0;t<o.length;t++){if(o[t]===">")return t===o.length-1&&s.length>t;if(t>=s.length||o[t]!=="*"&&o[t]!==s[t])return!1}return o.length===s.length}a(X,"matchSubject");const H={"next-turn":1,"after-turn":2,steer:3,interrupt:4};function Y(n,e){let o=null;for(const s of n){let t=null;for(const d of s)d.threshold<=e&&(!t||d.threshold>t.threshold)&&(t=d);t&&(!o||H[t.delivery]>H[o])&&(o=t.delivery)}return o}a(Y,"resolveDelivery");function fe(n,e,o){if(e.config)return e.config;const s=m(n,"SELECT listen_json FROM objects WHERE rowid = ?").get(o.rowid);return s?.listen_json?T(s.listen_json):F}a(fe,"listenConfigFor");function z(n,e){return e.inboxKind??(n.level>=7?"urgent":"update")}a(z,"inboxKind");function P(n,e){return e.type==="node"?e.id:m(n,`SELECT 1 FROM objects WHERE type IN ('person','app','repo','profile')
2
- AND name = ? AND rowid != ? LIMIT 1`).get(e.name,e.rowid)?`${e.type}:${e.name}`:e.name}a(P,"ownerHandleOf");function V(n,e,o){const s=e.object.owner===null?null:m(n,"SELECT * FROM objects WHERE rowid = ?").get(e.object.owner),t=s?.id??null,d=s?P(n,s):null;return{event:e.event,level:e.level,object:{id:e.object.id,type:e.object.type,kind:e.object.kind,owner:t,owner_handle:d,name:e.object.name},actor:e.actor?.id??null,attrs:o,...e.pointer===void 0?{}:{pointer:e.pointer}}}a(V,"pushFields");function Q(n,e,o,s){const t=V(n,e,s),d=e.messageBody!==void 0&&o.carriesBody===!0,g=d?typeof e.messageBody=="object"&&e.messageBody!==null&&!Array.isArray(e.messageBody)?{...e.messageBody,push:t}:{body:e.messageBody,push:t}:{push:t},l=d?e.messageBody:null,c=l?.bodyRef===void 0?null:l;return{kind:z(e,o),label:c?.label??(typeof l?.label=="string"?l.label:e.object.type.includes(".")&&typeof e.messageBody?.summary=="string"?e.messageBody.summary.split(`
3
- `)[0]:`${e.event} ${e.object.type} ${e.object.name}`),...c?.bodyRef===void 0?{}:{bodyRef:c.bodyRef},...c?.disposition===void 0?{}:{disposition:c.disposition},senderNodeId:e.actor?.type==="node"?e.actor.id:null,senderName:e.actor?.name??null,bodyJson:g}}a(Q,"messageInput");function Z(n,e,o,s,t){if(!o.supersedes)return[{attrs:e.attrs,destinations:[...s]}];const d=m(n,`SELECT d.delivery_id, d.message_id, d.state, m.body_json FROM deliveries d
1
+ var $=Object.defineProperty;var a=(n,e)=>$(n,"name",{value:e,configurable:!0});import{prepared as m}from"../sqlite-statements.js";import{evalCondition as x}from"../predicate.js";import{finalizeNodeMail as W,insertMessage as B}from"../feed/messages.js";import{ownerChain as J,readCheck as K}from"./access.js";import{publishLookups as U,readerForObject as G}from"./reader.js";import{configFromJson as C}from"./watches.js";import{CrtrError as L}from"../errors.js";import{ExitCode as A}from"../../types.js";import{eventSpec as q,kindOf as D}from"./kinds/registry.js";import{machineNameOf as M}from"../peer/names.js";import{RECEIVERS as F}from"./receivers.js";import{DEFAULT_LISTEN as H}from"./types.js";function X(n,e){if(n.level===void 0||n.level===e.level||e.levels?.includes(n.level))return n.level??e.level;const o=[e.level,...e.levels??[]].filter((s,t,c)=>c.indexOf(s)===t).sort((s,t)=>s-t);throw new L("invalid_level",`${n.object.type} ${n.event} publishes at level ${o.join(", ")}, not ${n.level}`,A.USAGE,{kind:n.object.type,event:n.event,level:n.level,allowed:o})}a(X,"resolveLevel");function Y(n,e){const o=n.split("."),s=e.split(".");for(let t=0;t<o.length;t++){if(o[t]===">")return t===o.length-1&&s.length>t;if(t>=s.length||o[t]!=="*"&&o[t]!==s[t])return!1}return o.length===s.length}a(Y,"matchSubject");const k={"next-turn":1,"after-turn":2,steer:3,interrupt:4};function z(n,e){let o=null;for(const s of n){let t=null;for(const c of s)c.threshold<=e&&(!t||c.threshold>t.threshold)&&(t=c);t&&(!o||k[t.delivery]>k[o])&&(o=t.delivery)}return o}a(z,"resolveDelivery");function he(n,e,o){if(e.config)return e.config;const s=m(n,"SELECT listen_json FROM objects WHERE rowid = ?").get(o.rowid);return s?.listen_json?C(s.listen_json):H}a(he,"listenConfigFor");function P(n,e){return e.inboxKind??(n.level>=7?"urgent":"update")}a(P,"inboxKind");function V(n,e){return e.type==="node"?e.id:m(n,`SELECT 1 FROM objects WHERE type IN ('person','app','repo','profile')
2
+ AND name = ? AND rowid != ? LIMIT 1`).get(e.name,e.rowid)?`${e.type}:${e.name}`:e.name}a(V,"ownerHandleOf");function Q(n,e,o){const s=e.object.owner===null?null:m(n,"SELECT * FROM objects WHERE rowid = ?").get(e.object.owner),t=s?.id??null,c=s?V(n,s):null;return{event:e.event,level:e.level,object:{id:e.object.id,type:e.object.type,kind:e.object.kind,owner:t,owner_handle:c,name:e.object.name,...e.object.home_machine?{machine:M(e.object.home_machine),machine_id:e.object.home_machine}:{}},actor:e.actor?.id??null,...e.actor?.home_machine?{actor_machine:M(e.actor.home_machine),actor_machine_id:e.actor.home_machine}:{},attrs:o,...e.pointer===void 0?{}:{pointer:e.pointer}}}a(Q,"pushFields");function Z(n,e,o,s){const t=Q(n,e,s),c=e.messageBody!==void 0&&o.carriesBody===!0,y=c?typeof e.messageBody=="object"&&e.messageBody!==null&&!Array.isArray(e.messageBody)?{...e.messageBody,push:t}:{body:e.messageBody,push:t}:{push:t},d=c?e.messageBody:null,l=d?.bodyRef===void 0?null:d;return{kind:P(e,o),label:l?.label??(typeof d?.label=="string"?d.label:e.object.type.includes(".")&&typeof e.messageBody?.summary=="string"?e.messageBody.summary.split(`
3
+ `)[0]:`${e.event} ${e.object.type} ${e.object.name}`),...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===null||e.actor===void 0?null:e.actor.home_machine?`${e.actor.name} on ${M(e.actor.home_machine)}`:e.actor.name,bodyJson:y}}a(Z,"messageInput");function ee(n,e,o,s,t){if(!o.supersedes)return[{attrs:e.attrs,destinations:[...s]}];const c=m(n,`SELECT d.delivery_id, d.message_id, d.state, m.body_json FROM deliveries d
4
4
  JOIN messages m ON m.message_id = d.message_id
5
5
  WHERE d.destination_id = ? AND d.state IN ('pending', 'handed_off')
6
6
  AND d.canceled_at IS NULL AND m.event = ? AND m.object_id = ?
7
- ORDER BY m.seq`),g=m(n,"DELETE FROM deliveries WHERE delivery_id = ?"),l=m(n,"SELECT * FROM objects WHERE id = ?"),c=new Map,j=new Set;for(const u of s){const w=t(u),R=l.get(w),S=R===void 0?void 0:C(n,R.type),N=d.all(w,e.event,e.object.id),v=new Set;for(const r of N)r.state==="handed_off"?S?.supersedeClaimed?.(n,r.delivery_id)&&v.add(r.delivery_id):(g.run(r.delivery_id),j.add(r.message_id));const y=N.filter(r=>r.state==="pending"||v.has(r.delivery_id)).at(-1),b=y?.message_id??null,i=c.get(b)??{prior:y?.body_json?JSON.parse(y.body_json).push?.attrs??null:null,destinations:[]};i.destinations.push(u),c.set(b,i)}const _=m(n,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)");for(const u of j)_.run(u,u);return[...c.values()].map(u=>({attrs:o.merge?o.merge(u.prior,e):e.attrs,destinations:u.destinations}))}a(Z,"supersede");const M=8;class ee extends L{static{a(this,"HopLimitError")}constructor(e){super("hop_limit",`event depth ${e} exceeds ${M}`,A.GENERAL,{depth:e,limit:M})}}function me(n,e,o=()=>[]){const s=G(n,e.object.type,e.event),t={...e,level:q(e,s)},d=e.cause??null,g=d===null?null:m(n,`SELECT depth FROM messages WHERE message_id = ?
8
- UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(d,d);if(d!==null&&g===void 0)throw new L("unknown_cause",`no message ${d}`,A.USAGE,{cause:d});const l=g===null?0:g.depth+1;if(l>M)throw new ee(l);const c=B(n,t.object),j=`w.watcher, o.id AS watcher_id, o.type AS watcher_type, o.reception, s.pattern, s.filter_json,
9
- COALESCE(s.config_json, o.listen_json) AS config_json`,_=n.prepare(s.durable?`SELECT ${j} FROM watches w JOIN watch_subscriptions s ON s.watch = w.rowid
10
- JOIN objects o ON o.rowid = w.watcher WHERE w.object = ? AND w.watcher != ? AND o.reception >= 1`:`SELECT ${j} FROM objects o INDEXED BY objects_receiving CROSS JOIN watches w CROSS JOIN watch_subscriptions s
11
- WHERE o.reception = 2 AND w.watcher = o.rowid AND s.watch = w.rowid AND w.object = ? AND w.watcher != ?`).all(t.object.rowid,t.actor?.rowid??-1);if(_.length===0)return{delivered:0,message_id:null,depth:l};const u={event:t.event,level:t.level,id:t.object.id,type:t.object.type,kind:t.object.kind,actor:t.actor?.id??null,...t.attrs},w=new Map;for(const i of _){if(!X(i.pattern,t.event)||i.filter_json!==null&&!x(JSON.parse(i.filter_json),u))continue;const r=w.get(i.watcher)??{id:i.watcher_id,type:i.watcher_type,reception:i.reception,configs:[]};r.configs.push(i.config_json?T(i.config_json):F),w.set(i.watcher,r)}const R=(c.length?c[c.length-1]:t.object).type==="repo",S=a(()=>[],"noRepos"),N=J(n,t.object),v=K(n),y=[];for(const i of w.values()){const r=m(n,"SELECT * FROM objects WHERE id = ?").get(i.id),p=C(n,r.type),O=p.receiverFor?p.receiverFor(n,r):p.receiver;if(O===void 0)continue;const E=Y(i.configs,t.level);if(E===null)continue;const h=U(n,r,R?o:S,v);h.scopes===null&&(h.class==="node"||h.owner.type!=="person")||N(h)&&y.push({id:i.id,reception:i.reception,delivery:E,receiver:O})}if(y.length===0)return{delivered:0,message_id:null,depth:l};let b=null;for(const i of Z(n,t,s,y,r=>r.id)){const r=new Map;for(const f of i.destinations)r.set(f.receiver,[...r.get(f.receiver)??[],f]);const p=Q(n,t,s,i.attrs),O=[...r].map(([f,I])=>D[f].bodyOwner(I)).find(f=>f!==null)??null,E=W({...p,urgency:"normal"},O),h=$(n,{operationId:E.operationId,createdAt:E.createdAt,senderNodeId:p.senderNodeId,senderName:p.senderName,kind:p.kind,label:p.label,bodyRef:E.bodyRef,bodyJson:E.bodyJson,disposition:p.disposition??null,event:{event:t.event,objectId:t.object.id,level:t.level,actorId:t.actor?.id??null},causeMessageId:d,depth:l});b??=h;for(const[f,I]of r)D[f].enqueue(n,{messageId:h,event:t.event,level:t.level,senderNodeId:p.senderNodeId,elevatesTier:s.elevatesTier===!0,...t.idempotencyKey===void 0?{}:{idempotencyKey:t.idempotencyKey}},I)}return{delivered:y.length,message_id:b,depth:l}}a(me,"publish");export{M as HOP_LIMIT,ee as HopLimitError,fe as listenConfigFor,X as matchSubject,me as publish,Y as resolveDelivery,q as resolveLevel,Z as supersede};
7
+ ORDER BY m.seq`),y=m(n,"DELETE FROM deliveries WHERE delivery_id = ?"),d=m(n,"SELECT * FROM objects WHERE id = ?"),l=new Map,w=new Set;for(const u of s){const _=t(u),R=d.get(_),I=R===void 0?void 0:D(n,R.type),N=c.all(_,e.event,e.object.id),O=new Set;for(const r of N)r.state==="handed_off"?I?.supersedeClaimed?.(n,r.delivery_id)&&O.add(r.delivery_id):(y.run(r.delivery_id),w.add(r.message_id));const h=N.filter(r=>r.state==="pending"||O.has(r.delivery_id)).at(-1),j=h?.message_id??null,i=l.get(j)??{prior:h?.body_json?JSON.parse(h.body_json).push?.attrs??null:null,destinations:[]};i.destinations.push(u),l.set(j,i)}const b=m(n,"DELETE FROM messages WHERE message_id = ? AND NOT EXISTS (SELECT 1 FROM deliveries WHERE message_id = ?)");for(const u of w)b.run(u,u);return[...l.values()].map(u=>({attrs:o.merge?o.merge(u.prior,e):e.attrs,destinations:u.destinations}))}a(ee,"supersede");const T=8;class te extends L{static{a(this,"HopLimitError")}constructor(e){super("hop_limit",`event depth ${e} exceeds ${T}`,A.GENERAL,{depth:e,limit:T})}}function ye(n,e,o=()=>[]){const s=q(n,e.object.type,e.event),t={...e,level:X(e,s)},c=e.cause??null,y=c===null?null:m(n,`SELECT depth FROM messages WHERE message_id = ?
8
+ UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(c,c);if(c!==null&&y===void 0)throw new L("unknown_cause",`no message ${c}`,A.USAGE,{cause:c});const d=y===null?0:y.depth+1;if(d>T)throw new te(d);const l=J(n,t.object),w=`w.watcher, o.id AS watcher_id, o.type AS watcher_type, o.reception, s.pattern, s.filter_json,
9
+ COALESCE(s.config_json, o.listen_json) AS config_json`,b=n.prepare(s.durable?`SELECT ${w} FROM watches w JOIN watch_subscriptions s ON s.watch = w.rowid
10
+ JOIN objects o ON o.rowid = w.watcher WHERE w.object = ? AND w.watcher != ? AND o.reception >= 1`:`SELECT ${w} FROM objects o INDEXED BY objects_receiving CROSS JOIN watches w CROSS JOIN watch_subscriptions s
11
+ WHERE o.reception = 2 AND w.watcher = o.rowid AND s.watch = w.rowid AND w.object = ? AND w.watcher != ?`).all(t.object.rowid,t.actor?.rowid??-1);if(b.length===0)return{delivered:0,message_id:null,depth:d};const u={event:t.event,level:t.level,id:t.object.id,type:t.object.type,kind:t.object.kind,actor:t.actor?.id??null,...t.attrs},_=new Map;for(const i of b){if(!Y(i.pattern,t.event)||i.filter_json!==null&&!x(JSON.parse(i.filter_json),u))continue;const r=_.get(i.watcher)??{id:i.watcher_id,type:i.watcher_type,reception:i.reception,configs:[]};r.configs.push(i.config_json?C(i.config_json):H),_.set(i.watcher,r)}const R=(l.length?l[l.length-1]:t.object).type==="repo",I=a(()=>[],"noRepos"),N=K(n,t.object),O=U(n),h=[];for(const i of _.values()){const r=m(n,"SELECT * FROM objects WHERE id = ?").get(i.id),p=D(n,r.type),S=p.receiverFor?p.receiverFor(n,r):p.receiver;if(S===void 0)continue;const g=z(i.configs,t.level);if(g===null)continue;const E=G(n,r,R?o:I,O);E.scopes===null&&(E.class==="node"||E.owner.type!=="person")||N(E)&&h.push({id:i.id,reception:i.reception,delivery:g,receiver:S})}if(h.length===0)return{delivered:0,message_id:null,depth:d};let j=null;for(const i of ee(n,t,s,h,r=>r.id)){const r=new Map;for(const f of i.destinations)r.set(f.receiver,[...r.get(f.receiver)??[],f]);const p=Z(n,t,s,i.attrs),S=[...r].map(([f,v])=>F[f].bodyOwner(v)).find(f=>f!==null)??null,g=W({...p,urgency:"normal"},S),E=B(n,{operationId:g.operationId,createdAt:g.createdAt,senderNodeId:p.senderNodeId,senderName:p.senderName,kind:p.kind,label:p.label,bodyRef:g.bodyRef,bodyJson:g.bodyJson,disposition:p.disposition??null,event:{event:t.event,objectId:t.object.id,level:t.level,actorId:t.actor?.id??null},causeMessageId:c,depth:d});j??=E;for(const[f,v]of r)F[f].enqueue(n,{messageId:E,event:t.event,level:t.level,senderNodeId:p.senderNodeId,elevatesTier:s.elevatesTier===!0,...t.idempotencyKey===void 0?{}:{idempotencyKey:t.idempotencyKey}},v)}return{delivered:h.length,message_id:j,depth:d}}a(ye,"publish");export{T as HOP_LIMIT,te as HopLimitError,he as listenConfigFor,Y as matchSubject,ye as publish,z as resolveDelivery,X as resolveLevel,ee as supersede};
@@ -23,6 +23,8 @@ export interface PeerRequest {
23
23
  export interface PeerOptions {
24
24
  /** On whose behalf the call is made. Without one the call is the machine's own (class `machine` rows). */
25
25
  envelope?: Envelope;
26
+ /** Ends the call early (the caller's own budget ran out); the result is then `{unreachable: timeout}`. */
27
+ signal?: AbortSignal;
26
28
  }
27
29
  export interface PeerResponse {
28
30
  status: number;
@@ -1 +1 @@
1
- var S=Object.defineProperty;var i=(e,r)=>S(e,"name",{value:r,configurable:!0});import{crtrVersion as M}from"../../shared/crtr-version.js";import{directory as $,directoryIssuer as v,normalizeMachineId as T}from"./directory.js";import{ENVELOPE_HEADER as N,encodeEnvelope as O}from"./envelope.js";import{peerTokens as x}from"./token.js";import{PEER_VERSION_FLOOR as h,PEER_VERSION_HEADER as P,peerVersionRefusal as k}from"./version.js";const U=12e4,A=6e4,w={first:1e3,max:8e3},p=8*1024*1024,H=i(e=>"unreachable"in e,"isUnreachable"),s=i((e,r)=>({unreachable:{reason:e,message:r}}),"unreachable");function B(e){const r=e?.error;if(typeof r=="string")return r;const o=r?.code;return typeof o=="string"?o:null}i(B,"errorCode");async function I(e){if(Number(e.headers.get("content-length")??"0")>p)throw new RangeError("the peer answer is too large");const o=await e.text();if(o.length>p)throw new RangeError("the peer answer is too large");if(o==="")return null;try{return JSON.parse(o)}catch{return o}}i(I,"readBody");function L(e,r){const o=new URL(`${e.url}${r.path.startsWith("/")?"":"/"}${r.path}`),c=r.query instanceof URLSearchParams?r.query:Object.entries(r.query??{});for(const[l,u]of c instanceof URLSearchParams?c.entries():c)u!==void 0&&o.searchParams.append(l,String(u));return o.toString()}i(L,"targetUrl");function C(e){return i(async function(o,c,l={}){const u=e.issuer();if(u===null)return s("no_directory","this machine is not joined to a Northlight account, so it has no peers");let t;try{t=await e.directory.reach(o)}catch(a){return s("directory_unreachable",a instanceof Error?a.message:String(a))}if(t===null)return s("unknown_machine",`No machine ${T(o)} on your account.`);const d=t.crtr_version===null?null:k(t.crtr_version,h,e.version());if(d!==null)return{status:426,headers:{},body:{error:{code:d.code,message:`${t.name} runs crtr ${t.crtr_version}; peers below ${h} cannot take part. Update it with \`crtr sys update\`.`,details:{peer_version:t.crtr_version,floor:h,receiver_version:e.version()}}}};const R=L(t,c),_=e.now();let f=w.first,E=!1,g=!1;for(;;){let a;try{const n=await e.tokens.get(u,t.url);a=await e.fetch(R,{method:c.method,redirect:"manual",headers:{authorization:`Bearer ${n}`,[P]:e.version(),...l.envelope?{[N]:O(l.envelope)}:{},...c.body===void 0?{}:{"content-type":"application/json"}},...c.body===void 0?{}:{body:JSON.stringify(c.body)},signal:AbortSignal.timeout(g?Math.max(1e3,Math.min(e.timeoutMs,e.startingBudgetMs-(e.now()-_))):e.timeoutMs)})}catch(n){return n instanceof Error&&(n.name==="TimeoutError"||n.name==="AbortError")?s("timeout",`${t.name} did not answer within ${Math.round(e.timeoutMs/1e3)} s.`):n instanceof Error&&/directory/.test(n.message)?s("directory_unreachable",n.message):s("network",`${t.name} could not be reached: ${n instanceof Error?n.message:String(n)}`)}let m;try{m=await I(a)}catch(n){return s("network",`${t.name}'s answer could not be read: ${n instanceof Error?n.message:String(n)}`)}const y=B(m),b=Object.fromEntries(a.headers.entries());if(a.status===401&&!E){E=!0,e.tokens.drop(u,t.url);continue}if(a.status===503&&y==="machine_offline")return s("machine_offline",`${t.name} is offline.`);if(a.status===504)return s("timeout",`${t.name} did not answer in time.`);if(a.status===503&&y==="runtime_starting"){if(e.now()-_+f>e.startingBudgetMs)return s("runtime_starting",`${t.name} is still starting.`);await e.sleep(f),f=Math.min(f*2,w.max),g=!0;continue}return{status:a.status,headers:b,body:m}}},"peerCall")}i(C,"createPeerCall");const J=C({issuer:v,directory:$,tokens:x,fetch:i((e,r)=>fetch(e,r),"fetch"),sleep:i(e=>new Promise(r=>setTimeout(r,e)),"sleep"),now:Date.now,version:M,timeoutMs:U,startingBudgetMs:A});export{p as MAX_PEER_RESPONSE_BYTES,U as PEER_CALL_TIMEOUT_MS,w as RUNTIME_STARTING_BACKOFF_MS,A as RUNTIME_STARTING_RETRY_BUDGET_MS,C as createPeerCall,H as isUnreachable,J as peerCall};
1
+ var S=Object.defineProperty;var i=(e,r)=>S(e,"name",{value:r,configurable:!0});import{crtrVersion as M}from"../../shared/crtr-version.js";import{directory as $,directoryIssuer as v,normalizeMachineId as T}from"./directory.js";import{ENVELOPE_HEADER as N,encodeEnvelope as O}from"./envelope.js";import{peerTokens as x}from"./token.js";import{PEER_VERSION_FLOOR as h,PEER_VERSION_HEADER as P,peerVersionRefusal as k}from"./version.js";const A=12e4,U=6e4,w={first:1e3,max:8e3},R=8*1024*1024,H=i(e=>"unreachable"in e,"isUnreachable"),s=i((e,r)=>({unreachable:{reason:e,message:r}}),"unreachable");function B(e){const r=e?.error;if(typeof r=="string")return r;const o=r?.code;return typeof o=="string"?o:null}i(B,"errorCode");async function I(e){if(Number(e.headers.get("content-length")??"0")>R)throw new RangeError("the peer answer is too large");const o=await e.text();if(o.length>R)throw new RangeError("the peer answer is too large");if(o==="")return null;try{return JSON.parse(o)}catch{return o}}i(I,"readBody");function L(e,r){const o=new URL(`${e.url}${r.path.startsWith("/")?"":"/"}${r.path}`),c=r.query instanceof URLSearchParams?r.query:Object.entries(r.query??{});for(const[u,l]of c instanceof URLSearchParams?c.entries():c)l!==void 0&&o.searchParams.append(u,String(l));return o.toString()}i(L,"targetUrl");function C(e){return i(async function(o,c,u={}){const l=e.issuer();if(l===null)return s("no_directory","this machine is not joined to a Northlight account, so it has no peers");let t;try{t=await e.directory.reach(o)}catch(a){return s("directory_unreachable",a instanceof Error?a.message:String(a))}if(t===null)return s("unknown_machine",`No machine ${T(o)} on your account.`);const d=t.crtr_version===null?null:k(t.crtr_version,h,e.version());if(d!==null)return{status:426,headers:{},body:{error:{code:d.code,message:`${t.name} runs crtr ${t.crtr_version}; peers below ${h} cannot take part. Update it with \`crtr sys update\`.`,details:{peer_version:t.crtr_version,floor:h,receiver_version:e.version()}}}};const b=L(t,c),g=e.now();let f=w.first,_=!1,E=!1;for(;;){let a;try{const n=await e.tokens.get(l,t.url);a=await e.fetch(b,{method:c.method,redirect:"manual",headers:{authorization:`Bearer ${n}`,[P]:e.version(),...u.envelope?{[N]:O(u.envelope)}:{},...c.body===void 0?{}:{"content-type":"application/json"}},...c.body===void 0?{}:{body:JSON.stringify(c.body)},signal:AbortSignal.any([AbortSignal.timeout(E?Math.max(1e3,Math.min(e.timeoutMs,e.startingBudgetMs-(e.now()-g))):e.timeoutMs),...u.signal?[u.signal]:[]])})}catch(n){return n instanceof Error&&(n.name==="TimeoutError"||n.name==="AbortError")?s("timeout",`${t.name} did not answer within ${Math.round(e.timeoutMs/1e3)} s.`):n instanceof Error&&/directory/.test(n.message)?s("directory_unreachable",n.message):s("network",`${t.name} could not be reached: ${n instanceof Error?n.message:String(n)}`)}let m;try{m=await I(a)}catch(n){return s("network",`${t.name}'s answer could not be read: ${n instanceof Error?n.message:String(n)}`)}const y=B(m),p=Object.fromEntries(a.headers.entries());if(a.status===401&&!_){_=!0,e.tokens.drop(l,t.url);continue}if(a.status===503&&y==="machine_offline")return s("machine_offline",`${t.name} is offline.`);if(a.status===504)return s("timeout",`${t.name} did not answer in time.`);if(a.status===503&&y==="runtime_starting"){if(e.now()-g+f>e.startingBudgetMs)return s("runtime_starting",`${t.name} is still starting.`);await e.sleep(f),f=Math.min(f*2,w.max),E=!0;continue}return{status:a.status,headers:p,body:m}}},"peerCall")}i(C,"createPeerCall");const J=C({issuer:v,directory:$,tokens:x,fetch:i((e,r)=>fetch(e,r),"fetch"),sleep:i(e=>new Promise(r=>setTimeout(r,e)),"sleep"),now:Date.now,version:M,timeoutMs:A,startingBudgetMs:U});export{R as MAX_PEER_RESPONSE_BYTES,A as PEER_CALL_TIMEOUT_MS,w as RUNTIME_STARTING_BACKOFF_MS,U as RUNTIME_STARTING_RETRY_BUDGET_MS,C as createPeerCall,H as isUnreachable,J as peerCall};
@@ -1 +1 @@
1
- var x=Object.defineProperty;var n=(t,e)=>x(t,"name",{value:e,configurable:!0});import{prepared as y}from"../sqlite-statements.js";import{openDb as C,withCanvasWrite as k}from"../canvas/db.js";import{readConfig as v}from"../config.js";import{runtimeClient as p}from"../grants/sync.js";import{crtrVersion as M}from"../../shared/crtr-version.js";import{installedPluginIds as S}from"../directory/join.js";import{peerTokens as I}from"./token.js";const R=5*6e4,O=1e4;class c extends Error{static{n(this,"DirectoryError")}status;constructor(e,i){super(i),this.status=e,this.name="DirectoryError"}}const m=n(t=>t.startsWith("runtime:")?t:`runtime:${t}`,"normalizeMachineId");function _(t){if(!t||typeof t!="object")return null;const{kind:e,relay:i}=t;return e==="stable"?{kind:"stable"}:e==="relay"&&typeof i=="string"?{kind:"relay",relay:i}:null}n(_,"asReach");function g(t){const e=t,i=_(e?.reach);if(!e||typeof e.machine_id!="string"||typeof e.url!="string"||i===null)throw new c(502,"the directory answered a machine record with a missing machine_id, url or reach");return{machine_id:e.machine_id,name:typeof e.name=="string"?e.name:e.machine_id,url:e.url.replace(/\/$/,""),reach:i,crtr_version:typeof e.crtr_version=="string"?e.crtr_version:null,last_seen:typeof e.last_seen=="string"?e.last_seen:null,...typeof e.is_default=="boolean"?{is_default:e.is_default}:{}}}n(g,"asMachine");function U(t,e){const i=new Map;async function h(o,s,r){const a=t.issuer();if(a===null)throw new c(0,"this daemon has no directory");const f=a.replace(/\/$/,"");for(let w=0;;w++){let u;try{const l=await t.tokens.get(f);u=await t.fetch(`${f}${s}`,{method:o,headers:{authorization:`Bearer ${l}`,...r===void 0?{}:{"content-type":"application/json"}},...r===void 0?{}:{body:JSON.stringify(r)},signal:AbortSignal.timeout(O)})}catch(l){throw new c(0,`the directory could not be reached: ${l instanceof Error?l.message:String(l)}`)}if(u.status===401&&w===0){t.tokens.drop(f);continue}if(!u.ok)throw new c(u.status,`directory ${o} ${s} answered ${u.status}`);try{return await u.json()}catch{throw new c(502,`directory ${o} ${s} answered a body that is not JSON`)}}}return n(h,"call"),{async list(){const o=await h("GET","/v1/machines");if(!Array.isArray(o))throw new c(502,"the directory answered a machine list that is not an array");const s=o.map(g),r=t.now();for(const a of s)i.set(a.machine_id,{machine:a,at:r});return s},async reach(o){const s=m(o),r=i.get(s);if(r&&t.now()-r.at<R)return r.machine;try{const a=g(await h("GET",`/v1/machines/${encodeURIComponent(s)}/reach`));return i.set(s,{machine:a,at:t.now()}),a}catch(a){if(a instanceof c&&a.status===404)return i.delete(s),null;if(r&&a instanceof c&&a.status===0)return r.machine;throw a}},async status(o){const s=e(),r=await h("PUT",`/v1/machines/${encodeURIComponent(s)}/status`,o);if(!r||typeof r.machine_id!="string"||typeof r.url!="string")throw new c(502,"the directory answered a status record with a missing machine_id or url");return{machine_id:r.machine_id,url:r.url.replace(/\/$/,""),reach:_(r.reach)}},invalidate(o){o===void 0?i.clear():i.delete(m(o))}}}n(U,"createDirectoryClient");function E(){return m(p().runtime)}n(E,"ownMachineId");function T(){return v("user").runtime?.issuer??null}n(T,"directoryIssuer");const d=U({issuer:T,tokens:I,fetch:n((t,e)=>fetch(t,e),"fetch"),now:Date.now},E),z=n(()=>d.list(),"listMachines"),F=n(t=>d.reach(t),"machineReach"),$="machine_url";function G(){return y(C(),"SELECT value FROM runtime_state WHERE key = ?").get($)?.value??null}n(G,"machineUrl");function b(t){k(e=>{y(e,"INSERT INTO runtime_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run($,t)})}n(b,"storeMachineUrl");async function J(t=d){if(T()===null)return null;try{p()}catch{return null}const e=await t.status({plugins:S(),crtr_version:M()});if(e.machine_id!==E())throw new c(502,"the directory answered a status for another machine");return b(e.url),e}n(J,"reportMachineStatus");export{O as DIRECTORY_CALL_TIMEOUT_MS,c as DirectoryError,R as REACH_TTL_MS,U as createDirectoryClient,d as directory,T as directoryIssuer,z as listMachines,F as machineReach,G as machineUrl,m as normalizeMachineId,E as ownMachineId,J as reportMachineStatus};
1
+ var C=Object.defineProperty;var i=(t,e)=>C(t,"name",{value:e,configurable:!0});import{rememberMachineName as y}from"./names.js";import{prepared as p}from"../sqlite-statements.js";import{openDb as M,withCanvasWrite as k}from"../canvas/db.js";import{readConfig as v}from"../config.js";import{runtimeClient as _}from"../grants/sync.js";import{crtrVersion as S}from"../../shared/crtr-version.js";import{installedPluginIds as I}from"../directory/join.js";import{peerTokens as R}from"./token.js";const b=5*6e4,O=1e4;class c extends Error{static{i(this,"DirectoryError")}status;constructor(e,a){super(a),this.status=e,this.name="DirectoryError"}}const f=i(t=>t.startsWith("runtime:")?t:`runtime:${t}`,"normalizeMachineId");function g(t){if(!t||typeof t!="object")return null;const{kind:e,relay:a}=t;return e==="stable"?{kind:"stable"}:e==="relay"&&typeof a=="string"?{kind:"relay",relay:a}:null}i(g,"asReach");function E(t){const e=t,a=g(e?.reach);if(!e||typeof e.machine_id!="string"||typeof e.url!="string"||a===null)throw new c(502,"the directory answered a machine record with a missing machine_id, url or reach");return{machine_id:e.machine_id,name:typeof e.name=="string"?e.name:e.machine_id,url:e.url.replace(/\/$/,""),reach:a,crtr_version:typeof e.crtr_version=="string"?e.crtr_version:null,last_seen:typeof e.last_seen=="string"?e.last_seen:null,...typeof e.is_default=="boolean"?{is_default:e.is_default}:{}}}i(E,"asMachine");function U(t,e){const a=new Map;async function h(o,s,r){const n=t.issuer();if(n===null)throw new c(0,"this daemon has no directory");const m=n.replace(/\/$/,"");for(let w=0;;w++){let u;try{const l=await t.tokens.get(m);u=await t.fetch(`${m}${s}`,{method:o,headers:{authorization:`Bearer ${l}`,...r===void 0?{}:{"content-type":"application/json"}},...r===void 0?{}:{body:JSON.stringify(r)},signal:AbortSignal.timeout(O)})}catch(l){throw new c(0,`the directory could not be reached: ${l instanceof Error?l.message:String(l)}`)}if(u.status===401&&w===0){t.tokens.drop(m);continue}if(!u.ok)throw new c(u.status,`directory ${o} ${s} answered ${u.status}`);try{return await u.json()}catch{throw new c(502,`directory ${o} ${s} answered a body that is not JSON`)}}}return i(h,"call"),{async list(){const o=await h("GET","/v1/machines");if(!Array.isArray(o))throw new c(502,"the directory answered a machine list that is not an array");const s=o.map(E),r=t.now();for(const n of s)a.set(n.machine_id,{machine:n,at:r}),y(n.machine_id,n.name);return s},async reach(o){const s=f(o),r=a.get(s);if(r&&t.now()-r.at<b)return r.machine;try{const n=E(await h("GET",`/v1/machines/${encodeURIComponent(s)}/reach`));return a.set(s,{machine:n,at:t.now()}),y(n.machine_id,n.name),n}catch(n){if(n instanceof c&&n.status===404)return a.delete(s),null;if(r&&n instanceof c&&n.status===0)return r.machine;throw n}},async status(o){const s=e(),r=await h("PUT",`/v1/machines/${encodeURIComponent(s)}/status`,o);if(!r||typeof r.machine_id!="string"||typeof r.url!="string")throw new c(502,"the directory answered a status record with a missing machine_id or url");return{machine_id:r.machine_id,url:r.url.replace(/\/$/,""),reach:g(r.reach)}},invalidate(o){o===void 0?a.clear():a.delete(f(o))}}}i(U,"createDirectoryClient");function T(){return f(_().runtime)}i(T,"ownMachineId");function $(){return v("user").runtime?.issuer??null}i($,"directoryIssuer");const d=U({issuer:$,tokens:R,fetch:i((t,e)=>fetch(t,e),"fetch"),now:Date.now},T),G=i(()=>d.list(),"listMachines"),J=i(t=>d.reach(t),"machineReach"),x="machine_url";function V(){return p(M(),"SELECT value FROM runtime_state WHERE key = ?").get(x)?.value??null}i(V,"machineUrl");function A(t){k(e=>{p(e,"INSERT INTO runtime_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(x,t)})}i(A,"storeMachineUrl");async function Y(t=d){if($()===null)return null;try{_()}catch{return null}const e=await t.status({plugins:I(),crtr_version:S()});if(e.machine_id!==T())throw new c(502,"the directory answered a status for another machine");return A(e.url),e}i(Y,"reportMachineStatus");export{O as DIRECTORY_CALL_TIMEOUT_MS,c as DirectoryError,b as REACH_TTL_MS,U as createDirectoryClient,d as directory,$ as directoryIssuer,G as listMachines,J as machineReach,V as machineUrl,f as normalizeMachineId,T as ownMachineId,Y as reportMachineStatus};
@@ -0,0 +1,40 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import { type PeerRequest, type PeerResult } from './client.js';
3
+ import { type Machine } from './directory.js';
4
+ export type HomeAnswer = {
5
+ where: 'here';
6
+ } | {
7
+ where: 'machine';
8
+ machine: string; /** How it was learned: a stub, the request's hint, the cache, or a fresh `peer.locate`. */
9
+ source: 'stub' | 'hint' | 'cache' | 'locate';
10
+ }
11
+ /** No machine answered `here`, and `unreachable` names the ones that could not answer. Never "missing". */
12
+ | {
13
+ where: 'unknown';
14
+ unreachable: string[];
15
+ }
16
+ /** Every other machine answered `no`. The only answer from which a caller may say "missing". */
17
+ | {
18
+ where: 'nowhere';
19
+ };
20
+ export interface HomeDeps {
21
+ call(machine: string, request: PeerRequest): Promise<PeerResult>;
22
+ machines(): Promise<Machine[]>;
23
+ self(): string;
24
+ now(): number;
25
+ /** Whether this daemon belongs to an account at all; an unjoined daemon has no peers, so nothing is ever "elsewhere". */
26
+ joined(): boolean;
27
+ }
28
+ export declare const defaultHomeDeps: HomeDeps;
29
+ export declare function cachedLocation(db: DatabaseSync, id: string): string | null;
30
+ export declare function rememberLocation(db: DatabaseSync, id: string, machine: string, now?: number): void;
31
+ export declare function dropLocation(db: DatabaseSync, id: string): void;
32
+ /** Ask every other machine "do you hold `id`?". First `here`/`moved` wins; unreachable machines are listed. */
33
+ export declare function locate(db: DatabaseSync, id: string, deps?: HomeDeps, skip?: string): Promise<HomeAnswer>;
34
+ /** Where `id` lives. `hint` is the machine the request names (`?machine=`). */
35
+ export declare function homeOf(db: DatabaseSync, id: string, opts?: {
36
+ hint?: string | null;
37
+ deps?: HomeDeps;
38
+ }): Promise<HomeAnswer>;
39
+ /** A cached or hinted machine said `404 not_here`: forget it and ask everyone else. */
40
+ export declare function relocate(db: DatabaseSync, id: string, wrong: string, deps?: HomeDeps): Promise<HomeAnswer>;
@@ -0,0 +1,2 @@
1
+ var E=Object.defineProperty;var a=(n,r)=>E(n,"name",{value:r,configurable:!0});import{prepared as l}from"../sqlite-statements.js";import{getById as y}from"../graph/objects.js";import{isUnreachable as b,peerCall as x}from"./client.js";import{directoryIssuer as O,listMachines as T,normalizeMachineId as w,ownMachineId as I}from"./directory.js";import{stubOf as L}from"./stubs.js";const m={call:a((n,r)=>x(n,r),"call"),machines:T,self:I,now:Date.now,joined:a(()=>O()!==null,"joined")};function S(n,r){return l(n,"SELECT machine_id FROM peer_locations WHERE id = ?").get(r)?.machine_id??null}a(S,"cachedLocation");function g(n,r,t,i=Date.now()){l(n,`INSERT INTO peer_locations (id, machine_id, learned_at) VALUES (?, ?, ?)
2
+ ON CONFLICT(id) DO UPDATE SET machine_id = excluded.machine_id, learned_at = excluded.learned_at`).run(r,t,i)}a(g,"rememberLocation");function D(n,r){l(n,"DELETE FROM peer_locations WHERE id = ?").run(r)}a(D,"dropLocation");async function p(n,r,t=m,i){if(!t.joined())return{where:"nowhere"};let c;try{c=await t.machines()}catch(e){return{where:"unknown",unreachable:[`directory (${e instanceof Error?e.message:String(e)})`]}}const s=t.self(),_=c.filter(e=>e.machine_id!==s&&e.machine_id!==i),d=await Promise.all(_.map(async e=>{const o=await t.call(e.machine_id,{method:"POST",path:"/v1/peer/locate",body:{id:r}});if(b(o))return{m:e,state:"unreachable"};const h=o.body;return o.status===200&&h?.answer==="here"?{m:e,state:"here",machine:e.machine_id}:o.status===200&&h?.answer==="moved"&&typeof h.machine=="string"?{m:e,state:"here",machine:w(h.machine)}:o.status===200&&h?.answer==="no"?{m:e,state:"no"}:{m:e,state:"unreachable"}})),u=d.find(e=>e.state==="here");if(u&&"machine"in u)return g(n,r,u.machine,t.now()),{where:"machine",machine:u.machine,source:"locate"};const f=d.filter(e=>e.state==="unreachable").map(e=>e.m.name||e.m.machine_id);return f.length>0?{where:"unknown",unreachable:f}:{where:"nowhere"}}a(p,"locate");async function H(n,r,t={}){const i=y(n,r);if(i!==null){const s=L(n,i);return s===null?{where:"here"}:{where:"machine",machine:s.home_machine,source:"stub"}}if(!(t.deps??m).joined())return{where:"nowhere"};if(t.hint)return{where:"machine",machine:w(t.hint),source:"hint"};const c=S(n,r);return c!==null?{where:"machine",machine:c,source:"cache"}:p(n,r,t.deps)}a(H,"homeOf");async function P(n,r,t,i=m){return D(n,r),p(n,r,i,t)}a(P,"relocate");export{S as cachedLocation,m as defaultHomeDeps,D as dropLocation,H as homeOf,p as locate,P as relocate,g as rememberLocation};
@@ -0,0 +1,3 @@
1
+ export declare const rememberMachineName: (machineId: string, name: string) => void;
2
+ /** What to call a machine in text a person reads: its directory name when this daemon has seen it, else its id. */
3
+ export declare const machineNameOf: (machineId: string) => string;
@@ -0,0 +1 @@
1
+ var m=Object.defineProperty;var n=(e,t)=>m(e,"name",{value:t,configurable:!0});const a=new Map,s=n((e,t)=>{a.set(e,t)},"rememberMachineName"),c=n(e=>a.get(e)??e,"machineNameOf");export{c as machineNameOf,s as rememberMachineName};
@@ -47,5 +47,7 @@ export declare const isStub: (db: DatabaseSync, object: ObjectRow) => boolean;
47
47
  * understand. No `nodes` row is written.
48
48
  */
49
49
  export declare function ensureStub(db: DatabaseSync, id: string, type: string, machine: string, envelope?: Envelope, ref?: ObjectRef): ObjectRow;
50
+ /** The object moved: the machine that answered for it is now its home. A no-op for a local object or a stub already homed there. */
51
+ export declare function rehomeStub(db: DatabaseSync, id: string, machine: string): void;
50
52
  /** How `o` is described to another machine. */
51
53
  export declare function describeObject(db: DatabaseSync, o: ObjectRow): ObjectRef;
@@ -1 +1 @@
1
- var S=Object.defineProperty;var r=(n,e)=>S(n,"name",{value:e,configurable:!0});import{prepared as l}from"../sqlite-statements.js";import{createObject as R,ensureOwner as w,getById as f,getByRowid as m}from"../graph/objects.js";import{CrtrError as d}from"../errors.js";import{ExitCode as _}from"../../types.js";const j=r(n=>{const e=n.act.act;return e===void 0?{sub:n.sub,app:null,node:null,node_scopes:null,place:null}:{sub:n.sub,app:e.sub,node:e.node,node_scopes:e.node_scopes,place:e.place}},"identityOfEnvelope");function O(n,e){const t=typeof e=="string"?f(n,e):e;if(t===null)return null;const o=l(n,"SELECT home_machine, envelope_json FROM peer_stubs WHERE object = ?").get(t.rowid);return o?{object:t,home_machine:o.home_machine,identity:o.envelope_json===null?null:JSON.parse(o.envelope_json)}:null}r(O,"stubOf");const H=r((n,e)=>l(n,"SELECT 1 FROM peer_stubs WHERE object = ?").get(e.rowid)!==void 0,"isStub");function E(n){return w(n,"person","local-user","user:local","user")}r(E,"localPerson");function h(n,e){return e==="user:local"?E(n):w(n,"app","app",e,e.replace(/^app:/,""))}r(h,"localApp");function T(n,e,t){if(e!==null)return e.app===null?E(n):h(n,e.app);const o=t?.owner;return o?.type==="app"?h(n,o.external_id??`app:${o.handle}`):E(n)}r(T,"ownerFor");function N(n,e,t,o,p,s){const i=p===void 0?null:j(p),u=f(n,e);if(u!==null){const a=O(n,u);if(a===null)throw new d("conflict",`${e} is a local ${u.type}, not a stub of ${o}`,_.GENERAL,{id:e});if(a.home_machine!==o)throw new d("conflict",`${e} is already a stub of ${a.home_machine}, not ${o}`,_.GENERAL,{id:e,home_machine:a.home_machine});return i!==null&&(l(n,"UPDATE peer_stubs SET envelope_json = ? WHERE object = ?").run(JSON.stringify(i),u.rowid),l(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(u.rowid)),u}const y=T(n,i,s),c=R(n,{id:e,type:t,kind:s?.kind??null,owner:y.rowid,privacy:"public",name:s?.name??e,preview:s?.preview??""});return l(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(o,c.rowid),l(n,"INSERT INTO peer_stubs (object, home_machine, envelope_json, created_at) VALUES (?, ?, ?, ?)").run(c.rowid,o,i===null?null:JSON.stringify(i),new Date().toISOString()),i!==null&&l(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(c.rowid),m(n,c.rowid)}r(N,"ensureStub");function W(n,e){const t=e.owner===null?null:m(n,e.owner);let o=null;if(t!==null&&(t.type==="person"||t.type==="app"||t.type==="repo"||t.type==="profile")){const p=l(n,"SELECT scheme, external_id FROM identities WHERE object = ?").get(t.rowid);o={id:t.id,type:t.type,scheme:p?.scheme??null,external_id:p?.external_id??null,handle:t.name}}return{id:e.id,type:e.type,kind:e.kind,name:e.name,preview:e.preview,owner:o}}r(W,"describeObject");export{W as describeObject,N as ensureStub,j as identityOfEnvelope,H as isStub,O as stubOf};
1
+ var j=Object.defineProperty;var r=(n,e)=>j(n,"name",{value:e,configurable:!0});import{prepared as i}from"../sqlite-statements.js";import{createObject as b,ensureOwner as w,getById as m,getByRowid as f}from"../graph/objects.js";import{CrtrError as d}from"../errors.js";import{ExitCode as _}from"../../types.js";const R=r(n=>{const e=n.act.act;return e===void 0?{sub:n.sub,app:null,node:null,node_scopes:null,place:null}:{sub:n.sub,app:e.sub,node:e.node,node_scopes:e.node_scopes,place:e.place}},"identityOfEnvelope");function h(n,e){const t=typeof e=="string"?m(n,e):e;if(t===null)return null;const o=i(n,"SELECT home_machine, envelope_json FROM peer_stubs WHERE object = ?").get(t.rowid);return o?{object:t,home_machine:o.home_machine,identity:o.envelope_json===null?null:JSON.parse(o.envelope_json)}:null}r(h,"stubOf");const g=r((n,e)=>i(n,"SELECT 1 FROM peer_stubs WHERE object = ?").get(e.rowid)!==void 0,"isStub");function E(n){return w(n,"person","local-user","user:local","user")}r(E,"localPerson");function S(n,e){return e==="user:local"?E(n):w(n,"app","app",e,e.replace(/^app:/,""))}r(S,"localApp");function T(n,e,t){if(e!==null)return e.app===null?E(n):S(n,e.app);const o=t?.owner;return o?.type==="app"?S(n,o.external_id??`app:${o.handle}`):E(n)}r(T,"ownerFor");function v(n,e,t,o,p,s){const l=p===void 0?null:R(p),u=m(n,e);if(u!==null){const a=h(n,u);if(a===null)throw new d("conflict",`${e} is a local ${u.type}, not a stub of ${o}`,_.GENERAL,{id:e});if(a.home_machine!==o)throw new d("conflict",`${e} is already a stub of ${a.home_machine}, not ${o}`,_.GENERAL,{id:e,home_machine:a.home_machine});return l!==null&&(i(n,"UPDATE peer_stubs SET envelope_json = ? WHERE object = ?").run(JSON.stringify(l),u.rowid),i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(u.rowid)),u}const y=T(n,l,s),c=b(n,{id:e,type:t,kind:s?.kind??null,owner:y.rowid,privacy:"public",name:s?.name??e,preview:s?.preview??""});return i(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(o,c.rowid),i(n,"INSERT INTO peer_stubs (object, home_machine, envelope_json, created_at) VALUES (?, ?, ?, ?)").run(c.rowid,o,l===null?null:JSON.stringify(l),new Date().toISOString()),l!==null&&i(n,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(c.rowid),f(n,c.rowid)}r(v,"ensureStub");function D(n,e,t){const o=h(n,e);o===null||o.home_machine===t||(i(n,"UPDATE peer_stubs SET home_machine = ? WHERE object = ?").run(t,o.object.rowid),i(n,"UPDATE objects SET home_machine = ? WHERE rowid = ?").run(t,o.object.rowid))}r(D,"rehomeStub");function N(n,e){const t=e.owner===null?null:f(n,e.owner);let o=null;if(t!==null&&(t.type==="person"||t.type==="app"||t.type==="repo"||t.type==="profile")){const p=i(n,"SELECT scheme, external_id FROM identities WHERE object = ?").get(t.rowid);o={id:t.id,type:t.type,scheme:p?.scheme??null,external_id:p?.external_id??null,handle:t.name}}return{id:e.id,type:e.type,kind:e.kind,name:e.name,preview:e.preview,owner:o}}r(N,"describeObject");export{N as describeObject,v as ensureStub,R as identityOfEnvelope,g as isStub,D as rehomeStub,h as stubOf};
@@ -1,7 +1,7 @@
1
1
  /** Request header naming the sending machine's crtr release. */
2
2
  export declare const PEER_VERSION_HEADER = "Crtr-Version";
3
- /** The oldest crtr release that speaks the peer protocol. L1 sets this to the release that ships C1–C6. */
4
- export declare const PEER_VERSION_FLOOR = "0.3.442";
3
+ /** The oldest crtr release that speaks the peer protocol. The release that first ships C1–C6 with the peer relay switched on. */
4
+ export declare const PEER_VERSION_FLOOR = "0.3.448";
5
5
  export interface PeerVersionRefusal {
6
6
  status: 426;
7
7
  code: 'peer_version_too_old';
@@ -1 +1 @@
1
- var n=Object.defineProperty;var i=(r,e)=>n(r,"name",{value:e,configurable:!0});import{compareReleaseVersions as a}from"../../api/runtime-version.js";import{crtrVersion as o}from"../../shared/crtr-version.js";const l="Crtr-Version",c="0.3.442";function d(r,e=c,s=o()){const t=r?.trim()?r.trim():null;return t!==null&&a(t,e)>=0?null:{status:426,code:"peer_version_too_old",message:t===null?`The peer did not name its crtr version; machines below ${e} cannot take part (this machine is ${s}). Update it with \`crtr sys update\`.`:`The peer runs crtr ${t}; machines below ${e} cannot take part (this machine is ${s}). Update it with \`crtr sys update\`.`,details:{peer_version:t,floor:e,receiver_version:s}}}i(d,"peerVersionRefusal");export{c as PEER_VERSION_FLOOR,l as PEER_VERSION_HEADER,d as peerVersionRefusal};
1
+ var n=Object.defineProperty;var i=(r,e)=>n(r,"name",{value:e,configurable:!0});import{compareReleaseVersions as a}from"../../api/runtime-version.js";import{crtrVersion as o}from"../../shared/crtr-version.js";const l="Crtr-Version",c="0.3.448";function d(r,e=c,s=o()){const t=r?.trim()?r.trim():null;return t!==null&&a(t,e)>=0?null:{status:426,code:"peer_version_too_old",message:t===null?`The peer did not name its crtr version; machines below ${e} cannot take part (this machine is ${s}). Update it with \`crtr sys update\`.`:`The peer runs crtr ${t}; machines below ${e} cannot take part (this machine is ${s}). Update it with \`crtr sys update\`.`,details:{peer_version:t,floor:e,receiver_version:s}}}i(d,"peerVersionRefusal");export{c as PEER_VERSION_FLOOR,l as PEER_VERSION_HEADER,d as peerVersionRefusal};
@@ -1 +1 @@
1
- var u=Object.defineProperty;var d=(e,t)=>u(e,"name",{value:t,configurable:!0});import{formatInboxCard as p}from"../../../shared/generated-context.js";import{renderEntryBody as m}from"../../../shared/inbox-entry-body.js";import{reportRefNodeId as I}from"../../../shared/inbox-entry-body.js";function y(e){const t=e.data?.report_body;return m(e,typeof t=="string"?t:null)}d(y,"entryBody");function l(e){return{kind:e.kind,body:y(e),...e.ref===void 0?{}:{ref:e.ref},...e.disposition===void 0?{}:{disposition:e.disposition}}}d(l,"cardEntry");function c(e){const t=e.data?.cards;return Array.isArray(t)?t.filter(s=>typeof s=="string"):[]}d(c,"entryCards");function b(e){const t=[];let s=[];const a=d(()=>{if(s.length===0)return;const r=new Map;for(const n of s){const i=n.from??"system";r.has(i)||r.set(i,[]),r.get(i).push(n)}const o=[...r].map(([n,i])=>({id:n,entries:i.map(f=>l(f))}));t.push({kind:"card",text:p(o),cards:s.flatMap(n=>c(n)),entries:s}),s=[]},"flushRun");for(const r of e){if((r.from??"system")!=="human"){s.push(r);continue}a();const o=r.data?.body;t.push({kind:"human",text:typeof o=="string"?o:r.label,cards:c(r),entries:[r]})}a();for(let r=t.length-2;r>=0;r-=1){const o=t[r],n=t[r+1];o.kind!=="card"||n.kind!=="human"||o.entries.every(i=>i.tier==="deferred")&&(t[r]={...n,cards:[...o.cards,o.text,...n.cards],entries:[...o.entries,...n.entries]},t.splice(r+1,1))}return t}d(b,"coalesceBrokerInbox");export{b as coalesceBrokerInbox,c as entryCards,I as reportRefNodeId};
1
+ var u=Object.defineProperty;var d=(n,e)=>u(n,"name",{value:e,configurable:!0});import{formatInboxCard as p,machineOfEntries as m}from"../../../shared/generated-context.js";import{renderEntryBody as y}from"../../../shared/inbox-entry-body.js";import{reportRefNodeId as I}from"../../../shared/inbox-entry-body.js";function l(n){const e=n.data?.report_body;return y(n,typeof e=="string"?e:null)}d(l,"entryBody");function h(n){return{kind:n.kind,body:l(n),...n.ref===void 0?{}:{ref:n.ref},...n.disposition===void 0?{}:{disposition:n.disposition}}}d(h,"cardEntry");function c(n){const e=n.data?.cards;return Array.isArray(e)?e.filter(s=>typeof s=="string"):[]}d(c,"entryCards");function g(n){const e=[];let s=[];const a=d(()=>{if(s.length===0)return;const r=new Map;for(const t of s){const i=t.from??"system";r.has(i)||r.set(i,[]),r.get(i).push(t)}const o=[...r].map(([t,i])=>({id:t,...m(i),entries:i.map(f=>h(f))}));e.push({kind:"card",text:p(o),cards:s.flatMap(t=>c(t)),entries:s}),s=[]},"flushRun");for(const r of n){if((r.from??"system")!=="human"){s.push(r);continue}a();const o=r.data?.body;e.push({kind:"human",text:typeof o=="string"?o:r.label,cards:c(r),entries:[r]})}a();for(let r=e.length-2;r>=0;r-=1){const o=e[r],t=e[r+1];o.kind!=="card"||t.kind!=="human"||o.entries.every(i=>i.tier==="deferred")&&(e[r]={...t,cards:[...o.cards,o.text,...t.cards],entries:[...o.entries,...t.entries]},e.splice(r+1,1))}return e}d(g,"coalesceBrokerInbox");export{g as coalesceBrokerInbox,c as entryCards,I as reportRefNodeId};
@@ -1 +1 @@
1
- var d=Object.defineProperty;var r=(e,t)=>d(e,"name",{value:t,configurable:!0});import{prepared as a}from"../sqlite-statements.js";import{createHash as E}from"node:crypto";import{openScopedStore as p,withScopedWrite as _}from"./db.js";function l(e,t){const n=a(e,"SELECT kind, root, profile_id FROM scope_owners WHERE owner_key = ?").get(t.key);if(!n)throw new Error(`scoped SQL owner is missing: ${t.key}`);if(n.kind!==t.kind||n.root!==t.root||(t.profileId??null)!==n.profile_id)throw new Error(`scoped SQL owner does not match its root: ${t.key}`)}r(l,"ensureOwner");function u(e){return e===void 0?"absent":E("sha256").update(e.raw_json).digest("hex")}r(u,"revisionFor");function k(e,t){return a(e,"SELECT raw_json FROM scope_settings WHERE owner_key = ?").get(t.key)}r(k,"readRawSettingsRow");class g extends Error{static{r(this,"SettingsRevisionConflictError")}constructor(){super("settings changed since the supplied revision"),this.name="SettingsRevisionConflictError"}}function f(e){const t=p(e.store);l(t,e);const n=k(t,e);return{revision:u(n),settings:n===void 0?null:JSON.parse(n.raw_json)}}r(f,"readRawSettingsWithRevision");function T(e){return f(e).settings}r(T,"readRawSettings");function h(e,t,n,i){return _(e.store,o=>{l(o,e);const s=k(o,e);if(n!==u(s))throw new g;i(t);const c=JSON.stringify(t);return s?.raw_json===c?{revision:u(s),changed:!1}:(a(o,"INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json").run(e.key,c),{revision:u({raw_json:c}),changed:!0})})}r(h,"replaceRawSettings");function m(e,t){_(e.store,n=>{l(n,e),a(n,"INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json").run(e.key,JSON.stringify(t))})}r(m,"writeRawSettings");function v(e,t){return _(e.store,n=>{l(n,e);const i=a(n,"SELECT raw_json FROM scope_settings WHERE owner_key = ?").get(e.key),o=t(i?JSON.parse(i.raw_json):{});return a(n,"INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json").run(e.key,JSON.stringify(o.next)),o.value})}r(v,"mutateRawSettings");function N(e){const t=p(e.store);l(t,e);const n=a(t,"SELECT last_self_check, active_canvas FROM scope_bookkeeping WHERE owner_key = ?").get(e.key),i=Object.fromEntries(a(t,"SELECT name, last_updated FROM scope_marketplaces WHERE owner_key = ?").all(e.key).map(s=>[s.name,s.last_updated===null?{}:{last_updated:s.last_updated}])),o=Object.fromEntries(a(t,"SELECT name, last_updated, etag, checked_at FROM scope_plugins WHERE owner_key = ?").all(e.key).map(s=>[s.name,{...s.last_updated===null?{}:{last_updated:s.last_updated},...s.etag===null?{}:{etag:s.etag},...s.checked_at===null?{}:{checked_at:s.checked_at}}]));return{marketplaces:i,plugins:o,...n?.last_self_check===null||n===void 0?{}:{last_self_check:n.last_self_check},activeCanvas:n?.active_canvas??null}}r(N,"readBookkeeping");function w(e,t){_(e.store,n=>{l(n,e),a(n,"INSERT INTO scope_bookkeeping(owner_key, last_self_check, active_canvas) VALUES (?, ?, ?) ON CONFLICT(owner_key) DO UPDATE SET last_self_check=excluded.last_self_check, active_canvas=excluded.active_canvas").run(e.key,t.last_self_check??null,t.activeCanvas??null),a(n,"DELETE FROM scope_marketplaces WHERE owner_key = ?").run(e.key),a(n,"DELETE FROM scope_plugins WHERE owner_key = ?").run(e.key);const i=a(n,"INSERT INTO scope_marketplaces(owner_key, name, last_updated) VALUES (?, ?, ?)");for(const[s,c]of Object.entries(t.marketplaces))i.run(e.key,s,c.last_updated??null);const o=a(n,"INSERT INTO scope_plugins(owner_key, name, last_updated, etag, checked_at) VALUES (?, ?, ?, ?, ?)");for(const[s,c]of Object.entries(t.plugins))o.run(e.key,s,c.last_updated??null,c.etag??null,c.checked_at??null)})}r(w,"writeBookkeeping");export{g as SettingsRevisionConflictError,v as mutateRawSettings,N as readBookkeeping,T as readRawSettings,f as readRawSettingsWithRevision,h as replaceRawSettings,w as writeBookkeeping,m as writeRawSettings};
1
+ var d=Object.defineProperty;var a=(e,t)=>d(e,"name",{value:t,configurable:!0});import{prepared as r}from"../sqlite-statements.js";import{createHash as E}from"node:crypto";import{openScopedStore as p,withScopedWrite as l}from"./db.js";function _(e,t,n){const o=r(e,"SELECT kind, root, profile_id FROM scope_owners WHERE owner_key = ?").get(t.key);if(!o)throw new Error(`scoped SQL owner is missing: ${t.key}`);if(o.kind!==t.kind||(t.profileId??null)!==o.profile_id)throw new Error(`scoped SQL owner does not match its root: ${t.key}`);if(o.root!==t.root){if(t.kind!=="project"||t.store.kind!=="project"||t.store.root!==t.root)throw new Error(`scoped SQL owner does not match its root: ${t.key}`);n==="write"&&r(e,"UPDATE scope_owners SET root = ? WHERE owner_key = ?").run(t.root,t.key)}}a(_,"ensureOwner");function u(e){return e===void 0?"absent":E("sha256").update(e.raw_json).digest("hex")}a(u,"revisionFor");function k(e,t){return r(e,"SELECT raw_json FROM scope_settings WHERE owner_key = ?").get(t.key)}a(k,"readRawSettingsRow");class f extends Error{static{a(this,"SettingsRevisionConflictError")}constructor(){super("settings changed since the supplied revision"),this.name="SettingsRevisionConflictError"}}function g(e){const t=p(e.store);_(t,e,"read");const n=k(t,e);return{revision:u(n),settings:n===void 0?null:JSON.parse(n.raw_json)}}a(g,"readRawSettingsWithRevision");function R(e){return g(e).settings}a(R,"readRawSettings");function h(e,t,n,o){return l(e.store,c=>{_(c,e,"write");const s=k(c,e);if(n!==u(s))throw new f;o(t);const i=JSON.stringify(t);return s?.raw_json===i?{revision:u(s),changed:!1}:(r(c,"INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json").run(e.key,i),{revision:u({raw_json:i}),changed:!0})})}a(h,"replaceRawSettings");function m(e,t){l(e.store,n=>{_(n,e,"write"),r(n,"INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json").run(e.key,JSON.stringify(t))})}a(m,"writeRawSettings");function w(e,t){return l(e.store,n=>{_(n,e,"write");const o=r(n,"SELECT raw_json FROM scope_settings WHERE owner_key = ?").get(e.key),c=t(o?JSON.parse(o.raw_json):{});return r(n,"INSERT INTO scope_settings(owner_key, raw_json) VALUES (?, ?) ON CONFLICT(owner_key) DO UPDATE SET raw_json=excluded.raw_json").run(e.key,JSON.stringify(c.next)),c.value})}a(w,"mutateRawSettings");function v(e){const t=p(e.store);_(t,e,"read");const n=r(t,"SELECT last_self_check, active_canvas FROM scope_bookkeeping WHERE owner_key = ?").get(e.key),o=Object.fromEntries(r(t,"SELECT name, last_updated FROM scope_marketplaces WHERE owner_key = ?").all(e.key).map(s=>[s.name,s.last_updated===null?{}:{last_updated:s.last_updated}])),c=Object.fromEntries(r(t,"SELECT name, last_updated, etag, checked_at FROM scope_plugins WHERE owner_key = ?").all(e.key).map(s=>[s.name,{...s.last_updated===null?{}:{last_updated:s.last_updated},...s.etag===null?{}:{etag:s.etag},...s.checked_at===null?{}:{checked_at:s.checked_at}}]));return{marketplaces:o,plugins:c,...n?.last_self_check===null||n===void 0?{}:{last_self_check:n.last_self_check},activeCanvas:n?.active_canvas??null}}a(v,"readBookkeeping");function N(e,t){l(e.store,n=>{_(n,e,"write"),r(n,"INSERT INTO scope_bookkeeping(owner_key, last_self_check, active_canvas) VALUES (?, ?, ?) ON CONFLICT(owner_key) DO UPDATE SET last_self_check=excluded.last_self_check, active_canvas=excluded.active_canvas").run(e.key,t.last_self_check??null,t.activeCanvas??null),r(n,"DELETE FROM scope_marketplaces WHERE owner_key = ?").run(e.key),r(n,"DELETE FROM scope_plugins WHERE owner_key = ?").run(e.key);const o=r(n,"INSERT INTO scope_marketplaces(owner_key, name, last_updated) VALUES (?, ?, ?)");for(const[s,i]of Object.entries(t.marketplaces))o.run(e.key,s,i.last_updated??null);const c=r(n,"INSERT INTO scope_plugins(owner_key, name, last_updated, etag, checked_at) VALUES (?, ?, ?, ?, ?)");for(const[s,i]of Object.entries(t.plugins))c.run(e.key,s,i.last_updated??null,i.etag??null,i.checked_at??null)})}a(N,"writeBookkeeping");export{f as SettingsRevisionConflictError,w as mutateRawSettings,v as readBookkeeping,R as readRawSettings,g as readRawSettingsWithRevision,h as replaceRawSettings,N as writeBookkeeping,m as writeRawSettings};