@north-light/cli 0.3.579 → 0.3.581
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 +6 -1
- package/dist/api/client.js +3 -3
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/execution-start.d.ts +14 -0
- package/dist/api/dto/health.d.ts +20 -0
- package/dist/api/dto/imports.d.ts +142 -0
- package/dist/api/dto/imports.js +0 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -1
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +2 -2
- package/dist/clients/attach/session/connection.d.ts +3 -1
- package/dist/clients/attach/session/connection.js +1 -1
- package/dist/clients/attach/viewer.js +510 -510
- package/dist/commands/api-client.d.ts +7 -4
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/sys/daemon.js +1 -1
- package/dist/commands/sys/import/resume.d.ts +1 -0
- package/dist/commands/sys/import/resume.js +1 -0
- package/dist/commands/sys/import/start.d.ts +5 -0
- package/dist/commands/sys/import/start.js +4 -0
- package/dist/commands/sys/join.d.ts +2 -0
- package/dist/commands/sys/join.js +1 -1
- package/dist/commands/sys/machine/rotate.d.ts +1 -0
- package/dist/commands/sys/machine/rotate.js +1 -0
- package/dist/commands/sys/signin.d.ts +7 -0
- package/dist/commands/sys/signin.js +2 -0
- package/dist/commands/sys.js +1 -1
- package/dist/core/blobs/r2.d.ts +4 -0
- package/dist/core/blobs/r2.js +7 -4
- package/dist/core/canvas/daemon-owner.d.ts +1 -1
- package/dist/core/canvas/executions.d.ts +1 -1
- package/dist/core/canvas/export-sqlite.js +1 -1
- package/dist/core/canvas/node-tokens.d.ts +2 -0
- package/dist/core/canvas/node-tokens.js +3 -3
- package/dist/core/canvas/schema-pg/009-bodies.sql +10 -0
- package/dist/core/canvas/schema-version.d.ts +1 -1
- package/dist/core/canvas/schema-version.js +1 -1
- package/dist/core/directory/person-credential.d.ts +36 -0
- package/dist/core/directory/person-credential.js +1 -0
- package/dist/core/feed/feed.js +3 -3
- package/dist/core/feed/inbox.js +1 -1
- package/dist/core/feed/reports.js +2 -2
- package/dist/core/graph/access.d.ts +1 -1
- package/dist/core/graph/bodies-boot-copy.d.ts +14 -0
- package/dist/core/graph/bodies-boot-copy.js +1 -0
- package/dist/core/graph/bodies.d.ts +13 -6
- package/dist/core/graph/bodies.js +1 -1
- package/dist/core/graph/deletion.d.ts +4 -1
- package/dist/core/graph/deletion.js +3 -3
- package/dist/core/graph/documents.d.ts +4 -0
- package/dist/core/graph/documents.js +10 -10
- package/dist/core/graph/objects.js +11 -11
- package/dist/core/graph/package-docs.js +5 -5
- package/dist/core/graph/reader.js +2 -2
- package/dist/core/graph/repo-sync/exchange.js +2 -2
- package/dist/core/graph/repo-sync/ref-format.d.ts +0 -1
- package/dist/core/graph/repo-sync/ref-format.js +2 -2
- package/dist/core/import/bundle/blob-keys.d.ts +10 -0
- package/dist/core/import/bundle/blob-keys.js +1 -0
- package/dist/core/import/bundle/layout.d.ts +89 -0
- package/dist/core/import/bundle/layout.js +5 -0
- package/dist/core/import/export/archive.d.ts +11 -0
- package/dist/core/import/export/archive.js +1 -0
- package/dist/core/import/export/cloud.d.ts +21 -0
- package/dist/core/import/export/cloud.js +1 -0
- package/dist/core/import/export/dump.d.ts +34 -0
- package/dist/core/import/export/dump.js +3 -0
- package/dist/core/import/export/files.d.ts +22 -0
- package/dist/core/import/export/files.js +1 -0
- package/dist/core/import/export/inline.d.ts +3 -0
- package/dist/core/import/export/inline.js +1 -0
- package/dist/core/import/export/source.d.ts +2 -0
- package/dist/core/import/export/source.js +1 -0
- package/dist/core/import/export/stores.d.ts +9 -0
- package/dist/core/import/export/stores.js +1 -0
- package/dist/core/import/load/bind.d.ts +19 -0
- package/dist/core/import/load/bind.js +15 -0
- package/dist/core/import/load/blobs.d.ts +10 -0
- package/dist/core/import/load/blobs.js +1 -0
- package/dist/core/import/load/catalog.d.ts +32 -0
- package/dist/core/import/load/catalog.js +9 -0
- package/dist/core/import/load/copy.d.ts +26 -0
- package/dist/core/import/load/copy.js +6 -0
- package/dist/core/import/load/path-map.d.ts +7 -0
- package/dist/core/import/load/path-map.js +1 -0
- package/dist/core/import/load/prepare.d.ts +37 -0
- package/dist/core/import/load/prepare.js +10 -0
- package/dist/core/import/load/rewrite.d.ts +39 -0
- package/dist/core/import/load/rewrite.js +9 -0
- package/dist/core/import/load/rollback.d.ts +3 -0
- package/dist/core/import/load/rollback.js +6 -0
- package/dist/core/import/load/run.d.ts +22 -0
- package/dist/core/import/load/run.js +3 -0
- package/dist/core/import/load/stage.d.ts +15 -0
- package/dist/core/import/load/stage.js +1 -0
- package/dist/core/import/load/stores.d.ts +24 -0
- package/dist/core/import/load/stores.js +2 -0
- package/dist/core/import/load/verify.d.ts +4 -0
- package/dist/core/import/load/verify.js +11 -0
- package/dist/core/peer/receiver.js +1 -1
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/revive.d.ts +5 -2
- package/dist/core/runtime/revive.js +2 -2
- package/dist/core/runtime/roadmap.js +3 -3
- package/dist/core/runtime/runner/blaxel/runner.js +3 -3
- package/dist/core/runtime/runner/index.d.ts +1 -0
- package/dist/core/runtime/runner/index.js +1 -1
- package/dist/core/runtime/runner/machine/call.d.ts +8 -0
- package/dist/core/runtime/runner/machine/call.js +3 -0
- package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
- package/dist/core/runtime/runner/machine/environment.js +1 -0
- package/dist/core/runtime/runner/machine/executor.d.ts +23 -0
- package/dist/core/runtime/runner/machine/executor.js +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
- package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
- package/dist/core/runtime/runner/machine/ops.js +1 -0
- package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
- package/dist/core/runtime/runner/machine/runner.js +1 -0
- package/dist/core/runtime/runner/types.d.ts +7 -1
- package/dist/core/runtime/warm-pool.d.ts +2 -0
- package/dist/core/runtime/warm-pool.js +1 -1
- package/dist/core/secrets/env-rows.d.ts +1 -0
- package/dist/core/secrets/env-rows.js +1 -1
- package/dist/core/storage/purge.d.ts +6 -0
- package/dist/core/storage/purge.js +4 -0
- package/dist/core/subscription-state.d.ts +1 -0
- package/dist/core/subscription-state.js +1 -1
- package/dist/core/substrate/delivery/corpus.d.ts +2 -0
- package/dist/core/substrate/delivery/corpus.js +3 -3
- package/dist/core/substrate/delivery/deliver.js +3 -3
- package/dist/core/substrate/delivery/render-boot.js +25 -25
- package/dist/core/substrate/delivery/render-event.d.ts +1 -1
- package/dist/core/substrate/delivery/render-event.js +4 -4
- package/dist/core/substrate/delivery/types.d.ts +2 -2
- package/dist/daemon/api/bridge.js +3 -3
- package/dist/daemon/api/document-listener.js +4 -4
- package/dist/daemon/api/handlers/attach.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/daemon.js +1 -1
- package/dist/daemon/api/handlers/docs.js +1 -1
- package/dist/daemon/api/handlers/execution-start.js +2 -2
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/imports.d.ts +2 -0
- package/dist/daemon/api/handlers/imports.js +1 -0
- package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
- package/dist/daemon/api/handlers/machine-credential.js +1 -0
- package/dist/daemon/api/handlers/memory.js +4 -4
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/reports.js +4 -4
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/import-auth.d.ts +6 -0
- package/dist/daemon/api/import-auth.js +1 -0
- package/dist/daemon/api/operations.d.ts +2 -2
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +7 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/boot-migrations.js +1 -1
- package/dist/daemon/broker-signals/bus.d.ts +3 -2
- package/dist/daemon/broker-signals/bus.js +1 -1
- package/dist/daemon/cron/armed-context.d.ts +1 -0
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/closed.d.ts +31 -0
- package/dist/daemon/cron/closed.js +1 -0
- package/dist/daemon/cron/ended.js +2 -2
- package/dist/daemon/cron/lease-recovery.js +1 -1
- package/dist/daemon/cron/machine-exec.d.ts +5 -0
- package/dist/daemon/cron/machine-exec.js +1 -0
- package/dist/daemon/cron/machine-fire.d.ts +26 -0
- package/dist/daemon/cron/machine-fire.js +1 -0
- package/dist/daemon/cron/recurrence.d.ts +14 -0
- package/dist/daemon/cron/recurrence.js +1 -0
- package/dist/daemon/cron/run-token.d.ts +7 -0
- package/dist/daemon/cron/run-token.js +1 -0
- package/dist/daemon/cron-run.js +2 -2
- package/dist/daemon/crtrd-cli.js +1 -1
- package/dist/daemon/crtrd.d.ts +2 -2
- package/dist/daemon/crtrd.js +10 -10
- package/dist/daemon/drain.d.ts +25 -0
- package/dist/daemon/drain.js +9 -0
- package/dist/daemon/due-sources.d.ts +1 -1
- package/dist/daemon/due-sources.js +1 -1
- package/dist/daemon/due.js +8 -8
- package/dist/daemon/fleet.js +5 -5
- package/dist/daemon/import-dump-run.d.ts +1 -0
- package/dist/daemon/import-dump-run.js +5 -0
- package/dist/daemon/machine-daemon.d.ts +7 -0
- package/dist/daemon/machine-daemon.js +1 -0
- package/dist/daemon/machine-link/client.d.ts +29 -0
- package/dist/daemon/machine-link/client.js +1 -0
- package/dist/daemon/machine-link/config.d.ts +14 -0
- package/dist/daemon/machine-link/config.js +3 -0
- package/dist/daemon/machine-link/credential.d.ts +7 -0
- package/dist/daemon/machine-link/credential.js +2 -0
- package/dist/daemon/machine-link/forward.d.ts +16 -0
- package/dist/daemon/machine-link/forward.js +1 -0
- package/dist/daemon/machine-link/frames.d.ts +49 -0
- package/dist/daemon/machine-link/frames.js +1 -0
- package/dist/daemon/machine-link/holder.d.ts +73 -0
- package/dist/daemon/machine-link/holder.js +9 -0
- package/dist/daemon/manage.d.ts +19 -3
- package/dist/daemon/manage.js +3 -3
- package/dist/daemon/reconcilers/cron-lane.js +1 -1
- package/dist/daemon/reconcilers/delivery-lane.js +2 -2
- package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
- package/dist/daemon/reconcilers/import-lane.js +1 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
- package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
- package/dist/daemon/reconcilers/purge-due-work.js +1 -0
- package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
- package/dist/daemon/reconcilers/row-claim.js +2 -2
- package/dist/daemon/review/finish.js +1 -1
- package/dist/daemon/runs/observer.js +3 -3
- package/dist/migrations/004-canvas-documents/apply.d.ts +2 -4
- package/dist/migrations/004-canvas-documents/apply.js +1 -1
- package/dist/migrations/004-canvas-documents/index.js +2 -2
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
- package/scripts/sandbox-start.mjs +185 -2
|
@@ -23,10 +23,6 @@ export declare function rethrowAsCliError(err: unknown, next?: string): never;
|
|
|
23
23
|
* 404-only→null mapping as `ApiCanvasSource.orNull`, but returning the raw DTO
|
|
24
24
|
* and rethrowing non-404s as CLI errors rather than swallowing them. */
|
|
25
25
|
export declare function getNodeOrNull(nodeId: string): Promise<NodeDetailDTO | null>;
|
|
26
|
-
/** Bounded tail of crtrd.log surfaced on a cold-start `/healthz` timeout.
|
|
27
|
-
* Large enough to catch a real startup failure/stack trace,
|
|
28
|
-
* small enough that a runaway daemon log can never leak unbounded into a CLI
|
|
29
|
-
* error message. */
|
|
30
26
|
export declare const COLD_START_DIAGNOSTIC_TAIL_BYTES = 4096;
|
|
31
27
|
/** Read a bounded tail of crtrd's event log to enrich a `daemon_unavailable`
|
|
32
28
|
* cold-start timeout, so the actual startup failure is not discarded behind a
|
|
@@ -37,6 +33,12 @@ export declare const COLD_START_DIAGNOSTIC_TAIL_BYTES = 4096;
|
|
|
37
33
|
* contract — a missing/unreadable/empty log yields `undefined` so the base
|
|
38
34
|
* message is used unchanged. */
|
|
39
35
|
export declare function readColdStartDiagnostic(): string | undefined;
|
|
36
|
+
/** Where a `crtr` process outside a node reaches the control plane instead of a local daemon: a machine cron's run token goes to
|
|
37
|
+
* `CRTR_API_URL`; an imported Mac (`runtime/machine.json`) goes to its `apiUrl` with the signed-in person's bearer. Neither: undefined,
|
|
38
|
+
* and the local socket is used. `CRTR_API_SOCKET` (inside a node) is checked by the caller first and is never remote. */
|
|
39
|
+
export declare function remoteTarget(): {
|
|
40
|
+
client(timeoutMs?: number): CrtrClient;
|
|
41
|
+
} | undefined;
|
|
40
42
|
/** The local-socket `CrtrClient` every CLI verb uses. Autostart is on by
|
|
41
43
|
* default: on a cold socket the injected `onColdSocket` hook fires
|
|
42
44
|
* `ensureDaemon()` (spawns crtrd detached via the branded host), then the
|
|
@@ -53,6 +55,7 @@ export declare function readColdStartDiagnostic(): string | undefined;
|
|
|
53
55
|
* cannot outlast the initiating CLI command. */
|
|
54
56
|
export declare function cliClient(opts?: {
|
|
55
57
|
timeoutMs?: number;
|
|
58
|
+
local?: boolean;
|
|
56
59
|
}): CrtrClient;
|
|
57
60
|
export declare class ApiCanvasSource implements CanvasSource {
|
|
58
61
|
private readonly client;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var y=Object.defineProperty;var a=(r,t)=>y(r,"name",{value:t,configurable:!0});import{closeSync as
|
|
2
|
-
`).filter(c=>c.trim()!=="").slice(-
|
|
1
|
+
var y=Object.defineProperty;var a=(r,t)=>y(r,"name",{value:t,configurable:!0});import{closeSync as C,existsSync as v,fstatSync as R,openSync as N,readFileSync as m,readSync as T}from"node:fs";import{dirname as E,join as l}from"node:path";import{gunzipSync as A}from"node:zlib";import{CrtrClient as p,ApiError as u}from"../api/index.js";import{localClient as f}from"../api/node-transport.js";import{personBearer as O,notSignedIn as I,readPersonCredential as M}from"../core/directory/person-credential.js";import{readMachineConfig as x}from"../daemon/machine-link/config.js";import{apiErrorToCliError as w}from"../core/io.js";import{autostartDisabled as _,daemonAutostartStartupFailure as F,ensureDaemon as D}from"../daemon/manage.js";import{DAEMON_STARTUP_WINDOW_MS as b}from"../daemon/startup-policy.js";import{readState as k}from"../core/config.js";import{resolveRemoteCanvasTarget as L}from"../core/remote-canvas-target.js";import{RemoteCanvasSource as P}from"../core/canvas/remote-canvas-source.js";import{BROKER_RELEASED_MARKER as B,BROKER_RELAY_GENERATION as U,crtrHome as K}from"../core/canvas/paths.js";import{envTestNodeSocketWindowMs as W,isSandboxedNode as z}from"../shared/env.js";import{ownerOnly as j}from"../core/errors.js";function d(r){return{...r,fault:r.fault??null}}a(d,"withRowFault");function G(r,t){throw r instanceof u?w(r,t):r}a(G,"rethrowAsCliError");async function mt(r){try{return await g().getNode(r)}catch(t){if(t instanceof u&&t.status===404)return null;G(t)}}a(mt,"getNodeOrNull");const H="crtr_run_",$=4096,Y=20;function q(){let r;try{r=N(l(K(),"crtrd.log"),"r");const{size:t}=R(r),n=Math.max(0,t-$),s=t-n;if(s<=0)return;const e=Buffer.alloc(s);T(r,e,0,s,n);const o=e.toString("utf8").split(`
|
|
2
|
+
`).filter(c=>c.trim()!=="").slice(-Y).join(`
|
|
3
3
|
`).trim();return o===""?void 0:`crtrd.log (tail):
|
|
4
|
-
${o}`}catch{return}finally{if(r!==void 0)try{
|
|
4
|
+
${o}`}catch{return}finally{if(r!==void 0)try{C(r)}catch{}}}a(q,"readColdStartDiagnostic");function J(){if(process.env.CRTR_API_SOCKET)return;const r=process.env.CRTR_API_TOKEN,t=process.env.CRTR_API_URL;if(r!==void 0&&r.startsWith(H)&&t)return{client:a(e=>new p({baseUrl:t,headers:{authorization:`Bearer ${r}`},...e!==void 0?{timeoutMs:e}:{}}),"client")};const n=x();if(n===void 0)return;if(M()===void 0)throw I("this computer is imported into Northlight, so crtr needs your sign-in");const s=a(async(e,i)=>{const o=new Headers(i?.headers);return o.set("authorization",`Bearer ${await O(n.directoryUrl)}`),fetch(e,{...i,headers:o})},"authed");return{client:a(e=>new p({baseUrl:n.apiUrl,fetch:s,...e!==void 0?{timeoutMs:e}:{}}),"client")}}a(J,"remoteTarget");function g(r){const t=r?.local===!0?void 0:J();if(t!==void 0)return t.client(r?.timeoutMs);const n=process.env.CRTR_API_SOCKET;if(n){const s=a(()=>{if(!v(n))try{const i=E(n),o=m(l(i,B),"utf8"),c=m(l(i,U),"utf8");if(o===c&&o!=="")return new u(503,"broker_released","This node's broker has been released, so crtr commands from its background jobs are not available.")}catch(i){if(i.code!=="ENOENT")throw i}},"availabilityFailure"),e=s();if(e!==void 0)throw w(e,"Run the command from the node's own turn instead.");return f({autostart:!1,availabilityFailure:s,coldStartPollWindowMs:(_()?W():void 0)??b,...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{}})}return _()?f({autostart:!1,...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{}}):f({...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{},onColdSocket:a(async()=>{D()},"onColdSocket"),coldStartDiagnostic:q,coldStartFailure:F,coldStartPollWindowMs:b})}a(g,"cliClient");const V=2e3;class S{static{a(this,"ApiCanvasSource")}client;rosterCache=null;rosterInFlight=null;constructor(t=g()){this.client=t}async roster(){const t=Date.now();return this.rosterCache!==null&&t-this.rosterCache.at<V?this.rosterCache.index:this.rosterInFlight!==null?this.rosterInFlight:(this.rosterInFlight=this.client.canvasRoster().then(n=>{const s={snapshot:n,subscriptions:new Map,subscribers:new Map};for(const e of n.edges){const i={node_id:e.to_id,active:e.active,created:e.created},o={node_id:e.from_id,active:e.active,created:e.created},c=s.subscriptions.get(e.from_id);c===void 0?s.subscriptions.set(e.from_id,[i]):c.push(i);const h=s.subscribers.get(e.to_id);h===void 0?s.subscribers.set(e.to_id,[o]):h.push(o)}return this.rosterCache={at:Date.now(),index:s},s}).finally(()=>{this.rosterInFlight=null}),this.rosterInFlight)}async orNull(t){try{return await t()}catch(n){if(n instanceof u&&n.status===404)return null;throw n}}async getNode(t){const n=await this.orNull(()=>this.client.getNode(t));return n===null?null:d(n)}async getRow(t){const n=await this.orNull(()=>this.client.getNode(t));return n===null?null:d(n)}async listNodes(t){const n=t?.status,s=t?.under===void 0?{}:{under:t.under,under_inclusive:!0};let e;if(Array.isArray(n)){const i=new Set(n);e=(await this.client.listNodes(s)).filter(c=>i.has(c.status))}else e=await this.client.listNodes({...n!==void 0?{status:n}:{},...s});return e.map(i=>d(i))}async browseSnapshot(){const t=await this.client.canvasBrowseCompressed(),n=JSON.parse(A(t).toString("utf8")),s={subscriptions:new Map,subscribers:new Map};for(const e of n.edges){const i=s.subscriptions.get(e.from_id)??[];i.push({node_id:e.to_id,active:e.active,created:e.created}),s.subscriptions.set(e.from_id,i);const o=s.subscribers.get(e.to_id)??[];o.push({node_id:e.from_id,active:e.active,created:e.created}),s.subscribers.set(e.to_id,o)}return this.rosterCache={at:Date.now(),index:s},n.nodes.map(e=>d(e))}async panelRoster(){const t=await this.roster();return t.snapshot!==void 0?t.snapshot:(this.rosterCache=null,(await this.roster()).snapshot)}async subscriptionsOf(t){return this.roster().then(n=>n.subscriptions.get(t)??[])}async subscribersOf(t){return this.roster().then(n=>n.subscribers.get(t)??[])}async view(t){const n=new Set([t]),s=[],e=(await this.subscriptionsOf(t)).map(i=>i.node_id);for(;e.length>0;){const i=e.shift();if(!n.has(i)){n.add(i),s.push(i);for(const o of await this.subscriptionsOf(i))n.has(o.node_id)||e.push(o.node_id)}}return s}async ticketCountsForNodes(t){return(await this.client.attentionCounts([...t])).counts}async ticketCountsForView(t){return this.ticketCountsForNodes([t,...await this.view(t)])}async newestReport(t){return(await this.client.getReports(t,{limit:1}))[0]??null}async hasActiveLiveSubscription(t){const n=(await this.subscriptionsOf(t)).filter(s=>s.active);for(const s of n){const e=await this.getRow(s.node_id);if(e&&(e.status==="active"||e.status==="idle"))return!0}return!1}}function pt(r,t=z()){const n=r?.scope??"user";if(t){if(r?.canvasName!==void 0)throw j("--canvas selects the person's remote canvas targets, which an app's node cannot reach","Omit --canvas; this node reads its own runtime's canvas.");return new S}const s=r?.canvasName??k(n).activeCanvas??null;if(!s)return new S;const{previewEndpoint:e,relayToken:i}=L(s,n);return new P(e,i)}a(pt,"cliCanvasSource");export{S as ApiCanvasSource,$ as COLD_START_DIAGNOSTIC_TAIL_BYTES,pt as cliCanvasSource,g as cliClient,mt as getNodeOrNull,q as readColdStartDiagnostic,J as remoteTarget,G as rethrowAsCliError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var w=Object.defineProperty;var o=(t,e)=>w(t,"name",{value:e,configurable:!0});import{defineLeaf as u,defineBranch as f}from"../../core/command.js";import{InputError as d}from"../../core/io.js";import{parseDurationMs as g}from"../../core/wake.js";import{DaemonNotPreparedError as y,probeDaemonHealth as p,probeErrorMessage as b,spawnDaemon as h,stopDaemonProcess as v,sweepStrayDaemons as _}from"../../daemon/manage.js";import{isDaemonRunning as k,readPidfile as l,isPidAlive as m}from"../../daemon/pidfile.js";import{cliClient as T}from"../api-client.js";import{localClient as c}from"../../api/node-transport.js";import{defineSysBranch as q}from"./branch.js";import{selectedGeneration as x}from"./shared.js";const I=u({name:"start",description:"start the daemon in the background",whenToUse:"bringing the crtrd supervisor up for the first time in a session so node window-exits get handled (no-op only when its Unix API socket already serves)",help:{name:"sys daemon start",summary:"start the crtrd supervisor daemon in the background (no-op only if its Unix API socket serves)",params:[{kind:"flag",name:"prepare",type:"bool",required:!1,constraint:"Hold normal daemon work after storage preparation until `sys daemon admit`; omit for ordinary startup."},{kind:"flag",name:"force",type:"bool",required:!1,default:!1,constraint:"Start even while `crtr sys join` holds the daemon down. Joining then fails `daemon_running`; use only to clear a join that is stuck."}],output:[{name:"started",type:"boolean",required:!0,constraint:"True when a new daemon process was spawned; false when one was already running."},{name:"pid",type:"number",required:!1,constraint:"PID of the newly spawned daemon."},{name:"existing_pid",type:"number",required:!1,constraint:"PID of the already-running daemon (when started=false)."},{name:"running",type:"boolean",required:!1,constraint:"True only when a live pidfile owner does not serve its Unix API socket."},{name:"serving",type:"boolean",required:!1,constraint:"False only when a live pidfile owner does not answer /healthz."},{name:"probe_error",type:"string",required:!1,constraint:"The final /healthz probe failure when a live owner missed readiness."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon phase when it was reachable."}],outputKind:"object",effects:["Refuses `join_in_progress` while a live `crtr sys join` holds the daemon down, unless `--force`. `sys daemon restart` and `ensureDaemon` stand down the same way.","Spawns dist/daemon/crtrd-cli.js detached + unref'd; it outlives this process.","Writes a pidfile at CRTR_PIDFILE if set, otherwise crtrHome()/crtrd.pid."]},run:o(async t=>{try{return await h({targetPhase:t.prepare===!0?"prepared":"ready",ignoreJoinLock:t.force===!0})}catch(e){throw e instanceof y?new d({error:"daemon_not_prepared",message:e.message,next:"Stop the live daemon, then start a new one with `crtr sys daemon start --prepare` before setup."}):e}},"run")}),D=100;async function S(t,e){const a=Date.now(),r=a+e;let s="initializing",i="the daemon socket was not reachable";for(;Date.now()<r;){try{const n=await p(Math.min(1e3,Math.max(1,r-Date.now())));if(s=n.startup_phase,i=n.startup_blocked??`daemon is ${n.startup_phase}`,n.startup_phase==="blocked")throw new d({error:"startup_blocked",message:n.startup_blocked??"daemon startup is blocked",received:n.startup_phase,field:"phase",next:"Repair the reported blocker paths with `crtr sys migrate`, then retry the await."});if(n.startup_phase===t)return{startup_phase:t,waited_ms:Date.now()-a};if(t==="prepared"&&n.startup_phase==="ready")throw new d({error:"await_missed_phase",message:"daemon is already ready and cannot return to prepared in this process",received:n.startup_phase,field:"phase",next:"Start a new daemon with `crtr sys daemon start --prepare` before awaiting prepared."})}catch(n){if(n instanceof d)throw n;i=n instanceof Error?n.message:String(n)}await new Promise(n=>setTimeout(n,Math.min(D,Math.max(0,r-Date.now()))))}throw new d({error:"await_timeout",message:`daemon did not reach ${t} within ${e}ms (last phase: ${s}; last observation: ${i})`,received:s,field:"phase",next:"Inspect `crtr sys daemon status` and resolve any reported startup blocker before retrying."})}o(S,"awaitDaemonPhase");const E=u({name:"await",description:"wait for a daemon startup phase",whenToUse:"a caller started or was given a daemon that must reach prepared or ready before its next step.",help:{name:"sys daemon await",summary:"wait for the running daemon to report one startup phase",params:[{kind:"flag",name:"phase",type:"enum",choices:["prepared","ready"],required:!0,constraint:"The phase that permits the caller\u2019s next step."},{kind:"flag",name:"timeout",type:"string",required:!1,default:"5m",constraint:"Maximum wait as a positive duration such as 90s, 5m, or 1h30m."}],output:[{name:"startup_phase",type:"string",required:!0,constraint:"The requested phase."},{name:"waited_ms",type:"number",required:!0,constraint:"Elapsed wall-clock time before the phase was observed."}],outputKind:"object",effects:["Read-only: polls /healthz until the requested phase is observed or the caller-bounded timeout expires."]},run:o(async t=>{const e=t.timeout,a=g(e);if(a===null||a<=0)throw new d({error:"invalid_duration",message:`not a positive duration: ${e}`,received:e,field:"timeout",next:"Use a positive duration such as 90s, 5m, or 1h30m."});return S(t.phase,a)},"run")}),R=u({name:"admit",description:"admit normal work after daemon preparation",whenToUse:"the caller has completed its setup against a prepared daemon and is ready for normal daemon work to start.",help:{name:"sys daemon admit",summary:"ask a prepared daemon to start normal services",params:[],output:[{name:"admitted",type:"boolean",required:!0,constraint:"True only when this request began admission."},{name:"already_admitted",type:"boolean",required:!0,constraint:"True when admission had already begun or completed."},{name:"startup_phase",type:"string",required:!0,constraint:"The phase after admission settles."}],outputKind:"object",effects:["Starts normal daemon services when the daemon is prepared."]},run:o(async t=>await c({autostart:!1}).admitDaemon(),"run")});async function P(){try{return(await c({autostart:!1}).status()).generation_id??void 0}catch{return}}o(P,"servingGenerationId");const A=u({name:"status",description:"check whether the daemon is running",whenToUse:"operator/dev diagnostic \u2014 probing whether the crtrd supervisor process is alive while troubleshooting revive/supervision behavior",help:{name:"sys daemon status",summary:"check whether the crtrd supervisor daemon is currently running",params:[],output:[{name:"running",type:"boolean",required:!0,constraint:"True when the daemon pidfile owner is alive."},{name:"serving",type:"boolean",required:!1,constraint:"True when the live daemon answers /healthz; false when one probe does not."},{name:"pid",type:"number",required:!1,constraint:"PID of the running daemon."},{name:"probe_error",type:"string",required:!1,constraint:"The failed /healthz observation when the live owner did not answer."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon startup phase when the socket answered."},{name:"serving_generation",type:"string",required:!1,constraint:"Id of the runtime generation the daemon process was launched from. Absent when the daemon reports none (it predates the field, or runs from a development checkout) or its status could not be read."},{name:"selected_generation",type:"string",required:!1,constraint:"Id of the selected runtime generation. Absent when none is selected or it cannot be read."},{name:"generation_drift",type:"string",required:!1,constraint:"`none` when the serving and selected generation ids are equal; `differs` when they are not, meaning the daemon runs older bytes than the install selected until `sys daemon restart`; `unknown` when either id is absent. Never `none` on missing evidence."}],outputKind:"object",effects:["Read-only: reads the pidfile, probes the pid, and requests /healthz and /v1/status without autostart."]},run:o(async t=>{const e=l();if(!(e!==null&&m(e)))return{running:!1};try{const r=await p(),s=await P(),i=x().generation?.id;return{running:!0,serving:r.ok,pid:e,...r.startup_phase===void 0?{}:{startup_phase:r.startup_phase},...s===void 0?{}:{serving_generation:s},...i===void 0?{}:{selected_generation:i},generation_drift:s===void 0||i===void 0?"unknown":s===i?"none":"differs"}}catch(r){return{running:!0,serving:!1,pid:e,probe_error:b(r)}}},"run")}),j=u({name:"restart",description:"put the selected runtime generation in service via a daemon-owned handover",whenToUse:"adopting a newly installed runtime generation \u2014 the routine restart. Use this instead of stop+start: the daemon answers before it hands over, so a caller running inside a node (whose broker the handover tears down) still gets a settled result and is resumed automatically.",help:{name:"sys daemon restart",summary:"ask the running daemon to replace itself with a successor on the selected runtime generation",params:[],output:[{name:"restarted",type:"boolean",required:!0,constraint:"True when a running daemon accepted the handover. False when none was running \u2014 one was started instead."},{name:"started",type:"boolean",required:!1,constraint:"True when no daemon was running and this call started one (restarted=false)."},{name:"pid",type:"number",required:!1,constraint:"The daemon that accepted the handover, or the one just started."},{name:"grace_ms",type:"number",required:!1,constraint:"How long the daemon waits after answering before teardown begins."},{name:"note",type:"string",required:!1,constraint:"What happens next to the calling node. Read it before acting on an interrupted turn."}],outputKind:"object",effects:["The daemon acknowledges immediately, then after a short grace tears its whole broker fleet down, releases its claim, spawns a successor on the runtime generation currently selected, and exits.","Each torn-down broker hands its foreground bash commands to the file-backed background job system; they keep running and report completion by urgent inbox message.","Every node it tore down \u2014 including the caller \u2014 is resolved by the successor through the ordinary row-driven supervision tick.","The calling process is killed as part of that teardown, AFTER this result is returned."]},run:o(async t=>{if(!k()){const e=await h();return{restarted:!1,started:e.started,...e.pid!==void 0?{pid:e.pid}:{},...e.existing_pid!==void 0?{pid:e.existing_pid}:{},note:"No daemon was running, so there was nothing to hand over; one was started on the selected runtime generation."}}try{const e=await T().restartDaemon();return{restarted:e.accepted,pid:e.predecessor_pid,grace_ms:e.grace_ms,note:"Handover accepted. If you are running inside a node, your broker is about to be torn down and resumed on the new generation \u2014 THIS RESTART ALREADY SUCCEEDED. When your turn resumes with this result in the transcript, do not re-run it."}}catch(e){throw new d({error:"daemon_restart_failed",message:`the daemon did not accept a handover: ${e.message}`,next:"Run `crtr sys daemon status` to inspect the daemon."})}},"run")}),U=u({name:"stop",description:"stop the running daemon",whenToUse:"shutting the crtrd supervisor down for good, ending auto-revival of nodes on window exit. Not the way to pick up a new runtime generation \u2014 `restart` is, because stopping kills every broker of this epoch, including the caller's own, so this command cannot report its result to an agent that runs it.",help:{name:"sys daemon stop",summary:"stop the crtrd supervisor daemon and confirm it exited",params:[],output:[{name:"stopped",type:"boolean",required:!0,constraint:"True when a daemon (recorded or stray) was found and killed. False when none was running."},{name:"pid",type:"number",required:!1,constraint:"The recorded pidfile pid that exited (when one was recorded and alive)."},{name:"strays_killed",type:"array",required:!1,constraint:"PIDs of stray crtrd daemons (alive but not in the pidfile) swept alongside the recorded one \u2014 the backstop against a daemon that survived a prior stop."},{name:"force_killed",type:"boolean",required:!1,constraint:"True when the recorded daemon ignored SIGTERM and had to be SIGKILLed."}],outputKind:"object",effects:["Sends SIGTERM to the recorded pid and waits for it to exit and release its pidfile, escalating to SIGKILL if it does not.","Then sweeps any OTHER live crtrd daemon process (SIGTERM, escalating to SIGKILL after 2s) so a stray daemon orphaned from the pidfile cannot keep supervising."]},run:o(async t=>{const e=l(),a=e!==null&&m(e);try{let r=!1;a&&(r=await v(e)==="killed");const s=_(null);return!a&&s.length===0?{stopped:!1}:{stopped:!0,...a?{pid:e}:{},...s.length>0?{strays_killed:s}:{},...r?{force_killed:!0}:{}}}catch(r){throw new d({error:"daemon_stop_failed",message:`failed to stop daemon${a?` pid ${e}`:""}: ${r.message}`,next:"Run `crtr sys daemon status` to inspect the daemon before retrying."})}},"run")}),L=f({name:"daemon",description:"manage the crtrd supervisor process",whenToUse:"managing the crtrd supervisor that auto-revives nodes on window exit \u2014 start it, restart it onto a new runtime generation, check its status, or stop it",help:{name:"sys daemon",summary:"manage the crtrd canvas supervisor daemon",model:"crtrd is a thin background daemon that polls active+idle nodes and acts on window exit: crashed windows become dead; refresh-yield windows get a fresh respawn. It holds no orchestration logic \u2014 just process supervision. These are operator/dev self-management, not something a normal agent turn needs to check. Picking up a newly installed runtime generation is `restart` (a handover the daemon performs on itself); `stop` is the terminal one, and it kills the caller along with the rest of the fleet."},children:[I,E,R,j,A,U]});function W(){return q([L])}o(W,"registerDaemonControlSys");export{L as daemonBranch,W as registerDaemonControlSys};
|
|
1
|
+
var w=Object.defineProperty;var o=(t,e)=>w(t,"name",{value:e,configurable:!0});import{defineLeaf as u,defineBranch as f}from"../../core/command.js";import{InputError as d}from"../../core/io.js";import{parseDurationMs as g}from"../../core/wake.js";import{DaemonNotPreparedError as y,probeDaemonHealth as p,probeErrorMessage as b,spawnDaemon as h,stopDaemonProcess as v,sweepStrayDaemons as _}from"../../daemon/manage.js";import{isDaemonRunning as k,readPidfile as l,isPidAlive as m}from"../../daemon/pidfile.js";import{cliClient as T}from"../api-client.js";import{localClient as c}from"../../api/node-transport.js";import{defineSysBranch as q}from"./branch.js";import{selectedGeneration as x}from"./shared.js";const I=u({name:"start",description:"start the daemon in the background",whenToUse:"bringing the crtrd supervisor up for the first time in a session so node window-exits get handled (no-op only when its Unix API socket already serves)",help:{name:"sys daemon start",summary:"start the crtrd supervisor daemon in the background (no-op only if its Unix API socket serves)",params:[{kind:"flag",name:"prepare",type:"bool",required:!1,constraint:"Hold normal daemon work after storage preparation until `sys daemon admit`; omit for ordinary startup."},{kind:"flag",name:"force",type:"bool",required:!1,default:!1,constraint:"Start even while `crtr sys join` holds the daemon down. Joining then fails `daemon_running`; use only to clear a join that is stuck."}],output:[{name:"started",type:"boolean",required:!0,constraint:"True when a new daemon process was spawned; false when one was already running."},{name:"pid",type:"number",required:!1,constraint:"PID of the newly spawned daemon."},{name:"existing_pid",type:"number",required:!1,constraint:"PID of the already-running daemon (when started=false)."},{name:"running",type:"boolean",required:!1,constraint:"True only when a live pidfile owner does not serve its Unix API socket."},{name:"serving",type:"boolean",required:!1,constraint:"False only when a live pidfile owner does not answer /healthz."},{name:"probe_error",type:"string",required:!1,constraint:"The final /healthz probe failure when a live owner missed readiness."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon phase when it was reachable."}],outputKind:"object",effects:["Refuses `join_in_progress` while a live `crtr sys join` holds the daemon down, unless `--force`. `sys daemon restart` and `ensureDaemon` stand down the same way.","Spawns dist/daemon/crtrd-cli.js detached + unref'd; it outlives this process.","Writes a pidfile at CRTR_PIDFILE if set, otherwise crtrHome()/crtrd.pid."]},run:o(async t=>{try{return await h({targetPhase:t.prepare===!0?"prepared":"ready",ignoreJoinLock:t.force===!0})}catch(e){throw e instanceof y?new d({error:"daemon_not_prepared",message:e.message,next:"Stop the live daemon, then start a new one with `crtr sys daemon start --prepare` before setup."}):e}},"run")}),D=100;async function S(t,e){const a=Date.now(),r=a+e;let s="initializing",i="the daemon socket was not reachable";for(;Date.now()<r;){try{const n=await p(Math.min(1e3,Math.max(1,r-Date.now())));if(s=n.startup_phase,i=n.startup_blocked??`daemon is ${n.startup_phase}`,n.startup_phase==="blocked")throw new d({error:"startup_blocked",message:n.startup_blocked??"daemon startup is blocked",received:n.startup_phase,field:"phase",next:"Repair the reported blocker paths with `crtr sys migrate`, then retry the await."});if(n.startup_phase===t)return{startup_phase:t,waited_ms:Date.now()-a};if(t==="prepared"&&n.startup_phase==="ready")throw new d({error:"await_missed_phase",message:"daemon is already ready and cannot return to prepared in this process",received:n.startup_phase,field:"phase",next:"Start a new daemon with `crtr sys daemon start --prepare` before awaiting prepared."})}catch(n){if(n instanceof d)throw n;i=n instanceof Error?n.message:String(n)}await new Promise(n=>setTimeout(n,Math.min(D,Math.max(0,r-Date.now()))))}throw new d({error:"await_timeout",message:`daemon did not reach ${t} within ${e}ms (last phase: ${s}; last observation: ${i})`,received:s,field:"phase",next:"Inspect `crtr sys daemon status` and resolve any reported startup blocker before retrying."})}o(S,"awaitDaemonPhase");const E=u({name:"await",description:"wait for a daemon startup phase",whenToUse:"a caller started or was given a daemon that must reach prepared or ready before its next step.",help:{name:"sys daemon await",summary:"wait for the running daemon to report one startup phase",params:[{kind:"flag",name:"phase",type:"enum",choices:["prepared","ready"],required:!0,constraint:"The phase that permits the caller\u2019s next step."},{kind:"flag",name:"timeout",type:"string",required:!1,default:"5m",constraint:"Maximum wait as a positive duration such as 90s, 5m, or 1h30m."}],output:[{name:"startup_phase",type:"string",required:!0,constraint:"The requested phase."},{name:"waited_ms",type:"number",required:!0,constraint:"Elapsed wall-clock time before the phase was observed."}],outputKind:"object",effects:["Read-only: polls /healthz until the requested phase is observed or the caller-bounded timeout expires."]},run:o(async t=>{const e=t.timeout,a=g(e);if(a===null||a<=0)throw new d({error:"invalid_duration",message:`not a positive duration: ${e}`,received:e,field:"timeout",next:"Use a positive duration such as 90s, 5m, or 1h30m."});return S(t.phase,a)},"run")}),R=u({name:"admit",description:"admit normal work after daemon preparation",whenToUse:"the caller has completed its setup against a prepared daemon and is ready for normal daemon work to start.",help:{name:"sys daemon admit",summary:"ask a prepared daemon to start normal services",params:[],output:[{name:"admitted",type:"boolean",required:!0,constraint:"True only when this request began admission."},{name:"already_admitted",type:"boolean",required:!0,constraint:"True when admission had already begun or completed."},{name:"startup_phase",type:"string",required:!0,constraint:"The phase after admission settles."}],outputKind:"object",effects:["Starts normal daemon services when the daemon is prepared."]},run:o(async t=>await c({autostart:!1}).admitDaemon(),"run")});async function P(){try{return(await c({autostart:!1}).status()).generation_id??void 0}catch{return}}o(P,"servingGenerationId");const A=u({name:"status",description:"check whether the daemon is running",whenToUse:"operator/dev diagnostic \u2014 probing whether the crtrd supervisor process is alive while troubleshooting revive/supervision behavior",help:{name:"sys daemon status",summary:"check whether the crtrd supervisor daemon is currently running",params:[],output:[{name:"running",type:"boolean",required:!0,constraint:"True when the daemon pidfile owner is alive."},{name:"serving",type:"boolean",required:!1,constraint:"True when the live daemon answers /healthz; false when one probe does not."},{name:"pid",type:"number",required:!1,constraint:"PID of the running daemon."},{name:"probe_error",type:"string",required:!1,constraint:"The failed /healthz observation when the live owner did not answer."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon startup phase when the socket answered."},{name:"serving_generation",type:"string",required:!1,constraint:"Id of the runtime generation the daemon process was launched from. Absent when the daemon reports none (it predates the field, or runs from a development checkout) or its status could not be read."},{name:"selected_generation",type:"string",required:!1,constraint:"Id of the selected runtime generation. Absent when none is selected or it cannot be read."},{name:"generation_drift",type:"string",required:!1,constraint:"`none` when the serving and selected generation ids are equal; `differs` when they are not, meaning the daemon runs older bytes than the install selected until `sys daemon restart`; `unknown` when either id is absent. Never `none` on missing evidence."}],outputKind:"object",effects:["Read-only: reads the pidfile, probes the pid, and requests /healthz and /v1/status without autostart."]},run:o(async t=>{const e=l();if(!(e!==null&&m(e)))return{running:!1};try{const r=await p(),s=await P(),i=x().generation?.id;return{running:!0,serving:r.ok,pid:e,...r.startup_phase===void 0?{}:{startup_phase:r.startup_phase},...s===void 0?{}:{serving_generation:s},...i===void 0?{}:{selected_generation:i},generation_drift:s===void 0||i===void 0?"unknown":s===i?"none":"differs"}}catch(r){return{running:!0,serving:!1,pid:e,probe_error:b(r)}}},"run")}),j=u({name:"restart",description:"put the selected runtime generation in service via a daemon-owned handover",whenToUse:"adopting a newly installed runtime generation \u2014 the routine restart. Use this instead of stop+start: the daemon answers before it hands over, so a caller running inside a node (whose broker the handover tears down) still gets a settled result and is resumed automatically.",help:{name:"sys daemon restart",summary:"ask the running daemon to replace itself with a successor on the selected runtime generation",params:[],output:[{name:"restarted",type:"boolean",required:!0,constraint:"True when a running daemon accepted the handover. False when none was running \u2014 one was started instead."},{name:"started",type:"boolean",required:!1,constraint:"True when no daemon was running and this call started one (restarted=false)."},{name:"pid",type:"number",required:!1,constraint:"The daemon that accepted the handover, or the one just started."},{name:"grace_ms",type:"number",required:!1,constraint:"How long the daemon waits after answering before teardown begins."},{name:"note",type:"string",required:!1,constraint:"What happens next to the calling node. Read it before acting on an interrupted turn."}],outputKind:"object",effects:["The daemon acknowledges immediately, then after a short grace tears its whole broker fleet down, releases its claim, spawns a successor on the runtime generation currently selected, and exits.","Each torn-down broker hands its foreground bash commands to the file-backed background job system; they keep running and report completion by urgent inbox message.","Every node it tore down \u2014 including the caller \u2014 is resolved by the successor through the ordinary row-driven supervision tick.","The calling process is killed as part of that teardown, AFTER this result is returned."]},run:o(async t=>{if(!k()){const e=await h();return{restarted:!1,started:e.started,...e.pid!==void 0?{pid:e.pid}:{},...e.existing_pid!==void 0?{pid:e.existing_pid}:{},note:"No daemon was running, so there was nothing to hand over; one was started on the selected runtime generation."}}try{const e=await T({local:!0}).restartDaemon();return{restarted:e.accepted,pid:e.predecessor_pid,grace_ms:e.grace_ms,note:"Handover accepted. If you are running inside a node, your broker is about to be torn down and resumed on the new generation \u2014 THIS RESTART ALREADY SUCCEEDED. When your turn resumes with this result in the transcript, do not re-run it."}}catch(e){throw new d({error:"daemon_restart_failed",message:`the daemon did not accept a handover: ${e.message}`,next:"Run `crtr sys daemon status` to inspect the daemon."})}},"run")}),U=u({name:"stop",description:"stop the running daemon",whenToUse:"shutting the crtrd supervisor down for good, ending auto-revival of nodes on window exit. Not the way to pick up a new runtime generation \u2014 `restart` is, because stopping kills every broker of this epoch, including the caller's own, so this command cannot report its result to an agent that runs it.",help:{name:"sys daemon stop",summary:"stop the crtrd supervisor daemon and confirm it exited",params:[],output:[{name:"stopped",type:"boolean",required:!0,constraint:"True when a daemon (recorded or stray) was found and killed. False when none was running."},{name:"pid",type:"number",required:!1,constraint:"The recorded pidfile pid that exited (when one was recorded and alive)."},{name:"strays_killed",type:"array",required:!1,constraint:"PIDs of stray crtrd daemons (alive but not in the pidfile) swept alongside the recorded one \u2014 the backstop against a daemon that survived a prior stop."},{name:"force_killed",type:"boolean",required:!1,constraint:"True when the recorded daemon ignored SIGTERM and had to be SIGKILLed."}],outputKind:"object",effects:["Sends SIGTERM to the recorded pid and waits for it to exit and release its pidfile, escalating to SIGKILL if it does not.","Then sweeps any OTHER live crtrd daemon process (SIGTERM, escalating to SIGKILL after 2s) so a stray daemon orphaned from the pidfile cannot keep supervising."]},run:o(async t=>{const e=l(),a=e!==null&&m(e);try{let r=!1;a&&(r=await v(e)==="killed");const s=_(null);return!a&&s.length===0?{stopped:!1}:{stopped:!0,...a?{pid:e}:{},...s.length>0?{strays_killed:s}:{},...r?{force_killed:!0}:{}}}catch(r){throw new d({error:"daemon_stop_failed",message:`failed to stop daemon${a?` pid ${e}`:""}: ${r.message}`,next:"Run `crtr sys daemon status` to inspect the daemon before retrying."})}},"run")}),L=f({name:"daemon",description:"manage the crtrd supervisor process",whenToUse:"managing the crtrd supervisor that auto-revives nodes on window exit \u2014 start it, restart it onto a new runtime generation, check its status, or stop it",help:{name:"sys daemon",summary:"manage the crtrd canvas supervisor daemon",model:"crtrd is a thin background daemon that polls active+idle nodes and acts on window exit: crashed windows become dead; refresh-yield windows get a fresh respawn. It holds no orchestration logic \u2014 just process supervision. These are operator/dev self-management, not something a normal agent turn needs to check. Picking up a newly installed runtime generation is `restart` (a handover the daemon performs on itself); `stop` is the terminal one, and it kills the caller along with the rest of the fleet."},children:[I,E,R,j,A,U]});function W(){return q([L])}o(W,"registerDaemonControlSys");export{L as daemonBranch,W as registerDaemonControlSys};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const resume: import("../../../core/command.js").LeafDef;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var t=Object.defineProperty;var a=(e,n)=>t(e,"name",{value:n,configurable:!0});import{defineLeaf as r}from"../../../core/command.js";import{cliClient as i}from"../../api-client.js";const m=r({name:"resume",description:"end the pause an import left on this daemon",whenToUse:"an import failed or its exporter was killed, so this runtime stayed paused and should carry on",help:{name:"sys import resume",summary:"end the drain so the daemon takes wakes, cron fires and deliveries again",guide:"Safe to repeat: when the daemon is not drained it says so and exits 0.",params:[],output:[{name:"was_drained",type:"boolean",required:!0,constraint:"Whether the daemon was drained before this call."}],outputKind:"object",effects:["Deletes the `drain` row of `runtime_state`; nothing else changes."]},run:a(async()=>({was_drained:(await i().endDaemonDrain()).was_drained===!0}),"run"),render:a(e=>e.was_drained===!0?'<import drain="ended"/>':'<import drain="none">the daemon was not drained</import>',"render")});export{m as resume};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The drain's end: a failed or errored run ends it; a hosted run that reached `done` leaves it on for hosting's quiesce (`crtr sys import resume` ends it). */
|
|
2
|
+
export declare function settleDrainAtEnd(kind: 'machine' | 'hosted', done: boolean, client: {
|
|
3
|
+
endDaemonDrain(): Promise<unknown>;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare const sysImportBranch: import("../../../core/command.js").BranchDef;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var j=Object.defineProperty;var l=(t,s)=>j(t,"name",{value:s,configurable:!0});import{mkdtempSync as B,readFileSync as f,readdirSync as w,rmSync as C}from"node:fs";import{tmpdir as E}from"node:os";import{join as o}from"node:path";import{defineBranch as F,defineLeaf as M}from"../../../core/command.js";import{general as x,usage as k}from"../../../core/errors.js";import{cliClient as U}from"../../api-client.js";import{ImportCloud as P,STORE_FILE as A,STORE_ORDER as K}from"../../../core/import/export/cloud.js";import{Uploader as L,localObjects as I,reviewSourceObject as z}from"../../../core/import/export/files.js";import{bundleKeys as h}from"../../../core/import/bundle/layout.js";import{crtrVersion as J}from"../../../shared/crtr-version.js";import{nodeArchive as V}from"../../../core/import/export/archive.js";import{buildSourceJson as W}from"../../../core/import/export/source.js";import{runDaemonBodiesDump as G,runDaemonImportDump as H,runDaemonInstallId as Q,spawnDaemon as X,stopAllDaemons as Y}from"../../../daemon/manage.js";import{writeMachineConfig as Z,writeMachineCredential as ee}from"../../../daemon/machine-link/config.js";import{ensureSignedIn as te}from"../../../core/directory/person-credential.js";import{showSignIn as re}from"../signin.js";import{insideBroker as ne}from"../join.js";import{resume as ie}from"./resume.js";const oe=l(t=>new Promise(s=>setTimeout(s,t)),"sleep");async function se(t,s,i){t==="hosted"&&s||await i.endDaemonDrain().catch(a=>{process.stderr.write(`warning: the daemon is still drained (ending the drain failed: ${a instanceof Error?a.message:String(a)}); run \`crtr sys import resume\` to end it
|
|
2
|
+
`)})}l(se,"settleDrainAtEnd");const ae=M({name:"start",description:"export this runtime into the person's Northlight account",whenToUse:"moving this computer or hosted runtime's canvas into Northlight: run it from a plain terminal, not from inside a node",help:{name:"sys import start",summary:"upload blobs, pause the daemon, dump rows and file stores, and have the control plane load them",guide:"Order: open the run, upload blobs and request files, drain the daemon (turns in flight settle for up to 10 minutes), upload them again, dump every table and the file stores, upload the rows, request the load and wait for `done` or `failed`, printing each step's time. On `done` for a machine this install writes `runtime/machine.json` and the machine credential and restarts the daemon in machine role. On `done` for a hosted runtime the daemon stays drained, because hosting quiesces and stops the VM next and a turn taken in between would write rows the shared database never sees. On `failed`, or any error, the drain ends and this runtime carries on as before; a killed exporter never ends it, so `crtr sys import resume` does. A machine import signs the person in first when this computer has no stored sign-in (`crtr sys signin`: a code and the directory's confirm page, which you allow in the browser) and uses that sign-in's bearer. With `--run`, hosting has already opened the run: the exporter uses it and reads that run's import token from the environment variable `CRTR_IMPORT_TOKEN`.",params:[{kind:"flag",name:"api-url",type:"string",required:!0,constraint:"Control plane base URL (http or https)."},{kind:"flag",name:"kind",type:"enum",choices:["machine","hosted"],required:!0,constraint:"`machine` for a person's computer, `hosted` for a hosted runtime (also archives running nodes' files)."},{kind:"flag",name:"run",type:"string",required:!1,constraint:"An import run hosting opened for this runtime (only with `--kind hosted`); its import token is read from `CRTR_IMPORT_TOKEN`."},{kind:"flag",name:"directory-url",type:"string",required:!1,constraint:"Directory base URL (http or https) the person signs in against; required with `--kind machine` (stored in `runtime/machine.json`). Not used with `--kind hosted`."}],output:[{name:"run_id",type:"string",required:!0,constraint:"The import run."},{name:"state",type:"string",required:!0,constraint:"Final state of the run, `done`."},{name:"timings",type:"string",required:!0,constraint:"Per-step seconds."}],outputKind:"object",effects:["Drains the local daemon (no new wake or cron fire) until the load finishes; a failed run ends the drain. A hosted run that reaches `done` leaves the daemon drained (the drain is a `runtime_state` row, so it survives a daemon restart) until `crtr sys import resume`.","Machine, on `done`: writes `runtime/machine.json` and `runtime/secrets/machine-credential` (0600) and restarts the daemon in machine role.","Opens a temporary directory holding the opened file stores (0600), removed before the command ends."]},run:l(async t=>{const s=String(t["api-url"]),i=t.kind,a=t.run===void 0?void 0:String(t.run);if(a!==void 0&&i!=="hosted")throw k("--run takes an existing run, which only a hosted import has: use --kind hosted");const g=t["directory-url"]===void 0?void 0:String(t["directory-url"]);if(i==="machine"&&g===void 0)throw k("--kind machine needs --directory-url: the directory the person signs in against");if(ne())throw x("import_inside_node: run this from a plain terminal; a node's broker ends with the daemon restart",{code:"import_inside_node"});const v=(a===void 0?await te(g,re):process.env.CRTR_IMPORT_TOKEN??"").trim();if(v==="")throw k("set CRTR_IMPORT_TOKEN to the run's import token");const m=new P(s,v),R=[];let y=Date.now();const u=l(c=>{const r=Date.now();R.push(`${c}=${((r-y)/1e3).toFixed(1)}s`),process.stderr.write(`${c} ${((r-y)/1e3).toFixed(1)}s
|
|
3
|
+
`),y=r},"step"),n=B(o(E(),"crtr-import-")),O=U({local:!0});let _=!1,T=!1;try{const{installId:c}=await Q(),r=a===void 0?await m.start({source_install_id:c,source_kind:i,release:J()}):{run_id:a},d=new L(m,r.run_id,e=>process.stderr.write(`${e}
|
|
4
|
+
`));await d.upload(I()),await G(n);for(const e of w(o(n,"rows","bodies")))await d.uploadBytes(h.bodyRows(r.run_id,e.replace(/\.jsonl\.zst$/,"")),f(o(n,"rows","bodies",e)));u("blobs"),await O.drainDaemon({settle_limit_ms:6e5}),_=!0,u("drain"),await d.upload(I());const b=await H(n,i),S=[];for(const e of b.requests){const p=z(e.id,e.dir,e.file);p!==null&&S.push(p)}await d.upload(S),u("dump");for(const e of w(o(n,"rows"),{withFileTypes:!0}))e.isFile()&&await d.uploadBytes(h.rows(r.run_id,e.name.replace(/\.jsonl\.zst$/,"")),f(o(n,"rows",e.name)));const $=new Set(w(o(n,"rows","bodies")).filter(e=>e.startsWith("pre-")));for(const e of w(o(n,"rows","bodies")))$.has(e)||await d.uploadBytes(h.bodyRows(r.run_id,e.replace(/\.jsonl\.zst$/,"")),f(o(n,"rows","bodies",e)));const D=[];if(i==="hosted")for(const e of b.runningNodes){const{data:p,prefixes:q}=await V(e);D.push(...q),await d.uploadBytes(h.nodeFiles(r.run_id,e.node),p)}await d.uploadBytes(h.source(r.run_id),Buffer.from(JSON.stringify(W(b.source,D))));const N=JSON.parse(f(o(n,A),"utf8"));for(const e of K)await m.putStore(r.run_id,e,N[e]);for(u("upload"),await m.load(r.run_id);;){const e=await m.get(r.run_id);if(e.state==="done")break;if(e.state==="failed"||e.state==="rolled_back")throw x(`the import failed at ${e.failure?.step??e.step??"an unknown step"}: ${e.failure?.message??e.state}`,{code:e.failure?.code??"import_failed"});await oe(2e3)}if(u("load"),T=!0,i==="machine"){const e=await m.machineCredential(r.run_id);ee(e.credential),Z({apiUrl:s,environmentId:e.environment_id,runId:r.run_id,directoryUrl:g}),_=!1,await Y(),await X(),u("restart")}return{run_id:r.run_id,state:"done",timings:R.join(" ")}}finally{C(n,{recursive:!0,force:!0}),_&&await se(i,T,O)}},"run"),render:l(t=>`<import run="${t.run_id}" state="${t.state}" timings="${t.timings}"/>`,"render")}),De=F({name:"import",description:"move this runtime into the person's Northlight account",whenToUse:"importing this computer or hosted runtime into Northlight",help:{name:"sys import",summary:"export this runtime to Northlight",model:"The exporter pauses the local daemon, uploads a bundle the control plane loads into the shared database, and on a computer switches it to the machine role."},children:[ae,ie]});export{se as settleDrainAtEnd,De as sysImportBranch};
|
|
@@ -1 +1,3 @@
|
|
|
1
|
+
/** Whether a broker (a node's engine) is an ancestor of this process, or cannot be ruled out. Stopping the daemon kills every broker, so a join run from one would die mid-way. */
|
|
2
|
+
export declare function insideBroker(): boolean;
|
|
1
3
|
export declare const sysJoinLeaf: import("../../core/command.js").LeafDef;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var a=Object.defineProperty;var n=(e,r)=>a(e,"name",{value:r,configurable:!0});import{defineLeaf as o}from"../../core/command.js";import{existsSync as s}from"node:fs";import{join as d}from"node:path";import{pluginsDir as l}from"../../core/scope.js";import{joinDirectory as c}from"../../core/directory/join.js";import{apiSocketPath as u}from"../../core/canvas/paths.js";import{listProcessTable as h}from"../../core/canvas/pid.js";import{daemonAnswers as m}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=h();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"&&s("/run/crtr/launcher.sock")))return{running:n(async()=>f()||w().length>0||await m(u()),"running"),stop:n(async()=>{await g()},"stop"),start:n(async()=>{await y()},"start"),firstSync:n(async()=>{const e=l("user"),r=Date.now()+2e4;for(;e&&Date.now()<r&&!s(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. It writes no settings and touches no canvas.db: the daemon's next start replaces this install's person with the owner everywhere in canvas.db, sets the user settings `runtime.issuer` and `runtime.audience` from the key file, and rewrites the legacy local person's profile grantees to `app:terminal`, all 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 change. 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 this install's own person at the next daemon start."},{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 the legacy local person's profile grantees to `app:terminal`.","At the next daemon start, before any listener, the person this install minted (or still the legacy local person) is replaced by the owner in every canvas.db column that holds a person: node, token, grant, cron, audit and usage subjects and grantees, the person's identity, and queued cron births. References to the install's local terminal grant are cleared and that local grant mirror is deleted so grant sync installs the directory's. Free text is unchanged. The start after that changes nothing."]},run:n(async e=>({...await c(e.directory,e.token,e.name||void 0,b()),canvas_rewrite:"next daemon start"}),"run")});export{T as sysJoinLeaf};
|
|
1
|
+
var a=Object.defineProperty;var n=(e,r)=>a(e,"name",{value:r,configurable:!0});import{defineLeaf as o}from"../../core/command.js";import{existsSync as s}from"node:fs";import{join as d}from"node:path";import{pluginsDir as l}from"../../core/scope.js";import{joinDirectory as c}from"../../core/directory/join.js";import{apiSocketPath as u}from"../../core/canvas/paths.js";import{listProcessTable as h}from"../../core/canvas/pid.js";import{daemonAnswers as m}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=h();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"&&s("/run/crtr/launcher.sock")))return{running:n(async()=>f()||w().length>0||await m(u()),"running"),stop:n(async()=>{await g()},"stop"),start:n(async()=>{await y()},"start"),firstSync:n(async()=>{const e=l("user"),r=Date.now()+2e4;for(;e&&Date.now()<r&&!s(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. It writes no settings and touches no canvas.db: the daemon's next start replaces this install's person with the owner everywhere in canvas.db, sets the user settings `runtime.issuer` and `runtime.audience` from the key file, and rewrites the legacy local person's profile grantees to `app:terminal`, all 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 change. 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 this install's own person at the next daemon start."},{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 the legacy local person's profile grantees to `app:terminal`.","At the next daemon start, before any listener, the person this install minted (or still the legacy local person) is replaced by the owner in every canvas.db column that holds a person: node, token, grant, cron, audit and usage subjects and grantees, the person's identity, and queued cron births. References to the install's local terminal grant are cleared and that local grant mirror is deleted so grant sync installs the directory's. Free text is unchanged. The start after that changes nothing."]},run:n(async e=>({...await c(e.directory,e.token,e.name||void 0,b()),canvas_rewrite:"next daemon start"}),"run")});export{k as insideBroker,T as sysJoinLeaf};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sysMachineBranch: import("../../../core/command.js").BranchDef;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var c=Object.defineProperty;var r=(e,n)=>c(e,"name",{value:n,configurable:!0});import{defineBranch as h,defineLeaf as d}from"../../../core/command.js";import{general as o,usage as m}from"../../../core/errors.js";import{personBearer as l}from"../../../core/directory/person-credential.js";import{readMachineConfig as p,writeMachineCredential as u}from"../../../daemon/machine-link/config.js";const w=d({name:"rotate",description:"replace this Mac's machine credential",whenToUse:"the credential this imported Mac presents on its link to Northlight may have leaked, or the console revoked it",help:{name:"sys machine rotate",summary:"ask the control plane for a new machine credential for this Mac and store it",params:[],output:[{name:"environment_id",type:"string",required:!0,constraint:"The machine environment the new credential belongs to."}],outputKind:"object",effects:["Calls `POST /v1/imports/<run>/machine-credential?rotate=1`, which replaces the stored hash for this machine environment, then writes the new secret to `runtime/secrets/machine-credential` (mode 0600). Authenticates with the person's sign-in (`crtr sys signin`; `not_signed_in` without one). Fails when `runtime/machine.json` is missing: this install was not imported as a machine.","The old credential stops opening links as soon as the control plane has replaced it, and the link it already holds is closed within 15 seconds; the Mac reconnects with the new credential."]},run:r(async()=>{const e=p();if(e===void 0)throw m("this install was not imported as a machine: runtime/machine.json is missing");const n=await l(e.directoryUrl),s=new URL(`/v1/imports/${encodeURIComponent(e.runId)}/machine-credential?rotate=1`,e.apiUrl),i=await fetch(s,{method:"POST",headers:{authorization:`Bearer ${n}`},signal:AbortSignal.timeout(3e4)}),a=await i.text();if(!i.ok)throw o(`the control plane answered ${i.status}: ${a.slice(0,300)}`);const t=JSON.parse(a);if(typeof t.credential!="string"||typeof t.environment_id!="string")throw o("the control plane answered without a credential");return u(t.credential),{environment_id:t.environment_id}},"run"),render:r(e=>`<machine environment="${e.environment_id}" credential="rotated"/>`,"render")}),k=h({name:"machine",description:"this Mac's link to Northlight after import",whenToUse:"an imported Mac's machine credential must be replaced",help:{name:"sys machine",summary:"manage the machine credential of an imported Mac",model:"After import the Mac runs `crtrd --role machine`: it holds one outbound link to Northlight, authenticated by a machine credential stored in `runtime/secrets/machine-credential`, and executes the runner calls Northlight sends over it."},children:[w]});export{k as sysMachineBranch};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type SignInPrompt } from '../../core/directory/person-credential.js';
|
|
2
|
+
/** Print the pairing code and open the confirm page: what the person sees while `signIn` polls. */
|
|
3
|
+
export declare function showSignIn(prompt: SignInPrompt & {
|
|
4
|
+
complete: string;
|
|
5
|
+
}): void;
|
|
6
|
+
export declare const sysSigninLeaf: import("../../core/command.js").LeafDef;
|
|
7
|
+
export declare const sysSignoutLeaf: import("../../core/command.js").LeafDef;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var s=Object.defineProperty;var n=(e,t)=>s(e,"name",{value:t,configurable:!0});import{spawn as i}from"node:child_process";import{defineLeaf as o}from"../../core/command.js";import{usage as a}from"../../core/errors.js";import{readPersonCredential as d,signIn as c,signOut as u}from"../../core/directory/person-credential.js";import{readMachineConfig as h}from"../../daemon/machine-link/config.js";function l(e){const[t,r]=process.platform==="darwin"?["open",[e]]:["xdg-open",[e]];try{i(t,r,{stdio:"ignore",detached:!0}).on("error",()=>{}).unref()}catch{}}n(l,"openBrowser");function p(e){process.stderr.write(`Confirm this code at ${e.verificationUri}: ${e.userCode}
|
|
2
|
+
`),l(e.complete)}n(p,"showSignIn");const k=o({name:"signin",description:"sign this computer's crtr in to the person's Northlight account",whenToUse:"this computer was imported into Northlight, so its `crtr` commands go to the control plane and answer `not_signed_in` until the person has signed in; or the sign-in was revoked or ended",help:{name:"sys signin",summary:"pair this computer with the directory in the browser and store a revocable credential",guide:"Prints a code and opens the directory's confirm page; sign in there if asked, check the code and click Allow. The command waits for that. The credential does not end on a fixed date: it ends after 90 days without use, or when you revoke it on the account page or run `crtr sys signout`. `crtr sys import start` signs in itself when none is stored.",params:[{kind:"flag",name:"directory-url",type:"string",required:!1,constraint:"Directory base URL (http or https). Default: the one in `runtime/machine.json`, else the one of the stored sign-in."}],output:[{name:"account",type:"string",required:!0,constraint:"The account this computer is now signed in as."}],outputKind:"object",effects:["Calls the directory (`POST /v1/developer/logins`, then polls `/v1/developer/logins/poll`) and, once approved, writes `runtime/secrets/person-credential` (0600) and removes any cached bearer. An abandoned or declined sign-in writes nothing and leaves an existing sign-in as it was."]},run:n(async e=>{const t=e["directory-url"]===void 0?(h()??d())?.directoryUrl:String(e["directory-url"]);if(t===void 0)throw a("name the directory: pass --directory-url");const{account:r}=await c(t,p);return{account:r}},"run"),render:n(e=>`<signin account="${e.account}"/>`,"render")}),b=o({name:"signout",description:"sign this computer's crtr out and revoke its credential",whenToUse:"this computer should stop acting as the person on Northlight, or its stored sign-in may have leaked",help:{name:"sys signout",summary:"revoke the stored credential at the directory and delete it from this computer",params:[],output:[{name:"signed_out",type:"boolean",required:!0,constraint:"False when no sign-in was stored."},{name:"revoked",type:"boolean",required:!0,constraint:"True when the directory ended the credential now; false when it was already ended or none was stored."}],outputKind:"object",effects:["Calls `POST /v1/developer/token/revoke` with the stored secret, then deletes `runtime/secrets/person-credential` and `person-bearer.json`. A bearer already issued stays valid for at most 5 minutes. After this, `crtr` on an imported computer answers `not_signed_in`."]},run:n(async()=>{const{signedOut:e,revoked:t}=await u();return{signed_out:e,revoked:t}},"run"),render:n(e=>`<signout signed_out="${e.signed_out}" revoked="${e.revoked}"/>`,"render")});export{p as showSignIn,k as sysSigninLeaf,b as sysSignoutLeaf};
|
package/dist/commands/sys.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=Object.defineProperty;var r=(o,m)=>s(o,"name",{value:m,configurable:!0});import{defineSysBranch as t}from"./sys/branch.js";import{configBranch as f}from"./sys/config.js";import{sysEnvBranch as
|
|
1
|
+
var s=Object.defineProperty;var r=(o,m)=>s(o,"name",{value:m,configurable:!0});import{defineSysBranch as t}from"./sys/branch.js";import{configBranch as f}from"./sys/config.js";import{sysEnvBranch as i}from"./sys/env.js";import{daemonBranch as e}from"./sys/daemon.js";import{sysDoctorLeaf as a}from"./sys/doctor.js";import{sysFeedbackLeaf as p}from"./sys/feedback.js";import{sysLogsLeaf as n}from"./sys/logs.js";import{sysRetryDeliveryLeaf as y}from"./sys/retry-delivery.js";import{sysWorktreesLeaf as c}from"./sys/worktrees.js";import{sysSpacesLeaf as L}from"./sys/spaces.js";import{sysLimitsBranch as h}from"./sys/limits.js";import{sysSpendBranch as B}from"./sys/spend.js";import{sysSettingsLeaf as S}from"./sys/settings.js";import{sysConnectLeaf as g}from"./sys/connect.js";import{sysSetupLeaf as u}from"./sys/setup.js";import{tutorialBranch as d}from"./sys/tutorial/branch.js";import{supportBranch as D}from"./sys/support.js";import{contextBranch as b}from"./sys/context.js";import{sysSyncBranch as k}from"./sys/sync.js";import{sysMigrateLeaf as l}from"./sys/migrate.js";import{sysJoinLeaf as v}from"./sys/join.js";import{sysDbBranch as x}from"./sys/db.js";import{sysImportBranch as M}from"./sys/import/start.js";import{sysMachineBranch as C}from"./sys/machine/rotate.js";import{sysSigninLeaf as E,sysSignoutLeaf as F}from"./sys/signin.js";import{sysUpdateLeaf as I,sysVersionLeaf as J}from"./sys/update.js";function fr(){return t([S,f,i,g,u,d,a,b,p,n,c,y,L,h,B,D,I,J,l,v,k,x,M,C,E,F,e])}r(fr,"registerSys");export{fr as registerSys};
|
package/dist/core/blobs/r2.d.ts
CHANGED
|
@@ -7,3 +7,7 @@ export interface R2Config {
|
|
|
7
7
|
}
|
|
8
8
|
/** S3-compatible object store (R2) over `fetch`: path-style requests signed with SigV4, region `auto`. */
|
|
9
9
|
export declare function createR2BlobStore(config: R2Config): BlobStore;
|
|
10
|
+
/** The R2 settings of this process, or `null` when it uses the directory store. Same variables as `blobStore()`. */
|
|
11
|
+
export declare function r2ConfigFromEnv(): R2Config | null;
|
|
12
|
+
/** A SigV4 query-signed URL that lets its holder PUT one object at `key` until `expiresSeconds` pass. */
|
|
13
|
+
export declare function presignPutUrl(config: R2Config, key: string, expiresSeconds: number, now?: Date): string;
|
package/dist/core/blobs/r2.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
var
|
|
2
|
-
`).join(""),
|
|
3
|
-
`),
|
|
4
|
-
`),
|
|
1
|
+
var q=Object.defineProperty;var c=(e,a)=>q(e,"name",{value:a,configurable:!0});import{createHash as z,createHmac as R}from"node:crypto";import{assertBlobKey as y}from"./index.js";import{blobFault as I}from"./fault.js";const E=c(e=>z("sha256").update(e).digest("hex"),"sha256"),p=c((e,a)=>R("sha256",e).update(a).digest(),"hmac"),f=c(e=>encodeURIComponent(e).replace(/[!'()*]/g,a=>`%${a.charCodeAt(0).toString(16).toUpperCase()}`),"encode");function X(e){const a=new URL(e.endpoint);async function i(t,s,n,r,o){const m=`/${f(e.bucket)}${s===""?"":`/${s.split("/").map(f).join("/")}`}`,l=[...n].sort(([h],[C])=>h<C?-1:1).map(([h,C])=>`${f(h)}=${f(C)}`).join("&"),g=new Date().toISOString().replace(/[:-]|\.\d{3}/g,""),w=g.slice(0,8),S=E(r??""),d={host:a.host,"x-amz-content-sha256":S,"x-amz-date":g},$=Object.keys(d).sort(),A=$.map(h=>`${h}:${d[h]}
|
|
2
|
+
`).join(""),_=$.join(";"),T=`${w}/auto/s3/aws4_request`,B=[t,m,l,A,_,S].join(`
|
|
3
|
+
`),K=["AWS4-HMAC-SHA256",g,T,E(B)].join(`
|
|
4
|
+
`),H=p(p(p(p(`AWS4${e.secretAccessKey}`,w),"auto"),"s3"),"aws4_request"),j=R("sha256",H).update(K).digest("hex");d.authorization=`AWS4-HMAC-SHA256 Credential=${e.accessKeyId}/${T}, SignedHeaders=${_}, Signature=${j}`;const{host:U,...x}=d;return o&&(x.range=`bytes=${o.start}-${o.start+o.length-1}`),await fetch(`${a.origin}${m}${l?`?${l}`:""}`,{method:t,headers:x,...r?{body:new Uint8Array(r)}:{}})}c(i,"send");async function u(t,s){if(!t.ok)throw new Error(`R2 ${s} failed (${t.status}): ${(await t.text()).slice(0,300)}`);return t}return c(u,"ok"),{async put(t,s){I("put"),y(t),await u(await i("PUT",t,[],typeof s=="string"?Buffer.from(s,"utf8"):Buffer.from(s)),`put ${t}`)},async get(t,s){y(t);const n=await i("GET",t,[],void 0,s);return n.status===416?(await n.arrayBuffer(),Buffer.alloc(0)):n.status===404?(await n.arrayBuffer(),null):Buffer.from(await(await u(n,`get ${t}`)).arrayBuffer())},async list(t){const s=[];let n;do{const r=[["list-type","2"],["prefix",t],...n?[["continuation-token",n]]:[]],o=await(await u(await i("GET","",r),`list ${t}`)).text();for(const m of o.matchAll(/<Key>([^<]*)<\/Key>/g))s.push(m[1].replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'"));n=/<IsTruncated>true<\/IsTruncated>/.test(o)?/<NextContinuationToken>([^<]*)<\/NextContinuationToken>/.exec(o)?.[1]:void 0}while(n);return s.sort((r,o)=>Buffer.compare(Buffer.from(r),Buffer.from(o)))},async delete(t){y(t);const s=await i("DELETE",t,[]);s.status!==404?await u(s,`delete ${t}`):await s.arrayBuffer()}}}c(X,"createR2BlobStore");function O(){const e=process.env.CRTR_R2_BUCKET;if(!e)return null;const a=c(i=>{const u=process.env[i];if(!u)throw new Error(`${i} is required when CRTR_R2_BUCKET is set`);return u},"need");return{bucket:e,endpoint:a("CRTR_R2_ENDPOINT"),accessKeyId:a("CRTR_R2_ACCESS_KEY_ID"),secretAccessKey:a("CRTR_R2_SECRET_ACCESS_KEY")}}c(O,"r2ConfigFromEnv");function P(e,a,i,u=new Date){y(a);const t=new URL(e.endpoint),s=`/${f(e.bucket)}/${a.split("/").map(f).join("/")}`,n=u.toISOString().replace(/[:-]|\.\d{3}/g,""),r=n.slice(0,8),o=`${r}/auto/s3/aws4_request`,l=[["X-Amz-Algorithm","AWS4-HMAC-SHA256"],["X-Amz-Credential",`${e.accessKeyId}/${o}`],["X-Amz-Date",n],["X-Amz-Expires",String(i)],["X-Amz-SignedHeaders","host"]].sort(([$],[A])=>$<A?-1:1).map(([$,A])=>`${f($)}=${f(A)}`).join("&"),g=["PUT",s,l,`host:${t.host}
|
|
5
|
+
`,"host","UNSIGNED-PAYLOAD"].join(`
|
|
6
|
+
`),w=["AWS4-HMAC-SHA256",n,o,E(g)].join(`
|
|
7
|
+
`),S=p(p(p(p(`AWS4${e.secretAccessKey}`,r),"auto"),"s3"),"aws4_request"),d=R("sha256",S).update(w).digest("hex");return`${t.origin}${s}?${l}&X-Amz-Signature=${d}`}c(P,"presignPutUrl");export{X as createR2BlobStore,P as presignPutUrl,O as r2ConfigFromEnv};
|
|
@@ -15,7 +15,7 @@ export interface DaemonOwnerClaim {
|
|
|
15
15
|
* process running both roles); the cloud's `--role api` and `--role reconcile` processes are several per database by design, so
|
|
16
16
|
* they never claim, never release, and never read the row. The caller (`crtrd.ts`) checks this before
|
|
17
17
|
* `claimDaemonOwnership` / `releaseDaemonOwnership` / `daemonOwnershipClaimed`. */
|
|
18
|
-
export declare function daemonOwnershipApplies(role: 'api' | 'reconcile' | undefined): boolean;
|
|
18
|
+
export declare function daemonOwnershipApplies(role: 'api' | 'reconcile' | 'machine' | undefined): boolean;
|
|
19
19
|
/** Every terminal, non-retriable way `claimDaemonOwnership` can lose. A loss
|
|
20
20
|
* never deletes or otherwise mutates the incumbent row — only a positively
|
|
21
21
|
* DEAD incumbent is ever CAS-replaced. */
|
|
@@ -8,7 +8,7 @@ export interface Execution {
|
|
|
8
8
|
startedAt: string;
|
|
9
9
|
endedAt: string | null;
|
|
10
10
|
exitReason: string | null;
|
|
11
|
-
runner: 'process' | 'blaxel';
|
|
11
|
+
runner: 'process' | 'blaxel' | 'machine';
|
|
12
12
|
sandboxName: string | null;
|
|
13
13
|
attempt: number;
|
|
14
14
|
/** The release the wake accepted. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var V=Object.defineProperty;var d=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as D,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{canvasSearchPathOptions as ee}from"./schema-version.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as te,OBJECT_COLUMNS as ne,pgColumns as se,quote as p,TABLE_ORDER as C}from"./import-sqlite.js";const
|
|
1
|
+
var V=Object.defineProperty;var d=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as D,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{canvasSearchPathOptions as ee}from"./schema-version.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as te,OBJECT_COLUMNS as ne,pgColumns as se,quote as p,TABLE_ORDER as C}from"./import-sqlite.js";const oe=5e3,re=new Set(["environments","relations","schemas","charges","charge_totals","limits","viewer_attachments","reconciler_leases","environment_repos","conversation_entries","conversation_parts","charge_chunks","profiles","profile_projects","profile_defaults","scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins","person_settings","model_accounts","model_account_cooldowns","model_api_keys","model_settings","model_sign_in_flows","env_vars","import_runs","import_key_map","import_manifest","import_id_map","machine_links","machine_calls","machine_credentials","import_node_files","bodies","usage","grantee_usage","provider_receipts"]),ae={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ie={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated",te,F]);function ce(i){const a=J(A(Q(),"crtr-export-")),s=process.env.CRTR_HOME;process.env.CRTR_HOME=a,D(A(a,"runtime","nodes"),{recursive:!0});try{const o=new W(i);return o.exec("PRAGMA journal_mode = DELETE;"),o.exec("PRAGMA foreign_keys = ON;"),z(o),o.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),o}finally{s===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=s,P(a,{recursive:!0,force:!0})}}d(ce,"createEmptyDatabase");async function Re(i,a=()=>{}){const s=Z();if(Y(s))throw new Error(`${s} already exists; refusing to overwrite it`);D(A(s,".."),{recursive:!0,mode:448});const o=`${s}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:d(((f,r)=>f===20?w=>Number(w):j.types.getTypeParser(f,r)),"getTypeParser")}});await t.connect();let _;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");_=ce(o);const r=_;r.exec("PRAGMA foreign_keys = OFF;");const w=await se(t),L=[...w.keys()].filter(e=>!re.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const E={};for(const[e,S]of Object.entries(ie)){const h=new Map;for(const l of(await t.query(`SELECT ${S} AS id, seq FROM ${e}`)).rows)h.set(l.id,Number(l.seq));E[e]=h}const B=E.objects;let M=0;r.exec("BEGIN");for(const e of C){const S=Date.now(),h=new Set(r.prepare(`PRAGMA table_info(${p(e)})`).all().map(n=>n.name)),l=w.get(e);if(l===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=l.some(n=>n.name==="seq"),O=l.filter(n=>h.has(n.name)),K=new Set(ne[e]??[]),g=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",$=O.map(n=>n.type==="jsonb"?`${p(n.name)}::text AS ${p(n.name)}`:p(n.name));e==="watches"&&$.push("watch_id AS _watch_id"),g&&$.push("seq AS _seq");const R=O.map(n=>p(n.name)),U=r.prepare(`INSERT INTO ${p(e)} (${g?["rowid",...R]:R}) VALUES (${(g?["?",...R]:R).map(()=>"?").join(", ")})`),k=G?"seq":ae[e],b=`export_${e}`;await t.query(`DECLARE ${b} NO SCROLL CURSOR FOR SELECT ${$.join(", ")} FROM ${p(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${k===void 0?"":` ORDER BY ${k}`}`);let T=0;for(;;){const n=await t.query(`FETCH ${oe} FROM ${b}`);if(n.rows.length===0)break;for(const q of n.rows){const N=g?[Number(q._seq)]:[];for(const c of O){let m=q[c.name];e==="revision_parents"&&(c.name==="child"||c.name==="parent")?m=x(E.revisions,"revision",e,c.name,m):e==="watch_subscriptions"&&c.name==="watch"?m=x(E.watches,"watch",e,c.name,m):K.has(c.name)?m=x(B,"object",e,c.name,m):e==="jobs"&&(c.name==="job_dir"||c.name==="log_path")&&(m=m??""),N.push(m??null)}U.run(...N)}T+=n.rows.length}await t.query(`CLOSE ${b}`),M+=T,a(`export ${e}: ${T} rows in ${Date.now()-S} ms`)}r.exec("COMMIT"),await t.query("COMMIT");const y=r.prepare("PRAGMA foreign_key_check").all();if(y.length>0)throw new Error(`export: ${y.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(y[0])}`);await me(t,r);const v=r.prepare("PRAGMA user_version").get().user_version;if(v!==I.length)throw new Error(`export: exported canvas.db is at user_version ${v}, expected ${I.length}`);return r.close(),_=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:s})]),X(o,s),{path:s,rows:M,ms:Date.now()-u,userVersion:v}}catch(f){await t.query("ROLLBACK").catch(()=>{});try{_?.close()}catch{}for(const r of["","-wal","-shm","-journal"])P(`${o}${r}`,{force:!0});throw f}finally{await t.end().catch(()=>{})}}d(Re,"exportPostgresToSqlite");function x(i,a,s,o,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${s}.${o} references ${a} ${String(u)}, which was not exported (`);return t}d(x,"mapId");async function me(i,a){const s=[];for(const o of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${p(o)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${p(o)}`).get().n),_=u;o==="canvas_meta"&&(_=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),_!==t&&s.push(`${o}: Postgres ${_} rows, SQLite ${t}`)}if(s.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
|
|
2
2
|
${s.join(`
|
|
3
3
|
`)}`)}d(me,"verify");export{Re as exportPostgresToSqlite};
|
|
@@ -10,6 +10,8 @@ export interface NodePrincipal {
|
|
|
10
10
|
scopes: string[] | null;
|
|
11
11
|
}
|
|
12
12
|
export declare function mintNodeToken(db: CanvasDb, executionId: string, nodeId: string, issuedAt: string): Promise<string>;
|
|
13
|
+
/** Mints the node token of a `launching` execution in its own canvas write: what a runner's `start` hands to the process it launches. */
|
|
14
|
+
export declare const mintExecutionToken: (executionId: string, nodeId: string) => Promise<string>;
|
|
13
15
|
/** A stopped or superseded execution can never authenticate, even after restart. */
|
|
14
16
|
export declare function resolveNodeToken(token: string): Promise<NodePrincipal | null>;
|
|
15
17
|
export declare function revokeExitedNodeToken(executionId: string): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var E=Object.defineProperty;var r=(n,e)=>E(n,"name",{value:e,configurable:!0});import{createHash as g,randomBytes as l}from"node:crypto";import{openDb as s}from"./db.js";import{terminalGrant as
|
|
1
|
+
var E=Object.defineProperty;var r=(n,e)=>E(n,"name",{value:e,configurable:!0});import{createHash as g,randomBytes as l}from"node:crypto";import{openDb as s,withCanvasWrite as k}from"./db.js";import{terminalGrant as w}from"../grants/mirror.js";import{personIdAsync as N}from"./person.js";import{uidForApp as m}from"../spaces/users.js";const p=r(n=>g("sha256").update(n).digest("hex"),"hash");async function S(n,e,o,i){const t=await n.prepare("SELECT grantee, sub, grant_id, client_id, scopes_json, run_id FROM node_rows WHERE node_id = ?").get(o);if(!t)throw new Error(`cannot mint node token: missing node ${o}`);const a=t.run_id&&t.run_id!==o?await n.prepare("SELECT sub, grant_id, client_id FROM nodes WHERE node_id = ?").get(t.run_id):void 0,d=a?.sub??t.sub??await N(),c=t.grantee==="app:terminal"?await w(d):null;if(t.grantee==="app:terminal"&&!c)throw new Error("cannot mint node token: terminal grant is not active");const u=c?.id??a?.grant_id??t.grant_id??null,_=l(32).toString("base64url");if(!await n.prepare("SELECT 1 AS ok FROM executions WHERE execution_id = ? AND node_id = ? AND state = 'launching'").get(e,o))throw new Error(`cannot mint node token: execution ${e} is not launching`);return await n.prepare('INSERT INTO node_tokens (broker_process_id, node_id, token_hash, grantee, uid, sub, "grant", client_id, scopes_json, issued_at, revoked_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)').run(e,o,p(_),t.grantee,await m(t.grantee),d,u,a?.client_id??t.client_id,t.scopes_json,i),_}r(S,"mintNodeToken");const D=r(async(n,e)=>await k(o=>S(o,n,e,new Date().toISOString())),"mintExecutionToken");async function v(n){if(!/^[A-Za-z0-9_-]{43}$/.test(n))return null;const e=await(await s()).prepare(`SELECT t.node_id, t.grantee, t.uid, t.sub, t."grant", t.client_id, t.scopes_json, n.run_id
|
|
2
2
|
FROM node_tokens t JOIN executions e ON e.execution_id = t.broker_process_id
|
|
3
3
|
JOIN nodes n ON n.node_id = t.node_id
|
|
4
|
-
WHERE t.token_hash = ? AND t.revoked_at IS NULL AND e.state != 'exited'`).get(p(n));return e?{app:e.grantee,node:e.node_id,run:e.run_id,uid:e.uid,sub:e.sub,grant:e.grant,client_id:e.client_id,scopes:e.scopes_json===null?null:JSON.parse(e.scopes_json)}:null}r(
|
|
5
|
-
AND broker_process_id IN (SELECT execution_id FROM executions WHERE daemon_epoch IS DISTINCT FROM ?)`).run(new Date().toISOString(),n)}r(
|
|
4
|
+
WHERE t.token_hash = ? AND t.revoked_at IS NULL AND e.state != 'exited'`).get(p(n));return e?{app:e.grantee,node:e.node_id,run:e.run_id,uid:e.uid,sub:e.sub,grant:e.grant,client_id:e.client_id,scopes:e.scopes_json===null?null:JSON.parse(e.scopes_json)}:null}r(v,"resolveNodeToken");async function b(n){await h(await s(),"broker_process_id",n,new Date().toISOString())}r(b,"revokeExitedNodeToken");async function I(n){await(await s()).prepare(`UPDATE node_tokens SET revoked_at = ? WHERE revoked_at IS NULL
|
|
5
|
+
AND broker_process_id IN (SELECT execution_id FROM executions WHERE daemon_epoch IS DISTINCT FROM ?)`).run(new Date().toISOString(),n)}r(I,"revokePriorEpochNodeTokens");async function h(n,e,o,i){await n.prepareUncached(`UPDATE node_tokens SET revoked_at = ? WHERE ${e} = ? AND revoked_at IS NULL`).run(i,o)}r(h,"revokeNodeTokens");export{D as mintExecutionToken,S as mintNodeToken,v as resolveNodeToken,b as revokeExitedNodeToken,h as revokeNodeTokens,I as revokePriorEpochNodeTokens};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- 009-bodies.sql — cycle 5 D106: document revision text in the database. One transaction, additions only: `schema_min_reader` is not
|
|
2
|
+
-- touched and stays 2. The previous build (version 8) opens the result but never names `bodies`: it reads the body files of revisions written before this release, and answers `no body` for a revision written after it (no file is written). A rollback keeps old documents readable, not new ones.
|
|
3
|
+
--
|
|
4
|
+
-- A revision names its text by `body_hash`, the sha256 of the text. The text is written with `INSERT ... ON CONFLICT DO NOTHING` in the same
|
|
5
|
+
-- transaction as the revision that names it, so a rolled-back write leaves neither. No foreign key from `revisions.body_hash`: revisions
|
|
6
|
+
-- written before this release name text that exists only in a Mac's body files (or, in the cloud, nowhere). Nothing deletes a row (no GC).
|
|
7
|
+
CREATE TABLE IF NOT EXISTS bodies (
|
|
8
|
+
hash text PRIMARY KEY,
|
|
9
|
+
text text NOT NULL
|
|
10
|
+
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The canvas schema version this build supports: the number of the highest delta in `schema-pg/` (the baseline
|
|
2
2
|
* `schema-pg.sql` is version 1). `scripts/install-runtime.mjs` reads this module from a candidate generation to refuse
|
|
3
3
|
* one below the live database's `schema_min_reader`. */
|
|
4
|
-
export declare const CANVAS_SCHEMA_VERSION =
|
|
4
|
+
export declare const CANVAS_SCHEMA_VERSION = 9;
|
|
5
5
|
/** The Postgres schema holding the canvas tables: `CRTR_DATABASE_SCHEMA` (`crtr` in the cloud deploy). Unset or empty
|
|
6
6
|
* means the role's own search_path (`public` on a Mac and on a VM). A name that is not a plain identifier is an error:
|
|
7
7
|
* it is put in a `search_path` connection option. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=
|
|
1
|
+
var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=9;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CrtrError } from '../errors.js';
|
|
2
|
+
export declare const personCredentialPath: () => string;
|
|
3
|
+
export declare const personBearerPath: () => string;
|
|
4
|
+
/** What the credential file holds: the secret and the directory that issued it (a Mac needs the directory before it has a `machine.json`). */
|
|
5
|
+
export interface PersonCredential {
|
|
6
|
+
directoryUrl: string;
|
|
7
|
+
secret: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const notSignedIn: (why: string) => CrtrError;
|
|
10
|
+
export declare function readPersonCredential(): PersonCredential | undefined;
|
|
11
|
+
export interface SignInPrompt {
|
|
12
|
+
verificationUri: string;
|
|
13
|
+
userCode: string;
|
|
14
|
+
}
|
|
15
|
+
/** Pair this computer with the directory: start a developer login of kind `crtr`, show the code, poll until the person allows it, then store the secret.
|
|
16
|
+
* Nothing durable is written until the directory has approved, so an abandoned sign-in leaves the previous state untouched. */
|
|
17
|
+
export declare function signIn(directoryUrl: string, show: (prompt: SignInPrompt & {
|
|
18
|
+
complete: string;
|
|
19
|
+
}) => void, opts?: {
|
|
20
|
+
sleep?: (ms: number) => Promise<void>;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
account: string;
|
|
23
|
+
credentialId: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** A person bearer for the control plane: the cached one while it has more than 30 seconds left, else a fresh exchange of the stored secret.
|
|
26
|
+
* A revoked, expired or unknown credential is `not_signed_in`. */
|
|
27
|
+
export declare function personBearer(directoryUrl?: string): Promise<string>;
|
|
28
|
+
/** Revoke the stored credential at the directory (best effort when it is already gone) and delete both files. Returns whether one was stored. */
|
|
29
|
+
export declare function signOut(): Promise<{
|
|
30
|
+
signedOut: boolean;
|
|
31
|
+
revoked: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
/** The sign-in a command needs when the person has none yet: pairs once with `directoryUrl`, then returns a bearer. */
|
|
34
|
+
export declare function ensureSignedIn(directoryUrl: string, show: (prompt: SignInPrompt & {
|
|
35
|
+
complete: string;
|
|
36
|
+
}) => void): Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var B=Object.defineProperty;var o=(e,r)=>B(e,"name",{value:r,configurable:!0});import{createHash as C,randomBytes as H}from"node:crypto";import{renameSync as I,rmSync as h}from"node:fs";import{hostname as R}from"node:os";import{join as w}from"node:path";import{CrtrError as J,general as p,network as N}from"../errors.js";import{atomicWriteJson as S,readJsonOrNull as $}from"../fs-utils.js";import{secretsDir as _}from"../layout.js";import{ExitCode as A}from"../../types.js";const x=o(()=>w(_(),"person-credential"),"personCredentialPath"),y=o(()=>w(_(),"person-bearer.json"),"personBearerPath"),O=o(()=>w(_(),"person-credential.pending"),"pendingPath"),M=3e4,z=o(e=>C("sha256").update(e).digest("hex"),"sha256Hex"),b=o(e=>e.replace(/\/+$/,""),"trimUrl"),U=o(e=>new J("not_signed_in",e,A.USAGE,{next:"Run `crtr sys signin`, then retry."}),"notSignedIn");function v(){const e=$(x());if(!(e===null||typeof e.secret!="string"||typeof e.directoryUrl!="string"))return{directoryUrl:e.directoryUrl,secret:e.secret}}o(v,"readPersonCredential");function f(e,r){const n=e!==null&&typeof e=="object"?e:{},t=n.error;if(t!==null&&typeof t=="object"){const s=t;return{code:String(s.code??"error"),message:String(s.message??`HTTP ${r}`)}}return typeof t=="string"?{code:t,message:String(n.error_description??n.message??t)}:{code:"error",message:`the directory answered HTTP ${r}`}}o(f,"errorOf");async function m(e,r,n,t){let s;try{s=await fetch(`${b(e)}${r}`,{method:"POST",headers:{"content-type":"application/json",...t===void 0?{}:{authorization:`Bearer ${t}`}},body:JSON.stringify(n),signal:AbortSignal.timeout(3e4)})}catch(c){const l=c.cause;throw N(`could not reach the directory at ${e} (${l?.code??l?.message??c.message})`)}const u=await s.text();let a={};try{a=u===""?{}:JSON.parse(u)}catch{}return{status:s.status,body:a}}o(m,"call");const g=o((e,r)=>{const n=f(r,e);throw p(`the directory refused: ${n.message}`,{code:n.code})},"refuse");async function G(e,r,n={}){const t=b(e),s=n.sleep??(i=>new Promise(d=>setTimeout(d,i))),u=`nld_${H(32).toString("base64url")}`,a=await m(t,"/v1/developer/logins",{credential_hash:z(u),name:R().slice(0,64),platform:process.platform==="darwin"||process.platform==="win32"?process.platform:"linux",sdk_version:"crtr",kind:"crtr"});a.status!==200&&a.status!==201&&g(a.status,a.body);const c=a.body;r({verificationUri:c.verification_uri,userCode:c.user_code,complete:c.verification_uri_complete});let l=Math.max(1,c.interval)*1e3;const T=Date.now()+c.expires_in*1e3;for(;;){if(Date.now()>T)throw p("the sign-in code expired before it was allowed: run `crtr sys signin` again",{code:"expired_token"});await s(l);const i=await m(t,"/v1/developer/logins/poll",{device_code:c.device_code});if(i.status===200){const k=i.body,j=await E(t,u);return S(O(),{directoryUrl:t,secret:u},{mode:384}),I(O(),x()),D(t,j),{account:k.account.display,credentialId:k.credential_id}}const d=f(i.body,i.status).code;if(!(i.status===400&&d==="authorization_pending")){if(i.status===400&&d==="slow_down"){l+=5e3;continue}if(i.status===400&&d==="access_denied")throw p("the sign-in was declined in the browser",{code:d});if(i.status===400&&d==="expired_token")throw p("the sign-in code expired before it was allowed: run `crtr sys signin` again",{code:d});g(i.status,i.body)}}}o(G,"signIn");const P=new Set(["credential_revoked","credential_expired","credential_unknown"]);async function E(e,r,n=!1){const t=await m(e,"/v1/developer/token",{},r);if(t.status===401&&P.has(f(t.body,401).code))throw n&&h(y(),{force:!0}),U(`the sign-in on this computer is no longer valid (${f(t.body,401).code})`);t.status!==200&&g(t.status,t.body);const s=t.body;if(typeof s.access_token!="string"||typeof s.expires_in!="number")throw p("the directory answered without a bearer token");return{access_token:s.access_token,expires_in:s.expires_in}}o(E,"exchange");function D(e,r){S(y(),{directoryUrl:e,access_token:r.access_token,expires_at_ms:Date.now()+r.expires_in*1e3},{mode:384})}o(D,"writeBearer");async function F(e){const r=v();if(r===void 0)throw U("this computer is not signed in");const n=b(e??r.directoryUrl),t=$(y());if(t!==null&&t.directoryUrl===n&&typeof t.access_token=="string"&&t.expires_at_ms-Date.now()>M)return t.access_token;const s=await E(n,r.secret,!0);return D(n,s),s.access_token}o(F,"personBearer");async function ee(){const e=v();if(e===void 0)return{signedOut:!1,revoked:!1};let r=!1;const n=await m(e.directoryUrl,"/v1/developer/token/revoke",{},e.secret);return n.status===200?r=!0:n.status===401&&P.has(f(n.body,401).code)||g(n.status,n.body),h(x(),{force:!0}),h(y(),{force:!0}),{signedOut:!0,revoked:r}}o(ee,"signOut");async function te(e,r){return v()===void 0&&await G(e,r),F(e)}o(te,"ensureSignedIn");export{te as ensureSignedIn,U as notSignedIn,F as personBearer,y as personBearerPath,x as personCredentialPath,v as readPersonCredential,G as signIn,ee as signOut};
|
package/dist/core/feed/feed.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var O=Object.defineProperty;var i=(e,t)=>O(e,"name",{value:t,configurable:!0});import{cancelCronsOnWake as q,getNode as E}from"../canvas/index.js";import{runStructuredRequestEffects as
|
|
2
|
-
`)[0]??"";return r.length>t?`${r.slice(0,t-1)}\u2026`:r}i(S,"firstLine");function
|
|
1
|
+
var O=Object.defineProperty;var i=(e,t)=>O(e,"name",{value:t,configurable:!0});import{cancelCronsOnWake as q,getNode as E}from"../canvas/index.js";import{runStructuredRequestEffects as R,settleStructuredRequestIn as y}from"../canvas/structured-requests.js";import{afterCommit as x,openDb as F,withCanvasWrite as k}from"../canvas/db.js";import{brokerRelayGeneration as M,recordBrokerRelease as W}from"../runtime/lifecycle.js";import{randomUUID as z}from"node:crypto";import{composeNodeOutcomeDocument as A}from"../runtime/outcome-document.js";import{closeNode as L}from"../runtime/close.js";import{writeDocument as H}from"../graph/documents.js";import{asRuntimeWriter as j}from"../permissions/ceiling.js";import{publish as T}from"../graph/events.js";import{nodeObject as v}from"../graph/objects.js";import{readerForNode as B}from"../graph/reader.js";import{endMessageSubscriptions as C}from"../graph/watches.js";import{operationIdContext as D}from"../events/operation-id.js";class J extends Error{static{i(this,"StructuredRequestFinalizationError")}state;constructor(t){super(t==="expired"?"structured_request_expired":"structured_request_settled"),this.state=t}}class h extends Error{static{i(this,"FinalizationError")}code;report;constructor(t,r){super(t),this.code=t,this.report=r}}function b(e){const t=i((r,n=2)=>String(r).padStart(n,"0"),"pad");return`${e.getUTCFullYear()}${t(e.getUTCMonth()+1)}${t(e.getUTCDate())}T${t(e.getUTCHours())}${t(e.getUTCMinutes())}${t(e.getUTCSeconds())}${t(e.getUTCMilliseconds(),3)}`}i(b,"compactTs");function S(e,t=80){const r=e.split(`
|
|
2
|
+
`)[0]??"";return r.length>t?`${r.slice(0,t-1)}\u2026`:r}i(S,"firstLine");function Y(e){return e.kind==="final"?{event:"report.final"}:e.result!==void 0?{event:"report.result"}:e.kind==="urgent"?{event:"report.update",level:7}:{event:"report.update",level:e.deliveryTier==="deferred"?3:e.deliveryTier==="urgent"?7:5}}i(Y,"eventFor");async function U(e,t,r){const n=r.from??t,o=`reports/${b(new Date)}-${z().slice(-8)}-${r.kind}`,u=await v(e,t),c=await v(e,n),{object:d}=await H(e,j(await B(e,n,async()=>[])),{owner:u,kind:"report",name:o,preview:S(r.body)||r.kind,privacy:"public"},r.body),s=`${t}/${o}`,{event:p,level:m}=Y(r);await T(e,{event:p,object:u,actor:c,...m===void 0?{}:{level:m},attrs:{},pointer:d.id,messageBody:{kind:r.kind,label:S(r.body),bodyRef:s,...r.disposition===void 0?{}:{disposition:r.disposition}}});const l=(await e.prepare(`SELECT d.destination_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
3
3
|
JOIN objects receiver ON receiver.id = d.destination_id
|
|
4
|
-
WHERE m.body_ref = ? AND receiver.type = 'node' ORDER BY d.destination_id`).all(s)).map(w=>w.destination_id);return r.kind==="final"?await C(e,u,"final"):r.result?.requestMessageId&&await C(e,u,{result:r.result.requestMessageId}),{name:o,ref:s,doc:d,createdAt:d.created_at,deliveredTo:l}}i(U,"writeReport");async function
|
|
4
|
+
WHERE m.body_ref = ? AND receiver.type = 'node' ORDER BY d.destination_id`).all(s)).map(w=>w.destination_id);return r.kind==="final"?await C(e,u,"final"):r.result?.requestMessageId&&await C(e,u,{result:r.result.requestMessageId}),{name:o,ref:s,doc:d,createdAt:d.created_at,deliveredTo:l}}i(U,"writeReport");async function P(e,t){return k(async r=>{const n=await r.prepare("SELECT status, final_report FROM nodes WHERE node_id = ?").get(e);if(n===void 0)throw new h("unknown_node");const o=t.outcome;let u;if(o?.structured!==void 0){const a=await y(r,o.structured.requestId,o.structured.declined===void 0?{intended:"completed",result:o.structured.value}:{intended:"declined",declined:o.structured.declined});if(!a.changed||a.row===null||a.row.state!=="completed"&&a.row.state!=="declined")return{written:void 0,...a.row===null?{}:{structured:a.row},structuredRefused:!0};u=a.row}else{const a=await r.prepareUncached("SELECT request_id FROM structured_requests WHERE node_id = ? AND state = 'pending'").get(e);if(a!==void 0){const f=await y(r,a.request_id,{intended:"failed",failureReason:"finalized"});if(!f.changed||f.row===null||f.row.state!=="failed")return{written:void 0,...f.row===null?{}:{structured:f.row},structuredRefused:!0};u=f.row}}if(n.final_report!==null)throw new h("already_finalized",`${e}/${n.final_report}`);if(n.status!=="active"&&n.status!=="idle"&&n.status!=="done")throw new h("finalization_forbidden");const c=await U(r,e,t),d=await E(e),s=d?.kind!=="human",p=(d?.outcome_revision??0)+1,m=t.outcome?.kind??"result",l=t.outcome?.reason??"finalized",w=t.outcome?.detailJson??null,_=c.createdAt;if(s){const a=d===null?null:await A({node:{...d,status:"done",intent:"done",terminal_reason:l},outcome:{kind:m,reason:l,at:_,revision:p,detailJson:w},finalReport:c.name,...u?.state==="completed"?{structuredResult:u.result}:{}});await r.prepare("UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = ?, final_report = ?, finalized_at = ?, outcome_kind = ?, outcome_reason = ?, outcome_at = ?, outcome_revision = outcome_revision + 1, outcome_detail_json = ? WHERE node_id = ? AND final_report IS NULL").run(l,c.name,_,m,l,_,w,e),a!==null&&await T(r,{event:"outcome",object:await v(r,e),actor:null,attrs:{revision:p},messageBody:a})}else await r.prepare("UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = 'finalized', final_report = ?, finalized_at = ? WHERE node_id = ? AND final_report IS NULL").run(c.name,_,e);const N=M(e);return x(()=>W(e,N)),{written:c,...u===void 0?{}:{structured:u}}})}i(P,"commitCanonicalFinal");async function $(e,t){if(!e)throw new Error(`push: empty nodeId (kind=${t.kind}) \u2014 a report must belong to a node`);if(t.kind==="final"&&t.deliveryTier!==void 0)throw new Error("push final does not accept a delivery tier");if(t.kind!=="final"&&t.outcome!==void 0)throw new Error("push outcome is final-only");if(t.kind!=="update"&&t.result!==void 0)throw new Error("a one-off result is an update report");if(t.kind==="final"){const n=await P(e,t);if(n.structuredRefused===!0)throw n.structured!==void 0&&R(n.structured),new J(n.structured?.state??"failed");n.structured!==void 0&&t.outcome?.structured?.skipEffects!==!0&&R(n.structured);const{written:o}=n;if(await q(e),(await E(e))?.run_id===e){const c=await(await F()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ? ORDER BY created DESC").all(e,e);for(const d of c){const s=await E(d.node_id);s&&s.status!=="done"&&s.status!=="canceled"&&s.status!=="dead"&&await L(d.node_id,{preserveEmpty:!0})}}return{report:o.ref,reportId:o.doc.id,deliveredTo:o.deliveredTo}}const r=await k(async n=>await U(n,e,t));return{report:r.ref,reportId:r.doc.id,deliveredTo:r.deliveredTo}}i($,"pushWithContext");function g(e,t){return D.current()===void 0?D.fresh(()=>$(e,t)):$(e,t)}i(g,"push");async function de(e,t,r){return g(e,{kind:"update",body:t,...r})}i(de,"pushUpdate");async function se(e,t,r){return g(e,{kind:"urgent",body:t,...r})}i(se,"pushUrgent");async function ce(e,t,r){return g(e,{kind:"final",body:t,...r})}i(ce,"pushFinal");export{h as FinalizationError,J as StructuredRequestFinalizationError,g as push,ce as pushFinal,de as pushUpdate,se as pushUrgent};
|