@nylorun/runtime 0.10.0-beta → 0.12.0-beta
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 +86 -0
- package/README.md +53 -51
- package/dist/adapters/execution/restate.d.ts +130 -0
- package/dist/adapters/execution/restate.js +442 -0
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/adapters/streams/s2.d.ts +18 -0
- package/dist/adapters/streams/s2.js +361 -0
- package/dist/core/executors.d.ts +4 -1
- package/dist/core/executors.js +20 -1
- package/dist/core/flow-host.d.ts +93 -132
- package/dist/core/flow-host.js +282 -547
- package/dist/core/sandbox-routes.d.ts +13 -4
- package/dist/core/sandbox-routes.js +14 -12
- package/dist/execution/memory.d.ts +54 -0
- package/dist/execution/memory.js +217 -0
- package/dist/execution/types.d.ts +125 -0
- package/dist/execution/types.js +52 -0
- package/dist/host/config-for.d.ts +2 -1
- package/dist/host/config-for.js +3 -21
- package/dist/host/config.d.ts +0 -8
- package/dist/host/create-host.d.ts +36 -1
- package/dist/host/create-host.js +51 -24
- package/dist/host/execution.d.ts +61 -0
- package/dist/host/execution.js +69 -0
- package/dist/host/http.d.ts +4 -0
- package/dist/host/http.js +6 -0
- package/dist/host/index.d.ts +3 -1
- package/dist/host/index.js +2 -0
- package/dist/host/main.js +106 -47
- package/dist/host/stack-config.d.ts +72 -0
- package/dist/host/stack-config.js +193 -0
- package/dist/index.d.ts +1 -1
- package/dist/infra/database.d.ts +16 -0
- package/dist/infra/database.js +31 -0
- package/dist/infra/execution.d.ts +39 -0
- package/dist/infra/execution.js +68 -0
- package/dist/infra/index.d.ts +42 -0
- package/dist/infra/index.js +56 -0
- package/dist/infra/readiness.d.ts +33 -0
- package/dist/infra/readiness.js +53 -0
- package/dist/infra/streams.d.ts +17 -0
- package/dist/infra/streams.js +33 -0
- package/dist/infra/workers.d.ts +39 -0
- package/dist/infra/workers.js +31 -0
- package/dist/model/pi-model.d.ts +2 -2
- package/dist/model/pi-model.js +5 -5
- package/dist/sandbox/manager.d.ts +26 -8
- package/dist/sandbox/manager.js +95 -47
- package/dist/sandbox/select.d.ts +1 -1
- package/dist/sandbox/select.js +4 -5
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/canonical.d.ts +5 -0
- package/dist/store/canonical.js +17 -0
- package/dist/store/cursor.d.ts +9 -0
- package/dist/store/cursor.js +18 -0
- package/dist/store/memory.d.ts +57 -0
- package/dist/store/memory.js +645 -0
- package/dist/store/ownership.d.ts +19 -0
- package/dist/store/ownership.js +33 -0
- package/dist/store/postgres/connect.d.ts +25 -0
- package/dist/store/postgres/connect.js +25 -0
- package/dist/store/postgres/locking.d.ts +16 -0
- package/dist/store/postgres/locking.js +22 -0
- package/dist/store/postgres/migrations/001_initial.d.ts +27 -0
- package/dist/store/postgres/migrations/001_initial.js +196 -0
- package/dist/store/postgres/migrations/002_session_owner.d.ts +6 -0
- package/dist/store/postgres/migrations/002_session_owner.js +13 -0
- package/dist/store/postgres/migrations/index.d.ts +49 -0
- package/dist/store/postgres/migrations/index.js +76 -0
- package/dist/store/postgres/names.d.ts +11 -0
- package/dist/store/postgres/names.js +42 -0
- package/dist/store/postgres/store.d.ts +11 -0
- package/dist/store/postgres/store.js +797 -0
- package/dist/store/postgres/tenants.d.ts +90 -0
- package/dist/store/postgres/tenants.js +214 -0
- package/dist/store/types.d.ts +442 -0
- package/dist/store/types.js +10 -0
- package/dist/streams/basin.d.ts +23 -0
- package/dist/streams/basin.js +51 -0
- package/dist/streams/memory.d.ts +25 -0
- package/dist/streams/memory.js +124 -0
- package/dist/streams/relay.d.ts +37 -0
- package/dist/streams/relay.js +254 -0
- package/dist/streams/types.d.ts +115 -0
- package/dist/streams/types.js +86 -0
- package/dist/tenant/actions.d.ts +153 -0
- package/dist/tenant/actions.js +191 -0
- package/dist/tenant/advance.d.ts +15 -0
- package/dist/tenant/advance.js +439 -0
- package/dist/tenant/auth.d.ts +32 -0
- package/dist/tenant/auth.js +169 -0
- package/dist/tenant/commands.d.ts +3 -0
- package/dist/tenant/commands.js +393 -0
- package/dist/tenant/context.d.ts +147 -0
- package/dist/tenant/context.js +48 -0
- package/dist/tenant/effects.d.ts +20 -0
- package/dist/tenant/effects.js +564 -0
- package/dist/tenant/envelope.d.ts +0 -3
- package/dist/tenant/envelope.js +2 -34
- package/dist/tenant/ephemeral.d.ts +15 -4
- package/dist/tenant/ephemeral.js +106 -38
- package/dist/tenant/http.d.ts +38 -0
- package/dist/tenant/http.js +53 -0
- package/dist/tenant/live.d.ts +92 -0
- package/dist/tenant/live.js +290 -0
- package/dist/tenant/model-setting.d.ts +16 -0
- package/dist/tenant/model-setting.js +12 -0
- package/dist/tenant/module.d.ts +29 -8
- package/dist/tenant/module.js +165 -158
- package/dist/tenant/paths.d.ts +12 -8
- package/dist/tenant/paths.js +38 -12
- package/dist/tenant/principals.d.ts +12 -13
- package/dist/tenant/principals.js +50 -38
- package/dist/tenant/quarantine-error.d.ts +1 -1
- package/dist/tenant/quarantine-error.js +2 -8
- package/dist/tenant/quarantine.d.ts +0 -8
- package/dist/tenant/quarantine.js +8 -30
- package/dist/tenant/reset.d.ts +7 -6
- package/dist/tenant/reset.js +7 -51
- package/dist/tenant/routes-tenant.d.ts +12 -0
- package/dist/tenant/routes-tenant.js +137 -0
- package/dist/tenant/routes.d.ts +9 -0
- package/dist/tenant/routes.js +151 -0
- package/dist/tenant/runtime.d.ts +59 -58
- package/dist/tenant/runtime.js +243 -2201
- package/dist/tenant/scheduler.d.ts +25 -0
- package/dist/tenant/scheduler.js +84 -0
- package/dist/tenant/session.d.ts +60 -0
- package/dist/tenant/session.js +94 -0
- package/dist/tenant/sessions.d.ts +40 -0
- package/dist/tenant/sessions.js +163 -0
- package/dist/tenant/status.d.ts +13 -4
- package/dist/tenant/status.js +76 -41
- package/dist/tenant/store-memory.d.ts +9 -12
- package/dist/tenant/store-memory.js +79 -68
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +140 -0
- package/dist/tenant/streams.d.ts +126 -0
- package/dist/tenant/streams.js +419 -0
- package/dist/tenant/sweep.d.ts +15 -0
- package/dist/tenant/sweep.js +122 -0
- package/dist/tenant/transcript.d.ts +104 -0
- package/dist/tenant/transcript.js +58 -0
- package/dist/tenant/types.d.ts +101 -15
- package/dist/tenant/types.js +23 -1
- package/dist/tenant/worker.d.ts +84 -0
- package/dist/tenant/worker.js +189 -0
- package/dist/vault/service.d.ts +44 -31
- package/dist/vault/service.js +454 -360
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +15 -14
- package/dist/adapters/sandbox/microsandbox.d.ts +0 -2
- package/dist/adapters/sandbox/microsandbox.js +0 -238
- package/dist/core/store.d.ts +0 -32
- package/dist/core/store.js +0 -143
- package/dist/launcher/commands.d.ts +0 -24
- package/dist/launcher/commands.js +0 -260
- package/dist/launcher/ensure-config.d.ts +0 -16
- package/dist/launcher/ensure-config.js +0 -79
- package/dist/launcher/errors.d.ts +0 -12
- package/dist/launcher/errors.js +0 -22
- package/dist/launcher/host-config.d.ts +0 -17
- package/dist/launcher/host-config.js +0 -64
- package/dist/launcher/host-state.d.ts +0 -17
- package/dist/launcher/host-state.js +0 -61
- package/dist/launcher/lifecycle.d.ts +0 -66
- package/dist/launcher/lifecycle.js +0 -407
- package/dist/launcher/locks.d.ts +0 -26
- package/dist/launcher/locks.js +0 -117
- package/dist/launcher/logs.d.ts +0 -18
- package/dist/launcher/logs.js +0 -117
- package/dist/launcher/main.d.ts +0 -2
- package/dist/launcher/main.js +0 -66
- package/dist/launcher/output.d.ts +0 -11
- package/dist/launcher/output.js +0 -68
- package/dist/launcher/paths.d.ts +0 -20
- package/dist/launcher/paths.js +0 -36
- package/dist/launcher/probe.d.ts +0 -23
- package/dist/launcher/probe.js +0 -72
- package/dist/launcher/protocol.d.ts +0 -62
- package/dist/launcher/protocol.js +0 -1
- package/dist/launcher/schema-guard.d.ts +0 -6
- package/dist/launcher/schema-guard.js +0 -36
- package/dist/launcher/spawn.d.ts +0 -22
- package/dist/launcher/spawn.js +0 -85
- package/dist/launcher/status.d.ts +0 -6
- package/dist/launcher/status.js +0 -65
- package/dist/tenant/migration.d.ts +0 -16
- package/dist/tenant/migration.js +0 -116
- package/dist/tenant/schema.d.ts +0 -23
- package/dist/tenant/schema.js +0 -151
- package/dist/tenant/store-fs.d.ts +0 -16
- package/dist/tenant/store-fs.js +0 -205
package/dist/model/pi-model.d.ts
CHANGED
|
@@ -10,8 +10,8 @@ export interface PiModelOptions {
|
|
|
10
10
|
readonly root?: string;
|
|
11
11
|
readonly onPreview?: (preview: ModelPreview) => void;
|
|
12
12
|
readonly media?: Pick<RuntimeMedia, "dataUrl">;
|
|
13
|
-
readonly readHostModel?: () => HostModelSecret | undefined
|
|
14
|
-
readonly writeHostCredential?: (credential: Credential) => void
|
|
13
|
+
readonly readHostModel?: () => HostModelSecret | undefined | Promise<HostModelSecret | undefined>;
|
|
14
|
+
readonly writeHostCredential?: (credential: Credential) => void | Promise<void>;
|
|
15
15
|
}
|
|
16
16
|
/** Node model adapter. Local provider configuration is read only when invoked. */
|
|
17
17
|
export declare function piModel(options?: PiModelOptions): RuntimeModelAdapter;
|
package/dist/model/pi-model.js
CHANGED
|
@@ -14,9 +14,9 @@ export function piModel(options = {}) {
|
|
|
14
14
|
return async (call, context) => {
|
|
15
15
|
context.signal.throwIfAborted();
|
|
16
16
|
const root = options.root ?? "";
|
|
17
|
-
const stored = options.readHostModel?.();
|
|
17
|
+
const stored = await options.readHostModel?.();
|
|
18
18
|
if (!stored)
|
|
19
|
-
throw new Error("Model provider is not configured.
|
|
19
|
+
throw new Error("Model provider is not configured. Set it in Studio, or run nylo configure (npx @nylorun/cli configure).");
|
|
20
20
|
const requested = call.model?.id;
|
|
21
21
|
const selection = {
|
|
22
22
|
provider: stored.provider,
|
|
@@ -28,7 +28,7 @@ export function piModel(options = {}) {
|
|
|
28
28
|
const registry = modelsFor(selection, hostCredentialStore(stored, options.writeHostCredential), { environment: false });
|
|
29
29
|
const selected = registry.getModel(selection.provider, selection.model);
|
|
30
30
|
if (!selected)
|
|
31
|
-
throw new Error("Unknown model. Run nylorun configure.");
|
|
31
|
+
throw new Error("Unknown model. Run nylo configure (npx @nylorun/cli configure).");
|
|
32
32
|
const signatureFor = (part) => {
|
|
33
33
|
const metadata = "providerMetadata" in part ? part.providerMetadata?.pi : undefined;
|
|
34
34
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
@@ -290,12 +290,12 @@ function hostCredentialStore(stored, write) {
|
|
|
290
290
|
const next = await fn(current);
|
|
291
291
|
if (next === undefined)
|
|
292
292
|
return current;
|
|
293
|
-
write?.(next);
|
|
293
|
+
await write?.(next);
|
|
294
294
|
current = next;
|
|
295
295
|
return next;
|
|
296
296
|
},
|
|
297
297
|
async delete() {
|
|
298
|
-
throw new Error("Replace the model provider
|
|
298
|
+
throw new Error("Replace the model provider in Studio, or with nylo configure.");
|
|
299
299
|
},
|
|
300
300
|
};
|
|
301
301
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AgentManifest, type CapabilityManifest, type SandboxToolName } from "@nylorun/core/define";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SessionStore } from "../store/types.js";
|
|
3
3
|
import { type SandboxSelection, type SandboxSelectionReport } from "./select.js";
|
|
4
4
|
import { type SandboxToolOutcome } from "./tools.js";
|
|
5
5
|
import type { SandboxBackend } from "./types.js";
|
|
@@ -18,16 +18,23 @@ interface SandboxRecord {
|
|
|
18
18
|
createdAt: string;
|
|
19
19
|
updatedAt: string;
|
|
20
20
|
}
|
|
21
|
+
export interface SandboxSweepOptions {
|
|
22
|
+
/** The sweep's clock, in ms since the epoch. Defaults to `Date.now()`. */
|
|
23
|
+
readonly now?: number;
|
|
24
|
+
/** Whether a session still exists; sandboxes of deleted sessions are removed. */
|
|
25
|
+
readonly sessionExists: (sessionId: string) => boolean | Promise<boolean>;
|
|
26
|
+
}
|
|
21
27
|
export interface SandboxManagerOptions {
|
|
22
28
|
/** Distinguishes this Runtime's sandboxes from other Runtimes on the same machine. */
|
|
23
29
|
readonly scope: string;
|
|
24
|
-
readonly store:
|
|
30
|
+
readonly store: SessionStore;
|
|
25
31
|
readonly backends: readonly SandboxBackend[];
|
|
26
|
-
/** `auto
|
|
32
|
+
/** `auto` or `virtual`. Undefined means an invalid NYLORUN_SANDBOX value. */
|
|
27
33
|
readonly preference: string | undefined;
|
|
28
34
|
/** Delete sandboxes on close (the Runtime's store does not outlive the process). */
|
|
29
35
|
readonly ephemeral: boolean;
|
|
30
|
-
|
|
36
|
+
/** Writes a session event in its own transaction; never called inside one. */
|
|
37
|
+
readonly emit: (sessionId: string, turnId: string | null, type: string, payload: unknown) => void | Promise<void>;
|
|
31
38
|
}
|
|
32
39
|
/** The sandbox capability that owns this tool call, when the call is a built-in sandbox tool. */
|
|
33
40
|
export declare function sandboxCapabilityOf(manifest: AgentManifest | undefined, capabilityId: string | undefined, toolName: string | undefined): CapabilityManifest | undefined;
|
|
@@ -37,25 +44,36 @@ export declare class SandboxManager {
|
|
|
37
44
|
private readonly live;
|
|
38
45
|
private readonly prefix;
|
|
39
46
|
private closing;
|
|
47
|
+
private reconciled;
|
|
40
48
|
constructor(options: SandboxManagerOptions);
|
|
49
|
+
/**
|
|
50
|
+
* One Tenant sweep pass:
|
|
51
|
+
* - stops sandboxes idle for at least their idle timeout (their files remain; the next
|
|
52
|
+
* call reattaches);
|
|
53
|
+
* - marks records whose compute this process does not hold as stopped (compute is gone
|
|
54
|
+
* after a Runtime restart);
|
|
55
|
+
* - on the first pass of this process, removes sandboxes whose session no longer exists
|
|
56
|
+
* (skipped for ephemeral Runtimes, which delete theirs on close).
|
|
57
|
+
*/
|
|
58
|
+
sweep(options: SandboxSweepOptions): Promise<void>;
|
|
41
59
|
/** Backends are probed once, on first use, and the choice holds for the life of the Runtime. */
|
|
42
60
|
get ready(): Promise<SandboxSelection>;
|
|
43
61
|
/** Whether any sandbox was ever recorded, so startup can skip probing for agents without one. */
|
|
44
|
-
hasRecords(): boolean
|
|
62
|
+
hasRecords(): Promise<boolean>;
|
|
45
63
|
keyOf(sessionId: string): string;
|
|
46
64
|
report(): Promise<SandboxSelectionReport & {
|
|
47
65
|
readonly defaultImage: string;
|
|
48
66
|
}>;
|
|
49
67
|
run(session: SandboxSessionRef, capability: CapabilityManifest, toolName: SandboxToolName, input: unknown, signal: AbortSignal): Promise<SandboxToolOutcome>;
|
|
50
68
|
private execute;
|
|
69
|
+
private executeOpen;
|
|
51
70
|
private open;
|
|
52
|
-
private armIdle;
|
|
53
71
|
private stop;
|
|
54
72
|
private record;
|
|
55
73
|
/** The sandbox record for a session, if one was ever created. */
|
|
56
|
-
status(sessionId: string): SandboxRecord | undefined
|
|
74
|
+
status(sessionId: string): Promise<SandboxRecord | undefined>;
|
|
57
75
|
/** Delete sandboxes this Runtime owns whose session no longer exists. */
|
|
58
|
-
reconcile(sessionExists: (sessionId: string) => boolean): Promise<void>;
|
|
76
|
+
reconcile(sessionExists: (sessionId: string) => boolean | Promise<boolean>): Promise<void>;
|
|
59
77
|
close(): Promise<void>;
|
|
60
78
|
}
|
|
61
79
|
export {};
|
package/dist/sandbox/manager.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Sandbox Manager: owns sandbox lifecycle for the Runtime. One sandbox per session, created on
|
|
3
3
|
* the first sandbox tool call, stopped after its idle timeout, reattached on the next call.
|
|
4
4
|
* Commands against one sandbox run one at a time.
|
|
5
|
+
*
|
|
6
|
+
* The manager keeps no timers. The Tenant sweep calls `sweep`, which stops sandboxes idle
|
|
7
|
+
* past their timeout, marks records of compute this process no longer holds as stopped
|
|
8
|
+
* (after a restart), and, once per process, removes sandboxes whose session is gone.
|
|
5
9
|
*/
|
|
6
10
|
import { createHash } from "node:crypto";
|
|
7
11
|
import { isSandboxToolName, } from "@nylorun/core/define";
|
|
@@ -24,15 +28,53 @@ export class SandboxManager {
|
|
|
24
28
|
live = new Map();
|
|
25
29
|
prefix;
|
|
26
30
|
closing = false;
|
|
31
|
+
reconciled = false;
|
|
27
32
|
constructor(options) {
|
|
28
33
|
this.options = options;
|
|
29
34
|
this.prefix = `nylorun-${options.scope}-`;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One Tenant sweep pass:
|
|
38
|
+
* - stops sandboxes idle for at least their idle timeout (their files remain; the next
|
|
39
|
+
* call reattaches);
|
|
40
|
+
* - marks records whose compute this process does not hold as stopped (compute is gone
|
|
41
|
+
* after a Runtime restart);
|
|
42
|
+
* - on the first pass of this process, removes sandboxes whose session no longer exists
|
|
43
|
+
* (skipped for ephemeral Runtimes, which delete theirs on close).
|
|
44
|
+
*/
|
|
45
|
+
async sweep(options) {
|
|
46
|
+
if (this.closing)
|
|
47
|
+
return;
|
|
48
|
+
const now = options.now ?? Date.now();
|
|
49
|
+
const idle = (live) => !!live.handle && live.active === 0 && now - live.lastUsedAt >= live.idleMs;
|
|
50
|
+
const stops = [];
|
|
51
|
+
for (const [key, live] of this.live) {
|
|
52
|
+
if (!idle(live))
|
|
53
|
+
continue;
|
|
54
|
+
// Queued behind any call already waiting; checked again when its turn comes.
|
|
55
|
+
live.tail = live.tail
|
|
56
|
+
.then(() => (idle(live) ? this.stop(key, live) : undefined))
|
|
57
|
+
.catch(() => undefined);
|
|
58
|
+
stops.push(live.tail);
|
|
59
|
+
}
|
|
60
|
+
await Promise.all(stops);
|
|
61
|
+
await this.options.store.tx(async (t) => {
|
|
62
|
+
for (const record of await t.listSandboxes()) {
|
|
63
|
+
if (record.state === "stopped")
|
|
64
|
+
continue;
|
|
65
|
+
const live = this.live.get(record.key);
|
|
66
|
+
if (live && (live.handle || live.active > 0))
|
|
67
|
+
continue;
|
|
68
|
+
await t.put("sandboxes", record.key, { ...record, state: "stopped" });
|
|
69
|
+
}
|
|
35
70
|
});
|
|
71
|
+
if (this.reconciled || this.options.ephemeral)
|
|
72
|
+
return;
|
|
73
|
+
this.reconciled = true;
|
|
74
|
+
if (!(await this.hasRecords()))
|
|
75
|
+
return;
|
|
76
|
+
await this.reconcile(async (sessionId) => [...this.live.values()].some((live) => live.sessionId === sessionId) ||
|
|
77
|
+
(await options.sessionExists(sessionId)));
|
|
36
78
|
}
|
|
37
79
|
/** Backends are probed once, on first use, and the choice holds for the life of the Runtime. */
|
|
38
80
|
get ready() {
|
|
@@ -42,15 +84,15 @@ export class SandboxManager {
|
|
|
42
84
|
? selectSandboxBackend(this.options.backends, preference)
|
|
43
85
|
: Promise.resolve({
|
|
44
86
|
preference: "auto",
|
|
45
|
-
reason: `NYLORUN_SANDBOX='${this.options.preference}' is not valid; use auto
|
|
87
|
+
reason: `NYLORUN_SANDBOX='${this.options.preference}' is not valid; use auto or virtual`,
|
|
46
88
|
probes: [],
|
|
47
89
|
});
|
|
48
90
|
}
|
|
49
91
|
return this.selection;
|
|
50
92
|
}
|
|
51
93
|
/** Whether any sandbox was ever recorded, so startup can skip probing for agents without one. */
|
|
52
|
-
hasRecords() {
|
|
53
|
-
return this.options.store.
|
|
94
|
+
async hasRecords() {
|
|
95
|
+
return (await this.options.store.tx((t) => t.counts())).sandboxes > 0;
|
|
54
96
|
}
|
|
55
97
|
keyOf(sessionId) {
|
|
56
98
|
return this.prefix + createHash("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
@@ -84,7 +126,14 @@ export class SandboxManager {
|
|
|
84
126
|
};
|
|
85
127
|
let live = this.live.get(key);
|
|
86
128
|
if (!live) {
|
|
87
|
-
live = {
|
|
129
|
+
live = {
|
|
130
|
+
sessionId: session.id,
|
|
131
|
+
backend,
|
|
132
|
+
idleMs: idleMsOf(capability.sandbox),
|
|
133
|
+
active: 0,
|
|
134
|
+
lastUsedAt: Date.now(),
|
|
135
|
+
tail: Promise.resolve(),
|
|
136
|
+
};
|
|
88
137
|
this.live.set(key, live);
|
|
89
138
|
}
|
|
90
139
|
const entry = live;
|
|
@@ -97,15 +146,24 @@ export class SandboxManager {
|
|
|
97
146
|
throw new Error("Turn cancelled");
|
|
98
147
|
if (this.closing)
|
|
99
148
|
throw new Error("Runtime is shutting down");
|
|
100
|
-
|
|
149
|
+
live.active += 1;
|
|
150
|
+
try {
|
|
151
|
+
return await this.executeOpen(live, session, spec, toolName, input, signal);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
live.active -= 1;
|
|
155
|
+
live.lastUsedAt = Date.now();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async executeOpen(live, session, spec, toolName, input, signal) {
|
|
101
159
|
if (!live.handle) {
|
|
102
160
|
try {
|
|
103
161
|
live.handle = await this.open(live, session, spec);
|
|
104
162
|
}
|
|
105
163
|
catch (error) {
|
|
106
164
|
const message = error instanceof Error ? error.message : String(error);
|
|
107
|
-
this.record(spec.key, session.id, live.backend.name, spec.image, "stopped");
|
|
108
|
-
this.options.emit(session.id, session.activeTurnId, "sandbox.state", {
|
|
165
|
+
await this.record(spec.key, session.id, live.backend.name, spec.image, "stopped");
|
|
166
|
+
await this.options.emit(session.id, session.activeTurnId, "sandbox.state", {
|
|
109
167
|
state: "stopped",
|
|
110
168
|
backend: live.backend.name,
|
|
111
169
|
error: message,
|
|
@@ -124,7 +182,7 @@ export class SandboxManager {
|
|
|
124
182
|
const outcome = await runSandboxTool(live.handle, toolName, (input ?? {}), signal, (value) => {
|
|
125
183
|
report = value;
|
|
126
184
|
});
|
|
127
|
-
this.options.emit(session.id, session.activeTurnId, "sandbox.exec", {
|
|
185
|
+
await this.options.emit(session.id, session.activeTurnId, "sandbox.exec", {
|
|
128
186
|
tool: toolName,
|
|
129
187
|
...report,
|
|
130
188
|
...(report.command ? { command: report.command.slice(0, 500) } : {}),
|
|
@@ -145,38 +203,26 @@ export class SandboxManager {
|
|
|
145
203
|
message: error instanceof Error ? error.message : String(error),
|
|
146
204
|
};
|
|
147
205
|
}
|
|
148
|
-
finally {
|
|
149
|
-
this.armIdle(spec.key, live);
|
|
150
|
-
}
|
|
151
206
|
}
|
|
152
207
|
async open(live, session, spec) {
|
|
153
|
-
const existing = this.options.store.get("sandboxes", spec.key);
|
|
208
|
+
const existing = await this.options.store.tx((t) => t.get("sandboxes", spec.key));
|
|
154
209
|
const payload = {
|
|
155
210
|
backend: live.backend.name,
|
|
156
211
|
isolation: live.backend.isolation,
|
|
157
212
|
image: spec.image,
|
|
158
213
|
network: describeNetwork(spec.network),
|
|
159
214
|
};
|
|
160
|
-
this.record(spec.key, session.id, live.backend.name, spec.image, "creating", existing);
|
|
161
|
-
this.options.emit(session.id, session.activeTurnId, "sandbox.state", {
|
|
215
|
+
await this.record(spec.key, session.id, live.backend.name, spec.image, "creating", existing);
|
|
216
|
+
await this.options.emit(session.id, session.activeTurnId, "sandbox.state", {
|
|
162
217
|
state: "creating",
|
|
163
218
|
...payload,
|
|
164
219
|
...(existing ? { reattach: true } : {}),
|
|
165
220
|
});
|
|
166
221
|
const handle = await live.backend.open(spec);
|
|
167
|
-
this.record(spec.key, session.id, live.backend.name, spec.image, "running", existing);
|
|
168
|
-
this.options.emit(session.id, session.activeTurnId, "sandbox.state", { state: "running", ...payload });
|
|
222
|
+
await this.record(spec.key, session.id, live.backend.name, spec.image, "running", existing);
|
|
223
|
+
await this.options.emit(session.id, session.activeTurnId, "sandbox.state", { state: "running", ...payload });
|
|
169
224
|
return handle;
|
|
170
225
|
}
|
|
171
|
-
armIdle(key, live) {
|
|
172
|
-
clearTimeout(live.timer);
|
|
173
|
-
if (this.closing)
|
|
174
|
-
return;
|
|
175
|
-
live.timer = setTimeout(() => {
|
|
176
|
-
live.tail = live.tail.then(() => this.stop(key, live)).catch(() => undefined);
|
|
177
|
-
}, live.idleMs);
|
|
178
|
-
live.timer.unref();
|
|
179
|
-
}
|
|
180
226
|
async stop(key, live) {
|
|
181
227
|
const handle = live.handle;
|
|
182
228
|
if (!handle)
|
|
@@ -186,25 +232,28 @@ export class SandboxManager {
|
|
|
186
232
|
await handle.stop();
|
|
187
233
|
}
|
|
188
234
|
finally {
|
|
189
|
-
this.record(key, live.sessionId, live.backend.name, undefined, "stopped");
|
|
190
|
-
this.options.emit(live.sessionId, null, "sandbox.state", { state: "stopped", backend: live.backend.name });
|
|
235
|
+
await this.record(key, live.sessionId, live.backend.name, undefined, "stopped");
|
|
236
|
+
await this.options.emit(live.sessionId, null, "sandbox.state", { state: "stopped", backend: live.backend.name });
|
|
191
237
|
}
|
|
192
238
|
}
|
|
193
|
-
record(key, sessionId, backend, image, state,
|
|
239
|
+
async record(key, sessionId, backend, image, state, known) {
|
|
194
240
|
const now = new Date().toISOString();
|
|
195
|
-
this.options.store.tx(() =>
|
|
196
|
-
key
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
241
|
+
await this.options.store.tx(async (t) => {
|
|
242
|
+
const existing = known ?? (await t.get("sandboxes", key));
|
|
243
|
+
await t.put("sandboxes", key, {
|
|
244
|
+
key,
|
|
245
|
+
sessionId,
|
|
246
|
+
backend,
|
|
247
|
+
image: image ?? existing?.image ?? "",
|
|
248
|
+
state,
|
|
249
|
+
createdAt: existing?.createdAt ?? now,
|
|
250
|
+
updatedAt: now,
|
|
251
|
+
});
|
|
252
|
+
});
|
|
204
253
|
}
|
|
205
254
|
/** The sandbox record for a session, if one was ever created. */
|
|
206
255
|
status(sessionId) {
|
|
207
|
-
return this.options.store.get("sandboxes", this.keyOf(sessionId));
|
|
256
|
+
return this.options.store.tx((t) => t.get("sandboxes", this.keyOf(sessionId)));
|
|
208
257
|
}
|
|
209
258
|
/** Delete sandboxes this Runtime owns whose session no longer exists. */
|
|
210
259
|
async reconcile(sessionExists) {
|
|
@@ -212,11 +261,11 @@ export class SandboxManager {
|
|
|
212
261
|
if (!backend)
|
|
213
262
|
return;
|
|
214
263
|
for (const key of await backend.list(this.prefix)) {
|
|
215
|
-
const record = this.options.store.get("sandboxes", key);
|
|
216
|
-
if (record && sessionExists(record.sessionId))
|
|
264
|
+
const record = await this.options.store.tx((t) => t.get("sandboxes", key));
|
|
265
|
+
if (record && (await sessionExists(record.sessionId)))
|
|
217
266
|
continue;
|
|
218
267
|
await backend.remove(key);
|
|
219
|
-
this.options.store.tx(() =>
|
|
268
|
+
await this.options.store.tx((t) => t.delete("sandboxes", key));
|
|
220
269
|
}
|
|
221
270
|
}
|
|
222
271
|
async close() {
|
|
@@ -224,7 +273,6 @@ export class SandboxManager {
|
|
|
224
273
|
if (!this.selection)
|
|
225
274
|
return;
|
|
226
275
|
const tasks = [...this.live.entries()].map(async ([key, live]) => {
|
|
227
|
-
clearTimeout(live.timer);
|
|
228
276
|
await live.tail;
|
|
229
277
|
if (this.options.ephemeral) {
|
|
230
278
|
live.handle = undefined;
|
package/dist/sandbox/select.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface SandboxSelectionReport {
|
|
|
15
15
|
readonly reason: string;
|
|
16
16
|
readonly probes: readonly SandboxProbe[];
|
|
17
17
|
}
|
|
18
|
-
/** The
|
|
18
|
+
/** The backends a Runtime probes, in order. `auto` picks the first available one. */
|
|
19
19
|
export declare function defaultSandboxBackends(options: {
|
|
20
20
|
readonly root: string;
|
|
21
21
|
}): SandboxBackend[];
|
package/dist/sandbox/select.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { microsandboxBackend } from "../adapters/sandbox/microsandbox.js";
|
|
2
1
|
import { virtualBackend } from "../adapters/sandbox/virtual.js";
|
|
3
|
-
const PREFERENCES = ["auto", "
|
|
4
|
-
/** The
|
|
2
|
+
const PREFERENCES = ["auto", "virtual"];
|
|
3
|
+
/** The backends a Runtime probes, in order. `auto` picks the first available one. */
|
|
5
4
|
export function defaultSandboxBackends(options) {
|
|
6
|
-
return [
|
|
5
|
+
return [virtualBackend({ root: options.root })];
|
|
7
6
|
}
|
|
8
7
|
export function parseSandboxPreference(value) {
|
|
9
8
|
const normalized = (value ?? "auto").trim().toLowerCase() || "auto";
|
|
@@ -75,7 +74,7 @@ export async function probeSandboxBackends(options = {}) {
|
|
|
75
74
|
preference: "auto",
|
|
76
75
|
backend: null,
|
|
77
76
|
isolation: null,
|
|
78
|
-
reason: "sandbox.backend must be auto
|
|
77
|
+
reason: "sandbox.backend must be auto or virtual",
|
|
79
78
|
probes: await Promise.all(backends.map((backend) => backend.probe())),
|
|
80
79
|
};
|
|
81
80
|
return reportSelection(await selectSandboxBackend(backends, preference));
|
package/dist/sandbox/types.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* and import a substrate SDK; everything else in the Runtime talks to this interface.
|
|
4
4
|
*/
|
|
5
5
|
import type { SandboxNetworkPreset } from "@nylorun/core/define";
|
|
6
|
-
export type SandboxBackendName = "
|
|
7
|
-
export type SandboxIsolation = "
|
|
6
|
+
export type SandboxBackendName = "virtual";
|
|
7
|
+
export type SandboxIsolation = "process";
|
|
8
8
|
export interface SandboxProbe {
|
|
9
9
|
readonly name: SandboxBackendName;
|
|
10
10
|
readonly available: boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON: object keys sorted, `undefined` members dropped. Idempotency
|
|
3
|
+
* comparisons (commands, effects, vault requests) compare canonical forms.
|
|
4
|
+
*/
|
|
5
|
+
export function canonical(value) {
|
|
6
|
+
if (Array.isArray(value))
|
|
7
|
+
return "[" + value.map(canonical).join(",") + "]";
|
|
8
|
+
if (value && typeof value === "object")
|
|
9
|
+
return ("{" +
|
|
10
|
+
Object.keys(value)
|
|
11
|
+
.sort()
|
|
12
|
+
.filter((k) => value[k] !== undefined)
|
|
13
|
+
.map((k) => JSON.stringify(k) + ":" + canonical(value[k]))
|
|
14
|
+
.join(",") +
|
|
15
|
+
"}");
|
|
16
|
+
return JSON.stringify(value) ?? "null";
|
|
17
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event cursors. A cursor names the last event a client has seen:
|
|
3
|
+
* `base64url("<sessionId>:<seq>")`, where `seq` is the per-session event
|
|
4
|
+
* sequence and also the S2 sequence number in `sessions/<sessionId>`.
|
|
5
|
+
* The encoding matches the old global-sequence cursors, so clients see no format change.
|
|
6
|
+
*/
|
|
7
|
+
export declare function encodeCursor(sessionId: string, seq: number): string;
|
|
8
|
+
/** Decodes a cursor for `sessionId`, or throws `Invalid cursor`. */
|
|
9
|
+
export declare function decodeCursor(sessionId: string, cursor: string): number;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event cursors. A cursor names the last event a client has seen:
|
|
3
|
+
* `base64url("<sessionId>:<seq>")`, where `seq` is the per-session event
|
|
4
|
+
* sequence and also the S2 sequence number in `sessions/<sessionId>`.
|
|
5
|
+
* The encoding matches the old global-sequence cursors, so clients see no format change.
|
|
6
|
+
*/
|
|
7
|
+
export function encodeCursor(sessionId, seq) {
|
|
8
|
+
return Buffer.from(`${sessionId}:${seq}`).toString("base64url");
|
|
9
|
+
}
|
|
10
|
+
/** Decodes a cursor for `sessionId`, or throws `Invalid cursor`. */
|
|
11
|
+
export function decodeCursor(sessionId, cursor) {
|
|
12
|
+
const decoded = Buffer.from(cursor, "base64url").toString();
|
|
13
|
+
const prefix = `${sessionId}:`;
|
|
14
|
+
const rest = decoded.slice(prefix.length);
|
|
15
|
+
if (!decoded.startsWith(prefix) || !/^\d+$/.test(rest))
|
|
16
|
+
throw new Error("Invalid cursor");
|
|
17
|
+
return Number(rest);
|
|
18
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type CommitListener, type DocTable, type ExecutorRow, type PrincipalRow, type SessionOwnership, type SessionStore, type SessionStoreOptions, type StoreHealth, type Tx, type VaultAuditRow, type VaultCredentialRow, type VaultIdempotencyRow, type VaultRow } from "./types.js";
|
|
2
|
+
/** Schema version the fake reports; it has no migrations. */
|
|
3
|
+
export declare const MEMORY_SCHEMA_VERSION = 1;
|
|
4
|
+
interface SessionMeta extends SessionOwnership {
|
|
5
|
+
nextSeq: number;
|
|
6
|
+
}
|
|
7
|
+
interface State {
|
|
8
|
+
/** JSON text per id, like a `body` column. */
|
|
9
|
+
docs: Record<DocTable, Map<string, string>>;
|
|
10
|
+
sessionMeta: Map<string, SessionMeta>;
|
|
11
|
+
/** sessionId → seq → event JSON. */
|
|
12
|
+
outbox: Map<string, Map<number, string>>;
|
|
13
|
+
executors: Map<string, ExecutorRow>;
|
|
14
|
+
principals: Map<string, PrincipalRow>;
|
|
15
|
+
vaults: Map<string, VaultRow>;
|
|
16
|
+
credentials: Map<string, VaultCredentialRow>;
|
|
17
|
+
audit: VaultAuditRow[];
|
|
18
|
+
idempotency: Map<string, VaultIdempotencyRow>;
|
|
19
|
+
settings: Map<string, string>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The data behind in-memory Session Stores: what a database is to its connections. Several
|
|
23
|
+
* `MemorySessionStore`s on one `MemoryStoreData` see each other's commits and serialize their
|
|
24
|
+
* transactions together, as several processes on one Tenant schema would; it outlives them,
|
|
25
|
+
* so a test can close a Tenant and open it again.
|
|
26
|
+
*/
|
|
27
|
+
export declare class MemoryStoreData {
|
|
28
|
+
/** @internal */ state: State;
|
|
29
|
+
/** @internal */ queue: Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* In-memory `SessionStore` for unit tests. Not a supported profile.
|
|
33
|
+
*
|
|
34
|
+
* Transactions are serialized (one at a time), each works on a copy of the
|
|
35
|
+
* state and replaces it on commit, so a throw rolls back everything: documents,
|
|
36
|
+
* sequences, outbox rows, `afterCommit` callbacks and `signalWork`. Commit listeners see
|
|
37
|
+
* only this store's commits, as with a Postgres connection.
|
|
38
|
+
*/
|
|
39
|
+
export declare class MemorySessionStore implements SessionStore {
|
|
40
|
+
/** Shared with other stores on the same data; a fresh, private one by default. */
|
|
41
|
+
private readonly data;
|
|
42
|
+
readonly tenantId: string;
|
|
43
|
+
private readonly active;
|
|
44
|
+
private readonly listeners;
|
|
45
|
+
private readonly now;
|
|
46
|
+
private readonly onError;
|
|
47
|
+
private closed;
|
|
48
|
+
constructor(options: SessionStoreOptions,
|
|
49
|
+
/** Shared with other stores on the same data; a fresh, private one by default. */
|
|
50
|
+
data?: MemoryStoreData);
|
|
51
|
+
tx<T>(fn: (t: Tx) => Promise<T>): Promise<T>;
|
|
52
|
+
onCommit(listener: CommitListener): () => void;
|
|
53
|
+
health(): Promise<StoreHealth>;
|
|
54
|
+
close(): Promise<void>;
|
|
55
|
+
private acquire;
|
|
56
|
+
}
|
|
57
|
+
export {};
|