@nylorun/runtime 0.13.0-beta → 0.15.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 +434 -0
- package/README.md +137 -41
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +40 -10
- package/dist/adapters/replication/pgoutput.d.ts +21 -0
- package/dist/adapters/replication/pgoutput.js +170 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +570 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +8065 -0
- package/dist/api/components.js +82 -0
- package/dist/api/gate/keys.d.ts +14 -0
- package/dist/api/gate/keys.js +59 -0
- package/dist/api/gate/routes.d.ts +34 -0
- package/dist/api/gate/routes.js +235 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +122 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +266 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +11 -0
- package/dist/api/http/routes/tenant.js +244 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +137 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +16 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/gates/contract.d.ts +51 -0
- package/dist/gates/contract.js +37 -0
- package/dist/gates/handler.d.ts +21 -0
- package/dist/gates/handler.js +55 -0
- package/dist/gates/http-client.d.ts +12 -0
- package/dist/gates/http-client.js +160 -0
- package/dist/gates/in-process.d.ts +32 -0
- package/dist/gates/in-process.js +42 -0
- package/dist/gates/inflight.d.ts +39 -0
- package/dist/gates/inflight.js +70 -0
- package/dist/gates/mcp-handler.d.ts +35 -0
- package/dist/gates/mcp-handler.js +142 -0
- package/dist/gates/meter.d.ts +24 -0
- package/dist/gates/meter.js +165 -0
- package/dist/gates/model-gate.d.ts +45 -0
- package/dist/gates/model-gate.js +1 -0
- package/dist/gates/tenant-vaults.d.ts +64 -0
- package/dist/gates/tenant-vaults.js +94 -0
- package/dist/gates/tool-calls.d.ts +51 -0
- package/dist/gates/tool-calls.js +64 -0
- package/dist/gates/tool-client.d.ts +12 -0
- package/dist/gates/tool-client.js +201 -0
- package/dist/gates/tool-contract.d.ts +116 -0
- package/dist/gates/tool-contract.js +64 -0
- package/dist/gates/tool-gate.d.ts +45 -0
- package/dist/gates/tool-gate.js +10 -0
- package/dist/host/admin-api.d.ts +23 -0
- package/dist/host/admin-api.js +74 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +181 -0
- package/dist/host/config-for.d.ts +3 -1
- package/dist/host/config-for.js +3 -2
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +36 -8
- package/dist/host/create-host.js +161 -330
- package/dist/host/execution.d.ts +20 -18
- package/dist/host/execution.js +2 -2
- package/dist/host/gates.d.ts +58 -0
- package/dist/host/gates.js +127 -0
- package/dist/host/http.d.ts +20 -4
- package/dist/host/http.js +81 -23
- package/dist/host/index.d.ts +1 -1
- package/dist/host/main.js +189 -58
- package/dist/host/stack-config.d.ts +91 -19
- package/dist/host/stack-config.js +246 -17
- package/dist/infra/database.d.ts +2 -2
- package/dist/infra/execution.d.ts +5 -5
- package/dist/infra/execution.js +8 -6
- package/dist/infra/index.d.ts +1 -1
- package/dist/infra/workers.d.ts +17 -17
- package/dist/infra/workers.js +9 -9
- package/dist/keys/client.d.ts +9 -0
- package/dist/keys/client.js +70 -0
- package/dist/keys/contract.d.ts +24 -0
- package/dist/keys/contract.js +12 -0
- package/dist/keys/keys.d.ts +41 -0
- package/dist/keys/keys.js +50 -0
- package/dist/mcp/connect.d.ts +43 -3
- package/dist/mcp/connect.js +12 -5
- package/dist/mcp/pool.d.ts +39 -3
- package/dist/mcp/pool.js +74 -11
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25889 -0
- package/dist/record/append.d.ts +51 -0
- package/dist/record/append.js +20 -0
- package/dist/record/envelope.d.ts +19 -0
- package/dist/record/envelope.js +49 -0
- package/dist/record/index.d.ts +11 -0
- package/dist/record/index.js +11 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/postgres/connect.d.ts +25 -9
- package/dist/store/postgres/connect.js +27 -10
- package/dist/store/postgres/db.d.ts +16 -0
- package/dist/store/postgres/db.js +28 -0
- package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
- package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
- package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
- package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
- package/dist/store/postgres/migrate.d.ts +68 -0
- package/dist/store/postgres/migrate.js +183 -0
- package/dist/store/postgres/record-writer.d.ts +4 -0
- package/dist/store/postgres/record-writer.js +34 -0
- package/dist/store/postgres/record.d.ts +5 -0
- package/dist/store/postgres/record.js +60 -0
- package/dist/store/postgres/schema.d.ts +3096 -0
- package/dist/store/postgres/schema.js +363 -0
- package/dist/store/postgres/store.d.ts +2 -4
- package/dist/store/postgres/store.js +590 -434
- package/dist/store/postgres/tenant.d.ts +56 -0
- package/dist/store/postgres/tenant.js +154 -0
- package/dist/store/types.d.ts +183 -132
- package/dist/store/types.js +0 -1
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +33 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/types.d.ts +78 -0
- package/dist/streams/relay/types.js +8 -0
- package/dist/streams/types.d.ts +71 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.d.ts +7 -2
- package/dist/tenant/advance.js +125 -14
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/cause.d.ts +25 -0
- package/dist/tenant/cause.js +45 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +111 -115
- package/dist/tenant/context.d.ts +101 -21
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +104 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +481 -0
- package/dist/tenant/effects.d.ts +20 -1
- package/dist/tenant/effects.js +159 -42
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +22 -7
- package/dist/tenant/ephemeral.js +83 -125
- package/dist/tenant/history.d.ts +64 -0
- package/dist/tenant/history.js +120 -0
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/model-setting.d.ts +2 -2
- package/dist/tenant/module.d.ts +17 -28
- package/dist/tenant/module.js +133 -279
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/paths.d.ts +11 -16
- package/dist/tenant/paths.js +24 -68
- package/dist/tenant/pool.d.ts +1 -3
- package/dist/tenant/pool.js +1 -19
- package/dist/tenant/principals.d.ts +23 -11
- package/dist/tenant/principals.js +61 -49
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +28 -20
- package/dist/tenant/runtime.js +95 -64
- package/dist/tenant/scheduler.d.ts +10 -3
- package/dist/tenant/scheduler.js +9 -5
- package/dist/tenant/session-streams.d.ts +127 -0
- package/dist/tenant/session-streams.js +404 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +3 -3
- package/dist/tenant/status.js +20 -13
- package/dist/tenant/store-pg.d.ts +9 -6
- package/dist/tenant/store-pg.js +60 -125
- package/dist/tenant/streams.d.ts +62 -85
- package/dist/tenant/streams.js +143 -226
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +180 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +69 -98
- package/dist/tenant/types.js +4 -15
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +13 -7
- package/dist/vault/host-model.d.ts +31 -0
- package/dist/vault/host-model.js +57 -0
- package/dist/vault/service.d.ts +44 -17
- package/dist/vault/service.js +126 -121
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -9
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/store/memory.d.ts +0 -57
- package/dist/store/memory.js +0 -645
- package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
- package/dist/store/postgres/migrations/001_initial.js +0 -196
- package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
- package/dist/store/postgres/migrations/002_session_owner.js +0 -13
- package/dist/store/postgres/migrations/index.d.ts +0 -49
- package/dist/store/postgres/migrations/index.js +0 -76
- package/dist/store/postgres/names.d.ts +0 -11
- package/dist/store/postgres/names.js +0 -42
- package/dist/store/postgres/tenants.d.ts +0 -90
- package/dist/store/postgres/tenants.js +0 -214
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/envelope.d.ts +0 -7
- package/dist/tenant/envelope.js +0 -22
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/quarantine-error.d.ts +0 -6
- package/dist/tenant/quarantine-error.js +0 -9
- package/dist/tenant/quarantine.d.ts +0 -27
- package/dist/tenant/quarantine.js +0 -72
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
- package/dist/tenant/store-memory.d.ts +0 -16
- package/dist/tenant/store-memory.js +0 -111
- /package/dist/{store → record}/cursor.d.ts +0 -0
- /package/dist/{store → record}/cursor.js +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only write path into the record (Durable Streams §6, blueprint D27): the session's log
|
|
3
|
+
* head allocates the next sequence, the event is built on the envelope and checked against the
|
|
4
|
+
* catalog, and its row is written, both in the caller's transaction. The database holds one
|
|
5
|
+
* Tenant, so rows are keyed by session alone; the Tenant id is the caller's and goes into the
|
|
6
|
+
* envelope only. The caller holds the session row lock, which orders the session's appends,
|
|
7
|
+
* and passes the epoch it read under that lock.
|
|
8
|
+
*
|
|
9
|
+
* The two statements run through `RecordWriter`, which the store implements on its
|
|
10
|
+
* transaction (`store/postgres/record-writer.ts`): the database driver stays behind
|
|
11
|
+
* `store/postgres/` (session-store.md §3), and this module decides what is written.
|
|
12
|
+
*/
|
|
13
|
+
import type { EventPayload, EventType, LiveEvent, SessionEventOf } from "@nylorun/core/contracts";
|
|
14
|
+
/** The record's two statements, in the caller's transaction. */
|
|
15
|
+
export interface RecordWriter {
|
|
16
|
+
/**
|
|
17
|
+
* Advances the session's log head: the seq its next event takes, and the basin generation
|
|
18
|
+
* its events go to (a session's first event takes the Tenant's current one).
|
|
19
|
+
*/
|
|
20
|
+
advance(sessionId: string): Promise<{
|
|
21
|
+
seq: number;
|
|
22
|
+
generation: number;
|
|
23
|
+
}>;
|
|
24
|
+
/** Writes the event's row. */
|
|
25
|
+
insert(row: {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
seq: number;
|
|
28
|
+
generation: number;
|
|
29
|
+
type: string;
|
|
30
|
+
body: LiveEvent;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export interface AppendInput<T extends EventType> {
|
|
34
|
+
/** The database's one Tenant, for the envelope. */
|
|
35
|
+
tenantId: string;
|
|
36
|
+
sessionId: string;
|
|
37
|
+
turnId: string | null;
|
|
38
|
+
/** The session's ownership epoch, read under the caller's session row lock. */
|
|
39
|
+
epoch: number;
|
|
40
|
+
time: Date;
|
|
41
|
+
type: T;
|
|
42
|
+
payload: EventPayload<T>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Appends one event through `writer`. An event the catalog does not describe throws
|
|
46
|
+
* `InvalidEventError` before its row is written; the caller's transaction then rolls back.
|
|
47
|
+
*/
|
|
48
|
+
export declare function appendEvent<T extends EventType>(writer: RecordWriter, input: AppendInput<T>): Promise<{
|
|
49
|
+
event: SessionEventOf<T>;
|
|
50
|
+
generation: number;
|
|
51
|
+
}>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { buildEvent } from "./envelope.js";
|
|
2
|
+
/**
|
|
3
|
+
* Appends one event through `writer`. An event the catalog does not describe throws
|
|
4
|
+
* `InvalidEventError` before its row is written; the caller's transaction then rolls back.
|
|
5
|
+
*/
|
|
6
|
+
export async function appendEvent(writer, input) {
|
|
7
|
+
const { seq, generation } = await writer.advance(input.sessionId);
|
|
8
|
+
const event = buildEvent({
|
|
9
|
+
tenantId: input.tenantId,
|
|
10
|
+
sessionId: input.sessionId,
|
|
11
|
+
turnId: input.turnId,
|
|
12
|
+
seq,
|
|
13
|
+
epoch: input.epoch,
|
|
14
|
+
time: input.time,
|
|
15
|
+
type: input.type,
|
|
16
|
+
payload: input.payload,
|
|
17
|
+
});
|
|
18
|
+
await writer.insert({ sessionId: input.sessionId, seq, generation, type: input.type, body: event });
|
|
19
|
+
return { event, generation };
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EventPayload, type EventType, type SessionEventOf } from "@nylorun/core/contracts";
|
|
2
|
+
/** A write of an event the catalog does not describe: a bug in the writer. */
|
|
3
|
+
export declare class InvalidEventError extends Error {
|
|
4
|
+
readonly eventType: string;
|
|
5
|
+
constructor(eventType: string, detail: string);
|
|
6
|
+
}
|
|
7
|
+
export interface EventInput<T extends EventType> {
|
|
8
|
+
tenantId: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
turnId: string | null;
|
|
11
|
+
seq: number;
|
|
12
|
+
/** The session's ownership epoch when the event is written. */
|
|
13
|
+
epoch: number;
|
|
14
|
+
time: Date;
|
|
15
|
+
type: T;
|
|
16
|
+
payload: EventPayload<T>;
|
|
17
|
+
}
|
|
18
|
+
/** The event as stored and streamed: JSON-normalized, validated. Throws `InvalidEventError`. */
|
|
19
|
+
export declare function buildEvent<T extends EventType>(input: EventInput<T>): SessionEventOf<T>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a session event on the `nylorun.event/2` envelope and checks it against the event
|
|
3
|
+
* catalog (Durable Streams §9.5) before a store writes it. Both stores call it inside
|
|
4
|
+
* `Tx.event`, so nothing the catalog does not describe reaches a stream.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { EVENT_CATALOG, EVENT_SCHEMA, EVENT_SCHEMAS, isEventType, } from "@nylorun/core/contracts";
|
|
9
|
+
import { encodeCursor } from "./cursor.js";
|
|
10
|
+
/** A write of an event the catalog does not describe: a bug in the writer. */
|
|
11
|
+
export class InvalidEventError extends Error {
|
|
12
|
+
eventType;
|
|
13
|
+
constructor(eventType, detail) {
|
|
14
|
+
super(`Event ${eventType} does not match the event catalog: ${detail}`);
|
|
15
|
+
this.eventType = eventType;
|
|
16
|
+
this.name = "InvalidEventError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** The event as stored and streamed: JSON-normalized, validated. Throws `InvalidEventError`. */
|
|
20
|
+
export function buildEvent(input) {
|
|
21
|
+
const type = input.type;
|
|
22
|
+
if (!isEventType(type))
|
|
23
|
+
throw new InvalidEventError(type, "unknown type");
|
|
24
|
+
const entry = EVENT_CATALOG[type];
|
|
25
|
+
const event = JSON.parse(JSON.stringify({
|
|
26
|
+
schema: EVENT_SCHEMA,
|
|
27
|
+
eventId: randomUUID(),
|
|
28
|
+
tenantId: input.tenantId,
|
|
29
|
+
sessionId: input.sessionId,
|
|
30
|
+
runId: null,
|
|
31
|
+
turnId: input.turnId,
|
|
32
|
+
incarnation: 0,
|
|
33
|
+
epoch: input.epoch,
|
|
34
|
+
seq: input.seq,
|
|
35
|
+
cursor: encodeCursor(input.sessionId, input.seq),
|
|
36
|
+
time: input.time.toISOString(),
|
|
37
|
+
schemaVersion: entry.version,
|
|
38
|
+
source: { kind: entry.source, id: "runtime" },
|
|
39
|
+
evidence: "observed",
|
|
40
|
+
visibility: entry.visibility ?? "public",
|
|
41
|
+
retention: "full",
|
|
42
|
+
type,
|
|
43
|
+
payload: input.payload,
|
|
44
|
+
}));
|
|
45
|
+
const checked = EVENT_SCHEMAS[type].safeParse(event);
|
|
46
|
+
if (!checked.success)
|
|
47
|
+
throw new InvalidEventError(type, z.prettifyError(checked.error));
|
|
48
|
+
return event;
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record module (blueprint D27): the one write path into the record. It builds each event
|
|
3
|
+
* on the `nylorun.event/2` envelope, checks it against the event catalog, and holds the only
|
|
4
|
+
* insert into `session_events` and `session_log_heads`, whose Postgres statements it runs
|
|
5
|
+
* through the store's `RecordWriter` (`store/postgres/record-writer.ts`;
|
|
6
|
+
* `scripts/check-boundaries.mjs` refuses an insert anywhere else). Other folders import this
|
|
7
|
+
* file, never the module's own files.
|
|
8
|
+
*/
|
|
9
|
+
export { buildEvent, InvalidEventError, type EventInput } from "./envelope.js";
|
|
10
|
+
export { decodeCursor, encodeCursor } from "./cursor.js";
|
|
11
|
+
export { appendEvent, type AppendInput, type RecordWriter } from "./append.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record module (blueprint D27): the one write path into the record. It builds each event
|
|
3
|
+
* on the `nylorun.event/2` envelope, checks it against the event catalog, and holds the only
|
|
4
|
+
* insert into `session_events` and `session_log_heads`, whose Postgres statements it runs
|
|
5
|
+
* through the store's `RecordWriter` (`store/postgres/record-writer.ts`;
|
|
6
|
+
* `scripts/check-boundaries.mjs` refuses an insert anywhere else). Other folders import this
|
|
7
|
+
* file, never the module's own files.
|
|
8
|
+
*/
|
|
9
|
+
export { buildEvent, InvalidEventError } from "./envelope.js";
|
|
10
|
+
export { decodeCursor, encodeCursor } from "./cursor.js";
|
|
11
|
+
export { appendEvent } from "./append.js";
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Manager: owns sandbox lifecycle for the Runtime. One sandbox per session, created on
|
|
3
|
+
* the first sandbox tool call, stopped after its idle timeout, reattached on the next call.
|
|
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.
|
|
9
|
+
*/
|
|
10
|
+
import type { EventPayload } from "@nylorun/core/contracts";
|
|
1
11
|
import { type AgentManifest, type CapabilityManifest, type SandboxToolName } from "@nylorun/core/define";
|
|
2
12
|
import type { SessionStore } from "../store/types.js";
|
|
3
13
|
import { type SandboxSelection, type SandboxSelectionReport } from "./select.js";
|
|
@@ -34,8 +44,10 @@ export interface SandboxManagerOptions {
|
|
|
34
44
|
/** Delete sandboxes on close (the Runtime's store does not outlive the process). */
|
|
35
45
|
readonly ephemeral: boolean;
|
|
36
46
|
/** Writes a session event in its own transaction; never called inside one. */
|
|
37
|
-
readonly emit: (sessionId: string, turnId: string | null, type:
|
|
47
|
+
readonly emit: <T extends SandboxEventType>(sessionId: string, turnId: string | null, type: T, payload: EventPayload<T>) => void | Promise<void>;
|
|
38
48
|
}
|
|
49
|
+
/** The session events a sandbox writes. */
|
|
50
|
+
export type SandboxEventType = "sandbox.state" | "sandbox.exec";
|
|
39
51
|
/** The sandbox capability that owns this tool call, when the call is a built-in sandbox tool. */
|
|
40
52
|
export declare function sandboxCapabilityOf(manifest: AgentManifest | undefined, capabilityId: string | undefined, toolName: string | undefined): CapabilityManifest | undefined;
|
|
41
53
|
export declare class SandboxManager {
|
package/dist/sandbox/manager.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sandbox Manager: owns sandbox lifecycle for the Runtime. One sandbox per session, created on
|
|
3
|
-
* the first sandbox tool call, stopped after its idle timeout, reattached on the next call.
|
|
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.
|
|
9
|
-
*/
|
|
10
1
|
import { createHash } from "node:crypto";
|
|
11
2
|
import { isSandboxToolName, } from "@nylorun/core/define";
|
|
12
3
|
import { DEFAULT_SANDBOX_CPUS, DEFAULT_SANDBOX_IMAGE, describeNetwork, idleMsOf, memoryMiBOf, resolveNetwork, } from "./policy.js";
|
|
@@ -172,7 +163,7 @@ export class SandboxManager {
|
|
|
172
163
|
return {
|
|
173
164
|
kind: "failed",
|
|
174
165
|
code: "sandbox.start_failed",
|
|
175
|
-
message: `The sandbox could not start on ${live.backend.name} (image ${spec.image}): ${message}`,
|
|
166
|
+
message: `The sandbox could not start on ${live.backend.name} (image ${spec.image ?? "the backend's default"}): ${message}`,
|
|
176
167
|
};
|
|
177
168
|
}
|
|
178
169
|
}
|
|
@@ -220,7 +211,13 @@ export class SandboxManager {
|
|
|
220
211
|
});
|
|
221
212
|
const handle = await live.backend.open(spec);
|
|
222
213
|
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", {
|
|
214
|
+
await this.options.emit(session.id, session.activeTurnId, "sandbox.state", {
|
|
215
|
+
state: "running",
|
|
216
|
+
...payload,
|
|
217
|
+
...(existing && handle.created
|
|
218
|
+
? { lost: true, note: "The sandbox was gone and was created again; its earlier files are lost." }
|
|
219
|
+
: {}),
|
|
220
|
+
});
|
|
224
221
|
return handle;
|
|
225
222
|
}
|
|
226
223
|
async stop(key, live) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the sandbox a session asks for when it is opened against its Tenant's configuration.
|
|
3
|
+
* Pure: the caller supplies the configuration and who is asking. Every problem is
|
|
4
|
+
* reported, not only the first, so one response lists all of them.
|
|
5
|
+
*/
|
|
6
|
+
import type { SandboxInlineRequest } from "@nylorun/core/contracts";
|
|
7
|
+
import type { SandboxManifest } from "@nylorun/core/define";
|
|
8
|
+
import { type EffectiveSandboxConfig } from "./tenant-config.js";
|
|
9
|
+
export type SandboxSource = "default" | "inline";
|
|
10
|
+
export type SandboxResolution = {
|
|
11
|
+
readonly kind: "none";
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "sandbox";
|
|
14
|
+
readonly spec: SandboxManifest;
|
|
15
|
+
readonly source: SandboxSource;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "error";
|
|
18
|
+
readonly status: 400 | 403;
|
|
19
|
+
readonly errors: readonly string[];
|
|
20
|
+
};
|
|
21
|
+
export interface ResolveSandboxInput {
|
|
22
|
+
/** `PutSessionRequest.sandbox` other than `{ session }`, which the caller handles. */
|
|
23
|
+
readonly request: false | SandboxInlineRequest | undefined;
|
|
24
|
+
readonly config: EffectiveSandboxConfig;
|
|
25
|
+
/** The request acts for a subject (`Nylorun-Subject`) rather than as the application. */
|
|
26
|
+
readonly actingForSubject: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare const SUBJECT_INLINE_MESSAGE = "Defining a sandbox needs an application key. Use the Tenant default, or open the session from the application backend.";
|
|
29
|
+
export declare function resolveSandbox(input: ResolveSandboxInput): SandboxResolution;
|
|
30
|
+
/** Whether `host` (a name or `*.suffix` pattern) is inside one of the ceiling's patterns. */
|
|
31
|
+
export declare function withinCeiling(host: string, ceiling: readonly string[]): boolean;
|
|
32
|
+
/** Problems with a Tenant configuration, checked when an operator saves it. */
|
|
33
|
+
export declare function sandboxConfigErrors(config: EffectiveSandboxConfig): string[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { memoryMiB } from "./tenant-config.js";
|
|
2
|
+
export const SUBJECT_INLINE_MESSAGE = "Defining a sandbox needs an application key. Use the Tenant default, or open the session from the application backend.";
|
|
3
|
+
export function resolveSandbox(input) {
|
|
4
|
+
const { request, config } = input;
|
|
5
|
+
if (request === false)
|
|
6
|
+
return { kind: "none" };
|
|
7
|
+
if (request === undefined) {
|
|
8
|
+
const fallback = config.default;
|
|
9
|
+
if (fallback === "none")
|
|
10
|
+
return { kind: "none" };
|
|
11
|
+
return build(fallback === "virtual" ? {} : fallback, "default", input);
|
|
12
|
+
}
|
|
13
|
+
if (input.actingForSubject)
|
|
14
|
+
return { kind: "error", status: 403, errors: [SUBJECT_INLINE_MESSAGE] };
|
|
15
|
+
return build(request, "inline", input);
|
|
16
|
+
}
|
|
17
|
+
function build(request, source, input) {
|
|
18
|
+
const { limits } = input.config;
|
|
19
|
+
const errors = [];
|
|
20
|
+
if (request.image !== undefined)
|
|
21
|
+
errors.push("sandbox.image is not supported: the virtual sandbox has no images.");
|
|
22
|
+
const allow = (request.network?.allow ?? []).map((host) => host.toLowerCase());
|
|
23
|
+
for (const host of allow) {
|
|
24
|
+
if (!withinCeiling(host, limits.network))
|
|
25
|
+
errors.push(`sandbox.network.allow includes ${host}, which this Tenant does not allow.`);
|
|
26
|
+
else if (host.startsWith("*."))
|
|
27
|
+
errors.push(`sandbox.network.allow includes ${host}; the virtual sandbox allows exact host names only, such as ${host.slice(2)}.`);
|
|
28
|
+
}
|
|
29
|
+
const cpus = request.resources?.cpus ?? limits.defaultResources.cpus;
|
|
30
|
+
const memory = request.resources?.memory === undefined
|
|
31
|
+
? limits.defaultResources.memoryMiB
|
|
32
|
+
: memoryMiB(request.resources.memory);
|
|
33
|
+
if (cpus > limits.resources.cpus)
|
|
34
|
+
errors.push(`sandbox.resources.cpus asks for ${cpus}; this Tenant allows at most ${limits.resources.cpus}.`);
|
|
35
|
+
if (memory > limits.resources.memoryMiB)
|
|
36
|
+
errors.push(`sandbox.resources.memory asks for ${memory}MiB; this Tenant allows at most ${limits.resources.memoryMiB}MiB.`);
|
|
37
|
+
if (errors.length > 0)
|
|
38
|
+
return { kind: "error", status: 400, errors };
|
|
39
|
+
const unique = [...new Set(allow)].sort();
|
|
40
|
+
const spec = {
|
|
41
|
+
...(request.image === undefined ? {} : { image: request.image }),
|
|
42
|
+
network: { preset: "none", ...(unique.length > 0 ? { allow: unique } : {}) },
|
|
43
|
+
resources: { cpus, memory: `${memory}MiB` },
|
|
44
|
+
idle: limits.idle,
|
|
45
|
+
};
|
|
46
|
+
return { kind: "sandbox", spec, source };
|
|
47
|
+
}
|
|
48
|
+
/** Whether `host` (a name or `*.suffix` pattern) is inside one of the ceiling's patterns. */
|
|
49
|
+
export function withinCeiling(host, ceiling) {
|
|
50
|
+
const name = host.toLowerCase();
|
|
51
|
+
for (const entry of ceiling.map((item) => item.toLowerCase())) {
|
|
52
|
+
if (entry === name)
|
|
53
|
+
return true;
|
|
54
|
+
if (entry.startsWith("*.")) {
|
|
55
|
+
const suffix = entry.slice(1);
|
|
56
|
+
const target = name.startsWith("*.") ? name.slice(1) : name;
|
|
57
|
+
if (target.endsWith(suffix))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
/** Problems with a Tenant configuration, checked when an operator saves it. */
|
|
64
|
+
export function sandboxConfigErrors(config) {
|
|
65
|
+
const errors = [];
|
|
66
|
+
const { limits } = config;
|
|
67
|
+
if (limits.defaultResources.cpus > limits.resources.cpus)
|
|
68
|
+
errors.push("limits.defaultResources.cpus is above limits.resources.cpus.");
|
|
69
|
+
if (limits.defaultResources.memoryMiB > limits.resources.memoryMiB)
|
|
70
|
+
errors.push("limits.defaultResources.memory is above limits.resources.memory.");
|
|
71
|
+
if (config.default !== "none" && config.default !== "virtual") {
|
|
72
|
+
const resolved = resolveSandbox({
|
|
73
|
+
request: undefined,
|
|
74
|
+
config,
|
|
75
|
+
actingForSubject: false,
|
|
76
|
+
});
|
|
77
|
+
if (resolved.kind === "error")
|
|
78
|
+
errors.push(...resolved.errors.map((message) => `default: ${message}`));
|
|
79
|
+
}
|
|
80
|
+
return errors;
|
|
81
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AgentManifest, type SandboxManifest } from "@nylorun/core/define";
|
|
2
|
+
export type SandboxedManifest = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly manifest: AgentManifest;
|
|
5
|
+
readonly manifestHash: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly ok: false;
|
|
8
|
+
readonly message: string;
|
|
9
|
+
};
|
|
10
|
+
/** The agent's manifest with the sandbox capability added, or why it cannot take one. */
|
|
11
|
+
export declare function withSandboxCapability(manifest: AgentManifest, spec: SandboxManifest): SandboxedManifest;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gives a session the sandbox chosen when it was opened. An agent session's pinned manifest is
|
|
3
|
+
* its definition plus the `nylorun.sandbox` capability (the six built-in tools, the sandbox
|
|
4
|
+
* instructions and the resolved spec), also added to each agent it uses as a tool, so the tree
|
|
5
|
+
* shares one spec. The definition in the registry never changes.
|
|
6
|
+
*/
|
|
7
|
+
import { AgentManifestSchema } from "@nylorun/core/contracts";
|
|
8
|
+
import { hashManifest, sandboxCapabilityManifest, } from "@nylorun/core/define";
|
|
9
|
+
import { sandboxSpecOf } from "./share.js";
|
|
10
|
+
function isAgentManifest(value) {
|
|
11
|
+
return (typeof value === "object" &&
|
|
12
|
+
value !== null &&
|
|
13
|
+
value.kind !== "workflow" &&
|
|
14
|
+
Array.isArray(value.capabilities));
|
|
15
|
+
}
|
|
16
|
+
function addCapability(manifest, spec) {
|
|
17
|
+
const capabilities = manifest.capabilities.map((capability) => capability.tools === undefined
|
|
18
|
+
? capability
|
|
19
|
+
: {
|
|
20
|
+
...capability,
|
|
21
|
+
tools: capability.tools.map((tool) => {
|
|
22
|
+
// Agents used as tools share the session's sandbox; flow agents used as tools run
|
|
23
|
+
// in linked sessions that inherit it.
|
|
24
|
+
if (tool.agent === undefined || !isAgentManifest(tool.agent))
|
|
25
|
+
return tool;
|
|
26
|
+
if (sandboxSpecOf(tool.agent) !== undefined)
|
|
27
|
+
return tool;
|
|
28
|
+
return { ...tool, agent: addCapability(tool.agent, spec) };
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
...manifest,
|
|
33
|
+
capabilities: [...capabilities, sandboxCapabilityManifest(spec)],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** The agent's manifest with the sandbox capability added, or why it cannot take one. */
|
|
37
|
+
export function withSandboxCapability(manifest, spec) {
|
|
38
|
+
const sandboxed = addCapability(manifest, spec);
|
|
39
|
+
const parsed = AgentManifestSchema.safeParse(sandboxed);
|
|
40
|
+
if (!parsed.success) {
|
|
41
|
+
const issues = parsed.error.issues.map((issue) => issue.message);
|
|
42
|
+
const clash = issues.some((message) => message === "Tool names must be unique");
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
message: clash
|
|
46
|
+
? `'${manifest.id}' declares a tool named like a sandbox tool (bash, read, write, edit, grep, glob). Rename it, or open the session with sandbox: false.`
|
|
47
|
+
: `'${manifest.id}' cannot take this sandbox: ${[...new Set(issues)].join("; ")}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { ok: true, manifest: sandboxed, manifestHash: hashManifest(sandboxed) };
|
|
51
|
+
}
|
package/dist/sandbox/share.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
3
|
-
*
|
|
2
|
+
* Shared sandboxes: one sandbox per owning session, attached via PutSession.sandbox =
|
|
3
|
+
* { session }; keys use the owner id. A sandbox is either declared by the definition (an agent
|
|
4
|
+
* capability or a workflow's `sandbox`) or chosen when the session was opened and pinned on the
|
|
5
|
+
* session record (`sandbox`).
|
|
4
6
|
*/
|
|
5
7
|
import { type AgentManifest, type CapabilityManifest, type SandboxManifest, type WorkflowManifest } from "@nylorun/core/define";
|
|
6
8
|
export type SessionSandboxRef = {
|
|
@@ -9,9 +11,19 @@ export type SessionSandboxRef = {
|
|
|
9
11
|
readonly manifest: AgentManifest | WorkflowManifest | Record<string, unknown>;
|
|
10
12
|
/** Session whose id keys the sandbox. Absent means this session owns its own. */
|
|
11
13
|
readonly sandboxOwnerId?: string | null;
|
|
14
|
+
/** The sandbox chosen when the session was opened (or inherited from its owner), resolved. */
|
|
15
|
+
readonly sandbox?: SandboxManifest;
|
|
12
16
|
};
|
|
17
|
+
/** The session's sandbox spec: the one pinned at open, else the one its definition declares. */
|
|
18
|
+
export declare function sessionSandboxSpec(session: SessionSandboxRef): SandboxManifest | undefined;
|
|
13
19
|
/** Sandbox spec declared on an agent capability or a workflow manifest. */
|
|
14
20
|
export declare function sandboxSpecOf(manifest: AgentManifest | WorkflowManifest | Record<string, unknown> | undefined): SandboxManifest | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Where a definition document declares a sandbox: agent capabilities, the agents inlined as
|
|
23
|
+
* tools or embedded in a flow agent, and a workflow's `sandbox`. Definitions no longer declare
|
|
24
|
+
* one (Sandboxes v3), so registration refuses any of these.
|
|
25
|
+
*/
|
|
26
|
+
export declare function declaredSandboxes(manifest: unknown, into?: string[]): string[];
|
|
15
27
|
export declare function sandboxSpecsEqual(a: SandboxManifest | undefined, b: SandboxManifest | undefined): boolean;
|
|
16
28
|
/** Synthetic capability so SandboxManager.run can use a workflow (or shared) spec. */
|
|
17
29
|
export declare function capabilityForSandbox(sandbox: SandboxManifest): CapabilityManifest;
|
package/dist/sandbox/share.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
3
|
-
*
|
|
2
|
+
* Shared sandboxes: one sandbox per owning session, attached via PutSession.sandbox =
|
|
3
|
+
* { session }; keys use the owner id. A sandbox is either declared by the definition (an agent
|
|
4
|
+
* capability or a workflow's `sandbox`) or chosen when the session was opened and pinned on the
|
|
5
|
+
* session record (`sandbox`).
|
|
4
6
|
*/
|
|
5
7
|
import { canonical, } from "@nylorun/core/define";
|
|
8
|
+
/** The session's sandbox spec: the one pinned at open, else the one its definition declares. */
|
|
9
|
+
export function sessionSandboxSpec(session) {
|
|
10
|
+
return session.sandbox ?? sandboxSpecOf(session.manifest);
|
|
11
|
+
}
|
|
6
12
|
/** Sandbox spec declared on an agent capability or a workflow manifest. */
|
|
7
13
|
export function sandboxSpecOf(manifest) {
|
|
8
14
|
if (!manifest || typeof manifest !== "object")
|
|
@@ -17,6 +23,30 @@ export function sandboxSpecOf(manifest) {
|
|
|
17
23
|
return capability.sandbox;
|
|
18
24
|
return undefined;
|
|
19
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Where a definition document declares a sandbox: agent capabilities, the agents inlined as
|
|
28
|
+
* tools or embedded in a flow agent, and a workflow's `sandbox`. Definitions no longer declare
|
|
29
|
+
* one (Sandboxes v3), so registration refuses any of these.
|
|
30
|
+
*/
|
|
31
|
+
export function declaredSandboxes(manifest, into = []) {
|
|
32
|
+
if (!manifest || typeof manifest !== "object")
|
|
33
|
+
return into;
|
|
34
|
+
const document = manifest;
|
|
35
|
+
if (document.kind === "workflow") {
|
|
36
|
+
if (document.sandbox !== undefined)
|
|
37
|
+
into.push(`flow agent '${document.id}'`);
|
|
38
|
+
for (const agent of Object.values(document.agents ?? {}))
|
|
39
|
+
declaredSandboxes(agent, into);
|
|
40
|
+
return into;
|
|
41
|
+
}
|
|
42
|
+
for (const capability of document.capabilities ?? []) {
|
|
43
|
+
if (capability.sandbox !== undefined)
|
|
44
|
+
into.push(`'${document.id}' (capability '${capability.id}')`);
|
|
45
|
+
for (const tool of capability.tools ?? [])
|
|
46
|
+
declaredSandboxes(tool.agent, into);
|
|
47
|
+
}
|
|
48
|
+
return into;
|
|
49
|
+
}
|
|
20
50
|
export function sandboxSpecsEqual(a, b) {
|
|
21
51
|
if (a === undefined && b === undefined)
|
|
22
52
|
return true;
|
|
@@ -54,5 +84,5 @@ export function owningSandboxSessionId(session, lookup) {
|
|
|
54
84
|
export function sessionHasSandbox(session, lookup) {
|
|
55
85
|
const ownerId = owningSandboxSessionId(session, lookup);
|
|
56
86
|
const owner = ownerId === session.id ? session : lookup(ownerId) ?? session;
|
|
57
|
-
return
|
|
87
|
+
return sessionSandboxSpec(owner) !== undefined || sessionSandboxSpec(session) !== undefined;
|
|
58
88
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Tenant's sandbox configuration, stored as Tenant setting `sandbox.config`: the sandbox a
|
|
3
|
+
* session gets when it names none, and the limits every session's sandbox must fit. Unset
|
|
4
|
+
* fields take the defaults below.
|
|
5
|
+
*/
|
|
6
|
+
import { type SandboxInlineRequest, type TenantSandboxConfig } from "@nylorun/core/contracts";
|
|
7
|
+
export declare const SANDBOX_CONFIG_SETTING = "sandbox.config";
|
|
8
|
+
/** The most a session may ask for when the Tenant sets no maximum. */
|
|
9
|
+
export declare const DEFAULT_MAX_CPUS = 4;
|
|
10
|
+
export declare const DEFAULT_MAX_MEMORY_MIB = 8192;
|
|
11
|
+
export declare const DEFAULT_SANDBOX_IDLE = "15m";
|
|
12
|
+
export interface SandboxResourcesLimit {
|
|
13
|
+
readonly cpus: number;
|
|
14
|
+
readonly memoryMiB: number;
|
|
15
|
+
}
|
|
16
|
+
/** A configuration with every default applied. */
|
|
17
|
+
export interface EffectiveSandboxConfig {
|
|
18
|
+
readonly default: "none" | "virtual" | SandboxInlineRequest;
|
|
19
|
+
readonly limits: {
|
|
20
|
+
readonly network: readonly string[];
|
|
21
|
+
readonly resources: SandboxResourcesLimit;
|
|
22
|
+
readonly defaultResources: SandboxResourcesLimit;
|
|
23
|
+
readonly idle: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
interface SettingsReader {
|
|
27
|
+
getSetting(key: string): Promise<string | undefined>;
|
|
28
|
+
}
|
|
29
|
+
interface SettingsWriter extends SettingsReader {
|
|
30
|
+
putSetting(key: string, value: string): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export declare function memoryMiB(value: string): number;
|
|
33
|
+
export declare function effectiveSandboxConfig(config: TenantSandboxConfig): EffectiveSandboxConfig;
|
|
34
|
+
/** The stored configuration, or `{}` when the Tenant has none. */
|
|
35
|
+
export declare function readSandboxConfig(t: SettingsReader): Promise<TenantSandboxConfig>;
|
|
36
|
+
export declare function writeSandboxConfig(t: SettingsWriter, config: TenantSandboxConfig): Promise<void>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Tenant's sandbox configuration, stored as Tenant setting `sandbox.config`: the sandbox a
|
|
3
|
+
* session gets when it names none, and the limits every session's sandbox must fit. Unset
|
|
4
|
+
* fields take the defaults below.
|
|
5
|
+
*/
|
|
6
|
+
import { TenantSandboxConfigSchema, } from "@nylorun/core/contracts";
|
|
7
|
+
import { parseSandboxSize } from "@nylorun/core/define";
|
|
8
|
+
import { DEFAULT_SANDBOX_CPUS, DEFAULT_SANDBOX_MEMORY_MIB, DEV_PRESET_HOSTS, } from "./policy.js";
|
|
9
|
+
export const SANDBOX_CONFIG_SETTING = "sandbox.config";
|
|
10
|
+
/** The most a session may ask for when the Tenant sets no maximum. */
|
|
11
|
+
export const DEFAULT_MAX_CPUS = 4;
|
|
12
|
+
export const DEFAULT_MAX_MEMORY_MIB = 8192;
|
|
13
|
+
export const DEFAULT_SANDBOX_IDLE = "15m";
|
|
14
|
+
export function memoryMiB(value) {
|
|
15
|
+
const bytes = parseSandboxSize(value);
|
|
16
|
+
return bytes === undefined ? 0 : Math.max(128, Math.ceil(bytes / 1024 ** 2));
|
|
17
|
+
}
|
|
18
|
+
function resources(value, fallback) {
|
|
19
|
+
return {
|
|
20
|
+
cpus: value?.cpus ?? fallback.cpus,
|
|
21
|
+
memoryMiB: value?.memory === undefined ? fallback.memoryMiB : memoryMiB(value.memory),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function effectiveSandboxConfig(config) {
|
|
25
|
+
const limits = config.limits ?? {};
|
|
26
|
+
return {
|
|
27
|
+
default: config.default ?? "none",
|
|
28
|
+
limits: {
|
|
29
|
+
network: limits.network ?? DEV_PRESET_HOSTS,
|
|
30
|
+
resources: resources(limits.resources, {
|
|
31
|
+
cpus: DEFAULT_MAX_CPUS,
|
|
32
|
+
memoryMiB: DEFAULT_MAX_MEMORY_MIB,
|
|
33
|
+
}),
|
|
34
|
+
defaultResources: resources(limits.defaultResources, {
|
|
35
|
+
cpus: DEFAULT_SANDBOX_CPUS,
|
|
36
|
+
memoryMiB: DEFAULT_SANDBOX_MEMORY_MIB,
|
|
37
|
+
}),
|
|
38
|
+
idle: limits.idle ?? DEFAULT_SANDBOX_IDLE,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** The stored configuration, or `{}` when the Tenant has none. */
|
|
43
|
+
export async function readSandboxConfig(t) {
|
|
44
|
+
const raw = await t.getSetting(SANDBOX_CONFIG_SETTING);
|
|
45
|
+
if (raw === undefined)
|
|
46
|
+
return {};
|
|
47
|
+
return TenantSandboxConfigSchema.parse(JSON.parse(raw));
|
|
48
|
+
}
|
|
49
|
+
export async function writeSandboxConfig(t, config) {
|
|
50
|
+
await t.putSetting(SANDBOX_CONFIG_SETTING, JSON.stringify(config));
|
|
51
|
+
}
|
package/dist/sandbox/tools.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
+
import type { SandboxToolOutcome } from "@nylorun/core/contracts";
|
|
1
2
|
import { type SandboxToolName } from "@nylorun/core/define";
|
|
2
3
|
import type { SandboxHandle } from "./types.js";
|
|
3
|
-
export type SandboxToolOutcome
|
|
4
|
-
readonly kind: "completed";
|
|
5
|
-
readonly output: unknown;
|
|
6
|
-
} | {
|
|
7
|
-
readonly kind: "failed";
|
|
8
|
-
readonly code: string;
|
|
9
|
-
readonly message: string;
|
|
10
|
-
};
|
|
4
|
+
export type { SandboxToolOutcome };
|
|
11
5
|
/** What the Runtime reports about a tool call on the session stream. */
|
|
12
6
|
export interface SandboxToolReport {
|
|
13
7
|
readonly command?: string;
|
|
@@ -16,7 +10,11 @@ export interface SandboxToolReport {
|
|
|
16
10
|
readonly killed?: boolean;
|
|
17
11
|
}
|
|
18
12
|
export declare function quote(value: string): string;
|
|
19
|
-
|
|
13
|
+
/**
|
|
14
|
+
* An absolute path inside the sandbox. Relative paths are relative to the workspace; on a backend
|
|
15
|
+
* whose workspace is not `/workspace`, `/workspace/…` still names it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveSandboxPath(path: string, workspace?: string): string;
|
|
20
18
|
/** Keep the head and tail of long output so the model sees both the start and the error. */
|
|
21
19
|
export declare function truncateMiddle(value: string, limit?: number): {
|
|
22
20
|
text: string;
|
package/dist/sandbox/tools.js
CHANGED
|
@@ -14,8 +14,14 @@ const GLOB_SCAN_LIMIT = 20_000;
|
|
|
14
14
|
export function quote(value) {
|
|
15
15
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* An absolute path inside the sandbox. Relative paths are relative to the workspace; on a backend
|
|
19
|
+
* whose workspace is not `/workspace`, `/workspace/…` still names it.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveSandboxPath(path, workspace = SANDBOX_WORKSPACE) {
|
|
22
|
+
if (workspace !== SANDBOX_WORKSPACE && (path === SANDBOX_WORKSPACE || path.startsWith(`${SANDBOX_WORKSPACE}/`)))
|
|
23
|
+
path = workspace + path.slice(SANDBOX_WORKSPACE.length);
|
|
24
|
+
return posix.normalize(path.startsWith("/") ? path : posix.join(workspace, path));
|
|
19
25
|
}
|
|
20
26
|
/** Keep the head and tail of long output so the model sees both the start and the error. */
|
|
21
27
|
export function truncateMiddle(value, limit = OUTPUT_LIMIT) {
|
|
@@ -34,7 +40,9 @@ const failed = (code, message) => ({
|
|
|
34
40
|
message,
|
|
35
41
|
});
|
|
36
42
|
export async function runSandboxTool(handle, name, input, signal, report) {
|
|
37
|
-
const
|
|
43
|
+
const workspace = handle.workspace ?? SANDBOX_WORKSPACE;
|
|
44
|
+
const resolvePath = (path) => resolveSandboxPath(path, workspace);
|
|
45
|
+
const exec = (command, timeoutMs = 60_000) => handle.exec({ command, cwd: workspace, timeoutMs }, signal);
|
|
38
46
|
switch (name) {
|
|
39
47
|
case "bash": {
|
|
40
48
|
const command = String(input.command);
|