@nylorun/runtime 0.15.2-beta → 0.17.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 +134 -0
- package/README.md +68 -29
- package/dist/adapters/execution/restate.d.ts +9 -1
- package/dist/adapters/execution/restate.js +64 -5
- package/dist/adapters/sandbox/local.d.ts +10 -0
- package/dist/adapters/sandbox/local.js +195 -0
- package/dist/adapters/sandbox/virtual.js +36 -1
- package/dist/admin-openapi.json +341 -7
- package/dist/api/a2a/tasks.js +6 -1
- package/dist/api/ag-ui/endpoint.js +1 -2
- package/dist/api/ag-ui/history.js +6 -1
- package/dist/api/ag-ui/routes.d.ts +1 -1
- package/dist/api/ag-ui/routes.js +15 -13
- package/dist/api/components.d.ts +2667 -711
- package/dist/api/components.js +24 -11
- package/dist/api/gate/keys.d.ts +4 -3
- package/dist/api/gate/routes.d.ts +19 -3
- package/dist/api/gate/routes.js +162 -53
- package/dist/api/http/app.js +6 -0
- package/dist/api/http/define.d.ts +19 -12
- package/dist/api/http/define.js +13 -27
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +12 -172
- package/dist/api/http/routes/actions.js +0 -1
- package/dist/api/http/routes/artifacts.d.ts +11 -0
- package/dist/api/http/routes/artifacts.js +516 -0
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +45 -0
- package/dist/api/http/routes/sandboxes.d.ts +9 -0
- package/dist/api/http/routes/sandboxes.js +150 -0
- package/dist/api/http/routes/sessions.js +9 -13
- package/dist/api/http/routes/tenant.js +36 -2
- package/dist/api/http/routes/vaults.d.ts +12 -3
- package/dist/api/http/routes/vaults.js +122 -54
- package/dist/api/openapi.js +4 -10
- package/dist/artifacts/config.d.ts +23 -0
- package/dist/artifacts/config.js +27 -0
- package/dist/artifacts/export.d.ts +8 -0
- package/dist/artifacts/export.js +205 -0
- package/dist/artifacts/files.d.ts +36 -0
- package/dist/artifacts/files.js +45 -0
- package/dist/artifacts/folders.d.ts +60 -0
- package/dist/artifacts/folders.js +209 -0
- package/dist/artifacts/links.d.ts +28 -0
- package/dist/artifacts/links.js +92 -0
- package/dist/artifacts/media-types.d.ts +13 -0
- package/dist/artifacts/media-types.js +80 -0
- package/dist/artifacts/parts.d.ts +34 -0
- package/dist/artifacts/parts.js +38 -0
- package/dist/artifacts/service.d.ts +74 -0
- package/dist/artifacts/service.js +301 -0
- package/dist/artifacts/workspace.d.ts +60 -0
- package/dist/artifacts/workspace.js +57 -0
- package/dist/artifacts/zip.d.ts +10 -0
- package/dist/artifacts/zip.js +126 -0
- package/dist/blob/fs.d.ts +13 -0
- package/dist/blob/fs.js +194 -0
- package/dist/blob/index.d.ts +9 -0
- package/dist/blob/index.js +9 -0
- package/dist/blob/s3.d.ts +42 -0
- package/dist/blob/s3.js +366 -0
- package/dist/blob/types.d.ts +108 -0
- package/dist/blob/types.js +85 -0
- package/dist/core/sandbox-routes.d.ts +2 -2
- package/dist/core/sandbox-routes.js +4 -3
- package/dist/execution/memory.d.ts +11 -1
- package/dist/execution/memory.js +77 -3
- package/dist/execution/types.d.ts +54 -1
- package/dist/execution/types.js +31 -0
- package/dist/gates/contract.d.ts +14 -13
- package/dist/gates/contract.js +13 -12
- package/dist/gates/egress.d.ts +72 -0
- package/dist/gates/egress.js +319 -0
- package/dist/gates/handler.d.ts +3 -0
- package/dist/gates/handler.js +3 -2
- package/dist/gates/http-client.d.ts +3 -2
- package/dist/gates/http-client.js +17 -6
- package/dist/gates/in-process.d.ts +13 -1
- package/dist/gates/in-process.js +11 -0
- package/dist/gates/inflight.d.ts +23 -4
- package/dist/gates/inflight.js +18 -4
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/model-gate.d.ts +6 -2
- package/dist/gates/tenant-vaults.d.ts +18 -9
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/gates/tool-calls.d.ts +9 -5
- package/dist/gates/tool-calls.js +6 -6
- package/dist/gates/tool-client.d.ts +9 -2
- package/dist/gates/tool-client.js +56 -22
- package/dist/gates/tool-contract.d.ts +12 -7
- package/dist/gates/tool-contract.js +10 -4
- package/dist/gates/tool-gate.d.ts +1 -0
- package/dist/harness/calls.d.ts +52 -0
- package/dist/harness/calls.js +113 -0
- package/dist/harness/executors.d.ts +22 -0
- package/dist/harness/executors.js +96 -0
- package/dist/harness/main.d.ts +6 -0
- package/dist/harness/main.js +103 -0
- package/dist/harness/pod.d.ts +21 -0
- package/dist/harness/pod.js +119 -0
- package/dist/harness/run-tokens.d.ts +18 -0
- package/dist/harness/run-tokens.js +15 -0
- package/dist/harness/service.d.ts +46 -0
- package/dist/harness/service.js +179 -0
- package/dist/harness/ws-client.d.ts +43 -0
- package/dist/harness/ws-client.js +122 -0
- package/dist/harness/ws-port.d.ts +22 -0
- package/dist/harness/ws-port.js +78 -0
- package/dist/harness-api/in-process.d.ts +24 -0
- package/dist/harness-api/in-process.js +119 -0
- package/dist/harness-api/record.d.ts +44 -0
- package/dist/harness-api/record.js +250 -0
- package/dist/harness-api/renew.d.ts +37 -0
- package/dist/harness-api/renew.js +77 -0
- package/dist/harness-api/server.d.ts +104 -0
- package/dist/harness-api/server.js +408 -0
- package/dist/harness-api/start.d.ts +36 -0
- package/dist/harness-api/start.js +118 -0
- package/dist/harness-api/workspace.d.ts +53 -0
- package/dist/harness-api/workspace.js +201 -0
- package/dist/harness-api/ws-server.d.ts +34 -0
- package/dist/harness-api/ws-server.js +227 -0
- package/dist/host/admin-api.d.ts +12 -5
- package/dist/host/admin-api.js +72 -6
- package/dist/host/app.d.ts +0 -1
- package/dist/host/app.js +35 -24
- package/dist/host/config-for.d.ts +4 -0
- package/dist/host/config-for.js +2 -0
- package/dist/host/config.d.ts +0 -6
- package/dist/host/create-host.d.ts +3 -10
- package/dist/host/create-host.js +4 -1
- package/dist/host/gates.d.ts +27 -2
- package/dist/host/gates.js +52 -2
- package/dist/host/http.d.ts +4 -1
- package/dist/host/http.js +6 -1
- package/dist/host/main.js +167 -37
- package/dist/host/stack-config.d.ts +112 -9
- package/dist/host/stack-config.js +222 -20
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +8 -0
- package/dist/mcp/connect.js +2 -1
- package/dist/mcp/pool.d.ts +1 -0
- package/dist/mcp/pool.js +1 -0
- package/dist/mcp/snapshot.d.ts +8 -1
- package/dist/mcp/snapshot.js +28 -0
- package/dist/media.d.ts +5 -24
- package/dist/media.js +5 -0
- package/dist/model/pi-model.d.ts +6 -2
- package/dist/model/pi-model.js +39 -22
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +1 -1
- package/dist/openapi.json +14084 -9426
- package/dist/record/index.d.ts +1 -0
- package/dist/record/index.js +1 -0
- package/dist/record/sandbox.d.ts +25 -0
- package/dist/record/sandbox.js +43 -0
- package/dist/sandbox/capability.d.ts +3 -0
- package/dist/sandbox/capability.js +10 -0
- package/dist/sandbox/egress-token.d.ts +26 -0
- package/dist/sandbox/egress-token.js +8 -0
- package/dist/sandbox/join.d.ts +37 -0
- package/dist/sandbox/join.js +165 -0
- package/dist/sandbox/manager.d.ts +99 -34
- package/dist/sandbox/manager.js +216 -84
- package/dist/sandbox/placement.d.ts +27 -0
- package/dist/sandbox/placement.js +44 -0
- package/dist/sandbox/pods/client.d.ts +90 -0
- package/dist/sandbox/pods/client.js +79 -0
- package/dist/sandbox/pods/lifecycle.d.ts +91 -0
- package/dist/sandbox/pods/lifecycle.js +178 -0
- package/dist/sandbox/pods/name.d.ts +1 -0
- package/dist/sandbox/pods/name.js +28 -0
- package/dist/sandbox/pods/network-gate.d.ts +25 -0
- package/dist/sandbox/pods/network-gate.js +71 -0
- package/dist/sandbox/pods/reconcile.d.ts +11 -0
- package/dist/sandbox/pods/reconcile.js +152 -0
- package/dist/sandbox/pods/spec.d.ts +45 -0
- package/dist/sandbox/pods/spec.js +62 -0
- package/dist/sandbox/records.d.ts +29 -0
- package/dist/sandbox/records.js +79 -0
- package/dist/sandbox/resolve.d.ts +5 -0
- package/dist/sandbox/resolve.js +3 -2
- package/dist/sandbox/session-sandbox.js +7 -5
- package/dist/sandbox/share.d.ts +11 -0
- package/dist/sandbox/share.js +10 -0
- package/dist/sandbox/tenant-config.d.ts +16 -1
- package/dist/sandbox/tenant-config.js +9 -0
- package/dist/sandbox/types.d.ts +29 -1
- package/dist/sandbox/types.js +13 -1
- package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
- package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
- package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
- package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
- package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
- package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
- package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
- package/dist/store/postgres/record-writer.d.ts +6 -1
- package/dist/store/postgres/record-writer.js +20 -2
- package/dist/store/postgres/schema.d.ts +1168 -250
- package/dist/store/postgres/schema.js +174 -28
- package/dist/store/postgres/store.js +339 -103
- package/dist/store/postgres/tenant.d.ts +1 -1
- package/dist/store/postgres/tenant.js +2 -2
- package/dist/store/types.d.ts +163 -32
- package/dist/streams/control.d.ts +2 -5
- package/dist/streams/control.js +5 -8
- package/dist/streams/types.d.ts +8 -8
- package/dist/streams/types.js +1 -1
- package/dist/tenant/advance.js +218 -160
- package/dist/tenant/artifact-tool.d.ts +5 -0
- package/dist/tenant/artifact-tool.js +119 -0
- package/dist/tenant/auth.d.ts +13 -9
- package/dist/tenant/auth.js +25 -32
- package/dist/tenant/commands.js +31 -10
- package/dist/tenant/context.d.ts +83 -26
- package/dist/tenant/delivery-token.js +4 -4
- package/dist/tenant/effects.d.ts +24 -14
- package/dist/tenant/effects.js +10 -398
- package/dist/tenant/ephemeral.d.ts +14 -2
- package/dist/tenant/ephemeral.js +8 -1
- package/dist/tenant/history.d.ts +13 -23
- package/dist/tenant/history.js +14 -62
- package/dist/tenant/host-token.d.ts +92 -0
- package/dist/tenant/host-token.js +179 -0
- package/dist/tenant/identity-file.d.ts +75 -0
- package/dist/tenant/identity-file.js +202 -0
- package/dist/tenant/issuers.d.ts +48 -0
- package/dist/tenant/issuers.js +311 -0
- package/dist/tenant/jwt.js +3 -3
- package/dist/tenant/module.js +1 -0
- package/dist/tenant/operator-keys.d.ts +18 -0
- package/dist/tenant/operator-keys.js +55 -0
- package/dist/tenant/outbound.d.ts +30 -0
- package/dist/tenant/outbound.js +142 -10
- package/dist/tenant/paths.d.ts +2 -1
- package/dist/tenant/paths.js +3 -1
- package/dist/tenant/principals.d.ts +2 -11
- package/dist/tenant/principals.js +10 -30
- package/dist/tenant/reset.d.ts +5 -2
- package/dist/tenant/reset.js +22 -2
- package/dist/tenant/run-grants.d.ts +49 -0
- package/dist/tenant/run-grants.js +62 -0
- package/dist/tenant/run-token.d.ts +70 -0
- package/dist/tenant/run-token.js +138 -0
- package/dist/tenant/runtime.d.ts +42 -0
- package/dist/tenant/runtime.js +134 -33
- package/dist/tenant/sandboxes.d.ts +119 -0
- package/dist/tenant/sandboxes.js +551 -0
- package/dist/tenant/session-streams.d.ts +6 -10
- package/dist/tenant/session-streams.js +9 -27
- package/dist/tenant/session.d.ts +2 -13
- package/dist/tenant/session.js +4 -28
- package/dist/tenant/sessions.d.ts +6 -1
- package/dist/tenant/sessions.js +29 -2
- package/dist/tenant/signing-keys.d.ts +2 -1
- package/dist/tenant/signing-keys.js +2 -2
- package/dist/tenant/status.d.ts +8 -3
- package/dist/tenant/status.js +5 -0
- package/dist/tenant/streams.d.ts +8 -2
- package/dist/tenant/streams.js +25 -6
- package/dist/tenant/sweep.js +37 -4
- package/dist/tenant/types.d.ts +39 -1
- package/dist/tenant/worker.d.ts +11 -5
- package/dist/tenant/worker.js +12 -6
- package/dist/vault/crypto.d.ts +7 -0
- package/dist/vault/crypto.js +16 -0
- package/dist/vault/oauth.d.ts +55 -0
- package/dist/vault/oauth.js +186 -0
- package/dist/vault/service.d.ts +54 -7
- package/dist/vault/service.js +261 -17
- package/dist/vault/sources.d.ts +54 -0
- package/dist/vault/sources.js +185 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -10
- package/dist/adapters/media.d.ts +0 -24
- package/dist/adapters/media.js +0 -111
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- package/dist/tenant/subject-limits.d.ts +0 -16
- package/dist/tenant/subject-limits.js +0 -45
- package/dist/tenant/tokens.d.ts +0 -14
- package/dist/tenant/tokens.js +0 -180
package/dist/tenant/runtime.js
CHANGED
|
@@ -5,8 +5,10 @@ import { tenantModelGate } from "../gates/in-process.js";
|
|
|
5
5
|
import { createKekFile, readVaultKek } from "../vault/kek.js";
|
|
6
6
|
import { SigningKeys } from "./signing-keys.js";
|
|
7
7
|
import { VaultService } from "../vault/service.js";
|
|
8
|
+
import { CredentialSources } from "../vault/sources.js";
|
|
8
9
|
import { McpPool } from "../mcp/pool.js";
|
|
9
10
|
import { SandboxManager } from "../sandbox/manager.js";
|
|
11
|
+
import { storeSandboxRecords } from "../sandbox/records.js";
|
|
10
12
|
import { defaultSandboxBackends } from "../sandbox/select.js";
|
|
11
13
|
import { MemoryExecution } from "../execution/memory.js";
|
|
12
14
|
import { openError } from "./cause.js";
|
|
@@ -20,8 +22,18 @@ import { deliverAction } from "./delivery.js";
|
|
|
20
22
|
import { TenantWorkers, WORKER_ID, } from "./worker.js";
|
|
21
23
|
import { authorize } from "./effects.js";
|
|
22
24
|
import { inProcessToolGate } from "../gates/tool-gate.js";
|
|
25
|
+
import { guardedFetch } from "./outbound.js";
|
|
23
26
|
import { inProcessKeys } from "../keys/keys.js";
|
|
24
27
|
import { tenantApi } from "../api/http/app.js";
|
|
28
|
+
import { createFsBlobStore } from "../blob/index.js";
|
|
29
|
+
import { createHarnessApiServer, } from "../harness-api/server.js";
|
|
30
|
+
import { harnessStatusOf } from "./status.js";
|
|
31
|
+
import { startInProcessHarness, startLoopbackHarness, } from "../harness-api/in-process.js";
|
|
32
|
+
import { localWorkspace, remoteWorkspace, withPodWorkspaces, } from "../harness-api/workspace.js";
|
|
33
|
+
import { hostAuthority } from "../sandbox/join.js";
|
|
34
|
+
import { reconcileSandbox } from "../sandbox/pods/reconcile.js";
|
|
35
|
+
import { sandboxWorkspaceReader } from "../artifacts/workspace.js";
|
|
36
|
+
import { operatorKeys } from "./operator-keys.js";
|
|
25
37
|
/** Default ownership lease of an advance; the heartbeat renews it every third. */
|
|
26
38
|
const DEFAULT_OWNER_LEASE_MS = 30_000;
|
|
27
39
|
function validateConfig(config) {
|
|
@@ -45,16 +57,24 @@ export class TenantRuntime {
|
|
|
45
57
|
worker;
|
|
46
58
|
detach;
|
|
47
59
|
closeGraceMs;
|
|
60
|
+
harness;
|
|
61
|
+
hosts;
|
|
48
62
|
constructor(ctx, envelope,
|
|
49
63
|
/** This Tenant's Worker handlers, as registered with the execution. */
|
|
50
64
|
worker, detach,
|
|
51
65
|
/** How long close waits for running advances: the execution's advance grace period. */
|
|
52
|
-
closeGraceMs) {
|
|
66
|
+
closeGraceMs, harness, hosts) {
|
|
53
67
|
this.ctx = ctx;
|
|
54
68
|
this.envelope = envelope;
|
|
55
69
|
this.worker = worker;
|
|
56
70
|
this.detach = detach;
|
|
57
71
|
this.closeGraceMs = closeGraceMs;
|
|
72
|
+
this.harness = harness;
|
|
73
|
+
this.hosts = hosts;
|
|
74
|
+
}
|
|
75
|
+
/** The Tenant's side of a sandbox pod's join (F7.2). */
|
|
76
|
+
hostAuthority() {
|
|
77
|
+
return this.ctx.closing || this.ctx.closed ? undefined : this.hosts;
|
|
58
78
|
}
|
|
59
79
|
static async open(config, hooks) {
|
|
60
80
|
const envelope = hooks.envelope;
|
|
@@ -69,6 +89,7 @@ export class TenantRuntime {
|
|
|
69
89
|
const store = hooks.store;
|
|
70
90
|
let wired;
|
|
71
91
|
let detach;
|
|
92
|
+
let harness;
|
|
72
93
|
try {
|
|
73
94
|
// With the keys service (F4.2) the key lives in the gateway: this process never reads,
|
|
74
95
|
// creates or holds it, and the gateway reports a missing key on its readiness.
|
|
@@ -101,38 +122,75 @@ export class TenantRuntime {
|
|
|
101
122
|
const vault = new VaultService({
|
|
102
123
|
store: opened,
|
|
103
124
|
kek: ensureKek,
|
|
104
|
-
|
|
125
|
+
// OAuth refresh and connect follow the Host's address policy (F9 C2).
|
|
126
|
+
fetch: config.vaultFetch ?? guardedFetch(config.delivery ?? {}),
|
|
127
|
+
});
|
|
128
|
+
const credentials = new CredentialSources({
|
|
129
|
+
vault,
|
|
130
|
+
...(config.resolver ? { resolver: config.resolver } : {}),
|
|
105
131
|
});
|
|
106
132
|
// `ctx` is assigned below; these callbacks only run once the Tenant is open.
|
|
107
133
|
let ctx;
|
|
108
134
|
const toolGate = hooks.toolGate ?? inProcessToolGate(config.delivery ?? {});
|
|
109
135
|
const signingKeys = new SigningKeys({ tenantId: config.tenantId, kek: ensureKek });
|
|
110
136
|
const keys = hooks.keys ?? inProcessKeys({ store: opened, vault, signingKeys, kek: ensureKek });
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
137
|
+
const harnessMode = hooks.harness ?? "memory";
|
|
138
|
+
const inProcess = harnessMode === "memory" || harnessMode === "json";
|
|
139
|
+
const preference = seededBackend ?? config.sandbox.backend;
|
|
140
|
+
// With a harness in this process, the Tenant's MCP pool and SandboxManager are its
|
|
141
|
+
// executors; otherwise the harness holds both (F6.2).
|
|
142
|
+
const mcp = inProcess
|
|
143
|
+
? new McpPool({
|
|
144
|
+
pluginData: paths.pluginData,
|
|
145
|
+
childEnv: config.childEnv,
|
|
146
|
+
authorize: (sessionId, request) => authorize(ctx, sessionId, request),
|
|
147
|
+
...(toolGate.openMcp ? { openRemote: (server) => toolGate.openMcp(server) } : {}),
|
|
148
|
+
})
|
|
149
|
+
: undefined;
|
|
117
150
|
const ephemeral = config.mode === "ephemeral";
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
const manager = inProcess
|
|
152
|
+
? new SandboxManager({
|
|
153
|
+
scope: config.tenantId,
|
|
154
|
+
records: storeSandboxRecords(opened),
|
|
155
|
+
backends: config.sandbox.backends ??
|
|
156
|
+
defaultSandboxBackends({ root: paths.sandboxes }),
|
|
157
|
+
preference,
|
|
158
|
+
ephemeral,
|
|
159
|
+
emit: async (sessionId, turnId, type, payload) => {
|
|
160
|
+
if (ctx.closed)
|
|
161
|
+
return;
|
|
162
|
+
try {
|
|
163
|
+
await opened.tx((t) => t.event(sessionId, turnId, type, payload));
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
/* the session is gone (reset) or the store closed */
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
: undefined;
|
|
171
|
+
let harnessServer;
|
|
172
|
+
const workspaces = manager
|
|
173
|
+
? localWorkspace(manager)
|
|
174
|
+
: remoteWorkspace({
|
|
175
|
+
get server() {
|
|
176
|
+
return harnessServer;
|
|
177
|
+
},
|
|
178
|
+
store: opened,
|
|
179
|
+
logger: config.logger,
|
|
180
|
+
preference,
|
|
181
|
+
});
|
|
182
|
+
// A pod sandbox's workspace is in its pod, served by its engine (F7.2).
|
|
183
|
+
const sandbox = hooks.pods
|
|
184
|
+
? withPodWorkspaces(workspaces, {
|
|
185
|
+
get server() {
|
|
186
|
+
return harnessServer;
|
|
187
|
+
},
|
|
188
|
+
store: opened,
|
|
189
|
+
logger: config.logger,
|
|
190
|
+
preference,
|
|
191
|
+
isPod: async (sandboxId) => (await opened.tx((t) => t.sandboxResource(sandboxId)))?.kind === "pod",
|
|
192
|
+
})
|
|
193
|
+
: workspaces;
|
|
136
194
|
const useVaultModel = config.model.kind === "vault";
|
|
137
195
|
let modelProvider;
|
|
138
196
|
if (hooks.modelProvider)
|
|
@@ -143,6 +201,7 @@ export class TenantRuntime {
|
|
|
143
201
|
modelProvider = toolFixtureModel();
|
|
144
202
|
else
|
|
145
203
|
modelProvider = scriptedModel();
|
|
204
|
+
const blobs = hooks.blobs ?? createFsBlobStore({ root: paths.blobs });
|
|
146
205
|
// With the gates service the loop never reads a model credential: only a Runtime
|
|
147
206
|
// without one (embedding, the ephemeral Runtime, tests) reads it here.
|
|
148
207
|
const modelGate = hooks.modelGate ??
|
|
@@ -151,40 +210,48 @@ export class TenantRuntime {
|
|
|
151
210
|
kek: ensureKek,
|
|
152
211
|
root: paths.home,
|
|
153
212
|
logger: config.logger,
|
|
213
|
+
blobs,
|
|
154
214
|
...(config.modelCall ? { settings: config.modelCall } : {}),
|
|
155
215
|
});
|
|
156
216
|
const sessionStreams = createSessionStreams();
|
|
217
|
+
const workers = hooks.execution?.workers ?? new TenantWorkers();
|
|
157
218
|
const local = hooks.execution
|
|
158
219
|
? undefined
|
|
159
220
|
: new MemoryExecution({
|
|
160
221
|
sweepIntervalMs: config.sweepIntervalMs ?? Math.min(config.leaseMs ?? 30_000, 5000),
|
|
222
|
+
// Close's `detach` step waits no longer for running work than for its advances.
|
|
223
|
+
stopGraceMs: workers.graceMs,
|
|
161
224
|
onError: (error) => config.logger.error("tenant execution failed", {
|
|
162
225
|
message: error instanceof Error ? error.message : String(error),
|
|
163
226
|
}),
|
|
164
227
|
});
|
|
165
|
-
const
|
|
166
|
-
execution: local,
|
|
167
|
-
workers: new TenantWorkers(),
|
|
168
|
-
};
|
|
228
|
+
const execution = hooks.execution?.execution ?? local;
|
|
169
229
|
const sweepHooks = new Set();
|
|
170
230
|
ctx = {
|
|
171
231
|
config,
|
|
172
232
|
envelope,
|
|
173
233
|
store: opened,
|
|
174
234
|
vault,
|
|
175
|
-
|
|
235
|
+
credentials,
|
|
236
|
+
...(mcp ? { mcp } : {}),
|
|
176
237
|
sandbox,
|
|
177
238
|
flowLimits,
|
|
178
239
|
modelProvider,
|
|
179
240
|
useVaultModel,
|
|
180
241
|
modelGate,
|
|
181
242
|
toolGate,
|
|
243
|
+
get harness() {
|
|
244
|
+
return harnessServer;
|
|
245
|
+
},
|
|
182
246
|
closing: false,
|
|
183
247
|
closed: false,
|
|
184
248
|
work: createWorkState(),
|
|
185
249
|
sessionStreams,
|
|
186
250
|
signingKeys,
|
|
187
251
|
keys,
|
|
252
|
+
blobs,
|
|
253
|
+
workspaces: sandboxWorkspaceReader(sandbox),
|
|
254
|
+
...(hooks.runGrants ? { runGrants: hooks.runGrants } : {}),
|
|
188
255
|
workerId: hooks.workerId ?? WORKER_ID,
|
|
189
256
|
ownerLeaseMs: config.ownerLeaseMs ?? DEFAULT_OWNER_LEASE_MS,
|
|
190
257
|
wake: async (sessionId, wake) => {
|
|
@@ -198,6 +265,12 @@ export class TenantRuntime {
|
|
|
198
265
|
return;
|
|
199
266
|
await execution.deliver(config.tenantId, actionId);
|
|
200
267
|
},
|
|
268
|
+
...(hooks.pods ? { pods: hooks.pods } : {}),
|
|
269
|
+
sandboxSignal: async (sandboxId, signal) => {
|
|
270
|
+
if (ctx.closing || ctx.closed || !execution.sandbox)
|
|
271
|
+
return;
|
|
272
|
+
await execution.sandbox(config.tenantId, sandboxId, signal);
|
|
273
|
+
},
|
|
201
274
|
...(hooks.execution?.stuckInvocations
|
|
202
275
|
? {
|
|
203
276
|
stuckInvocations: () => hooks.execution.stuckInvocations(config.tenantId),
|
|
@@ -209,6 +282,16 @@ export class TenantRuntime {
|
|
|
209
282
|
return () => sweepHooks.delete(hook);
|
|
210
283
|
},
|
|
211
284
|
};
|
|
285
|
+
harnessServer = createHarnessApiServer(ctx, { sandboxPreference: preference });
|
|
286
|
+
if (mcp && manager)
|
|
287
|
+
harness = await startInProcessHarness(ctx, { mcp, sandbox: manager }, {
|
|
288
|
+
json: harnessMode === "json",
|
|
289
|
+
...(hooks.harnessTap ? { tap: hooks.harnessTap } : {}),
|
|
290
|
+
});
|
|
291
|
+
else if (harnessMode === "ws")
|
|
292
|
+
harness = await startLoopbackHarness(ctx, {
|
|
293
|
+
...(config.sandbox.backends ? { sandboxBackends: config.sandbox.backends } : {}),
|
|
294
|
+
});
|
|
212
295
|
wired = await wireStreams(ctx, {
|
|
213
296
|
store: opened,
|
|
214
297
|
streams: hooks.streams ?? new MemoryStreams(),
|
|
@@ -223,6 +306,7 @@ export class TenantRuntime {
|
|
|
223
306
|
advance: (sessionId, signal) => advance(ctx, sessionId, signal),
|
|
224
307
|
deliver: (actionId, signal) => deliverAction(ctx, actionId, signal),
|
|
225
308
|
sweep: () => sweep(ctx),
|
|
309
|
+
sandbox: (sandboxId, trigger) => reconcileSandbox(ctx, sandboxId, trigger),
|
|
226
310
|
};
|
|
227
311
|
const unregister = workers.register(config.tenantId, worker);
|
|
228
312
|
detach = async () => {
|
|
@@ -232,10 +316,11 @@ export class TenantRuntime {
|
|
|
232
316
|
if (local)
|
|
233
317
|
await local.start(workers.handlers);
|
|
234
318
|
await execution.armSweep(config.tenantId);
|
|
235
|
-
return new TenantRuntime(ctx, envelope, worker, detach, workers.graceMs);
|
|
319
|
+
return new TenantRuntime(ctx, envelope, worker, detach, workers.graceMs, harness, hooks.pods ? hostAuthority(ctx) : undefined);
|
|
236
320
|
}
|
|
237
321
|
catch (error) {
|
|
238
322
|
await detach?.().catch(() => undefined);
|
|
323
|
+
await harness?.stop().catch(() => undefined);
|
|
239
324
|
await wired?.close().catch(() => undefined);
|
|
240
325
|
await store.close().catch(() => undefined);
|
|
241
326
|
throw error;
|
|
@@ -256,6 +341,7 @@ export class TenantRuntime {
|
|
|
256
341
|
inFlightDeliveries: [...this.ctx.work.deliveries.values()].reduce((n, set) => n + set.size, 0),
|
|
257
342
|
pendingActions: counts.pendingActions,
|
|
258
343
|
uncertainEffects: counts.uncertainEffects,
|
|
344
|
+
harness: harnessStatusOf(this.ctx),
|
|
259
345
|
};
|
|
260
346
|
}
|
|
261
347
|
drain(activeWork, timeoutMs = 30_000) {
|
|
@@ -279,6 +365,13 @@ export class TenantRuntime {
|
|
|
279
365
|
onSweep(hook) {
|
|
280
366
|
return this.ctx.onSweep(hook);
|
|
281
367
|
}
|
|
368
|
+
attachHarness(channel, peer) {
|
|
369
|
+
return this.ctx.harness.attach(channel, peer);
|
|
370
|
+
}
|
|
371
|
+
/** The Tenant's operator keys (F9 I1), over its store. */
|
|
372
|
+
operatorKeys() {
|
|
373
|
+
return operatorKeys(this.ctx.store);
|
|
374
|
+
}
|
|
282
375
|
async close() {
|
|
283
376
|
const ctx = this.ctx;
|
|
284
377
|
ctx.closing = true;
|
|
@@ -289,10 +382,18 @@ export class TenantRuntime {
|
|
|
289
382
|
// error is rethrown at the end.
|
|
290
383
|
const steps = [
|
|
291
384
|
["detach", () => this.detach()],
|
|
292
|
-
["mcp", () => ctx.mcp
|
|
385
|
+
["mcp", () => ctx.mcp?.close()],
|
|
293
386
|
["session streams", () => endAllStreams(ctx.sessionStreams)],
|
|
294
387
|
// Bounded: an advance that ignores its abort is abandoned; its lease lapses (§11.4).
|
|
295
388
|
["idle", () => waitForIdle(ctx, Math.max(0, idleBy - Date.now()))],
|
|
389
|
+
[
|
|
390
|
+
"harness",
|
|
391
|
+
async () => {
|
|
392
|
+
ctx.harness.close();
|
|
393
|
+
// Advances still running were abandoned at "idle": their runs are left behind too.
|
|
394
|
+
await this.harness?.stop(Math.max(0, idleBy - Date.now()));
|
|
395
|
+
},
|
|
396
|
+
],
|
|
296
397
|
["sandbox", () => ctx.sandbox.close()],
|
|
297
398
|
[
|
|
298
399
|
"streams",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandboxes as a resource (blueprint D39, F7.1; Host feature `sandboxes`): `PUT`, `GET` and
|
|
3
|
+
* `DELETE /v1/sandboxes/{id}`, the list by label, sessions attaching with `sandbox: { id }`, and
|
|
4
|
+
* the checks every turn start makes on the sandbox its session is attached to.
|
|
5
|
+
*
|
|
6
|
+
* - **No scope.** The Runtime decides nothing about who shares a sandbox. It stores the
|
|
7
|
+
* sandbox's id, kind, spec and labels, and the session body names the sandbox it is attached
|
|
8
|
+
* to (`Session.sandboxId`); deleting a session (a sessions reset) only detaches it.
|
|
9
|
+
* - **Access.** An application key reaches every sandbox, and so does an application key acting
|
|
10
|
+
* for a subject (`Nylorun-Subject`), which needs `sandboxes:write` to change one. A token
|
|
11
|
+
* caller (a trusted issuer's token) reaches only the ids its grants match, and changes them
|
|
12
|
+
* only with `sandboxes:write`; any other id is the 404 of a missing one.
|
|
13
|
+
* - **Serial turns.** One turn at a time per sandbox: a session whose sandbox another attached
|
|
14
|
+
* session's turn holds is refused (`409 sandbox_busy`), as the session sandbox route is
|
|
15
|
+
* refused while its session's turn runs. Commands within the workspace keep queueing in the
|
|
16
|
+
* Sandbox Manager.
|
|
17
|
+
* - **Kinds.** `virtual`: its workspace is the virtual backend's, keyed by the sandbox id
|
|
18
|
+
* (`SandboxManager.sandboxKeyOf`) and created on the first tool call. `pod` (F7.2, Host feature
|
|
19
|
+
* `sandbox-pods`): an agent-sandbox pod on the Tenant's cluster, created at once, whose engine
|
|
20
|
+
* runs the turns of the sessions attached to it. Its lifecycle (`sandbox/pods/lifecycle.ts`)
|
|
21
|
+
* runs in the `Sandbox` object: this module records what is wanted (`desired`, `rev`) and asks
|
|
22
|
+
* for a reconcile after the commit (`ctx.sandboxSignal`). Without sandbox pods kind `pod` is
|
|
23
|
+
* `409 sandbox_unavailable`. `POST .../stop` suspends a pod (the volume is kept; the next turn
|
|
24
|
+
* starts it), `POST .../reset` gives it a new pod on a new volume. An expired pod refuses
|
|
25
|
+
* turns (`sandbox_expired`) until a `PUT` sets a longer `lifecycle.ttl`; a lost one
|
|
26
|
+
* (`sandbox_lost`) until a reset. Deleting a pod sandbox keeps its row (`desired: deleted`,
|
|
27
|
+
* invisible here) until its Sandbox is gone.
|
|
28
|
+
* - **Lifecycle events** go to the sandbox's own stream in the record (`Tx.sandboxEvent`,
|
|
29
|
+
* `record/sandbox.ts`); the session's log records `sandbox.attached` too.
|
|
30
|
+
*/
|
|
31
|
+
import { type PutSandboxRequest, type SandboxView } from "@nylorun/core/contracts";
|
|
32
|
+
import type { SandboxResource, Tx } from "../store/types.js";
|
|
33
|
+
import type { AuthScope, Session, TenantContext } from "./context.js";
|
|
34
|
+
/** The sandbox grants that limit `scope`, or undefined when it reaches every sandbox. */
|
|
35
|
+
export declare function sandboxGrantsOf(scope: AuthScope): readonly string[] | undefined;
|
|
36
|
+
/** A sandbox resource, unless it is gone or being deleted. */
|
|
37
|
+
export declare function liveSandbox(t: Tx, id: string, options?: {
|
|
38
|
+
lock?: boolean;
|
|
39
|
+
}): Promise<SandboxResource | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* `PUT /v1/sandboxes/{id}`: creates the sandbox (within the Tenant's limit on their number), or
|
|
42
|
+
* finds the one with this id, so "get or create" is one call. The spec is fixed once it exists;
|
|
43
|
+
* labels, when sent, replace its labels. On an existing pod sandbox it also starts a stopped or
|
|
44
|
+
* failed pod again, and a new `lifecycle.ttl` sets a new expiry (reviving an expired one).
|
|
45
|
+
*/
|
|
46
|
+
export declare function putSandbox(ctx: TenantContext, id: string, body: PutSandboxRequest, scope: AuthScope): Promise<SandboxView>;
|
|
47
|
+
/** `GET /v1/sandboxes/{id}`. */
|
|
48
|
+
export declare function getSandbox(ctx: TenantContext, id: string, scope: AuthScope): Promise<SandboxView>;
|
|
49
|
+
/** `GET /v1/sandboxes?label=k=v`: every sandbox with all the labels, that the caller reaches. */
|
|
50
|
+
export declare function listSandboxes(ctx: TenantContext, labels: Record<string, string>, scope: AuthScope): Promise<{
|
|
51
|
+
sandboxes: SandboxView[];
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* `DELETE /v1/sandboxes/{id}`: deletes the sandbox and its workspace. Refused while a session
|
|
55
|
+
* attached to it has an active turn. Sessions stay attached by id: their next turn is refused
|
|
56
|
+
* (`sandbox_unavailable`) unless a sandbox with this id exists again. A pod sandbox's row stays
|
|
57
|
+
* (`desired: deleted`) until its Sandbox is gone.
|
|
58
|
+
*/
|
|
59
|
+
export declare function deleteSandbox(ctx: TenantContext, id: string, scope: AuthScope): Promise<{
|
|
60
|
+
id: string;
|
|
61
|
+
deleted: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* `POST /v1/sandboxes/{id}/stop`: suspends a pod sandbox. Its volume is kept, and the next turn
|
|
65
|
+
* of a session attached to it (or a `PUT`) starts it again. Refused during a turn.
|
|
66
|
+
*/
|
|
67
|
+
export declare function stopSandbox(ctx: TenantContext, id: string, scope: AuthScope): Promise<SandboxView>;
|
|
68
|
+
/**
|
|
69
|
+
* `POST /v1/sandboxes/{id}/reset`: a new pod on a new, empty volume (the next volume
|
|
70
|
+
* generation), with a new join token; the old Sandbox and volume are deleted. Leaves `lost` and
|
|
71
|
+
* `failed`; an expired sandbox stays expired. Refused during a turn, and while an earlier
|
|
72
|
+
* reset's Sandbox is still being deleted.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resetSandbox(ctx: TenantContext, id: string, scope: AuthScope): Promise<SandboxView>;
|
|
75
|
+
/** `GET /v1/sandboxes/{id}/events`: the sandbox's lifecycle stream. */
|
|
76
|
+
export declare function sandboxEventsOf(ctx: TenantContext, id: string, scope: AuthScope, fromSeq: number | undefined): Promise<{
|
|
77
|
+
events: {
|
|
78
|
+
schema: "nylorun.sandbox-event/1";
|
|
79
|
+
eventId: string;
|
|
80
|
+
tenantId: string;
|
|
81
|
+
sandboxId: string;
|
|
82
|
+
seq: number;
|
|
83
|
+
time: string;
|
|
84
|
+
type: "sandbox.attached" | "sandbox.created" | "sandbox.deleted" | "sandbox.detached" | "sandbox.expired" | "sandbox.failed" | "sandbox.lost" | "sandbox.relaunched" | "sandbox.reset" | "sandbox.running" | "sandbox.suspended";
|
|
85
|
+
payload: Record<string, unknown>;
|
|
86
|
+
}[];
|
|
87
|
+
}>;
|
|
88
|
+
/**
|
|
89
|
+
* Attaches a new session to sandbox `id` (`PutSessionRequest.sandbox = { id }`): the sandbox
|
|
90
|
+
* must exist and be reachable with `grants`. Returns its spec, which the session pins. Runs in
|
|
91
|
+
* the session's transaction, after its session row lock.
|
|
92
|
+
*/
|
|
93
|
+
export declare function attachSandbox(t: Tx, id: string, grants: readonly string[] | undefined): Promise<SandboxResource>;
|
|
94
|
+
/** Records the attachment on the session's log and on the sandbox's stream. */
|
|
95
|
+
export declare function recordAttachment(t: Tx, sessionId: string, sandboxId: string): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Checks a turn start on a session attached to a sandbox, in the command's transaction after
|
|
98
|
+
* the session row lock: the sandbox still exists, a token caller's grants reach it, and
|
|
99
|
+
* no other session attached to it has a turn running. Holds the sandbox row's lock until the
|
|
100
|
+
* transaction ends, so two turn starts on one sandbox are decided one after the other.
|
|
101
|
+
*
|
|
102
|
+
* A pod sandbox must also be usable: not lost (`sandbox_lost`) or expired (`sandbox_expired`),
|
|
103
|
+
* on a Runtime with sandbox pods (`sandbox_unavailable`). A stopped or failed pod is asked to
|
|
104
|
+
* run again, after the commit.
|
|
105
|
+
*/
|
|
106
|
+
export declare function checkSandboxTurn(t: Tx, session: Session, scope: AuthScope, ctx?: TenantContext): Promise<void>;
|
|
107
|
+
/** A sessions reset detaches every session from its sandbox: each sandbox's stream says so. */
|
|
108
|
+
export declare function detachAllSessions(t: Tx): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Marks the end of a turn's segment on pod sandbox `id`: idle counts from now, and the idle
|
|
111
|
+
* timer is armed (D34). Best effort.
|
|
112
|
+
*/
|
|
113
|
+
export declare function touchPodSandbox(ctx: TenantContext, id: string): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Re-arms the idle timer of every running pod sandbox after the Tenant's settings change, so a
|
|
116
|
+
* shorter `limits.idle` applies to pods already idle (D36: the settings are read whenever a
|
|
117
|
+
* timer is set). Best effort.
|
|
118
|
+
*/
|
|
119
|
+
export declare function rearmPodTimers(ctx: TenantContext): Promise<void>;
|