@parcha/pi-durable-disk 0.1.0-beta.14 → 0.1.0-beta.16
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 +211 -0
- package/README.md +225 -15
- package/dist/app.d.ts +5 -1
- package/dist/claim.d.ts +45 -2
- package/dist/claim.js +145 -15
- package/dist/cli.js +16 -15
- package/dist/errors.d.ts +2 -1
- package/dist/hosts/attempt-env.d.ts +3 -0
- package/dist/hosts/attempt-env.js +15 -0
- package/dist/hosts/daytona-launch.js +8 -7
- package/dist/hosts/daytona.d.ts +27 -2
- package/dist/hosts/daytona.js +92 -3
- package/dist/hosts/docker.d.ts +2 -2
- package/dist/hosts/docker.js +22 -5
- package/dist/hosts/local-host.d.ts +2 -2
- package/dist/hosts/local-host.js +34 -6
- package/dist/index.d.ts +8 -4
- package/dist/index.js +6 -2
- package/dist/lease.d.ts +1 -1
- package/dist/place.d.ts +236 -0
- package/dist/place.js +859 -0
- package/dist/run.d.ts +51 -10
- package/dist/run.js +75 -33
- package/dist/status.d.ts +29 -0
- package/dist/status.js +89 -0
- package/dist/supervise.d.ts +44 -14
- package/dist/supervise.js +5 -18
- package/dist/with-claim.d.ts +63 -0
- package/dist/with-claim.js +325 -0
- package/package.json +3 -2
package/dist/hosts/daytona.js
CHANGED
|
@@ -15,11 +15,38 @@
|
|
|
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";
|
|
21
22
|
export const LABEL_FLEET = "pda-fleet";
|
|
22
23
|
export const LABEL_RUN = "pda-run";
|
|
24
|
+
/**
|
|
25
|
+
* Daytona's published list prices, as its pricing page gave them on `readOn`: billed per second, quoted per hour, for
|
|
26
|
+
* each vCPU, each GiB of memory and each GiB of disk. The page's footnote "Price per GiB after first 5 free" is applied
|
|
27
|
+
* to storage (disk) only: its placement is ambiguous and could be read as covering memory too, so this is a choice.
|
|
28
|
+
* The page lists GPU rates per GPU model; this driver does not map a box's GPU to a model, so a GPU box is unpriced.
|
|
29
|
+
*/
|
|
30
|
+
export const DAYTONA_LIST_PRICES = {
|
|
31
|
+
page: "https://www.daytona.io/pricing",
|
|
32
|
+
readOn: "2026-10-16",
|
|
33
|
+
vcpuPerHour: 0.0504,
|
|
34
|
+
memoryGiBPerHour: 0.0162,
|
|
35
|
+
diskGiBPerHour: 0.000108,
|
|
36
|
+
freeDiskGiB: 5,
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* A box's list price per second at DAYTONA_LIST_PRICES, from its size; null when its size is unknown or it has a GPU
|
|
40
|
+
* (priced per GPU model on the page, which this driver does not map).
|
|
41
|
+
*/
|
|
42
|
+
export function daytonaUsdPerSecond(size) {
|
|
43
|
+
const { cpu, memory, disk, gpu } = size;
|
|
44
|
+
const count = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0;
|
|
45
|
+
if (!count(cpu) || !count(memory) || !count(disk) || (gpu !== undefined && gpu !== 0))
|
|
46
|
+
return null;
|
|
47
|
+
const p = DAYTONA_LIST_PRICES;
|
|
48
|
+
return (cpu * p.vcpuPerHour + memory * p.memoryGiBPerHour + Math.max(0, disk - p.freeDiskGiB) * p.diskGiBPerHour) / 3600;
|
|
49
|
+
}
|
|
23
50
|
export class DaytonaApiError extends PdaError {
|
|
24
51
|
/** The HTTP status, null when no response arrived (network failure or timeout). */
|
|
25
52
|
status;
|
|
@@ -255,13 +282,22 @@ export function daytonaHost(opts) {
|
|
|
255
282
|
throw new DaytonaHostError("START_FAILED", `${what} in ${box.name} exited ${r.exitCode}: ${r.result.trim().split("\n").at(-1)?.slice(0, 300) ?? ""}`);
|
|
256
283
|
return r;
|
|
257
284
|
}
|
|
285
|
+
/** Each box this driver made, for its receipt: what Daytona said of it, when it was made, and when it ended. */
|
|
286
|
+
const made = new Map();
|
|
287
|
+
const ended = (id) => {
|
|
288
|
+
const m = made.get(id);
|
|
289
|
+
if (m && m.to === null)
|
|
290
|
+
m.to = new Date(now()).toISOString();
|
|
291
|
+
};
|
|
258
292
|
return {
|
|
259
293
|
fleet,
|
|
260
294
|
mountRoot,
|
|
261
|
-
|
|
295
|
+
kills: "box",
|
|
296
|
+
async start(ref, mountToken, attempt) {
|
|
262
297
|
runPath(ref.id);
|
|
263
298
|
if (!mountToken)
|
|
264
299
|
throw new DaytonaHostError("INVALID_ARGUMENT", "no mount token");
|
|
300
|
+
const extra = attemptEnv(attempt, (m) => new DaytonaHostError("INVALID_ARGUMENT", m));
|
|
265
301
|
const t0 = now();
|
|
266
302
|
const name = sandboxName(prefix, ref.id, t0);
|
|
267
303
|
const body = {
|
|
@@ -276,8 +312,10 @@ export function daytonaHost(opts) {
|
|
|
276
312
|
...(opts.network?.allowList ? { networkAllowList: opts.network.allowList } : {}),
|
|
277
313
|
};
|
|
278
314
|
let box = await createOrAdopt(body, ref.id);
|
|
315
|
+
made.set(box.id, { info: box, from: box.createdAt ?? new Date(t0).toISOString(), to: null });
|
|
279
316
|
try {
|
|
280
317
|
box = await waitStarted(box, t0 + startTimeoutMs);
|
|
318
|
+
made.get(box.id).info = { ...made.get(box.id).info, ...box };
|
|
281
319
|
const prepared = now();
|
|
282
320
|
if (opts.prepare)
|
|
283
321
|
await opts.prepare(box, client);
|
|
@@ -285,7 +323,7 @@ export function daytonaHost(opts) {
|
|
|
285
323
|
const handle = { driver: "daytona", fleet, target: box.target ?? opts.target ?? null, sandboxId: box.id, name, mountpoint: `${mountRoot}/${runPath(ref.id)}` };
|
|
286
324
|
const spec = {
|
|
287
325
|
argv: [...command, "run", "--disk", ref.disk, "--region", ref.region, "--id", ref.id, "--mount-root", mountRoot, "--archil", archil, ...(opts.runArgs ?? []), "--token-stdin"],
|
|
288
|
-
env: { ...opts.env, PDA_HOLDER: JSON.stringify(handle) },
|
|
326
|
+
env: { ...opts.env, ...extra, PDA_HOLDER: JSON.stringify(handle) },
|
|
289
327
|
user,
|
|
290
328
|
group,
|
|
291
329
|
restart: opts.restart !== false,
|
|
@@ -305,7 +343,9 @@ export function daytonaHost(opts) {
|
|
|
305
343
|
return handle;
|
|
306
344
|
}
|
|
307
345
|
catch (err) {
|
|
346
|
+
// The box was made and is deleted now: its receipt ends here, deleted or not (a delete that failed is the sweep's).
|
|
308
347
|
await client.remove(box.id).catch(() => { });
|
|
348
|
+
ended(box.id);
|
|
309
349
|
if (err instanceof DaytonaHostError)
|
|
310
350
|
throw err;
|
|
311
351
|
throw new DaytonaHostError("START_FAILED", `starting an instance in ${name} failed: ${err.message}`, { cause: err });
|
|
@@ -321,8 +361,10 @@ export function daytonaHost(opts) {
|
|
|
321
361
|
catch (err) {
|
|
322
362
|
throw new DaytonaHostError("DAYTONA_API_FAILED", `reading sandbox ${h.name}: ${err.message}`, { cause: err });
|
|
323
363
|
}
|
|
324
|
-
if (!box)
|
|
364
|
+
if (!box) {
|
|
365
|
+
ended(h.sandboxId);
|
|
325
366
|
return "gone";
|
|
367
|
+
}
|
|
326
368
|
// The label, not the id in a handle, decides whose box this is: a handle can never reach another fleet's box.
|
|
327
369
|
if (box.labels?.[LABEL_FLEET] !== fleet)
|
|
328
370
|
return "unknown";
|
|
@@ -363,6 +405,53 @@ export function daytonaHost(opts) {
|
|
|
363
405
|
throw new DaytonaHostError("STOP_FAILED", `deleting sandbox ${h.name} failed: ${err.message}`, { cause: first });
|
|
364
406
|
}
|
|
365
407
|
}
|
|
408
|
+
ended(id);
|
|
409
|
+
},
|
|
410
|
+
async kill(h) {
|
|
411
|
+
if (!mine(h))
|
|
412
|
+
return;
|
|
413
|
+
const id = h.sandboxId;
|
|
414
|
+
let box;
|
|
415
|
+
try {
|
|
416
|
+
box = await client.get(id);
|
|
417
|
+
}
|
|
418
|
+
catch (err) {
|
|
419
|
+
throw new DaytonaHostError("KILL_FAILED", `reading sandbox ${h.name} to confirm it is this fleet's: ${err.message}`, { cause: err });
|
|
420
|
+
}
|
|
421
|
+
if (box === null || box.labels?.[LABEL_FLEET] !== fleet)
|
|
422
|
+
return;
|
|
423
|
+
// The box goes with everything in it, the instance and its mount: no launcher stop, no drain.
|
|
424
|
+
try {
|
|
425
|
+
await client.remove(id);
|
|
426
|
+
}
|
|
427
|
+
catch (first) {
|
|
428
|
+
// A delete refused mid-transition: power the box off, then delete again.
|
|
429
|
+
await client.stop(id, true).catch(() => { });
|
|
430
|
+
try {
|
|
431
|
+
await client.remove(id);
|
|
432
|
+
}
|
|
433
|
+
catch (err) {
|
|
434
|
+
throw new DaytonaHostError("KILL_FAILED", `deleting sandbox ${h.name} failed: ${err.message}`, { cause: first });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
ended(id);
|
|
438
|
+
},
|
|
439
|
+
box(h) {
|
|
440
|
+
if (!mine(h))
|
|
441
|
+
return null;
|
|
442
|
+
const m = made.get(h.sandboxId);
|
|
443
|
+
if (!m)
|
|
444
|
+
return null;
|
|
445
|
+
const { cpu, gpu, memory, disk } = m.info;
|
|
446
|
+
const usdPerSecond = daytonaUsdPerSecond(m.info);
|
|
447
|
+
const size = [cpu, memory, disk].every((n) => typeof n === "number") ? ` ${cpu} vCPU, ${memory} GiB memory, ${disk} GiB disk${gpu ? `, ${gpu} GPU` : ""}` : "";
|
|
448
|
+
return {
|
|
449
|
+
provider: "daytona",
|
|
450
|
+
kind: `sandbox${size}`,
|
|
451
|
+
from: m.from,
|
|
452
|
+
to: m.to,
|
|
453
|
+
...(usdPerSecond === null ? {} : { usdPerSecond, source: DAYTONA_LIST_PRICES.page, priced: DAYTONA_LIST_PRICES.readOn }),
|
|
454
|
+
};
|
|
366
455
|
},
|
|
367
456
|
};
|
|
368
457
|
}
|
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,7 +50,7 @@ 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
|
}
|
|
@@ -66,7 +66,7 @@ export declare function currentBootId(): string;
|
|
|
66
66
|
export declare function parseShow(text: string): Record<string, string>;
|
|
67
67
|
/** A unit's state as a host status: loaded and active (or restarting) is running; unloaded is gone. */
|
|
68
68
|
export declare function unitStatus(show: Record<string, string>): HostStatus;
|
|
69
|
-
export declare function localHost(opts?: LocalHostOptions): HostDriver & {
|
|
69
|
+
export declare function localHost(opts?: LocalHostOptions): HostDriver & Required<Pick<HostDriver, "kill" | "kills">> & {
|
|
70
70
|
readonly hostName: string;
|
|
71
71
|
readonly mountRoot: string;
|
|
72
72
|
};
|
package/dist/hosts/local-host.js
CHANGED
|
@@ -20,6 +20,7 @@ import { ARCHIL_SCOPED, DEAD_CONNECTION, DEFAULT_MOUNT_ROOT, runPath } from "../
|
|
|
20
20
|
import { EXIT_FENCED, EXIT_HELD, PdaError } from "../errors.js";
|
|
21
21
|
import { LOCAL_PARK_THRESHOLD_MS } from "../park.js";
|
|
22
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);
|
|
@@ -161,13 +162,13 @@ export function localHost(opts = {}) {
|
|
|
161
162
|
return rm(path, { force: true });
|
|
162
163
|
await root(["/bin/rm", "-f", path]);
|
|
163
164
|
}
|
|
164
|
-
async function startUnit(ref, token) {
|
|
165
|
+
async function startUnit(ref, token, extra) {
|
|
165
166
|
const unit = `${prefix}${ref.id}-${Date.now().toString(36)}${Math.floor(Math.random() * 1296).toString(36).padStart(2, "0")}`;
|
|
166
167
|
if (!UNIT_SAFE.test(unit))
|
|
167
168
|
throw new LocalHostError("INVALID_ARGUMENT", `unit name ${unit} is not safe`);
|
|
168
169
|
const mountpoint = mountpointOf(ref);
|
|
169
170
|
const handle = { driver: "local", mode: "systemd", host: hostName, bootId, unit, mountpoint };
|
|
170
|
-
const env = { ...opts.env, PATH: SAFE_PATH, PDA_HOLDER: holderEnv(handle) };
|
|
171
|
+
const env = { ...opts.env, ...extra, PATH: SAFE_PATH, PDA_HOLDER: holderEnv(handle) };
|
|
171
172
|
const path = tokenFile(unit);
|
|
172
173
|
await writeTokenFile(path, token);
|
|
173
174
|
let started = false;
|
|
@@ -202,7 +203,7 @@ export function localHost(opts = {}) {
|
|
|
202
203
|
await (started ? emptyTokenFile(path) : removeFile(path)).catch(() => { });
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
|
-
async function startChild(ref, token) {
|
|
206
|
+
async function startChild(ref, token, extra) {
|
|
206
207
|
const tag = `${prefix}${ref.id}-${Date.now().toString(36)}`;
|
|
207
208
|
const mountpoint = mountpointOf(ref);
|
|
208
209
|
const base = { driver: "local", mode: "child", host: hostName, bootId, mountpoint, tag };
|
|
@@ -211,7 +212,7 @@ export function localHost(opts = {}) {
|
|
|
211
212
|
const { openSync } = await import("node:fs");
|
|
212
213
|
out = openSync(join(opts.logDir, `${tag}.log`), "a", 0o600);
|
|
213
214
|
}
|
|
214
|
-
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) };
|
|
215
216
|
const child = spawn(command[0], [...command.slice(1), ...runFlags(ref), "--token-stdin"], { detached: true, stdio: ["pipe", out, out], env });
|
|
216
217
|
await new Promise((resolve, reject) => {
|
|
217
218
|
child.once("spawn", resolve);
|
|
@@ -288,9 +289,11 @@ export function localHost(opts = {}) {
|
|
|
288
289
|
return {
|
|
289
290
|
hostName,
|
|
290
291
|
mountRoot,
|
|
291
|
-
|
|
292
|
+
kills: "process",
|
|
293
|
+
async start(ref, token, attempt) {
|
|
292
294
|
runPath(ref.id);
|
|
293
|
-
|
|
295
|
+
const extra = attemptEnv(attempt, (m) => new LocalHostError("INVALID_ARGUMENT", m));
|
|
296
|
+
return mode === "systemd" ? startUnit(ref, token, extra) : startChild(ref, token, extra);
|
|
294
297
|
},
|
|
295
298
|
async status(h) {
|
|
296
299
|
if (!mine(h))
|
|
@@ -347,6 +350,31 @@ export function localHost(opts = {}) {
|
|
|
347
350
|
await root([SYSTEMCTL, "reset-failed", `${h.unit}.service`]);
|
|
348
351
|
await removeFile(tokenFile(h.unit));
|
|
349
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
|
+
},
|
|
350
378
|
};
|
|
351
379
|
}
|
|
352
380
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export { openDurableRun, RunError, StoreBehindSealError } from "./run.js";
|
|
2
|
-
export type { BeforeResume, DurableRun, LeaseOptions, OpenDurableRunOptions, OpenRunLeaseOptions, OpenStep, ReleaseStep, RunClaim, RunEnv, RunErrorCode, RunLease, StoreHeadSource, StoreReads } 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
3
|
export { localRun } from "./local-run.js";
|
|
4
4
|
export type { LocalRun } from "./local-run.js";
|
|
5
|
-
export { CLAIM_UNMOUNTED, openClaimDir, RunRecordError } from "./status.js";
|
|
5
|
+
export { CLAIM_UNMOUNTED, openClaimDir, readLocalRunStatus, RunRecordError } from "./status.js";
|
|
6
6
|
export type { ClaimDir, OpenClaimDirOptions, PersistRecord, RunHolder, RunRecord, RunStatus } from "./status.js";
|
|
7
7
|
export { ArchilCodingTools, archilEnv, EnvClosedError, EnvError, markReadOnlySafe } from "./env.js";
|
|
8
8
|
export type { ArchilEnvClaim, ArchilEnvFactory, ArchilEnvOptions, EnvErrorCode } from "./env.js";
|
|
9
9
|
export { acquire, ARCHIL_SCOPED, revoke, takeOver } from "./claim.js";
|
|
10
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";
|
|
11
13
|
export { PathOutsideWorkError } from "./confine.js";
|
|
12
14
|
export type { RefusalReason } from "./confine.js";
|
|
13
15
|
export { ClaimError, FencedError, HeldError, PdaError } from "./errors.js";
|
|
@@ -16,15 +18,17 @@ export type { ArchilStore, Fence, FencedDatabase, Profile, StoreFencedError } fr
|
|
|
16
18
|
export { AppError } from "./app.js";
|
|
17
19
|
export type { AppContext, AppModule, AppOptions } from "./app.js";
|
|
18
20
|
export { archilControl, deleteRunTree, ensureRunning, readRunStatus, SuperviseError } from "./supervise.js";
|
|
19
|
-
export type { ArchilControl, CheckControl, CheckOptions, DiskClient, EnsureOptions, EnsureResult, HostDriver, HostHandle, HostStatus, Json, StartAttempt, StartReason, StonithOutcome, SuperviseErrorCode, SupervisorControl, TokenUser } from "./supervise.js";
|
|
21
|
+
export type { ArchilControl, CheckControl, CheckOptions, DiskClient, EnsureOptions, EnsureResult, HostBox, HostDriver, HostHandle, HostStatus, Json, StartAttempt, StartReason, StonithOutcome, SuperviseErrorCode, SupervisorControl, TokenUser } from "./supervise.js";
|
|
20
22
|
export { localHost, LocalHostError } from "./hosts/local-host.js";
|
|
21
23
|
export type { LocalHostOptions, Ran, Runner } from "./hosts/local-host.js";
|
|
22
24
|
export { dockerHost, DockerHostError } from "./hosts/docker.js";
|
|
23
25
|
export type { ContainerInfo, DockerHostOptions, DockerRan, DockerRunner } from "./hosts/docker.js";
|
|
24
|
-
export { DaytonaApiError, daytonaHost, DaytonaHostError, daytonaRest } from "./hosts/daytona.js";
|
|
26
|
+
export { DAYTONA_LIST_PRICES, DaytonaApiError, daytonaHost, DaytonaHostError, daytonaRest, daytonaUsdPerSecond } from "./hosts/daytona.js";
|
|
25
27
|
export type { CreateSandboxBody, DaytonaClient, DaytonaHostErrorCode, DaytonaHostOptions, DaytonaRestOptions, ExecResult, SandboxInfo } from "./hosts/daytona.js";
|
|
26
28
|
export { requestRun, ServeConfigError, ServeError, serveRun } from "./serve.js";
|
|
27
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, PlaceRecordHome, PlaceRecovery, PlaceRunner, PlaceSignal, PlaceStatus, RecordControl, RunnerOrder } from "./place.js";
|
|
28
32
|
export { busyState, drain, leaseParkTarget, watchParking } from "./park.js";
|
|
29
33
|
export type { BusyState, DrainOptions, Parking, ParkOptions, ParkResult, ParkTarget } from "./park.js";
|
|
30
34
|
export { fork, ForkError, forkLocal } from "./fork.js";
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
// The run: open it, on a disk or a local directory, with its environment, its claim and the errors it fails with.
|
|
5
5
|
export { openDurableRun, RunError, StoreBehindSealError } from "./run.js";
|
|
6
6
|
export { localRun } from "./local-run.js";
|
|
7
|
-
export { CLAIM_UNMOUNTED, openClaimDir, RunRecordError } from "./status.js";
|
|
7
|
+
export { CLAIM_UNMOUNTED, openClaimDir, readLocalRunStatus, RunRecordError } from "./status.js";
|
|
8
8
|
export { ArchilCodingTools, archilEnv, EnvClosedError, EnvError, markReadOnlySafe } from "./env.js";
|
|
9
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";
|
|
10
12
|
export { PathOutsideWorkError } from "./confine.js";
|
|
11
13
|
export { ClaimError, FencedError, HeldError, PdaError } from "./errors.js";
|
|
12
14
|
// The app contract: what `--app` names.
|
|
@@ -15,9 +17,11 @@ export { AppError } from "./app.js";
|
|
|
15
17
|
export { archilControl, deleteRunTree, ensureRunning, readRunStatus, SuperviseError } from "./supervise.js";
|
|
16
18
|
export { localHost, LocalHostError } from "./hosts/local-host.js";
|
|
17
19
|
export { dockerHost, DockerHostError } from "./hosts/docker.js";
|
|
18
|
-
export { DaytonaApiError, daytonaHost, DaytonaHostError, daytonaRest } from "./hosts/daytona.js";
|
|
20
|
+
export { DAYTONA_LIST_PRICES, DaytonaApiError, daytonaHost, DaytonaHostError, daytonaRest, daytonaUsdPerSecond } from "./hosts/daytona.js";
|
|
19
21
|
// Serve: one HTTP request runs one turn.
|
|
20
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";
|
|
21
25
|
// Parking: an idle run gives its host back.
|
|
22
26
|
export { busyState, drain, leaseParkTarget, watchParking } from "./park.js";
|
|
23
27
|
// Fork: a new run from a run's committed state.
|
package/dist/lease.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { EXIT_DATAERR, EXIT_SOFTWARE, LeaseLapsedError, openRunLease, OWNER_LOCK, RunError, StoreBehindSealError, storeHead, STORE_FILE } from "./run.js";
|
|
2
|
-
export type { LeaseOptions, OpenRunLeaseOptions, OpenStep, ReleaseStep, RunClaim, RunErrorCode, RunLease, StoreHeadSource } from "./run.js";
|
|
2
|
+
export type { LeaseOptions, OpenRunLeaseOptions, OpenStep, ReleaseStep, RunClaim, RunErrorCode, RunLease, RunSeed, RunSeedDir, StoreHeadSource } from "./run.js";
|
|
3
3
|
export * from "./errors.js";
|
|
4
4
|
export { assertPragmas, Fence, FencedDatabase, openArchilStore, PROFILE_PRAGMAS, StoreBusyError, StoreFencedError, StorePragmaError } from "./store.js";
|
|
5
5
|
export type { ArchilStore, OpenOptions, PragmaMismatch, Profile } from "./store.js";
|