@north-light/cli 0.3.414 → 0.3.416
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/client.d.ts +20 -0
- package/dist/api/client.js +3 -3
- package/dist/api/dto/common.d.ts +1 -1
- package/dist/api/dto/crons.d.ts +7 -1
- package/dist/api/dto/human-requests.d.ts +1 -1
- package/dist/api/dto/run-events.d.ts +1 -1
- package/dist/api/dto/usage.d.ts +27 -0
- package/dist/api/dto/usage.js +0 -0
- package/dist/api/errors.js +1 -1
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.js +1 -1
- package/dist/builtin-memory/crouter-sdk/README.md +22 -4
- package/dist/builtin-memory/crouter-sdk/client.md +32 -1
- package/dist/builtin-memory/crouter-sdk/errors.md +3 -0
- package/dist/builtin-memory/crouter-sdk/getting-started.md +26 -4
- package/dist/builtin-memory/crouter-sdk/release-notes.md +23 -0
- package/dist/builtin-memory/crouter-sdk/testing.md +9 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -0
- package/dist/cli.js +7 -7
- package/dist/clients/attach/viewer.js +606 -606
- package/dist/commands/cron.js +1 -1
- package/dist/commands/node/create.js +1 -1
- package/dist/core/canvas/crons.d.ts +15 -1
- package/dist/core/canvas/crons.js +13 -13
- package/dist/core/canvas/delivery-contract.d.ts +1 -1
- package/dist/core/canvas/migrations.js +73 -66
- package/dist/core/canvas/types.d.ts +1 -1
- package/dist/core/command.d.ts +7 -0
- package/dist/core/command.js +9 -9
- package/dist/core/grants/mirror.d.ts +4 -0
- package/dist/core/grants/mirror.js +2 -2
- package/dist/core/grants/on-sync.js +1 -1
- package/dist/core/graph/custom.js +5 -5
- package/dist/core/graph/reader.js +3 -3
- package/dist/core/graph/spaces/app.js +1 -1
- package/dist/core/graph/types.d.ts +5 -0
- package/dist/core/layout.d.ts +2 -0
- package/dist/core/layout.js +2 -2
- package/dist/core/llm/run.d.ts +5 -1
- package/dist/core/llm/run.js +3 -3
- package/dist/core/providers/call.d.ts +2 -0
- package/dist/core/providers/call.js +1 -1
- package/dist/core/providers/errors.js +1 -1
- package/dist/core/providers/receipts.d.ts +2 -0
- package/dist/core/providers/receipts.js +2 -3
- package/dist/core/runs/confinement.d.ts +2 -0
- package/dist/core/runs/confinement.js +2 -2
- package/dist/core/runs/operations.d.ts +2 -0
- package/dist/core/runs/operations.js +4 -4
- package/dist/core/runtime/broker/model-adapter.js +2 -2
- package/dist/core/runtime/run-command.d.ts +10 -0
- package/dist/core/runtime/run-command.js +1 -1
- package/dist/core/spaces/mounts.d.ts +7 -0
- package/dist/core/spaces/mounts.js +1 -1
- package/dist/core/spaces/process.d.ts +22 -2
- package/dist/core/spaces/process.js +1 -1
- package/dist/core/usage/ledger.d.ts +49 -0
- package/dist/core/usage/ledger.js +10 -0
- package/dist/daemon/api/app-listener.js +12 -12
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/llm.d.ts +8 -0
- package/dist/daemon/api/handlers/llm.js +3 -3
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/map.d.ts +1 -1
- package/dist/daemon/api/map.js +1 -1
- package/dist/daemon/api/operations.d.ts +1 -1
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +15 -0
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.d.ts +5 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/cron/armed-context.d.ts +21 -4
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/authority.d.ts +27 -0
- package/dist/daemon/cron/authority.js +1 -0
- package/dist/daemon/cron/disposition.d.ts +2 -0
- package/dist/daemon/cron/disposition.js +1 -1
- package/dist/daemon/cron/escalation.js +3 -3
- package/dist/daemon/cron/folders.d.ts +5 -0
- package/dist/daemon/cron/folders.js +1 -0
- package/dist/daemon/cron/lease-recovery.js +1 -1
- package/dist/daemon/cron/live-runs.d.ts +28 -0
- package/dist/daemon/cron/live-runs.js +1 -1
- package/dist/daemon/cron/run-socket.d.ts +30 -0
- package/dist/daemon/cron/run-socket.js +1 -0
- package/dist/daemon/cron/settle.d.ts +10 -0
- package/dist/daemon/cron/settle.js +1 -1
- package/dist/daemon/cron/sinks.d.ts +2 -1
- package/dist/daemon/cron/sinks.js +2 -2
- package/dist/daemon/cron-run.js +2 -1
- package/dist/daemon/crtrd.js +7 -7
- package/dist/daemon/reconcilers/action-lane.js +1 -1
- package/dist/daemon/reconcilers/broker-supervision.d.ts +10 -0
- package/dist/daemon/reconcilers/broker-supervision.js +4 -4
- package/dist/daemon/reconcilers/cron-lane.d.ts +1 -0
- package/dist/daemon/reconcilers/cron-lane.js +1 -1
- package/dist/shared/env.d.ts +6 -5
- package/dist/shared/env.js +2 -2
- package/docs/sdk/README.md +22 -4
- package/docs/sdk/client.md +32 -1
- package/docs/sdk/errors.md +3 -0
- package/docs/sdk/getting-started.md +26 -4
- package/docs/sdk/release-notes.md +23 -0
- package/docs/sdk/testing.md +9 -3
- package/package.json +2 -2
- package/packages/crouter-identity/dist/index.d.ts +29 -2
- package/packages/crouter-identity/dist/index.js +81 -12
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -18,7 +18,7 @@ export type Lifecycle = 'terminal' | 'resident';
|
|
|
18
18
|
export type Mode = 'base' | 'orchestrator';
|
|
19
19
|
/** Why a node last stopped — drives the daemon's reap-vs-revive decision. */
|
|
20
20
|
export type ExitIntent = 'done' | 'refresh' | 'idle-release' | 'parked' | null;
|
|
21
|
-
export type TerminalReason = 'finalized' | 'finished' | 'parked' | 'closed' | 'retired' | 'crashed' | 'stranded' | 'boot_failed' | 'crash_looped' | 'launch_failed' | 'context_overflow' | 'deadline_exceeded' | 'provider_fatal' | 'declined' | 'out_of_memory';
|
|
21
|
+
export type TerminalReason = 'finalized' | 'finished' | 'parked' | 'closed' | 'retired' | 'crashed' | 'stranded' | 'boot_failed' | 'crash_looped' | 'launch_failed' | 'context_overflow' | 'deadline_exceeded' | 'provider_fatal' | 'declined' | 'out_of_memory' | 'cap_exhausted';
|
|
22
22
|
export type NodeOutcomeKind = 'result' | 'failure';
|
|
23
23
|
/** Maximum serialized opaque action payload; enforced at registration in Phase 5. */
|
|
24
24
|
export declare const OUTCOME_PAYLOAD_MAX_BYTES: number;
|
package/dist/core/command.d.ts
CHANGED
|
@@ -180,6 +180,13 @@ export declare function renderNode(node: AnyNode): string;
|
|
|
180
180
|
* the full path to the matched node (not just its local name), so the recovery
|
|
181
181
|
* hint is a command that actually exists. No fuzzy matching. */
|
|
182
182
|
export declare function unknownPathError(node: AnyNode, path: string[], bad: string): CrtrError;
|
|
183
|
+
/** crouter cloud's account site: where a person creates a join token. */
|
|
184
|
+
export declare const DEFAULT_DIRECTORY_ISSUER = "https://account.northlight.ai";
|
|
185
|
+
/** An unknown top-level command on a runtime never joined to a directory
|
|
186
|
+
* (no `runtime.issuer`). Every `cloud` leaf and every other provider command
|
|
187
|
+
* is installed by grant sync, which runs only on a joined runtime, so the
|
|
188
|
+
* likeliest cause of a missing command there is the missing join. */
|
|
189
|
+
export declare function notJoinedError(bad: string): CrtrError;
|
|
183
190
|
/** Options for parseArgv. */
|
|
184
191
|
export interface ParseArgvOptions {
|
|
185
192
|
/** Optional collector for tracking which parameters were explicitly provided.
|
package/dist/core/command.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var
|
|
1
|
+
var M=Object.defineProperty;var d=(e,i)=>M(e,"name",{value:i,configurable:!0});import{renderRoot as V,renderBranch as q,renderLeafArgv as O,withAsyncHelpState as z,collapseWideChildren as G,rootPromotedChildren as I}from"./help.js";import{beginPreview as L,publishPreviewResult as K,readStdinRaw as B,peekStdinRaw as D,emit as Y,handle as Q,setJsonOutput as X,isJsonOutput as Z}from"./io.js";import{renderResult as ee}from"./render.js";import{CrtrError as H}from"./errors.js";import{isManifestStaleError as ne}from"./manifest-stale.js";import{operationIdContext as te}from"./events/operation-id.js";import{ExitCode as $}from"../types.js";import{envNodeId as ie}from"../shared/env.js";import{readFileSync as re}from"node:fs";import{spawnSync as oe}from"node:child_process";import{constants as ae}from"node:os";import{isatty as se}from"node:tty";import{resolve as le}from"node:path";function W(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))W(i);return Object.freeze(e)}d(W,"freezeCommandMetadata");function Fe(e){const i=W(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>z(i,r,n)),slash:e.slash===void 0?void 0:W(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}d(Fe,"defineLeaf");function de(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}d(de,"visibleSubCount");function Ve(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=de(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:i.tier??"normal",kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:e.children}}d(Ve,"defineBranch");function qe(e){const i=[],r=d(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const o of n.children)r(o)},"visit");for(const n of e.subtrees)r(n);return i}d(qe,"collectSlashSpecs");function Ie(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0).map(n=>{const o=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),f=I(o).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:f.length>0?f:void 0,otherSubcommandCount:o.length-f.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}d(Ie,"defineRoot");function fe(e){if(e.kind==="root")return e.subtrees.filter(i=>i.tier!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=G(e.children.filter(n=>n.tier!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}d(fe,"childNames");function ce(e,i){let r=e;const n=[];let o=0;for(;o<i.length;){const f=i[o];if(f==="-h"||f==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(a=>a.name===f);if(s===void 0)break;r=s,n.push(s.name),o++}else if(r.kind==="branch"){const s=r.children.find(a=>a.name===f);if(s===void 0)break;r=s,n.push(s.name),o++}else break}return{node:r,path:n,remaining:i.slice(o)}}d(ce,"walk");function ue(e){return e.kind==="root"?V(e.help):e.kind==="branch"?q(e.help):O(e.help)}d(ue,"renderNode");async function J(e,i,r){if(r!==void 0&&e.kind==="leaf")return O(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?O(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?q(await e.effectiveHelp()):ue(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:o}=await import("./command-plugins/presence.js");if(!o())return n;const[{collectHelpAddenda:f},{SUBTREE_NAMES:s,coreCommandPathsCovering:a,extensibleCoreBranches:x}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),w=await f(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:a,extensibleCoreBranches:x});return n+w.map(S=>`
|
|
2
2
|
|
|
3
|
-
<plugin-help plugin="${
|
|
4
|
-
${
|
|
5
|
-
</plugin-help>`).join("")}
|
|
6
|
-
`),process.exitCode
|
|
7
|
-
`),process.exitCode
|
|
8
|
-
`),process.exitCode
|
|
9
|
-
`),process.exitCode
|
|
10
|
-
`))}}catch(
|
|
3
|
+
<plugin-help plugin="${S.plugin}">
|
|
4
|
+
${S.text}
|
|
5
|
+
</plugin-help>`).join("")}d(J,"renderNodeWithAddenda");function he(e){return e.some(i=>i==="-h"||i==="--help")}d(he,"helpRequested");function pe(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const o=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===o)?.focusedHelp!==void 0?o:void 0}d(pe,"focusedHelpFlag");function me(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}d(me,"rawArgsAfterPath");function ge(e,i){const r=oe(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new H("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,$.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=ae.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}d(ge,"runPassthrough");function we(e,i,r){const n=fe(e),o=n.length>0?n.join(", "):"(none)",f=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(a=>a.startsWith("(and "));return new H("unknown_path",`unknown subcommand: ${r}`,$.USAGE,{received:r,next:s?`Valid children include: ${o}. Run \`${f}\` for how to find the rest.`:`Valid children: ${o}. Run \`${f}\` for the full list.`})}d(we,"unknownPathError");const be="https://account.northlight.ai";function ve(e){const i=be,r=`open ${i}/account/machines, choose Create join token, then run \`crtr sys join --directory ${i} --token <token>\``;return new H("not_joined",`\`crtr ${e}\` isn't a command on this machine. If it comes from crouter cloud, this machine isn't joined to your account yet: ${r}.`,$.USAGE,{received:e,next:`${r[0].toUpperCase()}${r.slice(1)}.`})}d(ve,"notJoinedError");async function xe(){try{const{readConfig:e}=await import("./config.js");return!e("user").runtime?.issuer}catch{return!1}}d(xe,"runtimeUnjoined");function m(e,i,r,n,o){return new H(e,i,$.USAGE,{received:r,field:n,next:o!==void 0?o:"Run the command with -h to see the parameter schema."})}d(m,"parseArgvError");function v(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}d(v,"flagNameToKey");async function ke(e,i,r){const n={},o=new Set,f=new Map,s=e.filter(t=>t.kind==="positional"),a=e.find(t=>t.kind==="stdin"),x=e.find(t=>t.kind==="context-file"),w=e.filter(t=>t.kind==="flag"),S=new Map(w.map(t=>[t.name,t])),y=w.some(t=>t.sensitive===!0);for(const t of w)t.type==="bool"?n[v(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[v(t.name)]=t.default);const g=[];let c=0,k=!1,A=!1,b=0;for(;b<i.length;){const t=i[b];if(t==="--"&&!A){A=!0,b++;continue}if(!A&&t.startsWith("--")){const h=t.indexOf("=");let l,C;if(h!==-1?(l=t.slice(2,h),C=t.slice(h+1)):l=t.slice(2),l==="context-file"){if(x===void 0)throw m("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const p=C!==void 0?C:i[++b];if(p===void 0)throw m("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let R;try{R=re(p,"utf8")}catch{throw m("invalid_type",`--context-file: cannot read file: ${p}`,p,"context-file","Provide a readable file path.")}let F;try{F=JSON.parse(R)}catch{throw m("invalid_type",`--context-file: file is not valid JSON: ${p}`,p,"context-file","Ensure the file contains a valid JSON object.")}const T=v(x.name);n[T]=F,f.set(T,le(p)),o.add(T),b++;continue}const E=S.get(l);if(E===void 0)throw m("unknown_flag",`unknown flag: --${l}`,`--${l}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const U=v(l);if(o.add(U),E.type==="bool"){if(C!==void 0)throw m("invalid_type",`boolean flag --${l} takes no value`,`--${l}=${C}`,l,`Use --${l} (presence = true) with no value.`);n[U]=!0,b++;continue}const P=C!==void 0?C:i[++b];if(P===void 0||C===void 0&&P.startsWith("--")){const p=E.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${l} -h\` and read the focused value contract before retrying.`:void 0;throw m("missing_parameter",`--${l} requires a value`,`--${l}`,l,p)}let _;if(E.type==="int"){const p=Number(P);if(!Number.isInteger(p))throw m("invalid_type",`--${l} must be an integer`,P,l,`Provide an integer value for --${l}.`);_=p}else if(E.type==="enum"){const p=E.choices;if(p!==void 0&&!p.includes(P))throw m("invalid_type",`--${l} must be one of: ${p.join(", ")}`,P,l,`Retry with one of: ${p.join(", ")}.`);_=P}else _=P;if(E.repeatable===!0){const p=n[U],R=Array.isArray(p)?p:[];R.push(_),n[U]=R}else n[U]=_;b++;continue}const u=s[c];if(u!==void 0){const h=v(u.name);if(u.repeatable===!0){const l=n[h]??[];l.push(t),n[h]=l}else n[h]=t,c++;g.push(t),k=!0,o.add(h),b++;continue}if(g.length>0){if(a!==void 0&&s.length===0){g.push(t),k=!0,b++;continue}throw a!==void 0?m("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${a.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):m("bad_invocation",y?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,y?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(a===void 0||a.allowPositional===!1))throw m("bad_invocation",y?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,y?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");g.push(t),k=!0,b++}let N=!1;if(a!==void 0){if(g.length>1&&s.length===0){const t=a.positionalStdinConflict?.(n);throw t!==void 0?m("bad_invocation",t.message,void 0,t.field,t.next):m("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${a.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(g.length>0&&s.length===0&&a.allowPositional!==!1){const t=g[0];if(t==="-"){const u=await B();(u.trim()!==""||!a.required)&&(n[v(a.name)]=u),N=!0}else{if((await D()).trim()!==""){const h=a.positionalStdinConflict?.(n);throw m("bad_invocation",h?.message??`both a positional argument ("${t}") and piped stdin were provided for ${a.name}`,t,h?.field??a.name,h?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[v(a.name)]=t,N=!0}}else{const t=se(0),u=await B();(u.trim()!==""||!a.required)&&(n[v(a.name)]=u),N=!t||k}}N&&a!==void 0&&o.add(v(a.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const u=v(t.name);if(o.has(u))continue;const h=process.env[t.defaultFromEnv];h===void 0||h===""||(n[u]=t.kind==="positional"&&t.repeatable===!0?[h]:h,o.add(u))}const j=[];for(const t of e){const u=v(t.name),h=t.kind==="flag"&&t.type==="bool"&&!o.has(u);if(t.required&&(h||n[u]===void 0||n[u]===null)){const l=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";j.push({display:l,name:t.name})}}if(j.length>0){const t=j.map(l=>l.display).join(", "),u=r?.leafPath,h=u!==void 0&&u.length>0?`\`crtr ${u.join(" ")} -h\``:"this command with -h";throw m("missing_parameter",j.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,j.map(l=>l.name).join(", "),`Run ${h} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(o),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(f),n}d(ke,"parseArgv");const ye=new Set(["--json","--no-autostart"]);async function Be(e,i){const r=i.slice(2);r.includes("--json")&&X(!0);const n=r.filter(a=>!ye.has(a));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const a=ie()?{...e.help,commands:await Promise.all(e.help.commands.map(async x=>{const w=e.subtrees.find(c=>c.name===x.name);if(w?.effectiveHelp===void 0)return x;const y=((await w.effectiveHelp()).listing??[]).filter(c=>c.tier!=="hidden"),g=I(y).map(c=>({path:`${w.name} ${c.name}`,desc:c.tier==="important"?c.description:void 0}));return{...x,subcommands:g.length?g:void 0,otherSubcommandCount:y.length-g.length}}))}:e.help;process.stdout.write(V(a)+`
|
|
6
|
+
`),process.exitCode=$.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:a}=await import("../commands/sys/shared.js");process.stdout.write(a()+`
|
|
7
|
+
`),process.exitCode=$.SUCCESS;return}let{node:o,path:f,remaining:s}=ce(e,n);try{if(o.kind==="branch"&&o.passthrough!==void 0&&ge(o.passthrough,me(r,f)),o.kind==="branch"&&o.unavailable!==void 0&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw new H("provider_unavailable",o.unavailable,$.NETWORK,{next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."});if((o.kind==="root"||o.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw o.kind==="root"&&!s[0].startsWith("-")&&await xe()?ve(s[0]):we(o,f,s[0]);if(he(s)){process.stdout.write(await J(o,f,pe(o,s))+`
|
|
8
|
+
`),process.exitCode=$.SUCCESS;return}if(o.kind==="root"||o.kind==="branch"){process.stdout.write(await J(o,f)+`
|
|
9
|
+
`),process.exitCode=$.SUCCESS;return}const a=o;L(f.join(" "));const x=a.help.params!==void 0?a.help.params:[];let w=new Set,S=new Map;const y=await ke(x,s,{leafPath:f,onProvidedParams:d(k=>{w=k},"onProvidedParams"),onContextFilePaths:d(k=>{S=k},"onContextFilePaths")}),g={providedParams:w,contextFilePaths:S},c=await te.fresh(()=>a.run(y,g));if(K(c==null?c:a.preview?.(c)??c),c!=null)if(Z())Y(c);else{const k=a.render!==void 0?a.render(c):ee(c,a.help);process.stdout.write(k+(a.stdoutExact?.(c)?"":`
|
|
10
|
+
`))}}catch(a){if(ne(a))throw a;Q(a)}}d(Be,"runCli");export{be as DEFAULT_DIRECTORY_ISSUER,ye as GLOBAL_TOKENS,qe as collectSlashSpecs,Ve as defineBranch,Fe as defineLeaf,Ie as defineRoot,ve as notJoinedError,ke as parseArgv,ue as renderNode,Be as runCli,we as unknownPathError,ce as walk};
|
|
@@ -54,6 +54,8 @@ export interface GrantSyncState {
|
|
|
54
54
|
export declare function grantSyncState(grantId: string): GrantSyncState | null;
|
|
55
55
|
/** The mirrored state of the grant `sub` gave `grantee`, whatever its status, or null when the mirror lacks it. */
|
|
56
56
|
export declare function grantSyncStateFor(grantee: string, sub: string): GrantSyncState | null;
|
|
57
|
+
/** The mirrored grant `grantId`, whatever its status, or null when the mirror lacks it or the row is unparsable. */
|
|
58
|
+
export declare function grantRecord(grantId: string): GrantRecord | null;
|
|
57
59
|
export declare function activeGrant(grantId: string): ActiveGrant | null;
|
|
58
60
|
export declare function grantFor(grantee: string, sub: string): ActiveGrant | null;
|
|
59
61
|
export declare function activeGrantForApp(grantee: string): ActiveGrant | null;
|
|
@@ -64,6 +66,8 @@ export declare function runNoticeTarget(grantId: string): {
|
|
|
64
66
|
grantee: string;
|
|
65
67
|
} | null;
|
|
66
68
|
export declare function terminalGrant(): ActiveGrant | null;
|
|
69
|
+
/** Where the grant mirror came from: `'local'` for an unjoined runtime's own grant, the directory issuer once joined, null before any install. */
|
|
70
|
+
export declare function grantsSource(): string | null;
|
|
67
71
|
export declare function hasSyncedGrants(source: string): boolean;
|
|
68
72
|
/** OWN-11 for a joined runtime: the unjoined runtime's local mirror grants
|
|
69
73
|
* `user:local` full local scopes and is not the directory's. Delete it so no
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var g=Object.defineProperty;var r=(e,t)=>g(e,"name",{value:t,configurable:!0});import{createHash as _}from"node:crypto";import{prepared as
|
|
1
|
+
var g=Object.defineProperty;var r=(e,t)=>g(e,"name",{value:t,configurable:!0});import{createHash as _}from"node:crypto";import{prepared as n}from"../sqlite-statements.js";import{parseScope as y}from"@north-light/identity";import{openDb as i,withCanvasWrite as S}from"../canvas/db.js";const v=["not_started","pending","ready","failed"];function c(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(t=>{if(!t||typeof t!="object"||typeof t.scope!="string"||!["always","ask"].includes(t.mode))throw new TypeError("invalid grant scope entry");return{scope:t.scope,mode:t.mode}})}r(c,"entries");function T(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:t,message:a,next:u}=e;return typeof t!="string"||!v.includes(t)||a!==void 0&&typeof a!="string"||typeof u!="string"||!u?null:{state:t,...a===void 0?{}:{message:a},next:u}}r(T,"dependencyOf");function l(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const t={};for(const[a,u]of Object.entries(e)){const s=T(u);s&&(t[a]=s)}return t}r(l,"dependenciesOf");function E(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:c(JSON.parse(e.scopes_json)),resolved_scopes:c(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:l(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,synced_at:e.synced_at}}r(E,"record");function p(e){if(!e||e.status!=="active")return null;try{return m(E(e))}catch(t){if(t instanceof TypeError||t instanceof SyntaxError)return null;throw t}}r(p,"active");function h(){return n(i(),"SELECT * FROM grants").all().flatMap(e=>{try{return[E(e)]}catch(t){if(t instanceof TypeError||t instanceof SyntaxError)return[];throw t}})}r(h,"allGrants");function m(e){if(e.status!=="active")return null;try{const t=e.resolved_scopes.map(({scope:a})=>y(a));return{...e,resolved:e.resolved_scopes.flatMap(({mode:a},u)=>a==="always"?[t[u]]:[])}}catch(t){if(t instanceof TypeError||t instanceof SyntaxError)return null;throw t}}r(m,"activeRecord");function d(e){return Array.isArray(e)?`[${e.map(d).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(t=>e[t]!==void 0).map(t=>`${JSON.stringify(t)}:${d(e[t])}`).join(",")}}`:JSON.stringify(e)??"null"}r(d,"canonicalJson");function L(e){const t={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{}};return _("sha256").update(d(t)).digest("base64url").slice(0,22)}r(L,"grantVersion");function F(e){return!!n(i(),"SELECT 1 FROM grants WHERE id = ?").get(e)}r(F,"hasGrant");function C(e){return n(i(),"SELECT rev, status FROM grants WHERE id = ?").get(e)??null}r(C,"grantSyncState");function M(e,t){return n(i(),"SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,t)??null}r(M,"grantSyncStateFor");function b(e){const t=n(i(),"SELECT * FROM grants WHERE id = ?").get(e);if(!t)return null;try{return E(t)}catch(a){if(a instanceof TypeError||a instanceof SyntaxError)return null;throw a}}r(b,"grantRecord");function k(e){return p(n(i(),"SELECT * FROM grants WHERE id = ?").get(e))}r(k,"activeGrant");function H(e,t){return p(n(i(),"SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,t))}r(H,"grantFor");function W(e){const t=n(i(),"SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(t.length>1)throw new Error(`multiple active grants for ${e}`);return p(t[0])}r(W,"activeGrantForApp");function j(e){const t=n(i(),"SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return t&&t.url?{url:t.url,sub:t.sub,grantee:t.grantee}:null}r(j,"runNoticeTarget");function D(){const e=n(i(),"SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return p(e[0])}r(D,"terminalGrant");function G(){return n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value??null}r(G,"grantsSource");function I(e){return n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value===e}r(I,"hasSyncedGrants");function J(e){return n(e,"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value!=="local"?0:(n(e,"DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number(n(e,"DELETE FROM grants").run().changes))}r(J,"dropLocalGrantMirror");function U(e,t,a){const u=new Set;for(const s of e){if(u.has(s.id)||!s.id||!s.runtime||!s.sub||!s.grantee||!Number.isInteger(s.rev)||!["active","revoked","removed"].includes(s.status))throw new TypeError("invalid grant sync response");u.add(s.id),c(s.scopes),c(s.resolved_scopes)}S(s=>{s.exec("DELETE FROM grants");const f=n(s,`INSERT INTO grants
|
|
2
2
|
(id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
|
|
3
3
|
status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, synced_at)
|
|
4
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const
|
|
4
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const o of e)f.run(o.id,o.runtime,o.sub,o.grantee,o.rev,JSON.stringify(o.scopes),JSON.stringify(o.resolved_scopes),o.spend_cap,o.expires_at,o.status,o.revoked_at,o.approved_at,o.payer,JSON.stringify(l(o.dependencies)),o.run_notice_url??null,o.synced_at);n(s,"INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(a),n(s,"INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT)").run()})}r(U,"replaceGrants");function w(){const e=n(i(),"SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}r(w,"grantsGeneration");export{k as activeGrant,W as activeGrantForApp,m as activeRecord,h as allGrants,l as dependenciesOf,J as dropLocalGrantMirror,H as grantFor,b as grantRecord,C as grantSyncState,M as grantSyncStateFor,L as grantVersion,w as grantsGeneration,G as grantsSource,F as hasGrant,I as hasSyncedGrants,U as replaceGrants,j as runNoticeTarget,D as terminalGrant};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var w=Object.defineProperty;var c=(n,r)=>w(n,"name",{value:r,configurable:!0});import{prepared as v}from"../sqlite-statements.js";import{relative as b,sep as g}from"node:path";import{activeBackgroundBashJobs as x,readJobPgid as y}from"../bash-jobs.js";import{appSpace as S}from"../layout.js";import{getNode as l,recordSettledTurn as k}from"../canvas/index.js";import{transition as E}from"../runtime/lifecycle.js";import{openDb as _}from"../canvas/db.js";import{contextDir as R}from"../canvas/node-agent-paths.js";import{emitEvent as p}from"../events/emit.js";import{closeGrantStreams as C}from"../runs/events.js";import{grantedMounts as G,isGrantedSource as I,mountAllowed as P}from"../spaces/mounts.js";import{permissionsForRun as m}from"../spaces/permissions.js";import{stopNodeSandboxes as T}from"../spaces/stop.js";import{discardChangedGrantTokens as F}from"../providers/token-cache.js";import{activeGrantForApp as M,allGrants as O}from"./mirror.js";import{runRemovalCascades as A}from"./remove.js";import{notifyGrantChanges as D}from"./watch.js";import{launcherPresent as N,listSandboxes as W,stopSandboxPid as j}from"./sandboxes.js";import{checkCronFire as B}from"../../daemon/cron/authority.js";import{cronExecutionFor as J}from"../../daemon/cron/live-runs.js";import{cronExecutionsReady as L}from"../../daemon/cron/run-socket.js";function h(){return new Map(v(_(),"SELECT uid, app, status FROM app_users").all().map(n=>[n.uid,{app:n.app,status:n.status}]))}c(h,"appUserOwners");function U(n,r=h()){const o=r.get(n.uid),i=n.nodeId.startsWith("cron-"),e=i?J(n.nodeId.slice(5)):void 0;if(i&&(e===void 0||!o))return"permission_revoked";if(e!==void 0&&o.status==="active"){const s=B({grantee:e.grantee,sub:e.sub,grant_id:e.grantId,scopes:e.scopes});if(!s.allowed)return e.refusal??={reason:s.reason,message:s.message},"permission_revoked"}if(!o||o.app==="app:terminal")return null;if(o.status!=="active")return"app_removed";const t=l(n.nodeId);try{let s;if(e!==void 0)s=m(e.grantee,e.sub,e.grantId,e.scopes);else if(t&&t.grantee===o.app)s=m(o.app,t.sub??"user:local",t.grant_id??null,t.scopes??null);else{const d=M(o.app);if(!d)return"permission_revoked";s=m(o.app,d.sub,d.id,null)}const u=G(s),f=S(o.app).root;return n.mounts.every(d=>{const a=b(f,d.source);return a===""||a!==".."&&!a.startsWith(`..${g}`)&&!a.startsWith(g)||!I(o.app,d.source)||P(d,u)})?null:"permission_revoked"}catch{return"permission_revoked"}}c(U,"exceeds");async function $(n){const r=l(n);if(!r)return;(r.status==="active"||r.status==="idle")&&E(n,"release");const o=new Date().toISOString();k(n,o),r.run_id&&r.run_id!==n&&v(_(),"UPDATE nodes SET last_settled_at = COALESCE(last_settled_at, ?) WHERE node_id = ?").run(o,r.run_id)}c($,"afterRevocationStop");function H(n){const r=new Set,o=l(n);o?.pi_pid!=null&&r.add(o.pi_pid);try{const i=R(n,o?.grantee);for(const e of x(i,!0)){const t=y(i,e.jobId);t!==void 0&&r.add(t)}}catch{}return r}c(H,"nodePids");async function q(){if(!N())return{completion:Promise.resolve()};await L();const n=await W(),r=h(),o=n.flatMap(t=>{const s=U(t,r);return s?[{sandbox:t,reason:s}]:[]}),i=[],e=new Map;for(const t of o){p({level:"info",event:"daemon.grants.sandbox_stop",fields:{sandbox_node:t.sandbox.nodeId,pid:t.sandbox.pid,grantee:r.get(t.sandbox.uid).app,reason:t.reason}});const s=e.get(t.sandbox.nodeId)??[];s.push(t),e.set(t.sandbox.nodeId,s)}for(const[t,s]of e){const u=l(t),f=u?H(t):new Set,d=u!==null&&s.some(({sandbox:a})=>f.has(a.pid));i.push((async()=>{if(d){const a=await T(t,"permission_revoked",{holdRecovery:!0,after:c(()=>$(t),"after")});if(!a.confirmed)throw a.error??new Error(`stop of ${t} was not confirmed`)}for(const{sandbox:a}of s)(!d||!f.has(a.pid))&&await j(a.pid)})().catch(a=>{p({level:"error",event:"daemon.grants.sandbox_stop_failed",...u?{node_id:t}:{fields:{sandbox_node:t}},error:a})}))}return{completion:Promise.all(i).then(()=>{})}}c(q,"sweepSandboxes");function z(n,r){try{const{pairs:o,tokens:i}=F(n,r);o&&p({level:"info",event:"daemon.grants.outbound_tokens_discarded",fields:{grants:o,tokens:i}})}catch(o){p({level:"error",event:"daemon.grants.outbound_tokens_discard_failed",error:o})}}c(z,"discardOutboundTokens");async function _e(n,r=o=>{}){const o=O();try{D(n,o)}catch(e){p({level:"error",event:"daemon.grants.notify_failed",error:e})}z(n,o);for(const e of n){if(e.status!=="active"||o.find(u=>u.id===e.id)?.status==="active")continue;const s=C(e.id);s&&p({level:"info",event:"daemon.grants.streams_closed",fields:{grant:e.id,grantee:e.grantee,streams:s}})}try{const{completion:e}=await q();r(e)}catch(e){p({level:"error",event:"daemon.grants.sweep_failed",error:e})}const i=new Set(o.filter(e=>e.status==="removed"&&n.find(t=>t.id===e.id)?.status!=="removed").map(e=>e.grantee));r(A(i).catch(()=>{})),r(import("../providers/bundles.js").then(({syncProviderBundles:e})=>e()).catch(e=>{p({level:"error",event:"daemon.providers.bundle_sync_failed",error:e})}))}c(_e,"onGrantSync");export{U as exceeds,_e as onGrantSync,q as sweepSandboxes};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
var $=Object.defineProperty;var u=(e,t)=>$(e,"name",{value:t,configurable:!0});import{prepared as a}from"../sqlite-statements.js";import{objectPlace as C,runPlace as x,runsSeeEachOther as P}from"../runs/confinement.js";import{createHash as J,randomBytes as q,timingSafeEqual as F}from"node:crypto";import{CrtrError as S,usage as g}from"../errors.js";import{ExitCode as O}from"../../types.js";import{createObject as M,liveDocumentNamed as B}from"./objects.js";import{invalidateCustomKind as z}from"./kinds/registry.js";import{mayActAs as L}from"./access.js";import{publish as b,HOP_LIMIT as K,HopLimitError as G}from"./events.js";import{checkoutsForCwd as N}from"./repo-sync/repos.js";import{onQueueSignal as Q,signalQueue as R}from"./receivers.js";import{receiverEndpoint as V,isPublicReceiverAddress as Y}from"./receiver-url.js";import{isIP as X}from"node:net";const Z=/^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$/,k=/^[a-z][a-z0-9-]*$/;function h(e,t){throw new S(e,t,O.GENERAL)}u(h,"conflict");function D(e){return J("sha256").update(e).digest("hex")}u(D,"hash");function H(){return`crtr_pub_${q(32).toString("base64url")}`}u(H,"issueToken");function ee(e){if(e==null)return null;if(e==="queue")return JSON.stringify(e);if(typeof e!="object"||Array.isArray(e))throw g("invalid custom receiver; expected queue, an https url, or argv and absolute cwd");if("url"in e&&typeof e.url=="string")try{const s=V(e.url).hostname.replace(/^\[|\]$/g,"");if(X(s)&&!Y(s))throw new Error("non-public IP address");return JSON.stringify({url:e.url})}catch(t){throw g(`invalid custom receiver: ${String(t)}`)}if("argv"in e&&Array.isArray(e.argv)&&e.argv.length>0&&e.argv.every(t=>typeof t=="string")&&typeof e.cwd=="string"&&e.cwd.startsWith("/"))return JSON.stringify(e);throw g("invalid custom receiver; url must be https and argv requires a command and absolute cwd")}u(ee,"receiverJson");function _e(e,t,s){const{kind:n,name:i,owner:o,description:r,events:l,receiver:m,rotateToken:w}=s;if(L(e,t,o)||h("forbidden",`cannot act as ${o.id}`),!Z.test(n)||["report","notice","node","job","document","person","app","repo","profile","action"].includes(n.split(".")[0]))throw g(`invalid or reserved kind ${n}`);if(!i.trim()||i!==i.trim()||i.includes("\0"))throw g(`invalid object name ${JSON.stringify(i)}`);const
|
|
2
|
-
VALUES (?, ?, ?, ?)`).run(c.rowid,r,
|
|
1
|
+
var $=Object.defineProperty;var u=(e,t)=>$(e,"name",{value:t,configurable:!0});import{prepared as a}from"../sqlite-statements.js";import{objectPlace as C,runPlace as x,runsSeeEachOther as P}from"../runs/confinement.js";import{createHash as J,randomBytes as q,timingSafeEqual as F}from"node:crypto";import{CrtrError as S,usage as g}from"../errors.js";import{ExitCode as O}from"../../types.js";import{createObject as M,liveDocumentNamed as B}from"./objects.js";import{invalidateCustomKind as z}from"./kinds/registry.js";import{mayActAs as L}from"./access.js";import{publish as b,HOP_LIMIT as K,HopLimitError as G}from"./events.js";import{checkoutsForCwd as N}from"./repo-sync/repos.js";import{onQueueSignal as Q,signalQueue as R}from"./receivers.js";import{receiverEndpoint as V,isPublicReceiverAddress as Y}from"./receiver-url.js";import{isIP as X}from"node:net";const Z=/^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$/,k=/^[a-z][a-z0-9-]*$/;function h(e,t){throw new S(e,t,O.GENERAL)}u(h,"conflict");function D(e){return J("sha256").update(e).digest("hex")}u(D,"hash");function H(){return`crtr_pub_${q(32).toString("base64url")}`}u(H,"issueToken");function ee(e){if(e==null)return null;if(e==="queue")return JSON.stringify(e);if(typeof e!="object"||Array.isArray(e))throw g("invalid custom receiver; expected queue, an https url, or argv and absolute cwd");if("url"in e&&typeof e.url=="string")try{const s=V(e.url).hostname.replace(/^\[|\]$/g,"");if(X(s)&&!Y(s))throw new Error("non-public IP address");return JSON.stringify({url:e.url})}catch(t){throw g(`invalid custom receiver: ${String(t)}`)}if("argv"in e&&Array.isArray(e.argv)&&e.argv.length>0&&e.argv.every(t=>typeof t=="string")&&typeof e.cwd=="string"&&e.cwd.startsWith("/"))return JSON.stringify(e);throw g("invalid custom receiver; url must be https and argv requires a command and absolute cwd")}u(ee,"receiverJson");function _e(e,t,s){const{kind:n,name:i,owner:o,description:r,events:l,receiver:m,rotateToken:w}=s;if(L(e,t,o)||h("forbidden",`cannot act as ${o.id}`),!Z.test(n)||["report","notice","node","job","document","person","app","repo","profile","action"].includes(n.split(".")[0]))throw g(`invalid or reserved kind ${n}`);if(!i.trim()||i!==i.trim()||i.includes("\0"))throw g(`invalid object name ${JSON.stringify(i)}`);const p=ee(m);if(m!=="queue"&&m!==null&&m!==void 0&&"argv"in m&&t.class!=="person"&&t.class!=="node"&&h("argv_forbidden","argv receiver requires a person or node principal"),Object.keys(l).length>64||Object.entries(l).some(([c,_])=>!k.test(c)||!Number.isInteger(_?.level)||_.level<1||_.level>10||_.supersedes!==void 0&&typeof _.supersedes!="boolean"))throw g("invalid custom event catalog");const d=B(e,o.rowid,i);if(d&&!te(e,t,d)&&h("forbidden",`cannot act as ${o.id}`),d&&t.class!=="person"&&t.class!=="node"){const c=a(e,"SELECT receiver_json FROM custom_objects WHERE object = ?").get(d.rowid)?.receiver_json;c&&typeof JSON.parse(c)=="object"&&"argv"in JSON.parse(c)&&h("argv_forbidden","argv receiver requires a person or node principal")}d&&d.type!==n&&h("name_taken",`${o.name}/${i} is already held by ${d.id}`);const f=a(e,"SELECT owner, description, events_json FROM object_kinds WHERE name = ?").get(n);f&&f.owner!==o.rowid&&h("kind_owned",`${n} belongs to another owner`);const T=new Date().toISOString(),v=U(l),A=f===void 0?null:U(JSON.parse(f.events_json));if(f?(f.description!==r||A!==v)&&a(e,"UPDATE object_kinds SET description = ?, events_json = ?, updated_at = ? WHERE name = ?").run(r,v,T,n):a(e,"INSERT INTO object_kinds VALUES (?, ?, ?, ?, ?, ?)").run(n,o.rowid,r,v,T,T),(!f||A!==v)&&z(e,n),!d){const c=M(e,{type:n,kind:null,owner:o.rowid,privacy:"public",name:i,preview:""}),_=H();return a(e,`INSERT INTO custom_objects (object, description, receiver_json, token_hash)
|
|
2
|
+
VALUES (?, ?, ?, ?)`).run(c.rowid,r,p,D(_)),t.class==="node"&&a(e,"UPDATE custom_objects SET registered_by = ? WHERE object = ?").run(t.node.id,c.rowid),p!==null&&a(e,"UPDATE objects SET reception = 2 WHERE rowid = ?").run(c.rowid),b(e,{event:"created",object:c,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{}},I=>N(e,I)),{object:c,token:_,created:!0,changed:[]}}const y=a(e,"SELECT description, receiver_json FROM custom_objects WHERE object = ?").get(d.rowid);y||h("name_taken",`${o.name}/${i} is not a custom object`);const E=[];if(y.description!==r&&E.push("description"),y.receiver_json!==p&&E.push("receiver"),f&&A!==v&&E.push("events"),f&&f.description!==r&&!E.includes("description")&&E.push("description"),w&&E.push("token"),E.length===0)return{object:d,created:!1,changed:E};const j=w?H():void 0;if(a(e,"UPDATE custom_objects SET description = ?, receiver_json = ?, token_hash = COALESCE(?, token_hash) WHERE object = ?").run(r,p,j===void 0?null:D(j),d.rowid),a(e,"UPDATE objects SET reception = ? WHERE rowid = ?").run(p===null?0:2,d.rowid),y.receiver_json!==p){const c=Date.now();if(p===null){const _=a(e,`UPDATE deliveries SET canceled_at = ?, updated_at = ?
|
|
3
3
|
WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(c,c,d.id);a(e,"UPDATE custom_objects SET dropped = dropped + ? WHERE object = ?").run(_.changes,d.rowid)}else a(e,`UPDATE deliveries SET next_attempt_at = ?, updated_at = ?
|
|
4
|
-
WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(
|
|
4
|
+
WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(p==='"queue"'?null:c,c,d.id);R(d.id)}return w&&R(d.id),b(e,{event:"configured",object:d,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{changed:E.join(",")}},c=>N(e,c)),{object:d,...j===void 0?{}:{token:j},created:!1,changed:E}}u(_e,"registerCustomObject");function ge(e,t,s,n){const i=a(e,"SELECT events_json FROM object_kinds WHERE name = ?").get(t.type);if(!i||t.deleted_at!==null)throw g(`${t.id} is not a live custom object`);const o=JSON.parse(i.events_json);if(!k.test(n.event)||!Object.hasOwn(o,n.event))throw new S("unknown_event",`${t.type} declares ${Object.keys(o).join(", ")}, not ${n.event}`,O.USAGE,{declared:Object.keys(o),event:n.event});if(typeof n.summary!="string")throw g("summary must be text");const r=n.state===void 0?null:JSON.stringify(n.state);if(n.state!==void 0&&r===void 0)throw g("state must be JSON-serializable");if(Buffer.byteLength(JSON.stringify({summary:n.summary,...n.state===void 0?{}:{state:n.state}}))>64*1024)throw new S("body_too_large","custom event summary and state exceed 64 KiB",O.USAGE);if(r!==null&&Buffer.byteLength(r)>64*1024)throw g("state exceeds 64 KiB");const l=n.cause??null,m=l===null?null:a(e,`SELECT depth FROM messages WHERE message_id = ?
|
|
5
5
|
UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(l,l);if(l!==null&&m===void 0)throw new S("unknown_cause",`no message ${l}`,O.USAGE);if(m&&m.depth>=K)throw new G(m.depth+1);const w=new Date().toISOString();return a(e,"UPDATE custom_objects SET summary = ?, state_json = ?, published_at = ? WHERE object = ?").run(n.summary,r,w,t.rowid),a(e,"UPDATE objects SET preview = ? WHERE rowid = ?").run(n.summary.split(`
|
|
6
|
-
`)[0],t.rowid),b(e,{event:`${t.type}.${n.event}`,object:t,actor:s,attrs:{},messageBody:{summary:n.summary,...n.state===void 0?{}:{state:n.state}},...n.cause===void 0?{}:{cause:n.cause}},
|
|
6
|
+
`)[0],t.rowid),b(e,{event:`${t.type}.${n.event}`,object:t,actor:s,attrs:{},messageBody:{summary:n.summary,...n.state===void 0?{}:{state:n.state}},...n.cause===void 0?{}:{cause:n.cause}},p=>N(e,p))}u(ge,"publishCustom");function he(e,t,s){if(t.deleted_at!==null)return;b(e,{event:"deleted",object:t,actor:s,attrs:{}},i=>N(e,i));const n=new Date().toISOString();a(e,"UPDATE objects SET deleted_at = ?, reception = 0 WHERE rowid = ?").run(n,t.rowid),a(e,"UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL").run(Date.now(),Date.now(),t.id),R(t.id)}u(he,"unregisterCustomObject");function U(e){return JSON.stringify(Object.fromEntries(Object.entries(e).sort(([t],[s])=>t.localeCompare(s)).map(([t,s])=>[t,{level:s.level,...s.supersedes===void 0?{}:{supersedes:s.supersedes}}])))}u(U,"canonicalEvents");function we(e,t,s=0){return W(e,t.id,s,"pending")}u(we,"pendingFor");function ve(e,t){const s=a(e,"SELECT destination_id FROM deliveries WHERE delivery_id = ?").get(t);return s?W(e,s.destination_id,-1,"handed_off").find(n=>n.delivery_id===t)??null:null}u(ve,"pushLineFor");function W(e,t,s,n){return a(e,`SELECT d.delivery_id, m.message_id, m.seq, m.event, m.level, m.object_id, m.actor_id, m.body_json, m.body_ref, m.depth, m.created_at,
|
|
7
7
|
o.type, o.name, owner.id AS owner_id
|
|
8
8
|
FROM deliveries d JOIN messages m ON m.message_id = d.message_id JOIN objects o ON o.id = m.object_id
|
|
9
9
|
LEFT JOIN objects owner ON owner.rowid = o.owner
|
|
10
10
|
WHERE d.destination_id = ? AND d.state = ? AND d.canceled_at IS NULL AND m.seq > ? ORDER BY m.seq`).all(t,n,s).map(o=>{const r=o.body_json?JSON.parse(o.body_json):{};return{delivery_id:o.delivery_id,message_id:o.message_id,seq:o.seq,event:o.event,level:o.level,object:{id:o.object_id,type:o.type,name:o.name,owner:o.owner_id},actor:o.actor_id,attrs:r.push?.attrs??{},...r.summary===void 0?{}:{summary:r.summary},...r.state===void 0?{}:{state:r.state},...r.push?.pointer===void 0?{}:{pointer:r.push.pointer},depth:o.depth,created_at:o.created_at}})}u(W,"queueLines");function ye(e,t,s){const n=Date.now(),i=[];let o=0;for(const r of s){const l=a(e,`UPDATE deliveries SET state = 'acknowledged', acknowledged_at = ?, updated_at = ?
|
|
11
|
-
WHERE delivery_id = ? AND destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(n,n,r,t.id);Number(l.changes)?o++:i.push(r)}return{acknowledged:o,...i.length?{unknown:i}:{}}}u(ye,"acknowledge");function je(e,t,s){const n=a(e,"SELECT token_hash FROM custom_objects WHERE object = ?").get(t.rowid),i=Buffer.from(n?.token_hash??"".padEnd(64,"0"),"hex"),o=Buffer.from(D(s),"hex");return s.startsWith("crtr_pub_")&&t.deleted_at===null&&i.length===o.length&&F(i,o)&&n?.token_hash!=null}u(je,"verifyPublishToken");function te(e,t,s){if(t.class!=="node")return!0;const n=x(t.node.id,e);if(n===null)return!0;const i=C(s.rowid,e);return i===null?!n.isolated:P(n,i)}u(te,"customReachable");export{ye as acknowledge,te as customReachable,L as mayActAs,Q as onQueueSignal,we as pendingFor,ge as publishCustom,ve as pushLineFor,_e as registerCustomObject,he as unregisterCustomObject,je as verifyPublishToken};
|
|
11
|
+
WHERE delivery_id = ? AND destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(n,n,r,t.id);Number(l.changes)?o++:i.push(r)}return{acknowledged:o,...i.length?{unknown:i}:{}}}u(ye,"acknowledge");function je(e,t,s){const n=a(e,"SELECT token_hash FROM custom_objects WHERE object = ?").get(t.rowid),i=Buffer.from(n?.token_hash??"".padEnd(64,"0"),"hex"),o=Buffer.from(D(s),"hex");return s.startsWith("crtr_pub_")&&t.deleted_at===null&&i.length===o.length&&F(i,o)&&n?.token_hash!=null}u(je,"verifyPublishToken");function te(e,t,s){if(t.class!=="node"&&!(t.class==="app"&&t.place!==void 0))return!0;const n=t.class==="app"?t.place:x(t.node.id,e);if(n===null)return!0;const i=C(s.rowid,e);return i===null?!n.isolated:P(n,i)}u(te,"customReachable");export{ye as acknowledge,te as customReachable,L as mayActAs,Q as onQueueSignal,we as pendingFor,ge as publishCustom,ve as pushLineFor,_e as registerCustomObject,he as unregisterCustomObject,je as verifyPublishToken};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
sub, grant_id, scopes_json, cwd, profile_id FROM nodes WHERE node_id = ?`).get(t?.owner??null,e);if(!t||!
|
|
3
|
-
WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??w(o,"profile","profile",e,
|
|
1
|
+
var b=Object.defineProperty;var s=(o,e)=>b(o,"name",{value:e,configurable:!0});import{prepared as f}from"../sqlite-statements.js";import{covers as O,parseScope as x}from"@north-light/identity";import{activeGrant as _,grantFor as y}from"../grants/mirror.js";import{prepared as R}from"../canvas/db.js";import{readConfig as C}from"../config.js";import{SCOPE_VOCABULARY as m}from"../scopes.js";import{loadExactProfileManifest as L}from"../profiles/manifest.js";import{ensureOwner as w,getByRowid as v,nodeObject as M}from"./objects.js";function F(o){return{repos:o.map(e=>e.repo),checkouts:o}}s(F,"inRepos");function V(o,e,a){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:h(o)};if(e.class==="app")return{class:"app",app:H(o,e.app),scopes:e.scopes};if(e.class==="cron"){const i=e.grant?_(e.grant)?.resolved??[]:m,c=e.scopes;return{class:"app",app:H(o,e.app),scopes:c===null?i:c.filter(u=>i.some(p=>O(p,u))),place:{run:e.origin_run??"",isolated:e.isolated}}}const n=d(o),r=M(o,e.node),t=r.owner===null?null:n.object(r.owner);if(!t)throw new Error(`node ${e.node} has no owner`);const l=f(o,"SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e.node);return{class:"node",node:r,app:t,scopes:S(e.app,e.sub,e.grant,e.scopes,n),cwd:l.cwd,...F(a(l.cwd)),profile:l.profile_id===null?null:n.profile(l.profile_id,t)}}s(V,"readerFor");function $(o,e,a,n=d(o),r=!1){const t=R(o,"SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=R(o,`SELECT (SELECT external_id FROM identities WHERE object = ?) AS grantee,
|
|
2
|
+
sub, grant_id, scopes_json, cwd, profile_id FROM nodes WHERE node_id = ?`).get(t?.owner??null,e);if(!t||!l)throw new Error(`no node ${e}`);const{grantee:i,sub:c,grant_id:u,scopes_json:p,cwd:g,profile_id:j}=l,E=t.owner===null?null:n.object(t.owner);if(!E)throw new Error(`node ${e} has no owner`);const N=p===null?null:JSON.parse(p);return{class:"node",node:t,app:E,scopes:S(i,c??"",u,N,n,r),cwd:g,...F(a(g)),profile:j===null?null:n.profile(j,E)}}s($,"readerForNode");function Y(o,e,a=()=>[],n=d(o)){if(e.type==="node")return $(o,e.id,a,n,!0);const r=e.owner===null?null:n.object(e.owner);if(!r)throw new Error(`watcher ${e.id} has no owner`);const l=(r.type==="app"?r:r.type==="profile"&&r.owner!==null?n.object(r.owner):null)??f(o,"SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??f(o,"SELECT * FROM objects WHERE type = 'person' AND name = 'user'").get();if(!l)throw new Error("no terminal app or person object");const i=f(o,"SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(l.rowid)?.external_id??"app:terminal",c=f(o,"SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(i),u=r.type==="person"?null:c.length===0?i==="app:terminal"&&!n.terminalHasIssuer()?m:null:c.length===1?JSON.parse(c[0].resolved_scopes_json).filter(p=>p.mode==="always").map(p=>x(p.scope)):null;return{class:"object",object:e,node:e,owner:r,app:l,scopes:u,repos:r.type==="repo"?[r]:[]}}s(Y,"readerForObject");function d(o){return{grantById:_,grantFor:y,terminalHasIssuer:s(()=>!!C("user").runtime?.issuer,"terminalHasIssuer"),object:s(e=>v(o,e),"object"),profile:s((e,a)=>A(o,e,a),"profile")}}s(d,"liveLookups");function q(o){const e=d(o),a=new Map,n=s((r,t)=>(a.has(r)||a.set(r,t()),a.get(r)),"memo");return{grantById:s(r=>n(`g\0${r}`,()=>e.grantById(r)),"grantById"),grantFor:s((r,t)=>n(`a\0${r}\0${t}`,()=>e.grantFor(r,t)),"grantFor"),terminalHasIssuer:s(()=>n("issuer",()=>e.terminalHasIssuer()),"terminalHasIssuer"),object:s(r=>n(`o\0${r}`,()=>e.object(r)),"object"),profile:s((r,t)=>n(`p\0${r}\0${t.rowid}`,()=>e.profile(r,t)),"profile")}}s(q,"publishLookups");function S(o,e,a,n,r,t=!1){const l=a?r.grantById(a):r.grantFor(o,e),i=l&&l.grantee===o?l.resolved:o==="app:terminal"&&!r.terminalHasIssuer()?m:null;if(i===null){if(t)return null;throw new Error(`the grant of ${o} is not active`)}return n===null?i:n.filter(c=>i.some(u=>O(u,c)))}s(S,"nodeScopes");function h(o){return w(o,"person","local-user","user:local","user")}s(h,"personObject");function H(o,e){return e==="user:local"?h(o):w(o,"app","app",e,e.replace(/^app:/,""))}s(H,"appObject");function A(o,e,a){return f(o,`SELECT o.* FROM identities i JOIN objects o ON o.rowid = i.object
|
|
3
|
+
WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??w(o,"profile","profile",e,L(e).manifest.name,a)}s(A,"profileObject");export{d as liveLookups,A as profileObject,q as publishLookups,V as readerFor,$ as readerForNode,Y as readerForObject};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var g=Object.defineProperty;var s=(t,n)=>g(t,"name",{value:n,configurable:!0});import{covers as u}from"@north-light/identity";import{identityOf as c}from"./index.js";import{objectPlace as $,runPlace as
|
|
1
|
+
var g=Object.defineProperty;var s=(t,n)=>g(t,"name",{value:n,configurable:!0});import{covers as u}from"@north-light/identity";import{identityOf as c}from"./index.js";import{objectPlace as $,runPlace as p,runsSeeEachOther as a}from"../../runs/confinement.js";const j={identity:c,sync:"runtime",canRead(t,n,o,e,r){if(n.class==="person"||e.length===0)return!0;if(n.app.rowid===o.rowid)return m(t,n,e);const i=c(t,o).external_id,l=s(d=>n.scopes!==null&&n.scopes.some(w=>u(w,d)),"held");return e[0].type==="node"?l(`crtr:objects:read:${i}`)||r.type==="document"&&r.kind==="report"&&l(`crtr:conversations:read:${i}`):l(y(t,i,"read",e))},canChange(t,n,o,e){if(n.class==="person")return!0;if(n.app.rowid===o.rowid)return e.length===0||m(t,n,e);if(e.length===0||e[0].type==="node")return!1;const r=y(t,c(t,o).external_id,"rw",e);return n.scopes!==null&&n.scopes.some(i=>u(i,r))}},f=new WeakMap;function m(t,n,o){if(n.class==="person"||o.length===0||o[0].type!=="node"||n.class==="app"&&n.place===void 0)return!0;let e=f.get(n);if(e===void 0&&(e=n.class==="app"?n.place:n.class==="node"?p(n.node.id,t):$(n.object.rowid,t),f.set(n,e)),e===null)return!0;const r=p(o[0].id,t);return r===null?!e.isolated:a(e,r)}s(m,"runConfined");function y(t,n,o,e){const r=e[0];return r.type!=="profile"?`crtr:memory:${o}:${n}`:`crtr:memory:${o}:${n}:profile:${c(t,r).external_id}`}s(y,"memoryScope");export{j as appSpace};
|
|
@@ -52,6 +52,11 @@ export type Reader = {
|
|
|
52
52
|
class: 'app';
|
|
53
53
|
app: ObjectRow;
|
|
54
54
|
scopes: readonly string[];
|
|
55
|
+
/** A cron command's run place (its origin run, and whether that run is isolated): it reads its own app's nodes under run confinement, as a node of that run does. Absent for the app's server. */
|
|
56
|
+
place?: {
|
|
57
|
+
run: string;
|
|
58
|
+
isolated: boolean;
|
|
59
|
+
};
|
|
55
60
|
} | {
|
|
56
61
|
class: 'node';
|
|
57
62
|
node: ObjectRow;
|
package/dist/core/layout.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export declare function appSpace(app: string): {
|
|
|
22
22
|
home: string;
|
|
23
23
|
cache: string;
|
|
24
24
|
};
|
|
25
|
+
/** The folder an isolated cron runs in: `<app space>/crons/<cron_id>/`. It belongs to the cron, not to the node that armed it. */
|
|
26
|
+
export declare function cronFolder(app: string, cronId: string): string;
|
|
25
27
|
/** The HOME an app's processes run with. With no sandbox (a Mac) the terminal is
|
|
26
28
|
* the person at their own machine — its grant already holds all of the person's
|
|
27
29
|
* files and nothing at the OS level confines its bash — so it gets the person's
|
package/dist/core/layout.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var w=Object.defineProperty;var t=(r,
|
|
2
|
-
`,{flag:"wx",mode:384})}t(
|
|
1
|
+
var w=Object.defineProperty;var t=(r,o)=>w(r,"name",{value:o,configurable:!0});import{existsSync as i,mkdirSync as m,readFileSync as E,statSync as R,writeFileSync as _}from"node:fs";import{homedir as S,userInfo as T}from"node:os";import{basename as l,dirname as A,isAbsolute as O,join as e,resolve as f}from"node:path";import{envHomeOverride as $}from"../shared/env.js";import{readConfig as b}from"./config.js";const d="1";function s(){const r=f($()||(process.platform==="linux"?"/":e(S(),".crouter")));if(g()&&r===z())throw new Error(`refusing to use the account's real crouter root ${r} from a node --test process; point CRTR_HOME (not only HOME) at a temporary directory`);return r}t(s,"layoutRoot");function g(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}t(g,"runningUnderNodeTest");let c;function z(){if(c===void 0)try{c=f(T().homedir,".crouter")}catch{c=null}return c}t(z,"accountLayoutRoot");function u(){return e(s(),"runtime")}t(u,"runtimeRoot");function h(){return e(s(),"user")}t(h,"userRoot");function x(){return e(s(),"apps")}t(x,"appsRoot");function y(){return e(u(),"secrets")}t(y,"secretsDir");function D(r,o){if(!/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/.test(o))throw new Error(`invalid profile id: ${o}`);const n=p(r);return{memory:e(n.memory,"profiles",o),env:e(y(),"env",l(n.root),o),home:n.home}}t(D,"profileSpace");function v(){return process.platform==="linux"?"/run/crtr":u()}t(v,"socketDir");function a(){return e(u(),"layout")}t(a,"layoutVersionFile");function F(){const r=b("user").paths?.user_files??(process.platform==="linux"?e(h(),"files"):A(s()));if(!O(r))throw new Error("paths.user_files must be absolute");return f(r)}t(F,"userFilesRoot");function p(r){const o=r.startsWith("app:")?r.slice(4):r;if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o)||o==="."||o==="..")throw new Error(`invalid app id: ${r}`);const n=e(x(),o);return{root:n,memory:e(n,"memory"),conversations:e(n,"conversations"),nodes:e(n,"nodes"),files:e(n,"files"),home:e(n,"home"),cache:e(n,"cache")}}t(p,"appSpace");function M(r,o){if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o))throw new Error(`invalid cron id: ${o}`);return e(p(r).root,"crons",o)}t(M,"cronFolder");function V(r){const o=p(r);return process.platform==="linux"?o.home:l(o.root)==="terminal"?F():o.home}t(V,"appProcessHome");function C(){if(process.env.CRTR_LAYOUT_REFUSE==="1")return!0;if(i(a())){if(E(a(),"utf8").trim()!==d)throw new Error("unsupported runtime layout version");return!1}const r=s(),o=process.platform==="linux"&&r==="/"&&i("/volume")?"/volume":r;return i(e(o,"canvas"))||i(e(o,"canvas.db"))?!0:i(e(u(),"canvas.db"))}t(C,"layoutNeedsMigration");function Y(){if(C())throw new Error("layout_migration_required");for(const r of[u(),h(),x(),y()])m(r,{recursive:!0,mode:448});if(m(v(),{recursive:!0,mode:493}),process.platform==="linux"&&(R(v()).mode&511)!==493)throw new Error("launcher socket directory must be mode 0755");i(a())||_(a(),`${d}
|
|
2
|
+
`,{flag:"wx",mode:384})}t(Y,"initializeEmptyLayout");export{d as LAYOUT_VERSION,V as appProcessHome,p as appSpace,x as appsRoot,M as cronFolder,Y as initializeEmptyLayout,C as layoutNeedsMigration,s as layoutRoot,a as layoutVersionFile,D as profileSpace,u as runtimeRoot,y as secretsDir,v as socketDir,F as userFilesRoot,h as userRoot};
|
package/dist/core/llm/run.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ThinkingLevel } from '@earendil-works/pi-ai/compat';
|
|
2
|
+
import { type SpendTarget } from '../usage/ledger.js';
|
|
2
3
|
type WireContent = string | null | Array<{
|
|
3
4
|
type: 'text';
|
|
4
5
|
text: string;
|
|
@@ -53,8 +54,11 @@ export interface LlmResult {
|
|
|
53
54
|
completion: Record<string, unknown>;
|
|
54
55
|
chunks: Array<Record<string, unknown>>;
|
|
55
56
|
}
|
|
57
|
+
/** `spend`: the grantee a user-paid call bills and its grant's cap. The call is refused
|
|
58
|
+
* `cap_exhausted` once the grantee's month spend reached the cap, and a completed call
|
|
59
|
+
* is added to the grantee's month. Omitted, the call is neither capped nor counted. */
|
|
56
60
|
export declare function runLlm(input: LlmRequest, signal?: AbortSignal, onChunk?: (chunk: Record<string, unknown>) => void, target?: {
|
|
57
61
|
cwd: string;
|
|
58
62
|
profileId: string | null;
|
|
59
|
-
}): Promise<LlmResult>;
|
|
63
|
+
}, spend?: SpendTarget): Promise<LlmResult>;
|
|
60
64
|
export {};
|
package/dist/core/llm/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
`).slice(0,16384),
|
|
1
|
+
var de=Object.defineProperty;var p=(t,e)=>de(t,"name",{value:e,configurable:!0});import{randomUUID as ce}from"node:crypto";import{streamSimple as fe}from"@earendil-works/pi-ai/compat";import{expandModelCandidates as Y,parseModelSpec as ue,probeRouteAvailability as me}from"../model-routes.js";import{readMergedLaunchConfig as ee}from"../config.js";import{openModelRegistry as pe}from"../runtime/model-registry.js";import{coolingUntilFor as te,isManagedProvider as E,markSubscriptionRateLimited as he,markSubscriptionSuccess as ye,readSubscriptionPool as _e,retryAfterMsFromHeaders as Z,retryAfterMsFromError as ge,refreshSubscriptionCredential as ve,subscriptionNeedsSuccessWrite as be}from"../subscription-state.js";import{classifyRateLimit as we,describeRateLimitScope as oe,rateLimitHeaders as xe}from"../rate-limit-scope.js";import{RunError as l}from"../runs/operations.js";import{recognizeProviderError as ne}from"../provider-error-recognition.js";import{isSafetyRefusal as ke}from"./refusal.js";import{assertUnderCap as je,recordModelUsage as qe}from"../usage/ledger.js";function Ae(t){return typeof t=="string"||t===null||Array.isArray(t)&&t.every(e=>e&&typeof e=="object"&&(e.type==="text"&&typeof e.text=="string"||e.type==="image_url"&&typeof e.image_url?.url=="string"&&/^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/.test(e.image_url.url)))}p(Ae,"validContent");function re(t){return Array.isArray(t)?t.map(e=>{if(e.type==="text")return e;const[,n,r]=/^data:(image\/(?:png|jpeg|gif|webp));base64,(.*)$/.exec(e.image_url.url);return{type:"image",mimeType:n,data:r}}):t??""}p(re,"imageContent");function We(t){if(!t||typeof t!="object"||Array.isArray(t))throw new l(400,"invalid_request","body must be an object");const e=t;if(!Array.isArray(e.messages)||!e.messages.every(n=>n&&typeof n=="object"&&["system","user","assistant","tool"].includes(n.role)&&Ae(n.content)))throw new l(400,"invalid_request","messages must be chat messages","messages");if(e.messages.some(n=>["system","assistant"].includes(n.role)&&typeof n.content!="string"&&n.content!==null))throw new l(400,"invalid_request","system and assistant content must be text","messages");if(e.model!==void 0&&typeof e.model!="string")throw new l(400,"invalid_request","model must be a string","model");if(e.model_policy!==void 0&&!["exact","substitute"].includes(e.model_policy))throw new l(400,"invalid_request","invalid model_policy","model_policy");if(e.payer!==void 0&&!["user","app"].includes(e.payer))throw new l(400,"invalid_request","invalid payer","payer");if(e.reasoning_effort!==void 0&&!["minimal","low","medium","high","xhigh","max"].includes(e.reasoning_effort))throw new l(400,"invalid_request","invalid reasoning_effort","reasoning_effort");if(e.stream!==void 0&&typeof e.stream!="boolean")throw new l(400,"invalid_request","stream must be boolean","stream");if(e.tools!==void 0&&(!Array.isArray(e.tools)||!e.tools.every(n=>n?.type==="function"&&typeof n.function?.name=="string"&&n.function.name.length>0&&(n.function.description===void 0||typeof n.function.description=="string")&&(n.function.parameters===void 0||n.function.parameters!==null&&typeof n.function.parameters=="object"&&!Array.isArray(n.function.parameters)&&n.function.parameters.type==="object"))))throw new l(400,"invalid_request","invalid tools","tools");if(e.tool_choice!==void 0&&e.tool_choice!=="auto"&&e.tool_choice!=="none"&&e.tool_choice!=="required"&&(typeof e.tool_choice!="object"||e.tool_choice===null||e.tool_choice.type!=="function"||typeof e.tool_choice.function?.name!="string"))throw new l(400,"invalid_request","invalid tool_choice","tool_choice");if(e.messages.some(n=>n.role==="assistant"&&n.tool_calls!==void 0&&(!Array.isArray(n.tool_calls)||!n.tool_calls.every(r=>r&&typeof r.id=="string"&&typeof r.function?.name=="string"&&typeof r.function.arguments=="string"&&Me(r.function.arguments)))))throw new l(400,"invalid_request","invalid assistant tool_calls","messages");if(e.stop!==void 0&&!(typeof e.stop=="string"||Array.isArray(e.stop)&&e.stop.every(n=>typeof n=="string")))throw new l(400,"invalid_request","invalid stop","stop");if(e.seed!==void 0&&!Number.isSafeInteger(e.seed))throw new l(400,"invalid_request","invalid seed","seed");for(const[n,r]of[["temperature",e.temperature],["top_p",e.top_p],["max_tokens",e.max_tokens]])if(r!==void 0&&(typeof r!="number"||!Number.isFinite(r)||n==="temperature"&&(r<0||r>2)||n==="top_p"&&(r<0||r>1)||n==="max_tokens"&&(!Number.isSafeInteger(r)||r<1)))throw new l(400,"invalid_request",`invalid ${n}`,n);if(e.response_format!==void 0){const n=e.response_format;if(!n||typeof n!="object"||Array.isArray(n)||!["text","json_object","json_schema"].includes(n.type)||n.type==="json_schema"&&(typeof n.json_schema?.name!="string"||!n.json_schema.name||!n.json_schema.schema||typeof n.json_schema.schema!="object"||Array.isArray(n.json_schema.schema)))throw new l(400,"invalid_request","invalid response_format","response_format")}if(e.metadata!==void 0&&Buffer.byteLength(JSON.stringify(e.metadata)??"")>4096)throw new l(400,"invalid_request","metadata exceeds 4 KB","metadata");return e}p(We,"parseLlmRequest");function Me(t){try{const e=JSON.parse(t);return e!==null&&typeof e=="object"&&!Array.isArray(e)}catch{return!1}}p(Me,"isJsonObject");function $e(t,e){const n=Date.now(),r=t.messages.map(s=>s.role==="system"?{role:"system",content:s.content??"",timestamp:n}:s.role==="tool"?{role:"toolResult",toolCallId:s.tool_call_id??"",toolName:"",content:typeof s.content=="string"||s.content===null?[{type:"text",text:s.content??""}]:re(s.content),isError:!1,timestamp:n}:s.role==="assistant"?{role:"assistant",content:[...s.content?[{type:"text",text:s.content}]:[],...(s.tool_calls??[]).map(c=>({type:"toolCall",id:c.id,name:c.function.name,arguments:JSON.parse(c.function.arguments)}))],api:"openai-completions",provider:"openai",model:t.model??"",usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"stop",timestamp:n}:{role:"user",content:re(s.content),timestamp:n});if(e){const s=r.filter(f=>f.role==="system").map(f=>f.content).join(`
|
|
2
|
+
`).slice(0,16384),c=r.findIndex(f=>f.role==="user");if(s&&c>=0){const f=r[c];r[c]={...f,content:typeof f.content=="string"?`${s}
|
|
3
3
|
|
|
4
|
-
${
|
|
4
|
+
${f.content}`:[{type:"text",text:s},...f.content]}}s&&c<0&&r.unshift({role:"user",content:s,timestamp:n});for(let f=r.length-1;f>=0;f--)r[f]?.role==="system"&&r.splice(f,1)}const _=t.tools?.map(({function:s})=>({name:s.name,description:s.description??"",parameters:s.parameters??{type:"object",properties:{}}}));return{messages:r,..._?{tools:_}:{}}}p($e,"contextOf");function se(t,e,n,r){if(!t){const s=ee(n,r),c=Object.values({...s.modelRoutes??{},...s.legacyModelRoutes??{}}),f=c.length?[s.modelLadders.defaultProvider,...s.modelRouting?.familyOrder??[],...c.map(k=>k.family)].find(k=>c.some(I=>I.family===k)):s.modelLadders.defaultProvider??"anthropic";return{kind:"logical",family:f,strength:"strong",spec:`${f}/strong`}}const _=/^([^/]+)\/(ultra|strong|medium|light)$/.exec(t);return _?{kind:"logical",family:_[1],strength:_[2],spec:t}:{kind:e==="exact"?"exact":"concrete",spec:t}}p(se,"requestFor");function Re(t,e){const{input:n,output:r,cacheRead:_,cacheWrite:s}=t.cost;return n===0&&r===0&&_===0&&s===0?null:Number.isFinite(e)&&e>=0?e:null}p(Re,"usdEquivalent");function Ie(t,e){return t.compat?.allowedFallbackModels?.find(r=>r.provider===t.provider&&r.model===e)}p(Ie,"allowedFallback");async function He(t,e,n,r,_){if(t.payer==="app")throw new l(403,"payer_not_app","app-paid inference routes are not registered");je(_);const s=await pe(),c=se(t.model,t.model_policy,r?.cwd,r?.profileId),f=p(m=>m.routeId!=="concrete"&&m.routeId!=="exact","configured"),k=p(m=>{const a=s.find(m.providerId,m.modelId);return a!==void 0&&(!t.reasoning_effort||a.reasoning)&&(t.seed===void 0&&t.response_format===void 0||a.api==="openai-completions")&&(t.stop===void 0||["openai-completions","anthropic-messages"].includes(a.api))},"matchesFeatures"),I=Y(c.kind==="exact"?{kind:"concrete",spec:c.spec}:c,r?.cwd,r?.profileId).filter(f);let w;if(c.kind==="logical")w=t.model_policy==="exact"?I.filter(m=>m.family===c.family&&m.strength===c.strength).slice(0,1):I;else{const m=ue(c.spec),a=I.filter(u=>u.providerId===m?.providerId&&u.modelId===m.modelId&&(t.model_policy!=="exact"||u.thinkingLevel===m.thinkingLevel)&&k(u));if(t.model_policy==="exact")w=a.slice(0,1);else if(a.length)w=a;else{const u=ee(r?.cwd,r?.profileId),q=[...Object.values(u.modelRoutes??{}).flatMap(i=>Object.entries(i.models)),...[u.modelLadders.anthropic,u.modelLadders.openai].flatMap(i=>Object.entries(i))].find(([,i])=>i===c.spec)?.[0],L=se(void 0,"substitute",r?.cwd,r?.profileId);w=q&&L.kind==="logical"?Y({...L,strength:q,spec:c.spec},r?.cwd,r?.profileId).filter(i=>f(i)&&i.strength===q):[]}}if(w=w.filter(k),w.length===0)throw t.model_policy==="exact"?new l(400,"substitution_refused","no eligible model route is configured"):new l(404,"model_unavailable","no eligible model route is configured");const G=`chatcmpl-${ce()}`;let j=1/0,y,U;for(const m of w){if(!me(m,s).authenticated)continue;const a=s.find(m.providerId,m.modelId);if(!a)continue;const u=m.providerId,S=E(u)?_e(u).filter(i=>!i.authFailure):[],q=S.filter(i=>te(i,a.id)<=Date.now());if(S.length&&!q.length){const i=Math.min(...S.map(A=>te(A,a.id)));i<j&&(j=i,U=`${a.provider}/${a.id}`);continue}const L=S.length?q:[void 0];for(const i of L){const A=Date.now();let Q=i?.access;if(i&&i.expires<=Date.now()&&E(u)){const o=s.getProvider(u)?.auth.oauth;if(!o)throw new l(503,"upstream_model_unavailable",`no OAuth refresh for ${u}`,void 0,{served_model:`${a.provider}/${a.id}`});Q=(await ve(u,i.label,async(g,X)=>{const P=await o.refresh({type:"oauth",refresh:g,access:"",expires:0,...X?{clientId:X}:{}},e??new AbortController().signal),z=P.clientId;return{refresh:P.refresh,access:P.access,expires:P.expires,...typeof z=="string"&&z?{clientId:z}:{}}})).access}const ie=$e(t,!!i),M=i?void 0:await s.getApiKeyAndHeaders(a);if(M&&!M.ok)continue;const v=`${a.provider}/${a.id}`,$=t.model??v;let x=!1,V,b=3e4,R;const J=[],T=[],O=[];let d;const N=p((o,h=null)=>({id:G,object:"chat.completion.chunk",created:Math.floor(A/1e3),model:v,served_model:v,requested_model:$,substituted:$!==v&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test($),choices:[{index:0,delta:o,finish_reason:h}]}),"chunk"),D=p(o=>{J.push(o),n?.(o)},"emit"),ae=fe(a,ie,{apiKey:Q??(M?.ok?M.apiKey:void 0),...M?.ok?{headers:M.headers}:{},signal:e,reasoning:t.reasoning_effort??m.thinkingLevel,...t.temperature!==void 0?{temperature:t.temperature}:{},...t.max_tokens!==void 0?{maxTokens:t.max_tokens}:{},...t.top_p!==void 0?{samplingParams:{top_p:t.top_p}}:{},...t.tool_choice==="none"?{toolChoice:"none"}:{},onPayload:p(o=>{if(!o||typeof o!="object")return;const h=t.tool_choice,g=h==="required"?a.api==="anthropic-messages"?{type:"any"}:"required":typeof h=="object"&&h!==null?a.api==="anthropic-messages"?{type:"tool",name:h.function.name}:h:void 0;return{...o,...g?{tool_choice:g}:{},...t.stop!==void 0?{[a.api==="anthropic-messages"?"stop_sequences":"stop"]:typeof t.stop=="string"?[t.stop]:t.stop}:{},...t.seed!==void 0?{seed:t.seed}:{},...t.response_format!==void 0?{response_format:t.response_format}:{}}},"onPayload"),fetch:p(async(o,h)=>{const g=await fetch(o,h);return V=g.status,g.status===429&&(x=!0,R=g.headers,b=Z(g.headers)??15e3),g},"fetch"),onResponse:p(o=>{o.status===429&&(x=!0,R=o.headers,b=Z(o.headers)??15e3)},"onResponse")});try{for await(const o of ae)o.type==="text_delta"&&(T.push(o.delta),D(N({content:o.delta}))),o.type==="thinking_delta"&&(O.push(o.delta),D(N({reasoning_content:o.delta}))),o.type==="done"&&(d=o.message),o.type==="error"&&(d=o.error)}catch(o){const h=ge(o);R===void 0&&o&&typeof o=="object"&&"headers"in o&&Object.keys(xe(o.headers)).length&&(R=o.headers);const g=ne({status:o&&typeof o=="object"&&"status"in o?o.status:void 0,message:o instanceof Error?o.message:String(o)});if(!x&&g.kind!=="rate-limit")throw J.length?new l(503,"upstream_model_unavailable",o instanceof Error?o.message:String(o),void 0,{served_model:v}):o;x=!0,b=h??b}if(!x&&d?.stopReason==="error"&&ne({message:d.errorMessage}).kind==="rate-limit"&&(x=!0,b=15e3),x){const o=we(R,a.id);if(Z(R)===void 0&&o.resetAt!==void 0&&(b=Math.max(b,o.resetAt-Date.now())),y={provider:u,model:a.id,...i?{account:i.label}:{},observation:o},console.error(`[llm] 429 from ${v}${i?` account "${i.label}"`:""}: scope=${oe(o.scope)} retry_after_ms=${b} headers=${JSON.stringify(o.headers)}`),i&&E(u)&&await he(u,i.label,b,A,Date.now(),o.scope),Date.now()+b<j&&(j=Date.now()+b,U=v),T.length||O.length)throw new l(503,"service_unavailable","vendor rate limit after output began",void 0,{served_model:v});continue}const F=ke(d);if(V===400&&t.tool_choice!==void 0&&t.tool_choice!=="auto"&&t.tool_choice!=="none"&&/\btool[_ ]choice\b/i.test(d?.errorMessage??"")&&/not supported|does not support|unsupported|cannot|must be auto/i.test(d?.errorMessage??""))throw new l(400,"invalid_request",d.errorMessage,"tool_choice",{served_model:v});if(!d||d.stopReason==="error"&&!F||d.stopReason==="aborted")throw new l(503,"upstream_model_unavailable",d?.errorMessage??"model request failed",void 0,{served_model:v});i&&E(u)&&be(i,a.id)&&await ye(u,i.label,A,a.id);const W=d.content.filter(o=>o.type==="toolCall").map(o=>({id:o.id,type:"function",function:{name:o.name,arguments:JSON.stringify(o.arguments)}})),H=F?"content_filter":d.stopReason==="toolUse"?"tool_calls":d.stopReason==="length"?"length":"stop",K=d.responseModel?Ie(a,d.responseModel):void 0,C=K?`${a.provider}/${K.model}`:v,B={prompt_tokens:d.usage.input,completion_tokens:d.usage.output,total_tokens:d.usage.totalTokens,cache_read_tokens:d.usage.cacheRead,cache_write_tokens:d.usage.cacheWrite,usd_equivalent:Re(K??a,d.usage.cost.total),served_model:C};_&&qe(_.grantee,"user",C,d.usage,B.usd_equivalent);const le={id:G,object:"chat.completion",created:Math.floor(A/1e3),model:C,served_model:C,requested_model:$,substituted:$!==C&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test($),choices:[{index:0,message:{role:"assistant",content:T.join("")||null,...O.length?{reasoning_content:O.join("")}:{},...W.length?{tool_calls:W}:{},...F?{refusal:d.errorMessage??"Content filtered"}:{}},finish_reason:H}],usage:B,...t.metadata!==void 0?{metadata:t.metadata}:{}};for(const[o,h]of W.entries())D(N({tool_calls:[{index:o,...h}]}));return D({...N({},H),...F?{choices:[{index:0,delta:{},finish_reason:H,message:{refusal:d.errorMessage??"Content filtered"}}]}:{},usage:B}),{completion:le,chunks:J}}}throw j!==1/0?new l(429,"all_routes_exhausted","all eligible model routes are rate-limited",void 0,{retry_after_s:Math.max(1,Math.ceil((j-Date.now())/1e3)),served_model:y?`${y.provider}/${y.model}`:U,...y?{rate_limit:{provider:y.provider,model:y.model,...y.account?{account:y.account}:{},scope:oe(y.observation.scope),...y.observation.claim?{claim:y.observation.claim}:{},headers:y.observation.headers}}:{}},void 0,!0):t.model_policy==="exact"?new l(400,"substitution_refused","the requested model is unavailable"):new l(404,"model_unavailable","no usable model route is available")}p(He,"runLlm");export{We as parseLlmRequest,He as runLlm};
|
|
@@ -53,6 +53,8 @@ export interface ProviderResponseContext {
|
|
|
53
53
|
run_id: string | null;
|
|
54
54
|
sub: string;
|
|
55
55
|
grantee: string;
|
|
56
|
+
/** Whose month the receipt adds to: the calling app, or `app:terminal` for the person (whose outbound identity is their sub). */
|
|
57
|
+
ledger_grantee: string;
|
|
56
58
|
jti: string;
|
|
57
59
|
status: number;
|
|
58
60
|
headers: Record<string, string | string[] | undefined>;
|