@north-light/cli 0.3.549 → 0.3.551
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/custom-objects.d.ts +1 -1
- package/dist/api/dto/docs.d.ts +12 -0
- package/dist/api/dto/objects.d.ts +17 -1
- package/dist/api/dto/objects.js +1 -1
- package/dist/api/dto/permissions.d.ts +76 -0
- package/dist/api/dto/permissions.js +1 -0
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.js +1 -1
- package/dist/api/routes.d.ts +4 -0
- package/dist/api/routes.js +1 -1
- package/dist/builtin-memory/crouter-concepts/documents.md +1 -1
- package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +2 -2
- package/dist/builtin-memory/crouter-sdk/release-notes.md +11 -0
- package/dist/builtin-memory/internal/INDEX.md +2 -1
- package/dist/builtin-memory/internal/agent-shaping.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/northlight-apps/guides/files-and-documents.md +3 -3
- package/dist/builtin-memory/northlight-apps/guides/many-users.md +1 -1
- package/dist/builtin-memory/northlight-apps/guides/test-your-app.md +1 -1
- package/dist/clients/attach/viewer.js +9 -9
- package/dist/commands/canvas/list.js +2 -2
- package/dist/commands/canvas/share.d.ts +2 -0
- package/dist/commands/canvas/share.js +2 -0
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/doc/edit.js +1 -1
- package/dist/commands/doc/shared.js +1 -1
- package/dist/commands/doc/write.js +1 -1
- package/dist/commands/sys/sync-skills.js +2 -2
- package/dist/core/canvas/canvas.js +20 -20
- package/dist/core/canvas/db.js +4 -4
- package/dist/core/canvas/export-sqlite.js +2 -2
- package/dist/core/canvas/import-sqlite.js +5 -5
- package/dist/core/canvas/outcome-actions.js +3 -3
- package/dist/core/canvas/person.js +11 -9
- package/dist/core/canvas/schema-pg.sql +49 -5
- package/dist/core/environments.js +1 -1
- package/dist/core/feed/feed.js +1 -1
- package/dist/core/grants/mirror.d.ts +9 -1
- package/dist/core/grants/mirror.js +2 -2
- package/dist/core/grants/sync.js +1 -1
- package/dist/core/graph/access.d.ts +8 -49
- package/dist/core/graph/access.js +4 -7
- package/dist/core/graph/custom.d.ts +5 -2
- package/dist/core/graph/custom.js +9 -9
- package/dist/core/graph/deletion.js +5 -4
- package/dist/core/graph/documents.d.ts +12 -3
- package/dist/core/graph/documents.js +13 -7
- package/dist/core/graph/edges.js +3 -3
- package/dist/core/graph/events.js +12 -8
- package/dist/core/graph/kinds/document.js +4 -4
- package/dist/core/graph/kinds/kind.js +1 -1
- package/dist/core/graph/kinds/node.js +5 -5
- package/dist/core/graph/names.d.ts +16 -1
- package/dist/core/graph/names.js +11 -3
- package/dist/core/graph/objects.d.ts +19 -2
- package/dist/core/graph/objects.js +16 -10
- package/dist/core/graph/package-docs.js +5 -5
- package/dist/core/graph/package-files.js +2 -2
- package/dist/core/graph/reader.js +3 -3
- package/dist/core/graph/repo-sync/exchange.js +2 -2
- package/dist/core/graph/rules.d.ts +7 -5
- package/dist/core/graph/rules.js +3 -3
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/graph/types.js +1 -1
- package/dist/core/graph/watches.d.ts +1 -1
- package/dist/core/graph/watches.js +9 -8
- package/dist/core/peer/envelope.d.ts +1 -1
- package/dist/core/peer/envelope.js +1 -1
- package/dist/core/peer/stubs.d.ts +1 -1
- package/dist/core/peer/stubs.js +1 -1
- package/dist/core/permissions/boot.d.ts +17 -0
- package/dist/core/permissions/boot.js +18 -0
- package/dist/core/permissions/builtin.d.ts +8 -0
- package/dist/core/permissions/builtin.js +20 -0
- package/dist/core/permissions/ceiling.d.ts +57 -0
- package/dist/core/permissions/ceiling.js +22 -0
- package/dist/core/permissions/compile.d.ts +14 -0
- package/dist/core/permissions/compile.js +1 -0
- package/dist/core/permissions/errors.d.ts +7 -0
- package/dist/core/permissions/errors.js +1 -0
- package/dist/core/permissions/evaluator.d.ts +28 -0
- package/dist/core/permissions/evaluator.js +6 -0
- package/dist/core/permissions/facts.d.ts +56 -0
- package/dist/core/permissions/facts.js +2 -0
- package/dist/core/permissions/schema-parser.d.ts +29 -0
- package/dist/core/permissions/schema-parser.js +2 -0
- package/dist/core/permissions/schemas.d.ts +21 -0
- package/dist/core/permissions/schemas.js +2 -0
- package/dist/core/permissions/sql.d.ts +16 -0
- package/dist/core/permissions/sql.js +1 -0
- package/dist/core/runtime/broker/frame-client.d.ts +1 -1
- package/dist/core/runtime/broker/frame-client.js +1 -1
- package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +1 -1
- package/dist/core/runtime/broker/frame-read.d.ts +4 -3
- package/dist/core/runtime/broker/frame-read.js +1 -1
- package/dist/core/runtime/broker-protocol.d.ts +1 -1
- package/dist/core/runtime/host.js +3 -3
- package/dist/core/runtime/placement.js +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/substrate/delivery/corpus.d.ts +2 -2
- package/dist/core/substrate/delivery/corpus.js +3 -3
- package/dist/core/substrate/delivery/deliver.js +2 -2
- package/dist/core/substrate/delivery/render-event.js +6 -6
- package/dist/core/substrate/subject-fields.d.ts +2 -0
- package/dist/core/substrate/subject.js +1 -1
- package/dist/daemon/api/app-listener.js +12 -12
- package/dist/daemon/api/bridge.d.ts +1 -1
- package/dist/daemon/api/bridge.js +4 -4
- package/dist/daemon/api/handlers/attach.d.ts +4 -1
- package/dist/daemon/api/handlers/attach.js +1 -1
- package/dist/daemon/api/handlers/canvas.js +5 -5
- package/dist/daemon/api/handlers/check.d.ts +2 -0
- package/dist/daemon/api/handlers/check.js +1 -0
- package/dist/daemon/api/handlers/custom-objects.js +2 -2
- package/dist/daemon/api/handlers/docs.js +1 -1
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/memory.js +7 -6
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/objects.js +3 -3
- package/dist/daemon/api/handlers/relations.d.ts +2 -0
- package/dist/daemon/api/handlers/relations.js +1 -0
- package/dist/daemon/api/handlers/schemas.d.ts +12 -0
- package/dist/daemon/api/handlers/schemas.js +1 -0
- package/dist/daemon/api/handlers/shares.d.ts +2 -0
- package/dist/daemon/api/handlers/shares.js +1 -0
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +10 -6
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/crtrd.js +12 -12
- package/dist/daemon/reconcilers/delivery-lane.js +2 -2
- package/dist/doc-editor/editor.css +1 -1
- package/dist/doc-editor/editor.js +16 -16
- package/dist/migrations/004-canvas-documents/fields.d.ts +1 -1
- package/dist/migrations/004-canvas-documents/fields.js +2 -2
- package/docs/apps/guides/files-and-documents.md +3 -3
- package/docs/apps/guides/many-users.md +1 -1
- package/docs/apps/guides/test-your-app.md +1 -1
- package/docs/cli/documents-and-preferences.md +1 -1
- package/docs/concepts/documents.md +1 -1
- package/docs/concepts/scopes-and-trust.md +2 -2
- package/docs/sdk/release-notes.md +11 -0
- package/package.json +2 -2
- package/packages/crouter-identity/dist/index.js +14 -1
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
- package/dist/core/graph/spaces/app.d.ts +0 -3
- package/dist/core/graph/spaces/app.js +0 -1
- package/dist/core/graph/spaces/index.d.ts +0 -18
- package/dist/core/graph/spaces/index.js +0 -1
- package/dist/core/graph/spaces/own-memory.d.ts +0 -18
- package/dist/core/graph/spaces/own-memory.js +0 -1
- package/dist/core/graph/spaces/person.d.ts +0 -5
- package/dist/core/graph/spaces/person.js +0 -3
- package/dist/core/graph/spaces/repo.d.ts +0 -14
- package/dist/core/graph/spaces/repo.js +0 -1
- package/dist/core/runs/confinement.d.ts +0 -25
- package/dist/core/runs/confinement.js +0 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
${r}${e.summary?` \u2014 ${e.summary}`:""}`}s(y,"listItemRow");const v=
|
|
1
|
+
var h=Object.defineProperty;var s=(e,n)=>h(e,"name",{value:n,configurable:!0});import{defineLeaf as m}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 n=e.machine?`${l(e)} machine=${e.machine.name}`:l(e);if(e.type!=="document")return n;const r=[`privacy=${e.privacy}`,e.rules!==void 0?`rules=${e.rules}`:""].filter(Boolean).join(" ");return`${n}
|
|
2
|
+
${r}${e.summary?` \u2014 ${e.summary}`:""}`}s(y,"listItemRow");const v=m({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:"shared",type:"bool",required:!1,constraint:"When present, lists only objects you reach through a share from another person, not through your own objects."},{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 n={...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.shared===!0?{shared:!0}:{},...e.machine?{machine:e.machine}:{},limit:e.limit??50,...e.cursor?{cursor:e.cursor}:{}};try{return{...await f().objects.list(n)}}catch(r){p(r)}},"run"),render:s(e=>{const{items:n,next_cursor:r,machines:o}=e,i=o?.length===1?o[0].machine_id:null,a=n.length===0?["No objects match."]:n.map(y),c=(o??[]).filter(t=>t.state!=="ok"),d=(o??[]).filter(t=>t.state==="ok"&&t.more&&t.machine_id!==o[0].machine_id&&i===null);(c.length>0||d.length>0)&&a.push("");for(const t of c)a.push(t.state==="too_old"?`Not listed: ${t.name} (${t.machine_id}) runs a crtr too old to take part; update it with \`crtr sys update\` there.`:`Not listed: ${t.name} (${t.machine_id}) is unreachable, so its objects are missing from this list.`);for(const t of d)a.push(`${t.name} has more: \`crtr canvas list --machine ${t.machine_id} \u2026\`.`);return r&&a.push("",`More: \`crtr canvas list \u2026${i===null?"":` --machine ${i}`} --cursor ${r}\`.`),a.join(`
|
|
3
3
|
`)},"render")});export{y as listItemRow,v as listLeaf};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var l=Object.defineProperty;var a=(e,t)=>l(e,"name",{value:t,configurable:!0});import{defineLeaf as f}from"../../core/command.js";import{usage as i}from"../../core/errors.js";import{SHARE_VERBS as d}from"../../api/dto/permissions.js";import{routes as c}from"../../api/routes.js";import{cliClient as h,rethrowAsCliError as u}from"../api-client.js";import{REF_CONSTRAINT as v}from"./read.js";function j(e){if(e==="everyone")return"user:*";if(!/^(user|team):[^\s:*]+$/.test(e))throw i(`\`${e}\` is not a subject`,{field:"subject",received:e,next:"Use `user:<id>`, `team:<id>` or `everyone`."});return e}a(j,"subjectOf");const q=f({name:"share",description:"share an object you own with a person, a team or everyone, take the share back, or list who it is shared with",whenToUse:"another person (or a directory team, or everyone) should read, edit, message or use something you own \u2014 a document, a node, an environment. Only the owner and the owner\u2019s agents can share; someone a thing was shared with cannot pass it on.",help:{name:"canvas share",summary:"give a person, team or everyone one permission on an object; `--revoke` removes it; with only the object, lists its shares",params:[{kind:"positional",name:"object",required:!0,constraint:v},{kind:"positional",name:"verb",required:!1,constraint:"Omit with the subject to list the object\u2019s shares. `read` (any object), `edit` (documents), `message` (nodes and profiles: attach, drive, interrupt and read in full; the owner pays for the turns), `use` (environments)."},{kind:"positional",name:"subject",required:!1,constraint:"`user:<id>`, `team:<id>` (a directory team) or `everyone`. `everyone` takes only `read` and needs an install that has joined the directory."},{kind:"flag",name:"revoke",type:"bool",required:!1,constraint:"Remove the share instead of adding it. Takes effect on the next check: a connected viewer is closed and watchers stop receiving events."}],output:[{name:"result",type:"string",required:!0,constraint:"What was shared or revoked, or that it already held; or one line per share (verb, subject, who wrote it, when) for the list."}],outputKind:"object",effects:["With a verb and subject, writes or deletes one permission fact on the object; it applies to everything the object owns, and the object publishes `relation.written` or `relation.deleted`."]},run:a(async e=>{const t=e.object,r=e.verb,s=e.subject;if(r===void 0||s===void 0){if(r!==void 0||e.revoke===!0)throw i("`share` needs a verb and a subject",{field:"subject",next:"Give `<object> <verb> <subject>`, or only `<object>` to list its shares."});try{return{list:!0,...await h().request("GET",c.objectShares(t))}}catch(n){u(n)}}if(!d.includes(r))throw i(`\`${r}\` is not a share verb`,{field:"verb",received:r,next:`Use one of ${d.join(", ")}.`});const o=j(s);try{const n=h();if(e.revoke===!0){const b=new URLSearchParams({verb:r,subject:o});return{revoked:!0,...await n.request("DELETE",`${c.objectShares(t)}?${b}`)}}const m={verb:r,subject:o};return{revoked:!1,...await n.request("POST",c.objectShares(t),m)}}catch(n){u(n)}},"run"),render:a(e=>{if(e.list===!0){const o=e;return o.items.length===0?`[[${o.name}]] is shared with no one.`:[`[[${o.name}]] is shared with:`,...o.items.map(n=>`- ${n.subject==="user:*"?"everyone":n.subject} can ${n.verb} (written by ${n.written_by}, ${n.created_at})`)].join(`
|
|
2
|
+
`)}const t=e,r=t.subject==="user:*"?"everyone":t.subject,s=Object.entries({reader:"read",editor:"edit",messenger:"message",user_of:"use"}).find(([o])=>o===t.relation)?.[1]??t.relation;return t.revoked?t.changed?`Revoked ${s} on [[${t.name}]] from ${r}.`:`${r} held no ${s} share on [[${t.name}]].`:t.changed?`Shared [[${t.name}]]: ${r} can ${s} it.`:`${r} could already ${s} [[${t.name}]].`},"render")});export{q as shareLeaf};
|
package/dist/commands/canvas.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var r=Object.defineProperty;var e=(t,o)=>r(t,"name",{value:o,configurable:!0});import{defineBranch as a}from"../core/command.js";import{dashboardLeaf as n}from"./dashboard.js";import{canvasSnapshotLeaf as s}from"./canvas-snapshot.js";import{canvasRosterLeaf as i}from"./canvas-roster.js";import{browseLeaf as d}from"./canvas-browse.js";import{reviveLeaf as c}from"./revive.js";import{attentionBranch as h}from"./attention.js";import{chordLeaf as m}from"./chord.js";import{canvasPruneLeaf as p}from"./canvas-prune.js";import{historyBranch as u}from"./canvas-history.js";import{issueNewLeaf as f}from"./canvas-issue.js";import{canvasConfigBranch as l}from"./canvas-config.js";import{canvasUseLeaf as g}from"./canvas-use.js";import{readLeaf as w}from"./canvas/read.js";import{listLeaf as v}from"./canvas/list.js";import{searchLeaf as b}from"./canvas/search.js";import{watchLeaf as y}from"./canvas/watch.js";import{unwatchLeaf as L}from"./canvas/unwatch.js";import{
|
|
1
|
+
var r=Object.defineProperty;var e=(t,o)=>r(t,"name",{value:o,configurable:!0});import{defineBranch as a}from"../core/command.js";import{dashboardLeaf as n}from"./dashboard.js";import{canvasSnapshotLeaf as s}from"./canvas-snapshot.js";import{canvasRosterLeaf as i}from"./canvas-roster.js";import{browseLeaf as d}from"./canvas-browse.js";import{reviveLeaf as c}from"./revive.js";import{attentionBranch as h}from"./attention.js";import{chordLeaf as m}from"./chord.js";import{canvasPruneLeaf as p}from"./canvas-prune.js";import{historyBranch as u}from"./canvas-history.js";import{issueNewLeaf as f}from"./canvas-issue.js";import{canvasConfigBranch as l}from"./canvas-config.js";import{canvasUseLeaf as g}from"./canvas-use.js";import{readLeaf as w}from"./canvas/read.js";import{listLeaf as v}from"./canvas/list.js";import{searchLeaf as b}from"./canvas/search.js";import{watchLeaf as y}from"./canvas/watch.js";import{unwatchLeaf as L}from"./canvas/unwatch.js";import{shareLeaf as k}from"./canvas/share.js";import{edgesLeaf as j}from"./canvas/edges.js";import{analyticsLeaf as x}from"./canvas-analytics.js";const B=a({name:"issue",description:"capture a quick todo/idea/bug into a GitHub issue (or a .todos/ note)",whenToUse:"the alt+c \u2192 i chord uses this; capture a passing thought without leaving your pane",tier:"hidden",help:{name:"canvas issue",summary:"capture a quick todo/idea/bug into a GitHub issue",model:"Quick brain-dump capture, bound to the alt+c \u2192 i \u2192 (idea|bug) prefix chord. `new` reads a free-text note, auto-generates the title via a headless pi call, and files it with `gh issue create --label <type>` when the pane\u2019s cwd is a GitHub repo, else writes `<git-root>/.todos/<slug>.md`. Meant for the chord/popup, not hand-running."},children:[f]});function V(){return a({name:"canvas",rootEntry:{concept:"every object in the agent graph \u2014 documents, nodes, jobs and their owners \u2014 read, listed, searched and watched by name, plus the whole-graph views: `dashboard` prints the subscription tree once, `browse` is the interactive navigator, `attention` shows who is blocked, `revive --all` mass-revives disconnected nodes",desc:"read, list, search and watch objects; whole-graph views and supervision",useWhen:"reading a document, node or job by name, finding one you cannot name, watching an object for changes, surveying the full graph, finding blocked agents, or mass-reviving every disconnected node; documents are written with `crtr doc`; run `crtr canvas -h`"},help:{name:"canvas",summary:"read, find and watch objects; observe and supervise the whole agent graph",model:"Every document, node, bash job and owner (person, app, repo, plugin, profile) is an object with an id and a name, and the same verbs work on all of them. `read` returns one object by name or id and makes you a watcher of it; `list` filters objects by type, kind, owner, privacy or name prefix; `search` finds them by text; `edges` shows what an object links to, who acted on it and who watches it; `watch`/`unwatch` add or remove your watch without reading. `list`, `search` and `edges` record nothing. Documents are written, edited and moved with `crtr doc`, and pointed at as `[[name]]`. Canvas-wide views, distinct from per-node work (`node`) and a node's own spine write verb (`push`): `dashboard` renders the subscription forest as a tree; `browse` opens an interactive full-screen navigator over the whole canvas and resumes the chosen node; `attention` aggregates pending replies from the user across the graph; `revive --all` mass-reconnects every disconnected node (bringing back ONE named node is `node lifecycle revive <id>` instead); `history` searches past node conversations; `prune` bounds growth by deleting terminal nodes past a TTL; `analytics` opens the read-only Analytics page (graph sizes, agent time, document reads, API-price estimate) for a person. `config` registers/lists remote canvas targets (preview endpoint + relay token); `use` switches remote-capable read commands such as `node inspect` onto a configured target or back to local."},children:[w,v,b,y,L,k,j,n,s,i,d,h,c,u,m,B,p,l,g,x]})}e(V,"registerCanvas");export{B as issueBranch,V as registerCanvas};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var h=Object.defineProperty;var
|
|
1
|
+
var h=Object.defineProperty;var s=(e,o)=>h(e,"name",{value:o,configurable:!0});import{defineLeaf as m}from"../../core/command.js";import{usage as d}from"../../core/errors.js";import{DOC_UNSET_FIELDS as u}from"../../api/dto/docs.js";import{cliClient as f}from"../api-client.js";import{parseReplaceBlocks as p}from"./replace-blocks.js";import{FIELD_FLAGS as t,REF_CONSTRAINT as y,fieldsFromInput as b,rethrowDocError as w,renderLinkReport as v,ruleParam as g}from"./shared.js";const q=m({name:"edit",description:"revise a document you have read",whenToUse:"a document you read needs a change to its body or fields",help:{name:"doc edit",summary:"revise a document you have read",guide:"The edit is made from the revision you last read (`crtr canvas read`); pass no revision. If someone edited it after your read, the edit is refused and nothing is overwritten. For a partial edit, pipe one or more literal blocks on stdin with --blocks: <<<<<<< SEARCH, old text, =======, new text, >>>>>>> REPLACE (markers on their own lines). Each SEARCH must match exactly once; empty REPLACE deletes text. Alternatively pipe a whole new body without --blocks, or use --replace JSON for scripts. Choose one mode. Pipe text straight in with a quoted heredoc (`<<'EOF'`); an unquoted one lets the shell expand `$` and backticks in the text, and the mangled text is what gets saved. A field flag replaces that field; omitted fields stay as they are. A change to fields alone (preview, summary, privacy, rules, why-it-exists, \u2026) takes effect at once and records a new revision with the body unchanged, attributed to you with the rationale, so `crtr doc history` lists it and the revision number advances. A caller that read the document earlier needs a fresh read before its next body edit.",params:[{kind:"positional",name:"ref",required:!0,constraint:y},{kind:"flag",name:"rationale",type:"string",required:!0,constraint:"Why this revision is made; recorded on the revision, never in the body. A fields-only edit records its revision with the same body."},{kind:"stdin",name:"body",required:!1,constraint:"Whole new body by default; with --blocks, exact SEARCH/REPLACE blocks."},{kind:"flag",name:"blocks",type:"bool",required:!1,constraint:"Interpret stdin as one or more whole-line <<<<<<< SEARCH / ======= / >>>>>>> REPLACE blocks; empty SEARCH is invalid, empty REPLACE deletes."},{kind:"flag",name:"replace",type:"string",required:!1,constraint:'JSON list `[{"old": "\u2026", "new": "\u2026"}]` of exact-text replacements; each `old` must match exactly once in the body you read.'},{kind:"flag",name:"verbatim",type:"bool",required:!1,constraint:"The body is the person's own text relayed unchanged; recorded on the revision."},t.preview("New one-line preview."),t.summary,t.privacy("`public` or `private`. A private document is kept from apps without a private-read scope; the owner and anyone it is shared with still read it."),g("Replaces the whole rule list."),t.whyItExists,t.unlisted("Sets unlisted; remove it with `--unset unlisted`."),t.lintIgnore,t.extension,{kind:"flag",name:"unset",type:"enum",choices:[...u],required:!1,repeatable:!0,constraint:"Clear one optional field."}],output:[{name:"revision",type:"number",required:!0,constraint:"The new revision number; a fields-only edit advances it too."},{name:"unresolved_links",type:"string[]",required:!1,constraint:"`[[links]]` in the new body that name no document yet."},{name:"remote_links",type:"string[]",required:!1,constraint:"`[[links]]` whose first segment is no owner on this runtime: on another runtime, or misspelled."}],outputKind:"object",effects:["Commits a new revision based on your last read and pushes the edit to the document's watchers."]},run:s(async e=>{const o=e.ref;let n=typeof e.body=="string"&&e.body!==""?e.body:void 0,i;if(e.blocks===!0){if(e.replace!==void 0)throw d("choose --blocks or --replace, not both",{field:"blocks"});i=p(n??""),n=void 0}if(typeof e.replace=="string"){if(n!==void 0)throw d("give a stdin body or --replace, not both",{field:"replace",next:"Drop one of them and rerun."});try{const r=JSON.parse(e.replace);if(!Array.isArray(r)||r.length===0||r.some(a=>typeof a!="object"||a===null||typeof a.old!="string"||typeof a.new!="string"))throw new Error;i=r}catch{throw d('--replace must be a non-empty JSON list of {"old": string, "new": string}',{field:"replace",next:"Fix the JSON and rerun."})}}const l=e.unset===void 0?[]:[].concat(e.unset),c={rationale:e.rationale,...n!==void 0?{body:n}:{},...i!==void 0?{replace:i}:{},...e.verbatim===!0?{verbatim:!0}:{},...b(e),...l.length>0?{unset:l}:{}};try{return{...await f().docs.edit(o,c)}}catch(r){w(r,o)}},"run"),render:s(e=>`<edited name="${String(e.name)}" revision=${String(e.revision)}>${v(e)}`,"render")});export{q as editLeaf};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var f=Object.defineProperty;var o=(e,t)=>f(e,"name",{value:t,configurable:!0});import{usage as m}from"../../core/errors.js";import{ApiError as w}from"../../api/errors.js";import{rethrowAsCliError as h}from"../api-client.js";import{RULE_ON as g,RULE_DELIVER as v,DOC_KINDS as y}from"../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as b}from"../../api/dto/objects.js";const q="A document name (with or without [[ ]]) or a raw object id.",p="node | user | profile | profile:<name> | app | repo | repo:<handle>";function O(e){return{kind:"flag",name:"owner",type:"string",required:!1,constraint:e,focusedHelp:{whenToUse:"Omit it for a document only this node and the nodes it points at need: the writing node owns it and it is deleted with the node, unless a kept document links to it. Give another owner when the document must outlive this node or reach other nodes by itself. A document worth another owner carries reusable current truth: a non-obvious procedure, a gotcha, a lasting preference, a convention across repos. Task notes, conversation summaries and facts the repo already records stay with the node or are not written. Before writing one, search for a document that already covers it (`crtr canvas search`) and revise that one with `crtr doc edit` instead of adding a near copy. When one document grows long, keep the most important content in it and move separate subjects into documents named under it (`topic/part`), each pointed at with a `[[link]]`. Choose the narrowest owner that still reaches the next agent who needs it.",value:`One of ${p}. \`node\` \u2014 the writing node; only this node loads it by rule, other nodes read it when pointed at it; deleted with the node. \`profile\` \u2014 the node's profile, or \`profile:<name>\`; loads for nodes running in that profile; stays on this runtime. \`app\` \u2014 the app that runs the node; kept until deleted; readable by that app's nodes. \`repo\` \u2014 the repo the node works in (from its working directory), or \`repo:<handle>\` for another repo whose checkout the node's file permissions let it write (such as a project of its profile); every clone and worktree of the repo shares it, and it syncs to collaborators over the repo's \`refs/crtr/docs\`. A repo owns only public documents: a private or friends document that should load in one repo is owned by \`user\` and carries a rule with \`if: {"repo": "<handle>"}\`. \`user\` \u2014 you, the person; loads for every node by its rules; readable by apps by privacy level. Default: the writing node; a person's own run (not a node) defaults to \`user\`. The owner becomes the start of the document's full name (\`<owner-handle>/<name>\`); \`crtr doc move --owner\` changes it later and keeps the id and every link.`,effects:["Decides who may read and change the document, where it syncs, and what deleting takes it with."]}}}o(O,"ownerParam");const k="`if` is a condition on the receiving node. Fields: `kind`, `mode`, `lifecycle`, `cwd`, `repo` (the handle of a repo the node is eligible for: those enclosing its cwd and those of its profile's projects; use `cwd` for where it works), `profile`, `depth`. A field map is ANDed across fields; dotted fields resolve nested values. A scalar matches by equality (a list value matches when any element is equal); a list matcher matches by membership or overlap; an object matcher takes `eq`, `ne`, `in`, `nin`, `exists`, `contains`, `containsAll`, `containsAny`, `matches`, `imatches`, `gt`, `gte`, `lt`, `lte`. `all`, `any` and `not` combine conditions; field matchers beside them are ANDed in. An empty condition always matches; an unknown operator never matches. `match_frontmatter` uses the same language against the read file's YAML frontmatter.";function S(e){return{kind:"flag",name:"rule",type:"string",required:!1,repeatable:!0,constraint:e,focusedHelp:{whenToUse:"Omit it for most documents: a document with no rule is read when a node is pointed at it (`[[name]]`), finds it with `crtr canvas search`, or sees it in a listing. Add a rule only when the document must load on its own because of what a node is doing; every rule costs context in every node it fires on. Prefer the narrowest trigger: a `file-read` rule with a real glob over `boot` for knowledge about specific code; `boot` for knowledge about a person or process with no file boundary. When the whole document is one sentence, deliver `content` \u2014 a preview line would be longer than the document.",value:`One JSON object per occurrence: \`{"on": \u2026, "match"?: [globs], "match_frontmatter"?: {\u2026}, "if"?: {\u2026}, "deliver": \u2026}\`. \`on\` is the node's activity: ${g.join(" | ")}. \`boot\` \u2014 every session start of the node. \`workspace-open\` \u2014 the node's first message when it works in the document's repo. \`file-read\` \u2014 the node reads the content of a file whose absolute path or basename matches a \`match\` glob, or whose frontmatter matches \`match_frontmatter\`; through the Read tool, or a bash segment of \`cat\`, \`sed\` (not \`-i\`), \`head\`, \`tail\` (not \`-f\`), \`nl\` or \`awk\` on a literal path (resolved after the command's own \`cd\`s; \`rg\`, \`grep\`, \`find\`, \`ls\`, \`wc\`, \`stat\` are searches, not reads, and never fire it). \`document-read\` \u2014 another document whose name matches is delivered at content, including an explicit read. \`command\` \u2014 a shell segment of a bash command matches (segments split on \`&&\`, \`||\`, \`;\`, \`|\` and newlines outside quotes, leading \`VAR=value\` dropped; \`*\` spans whole whitespace-separated tokens, \`?\` one character, quoted text is opaque). \`pre-command\` \u2014 as \`command\`, but runs before the command: when the node has not read the document, the command does not run and the document is returned instead; the node re-issues the command. \`slash-command\` \u2014 the document is a \`/<name>\` slash command whose body is the injected prompt. \`match\` is required on \`file-read\` (unless \`match_frontmatter\` is given), \`document-read\`, \`command\` and \`pre-command\`, and not allowed on \`boot\`, \`workspace-open\` and \`slash-command\`. `+k+` \`deliver\` is ${v.join(" | ")}: the bare name, the preview line, or the whole body. The flag set replaces the document's whole rule list; on \`crtr doc edit\`, \`--unset rules\` removes every rule.`,effects:["When several rules deliver one document to a node, the highest delivery wins: content over preview over name; content already in the node's context is not delivered again.","A document delivered at content is a read: it records the node as a watcher, so the node is pushed later edits.","The profile's delivery limit per owner caps what `boot` and `workspace-open` rules deliver for that owner.",'A rule that delivers at `preview` requires the preview in the shape "When <situation in the reader\'s work>, read this because <what the reader gains>"; `crtr doc lint` checks it. There the line alone decides whether a node that did not ask for the document opens it.']}}}o(S,"ruleParam");const N={preview:o(e=>({kind:"flag",name:"preview",type:"string",required:!1,constraint:e}),"preview"),kind:{kind:"flag",name:"kind",type:"enum",choices:[...y],required:!1,constraint:"knowledge is consulted for facts or procedures; preference directs behavior. Default knowledge."},summary:{kind:"flag",name:"summary",type:"string",required:!1,constraint:"A very short version of the content, shown in `crtr canvas list`."},privacy:o(e=>({kind:"flag",name:"privacy",type:"enum",choices:[...b],required:!1,constraint:e}),"privacy"),whyItExists:{kind:"flag",name:"why-it-exists",type:"string",required:!1,constraint:"The observed failure this document exists to prevent; shown only with `crtr canvas read --fields`."},unlisted:o(e=>({kind:"flag",name:"unlisted",type:"bool",required:!1,constraint:e}),"unlisted"),lintIgnore:{kind:"flag",name:"lint-ignore",type:"string",required:!1,repeatable:!0,constraint:"A `crtr doc lint` rule name to skip for this document."},extension:{kind:"flag",name:"extension",type:"string",required:!1,repeatable:!0,constraint:"One declared plugin field as `<plugin>.<field>=<json value>`."}};function c(e){return e===void 0?[]:Array.isArray(e)?e.map(String):[String(e)]}o(c,"list");function T(e){const t=[],n={};for(const[r,a]of[["preview","preview"],["kind","kind"],["summary","summary"],["privacy","privacy"],["whyItExists","why_it_exists"]])typeof e[r]=="string"&&(n[a]=e[r]);typeof n.preview=="string"&&/[\r\n]/.test(n.preview)&&t.push("--preview must be one line"),e.unlisted===!0&&(n.unlisted=!0);const i=c(e.lintIgnore);i.length>0&&(n.lint_ignore=i);const s=c(e.rule);if(s.length>0){const r=[];s.forEach((a,u)=>{try{const d=JSON.parse(a);typeof d!="object"||d===null||Array.isArray(d)?t.push(`--rule #${u+1} must be a JSON object`):r.push(d)}catch{t.push(`--rule #${u+1} is not valid JSON: ${a}`)}}),n.rules=r}const l={};for(const r of c(e.extension)){const a=/^([^.=]+)\.([^=]+)=(.*)$/s.exec(r);if(!a){t.push(`--extension ${r} is not <plugin>.<field>=<json value>`);continue}try{(l[a[1]]??={})[a[2]]=JSON.parse(a[3])}catch{t.push(`--extension ${r}: the value after = is not valid JSON`)}}if(Object.keys(l).length>0&&(n.extensions=l),t.length>0)throw m(t.join("; "),{next:"Fix every value above and rerun; `--rule -h` gives the rule grammar."});return n}o(T,"fieldsFromInput");function R(e){const t=e.owner;if(t===void 0)return{};const n=String(t);if(!/^(node|user|profile|app|repo)$|^(profile|repo):.+$/.test(n))throw m(`invalid --owner ${n}`,{field:"owner",next:`Pass one of ${p}.`});return{owner:n}}o(R,"ownerFromInput");function D(e,t){e instanceof w&&(e.code==="read_first"&&h(e,`Read it first with \`crtr canvas read ${t}\`, then rerun the edit.`),e.code==="stale_revision"&&h(e,`Someone edited it after your read. Read it again with \`crtr canvas read ${t}\`, make your change on the current text, and rerun the edit.`)),h(e)}o(D,"rethrowDocError");function I(e){const t=[],n=e.unresolved_links,i=e.remote_links;return n?.length&&t.push(`Unresolved links (no document by these names yet): ${n.map(s=>`[[${s}]]`).join(", ")}`),i?.length&&t.push(`Not on this runtime: ${i.map(s=>`[[${s}]]`).join(", ")} \u2014 each may be on another runtime, or the name may be misspelled.`),t.map(s=>`
|
|
1
|
+
var f=Object.defineProperty;var o=(e,t)=>f(e,"name",{value:t,configurable:!0});import{usage as m}from"../../core/errors.js";import{ApiError as w}from"../../api/errors.js";import{rethrowAsCliError as h}from"../api-client.js";import{RULE_ON as g,RULE_DELIVER as v,DOC_KINDS as y}from"../../api/dto/docs.js";import{PRIVACY_LEVELS_DTO as b}from"../../api/dto/objects.js";const q="A document name (with or without [[ ]]) or a raw object id.",p="node | user | profile | profile:<name> | app | repo | repo:<handle>";function O(e){return{kind:"flag",name:"owner",type:"string",required:!1,constraint:e,focusedHelp:{whenToUse:"Omit it for a document only this node and the nodes it points at need: the writing node owns it and it is deleted with the node, unless a kept document links to it. Give another owner when the document must outlive this node or reach other nodes by itself. A document worth another owner carries reusable current truth: a non-obvious procedure, a gotcha, a lasting preference, a convention across repos. Task notes, conversation summaries and facts the repo already records stay with the node or are not written. Before writing one, search for a document that already covers it (`crtr canvas search`) and revise that one with `crtr doc edit` instead of adding a near copy. When one document grows long, keep the most important content in it and move separate subjects into documents named under it (`topic/part`), each pointed at with a `[[link]]`. Choose the narrowest owner that still reaches the next agent who needs it.",value:`One of ${p}. \`node\` \u2014 the writing node; only this node loads it by rule, other nodes read it when pointed at it; deleted with the node. \`profile\` \u2014 the node's profile, or \`profile:<name>\`; loads for nodes running in that profile; stays on this runtime. \`app\` \u2014 the app that runs the node; kept until deleted; readable by that app's nodes. \`repo\` \u2014 the repo the node works in (from its working directory), or \`repo:<handle>\` for another repo whose checkout the node's file permissions let it write (such as a project of its profile); every clone and worktree of the repo shares it, and it syncs to collaborators over the repo's \`refs/crtr/docs\`. A repo owns only public documents: a private document that should load in one repo is owned by \`user\` and carries a rule with \`if: {"repo": "<handle>"}\`. \`user\` \u2014 you, the person; loads for every node by its rules; readable by apps by privacy level. Default: the writing node; a person's own run (not a node) defaults to \`user\`. The owner becomes the start of the document's full name (\`<owner-handle>/<name>\`); \`crtr doc move --owner\` changes it later and keeps the id and every link.`,effects:["Decides who may read and change the document, where it syncs, and what deleting takes it with."]}}}o(O,"ownerParam");const k="`if` is a condition on the receiving node. Fields: `kind`, `mode`, `lifecycle`, `cwd`, `repo` (the handle of a repo the node is eligible for: those enclosing its cwd and those of its profile's projects; use `cwd` for where it works), `profile`, `depth`. A field map is ANDed across fields; dotted fields resolve nested values. A scalar matches by equality (a list value matches when any element is equal); a list matcher matches by membership or overlap; an object matcher takes `eq`, `ne`, `in`, `nin`, `exists`, `contains`, `containsAll`, `containsAny`, `matches`, `imatches`, `gt`, `gte`, `lt`, `lte`. `all`, `any` and `not` combine conditions; field matchers beside them are ANDed in. An empty condition always matches; an unknown operator never matches. `match_frontmatter` uses the same language against the read file's YAML frontmatter.";function S(e){return{kind:"flag",name:"rule",type:"string",required:!1,repeatable:!0,constraint:e,focusedHelp:{whenToUse:"Omit it for most documents: a document with no rule is read when a node is pointed at it (`[[name]]`), finds it with `crtr canvas search`, or sees it in a listing. Add a rule only when the document must load on its own because of what a node is doing; every rule costs context in every node it fires on. Prefer the narrowest trigger: a `file-read` rule with a real glob over `boot` for knowledge about specific code; `boot` for knowledge about a person or process with no file boundary. When the whole document is one sentence, deliver `content` \u2014 a preview line would be longer than the document.",value:`One JSON object per occurrence: \`{"on": \u2026, "match"?: [globs], "match_frontmatter"?: {\u2026}, "if"?: {\u2026}, "deliver": \u2026}\`. \`on\` is the node's activity: ${g.join(" | ")}. \`boot\` \u2014 every session start of the node. \`workspace-open\` \u2014 the node's first message when it works in the document's repo. \`file-read\` \u2014 the node reads the content of a file whose absolute path or basename matches a \`match\` glob, or whose frontmatter matches \`match_frontmatter\`; through the Read tool, or a bash segment of \`cat\`, \`sed\` (not \`-i\`), \`head\`, \`tail\` (not \`-f\`), \`nl\` or \`awk\` on a literal path (resolved after the command's own \`cd\`s; \`rg\`, \`grep\`, \`find\`, \`ls\`, \`wc\`, \`stat\` are searches, not reads, and never fire it). \`document-read\` \u2014 another document whose name matches is delivered at content, including an explicit read. \`command\` \u2014 a shell segment of a bash command matches (segments split on \`&&\`, \`||\`, \`;\`, \`|\` and newlines outside quotes, leading \`VAR=value\` dropped; \`*\` spans whole whitespace-separated tokens, \`?\` one character, quoted text is opaque). \`pre-command\` \u2014 as \`command\`, but runs before the command: when the node has not read the document, the command does not run and the document is returned instead; the node re-issues the command. \`slash-command\` \u2014 the document is a \`/<name>\` slash command whose body is the injected prompt. \`match\` is required on \`file-read\` (unless \`match_frontmatter\` is given), \`document-read\`, \`command\` and \`pre-command\`, and not allowed on \`boot\`, \`workspace-open\` and \`slash-command\`. `+k+` \`deliver\` is ${v.join(" | ")}: the bare name, the preview line, or the whole body. The flag set replaces the document's whole rule list; on \`crtr doc edit\`, \`--unset rules\` removes every rule.`,effects:["When several rules deliver one document to a node, the highest delivery wins: content over preview over name; content already in the node's context is not delivered again.","A document delivered at content is a read: it records the node as a watcher, so the node is pushed later edits.","The profile's delivery limit per owner caps what `boot` and `workspace-open` rules deliver for that owner.",'A rule that delivers at `preview` requires the preview in the shape "When <situation in the reader\'s work>, read this because <what the reader gains>"; `crtr doc lint` checks it. There the line alone decides whether a node that did not ask for the document opens it.']}}}o(S,"ruleParam");const N={preview:o(e=>({kind:"flag",name:"preview",type:"string",required:!1,constraint:e}),"preview"),kind:{kind:"flag",name:"kind",type:"enum",choices:[...y],required:!1,constraint:"knowledge is consulted for facts or procedures; preference directs behavior. Default knowledge."},summary:{kind:"flag",name:"summary",type:"string",required:!1,constraint:"A very short version of the content, shown in `crtr canvas list`."},privacy:o(e=>({kind:"flag",name:"privacy",type:"enum",choices:[...b],required:!1,constraint:e}),"privacy"),whyItExists:{kind:"flag",name:"why-it-exists",type:"string",required:!1,constraint:"The observed failure this document exists to prevent; shown only with `crtr canvas read --fields`."},unlisted:o(e=>({kind:"flag",name:"unlisted",type:"bool",required:!1,constraint:e}),"unlisted"),lintIgnore:{kind:"flag",name:"lint-ignore",type:"string",required:!1,repeatable:!0,constraint:"A `crtr doc lint` rule name to skip for this document."},extension:{kind:"flag",name:"extension",type:"string",required:!1,repeatable:!0,constraint:"One declared plugin field as `<plugin>.<field>=<json value>`."}};function c(e){return e===void 0?[]:Array.isArray(e)?e.map(String):[String(e)]}o(c,"list");function T(e){const t=[],n={};for(const[r,a]of[["preview","preview"],["kind","kind"],["summary","summary"],["privacy","privacy"],["whyItExists","why_it_exists"]])typeof e[r]=="string"&&(n[a]=e[r]);typeof n.preview=="string"&&/[\r\n]/.test(n.preview)&&t.push("--preview must be one line"),e.unlisted===!0&&(n.unlisted=!0);const i=c(e.lintIgnore);i.length>0&&(n.lint_ignore=i);const s=c(e.rule);if(s.length>0){const r=[];s.forEach((a,u)=>{try{const d=JSON.parse(a);typeof d!="object"||d===null||Array.isArray(d)?t.push(`--rule #${u+1} must be a JSON object`):r.push(d)}catch{t.push(`--rule #${u+1} is not valid JSON: ${a}`)}}),n.rules=r}const l={};for(const r of c(e.extension)){const a=/^([^.=]+)\.([^=]+)=(.*)$/s.exec(r);if(!a){t.push(`--extension ${r} is not <plugin>.<field>=<json value>`);continue}try{(l[a[1]]??={})[a[2]]=JSON.parse(a[3])}catch{t.push(`--extension ${r}: the value after = is not valid JSON`)}}if(Object.keys(l).length>0&&(n.extensions=l),t.length>0)throw m(t.join("; "),{next:"Fix every value above and rerun; `--rule -h` gives the rule grammar."});return n}o(T,"fieldsFromInput");function R(e){const t=e.owner;if(t===void 0)return{};const n=String(t);if(!/^(node|user|profile|app|repo)$|^(profile|repo):.+$/.test(n))throw m(`invalid --owner ${n}`,{field:"owner",next:`Pass one of ${p}.`});return{owner:n}}o(R,"ownerFromInput");function D(e,t){e instanceof w&&(e.code==="read_first"&&h(e,`Read it first with \`crtr canvas read ${t}\`, then rerun the edit.`),e.code==="stale_revision"&&h(e,`Someone edited it after your read. Read it again with \`crtr canvas read ${t}\`, make your change on the current text, and rerun the edit.`)),h(e)}o(D,"rethrowDocError");function I(e){const t=[],n=e.unresolved_links,i=e.remote_links;return n?.length&&t.push(`Unresolved links (no document by these names yet): ${n.map(s=>`[[${s}]]`).join(", ")}`),i?.length&&t.push(`Not on this runtime: ${i.map(s=>`[[${s}]]`).join(", ")} \u2014 each may be on another runtime, or the name may be misspelled.`),t.map(s=>`
|
|
2
2
|
${s}`).join("")}o(I,"renderLinkReport");export{N as FIELD_FLAGS,p as OWNER_VALUES,q as REF_CONSTRAINT,T as fieldsFromInput,R as ownerFromInput,O as ownerParam,I as renderLinkReport,D as rethrowDocError,S as ruleParam};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var o=Object.defineProperty;var
|
|
1
|
+
var o=Object.defineProperty;var i=(e,n)=>o(e,"name",{value:n,configurable:!0});import{defineLeaf as a}from"../../core/command.js";import{cliClient as s,rethrowAsCliError as d}from"../api-client.js";import{FIELD_FLAGS as t,fieldsFromInput as l,ownerFromInput as u,ownerParam as m,renderLinkReport as c,ruleParam as h}from"./shared.js";const b=a({name:"write",description:"write a new document from a stdin body",whenToUse:"a spec, plan, map, finding, knowledge or preference should exist as a document other nodes can read; creation only \u2014 revise an existing one with `crtr doc edit`",help:{name:"doc write",summary:"write a new document from a stdin body",guide:"Most documents need only a name, a preview and a body: the writing node owns it, it has no delivery rules, and other nodes read it when pointed at it as `[[<name printed below>]]`, and each `[[link]]` in its body attaches that document's preview wherever this one is delivered in full. Give --owner or --rule only when the document must outlive this node or load on its own; each has focused help. Read, list and search documents with `crtr canvas read|list|search`.",params:[{kind:"positional",name:"name",required:!0,constraint:"Name under its owner: segments of letters, digits, `_` and `-`, joined by `/`. Must be free under that owner."},{kind:"stdin",name:"body",required:!0,constraint:"Document body (markdown), piped on stdin and saved byte for byte. Pipe it straight in with a quoted heredoc (`<<'EOF'`); an unquoted one lets the shell expand `$` and backticks in the text before it is saved."},{...t.preview("One line shown wherever the document is listed or delivered without its body, telling a reader whether to open it."),required:!0},t.kind,t.summary,t.privacy("`public` (default) or `private`: a private document is kept from apps without a private-read scope; the owner and anyone it is shared with still read it. A repo-owned document must be public."),m("Whose document it is; default the writing node."),h("A delivery rule that loads the document into nodes by their activity."),t.whyItExists,t.unlisted("Keep it out of listings; reads, links and rules still work."),t.lintIgnore,t.extension],output:[{name:"name",type:"string",required:!0,constraint:"Full name, `<owner-handle>/<name>`; point at it as `[[name]]`."},{name:"id",type:"string",required:!0,constraint:"Object id."},{name:"unresolved_links",type:"string[]",required:!1,constraint:"`[[links]]` in the body that name no document yet; absent when every link resolves."},{name:"remote_links",type:"string[]",required:!1,constraint:"`[[links]]` whose first segment is no owner on this runtime: on another runtime, or misspelled."}],outputKind:"object",effects:["Creates the document at revision 1 and makes the writer a watcher, so it is pushed later edits."]},run:i(async e=>{const n={name:e.name,body:e.body,...l(e),...u(e),cwd:process.cwd()};try{return{...await s().docs.write(n)}}catch(r){d(r)}},"run"),render:i(e=>`<written name="${String(e.name)}" id="${String(e.id)}">${c(e)}`,"render")});export{b as writeLeaf};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var _=Object.defineProperty;var i=(e,o)=>_(e,"name",{value:o,configurable:!0});import{existsSync as j}from"node:fs";import{userFilesRoot as C}from"../../core/layout.js";import{basename as R,dirname as k,join as f,relative as E,resolve as h,sep as N}from"node:path";import{defineLeaf as O}from"../../core/command.js";import{usage as L}from"../../core/errors.js";import{parseFrontmatterGeneric as q}from"../../core/frontmatter.js";import{readJsonIfExists as P,readText as D,walkFiles as M,writeJson as T}from"../../core/fs-utils.js";import{
|
|
2
|
-
`)}i(
|
|
1
|
+
var _=Object.defineProperty;var i=(e,o)=>_(e,"name",{value:o,configurable:!0});import{existsSync as j}from"node:fs";import{userFilesRoot as C}from"../../core/layout.js";import{basename as R,dirname as k,join as f,relative as E,resolve as h,sep as N}from"node:path";import{defineLeaf as O}from"../../core/command.js";import{usage as L}from"../../core/errors.js";import{parseFrontmatterGeneric as q}from"../../core/frontmatter.js";import{readJsonIfExists as P,readText as D,walkFiles as M,writeJson as T}from"../../core/fs-utils.js";import{readLegacyPrivacy as J}from"../../core/graph/types.js";import{identifyCheckout as W}from"../../core/graph/repo-sync/identity.js";import{runtimeRoot as G}from"../../core/layout.js";import{piAgentDir as H}from"../../core/runtime/package-health.js";import{CRTR_DIR_NAME as U}from"../../types.js";import{cliClient as z}from"../api-client.js";import{normalizedName as x,putDoc as B,repoOwner as V,routeFromDescription as X,rulesFromLegacyFrontmatter as Y,scalarString as S,userOwner as Q,writeFailure as Z}from"./sync-shared.js";const ee="<!-- crtr-boot-skill v",$="SKILL.md",y="skill-import-ignore.json",oe=new Set(["node_modules",".git",".venv","venv","dist","build",".next",".cache",".yalc",".sisyphus",U]);function te(){const e=W(process.cwd());return e.ok?e.root:null}i(te,"projectDir");function w(){return f(G(),y)}i(w,"ignoreFilePath");function re(){const e=[{label:"claude-user",root:f(C(),".claude","skills"),defaultScope:"user"},{label:"pi-user",root:f(H(),"skills"),defaultScope:"user"}],o=te();return o&&(e.push({label:"claude-project",root:f(o,".claude","skills"),defaultScope:"project"}),e.push({label:"pi-project",root:f(o,".pi","agent","skills"),defaultScope:"project"})),e}i(re,"defaultSourceRoots");function ne(e,o){if(e===void 0)return re();const r=h(e);if(o!==void 0&&o!=="user"&&o!=="project")throw L(`invalid --scope: ${o} (expected user|project)`);return[{label:"source",root:r,defaultScope:o??"user"}]}i(ne,"sourceRootsFromArg");function se(e){const o=e.root;if(!j(o))return[];const r=h(o),t=[];if(R(r)===$)return t.push({path:r,root:k(r),source:e.label,defaultScope:e.defaultScope}),t;const n=f(r,$);if(j(n))return t.push({path:n,root:k(r),source:e.label,defaultScope:e.defaultScope}),t;for(const a of M(r,c=>c===$,c=>oe.has(c)))t.push({path:a,root:r,source:e.label,defaultScope:e.defaultScope});return t}i(se,"collectCandidates");function ie(e,o){const r=S(o?.name);if(r&&r.trim()!=="")return x(r);const t=E(e.root,k(e.path)).split(N).join("/");return x(t||R(k(e.path)))}i(ie,"nameForCandidate");function ce(e){const o=new Map;return r=>{let t=o.get(r);return t===void 0&&(t=r==="user"?Q(e):V(e,process.cwd()),o.set(r,t)),t}}i(ce,"ownerResolver");function ae(){let e;try{e=P(w())}catch(r){throw L(`invalid JSON in ${w()}: ${r instanceof Error?r.message:String(r)}`)}if(e===null)return{version:1,ignored:[]};if(typeof e!="object"||e===null||e.version!==1||!Array.isArray(e.ignored))throw L(`malformed ${w()}: expected {"version":1,"ignored":[...]}`);const o=[];for(const r of e.ignored){if(typeof r!="object"||r===null)continue;const t=r;typeof t.source=="string"&&typeof t.name=="string"&&(t.scope==="user"||t.scope==="project")&&typeof t.ignoredAt=="string"&&o.push({source:h(t.source),name:t.name,scope:t.scope,ignoredAt:t.ignoredAt})}return{version:1,ignored:o}}i(ae,"readIgnoreState");function ue(e){T(w(),e)}i(ue,"writeIgnoreState");function F(e,o){const r=h(e.candidate.path);return o.ignored.some(t=>t.source===r||t.scope===e.scope&&t.name===e.name)}i(F,"isIgnored");function le(e,o){return F(e,o)?!1:(o.ignored.push({source:h(e.candidate.path),name:e.name,scope:e.scope,ignoredAt:new Date().toISOString()}),o.ignored.sort((r,t)=>`${r.scope}/${r.name}`.localeCompare(`${t.scope}/${t.name}`)),!0)}i(le,"rememberIgnored");function K(e,o,r){return{source:e.path,name:"",scope:o,status:"skipped",reason:r}}i(K,"skipResult");async function de(e,o,r){const t=D(e.path),n=r??e.defaultScope;if(t.includes(ee))return K(e,n,"legacy generated crtr boot skill is pruned by host exports, not imported");const a=q(t),c=a.data??{},u=c.kind==="preference"?"preference":"knowledge",l=ie(e,c);if(l==="")return K(e,n,"could not derive a document name");const p=await o(n);return{candidate:e,raw:t,parsed:a,sourceFm:c,kind:u,localName:l,name:`${p.handle}/${l}`,scope:n,owner:p}}i(de,"prepareCandidate");const pe=["public","private"];async function me(e,o,r){const t={source:o.candidate.path,name:o.name,scope:o.scope};if(o.owner.existing.has(o.localName)&&!r.overwrite)return{...t,status:"skipped",reason:"a document with this name already exists; re-run with --overwrite to replace it"};if(r.dryRun)return{...t,status:"would-import"};const n=o.sourceFm,a=S(n.description)??"",c=S(n["when-and-why-to-read"])?.trim(),u=S(n["short-form"])?.trim(),l=Y(n),p=pe.find(m=>m===J(n.privacy)),s={name:o.localName,kind:o.kind,preview:(c!==void 0&&c!==""?c:X(a,o.kind)).replace(/\s+/g," "),summary:u!==void 0&&u!==""?u:a.replace(/\s+/g," ").trim(),...p!==void 0?{privacy:p}:{},rules:Array.isArray(n.surfaces)?l:[{on:"boot",deliver:"preview"}],body:o.parsed.body};try{const m=await B(e,o.owner,s,"imported by crtr sys sync skills");return{...t,name:m.name,status:m.changed?"imported":"unchanged"}}catch(m){return{...t,status:"skipped",reason:Z(m)}}}i(me,"convertPrepared");function A(e,o,r){return{source:e.candidate.path,name:e.name,scope:e.scope,status:r?"would-ignore":"ignored",reason:o}}i(A,"ignoreResult");function fe(e,o){const r=e.filter(s=>s.status==="imported").length,t=e.filter(s=>s.status==="would-import").length,n=e.filter(s=>s.status==="skipped").length,a=e.filter(s=>s.status==="ignored").length,c=e.filter(s=>s.status==="would-ignore").length;let u;o?u=`crtr sys sync skills \u2014 ${c>0?`${c} would ignore`:`${a} ignored`}, ${n} skipped`:t>0?u=`crtr sys sync skills \u2014 dry run: ${t} would import, ${n} skipped, ${a} ignored`:u=`crtr sys sync skills \u2014 ${r} imported, ${n} skipped, ${a} ignored`;const l=e.map(s=>{const m=s.reason?` (${s.reason})`:"";return`| ${s.status} | ${s.name||"\u2014"} | ${s.source}${m} |`}),p=l.length===0?["No sync candidates."]:["| status | document | source |","| --- | --- | --- |",...l];return[u,"",...p].join(`
|
|
2
|
+
`)}i(fe,"renderSummary");const Ke=O({name:"skills",description:"import host Agent Skill (SKILL.md) bundles as documents",whenToUse:"migrating legacy host-native Agent Skill bundles (SKILL.md \u2014 Claude/pi user or project skill roots, or an explicit bundle/root via --source) into documents. One-way: crouter never re-exports documents back to SKILL.md, and stops reading the originals unconditionally once migrated.",help:{name:"sys sync skills",summary:"one-way import: host SKILL.md bundles \u2192 documents owned by you or the repo",params:[{kind:"flag",name:"source",type:"string",required:!1,constraint:"A SKILL.md file, a single skill bundle dir containing SKILL.md, or a skills root to scan recursively. Default: the user/project Claude and pi skill roots that exist."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Owner of the imported documents. `user` \u2014 you, the person; `project` \u2014 the repo of the working directory (a repo owns only public documents). Default: user for user host roots and project for project host roots; an explicit value applies to every selected source."},{kind:"flag",name:"dry-run",type:"bool",required:!1,constraint:"Show what would be imported without writing documents."},{kind:"flag",name:"overwrite",type:"bool",required:!1,constraint:"Replace the body and fields of an existing document of the same name (a new revision). Default: skip it."},{kind:"flag",name:"ignore",type:"bool",required:!1,constraint:`Permanently ignore every selected SKILL.md source by recording it in ~/.crouter/${y}. Honors --source/--scope; with no --source, ignores every currently discovered default host skill candidate.`},{kind:"flag",name:"include-ignored",type:"bool",required:!1,constraint:"Include ignored candidates in the report without making sync read-only. Pair with --dry-run to audit the full candidate set without importing non-ignored skills."}],output:[{name:"results",type:"object[]",required:!0,constraint:"Each: {source,target,name,scope,status,reason?}; status is one of imported, unchanged, skipped, would-import, ignored, would-ignore. `name` is the document's full name, `<owner handle>/<bundle name>`."}],outputKind:"object",effects:["Writes the document `<owner handle>/<bundle name>` with the SKILL.md body; its preview comes from the skill description, and it carries a boot rule at preview unless the source declares its own `surfaces`.","Skips existing documents unless --overwrite is present.","Skips marker-bearing generated crtr boot skills; host exports prune those legacy artifacts instead.",`With --ignore: writes ~/.crouter/${y} and does not import the selected sources.`,"With --dry-run: read-only; writes nothing."]},run:i(async e=>{const o=e.source,r=e.scope,t=e.dryRun??!1,n=e.overwrite??!1,a=e.ignore??!1,c=e.includeIgnored??!1,u=ne(o,r),l=new Set,p=[];for(const I of u)for(const d of se(I))l.has(d.path)||(l.add(d.path),p.push(d));const s=z(),m=ce(s),b=ae(),g=[];let v=!1;for(const I of p){const d=await de(I,m,r);if("status"in d){g.push(d);continue}if(a){t||(v=le(d,b)||v),g.push(A(d,t?"would record permanent ignore":`recorded in ~/.crouter/${y}`,t));continue}if(F(d,b)){c&&g.push(A(d,`matched ~/.crouter/${y}`,!1));continue}g.push(await me(s,d,{dryRun:t,overwrite:n}))}return v&&ue(b),{results:g,ignoreFile:w(),ignoreMode:a}},"run"),render:i(e=>fe(e.results,e.ignoreMode===!0),"render")});export{Ke as sysSyncSkillsLeaf};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var
|
|
1
|
+
var ie=Object.defineProperty;var i=(e,n)=>ie(e,"name",{value:n,configurable:!0});import{envNodeId as W}from"../../shared/env.js";import{lstatSync as oe,realpathSync as F,rmdirSync as re,rmSync as se,statSync as de}from"node:fs";import{basename as ue,dirname as b,isAbsolute as le,join as S,relative as ce,resolve as _e,sep as pe}from"node:path";import{removeTreeBeneath as C}from"../spaces/open-beneath.js";import{emitEvent as fe}from"../events/emit.js";import{withCanvasWrite as p,openDb as s}from"./db.js";import{conversationStore as Ee,ConversationNotFoundError as we}from"../conversation-store/index.js";import{hasUnseenWakeMail as ye,UNSEEN_WAKE_MAIL_SQL as me}from"./deliveries.js";import{isAppRun as H}from"./app-run.js";import{registerOutcomeAction as M}from"./outcome-actions.js";import{publish as Ne}from"../graph/events.js";import{nodeObject as Re}from"../graph/objects.js";import{findNodeBySessionFileSql as Se}from"./sessions.js";import{hydrateFault as he}from"./recovery.js";import{hasUnreconciledWorktree as Oe,openWorktree as Te,readWorktree as Le,worktreeFromRow as De}from"./worktrees.js";import{isPidAlive as ve,isPidPresent as P,recordedPidLiveness as ge}from"./pid.js";import{ensureHome as Ae,jobDir as ke,nodeDir as xe,nodeRunDir as $}from"./paths.js";import{appForNode as Ue,ensureNodeDirs as je,nodeAgentDir as Ie}from"./node-agent-paths.js";import{appSpace as T}from"../layout.js";import{createObject as We,getById as L,ownerForGrantee as Fe,setObjectKind as be,setObjectName as Ce}from"../graph/objects.js";import{profileObject as He}from"../graph/reader.js";import{writeActsFor as q}from"../permissions/facts.js";import{CrtrError as Me}from"../errors.js";import{deleteNodeWithOwned as Pe}from"../graph/deletion.js";import{SUBTREE_ALL_IDS_SQL as $e,SUBTREE_IDS_SQL as qe}from"./tree.js";import{filterAsync as m,mapAsync as D}from"../async-array.js";function E(e,n){if(e==null)return null;const t=JSON.stringify(e);if(t===void 0)throw new TypeError(`${n} must be JSON-serializable`);return t}i(E,"jsonValue");async function vn(e,n){if((await e.prepare("SELECT deleting_at FROM nodes WHERE node_id = ?").get(n))?.deleting_at)throw new Error("run is deleting")}i(vn,"assertRunNotDeleting");async function B(e,n,t){try{return await He(e,n,t)}catch(a){if(a instanceof Me&&a.code==="not_found")return null;throw a}}i(B,"profileObjectFor");async function Be(e,n){const t=await Fe(n,e.grantee??"app:terminal"),a=e.profile_id?await B(n,e.profile_id,t):null;await We(n,{id:e.node_id,type:"node",kind:e.kind,owner:(a??t).id,privacy:"public",name:e.name,preview:""}),a===null&&e.sub&&await q(n,e.node_id,e.sub),await n.prepare(`INSERT INTO nodes (node_id, description, title, icon, cycles, mode, lifecycle, cwd, host_kind, profile_id, parent, spawned_by, creator, fork_from, fork_source_file, created, passive_default, cycle_pending, model_override, persona_ack_json, review_binding_json, launch_config_json, scopes_json, isolated, status, intent, final_report, finalized_at, deadline_at, "window", tmux_session, pane, sub, grant_id, client_id, metadata_json, run_id, event_cursor, usage_json, deleting_at, app) VALUES (${Array(41).fill("?").join(", ")})`).run(e.node_id,e.description??null,e.title??null,e.icon??null,e.cycles??null,e.mode,e.lifecycle,e.cwd,e.host_kind??null,e.profile_id??null,e.parent??null,e.spawned_by??null,e.creator??null,e.fork_from??null,e.fork_source_file??null,e.created,e.passive_default===!0?1:0,e.cycle_pending===!0?1:0,e.model_override??null,E(e.persona_ack,"persona_ack"),E(e.review_binding,"review_binding"),E(e.launch,"launch"),E(e.scopes,"scopes"),e.isolated===!0?1:0,e.status??"active",e.intent??null,e.final_report??null,e.finalized_at??null,e.deadline_at??null,e.window??null,e.tmux_session??null,e.pane??null,e.sub??null,e.grant_id??null,e.client_id??null,e.metadata_json??null,e.run_id??null,e.event_cursor??null,e.usage_json??null,e.deleting_at??null,t.id)}i(Be,"seedRow");function z(e){if(e.fault_id==null)return null;const n=Object.entries(e).filter(([t])=>t.startsWith("fault_")).map(([t,a])=>[t==="fault_id"?t:t.slice(6),a]);return he(Object.fromEntries(n))}i(z,"faultFrom");function f(e){const n={node_id:e.node_id,name:e.name,description:e.description??void 0,cycles:e.cycles??void 0,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,profile_id:e.profile_id??null,scopes:e.scopes_json==null?null:JSON.parse(e.scopes_json),isolated:e.isolated===1,grantee:e.grantee,sub:e.sub??null,grant_id:e.grant_id??null,client_id:e.client_id??null,metadata_json:e.metadata_json??null,run_id:e.run_id??null,event_cursor:e.event_cursor??null,last_activity_at:e.last_activity_at??null,usage_json:e.usage_json??null,last_turn_error_json:e.last_turn_error_json??null,deleting_at:e.deleting_at??null,parent:e.parent??null,created:e.created,intent:e.intent??null,frozen_at:e.frozen_at??null,respawn_failures:e.respawn_failures??0,respawn_not_before:e.respawn_not_before??null,launched_at:e.launched_at??null,last_settled_at:e.last_settled_at??null,terminal_reason:e.terminal_reason??null,final_report:e.final_report??null,finalized_at:e.finalized_at??null,outcome_kind:e.outcome_kind??null,outcome_reason:e.outcome_reason??null,outcome_at:e.outcome_at??null,outcome_revision:e.outcome_revision??0,outcome_detail_json:e.outcome_detail_json??null,deadline_at:e.deadline_at??null,telemetry_tokens_in:e.telemetry_tokens_in??null,telemetry_context_tokens:e.telemetry_context_tokens??null,telemetry_last_activity:e.telemetry_last_activity??null,telemetry_updated_at:e.telemetry_updated_at??null,pi_pid:e.pi_pid??null,pi_pid_identity:e.pi_pid_identity??null,window:e.window??null,tmux_session:e.tmux_session??null,pane:e.pane??null,fault:null,streaming:!1},t=P(n.pi_pid),a=z(e);return n.fault=a?.link==="pi\u2192provider"&&a.retry.by==="daemon"&&a.retry.disposition==="auto"&&!t?null:a,n.streaming=e.turn_open_at!==null&&e.turn_closed_at===null&&t,n}i(f,"rowFrom");async function J(e,n){return n??=await Le(e.node_id),{...f(e),title:e.title??void 0,icon:e.icon??void 0,spawned_by:e.spawned_by??null,creator:e.creator??null,fork_from:e.fork_from??null,fork_source_file:e.fork_source_file??null,passive_default:e.passive_default===1,cycle_pending:e.cycle_pending===1,model_override:e.model_override??null,scopes:e.scopes_json==null?null:JSON.parse(e.scopes_json),persona_ack:e.persona_ack_json==null?void 0:JSON.parse(e.persona_ack_json),review_binding:e.review_binding_json==null?void 0:JSON.parse(e.review_binding_json),launch:e.launch_config_json==null?void 0:JSON.parse(e.launch_config_json),pi_session_id:e.pi_session_id??null,pi_session_file:e.pi_session_file??null,...n===null?{}:{managed_worktree:n}}}i(J,"metaFrom");async function gn(e,n={}){return Ae(),await p(async t=>{await Be(e,t),e.managed_worktree!==void 0&&e.managed_worktree!==null&&await Te(e.node_id,e.managed_worktree,t),n.outcomeDelivery!==void 0&&await M(t,e.node_id,n.outcomeDelivery),await n.withinCreate?.(t,e)}),await je(e.node_id),await h(e.node_id)}i(gn,"createNode");async function h(e){const n=await(await s()).prepare("SELECT * FROM node_rows WHERE node_id = ?").get(e);return n===void 0?null:await J(n)}i(h,"getNode");const ze=1440*60*1e3;function Y(e){return{requestKey:e.request_key,nodeId:e.node_id,state:e.state}}i(Y,"createRequestRecord");async function An(e){const n=await(await s()).prepareUncached("SELECT request_key, node_id, state FROM create_requests WHERE request_key = ?").get(e);return n===void 0?null:Y(n)}i(An,"findCreateRequest");async function kn(e){return await p(async n=>{const t=Date.now();await n.prepareUncached("DELETE FROM create_requests WHERE expires_at <= ?").run(t);const a=await n.prepareUncached("SELECT request_key, node_id, state FROM create_requests WHERE request_key = ?").get(e);return a!==void 0?Y(a):(await n.prepareUncached("INSERT INTO create_requests (request_key, node_id, expires_at, state) VALUES (?, NULL, ?, 'reserved')").run(e,t+ze),{requestKey:e,nodeId:null,state:"reserved"})})}i(kn,"reserveCreateRequest");async function xn(e=Date.now()){return Number((await(await s()).prepareUncached("DELETE FROM create_requests WHERE expires_at <= ?").run(e)).changes)}i(xn,"pruneExpiredCreateRequests");async function Un(e,n){await(await s()).prepareUncached("UPDATE create_requests SET node_id = ?, state = 'complete' WHERE request_key = ?").run(n,e)}i(Un,"recordCreateRequestNode");async function jn(e,n){await(await s()).prepareUncached("UPDATE create_requests SET node_id = ?, state = 'reserved' WHERE request_key = ?").run(n,e)}i(jn,"reserveCreateRequestNode");async function In(e,n){await(await s()).prepareUncached("UPDATE create_requests SET state = 'complete' WHERE request_key = ? AND node_id = ?").run(e,n)}i(In,"completeCreateRequest");async function Wn(e,n){await p(async t=>{if(n===null){await t.prepareUncached("DELETE FROM create_requests WHERE request_key = ? AND node_id IS NULL").run(e);return}await t.prepareUncached("DELETE FROM create_requests WHERE request_key = ? AND node_id = ?").run(e,n)})}i(Wn,"abandonCreateRequest");function Fn(e){return _e(e)}i(Fn,"canonicalSessionFile");async function bn(e){const n=await Se(e);return n===null?null:await h(n)}i(bn,"findNodeBySessionFile");async function Cn(e){const n=await(await s()).prepare("SELECT * FROM node_rows WHERE node_id = ?").get(e);return n?f(n):null}i(Cn,"getRow");async function Hn(e){const n=await(await s()).prepare("SELECT * FROM node_rows WHERE pane = ?").get(e);return n?f(n):null}i(Hn,"getRowByPane");async function Mn(e){return(await(await s()).prepare(`SELECT * FROM node_rows
|
|
2
2
|
WHERE deadline_at IS NOT NULL AND outcome_at IS NULL
|
|
3
3
|
AND deadline_at <= ? AND status IN ('active','idle') AND kind != 'human'
|
|
4
|
-
ORDER BY deadline_at, created, node_id`).all(e)).map(f)}i(
|
|
5
|
-
WHERE profile_id = ? AND deadline_at IS NOT NULL AND status IN ('active', 'idle')`).all(e),o=t.prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ? AND deadline_at = ?");let
|
|
4
|
+
ORDER BY deadline_at, created, node_id`).all(e)).map(f)}i(Mn,"dueNodeDeadlines");async function Pn(e,n){return Number((await(await s()).prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ?").run(n,e)).changes)===1}i(Pn,"setNodeDeadline");async function $n(e,n){return!Number.isFinite(n)||n<=0?0:await p(async t=>{const a=await t.prepare(`SELECT node_id, deadline_at FROM nodes
|
|
5
|
+
WHERE profile_id = ? AND deadline_at IS NOT NULL AND status IN ('active', 'idle')`).all(e),o=t.prepare("UPDATE nodes SET deadline_at = ? WHERE node_id = ? AND deadline_at = ?");let r=0;for(const d of a){const l=Date.parse(d.deadline_at);Number.isNaN(l)||(r+=Number((await o.run(new Date(l+n).toISOString(),d.node_id,d.deadline_at)).changes))}return r})}i($n,"shiftProfileNodeDeadlines");function v(e,n){if(e==null)return null;if(typeof e=="string"||typeof e=="number"||typeof e=="bigint"||e instanceof Uint8Array)return e;throw new TypeError(`${n} must be a SQLite scalar`)}i(v,"sqlInput");async function qn(e,n){const t=[],a=i((r,d=r)=>{Object.prototype.hasOwnProperty.call(n,r)&&t.push([d,v(n[r],r)])},"add");if(a("description"),a("title"),a("icon"),a("cycles"),a("mode"),a("lifecycle"),a("cwd"),a("host_kind"),a("profile_id"),a("parent"),a("spawned_by"),a("creator"),a("fork_from"),a("fork_source_file"),a("model_override"),Object.prototype.hasOwnProperty.call(n,"passive_default")&&t.push(["passive_default",n.passive_default===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"cycle_pending")&&t.push(["cycle_pending",n.cycle_pending===!0?1:0]),Object.prototype.hasOwnProperty.call(n,"persona_ack")&&t.push(["persona_ack_json",E(n.persona_ack,"persona_ack")]),Object.prototype.hasOwnProperty.call(n,"review_binding")&&t.push(["review_binding_json",E(n.review_binding,"review_binding")]),Object.prototype.hasOwnProperty.call(n,"isolated"))throw new TypeError("isolated cannot change after node creation");Object.prototype.hasOwnProperty.call(n,"scopes")&&t.push(["scopes_json",E(n.scopes,"scopes")]),Object.prototype.hasOwnProperty.call(n,"launch")&&t.push(["launch_config_json",E(n.launch,"launch")]);const o=i(r=>Object.prototype.hasOwnProperty.call(n,r),"has");return await p(async r=>{const d=await L(r,e);if(d===null||d.type!=="node")throw new Error(`unknown node: ${e}`);if(o("name")&&await Ce(r,d,v(n.name,"name")),o("kind")&&await be(r,d,v(n.kind,"kind")),t.length===0)return;if(o("profile_id")){const u=await r.prepare("SELECT app FROM nodes WHERE node_id = ?").get(e),_=u?.app?await L(r,u.app):null;if(_){const w=n.profile_id?await B(r,n.profile_id,_):null;await r.prepare("UPDATE objects SET owner = ? WHERE id = ?").run((w??_).id,e);const R=(await r.prepare("SELECT sub FROM nodes WHERE node_id = ?").get(e)).sub;w===null&&R&&await q(r,e,R)}}if((await r.prepareUncached(`UPDATE nodes SET ${t.map(([u])=>`${u} = ?`).join(", ")} WHERE node_id = ?`).run(...t.map(([,u])=>u),e)).changes!==1)throw new Error(`unknown node: ${e}`)}),await h(e)}i(qn,"updateNode");async function Bn(e,n){await(await s()).prepare("UPDATE nodes SET status = ? WHERE node_id = ?").run(n,e)}i(Bn,"setStatus");async function zn(e,n){await(await s()).prepare("UPDATE nodes SET intent = ? WHERE node_id = ?").run(n??null,e)}i(zn,"setIntent");async function Jn(e,n,t={}){if(t.outcome===void 0){const o=[],r=[];Object.prototype.hasOwnProperty.call(n,"status")&&(o.push("status = ?"),r.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(o.push("intent = ?"),r.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(o.push("terminal_reason = ?"),r.push(n.terminal_reason??null));const d=["node_id = ?"];r.push(e),t.fromStatuses!==void 0&&(d.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),r.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&d.push("final_report IS NULL");const l=await(await s()).prepareUncached(`UPDATE nodes SET ${o.join(", ")} WHERE ${d.join(" AND ")}`).run(...r);return Number(l.changes)}const a=t.outcome;return await p(async o=>{const r=[],d=[];Object.prototype.hasOwnProperty.call(n,"status")&&(r.push("status = ?"),d.push(n.status??null)),Object.prototype.hasOwnProperty.call(n,"intent")&&(r.push("intent = ?"),d.push(n.intent??null)),Object.prototype.hasOwnProperty.call(n,"terminal_reason")&&(r.push("terminal_reason = ?"),d.push(n.terminal_reason??null));const l=i((R,y)=>a.supersede===!0?"?":`CASE WHEN outcome_at IS NULL THEN ? ELSE ${y} END`,"outcomeValue");r.push(`outcome_kind = ${l(a.kind,"outcome_kind")}`,`outcome_reason = ${l(a.reason,"outcome_reason")}`,`outcome_detail_json = ${l(a.detailJson,"outcome_detail_json")}`,`outcome_revision = ${a.supersede===!0?"outcome_revision + 1":"CASE WHEN outcome_at IS NULL THEN outcome_revision + 1 ELSE outcome_revision END"}`,`outcome_at = ${l(a.at,"outcome_at")}`),d.push(a.kind,a.reason,a.detailJson,a.at);const u=["node_id = ?"];d.push(e),t.fromStatuses!==void 0&&(u.push(`status IN (${t.fromStatuses.map(()=>"?").join(", ")})`),d.push(...t.fromStatuses)),t.requireNoFinalReport===!0&&u.push("final_report IS NULL");const _=await o.prepareUncached(`UPDATE nodes SET ${r.join(", ")} WHERE ${u.join(" AND ")}`).run(...d);if(Number(_.changes)===0)return 0;const w=await o.prepare("SELECT outcome_at FROM nodes WHERE node_id = ?").get(e);return(a.supersede===!0||w.outcome_at===a.at)&&a.documentJson!==null&&a.documentJson!==void 0&&await Ne(o,{event:"outcome",object:await Re(o,e),actor:null,attrs:{revision:Number((await o.prepare("SELECT outcome_revision FROM nodes WHERE node_id = ?").get(e)).outcome_revision)},messageBody:JSON.parse(a.documentJson)}),Number(_.changes)})}i(Jn,"transitionStatusIntent");async function Yn(e){return(await(await s()).prepare("SELECT final_report FROM nodes WHERE node_id = ?").get(e))?.final_report??null}i(Yn,"getFinalReport");async function Qn(e,n){return(await(await s()).prepare("UPDATE nodes SET final_report = NULL, finalized_at = NULL WHERE node_id = ? AND final_report = ?").run(e,n)).changes>0}i(Qn,"clearFinalReportIfMatches");async function Vn(e,n){await(await s()).prepare('UPDATE nodes SET tmux_session = ?, "window" = ?, pane = ? WHERE node_id = ?').run(n.tmux_session??null,n.window??null,n.pane??null,e)}i(Vn,"setPresence");async function Kn(e,n){await(await s()).prepare("UPDATE nodes SET launched_at = ?, frozen_at = NULL, respawn_not_before = NULL WHERE node_id = ?").run(n,e)}i(Kn,"recordLaunch");async function Gn(e,n){await(await s()).prepare("UPDATE nodes SET last_settled_at = ? WHERE node_id = ?").run(n,e)}i(Gn,"recordSettledTurn");async function Xn(e,n){await(await s()).prepare("UPDATE nodes SET frozen_at = ? WHERE node_id = ?").run(n,e)}i(Xn,"setFrozen");async function Zn(e){await(await s()).prepare("UPDATE nodes SET frozen_at = NULL WHERE node_id = ?").run(e)}i(Zn,"clearFrozen");async function et(e,n){await(await s()).prepare("UPDATE nodes SET respawn_failures = ?, respawn_not_before = ? WHERE node_id = ?").run(n.failures,n.notBefore,e)}i(et,"recordRespawnAttempt");const N="node_id NOT IN (SELECT node_id FROM warm_pool)",Q=["state","cleanup_state","path","branch","repo_root","base_ref","base_sha","created_at","closed_at","next_attempt_at","attempts","quarantined","sweep_reason","sweep_detail","sweep_last_at","sweep_observed_json","abandoned_json","land_intent_json","disposal_json"];function Je(e){return e.worktree_state===null?null:De(Object.fromEntries(Q.map(n=>[n,e[`worktree_${n}`]])))}i(Je,"worktreeFromProjection");async function nt(e){const n=Q.map(a=>`w.${a} AS worktree_${a}`).join(", "),t=await(await s()).prepareUncached(`SELECT n.*, ${n} FROM node_rows n LEFT JOIN worktrees w ON w.node_id = n.node_id WHERE n.profile_id = ? ORDER BY n.created`).all(e);return await D(t,async a=>await J(a,Je(a)))}i(nt,"listNodesByProfile");async function tt(){return(await(await s()).prepare(`SELECT
|
|
6
6
|
node_id, name, description, kind, mode, lifecycle, status, cwd, profile_id,
|
|
7
7
|
grantee, parent, created, frozen_at, terminal_reason, final_report,
|
|
8
8
|
telemetry_tokens_in, telemetry_updated_at, pi_pid, turn_open_at, turn_closed_at,
|
|
@@ -10,13 +10,13 @@ var te=Object.defineProperty;var i=(e,n)=>te(e,"name",{value:n,configurable:!0})
|
|
|
10
10
|
fault_operation_id, fault_anchor_entry_id, fault_retry_disposition,
|
|
11
11
|
fault_retry_by, fault_retry_attempt, fault_retry_max, fault_retry_next_at,
|
|
12
12
|
fault_provider_error_json
|
|
13
|
-
FROM node_rows WHERE ${
|
|
13
|
+
FROM node_rows WHERE ${N} ORDER BY created`).all()).map(f)}i(tt,"browseNodes");async function at(e,n){await(await s()).prepare(`UPDATE nodes
|
|
14
14
|
SET telemetry_tokens_in = ?,
|
|
15
15
|
telemetry_context_tokens = COALESCE(?, telemetry_context_tokens),
|
|
16
16
|
telemetry_last_activity = COALESCE(?, telemetry_last_activity),
|
|
17
17
|
telemetry_updated_at = ?
|
|
18
18
|
WHERE node_id = ?
|
|
19
|
-
AND (telemetry_updated_at IS NULL OR telemetry_updated_at < ?)`).run(n.tokensIn,n.contextTokens,n.lastActivity,n.updatedAt,e,n.updatedAt)}i(
|
|
19
|
+
AND (telemetry_updated_at IS NULL OR telemetry_updated_at < ?)`).run(n.tokensIn,n.contextTokens,n.lastActivity,n.updatedAt,e,n.updatedAt)}i(at,"updateNodeTelemetry");async function it(){return(await(await s()).prepare(`SELECT
|
|
20
20
|
node_id, name, description, cycles, kind, status, frozen_at,
|
|
21
21
|
telemetry_context_tokens, telemetry_last_activity,
|
|
22
22
|
pi_pid, turn_open_at, turn_closed_at,
|
|
@@ -24,14 +24,14 @@ var te=Object.defineProperty;var i=(e,n)=>te(e,"name",{value:n,configurable:!0})
|
|
|
24
24
|
fault_operation_id, fault_anchor_entry_id, fault_retry_disposition,
|
|
25
25
|
fault_retry_by, fault_retry_attempt, fault_retry_max, fault_retry_next_at,
|
|
26
26
|
fault_provider_error_json
|
|
27
|
-
FROM node_rows WHERE ${
|
|
27
|
+
FROM node_rows WHERE ${N} ORDER BY created`).all()).map(n=>{const t=n.pi_pid??null,a=P(t),o=z(n);return{node_id:n.node_id,name:n.name,description:n.description??null,cycles:n.cycles??null,kind:n.kind,status:n.status,frozen_at:n.frozen_at??null,pi_pid:t,telemetry_context_tokens:n.telemetry_context_tokens??null,telemetry_last_activity:n.telemetry_last_activity??null,fault:o?.link==="pi\u2192provider"&&o.retry.by==="daemon"&&o.retry.disposition==="auto"&&!a?null:o,streaming:n.turn_open_at!==null&&n.turn_closed_at===null&&a}})}i(it,"graphNodes");async function Ye(e){const n=[N],t=[];if(e?.under!==void 0&&(e.underInclusive===!0?(n.push(`(node_id IN (${$e}) OR node_id = ?)`),t.push(e.under,e.under,e.under)):(n.push(`node_id IN (${qe})`),t.push(e.under,e.under))),e?.lifecycle!==void 0&&(n.push("lifecycle = ?"),t.push(e.lifecycle)),e?.kind!==void 0&&(n.push("kind = ?"),t.push(e.kind)),e?.mode!==void 0&&(n.push("mode = ?"),t.push(e.mode)),e?.profileId!==void 0&&(n.push("profile_id = ?"),t.push(e.profileId)),e?.profilePrefix!==void 0&&(n.push("profile_id LIKE ? ESCAPE '\\'"),t.push(`${e.profilePrefix.replace(/[\\%_]/g,o=>`\\${o}`)}%`)),e?.parent!==void 0&&(n.push("parent = ?"),t.push(e.parent)),e?.topLevel===!0&&n.push("parent IS NULL"),e?.hanging===!0&&n.push("parent IS NOT NULL AND parent NOT IN (SELECT node_id FROM node_rows)"),e?.status!==void 0){const o=Array.isArray(e.status)?e.status:[e.status];n.push(`status IN (${o.map(()=>"?").join(",")})`),t.push(...o)}return e?.withRecordedPid===!0&&n.push("pi_pid IS NOT NULL"),e?.withRecordedPidOrFrozen===!0&&n.push("(pi_pid IS NOT NULL OR frozen_at IS NOT NULL)"),(await(await s()).prepareUncached(`SELECT * FROM node_rows WHERE ${n.join(" AND ")} ORDER BY created`).all(...t)).map(f)}i(Ye,"listNodes");const Qe=`SELECT
|
|
28
28
|
node_id, created, launched_at, last_settled_at, finalized_at,
|
|
29
29
|
outcome_at, last_activity_at, pane, "window", tmux_session
|
|
30
30
|
FROM nodes
|
|
31
|
-
WHERE ${
|
|
31
|
+
WHERE ${N}
|
|
32
32
|
AND status IN ('done', 'canceled', 'dead')
|
|
33
33
|
AND (pane IS NOT NULL OR "window" IS NOT NULL OR tmux_session IS NOT NULL)
|
|
34
|
-
ORDER BY created`;async function
|
|
34
|
+
ORDER BY created`;async function ot(){return await(await s()).prepare(Qe).all()}i(ot,"listTerminalPlacementResidue");const Ve=`WITH candidates AS MATERIALIZED (
|
|
35
35
|
SELECT node_id FROM nodes WHERE status IN ('active', 'idle')
|
|
36
36
|
UNION
|
|
37
37
|
SELECT node_id FROM nodes
|
|
@@ -45,23 +45,23 @@ var te=Object.defineProperty;var i=(e,n)=>te(e,"name",{value:n,configurable:!0})
|
|
|
45
45
|
AND v.node_id NOT IN (SELECT node_id FROM warm_pool)
|
|
46
46
|
AND v.kind != 'human'
|
|
47
47
|
AND (v.status IN ('active', 'idle') OR (v.status IN ('done', 'canceled', 'dead') AND (v.pi_pid IS NOT NULL OR v.frozen_at IS NOT NULL)))
|
|
48
|
-
ORDER BY v.created`;async function
|
|
49
|
-
WHERE ${
|
|
50
|
-
AND EXISTS (SELECT 1 FROM deliveries d WHERE d.destination_id = v.node_id AND ${
|
|
51
|
-
ORDER BY v.created`).all()).map(f)}i(
|
|
48
|
+
ORDER BY v.created`;async function rt(){return(await(await s()).prepare(Ve).all()).map(f)}i(rt,"listNodesForSupervision");const V=i(e=>`(${e}.status = 'done' AND ${e}.intent = 'parked' AND ${e}.lifecycle = 'resident' AND ${e}.final_report IS NULL AND ${e}.deleting_at IS NULL)`,"PARKED_RESIDENT_SQL"),Ke=i(e=>`(${e}.status IN ('active', 'idle') OR ${V(e)} OR (${e}.status IN ('done', 'canceled', 'dead') AND ${e}.frozen_at IS NOT NULL AND ${e}.final_report IS NULL))`,"RESUMABLE_NODE_SQL");async function st(e){return await(await s()).prepare(`SELECT node_id, cwd FROM nodes WHERE ${Ke("nodes")} AND node_id != ?`).all(e)}i(st,"resumableNodeCwds");async function dt(){return(await(await s()).prepare(`SELECT v.* FROM node_rows v
|
|
49
|
+
WHERE ${V("v")}
|
|
50
|
+
AND EXISTS (SELECT 1 FROM deliveries d WHERE d.destination_id = v.node_id AND ${me("d")}) AND v.kind != 'human' AND v.node_id NOT IN (SELECT node_id FROM warm_pool)
|
|
51
|
+
ORDER BY v.created`).all()).map(f)}i(dt,"listParkedResidentsWithWakeMail");async function ut(e,n){await(await s()).prepare("INSERT INTO warm_pool (node_id, recipe_key, created) VALUES (?, ?, ?) ON CONFLICT (node_id) DO UPDATE SET recipe_key = excluded.recipe_key, created = excluded.created").run(e,n,new Date().toISOString())}i(ut,"registerWarmSpare");async function lt(e){return(await(await s()).prepare("DELETE FROM warm_pool WHERE node_id = ?").run(e)).changes>0}i(lt,"unregisterWarmSpare");async function g(e,n){const t=`SELECT w.node_id AS node_id, w.recipe_key AS recipe_key, w.created AS created,
|
|
52
52
|
n.status AS status, n.pi_pid AS pi_pid, n.pi_pid_identity AS pi_pid_identity
|
|
53
|
-
FROM warm_pool w JOIN node_rows n ON n.node_id = w.node_id`+(n===void 0?"":" WHERE w.recipe_key = ?")+" ORDER BY w.created";return(n===void 0?await e.prepareUncached(t).all():await e.prepareUncached(t).all(n)).map(o=>({node_id:o.node_id,recipe_key:o.recipe_key,created:o.created,status:o.status,pi_pid:o.pi_pid??null,pi_pid_identity:o.pi_pid_identity??null}))}i(
|
|
54
|
-
FROM node_rows WHERE ${
|
|
53
|
+
FROM warm_pool w JOIN node_rows n ON n.node_id = w.node_id`+(n===void 0?"":" WHERE w.recipe_key = ?")+" ORDER BY w.created";return(n===void 0?await e.prepareUncached(t).all():await e.prepareUncached(t).all(n)).map(o=>({node_id:o.node_id,recipe_key:o.recipe_key,created:o.created,status:o.status,pi_pid:o.pi_pid??null,pi_pid_identity:o.pi_pid_identity??null}))}i(g,"warmSpareRowsFor");async function ct(){return await g(await s())}i(ct,"listWarmSpares");async function _t(e){return await(await s()).prepare("SELECT 1 FROM warm_pool WHERE node_id = ?").get(e)!==void 0}i(_t,"isWarmSpare");function K(e){return e.status!=="active"&&e.status!=="idle"?!1:ge(e.pi_pid,e.pi_pid_identity)==="alive"}i(K,"spareIsClaimable");async function pt(e,n,t={}){return await p(async a=>{const o=(await g(a,e)).find(K);return o===void 0||t.requestKey!==void 0&&(await a.prepareUncached("UPDATE create_requests SET node_id = ?, state = 'warm_claimed' WHERE request_key = ? AND node_id IS NULL").run(o.node_id,t.requestKey)).changes===0||(await a.prepare("DELETE FROM warm_pool WHERE node_id = ?").run(o.node_id)).changes===0?null:(await a.prepare("UPDATE nodes SET created = ?, deadline_at = ? WHERE node_id = ?").run(n,t.deadlineAt??null,o.node_id),t.outcomeDelivery!==void 0&&await M(a,o.node_id,t.outcomeDelivery),o.node_id)})}i(pt,"claimWarmSpare");async function ft(e){return(await g(await s(),e)).filter(K).length}i(ft,"warmSpareCount");async function Et(e){return(await(await s()).prepare("SELECT node_id FROM nodes WHERE parent = ? ORDER BY created").all(e)).map(n=>n.node_id)}i(Et,"childrenOf");async function wt(){return(await(await s()).prepareUncached(`SELECT node_id, name, kind, mode, lifecycle, status, cwd, host_kind, parent, created, description
|
|
54
|
+
FROM node_rows WHERE ${N} ORDER BY created`).all()).map(e=>({node_id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,status:e.status,cwd:e.cwd,host_kind:e.host_kind??null,parent:e.parent??null,created:e.created,description:e.description??null}))}i(wt,"rosterNodes");async function yt(e){return(await(await s()).prepareUncached(`SELECT * FROM node_rows
|
|
55
55
|
WHERE parent IS NULL AND kind != 'human'
|
|
56
56
|
AND status IN ('active','idle') AND cwd = ?
|
|
57
|
-
AND ${
|
|
58
|
-
ORDER BY created DESC`).all(e)).map(f)}i(
|
|
59
|
-
AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.node_id = n.node_id) LIMIT 1`);for(const{id:o,path:
|
|
57
|
+
AND ${N}
|
|
58
|
+
ORDER BY created DESC`).all(e)).map(f)}i(yt,"listResumableRoots");export*from"./tree.js";async function mt(e,n,t){return await p(async a=>{const o=await a.prepare("SELECT status, final_report, frozen_at FROM nodes WHERE node_id = ?").get(e);return o===void 0?{kind:"missing"}:n(o)?{kind:"terminal",status:o.status,final_report:o.final_report}:{kind:"ran",result:await t()}})}i(mt,"withFreshTerminalGuard");async function O(e){return await Oe(e)}i(O,"hasUnreconciledManagedWorktree");async function G(e,n={}){return!["done","dead","canceled"].includes(e.status)||e.pi_pid!==null||e.frozen_at!==null||await O(e.node_id)||e.node_id===W()||H(e)?!1:n.pastTtl===!0||!["crashed","stranded",null].includes(e.terminal_reason??null)}i(G,"isCollectible");async function A(e){if(e.length===0)return[];const n=e.map(()=>"?").join(","),t=await(await s()).prepareUncached(`SELECT s.session_id, s.transcript_path, s.node_id, n.grantee FROM sessions s JOIN node_rows n ON n.node_id = s.node_id WHERE s.node_id IN (${n}) AND s.session_id IS NOT NULL AND s.transcript_path IS NOT NULL ORDER BY s.created_at, s.seq`).all(...e);return[...new Map(t.map(a=>[a.session_id,{id:a.session_id,path:a.transcript_path,dir:S(T(a.grantee).conversations,a.node_id)}])).values()]}i(A,"nodeConversationIds");async function k(e){const n=await s(),t=n.prepare("SELECT 1 FROM sessions WHERE session_id = ? LIMIT 1"),a=n.prepare(`SELECT 1 FROM nodes n WHERE n.fork_source_file LIKE '%\\_' || ? || '.jsonl' ESCAPE '\\'
|
|
59
|
+
AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.node_id = n.node_id) LIMIT 1`);for(const{id:o,path:r,dir:d}of e)if(!(await t.get(o)!==void 0||await a.get(o)!==void 0)&&oe(r,{throwIfNoEntry:!1})!==void 0){if(!Ge(d,r)){console.error(`[canvas] not deleting transcript outside its node's conversations directory: ${r}`);continue}try{await Ee().delete(o,r)}catch(l){if(!(l instanceof we))throw l}}}i(k,"deleteNodeConversations");function Ge(e,n){try{const t=ce(F(e),S(F(b(n)),ue(n)));return t!==""&&t!==".."&&!t.startsWith(`..${pe}`)&&!le(t)}catch(t){if(t.code==="ENOENT")return!1;throw t}}i(Ge,"isBeneath");async function x(e,n){const t=await L(e,n);return t===null||t.deleted_at!==null?!1:(await Pe(e,t),!0)}i(x,"deleteNodeObject");async function U(e){return{nodeId:e,app:await h(e)===null?null:await Ue(e)}}i(U,"nodeFiles");async function X({nodeId:e,app:n}){n!==null&&C(T(n).nodes,await Ie(e,n)),C(b($(e)),$(e))}i(X,"removeNodeAppTrees");function Z({nodeId:e}){return[xe(e)]}i(Z,"nodeOwnedDirs");function ee({nodeId:e,app:n}){if(n!==null)try{re(S(T(n).conversations,e))}catch(t){if(!["ENOENT","ENOTEMPTY"].includes(t.code??""))throw t}}i(ee,"removeNodeConversationDir");async function j(e){await X(e);for(const n of Z(e))se(n,{recursive:!0,force:!0});ee(e)}i(j,"removeNodeFiles");async function Nt(e){if(await O(e))return{deleted:!1};const n=await U(e),t=await A([e]),a=await p(async o=>(await x(o,e),{deleted:!0}));return await k(t),await j(n),a}i(Nt,"deleteNode");async function Rt(e){const n=[...new Set(e)];if(n.length===0)return[];const t=await m(n,O);if(t.length>0)throw new Error(`refusing to delete nodes with unreconciled managed worktrees: ${t.join(", ")}`);const a=await D(n,U),o=await A(n),r=n.map(()=>"?").join(","),d=(await(await s()).prepareUncached(`SELECT node_id FROM nodes WHERE node_id IN (${r}) ORDER BY created`).all(...n)).map(({node_id:u})=>u),l=await m(d,async u=>await p(async _=>await x(_,u)));await k(o);for(const u of l){const _=a[n.indexOf(u)];await I(_,async()=>await j(_))}return l}i(Rt,"deleteNodes");async function St(e){const n=e.dryRun??!1,t=e.includeStale??!1,a=Date.now()-e.ttlDays*864e5,o=new Date(a).toISOString(),r=W()??"",d=await s(),l=i(c=>ae(c)<a,"quiet"),u=i(c=>({node_id:c.node_id,status:c.status,created:c.created}),"toPruned"),_=await Xe(),w=(await m((await d.prepare(`SELECT * FROM node_rows
|
|
60
60
|
WHERE status IN ('dead', 'done', 'canceled') AND created < ?
|
|
61
|
-
ORDER BY created`).all(o)).map(f),async c=>await
|
|
61
|
+
ORDER BY created`).all(o)).map(f),async c=>await G(c,{pastTtl:!0})&&l(c)&&!_.has(c.node_id)&&!await e.held(c.node_id))).map(u),R=t?(await m((await d.prepare(`SELECT * FROM node_rows
|
|
62
62
|
WHERE status IN ('active', 'idle') AND created < ?
|
|
63
|
-
ORDER BY created`).all(o)).map(f),async c=>c.node_id!==
|
|
63
|
+
ORDER BY created`).all(o)).map(f),async c=>c.node_id!==r&&!ve(c.pi_pid??null)&&(c.frozen_at??null)===null&&(c.respawn_not_before??null)===null&&c.intent!=="idle-release"&&!H(c)&&!await ye(c.node_id)&&!await O(c.node_id)&&l(c)&&!await e.held(c.node_id))).map(u):[],y=await ne([...w,...R]);return n||y.length===0?{pruned:y,dryRun:n}:(await te(y,e.removeDirs),{pruned:y,dryRun:n})}i(St,"pruneNodes");async function Xe(){const e=await(await s()).prepare(`SELECT DISTINCT d.owner AS owner FROM watches w
|
|
64
64
|
JOIN objects d ON d.id = w.object AND d.type = 'document' AND d.deleted_at IS NULL
|
|
65
65
|
JOIN objects o ON o.id = w.watcher AND o.deleted_at IS NULL
|
|
66
66
|
LEFT JOIN nodes n ON n.node_id = w.watcher
|
|
67
|
-
WHERE w.watcher != d.owner AND d.owner IS NOT NULL AND (n.node_id IS NULL OR n.status IN ('active', 'idle'))`).all();return new Set(e.map(n=>n.owner))}i(
|
|
67
|
+
WHERE w.watcher != d.owner AND d.owner IS NOT NULL AND (n.node_id IS NULL OR n.status IN ('active', 'idle'))`).all();return new Set(e.map(n=>n.owner))}i(Xe,"ownersOfWatchedDocuments");async function ne(e){const t=(await s()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ?"),a=new Set(e.map(o=>o.node_id));return await m(e,async o=>(await t.all(o.node_id,o.node_id)).every(r=>a.has(r.node_id)))}i(ne,"retainRunRootsWithMembers");async function te(e,n){if(e.length===0)return;const t=await D(e,async o=>await U(o.node_id)),a=await A(e.map(o=>o.node_id));for(const o of e)await p(async r=>await x(r,o.node_id));if(await k(a),n!==void 0){for(const o of t)await I(o,async()=>{await X(o),ee(o)});n(t.flatMap(Z));return}for(const o of t)await I(o,async()=>await j(o))}i(te,"deletePrunedNodes");async function I(e,n){try{n()}catch(t){fe({level:"error",event:"canvas.node_files.remove_failed",error:t,fields:{node_id:e.nodeId}})}}i(I,"eachNodeBestEffort");function ae(e){let n=0;for(const t of[e.created,e.launched_at,e.last_settled_at,e.finalized_at,e.outcome_at,e.last_activity_at]){if(t==null)continue;const a=Date.parse(t);Number.isFinite(a)&&a>n&&(n=a)}try{const t=de(S(ke(e.node_id),"telemetry.json"),{throwIfNoEntry:!1});t!==void 0&&t.mtimeMs>n&&(n=t.mtimeMs)}catch{}return n}i(ae,"lastActivityMs");async function ht(e){const n=e.dryRun??!1,t=await Ye(),a=await m(t,async u=>await G(u)&&!await e.held(u.node_id)),o=t.length-a.length,r=Math.max(0,e.maxNodes-o),d=a.map(u=>({row:u,recency:ae(u)})).sort((u,_)=>_.recency-u.recency).slice(r).map(({row:u})=>({node_id:u.node_id,status:u.status,created:u.created})),l=await ne(d);return n||l.length===0?{pruned:l,dryRun:n}:(await te(l),{pruned:l,dryRun:n})}i(ht,"pruneToLimit");export{V as PARKED_RESIDENT_SQL,Ke as RESUMABLE_NODE_SQL,Wn as abandonCreateRequest,vn as assertRunNotDeleting,tt as browseNodes,Fn as canonicalSessionFile,Et as childrenOf,pt as claimWarmSpare,Qn as clearFinalReportIfMatches,Zn as clearFrozen,In as completeCreateRequest,gn as createNode,Nt as deleteNode,Rt as deleteNodes,Mn as dueNodeDeadlines,An as findCreateRequest,bn as findNodeBySessionFile,Yn as getFinalReport,h as getNode,Cn as getRow,Hn as getRowByPane,it as graphNodes,G as isCollectible,_t as isWarmSpare,ae as lastActivityMs,Ye as listNodes,nt as listNodesByProfile,rt as listNodesForSupervision,dt as listParkedResidentsWithWakeMail,yt as listResumableRoots,ot as listTerminalPlacementResidue,ct as listWarmSpares,U as nodeFiles,Ve as nodesForSupervisionSql,xn as pruneExpiredCreateRequests,St as pruneNodes,ht as pruneToLimit,Un as recordCreateRequestNode,Kn as recordLaunch,et as recordRespawnAttempt,Gn as recordSettledTurn,ut as registerWarmSpare,j as removeNodeFiles,kn as reserveCreateRequest,jn as reserveCreateRequestNode,st as resumableNodeCwds,wt as rosterNodes,Xn as setFrozen,zn as setIntent,Pn as setNodeDeadline,Vn as setPresence,Bn as setStatus,$n as shiftProfileNodeDeadlines,Qe as terminalPlacementResidueSql,Jn as transitionStatusIntent,lt as unregisterWarmSpare,qn as updateNode,at as updateNodeTelemetry,ft as warmSpareCount,mt as withFreshTerminalGuard};
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var L=Object.defineProperty;var
|
|
2
|
-
`;
|
|
3
|
-
`,384))return await
|
|
4
|
-
${
|
|
1
|
+
var L=Object.defineProperty;var a=(e,t)=>L(e,"name",{value:t,configurable:!0});import{AsyncLocalStorage as x}from"node:async_hooks";import{createHash as N,randomBytes as P}from"node:crypto";import{appendFileSync as D,mkdirSync as M,readFileSync as b}from"node:fs";import{dirname as q,join as T}from"node:path";import{fileURLToPath as I}from"node:url";import p from"pg";import{resolveDatabaseUrl as B}from"../../daemon/postgres/supervisor.js";import{publishFileExclusive as F}from"../fs-utils.js";import{initializeEmptyLayout as U,runtimeRoot as d}from"../layout.js";import{translatePlaceholders as H,translateOnce as K}from"../statements.js";import{ensureHome as V}from"./paths.js";import{CANVAS_SCHEMA_VERSION as w}from"./schema-version.js";const G="crtr_test_template";class W extends Error{static{a(this,"CanvasConstraintError")}constraint;code="23505";constructor(t,n,r){super(t,r),this.name="CanvasConstraintError",this.constraint=n}}function v(e){const t=process.env.CRTR_DB_OPEN_LEDGER;if(t)try{const n=`${new Date().toISOString()} ${process.pid} ${process.argv[1]??"<unknown>"} ${e.replace(/\/\/([^:@/]*):[^@/]*@/,"//$1@")}
|
|
2
|
+
`;D(t,n)}catch{}}a(v,"recordOpenLedger");const Y=4,z=/\b(?:FOR\s+(?:NO\s+KEY\s+)?(?:UPDATE|SHARE)|FOR\s+KEY\s+SHARE|INTO|nextval|setval|set_config|pg_notify|pg_advisory\w*|pg_try_advisory\w*|lo_\w+)\b/i;function J(e){return/^\s*SELECT\b/i.test(e)&&!z.test(e)}a(J,"isReadStatement");const X=1e3,h=new x;class j{static{a(this,"FifoLock")}tail=Promise.resolve();pending=0;get busy(){return this.pending>0}drained(){return this.tail}async acquire(){this.pending++;const t=this.tail;let n;return this.tail=new Promise(r=>{n=r}),await t,()=>{this.pending--,n()}}}const Z=new Set([114,3802]),C={getTypeParser:a(((e,t)=>Z.has(e)?n=>n:e===20||e===1700?n=>{const r=Number(n);if(e===20&&!Number.isSafeInteger(r))throw new Error(`canvas db: bigint ${n} does not fit a JS number`);return r}:p.types.getTypeParser(e,t)),"getTypeParser")};class Q{static{a(this,"Handle")}url;lock=new j;db;client=null;pool=null;connecting=null;schemaChecked=!1;active=0;inFlight=new Set;constructor(t){this.url=t;const n=this;this.db={prepare:a(r=>this.statement(r,!0),"prepare"),prepareUncached:a(r=>this.statement(r,!1),"prepareUncached"),exec:a(r=>this.gated(async()=>{await this.send({text:r})}),"exec"),get inTransaction(){return E(n)}}}async connected(){return this.client!==null?this.client:(this.connecting??=this.connect().finally(()=>{this.connecting=null}),this.connecting)}async connect(){v(this.url);const t=new p.Client({connectionString:this.url,types:C,application_name:`crtr-${process.pid}`,connectionTimeoutMillis:1e4});t.on("error",n=>{console.warn(`[canvas db] connection error: ${n.message}`),this.drop(t)}),t.on("end",()=>this.drop(t)),await t.connect();try{this.schemaChecked||(await rt(t),this.schemaChecked=!0)}catch(n){throw await t.end().catch(()=>{}),n}return t.connection.stream.unref(),this.client=t,t}readPool(){if(this.pool!==null)return this.pool;v(this.url);const t=new p.Pool({connectionString:this.url,types:C,application_name:`crtr-${process.pid}`,connectionTimeoutMillis:1e4,max:Y,idleTimeoutMillis:1e4,allowExitOnIdle:!0});return t.on("error",n=>console.warn(`[canvas db] read connection error: ${n.message}`)),this.pool=t}async read(t){await this.lock.drained(),this.inFlight.size>0&&await Promise.all([...this.inFlight]);try{return await this.readPool().query(t)}catch(n){throw this.mapError(n,null)}}drop(t){this.client===t&&(this.client=null)}async send(t){const n=await this.connected(),r=h.getStore();if(r!==void 0&&r.open&&r.handle===this&&r.client!==n)throw new Error("canvas db: the connection was lost during this transaction; it was rolled back");return this.sendOn(n,t)}async sendOn(t,n){const r=t.connection.stream;this.active++===0&&r.ref();const o=t.query(n),s=o.then(()=>{},()=>{});this.inFlight.add(s),s.then(()=>this.inFlight.delete(s));try{return await o}catch(i){throw this.mapError(i,t)}finally{--this.active===0&&r.unref()}}mapError(t,n){const r=t;return r.code==="23505"?new W(r.message??"unique constraint violation",r.constraint,{cause:t}):((r.code==="57P01"||r.code==="ECONNRESET"||r.code==="EPIPE"||/Connection terminated|connection error|Client was closed/i.test(r.message??""))&&n!==null&&(this.drop(n),n.end().catch(()=>{})),t)}async gated(t){if(E(this)){const r=h.getStore(),o=performance.now();try{return await t()}finally{r.statementMs+=performance.now()-o}}if(!this.lock.busy)return t();const n=await this.lock.acquire();try{return await t()}finally{n()}}statement(t,n){const r=n?(()=>{const i=H(t);return{text:i.text,name:i.name}})():{text:K(t).text},o=J(r.text),s=a(i=>{const m={...r,values:i.map(tt)};return o&&!E(this)?this.read(m):this.gated(()=>this.send(m))},"query");return{get:a(async(...i)=>(await s(i)).rows[0],"get"),all:a(async(...i)=>(await s(i)).rows,"all"),run:a(async(...i)=>({changes:(await s(i)).rowCount??0}),"run")}}async close(){const t=this.pool;this.pool=null,await t?.end().catch(()=>{}),(await this.lock.acquire())();const n=this.client;this.client=null,n!==null&&(n.connection.stream.ref(),await n.end().catch(()=>{}))}}function tt(e){return ArrayBuffer.isView(e)&&!Buffer.isBuffer(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):e}a(tt,"bindable");function E(e){const t=h.getStore();return t!==void 0&&t.open&&t.handle===e}a(E,"ownsTransaction");const et=q(I(import.meta.url)),nt=T(et,"schema-pg.sql"),A=7427001;async function O(e,t){await e.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[t.key,t.hash])}a(O,"recordBlock");async function R(e){if((await e.query("SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'canvas_meta'")).rowCount===0)return;const n=await e.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'");return n.rows[0]===void 0?void 0:Number(n.rows[0].value)}a(R,"storedSchemaVersion");async function rt(e){await e.query("SELECT pg_advisory_lock($1)",[A]);try{const t=b(nt,"utf8"),n=[...t.matchAll(/^-- (\w+):begin\n([\s\S]*?)^-- \1:end$/gm)].map(([,o,s])=>({key:`schema_block.${o}`,sql:s,hash:N("sha256").update(s).digest("hex")}));let r=await R(e);if(r===void 0){await e.query(t),r=await R(e);for(const o of n)await O(e,o)}else if(r===w){const o=new Map((await e.query("SELECT key, value FROM canvas_meta WHERE key LIKE 'schema\\_block.%'")).rows.map(s=>[s.key,s.value]));for(const s of n)o.get(s.key)!==s.hash&&(await e.query(s.sql),await O(e,s))}if(r!==w)throw new Error(r!==void 0&&r>w?`unsupported canvas schema version ${r} (this build supports ${w}); this build is older than the database. Select a newer runtime generation.`:`canvas schema version ${String(r)} does not match this build's ${w}`)}finally{await e.query("SELECT pg_advisory_unlock($1)",[A]).catch(()=>{})}}a(rt,"ensureSchema");function at(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}a(at,"runningUnderNodeTest");function y(e,t){const n=/^(postgres(?:ql)?:\/\/[^/?#]*)(\/[^?#]*)?(.*)$/s.exec(e);if(n===null)throw new Error(`not a postgres URL: ${e.replace(/\/\/[^@/]*@/,"//")}`);return`${n[1]}/${t}${n[3]}`}a(y,"withDatabase");async function st(e){const t=T(d(),"test-db"),n=a(()=>{try{return b(t,"utf8").trim()||void 0}catch(i){if(i.code==="ENOENT")return;throw i}},"recorded"),r=n();if(r!==void 0)return y(e,r);M(d(),{recursive:!0});const o=`t_${P(8).toString("hex")}`,s=new p.Client({connectionString:y(e,"postgres")});await s.connect();try{if(await s.query(`CREATE DATABASE ${o} TEMPLATE ${G} STRATEGY file_copy`),!F(t,`${o}
|
|
3
|
+
`,384))return await s.query(`DROP DATABASE ${o}`),y(e,n())}finally{await s.end()}return y(e,o)}a(st,"testDatabaseUrl");async function ot(){const e=process.env.CRTR_TEST_PG_URL;if(e)return st(e);if(at())throw new Error("CRTR_TEST_PG_URL is not set: a test must reach its own Postgres (run the suite through the lane runner); refusing to open a real canvas database");return B(d())}a(ot,"canvasDatabaseUrl");const f=new Map,g=new Map;async function k(){U(),V();const e=await ot();g.set(d(),e);let t=f.get(e);return t===void 0&&f.set(e,t=new Q(e)),await t.connected(),t}a(k,"openHandle");async function _t(){return(await k()).db}a(_t,"openDb");async function bt(){const e=g.get(d());if(e===void 0)return;g.delete(d());const t=f.get(e);t!==void 0&&(f.delete(e),await t.close())}a(bt,"closeDb");async function Tt(){const e=[...f.values()];f.clear(),g.clear(),await Promise.all(e.map(t=>t.close()))}a(Tt,"closeAllDbs");function vt(e){const t=h.getStore();if(t===void 0||!t.open){queueMicrotask(e);return}t.afterCommit.push(e)}a(vt,"afterCommit");let it=0;async function Ct(e,t={}){const n=await k(),r=n.db;if(E(n)){if(!t.savepoint&&!t.rollback)return e(r);const c=`canvas_write_${++it}`;await n.send({text:`SAVEPOINT ${c}`});const l=h.getStore()?.afterCommit,S=l?.length??0;let _;try{_=await e(r)}catch($){throw await n.send({text:`ROLLBACK TO ${c}; RELEASE ${c}`}),l?.splice(S),$}return await n.send({text:t.rollback?`ROLLBACK TO ${c}; RELEASE ${c}`:`RELEASE ${c}`}),t.rollback&&l?.splice(S),_}const o=await n.lock.acquire(),s=performance.now(),i=new Error("canvas write transaction entered here");let m=!1;const u={handle:n,client:void 0,open:!0,afterCommit:[],statementMs:0};try{const c=await n.connected();await n.sendOn(c,{text:"BEGIN"}),u.client=c;try{const l=await h.run(u,()=>e(r));return await n.sendOn(c,{text:t.rollback?"ROLLBACK":"COMMIT"}),m=!t.rollback,l}catch(l){try{await n.sendOn(c,{text:"ROLLBACK"})}catch{}throw l}}finally{u.open=!1;const c=performance.now()-s;if(c-u.statementMs>X&&console.warn(`[canvas db] write transaction held ${Math.round(c)}ms (${Math.round(u.statementMs)}ms in its statements); its body did non-db work while every other db caller waited
|
|
4
|
+
${i.stack}`),o(),m)for(const l of u.afterCommit)queueMicrotask(l)}}a(Ct,"withCanvasWrite");export{W as CanvasConstraintError,vt as afterCommit,ot as canvasDatabaseUrl,Tt as closeAllDbs,bt as closeDb,J as isReadStatement,_t as openDb,Ct as withCanvasWrite};
|