@parcha/pi-durable-disk 0.1.0-beta.13 → 0.1.0-beta.15
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/CHANGELOG.md +262 -0
- package/README.md +281 -47
- package/dist/app.d.ts +5 -1
- package/dist/claim.d.ts +51 -3
- package/dist/claim.js +157 -17
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +112 -47
- package/dist/commands.d.ts +4 -1
- package/dist/commands.js +48 -90
- package/dist/confine.d.ts +36 -7
- package/dist/confine.js +125 -18
- package/dist/env.d.ts +7 -22
- package/dist/env.js +25 -60
- package/dist/errors.d.ts +2 -1
- package/dist/fork.d.ts +23 -0
- package/dist/fork.js +103 -3
- package/dist/hosts/attempt-env.d.ts +3 -0
- package/dist/hosts/attempt-env.js +15 -0
- package/dist/hosts/daytona-launch.d.ts +3 -1
- package/dist/hosts/daytona-launch.js +35 -12
- package/dist/hosts/daytona.d.ts +2 -2
- package/dist/hosts/daytona.js +48 -5
- package/dist/hosts/docker.d.ts +2 -2
- package/dist/hosts/docker.js +22 -5
- package/dist/hosts/local-host.d.ts +9 -3
- package/dist/hosts/local-host.js +59 -22
- package/dist/index.d.ts +35 -16
- package/dist/index.js +27 -16
- package/dist/internals.d.ts +3 -0
- package/dist/internals.js +6 -0
- package/dist/lease.d.ts +4 -1
- package/dist/lease.js +2 -0
- package/dist/local-run.js +3 -2
- package/dist/place.d.ts +190 -0
- package/dist/place.js +526 -0
- package/dist/process-table.d.ts +38 -0
- package/dist/process-table.js +158 -0
- package/dist/run.d.ts +61 -10
- package/dist/run.js +69 -33
- package/dist/status.d.ts +19 -3
- package/dist/status.js +25 -13
- package/dist/store.d.ts +14 -2
- package/dist/store.js +16 -4
- package/dist/supervise.d.ts +43 -1
- package/dist/supervise.js +26 -1
- package/dist/watchdog.d.ts +3 -1
- package/dist/watchdog.js +43 -12
- package/dist/with-claim.d.ts +63 -0
- package/dist/with-claim.js +325 -0
- package/package.json +9 -3
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
// status <name> print running, stopped or failed from the state file and whether the processes still live
|
|
10
10
|
// stop <name> SIGTERM the launcher (it drains the instance), SIGKILL both after the timeout, print the state
|
|
11
11
|
// Files live in a root-only directory (default /run/pda, 0700), which the run user can never traverse: `<name>.json`
|
|
12
|
-
// (the spec), `<name>.token` (gone once read), `<name>.state`, `<name>.log` (the instance's output).
|
|
13
|
-
//
|
|
12
|
+
// (the spec), `<name>.token` (gone once read), `<name>.state`, `<name>.log` (the instance's output). Every incarnation of
|
|
13
|
+
// the attempt gets its token on stdin: a restart whose mount is still live reuses it and never needs the token, and one
|
|
14
|
+
// whose mount went with the failed incarnation mounts again (a takeover in between makes that mount HeldError, 76,
|
|
15
|
+
// terminal). The token lives in this launcher's memory for the attempt.
|
|
14
16
|
// No cgroup per incarnation: commands a crashed instance detached can outlive it until the box is deleted.
|
|
15
17
|
import { spawn } from "node:child_process";
|
|
16
|
-
import { closeSync, existsSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { closeSync, existsSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
17
19
|
import { join, resolve } from "node:path";
|
|
18
20
|
import { fileURLToPath } from "node:url";
|
|
19
21
|
import { procStartTicks, TERMINAL_EXITS } from "./local-host.js";
|
|
@@ -43,14 +45,17 @@ function writeState(path, state) {
|
|
|
43
45
|
// Only a real process with its start ticks: `stop` signals these pids, the instance's as a group, and a pid of 1 or below
|
|
44
46
|
// names a group (`kill(-1)` is every process this user may signal). The state file holds whatever was written there.
|
|
45
47
|
const alive = (pid, ticks) => Number.isInteger(pid) && pid > 1 && typeof ticks === "number" && procStartTicks(pid) === ticks;
|
|
46
|
-
/** running while either process lives; otherwise stopped after a clean exit,
|
|
48
|
+
/** running while either process lives and the launcher has not recorded its exit; otherwise stopped after a clean exit,
|
|
49
|
+
* failed after any other end. Every "exited" the launcher saves is final and comes after its instance ended, so once it is
|
|
50
|
+
* saved (and no instance lives) the record is the answer, even while the launcher's own process is still exiting. */
|
|
47
51
|
export function launchStatus(dir, name) {
|
|
48
52
|
const state = readState(dir, name);
|
|
49
53
|
if (!state)
|
|
50
54
|
return { status: "failed", state: null, launcherAlive: false, instanceAlive: false };
|
|
51
55
|
const launcherAlive = alive(state.launcher, state.launcherTicks);
|
|
52
56
|
const instanceAlive = alive(state.instance, state.instanceTicks);
|
|
53
|
-
|
|
57
|
+
const ended = state.phase === "exited" && !instanceAlive;
|
|
58
|
+
if (!ended && (launcherAlive || instanceAlive))
|
|
54
59
|
return { status: "running", state, launcherAlive, instanceAlive };
|
|
55
60
|
const clean = state.phase === "exited" && state.exit === 0;
|
|
56
61
|
return { status: clean ? "stopped" : "failed", state, launcherAlive, instanceAlive };
|
|
@@ -125,9 +130,9 @@ export async function serve(dir, name) {
|
|
|
125
130
|
try {
|
|
126
131
|
for (;;) {
|
|
127
132
|
const env = { PATH: SAFE_PATH, LANG: "C.UTF-8", HOME: ids.home, ...spec.env };
|
|
128
|
-
const
|
|
133
|
+
const piped = Boolean(token);
|
|
129
134
|
try {
|
|
130
|
-
child = spawn(spec.argv[0], spec.argv.slice(1), { uid: ids.uid, gid: ids.gid, env, cwd: "/", stdio: [
|
|
135
|
+
child = spawn(spec.argv[0], spec.argv.slice(1), { uid: ids.uid, gid: ids.gid, env, cwd: "/", stdio: [piped ? "pipe" : "ignore", log, log], detached: true });
|
|
131
136
|
await new Promise((resolve, reject) => {
|
|
132
137
|
child.once("spawn", resolve);
|
|
133
138
|
child.once("error", reject);
|
|
@@ -137,10 +142,9 @@ export async function serve(dir, name) {
|
|
|
137
142
|
save({ phase: "exited", instance: null, instanceTicks: null, exit: 1, reason: `spawn failed: ${err.message}` });
|
|
138
143
|
return state;
|
|
139
144
|
}
|
|
140
|
-
if (
|
|
145
|
+
if (piped) {
|
|
141
146
|
child.stdin.on("error", () => { });
|
|
142
|
-
child.stdin.end(
|
|
143
|
-
token = null;
|
|
147
|
+
child.stdin.end(`${token}\n`);
|
|
144
148
|
}
|
|
145
149
|
save({ phase: stopping ? "stopping" : "running", instance: child.pid, instanceTicks: procStartTicks(child.pid), spawned: state.spawned + 1, exit: null, signal: null });
|
|
146
150
|
if (stopping)
|
|
@@ -216,8 +220,12 @@ export async function stop(dir, name, timeoutMs) {
|
|
|
216
220
|
}
|
|
217
221
|
};
|
|
218
222
|
let s = launchStatus(dir, name);
|
|
219
|
-
if (s.status !== "running")
|
|
223
|
+
if (s.status !== "running") {
|
|
224
|
+
// The launcher recorded its exit and is ending on its own; a launcher process that outstays its record is still sent away.
|
|
225
|
+
if (s.launcherAlive)
|
|
226
|
+
kill(s.state.launcher, s.state.launcherTicks, "SIGTERM");
|
|
220
227
|
return s;
|
|
228
|
+
}
|
|
221
229
|
if (s.launcherAlive)
|
|
222
230
|
kill(s.state.launcher, s.state.launcherTicks, "SIGTERM");
|
|
223
231
|
else
|
|
@@ -262,7 +270,22 @@ async function main(argv) {
|
|
|
262
270
|
return 2;
|
|
263
271
|
}
|
|
264
272
|
}
|
|
265
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Whether Node was started with this module. LAUNCH_SCRIPT comes from import.meta.url, which is the real path; argv[1] is
|
|
275
|
+
* the path as given, which may go through a link (a symlinked package directory, pnpm's linked node_modules).
|
|
276
|
+
*/
|
|
277
|
+
function invokedAsMain() {
|
|
278
|
+
const arg = process.argv[1];
|
|
279
|
+
if (!arg)
|
|
280
|
+
return false;
|
|
281
|
+
try {
|
|
282
|
+
return realpathSync(arg) === LAUNCH_SCRIPT;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return resolve(arg) === LAUNCH_SCRIPT;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (invokedAsMain()) {
|
|
266
289
|
main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
|
|
267
290
|
process.stderr.write(`${err.message}\n`);
|
|
268
291
|
process.exit(1);
|
package/dist/hosts/daytona.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface DaytonaRestOptions {
|
|
|
60
60
|
}
|
|
61
61
|
/** The REST API with an API key (Bearer). Error messages carry the method, path and status, never a header or a body sent. */
|
|
62
62
|
export declare function daytonaRest(options: DaytonaRestOptions): DaytonaClient;
|
|
63
|
-
export type DaytonaHostErrorCode = "INVALID_ARGUMENT" | "START_FAILED" | "STOP_FAILED" | "DAYTONA_API_FAILED";
|
|
63
|
+
export type DaytonaHostErrorCode = "INVALID_ARGUMENT" | "START_FAILED" | "STOP_FAILED" | "KILL_FAILED" | "DAYTONA_API_FAILED";
|
|
64
64
|
export declare class DaytonaHostError extends PdaError {
|
|
65
65
|
constructor(code: DaytonaHostErrorCode, message: string, options?: {
|
|
66
66
|
cause?: unknown;
|
|
@@ -133,7 +133,7 @@ export interface DaytonaHostOptions {
|
|
|
133
133
|
export declare function sandboxStatus(state: string | undefined): HostStatus | "check";
|
|
134
134
|
/** A Daytona-safe box name for a run: lowercase, at most 63 characters, unique per call. */
|
|
135
135
|
export declare function sandboxName(prefix: string, runId: string, now: number): string;
|
|
136
|
-
export declare function daytonaHost(opts: DaytonaHostOptions): HostDriver & {
|
|
136
|
+
export declare function daytonaHost(opts: DaytonaHostOptions): HostDriver & Required<Pick<HostDriver, "kill" | "kills">> & {
|
|
137
137
|
readonly fleet: string;
|
|
138
138
|
readonly mountRoot: string;
|
|
139
139
|
};
|
package/dist/hosts/daytona.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// in it, the commands a crashed incarnation detached included. The supervisor holds the API key; boxes never do.
|
|
16
16
|
import { DEFAULT_MOUNT_ROOT, runPath } from "../claim.js";
|
|
17
17
|
import { PdaError } from "../errors.js";
|
|
18
|
+
import { attemptEnv } from "./attempt-env.js";
|
|
18
19
|
import { LAUNCH_DIR } from "./daytona-launch.js";
|
|
19
20
|
import { TERMINAL_EXITS } from "./local-host.js";
|
|
20
21
|
export const DAYTONA_API_URL = "https://app.daytona.io/api";
|
|
@@ -121,7 +122,11 @@ export class DaytonaHostError extends PdaError {
|
|
|
121
122
|
super(code, message, options);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
+
// Shell-inert characters only: the paths go into launcher command lines. `@` admits npm's scoped package directories
|
|
126
|
+
// (`node_modules/@scope/pkg`), where the package is installed with npm and not copied to a fixed directory.
|
|
127
|
+
const BOX_PATH = /^\/[A-Za-z0-9._@/-]+$/;
|
|
128
|
+
/** A path that climbs out of where it names: never a box path, whatever else is in it. */
|
|
129
|
+
const climbs = (path) => path.split("/").includes("..");
|
|
125
130
|
const ACCOUNT = /^(?:[a-z_][a-z0-9_-]{0,31}|\d{1,10})$/;
|
|
126
131
|
const FLEET = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
127
132
|
const UP = new Set(["creating", "pulling_snapshot", "pending_build", "building_snapshot", "starting", "restoring", "resuming", "resizing", "snapshotting", "forking", "stopping", "pausing", "paused"]);
|
|
@@ -152,6 +157,14 @@ export function sandboxName(prefix, runId, now) {
|
|
|
152
157
|
return `${prefix}${slug}-${stamp}`.slice(0, 63);
|
|
153
158
|
}
|
|
154
159
|
/** The last line of a command's output that parses as JSON, or null. */
|
|
160
|
+
/**
|
|
161
|
+
* What the launcher printed, for a start it gave no reason for: the last 4 KB, or that it printed nothing. It prints only
|
|
162
|
+
* its state; the mount token is in a root-only file it reads, never in its output.
|
|
163
|
+
*/
|
|
164
|
+
function launcherOutput(text) {
|
|
165
|
+
const out = text.trim();
|
|
166
|
+
return out ? `the launcher printed: ${out.slice(-4096)}` : "the launcher printed nothing and exited 0";
|
|
167
|
+
}
|
|
155
168
|
function lastJson(text) {
|
|
156
169
|
for (const line of text.trim().split("\n").reverse()) {
|
|
157
170
|
try {
|
|
@@ -185,7 +198,7 @@ export function daytonaHost(opts) {
|
|
|
185
198
|
const now = opts.now ?? Date.now;
|
|
186
199
|
const launch = opts.launcher ?? `${packageDir}/dist/hosts/daytona-launch.js`;
|
|
187
200
|
for (const [what, value] of Object.entries({ mountRoot, node, packageDir, archil, boxDir: dir, stageDir: stage, launcher: launch })) {
|
|
188
|
-
if (!BOX_PATH.test(value))
|
|
201
|
+
if (!BOX_PATH.test(value) || climbs(value))
|
|
189
202
|
throw new DaytonaHostError("INVALID_ARGUMENT", `${what} ${JSON.stringify(value)} is not a plain absolute path`);
|
|
190
203
|
}
|
|
191
204
|
if (!ACCOUNT.test(user) || !ACCOUNT.test(group))
|
|
@@ -246,10 +259,12 @@ export function daytonaHost(opts) {
|
|
|
246
259
|
return {
|
|
247
260
|
fleet,
|
|
248
261
|
mountRoot,
|
|
249
|
-
|
|
262
|
+
kills: "box",
|
|
263
|
+
async start(ref, mountToken, attempt) {
|
|
250
264
|
runPath(ref.id);
|
|
251
265
|
if (!mountToken)
|
|
252
266
|
throw new DaytonaHostError("INVALID_ARGUMENT", "no mount token");
|
|
267
|
+
const extra = attemptEnv(attempt, (m) => new DaytonaHostError("INVALID_ARGUMENT", m));
|
|
253
268
|
const t0 = now();
|
|
254
269
|
const name = sandboxName(prefix, ref.id, t0);
|
|
255
270
|
const body = {
|
|
@@ -273,7 +288,7 @@ export function daytonaHost(opts) {
|
|
|
273
288
|
const handle = { driver: "daytona", fleet, target: box.target ?? opts.target ?? null, sandboxId: box.id, name, mountpoint: `${mountRoot}/${runPath(ref.id)}` };
|
|
274
289
|
const spec = {
|
|
275
290
|
argv: [...command, "run", "--disk", ref.disk, "--region", ref.region, "--id", ref.id, "--mount-root", mountRoot, "--archil", archil, ...(opts.runArgs ?? []), "--token-stdin"],
|
|
276
|
-
env: { ...opts.env, PDA_HOLDER: JSON.stringify(handle) },
|
|
291
|
+
env: { ...opts.env, ...extra, PDA_HOLDER: JSON.stringify(handle) },
|
|
277
292
|
user,
|
|
278
293
|
group,
|
|
279
294
|
restart: opts.restart !== false,
|
|
@@ -288,7 +303,7 @@ export function daytonaHost(opts) {
|
|
|
288
303
|
const r = await run(box, launcher("start", name, left), Math.ceil(left / 1000) + 10, "starting the instance");
|
|
289
304
|
const state = lastJson(r.result);
|
|
290
305
|
if (!state || !(state.spawned > 0)) {
|
|
291
|
-
throw new DaytonaHostError("START_FAILED", `the instance in ${name} did not start: ${state?.reason
|
|
306
|
+
throw new DaytonaHostError("START_FAILED", `the instance in ${name} did not start: ${state?.reason || launcherOutput(r.result)}`);
|
|
292
307
|
}
|
|
293
308
|
return handle;
|
|
294
309
|
}
|
|
@@ -352,6 +367,34 @@ export function daytonaHost(opts) {
|
|
|
352
367
|
}
|
|
353
368
|
}
|
|
354
369
|
},
|
|
370
|
+
async kill(h) {
|
|
371
|
+
if (!mine(h))
|
|
372
|
+
return;
|
|
373
|
+
const id = h.sandboxId;
|
|
374
|
+
let box;
|
|
375
|
+
try {
|
|
376
|
+
box = await client.get(id);
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
throw new DaytonaHostError("KILL_FAILED", `reading sandbox ${h.name} to confirm it is this fleet's: ${err.message}`, { cause: err });
|
|
380
|
+
}
|
|
381
|
+
if (box === null || box.labels?.[LABEL_FLEET] !== fleet)
|
|
382
|
+
return;
|
|
383
|
+
// The box goes with everything in it, the instance and its mount: no launcher stop, no drain.
|
|
384
|
+
try {
|
|
385
|
+
await client.remove(id);
|
|
386
|
+
}
|
|
387
|
+
catch (first) {
|
|
388
|
+
// A delete refused mid-transition: power the box off, then delete again.
|
|
389
|
+
await client.stop(id, true).catch(() => { });
|
|
390
|
+
try {
|
|
391
|
+
await client.remove(id);
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
throw new DaytonaHostError("KILL_FAILED", `deleting sandbox ${h.name} failed: ${err.message}`, { cause: first });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
355
398
|
};
|
|
356
399
|
}
|
|
357
400
|
/** Delete every box of a fleet (the janitor after a crash of whoever started them); returns what it asked to delete. */
|
package/dist/hosts/docker.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export interface DockerHostOptions {
|
|
|
58
58
|
exec?: DockerRunner;
|
|
59
59
|
}
|
|
60
60
|
export declare class DockerHostError extends PdaError {
|
|
61
|
-
constructor(code: "INVALID_ARGUMENT" | "START_FAILED" | "STOP_FAILED" | "DOCKER_FAILED", message: string, options?: {
|
|
61
|
+
constructor(code: "INVALID_ARGUMENT" | "START_FAILED" | "STOP_FAILED" | "KILL_FAILED" | "DOCKER_FAILED", message: string, options?: {
|
|
62
62
|
cause?: unknown;
|
|
63
63
|
});
|
|
64
64
|
}
|
|
@@ -95,7 +95,7 @@ export type ContainerInfo = {
|
|
|
95
95
|
startedAt: string | null;
|
|
96
96
|
finishedAt: string | null;
|
|
97
97
|
};
|
|
98
|
-
export declare function dockerHost(opts?: DockerHostOptions): HostDriver & {
|
|
98
|
+
export declare function dockerHost(opts?: DockerHostOptions): HostDriver & Required<Pick<HostDriver, "kill" | "kills">> & {
|
|
99
99
|
readonly fleet: string;
|
|
100
100
|
readonly mountRoot: string;
|
|
101
101
|
describe(handle: HostHandle): Promise<ContainerInfo | null>;
|
package/dist/hosts/docker.js
CHANGED
|
@@ -23,6 +23,7 @@ import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:p
|
|
|
23
23
|
import { DEFAULT_MOUNT_ROOT, runPath } from "../claim.js";
|
|
24
24
|
import { PdaError } from "../errors.js";
|
|
25
25
|
import { LOCAL_PARK_THRESHOLD_MS } from "../park.js";
|
|
26
|
+
import { attemptEnv } from "./attempt-env.js";
|
|
26
27
|
export class DockerHostError extends PdaError {
|
|
27
28
|
constructor(code, message, options = {}) {
|
|
28
29
|
super(code, message, options);
|
|
@@ -191,8 +192,8 @@ export function dockerHost(opts = {}) {
|
|
|
191
192
|
if (!ok(r) && !NO_SUCH.test(r.stderr))
|
|
192
193
|
throw new DockerHostError("DOCKER_FAILED", `docker rm -f ${ref}: ${firstLine(r)}`);
|
|
193
194
|
}
|
|
194
|
-
function createArgs(ref, name, attempt, holder, profile) {
|
|
195
|
-
const env = { ...opts.env, PDA_HOLDER: JSON.stringify(holder) };
|
|
195
|
+
function createArgs(ref, name, attempt, holder, profile, extra) {
|
|
196
|
+
const env = { ...opts.env, ...extra, PDA_HOLDER: JSON.stringify(holder) };
|
|
196
197
|
const runFlags = [
|
|
197
198
|
"run",
|
|
198
199
|
"--disk", ref.disk,
|
|
@@ -228,10 +229,10 @@ export function dockerHost(opts = {}) {
|
|
|
228
229
|
...runFlags,
|
|
229
230
|
];
|
|
230
231
|
}
|
|
231
|
-
async function create(ref, name, attempt, holder) {
|
|
232
|
+
async function create(ref, name, attempt, holder, extra) {
|
|
232
233
|
const profile = await apparmor();
|
|
233
234
|
for (let tries = 0;; tries++) {
|
|
234
|
-
const r = await run(createArgs(ref, name, attempt, holder, profile), { timeoutMs: 120_000 });
|
|
235
|
+
const r = await run(createArgs(ref, name, attempt, holder, profile, extra), { timeoutMs: 120_000 });
|
|
235
236
|
if (ok(r))
|
|
236
237
|
return { id: r.stdout.trim().split("\n").at(-1).trim() };
|
|
237
238
|
if (!IN_USE.test(r.stderr) || tries > 0)
|
|
@@ -270,6 +271,7 @@ export function dockerHost(opts = {}) {
|
|
|
270
271
|
return {
|
|
271
272
|
fleet,
|
|
272
273
|
mountRoot,
|
|
274
|
+
kills: "box",
|
|
273
275
|
async start(ref, token, attemptInfo) {
|
|
274
276
|
runPath(ref.id);
|
|
275
277
|
if (!opts.image)
|
|
@@ -277,11 +279,12 @@ export function dockerHost(opts = {}) {
|
|
|
277
279
|
if (!token || /[\r\n]/.test(token))
|
|
278
280
|
throw new DockerHostError("INVALID_ARGUMENT", "no mount token, or one with a line break");
|
|
279
281
|
const attempt = attemptInfo?.attempt ?? null;
|
|
282
|
+
const extra = attemptEnv(attemptInfo, (m) => new DockerHostError("INVALID_ARGUMENT", m));
|
|
280
283
|
const name = `${prefix}${ref.id}-${diskKey(ref)}-${attempt === null ? `t${Date.now().toString(36)}` : `g${attempt}`}`;
|
|
281
284
|
if (!NAME_SAFE.test(name))
|
|
282
285
|
throw new DockerHostError("INVALID_ARGUMENT", `container name ${name} is not safe`);
|
|
283
286
|
const handle = { driver: "docker", fleet, daemon: await daemon(), name, mountpoint: posix.join(mountRoot, runPath(ref.id)), image: opts.image };
|
|
284
|
-
const made = await create(ref, name, attempt, handle);
|
|
287
|
+
const made = await create(ref, name, attempt, handle, extra);
|
|
285
288
|
// An earlier start of this attempt made the container: the token given to this start was not used.
|
|
286
289
|
if ("adopted" in made)
|
|
287
290
|
return { ...handle, adopted: true };
|
|
@@ -336,6 +339,20 @@ export function dockerHost(opts = {}) {
|
|
|
336
339
|
if (await inspect(ref))
|
|
337
340
|
throw new DockerHostError("STOP_FAILED", `container ${ref} is still there after docker rm -f`);
|
|
338
341
|
},
|
|
342
|
+
async kill(h) {
|
|
343
|
+
if (!(await mine(h)))
|
|
344
|
+
return;
|
|
345
|
+
const ref = target(h);
|
|
346
|
+
const found = await inspect(ref);
|
|
347
|
+
if (!found || found.labels["pda.fleet"] !== fleet)
|
|
348
|
+
return;
|
|
349
|
+
// SIGKILL, no stop timeout: the container dies with its mount (its own namespace), as on a power loss. It is left
|
|
350
|
+
// exited, not removed; the next start of the run removes it.
|
|
351
|
+
const r = await run(["kill", ref], { timeoutMs: 30_000 });
|
|
352
|
+
if (!ok(r) && !NO_SUCH.test(r.stderr) && (await inspect(ref).then((f) => f && containerStatus(f.state) === "running"))) {
|
|
353
|
+
throw new DockerHostError("KILL_FAILED", `docker kill ${ref}: ${firstLine(r)}`);
|
|
354
|
+
}
|
|
355
|
+
},
|
|
339
356
|
};
|
|
340
357
|
}
|
|
341
358
|
/** The bind mount of the app's directory (read-only, its node_modules hidden) and the module's path inside the container. */
|
|
@@ -50,22 +50,28 @@ export interface LocalHostOptions {
|
|
|
50
50
|
procMounts?: string;
|
|
51
51
|
}
|
|
52
52
|
export declare class LocalHostError extends PdaError {
|
|
53
|
-
constructor(code: "INVALID_ARGUMENT" | "START_FAILED" | "STOP_FAILED", message: string, options?: {
|
|
53
|
+
constructor(code: "INVALID_ARGUMENT" | "START_FAILED" | "STOP_FAILED" | "KILL_FAILED", message: string, options?: {
|
|
54
54
|
cause?: unknown;
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
/** The argv of that child for `path`. */
|
|
58
|
+
export declare const statCodeArgv: (path: string) => string[];
|
|
57
59
|
/** Exits a restart cannot fix: 65 data error, 70 store head unreadable, 75 fenced, 76 held. */
|
|
58
60
|
export declare const TERMINAL_EXITS: readonly [65, 70, 75, 76];
|
|
59
61
|
/** Minimal environment, a timeout that settles even if a grandchild keeps the pipes, and the input on stdin. */
|
|
60
62
|
export declare const runCommand: Runner;
|
|
63
|
+
/** This boot's id: `/proc/sys/kernel/random/boot_id`, or `kern.bootsessionuuid` on macOS. Throws when it cannot be read. */
|
|
61
64
|
export declare function currentBootId(): string;
|
|
62
65
|
/** `systemctl show` output as a map. */
|
|
63
66
|
export declare function parseShow(text: string): Record<string, string>;
|
|
64
67
|
/** A unit's state as a host status: loaded and active (or restarting) is running; unloaded is gone. */
|
|
65
68
|
export declare function unitStatus(show: Record<string, string>): HostStatus;
|
|
66
|
-
export declare function localHost(opts?: LocalHostOptions): HostDriver & {
|
|
69
|
+
export declare function localHost(opts?: LocalHostOptions): HostDriver & Required<Pick<HostDriver, "kill" | "kills">> & {
|
|
67
70
|
readonly hostName: string;
|
|
68
71
|
readonly mountRoot: string;
|
|
69
72
|
};
|
|
70
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* A live process's start (field 22 of /proc/<pid>/stat, in clock ticks; on macOS the second it started), so a reused pid is
|
|
75
|
+
* never mistaken for the instance. Null for a zombie, a process that is gone, or one the table cannot be read for.
|
|
76
|
+
*/
|
|
71
77
|
export declare function procStartTicks(pid: number): number | null;
|
package/dist/hosts/local-host.js
CHANGED
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
// place reads nothing and reuses its live mount), the instance closes its stdin after reading, and stop removes it.
|
|
13
13
|
// The token is never in argv, the environment, a systemd credential directory or anything the run's user can open.
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
|
-
import { readFileSync } from "node:fs";
|
|
16
15
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
17
16
|
import { hostname } from "node:os";
|
|
18
17
|
import { extname, join } from "node:path";
|
|
19
18
|
import { fileURLToPath } from "node:url";
|
|
20
|
-
import { ARCHIL_SCOPED, DEFAULT_MOUNT_ROOT, runPath } from "../claim.js";
|
|
19
|
+
import { ARCHIL_SCOPED, DEAD_CONNECTION, DEFAULT_MOUNT_ROOT, runPath } from "../claim.js";
|
|
21
20
|
import { EXIT_FENCED, EXIT_HELD, PdaError } from "../errors.js";
|
|
22
21
|
import { LOCAL_PARK_THRESHOLD_MS } from "../park.js";
|
|
22
|
+
import { processTable, thisBoot } from "../process-table.js";
|
|
23
|
+
import { attemptEnv } from "./attempt-env.js";
|
|
23
24
|
export class LocalHostError extends PdaError {
|
|
24
25
|
constructor(code, message, options = {}) {
|
|
25
26
|
super(code, message, options);
|
|
@@ -32,7 +33,13 @@ const SYSTEMD_RUN = "/usr/bin/systemd-run";
|
|
|
32
33
|
const ARCHIL = "/usr/bin/archil";
|
|
33
34
|
const UNIT_SAFE = /^[A-Za-z0-9:_.-]{1,200}$/;
|
|
34
35
|
const POLITE_UNMOUNT_MS = 10_000;
|
|
35
|
-
|
|
36
|
+
/**
|
|
37
|
+
* A stat of one path in a child process, so a stuck daemon never blocks this one: prints the errno code (or "ok"), never a
|
|
38
|
+
* message, which coreutils' `stat` prints in the locale's language.
|
|
39
|
+
*/
|
|
40
|
+
const STAT_CODE = `try { require("node:fs").statSync(process.argv[1]); process.stdout.write("ok"); } catch (e) { process.stdout.write(String(e.code)); process.exitCode = 1; }`;
|
|
41
|
+
/** The argv of that child for `path`. */
|
|
42
|
+
export const statCodeArgv = (path) => [process.execPath, "-e", STAT_CODE, path];
|
|
36
43
|
const FUSERMOUNT = "/usr/bin/fusermount";
|
|
37
44
|
const UMOUNT = "/usr/bin/umount";
|
|
38
45
|
/** How long a stat of the mountpoint may take before its daemon counts as alive but stuck (never removed). */
|
|
@@ -63,8 +70,12 @@ export const runCommand = (argv, opts = {}) => new Promise((resolve, reject) =>
|
|
|
63
70
|
child.stdin.on("error", () => { });
|
|
64
71
|
child.stdin.end(opts.input ?? "");
|
|
65
72
|
});
|
|
73
|
+
/** This boot's id: `/proc/sys/kernel/random/boot_id`, or `kern.bootsessionuuid` on macOS. Throws when it cannot be read. */
|
|
66
74
|
export function currentBootId() {
|
|
67
|
-
|
|
75
|
+
const boot = thisBoot();
|
|
76
|
+
if (boot === null)
|
|
77
|
+
throw new Error("this machine's boot id cannot be read");
|
|
78
|
+
return boot;
|
|
68
79
|
}
|
|
69
80
|
const firstLine = (r) => (r.stderr.trim().split("\n").at(-1) ?? "").slice(0, 300);
|
|
70
81
|
const okRan = (r) => r.code === 0 && !r.timedOut;
|
|
@@ -151,13 +162,13 @@ export function localHost(opts = {}) {
|
|
|
151
162
|
return rm(path, { force: true });
|
|
152
163
|
await root(["/bin/rm", "-f", path]);
|
|
153
164
|
}
|
|
154
|
-
async function startUnit(ref, token) {
|
|
165
|
+
async function startUnit(ref, token, extra) {
|
|
155
166
|
const unit = `${prefix}${ref.id}-${Date.now().toString(36)}${Math.floor(Math.random() * 1296).toString(36).padStart(2, "0")}`;
|
|
156
167
|
if (!UNIT_SAFE.test(unit))
|
|
157
168
|
throw new LocalHostError("INVALID_ARGUMENT", `unit name ${unit} is not safe`);
|
|
158
169
|
const mountpoint = mountpointOf(ref);
|
|
159
170
|
const handle = { driver: "local", mode: "systemd", host: hostName, bootId, unit, mountpoint };
|
|
160
|
-
const env = { ...opts.env, PATH: SAFE_PATH, PDA_HOLDER: holderEnv(handle) };
|
|
171
|
+
const env = { ...opts.env, ...extra, PATH: SAFE_PATH, PDA_HOLDER: holderEnv(handle) };
|
|
161
172
|
const path = tokenFile(unit);
|
|
162
173
|
await writeTokenFile(path, token);
|
|
163
174
|
let started = false;
|
|
@@ -192,7 +203,7 @@ export function localHost(opts = {}) {
|
|
|
192
203
|
await (started ? emptyTokenFile(path) : removeFile(path)).catch(() => { });
|
|
193
204
|
}
|
|
194
205
|
}
|
|
195
|
-
async function startChild(ref, token) {
|
|
206
|
+
async function startChild(ref, token, extra) {
|
|
196
207
|
const tag = `${prefix}${ref.id}-${Date.now().toString(36)}`;
|
|
197
208
|
const mountpoint = mountpointOf(ref);
|
|
198
209
|
const base = { driver: "local", mode: "child", host: hostName, bootId, mountpoint, tag };
|
|
@@ -201,7 +212,7 @@ export function localHost(opts = {}) {
|
|
|
201
212
|
const { openSync } = await import("node:fs");
|
|
202
213
|
out = openSync(join(opts.logDir, `${tag}.log`), "a", 0o600);
|
|
203
214
|
}
|
|
204
|
-
const env = { ...opts.env, PATH: SAFE_PATH, LANG: "C.UTF-8", HOME: process.env.HOME ?? "/", PDA_HOLDER: holderEnv(base) };
|
|
215
|
+
const env = { ...opts.env, ...extra, PATH: SAFE_PATH, LANG: "C.UTF-8", HOME: process.env.HOME ?? "/", PDA_HOLDER: holderEnv(base) };
|
|
205
216
|
const child = spawn(command[0], [...command.slice(1), ...runFlags(ref), "--token-stdin"], { detached: true, stdio: ["pipe", out, out], env });
|
|
206
217
|
await new Promise((resolve, reject) => {
|
|
207
218
|
child.once("spawn", resolve);
|
|
@@ -234,13 +245,14 @@ export function localHost(opts = {}) {
|
|
|
234
245
|
await new Promise((r) => setTimeout(r, 100));
|
|
235
246
|
}
|
|
236
247
|
/**
|
|
237
|
-
* Whether the mount at `mp` is dead: its FUSE daemon is gone, so the
|
|
238
|
-
*
|
|
239
|
-
*
|
|
248
|
+
* Whether the mount at `mp` is dead: its FUSE daemon is gone, so the stat fails with one of `DEAD_CONNECTION`'s codes
|
|
249
|
+
* (ENOTCONN, or ECONNABORTED for a stat in flight while the dying daemon's connection is torn down). The stat runs in a
|
|
250
|
+
* child with a timeout, so a live but stuck daemon never blocks this process, and the child prints the code, not a
|
|
251
|
+
* message; a stat that answers, fails otherwise or does not finish is a live mount.
|
|
240
252
|
*/
|
|
241
253
|
async function deadMount(mp) {
|
|
242
|
-
const r = await exec(
|
|
243
|
-
return !r.timedOut && r.code !== 0 &&
|
|
254
|
+
const r = await exec(statCodeArgv(mp), { timeoutMs: STAT_TIMEOUT_MS });
|
|
255
|
+
return !r.timedOut && r.code !== 0 && DEAD_CONNECTION.has(r.stdout.trim());
|
|
244
256
|
}
|
|
245
257
|
/** Remove a dead mount: `fusermount -u`, then a lazy `umount -l`. The mount table, not an exit code, decides. */
|
|
246
258
|
async function removeDeadMount(mp) {
|
|
@@ -277,9 +289,11 @@ export function localHost(opts = {}) {
|
|
|
277
289
|
return {
|
|
278
290
|
hostName,
|
|
279
291
|
mountRoot,
|
|
280
|
-
|
|
292
|
+
kills: "process",
|
|
293
|
+
async start(ref, token, attempt) {
|
|
281
294
|
runPath(ref.id);
|
|
282
|
-
|
|
295
|
+
const extra = attemptEnv(attempt, (m) => new LocalHostError("INVALID_ARGUMENT", m));
|
|
296
|
+
return mode === "systemd" ? startUnit(ref, token, extra) : startChild(ref, token, extra);
|
|
283
297
|
},
|
|
284
298
|
async status(h) {
|
|
285
299
|
if (!mine(h))
|
|
@@ -336,21 +350,44 @@ export function localHost(opts = {}) {
|
|
|
336
350
|
await root([SYSTEMCTL, "reset-failed", `${h.unit}.service`]);
|
|
337
351
|
await removeFile(tokenFile(h.unit));
|
|
338
352
|
},
|
|
353
|
+
async kill(h) {
|
|
354
|
+
if (!mine(h) || h.bootId !== bootId)
|
|
355
|
+
return;
|
|
356
|
+
if (h.mode === "child") {
|
|
357
|
+
const pid = Number(h.pid);
|
|
358
|
+
// As in stop: only a real process, and only the one the handle names (its start ticks).
|
|
359
|
+
if (!Number.isInteger(pid) || pid <= 1 || typeof h.startTicks !== "number" || procStartTicks(pid) !== h.startTicks)
|
|
360
|
+
return;
|
|
361
|
+
try {
|
|
362
|
+
process.kill(-pid, "SIGKILL");
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// gone already
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (typeof h.unit !== "string" || !UNIT_SAFE.test(h.unit))
|
|
370
|
+
return;
|
|
371
|
+
// Every process of the unit's control group at once, the instance's commands included. Its FUSE daemon runs in a
|
|
372
|
+
// scope of its own and keeps the mount, as after an OOM kill; the unit restarts in place unless restart is off.
|
|
373
|
+
const r = await root([SYSTEMCTL, "kill", "--signal=SIGKILL", "--kill-whom=all", `${h.unit}.service`]);
|
|
374
|
+
if (!okRan(r) && unitStatus(await show(h.unit)) === "running") {
|
|
375
|
+
throw new LocalHostError("KILL_FAILED", `systemctl kill ${h.unit}: ${r.timedOut ? "timed out" : firstLine(r)}`);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
339
378
|
};
|
|
340
379
|
}
|
|
341
|
-
/**
|
|
380
|
+
/**
|
|
381
|
+
* A live process's start (field 22 of /proc/<pid>/stat, in clock ticks; on macOS the second it started), so a reused pid is
|
|
382
|
+
* never mistaken for the instance. Null for a zombie, a process that is gone, or one the table cannot be read for.
|
|
383
|
+
*/
|
|
342
384
|
export function procStartTicks(pid) {
|
|
343
385
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
344
386
|
return null;
|
|
345
|
-
let stat;
|
|
346
387
|
try {
|
|
347
|
-
|
|
388
|
+
return processTable().snapshot().info(pid)?.start ?? null;
|
|
348
389
|
}
|
|
349
390
|
catch {
|
|
350
391
|
return null;
|
|
351
392
|
}
|
|
352
|
-
const rest = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
353
|
-
if (rest[0] === "Z" || rest[0] === "X")
|
|
354
|
-
return null;
|
|
355
|
-
return Number(rest[19]);
|
|
356
393
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,35 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
1
|
+
export { openDurableRun, RunError, StoreBehindSealError } from "./run.js";
|
|
2
|
+
export type { AfterClaim, BeforeResume, DurableRun, LeaseOptions, OpenDurableRunOptions, OpenRunLeaseOptions, OpenStep, ReleaseStep, RunClaim, RunEnv, RunErrorCode, RunLease, RunSeed, RunSeedDir, StoreHeadSource, StoreReads } from "./run.js";
|
|
3
|
+
export { localRun } from "./local-run.js";
|
|
4
|
+
export type { LocalRun } from "./local-run.js";
|
|
5
|
+
export { CLAIM_UNMOUNTED, openClaimDir, readLocalRunStatus, RunRecordError } from "./status.js";
|
|
6
|
+
export type { ClaimDir, OpenClaimDirOptions, PersistRecord, RunHolder, RunRecord, RunStatus } from "./status.js";
|
|
7
|
+
export { ArchilCodingTools, archilEnv, EnvClosedError, EnvError, markReadOnlySafe } from "./env.js";
|
|
8
|
+
export type { ArchilEnvClaim, ArchilEnvFactory, ArchilEnvOptions, EnvErrorCode } from "./env.js";
|
|
9
|
+
export { acquire, ARCHIL_SCOPED, revoke, takeOver } from "./claim.js";
|
|
10
|
+
export type { AcquireOptions, ArchilHost, Claim, ControlApi, PathlessResolver, RunRef, Unmounted } from "./claim.js";
|
|
11
|
+
export { readReleased, withClaim } from "./with-claim.js";
|
|
12
|
+
export type { ClaimControl, HeldPath, WithClaimOptions } from "./with-claim.js";
|
|
13
|
+
export { PathOutsideWorkError } from "./confine.js";
|
|
14
|
+
export type { RefusalReason } from "./confine.js";
|
|
15
|
+
export { ClaimError, FencedError, HeldError, PdaError } from "./errors.js";
|
|
16
|
+
export type { ClaimErrorCode, Holder } from "./errors.js";
|
|
17
|
+
export type { ArchilStore, Fence, FencedDatabase, Profile, StoreFencedError } from "./store.js";
|
|
18
|
+
export { AppError } from "./app.js";
|
|
19
|
+
export type { AppContext, AppModule, AppOptions } from "./app.js";
|
|
20
|
+
export { archilControl, deleteRunTree, ensureRunning, readRunStatus, SuperviseError } from "./supervise.js";
|
|
21
|
+
export type { ArchilControl, CheckControl, CheckOptions, DiskClient, EnsureOptions, EnsureResult, HostDriver, HostHandle, HostStatus, Json, StartAttempt, StartReason, StonithOutcome, SuperviseErrorCode, SupervisorControl, TokenUser } from "./supervise.js";
|
|
22
|
+
export { localHost, LocalHostError } from "./hosts/local-host.js";
|
|
23
|
+
export type { LocalHostOptions, Ran, Runner } from "./hosts/local-host.js";
|
|
24
|
+
export { dockerHost, DockerHostError } from "./hosts/docker.js";
|
|
25
|
+
export type { ContainerInfo, DockerHostOptions, DockerRan, DockerRunner } from "./hosts/docker.js";
|
|
26
|
+
export { DaytonaApiError, daytonaHost, DaytonaHostError, daytonaRest } from "./hosts/daytona.js";
|
|
27
|
+
export type { CreateSandboxBody, DaytonaClient, DaytonaHostErrorCode, DaytonaHostOptions, DaytonaRestOptions, ExecResult, SandboxInfo } from "./hosts/daytona.js";
|
|
28
|
+
export { requestRun, ServeConfigError, ServeError, serveRun } from "./serve.js";
|
|
29
|
+
export type { RequestOptions, RunResponse, RunServer, ServeConfigErrorCode, ServeErrorCode, ServeOptions } from "./serve.js";
|
|
30
|
+
export { archilPlace, localPlace, PlaceError } from "./place.js";
|
|
31
|
+
export type { ArchilPlaceOptions, LocalPlaceOptions, Place, PlaceContext, PlaceErrorCode, PlaceExit, PlaceKill, PlaceOutcome, PlacePaths, PlaceRecovery, PlaceRunner, PlaceSignal, PlaceStatus, RunnerOrder } from "./place.js";
|
|
32
|
+
export { busyState, drain, leaseParkTarget, watchParking } from "./park.js";
|
|
33
|
+
export type { BusyState, DrainOptions, Parking, ParkOptions, ParkResult, ParkTarget } from "./park.js";
|
|
34
|
+
export { fork, ForkError, forkLocal } from "./fork.js";
|
|
35
|
+
export type { ForkErrorCode, ForkLocalResult, ForkOptions, ForkResult } from "./fork.js";
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// The package's public API. Every name is listed: a new export is a reviewed line here, and test/entries.test.ts pins the
|
|
2
|
+
// list of each entry. `./lease` is the narrow entry for a host that owns its Harness; `./internals` holds plumbing that
|
|
3
|
+
// examples and demos use, with no compatibility promise.
|
|
4
|
+
// The run: open it, on a disk or a local directory, with its environment, its claim and the errors it fails with.
|
|
5
|
+
export { openDurableRun, RunError, StoreBehindSealError } from "./run.js";
|
|
6
|
+
export { localRun } from "./local-run.js";
|
|
7
|
+
export { CLAIM_UNMOUNTED, openClaimDir, readLocalRunStatus, RunRecordError } from "./status.js";
|
|
8
|
+
export { ArchilCodingTools, archilEnv, EnvClosedError, EnvError, markReadOnlySafe } from "./env.js";
|
|
9
|
+
export { acquire, ARCHIL_SCOPED, revoke, takeOver } from "./claim.js";
|
|
10
|
+
// A short exclusive claim on any path of a disk, and a consistent S3 read of a released file (an agent home beside the runs).
|
|
11
|
+
export { readReleased, withClaim } from "./with-claim.js";
|
|
4
12
|
export { PathOutsideWorkError } from "./confine.js";
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
|
|
13
|
+
export { ClaimError, FencedError, HeldError, PdaError } from "./errors.js";
|
|
14
|
+
// The app contract: what `--app` names.
|
|
15
|
+
export { AppError } from "./app.js";
|
|
16
|
+
// The supervisor and the host drivers.
|
|
17
|
+
export { archilControl, deleteRunTree, ensureRunning, readRunStatus, SuperviseError } from "./supervise.js";
|
|
18
|
+
export { localHost, LocalHostError } from "./hosts/local-host.js";
|
|
19
|
+
export { dockerHost, DockerHostError } from "./hosts/docker.js";
|
|
20
|
+
export { DaytonaApiError, daytonaHost, DaytonaHostError, daytonaRest } from "./hosts/daytona.js";
|
|
21
|
+
// Serve: one HTTP request runs one turn.
|
|
22
|
+
export { requestRun, ServeConfigError, ServeError, serveRun } from "./serve.js";
|
|
23
|
+
// Places: where a run lives for a supervisor that kills it and brings it back (agentrun's reference place seam).
|
|
24
|
+
export { archilPlace, localPlace, PlaceError } from "./place.js";
|
|
25
|
+
// Parking: an idle run gives its host back.
|
|
26
|
+
export { busyState, drain, leaseParkTarget, watchParking } from "./park.js";
|
|
27
|
+
// Fork: a new run from a run's committed state.
|
|
28
|
+
export { fork, ForkError, forkLocal } from "./fork.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createRunDir, findDelegations, mintMountToken, pathlessResolver, removeMountToken, runPath, unmountClaim } from "./claim.js";
|
|
2
|
+
export { LABEL_FLEET, LABEL_RUN, sandboxName, sandboxStatus, sweepSandboxes } from "./hosts/daytona.js";
|
|
3
|
+
export { isLeaseFresh, parseRunRecord, RUN_JSON } from "./status.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// `@parcha/pi-durable-disk/internals`: the claim, status and Daytona plumbing that this repository's examples and demos
|
|
2
|
+
// use. No compatibility promise: a name here may change or go in any release, and the CHANGELOG says when. A name moves
|
|
3
|
+
// to the root entry when something outside this repository needs it.
|
|
4
|
+
export { createRunDir, findDelegations, mintMountToken, pathlessResolver, removeMountToken, runPath, unmountClaim } from "./claim.js";
|
|
5
|
+
export { LABEL_FLEET, LABEL_RUN, sandboxName, sandboxStatus, sweepSandboxes } from "./hosts/daytona.js";
|
|
6
|
+
export { isLeaseFresh, parseRunRecord, RUN_JSON } from "./status.js";
|