@north-light/cli 0.3.529 → 0.3.530
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/dto/lifecycle.d.ts +3 -1
- package/dist/api/dto/messages.d.ts +5 -3
- package/dist/builtin-memory/crouter-plugin/bundles-and-memory.md +1 -1
- package/dist/builtin-memory/crouter-plugin/commands.md +1 -1
- package/dist/clients/attach/viewer.js +508 -508
- package/dist/commands/node/message.js +1 -1
- package/dist/commands/sys/join.js +1 -1
- package/dist/core/command-hooks/lifecycle-catalog.d.ts +4 -4
- package/dist/core/command-hooks/lifecycle-catalog.js +1 -1
- package/dist/core/command-plugins/bundle.d.ts +1 -1
- package/dist/core/command-plugins/bundle.js +1 -1
- package/dist/core/directory/join.d.ts +1 -1
- package/dist/core/runtime/reopen.d.ts +18 -7
- package/dist/core/runtime/reopen.js +1 -1
- package/dist/daemon/api/handlers/messages.js +2 -2
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/joined-owner.d.ts +1 -1
- package/dist/daemon/joined-owner.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
- package/dist/launcher/run.js +1 -1
- package/docs/plugin/bundles-and-memory.md +1 -1
- package/docs/plugin/commands.md +1 -1
- package/package.json +3 -3
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
var _=Object.defineProperty;var i=(e,n)=>_(e,"name",{value:n,configurable:!0});import{envNodeId as y}from"../../shared/env.js";import{defineBranch as C,defineLeaf as w}from"../../core/command.js";import{InputError as o}from"../../core/io.js";import{requireScope as N}from"../../core/errors.js";import{OUTPUT_SCHEMA_CONSTRAINTS as R,OUTPUT_SCHEMA_TRANSPORT as P,parseOutputSchemaValue as E}from"../../core/runtime/structured-output.js";import{cliClient as A,getNodeOrNull as O,rethrowAsCliError as $}from"../api-client.js";function D(e,n){const t=e[n];if(t!==void 0&&(typeof t!="string"||t.trim()==="")){const r=n.replace(/[A-Z]/g,a=>`-${a.toLowerCase()}`);throw new o({error:"empty_discriminant",message:`--${r} must contain non-whitespace text`,received:typeof t=="string"?t:String(t),field:r,next:`Pass a non-empty --${r} value, or omit the flag.`})}}i(D,"requireNonBlankDiscriminant");async function U(e,n){const t=e.to?.trim(),r=e.self===!0,a=t!==void 0&&t!=="";if(a===r)throw new o({error:"bad_target",message:a&&r?"--to and --self are mutually exclusive":n?"no target given \u2014 the message body is not a node id":"exactly one of --to or --self is required",field:a?void 0:"to",next:a?"Pass --to <node-id> or --self.":n?"Pass the node id with --to <node-id> and provide the message body as a positional argument or on stdin, or use --self.":"Pass --to <node-id> or --self."});if(r){const s=y();if(s===void 0||s==="")throw new o({error:"no_node",message:"no node to target with --self (CRTR_NODE_ID unset)",next:"Run from inside a node, or pass --to <node-id>."});return{targetId:s,isSelf:!0}}if(await O(t)===null)throw new o({error:"not_found",message:`no node: ${t}`,field:"to",next:"List nodes with `crtr node inspect list`."});return{targetId:t,isSelf:!1}}i(U,"resolveMsgTarget");function v(){return[{name:"revived",type:"boolean",required:!1,constraint:"True when this request synchronously revived the target; ordinary durable delivery to an active or idle dormant target queues lifecycle revival instead."},{name:"guidance",type:"string",required:!0,constraint:"Immediate action confirmation."}]}i(v,"messageOutput");function b(e){const n=e.to?.trim();if(!(n!==void 0&&n!==""||e.self===!0))return{message:"no target given \u2014 the positional argument is the message body, not a node id",field:"to",next:"Pass the node id with --to <node-id> and pipe the message body on stdin, or use --self."}}i(b,"missingMessageTargetForBody");function I(){return[{kind:"stdin",name:"body",required:!1,constraint:"Visible message body. Required unless --situational-context is supplied.",positionalStdinConflict:b},{kind:"flag",name:"to",type:"string",required:!1,constraint:"Target an existing node by id. Exactly one of --to or --self is required."},{kind:"flag",name:"self",type:"bool",required:!1,constraint:"Target the calling node. Exactly one of --to or --self is required."},{kind:"flag",name:"tier",type:"enum",choices:["critical","urgent","normal","deferred"],required:!1,default:"normal",constraint:"Delivery urgency. Deferred never wakes an idle node, except a terminal one, which has no later cycle to read it on and is raised to urgent."},{kind:"flag",name:"situational-context",type:"string",required:!1,constraint:"Non-empty hidden ambient context delivered beside the message, or alone."},{kind:"flag",name:"reopen",type:"bool",required:!1,constraint:"Re-task the target: commits it resident and clears any finalization latch before delivery. Valid for live, parked, and finalized targets."}]}i(I,"messageSendParams");function M(){return[{kind:"stdin",name:"body",required:!1,constraint:"Visible message body. The schema alone is a complete request.",positionalStdinConflict:b},{kind:"flag",name:"to",type:"string",required:!1,constraint:"Target an existing node by id. Exactly one of --to or --self is required."},{kind:"flag",name:"self",type:"bool",required:!1,constraint:"Target the calling node. Exactly one of --to or --self is required."},{kind:"flag",name:"tier",type:"enum",choices:["critical","urgent","normal"],required:!1,default:"normal",constraint:"Delivery urgency. Every accepted tier queues lifecycle revival for an active or idle dormant target because the request must be answered; deferred is rejected for that reason."},{kind:"flag",name:"output-schema",type:"string",required:!0,constraint:`JSON Schema the result must satisfy. ${P} The target answers with \`crtr push result\` (or declines with \`crtr push result --decline "<reason>" --code <token>\`), then keeps working. ${R}`},{kind:"flag",name:"situational-context",type:"string",required:!1,constraint:"Non-empty hidden ambient context delivered beside the message, or alone."},{kind:"flag",name:"reopen",type:"bool",required:!1,hidden:!0,constraint:"Clear a finalized target latch before delivery or fresh revive."}]}i(M,"messageRequestParams");async function x(e){N("crtr:act","crtr node message");const n=e.situationalContext!==void 0,t=e.situationalContext?.trim();if(n&&t==="")throw new o({error:"empty_situational_context",message:"--situational-context must contain non-whitespace text",field:"situational-context",next:"Pass non-whitespace text, or omit --situational-context."});const r=e.body,a=r!==void 0?r.trim():void 0,m=a!==void 0&&a!=="",{targetId:s}=await U(e,m),u=e.tier,q=u!==void 0&&u!=="",f=E(e.outputSchema),c=f!==null?JSON.stringify(f):null,g=t!==void 0&&t!=="",k=e.reopen===!0;if(c!==null&&u==="deferred")throw new o({error:"output_schema_deferred_tier",message:"--output-schema cannot combine with --tier deferred because the target must answer",field:"tier",next:"Use normal, urgent, or critical tier, or drop --output-schema."});const h=y(),T=A();if(!m&&!g&&c===null)throw new o({error:"empty_body",message:"a message body is required unless --output-schema or --situational-context is supplied",field:"body",next:"Pass the message on stdin, or supply one of the documented alternatives."});const p=q?u:"normal",d=await T.sendMessage(s,{body:a??"",tier:p,...k?{reopen:!0}:{},...g?{situational_context:t}:{},...c!==null?{output_schema:c}:{},...h!==void 0?{from:h}:{}}).catch($);if(d.wake_failed!==void 0){const l=d.wake_failed;return{revived:!1,guidance:`Accepted but NOT delivered to a running node \u2014 waking the target failed (${l.error}): ${l.message}${l.next===void 0?"":` ${l.next}`} The message is queued durably (${l.message_id}); do not resend it.`}}const S=d.revived?"Delivered \u2014 the dormant target was revived to receive it.":p!=="deferred"?"Delivered.":d.delivered_tier===void 0?"Delivered \u2014 held for the target's next natural cycle; not revived.":"Delivered \u2014 the target is terminal and takes no later cycle, so it was raised to urgent rather than held.";return{revived:d.revived,guidance:S}}i(x,"runMessageEngine");const j=w({name:"send",description:"deliver an inbox message to an existing node immediately",whenToUse:"a target chosen with `--to <node-id>` or `--self` should have information or new direction now \u2014 steering a worker mid-task, handing over a finding it needs, correcting a wrong turn, or waking a dormant node with fresh work. The target reads it and decides what to do with it. Use `node message request` instead when you need an answer back in a shape you specify, and `node subscription add` when the target should keep receiving this node\u2019s pushes rather than this one delivery",help:{name:"node message send",summary:"deliver an inbox message to an existing node immediately",params:I(),output:v(),outputKind:"object",effects:["Appends one inbox entry. Critical, urgent, and normal delivery queues lifecycle revival for an active or idle dormant target; deferred waits for its next natural cycle.","--reopen commits the target resident and clears any finalization latch before delivery, allowing it to take a new mandate; it is valid for live, parked, and finalized targets. Without it, a finalized target rejects delivery before an inbox entry is appended or a revive is attempted."]},run:i(e=>x(e),"run"),render:i(e=>String(e.guidance),"render")}),z=w({name:"request",description:"deliver an immediate typed-output request to an existing node",whenToUse:"a target chosen with `--to <node-id>` or `--self` must hand back one answer in a shape you fix \u2014 a choice among options, a structured extraction, a verdict your own logic will branch on \u2014 or must be held to answering at all before it goes quiet. Use `node message send` instead when prose in the target\u2019s own judgment is enough, and `node new --output-schema` when no node holds the context yet and the work needs a fresh agent",help:{name:"node message request",summary:"deliver an immediate typed-output request to an existing node",params:M(),output:v(),outputKind:"object",effects:["Installs the one-off output schema before appending the request."]},run:i(e=>(D(e,"outputSchema"),x(e)),"run"),render:i(e=>String(e.guidance),"render")}),G=C({name:"message",description:"deliver immediate inbox work to an existing node",whenToUse:"you need one immediate delivery: send ordinary information, or request one schema-constrained result",help:{name:"node message",summary:"deliver one immediate inbox entry to an existing node",model:"Both children append one inbox entry to a node that already exists, targeted with `--to <node-id>` or `--self`, and may revive it if it is dormant. They differ in what the delivery obligates. An ordinary message obligates nothing: the target reads it and acts however it judges best. A request installs a one-off output schema and holds the target awake and owing until it answers; the answer is validated against the schema, written to the target\u2019s result file, and pushed as an update report to the TARGET\u2019S subscribers, so subscribe to it first if the answer has to reach you. Neither child creates that standing relationship \u2014 a durable delivery edge is `node subscription`, and work that needs a fresh agent is `node new`."},children:[j,z]});export{G as nodeMessage};
|
|
1
|
+
var _=Object.defineProperty;var i=(e,n)=>_(e,"name",{value:n,configurable:!0});import{envNodeId as y}from"../../shared/env.js";import{defineBranch as C,defineLeaf as w}from"../../core/command.js";import{InputError as o}from"../../core/io.js";import{requireScope as N}from"../../core/errors.js";import{OUTPUT_SCHEMA_CONSTRAINTS as R,OUTPUT_SCHEMA_TRANSPORT as P,parseOutputSchemaValue as E}from"../../core/runtime/structured-output.js";import{cliClient as A,getNodeOrNull as I,rethrowAsCliError as O}from"../api-client.js";function $(e,n){const t=e[n];if(t!==void 0&&(typeof t!="string"||t.trim()==="")){const a=n.replace(/[A-Z]/g,r=>`-${r.toLowerCase()}`);throw new o({error:"empty_discriminant",message:`--${a} must contain non-whitespace text`,received:typeof t=="string"?t:String(t),field:a,next:`Pass a non-empty --${a} value, or omit the flag.`})}}i($,"requireNonBlankDiscriminant");async function D(e,n){const t=e.to?.trim(),a=e.self===!0,r=t!==void 0&&t!=="";if(r===a)throw new o({error:"bad_target",message:r&&a?"--to and --self are mutually exclusive":n?"no target given \u2014 the message body is not a node id":"exactly one of --to or --self is required",field:r?void 0:"to",next:r?"Pass --to <node-id> or --self.":n?"Pass the node id with --to <node-id> and provide the message body as a positional argument or on stdin, or use --self.":"Pass --to <node-id> or --self."});if(a){const s=y();if(s===void 0||s==="")throw new o({error:"no_node",message:"no node to target with --self (CRTR_NODE_ID unset)",next:"Run from inside a node, or pass --to <node-id>."});return{targetId:s,isSelf:!0}}if(await I(t)===null)throw new o({error:"not_found",message:`no node: ${t}`,field:"to",next:"List nodes with `crtr node inspect list`."});return{targetId:t,isSelf:!1}}i(D,"resolveMsgTarget");function v(){return[{name:"revived",type:"boolean",required:!1,constraint:"True when this request synchronously revived the target; ordinary durable delivery to an active or idle dormant target queues lifecycle revival instead."},{name:"guidance",type:"string",required:!0,constraint:"Immediate action confirmation."}]}i(v,"messageOutput");function b(e){const n=e.to?.trim();if(!(n!==void 0&&n!==""||e.self===!0))return{message:"no target given \u2014 the positional argument is the message body, not a node id",field:"to",next:"Pass the node id with --to <node-id> and pipe the message body on stdin, or use --self."}}i(b,"missingMessageTargetForBody");function U(){return[{kind:"stdin",name:"body",required:!1,constraint:"Visible message body. Required unless --situational-context is supplied.",positionalStdinConflict:b},{kind:"flag",name:"to",type:"string",required:!1,constraint:"Target an existing node by id. Exactly one of --to or --self is required."},{kind:"flag",name:"self",type:"bool",required:!1,constraint:"Target the calling node. Exactly one of --to or --self is required."},{kind:"flag",name:"tier",type:"enum",choices:["critical","urgent","normal","deferred"],required:!1,default:"normal",constraint:"Delivery urgency. Deferred never wakes an idle node, except a terminal one, which has no later cycle to read it on and is raised to urgent."},{kind:"flag",name:"situational-context",type:"string",required:!1,constraint:"Non-empty hidden ambient context delivered beside the message, or alone."},{kind:"flag",name:"reopen",type:"bool",required:!1,constraint:"Re-task the target: clears any finalization latch before delivery and brings a done target back to active. Commits the target resident, except when you are its manager (the node that spawned it): then it keeps its lifecycle, so a terminal child still owes a final report. Valid for live, parked, and finalized targets."}]}i(U,"messageSendParams");function M(){return[{kind:"stdin",name:"body",required:!1,constraint:"Visible message body. The schema alone is a complete request.",positionalStdinConflict:b},{kind:"flag",name:"to",type:"string",required:!1,constraint:"Target an existing node by id. Exactly one of --to or --self is required."},{kind:"flag",name:"self",type:"bool",required:!1,constraint:"Target the calling node. Exactly one of --to or --self is required."},{kind:"flag",name:"tier",type:"enum",choices:["critical","urgent","normal"],required:!1,default:"normal",constraint:"Delivery urgency. Every accepted tier queues lifecycle revival for an active or idle dormant target because the request must be answered; deferred is rejected for that reason."},{kind:"flag",name:"output-schema",type:"string",required:!0,constraint:`JSON Schema the result must satisfy. ${P} The target answers with \`crtr push result\` (or declines with \`crtr push result --decline "<reason>" --code <token>\`), then keeps working. ${R}`},{kind:"flag",name:"situational-context",type:"string",required:!1,constraint:"Non-empty hidden ambient context delivered beside the message, or alone."},{kind:"flag",name:"reopen",type:"bool",required:!1,hidden:!0,constraint:"Clear a finalized target latch before delivery or fresh revive."}]}i(M,"messageRequestParams");async function x(e){N("crtr:act","crtr node message");const n=e.situationalContext!==void 0,t=e.situationalContext?.trim();if(n&&t==="")throw new o({error:"empty_situational_context",message:"--situational-context must contain non-whitespace text",field:"situational-context",next:"Pass non-whitespace text, or omit --situational-context."});const a=e.body,r=a!==void 0?a.trim():void 0,m=r!==void 0&&r!=="",{targetId:s}=await D(e,m),u=e.tier,q=u!==void 0&&u!=="",f=E(e.outputSchema),c=f!==null?JSON.stringify(f):null,h=t!==void 0&&t!=="",k=e.reopen===!0;if(c!==null&&u==="deferred")throw new o({error:"output_schema_deferred_tier",message:"--output-schema cannot combine with --tier deferred because the target must answer",field:"tier",next:"Use normal, urgent, or critical tier, or drop --output-schema."});const g=y(),T=A();if(!m&&!h&&c===null)throw new o({error:"empty_body",message:"a message body is required unless --output-schema or --situational-context is supplied",field:"body",next:"Pass the message on stdin, or supply one of the documented alternatives."});const p=q?u:"normal",d=await T.sendMessage(s,{body:r??"",tier:p,...k?{reopen:!0}:{},...h?{situational_context:t}:{},...c!==null?{output_schema:c}:{},...g!==void 0?{from:g}:{}}).catch(O);if(d.wake_failed!==void 0){const l=d.wake_failed;return{revived:!1,guidance:`Accepted but NOT delivered to a running node \u2014 waking the target failed (${l.error}): ${l.message}${l.next===void 0?"":` ${l.next}`} The message is queued durably (${l.message_id}); do not resend it.`}}const S=d.revived?"Delivered \u2014 the dormant target was revived to receive it.":p!=="deferred"?"Delivered.":d.delivered_tier===void 0?"Delivered \u2014 held for the target's next natural cycle; not revived.":"Delivered \u2014 the target is terminal and takes no later cycle, so it was raised to urgent rather than held.";return{revived:d.revived,guidance:S}}i(x,"runMessageEngine");const j=w({name:"send",description:"deliver an inbox message to an existing node immediately",whenToUse:"a target chosen with `--to <node-id>` or `--self` should have information or new direction now \u2014 steering a worker mid-task, handing over a finding it needs, correcting a wrong turn, or waking a dormant node with fresh work. The target reads it and decides what to do with it. Use `node message request` instead when you need an answer back in a shape you specify, and `node subscription add` when the target should keep receiving this node\u2019s pushes rather than this one delivery",help:{name:"node message send",summary:"deliver an inbox message to an existing node immediately",params:U(),output:v(),outputKind:"object",effects:["Appends one inbox entry. Critical, urgent, and normal delivery queues lifecycle revival for an active or idle dormant target; deferred waits for its next natural cycle.","--reopen clears any finalization latch before delivery, allowing the target to take a new mandate. It commits the target resident unless you are its manager (the node that spawned it), in which case the target keeps its lifecycle and a terminal child still owes a final report. It is valid for live, parked, and finalized targets. Without it, a finalized target rejects delivery before an inbox entry is appended or a revive is attempted."]},run:i(e=>x(e),"run"),render:i(e=>String(e.guidance),"render")}),z=w({name:"request",description:"deliver an immediate typed-output request to an existing node",whenToUse:"a target chosen with `--to <node-id>` or `--self` must hand back one answer in a shape you fix \u2014 a choice among options, a structured extraction, a verdict your own logic will branch on \u2014 or must be held to answering at all before it goes quiet. Use `node message send` instead when prose in the target\u2019s own judgment is enough, and `node new --output-schema` when no node holds the context yet and the work needs a fresh agent",help:{name:"node message request",summary:"deliver an immediate typed-output request to an existing node",params:M(),output:v(),outputKind:"object",effects:["Installs the one-off output schema before appending the request."]},run:i(e=>($(e,"outputSchema"),x(e)),"run"),render:i(e=>String(e.guidance),"render")}),G=C({name:"message",description:"deliver immediate inbox work to an existing node",whenToUse:"you need one immediate delivery: send ordinary information, or request one schema-constrained result",help:{name:"node message",summary:"deliver one immediate inbox entry to an existing node",model:"Both children append one inbox entry to a node that already exists, targeted with `--to <node-id>` or `--self`, and may revive it if it is dormant. They differ in what the delivery obligates. An ordinary message obligates nothing: the target reads it and acts however it judges best. A request installs a one-off output schema and holds the target awake and owing until it answers; the answer is validated against the schema, written to the target\u2019s result file, and pushed as an update report to the TARGET\u2019S subscribers, so subscribe to it first if the answer has to reach you. Neither child creates that standing relationship \u2014 a durable delivery edge is `node subscription`, and work that needs a fresh agent is `node new`."},children:[j,z]});export{G as nodeMessage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=Object.defineProperty;var n=(e,r)=>s(e,"name",{value:r,configurable:!0});import{defineLeaf as o}from"../../core/command.js";import{existsSync as a}from"node:fs";import{join as d}from"node:path";import{pluginsDir as
|
|
1
|
+
var s=Object.defineProperty;var n=(e,r)=>s(e,"name",{value:r,configurable:!0});import{defineLeaf as o}from"../../core/command.js";import{existsSync as a}from"node:fs";import{join as d}from"node:path";import{pluginsDir as u}from"../../core/scope.js";import{joinDirectory as l}from"../../core/directory/join.js";import{apiSocketPath as c}from"../../core/canvas/paths.js";import{listProcessTable as m}from"../../core/canvas/pid.js";import{daemonAnswers as h}from"../../core/layout-migrate/index.js";import{envNodeId as p}from"../../shared/env.js";import{isDaemonRunning as f}from"../../daemon/pidfile.js";import{findDaemonPids as w,spawnDaemon as y,stopAllDaemons as g}from"../../daemon/manage.js";function k(){const e=m();if(e===null||(p()??"")!=="")return!0;const r=new Map(e.map(t=>[t.pid,t])),i=new Set;for(let t=process.ppid;t>1&&!i.has(t);t=r.get(t)?.ppid??0)if(i.add(t),r.get(t)?.command.includes("broker-cli"))return!0;return!1}n(k,"insideBroker");function b(){if(!(process.platform==="linux"&&a("/run/crtr/launcher.sock")))return{running:n(async()=>f()||w().length>0||await h(c()),"running"),stop:n(async()=>{await g()},"stop"),start:n(async()=>{await y()},"start"),firstSync:n(async()=>{const e=u("user"),r=Date.now()+2e4;for(;e&&Date.now()<r&&!a(d(e,"cloud","commands.json"));)await new Promise(i=>setTimeout(i,250))},"firstSync"),insideNode:k}}n(b,"daemonControl");const T=o({name:"join",description:"register this runtime with its directory using an enrollment token, offline",whenToUse:"on your own computer, with a token from `<issuer>/account/machines`: run it from a plain terminal, with any viewers open. It stops the daemon, joins, and starts it again. Also at a hosted runtime's first boot, before the daemon starts, with the enrollment token provisioning passed in.",help:{name:"sys join",summary:"exchange a directory enrollment token for a new runtime client key and record the returned identity",guide:"Generates a new ES256 client key, sends only its public key to `PUT <directory>/v1/runtimes/registration` with the token as bearer, and learns the runtime id, client id, issuer, owner and URL from the answer. Only after that answer does it write the key file, set the user settings `runtime.issuer` and `runtime.audience`, and rewrite `user:local` profile grantees to `app:terminal`. canvas.db belongs to the daemon: its next start rewrites `user:local` there, before it serves anything. On your own computer a running daemon is no obstacle: join takes a `join.lock` in the runtime directory (so no open viewer's redial can autostart a daemon while it works), registers with the directory while the daemon still serves, stops the daemon, writes the identity, and starts the daemon again, whose start applies the canvas.db rewrite. Run it from a plain terminal, not from inside a node: a node's broker dies with the daemon, so join refuses `join_inside_node` instead. Where the launcher owns the daemon (a hosted container) join keeps the offline contract and refuses `daemon_running`. Presenting the token of the stored successful registration again finishes those local writes without calling the directory. Any other token generates a new key and registers again.",params:[{kind:"flag",name:"directory",type:"string",required:!0,constraint:"Directory base URL (http or https) that minted the token."},{kind:"flag",name:"token",type:"string",required:!0,sensitive:!0,constraint:"Single-use enrollment token. Sensitive: never echoed in output or errors."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Label shown for this machine on the account page. Default: this computer's hostname."}],output:[{name:"runtime",type:"string",required:!0,constraint:"Runtime principal from the directory, `runtime:<id>`."},{name:"client_id",type:"string",required:!0,constraint:"OAuth client id registered for the new key."},{name:"issuer",type:"string",required:!0,constraint:"Canonical issuer, written to `runtime.issuer`."},{name:"owner",type:"string",required:!0,constraint:"Owner principal, `user:<id>`, that replaces `user:local` subjects."},{name:"canvas_rewrite",type:"string",required:!0,constraint:"Always `next daemon start`: when canvas.db identity fields are rewritten."},{name:"url",type:"string",required:!0,constraint:"Runtime URL, written to `runtime.audience`."},{name:"registered",type:"boolean",required:!0,constraint:"False when the token matched the stored registration and the directory was not called."},{name:"daemon",type:"string",required:!0,constraint:"`restarted` (a daemon was stopped and started on the joined settings), `started` (none was running; join started one and waited, up to 20s, for its first grant sync to install the account's provider plugins, so `crtr cloud \u2026` resolves), `not_running` (none was started: a hosted container's launcher owns its daemon), or `restart_failed` (joined, but the new daemon did not start: see `daemon_error`, then run `crtr sys daemon start`)."},{name:"daemon_error",type:"string",required:!1,constraint:"Why the new daemon did not start, with `daemon: restart_failed`."}],outputKind:"object",effects:["Holds `<runtime>/join.lock` (its pid) from the start until the daemon is back; `ensureDaemon` and `sys daemon start` (without `--force`) decline to start a daemon while a live process holds it (including a start already in flight when the join began). A lock whose holder died is reclaimed.","A running daemon is stopped (SIGTERM, then SIGKILL, plus strays) after the directory accepts the registration and before the local writes, then started again. Every broker dies with it and is revived by the new daemon. If the join fails after the stop, the daemon is started again anyway. With no daemon running join starts one anyway, because a bare `crtr cloud \u2026` is an unknown command (it never autostarts a daemon) until a daemon's grant sync has installed the provider plugins. Refuses `join_inside_node` when run from inside a node and a daemon is running; refuses `daemon_running` where a container launcher owns the daemon.","On a new token: calls the directory once; on success writes `<runtime>/secrets/runtime-client.json` (mode 0600) with the private key, registration and the token's SHA-256, replacing any earlier key file. A refused or failed call writes nothing.","Sends name, platform, crtr version, installed plugin ids and sandbox state with the public key. Before the request it writes `runtime-client.pending.json` (mode 0600); a rerun with the same token and directory reuses that key, so a lost reply is retried safely. The file is deleted on success or a refusal.","Sets `runtime.issuer` and `runtime.audience` in user settings. When the directory says this machine is not the account's default (`is_default: false`) it replaces the whole `runtime` setting with `{issuer, audience, unsandboxed: true}`: no app listener, and only `app:terminal` nodes run.","Rewrites `user:local` profile grantees to `app:terminal`.","At the first daemon start after a join as this owner, before any listener (a later start under the same owner skips it): exact `user:local` subjects in canvas.db nodes and node_tokens become the owner (as do missing subjects on `app:terminal` nodes), `user:local` grantees in nodes, run_events, idempotency_keys and node_tokens become `app:terminal`, references to the unjoined runtime's `local:terminal` grant are cleared, and that local grant mirror is deleted so grant sync installs the directory's. Application grantees and free text are unchanged; a join as a different owner runs it again."]},run:n(async e=>({...await l(e.directory,e.token,e.name||void 0,b()),canvas_rewrite:"next daemon start"}),"run")});export{T as sysJoinLeaf};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export declare const LIFECYCLE_CATALOG: {
|
|
2
2
|
readonly 'node:create': {
|
|
3
3
|
readonly phase: "before";
|
|
4
|
-
readonly hookTimeoutMs:
|
|
5
|
-
readonly eventBudgetMs:
|
|
4
|
+
readonly hookTimeoutMs: 15000;
|
|
5
|
+
readonly eventBudgetMs: 30000;
|
|
6
6
|
};
|
|
7
7
|
readonly 'node:start': {
|
|
8
8
|
readonly phase: "on";
|
|
9
|
-
readonly hookTimeoutMs:
|
|
10
|
-
readonly eventBudgetMs:
|
|
9
|
+
readonly hookTimeoutMs: 15000;
|
|
10
|
+
readonly eventBudgetMs: 30000;
|
|
11
11
|
};
|
|
12
12
|
readonly 'node:close': {
|
|
13
13
|
readonly phase: "on";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var n=Object.defineProperty;var t=(e,s)=>n(e,"name",{value:s,configurable:!0});const o={"node:create":{phase:"before",hookTimeoutMs:
|
|
1
|
+
var n=Object.defineProperty;var t=(e,s)=>n(e,"name",{value:s,configurable:!0});const o={"node:create":{phase:"before",hookTimeoutMs:15e3,eventBudgetMs:3e4},"node:start":{phase:"on",hookTimeoutMs:15e3,eventBudgetMs:3e4},"node:close":{phase:"on",hookTimeoutMs:12e4,eventBudgetMs:18e4}},r=Object.freeze(Object.keys(o));function u(e){return Object.hasOwn(o,e)}t(u,"isLifecycleEvent");export{o as LIFECYCLE_CATALOG,r as LIFECYCLE_EVENTS,u as isLifecycleEvent};
|
|
@@ -2,7 +2,7 @@ import { type ValidatedCommandManifest } from '../../api/command-manifest/manife
|
|
|
2
2
|
import type { KindConfig, MemoryExtensionDeclarations, ObjectKindDeclaration, PageComponentRegistration, PluginBundle, PluginManifest, Scope } from '../../types.js';
|
|
3
3
|
import { type BundleTransport } from './endpoint.js';
|
|
4
4
|
/** Who may call a capability provider's tool group (contract 3). */
|
|
5
|
-
export type ProviderGroupWho = 'own' | 'user' | 'allowlist' | 'any'
|
|
5
|
+
export type ProviderGroupWho = 'own' | 'user' | 'allowlist' | 'any' | `team:${string}`;
|
|
6
6
|
/** One tool group a capability provider declares in `bundle.json`. */
|
|
7
7
|
export interface ProviderGroupDeclaration {
|
|
8
8
|
group: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var P=Object.defineProperty;var c=(e,o)=>P(e,"name",{value:o,configurable:!0});import{Parser as J}from"tar";import{createHash as I}from"node:crypto";import{chmodSync as W,renameSync as k}from"node:fs";import{dirname as $,join as w,resolve as B,sep as V}from"node:path";import{validateCommandManifest as z}from"../../api/command-manifest/manifest.js";import{ensureScopeInitialized as R,invalidPluginKindsReasons as F,readConfig as M,updateConfig as D,updateState as C}from"../config.js";import{general as x}from"../errors.js";import{atomicWriteJson as T,atomicWriteText as q,ensureDir as j,nowIso as E,pathExists as _,removePath as S,syncDirectory as U,syncTree as H}from"../fs-utils.js";import{invalidPageComponentsReasons as K,normalizePageComponents as G}from"../human/page-catalog.js";import{validateMemoryExtensionDeclarations as X}from"../plugin-extensions.js";import{objectBlocksShapeReasons as Z}from"../plugin-objects-manifest.js";import{validateBundleTransport as L}from"./endpoint.js";const O=/^[A-Za-z0-9_-]{1,64}$/,Y=new Set(["id","major","groups"]),Q=new Set(["group","consent_line","rate","who","allowlist"]),ee=new Set(["own","user","allowlist","any"]),ne=new Set(["bundleVersion","kinds","page_components","memory_extensions","version","transport","provider","object_kinds","scopes","groups"]);function oe(e,o,n,r,s,a){return{code:e,message:o,received:n,expected:r,next:s,...a!==void 0?{path:a}:{}}}c(oe,"issue");function u(e,o,n,r,s){return oe("plugin_bundle_invalid",e,o,n,r,s)}c(u,"bundleInvalid");function re(e){if(e[0]===31&&e[1]===139)return u("bundle archive must be uncompressed tar data","gzip-compressed archive","an uncompressed tar archive","Serve the archive without compression.");if(e.length===0||e.length%512!==0)return u("bundle archive must be a non-empty whole number of 512-byte tar blocks",`${e.length} bytes`,"a non-empty tar stream with a 512-byte block length","Serve one complete uncompressed tar archive.");if(e.length<1024||!e.subarray(-1024).every(o=>o===0))return u("bundle archive is missing the required two zero-block terminator","archive tail is not 1024 zero bytes","a complete tar stream ending in two 512-byte zero blocks","Serve the complete uncompressed tar archive.")}c(re,"archiveEnvelopeIssue");function se(e,o){if(e.length===0||e.includes("\0")||e.includes("\\")||e.startsWith("/")||/^[A-Za-z]:($|\/)/.test(e))return u("bundle member path must be a safe relative POSIX path",e,"a relative path without absolute, drive-letter, backslash, or NUL forms","Use a safe relative POSIX member path.",e);if(o==="file"&&e.endsWith("/"))return u("only directory members may have a trailing slash",e,"a regular-file path without a trailing slash","Emit this member as a regular file path.",e);if((o==="directory"&&e.endsWith("/")?e.slice(0,-1):e).split("/").some(r=>r===""||r==="."||r===".."))return u("bundle member path contains an unsafe segment",e,"non-empty segments other than . or ..","Use a safe relative POSIX member path.",e)}c(se,"validateMemberName");function te(e,o){if(o==="file")return e==="bundle.json"||e==="commands.json"||e==="hooks.json"||e.startsWith("memory/")&&e.endsWith(".md")||e.startsWith("hooks/")?void 0:u("bundle contains an unsupported file member",e,"bundle.json, commands.json, hooks.json, memory/**/*.md, or hooks/**","Remove unsupported members from the archive.",e);if(!(e==="memory"||e.startsWith("memory/")||e==="hooks"||e.startsWith("hooks/")))return u("bundle contains an unsupported directory member",e,"memory or hooks, or a directory below either subtree","Remove unsupported directory members from the archive.",e)}c(te,"validateMemberShape");function ie(e){return e.type==="directory"&&e.path.endsWith("/")?e.path.slice(0,-1):e.path}c(ie,"materializedPath");function de(e){const o=new Map;for(const n of e){const r=ie(n),s=o.get(r);if(s!==void 0)return u(s===n.type?"bundle contains a duplicate member":"bundle claims one path as both a file and a directory",`${r} (${s} and ${n.type})`,"each materialized path exactly once, with one type","Emit each archive path once, as a single member type.",r);o.set(r,n.type)}for(const[n,r]of o)if(r==="file"){for(const s of o.keys())if(s.startsWith(`${n}/`))return u("bundle nests a member beneath a regular-file path",`${s} beneath file ${n}`,"members nested only beneath directory paths","Emit the parent path as a directory or remove the nested member.",s)}}c(de,"validateMemberTree");async function ae(e){const o=[],n=new Set;let r;return await new Promise((s,a)=>{const p=new J({strict:!0,maxMetaEntrySize:Number.MAX_SAFE_INTEGER,gzip:!1,brotli:!1,zstd:!1,onReadEntry:c(t=>{const d=t.path,f=t.type==="File"||t.type==="OldFile"?"file":t.type==="Directory"?"directory":void 0;if(f===void 0||t.unsupported||t.ignore||t.linkpath!==void 0){r??=u("bundle member must be a regular file or directory without a link target",`${d} (${t.type})`,"a regular file or directory member without link metadata","Remove links and unsupported member types from the archive.",d),t.resume();return}r??=se(d,f),r??=te(d,f),n.has(d)&&(r??=u("bundle contains a duplicate member",d,"each member path exactly once","Emit each archive member only once.",d)),n.add(d);const i=[];t.on("data",b=>i.push(b)),t.on("end",()=>{r===void 0&&o.push({path:d,type:f,bytes:new Uint8Array(Buffer.concat(i))})})},"onReadEntry")});p.on("ignoredEntry",t=>{const d=t.path;r??=u("bundle contains an ignored or unsupported archive member",`${d} (${t.type})`,"only supported regular file or directory members","Remove ignored or unsupported members from the archive.",d),t.resume()}),p.once("error",a),p.once("end",s);try{p.end(Buffer.from(e))}catch(t){a(t)}}).catch(s=>{r??=u("bundle archive could not be parsed",s instanceof Error?s.message:String(s),"a valid complete uncompressed tar archive","Fix the archive format and serve it again.")}),r===void 0?{members:o}:{issue:r}}c(ae,"parseArchive");function N(e){return typeof e=="number"&&Number.isInteger(e)&&e>=1}c(N,"isPositiveInt");function ue(e){const o=c((t,d,f,i)=>({issue:u(t,d,f,"Fix the provider block in bundle.json.",`bundle.json.${i}`)}),"bad");if(typeof e!="object"||e===null||Array.isArray(e))return o("bundle.json provider must be an object",JSON.stringify(e),"{ id, major, groups }","provider");const n=e,r=Object.keys(n).filter(t=>!Y.has(t));if(r.length>0)return o("bundle.json provider has unknown keys",r.join(", "),"only: id, major, groups","provider");if(typeof n.id!="string"||!O.test(n.id))return o("provider id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(n.id),'a provider id of 1-64 letters, digits, "_" or "-"',"provider.id");if(!N(n.major))return o("provider major must be an integer of at least 1",JSON.stringify(n.major),"the served major as an integer >= 1","provider.major");const s=n.groups;if(!Array.isArray(s)||s.length===0)return o("provider groups must be a non-empty array",JSON.stringify(s),"at least one { group, consent_line, rate?, who, allowlist }","provider.groups");const a=[],p=new Set;for(let t=0;t<s.length;t++){const d=`provider.groups[${t}]`,f=s[t];if(typeof f!="object"||f===null||Array.isArray(f))return o("provider group must be an object",JSON.stringify(f),"{ group, consent_line, rate?, who, allowlist }",d);const i=f,b=Object.keys(i).filter(v=>!Q.has(v));if(b.length>0)return o("provider group has unknown keys",b.join(", "),"only: group, consent_line, rate, who, allowlist",d);if(typeof i.group!="string"||!O.test(i.group))return o("provider group id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(i.group),'a group id of 1-64 letters, digits, "_" or "-"',`${d}.group`);if(p.has(i.group))return o("provider group ids must be unique",i.group,"each group declared once",`${d}.group`);if(p.add(i.group),typeof i.consent_line!="string"||i.consent_line.trim().length===0)return o("provider group consent_line must be a non-empty string",JSON.stringify(i.consent_line),"the consent line shown for this group",`${d}.consent_line`);const h=i.rate;if(h!=null&&(typeof h!="number"||!Number.isFinite(h)||h<0))return o("provider group rate must be a non-negative number or null",JSON.stringify(h),"a finite number >= 0, null, or omitted",`${d}.rate`);if(typeof i.who!="string"||!ee.has(i.who))return o("provider group who must be own|user|allowlist|any",JSON.stringify(i.who),"own | user | allowlist | any",`${d}.who`);const m=i.allowlist;if(!Array.isArray(m)||!m.every(v=>typeof v=="string"&&v.length>0))return o("provider group allowlist must be an array of non-empty strings",JSON.stringify(m),"an array of app principals ([] unless who is allowlist)",`${d}.allowlist`);if(m.length>0&&i.who!=="allowlist")return o("provider group allowlist must be empty unless who is allowlist",`who ${i.who}`,'who: "allowlist", or allowlist: []',`${d}.allowlist`);a.push({group:i.group,consent_line:i.consent_line,...h!==void 0?{rate:h}:{},who:i.who,allowlist:m})}return{provider:{id:n.id,major:n.major,groups:a}}}c(ue,"parseProviderBlock");function le(e){const o=[],n=c((r,s)=>{const a=[...s,r.name];if(r.kind==="leaf")o.push({path:a.join(" "),leaf:r});else for(const p of r.children)n(p,a)},"walk");for(const r of e.roots)n(r,[]);for(const r of e.coreMounts)n(r.node,r.parent);return o}c(le,"collectLeaves");function pe(e,o){const n=e.provider;if(n===void 0)return;if(e.transport===void 0)return u("a provider bundle must declare its transport","(missing)",'transport { kind: "http", endpoint } in bundle.json',"Add the provider endpoint as bundle.json transport.","bundle.json.transport");if(e.transport.kind==="http"&&e.transport.authEnv!==void 0)return u("a provider bundle must not declare authEnv",e.transport.authEnv,"no authEnv: the runtime presents its own outbound token","Remove transport.authEnv from bundle.json.","bundle.json.transport.authEnv");const r=new Set(n.groups.map(s=>s.group));for(const{path:s,leaf:a}of le(o)){const p="group"in a?a.group:void 0;if(p===void 0)return u(`provider leaf "${s}" has no group`,s,"a group on every leaf of a provider bundle",`Add a group to leaf "${s}".`,`commands.json:${s}.group`);if(!r.has(p))return u(`provider leaf "${s}" names undeclared group "${p}"`,p,`one of: ${[...r].join(", ")}`,`Declare group "${p}" in the provider block, or move leaf "${s}" to a declared group.`,`commands.json:${s}.group`);if("rest"in a&&a.rest.streaming===!0)return u(`provider leaf "${s}" streams`,"rest.streaming: true","no streaming leaves in a provider bundle",`Make leaf "${s}" return one response.`,`commands.json:${s}.rest.streaming`)}}c(pe,"providerBundleIssue");function ce(e){let o;try{o=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issue:u("bundle.json is not valid JSON","invalid JSON",'{"bundleVersion":1}',"Write valid bundle metadata.","bundle.json")}}if(typeof o!="object"||o===null||Array.isArray(o))return{issue:u("bundle.json must be a JSON object",JSON.stringify(o),"an object with bundleVersion set to 1 and optionally kinds",'Set bundle.json to {"bundleVersion":1}.',"bundle.json")};const n=o,r=Object.keys(n).filter(d=>!ne.has(d));if(n.bundleVersion!==1||r.length>0)return{issue:u("bundle.json must contain bundleVersion 1 and at most version, transport, provider, kinds, page_components, memory_extensions, object_kinds, scopes, and groups",JSON.stringify(o),"an object with bundleVersion set to 1 and optional version, transport, provider, kinds, page_components, memory_extensions, object_kinds, scopes, and groups",'Set bundle.json to {"bundleVersion":1} plus optional contribution blocks.',"bundle.json")};if(n.kinds!==void 0){const d=F(n.kinds);if(d.length>0)return{issue:u("bundle.json kinds block is invalid",d.join("; "),"each entry a full KindConfig (whenToUse required) or a sparse patch of launch fields","Fix the kinds entries in bundle.json.","bundle.json")}}if(n.page_components!==void 0){const d=K(n.page_components);if(d.length>0)return{issue:u("bundle.json page_components block is invalid",d.join("; "),"an array of page component registrations, each a kind string or {kind, description?, useWhen?, doc?, display?}","Fix the page_components entries in bundle.json.","bundle.json")}}const s=Z(n);if(n.provider!==void 0&&n.scopes!==void 0&&s.push("scopes is for local plugins; a provider gets its scopes from directory consent"),s.length>0)return{issue:u("bundle.json object_kinds or scopes block is invalid",s.join("; "),"object_kinds keyed by kind name with description, events and optional read.command; scopes an array of crtr:objects:<verb>:<target> strings (local plugins only)","Fix the object_kinds and scopes blocks in bundle.json.","bundle.json")};let a;if(n.memory_extensions!==void 0)try{a=X(n.memory_extensions)}catch(d){return{issue:u("bundle.json memory_extensions block is invalid",d instanceof Error?d.message:String(d),"a valid memory_extensions declaration object","Fix the memory_extensions declarations in bundle.json.","bundle.json")}}if(n.version!==void 0&&!N(n.version))return{issue:u("bundle.json version must be an integer of at least 1",JSON.stringify(n.version),"the plugin major as an integer >= 1, or omitted","Fix version in bundle.json.","bundle.json.version")};let p;if(n.transport!==void 0){const d=L(n.transport);if(d.transport===void 0)return{issue:u("bundle.json transport is invalid",d.errors.join("; "),'{ kind: "http", endpoint, authEnv? } or { kind: "machine" }',"Fix transport in bundle.json.","bundle.json.transport")};p=d.transport}let t;if(n.provider!==void 0){const d=ue(n.provider);if(d.issue!==void 0)return{issue:d.issue};if(t=d.provider,n.version!==void 0&&n.version!==t.major)return{issue:u("bundle.json provider major must equal version",`version ${String(n.version)}, major ${t.major}`,"one major","Set provider.major and version to the same integer.","bundle.json.provider.major")}}return{...n.version!==void 0?{version:n.version}:{},...p!==void 0?{transport:p}:{},...t!==void 0?{provider:t}:{},...n.kinds!==void 0?{kinds:n.kinds}:{},...n.page_components!==void 0?{page_components:G(n.page_components)}:{},...a!==void 0?{memory_extensions:a}:{},...n.object_kinds!==void 0?{object_kinds:n.object_kinds}:{},...n.scopes!==void 0?{scopes:n.scopes}:{}}}c(ce,"parseBundleMetadata");function me(e,o,n,r=new Set){let s;try{s=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issues:[u("commands.json is not valid JSON","invalid JSON","a valid command-manifest JSON object","Regenerate commands.json.","commands.json")]}}const a=z(s,{transport:"http",reservedCoreNames:o,extensibleCoreBranches:r,...n!==void 0?{coreCommandPaths:n}:{}});return{...a.manifest!==void 0?{commands:a.manifest}:{},issues:a.issues}}c(me,"parseCommands");async function fe(e,o){const n=re(e);if(n!==void 0)return{issues:[n]};const r=await ae(e);if(r.issue!==void 0||r.members===void 0)return{issues:[r.issue]};const s=de(r.members);if(s!==void 0)return{issues:[s]};const a=r.members.find(l=>l.path==="bundle.json"&&l.type==="file"),p=r.members.find(l=>l.path==="commands.json"&&l.type==="file");if(a===void 0||p===void 0)return{issues:[u("bundle must contain exactly one bundle.json and one commands.json regular file",`bundle.json=${a!==void 0}, commands.json=${p!==void 0}`,"one regular bundle.json and one regular commands.json member","Add the required metadata members to the archive.")]};const t=r.members.find(l=>l.path==="hooks.json"&&l.type==="file"),d=r.members.filter(l=>l.type==="file"&&l.path.startsWith("hooks/"));if((t!==void 0||r.members.some(l=>l.path==="hooks"||l.path.startsWith("hooks/")))&&(t===void 0||!d.some(l=>l.path==="hooks/dispatch")))return{issues:[u("bundle hook payload must contain both hooks.json and hooks/dispatch regular files",`hooks.json=${t!==void 0}, hooks/dispatch=${d.some(l=>l.path==="hooks/dispatch")}`,"both hooks.json and hooks/dispatch regular files","Add both required hook members or remove the hook payload.")]};const i=ce(a.bytes);if(i.issue!==void 0)return{issues:[i.issue]};const b=me(p.bytes,o.reservedCoreNames,o.coreCommandPaths,o.extensibleCoreBranches);if(b.commands===void 0)return{issues:b.issues};const h=b.commands.version;if(i.version!==void 0&&h!==void 0&&i.version!==h)return{issues:[u("bundle.json version and commands.json version differ",`bundle.json ${i.version}, commands.json ${h}`,"one plugin major","Emit the same version in bundle.json and commands.json.","bundle.json.version")]};const m=i.version??h;if(i.provider!==void 0&&m===void 0)return{issues:[u("a provider bundle requires a plugin version","no version in bundle.json or commands.json",`version ${i.provider.major}, the provider major`,"Emit the provider major as the plugin version.","bundle.json.version")]};if(i.provider!==void 0&&m!==i.provider.major)return{issues:[u("bundle provider major must equal the plugin version",`version ${m}, major ${i.provider.major}`,"one major","Emit the provider major as the plugin version.","bundle.json.provider.major")]};const v=pe(i,b.commands);return v!==void 0?{issues:[v]}:{bundle:{bundleVersion:1,...m!==void 0?{version:m}:{},...i.transport!==void 0?{transport:i.transport}:{},...i.provider!==void 0?{provider:i.provider}:{},...i.kinds!==void 0?{kinds:i.kinds}:{},...i.page_components!==void 0?{page_components:i.page_components}:{},...i.memory_extensions!==void 0?{memory_extensions:i.memory_extensions}:{},...i.object_kinds!==void 0?{object_kinds:i.object_kinds}:{},...i.scopes!==void 0?{scopes:i.scopes}:{},commands:b.commands,commandsBytes:p.bytes,directories:r.members.filter(l=>l.type==="directory").map(l=>({path:l.path})),memory:r.members.filter(l=>l.type==="file"&&l.path.startsWith("memory/")).map(l=>({path:l.path,bytes:l.bytes})),...t!==void 0?{hooks:{manifest:t.bytes,files:d.map(l=>({path:l.path,bytes:l.bytes}))}}:{}},issues:[]}}c(fe,"validatePluginBundle");function he(e){return I("sha256").update(e).digest("hex").slice(0,12)}c(he,"bundleVersion");function Pe(e,o,n,r){return M(o).plugins[e]?.version!==r?!1:_(w(n,"commands.json"))}c(Pe,"isInstalledGeneration");function be(e,o,n,r){C(e,s=>{s.plugins[o]===void 0&&(s.plugins[o]={}),r.swapped&&(s.plugins[o].last_updated=E()),s.plugins[o].checked_at=E(),n===void 0?delete s.plugins[o].etag:s.plugins[o].etag=n})}c(be,"recordCheckedGeneration");function g(e,o){const n=B(e,o);if(n===e||!n.startsWith(e+V))throw x(`bundle member path escapes the staging directory: ${o}`);return n}c(g,"stagedPath");function ve(e,o,n){const r=c((s,a,p=420)=>q(s,a,{mode:p,durable:!0}),"write");T(g(e,".crouter-plugin/plugin.json"),o,{durable:!0});for(const s of n.directories)j(g(e,s.path));r(g(e,"commands.json"),n.commandsBytes);for(const s of n.memory)r(g(e,s.path),s.bytes);if(n.hooks!==void 0){r(g(e,"hooks.json"),n.hooks.manifest);for(const s of n.hooks.files)r(g(e,s.path),s.bytes);W(g(e,"hooks/dispatch"),493)}H(e)}c(ve,"writeStagedBundle");async function Je(e,o,n){const{scope:r,scopeRootPath:s,root:a}=n,p=await fe(o,n.validation);if(p.bundle===void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,p.issues);const t=p.bundle,d=n.accept?.(t);if(d!==void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,[u(d,e,"the bundle this install asked for","Serve the requested bundle.")]);const f=n.version??he(o),i=t.provider!==void 0&&t.transport!==void 0?t.transport:n.transport,b={name:e,version:f,commands:"commands.json",transport:i,bundle:n.bundle,...t.kinds!==void 0?{kinds:t.kinds}:{},...t.page_components!==void 0?{page_components:t.page_components}:{},...t.memory_extensions!==void 0?{memory_extensions:t.memory_extensions}:{},...t.object_kinds!==void 0?{object_kinds:t.object_kinds}:{},...t.scopes!==void 0?{scopes:t.scopes}:{},...t.hooks!==void 0?{hooks:"hooks.json",hookExecutable:"hooks/dispatch"}:{},...t.provider!==void 0?{provider:t.provider}:{}},h=w(s,"tmp"),m=w(h,`${e}.${process.pid}`),v=`${m}.prev`;if(_(v))throw x(`cannot replace plugin "${e}": prior interrupted replacement remains at ${v}`);j(h),j($(a)),S(m);let l;try{j(m),ve(m,b,t),l=await n.validateStaged(m,b);let y=!1;_(a)&&(k(a,v),y=!0);try{k(m,a)}catch(A){throw y&&k(v,a),A}U($(a)),y&&S(v)}finally{S(m)}return R(r,s),D(r,y=>{y.plugins[e]={enabled:n.enable?!0:y.plugins[e]?.enabled??!0,version:f}}),be(r,e,n.etag,{swapped:!0}),{manifest:b,validated:t,version:f,root:a,staged:l}}c(Je,"installBundleArchive");export{he as bundleVersion,Je as installBundleArchive,Pe as isInstalledGeneration,be as recordCheckedGeneration,fe as validatePluginBundle,ve as writeStagedBundle};
|
|
1
|
+
var P=Object.defineProperty;var c=(e,o)=>P(e,"name",{value:o,configurable:!0});import{Parser as J}from"tar";import{createHash as I}from"node:crypto";import{chmodSync as W,renameSync as k}from"node:fs";import{dirname as $,join as w,resolve as V,sep as z}from"node:path";import{validateCommandManifest as B}from"../../api/command-manifest/manifest.js";import{ensureScopeInitialized as R,invalidPluginKindsReasons as F,readConfig as M,updateConfig as D,updateState as T}from"../config.js";import{general as x}from"../errors.js";import{atomicWriteJson as C,atomicWriteText as q,ensureDir as j,nowIso as E,pathExists as _,removePath as S,syncDirectory as H,syncTree as U}from"../fs-utils.js";import{invalidPageComponentsReasons as K,normalizePageComponents as G}from"../human/page-catalog.js";import{validateMemoryExtensionDeclarations as X}from"../plugin-extensions.js";import{objectBlocksShapeReasons as Z}from"../plugin-objects-manifest.js";import{validateBundleTransport as L}from"./endpoint.js";const O=/^[A-Za-z0-9_-]{1,64}$/,Y=new Set(["id","major","groups"]),Q=new Set(["group","consent_line","rate","who","allowlist"]),ee=new Set(["own","user","allowlist","any"]),ne=/^team:[a-z0-9]+$/,oe=new Set(["bundleVersion","kinds","page_components","memory_extensions","version","transport","provider","object_kinds","scopes","groups"]);function re(e,o,n,r,s,a){return{code:e,message:o,received:n,expected:r,next:s,...a!==void 0?{path:a}:{}}}c(re,"issue");function u(e,o,n,r,s){return re("plugin_bundle_invalid",e,o,n,r,s)}c(u,"bundleInvalid");function se(e){if(e[0]===31&&e[1]===139)return u("bundle archive must be uncompressed tar data","gzip-compressed archive","an uncompressed tar archive","Serve the archive without compression.");if(e.length===0||e.length%512!==0)return u("bundle archive must be a non-empty whole number of 512-byte tar blocks",`${e.length} bytes`,"a non-empty tar stream with a 512-byte block length","Serve one complete uncompressed tar archive.");if(e.length<1024||!e.subarray(-1024).every(o=>o===0))return u("bundle archive is missing the required two zero-block terminator","archive tail is not 1024 zero bytes","a complete tar stream ending in two 512-byte zero blocks","Serve the complete uncompressed tar archive.")}c(se,"archiveEnvelopeIssue");function te(e,o){if(e.length===0||e.includes("\0")||e.includes("\\")||e.startsWith("/")||/^[A-Za-z]:($|\/)/.test(e))return u("bundle member path must be a safe relative POSIX path",e,"a relative path without absolute, drive-letter, backslash, or NUL forms","Use a safe relative POSIX member path.",e);if(o==="file"&&e.endsWith("/"))return u("only directory members may have a trailing slash",e,"a regular-file path without a trailing slash","Emit this member as a regular file path.",e);if((o==="directory"&&e.endsWith("/")?e.slice(0,-1):e).split("/").some(r=>r===""||r==="."||r===".."))return u("bundle member path contains an unsafe segment",e,"non-empty segments other than . or ..","Use a safe relative POSIX member path.",e)}c(te,"validateMemberName");function ie(e,o){if(o==="file")return e==="bundle.json"||e==="commands.json"||e==="hooks.json"||e.startsWith("memory/")&&e.endsWith(".md")||e.startsWith("hooks/")?void 0:u("bundle contains an unsupported file member",e,"bundle.json, commands.json, hooks.json, memory/**/*.md, or hooks/**","Remove unsupported members from the archive.",e);if(!(e==="memory"||e.startsWith("memory/")||e==="hooks"||e.startsWith("hooks/")))return u("bundle contains an unsupported directory member",e,"memory or hooks, or a directory below either subtree","Remove unsupported directory members from the archive.",e)}c(ie,"validateMemberShape");function de(e){return e.type==="directory"&&e.path.endsWith("/")?e.path.slice(0,-1):e.path}c(de,"materializedPath");function ae(e){const o=new Map;for(const n of e){const r=de(n),s=o.get(r);if(s!==void 0)return u(s===n.type?"bundle contains a duplicate member":"bundle claims one path as both a file and a directory",`${r} (${s} and ${n.type})`,"each materialized path exactly once, with one type","Emit each archive path once, as a single member type.",r);o.set(r,n.type)}for(const[n,r]of o)if(r==="file"){for(const s of o.keys())if(s.startsWith(`${n}/`))return u("bundle nests a member beneath a regular-file path",`${s} beneath file ${n}`,"members nested only beneath directory paths","Emit the parent path as a directory or remove the nested member.",s)}}c(ae,"validateMemberTree");async function ue(e){const o=[],n=new Set;let r;return await new Promise((s,a)=>{const p=new J({strict:!0,maxMetaEntrySize:Number.MAX_SAFE_INTEGER,gzip:!1,brotli:!1,zstd:!1,onReadEntry:c(t=>{const d=t.path,f=t.type==="File"||t.type==="OldFile"?"file":t.type==="Directory"?"directory":void 0;if(f===void 0||t.unsupported||t.ignore||t.linkpath!==void 0){r??=u("bundle member must be a regular file or directory without a link target",`${d} (${t.type})`,"a regular file or directory member without link metadata","Remove links and unsupported member types from the archive.",d),t.resume();return}r??=te(d,f),r??=ie(d,f),n.has(d)&&(r??=u("bundle contains a duplicate member",d,"each member path exactly once","Emit each archive member only once.",d)),n.add(d);const i=[];t.on("data",b=>i.push(b)),t.on("end",()=>{r===void 0&&o.push({path:d,type:f,bytes:new Uint8Array(Buffer.concat(i))})})},"onReadEntry")});p.on("ignoredEntry",t=>{const d=t.path;r??=u("bundle contains an ignored or unsupported archive member",`${d} (${t.type})`,"only supported regular file or directory members","Remove ignored or unsupported members from the archive.",d),t.resume()}),p.once("error",a),p.once("end",s);try{p.end(Buffer.from(e))}catch(t){a(t)}}).catch(s=>{r??=u("bundle archive could not be parsed",s instanceof Error?s.message:String(s),"a valid complete uncompressed tar archive","Fix the archive format and serve it again.")}),r===void 0?{members:o}:{issue:r}}c(ue,"parseArchive");function N(e){return typeof e=="number"&&Number.isInteger(e)&&e>=1}c(N,"isPositiveInt");function le(e){const o=c((t,d,f,i)=>({issue:u(t,d,f,"Fix the provider block in bundle.json.",`bundle.json.${i}`)}),"bad");if(typeof e!="object"||e===null||Array.isArray(e))return o("bundle.json provider must be an object",JSON.stringify(e),"{ id, major, groups }","provider");const n=e,r=Object.keys(n).filter(t=>!Y.has(t));if(r.length>0)return o("bundle.json provider has unknown keys",r.join(", "),"only: id, major, groups","provider");if(typeof n.id!="string"||!O.test(n.id))return o("provider id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(n.id),'a provider id of 1-64 letters, digits, "_" or "-"',"provider.id");if(!N(n.major))return o("provider major must be an integer of at least 1",JSON.stringify(n.major),"the served major as an integer >= 1","provider.major");const s=n.groups;if(!Array.isArray(s)||s.length===0)return o("provider groups must be a non-empty array",JSON.stringify(s),"at least one { group, consent_line, rate?, who, allowlist }","provider.groups");const a=[],p=new Set;for(let t=0;t<s.length;t++){const d=`provider.groups[${t}]`,f=s[t];if(typeof f!="object"||f===null||Array.isArray(f))return o("provider group must be an object",JSON.stringify(f),"{ group, consent_line, rate?, who, allowlist }",d);const i=f,b=Object.keys(i).filter(v=>!Q.has(v));if(b.length>0)return o("provider group has unknown keys",b.join(", "),"only: group, consent_line, rate, who, allowlist",d);if(typeof i.group!="string"||!O.test(i.group))return o("provider group id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(i.group),'a group id of 1-64 letters, digits, "_" or "-"',`${d}.group`);if(p.has(i.group))return o("provider group ids must be unique",i.group,"each group declared once",`${d}.group`);if(p.add(i.group),typeof i.consent_line!="string"||i.consent_line.trim().length===0)return o("provider group consent_line must be a non-empty string",JSON.stringify(i.consent_line),"the consent line shown for this group",`${d}.consent_line`);const h=i.rate;if(h!=null&&(typeof h!="number"||!Number.isFinite(h)||h<0))return o("provider group rate must be a non-negative number or null",JSON.stringify(h),"a finite number >= 0, null, or omitted",`${d}.rate`);if(typeof i.who!="string"||!(ee.has(i.who)||ne.test(i.who)))return o("provider group who must be own|user|allowlist|any|team:<id>",JSON.stringify(i.who),"own | user | allowlist | any | team:<id> (id of lowercase letters and digits)",`${d}.who`);const m=i.allowlist;if(!Array.isArray(m)||!m.every(v=>typeof v=="string"&&v.length>0))return o("provider group allowlist must be an array of non-empty strings",JSON.stringify(m),"an array of app principals ([] unless who is allowlist)",`${d}.allowlist`);if(m.length>0&&i.who!=="allowlist")return o("provider group allowlist must be empty unless who is allowlist",`who ${i.who}`,'who: "allowlist", or allowlist: []',`${d}.allowlist`);a.push({group:i.group,consent_line:i.consent_line,...h!==void 0?{rate:h}:{},who:i.who,allowlist:m})}return{provider:{id:n.id,major:n.major,groups:a}}}c(le,"parseProviderBlock");function pe(e){const o=[],n=c((r,s)=>{const a=[...s,r.name];if(r.kind==="leaf")o.push({path:a.join(" "),leaf:r});else for(const p of r.children)n(p,a)},"walk");for(const r of e.roots)n(r,[]);for(const r of e.coreMounts)n(r.node,r.parent);return o}c(pe,"collectLeaves");function ce(e,o){const n=e.provider;if(n===void 0)return;if(e.transport===void 0)return u("a provider bundle must declare its transport","(missing)",'transport { kind: "http", endpoint } in bundle.json',"Add the provider endpoint as bundle.json transport.","bundle.json.transport");if(e.transport.kind==="http"&&e.transport.authEnv!==void 0)return u("a provider bundle must not declare authEnv",e.transport.authEnv,"no authEnv: the runtime presents its own outbound token","Remove transport.authEnv from bundle.json.","bundle.json.transport.authEnv");const r=new Set(n.groups.map(s=>s.group));for(const{path:s,leaf:a}of pe(o)){const p="group"in a?a.group:void 0;if(p===void 0)return u(`provider leaf "${s}" has no group`,s,"a group on every leaf of a provider bundle",`Add a group to leaf "${s}".`,`commands.json:${s}.group`);if(!r.has(p))return u(`provider leaf "${s}" names undeclared group "${p}"`,p,`one of: ${[...r].join(", ")}`,`Declare group "${p}" in the provider block, or move leaf "${s}" to a declared group.`,`commands.json:${s}.group`);if("rest"in a&&a.rest.streaming===!0)return u(`provider leaf "${s}" streams`,"rest.streaming: true","no streaming leaves in a provider bundle",`Make leaf "${s}" return one response.`,`commands.json:${s}.rest.streaming`)}}c(ce,"providerBundleIssue");function me(e){let o;try{o=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issue:u("bundle.json is not valid JSON","invalid JSON",'{"bundleVersion":1}',"Write valid bundle metadata.","bundle.json")}}if(typeof o!="object"||o===null||Array.isArray(o))return{issue:u("bundle.json must be a JSON object",JSON.stringify(o),"an object with bundleVersion set to 1 and optionally kinds",'Set bundle.json to {"bundleVersion":1}.',"bundle.json")};const n=o,r=Object.keys(n).filter(d=>!oe.has(d));if(n.bundleVersion!==1||r.length>0)return{issue:u("bundle.json must contain bundleVersion 1 and at most version, transport, provider, kinds, page_components, memory_extensions, object_kinds, scopes, and groups",JSON.stringify(o),"an object with bundleVersion set to 1 and optional version, transport, provider, kinds, page_components, memory_extensions, object_kinds, scopes, and groups",'Set bundle.json to {"bundleVersion":1} plus optional contribution blocks.',"bundle.json")};if(n.kinds!==void 0){const d=F(n.kinds);if(d.length>0)return{issue:u("bundle.json kinds block is invalid",d.join("; "),"each entry a full KindConfig (whenToUse required) or a sparse patch of launch fields","Fix the kinds entries in bundle.json.","bundle.json")}}if(n.page_components!==void 0){const d=K(n.page_components);if(d.length>0)return{issue:u("bundle.json page_components block is invalid",d.join("; "),"an array of page component registrations, each a kind string or {kind, description?, useWhen?, doc?, display?}","Fix the page_components entries in bundle.json.","bundle.json")}}const s=Z(n);if(n.provider!==void 0&&n.scopes!==void 0&&s.push("scopes is for local plugins; a provider gets its scopes from directory consent"),s.length>0)return{issue:u("bundle.json object_kinds or scopes block is invalid",s.join("; "),"object_kinds keyed by kind name with description, events and optional read.command; scopes an array of crtr:objects:<verb>:<target> strings (local plugins only)","Fix the object_kinds and scopes blocks in bundle.json.","bundle.json")};let a;if(n.memory_extensions!==void 0)try{a=X(n.memory_extensions)}catch(d){return{issue:u("bundle.json memory_extensions block is invalid",d instanceof Error?d.message:String(d),"a valid memory_extensions declaration object","Fix the memory_extensions declarations in bundle.json.","bundle.json")}}if(n.version!==void 0&&!N(n.version))return{issue:u("bundle.json version must be an integer of at least 1",JSON.stringify(n.version),"the plugin major as an integer >= 1, or omitted","Fix version in bundle.json.","bundle.json.version")};let p;if(n.transport!==void 0){const d=L(n.transport);if(d.transport===void 0)return{issue:u("bundle.json transport is invalid",d.errors.join("; "),'{ kind: "http", endpoint, authEnv? } or { kind: "machine" }',"Fix transport in bundle.json.","bundle.json.transport")};p=d.transport}let t;if(n.provider!==void 0){const d=le(n.provider);if(d.issue!==void 0)return{issue:d.issue};if(t=d.provider,n.version!==void 0&&n.version!==t.major)return{issue:u("bundle.json provider major must equal version",`version ${String(n.version)}, major ${t.major}`,"one major","Set provider.major and version to the same integer.","bundle.json.provider.major")}}return{...n.version!==void 0?{version:n.version}:{},...p!==void 0?{transport:p}:{},...t!==void 0?{provider:t}:{},...n.kinds!==void 0?{kinds:n.kinds}:{},...n.page_components!==void 0?{page_components:G(n.page_components)}:{},...a!==void 0?{memory_extensions:a}:{},...n.object_kinds!==void 0?{object_kinds:n.object_kinds}:{},...n.scopes!==void 0?{scopes:n.scopes}:{}}}c(me,"parseBundleMetadata");function fe(e,o,n,r=new Set){let s;try{s=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issues:[u("commands.json is not valid JSON","invalid JSON","a valid command-manifest JSON object","Regenerate commands.json.","commands.json")]}}const a=B(s,{transport:"http",reservedCoreNames:o,extensibleCoreBranches:r,...n!==void 0?{coreCommandPaths:n}:{}});return{...a.manifest!==void 0?{commands:a.manifest}:{},issues:a.issues}}c(fe,"parseCommands");async function he(e,o){const n=se(e);if(n!==void 0)return{issues:[n]};const r=await ue(e);if(r.issue!==void 0||r.members===void 0)return{issues:[r.issue]};const s=ae(r.members);if(s!==void 0)return{issues:[s]};const a=r.members.find(l=>l.path==="bundle.json"&&l.type==="file"),p=r.members.find(l=>l.path==="commands.json"&&l.type==="file");if(a===void 0||p===void 0)return{issues:[u("bundle must contain exactly one bundle.json and one commands.json regular file",`bundle.json=${a!==void 0}, commands.json=${p!==void 0}`,"one regular bundle.json and one regular commands.json member","Add the required metadata members to the archive.")]};const t=r.members.find(l=>l.path==="hooks.json"&&l.type==="file"),d=r.members.filter(l=>l.type==="file"&&l.path.startsWith("hooks/"));if((t!==void 0||r.members.some(l=>l.path==="hooks"||l.path.startsWith("hooks/")))&&(t===void 0||!d.some(l=>l.path==="hooks/dispatch")))return{issues:[u("bundle hook payload must contain both hooks.json and hooks/dispatch regular files",`hooks.json=${t!==void 0}, hooks/dispatch=${d.some(l=>l.path==="hooks/dispatch")}`,"both hooks.json and hooks/dispatch regular files","Add both required hook members or remove the hook payload.")]};const i=me(a.bytes);if(i.issue!==void 0)return{issues:[i.issue]};const b=fe(p.bytes,o.reservedCoreNames,o.coreCommandPaths,o.extensibleCoreBranches);if(b.commands===void 0)return{issues:b.issues};const h=b.commands.version;if(i.version!==void 0&&h!==void 0&&i.version!==h)return{issues:[u("bundle.json version and commands.json version differ",`bundle.json ${i.version}, commands.json ${h}`,"one plugin major","Emit the same version in bundle.json and commands.json.","bundle.json.version")]};const m=i.version??h;if(i.provider!==void 0&&m===void 0)return{issues:[u("a provider bundle requires a plugin version","no version in bundle.json or commands.json",`version ${i.provider.major}, the provider major`,"Emit the provider major as the plugin version.","bundle.json.version")]};if(i.provider!==void 0&&m!==i.provider.major)return{issues:[u("bundle provider major must equal the plugin version",`version ${m}, major ${i.provider.major}`,"one major","Emit the provider major as the plugin version.","bundle.json.provider.major")]};const v=ce(i,b.commands);return v!==void 0?{issues:[v]}:{bundle:{bundleVersion:1,...m!==void 0?{version:m}:{},...i.transport!==void 0?{transport:i.transport}:{},...i.provider!==void 0?{provider:i.provider}:{},...i.kinds!==void 0?{kinds:i.kinds}:{},...i.page_components!==void 0?{page_components:i.page_components}:{},...i.memory_extensions!==void 0?{memory_extensions:i.memory_extensions}:{},...i.object_kinds!==void 0?{object_kinds:i.object_kinds}:{},...i.scopes!==void 0?{scopes:i.scopes}:{},commands:b.commands,commandsBytes:p.bytes,directories:r.members.filter(l=>l.type==="directory").map(l=>({path:l.path})),memory:r.members.filter(l=>l.type==="file"&&l.path.startsWith("memory/")).map(l=>({path:l.path,bytes:l.bytes})),...t!==void 0?{hooks:{manifest:t.bytes,files:d.map(l=>({path:l.path,bytes:l.bytes}))}}:{}},issues:[]}}c(he,"validatePluginBundle");function be(e){return I("sha256").update(e).digest("hex").slice(0,12)}c(be,"bundleVersion");function Je(e,o,n,r){return M(o).plugins[e]?.version!==r?!1:_(w(n,"commands.json"))}c(Je,"isInstalledGeneration");function ve(e,o,n,r){T(e,s=>{s.plugins[o]===void 0&&(s.plugins[o]={}),r.swapped&&(s.plugins[o].last_updated=E()),s.plugins[o].checked_at=E(),n===void 0?delete s.plugins[o].etag:s.plugins[o].etag=n})}c(ve,"recordCheckedGeneration");function g(e,o){const n=V(e,o);if(n===e||!n.startsWith(e+z))throw x(`bundle member path escapes the staging directory: ${o}`);return n}c(g,"stagedPath");function ge(e,o,n){const r=c((s,a,p=420)=>q(s,a,{mode:p,durable:!0}),"write");C(g(e,".crouter-plugin/plugin.json"),o,{durable:!0});for(const s of n.directories)j(g(e,s.path));r(g(e,"commands.json"),n.commandsBytes);for(const s of n.memory)r(g(e,s.path),s.bytes);if(n.hooks!==void 0){r(g(e,"hooks.json"),n.hooks.manifest);for(const s of n.hooks.files)r(g(e,s.path),s.bytes);W(g(e,"hooks/dispatch"),493)}U(e)}c(ge,"writeStagedBundle");async function Ie(e,o,n){const{scope:r,scopeRootPath:s,root:a}=n,p=await he(o,n.validation);if(p.bundle===void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,p.issues);const t=p.bundle,d=n.accept?.(t);if(d!==void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,[u(d,e,"the bundle this install asked for","Serve the requested bundle.")]);const f=n.version??be(o),i=t.provider!==void 0&&t.transport!==void 0?t.transport:n.transport,b={name:e,version:f,commands:"commands.json",transport:i,bundle:n.bundle,...t.kinds!==void 0?{kinds:t.kinds}:{},...t.page_components!==void 0?{page_components:t.page_components}:{},...t.memory_extensions!==void 0?{memory_extensions:t.memory_extensions}:{},...t.object_kinds!==void 0?{object_kinds:t.object_kinds}:{},...t.scopes!==void 0?{scopes:t.scopes}:{},...t.hooks!==void 0?{hooks:"hooks.json",hookExecutable:"hooks/dispatch"}:{},...t.provider!==void 0?{provider:t.provider}:{}},h=w(s,"tmp"),m=w(h,`${e}.${process.pid}`),v=`${m}.prev`;if(_(v))throw x(`cannot replace plugin "${e}": prior interrupted replacement remains at ${v}`);j(h),j($(a)),S(m);let l;try{j(m),ge(m,b,t),l=await n.validateStaged(m,b);let y=!1;_(a)&&(k(a,v),y=!0);try{k(m,a)}catch(A){throw y&&k(v,a),A}H($(a)),y&&S(v)}finally{S(m)}return R(r,s),D(r,y=>{y.plugins[e]={enabled:n.enable?!0:y.plugins[e]?.enabled??!0,version:f}}),ve(r,e,n.etag,{swapped:!0}),{manifest:b,validated:t,version:f,root:a,staged:l}}c(Ie,"installBundleArchive");export{be as bundleVersion,Ie as installBundleArchive,Je as isInstalledGeneration,ve as recordCheckedGeneration,he as validatePluginBundle,ge as writeStagedBundle};
|
|
@@ -42,7 +42,7 @@ export interface JoinDaemonControl {
|
|
|
42
42
|
export declare function installedPluginIds(): string[];
|
|
43
43
|
/** The owner a successful join recorded, or null when `crtr sys join` never
|
|
44
44
|
* wrote the client file (no file, or one provisioned without join, which has
|
|
45
|
-
* no `token_sha256`). The daemon applies OWN-11 to canvas.db from it at
|
|
45
|
+
* no `token_sha256`). The daemon applies OWN-11 to canvas.db from it at the first start under each owner. */
|
|
46
46
|
export declare function joinedOwner(): string | null;
|
|
47
47
|
/** Join this runtime to a directory.
|
|
48
48
|
*
|
|
@@ -20,19 +20,30 @@ export declare function assertFinalizedForReopen(nodeId: string): string;
|
|
|
20
20
|
* canonical result. Callers MUST NOT proceed to revive/deliver when this
|
|
21
21
|
* throws. */
|
|
22
22
|
export declare function commitReopen(nodeId: string, expectedFinalReport: string): void;
|
|
23
|
-
/** Commit the wider message-delivery reopen:
|
|
23
|
+
/** Commit the wider message-delivery reopen: settle the node's lifecycle, then
|
|
24
24
|
* retract a final latch when one exists. Unlike the revive/fresh gate, an
|
|
25
|
-
* unlatched node is valid here
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
25
|
+
* unlatched node is valid here.
|
|
26
|
+
*
|
|
27
|
+
* Lifecycle rule: resident is for a node with no manager to report to (an app
|
|
28
|
+
* or person conversation, e.g. a hosted SMS thread reopened by a new text), so
|
|
29
|
+
* a reopen by a person, an app run, or a node that is not the target's manager
|
|
30
|
+
* makes the target resident. When `callerNodeId` is a node that is a tree
|
|
31
|
+
* subscriber of the target (its parent/manager re-tasking its own child), the
|
|
32
|
+
* target keeps its current lifecycle: a terminal child stays terminal and keeps
|
|
33
|
+
* owing a final report, which is what wakes the manager. Either way the final
|
|
34
|
+
* latch is cleared and a done/canceled/dead row returns to active.
|
|
35
|
+
*
|
|
36
|
+
* Writing lifecycle first leaves a failed latch CAS with the canonical-final
|
|
37
|
+
* pointer intact. Both writes finish before either delivery channel begins. */
|
|
38
|
+
export declare function commitReopenResident(nodeId: string, expectedFinalReport?: string, callerNodeId?: string | null): string | null;
|
|
29
39
|
/** Convenience wrapper for a doorway with NO intervening fallible step between
|
|
30
40
|
* the gate check and the reopen clear (`node lifecycle revive` has nothing
|
|
31
41
|
* fallible between the gate and `reviveNode`). Validates and, when `reopen`,
|
|
32
42
|
* clears in one call.
|
|
33
43
|
*
|
|
34
44
|
* - `reopen` false + latched → throws `node_finalized`.
|
|
35
|
-
* - `reopen` true + latched →
|
|
45
|
+
* - `reopen` true + latched → settles lifecycle per `commitReopenResident` (resident, unless `callerNodeId` is the target's manager),
|
|
46
|
+
* CAS-clears the latch (side effect) and returns.
|
|
36
47
|
* - `reopen` true + not latched → throws `not_finalized`.
|
|
37
48
|
* - `reopen` false + not latched → no-op (the common case). */
|
|
38
|
-
export declare function assertReopenGate(nodeId: string, reopen: boolean): void;
|
|
49
|
+
export declare function assertReopenGate(nodeId: string, reopen: boolean, callerNodeId?: string | null): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var l=Object.defineProperty;var n=(e,t)=>l(e,"name",{value:t,configurable:!0});import{clearFinalReportIfMatches as c,getFinalReport as f,getNode as u,updateNode as p}from"../canvas/index.js";import{subscribersOf as m}from"../canvas/tree.js";import{InputError as o}from"../io.js";import{transition as h}from"./lifecycle.js";import{noticeChangedWhileDone as d}from"./ledger.js";function s(e){return f(e)}n(s,"currentFinalReport");function w(e,t){throw new o({error:"node_finalized",message:`${e} already submitted its canonical final result (\`crtr canvas read ${e}/${t}\`) \u2014 reviving it now would hand it a mandate it can never finish, since a second \`push final\` always errors against an already-latched node.`,next:`Re-run this exact command with --reopen to clear ${e}'s spent latch and re-task it in place.`})}n(w,"throwNodeFinalized");function g(e){throw new o({error:"not_finalized",message:`${e} is not finalized \u2014 --reopen has nothing to clear.`,next:"Drop --reopen."})}n(g,"throwNotFinalized");function R(e){const t=s(e);t!==null&&w(e,t)}n(R,"assertNotFinalized");function x(e){const t=s(e);return t===null&&g(e),t}n(x,"assertFinalizedForReopen");function F(e,t){if(!c(e,t))throw new o({error:"reopen_latch_changed",message:`${e}'s finalization latch changed since it was last inspected \u2014 a concurrent --reopen (or a fresh push final) already moved it, so clearing it now would risk erasing a newer canonical result.`,next:"Re-run this exact command \u2014 it will re-inspect the current state before acting."})}n(F,"commitReopen");function _(e,t){return e==null?!1:m(t).some(i=>i.node_id===e)}n(_,"isManagerOf");function z(e,t,i){const a=s(e);_(i,e)||p(e,{lifecycle:"resident"});const r=u(e);return r!==null&&(r.status==="done"||r.status==="canceled"||r.status==="dead")&&(r.status==="done"&&r.last_settled_at!=null&&d(e,r.last_settled_at),h(e,"revive")),a!==null&&F(e,t??a),a}n(z,"commitReopenResident");function D(e,t,i){if(t){const a=x(e);z(e,a,i);return}R(e)}n(D,"assertReopenGate");export{x as assertFinalizedForReopen,R as assertNotFinalized,D as assertReopenGate,F as commitReopen,z as commitReopenResident};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)[0].slice(0,120)}u(
|
|
1
|
+
var ce=Object.defineProperty;var u=(t,e)=>ce(t,"name",{value:e,configurable:!0});import{randomBytes as ue}from"node:crypto";import{claimNodeMail as le,acknowledgeNodeMail as fe}from"../../../core/canvas/deliveries.js";import{nodeMailEntry as me}from"../../../core/feed/inbox.js";import{enqueueNodeMail as L,cancelPendingNodeMail as pe}from"../../../core/feed/messages.js";import{reviveNode as U}from"../../../core/runtime/revive.js";import{BrokerUnreachableError as D}from"../../../core/runtime/broker-request.js";import{isBrokerLive as R}from"../../../core/runtime/model-swap.js";import{deliverLive as ye,interruptLive as he}from"../../../core/runtime/interactive-deliver.js";import{isProfilePaused as be}from"../../../core/profiles/manifest.js";import{getNode as O,withFreshTerminalGuard as we}from"../../../core/canvas/canvas.js";import{getById as G,nodeObject as F,ownerForGrantee as ge}from"../../../core/graph/objects.js";import{recordActedOn as ve}from"../../../core/graph/edges.js";import{recordWatch as H}from"../../../core/graph/watches.js";import{publish as V}from"../../../core/graph/events.js";import{readerFor as K}from"../reader.js";import{canRead as _e}from"../../../core/graph/access.js";import{withCanvasWrite as k}from"../../../core/canvas/db.js";import{getPendingStructuredRequestForNode as Q}from"../../../core/canvas/structured-requests.js";import{cancelCronsOnWake as X}from"../../../core/canvas/crons.js";import{assertNotFinalized as M,assertFinalizedForReopen as ke,commitReopenResident as P}from"../../../core/runtime/reopen.js";import{admitLegacyStructuredOutput as Se}from"../../structured-requests/sidecar.js";import{assertOutputSchemaAccepted as $e}from"../../../core/runtime/structured-output.js";import{appendSituationalContext as E,formatSituationalProse as Ne}from"../../../core/runtime/situational-context.js";import{formatDataCard as Y}from"../../../shared/generated-context.js";import{hasNoNaturalCycle as je}from"../../../core/runtime/revive-all.js";import{recordPendingParkActivity as Ae}from"../../park-pending.js";import{wantsExecution as qe}from"../../reconcilers/node-lifecycle/wants-execution.js";import{hasFreshGroundState as Oe}from"../../../core/runtime/revive.js";import{CrtrError as Ce,notFound as C,usage as c}from"../../../core/errors.js";import{emitEvent as xe}from"../../../core/events/emit.js";import{assertScope as Re}from"../../../core/scopes.js";import{InputError as I}from"../../../core/io.js";import{ApiError as Fe}from"../../../api/index.js";import{describeObject as Me,stubOf as Pe}from"../../../core/peer/stubs.js";import{machineNameOf as Z}from"../../../core/peer/names.js";import{callerNode as Ee,callerStub as Ie,forwardVerb as ee,relayError as te,remoteStub as Te,unknownError as re,withMachine as ne}from"../../peer/forward.js";import{ownerOnly as Be}from"../router.js";const ze=["critical","urgent","normal","deferred"];function oe(t,e){if(typeof t!="object"||t===null||Array.isArray(t))throw c(`${e} must be an object { kind, facts?, body? }`);const i=t,n=i.kind;if(typeof n!="string"||n.trim()==="")throw c(`${e}.kind is required`);if(!n.includes(":"))throw c(`${e}.kind must be namespaced (contain ':'): ${n}`);const r={kind:n},o=i.facts;if(o!==void 0){if(typeof o!="object"||o===null||Array.isArray(o))throw c(`${e}.facts must be an object of string or number values`);const d={};for(const[l,m]of Object.entries(o)){if(typeof m!="string"&&typeof m!="number")throw c(`${e}.facts.${l} must be a string or number`);d[l]=m}r.facts=d}const s=i.body;if(s!==void 0){if(typeof s!="string")throw c(`${e}.body must be a string`);r.body=s}return r}u(oe,"parseCardRequest");function ie(t,e){try{return Y(t.kind,t.facts??{},t.body??"")}catch(i){throw c(`${e}: ${i instanceof Error?i.message:String(i)}`)}}u(ie,"renderCard");function We(t){if(typeof t!="object"||t===null)throw c("a message requires a JSON body { body, tier?, fresh? }");const e=t,i=e.body,n=e.fresh===!0,r=e.tier,o=e.situational_context,s=e.output_schema,d=typeof i=="string"&&i.trim()!=="",l=typeof o=="string"&&o.trim()!=="",m=typeof s=="string"&&s.trim()!=="",v=e.situational_card,S=e.context_cards,w=v===void 0?void 0:oe(v,"situational_card");if(S!==void 0&&!Array.isArray(S))throw c("context_cards must be an array of { kind, facts?, body? }");const p=S?.map((N,$)=>oe(N,`context_cards[${$}]`));if(w!==void 0&&typeof o=="string"&&o!=="")throw c("situational_card and situational_context are mutually exclusive");const b=w!==void 0||p!==void 0&&p.length>0;if(!n&&!d&&!l&&!m&&!b)throw c("a message requires a non-empty body (or fresh: true, situational_context, a runtime card, or output_schema)");if(n&&p!==void 0&&p.length>0)throw c("context_cards cannot ride a fresh revive (no inbox entry carries them)");if(r!==void 0&&!ze.includes(r))throw c(`invalid tier: ${String(r)} (expected critical|urgent|normal|deferred)`);const f={body:typeof i=="string"?i:""};r!==void 0&&(f.tier=r),n&&(f.fresh=!0),e.reopen===!0&&(f.reopen=!0),typeof o=="string"&&(f.situational_context=o),w!==void 0&&(f.situational_card=w),p!==void 0&&(f.context_cards=p),typeof s=="string"&&(f.output_schema=s),typeof e.from=="string"&&(f.from=e.from);const y=e.delivery;if(y!==void 0&&y!=="interactive")throw c(`invalid delivery: ${String(y)} (expected interactive)`);if(y==="interactive"){if(n||f.situational_context!==void 0||f.output_schema!==void 0)throw c("interactive delivery supports a body, runtime cards, and reopen only (no fresh, situational_context, or output_schema)");if(!d)throw c("interactive delivery requires a non-empty body");if(f.tier==="deferred")throw c("interactive delivery cannot be deferred");f.delivery="interactive"}return f}u(We,"parseSendBody");function Je(t){let e;try{e=JSON.parse(t)}catch{throw c("output_schema must be a JSON schema string")}if(typeof e!="object"||e===null||Array.isArray(e))throw c("output_schema must be a JSON schema object");return e}u(Je,"parseSchema");function Le(t){return t.split(`
|
|
2
|
+
`)[0].slice(0,120)}u(Le,"label");function ae(t,e){return e===null?"":Y("reopen-final-report",{report:`${t}/${e}`},"This node previously completed work. Read this final report before acting on the new instruction.")}u(ae,"previousFinalReportCard");function Ue(t,e,i){if(i!==null&&i!=="human"&&G(t,i)?.type==="node"){const o=F(t,i);return{actor:o,senderNode:o}}if(i==="human")return{actor:ge(t,"user:local"),senderNode:null};const n=K(t,e);return{actor:n.class==="person"?n.person:n.class==="app"?n.app:n.node,senderNode:n.class==="node"?n.node:null}}u(Ue,"messageActor");function se(t,e,i,n,r,o){const s=F(t,i),{actor:d,senderNode:l}=Ue(t,e,n),m=r===null?{}:{pointer:r};ve(t,d,s),l!==null&&l.rowid!==s.rowid&&H(t,l,s,"message",{endsOn:o&&r!==null?`result:${r}`:"final"}),V(t,{event:"messaged",object:s,actor:d,attrs:{},...m}),l!==null&&V(t,{event:"sent",object:l,actor:l,attrs:{to:s.id},...m})}u(se,"recordMessage");function De(t,e){return new I({error:"deferred_no_natural_cycle",message:`${t} is ${e} with no natural next cycle ahead of it \u2014 a deferred message would never be delivered.`,next:"Use --tier normal, urgent, or critical to wake it now, or --reopen (with a body or --fresh) to re-task it first."})}u(De,"deferredNoNaturalCycle");function Ge(t){if(!Oe(t))throw new Fe(422,"unprocessable",`node ${t} has no goal/roadmap on disk \u2014 a fresh revive would resume amnesiac.`)}u(Ge,"assertRecoverableForFresh");function He(t,e){return t instanceof I?{error:t.payload.error,message:t.payload.message,next:t.payload.next,message_id:e}:t instanceof Ce?{error:t.code,message:t.message,message_id:e}:{error:"wake_failed",message:"the target could not be woken; see the daemon log",message_id:e}}u(He,"wakeFailure");async function Ve(t,e){const i=typeof t.body=="object"&&t.body!==null&&!Array.isArray(t.body)?{...t.body}:t.body;typeof i=="object"&&i!==null&&delete i.from;const n=await ee(t,e,{method:"POST",path:`/v1/nodes/${encodeURIComponent(e)}/messages`,body:i});if(n.kind==="local")return null;if(n.kind==="unknown")throw re(e,n.unreachable);if(n.status>=400)throw te(n.status,n.body);return k(r=>{const o=Ee(r,t);if(o===null)return;const s=n.body?.object,d=Te(r,e,"node",n.machine.id,s);H(r,o,d,"message",{endsOn:"final"})}),{status:n.status,body:ne(n.body,n.machine)}}u(Ve,"forwardMessage");async function Ke(t){const e=await Qe(t);if(t.principal.class!=="forwarded"||typeof e.body!="object"||e.body===null)return e;const i=k(n=>{const r=F(n,t.params.id);return _e(n,K(n,t),r)?Me(n,r):{id:r.id,type:r.type,kind:null,name:r.id,owner:null}});return{...e,body:{...e.body,object:i}}}u(Ke,"handleMessage");async function Qe(t){const e=t.params.id,i=await Ve(t,e);if(i!==null)return i;const n=O(e);if(n===null)throw C(`unknown node: ${e}`,{received:e});const r=We(t.body),o=r.tier??"normal",s=be(n.profile_id),d=t.principal.class==="forwarded"?k(a=>Ie(a,t)?.id??null):r.from??null;if(d!==null){const a=O(d);a!==null&&Re(a,"crtr:act")}const l=d===null?null:k(a=>{const h=G(a,d);return h===null?null:Pe(a,h)?.home_machine??null}),m=l===null?void 0:`${d} on ${Z(l)}`,v=t.principal.class==="broker"?t.principal.node:null,S=l===null?{}:{from_machine:Z(l)},w=r.body.trim()!=="",p=r.situational_card!==void 0?ie(r.situational_card,"situational_card"):r.situational_context!==void 0?Ne(r.situational_context.trim()):"",b=p!=="",f=(r.context_cards??[]).map((a,h)=>ie(a,`context_cards[${h}]`)),y=r.output_schema!==void 0&&r.output_schema.trim()!==""?Je(r.output_schema):null;y!==null&&await $e(y);let N=!1;const $=u(a=>{if(!(y===null||N)){if(Q(e)!==null)throw new I({error:"structured_request_conflict",message:`node ${e} already has a pending structured request`,next:"Wait for the current request to settle before requesting another structured result."});Se(e,"oneoff",y,a),N=!0}},"ensureSchema");if(r.fresh){const a=r.reopen?ke(e):null;r.reopen||M(e),Ge(e),$(),b&&E(e,p),r.reopen&&P(e,a,v);const h=await U(e,{resume:!1,capacity:"refuse"});return{status:201,body:{node_id:e,delivered:!1,revived:h.launch!==void 0}}}r.delivery==="interactive"&&!r.reopen&&M(e);let T=!1,_="";if(r.delivery==="interactive"&&r.reopen&&(_=ae(e,P(e,void 0,v)),T=!0),r.delivery==="interactive"&&$(),r.delivery==="interactive"&&!s&&!n.run_id&&R(n))try{return b&&E(e,p),await ye(e,r.body,{cards:[..._===""?[]:[_],...b?[p]:[],...f],origin:d==="human"?{type:"human"}:{type:"runtime",kind:"inbox",entries:[{...d===null?{}:{from:d},...m===void 0?{}:{fromName:m},kind:"message",tier:o}]}}),k(h=>se(h,t,e,d,null,!1)),X(e),{status:201,body:{node_id:e,delivered:!0,revived:!1,delivered_via:"engine"}}}catch(a){if(!(a instanceof D))throw a}const B=u(()=>{r.reopen||M(e);const a=ue(16).toString("hex"),h=u(q=>se(q,t,e,d,a,y!==null),"withinEnqueue");$(a),b&&E(e,p),r.reopen&&!T&&(_=ae(e,P(e,void 0,v)));const x=[..._===""?[]:[_],...f],J=x.length>0?{cards:x}:{};if(w||y!==null){const q=w?r.body:`A one-off structured-output request has been granted. Submit a result matching the requested schema with the \`submit_result\` tool (\`request_id: "${Q(e)?.request_id??"<request-id>"}"\`) before you stop; after it is accepted, continue normally.`;return L(e,{senderNodeId:d,...m===void 0?{}:{senderName:m},urgency:o,wake:1,kind:"message",messageId:a,withinEnqueue:h,label:Le(q),bodyJson:{body:q,...S,...b?{situational:!0}:{},...J}})}return L(e,{senderNodeId:d,...m===void 0?{}:{senderName:m},urgency:o,wake:1,kind:"message",messageId:a,withinEnqueue:h,label:b?"(ambient context updated)":"(context delivered)",bodyJson:{...b?{situational:!0}:{},situationalOnly:!0,...J}})},"deliverImmediate"),g=u(()=>{if(o==="deferred"){const a=we(e,je,B);if(a.kind==="missing")throw C(`unknown node: ${e}`,{received:e});if(a.kind==="terminal")throw De(e,a.status);return a.result}return B()},"deliver")();g.urgency!=="deferred"&&k(()=>Ae(e));const j=O(e);if(j===null)throw C(`unknown node: ${e}`,{received:e});n.frozen_at!==null&&X(e);let z=!1,W=!1,A;if((r.reopen||j.lifecycle==="terminal"||!qe(j))&&g.urgency!=="deferred"&&!s&&!R(j))try{const a=await U(e,{resume:!0,capacity:"freeze"});z=a.launch!==void 0,W=a.outcome==="frozen"}catch(a){A=He(a,g.messageId),xe({level:"error",event:"message.wake_failed",node_id:e,error:a,fields:{message_id:g.messageId,error_code:A.error}})}return{status:201,body:{node_id:e,delivered:!0,revived:z,...W?{not_revived_reason:"capacity_frozen"}:{},...A===void 0?{}:{wake_failed:A},delivered_at:g.createdAt,...g.urgency===o?{}:{delivered_tier:g.urgency},...r.delivery==="interactive"?{delivered_via:"inbox"}:{}}}}u(Qe,"handleMessageHere");async function Xe(t){const e=t.params.id,i=await ee(t,e,{method:"POST",path:`/v1/nodes/${encodeURIComponent(e)}/interrupt`,body:t.body});if(i.kind==="unknown")throw re(e,i.unreachable);if(i.kind==="remote"){if(i.status>=400)throw te(i.status,i.body);return{status:i.status,body:ne(i.body,i.machine)}}const n=O(e);if(n===null)throw C(`unknown node: ${e}`,{received:e});const r=pe(e,{senderNodeId:"human",kind:"message"});let o=!1;if(R(n))try{o=await he(e)!=="idle"}catch(d){if(!(d instanceof D))throw d}return{status:200,body:{node_id:e,outcome:o?"aborted_turn":r.length>0?"canceled_pending":"idle",aborted_turn:o,canceled_pending:r.length}}}u(Xe,"handleInterrupt");function de(t){if(typeof t.body!="object"||t.body===null||Array.isArray(t.body))throw c("mail requires an object body");const e=t.body;if(typeof e.execution_id!="string"||e.execution_id.trim()==="")throw c("execution_id is required");return e}u(de,"mailBody");function Ye(t){const e=de(t),i=e.channel;if(i!=="wake"&&i!=="passive"&&i!=="deferred_run")throw c("channel must be wake, passive, or deferred_run");const n=le(t.params.id,e.execution_id,i);return{status:200,body:{action:n.action,deliveries:n.deliveries.map(o=>({delivery_id:o.deliveryId,attempts:o.attempts,handoff:o.handoff,entry:{...me(o),seq:o.message.seq}}))}}}u(Ye,"handleMailClaim");function Ze(t){const e=de(t);if(!Array.isArray(e.deliveries))throw c("deliveries must be an array");const i=e.deliveries.map(o=>{if(typeof o!="object"||o===null||Array.isArray(o))throw c("each delivery must be an object");const s=o;if(typeof s.delivery_id!="string"||s.delivery_id==="")throw c("delivery_id is required");if(!Number.isSafeInteger(s.attempts)||s.attempts<1)throw c("attempts must be a positive integer");if(s.disposition!=="acknowledged"&&s.disposition!=="released")throw c("disposition must be acknowledged or released");return{delivery_id:s.delivery_id,attempts:s.attempts,disposition:s.disposition}}),n=fe(t.params.id,e.execution_id,i.map(o=>({deliveryId:o.delivery_id,attempts:o.attempts,disposition:o.disposition})));return{status:200,body:{action:n.action,results:n.results.map(o=>({delivery_id:o.deliveryId,outcome:o.outcome}))}}}u(Ze,"handleMailAcknowledge");const Wt=Be([{method:"POST",pattern:"/v1/nodes/:id/mail/claim",handler:Ye},{method:"POST",pattern:"/v1/nodes/:id/mail/acknowledge",handler:Ze}]),Jt=[{method:"POST",pattern:"/v1/nodes/:id/messages",scope:"crtr:act",handler:Ke},{method:"POST",pattern:"/v1/nodes/:id/interrupt",scope:"crtr:act",handler:Xe}];export{Wt as brokerMailRoutes,Ke as handleMessage,Jt as messageRoutes};
|