@north-light/cli 0.3.580 → 0.3.582
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 +22 -0
- package/dist/api/dto/imports.d.ts +142 -0
- package/dist/api/dto/imports.js +0 -0
- package/dist/api/error-codes.d.ts +5 -0
- package/dist/api/error-codes.js +1 -1
- package/dist/api/errors.js +1 -1
- 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/build-root.d.ts +11 -4
- package/dist/build-root.js +1 -1
- package/dist/cli.js +7 -7
- 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 +529 -529
- package/dist/commands/api-client.d.ts +12 -4
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/sys/daemon.js +1 -1
- package/dist/commands/sys/desktop.d.ts +5 -0
- package/dist/commands/sys/desktop.js +1 -0
- package/dist/commands/sys/import/move.d.ts +55 -0
- package/dist/commands/sys/import/move.js +8 -0
- 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 +4 -0
- package/dist/commands/sys/import/start.js +1 -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/node-tokens.d.ts +2 -0
- package/dist/core/canvas/node-tokens.js +3 -3
- package/dist/core/canvas/pid.d.ts +6 -0
- package/dist/core/canvas/pid.js +5 -5
- package/dist/core/command-plugins/compose.js +1 -1
- package/dist/core/command.d.ts +17 -8
- package/dist/core/command.js +9 -9
- package/dist/core/directory/issuer.d.ts +3 -0
- package/dist/core/directory/issuer.js +1 -1
- package/dist/core/directory/person-credential.d.ts +61 -0
- package/dist/core/directory/person-credential.js +1 -0
- package/dist/core/directory/reach.d.ts +3 -0
- package/dist/core/directory/reach.js +1 -0
- package/dist/core/grants/mirror.d.ts +3 -0
- package/dist/core/grants/mirror.js +2 -2
- package/dist/core/graph/bodies-boot-copy.d.ts +16 -2
- package/dist/core/graph/bodies-boot-copy.js +1 -1
- package/dist/core/graph/bodies.d.ts +13 -4
- 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 +9 -9
- 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 +7 -2
- 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 +90 -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 +23 -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 +13 -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 +12 -0
- package/dist/core/io.js +4 -4
- package/dist/core/northlight/previews.d.ts +22 -0
- package/dist/core/northlight/previews.js +1 -0
- package/dist/core/providers/call.js +1 -1
- package/dist/core/providers/device.js +1 -1
- package/dist/core/providers/files.d.ts +2 -1
- package/dist/core/providers/files.js +1 -1
- package/dist/core/runtime/broker/cli-relay.js +1 -1
- package/dist/core/runtime/broker/model-adapter.js +3 -3
- package/dist/core/runtime/broker-ownership.d.ts +1 -0
- package/dist/core/runtime/broker-ownership.js +1 -0
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/first-run-offer.d.ts +7 -2
- package/dist/core/runtime/first-run-offer.js +10 -2
- package/dist/core/runtime/front-door.js +4 -4
- package/dist/core/runtime/revive.d.ts +5 -2
- package/dist/core/runtime/revive.js +2 -2
- 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 +29 -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/daemon/api/bridge.js +3 -3
- 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/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/profiles.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/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 +11 -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 +3 -3
- package/dist/daemon/crtrd.js +14 -14
- 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 +10 -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 +45 -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/machine-link/move-notify.d.ts +4 -0
- package/dist/daemon/machine-link/move-notify.js +1 -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/runs/observer.js +3 -3
- 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
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
var
|
|
1
|
+
var E=Object.defineProperty;var m=(i,s)=>E(i,"name",{value:s,configurable:!0});import{existsSync as _,statSync as x}from"node:fs";import{resolve as I}from"node:path";import{SUBTREE_NAMES as L}from"../../build-root.js";import{FRONT_DOOR_ENV as b}from"./front-door-env.js";import{frontDoorBootPrompt as A}from"./first-run-offer.js";import{readMachineConfig as W}from"../../daemon/machine-link/config.js";import{looksLikeNodeIdToken as $}from"./node-id-token.js";import{markTourOffered as M,recordSetupCompleted as B,setupCompleted as G,tourOfferPending as V}from"./setup-marker.js";function T(i){try{return _(i)&&x(i).isDirectory()}catch{return!1}}m(T,"isDir");const U=new Set(["--name","--kind","--profile","--pick-profile","--continue","-c","--resume","-r"]);async function Y(){let i=!1;const s=m(()=>{i=!0},"onSigint");process.once("SIGINT",s);try{const{runSetup:r}=await import("../../commands/sys/setup-wizard.js"),e=await r();if(i){const c=new Error("Interrupted");throw c.name="AbortError",c}await B(e)}finally{process.removeListener("SIGINT",s)}}m(Y,"runFirstSetup");function j(i){let s=process.cwd(),r,e,c,u=!1,p,d;const o=[];for(let f=0;f<i.length;f++){const n=i[f];if(n==="--name")r=i[++f];else if(n==="--kind")e=i[++f];else if(n==="--profile")c=i[++f];else if(n==="--pick-profile")u=!0;else if(n==="--continue"||n==="-c")p="continue";else if(n==="--resume"||n==="-r"){p="pick";const w=i[f+1];w!==void 0&&$(w)&&!T(I(w))&&(d=w,f++)}else n.startsWith("--")||o.push(n)}o.length>0&&T(I(o[0]))&&(s=I(o.shift()));const g=o.length>0?o.join(" "):void 0;return{cwd:s,prompt:g,name:r,kind:e,profile:c,pickProfile:u,resume:p,resumeId:d}}m(j,"parseRootArgs");import{FRONT_DOOR_ENV as ne}from"./front-door-env.js";async function te(i){const s=i.slice(2),r=s[0];if(process.env[b]||r==="-h"||r==="--help"||r==="--version"||r==="-v"||r!==void 0&&L.includes(r))return!1;if(r!==void 0){const t=/\s/.test(r),a=!r.startsWith("-")&&T(I(r)),l=U.has(r);if(!t&&!a&&!l)return!1;if(a){const{buildRoot:C}=await import("../../build-root.js");if((await C()).subtrees.some(D=>D.name===r))return!1}}const e=j(s),c=m(async()=>{if(e.profile===void 0)return;const{resolveProfileOperand:t}=await import("../profiles/manifest.js");e.profile=t(e.profile).profileId},"resolveProfileFlag");e.resumeId===void 0&&await c();const u=process.stdin.isTTY&&process.stdout.isTTY;let p=!1;u&&!G()&&(await Y(),p=!0);let d=null,o,g=process.cwd();if(e.resumeId!==void 0){const{resolveResumeTarget:t,noResumeTargetMessage:a}=await import("./resume-root.js"),l=await t(e.resumeId);"error"in l&&(process.stderr.write(`${l.error}
|
|
2
2
|
`),process.exit(1)),"none"in l?(e.resumeId.includes("-")&&(process.stderr.write(`${a(e.resumeId)}
|
|
3
|
-
`),process.exit(1)),e.prompt=e.prompt===void 0?e.resumeId:`${e.resumeId} ${e.prompt}`,e.resumeId=void 0,await c()):(d=l.node.profile_id,o=l.node.node_id,
|
|
3
|
+
`),process.exit(1)),e.prompt=e.prompt===void 0?e.resumeId:`${e.resumeId} ${e.prompt}`,e.resumeId=void 0,await c()):(d=l.node.profile_id,o=l.node.node_id,T(l.node.cwd)&&(g=l.node.cwd))}if(o===void 0){const{selectProfileForCwd:t,selectProfileForCwdReadOnly:a}=await import("../profiles/select.js");p&&e.profile===void 0&&process.stdout.write(`
|
|
4
4
|
Choose a profile
|
|
5
|
-
`),d=e.profile??(u?await t(e.cwd,void 0,e.pickProfile):a(e.cwd))}const{exitForMissingProviderCredential:f,hasConfiguredProviderCredential:n}=await import("./provider-credential-gate.js"),{cliClient:w}=await import("../../commands/api-client.js"),R=d===null?[]:(await w().listProfileEnv(d)).names;if(!await n(R)){u||f();const{runFrontDoorLogin:t}=await import("./front-door-login.js");await t()||f()}const{inTmux:
|
|
5
|
+
`),d=e.profile??(u?await t(e.cwd,void 0,e.pickProfile):a(e.cwd))}const{exitForMissingProviderCredential:f,hasConfiguredProviderCredential:n}=await import("./provider-credential-gate.js"),{cliClient:w}=await import("../../commands/api-client.js"),R=d===null?[]:(await w().listProfileEnv(d)).names;if(!await n(R)){u||f();const{runFrontDoorLogin:t}=await import("./front-door-login.js");await t()||f()}const{inTmux:P}=await import("./placement-tmux.js");if(u&&!P()){const{TMUX_STARTED_ENV:t,handoffToTmux:a}=await import("./tmux-handoff.js");process.env[t]===void 0&&a(s,o!==void 0?null:d,g)}const[{bootRoot:k},{resumeRoot:v},{rendererWarning:y,missingPackageWarning:N,piAgentDir:O}]=await Promise.all([import("./boot-root.js"),import("./resume-root.js"),import("./package-health.js")]);try{const t=N(O());t!==null&&process.stderr.write(`
|
|
6
6
|
${t}
|
|
7
7
|
|
|
8
8
|
`);const a=y();a!==null&&process.stderr.write(`
|
|
9
9
|
${a}
|
|
10
10
|
|
|
11
|
-
`)}catch{}if(o!==void 0){const{resumeNodeById:t}=await import("./resume-root.js");await t(o)}if(e.resume!==void 0&&await v(e.cwd,e.resume==="pick")==="aborted")return!0;const
|
|
11
|
+
`)}catch{}if(o!==void 0){const{resumeNodeById:t}=await import("./resume-root.js");await t(o)}if(e.resume!==void 0&&await v(e.cwd,e.resume==="pick")==="aborted")return!0;const h=u&&(p||await V()),{prompt:S,initialWhip:F}=A({offerTour:h,signedIn:h&&W()!==void 0,userPrompt:e.prompt});return h&&P()&&await M(),await k({...e,prompt:S,initialWhip:F,selectedProfileId:d??void 0}),!0}m(te,"maybeBootRoot");export{ne as FRONT_DOOR_ENV,te as maybeBootRoot,j as parseRootArgs};
|
|
@@ -106,6 +106,9 @@ export declare function decideAttachRevive(nodeId: string, opts: Omit<ReviveOpti
|
|
|
106
106
|
* reaped) can never host a launch. Fail loud with a typed, user-visible error naming the path instead
|
|
107
107
|
* of letting `headlessBrokerHost.launch` fail deep inside the crash/doctrine-wake catch, where it would
|
|
108
108
|
* surface as an opaque `internal error` (toErrorBody masks every 5xx). Callers admit work to a node
|
|
109
|
-
* with this before changing any state. Returns the node
|
|
110
|
-
export declare function assertRecordedCwdExists(nodeId: string, meta: NodeMeta): Promise<
|
|
109
|
+
* with this before changing any state. Returns where the node runs: its Blaxel environment (null on a machine) and whether a remote machine hosts it. */
|
|
110
|
+
export declare function assertRecordedCwdExists(nodeId: string, meta: NodeMeta): Promise<{
|
|
111
|
+
blaxelEnvironment: string | null;
|
|
112
|
+
remoteMachine: boolean;
|
|
113
|
+
}>;
|
|
111
114
|
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var te=Object.defineProperty;var s=(e,r)=>te(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as ne}from"./unsandboxed.js";import{existsSync as M}from"node:fs";import{conversationIdFromPath as v,conversationStore as h,ConversationNotFoundError as H}from"../conversation-store/index.js";import{isAbsolute as ae}from"node:path";import{findNodeBySessionFile as oe,getNode as $,updateNode as C,beginExecution as se,ExecutionAlreadyLive as le,recordExecutionPid as ce,abortExecution as N,endLiveExecution as ue,unbindSession as U,recordLaunch as fe,setFrozen as we,clearFrozen as G,fullName as de}from"../canvas/index.js";import{emitEvent as K}from"../events/emit.js";import{consumeDeadlineOnWake as pe}from"../feed/messages.js";import{isParked as W,transition as F}from"./lifecycle.js";import{publishNotice as he}from"./close.js";import{InputError as A}from"../io.js";import{buildPiArgv as _}from"./launch.js";import{buildReviveKickoff as me,drainBearings as ve,hasRetainedReports as _e,readGoal as g,readYieldMessage as ye,RUNTIME_RESTART_CONTINUATION as ke}from"./kickoff.js";import{readRoadmap as J}from"./roadmap.js";import{encodeKickoffOrigin as ge,KICKOFF_ORIGIN_ENV as be}from"./stamp/protocol.js";import{headlessBrokerHost as Re,brokerLaunchJson as xe}from"./host.js";import{processRunner as Ee}from"./runner/process.js";import{runnerForEnvironment as Se}from"./runner/index.js";import{blaxelEnvironmentOf as j}from"./blaxel-environment.js";import{recordLaunchFailure as $e}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as Ce,checkBilledWake as Ne,EnvironmentImagePending as Fe,imageForWake as Ae}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as Pe,liveExecutionForNode as Be,setExecutionLaunchJson as Oe}from"../canvas/executions.js";import{appMemoryFull as De,appMemoryTooSmall as Te,boundFleet as V,brokerCapReached as X,brokerThresholdsForDaemon as P}from"./fleet.js";import{admitBrokerMemory as Le}from"./memory-admission.js";import{beginBootFaultAttempt as ze,clearFault as Me,hasAdmittedTurn as He,readFault as Ue}from"../canvas/recovery.js";import{clearNodeLedger as Y,forkNodeLedger as Ge,noticeChangedWhileDone as Ke}from"./ledger.js";import{forkSourceNode as We,rootOfSpine as Je}from"./nodes.js";import{ApiError as je}from"../../api/errors.js";import{grantSnapshotMissing as Ve}from"../../daemon/readiness.js";import{checkStart as Xe}from"./start-check.js";import{isReviewCompanionBound as Ye}from"../review/companion.js";import{assertProfileActive as qe}from"../profiles/manifest.js";import{isPidAlive as Qe}from"../canvas/pid.js";import{crtrHome as Ze,jobDir as Ie}from"../canvas/paths.js";import{contextDir as er,ensureNodeDirs as rr,nodeAgentDir as ir}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as tr}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as nr}from"../command-hooks/transport/exec-lifecycle.js";async function ar(e,r){const i=e.pi_session_file,t=i?v(i):null;if(t===null)return{};try{const a=await h().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof H)return{};throw a}}s(ar,"resumeArgs");async function q(e){const r=v(e);if(r===null)return!1;try{return await h().header(r),!0}catch(i){if(i instanceof H)return!1;throw i}}s(q,"conversationExists");async function or(e){const r=v(e);return r===null?!0:(await h().read(r)).entries.some(i=>i.type==="message")}s(or,"sessionHasConversation");async function sr(e){const r=v(e);if(r===null)return!0;const i=(await h().header(r)).header.cwd;return typeof i!="string"||M(i)}s(sr,"sessionCwdExists");async function lr(e,r,i,t){const a=r.pi_session_file;if(!a||!await q(a))return{};const w=await oe(a);return w!==null&&w.node_id!==e&&Qe(w.pi_pid)?{}:t&&!await sr(a)?{}:await ar(r,i)}s(lr,"resumableSessionArgs");async function cr(e){return await J(e)===null&&await ye(e)===null&&!await _e(e)}s(cr,"isUnstartedBirth");async function qr(e){const r=await g(e);if(r!==null&&r.trim()!=="")return!0;const i=await J(e);return i!==null&&i.trim()!==""}s(qr,"hasFreshGroundState");async function Qr(e,r){const i=await Q(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(Qr,"reviveNode");async function Zr(e,r,i){return Q(e,{...r,viewerAttach:!0},i)}s(Zr,"decideAttachRevive");async function Q(e,r,i){let t=await $(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await $(e)??t,t.run_id&&(await $(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);qe(t.profile_id),ne(t.grantee);const a=V(),w=t.grantee==="app:terminal"&&await Ve();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&W(t))&&(await Xe(t),await j(t.profile_id??null)!==null&&await Ne(t)),await a.has(e))return await F(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&W(t))return{parked:!0};if(w){if(r.capacity==="refuse")throw new je(503,"runtime_starting","the first grant snapshot from the directory has not committed");return await Z(e,t,{reason:"grant_snapshot"}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}const l=await a.reserveSlot(e);let o=null;if(l){try{o=await Le(e)}catch(b){throw a.releaseReservation(e),b}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw Te(o);if(!l||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?De(o):X(P().automaticReviveCap,await a.occupancy());return await Z(e,t,o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:P().automaticReviveCap}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await fr(e,t,r)}}finally{a.releaseReservation(e)}}s(Q,"decideRevive");async function Z(e,r,i){r.frozen_at==null&&(await we(e,new Date().toISOString()),await ue(e,{reason:"frozen"}),K({level:"info",event:"broker.frozen",node_id:e,fields:i}))}s(Z,"freezeForWait");async function ur(e,r){const i=await j(r.profile_id??null);if(i===null&&!M(r.cwd)){const t=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new A({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:t})}return i}s(ur,"assertRecordedCwdExists");async function fr(e,r,i){const t=await ur(e,r);r.host_kind!=="broker"&&(await C(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await Ye(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);const w=r.fork_from!=null&&r.pi_session_id==null,l=r.fork_source_file;if(w&&(typeof l!="string"||!ae(l)))throw new A({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const o=w&&typeof l=="string";if(o&&!await q(l))throw new A({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${l}`,next:`Restore ${l}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:b}=P();if(t!==null){const c=await Ae(t);if(c.state==="pending")throw new Fe(t,c.error)}const I=t===null?Ee:Se("blaxel");let f;try{f=await se(e,{daemonEpoch:V().epoch(),cap:b,...t===null?{}:{runner:"blaxel",release:Ce()}})}catch(c){if(c instanceof Pe)throw X(c.cap,c.live);if(c instanceof le)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw c}try{r.cycles=(r.cycles??0)+1,await C(e,{cycles:r.cycles});const R=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,p=o?{}:await lr(e,r,R,t===null),y=p.resumeSessionPath!==void 0,B=y&&p.newCycle===!0,m=y&&!B,O=!y&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await cr(e),x=m&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await He(e)&&!await or(p.resumeSessionPath)?(await g(e))?.trim():void 0,D=x!==void 0&&x!=="",ee=o||O||D,T=r.cycle_pending===!0||B;await C(e,{cycle_pending:T}),r.cycle_pending=T,!o&&!y&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await U(e),r.pi_session_id=null,r.pi_session_file=null);let u,d=null;const E=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(o){const n=await g(e),S=v(l);if(S===null)throw new Error(`invalid fork source ${l}`);const ie=(await h().header(await h().fork(S,e))).path;u=_(r,{resumeSessionPath:ie,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(d=E()),await Ge(e,(await We(l))?.node_id??null)}else if(D)u=_(r,{...p,prompt:x}),d=E();else if(m)u=_(r,{...p,...i.wakeReason==="runtime-restart-abort"?{prompt:ke}:{}}),i.wakeReason==="runtime-restart-abort"&&(d={type:"runtime",kind:"restart-continuation"});else if(O){const n=(await g(e))?.trim();u=_(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(d=E()),await Y(e)}else{const n=await ve(r,f);u=_(r,{...p,prompt:await me(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(u.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),d=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await Y(e)}u.env={...u.env,CRTR_SUBTREE:await Je(e)},d!==null&&(u.env[be]=ge(d)),r.status==="done"&&r.last_settled_at!=null&&await Ke(e,r.last_settled_at),await F(e,r.intent==="refresh"?"refresh-launch":"revive"),(await Ue(e))?.op!=="conversation_write_failed"&&await Me(e,{preserveEpisode:!0});const L=await ze(e);i.recovery!==!0&&i.viewerAttach!==!0&&await pe(e),await rr(e,r.grantee);const re=tr(r.cwd,r.profile_id).plans.get("node:start")??[];await nr(re,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await ir(e),contextDir:await er(e),jobDir:Ie(e)},runtime:{isBirth:ee,canvasHome:Ze(),profile:r.profile_id??null}}),u.env={...u.env,CRTR_EXECUTION_ID:f};const z=await xe(e,u,r.cwd);await Oe(f,z);let k;try{const n=await Be(e);if(k=await I.start({executionId:f,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:z,cwd:r.cwd,resuming:m}),t===null&&k.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){try{await F(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await he(e,"notice.crash",`Child crashed \u2014 ${de(r)} (${e}) failed to relaunch and is now dead: ${n.message}
|
|
1
|
+
var le=Object.defineProperty;var s=(e,r)=>le(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as ce}from"./unsandboxed.js";import{existsSync as U}from"node:fs";import{conversationIdFromPath as _,conversationStore as h,ConversationNotFoundError as H}from"../conversation-store/index.js";import{isAbsolute as ue}from"node:path";import{findNodeBySessionFile as fe,getNode as R,updateNode as N,beginExecution as we,ExecutionAlreadyLive as me,recordExecutionPid as de,abortExecution as F,endLiveExecution as he,unbindSession as G,recordLaunch as pe,setFrozen as ve,clearFrozen as K,fullName as _e}from"../canvas/index.js";import{emitEvent as W}from"../events/emit.js";import{consumeDeadlineOnWake as ye}from"../feed/messages.js";import{isParked as J,transition as A}from"./lifecycle.js";import{publishNotice as ke}from"./close.js";import{InputError as O}from"../io.js";import{buildPiArgv as y}from"./launch.js";import{buildReviveKickoff as ge,drainBearings as be,hasRetainedReports as Re,readGoal as E,readYieldMessage as Ee,RUNTIME_RESTART_CONTINUATION as xe}from"./kickoff.js";import{readRoadmap as j}from"./roadmap.js";import{encodeKickoffOrigin as Se,KICKOFF_ORIGIN_ENV as Ce}from"./stamp/protocol.js";import{headlessBrokerHost as $e,brokerLaunchJson as Ne}from"./host.js";import{isCloudRole as Fe}from"../canvas/person.js";import{asNodePerson as Ae}from"../../daemon/reconcilers/node-lifecycle/node-person.js";import{processRunner as Oe}from"./runner/process.js";import{runnerForEnvironment as V}from"./runner/index.js";import{MachineLaunchUncertainError as Pe,MachineOfflineError as Be}from"./runner/machine/ops.js";import{blaxelEnvironmentOf as X}from"./blaxel-environment.js";import{recordLaunchFailure as De}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as Y,remoteMachineEnvironmentOf as Me,checkBilledWake as Te,EnvironmentImagePending as Le,imageForWake as ze}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as Ue,liveExecutionForNode as He,setExecutionLaunchJson as Ge}from"../canvas/executions.js";import{isDrained as Ke}from"../../daemon/drain.js";import{appMemoryFull as We,appMemoryTooSmall as Je,boundFleet as q,brokerCapReached as Q,brokerThresholdsForDaemon as P}from"./fleet.js";import{admitBrokerMemory as je}from"./memory-admission.js";import{beginBootFaultAttempt as Ve,clearFault as Xe,hasAdmittedTurn as Ye,readFault as qe}from"../canvas/recovery.js";import{clearNodeLedger as Z,forkNodeLedger as Qe,noticeChangedWhileDone as Ze}from"./ledger.js";import{forkSourceNode as Ie,rootOfSpine as er}from"./nodes.js";import{ApiError as rr}from"../../api/errors.js";import{grantSnapshotMissing as ir}from"../../daemon/readiness.js";import{checkStart as tr}from"./start-check.js";import{isReviewCompanionBound as nr}from"../review/companion.js";import{assertProfileActive as ar}from"../profiles/manifest.js";import{isPidAlive as or}from"../canvas/pid.js";import{crtrHome as sr,jobDir as lr}from"../canvas/paths.js";import{contextDir as cr,ensureNodeDirs as ur,nodeAgentDir as fr}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as wr}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as mr}from"../command-hooks/transport/exec-lifecycle.js";async function dr(e,r){const i=e.pi_session_file,t=i?_(i):null;if(t===null)return{};try{const a=await h().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof H)return{};throw a}}s(dr,"resumeArgs");async function I(e){const r=_(e);if(r===null)return!1;try{return await h().header(r),!0}catch(i){if(i instanceof H)return!1;throw i}}s(I,"conversationExists");async function hr(e){const r=_(e);return r===null?!0:(await h().read(r)).entries.some(i=>i.type==="message")}s(hr,"sessionHasConversation");async function pr(e){const r=_(e);if(r===null)return!0;const i=(await h().header(r)).header.cwd;return typeof i!="string"||U(i)}s(pr,"sessionCwdExists");async function vr(e,r,i,t){const a=r.pi_session_file;if(!a||!await I(a))return{};const u=await fe(a);return u!==null&&u.node_id!==e&&or(u.pi_pid)?{}:t&&!await pr(a)?{}:await dr(r,i)}s(vr,"resumableSessionArgs");async function _r(e){return await j(e)===null&&await Ee(e)===null&&!await Re(e)}s(_r,"isUnstartedBirth");async function ci(e){const r=await E(e);if(r!==null&&r.trim()!=="")return!0;const i=await j(e);return i!==null&&i.trim()!==""}s(ci,"hasFreshGroundState");async function ui(e,r){const i=await ee(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(ui,"reviveNode");async function fi(e,r,i){return ee(e,{...r,viewerAttach:!0},i)}s(fi,"decideAttachRevive");async function ee(e,r,i){const t=Fe()?await R(e):null;return t===null?await re(e,r,i):await Ae({node_id:e,sub:t.sub??null,profile_id:t.profile_id??null},()=>re(e,r,i))}s(ee,"decideRevive");async function re(e,r,i){let t=await R(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await R(e)??t,t.run_id&&(await R(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);ar(t.profile_id),ce(t.grantee);const a=q(),u=t.grantee==="app:terminal"&&await ir();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&J(t))&&(await tr(t),await X(t.profile_id??null)!==null&&await Te(t)),await a.has(e))return await A(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&J(t))return{parked:!0};if(u){if(r.capacity==="refuse")throw new rr(503,"runtime_starting","the first grant snapshot from the directory has not committed");return await ie(e,t,{reason:"grant_snapshot"}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}const k=await Ke();if(k&&r.capacity==="refuse")throw new Error(`the daemon is drained for an import; ${e} cannot start until it ends`);const p=!k&&await a.reserveSlot(e);let o=null;if(p){try{o=await je(e)}catch(w){throw a.releaseReservation(e),w}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw Je(o);if(!p||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?We(o):Q(P().automaticReviveCap,await a.occupancy());return await ie(e,t,k?{reason:"drain"}:o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:P().automaticReviveCap}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await kr(e,t,r)}}finally{a.releaseReservation(e)}}s(re,"decideReviveAsCaller");async function ie(e,r,i){r.frozen_at==null&&(await ve(e,new Date().toISOString()),await he(e,{reason:"frozen"}),W({level:"info",event:"broker.frozen",node_id:e,fields:i}))}s(ie,"freezeForWait");async function yr(e,r){const i=await X(r.profile_id??null),t=await Me(r.profile_id??null)!==null;if(!(i!==null||t)&&!U(r.cwd)){const u=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new O({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:u})}return{blaxelEnvironment:i,remoteMachine:t}}s(yr,"assertRecordedCwdExists");async function kr(e,r,i){const{blaxelEnvironment:t,remoteMachine:a}=await yr(e,r),u=t!==null||a;r.host_kind!=="broker"&&(await N(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await nr(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);const p=r.fork_from!=null&&r.pi_session_id==null,o=r.fork_source_file;if(p&&(typeof o!="string"||!ue(o)))throw new O({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const w=p&&typeof o=="string";if(w&&!await I(o))throw new O({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${o}`,next:`Restore ${o}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:te}=P();if(t!==null){const l=await ze(t);if(l.state==="pending")throw new Le(t,l.error)}const ne=t!==null?V("blaxel"):a?V("machine"):Oe;let f;try{f=await we(e,{daemonEpoch:q().epoch(),cap:te,...t!==null?{runner:"blaxel",release:Y()}:a?{runner:"machine",release:Y()}:{}})}catch(l){if(l instanceof Ue)throw Q(l.cap,l.live);if(l instanceof me)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw l}try{r.cycles=(r.cycles??0)+1,await N(e,{cycles:r.cycles});const x=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,d=w?{}:await vr(e,r,x,!u),g=d.resumeSessionPath!==void 0,B=g&&d.newCycle===!0,v=g&&!B,D=!g&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await _r(e),S=v&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await Ye(e)&&!await hr(d.resumeSessionPath)?(await E(e))?.trim():void 0,M=S!==void 0&&S!=="",ae=w||D||M,T=r.cycle_pending===!0||B;await N(e,{cycle_pending:T}),r.cycle_pending=T,!w&&!g&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);let c,m=null;const C=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(w){const n=await E(e),$=_(o);if($===null)throw new Error(`invalid fork source ${o}`);const se=(await h().header(await h().fork($,e))).path;c=y(r,{resumeSessionPath:se,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(m=C()),await Qe(e,(await Ie(o))?.node_id??null)}else if(M)c=y(r,{...d,prompt:S}),m=C();else if(v)c=y(r,{...d,...i.wakeReason==="runtime-restart-abort"?{prompt:xe}:{}}),i.wakeReason==="runtime-restart-abort"&&(m={type:"runtime",kind:"restart-continuation"});else if(D){const n=(await E(e))?.trim();c=y(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(m=C()),await Z(e)}else{const n=await be(r,f);c=y(r,{...d,prompt:await ge(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),m=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await Z(e)}c.env={...c.env,CRTR_SUBTREE:await er(e)},m!==null&&(c.env[Ce]=Se(m)),r.status==="done"&&r.last_settled_at!=null&&await Ze(e,r.last_settled_at),await A(e,r.intent==="refresh"?"refresh-launch":"revive"),(await qe(e))?.op!=="conversation_write_failed"&&await Xe(e,{preserveEpisode:!0});const L=await Ve(e);i.recovery!==!0&&i.viewerAttach!==!0&&await ye(e),await ur(e,r.grantee);const oe=wr(r.cwd,r.profile_id).plans.get("node:start")??[];await mr(oe,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await fr(e),contextDir:await cr(e),jobDir:lr(e)},runtime:{isBirth:ae,canvasHome:sr(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:f};const z=await Ne(e,c,r.cwd);await Ge(f,z);let b;try{const n=await He(e);if(b=await ne.start({executionId:f,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:z,cwd:r.cwd,resuming:v}),!u&&b.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){if(n instanceof Be)throw n;try{await A(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await ke(e,"notice.crash",`Child crashed \u2014 ${_e(r)} (${e}) failed to relaunch and is now dead: ${n.message}
|
|
2
2
|
|
|
3
|
-
It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await
|
|
3
|
+
It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await F(f,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await pe(e,new Date().toISOString()),u)return await K(e),{window:null,session:r.tmux_session??null,resumed:v,faultExplanation:L.explanation,outcome:"launched"};if(await de(f,e,b.pid)==="shutdown")throw await F(f,{reason:"superseded"}),$e.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await K(e),{window:null,session:r.tmux_session??null,resumed:v,launch:b,faultExplanation:L.explanation,outcome:"launched"}}catch(l){throw l instanceof Pe||(await F(f,{reason:"launch_failed"}),await De(f,l).catch(x=>{W({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:x})})),l}}s(kr,"launchRevive");export{yr as assertRecordedCwdExists,fi as decideAttachRevive,ci as hasFreshGroundState,dr as resumeArgs,ui as reviveNode};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var g=Object.defineProperty;var
|
|
2
|
-
${
|
|
3
|
-
${
|
|
1
|
+
var g=Object.defineProperty;var i=(r,a)=>g(r,"name",{value:a,configurable:!0});import{openDb as k}from"../../../canvas/db.js";import{mintExecutionToken as I}from"../../../canvas/node-tokens.js";import{BlaxelApiError as T,isWorkloadUnavailable as x}from"./client.js";const m=4180,u="view",C="node /opt/crtr/current/scripts/sandbox-start.mjs --wake",S=3,N=/^crtr-sandbox-start: failed (\S+)/m,f=i(r=>`broker-${r}`,"brokerProcessName"),E=i(r=>r.sandboxName??`${r.nodeId}-${r.attempt}`,"sandboxNameFor");async function y(r){return(await(await k()).prepare("SELECT DISTINCT sandbox_name FROM executions WHERE node_id = ? AND sandbox_name IS NOT NULL").all(r)).map(l=>l.sandbox_name)}i(y,"defaultSandboxNames");function P(r){const a=`${r.logs}
|
|
2
|
+
${r.stderr}
|
|
3
|
+
${r.stdout}`;return{state:"failed",reason:N.exec(a)?.[1]??"start_failed",code:r.exitCode}}i(P,"failureOf");function h(r){const{client:a}=r,l=(r.stopGraceS??10)*1e3,b=new Map;async function d(t){return await a.getSandbox(E(t))}return i(d,"sandboxOf"),{kind:"blaxel",async start(t){const n=await r.launchContext(t),e=E(t),o=await a.createSandbox({name:e,image:n.image,memoryMb:n.memoryMb,ports:[{name:u,target:m}]}),s=f(t.executionId);if((await a.getProcess(o,s))?.status==="running")return{pid:null,exited:null};await a.ensurePreview(e,u,m);const w=await(r.mintToken??(p=>I(p.executionId,p.nodeId)))(t),R={...n.secrets,CRTR_API_URL:n.apiUrl,CRTR_EXECUTION_ID:t.executionId,CRTR_NODE_TOKEN:w,CRTR_RELEASE:t.release,CRTR_RELEASE_URL:n.releaseUrl,CRTR_RELEASE_SHA256:n.releaseSha256};return await a.startProcess(o,{name:s,command:r.bootstrap(),env:R,waitForCompletion:!1}),{pid:null,exited:null}},async wake(t){const n=await d(t);if(n===null)return"no_broker";try{const e=await a.exec(n,`wake-${t.executionId}-${Date.now()}`,`${C} ${t.executionId}`,30);if(e.exitCode===0)return"signalled";if(e.exitCode===S)return"no_broker";throw new Error(`wake of ${t.executionId} failed (exit ${e.exitCode}): ${(e.stderr||e.logs).slice(0,300)}`)}catch(e){if(x(e))return"no_broker";throw e}},async stop(t,n){try{const e=await d(t);if(e===null)return{confirmed:!0};const o=f(t.executionId),s=i(async()=>(await a.getProcess(e,o))?.status==="running","running");if(!await s())return{confirmed:!0};await a.stopProcess(e,o);const c=Date.now()+l;for(;Date.now()<c;){if(!await s())return{confirmed:!0};await new Promise(w=>setTimeout(w,250))}return await a.killProcess(e,o),{confirmed:!await s()}}catch(e){return x(e)?{confirmed:!0}:{confirmed:!1,error:e instanceof Error?e:new Error(String(e))}}},async status(t){try{const n=await d(t);if(n===null)return{state:"gone"};const e=await a.getProcess(n,f(t.executionId));return e===null?{state:"gone"}:e.status==="running"?{state:"running",pid:null}:e.status==="failed"||e.status==="completed"&&e.exitCode!==0?P(e):{state:"gone"}}catch(n){return x(n)?{state:"gone"}:{state:"unknown",error:n instanceof Error?n.message:String(n)}}},async delete(t){for(const n of await(r.sandboxNamesForNode??y)(t))await a.deleteSandbox(n)},async viewerAddress(t){const n=E(t),e=b.get(n);if(e&&e.expiresAt-Date.now()>5*6e4)return _(e.url,e.token);const o=await a.ensurePreview(n,u,m),s=new Date(Date.now()+60*6e4),{token:c}=await a.createPreviewToken(n,u,s);return b.set(n,{url:o,token:c,expiresAt:s.getTime()}),_(o,c)},async sandboxState(t){return(await d(t))?.state??null}}}i(h,"createBlaxelRunner");function _(r,a){return{kind:"wss",url:`${r.replace(/^https:/,"wss:")}/view`,headers:{"x-blaxel-preview-token":a}}}i(_,"viewer");export{m as BROKER_VIEW_PORT,T as BlaxelApiError,S as EXIT_NO_BROKER,N as FAILURE_LINE,C as WAKE_COMMAND,f as brokerProcessName,h as createBlaxelRunner,E as sandboxNameFor};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Runner, RunnerKind } from './types.js';
|
|
2
2
|
/** The Blaxel implementation is installed by the cloud runner module. */
|
|
3
3
|
export declare function registerBlaxelRunner(runner: Runner): void;
|
|
4
|
+
/** A machine environment is this process's own host for a Mac's crtrd (process runner) and another machine for a cloud role (the remote machine runner, design-import I13). */
|
|
4
5
|
export declare function runnerKindForEnvironment(kind: 'machine' | 'blaxel'): RunnerKind;
|
|
5
6
|
export declare function runnerForEnvironment(kind: 'machine' | 'blaxel'): Runner;
|
|
6
7
|
export declare function runnerForEnvironmentId(environmentId: string): Promise<Runner>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var t=Object.defineProperty;var r=(n,e)=>t(n,"name",{value:e,configurable:!0});import{openDb as i}from"../../canvas/db.js";import{isCloudRole as u}from"../../canvas/person-state.js";import{processRunner as l}from"./process.js";import{createMachineRunner as a}from"./machine/runner.js";let o=null;const c=a();function R(n){if(n.kind!=="blaxel")throw new Error("expected a Blaxel runner");o=n}r(R,"registerBlaxelRunner");function f(n){return n==="blaxel"?"blaxel":u()?"machine":"process"}r(f,"runnerKindForEnvironment");function m(n){return p(f(n))}r(m,"runnerForEnvironment");async function h(n){const e=await(await i()).prepare("SELECT kind FROM environments WHERE object = ?").get(n);if(!e)throw new Error(`environment ${n} not found`);return m(e.kind)}r(h,"runnerForEnvironmentId");function p(n){if(n==="process")return l;if(n==="machine")return c;if(o===null)throw new Error("Blaxel runner is not registered");return o}r(p,"runnerForExecution");export{R as registerBlaxelRunner,m as runnerForEnvironment,h as runnerForEnvironmentId,p as runnerForExecution,f as runnerKindForEnvironment};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type MachineOp, type MachineOpTable } from './ops.js';
|
|
2
|
+
/** Seconds a caller waits: `start` 60, every other op 15 (design). */
|
|
3
|
+
export declare const MACHINE_CALL_TIMEOUT_S: Record<MachineOp, number>;
|
|
4
|
+
export interface CallMachineOptions {
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
rereadMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function callMachine<O extends MachineOp>(environmentId: string, op: O, args: MachineOpTable[O]['args'], options?: CallMachineOptions): Promise<MachineOpTable[O]['result']>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var d=Object.defineProperty;var s=(e,n)=>d(e,"name",{value:n,configurable:!0});import{randomUUID as E}from"node:crypto";import{openDb as f,withCanvasWrite as h}from"../../../canvas/db.js";import{MachineCallFailedError as p,MachineLostError as l,MachineOfflineError as o}from"./ops.js";const S={start:60,wake:15,stop:15,status:15,delete:15,viewer:15,"cron.run":15,"cron.cancel":15},m=1e3;async function M(e,n,r,c={}){const i=E(),u=new Date().toISOString();if(!await h(async a=>await a.prepare("SELECT 1 AS live FROM machine_links WHERE environment = ? AND lease_expires_at > ?").get(e,u)===void 0?!1:(await a.prepare("INSERT INTO machine_calls (call_id, environment, op, args_json, created_at) VALUES (?, ?, ?, ?, ?)").run(i,e,n,JSON.stringify(r),u),await a.prepare("SELECT pg_notify(?, ?)").get("crtr_signal",`machine:${e}`),!0)))throw new o(e,"no live link");const w=Date.now()+(c.timeoutMs??S[n]*1e3);for(;;){const a=await(await f()).prepare("SELECT state, result_json, lease_expires_at, claimed_generation FROM machine_calls WHERE call_id = ?").get(i);if(a===void 0)throw new l(e,i);if(a.state==="done")return a.result_json===null?null:JSON.parse(a.result_json);if(a.state==="failed"){const t=a.result_json===null?{}:JSON.parse(a.result_json);throw t.code==="machine_offline"?new o(e,t.message,t.sent===!0):t.code==="machine_lost"?new l(e,i):new p(e,n,t.message??"unknown")}if(a.claimed_generation!==null&&a.lease_expires_at!==null&&a.lease_expires_at<=new Date().toISOString()){if(await _(i,"lease expired")===void 0)continue;throw new l(e,i)}if(Date.now()>=w){const t=await _(i,"timed out");if(t===void 0)continue;throw t===null?new o(e,"no holder claimed the call"):new l(e,i)}await new Promise(t=>setTimeout(t,c.rereadMs??m))}}s(M,"callMachine");async function _(e,n){const r=await(await f()).prepare(`UPDATE machine_calls SET state = 'failed', finished_at = ?,
|
|
2
|
+
result_json = CASE WHEN claimed_generation IS NULL THEN ? ELSE ? END
|
|
3
|
+
WHERE call_id = ? AND state IN ('pending', 'sent') RETURNING claimed_generation`).get(new Date().toISOString(),JSON.stringify({code:"machine_offline",message:n,sent:!1}),JSON.stringify({code:"machine_lost",message:n}),e);return r===void 0?void 0:r.claimed_generation===null?null:Number(r.claimed_generation)}s(_,"failOpenCall");export{S as MACHINE_CALL_TIMEOUT_S,M as callMachine};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function machineEnvironmentOfProfile(profileId: string): Promise<string | null>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var t=Object.defineProperty;var o=(e,n)=>t(e,"name",{value:n,configurable:!0});import{openDb as i}from"../../../canvas/db.js";async function p(e){return(await(await i()).prepare("SELECT e.object AS object FROM profiles p JOIN environments e ON e.object = p.environment_id WHERE p.id = ? AND e.kind = 'machine'").get(e))?.object??null}o(p,"machineEnvironmentOfProfile");export{p as machineEnvironmentOfProfile};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { StartBundleDTO } from '../../../../api/index.js';
|
|
2
|
+
import type { MachineOpTable } from './ops.js';
|
|
3
|
+
export interface MachineExecutorDeps {
|
|
4
|
+
/** `CRTR_API_URL` the brokers reach `/v1` at: the control plane. */
|
|
5
|
+
apiUrl: string;
|
|
6
|
+
/** The machine environment's object id, set as `CRTR_ENVIRONMENT_ID`. */
|
|
7
|
+
environmentId: string;
|
|
8
|
+
/** Seconds `stop` waits after SIGTERM before SIGKILL. */
|
|
9
|
+
stopGraceS?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Broker processes: `{pid, execution, node}` for each `broker-cli.js ... --epoch <execution> <node>` command line. Throws when the process table cannot
|
|
12
|
+
* be read: an unreadable table is not an empty one, and a caller that read it as "no brokers" would start a second broker or report a stop that did not happen. */
|
|
13
|
+
export declare function findBrokers(): Array<{
|
|
14
|
+
pid: number;
|
|
15
|
+
execution: string;
|
|
16
|
+
node: string;
|
|
17
|
+
command: string;
|
|
18
|
+
}>;
|
|
19
|
+
/** The env a machine's broker starts with: what the process runner's `buildBrokerEnv` builds for the same node. The environment's secret rows come from the
|
|
20
|
+
* control plane in the start bundle with the profile's metadata (this role has no database and no profiles, so the profile is never looked up here); the operational base, the profile's metadata, the app's pi dir and the launch recipe's env
|
|
21
|
+
* go over them in `buildBrokerEnv`'s order, and the link's own settings are pinned last. */
|
|
22
|
+
export declare function machineBrokerEnv(bundle: StartBundleDTO, deps: Pick<MachineExecutorDeps, 'apiUrl' | 'environmentId'>, executionId: string): NodeJS.ProcessEnv;
|
|
23
|
+
/** Stop every broker of this crtr home (a machine daemon starting with no sign-in, design-crtr-northlight-signin component 5). A broker of another
|
|
24
|
+
* home on the same computer (its `--canvas-home` differs) is not touched. Rejects when the process table cannot be read or a broker's tree survives
|
|
25
|
+
* SIGKILL, so the caller never reports stopped while something of this home may still run. */
|
|
26
|
+
export declare function stopAllBrokers(graceMs?: number): Promise<void>;
|
|
27
|
+
export declare function createMachineExecutor(deps: MachineExecutorDeps): {
|
|
28
|
+
[O in Exclude<keyof MachineOpTable, 'cron.run' | 'cron.cancel'>]: (args: MachineOpTable[O]['args'], signal?: AbortSignal) => Promise<MachineOpTable[O]['result']>;
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var D=Object.defineProperty;var c=(r,n)=>D(r,"name",{value:n,configurable:!0});import{spawn as O}from"node:child_process";import{closeSync as $,existsSync as b,mkdirSync as y,openSync as A,statSync as G}from"node:fs";import{rm as L}from"node:fs/promises";import{dirname as N,join as h}from"node:path";import{fileURLToPath as B}from"node:url";import{jobDir as U,nodeRunDir as v,crtrHome as E,viewSocketPath as j}from"../../../canvas/paths.js";import{captureTeardownSnapshot as F,isAnyPidAlive as M,killProcessTreePids as S,listProcessTable as K}from"../../../canvas/pid.js";import{scopesFilePath as H}from"../../../canvas/paths.js";import{writeFileBeneath as R}from"../../../spaces/open-beneath.js";import{FRONT_DOOR_ENV as V}from"../../front-door-env.js";import{brokerCommandDeclaresCanvasHome as J}from"../../broker-ownership.js";import{composeAppProcessEnv as W,overlayBrokerEnv as z}from"../../spawn-env.js";const w=c(()=>h(N(B(import.meta.url)),"..","..","broker-cli.js"),"brokerEntry"),x=c(r=>{if(r!==void 0)try{process.kill(-r,"SIGKILL")}catch{}},"killGroup"),X=c(r=>new Promise(n=>setTimeout(n,r)),"sleep");function _(){const r=[],n=K();if(n===null)throw new Error("the process table could not be read, so which brokers are running is unknown");for(const{pid:t,command:e}of n){const o=/broker-cli\.js .*--epoch (\S+) (\S+)\s*$/.exec(e);o!==null&&r.push({pid:t,execution:o[1],node:o[2],command:e})}return r}c(_,"findBrokers");async function q(r,n,t){const e=await fetch(`${r.replace(/\/$/,"")}/v1/executions/${n}/start`,{headers:{authorization:`Bearer ${t}`},signal:AbortSignal.timeout(3e4)});if(!e.ok)throw new Error(`the control plane answered ${e.status} for the start bundle of ${n}`);return await e.json()}c(q,"fetchBundle");function Q(r,n,t){const e=r.launch,o=z({argv:[],env:e.env??{}},W(r.node.cwd,null,r.node.app,r.machine_env??{},{}));return Object.assign(o,{CRTR_API_URL:n.apiUrl,CRTR_EXECUTION_ID:t,CRTR_ENVIRONMENT_ID:n.environmentId,CRTR_VIEW_WSS:"0",[V]:"1"})}c(Q,"machineBrokerEnv");async function T(r,n,t){const{tree:e,identities:o}=F(r,null,t);if(o===null)return{confirmed:!1,error:`the process tree of broker ${r} could not be read, so shutdown cannot be confirmed`};const i=c(async(u,l)=>{for(let a=0;M(u);a+=100){if(a>=l)return!1;await X(100)}return!0},"waitUntilDead");try{process.kill(r,"SIGTERM")}catch{}return await i([r],n),S(e,"SIGTERM",o),await i(e,2e3)?{confirmed:!0}:(S(e,"SIGKILL",o),await i(e,5e3)?{confirmed:!0}:{confirmed:!1,error:`broker process tree of ${r} survived SIGKILL`})}c(T,"stopBroker");async function ue(r=1e4){const t=(await Promise.all(_().filter(e=>J(e.command,E())).map(e=>T(e.pid,r)))).filter(e=>!e.confirmed);if(t.length>0)throw new Error(t.map(e=>e.error??"a broker did not stop").join("; "))}c(ue,"stopAllBrokers");function le(r){const n=(r.stopGraceS??10)*1e3,t=c(e=>_().find(o=>o.execution===e),"brokerOf");return{async start(e,o){const i=t(e.executionId);if(i!==void 0)return{pid:i.pid};if(o?.throwIfAborted(),!b(e.cwd)||!G(e.cwd).isDirectory())throw new Error(`the node's cwd ${e.cwd} is not a directory on this machine`);if(!b(w()))throw new Error(`the broker entry ${w()} does not exist`);const u=await q(r.apiUrl,e.executionId,e.nodeToken);o?.throwIfAborted();const l=u.launch,a=v(e.nodeId);y(a,{recursive:!0}),R(a,h(a,"broker-launch.json"),e.launchJson,{atomic:!0}),l.env?.CRTR_SCOPES_FILE!==void 0&&R(a,H(e.nodeId),JSON.stringify(l.scopes??[]),{atomic:!0});const k=U(e.nodeId);y(k,{recursive:!0});const m=A(h(k,"broker.log"),"a"),I=Q(u,r,e.executionId);delete I.CRTR_NODE_TOKEN;try{o?.throwIfAborted();const s=O(process.execPath,[w(),"--canvas-home",E(),"--epoch",e.executionId,e.nodeId],{cwd:e.cwd,detached:!0,stdio:["ignore",m,m,"pipe"],env:I}),d=s.stdio[3];if(d==null||!("end"in d))throw x(s.pid),new Error("broker token pipe did not open");const P=await new Promise((C,f)=>{s.once("error",f),s.once("spawn",()=>{s.off("error",f),d.once("error",p=>{x(s.pid),f(p)}),d.end(e.nodeToken,p=>{p?f(p):C(s.pid)})})});return s.unref(),{pid:P}}finally{$(m)}},async wake({execution:e}){const o=t(e.executionId);if(o===void 0)return"no_broker";try{return process.kill(o.pid,"SIGUSR1"),"signalled"}catch(i){if(i.code==="ESRCH")return"no_broker";throw i}},async stop({execution:e,options:o}){const i=t(e.executionId);return i===void 0?{confirmed:!0}:await T(i.pid,n,o.excludePid)},async status({execution:e}){const o=t(e.executionId);return o===void 0?{state:"gone"}:{state:"running",pid:o.pid}},async delete({nodeId:e}){return await L(v(e),{recursive:!0,force:!0}),null},async viewer({execution:e}){return{kind:"unix",path:j(e.nodeId)}}}}c(le,"createMachineExecutor");export{le as createMachineExecutor,_ as findBrokers,Q as machineBrokerEnv,ue as stopAllBrokers};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var y=Object.defineProperty;var m=(r,n)=>y(r,"name",{value:n,configurable:!0});import{after as O,before as g,test as u}from"node:test";import t from"node:assert/strict";import{spawn as I}from"node:child_process";import{mkdirSync as f,mkdtempSync as h,readFileSync as A,rmSync as v,writeFileSync as T}from"node:fs";import{tmpdir as w}from"node:os";import{join as s}from"node:path";import{EventEmitter as P}from"node:events";import{Readable as q}from"node:stream";import{createNode as H}from"../../../canvas/canvas.js";import{closeDb as N,openDb as E}from"../../../canvas/db.js";import{isPidAlive as l}from"../../../canvas/pid.js";import{bindPerson as x,installTerminalGrant as C,useEmptyPersonSettings as k}from"../../../__tests__/helpers/person.js";import{setAppEnvVar as R}from"../../../profiles/app-env-store.js";import{setProfileEnvVar as _}from"../../../profiles/env-store.js";import{createProfile as D}from"../../../profiles/manifest.js";import{putEnvRow as L}from"../../../secrets/env-rows.js";import{appSpace as F}from"../../../layout.js";import{executionStartRoutes as U}from"../../../../daemon/api/handlers/execution-start.js";import{Router as V}from"../../../../daemon/api/router.js";import{createMachineExecutor as M,machineBrokerEnv as S}from"./executor.js";let p;const G={HOME:process.env.HOME,CRTR_HOME:process.env.CRTR_HOME};g(()=>{process.env.HOME=p=h(s(w(),"crtr-machine-exec-")),process.env.CRTR_HOME=s(p,".crouter"),f(process.env.CRTR_HOME,{recursive:!0})}),O(async()=>{await N(),v(p,{recursive:!0,force:!0});for(const[r,n]of Object.entries(G))n===void 0?delete process.env[r]:process.env[r]=n});async function j(r,n){const i=new V().registerAll(U),a=Object.assign(q.from([]),{method:"GET",url:`/v1/executions/${n}/start`,headers:{}});let o="";const e=Object.assign(new P,{headersSent:!1,writableEnded:!1,writableFinished:!1});return e.setHeader=()=>e,e.writeHead=()=>(e.headersSent=!0,e),e.end=c=>(c&&(o+=c),e.writableEnded=!0,e.writableFinished=!0,e),await i.handle(a,e,null,r),JSON.parse(o)}m(j,"bundleFor"),u("a machine broker gets the profile, app and deployment rows, the app pi dir and the launch env, with the process runner's precedence",async()=>{await k(),await E();const r=await C(),n=(await D("machine-env")).profileId;await R("app:terminal","SHARED","from-app",r),await R("app:terminal","APP_SECRET","app-secret",r),await _(n,"SHARED","from-profile",r),await _(n,"PROFILE_VAR","profile-value",r),await L({person:null,kind:"deployment",id:"_deployment"},"DEPLOY_VAR","deploy-value");const i=s(p,"work");f(i,{recursive:!0}),await H({node_id:"m1",name:"m1",created:new Date().toISOString(),cwd:i,kind:"general",mode:"base",lifecycle:"resident",status:"active",parent:null,profile_id:n,grantee:"app:terminal"}),await(await E()).prepare("INSERT INTO executions (execution_id, node_id, state, started_at, launch_json, runner) VALUES ('em1', 'm1', 'launching', 't', ?, 'machine')").run(JSON.stringify({argv:["x"],env:{LAUNCH_VAR:"launch",CRTR_NODE_ID:"m1"}}));const a={class:"broker",app:"app:terminal",node:"m1",run:null,uid:process.getuid?.()??0,sub:await x(),grant:null,client_id:null,scopes:null},o=await j(a,"em1");t.equal(o.machine_env?.APP_SECRET,"app-secret");const e=S(o,{apiUrl:"http://cloud.invalid",environmentId:"env1"},"em1");t.equal(e.APP_SECRET,"app-secret"),t.equal(e.PROFILE_VAR,"profile-value"),t.equal(e.DEPLOY_VAR,"deploy-value"),t.equal(e.SHARED,"from-profile","the profile's row wins over the app's"),t.equal(e.LAUNCH_VAR,"launch"),t.equal(e.PI_CODING_AGENT_DIR,s(F("app:terminal").home,".pi","agent")),t.equal(e.CRTR_API_URL,"http://cloud.invalid"),t.equal(e.CRTR_EXECUTION_ID,"em1"),t.doesNotThrow(()=>S({...o,node:{...o.node,profile:"profile-the-mac-does-not-hold"}},{apiUrl:"http://cloud.invalid",environmentId:"env1"},"em1")),t.equal(e.GITHUB_TOKEN,void 0)}),u("stop ends the broker and the detached command it left behind, and confirms both gone",async()=>{const r=h(s(w(),"crtr-machine-stop-")),n=s(r,"child.pid"),i=s(r,"broker-cli.js");T(i,`const { spawn } = require('node:child_process');
|
|
2
|
+
const c = spawn('sleep', ['300'], { detached: true, stdio: 'ignore' }); c.unref();
|
|
3
|
+
require('node:fs').writeFileSync(${JSON.stringify(n)}, String(c.pid));
|
|
4
|
+
setInterval(() => {}, 1000);`);const a=I(process.execPath,[i,"--canvas-home",r,"--epoch","exec-stop-1","node-stop-1"],{detached:!0,stdio:"ignore"});a.unref();try{let o=0;for(let d=0;d<100&&o===0;d++)try{o=Number(A(n,"utf8"))}catch{await new Promise(b=>setTimeout(b,50))}t.ok(o>0&&l(o),"the detached child is running");const c=await M({apiUrl:"http://cloud.invalid",environmentId:"env1",stopGraceS:3}).stop({execution:{executionId:"exec-stop-1",nodeId:"node-stop-1"},options:{reason:"test"}});t.deepEqual(c,{confirmed:!0}),t.equal(l(a.pid),!1,"the broker is gone"),t.equal(l(o),!1,"the detached child is gone")}finally{try{process.kill(-a.pid,"SIGKILL")}catch{}v(r,{recursive:!0,force:!0})}});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { RunnerExecution, RunnerStartInput, RunnerStatus, RunnerStopOptions, RunnerStopResult, RunnerViewerAddress, RunnerWakeResult } from '../types.js';
|
|
2
|
+
export declare const MACHINE_OPS: readonly ["start", "wake", "stop", "status", "delete", "viewer", "cron.run", "cron.cancel"];
|
|
3
|
+
export type MachineOp = (typeof MACHINE_OPS)[number];
|
|
4
|
+
/** A `start` call carries the whole start input and the node token the control plane minted: the Mac has no database, so it cannot mint one.
|
|
5
|
+
* The token sits in `machine_calls.args_json` until the row is deleted a day after it finishes; the execution's token row holds its hash. */
|
|
6
|
+
export type MachineStartArgs = RunnerStartInput & {
|
|
7
|
+
nodeToken: string;
|
|
8
|
+
};
|
|
9
|
+
export interface MachineStartResult {
|
|
10
|
+
pid: number | null;
|
|
11
|
+
}
|
|
12
|
+
export interface MachineWakeArgs {
|
|
13
|
+
execution: RunnerExecution;
|
|
14
|
+
}
|
|
15
|
+
export interface MachineStopArgs {
|
|
16
|
+
execution: RunnerExecution;
|
|
17
|
+
options: RunnerStopOptions;
|
|
18
|
+
}
|
|
19
|
+
export interface MachineStatusArgs {
|
|
20
|
+
execution: RunnerExecution;
|
|
21
|
+
}
|
|
22
|
+
export interface MachineDeleteArgs {
|
|
23
|
+
nodeId: string;
|
|
24
|
+
}
|
|
25
|
+
export interface MachineViewerArgs {
|
|
26
|
+
execution: RunnerExecution;
|
|
27
|
+
}
|
|
28
|
+
/** A cron fire on a machine. `runId` is the cron run id: it fences a second fire after a reconnect. */
|
|
29
|
+
export interface MachineCronRunArgs {
|
|
30
|
+
runId: string;
|
|
31
|
+
cronId: string;
|
|
32
|
+
command: string;
|
|
33
|
+
cwd: string;
|
|
34
|
+
env: Record<string, string>;
|
|
35
|
+
/** The cron's app: the Mac decides whether the armed profile applies (only the terminal's crons run under their profile). */
|
|
36
|
+
grantee: string;
|
|
37
|
+
/** The cron's profile id; the Mac rebuilds its declared PATH entries from its own copy of the profile. */
|
|
38
|
+
profileId: string | null;
|
|
39
|
+
/** The anchor node's id and app when it is still on the canvas: the Mac sets `CRTR_CONTEXT_DIR` to that node's context dir. */
|
|
40
|
+
contextAnchor: {
|
|
41
|
+
node: string;
|
|
42
|
+
grantee: string;
|
|
43
|
+
} | null;
|
|
44
|
+
timeoutS: number;
|
|
45
|
+
/** The cron's lease expiry (ISO); the machine does not start a run whose lease has passed. */
|
|
46
|
+
leaseExpiresAt: string;
|
|
47
|
+
}
|
|
48
|
+
export interface MachineCronRunResult {
|
|
49
|
+
exitCode: number | null;
|
|
50
|
+
stdoutHead: string | null;
|
|
51
|
+
stderrHead: string | null;
|
|
52
|
+
durationMs: number;
|
|
53
|
+
/** SHA-256 of the whole stdout (not of the head): what `on-change` compares. */
|
|
54
|
+
stdoutSha256: string;
|
|
55
|
+
/** True when the machine did not start the command (its lease had passed): the sender may send the same run id again. */
|
|
56
|
+
notStarted?: true;
|
|
57
|
+
}
|
|
58
|
+
export interface MachineCronCancelArgs {
|
|
59
|
+
runId: string;
|
|
60
|
+
}
|
|
61
|
+
export interface MachineCronCancelResult {
|
|
62
|
+
canceled: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface MachineOpTable {
|
|
65
|
+
start: {
|
|
66
|
+
args: MachineStartArgs;
|
|
67
|
+
result: MachineStartResult;
|
|
68
|
+
};
|
|
69
|
+
wake: {
|
|
70
|
+
args: MachineWakeArgs;
|
|
71
|
+
result: RunnerWakeResult;
|
|
72
|
+
};
|
|
73
|
+
stop: {
|
|
74
|
+
args: MachineStopArgs;
|
|
75
|
+
result: Pick<RunnerStopResult, 'confirmed'> & {
|
|
76
|
+
error?: string;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
status: {
|
|
80
|
+
args: MachineStatusArgs;
|
|
81
|
+
result: RunnerStatus;
|
|
82
|
+
};
|
|
83
|
+
delete: {
|
|
84
|
+
args: MachineDeleteArgs;
|
|
85
|
+
result: null;
|
|
86
|
+
};
|
|
87
|
+
viewer: {
|
|
88
|
+
args: MachineViewerArgs;
|
|
89
|
+
result: RunnerViewerAddress;
|
|
90
|
+
};
|
|
91
|
+
'cron.run': {
|
|
92
|
+
args: MachineCronRunArgs;
|
|
93
|
+
result: MachineCronRunResult;
|
|
94
|
+
};
|
|
95
|
+
'cron.cancel': {
|
|
96
|
+
args: MachineCronCancelArgs;
|
|
97
|
+
result: MachineCronCancelResult;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** No live link row for the environment: the caller leaves its work pending and retries on its own backoff. */
|
|
101
|
+
export declare class MachineOfflineError extends Error {
|
|
102
|
+
readonly environmentId: string;
|
|
103
|
+
readonly sent: boolean;
|
|
104
|
+
readonly code = "machine_offline";
|
|
105
|
+
/** True when the call was written to the link before it dropped: the machine may have run it. False: it ran nowhere. */
|
|
106
|
+
constructor(environmentId: string, detail?: string, sent?: boolean);
|
|
107
|
+
}
|
|
108
|
+
/** A forwarded `start` whose outcome is unknown (link dropped or call lost): the execution stays `launching`, the caller never aborts it
|
|
109
|
+
* and never starts another. After the link returns `status` of that execution decides: running is adopted, gone is ended. */
|
|
110
|
+
export declare class MachineLaunchUncertainError extends MachineOfflineError {
|
|
111
|
+
readonly uncertain = true;
|
|
112
|
+
constructor(environmentId: string, detail: string);
|
|
113
|
+
}
|
|
114
|
+
/** The holder claimed the call and never answered before its lease expired: the call may or may not have run. The caller runs `status` before a new `start`. */
|
|
115
|
+
export declare class MachineLostError extends Error {
|
|
116
|
+
readonly environmentId: string;
|
|
117
|
+
readonly callId: string;
|
|
118
|
+
readonly code = "machine_lost";
|
|
119
|
+
constructor(environmentId: string, callId: string);
|
|
120
|
+
}
|
|
121
|
+
/** The machine executed the call and it failed (`result {ok: false, error}`). */
|
|
122
|
+
export declare class MachineCallFailedError extends Error {
|
|
123
|
+
readonly environmentId: string;
|
|
124
|
+
readonly op: MachineOp;
|
|
125
|
+
readonly code = "machine_call_failed";
|
|
126
|
+
constructor(environmentId: string, op: MachineOp, message: string);
|
|
127
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var o=Object.defineProperty;var t=(n,e)=>o(n,"name",{value:e,configurable:!0});const i=["start","wake","stop","status","delete","viewer","cron.run","cron.cancel"];class c extends Error{static{t(this,"MachineOfflineError")}environmentId;sent;code="machine_offline";constructor(e,r,s=!1){super(`machine ${e} is offline${r?`: ${r}`:""}`),this.environmentId=e,this.sent=s}}class l extends c{static{t(this,"MachineLaunchUncertainError")}uncertain=!0;constructor(e,r){super(e,r,!0)}}class h extends Error{static{t(this,"MachineLostError")}environmentId;callId;code="machine_lost";constructor(e,r){super(`call ${r} to machine ${e} was lost`),this.environmentId=e,this.callId=r}}class d extends Error{static{t(this,"MachineCallFailedError")}environmentId;op;code="machine_call_failed";constructor(e,r,s){super(`${r} on machine ${e} failed: ${s}`),this.environmentId=e,this.op=r}}export{i as MACHINE_OPS,d as MachineCallFailedError,l as MachineLaunchUncertainError,h as MachineLostError,c as MachineOfflineError};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Runner, RunnerStartInput } from '../types.js';
|
|
2
|
+
export interface MachineRunnerDeps {
|
|
3
|
+
/** The machine environment a node runs in. Default: the node's profile's `environment_id`, which must be kind `machine`. */
|
|
4
|
+
environmentOf?(nodeId: string): Promise<string>;
|
|
5
|
+
/** Mints the execution's node token (the execution must be `launching` and have none). Default: `mintNodeToken`. */
|
|
6
|
+
mintToken?(input: RunnerStartInput): Promise<string>;
|
|
7
|
+
/** Whether this execution already has a node token (a previous `start` minted it). Default: a `node_tokens` row for the execution. */
|
|
8
|
+
tokenMinted?(executionId: string): Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
/** The machine environment of a node's profile, or null when the profile points at no machine environment. */
|
|
11
|
+
export declare function machineEnvironmentOfNode(nodeId: string): Promise<string | null>;
|
|
12
|
+
export declare function createMachineRunner(deps?: MachineRunnerDeps): Runner;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var E=Object.defineProperty;var i=(e,t)=>E(e,"name",{value:t,configurable:!0});import{openDb as w}from"../../../canvas/db.js";import{mintExecutionToken as k}from"../../../canvas/node-tokens.js";import{callMachine as c}from"./call.js";import{machineEnvironmentOfProfile as p}from"./environment.js";import{MachineLaunchUncertainError as y,MachineLostError as m,MachineOfflineError as u}from"./ops.js";async function v(e){const t=await(await w()).prepare("SELECT profile_id FROM nodes WHERE node_id = ?").get(e);return t?.profile_id==null?null:await p(t.profile_id)}i(v,"machineEnvironmentOfNode");const I=i(async e=>{const t=await v(e);if(t===null)throw new Error(`node ${e} has no machine environment`);return t},"defaultEnvironmentOf"),M=i(async e=>await(await w()).prepare("SELECT 1 AS x FROM node_tokens WHERE broker_process_id = ?").get(e)!==void 0,"defaultTokenMinted"),h=i(e=>e instanceof Error?e:new Error(String(e)),"asError");function b(e={}){const t=e.environmentOf??I;return{kind:"machine",async start(n){const a=await t(n.nodeId),o=e.tokenMinted??M,s=i(r=>new y(a,`the launch of execution ${n.executionId} is unconfirmed: ${r}`),"uncertain"),f=await o(n.executionId);let d;try{d=await c(a,"status",{execution:n})}catch(r){throw f&&(r instanceof u||r instanceof m)?s("the machine could not be asked"):r}if(d.state==="running")return{pid:d.pid,exited:null};if(f)throw s("a start was handed over and no broker is running yet");const l=await(e.mintToken??(r=>k(r.executionId,r.nodeId)))(n);try{return{pid:(await c(a,"start",{...n,nodeToken:l})).pid,exited:null}}catch(r){throw r instanceof m||r instanceof u&&r.sent?s(r.message):r}},async wake(n){return await c(await t(n.nodeId),"wake",{execution:n})},async stop(n,a){try{const o=await c(await t(n.nodeId),"stop",{execution:n,options:a});return o.error===void 0?{confirmed:o.confirmed}:{confirmed:o.confirmed,error:new Error(o.error)}}catch(o){return{confirmed:!1,error:h(o)}}},async status(n){try{return await c(await t(n.nodeId),"status",{execution:n})}catch(a){return{state:"unknown",error:h(a).message}}},async delete(n){await c(await t(n),"delete",{nodeId:n})},async viewerAddress(n){return{kind:"machine",environmentId:await t(n.nodeId),nodeId:n.nodeId}}}}i(b,"createMachineRunner");export{b as createMachineRunner,v as machineEnvironmentOfNode};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type RunnerKind = 'process' | 'blaxel';
|
|
1
|
+
export type RunnerKind = 'process' | 'blaxel' | 'machine';
|
|
2
2
|
/** The identity of one execution as the runner is asked about it. Rows already hold these. */
|
|
3
3
|
export interface RunnerExecution {
|
|
4
4
|
executionId: string;
|
|
@@ -70,6 +70,12 @@ export type RunnerViewerAddress = {
|
|
|
70
70
|
kind: 'wss';
|
|
71
71
|
url: string;
|
|
72
72
|
headers: Record<string, string>;
|
|
73
|
+
}
|
|
74
|
+
/** The broker's `view.sock` is on a machine with no public address: the viewer opens a channel over that machine's link (design-import I13, `view.open`). */
|
|
75
|
+
| {
|
|
76
|
+
kind: 'machine';
|
|
77
|
+
environmentId: string;
|
|
78
|
+
nodeId: string;
|
|
73
79
|
};
|
|
74
80
|
export interface Runner {
|
|
75
81
|
readonly kind: RunnerKind;
|
|
@@ -60,6 +60,8 @@ export declare function resumeWarmClaim(request: WarmRequest, nodeId: string, be
|
|
|
60
60
|
* new owner. Only after winning the marker is the broker killed and the row +
|
|
61
61
|
* dir deleted. Never throws: a failed reap is retried on the next sweep. */
|
|
62
62
|
export declare function reapStaleSpares(): Promise<string[]>;
|
|
63
|
+
/** Remove every spare, live or not, with its broker. A drained daemon mints none, so the pool stays empty until the drain ends. */
|
|
64
|
+
export declare function emptyWarmPool(): Promise<void>;
|
|
63
65
|
/** Claims the lease and renews it until the returned release runs; undefined when another holder has it. */
|
|
64
66
|
export declare function holdMintLease(lane: string, key: string): Promise<(() => Promise<void>) | undefined>;
|
|
65
67
|
/** Bring the pool for `request` back to depth 1, in the background. Never
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var N=Object.defineProperty;var n=(e,r)=>N(e,"name",{value:r,configurable:!0});import{createHash as R,randomUUID as L}from"node:crypto";import{openDb as b}from"../canvas/db.js";import{claimReconcilerLease as C,releaseReconcilerLease as E,renewReconcilerLease as W}from"../canvas/reconciler-leases.js";import{claimWarmSpare as M,deleteNode as u,getNode as f,listNodes as P,listWarmSpares as y,registerWarmSpare as I,unregisterWarmSpare as h,updateNode as A,warmSpareCount as g}from"../canvas/index.js";import{recordedPidLiveness as D}from"../canvas/pid.js";import{nowIso as _}from"../fs-utils.js";import{admitNodeCreate as O}from"../command-hooks/lifecycle-create.js";import{isDrained as T}from"../../daemon/drain.js";import{spawnChildPrepared as H}from"./spawn.js";import{blaxelEnvironmentOf as F}from"./blaxel-environment.js";import{newNodeId as K}from"./nodes.js";import{checkStart as U}from"./start-check.js";import{buildLaunchSpecAsync as j}from"./launch.js";import{setModelLive as B}from"./model-swap.js";import{setSituationalContextLive as G}from"./situational-live.js";import{deliverBearingsLive as J}from"./deliver-live.js";import{closeNode as p}from"./close.js";function v(){return process.env.CRTR_WARM_POOL!=="0"}n(v,"warmPoolEnabled");function z(e,r,a){const t=Object.entries(a.env??{}).sort(([s],[i])=>s<i?-1:s>i?1:0),o=JSON.stringify([e,r,a.tools??[],a.extensions??[],a.systemPrompt??null,t]);return R("sha256").update(o).digest("hex").slice(0,32)}n(z,"warmRecipeKey");async function d(e){const{launch:r}=await j(e.kind,e.mode,{lifecycle:"resident",hasManager:!1,...e.model!==null?{model:e.model}:{},cwd:e.cwd,profileId:e.profileId});return{key:z(e.cwd,e.profileId,r),launch:r,request:e}}n(d,"resolveRecipe");async function S(e){const r=e.creator===null?null:await f(e.creator);await U({grantee:r?.grantee??"app:terminal",sub:r?.sub,grant_id:r?.grant_id})}n(S,"checkClaimantStart");async function ye(e,r={},a){await S(e);const t=await d(e);if(await g(t.key)===0)return null;const o=_(),s=await O({kind:e.kind,mode:e.mode,cwd:e.cwd,profile:e.profileId,root:!0,parentId:null,requestingNodeId:e.creator,lifecycle:"resident",replaces:null},e.creator===null?"app:terminal":await(async()=>{const c=await f(e.creator);if(!c?.grantee)throw new Error("warm node creator has no app");return c.grantee})());let i;try{i=await M(t.key,o,r)}finally{s()}return i===null?null:k(i,t,o,a)}n(ye,"claimWarmNode");async function he(e,r,a){return await S(e),k(r,await d(e),_(),a)}n(he,"resumeWarmClaim");async function k(e,r,a,t){const{request:o}=r,i=(await f(e))?.launch?.model;await A(e,{created:a,kind:o.kind,mode:o.mode,name:o.name??o.kind,creator:o.creator,launch:r.launch,persona_ack:{kind:o.kind,mode:o.mode,lifecycle:"resident"},...o.model!==null?{model_override:o.model}:{}});try{t?.(e)}catch(l){return await m(e,"birth preparation",l)}try{await J(e)}catch(l){return await m(e,"bearings delivery",l)}const c=r.launch.model;if(c!==void 0&&c!==i)try{await B(e,c,r.launch.modelIntent)}catch(l){return await m(e,"model swap",l)}if(o.situationalContext!==null)try{await G(e,o.situationalContext)}catch(l){return await m(e,"situational-context delivery",l)}return e}n(k,"completeWarmClaim");async function m(e,r,a){console.error(`[warm-pool] ${r} failed on claim; closing the spare: ${a.message}`);try{await p(e)}catch{}return null}n(m,"abandonSpare");const Q=3600*1e3;async function ge(){const e=[];let r;try{r=await y()}catch(t){return console.error(`[warm-pool] spare scan failed: ${t.message}`),e}const a=Date.now()-Q;for(const t of r){if(await V(t.node_id))continue;const o=Date.parse(t.created),s=Number.isNaN(o)||o<a,i=D(t.pi_pid,t.pi_pid_identity),c=(t.status==="active"||t.status==="idle")&&i==="alive";if(!(c&&!s)&&!(i==="indeterminate"&&!s))try{if(!await h(t.node_id))continue;if(c)try{await p(t.node_id)}catch{}await u(t.node_id),e.push(t.node_id)}catch(l){console.error(`[warm-pool] reap failed for ${t.node_id}: ${l.message}`)}}return e}n(ge,"reapStaleSpares");async function _e(){for(const e of await y())if(await h(e.node_id)){try{await p(e.node_id)}catch(r){console.error(`[warm-pool] close failed for ${e.node_id}: ${r.message}`)}await u(e.node_id)}}n(_e,"emptyWarmPool");const w=6e4;async function x(e,r){const a=L();if(await C(e,r,a,w)===void 0)return;const t=setInterval(()=>{W(e,r,a,w).catch(()=>{})},w/3);return t.unref(),async()=>{clearInterval(t),await E(e,r,a)}}n(x,"holdMintLease");async function V(e){return await(await b()).prepare("SELECT 1 FROM reconciler_leases WHERE lane = ? AND key = ? AND expires_at > ?").get("warm-pool-mint",e,new Date().toISOString())!==void 0}n(V,"mintLeaseHeld");function ve(e){if(v())try{d({...e,model:null,situationalContext:null}).then($).catch(r=>console.error(`[warm-pool] recipe resolution failed: ${r.message}`))}catch(r){console.error(`[warm-pool] recipe resolution failed: ${r.message}`)}}n(ve,"refillWarmPool");async function $(e){if(await T())return;const{key:r}=e;if(await F(e.request.profileId)!==null)return;const a=await x("warm-pool",r);if(a!==void 0){if(await g(r)>0){await a();return}X(e,await K()).finally(()=>a().catch(()=>{}))}}n($,"mint");async function X({key:e,request:r},a){const t=await x("warm-pool-mint",a);if(t===void 0){console.error(`[warm-pool] mint refused for ${r.kind}: the mint lease of ${a} is held elsewhere`);return}try{await H({nodeId:a,kind:r.kind,mode:r.mode,cwd:r.cwd,prompt:"",root:!0,readiness:"await",profile:r.profileId},async o=>{await I(o,e)})}catch(o){console.error(`[warm-pool] mint failed for ${r.kind}: ${o.message}`)}finally{await t().catch(()=>{})}}n(X,"birthSpare");const Y=2;async function Se(){if(!v())return;let e;try{const r=new Set;e=[];for(const a of(await P({status:["active","idle"]})).reverse()){if(a.parent!==null||a.kind==="human")continue;const t=a.profile_id??null;if(!r.has(t??"")&&(r.add(t??""),e.push({cwd:a.cwd,profileId:t}),e.length>=Y))break}}catch(r){console.error(`[warm-pool] prewarm scan failed: ${r.message}`);return}for(const r of e)try{d({kind:"general",mode:"base",cwd:r.cwd,profileId:r.profileId,model:null,situationalContext:null,name:null,creator:null}).then($).catch(a=>console.error(`[warm-pool] prewarm failed for ${r.cwd}: ${a.message}`))}catch(a){console.error(`[warm-pool] prewarm failed for ${r.cwd}: ${a.message}`)}}n(Se,"prewarmRecentRecipes");export{X as birthSpare,ye as claimWarmNode,_e as emptyWarmPool,x as holdMintLease,Se as prewarmRecentRecipes,ge as reapStaleSpares,ve as refillWarmPool,he as resumeWarmClaim,v as warmPoolEnabled};
|
|
@@ -6,6 +6,7 @@ export interface EnvOwner {
|
|
|
6
6
|
kind: EnvOwnerKind;
|
|
7
7
|
id: string;
|
|
8
8
|
}
|
|
9
|
+
export declare const aadOf: (owner: EnvOwner, name: string) => string;
|
|
9
10
|
/** Insert or replace one value. Returns true when the name was new. */
|
|
10
11
|
export declare function putEnvRow(owner: EnvOwner, name: string, value: string, key?: SecretsKey): Promise<boolean>;
|
|
11
12
|
/** Seal one value into an open transaction, keeping a row that already holds the name. Used by the one-time boot copy. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
var u=Object.defineProperty;var t=(e,n)=>u(e,"name",{value:n,configurable:!0});import{openDb as o}from"../canvas/db.js";import{loadSecretsKey as i,open as l,seal as c}from"./index.js";const E=t((e,n)=>`env_vars|${e.person??""}|${e.kind}|${e.id}|${n}`,"aadOf"),p=t(e=>e.person??"","personKey");async function D(e,n,a,d=i()){const{sealed:r,keyId:s}=c(a,d,E(e,n));return(await(await o()).prepare(`INSERT INTO env_vars (person, owner_kind, owner_id, name, value_sealed, key_id, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2
2
|
ON CONFLICT (COALESCE(person, ''), owner_kind, owner_id, name) DO UPDATE SET value_sealed = EXCLUDED.value_sealed, key_id = EXCLUDED.key_id, updated_at = EXCLUDED.updated_at
|
|
3
3
|
RETURNING (xmax = 0) AS created`).get(e.person,e.kind,e.id,n,r,s,new Date().toISOString())).created}t(D,"putEnvRow");async function N(e,n,a,d,r=i()){const{sealed:s,keyId:_}=c(d,r,E(n,a));await e.prepare(`INSERT INTO env_vars (person, owner_kind, owner_id, name, value_sealed, key_id, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
4
|
-
ON CONFLICT (COALESCE(person, ''), owner_kind, owner_id, name) DO NOTHING`).run(n.person,n.kind,n.id,a,s,_,new Date().toISOString())}t(N,"putEnvRowIfAbsent");async function C(e,n){return(await(await o()).prepare("DELETE FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ? AND name = ?").run(p(e),e.kind,e.id,n)).changes>0}t(C,"removeEnvRow");async function S(e){return(await(await o()).prepare("SELECT name FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ? ORDER BY name").all(p(e),e.kind,e.id)).map(a=>a.name)}t(S,"listEnvRowNames");async function m(e,n=i()){const a=await(await o()).prepare("SELECT name, value_sealed, key_id FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ?").all(p(e),e.kind,e.id),d=Object.create(null);for(const r of a)d[r.name]=l(r.value_sealed,r.key_id,n,E(e,r.name));return d}t(m,"readEnvRows");export{S as listEnvRowNames,D as putEnvRow,N as putEnvRowIfAbsent,m as readEnvRows,C as removeEnvRow};
|
|
4
|
+
ON CONFLICT (COALESCE(person, ''), owner_kind, owner_id, name) DO NOTHING`).run(n.person,n.kind,n.id,a,s,_,new Date().toISOString())}t(N,"putEnvRowIfAbsent");async function C(e,n){return(await(await o()).prepare("DELETE FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ? AND name = ?").run(p(e),e.kind,e.id,n)).changes>0}t(C,"removeEnvRow");async function S(e){return(await(await o()).prepare("SELECT name FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ? ORDER BY name").all(p(e),e.kind,e.id)).map(a=>a.name)}t(S,"listEnvRowNames");async function m(e,n=i()){const a=await(await o()).prepare("SELECT name, value_sealed, key_id FROM env_vars WHERE COALESCE(person, '') = ? AND owner_kind = ? AND owner_id = ?").all(p(e),e.kind,e.id),d=Object.create(null);for(const r of a)d[r.name]=l(r.value_sealed,r.key_id,n,E(e,r.name));return d}t(m,"readEnvRows");export{E as aadOf,S as listEnvRowNames,D as putEnvRow,N as putEnvRowIfAbsent,m as readEnvRows,C as removeEnvRow};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const PURGE_RETENTION_MS: number;
|
|
2
|
+
/** Purge up to `limit` free rows in one transaction; returns how many went. A reference added between the check and the delete
|
|
3
|
+
* (23503) leaves everything in place and the next pass tries again. */
|
|
4
|
+
export declare function purgeFreeRows(now: number, limit: number): Promise<number>;
|
|
5
|
+
/** Purge until a round finds nothing free or `budget` rows are gone; `holds` is checked before each round. */
|
|
6
|
+
export declare function purgeSoftDeleted(now: number, budget: number, holds?: () => Promise<boolean>, batch?: number): Promise<number>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var c=Object.defineProperty;var a=(o,r)=>c(o,"name",{value:r,configurable:!0});import{withCanvasWrite as d}from"../canvas/db.js";const s=720*60*6e4,R=`o.deleted_at IS NOT NULL AND o.deleted_at < ? AND o.type <> 'node'
|
|
2
|
+
AND NOT EXISTS (SELECT 1 FROM objects x WHERE x.owner = o.id)
|
|
3
|
+
AND NOT EXISTS (SELECT 1 FROM revisions r WHERE r.author = o.id)
|
|
4
|
+
AND NOT EXISTS (SELECT 1 FROM object_kinds k WHERE k.owner = o.id AND EXISTS (SELECT 1 FROM objects t WHERE t.type = k.name))`;async function S(o,r){const E=new Date(o-s).toISOString();try{return await d(async e=>{const t=(await e.prepare(`SELECT o.id FROM objects o WHERE ${R} ORDER BY o.deleted_at, o.id LIMIT ?`).all(E,r)).map(i=>i.id);return t.length===0?0:(await e.prepare("DELETE FROM object_kinds WHERE owner = ANY(?::text[])").run(t),(await e.prepare("DELETE FROM objects WHERE id = ANY(?::text[]) AND deleted_at < ?").run(t,E)).changes)},{savepoint:!0})}catch(e){if(e.code==="23503")return 0;throw e}}a(S,"purgeFreeRows");async function N(o,r,E=async()=>!0,e=100){let t=0;for(;t<r&&await E();){const n=await S(o,Math.min(e,r-t));if(n===0)break;t+=n}return t}a(N,"purgeSoftDeleted");export{s as PURGE_RETENTION_MS,S as purgeFreeRows,N as purgeSoftDeleted};
|
|
@@ -38,6 +38,7 @@ export declare class RefreshBusyError extends Error {
|
|
|
38
38
|
export declare class RefreshTimeoutError extends Error {
|
|
39
39
|
constructor(provider: ManagedProviderId);
|
|
40
40
|
}
|
|
41
|
+
export declare const aadOf: (person: string, provider: string, id: string, part: "refresh" | "access") => string;
|
|
41
42
|
export declare function insertAccount(db: CanvasDb, person: string, provider: ManagedProviderId, row: ProviderRow, entry: StoredCredential): Promise<void>;
|
|
42
43
|
export declare function upsertModelCooldown(db: CanvasDb, person: string, provider: string, accountId: string, family: string, until: number, rateLimitedAt: number): Promise<void>;
|
|
43
44
|
export declare function readSubscriptionPool(provider: ManagedProviderId): Promise<SubscriptionCredential[]>;
|