@north-light/crouter 0.3.354 → 0.3.356

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/api/dto/delivery.d.ts +13 -3
  2. package/dist/api/dto/docs.d.ts +2 -0
  3. package/dist/api/dto/objects.d.ts +25 -22
  4. package/dist/api/dto/objects.js +1 -1
  5. package/dist/api/dto/reviews.d.ts +6 -4
  6. package/dist/builtin-memory/02-turn-lifecycle/02-resident.md +1 -0
  7. package/dist/builtin-memory/04-orchestration-kernel.md +10 -31
  8. package/dist/builtin-memory/internal/memory-loading.md +1 -1
  9. package/dist/clients/attach/overlays/file-review.d.ts +1 -0
  10. package/dist/clients/attach/overlays/file-review.js +2 -2
  11. package/dist/clients/attach/render/chat-view.d.ts +6 -0
  12. package/dist/clients/attach/render/chat-view.js +2 -2
  13. package/dist/clients/attach/render/doc-links.d.ts +34 -0
  14. package/dist/clients/attach/render/doc-links.js +1 -0
  15. package/dist/clients/attach/render/markdown-source.js +5 -5
  16. package/dist/clients/attach/session/doc-materialize.d.ts +53 -0
  17. package/dist/clients/attach/session/doc-materialize.js +3 -0
  18. package/dist/clients/attach/session/document-links.d.ts +21 -0
  19. package/dist/clients/attach/session/document-links.js +1 -0
  20. package/dist/clients/attach/session/file-links.d.ts +2 -0
  21. package/dist/clients/attach/session/file-links.js +1 -1
  22. package/dist/clients/attach/viewer.js +837 -761
  23. package/dist/clients/inbox/review/review-client.d.ts +5 -1
  24. package/dist/clients/inbox/review/review-client.js +1 -1
  25. package/dist/commands/canvas/list.js +3 -3
  26. package/dist/commands/canvas/read.d.ts +2 -4
  27. package/dist/commands/canvas/read.js +3 -5
  28. package/dist/commands/canvas/search.js +1 -1
  29. package/dist/commands/doc/write.js +1 -1
  30. package/dist/commands/node/lifecycle.js +2 -2
  31. package/dist/commands/sys/context/admin/actions.js +1 -1
  32. package/dist/commands/sys/sync-deps.js +1 -1
  33. package/dist/commands/sys/sync-shared.js +1 -1
  34. package/dist/core/canvas/daemon-actions.d.ts +43 -0
  35. package/dist/core/canvas/daemon-actions.js +26 -0
  36. package/dist/core/canvas/deliveries.d.ts +1 -0
  37. package/dist/core/canvas/deliveries.js +1 -1
  38. package/dist/core/canvas/migrations.js +106 -46
  39. package/dist/core/feed/messages.d.ts +2 -20
  40. package/dist/core/feed/messages.js +6 -10
  41. package/dist/core/graph/deletion.d.ts +5 -4
  42. package/dist/core/graph/deletion.js +3 -3
  43. package/dist/core/graph/documents.d.ts +8 -1
  44. package/dist/core/graph/documents.js +9 -6
  45. package/dist/core/graph/edges.d.ts +3 -2
  46. package/dist/core/graph/edges.js +1 -1
  47. package/dist/core/graph/events.d.ts +11 -6
  48. package/dist/core/graph/events.js +5 -4
  49. package/dist/core/graph/jobs.js +2 -2
  50. package/dist/core/graph/kinds/document.d.ts +19 -0
  51. package/dist/core/graph/kinds/document.js +5 -0
  52. package/dist/core/graph/kinds/dto.d.ts +5 -0
  53. package/dist/core/graph/kinds/dto.js +1 -0
  54. package/dist/core/graph/kinds/job.d.ts +2 -0
  55. package/dist/core/graph/kinds/job.js +2 -0
  56. package/dist/core/graph/kinds/kind.d.ts +39 -0
  57. package/dist/core/graph/kinds/kind.js +1 -0
  58. package/dist/core/graph/kinds/node.d.ts +2 -0
  59. package/dist/core/graph/kinds/node.js +4 -0
  60. package/dist/core/graph/kinds/owner.d.ts +2 -0
  61. package/dist/core/graph/kinds/owner.js +2 -0
  62. package/dist/core/graph/kinds/registry.d.ts +8 -0
  63. package/dist/core/graph/kinds/registry.js +1 -0
  64. package/dist/core/graph/kinds/render.d.ts +9 -0
  65. package/dist/core/graph/kinds/render.js +1 -0
  66. package/dist/core/graph/names.d.ts +3 -1
  67. package/dist/core/graph/names.js +3 -3
  68. package/dist/core/graph/objects.d.ts +5 -4
  69. package/dist/core/graph/objects.js +5 -5
  70. package/dist/core/graph/repo-sync/exchange.js +2 -2
  71. package/dist/core/graph/types.d.ts +6 -5
  72. package/dist/core/runs/operations.js +5 -5
  73. package/dist/core/runtime/broker-persona-guidance.js +2 -2
  74. package/dist/core/runtime/broker-protocol.d.ts +2 -7
  75. package/dist/core/runtime/persona.js +2 -2
  76. package/dist/core/runtime/roadmap.d.ts +3 -0
  77. package/dist/core/runtime/roadmap.js +8 -4
  78. package/dist/core/substrate/delivery/deliver.js +1 -1
  79. package/dist/core/substrate/delivery/listings.d.ts +3 -0
  80. package/dist/core/substrate/delivery/listings.js +5 -2
  81. package/dist/core/substrate/delivery/render-boot.js +26 -26
  82. package/dist/core/substrate/delivery/render-event.d.ts +13 -6
  83. package/dist/core/substrate/delivery/render-event.js +6 -4
  84. package/dist/daemon/api/handlers/docs.js +1 -1
  85. package/dist/daemon/api/handlers/human-requests.js +1 -1
  86. package/dist/daemon/api/handlers/objects.js +2 -5
  87. package/dist/daemon/api/handlers/reviews.d.ts +4 -1
  88. package/dist/daemon/api/handlers/reviews.js +1 -1
  89. package/dist/daemon/cron/settle.js +1 -1
  90. package/dist/daemon/cron/sinks.d.ts +3 -3
  91. package/dist/daemon/cron/sinks.js +2 -2
  92. package/dist/daemon/crtrd.js +6 -6
  93. package/dist/daemon/human/finish.js +6 -6
  94. package/dist/daemon/human/settle.d.ts +2 -3
  95. package/dist/daemon/human/settle.js +1 -1
  96. package/dist/daemon/reconcilers/action-lane.d.ts +8 -0
  97. package/dist/daemon/reconcilers/action-lane.js +1 -0
  98. package/dist/daemon/reconcilers/delivery-lane.d.ts +1 -1
  99. package/dist/daemon/reconcilers/delivery-lane.js +1 -1
  100. package/dist/shared/inbox-entry-body.js +7 -7
  101. package/package.json +2 -2
  102. package/packages/crouter-identity/package.json +1 -1
  103. package/runtime.lock.json +11 -11
  104. package/scripts/lib/isolation-env.mjs +8 -2
@@ -14,10 +14,14 @@ export interface ResolvedReview {
14
14
  }
15
15
  export interface ReviewClient {
16
16
  byId(reviewId: string): Promise<ResolvedReview>;
17
+ /** Exactly one of a file path or a document ref. */
17
18
  createInline(p: {
18
19
  originNodeId: string;
20
+ } & ({
19
21
  file: string;
20
- }): Promise<ResolvedReview>;
22
+ } | {
23
+ document: string;
24
+ })): Promise<ResolvedReview>;
21
25
  comments(reviewId: string): CommentAuthority;
22
26
  submit(reviewId: string): Promise<SubmitDisposition>;
23
27
  cancel(reviewId: string, reason: string): Promise<void>;
@@ -1 +1 @@
1
- var u=Object.defineProperty;var r=(e,t)=>u(e,"name",{value:t,configurable:!0});import{randomUUID as l}from"node:crypto";import{ApiError as p}from"../../../api/errors.js";import{cliClient as a}from"../../../commands/api-client.js";import{createCommentAuthority as w}from"./comments-client.js";class s extends Error{static{r(this,"ReviewTerminalError")}state;resultPath;reason;constructor(t,n){if(t==="approved"){if(n===void 0||n==="")throw new Error("approved review omitted its canonical result path");super(`review is already approved; see ${n}`),this.resultPath=n}else super(n===void 0||n===""?"review is already canceled":`review is already canceled: ${n}`),n!==void 0&&n!==""&&(this.reason=n);this.name="ReviewTerminalError",this.state=t}}function b(e={}){const t=r(async n=>{try{return await n()}catch(i){const o=h(i);throw o!==void 0&&e.onTerminalConflict?.(o),i}},"request");return{async byId(n){const i=await t(()=>a().getReview(n));return await c(i,i.companion_node_id,t)},async createInline(n){const i=await t(()=>a().createReview({origin_kind:"inline",origin_node_id:n.originNodeId,file:n.file,idempotency_key:l()}));return await c(i,i.companion_node_id,t)},comments(n){return w(n,t)},async submit(n){const i=await t(()=>a().submitReview(n));return i.outcome==="awaiting_companion"?{outcome:"awaiting_companion"}:{outcome:"approved",changed:m(i.review),resultPath:d(i.review)}},async cancel(n,i){await t(()=>a().cancelReview(n,{reason:i,actor:"human",disposition:"dismissed"}))}}}r(b,"createReviewClient");async function c(e,t,n){if(e.state!=="open")throw f(e);const i=await n(()=>a().getNode(t));return{reviewId:e.review_id,companionNodeId:t,file:e.file,cwd:i.cwd}}r(c,"resolveReview");function f(e){if(e.state!=="approved"&&e.state!=="canceled")throw new Error(`terminal review ${e.review_id} has invalid state ${e.state}`);return e.state==="approved"?new s("approved",d(e)):new s("canceled",e.cancel_reason)}r(f,"terminalError");function m(e){if(typeof e.changed!="boolean")throw new Error(`approved review ${e.review_id} omitted its changed disposition`);return e.changed}r(m,"requireChanged");function d(e){if(typeof e.result_path!="string"||e.result_path==="")throw new Error(`terminal review ${e.review_id} omitted its canonical result path`);return e.result_path}r(d,"requireResultPath");function T(e){return e.state==="approved"?`This review was already approved. Result: ${e.resultPath??"unknown"}`:e.reason===void 0||e.reason===""?"This review was canceled.":`This review was canceled: ${e.reason}`}r(T,"terminalRefusalNotice");function h(e){if(!(e instanceof p)||e.status!==409||e.code!=="review_terminal_conflict")return;const t=e.details?.review;if(t!==void 0){if(t.state==="approved")return t.result_path===void 0?"This review was already approved by someone else.":`This review was already approved by someone else. Result: ${t.result_path}`;if(t.state==="canceled")return t.cancel_reason===void 0||t.cancel_reason===""?"This review was canceled before this submission landed.":`This review was canceled before this submission landed: ${t.cancel_reason}`}}r(h,"terminalConflictNotice");export{s as ReviewTerminalError,b as createReviewClient,h as terminalConflictNotice,T as terminalRefusalNotice};
1
+ var u=Object.defineProperty;var r=(e,t)=>u(e,"name",{value:t,configurable:!0});import{randomUUID as l}from"node:crypto";import{ApiError as w}from"../../../api/errors.js";import{cliClient as a}from"../../../commands/api-client.js";import{createCommentAuthority as p}from"./comments-client.js";class s extends Error{static{r(this,"ReviewTerminalError")}state;resultPath;reason;constructor(t,n){if(t==="approved"){if(n===void 0||n==="")throw new Error("approved review omitted its canonical result path");super(`review is already approved; see ${n}`),this.resultPath=n}else super(n===void 0||n===""?"review is already canceled":`review is already canceled: ${n}`),n!==void 0&&n!==""&&(this.reason=n);this.name="ReviewTerminalError",this.state=t}}function b(e={}){const t=r(async n=>{try{return await n()}catch(i){const o=h(i);throw o!==void 0&&e.onTerminalConflict?.(o),i}},"request");return{async byId(n){const i=await t(()=>a().getReview(n));return await c(i,i.companion_node_id,t)},async createInline(n){const i=await t(()=>a().createReview({origin_kind:"inline",origin_node_id:n.originNodeId,..."file"in n?{file:n.file}:{document:n.document},idempotency_key:l()}));return await c(i,i.companion_node_id,t)},comments(n){return p(n,t)},async submit(n){const i=await t(()=>a().submitReview(n));return i.outcome==="awaiting_companion"?{outcome:"awaiting_companion"}:{outcome:"approved",changed:m(i.review),resultPath:d(i.review)}},async cancel(n,i){await t(()=>a().cancelReview(n,{reason:i,actor:"human",disposition:"dismissed"}))}}}r(b,"createReviewClient");async function c(e,t,n){if(e.state!=="open")throw f(e);const i=await n(()=>a().getNode(t));return{reviewId:e.review_id,companionNodeId:t,file:e.file,cwd:i.cwd}}r(c,"resolveReview");function f(e){if(e.state!=="approved"&&e.state!=="canceled")throw new Error(`terminal review ${e.review_id} has invalid state ${e.state}`);return e.state==="approved"?new s("approved",d(e)):new s("canceled",e.cancel_reason)}r(f,"terminalError");function m(e){if(typeof e.changed!="boolean")throw new Error(`approved review ${e.review_id} omitted its changed disposition`);return e.changed}r(m,"requireChanged");function d(e){if(typeof e.result_path!="string"||e.result_path==="")throw new Error(`terminal review ${e.review_id} omitted its canonical result path`);return e.result_path}r(d,"requireResultPath");function T(e){return e.state==="approved"?`This review was already approved. Result: ${e.resultPath??"unknown"}`:e.reason===void 0||e.reason===""?"This review was canceled.":`This review was canceled: ${e.reason}`}r(T,"terminalRefusalNotice");function h(e){if(!(e instanceof w)||e.status!==409||e.code!=="review_terminal_conflict")return;const t=e.details?.review;if(t!==void 0){if(t.state==="approved")return t.result_path===void 0?"This review was already approved by someone else.":`This review was already approved by someone else. Result: ${t.result_path}`;if(t.state==="canceled")return t.cancel_reason===void 0||t.cancel_reason===""?"This review was canceled before this submission landed.":`This review was canceled before this submission landed: ${t.cancel_reason}`}}r(h,"terminalConflictNotice");export{s as ReviewTerminalError,b as createReviewClient,h as terminalConflictNotice,T as terminalRefusalNotice};
@@ -1,3 +1,3 @@
1
- var s=Object.defineProperty;var n=(e,r)=>s(e,"name",{value:r,configurable:!0});import{defineLeaf as a}from"../../core/command.js";import{OBJECT_TYPES as i,PRIVACY_LEVELS_DTO as c}from"../../api/dto/objects.js";import{cliClient as d,rethrowAsCliError as l}from"../api-client.js";import{objectRow as m}from"./read.js";function f(e){const r=m(e);if(e.type!=="document")return r;const t=[`privacy=${e.privacy}`,e.rules!==void 0?`rules=${e.rules}`:""].filter(Boolean).join(" ");return`${r}
2
- ${t}${e.summary?` \u2014 ${e.summary}`:""}`}n(f,"listItemRow");const w=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:"enum",choices:[...i],required:!1,constraint:"Only objects of this type."},{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:[...c],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:n(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 d().objects.list(r)}}catch(t){l(t)}},"run"),render:n(e=>{const{items:r,next_cursor:t}=e;if(r.length===0)return"No objects match.";const o=r.map(f);return t&&o.push("",`More: \`crtr canvas list \u2026 --cursor ${t}\`.`),o.join(`
3
- `)},"render")});export{f as listItemRow,w as listLeaf};
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 { ObjectDTO, ObjectReadDTO } from '../../api/dto/objects.js';
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 declare function attr(name: string, value: string | number | null | undefined): string;
5
- /** One line per object: id, name, type/kind, owner handle and preview. */
6
- export declare function objectRow(o: ObjectDTO): string;
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 c=Object.defineProperty;var o=(e,n)=>c(e,"name",{value:n,configurable:!0});import{defineLeaf as h}from"../../core/command.js";import{cliClient as m,rethrowAsCliError as u}from"../api-client.js";const p="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.";function a(e,n){return n==null||n===""?"":` ${e}="${String(n).replace(/"/g,"&quot;")}"`}o(a,"attr");function b(e){const n=e.kind?`${e.type}/${e.kind}`:e.type,t=e.owner?` owner=${e.owner.handle}`:"",i=e.deleted_at?" (deleted)":"";return`- ${e.id} [[${e.name}]] ${n}${t}${i} \u2014 ${e.preview}`}o(b,"objectRow");function d(e,n,t=""){return`<${e}${a("type",n.type)}${a("kind",n.kind)}${a("id",n.id)}${a("name",n.name)}${a("owner",n.owner?.handle)}${t}>`}o(d,"header");function r(e,n){return n.length===0?[]:[`${e}:`,...n.map(b)]}o(r,"section");function l(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 i=e.deleted;return[`${d("deleted",i,a("deleted_at",i.deleted_at))}`,`Last preview: ${i.preview}`,"</deleted>"].join(`
2
- `)}const n=e.object,t=[];switch(e.type){case"document":{e.attachments&&t.push(e.attachments,""),t.push(d("object",n,a("privacy",n.privacy)+a("revision",e.revision))),t.push(`Preview: ${n.preview}`),e.summary&&t.push(`Summary: ${e.summary}`),e.fields!==void 0&&t.push(`<fields>
3
- ${JSON.stringify(e.fields,null,2)}
4
- </fields>`),e.needs_merge?(e.heads.forEach((i,s)=>{t.push(`<head${a("rev",i.rev_id)}${a("number",i.number)}${a("author",i.author_id)}>`,e.bodies[s]??"","</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&&t.push("",...r(`Names under ${n.name}/`,e.listing));break}case"node":t.push(d("object",n,a("status",e.status))),t.push(`Mandate: ${e.mandate_first_line}`),t.push(...r("Children",e.children),...r("Owns",e.owned),...r("Jobs",e.jobs));break;case"job":t.push(d("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(d("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(l,"renderRead");const $=h({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:p},{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 with names under it lists them. 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 m().objects.read(e.ref,e.fields===!0?{fields:!0}:void 0)}}catch(n){u(n)}},"run"),render:o(e=>l(e),"render")});export{p as REF_CONSTRAINT,a as attr,b as objectRow,$ as readLeaf,l 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{OBJECT_TYPES as s}from"../../api/dto/objects.js";import{cliClient as c,rethrowAsCliError as d}from"../api-client.js";import{listItemRow as m}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:"enum",choices:[...s],required:!1,constraint:"Only objects of this type."},{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 c().objects.search(t)}}catch(n){d(n)}},"run"),render:r(e=>{const{items:t}=e;return t.length===0?"No objects match.":t.map(n=>[m(n.object),...(n.matches??[]).map(a=>` ${a.line}: ${a.text}`)].join(`
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 o=Object.defineProperty;var r=(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 m,ownerFromInput as u,ownerParam as l,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>]]`. 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."},{...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("Who beyond the owner may read it; default public. A repo-owned document must be public."),l("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:r(async e=>{const n={name:e.name,body:e.body,...m(e),...u(e),cwd:process.cwd()};try{return{...await s().docs.write(n)}}catch(i){d(i)}},"run"),render:r(e=>`<written name="${String(e.name)}" id="${String(e.id)}">${c(e)}`,"render")});export{b as writeLeaf};
1
+ var o=Object.defineProperty;var r=(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 m,ownerFromInput as l,ownerParam as u,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."},{...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("Who beyond the owner may read it; default public. A repo-owned document must be public."),u("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:r(async e=>{const n={name:e.name,body:e.body,...m(e),...l(e),cwd:process.cwd()};try{return{...await s().docs.write(n)}}catch(i){d(i)}},"run"),render:r(e=>`<written name="${String(e.name)}" id="${String(e.id)}">${c(e)}`,"render")});export{b as writeLeaf};
@@ -1,2 +1,2 @@
1
- var C=Object.defineProperty;var l=(e,t)=>C(e,"name",{value:t,configurable:!0});import{envNodeId as k}from"../../shared/env.js";import{defineBranch as P,defineLeaf as y}from"../../core/command.js";import{InputError as d}from"../../core/io.js";import{cliClient as f,getNodeOrNull as $,rethrowAsCliError as p}from"../api-client.js";import{DEFAULT_BASH_NICENESS as N,MAX_BASH_NICENESS as v,writeBashNiceness as D}from"../../core/bash-jobs.js";import{nodeReviveLeaf as I}from"../node-lifecycle-revive.js";import{kindsStateBlock as _}from"../profile/daemon-profiles.js";import{MODEL_SPEC_FORMS as O}from"../../core/runtime/model-selection.js";import{closePane as R,paneExists as U,respawnPaneSync as L,viewerEnv as j,viewerPaneOf as M,nodeInPane as x}from"../surface/node/placement.js";const F=y({name:"recycle",description:"finish the agent in your pane + recycle it into a fresh root",whenToUse:"you are at an agent's pane and done with it: finish it cleanly and recycle the pane in one move \u2014 push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane to keep working. The hands-on way to end an agent and immediately start over in place. Use `node lifecycle demote` instead to put it on a finishing track IN PLACE (flip terminal, keep it running) without ending it now, `node lifecycle close` to tear a node and its subtree down WITHOUT finishing (no report, revivable), and `push final` when the agent should finish ITSELF from inside its own turn",help:{name:"node lifecycle recycle",summary:"finish the agent in your current pane and recycle the pane \u2014 push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to finish. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to recycle. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"recycled",type:"boolean",required:!0,constraint:"True when the pane was recycled into a fresh root."},{name:"node_id",type:"string",required:!1,constraint:"The finished node."},{name:"new_root",type:"string",required:!1,constraint:"The fresh root node booted into the pane."}],outputKind:"object",effects:["Pushes a final report from the node (fans out to all subscribers) and marks it done.","Kills the agent's pi and respawns a fresh resident root in the same tmux pane."]},run:l(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=x(t)),n===void 0||n==="")throw new d({error:"no_node",message:"no node found in this pane to finish",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await f().getNode(n).catch(c=>p(c,"List nodes with `crtr node inspect list`.")),o=(await f().recycleNode(n).catch(p)).node_id;let a=!1;return t!==void 0&&t!==""&&(a=L({pane:t,cwd:i.cwd,env:j(),command:"crtr"})),{recycled:a,node_id:n,new_root:o}},"run"),render:l(e=>{if(e.recycled!==!0)return e.new_root?`Finished ${e.node_id} and spawned a fresh broker root (${e.new_root}), but its viewer did not attach \u2014 the new broker did not come up. Re-focus the root to view it.`:`Recycle failed for ${e.node_id??"?"} \u2014 not in tmux, or no agent in this pane.`;const t=[`Recycled the pane \u2014 finished ${e.node_id}.`];return e.new_root&&t.push(`- new root: ${e.new_root}`),t.join(`
2
- `)},"render")}),H=y({name:"close",description:"close a node + cascade-cancel its exclusive subtree",whenToUse:"you want to tear a node down WITHOUT finishing it, cascade-cancelling every descendant it exclusively owns: abandoning a line of work, killing a stuck or wrong-turn subtree, clearing a branch you no longer need. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`node lifecycle revive`). Use `node lifecycle recycle` instead to FINISH the agent in your pane with a final report, and `push final` when a worker should end its own work normally (Alt+C \u2192 x)",help:{name:"node lifecycle close",summary:"close a node and cascade-cancel its subtree \u2014 kill its tmux window plus those of every descendant it EXCLUSIVELY owns (down the subscribes_to spine), mark retained nodes canceled, and leave them a notice they read on resume. A descendant still subscribed to by a manager outside the subtree is left running. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`crtr node lifecycle revive`)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to close. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to close. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The node that was closed \u2014 the cascade root."},{name:"count",type:"number",required:!1,constraint:"How many nodes were closed (root + cascaded descendants)."},{name:"closed_ids",type:"string[]",required:!1,constraint:"All closed node ids, kill order (leaves first, root last)."},{name:"spared",type:"string[]",required:!1,constraint:"Descendants left alive because a manager outside the subtree still subscribes to them."}],outputKind:"object",effects:["Marks retained nodes and their exclusive descendants `canceled` and clears intent (the daemon never revives a canceled node).","Deletes a non-streaming node with no substantive assistant output unless it has an open managed worktree; a deleted node cannot be revived.","Kills each closed node's tmux window; retained nodes keep their pi sessions and canvas edges for a later revive.","Appends a cancellation notice to each retained node's inbox, surfaced on its next resume."]},run:l(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=x(t)),n===void 0||n==="")throw new d({error:"no_node",message:"no node found in this pane to close",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await f().closeNode(n,process.ppid>0?{caller_pid:process.ppid}:{}).catch(s=>p(s,"List nodes with `crtr node inspect list`."));return{node_id:i.root,count:i.closed.length,closed_ids:i.closed,spared:i.spared}},"run"),render:l(e=>{const t=e.spared?.length??0;return`Closed ${e.node_id} and its exclusive subtree \u2014 ${e.count} node(s) closed${t>0?`, ${t} spared (still managed from outside the subtree)`:""}.`},"render")}),A="Model change first: a running broker, including one still starting right after `node new`, switches over its socket; a dormant node persists the durable model override and launch recipe for its next revive.",ee=y({name:"config",description:"reconfigure a node's settings headlessly \u2014 model, lifecycle (terminal/resident), kind, mode, name; repair a missing cwd",whenToUse:"you want to reconfigure a node's settings headlessly after spawn \u2014 swap its model/provider, flip it terminal\u2194resident (interactable), respecialize its kind, switch base\u2194orchestrator mode, or rename it \u2014 without opening its viewer. It also repairs a dormant node whose recorded cwd is gone, starting its next revive in a fresh conversation. The durable attribute setter; reuses the same recipes a revive reads. Use `node promote` for the opinionated self-promotion path, and `node lifecycle` for teardown actions (recycle/close/demote).",help:{name:"node config",summary:"reconfigure a node's settings headlessly \u2014 model, lifecycle, kind, mode, name; repair a missing cwd",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Target node id. Defaults to the node in --pane, else CRTR_NODE_ID."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to resolve the node from. Defaults to $TMUX_PANE."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Durable model change. Omit unless the task or governing guidance requires a different model.",focusedHelp:{whenToUse:"Use only when intentionally changing this node\u2019s durable model because the user, task, or governing agent guidance requires a specific provider, model, capability tier, or family. Omit it to keep the current model; do not use it merely to inspect available models or experiment with a live node.",value:`Accepted durable forms: ${O}. A live broker also accepts a free-text substring matched against its loaded model registry; a dormant node cannot resolve a substring and requires a durable form.`,effects:[A,"A concrete provider/id runs exactly that model on every launch and revive. A portable provider/tier, bare tier, or family alias keeps its tier: when that model's provider is rate-limited or rejects its credential, the node moves to the next model in its route: another provider's model at the same tier first, then the configured fallback tier (strong by default)."]}},{kind:"flag",name:"lifecycle",type:"enum",choices:["terminal","resident"],required:!1,constraint:"Set lifecycle headlessly without closing the viewer. terminal owes a final and reaps when done; resident stays interactable while attached or awaiting something live."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Persona kind. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here."},{kind:"flag",name:"mode",type:"enum",choices:["base","orchestrator"],required:!1,constraint:"Set persona mode headlessly. base is hands-on; orchestrator holds a roadmap and delegates. orchestrator creates your document `roadmap` if absent."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Rename the node; if the node has a live window, also rename that viewer window."},{kind:"flag",name:"nice",type:"int",required:!1,constraint:`Scheduler niceness every command this node's bash tool starts at, 0\u2013${v} (default ${N}). ${N} keeps the node's builds and test suites below the daemon, the brokers, the viewers, and the person's own shell; 0 lets them compete on equal terms with everything else on the host; ${v} makes them yield to it. Applies to the node's very next command, foreground and background alike.`},{kind:"flag",name:"listen",type:"string",required:!1,constraint:`The node's default listening configuration, as JSON mapping an event level ("1".."10") to how events at or above it reach the node: interrupt, steer, after-turn, or next-turn (e.g. '{"3":"next-turn"}' \u2014 events of level 3+ wait for its next turn and never wake it). Applies to every watch with no configuration of its own. Pass \`null\` to reset to the global default.`},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Repair-only replacement launch directory. Must be a non-empty absolute existing directory and used alone; the daemon accepts it only for a dormant, non-forked node with no open managed worktree whose recorded cwd is gone. Clears the saved Pi session, so the next revive starts fresh while durable node work remains."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The reconfigured node, present for ordinary config changes."},{name:"model",type:"string",required:!1,constraint:"The resolved model, present only when --model changed it."},{name:"lifecycle",type:"string",required:!1,constraint:"The new lifecycle, present only when --lifecycle changed it."},{name:"kind",type:"string",required:!1,constraint:"The new kind, present only when --kind changed it."},{name:"mode",type:"string",required:!1,constraint:"The new mode, present only when --mode changed it."},{name:"name",type:"string",required:!1,constraint:"The new name, present only when --name changed it."},{name:"nice",type:"number",required:!1,constraint:"The new bash niceness, present only when --nice changed it."},{name:"listen",type:"object",required:!1,constraint:"The new default listening configuration (null = global default), present only when --listen changed it."},{name:"cwd",type:"string",required:!1,constraint:"The lexically resolved replacement directory, present only after a cwd repair."}],outputKind:"object",effects:[A,"Kind, mode, and lifecycle changes rebuild the launch spec from the fresh node meta; setting mode to orchestrator creates your document `roadmap` if absent.","Name changes update the row and, when the node already has a live window, rename that viewer window to the new full name.","A cwd repair writes only the lexically resolved cwd and clears saved Pi session pointers; the old transcript and durable node work remain untouched.","A niceness change is stored with the node's own bash-job state, so it takes effect on the next command without a revive and survives one.","A listen change sets the node's default listening configuration; it never changes which watches it holds.","Every successful change publishes a `configured` event on the node (level 1) naming the changed settings, so its watchers can see it."]},helpState:{leaf:_},run:l(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await E(e,t),i=e.cwd;if(i!==void 0&&["model","kind","lifecycle","mode","name","nice","listen"].some(w=>e[w]!==void 0))throw new d({error:"cwd_not_exclusive",message:"cwd repair cannot be combined with another config flag",field:"cwd",next:"Pass --cwd alone."});const s=e.model?.trim();if(s==="")throw new d({error:"empty_spec",message:"a model spec is required",field:"model",next:"Run `crtr node config --model -h` and read the focused contract before retrying."});const o=e.kind?.trim(),a=e.lifecycle?.trim(),c=e.mode?.trim(),T=e.name,u=e.nice,b=e.listen?.trim();let q;if(b!==void 0)try{q=JSON.parse(b)}catch{throw new d({error:"invalid_listen",message:`--listen is not JSON: ${b}`,field:"listen",next:`Pass a JSON object such as '{"3":"next-turn"}', or null.`})}if(u!==void 0&&(u<0||u>v))throw new d({error:"bad_nice",message:`niceness must be between 0 and ${v}: ${u}`,field:"nice",next:`A negative niceness needs root, so pass 0\u2013${v}.`});if(a!==void 0&&a.toLowerCase()!=="terminal"&&a.toLowerCase()!=="resident")throw new d({error:"bad_lifecycle",message:`invalid lifecycle: ${a}`,field:"lifecycle",next:"Pass `terminal` or `resident`."});if(c!==void 0&&c.toLowerCase()!=="base"&&c.toLowerCase()!=="orchestrator")throw new d({error:"bad_mode",message:`invalid mode: ${c}`,field:"mode",next:"Pass `base` or `orchestrator`."});const r=[],h={};if(s!==void 0&&(h.model=s,r.push("model")),o!==void 0&&(h.kind=o,r.push("kind")),a!==void 0&&(h.lifecycle=a.toLowerCase(),r.push("lifecycle")),c!==void 0&&(h.mode=c.toLowerCase(),r.push("mode")),T!==void 0&&(h.name=T,r.push("name")),i!==void 0&&(h.cwd=i,r.push("cwd")),b!==void 0&&(h.listen=q??null,r.push("listen")),u!==void 0){const w=await f().getNode(n).catch(S=>p(S));if(D(w.paths.context_dir,u),r.push("nice"),r.length===1)return{node_id:n,nice:u}}if(r.length===0)throw new d({error:"no_change",message:"no config changes requested",next:"Pass at least one of --model, --lifecycle, --kind, --mode, --name, --nice, --listen, --cwd."});const g=await f().patchConfig(n,h).catch(w=>p(w,"List nodes with `crtr node inspect list`."));if(i!==void 0)return{cwd:g.cwd};const m={node_id:n};return r.includes("model")&&(m.model=g.model_override??s),r.includes("lifecycle")&&(m.lifecycle=g.lifecycle),r.includes("kind")&&(m.kind=g.kind),r.includes("mode")&&(m.mode=g.mode),r.includes("name")&&(m.name=g.name),r.includes("nice")&&(m.nice=u),r.includes("listen")&&(m.listen=h.listen),m},"run"),render:l(e=>{if(e.cwd!==void 0)return`Repaired cwd: ${e.cwd}.`;const t=[];return e.model!==void 0&&t.push(`model=${e.model}`),e.lifecycle!==void 0&&t.push(`lifecycle=${e.lifecycle}`),e.kind!==void 0&&t.push(`kind=${e.kind}`),e.mode!==void 0&&t.push(`mode=${e.mode}`),e.name!==void 0&&t.push(`name=${e.name}`),e.nice!==void 0&&t.push(`nice=${e.nice}`),e.listen!==void 0&&t.push(`listen=${JSON.stringify(e.listen)}`),`Configured ${e.node_id} \u2014 ${t.join(", ")}`},"render")}),ne=y({name:"promote",description:"become an orchestrator of a chosen kind",whenToUse:"the remaining work contains independent units that can run in parallel, and the task is large enough that coordinating and integrating them will materially improve intelligence, productivity, or elapsed throughput. Become a long-lived, roadmap-holding orchestrator whose primary job is steering those children across refreshes (`node yield`). Keep sequential or tightly coupled work in base mode even when it spans phases or context windows; spawning one helper does not itself require promotion",tier:"important",help:{name:"node promote",summary:"promote yourself to an orchestrator when worthwhile parallel delegation should make coordination your primary job; task duration, sequential phases, or one helper do not qualify. Mode only \u2014 lifecycle stays as-is unless you pass --resident",params:[{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Specialize as this kind of orchestrator. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here. For an exact composed persona list, use `crtr sys context prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one \u2014 it sets the orchestrator persona you revive into."},{kind:"flag",name:"resident",type:"bool",required:!1,constraint:"ALSO flip lifecycle\u2192resident: make the node interactable \u2014 it is never forced to submit a final, and unattended grace parks it after a summary turn instead, leaving it revivable fresh against its goal and roadmap. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done)."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model you run on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to promote. Defaults to the caller (CRTR_NODE_ID)."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The promoted node."},{name:"kind",type:"string",required:!0,constraint:"The kind it now orchestrates as."},{name:"model",type:"string",required:!1,constraint:"The model tier it now runs on, present only when you changed it with --model."},{name:"lifecycle",type:"string",required:!0,constraint:'"resident" if you passed --resident, else unchanged (typically "terminal").'},{name:"roadmap_written",type:"boolean",required:!0,constraint:"True if the `roadmap` document was created by this call."},{name:"roadmap_ref",type:"string",required:!0,constraint:"Your roadmap document (`<node>/roadmap`) \u2014 author your plan with `crtr doc edit roadmap`."},{name:"goal_path",type:"string",required:!0,constraint:"Absolute path to your goal doc (context/initial-prompt.md) \u2014 the mandate you were spawned with."}],outputKind:"object",effects:["Flips mode\u2192orchestrator + kind\u2192chosen (lifecycle unchanged unless --resident, which also flips lifecycle\u2192resident); --model durably sets the model; rewrites the launch spec to that kind's orchestrator persona; creates your document `roadmap` (rule `on: boot`, `deliver: content`) if absent.","The next model request is gated on appending and persisting your new-role guidance. If a fresh context starts first, its rebuilt baseline carries the new role."]},helpState:{leaf:_},run:l(async e=>{const t=e.node??k();if(t===void 0||t==="")throw new d({error:"no_node",message:"no node to promote (set CRTR_NODE_ID or pass --node)",next:"Run from inside a node, or pass --node <id>."});const n=e.kind,i=e.model,s=e.resident===!0,o=await f().promoteNode(t,{...n!==void 0?{kind:n}:{},...s?{resident:!0}:{},...i!==void 0?{model:i}:{}}).catch(a=>p(a));return{node_id:o.node_id,kind:o.kind,lifecycle:o.lifecycle,...i!==void 0&&o.model_override!=null?{model:o.model_override}:{},roadmap_written:o.roadmap_written??!1,roadmap_ref:o.roadmap_ref??"",goal_path:o.goal_path??""}},"run")});async function E(e,t){let n=e.node;if((n===void 0||n==="")&&(n=x(t)),(n===void 0||n==="")&&(n=k()),n===void 0||n==="")throw new d({error:"no_node",message:"no node (set CRTR_NODE_ID, pass --node, or run from the agent's pane)",next:"Run from inside a node, pass --node <id>, or --pane <pane>."});if(await $(n)===null)throw new d({error:"not_found",message:`no node: ${n}`,next:"List nodes with `crtr node inspect list`."});return n}l(E,"resolveLifecycleNode");const K=y({name:"demote",description:"demote a node to terminal in place; it stays focused and running but now owes a final; --detach also closes its viewer (broker keeps running off-screen)",whenToUse:"you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE \u2014 it keeps its viewer and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and close its viewer so the agent finishes off-screen on its broker. Use `node lifecycle recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node config --lifecycle` for the orthogonal low-level flip (incl. terminal\u2192resident)",help:{name:"node lifecycle demote",summary:"demote a node to terminal IN PLACE \u2014 it stays focused and running but now owes a final up the spine and reaps when done. Pairs with `node promote` (mode\u2191); `--detach` also closes its viewer (the broker engine keeps running off-screen)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to demote. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to demote, when --node is omitted. Defaults to $TMUX_PANE. The Alt+C menu passes this for you."},{kind:"flag",name:"detach",type:"bool",required:!1,constraint:"After flipping terminal, stop foregrounding the agent by closing its viewer pane. The broker engine keeps generating off-screen and \u2014 now terminal \u2014 pushes a final up the spine when done; a later `focus` reattaches a viewer. The Alt+C \u2192 D move."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The demoted node."},{name:"detached",type:"boolean",required:!1,constraint:"True when --detach closed the agent's viewer pane (broker keeps running)."}],outputKind:"object",effects:["Flips the node's lifecycle\u2192terminal and rebuilds its launch spec so a future revive boots terminal \u2014 it stays focused and running, now owing a final up the spine.","The next model request is gated on appending and persisting the role transition. If a fresh context starts first, its rebuilt baseline carries the new lifecycle.","With --detach: closes the agent's viewer pane WITHOUT touching the broker engine \u2014 it keeps generating off-screen, reconnectable by a later `focus`."]},run:l(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await E(e,t),i=await f().patchConfig(n,{lifecycle:"terminal"}).catch(o=>p(o));let s=!1;if(e.detach===!0){const o=M(n)??t;o!==void 0&&o!==""&&U(o)&&(s=R(o))}return{node_id:i.node_id,detached:s}},"run"),render:l(e=>`Demoted ${e.node_id} \u2014 lifecycle now terminal (in place)${e.detached===!0?", viewer closed (broker still running off-screen)":""}.`,"render")}),te=P({name:"lifecycle",description:"lifecycle actions \u2014 recycle, close, demote, or revive a node",whenToUse:"you are ending, transitioning, or bringing back one node you can name: finish the agent in your pane and reboot a fresh root (`recycle`), tear a node + its exclusive subtree down without finishing (`close`), flip a node onto a finishing track in place (`demote`), or bring a dormant node back (`revive`). To merely SET the terminal/resident attribute headlessly use `node config --lifecycle`; to FINISH from inside a node's own turn use `crtr push final`; to mass-reconnect EVERY disconnected node at once use `canvas revive --all`.",help:{name:"node lifecycle",summary:"end, transition, or revive a node \u2014 recycle the pane, close + cascade-cancel a subtree, demote to terminal in place, or revive a dormant node",model:"Lifecycle actions preserve or replace an existing node identity. Finishing is permanent, closing is revivable, demotion changes the completion obligation in place, and revival relaunches a dormant broker. Teardown cascades only through descendants exclusively managed by the affected subtree."},children:[F,H,K,I]}),oe=y({name:"yield",description:"refresh your context, same mode, against your roadmap or goal",whenToUse:"your context is filling but the mandate remains; refresh in the same mode, or combine the refresh with promotion when meaningful independent work can run in parallel and coordinating it should become your primary job",help:{name:"node yield",summary:"request a fresh context window against your roadmap or original goal; mode stays unchanged unless --promote is passed",guide:"The yield aborts your turn the instant this command returns: no text or tool call you planned after it will run. So anything the user is owed \u2014 an answer, a status \u2014 must already be a visible message in the conversation before you call it; never plan to reply after yielding. Before invoking: (1) Bring your document `roadmap` current with `crtr doc edit roadmap` \u2014 it, not the yield note, carries the goal into the fresh window. Keep its seeded sections (Goal, Exit criteria, Phases) and keep it short: strategy and present state only \u2014 what is settled, what remains, the current phase\u2019s one-line status, plus dead ends worth not repeating. Delete completed items instead of marking them done; the roadmap should shrink as work completes. (2) Move anything worth remembering but not needed next window into a document (`crtr doc write -h`): one you own for lessons specific to this node\u2019s task, `--owner repo` for lessons that would benefit other agents working in this repo. (3) Send any pending human question; in-flight asks survive the refresh.",params:[{kind:"flag",name:"promote",type:"bool",required:!1,constraint:"Also become an orchestrator as you refresh. Pass only when (1) the remaining work contains independent units that can run in parallel and (2) the task is large enough that parallel execution will materially improve intelligence, productivity, or elapsed throughput after coordination and synthesis. Task length, context exhaustion, sequential phases, or one helper are not enough; keep hands-on work in base mode and yield again as needed. Promote when coordinating and integrating children becomes your primary job. Already an orchestrator? This is not an error: your mode stays orchestrator and a missing roadmap is seeded."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Respecialize as you refresh. The <kinds> list below names every top-level available kind and when to choose it; a registered sub-kind is valid too by exact path but not listed here. Defaults to your current kind. With --promote it sets the orchestrator kind you become; without --promote it changes the kind while preserving your current mode."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model your fresh revive runs on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. A node steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"stdin",name:"message",required:!0,constraint:"A note to your future self \u2014 what you just tried, where things stand, and the first move on waking: enough to continue efficiently, no more. If the user asked something you have not answered in a visible message, say so and quote the question. Never describe a reply as sent unless it is visible in the conversation above this call. Durable strategy belongs in the roadmap, not here. Surfaced as <yield-message> in the next revive. Pass as a positional or pipe via heredoc."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The yielding node."},{name:"model",type:"string",required:!1,constraint:"The model tier your fresh revive will run on, present only when you changed it with --model."}],outputKind:"object",effects:["Aborts your current turn as soon as this command returns \u2014 nothing after it runs \u2014 then the daemon revives the node fresh.","--kind and/or --model durably reshape the node (kind respecialization, model change) before the refresh-revive; --promote additionally flips mode\u2192orchestrator and creates your document `roadmap` \u2014 mode is otherwise left exactly as it was."]},helpState:{leaf:_},run:l(async e=>{const t=k();if(t===void 0||t==="")throw new d({error:"no_node",message:"no node to yield (CRTR_NODE_ID unset)",next:"Run from inside a node."});const n=e.kind,i=e.model,s=e.promote===!0,o=typeof e.message=="string"?e.message.trim():"";if(o==="")throw new d({error:"empty_message",message:"a yield message is required (stdin or positional)",next:"Pass a note to your future self as an argument or pipe it on stdin."});const a=await f().yieldNode(t,{note:o,...n!==void 0?{kind:n}:{},...i!==void 0?{model:i}:{},...s?{promote:!0}:{}}).catch(c=>p(c));return{node_id:a.node_id,...i!==void 0&&a.model_override!=null?{model:a.model_override}:{}}},"run")});export{ee as nodeConfig,te as nodeLifecycle,ne as nodePromote,oe as nodeYield};
1
+ var C=Object.defineProperty;var l=(e,t)=>C(e,"name",{value:t,configurable:!0});import{envNodeId as k}from"../../shared/env.js";import{defineBranch as $,defineLeaf as y}from"../../core/command.js";import{InputError as d}from"../../core/io.js";import{cliClient as f,getNodeOrNull as P,rethrowAsCliError as p}from"../api-client.js";import{DEFAULT_BASH_NICENESS as N,MAX_BASH_NICENESS as v,writeBashNiceness as D}from"../../core/bash-jobs.js";import{nodeReviveLeaf as I}from"../node-lifecycle-revive.js";import{kindsStateBlock as _}from"../profile/daemon-profiles.js";import{MODEL_SPEC_FORMS as O}from"../../core/runtime/model-selection.js";import{closePane as R,paneExists as U,respawnPaneSync as L,viewerEnv as j,viewerPaneOf as M,nodeInPane as x}from"../surface/node/placement.js";const F=y({name:"recycle",description:"finish the agent in your pane + recycle it into a fresh root",whenToUse:"you are at an agent's pane and done with it: finish it cleanly and recycle the pane in one move \u2014 push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane to keep working. The hands-on way to end an agent and immediately start over in place. Use `node lifecycle demote` instead to put it on a finishing track IN PLACE (flip terminal, keep it running) without ending it now, `node lifecycle close` to tear a node and its subtree down WITHOUT finishing (no report, revivable), and `push final` when the agent should finish ITSELF from inside its own turn",help:{name:"node lifecycle recycle",summary:"finish the agent in your current pane and recycle the pane \u2014 push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to finish. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to recycle. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"recycled",type:"boolean",required:!0,constraint:"True when the pane was recycled into a fresh root."},{name:"node_id",type:"string",required:!1,constraint:"The finished node."},{name:"new_root",type:"string",required:!1,constraint:"The fresh root node booted into the pane."}],outputKind:"object",effects:["Pushes a final report from the node (fans out to all subscribers) and marks it done.","Kills the agent's pi and respawns a fresh resident root in the same tmux pane."]},run:l(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=x(t)),n===void 0||n==="")throw new d({error:"no_node",message:"no node found in this pane to finish",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await f().getNode(n).catch(c=>p(c,"List nodes with `crtr node inspect list`.")),o=(await f().recycleNode(n).catch(p)).node_id;let a=!1;return t!==void 0&&t!==""&&(a=L({pane:t,cwd:i.cwd,env:j(),command:"crtr"})),{recycled:a,node_id:n,new_root:o}},"run"),render:l(e=>{if(e.recycled!==!0)return e.new_root?`Finished ${e.node_id} and spawned a fresh broker root (${e.new_root}), but its viewer did not attach \u2014 the new broker did not come up. Re-focus the root to view it.`:`Recycle failed for ${e.node_id??"?"} \u2014 not in tmux, or no agent in this pane.`;const t=[`Recycled the pane \u2014 finished ${e.node_id}.`];return e.new_root&&t.push(`- new root: ${e.new_root}`),t.join(`
2
+ `)},"render")}),H=y({name:"close",description:"close a node + cascade-cancel its exclusive subtree",whenToUse:"you want to tear a node down WITHOUT finishing it, cascade-cancelling every descendant it exclusively owns: abandoning a line of work, killing a stuck or wrong-turn subtree, clearing a branch you no longer need. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`node lifecycle revive`). Use `node lifecycle recycle` instead to FINISH the agent in your pane with a final report, and `push final` when a worker should end its own work normally (Alt+C \u2192 x)",help:{name:"node lifecycle close",summary:"close a node and cascade-cancel its subtree \u2014 kill its tmux window plus those of every descendant it EXCLUSIVELY owns (down the subscribes_to spine), mark retained nodes canceled, and leave them a notice they read on resume. A descendant still subscribed to by a manager outside the subtree is left running. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`crtr node lifecycle revive`)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to close. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to close. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The node that was closed \u2014 the cascade root."},{name:"count",type:"number",required:!1,constraint:"How many nodes were closed (root + cascaded descendants)."},{name:"closed_ids",type:"string[]",required:!1,constraint:"All closed node ids, kill order (leaves first, root last)."},{name:"spared",type:"string[]",required:!1,constraint:"Descendants left alive because a manager outside the subtree still subscribes to them."}],outputKind:"object",effects:["Marks retained nodes and their exclusive descendants `canceled` and clears intent (the daemon never revives a canceled node).","Deletes a non-streaming node with no substantive assistant output unless it has an open managed worktree; a deleted node cannot be revived.","Kills each closed node's tmux window; retained nodes keep their pi sessions and canvas edges for a later revive.","Appends a cancellation notice to each retained node's inbox, surfaced on its next resume."]},run:l(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=x(t)),n===void 0||n==="")throw new d({error:"no_node",message:"no node found in this pane to close",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await f().closeNode(n,process.ppid>0?{caller_pid:process.ppid}:{}).catch(s=>p(s,"List nodes with `crtr node inspect list`."));return{node_id:i.root,count:i.closed.length,closed_ids:i.closed,spared:i.spared}},"run"),render:l(e=>{const t=e.spared?.length??0;return`Closed ${e.node_id} and its exclusive subtree \u2014 ${e.count} node(s) closed${t>0?`, ${t} spared (still managed from outside the subtree)`:""}.`},"render")}),A="Model change first: a running broker, including one still starting right after `node new`, switches over its socket; a dormant node persists the durable model override and launch recipe for its next revive.",ee=y({name:"config",description:"reconfigure a node's settings headlessly \u2014 model, lifecycle (terminal/resident), kind, mode, name; repair a missing cwd",whenToUse:"you want to reconfigure a node's settings headlessly after spawn \u2014 swap its model/provider, flip it terminal\u2194resident (interactable), respecialize its kind, switch base\u2194orchestrator mode, or rename it \u2014 without opening its viewer. It also repairs a dormant node whose recorded cwd is gone, starting its next revive in a fresh conversation. The durable attribute setter; reuses the same recipes a revive reads. Use `node promote` for the opinionated self-promotion path, and `node lifecycle` for teardown actions (recycle/close/demote).",help:{name:"node config",summary:"reconfigure a node's settings headlessly \u2014 model, lifecycle, kind, mode, name; repair a missing cwd",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Target node id. Defaults to the node in --pane, else CRTR_NODE_ID."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to resolve the node from. Defaults to $TMUX_PANE."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Durable model change. Omit unless the task or governing guidance requires a different model.",focusedHelp:{whenToUse:"Use only when intentionally changing this node\u2019s durable model because the user, task, or governing agent guidance requires a specific provider, model, capability tier, or family. Omit it to keep the current model; do not use it merely to inspect available models or experiment with a live node.",value:`Accepted durable forms: ${O}. A live broker also accepts a free-text substring matched against its loaded model registry; a dormant node cannot resolve a substring and requires a durable form.`,effects:[A,"A concrete provider/id runs exactly that model on every launch and revive. A portable provider/tier, bare tier, or family alias keeps its tier: when that model's provider is rate-limited or rejects its credential, the node moves to the next model in its route: another provider's model at the same tier first, then the configured fallback tier (strong by default)."]}},{kind:"flag",name:"lifecycle",type:"enum",choices:["terminal","resident"],required:!1,constraint:"Set lifecycle headlessly without closing the viewer. terminal owes a final and reaps when done; resident stays interactable while attached or awaiting something live."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Persona kind. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here."},{kind:"flag",name:"mode",type:"enum",choices:["base","orchestrator"],required:!1,constraint:"Set persona mode headlessly. base is hands-on; orchestrator holds a roadmap and delegates. orchestrator creates your document `roadmap` if absent."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Rename the node; if the node has a live window, also rename that viewer window."},{kind:"flag",name:"nice",type:"int",required:!1,constraint:`Scheduler niceness every command this node's bash tool starts at, 0\u2013${v} (default ${N}). ${N} keeps the node's builds and test suites below the daemon, the brokers, the viewers, and the person's own shell; 0 lets them compete on equal terms with everything else on the host; ${v} makes them yield to it. Applies to the node's very next command, foreground and background alike.`},{kind:"flag",name:"listen",type:"string",required:!1,constraint:`The node's default listening configuration, as JSON mapping an event level ("1".."10") to how events at or above it reach the node: interrupt, steer, after-turn, or next-turn (e.g. '{"3":"next-turn"}' \u2014 events of level 3+ wait for its next turn and never wake it). Applies to every watch with no configuration of its own. Pass \`null\` to reset to the global default.`},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Repair-only replacement launch directory. Must be a non-empty absolute existing directory and used alone; the daemon accepts it only for a dormant, non-forked node with no open managed worktree whose recorded cwd is gone. Clears the saved Pi session, so the next revive starts fresh while durable node work remains."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The reconfigured node, present for ordinary config changes."},{name:"model",type:"string",required:!1,constraint:"The resolved model, present only when --model changed it."},{name:"lifecycle",type:"string",required:!1,constraint:"The new lifecycle, present only when --lifecycle changed it."},{name:"kind",type:"string",required:!1,constraint:"The new kind, present only when --kind changed it."},{name:"mode",type:"string",required:!1,constraint:"The new mode, present only when --mode changed it."},{name:"name",type:"string",required:!1,constraint:"The new name, present only when --name changed it."},{name:"nice",type:"number",required:!1,constraint:"The new bash niceness, present only when --nice changed it."},{name:"listen",type:"object",required:!1,constraint:"The new default listening configuration (null = global default), present only when --listen changed it."},{name:"cwd",type:"string",required:!1,constraint:"The lexically resolved replacement directory, present only after a cwd repair."}],outputKind:"object",effects:[A,"Kind, mode, and lifecycle changes rebuild the launch spec from the fresh node meta; setting mode to orchestrator creates your document `roadmap` if absent.","Name changes update the row and, when the node already has a live window, rename that viewer window to the new full name.","A cwd repair writes only the lexically resolved cwd and clears saved Pi session pointers; the old transcript and durable node work remain untouched.","A niceness change is stored with the node's own bash-job state, so it takes effect on the next command without a revive and survives one.","A listen change sets the node's default listening configuration; it never changes which watches it holds.","Every successful change publishes a `configured` event on the node (level 1) naming the changed settings, so its watchers can see it."]},helpState:{leaf:_},run:l(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await E(e,t),i=e.cwd;if(i!==void 0&&["model","kind","lifecycle","mode","name","nice","listen"].some(w=>e[w]!==void 0))throw new d({error:"cwd_not_exclusive",message:"cwd repair cannot be combined with another config flag",field:"cwd",next:"Pass --cwd alone."});const s=e.model?.trim();if(s==="")throw new d({error:"empty_spec",message:"a model spec is required",field:"model",next:"Run `crtr node config --model -h` and read the focused contract before retrying."});const o=e.kind?.trim(),a=e.lifecycle?.trim(),c=e.mode?.trim(),T=e.name,u=e.nice,b=e.listen?.trim();let q;if(b!==void 0)try{q=JSON.parse(b)}catch{throw new d({error:"invalid_listen",message:`--listen is not JSON: ${b}`,field:"listen",next:`Pass a JSON object such as '{"3":"next-turn"}', or null.`})}if(u!==void 0&&(u<0||u>v))throw new d({error:"bad_nice",message:`niceness must be between 0 and ${v}: ${u}`,field:"nice",next:`A negative niceness needs root, so pass 0\u2013${v}.`});if(a!==void 0&&a.toLowerCase()!=="terminal"&&a.toLowerCase()!=="resident")throw new d({error:"bad_lifecycle",message:`invalid lifecycle: ${a}`,field:"lifecycle",next:"Pass `terminal` or `resident`."});if(c!==void 0&&c.toLowerCase()!=="base"&&c.toLowerCase()!=="orchestrator")throw new d({error:"bad_mode",message:`invalid mode: ${c}`,field:"mode",next:"Pass `base` or `orchestrator`."});const r=[],h={};if(s!==void 0&&(h.model=s,r.push("model")),o!==void 0&&(h.kind=o,r.push("kind")),a!==void 0&&(h.lifecycle=a.toLowerCase(),r.push("lifecycle")),c!==void 0&&(h.mode=c.toLowerCase(),r.push("mode")),T!==void 0&&(h.name=T,r.push("name")),i!==void 0&&(h.cwd=i,r.push("cwd")),b!==void 0&&(h.listen=q??null,r.push("listen")),u!==void 0){const w=await f().getNode(n).catch(S=>p(S));if(D(w.paths.context_dir,u),r.push("nice"),r.length===1)return{node_id:n,nice:u}}if(r.length===0)throw new d({error:"no_change",message:"no config changes requested",next:"Pass at least one of --model, --lifecycle, --kind, --mode, --name, --nice, --listen, --cwd."});const g=await f().patchConfig(n,h).catch(w=>p(w,"List nodes with `crtr node inspect list`."));if(i!==void 0)return{cwd:g.cwd};const m={node_id:n};return r.includes("model")&&(m.model=g.model_override??s),r.includes("lifecycle")&&(m.lifecycle=g.lifecycle),r.includes("kind")&&(m.kind=g.kind),r.includes("mode")&&(m.mode=g.mode),r.includes("name")&&(m.name=g.name),r.includes("nice")&&(m.nice=u),r.includes("listen")&&(m.listen=h.listen),m},"run"),render:l(e=>{if(e.cwd!==void 0)return`Repaired cwd: ${e.cwd}.`;const t=[];return e.model!==void 0&&t.push(`model=${e.model}`),e.lifecycle!==void 0&&t.push(`lifecycle=${e.lifecycle}`),e.kind!==void 0&&t.push(`kind=${e.kind}`),e.mode!==void 0&&t.push(`mode=${e.mode}`),e.name!==void 0&&t.push(`name=${e.name}`),e.nice!==void 0&&t.push(`nice=${e.nice}`),e.listen!==void 0&&t.push(`listen=${JSON.stringify(e.listen)}`),`Configured ${e.node_id} \u2014 ${t.join(", ")}`},"render")}),ne=y({name:"promote",description:"become an orchestrator of a chosen kind",whenToUse:"the remaining work contains independent units that can run in parallel, and the task is large enough that coordinating and integrating them will materially improve intelligence, productivity, or elapsed throughput. Become a long-lived, roadmap-holding orchestrator whose primary job is steering those children across refreshes (`node yield`). Keep sequential or tightly coupled work in base mode even when it spans phases or context windows; spawning one helper does not itself require promotion",tier:"important",help:{name:"node promote",summary:"promote yourself to an orchestrator when worthwhile parallel delegation should make coordination your primary job; task duration, sequential phases, or one helper do not qualify. Mode only \u2014 lifecycle stays as-is unless you pass --resident",params:[{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Specialize as this kind of orchestrator. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here. For an exact composed persona list, use `crtr sys context prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one \u2014 it sets the orchestrator persona you revive into."},{kind:"flag",name:"resident",type:"bool",required:!1,constraint:"ALSO flip lifecycle\u2192resident: make the node interactable \u2014 it is never forced to submit a final, and unattended grace parks it after a summary turn instead, leaving it revivable fresh against its goal and roadmap. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done)."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model you run on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to promote. Defaults to the caller (CRTR_NODE_ID)."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The promoted node."},{name:"kind",type:"string",required:!0,constraint:"The kind it now orchestrates as."},{name:"model",type:"string",required:!1,constraint:"The model tier it now runs on, present only when you changed it with --model."},{name:"lifecycle",type:"string",required:!0,constraint:'"resident" if you passed --resident, else unchanged (typically "terminal").'},{name:"roadmap_written",type:"boolean",required:!0,constraint:"True if the `roadmap` document was created by this call."},{name:"roadmap_ref",type:"string",required:!0,constraint:"Your roadmap document (`<node>/roadmap`) \u2014 author your plan with `crtr doc edit roadmap`."},{name:"goal_path",type:"string",required:!0,constraint:"Absolute path to your goal doc (context/initial-prompt.md) \u2014 the mandate you were spawned with."}],outputKind:"object",effects:["Flips mode\u2192orchestrator + kind\u2192chosen (lifecycle unchanged unless --resident, which also flips lifecycle\u2192resident); --model durably sets the model; rewrites the launch spec to that kind's orchestrator persona; creates your document `roadmap` (rule `on: boot`, `deliver: content`) if absent.","The next model request is gated on appending and persisting your new-role guidance. If a fresh context starts first, its rebuilt baseline carries the new role."]},helpState:{leaf:_},run:l(async e=>{const t=e.node??k();if(t===void 0||t==="")throw new d({error:"no_node",message:"no node to promote (set CRTR_NODE_ID or pass --node)",next:"Run from inside a node, or pass --node <id>."});const n=e.kind,i=e.model,s=e.resident===!0,o=await f().promoteNode(t,{...n!==void 0?{kind:n}:{},...s?{resident:!0}:{},...i!==void 0?{model:i}:{}}).catch(a=>p(a));return{node_id:o.node_id,kind:o.kind,lifecycle:o.lifecycle,...i!==void 0&&o.model_override!=null?{model:o.model_override}:{},roadmap_written:o.roadmap_written??!1,roadmap_ref:o.roadmap_ref??"",goal_path:o.goal_path??""}},"run")});async function E(e,t){let n=e.node;if((n===void 0||n==="")&&(n=x(t)),(n===void 0||n==="")&&(n=k()),n===void 0||n==="")throw new d({error:"no_node",message:"no node (set CRTR_NODE_ID, pass --node, or run from the agent's pane)",next:"Run from inside a node, pass --node <id>, or --pane <pane>."});if(await P(n)===null)throw new d({error:"not_found",message:`no node: ${n}`,next:"List nodes with `crtr node inspect list`."});return n}l(E,"resolveLifecycleNode");const W=y({name:"demote",description:"demote a node to terminal in place; it stays focused and running but now owes a final; --detach also closes its viewer (broker keeps running off-screen)",whenToUse:"you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE \u2014 it keeps its viewer and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and close its viewer so the agent finishes off-screen on its broker. Use `node lifecycle recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node config --lifecycle` for the orthogonal low-level flip (incl. terminal\u2192resident)",help:{name:"node lifecycle demote",summary:"demote a node to terminal IN PLACE \u2014 it stays focused and running but now owes a final up the spine and reaps when done. Pairs with `node promote` (mode\u2191); `--detach` also closes its viewer (the broker engine keeps running off-screen)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to demote. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to demote, when --node is omitted. Defaults to $TMUX_PANE. The Alt+C menu passes this for you."},{kind:"flag",name:"detach",type:"bool",required:!1,constraint:"After flipping terminal, stop foregrounding the agent by closing its viewer pane. The broker engine keeps generating off-screen and \u2014 now terminal \u2014 pushes a final up the spine when done; a later `focus` reattaches a viewer. The Alt+C \u2192 D move."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The demoted node."},{name:"detached",type:"boolean",required:!1,constraint:"True when --detach closed the agent's viewer pane (broker keeps running)."}],outputKind:"object",effects:["Flips the node's lifecycle\u2192terminal and rebuilds its launch spec so a future revive boots terminal \u2014 it stays focused and running, now owing a final up the spine.","The next model request is gated on appending and persisting the role transition. If a fresh context starts first, its rebuilt baseline carries the new lifecycle.","With --detach: closes the agent's viewer pane WITHOUT touching the broker engine \u2014 it keeps generating off-screen, reconnectable by a later `focus`."]},run:l(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await E(e,t),i=await f().patchConfig(n,{lifecycle:"terminal"}).catch(o=>p(o));let s=!1;if(e.detach===!0){const o=M(n)??t;o!==void 0&&o!==""&&U(o)&&(s=R(o))}return{node_id:i.node_id,detached:s}},"run"),render:l(e=>`Demoted ${e.node_id} \u2014 lifecycle now terminal (in place)${e.detached===!0?", viewer closed (broker still running off-screen)":""}.`,"render")}),te=$({name:"lifecycle",description:"lifecycle actions \u2014 recycle, close, demote, or revive a node",whenToUse:"you are ending, transitioning, or bringing back one node you can name: finish the agent in your pane and reboot a fresh root (`recycle`), tear a node + its exclusive subtree down without finishing (`close`), flip a node onto a finishing track in place (`demote`), or bring a dormant node back (`revive`). To merely SET the terminal/resident attribute headlessly use `node config --lifecycle`; to FINISH from inside a node's own turn use `crtr push final`; to mass-reconnect EVERY disconnected node at once use `canvas revive --all`.",help:{name:"node lifecycle",summary:"end, transition, or revive a node \u2014 recycle the pane, close + cascade-cancel a subtree, demote to terminal in place, or revive a dormant node",model:"Lifecycle actions preserve or replace an existing node identity. Finishing is permanent, closing is revivable, demotion changes the completion obligation in place, and revival relaunches a dormant broker. Teardown cascades only through descendants exclusively managed by the affected subtree."},children:[F,H,W,I]}),oe=y({name:"yield",description:"refresh your context, same mode, against your roadmap or goal",whenToUse:"your context is filling but the mandate remains; refresh in the same mode, or combine the refresh with promotion when meaningful independent work can run in parallel and coordinating it should become your primary job",help:{name:"node yield",summary:"request a fresh context window against your roadmap or original goal; mode stays unchanged unless --promote is passed",guide:"The yield aborts your turn the instant this command returns: no text or tool call you planned after it will run. So anything the user is owed \u2014 an answer, a status \u2014 must already be a visible message in the conversation before you call it; never plan to reply after yielding. Before invoking: (1) If you hold a document `roadmap`, bring it current (`crtr doc edit roadmap`). It, not the yield note, carries your task into the fresh window. It has four sections: Goal (its first line is also the roadmap\u2019s preview, so set it with `--preview` whenever the goal changes), Exit criteria, Where things stand, and Working set. Keep it at your own level of abstraction. Detail below your level belongs to the node or document that holds it: link to it, don\u2019t copy it. Where things stand is present tense; delete what is done instead of marking it. Name units by what they are and how they fit, never by child node id or status: your bearings already list your children with their status. Working set holds `[[links]]`. Their previews load with the roadmap, so write no note beside a link. When a unit lands, delete its links: its evidence belongs in its report. (2) Give content its own document when it lives on a different timescale from the roadmap (exploration that outlives the current work), goes to a different reader (a brief for a child), or matters only when something specific happens. Link each such document from the Working set. Anything you had to re-derive in this window that a future window will need again belongs in a document: one you own for this task, `--owner repo` for lessons that would help others in the repo. A document that only points at other documents is not worth the extra hop. (3) Actions waiting on one specific event (\u201Cwhen X reports, send Y first\u201D) go in the yield note, not the roadmap. They are spent once they fire. (4) Send any pending human question; asks already in flight survive the refresh.",params:[{kind:"flag",name:"promote",type:"bool",required:!1,constraint:"Also become an orchestrator as you refresh. Pass only when (1) the remaining work contains independent units that can run in parallel and (2) the task is large enough that parallel execution will materially improve intelligence, productivity, or elapsed throughput after coordination and synthesis. Task length, context exhaustion, sequential phases, or one helper are not enough; keep hands-on work in base mode and yield again as needed. Promote when coordinating and integrating children becomes your primary job. Already an orchestrator? This is not an error: your mode stays orchestrator and a missing roadmap is seeded."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Respecialize as you refresh. The <kinds> list below names every top-level available kind and when to choose it; a registered sub-kind is valid too by exact path but not listed here. Defaults to your current kind. With --promote it sets the orchestrator kind you become; without --promote it changes the kind while preserving your current mode."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model your fresh revive runs on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. A node steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"stdin",name:"message",required:!0,constraint:"A note to your future self \u2014 what you just tried, where things stand, the first move on waking, and any action waiting on a specific event: enough to continue efficiently, no more. Never describe a reply as sent unless it is visible in the conversation above this call. Your task and its state belong in the roadmap. Surfaced as <yield-message> in the next revive. Pass as a positional or pipe via heredoc."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The yielding node."},{name:"model",type:"string",required:!1,constraint:"The model tier your fresh revive will run on, present only when you changed it with --model."}],outputKind:"object",effects:["Aborts your current turn as soon as this command returns \u2014 nothing after it runs \u2014 then the daemon revives the node fresh.","--kind and/or --model durably reshape the node (kind respecialization, model change) before the refresh-revive; --promote additionally flips mode\u2192orchestrator and creates your document `roadmap` \u2014 mode is otherwise left exactly as it was."]},helpState:{leaf:_},run:l(async e=>{const t=k();if(t===void 0||t==="")throw new d({error:"no_node",message:"no node to yield (CRTR_NODE_ID unset)",next:"Run from inside a node."});const n=e.kind,i=e.model,s=e.promote===!0,o=typeof e.message=="string"?e.message.trim():"";if(o==="")throw new d({error:"empty_message",message:"a yield message is required (stdin or positional)",next:"Pass a note to your future self as an argument or pipe it on stdin."});const a=await f().yieldNode(t,{note:o,...n!==void 0?{kind:n}:{},...i!==void 0?{model:i}:{},...s?{promote:!0}:{}}).catch(c=>p(c));return{node_id:a.node_id,...i!==void 0&&a.model_override!=null?{model:a.model_override}:{}}},"run")});export{ee as nodeConfig,te as nodeLifecycle,ne as nodePromote,oe as nodeYield};
@@ -1 +1 @@
1
- var u=Object.defineProperty;var n=(e,t)=>u(e,"name",{value:t,configurable:!0});import{ApiError as l}from"../../../../api/index.js";import{cliClient as a}from"../../../api-client.js";import{ruleToDTO as c}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}n(d,"readOnlyReason");function m(e){return e instanceof l?{ok:!1,message:e.message}:{ok:!1,message:e instanceof Error?e.message:String(e)}}n(m,"failure");async function s(e,t,r){const i=d(e);if(i!==null)return{ok:!1,message:i};try{return await a().docs.edit(e.doc.object.id,{rationale:f,...t}),{ok:!0,message:r,name:e.doc.displayName}}catch(o){return m(o)}}n(s,"runEdit");function h(e,t,r){return s(e,t.length===0?{unset:["rules"]}:{rules:t.map(c)},r)}n(h,"writeRules");function v(e){const t=e.doc.displayName;return e.doc.unlisted?s(e,{unset:["unlisted"]},`${t} is listed again.`):s(e,{unlisted:!0},`${t} is now unlisted.`)}n(v,"toggleUnlisted");function $(e,t){const r=t.trim();return r===""?Promise.resolve({ok:!1,message:"A preview cannot be empty \u2014 it is what a reader decides to open the document on."}):s(e,{preview:r},`Rewrote the preview of ${e.doc.displayName}.`)}n($,"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.`);if(t.needs_merge)throw new Error(`${e.displayName} has diverged heads; merge them with \`crtr doc edit\` first.`);return t.bodies[0]??""}n(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.`}):s(e,{body:t},`Rewrote the body of ${e.doc.displayName}.`)}n(b,"writeBody");async function k(e,t){const r=t.trim();if(r==="")return{ok:!1,message:"Type the new name."};const i=d(e);if(i!==null)return{ok:!1,message:i};try{const o=await a().docs.move(e.doc.object.id,{new_name:r});return{ok:!0,message:`${e.doc.displayName} now answers to ${o.name}.`,name:o.name}}catch(o){return m(o)}}n(k,"moveDoc");export{k as moveDoc,N as readBody,d as readOnlyReason,v as toggleUnlisted,b as writeBody,$ as writePreview,h as writeRules};
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 l=Object.defineProperty;var s=(n,e)=>l(n,"name",{value:e,configurable:!0});import{ApiError as f}from"../../api/index.js";import{identifyCheckout as h}from"../../core/graph/repo-sync/identity.js";import{rulesFromFrontmatter as p}from"../../core/graph/rules.js";import{usage as w}from"../../core/errors.js";function _(n){if(typeof n=="string")return n;if(typeof n=="number"||typeof n=="boolean")return String(n)}s(_,"scalarString");function c(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}s(c,"cleanClause");const g=/\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 S(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(g);if(r&&r.index!==void 0){const a=c(t.slice(0,r.index)),o=c(t.slice(r.index+r[0].length));if(o!==""){let i=`When ${o}, this ${e} should be read`;return a!==""&&(i+=` because ${a}`),`${i}.`}}return`When ${c(t)}, this ${e} should be read.`}s(S,"routeFromDescription");function v(n){return n.replace(/\\/g,"/").replace(/\.md$/i,"").split("/").map(t=>t.trim().toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"")).filter(Boolean).join("/")}s(v,"normalizedName");function C(n){return p(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}:{}}))}s(C,"rulesFromLegacyFrontmatter");async function m(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 i=await n.objects.read(o.id);if(i.state==="object"&&"identity"in i&&i.identity.external_id===t)return i.handle}r=a.next_cursor??void 0}while(r);return null}s(m,"ownerByIdentity");async function d(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}s(d,"documentNames");async function E(n,e){const t=h(e);if(!t.ok)throw w(`${e} is not inside a repo: ${t.reason}`);const r=await m(n,"repo",t.externalId);return{owner:"repo",cwd:t.root,handle:r??t.handleHint,existing:r?await d(n,r):new Set}}s(E,"repoOwner");async function W(n){const e=await m(n,"person","user:local");return{owner:"user",cwd:process.cwd(),handle:e??"user",existing:e?await d(n,e):new Set}}s(W,"userOwner");async function k(n,e,t,r){const a={preview:t.preview,...t.summary!==void 0&&t.summary!==""?{summary:t.summary}:{},...t.privacy!==void 0?{privacy:t.privacy}:{},rules:t.rules};if(!e.existing.has(t.name)){const u=await n.docs.write({name:t.name,body:t.body,kind:t.kind,owner:e.owner,cwd:e.cwd,...a});return e.handle=u.name.slice(0,u.name.length-t.name.length-1),e.existing.add(t.name),{name:u.name,changed:!0}}const o=`${e.handle}/${t.name}`,i=await n.objects.read(o);return i.state==="object"&&i.type==="document"&&i.bodies.length===1&&i.bodies[0]===t.body&&i.object.preview===t.preview?{name:o,changed:!1}:{name:(await n.docs.edit(o,{body:t.body,rationale:r,...a})).name,changed:!0}}s(k,"putDoc");function z(n){if(n instanceof f)return n.message;throw n}s(z,"writeFailure");export{v as normalizedName,k as putDoc,E as repoOwner,S as routeFromDescription,C as rulesFromLegacyFrontmatter,_ as scalarString,W as userOwner,z as writeFailure};
1
+ var f=Object.defineProperty;var i=(n,e)=>f(n,"name",{value:e,configurable:!0});import{ApiError as h}from"../../api/index.js";import{identifyCheckout as p}from"../../core/graph/repo-sync/identity.js";import{rulesFromFrontmatter as w}from"../../core/graph/rules.js";import{usage as g}from"../../core/errors.js";function S(n){if(typeof n=="string")return n;if(typeof n=="number"||typeof n=="boolean")return String(n)}i(S,"scalarString");function m(n){let e=n.replace(/\s+/g," ").trim();return e=e.replace(/^[,;:.\s]+/,"").replace(/[,;:.\s]+$/,""),/^[A-Z][a-z]/.test(e)&&(e=e.charAt(0).toLowerCase()+e.slice(1)),e}i(m,"cleanClause");const y=/\b(?:use\s+this\s+skill\s+when|use\s+this\s+when|this\s+skill\s+should\s+be\s+used\s+when|used\s+when|use\s+when)\b\s*/i;function v(n,e){const t=n.replace(/\s+/g," ").trim();if(t==="")return`When this migrated ${e} applies, this ${e} should be read.`;const r=t.match(y);if(r&&r.index!==void 0){const a=m(t.slice(0,r.index)),o=m(t.slice(r.index+r[0].length));if(o!==""){let s=`When ${o}, this ${e} should be read`;return a!==""&&(s+=` because ${a}`),`${s}.`}}return`When ${m(t)}, this ${e} should be read.`}i(v,"routeFromDescription");function C(n){return n.replace(/\\/g,"/").replace(/\.md$/i,"").split("/").map(t=>t.trim().toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"")).filter(Boolean).join("/")}i(C,"normalizedName");function E(n){return w(n).map(e=>({on:e.on,deliver:e.deliver,...e.match!==void 0?{match:e.match}:{},...e.matchFrontmatter!==void 0?{match_frontmatter:e.matchFrontmatter}:{},...e.if!==void 0?{if:e.if}:{}}))}i(E,"rulesFromLegacyFrontmatter");async function d(n,e,t){let r;do{const a=await n.objects.list({type:e,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};