@north-light/crouter 0.3.355 → 0.3.357
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/dto/delivery.d.ts +13 -3
- package/dist/api/dto/objects.d.ts +24 -22
- package/dist/api/dto/objects.js +1 -1
- package/dist/clients/attach/session/document-links.js +1 -1
- package/dist/clients/attach/viewer.js +829 -755
- package/dist/commands/canvas/list.js +3 -3
- package/dist/commands/canvas/read.d.ts +2 -4
- package/dist/commands/canvas/read.js +3 -5
- package/dist/commands/canvas/search.js +1 -1
- package/dist/commands/sys/context/admin/actions.js +1 -1
- package/dist/commands/sys/sync-deps.js +1 -1
- package/dist/commands/sys/sync-shared.js +1 -1
- package/dist/core/canvas/daemon-actions.d.ts +43 -0
- package/dist/core/canvas/daemon-actions.js +26 -0
- package/dist/core/canvas/deliveries.d.ts +1 -0
- package/dist/core/canvas/deliveries.js +1 -1
- package/dist/core/canvas/migrations.js +106 -46
- package/dist/core/feed/messages.d.ts +2 -20
- package/dist/core/feed/messages.js +6 -10
- package/dist/core/graph/events.d.ts +10 -5
- package/dist/core/graph/events.js +5 -5
- package/dist/core/graph/jobs.js +2 -2
- package/dist/core/graph/kinds/document.d.ts +19 -0
- package/dist/core/graph/kinds/document.js +5 -0
- package/dist/core/graph/kinds/dto.d.ts +5 -0
- package/dist/core/graph/kinds/dto.js +1 -0
- package/dist/core/graph/kinds/job.d.ts +2 -0
- package/dist/core/graph/kinds/job.js +2 -0
- package/dist/core/graph/kinds/kind.d.ts +39 -0
- package/dist/core/graph/kinds/kind.js +1 -0
- package/dist/core/graph/kinds/node.d.ts +2 -0
- package/dist/core/graph/kinds/node.js +4 -0
- package/dist/core/graph/kinds/owner.d.ts +2 -0
- package/dist/core/graph/kinds/owner.js +2 -0
- package/dist/core/graph/kinds/registry.d.ts +8 -0
- package/dist/core/graph/kinds/registry.js +1 -0
- package/dist/core/graph/kinds/render.d.ts +9 -0
- package/dist/core/graph/kinds/render.js +1 -0
- package/dist/core/graph/objects.d.ts +5 -4
- package/dist/core/graph/objects.js +5 -5
- package/dist/core/graph/types.d.ts +6 -5
- package/dist/core/providers/errors.d.ts +97 -0
- package/dist/core/providers/errors.js +1 -0
- package/dist/core/runtime/broker-protocol.d.ts +2 -7
- package/dist/daemon/api/app-listener.js +12 -12
- package/dist/daemon/api/handlers/human-requests.js +1 -1
- package/dist/daemon/api/handlers/objects.js +2 -5
- package/dist/daemon/api/handlers/providers.d.ts +69 -0
- package/dist/daemon/api/handlers/providers.js +1 -0
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/cron/settle.js +1 -1
- package/dist/daemon/cron/sinks.d.ts +3 -3
- package/dist/daemon/cron/sinks.js +2 -2
- package/dist/daemon/crtrd.js +6 -6
- package/dist/daemon/human/finish.js +6 -6
- package/dist/daemon/human/settle.d.ts +2 -3
- package/dist/daemon/human/settle.js +1 -1
- package/dist/daemon/reconcilers/action-lane.d.ts +8 -0
- package/dist/daemon/reconcilers/action-lane.js +1 -0
- package/dist/daemon/reconcilers/delivery-lane.d.ts +1 -1
- package/dist/daemon/reconcilers/delivery-lane.js +1 -1
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +11 -11
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var s=Object.defineProperty;var
|
|
2
|
-
${
|
|
3
|
-
`)},"render")});export{
|
|
1
|
+
var s=Object.defineProperty;var t=(e,r)=>s(e,"name",{value:r,configurable:!0});import{defineLeaf as a}from"../../core/command.js";import{PRIVACY_LEVELS_DTO as i}from"../../api/dto/objects.js";import{cliClient as c,rethrowAsCliError as d}from"../api-client.js";import{objectRow as l}from"./read.js";function m(e){const r=l(e);if(e.type!=="document")return r;const n=[`privacy=${e.privacy}`,e.rules!==void 0?`rules=${e.rules}`:""].filter(Boolean).join(" ");return`${r}
|
|
2
|
+
${n}${e.summary?` \u2014 ${e.summary}`:""}`}t(m,"listItemRow");const g=a({name:"list",description:"list objects by type, kind, owner, privacy or name prefix",whenToUse:"browsing what exists without reading it \u2014 the documents an owner holds, a node\u2019s reports, every job of a node. Records nothing, so listing does not make you a watcher.",help:{name:"canvas list",summary:"list the objects you can read, filtered; records nothing",params:[{kind:"flag",name:"type",type:"string",required:!1,constraint:"Only objects of this kind: document, node, job, person, app, repo or profile. The daemon rejects a name no kind declares and lists the kinds."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Only objects of this kind: for documents knowledge, preference or report; for nodes the node kind."},{kind:"flag",name:"owner",type:"string",required:!1,constraint:"Only objects this owner owns directly: a node id, a handle, or any name `crtr canvas read` accepts."},{kind:"flag",name:"privacy",type:"enum",choices:[...i],required:!1,constraint:"Only objects at this privacy level."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Only objects whose name starts with this prefix."},{kind:"flag",name:"deleted",type:"bool",required:!1,constraint:"When present, lists deleted objects instead of live ones."},{kind:"flag",name:"limit",type:"int",required:!1,default:50,constraint:"Page size, 1 to 100."},{kind:"flag",name:"cursor",type:"string",required:!1,constraint:"The `next_cursor` of the previous page."}],output:[{name:"items",type:"string[]",required:!0,constraint:"One row per object: id, `[[name]]`, type/kind, owner handle and preview. A document row adds its privacy, its rule count and its summary."},{name:"next_cursor",type:"string",required:!1,constraint:"Pass as `--cursor` for the next page; absent on the last page."}],outputKind:"object",effects:["None. Listing records no watch."]},run:t(async e=>{const r={...e.type?{type:e.type}:{},...e.kind?{kind:e.kind}:{},...e.owner?{owner:e.owner}:{},...e.privacy?{privacy:e.privacy}:{},...e.name?{name:e.name}:{},...e.deleted===!0?{deleted:!0}:{},limit:e.limit??50,...e.cursor?{cursor:e.cursor}:{}};try{return{...await c().objects.list(r)}}catch(n){d(n)}},"run"),render:t(e=>{const{items:r,next_cursor:n}=e;if(r.length===0)return"No objects match.";const o=r.map(m);return n&&o.push("",`More: \`crtr canvas list \u2026 --cursor ${n}\`.`),o.join(`
|
|
3
|
+
`)},"render")});export{m as listItemRow,g as listLeaf};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { LeafDef } from '../../core/command.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ObjectReadDTO } from '../../api/dto/objects.js';
|
|
3
3
|
export declare const REF_CONSTRAINT = "A name (the `[[ ]]` brackets are optional) or a raw object id. A name is `<owner-handle>/<path>`; a node or other owner is also named by its id or handle.";
|
|
4
|
-
export
|
|
5
|
-
/** One line per object: id, name, type/kind, owner handle and preview. */
|
|
6
|
-
export declare function objectRow(o: ObjectDTO): string;
|
|
4
|
+
export { attr, objectRow } from '../../core/graph/kinds/render.js';
|
|
7
5
|
export declare function renderRead(result: ObjectReadDTO): string;
|
|
8
6
|
export declare const readLeaf: LeafDef;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)}
|
|
3
|
-
|
|
4
|
-
</fields>`),e.needs_merge?(e.heads.forEach((i,c)=>{t.push(`<head${a("rev",i.rev_id)}${a("number",i.number)}${a("author",i.author_id)}>`,e.bodies[c]??"","</head>")}),t.push(`This document has ${e.heads.length} heads. Your next \`crtr doc edit\` writes the merged body and joins them into one revision.`)):t.push("",e.bodies[0]??""),e.listing&&e.listing.length>0){const i=e.listing.some(d=>d.owner?.id===n.owner?.id&&d.name.startsWith(`${n.name}/`)),c=e.listing.some(d=>d.owner?.id!==n.owner?.id||!d.name.startsWith(`${n.name}/`)),h=i&&c?`Names under ${n.name}/ and documents it links to`:i?`Names under ${n.name}/`:"Documents it links to";t.push("",...s(h,e.listing))}break}case"node":t.push(r("object",n,a("status",e.status))),t.push(`Mandate: ${e.mandate_first_line}`),t.push(...s("Children",e.children),...s("Owns",e.owned),...s("Jobs",e.jobs));break;case"job":t.push(r("object",n,a("status",e.status)+a("exit_code",e.exit_code))),t.push(`Command: ${e.command}`,`Log: ${e.log_path}`);break;default:t.push(r("object",n)),t.push(`Handle: ${e.handle}`,`Display name: ${e.display_name}`,`Identity: ${e.identity.scheme}:${e.identity.external_id}`)}return t.push("</object>"),t.join(`
|
|
5
|
-
`)}o(w,"renderRead");const g=u({name:"read",description:"read one object \u2014 a document, node, job or owner \u2014 by name or id",whenToUse:"you need the content or state of an object you can name: a document\u2019s body, a node\u2019s status and what it owns, a job\u2019s result. Reading makes you a watcher of the object. Find the name first with `crtr canvas search` or `crtr canvas list` when you do not know it.",help:{name:"canvas read",summary:"read one object by name or id and become a watcher of it",params:[{kind:"positional",name:"ref",required:!0,constraint:b},{kind:"flag",name:"fields",type:"bool",required:!1,constraint:"When present, a document read also returns its delivery rules, why-it-exists, unlisted, lint-ignore and plugin extension fields."}],output:[{name:"object",type:"string",required:!0,constraint:"`<object type kind id name owner \u2026>` header. A document adds privacy and revision, then its preview, summary and body; several heads show one `<head rev number author>` block each, and your next `crtr doc edit` merges them. A document lists the names under it and previews the documents it links to, as one section. It may be preceded by one `<auto-loaded-context>` block carrying the document\u2019s enclosing listings and the documents its `document-read` rules deliver; in a node, each is delivered once per context. A node shows its status, the first line of its mandate, its children, what it owns and its jobs; a job its command, status, exit code and log path; an owner its handle and identity."},{name:"deleted",type:"string",required:!1,constraint:"`<deleted \u2026>` with the last preview when the object was deleted."},{name:"remote",type:"string",required:!1,constraint:"A line saying no object has that name on this runtime: it may be on another runtime, or the name may be misspelled."}],outputKind:"object",effects:["A live read records a watch on the object for you (every event subject) and publishes a read event to its other watchers. A deleted or remote answer records nothing."]},run:o(async e=>{try{return{...await p().objects.read(e.ref,e.fields===!0?{fields:!0}:void 0)}}catch(n){l(n)}},"run"),render:o(e=>w(e),"render")});export{b as REF_CONSTRAINT,a as attr,y as objectRow,g as readLeaf,w as renderRead};
|
|
1
|
+
var o=Object.defineProperty;var n=(e,t)=>o(e,"name",{value:t,configurable:!0});import{defineLeaf as r}from"../../core/command.js";import{attr as a,header as d}from"../../core/graph/kinds/render.js";import{cliClient as i,rethrowAsCliError as s}from"../api-client.js";const c="A name (the `[[ ]]` brackets are optional) or a raw object id. A name is `<owner-handle>/<path>`; a node or other owner is also named by its id or handle.";import{attr as f,objectRow as j}from"../../core/graph/kinds/render.js";function m(e){if(e.state==="remote")return`No object named ${e.remote} on this runtime. It may be on another runtime, or the name may be misspelled.`;if(e.state==="deleted"){const t=e.deleted;return[`${d("deleted",t,a("deleted_at",t.deleted_at))}`,`Last preview: ${t.preview}`,"</deleted>"].join(`
|
|
2
|
+
`)}return`${e.text}
|
|
3
|
+
</object>`}n(m,"renderRead");const p=r({name:"read",description:"read one object \u2014 a document, node, job or owner \u2014 by name or id",whenToUse:"you need the content or state of an object you can name: a document\u2019s body, a node\u2019s status and what it owns, a job\u2019s result. Reading makes you a watcher of the object. Find the name first with `crtr canvas search` or `crtr canvas list` when you do not know it.",help:{name:"canvas read",summary:"read one object by name or id and become a watcher of it",params:[{kind:"positional",name:"ref",required:!0,constraint:c},{kind:"flag",name:"fields",type:"bool",required:!1,constraint:"When present, a document read also returns its delivery rules, why-it-exists, unlisted, lint-ignore and plugin extension fields."}],output:[{name:"object",type:"string",required:!0,constraint:"`<object type kind id name owner \u2026>` header. A document adds privacy and revision, then its preview, summary and body; several heads show one `<head rev number author>` block each, and your next `crtr doc edit` merges them. A document lists the names under it and previews the documents it links to, as one section. It may be preceded by one `<auto-loaded-context>` block carrying the document\u2019s enclosing listings and the documents its `document-read` rules deliver; in a node, each is delivered once per context. A node shows its status, the first line of its mandate, its children, what it owns and its jobs; a job its command, status, exit code and log path; an owner its handle and identity."},{name:"deleted",type:"string",required:!1,constraint:"`<deleted \u2026>` with the last preview when the object was deleted."},{name:"remote",type:"string",required:!1,constraint:"A line saying no object has that name on this runtime: it may be on another runtime, or the name may be misspelled."}],outputKind:"object",effects:["A live read records a watch on the object for you (every event subject) and publishes a read event to its other watchers. A deleted or remote answer records nothing."]},run:n(async e=>{try{return{...await i().objects.read(e.ref,e.fields===!0?{fields:!0}:void 0)}}catch(t){s(t)}},"run"),render:n(e=>m(e),"render")});export{c as REF_CONSTRAINT,f as attr,j as objectRow,p as readLeaf,m as renderRead};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var o=Object.defineProperty;var r=(e,t)=>o(e,"name",{value:t,configurable:!0});import{defineLeaf as i}from"../../core/command.js";import{
|
|
1
|
+
var o=Object.defineProperty;var r=(e,t)=>o(e,"name",{value:t,configurable:!0});import{defineLeaf as i}from"../../core/command.js";import{cliClient as s,rethrowAsCliError as c}from"../api-client.js";import{listItemRow as d}from"./list.js";const p=i({name:"search",description:"search objects by name, preview or body text",whenToUse:"finding an object when you do not know its name \u2014 a document on a topic, a report that mentions something, the exact line a regex matches. Records nothing; read a hit with `crtr canvas read`.",help:{name:"canvas search",summary:"find the objects you can read by text; records nothing",params:[{kind:"positional",name:"text",required:!0,constraint:"Words matched against names, previews and summaries (and document bodies with --body)."},{kind:"flag",name:"body",type:"bool",required:!1,constraint:"When present, also matches document bodies."},{kind:"flag",name:"grep",type:"string",required:!1,constraint:"An ECMAScript regex matched against document body lines; each hit lists its matching lines with line numbers."},{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."},{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:"limit",type:"int",required:!1,constraint:"Most hits returned, 1 to 100."}],output:[{name:"items",type:"string[]",required:!0,constraint:"One row per hit, as `crtr canvas list` shows it, followed by the matching body lines when --grep is set."}],outputKind:"object",effects:["None. Searching records no watch."]},run:r(async e=>{const t={text:e.text,...e.body===!0?{body:!0}:{},...e.grep?{grep:e.grep}:{},...e.type?{type:e.type}:{},...e.kind?{kind:e.kind}:{},...e.owner?{owner:e.owner}:{},...e.limit?{limit:e.limit}:{}};try{return{...await s().objects.search(t)}}catch(n){c(n)}},"run"),render:r(e=>{const{items:t}=e;return t.length===0?"No objects match.":t.map(n=>[d(n.object),...(n.matches??[]).map(a=>` ${a.line}: ${a.text}`)].join(`
|
|
2
2
|
`)).join(`
|
|
3
3
|
`)},"render")});export{p as searchLeaf};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var u=Object.defineProperty;var
|
|
1
|
+
var u=Object.defineProperty;var r=(e,t)=>u(e,"name",{value:t,configurable:!0});import{ApiError as c}from"../../../../api/index.js";import{cliClient as a}from"../../../api-client.js";import{ruleToDTO as l}from"./model.js";const f="editor";function d(e){const t=e.doc;return t.shipped?`${t.displayName} ships with the plugin \u201C${t.ownerHandle}\u201D and is read-only here \u2014 override it with \`crtr doc write ${t.name} --owner user\`.`:null}r(d,"readOnlyReason");function m(e){return e instanceof c?{ok:!1,message:e.message}:{ok:!1,message:e instanceof Error?e.message:String(e)}}r(m,"failure");async function i(e,t,n){const s=d(e);if(s!==null)return{ok:!1,message:s};try{return await a().docs.edit(e.doc.object.id,{rationale:f,...t}),{ok:!0,message:n,name:e.doc.displayName}}catch(o){return m(o)}}r(i,"runEdit");function h(e,t,n){return i(e,t.length===0?{unset:["rules"]}:{rules:t.map(l)},n)}r(h,"writeRules");function v(e){const t=e.doc.displayName;return e.doc.unlisted?i(e,{unset:["unlisted"]},`${t} is listed again.`):i(e,{unlisted:!0},`${t} is now unlisted.`)}r(v,"toggleUnlisted");function $(e,t){const n=t.trim();return n===""?Promise.resolve({ok:!1,message:"A preview cannot be empty \u2014 it is what a reader decides to open the document on."}):i(e,{preview:n},`Rewrote the preview of ${e.doc.displayName}.`)}r($,"writePreview");async function N(e){const t=await a().objects.read(e.object.id);if(t.state!=="object"||t.type!=="document")throw new Error(`${e.displayName} is no longer a readable document.`);const n=t.data;if(n.needs_merge)throw new Error(`${e.displayName} has diverged heads; merge them with \`crtr doc edit\` first.`);return n.bodies[0]??""}r(N,"readBody");function b(e,t){return t.trim()===""?Promise.resolve({ok:!1,message:`An empty body is not an edit \u2014 remove ${e.doc.displayName} with \`crtr doc delete\` instead.`}):i(e,{body:t},`Rewrote the body of ${e.doc.displayName}.`)}r(b,"writeBody");async function k(e,t){const n=t.trim();if(n==="")return{ok:!1,message:"Type the new name."};const s=d(e);if(s!==null)return{ok:!1,message:s};try{const o=await a().docs.move(e.doc.object.id,{new_name:n});return{ok:!0,message:`${e.doc.displayName} now answers to ${o.name}.`,name:o.name}}catch(o){return m(o)}}r(k,"moveDoc");export{k as moveDoc,N as readBody,d as readOnlyReason,v as toggleUnlisted,b as writeBody,$ as writePreview,h as writeRules};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var le=Object.defineProperty;var p=(s,n)=>le(s,"name",{value:n,configurable:!0});import{dirname as W,join as w,relative as F,resolve as X,sep as z}from"node:path";import{defineLeaf as pe}from"../../core/command.js";import{usage as D}from"../../core/errors.js";import{parseFrontmatterGeneric as Q}from"../../core/frontmatter.js";import{pathExists as M,readJsonIfExists as Y,readText as I,walkFiles as ee}from"../../core/fs-utils.js";import{cliClient as ue}from"../api-client.js";import{normalizedName as ne,putDoc as se,repoOwner as me,routeFromDescription as he,scalarString as fe,writeFailure as te}from"./sync-shared.js";import{isRecord as E}from"../../shared/predicates.js";import{readdirSync as ge}from"node:fs";const L="crtr sys sync deps";function f(s){throw D(`--deps: invalid package.json crtr.sync.deps.exclude: ${s}`)}p(f,"invalidDepsConfig");function ke(s){const n=Y(w(s,"package.json"));E(n)||f("package.json must contain an object");const o=n.crtr;if(o===void 0)return[];E(o)||f("crtr must be an object");const r=o.sync;if(r===void 0)return[];E(r)||f("crtr.sync must be an object");const t=r.deps;if(t===void 0)return[];E(t)||f("crtr.sync.deps must be an object");const i=t.exclude;if(i===void 0)return[];Array.isArray(i)||f("exclude must be an array of {package, version, skill} selectors");const m=new Set;return i.map((k,g)=>{E(k)||f(`exclude[${g}] must be an object with exactly package, version, and skill`),Object.keys(k).sort().join(",")!=="package,skill,version"&&f(`exclude[${g}] must contain exactly package, version, and skill`);const u=k.package,v=k.version,h=k.skill;(typeof u!="string"||!ye.test(u))&&f(`exclude[${g}].package must be one exact npm package name`),(typeof v!="string"||!$e.test(v))&&f(`exclude[${g}].version must be one exact installed semver version`),(typeof h!="string"||h===""||h!=="."&&(h.startsWith("/")||h.split("/").some(x=>x===""||x==="."||x==="..")||/[\\*?[\]{}]/.test(h)))&&f(`exclude[${g}].skill must be one exact POSIX path relative to the package skills/ directory (use . for a root SKILL.md)`);const $={package:u,version:v,skill:h},S=`${u}\0${v}\0${h}`;return m.has(S)&&f(`exclude[${g}] duplicates ${u}@${v} skill ${h}`),m.add(S),$})}p(ke,"readDepsExclusions");const ye=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,oe="(?:0|[1-9]\\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)",$e=new RegExp(`^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-${oe}(?:\\.${oe})*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$`);function _(s,n,o){return`${s}\0${n}\0${o}`}p(_,"exclusionKey");function be(s,n,o){return s.has(_(n.pkg,n.version,o))}p(be,"isExcluded");function we(s){if(s!==void 0)return X(s);let n=X(process.cwd());for(;;){if(M(w(n,"package.json")))return n;const o=W(n);if(o===n)throw D("--deps: no package.json found from cwd upward; run inside a Node project or pass --source <project-dir>");n=o}}p(we,"depsProjectDir");function ve(s){const n=[];for(const r of re(s))if(!r.startsWith("."))if(r.startsWith("@"))for(const t of re(w(s,r)))n.push(w(s,r,t));else n.push(w(s,r));const o=[];for(const r of n.sort()){const t=w(r,"skills");if(!M(t))continue;const i=Y(w(r,"package.json"));o.push({pkg:i?.name??F(s,r).split(z).join("/"),version:i?.version??"",skillsRoot:t})}return o}p(ve,"skillsPackages");function re(s){try{return ge(s)}catch{return[]}}p(re,"listDirNames");function xe(s,n){return n.find(o=>s===o||s.startsWith(`${o}${z}`))}p(xe,"owningBundle");function de(s,n=220){const o=s.replace(/\s+/g," ").trim(),r=o.split(/(?<=[.!?])\s+/)[0]??o,t=r.length>=30?r:o;if(t.length<=n)return t;const i=t.slice(0,n),m=i.lastIndexOf(" ");return`${i.slice(0,m>80?m:n).trimEnd()}\u2026`}p(de,"condenseDescription");function je(s){const n=s.match(/^#{1,6}\s+(.+)$/m);return n?n[1].trim():void 0}p(je,"firstHeading");async function Re(s,n){const o=we(n.sourceArg),r=w(o,"node_modules");if(!M(r))throw D(`sys sync deps: ${r} does not exist; install dependencies first (npm ci / npm install), then re-run`);const t=await me(s,o),i=[...t.existing].filter(e=>e.startsWith("deps/"));if(i.length>0||t.existing.has("deps")){const e=t.existing.has("deps")?await s.objects.read(`${t.handle}/deps`):null;if(!(e!==null&&e.state==="object"&&e.type==="document"&&e.bodies.some(c=>c.includes(L))))throw D(`--deps: repo ${t.handle} already has documents named deps/\u2026 but its \`deps\` document does not carry the "${L}" marker; they may be hand-written \u2014 move or delete them (\`crtr doc move\` / \`crtr doc delete\`) before vendoring dependency docs there`)}const m=ke(o),k=new Set(m.map(e=>_(e.package,e.version,e.skill))),g=new Map(m.map(e=>[_(e.package,e.version,e.skill),0])),y=new Map,u=[],v=ve(r),h=[];for(const e of v){const d=[],c=new Set;for(const a of ee(e.skillsRoot,l=>l==="SKILL.md",l=>l==="node_modules"||l===".git")){const l=W(a);d.push(l);const b=W(F(e.skillsRoot,a).split(z).join("/"));if(!be(k,e,b))continue;const j=_(e.pkg,e.version,b);g.set(j,(g.get(j)??0)+1),c.add(l),u.push({source:a,name:`${e.pkg}@${e.version}:${b}`,status:"excluded",reason:"matched package.json crtr.sync.deps.exclude"})}h.push({...e,bundleRoots:d.sort((a,l)=>l.length-a.length),excludedBundleRoots:c})}for(const e of m){const d=_(e.package,e.version,e.skill),c=g.get(d);if(c!==1)throw D(`--deps: package.json crtr.sync.deps.exclude selector ${e.package}@${e.version}:${e.skill} ${c===0?"does not match an installed dependency skill bundle":`matches ${c} installed dependency skill bundles`}; update or remove the selector before syncing`)}for(const e of h){const d=e.bundleRoots??[],c=e.excludedBundleRoots??new Set;for(const a of ee(e.skillsRoot,l=>l.toLowerCase().endsWith(".md"),l=>l==="node_modules"||l===".git")){const l=xe(a,d);if(l===void 0||c.has(l))continue;const b=F(e.skillsRoot,a).split(z).join("/");let j,R;if(b==="SKILL.md"||b.endsWith("/SKILL.md")){const V=W(b),H=ne(V==="."?e.pkg.split("/").pop()??e.pkg:V);R=H;let J={},P="";try{const U=Q(I(a));J=U.data??{},P=U.body}catch{P=I(a)}j={kind:"skill",sourceAbs:a,pkg:e.pkg,version:e.version,bundleRel:H,body:P,description:fe(J.description)??""}}else R=ne(b),j={kind:"sidecar-md",sourceAbs:a,pkg:e.pkg,version:e.version};if(R==="")continue;const q=y.get(R);if(q){j.kind==="skill"&&u.push({source:a,name:`${t.handle}/deps/${R}`,status:"skipped",reason:`collides with same-named document from ${q.pkg}`});continue}y.set(R,j)}}const $=[...y.entries()].filter(e=>e[1].kind==="skill"),S=$.map(([,e])=>e.bundleRel).sort((e,d)=>d.length-e.length),x=new Map;for(const[e,d]of y){if(d.kind==="skill")continue;const c=S.find(a=>e.startsWith(`${a}/`));c&&x.set(c,(x.get(c)??0)+1)}const C=new Set($.map(([,e])=>`${e.pkg}@${e.version}`)),O=`generated catalog of ${$.length} docs from ${C.size} package${C.size===1?"":"s"}`,B=p((e,d)=>{if(d.kind!=="skill")return;const c=x.get(d.bundleRel)??0;return c>0?`+${c} sidecar doc${c===1?"":"s"}`:void 0},"reasonFor"),G=i.filter(e=>!y.has(e.slice(5)));if(n.dryRun){for(const[e,d]of y){const c=B(e,d);u.push({source:d.sourceAbs,name:`${t.handle}/deps/${e}`,status:"would-import",...c?{reason:c}:{}})}for(const e of G)u.push({source:"\u2014",name:`${t.handle}/${e}`,status:"would-delete",reason:"no dependency ships it any more"});return u.push({source:r,name:`${t.handle}/deps`,status:"would-import",reason:O}),u}const T="regenerated by crtr sys sync deps";for(const[e,d]of y){const c=d.kind==="skill"?Se(e,d):Ae(e,d,S,t.handle);try{const a=await se(s,t,c,T),l=B(e,d);u.push({source:d.sourceAbs,name:a.name,status:a.changed?"imported":"unchanged",...l?{reason:l}:{}})}catch(a){u.push({source:d.sourceAbs,name:`${t.handle}/deps/${e}`,status:"skipped",reason:te(a)})}}for(const e of G)await s.docs.delete(`${t.handle}/${e}`),u.push({source:"\u2014",name:`${t.handle}/${e}`,status:"deleted",reason:"no dependency ships it any more"});const A=`${t.handle}/deps`,Z=[...new Set($.map(([,e])=>e.pkg))],ce=Z.slice(0,6).join(", ")+(Z.length>6?", \u2026":""),N=new Map;for(const[,e]of $){const d=`${e.pkg}@${e.version}`,c=N.get(d)??[],a=e.description!==""?` \u2014 ${de(e.description,180)}`:"";c.push(`- [[${A}/${e.bundleRel}]]${a}`),N.set(d,c)}const ie=[...N.entries()].map(([e,d])=>`## ${e}
|
|
1
|
+
var le=Object.defineProperty;var p=(s,n)=>le(s,"name",{value:n,configurable:!0});import{dirname as W,join as w,relative as F,resolve as X,sep as z}from"node:path";import{defineLeaf as pe}from"../../core/command.js";import{usage as D}from"../../core/errors.js";import{parseFrontmatterGeneric as Q}from"../../core/frontmatter.js";import{pathExists as M,readJsonIfExists as Y,readText as I,walkFiles as ee}from"../../core/fs-utils.js";import{cliClient as ue}from"../api-client.js";import{normalizedName as ne,putDoc as se,repoOwner as me,routeFromDescription as he,scalarString as fe,writeFailure as te}from"./sync-shared.js";import{isRecord as E}from"../../shared/predicates.js";import{readdirSync as ge}from"node:fs";const L="crtr sys sync deps";function f(s){throw D(`--deps: invalid package.json crtr.sync.deps.exclude: ${s}`)}p(f,"invalidDepsConfig");function ke(s){const n=Y(w(s,"package.json"));E(n)||f("package.json must contain an object");const o=n.crtr;if(o===void 0)return[];E(o)||f("crtr must be an object");const r=o.sync;if(r===void 0)return[];E(r)||f("crtr.sync must be an object");const t=r.deps;if(t===void 0)return[];E(t)||f("crtr.sync.deps must be an object");const i=t.exclude;if(i===void 0)return[];Array.isArray(i)||f("exclude must be an array of {package, version, skill} selectors");const m=new Set;return i.map((k,g)=>{E(k)||f(`exclude[${g}] must be an object with exactly package, version, and skill`),Object.keys(k).sort().join(",")!=="package,skill,version"&&f(`exclude[${g}] must contain exactly package, version, and skill`);const u=k.package,v=k.version,h=k.skill;(typeof u!="string"||!ye.test(u))&&f(`exclude[${g}].package must be one exact npm package name`),(typeof v!="string"||!$e.test(v))&&f(`exclude[${g}].version must be one exact installed semver version`),(typeof h!="string"||h===""||h!=="."&&(h.startsWith("/")||h.split("/").some(x=>x===""||x==="."||x==="..")||/[\\*?[\]{}]/.test(h)))&&f(`exclude[${g}].skill must be one exact POSIX path relative to the package skills/ directory (use . for a root SKILL.md)`);const $={package:u,version:v,skill:h},S=`${u}\0${v}\0${h}`;return m.has(S)&&f(`exclude[${g}] duplicates ${u}@${v} skill ${h}`),m.add(S),$})}p(ke,"readDepsExclusions");const ye=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,oe="(?:0|[1-9]\\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)",$e=new RegExp(`^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-${oe}(?:\\.${oe})*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$`);function _(s,n,o){return`${s}\0${n}\0${o}`}p(_,"exclusionKey");function be(s,n,o){return s.has(_(n.pkg,n.version,o))}p(be,"isExcluded");function we(s){if(s!==void 0)return X(s);let n=X(process.cwd());for(;;){if(M(w(n,"package.json")))return n;const o=W(n);if(o===n)throw D("--deps: no package.json found from cwd upward; run inside a Node project or pass --source <project-dir>");n=o}}p(we,"depsProjectDir");function ve(s){const n=[];for(const r of re(s))if(!r.startsWith("."))if(r.startsWith("@"))for(const t of re(w(s,r)))n.push(w(s,r,t));else n.push(w(s,r));const o=[];for(const r of n.sort()){const t=w(r,"skills");if(!M(t))continue;const i=Y(w(r,"package.json"));o.push({pkg:i?.name??F(s,r).split(z).join("/"),version:i?.version??"",skillsRoot:t})}return o}p(ve,"skillsPackages");function re(s){try{return ge(s)}catch{return[]}}p(re,"listDirNames");function xe(s,n){return n.find(o=>s===o||s.startsWith(`${o}${z}`))}p(xe,"owningBundle");function de(s,n=220){const o=s.replace(/\s+/g," ").trim(),r=o.split(/(?<=[.!?])\s+/)[0]??o,t=r.length>=30?r:o;if(t.length<=n)return t;const i=t.slice(0,n),m=i.lastIndexOf(" ");return`${i.slice(0,m>80?m:n).trimEnd()}\u2026`}p(de,"condenseDescription");function je(s){const n=s.match(/^#{1,6}\s+(.+)$/m);return n?n[1].trim():void 0}p(je,"firstHeading");async function Re(s,n){const o=we(n.sourceArg),r=w(o,"node_modules");if(!M(r))throw D(`sys sync deps: ${r} does not exist; install dependencies first (npm ci / npm install), then re-run`);const t=await me(s,o),i=[...t.existing].filter(e=>e.startsWith("deps/"));if(i.length>0||t.existing.has("deps")){const e=t.existing.has("deps")?await s.objects.read(`${t.handle}/deps`):null;if(!(e!==null&&e.state==="object"&&e.type==="document"&&e.data.bodies.some(c=>c.includes(L))))throw D(`--deps: repo ${t.handle} already has documents named deps/\u2026 but its \`deps\` document does not carry the "${L}" marker; they may be hand-written \u2014 move or delete them (\`crtr doc move\` / \`crtr doc delete\`) before vendoring dependency docs there`)}const m=ke(o),k=new Set(m.map(e=>_(e.package,e.version,e.skill))),g=new Map(m.map(e=>[_(e.package,e.version,e.skill),0])),y=new Map,u=[],v=ve(r),h=[];for(const e of v){const d=[],c=new Set;for(const a of ee(e.skillsRoot,l=>l==="SKILL.md",l=>l==="node_modules"||l===".git")){const l=W(a);d.push(l);const b=W(F(e.skillsRoot,a).split(z).join("/"));if(!be(k,e,b))continue;const j=_(e.pkg,e.version,b);g.set(j,(g.get(j)??0)+1),c.add(l),u.push({source:a,name:`${e.pkg}@${e.version}:${b}`,status:"excluded",reason:"matched package.json crtr.sync.deps.exclude"})}h.push({...e,bundleRoots:d.sort((a,l)=>l.length-a.length),excludedBundleRoots:c})}for(const e of m){const d=_(e.package,e.version,e.skill),c=g.get(d);if(c!==1)throw D(`--deps: package.json crtr.sync.deps.exclude selector ${e.package}@${e.version}:${e.skill} ${c===0?"does not match an installed dependency skill bundle":`matches ${c} installed dependency skill bundles`}; update or remove the selector before syncing`)}for(const e of h){const d=e.bundleRoots??[],c=e.excludedBundleRoots??new Set;for(const a of ee(e.skillsRoot,l=>l.toLowerCase().endsWith(".md"),l=>l==="node_modules"||l===".git")){const l=xe(a,d);if(l===void 0||c.has(l))continue;const b=F(e.skillsRoot,a).split(z).join("/");let j,R;if(b==="SKILL.md"||b.endsWith("/SKILL.md")){const V=W(b),H=ne(V==="."?e.pkg.split("/").pop()??e.pkg:V);R=H;let J={},P="";try{const U=Q(I(a));J=U.data??{},P=U.body}catch{P=I(a)}j={kind:"skill",sourceAbs:a,pkg:e.pkg,version:e.version,bundleRel:H,body:P,description:fe(J.description)??""}}else R=ne(b),j={kind:"sidecar-md",sourceAbs:a,pkg:e.pkg,version:e.version};if(R==="")continue;const q=y.get(R);if(q){j.kind==="skill"&&u.push({source:a,name:`${t.handle}/deps/${R}`,status:"skipped",reason:`collides with same-named document from ${q.pkg}`});continue}y.set(R,j)}}const $=[...y.entries()].filter(e=>e[1].kind==="skill"),S=$.map(([,e])=>e.bundleRel).sort((e,d)=>d.length-e.length),x=new Map;for(const[e,d]of y){if(d.kind==="skill")continue;const c=S.find(a=>e.startsWith(`${a}/`));c&&x.set(c,(x.get(c)??0)+1)}const C=new Set($.map(([,e])=>`${e.pkg}@${e.version}`)),O=`generated catalog of ${$.length} docs from ${C.size} package${C.size===1?"":"s"}`,B=p((e,d)=>{if(d.kind!=="skill")return;const c=x.get(d.bundleRel)??0;return c>0?`+${c} sidecar doc${c===1?"":"s"}`:void 0},"reasonFor"),G=i.filter(e=>!y.has(e.slice(5)));if(n.dryRun){for(const[e,d]of y){const c=B(e,d);u.push({source:d.sourceAbs,name:`${t.handle}/deps/${e}`,status:"would-import",...c?{reason:c}:{}})}for(const e of G)u.push({source:"\u2014",name:`${t.handle}/${e}`,status:"would-delete",reason:"no dependency ships it any more"});return u.push({source:r,name:`${t.handle}/deps`,status:"would-import",reason:O}),u}const T="regenerated by crtr sys sync deps";for(const[e,d]of y){const c=d.kind==="skill"?Se(e,d):Ae(e,d,S,t.handle);try{const a=await se(s,t,c,T),l=B(e,d);u.push({source:d.sourceAbs,name:a.name,status:a.changed?"imported":"unchanged",...l?{reason:l}:{}})}catch(a){u.push({source:d.sourceAbs,name:`${t.handle}/deps/${e}`,status:"skipped",reason:te(a)})}}for(const e of G)await s.docs.delete(`${t.handle}/${e}`),u.push({source:"\u2014",name:`${t.handle}/${e}`,status:"deleted",reason:"no dependency ships it any more"});const A=`${t.handle}/deps`,Z=[...new Set($.map(([,e])=>e.pkg))],ce=Z.slice(0,6).join(", ")+(Z.length>6?", \u2026":""),N=new Map;for(const[,e]of $){const d=`${e.pkg}@${e.version}`,c=N.get(d)??[],a=e.description!==""?` \u2014 ${de(e.description,180)}`:"";c.push(`- [[${A}/${e.bundleRel}]]${a}`),N.set(d,c)}const ie=[...N.entries()].map(([e,d])=>`## ${e}
|
|
2
2
|
|
|
3
3
|
${d.sort().join(`
|
|
4
4
|
`)}`).join(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var f=Object.defineProperty;var i=(n,e)=>f(n,"name",{value:e,configurable:!0});import{ApiError as h}from"../../api/index.js";import{identifyCheckout as p}from"../../core/graph/repo-sync/identity.js";import{rulesFromFrontmatter as w}from"../../core/graph/rules.js";import{usage as g}from"../../core/errors.js";function S(n){if(typeof n=="string")return n;if(typeof n=="number"||typeof n=="boolean")return String(n)}i(S,"scalarString");function m(n){let e=n.replace(/\s+/g," ").trim();return e=e.replace(/^[,;:.\s]+/,"").replace(/[,;:.\s]+$/,""),/^[A-Z][a-z]/.test(e)&&(e=e.charAt(0).toLowerCase()+e.slice(1)),e}i(m,"cleanClause");const y=/\b(?:use\s+this\s+skill\s+when|use\s+this\s+when|this\s+skill\s+should\s+be\s+used\s+when|used\s+when|use\s+when)\b\s*/i;function v(n,e){const t=n.replace(/\s+/g," ").trim();if(t==="")return`When this migrated ${e} applies, this ${e} should be read.`;const r=t.match(y);if(r&&r.index!==void 0){const a=m(t.slice(0,r.index)),o=m(t.slice(r.index+r[0].length));if(o!==""){let s=`When ${o}, this ${e} should be read`;return a!==""&&(s+=` because ${a}`),`${s}.`}}return`When ${m(t)}, this ${e} should be read.`}i(v,"routeFromDescription");function C(n){return n.replace(/\\/g,"/").replace(/\.md$/i,"").split("/").map(t=>t.trim().toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"")).filter(Boolean).join("/")}i(C,"normalizedName");function E(n){return w(n).map(e=>({on:e.on,deliver:e.deliver,...e.match!==void 0?{match:e.match}:{},...e.matchFrontmatter!==void 0?{match_frontmatter:e.matchFrontmatter}:{},...e.if!==void 0?{if:e.if}:{}}))}i(E,"rulesFromLegacyFrontmatter");async function d(n,e,t){let r;do{const a=await n.objects.list({type:e,limit:100,...r?{cursor:r}:{}});for(const o of a.items){const s=await n.objects.read(o.id);if(s.state!=="object"||s.type!==e)continue;const u=s.data;if(u.identity.external_id===t)return u.handle}r=a.next_cursor??void 0}while(r);return null}i(d,"ownerByIdentity");async function l(n,e){const t=new Set;let r;do{const a=await n.objects.list({type:"document",owner:e,limit:100,...r?{cursor:r}:{}});for(const o of a.items)t.add(o.name);r=a.next_cursor??void 0}while(r);return t}i(l,"documentNames");async function W(n,e){const t=p(e);if(!t.ok)throw g(`${e} is not inside a repo: ${t.reason}`);const r=await d(n,"repo",t.externalId);return{owner:"repo",cwd:t.root,handle:r??t.handleHint,existing:r?await l(n,r):new Set}}i(W,"repoOwner");async function k(n){const e=await d(n,"person","user:local");return{owner:"user",cwd:process.cwd(),handle:e??"user",existing:e?await l(n,e):new Set}}i(k,"userOwner");async function z(n,e,t,r){const a={preview:t.preview,...t.summary!==void 0&&t.summary!==""?{summary:t.summary}:{},...t.privacy!==void 0?{privacy:t.privacy}:{},rules:t.rules};if(!e.existing.has(t.name)){const c=await n.docs.write({name:t.name,body:t.body,kind:t.kind,owner:e.owner,cwd:e.cwd,...a});return e.handle=c.name.slice(0,c.name.length-t.name.length-1),e.existing.add(t.name),{name:c.name,changed:!0}}const o=`${e.handle}/${t.name}`,s=await n.objects.read(o),u=s.state==="object"&&s.type==="document"?s.data.bodies:null;return s.state==="object"&&u!==null&&u.length===1&&u[0]===t.body&&s.object.preview===t.preview?{name:o,changed:!1}:{name:(await n.docs.edit(o,{body:t.body,rationale:r,...a})).name,changed:!0}}i(z,"putDoc");function A(n){if(n instanceof h)return n.message;throw n}i(A,"writeFailure");export{C as normalizedName,z as putDoc,W as repoOwner,v as routeFromDescription,E as rulesFromLegacyFrontmatter,S as scalarString,k as userOwner,A as writeFailure};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { type DeliverySettlement } from './deliveries.js';
|
|
3
|
+
import type { DeliveryFailure, DeliverySettlement as ProcessDeliverySettlement } from './delivery-contract.js';
|
|
4
|
+
export type DaemonActionState = 'pending' | 'handed_off' | 'acknowledged' | 'permanent_failed';
|
|
5
|
+
export interface DaemonActionAdapter {
|
|
6
|
+
adapter: 'subprocess' | 'cron_spawn' | 'cron_human';
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface DaemonActionInput {
|
|
10
|
+
adapter: DaemonActionAdapter;
|
|
11
|
+
bodyJson: unknown;
|
|
12
|
+
/** Cron run id, or `human-action:<inbox ticket id>`. A duplicate returns the original action. */
|
|
13
|
+
idempotencyKey: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DaemonActionRecord {
|
|
16
|
+
actionId: string;
|
|
17
|
+
adapter: DaemonActionAdapter;
|
|
18
|
+
body: unknown | null;
|
|
19
|
+
idempotencyKey: string;
|
|
20
|
+
state: DaemonActionState;
|
|
21
|
+
attempts: number;
|
|
22
|
+
nextAttemptAt: number | null;
|
|
23
|
+
claimedAt: number | null;
|
|
24
|
+
claimOwner: string | null;
|
|
25
|
+
acknowledgedAt: number | null;
|
|
26
|
+
failedAt: number | null;
|
|
27
|
+
lastFailure: DeliveryFailure | null;
|
|
28
|
+
canceledAt: number | null;
|
|
29
|
+
createdAt: number;
|
|
30
|
+
updatedAt: number;
|
|
31
|
+
}
|
|
32
|
+
/** The table DDL. Shared by the migration that creates it; idempotent. */
|
|
33
|
+
export declare function createDaemonActionsTable(db: DatabaseSync): void;
|
|
34
|
+
/** Insert one frozen daemon action, due now. A duplicate idempotency key returns the original action id. */
|
|
35
|
+
export declare function enqueueDaemonAction(input: DaemonActionInput): string;
|
|
36
|
+
export declare function readDaemonAction(idempotencyKey: string): DaemonActionRecord | null;
|
|
37
|
+
export declare function readDaemonActionById(actionId: string): DaemonActionRecord | null;
|
|
38
|
+
export declare function dueDaemonActions(now: number): DaemonActionRecord[];
|
|
39
|
+
export declare function claimDaemonAction(actionId: string, now: number, claimOwner: string): DaemonActionRecord | null;
|
|
40
|
+
/** The state/attempt/owner fence, identical in shape to `settleDelivery`. */
|
|
41
|
+
export declare function settleDaemonAction(actionId: string, attempts: number, claimOwner: string, settlement: DeliverySettlement | ProcessDeliverySettlement, now?: number): boolean;
|
|
42
|
+
/** Daemon startup: an action a previous daemon handed off is due again now. */
|
|
43
|
+
export declare function resetHandedOffDaemonActions(now: number): number;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
var E=Object.defineProperty;var a=(t,e)=>E(t,"name",{value:e,configurable:!0});import{randomBytes as u}from"node:crypto";import{openDb as s,withCanvasWrite as _}from"./db.js";import{normalizedSettlement as N}from"./deliveries.js";function R(t){t.exec(`CREATE TABLE IF NOT EXISTS daemon_actions (
|
|
2
|
+
action_id TEXT PRIMARY KEY,
|
|
3
|
+
adapter_json TEXT NOT NULL,
|
|
4
|
+
body_json TEXT,
|
|
5
|
+
idempotency_key TEXT NOT NULL UNIQUE,
|
|
6
|
+
state TEXT NOT NULL CHECK (state IN ('pending', 'handed_off', 'acknowledged', 'permanent_failed')),
|
|
7
|
+
attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
|
|
8
|
+
next_attempt_at INTEGER,
|
|
9
|
+
claimed_at INTEGER,
|
|
10
|
+
claim_owner TEXT,
|
|
11
|
+
acknowledged_at INTEGER,
|
|
12
|
+
failed_at INTEGER,
|
|
13
|
+
last_failure_json TEXT,
|
|
14
|
+
canceled_at INTEGER,
|
|
15
|
+
created_at INTEGER NOT NULL,
|
|
16
|
+
updated_at INTEGER NOT NULL
|
|
17
|
+
);
|
|
18
|
+
CREATE INDEX IF NOT EXISTS daemon_actions_due ON daemon_actions(state, next_attempt_at, created_at, action_id) WHERE canceled_at IS NULL;`)}a(R,"createDaemonActionsTable");function r(t){return{actionId:t.action_id,adapter:JSON.parse(t.adapter_json),body:t.body_json===null?null:JSON.parse(t.body_json),idempotencyKey:t.idempotency_key,state:t.state,attempts:t.attempts,nextAttemptAt:t.next_attempt_at,claimedAt:t.claimed_at,claimOwner:t.claim_owner,acknowledgedAt:t.acknowledged_at,failedAt:t.failed_at,lastFailure:t.last_failure_json===null?null:JSON.parse(t.last_failure_json),canceledAt:t.canceled_at,createdAt:t.created_at,updatedAt:t.updated_at}}a(r,"actionFrom");function T(){return u(16).toString("hex")}a(T,"newId");function g(t){return _(e=>{const i=e.prepare("SELECT action_id FROM daemon_actions WHERE idempotency_key = ?").get(t.idempotencyKey);if(i!==void 0)return i.action_id;const o=Date.now(),n=T(),d=t.bodyJson===void 0?null:JSON.stringify(t.bodyJson);return e.prepare(`INSERT INTO daemon_actions (action_id, adapter_json, body_json, idempotency_key, state, attempts, next_attempt_at, created_at, updated_at)
|
|
19
|
+
VALUES (?, ?, ?, ?, 'pending', 0, ?, ?, ?)`).run(n,JSON.stringify(t.adapter),d,t.idempotencyKey,o,o,o),n})}a(g,"enqueueDaemonAction");function D(t){const e=s().prepare("SELECT * FROM daemon_actions WHERE idempotency_key = ?").get(t);return e===void 0?null:r(e)}a(D,"readDaemonAction");function S(t){const e=s().prepare("SELECT * FROM daemon_actions WHERE action_id = ?").get(t);return e===void 0?null:r(e)}a(S,"readDaemonActionById");function x(t){return s().prepare(`SELECT * FROM daemon_actions
|
|
20
|
+
WHERE state = 'pending' AND canceled_at IS NULL AND next_attempt_at <= ?
|
|
21
|
+
ORDER BY next_attempt_at, created_at, action_id`).all(t).map(r)}a(x,"dueDaemonActions");function I(t,e,i){return _(o=>{const n=o.prepare(`UPDATE daemon_actions SET state = 'handed_off', attempts = attempts + 1, claimed_at = ?, claim_owner = ?, updated_at = ?
|
|
22
|
+
WHERE action_id = ? AND state = 'pending' AND canceled_at IS NULL AND next_attempt_at <= ?`).run(e,i,e,t,e);return Number(n.changes)===0?null:r(o.prepare("SELECT * FROM daemon_actions WHERE action_id = ?").get(t))})}a(I,"claimDaemonAction");function O(t,e,i,o,n=Date.now()){const d=N(o),p=d.state==="pending"||d.state==="permanent_failed"?JSON.stringify(d.failure):null,c=d.state==="released"?"pending":d.state,m=d.state==="pending"?d.nextAttemptAt:c==="pending"?n:null;return _(l=>Number(l.prepare(`UPDATE daemon_actions
|
|
23
|
+
SET state = ?, next_attempt_at = ?, claimed_at = NULL, claim_owner = NULL,
|
|
24
|
+
acknowledged_at = ?, failed_at = ?, last_failure_json = ?, updated_at = ?
|
|
25
|
+
WHERE action_id = ? AND state = 'handed_off' AND attempts = ? AND claim_owner = ?`).run(c,m,c==="acknowledged"?n:null,c==="permanent_failed"?n:null,p,n,t,e,i).changes)===1)}a(O,"settleDaemonAction");function U(t){return _(e=>Number(e.prepare(`UPDATE daemon_actions SET state = 'pending', next_attempt_at = ?, claimed_at = NULL, claim_owner = NULL, updated_at = ?
|
|
26
|
+
WHERE state = 'handed_off'`).run(t,t).changes))}a(U,"resetHandedOffDaemonActions");export{I as claimDaemonAction,R as createDaemonActionsTable,x as dueDaemonActions,g as enqueueDaemonAction,D as readDaemonAction,S as readDaemonActionById,U as resetHandedOffDaemonActions,O as settleDaemonAction};
|
|
@@ -65,6 +65,7 @@ export type DeliverySettlement = {
|
|
|
65
65
|
nextAttemptAt: number;
|
|
66
66
|
failure: DeliveryFailure;
|
|
67
67
|
};
|
|
68
|
+
export declare function normalizedSettlement(settlement: DeliverySettlement | ProcessDeliverySettlement): DeliverySettlement;
|
|
68
69
|
/** The one execution/attempt/owner fence for node mail and external actions. */
|
|
69
70
|
export declare function settleDelivery(deliveryId: string, attempts: number, claimOwner: string, settlement: DeliverySettlement | ProcessDeliverySettlement, now?: number): boolean;
|
|
70
71
|
/** Broker acknowledgement additionally checks that the execution is live before the per-row fence.
|
|
@@ -90,4 +90,4 @@ CREATE TABLE IF NOT EXISTS waits (
|
|
|
90
90
|
);
|
|
91
91
|
CREATE UNIQUE INDEX IF NOT EXISTS waits_live ON waits(node_id) WHERE satisfied_at IS NULL;
|
|
92
92
|
CREATE INDEX IF NOT EXISTS waits_node ON waits(node_id);
|
|
93
|
-
`);const n=[];for(const t of e.prepare("SELECT node_id FROM nodes").all()){const a=t.node_id,l=R(T(a)),E=L(`${T(a)}.cursor`)?S(`${T(a)}.cursor`,"utf8").trim():"",A=l.findIndex(m=>m.entry_id===E),D=X(`${T(a)}.canceled`);l.forEach((m,f)=>n.push({nodeId:a,wake:1,source:"inbox",fileRank:0,physicalIndex:f,entry:m,acknowledged:A>=0&&f<=A,canceled:D.has(m.entry_id),seq:0}));for(const[m,f]of[[0,`${I(a)}.draining`],[1,I(a)]])R(f).forEach((x,v)=>n.push({nodeId:a,wake:0,source:"passive",fileRank:m,physicalIndex:v,entry:x,acknowledged:!1,canceled:!1,seq:0}))}n.sort((t,a)=>t.entry.ts.localeCompare(a.entry.ts)||t.nodeId.localeCompare(a.nodeId)||t.fileRank-a.fileRank||t.physicalIndex-a.physicalIndex),n.forEach((t,a)=>{t.seq=a+1});const d=new Map;for(const t of n){const a=`${t.nodeId}\0${t.source}`,l=d.get(a)??[];l.push(t),d.set(a,l)}for(const t of d.values()){const a=t.map(l=>l.seq).sort((l,E)=>l-E);t.sort((l,E)=>l.fileRank-E.fileRank||l.physicalIndex-E.physicalIndex).forEach((l,E)=>{l.seq=a[E]})}const r=e.prepare("INSERT INTO messages (message_id, seq, operation_id, created_at, sender_node_id, sender_name, kind, label, body_ref, body_json, disposition) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),i=e.prepare("INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, acknowledged_at, canceled_at, created_at, updated_at) VALUES (?, ?, 'node_inbox', ?, ?, ?, ?, 0, ?, ?, ?, ?)");for(const t of n){const a=t.entry;r.run(a.entry_id,t.seq,a.operation_id,a.ts,a.from,a.from_name??null,a.kind,a.label,a.ref??null,a.data===void 0?null:JSON.stringify(a.data),a.disposition??null);const l=Date.parse(a.ts),E=Number.isFinite(l)?l:0;i.run(p(),a.entry_id,t.nodeId,a.tier,t.wake,t.acknowledged?"acknowledged":"pending",t.acknowledged?E:null,t.canceled?E:null,E,E)}const _=e.prepare("SELECT * FROM human_action_deliveries").all();for(const t of _){const a=p();r.run(a,k(e),p(),new Date(t.created_at).toISOString(),null,null,"completed",t.request_id,null,t.completion_json,null);const l=t.state==="running"?"handed_off":t.state==="accepted"?"acknowledged":t.state;e.prepare("INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, next_attempt_at, claimed_at, claim_owner, acknowledged_at, failed_at, last_failure_json, adapter_json, idempotency_key, created_at, updated_at) VALUES (?, ?, 'external_action', 'human_action', 'normal', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(p(),a,l,t.attempt,t.next_attempt_at,t.claimed_at,t.claim_owner,t.accepted_at,t.permanent_failed_at,t.last_failure_json,JSON.stringify({adapter:"subprocess",argv:JSON.parse(t.argv_json),cwd:t.cwd}),`human-action:${t.request_id}`,t.created_at,t.updated_at)}const s=e.prepare("SELECT * FROM node_outcome_deliveries").all();for(const t of s){const a=t.document_json===null?null:p();a!==null&&r.run(a,k(e),p(),new Date(t.created_at).toISOString(),null,null,"completed",t.node_id,null,t.document_json,null);const l=t.document_json===null?"armed":t.state==="running"?"handed_off":t.state==="accepted"?"acknowledged":t.state;e.prepare("INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, next_attempt_at, claimed_at, claim_owner, acknowledged_at, failed_at, last_failure_json, adapter_json, idempotency_key, created_at, updated_at) VALUES (?, ?, 'external_action', ?, 'normal', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(p(),a,t.action_name,l,t.attempt,t.next_attempt_at,t.claimed_at,t.claim_owner,t.accepted_at,t.permanent_failed_at,t.last_failure_json,JSON.stringify({adapter:"subprocess",argv:JSON.parse(t.argv_json),cwd:t.cwd,config_origin:t.config_origin,payload:t.payload_json===null?null:JSON.parse(t.payload_json)}),`node-outcome:${t.node_id}`,t.created_at,t.updated_at)}const c=e.prepare("SELECT node_id, waiting_for, waiting_for_generation FROM nodes WHERE waiting_for IS NOT NULL").all();for(const t of c)e.prepare("INSERT INTO waits (wait_id, node_id, kind, target_id, generation, created_at, satisfied_at) VALUES (?, ?, 'controller', ?, ?, ?, NULL)").run(p(),t.node_id,t.waiting_for,t.waiting_for_generation,Date.now())}o(te,"migrateMessagingState");function k(e){return Number(e.prepare("SELECT COALESCE(MAX(seq), 0) AS seq FROM messages").get().seq)+1}o(k,"nextSeq");export{$ as acknowledgeNodeMail,z as acknowledgedNodeMailCount,ee as activateArmedExternalActionDelivery,Z as armExternalActionDelivery,G as claimExternalActionDelivery,q as claimKickoffMail,b as claimNodeMail,P as dueExternalActionDeliveries,V as hasUnseenWakeMail,Q as hasWakeMailSince,J as messageBodyForDelivery,te as migrateMessagingState,K as readExternalActionDelivery,B as resetHandedOffDeliveries,Y as resetStaleExecutionMail,h as settleDelivery};
|
|
93
|
+
`);const n=[];for(const t of e.prepare("SELECT node_id FROM nodes").all()){const a=t.node_id,l=R(T(a)),E=L(`${T(a)}.cursor`)?S(`${T(a)}.cursor`,"utf8").trim():"",A=l.findIndex(m=>m.entry_id===E),D=X(`${T(a)}.canceled`);l.forEach((m,f)=>n.push({nodeId:a,wake:1,source:"inbox",fileRank:0,physicalIndex:f,entry:m,acknowledged:A>=0&&f<=A,canceled:D.has(m.entry_id),seq:0}));for(const[m,f]of[[0,`${I(a)}.draining`],[1,I(a)]])R(f).forEach((x,v)=>n.push({nodeId:a,wake:0,source:"passive",fileRank:m,physicalIndex:v,entry:x,acknowledged:!1,canceled:!1,seq:0}))}n.sort((t,a)=>t.entry.ts.localeCompare(a.entry.ts)||t.nodeId.localeCompare(a.nodeId)||t.fileRank-a.fileRank||t.physicalIndex-a.physicalIndex),n.forEach((t,a)=>{t.seq=a+1});const d=new Map;for(const t of n){const a=`${t.nodeId}\0${t.source}`,l=d.get(a)??[];l.push(t),d.set(a,l)}for(const t of d.values()){const a=t.map(l=>l.seq).sort((l,E)=>l-E);t.sort((l,E)=>l.fileRank-E.fileRank||l.physicalIndex-E.physicalIndex).forEach((l,E)=>{l.seq=a[E]})}const r=e.prepare("INSERT INTO messages (message_id, seq, operation_id, created_at, sender_node_id, sender_name, kind, label, body_ref, body_json, disposition) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),i=e.prepare("INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, acknowledged_at, canceled_at, created_at, updated_at) VALUES (?, ?, 'node_inbox', ?, ?, ?, ?, 0, ?, ?, ?, ?)");for(const t of n){const a=t.entry;r.run(a.entry_id,t.seq,a.operation_id,a.ts,a.from,a.from_name??null,a.kind,a.label,a.ref??null,a.data===void 0?null:JSON.stringify(a.data),a.disposition??null);const l=Date.parse(a.ts),E=Number.isFinite(l)?l:0;i.run(p(),a.entry_id,t.nodeId,a.tier,t.wake,t.acknowledged?"acknowledged":"pending",t.acknowledged?E:null,t.canceled?E:null,E,E)}const _=e.prepare("SELECT * FROM human_action_deliveries").all();for(const t of _){const a=p();r.run(a,k(e),p(),new Date(t.created_at).toISOString(),null,null,"completed",t.request_id,null,t.completion_json,null);const l=t.state==="running"?"handed_off":t.state==="accepted"?"acknowledged":t.state;e.prepare("INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, next_attempt_at, claimed_at, claim_owner, acknowledged_at, failed_at, last_failure_json, adapter_json, idempotency_key, created_at, updated_at) VALUES (?, ?, 'external_action', 'human_action', 'normal', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(p(),a,l,t.attempt,t.next_attempt_at,t.claimed_at,t.claim_owner,t.accepted_at,t.permanent_failed_at,t.last_failure_json,JSON.stringify({adapter:"subprocess",argv:JSON.parse(t.argv_json),cwd:t.cwd}),`human-action:${t.request_id}`,t.created_at,t.updated_at)}const s=e.prepare("SELECT * FROM node_outcome_deliveries").all();for(const t of s){const a=t.document_json===null?null:p();a!==null&&r.run(a,k(e),p(),new Date(t.created_at).toISOString(),null,null,"completed",t.node_id,null,t.document_json,null);const l=t.document_json===null?"armed":t.state==="running"?"handed_off":t.state==="accepted"?"acknowledged":t.state;e.prepare("INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, next_attempt_at, claimed_at, claim_owner, acknowledged_at, failed_at, last_failure_json, adapter_json, idempotency_key, created_at, updated_at) VALUES (?, ?, 'external_action', ?, 'normal', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(p(),a,t.action_name,l,t.attempt,t.next_attempt_at,t.claimed_at,t.claim_owner,t.accepted_at,t.permanent_failed_at,t.last_failure_json,JSON.stringify({adapter:"subprocess",argv:JSON.parse(t.argv_json),cwd:t.cwd,config_origin:t.config_origin,payload:t.payload_json===null?null:JSON.parse(t.payload_json)}),`node-outcome:${t.node_id}`,t.created_at,t.updated_at)}const c=e.prepare("SELECT node_id, waiting_for, waiting_for_generation FROM nodes WHERE waiting_for IS NOT NULL").all();for(const t of c)e.prepare("INSERT INTO waits (wait_id, node_id, kind, target_id, generation, created_at, satisfied_at) VALUES (?, ?, 'controller', ?, ?, ?, NULL)").run(p(),t.node_id,t.waiting_for,t.waiting_for_generation,Date.now())}o(te,"migrateMessagingState");function k(e){return Number(e.prepare("SELECT COALESCE(MAX(seq), 0) AS seq FROM messages").get().seq)+1}o(k,"nextSeq");export{$ as acknowledgeNodeMail,z as acknowledgedNodeMailCount,ee as activateArmedExternalActionDelivery,Z as armExternalActionDelivery,G as claimExternalActionDelivery,q as claimKickoffMail,b as claimNodeMail,P as dueExternalActionDeliveries,V as hasUnseenWakeMail,Q as hasWakeMailSince,J as messageBodyForDelivery,te as migrateMessagingState,C as normalizedSettlement,K as readExternalActionDelivery,B as resetHandedOffDeliveries,Y as resetStaleExecutionMail,h as settleDelivery};
|