@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,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Runtime's OpenAPI 3.2 documents, generated from the routes as declared for serving:
|
|
3
|
+
*
|
|
4
|
+
* - the Tenant API (`tenantDocument`): every Tenant route, with `/health`, `/ready` and
|
|
5
|
+
* `/openapi.json`. Served at `GET /openapi.json`, packed as `@nylorun/runtime/openapi.json`
|
|
6
|
+
* and attached to each release.
|
|
7
|
+
* - the Admin API (`adminDocument`): served at `GET /v1/admin/openapi.json` behind the admin
|
|
8
|
+
* key, packed as `@nylorun/runtime/admin-openapi.json`.
|
|
9
|
+
*
|
|
10
|
+
* Event streams are `text/event-stream` with an `itemSchema` (OpenAPI 3.2). Nothing here reads
|
|
11
|
+
* the environment: the same Runtime makes the same document.
|
|
12
|
+
*/
|
|
13
|
+
import { OpenAPIRegistry, OpenApiGeneratorV32, } from "@asteasolutions/zod-to-openapi";
|
|
14
|
+
import { HOST_PROTOCOL } from "@nylorun/core/compatibility";
|
|
15
|
+
import { HealthResponseSchema, ReadyResponseSchema } from "@nylorun/core/contracts";
|
|
16
|
+
import { createAdminApi } from "../host/admin-api.js";
|
|
17
|
+
import { RUNTIME_VERSION } from "../version.js";
|
|
18
|
+
import { tenantApi } from "./http/app.js";
|
|
19
|
+
const PROTOCOL = {
|
|
20
|
+
min: HOST_PROTOCOL.min,
|
|
21
|
+
max: HOST_PROTOCOL.max,
|
|
22
|
+
features: [...HOST_PROTOCOL.features],
|
|
23
|
+
};
|
|
24
|
+
const bearer = (description, bearerFormat) => ({
|
|
25
|
+
type: "http",
|
|
26
|
+
scheme: "bearer",
|
|
27
|
+
...(bearerFormat ? { bearerFormat } : {}),
|
|
28
|
+
description,
|
|
29
|
+
});
|
|
30
|
+
/** `/health`, `/ready` and `/openapi.json`: what every listener answers without a key. */
|
|
31
|
+
function hostRoutes() {
|
|
32
|
+
const registry = new OpenAPIRegistry();
|
|
33
|
+
const route = (config) => registry.registerPath(config);
|
|
34
|
+
route({
|
|
35
|
+
method: "get",
|
|
36
|
+
path: "/health",
|
|
37
|
+
tags: ["Host"],
|
|
38
|
+
summary: "Check the Runtime",
|
|
39
|
+
description: "The protocol range and features a client checks first. Refuses an `Origin`.",
|
|
40
|
+
responses: {
|
|
41
|
+
200: {
|
|
42
|
+
description: "The Runtime is up",
|
|
43
|
+
content: { "application/json": { schema: HealthResponseSchema.meta({ id: "Health" }) } },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
route({
|
|
48
|
+
method: "get",
|
|
49
|
+
path: "/ready",
|
|
50
|
+
tags: ["Host"],
|
|
51
|
+
summary: "Check the Runtime is ready",
|
|
52
|
+
description: "The listeners, the Tenant (open), Postgres, Restate and S2.",
|
|
53
|
+
responses: {
|
|
54
|
+
200: {
|
|
55
|
+
description: "Ready",
|
|
56
|
+
content: { "application/json": { schema: ReadyResponseSchema.meta({ id: "Ready" }) } },
|
|
57
|
+
},
|
|
58
|
+
503: { description: "Not ready: `checks` says what is not" },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
route({
|
|
62
|
+
method: "get",
|
|
63
|
+
path: "/openapi.json",
|
|
64
|
+
tags: ["Host"],
|
|
65
|
+
summary: "Get this document",
|
|
66
|
+
responses: { 200: { description: "The Tenant API's OpenAPI document" } },
|
|
67
|
+
});
|
|
68
|
+
return registry;
|
|
69
|
+
}
|
|
70
|
+
function tenantSchemes(registry) {
|
|
71
|
+
registry.registerComponent("securitySchemes", "applicationKey", bearer("An application key of the Tenant, or a key derived from the admin key. With `Nylorun-Subject` and `Nylorun-Scopes`, it acts for that person, narrowed to those scopes."));
|
|
72
|
+
registry.registerComponent("securitySchemes", "subjectToken", bearer("A subject token (`POST /v1/tokens`): one person and one role of the access policy, for at most 15 minutes.", "JWT"));
|
|
73
|
+
registry.registerComponent("securitySchemes", "deliveryToken", bearer("The delivery token the Runtime signs each Action delivery with (`Nylorun-Signature`), sent back by the Action endpoint. It reaches only that Action's heartbeat, result and sandbox routes, while that delivery is current, for at most 15 minutes.", "JWT"));
|
|
74
|
+
registry.registerComponent("securitySchemes", "publishableKey", {
|
|
75
|
+
type: "apiKey",
|
|
76
|
+
in: "header",
|
|
77
|
+
name: "Nylorun-Key",
|
|
78
|
+
description: "A publishable key, sent by a browser page on one of its origins. Public by design: it grants the access policy's anonymous scopes.",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
let tenant;
|
|
82
|
+
let admin;
|
|
83
|
+
/** The Tenant API's document. */
|
|
84
|
+
export function tenantDocument() {
|
|
85
|
+
if (tenant)
|
|
86
|
+
return tenant;
|
|
87
|
+
const host = hostRoutes();
|
|
88
|
+
tenantSchemes(host);
|
|
89
|
+
tenant = new OpenApiGeneratorV32([
|
|
90
|
+
...host.definitions,
|
|
91
|
+
...tenantApi().openAPIRegistry.definitions,
|
|
92
|
+
]).generateDocument({
|
|
93
|
+
openapi: "3.2.0",
|
|
94
|
+
info: {
|
|
95
|
+
title: "Nylorun Runtime: Tenant API",
|
|
96
|
+
version: RUNTIME_VERSION,
|
|
97
|
+
description: "Agents, sessions and their events, vaults, access and the Tenant's settings. A Runtime serves one Tenant: its URL is the Tenant's, and no request names it. Every request sends `Nylorun-Protocol`. Who may call each operation is its `security` and its `x-nylorun-credentials`, `x-nylorun-scopes` (the subject scopes that reach it) and `x-nylorun-browser` fields.",
|
|
98
|
+
"x-nylorun-protocol": PROTOCOL,
|
|
99
|
+
},
|
|
100
|
+
servers: [
|
|
101
|
+
{
|
|
102
|
+
url: "{origin}",
|
|
103
|
+
description: "A Runtime: the local stack (`nylorun start`), or where yours runs",
|
|
104
|
+
variables: { origin: { default: "http://localhost:8787" } },
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
return tenant;
|
|
109
|
+
}
|
|
110
|
+
/** The Admin API's document. */
|
|
111
|
+
export function adminDocument() {
|
|
112
|
+
if (admin)
|
|
113
|
+
return admin;
|
|
114
|
+
// Only the routes' declarations are read: nothing is served from this app.
|
|
115
|
+
const api = createAdminApi({
|
|
116
|
+
status: () => Promise.reject(new Error("Not served")),
|
|
117
|
+
shutdown: () => { },
|
|
118
|
+
});
|
|
119
|
+
api.openAPIRegistry.registerComponent("securitySchemes", "adminKey", bearer("The Host's admin key (`host-credentials.json`). Never a Tenant bearer."));
|
|
120
|
+
admin = new OpenApiGeneratorV32(api.openAPIRegistry.definitions).generateDocument({
|
|
121
|
+
openapi: "3.2.0",
|
|
122
|
+
info: {
|
|
123
|
+
title: "Nylorun Runtime: Admin API",
|
|
124
|
+
version: RUNTIME_VERSION,
|
|
125
|
+
description: "The Host's status, its Tenant's state and shutdown, with the admin key. Served on the operator listener when the Host has one; the public listener answers these routes with the opaque 404.",
|
|
126
|
+
"x-nylorun-protocol": PROTOCOL,
|
|
127
|
+
},
|
|
128
|
+
servers: [
|
|
129
|
+
{
|
|
130
|
+
url: "{origin}",
|
|
131
|
+
description: "The operator listener: the local stack's, or where yours runs",
|
|
132
|
+
variables: { origin: { default: "http://localhost:8788" } },
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
return admin;
|
|
137
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the Runtime declares an HTTP route: once, for both serving and the OpenAPI document.
|
|
3
|
+
*
|
|
4
|
+
* `app.openapi(route, handler)` from `@hono/zod-openapi` also validates the path, query and
|
|
5
|
+
* body before the handler runs, answering failures its own way. The Runtime's handlers parse
|
|
6
|
+
* where they always have (after the lookups and checks a rejection must come after), so their
|
|
7
|
+
* rejections keep their order and messages; `serveRoute` registers the route and serves it
|
|
8
|
+
* with no validators of its own.
|
|
9
|
+
*/
|
|
10
|
+
import type { OpenAPIHono, RouteConfig } from "@hono/zod-openapi";
|
|
11
|
+
import type { Env, Handler } from "hono";
|
|
12
|
+
export declare function serveRoute<E extends Env>(app: OpenAPIHono<E>, route: RouteConfig, handler: Handler<E>): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application keys are 256-bit CSPRNG values and the wire schema requires at least 16
|
|
3
|
+
* characters, so an unsalted digest is adequate to keep them out of the Session Store at rest.
|
|
4
|
+
* This is not a password KDF and must not be used for one.
|
|
5
|
+
*/
|
|
6
|
+
export declare const hashToken: (token: string) => string;
|
|
7
|
+
/** Mint a 32-byte hex application key for tests and CLI bootstrap. */
|
|
8
|
+
export declare function mintBearerToken(): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Application keys are 256-bit CSPRNG values and the wire schema requires at least 16
|
|
4
|
+
* characters, so an unsalted digest is adequate to keep them out of the Session Store at rest.
|
|
5
|
+
* This is not a password KDF and must not be used for one.
|
|
6
|
+
*/
|
|
7
|
+
export const hashToken = (token) => createHash("sha256").update(token, "utf8").digest("hex");
|
|
8
|
+
/** Mint a 32-byte hex application key for tests and CLI bootstrap. */
|
|
9
|
+
export function mintBearerToken() {
|
|
10
|
+
return randomBytes(32).toString("hex");
|
|
11
|
+
}
|
package/dist/core/flow-host.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ActionOutcome } from "@nylorun/core/contracts";
|
|
2
2
|
import type { JsonValue, WorkflowManifest } from "@nylorun/core/define";
|
|
3
3
|
import type { HostEffect } from "@nylorun/harness/run";
|
|
4
4
|
import type { Wake } from "../execution/types.js";
|
|
@@ -73,7 +73,7 @@ export type CascadeCancelPlan = {
|
|
|
73
73
|
/** Linked agent sessions to cancel, deepest path first. */
|
|
74
74
|
readonly agentSessionIds: string[];
|
|
75
75
|
readonly pendingActionIds: string[];
|
|
76
|
-
readonly
|
|
76
|
+
readonly deliveringActionIds: string[];
|
|
77
77
|
};
|
|
78
78
|
/** Wakes a session after commit (`DurableExecution.wake` through the Tenant context). */
|
|
79
79
|
type Schedule = (sessionId: string, wake: Wake) => void | Promise<void>;
|
|
@@ -83,7 +83,7 @@ export type FlowEffect = EffectDoc & {
|
|
|
83
83
|
outcome?: ActionOutcome;
|
|
84
84
|
error?: string;
|
|
85
85
|
};
|
|
86
|
-
/** Active agent turns + pending/
|
|
86
|
+
/** Active agent turns + pending/delivering actions for one workflow turn. */
|
|
87
87
|
export declare function countActiveFlowWork(t: Tx, workflowSessionId: string, turnId: string): Promise<number>;
|
|
88
88
|
/** The `commands` document key of a session command's idempotency key (`tenant/commands.ts`). */
|
|
89
89
|
export declare function commandKey(sessionId: string, idempotencyKey: string): string;
|
|
@@ -132,20 +132,11 @@ export declare function reconcilePendingAgentEffect(input: {
|
|
|
132
132
|
readonly effectId: string;
|
|
133
133
|
readonly schedule: Schedule;
|
|
134
134
|
}): Promise<boolean>;
|
|
135
|
-
/** Claimed `fn`/`verify` Actions, for the re-offer after a Tenant opens. */
|
|
136
|
-
export declare function claimedFnVerifyActions(t: Tx): Promise<Action[]>;
|
|
137
|
-
/**
|
|
138
|
-
* Re-offer one claimed `fn`/`verify` Action (SD-P7 / WF-C9): they are pure or repeat-safe, so
|
|
139
|
-
* a claim left by an executor of an earlier process is offered again at once rather than
|
|
140
|
-
* after its lease. The next claim bumps the generation, which fences a late result. Locks
|
|
141
|
-
* only the Action's session. Returns true when it re-offered.
|
|
142
|
-
*/
|
|
143
|
-
export declare function reofferFnVerifyClaim(t: Tx, actionId: string): Promise<boolean>;
|
|
144
135
|
/** Path depth for deepest-first cancel ordering. */
|
|
145
136
|
export declare function pathDepth(path: string): number;
|
|
146
137
|
/**
|
|
147
138
|
* Plan a cancel cascade for a workflow session: linked agents deepest first,
|
|
148
|
-
* then pending → cancelled and
|
|
139
|
+
* then pending → cancelled and delivering → uncertain actions (SD-P11 / WF-R53).
|
|
149
140
|
*/
|
|
150
141
|
export declare function planCancelCascade(input: {
|
|
151
142
|
readonly t: Tx;
|
|
@@ -155,7 +146,7 @@ export declare function planCancelCascade(input: {
|
|
|
155
146
|
/**
|
|
156
147
|
* Cancel sibling work under a Parallel/Map parent when one branch fails (PAR-R6).
|
|
157
148
|
* Agent turns listed in `agentSessionIds` are returned for the caller to cancel;
|
|
158
|
-
* pending actions → cancelled,
|
|
149
|
+
* pending actions → cancelled, delivering tool actions → uncertain.
|
|
159
150
|
* When `cancelEffectIds` is provided, those effects are marked cancelled and their
|
|
160
151
|
* paths are included in the sibling path set.
|
|
161
152
|
*/
|
|
@@ -169,7 +160,8 @@ export declare function cancelSiblingWork(input: {
|
|
|
169
160
|
readonly cancelEffectIds?: readonly string[];
|
|
170
161
|
}): Promise<CancelSiblingResult>;
|
|
171
162
|
/**
|
|
172
|
-
* Apply cancel fencing to workflow-session actions (pending→cancelled,
|
|
163
|
+
* Apply cancel fencing to workflow-session actions (pending→cancelled,
|
|
164
|
+
* delivering→uncertain).
|
|
173
165
|
*/
|
|
174
166
|
export declare function fenceWorkflowActions(input: {
|
|
175
167
|
readonly t: Tx;
|
package/dist/core/flow-host.js
CHANGED
|
@@ -23,8 +23,6 @@
|
|
|
23
23
|
* - `wakeLinkedWorkflow({ t, agentSessionId, turnId, output?, failed?, cancelled?, error?, schedule }): Promise<void>`
|
|
24
24
|
* - `pendingAgentEffects(t): Promise<FlowEffect[]>`
|
|
25
25
|
* - `reconcilePendingAgentEffect({ t, effectId, schedule }): Promise<boolean>`
|
|
26
|
-
* - `claimedFnVerifyActions(t): Promise<Action[]>`
|
|
27
|
-
* - `reofferFnVerifyClaim(t, actionId): Promise<boolean>`
|
|
28
26
|
* - `planCancelCascade({ t, workflowSessionId, turnId }): Promise<CascadeCancelPlan>`
|
|
29
27
|
* - `cancelSiblingWork({ t, workflowSessionId, turnId, siblingPaths?, cancelEffectIds? }): Promise<CancelSiblingResult>`
|
|
30
28
|
* - `fenceWorkflowActions({ t, workflowSessionId, turnId }): Promise<{ cancelled; uncertain }>`
|
|
@@ -91,11 +89,12 @@ export function isFlowEffect(request) {
|
|
|
91
89
|
request.kind === "verify" ||
|
|
92
90
|
isFlowToolEffect(request));
|
|
93
91
|
}
|
|
94
|
-
|
|
92
|
+
/** Actions still to be settled: waiting to be delivered, or being delivered. */
|
|
93
|
+
const OPEN_ACTION = ["pending", "delivering"];
|
|
95
94
|
function scheduleAfterCommit(t, schedule, id, wake) {
|
|
96
95
|
t.afterCommit(() => schedule(id, wake));
|
|
97
96
|
}
|
|
98
|
-
/** Active agent turns + pending/
|
|
97
|
+
/** Active agent turns + pending/delivering actions for one workflow turn. */
|
|
99
98
|
export async function countActiveFlowWork(t, workflowSessionId, turnId) {
|
|
100
99
|
const effects = await t.effectsForTurn(workflowSessionId, turnId);
|
|
101
100
|
const openActions = new Set((await t.actionsForSession(workflowSessionId, {
|
|
@@ -254,34 +253,6 @@ export async function reconcilePendingAgentEffect(input) {
|
|
|
254
253
|
});
|
|
255
254
|
return true;
|
|
256
255
|
}
|
|
257
|
-
/** Claimed `fn`/`verify` Actions, for the re-offer after a Tenant opens. */
|
|
258
|
-
export async function claimedFnVerifyActions(t) {
|
|
259
|
-
return t.actionsWithStatus(["claimed"], { kinds: ["fn", "verify"] });
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Re-offer one claimed `fn`/`verify` Action (SD-P7 / WF-C9): they are pure or repeat-safe, so
|
|
263
|
-
* a claim left by an executor of an earlier process is offered again at once rather than
|
|
264
|
-
* after its lease. The next claim bumps the generation, which fences a late result. Locks
|
|
265
|
-
* only the Action's session. Returns true when it re-offered.
|
|
266
|
-
*/
|
|
267
|
-
export async function reofferFnVerifyClaim(t, actionId) {
|
|
268
|
-
const found = await t.get("actions", actionId);
|
|
269
|
-
if (!found)
|
|
270
|
-
return false;
|
|
271
|
-
await t.lockSession(found.sessionId);
|
|
272
|
-
// Read again under the session lock.
|
|
273
|
-
const action = await t.get("actions", actionId);
|
|
274
|
-
if (!action ||
|
|
275
|
-
action.status !== "claimed" ||
|
|
276
|
-
(action.kind !== "fn" && action.kind !== "verify"))
|
|
277
|
-
return false;
|
|
278
|
-
action.status = "pending";
|
|
279
|
-
action.claimId = null;
|
|
280
|
-
action.leaseExpiresAt = null;
|
|
281
|
-
await t.put("actions", action.actionId, action);
|
|
282
|
-
t.signalWork();
|
|
283
|
-
return true;
|
|
284
|
-
}
|
|
285
256
|
/** Path depth for deepest-first cancel ordering. */
|
|
286
257
|
export function pathDepth(path) {
|
|
287
258
|
if (!path)
|
|
@@ -290,14 +261,14 @@ export function pathDepth(path) {
|
|
|
290
261
|
}
|
|
291
262
|
/**
|
|
292
263
|
* Plan a cancel cascade for a workflow session: linked agents deepest first,
|
|
293
|
-
* then pending → cancelled and
|
|
264
|
+
* then pending → cancelled and delivering → uncertain actions (SD-P11 / WF-R53).
|
|
294
265
|
*/
|
|
295
266
|
export async function planCancelCascade(input) {
|
|
296
267
|
const { t } = input;
|
|
297
268
|
const agentEntries = (await t.linkedSessions(input.workflowSessionId)).map((linked) => ({ sessionId: linked.agentSessionId, path: linked.link.path }));
|
|
298
269
|
agentEntries.sort((a, b) => pathDepth(b.path) - pathDepth(a.path));
|
|
299
270
|
const pendingActionIds = [];
|
|
300
|
-
const
|
|
271
|
+
const deliveringActionIds = [];
|
|
301
272
|
const actions = await t.actionsForSession(input.workflowSessionId, {
|
|
302
273
|
...(input.turnId !== null ? { turnId: input.turnId } : {}),
|
|
303
274
|
statuses: OPEN_ACTION,
|
|
@@ -305,19 +276,20 @@ export async function planCancelCascade(input) {
|
|
|
305
276
|
for (const action of actions) {
|
|
306
277
|
if (action.status === "pending")
|
|
307
278
|
pendingActionIds.push(action.actionId);
|
|
308
|
-
|
|
309
|
-
|
|
279
|
+
// Being delivered: the code may already be running.
|
|
280
|
+
else
|
|
281
|
+
deliveringActionIds.push(action.actionId);
|
|
310
282
|
}
|
|
311
283
|
return {
|
|
312
284
|
agentSessionIds: agentEntries.map((e) => e.sessionId),
|
|
313
285
|
pendingActionIds,
|
|
314
|
-
|
|
286
|
+
deliveringActionIds,
|
|
315
287
|
};
|
|
316
288
|
}
|
|
317
289
|
/**
|
|
318
290
|
* Cancel sibling work under a Parallel/Map parent when one branch fails (PAR-R6).
|
|
319
291
|
* Agent turns listed in `agentSessionIds` are returned for the caller to cancel;
|
|
320
|
-
* pending actions → cancelled,
|
|
292
|
+
* pending actions → cancelled, delivering tool actions → uncertain.
|
|
321
293
|
* When `cancelEffectIds` is provided, those effects are marked cancelled and their
|
|
322
294
|
* paths are included in the sibling path set.
|
|
323
295
|
*/
|
|
@@ -363,7 +335,8 @@ export async function cancelSiblingWork(input) {
|
|
|
363
335
|
for (const action of actions) {
|
|
364
336
|
if (!matchesSibling(action.path))
|
|
365
337
|
continue;
|
|
366
|
-
|
|
338
|
+
// Delivered work may already have had an external effect.
|
|
339
|
+
const next = action.status === "pending" ? "cancelled" : "uncertain";
|
|
367
340
|
action.status = next;
|
|
368
341
|
await t.put("actions", action.actionId, action);
|
|
369
342
|
const effect = await t.get("effects", action.actionId);
|
|
@@ -379,7 +352,8 @@ export async function cancelSiblingWork(input) {
|
|
|
379
352
|
return { cancelledActions, uncertainActions, agentSessionIds };
|
|
380
353
|
}
|
|
381
354
|
/**
|
|
382
|
-
* Apply cancel fencing to workflow-session actions (pending→cancelled,
|
|
355
|
+
* Apply cancel fencing to workflow-session actions (pending→cancelled,
|
|
356
|
+
* delivering→uncertain).
|
|
383
357
|
*/
|
|
384
358
|
export async function fenceWorkflowActions(input) {
|
|
385
359
|
const { t } = input;
|
|
@@ -392,7 +366,8 @@ export async function fenceWorkflowActions(input) {
|
|
|
392
366
|
statuses: OPEN_ACTION,
|
|
393
367
|
});
|
|
394
368
|
for (const action of actions) {
|
|
395
|
-
|
|
369
|
+
// Delivered work may already have had an external effect.
|
|
370
|
+
const next = action.status === "pending" ? "cancelled" : "uncertain";
|
|
396
371
|
action.status = next;
|
|
397
372
|
await t.put("actions", action.actionId, action);
|
|
398
373
|
const effect = await t.get("effects", action.actionId);
|
|
@@ -27,18 +27,23 @@ export type SandboxRouteDeps = {
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function handleSessionSandboxTool(deps: SandboxRouteDeps, sessionId: string, toolName: string, body: unknown, signal: AbortSignal): Promise<SandboxToolOutcome>;
|
|
29
29
|
/**
|
|
30
|
-
* `POST /v1/actions/:id/sandbox/:tool` —
|
|
31
|
-
*
|
|
30
|
+
* `POST /v1/actions/:id/sandbox/:tool` — the Action endpoint, authorized by the delivery token
|
|
31
|
+
* of the Action's current delivery (`delivery.generation`). The body is the tool's input.
|
|
32
|
+
*/
|
|
33
|
+
export declare function handleActionSandboxTool(deps: SandboxRouteDeps, actionId: string, toolName: string, body: unknown, signal: AbortSignal, delivery: {
|
|
34
|
+
generation: number;
|
|
35
|
+
}): Promise<SandboxToolOutcome>;
|
|
36
|
+
/**
|
|
37
|
+
* Validate PutSession.sandbox attach: same owner, and a sandbox to share. A session whose
|
|
38
|
+
* definition declares a sandbox must declare the owner's spec; one whose definition declares
|
|
39
|
+
* none inherits it (`inherit`).
|
|
32
40
|
*/
|
|
33
|
-
export declare function handleActionSandboxTool(deps: SandboxRouteDeps, actionId: string, toolName: string, body: unknown, signal: AbortSignal): Promise<SandboxToolOutcome>;
|
|
34
|
-
/** Validate PutSession.sandbox attach: same owner, identical specs. */
|
|
35
41
|
export declare function validateSandboxAttach(body: {
|
|
36
42
|
ownerUserId: string;
|
|
37
|
-
sandbox?:
|
|
38
|
-
session: string;
|
|
39
|
-
};
|
|
43
|
+
sandbox?: unknown;
|
|
40
44
|
}, newManifest: SessionSandboxRef["manifest"], lookup: (id: string) => SessionSandboxRef | undefined,
|
|
41
45
|
/** Acting for a subject: another owner's session is the 404 of a missing one. */
|
|
42
46
|
options?: {
|
|
43
47
|
opaque?: boolean;
|
|
48
|
+
inherit?: boolean;
|
|
44
49
|
}): string | undefined;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Wired from TenantRuntime; keeps route bodies out of the hot runtime file.
|
|
4
4
|
*/
|
|
5
5
|
import { isSandboxToolName, } from "@nylorun/core/define";
|
|
6
|
-
import { capabilityForSandbox, owningSandboxSessionId, sandboxSpecOf, sandboxSpecsEqual, } from "../sandbox/share.js";
|
|
6
|
+
import { capabilityForSandbox, owningSandboxSessionId, sandboxSpecOf, sandboxSpecsEqual, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
7
7
|
export class SandboxRouteError extends Error {
|
|
8
8
|
status;
|
|
9
9
|
constructor(status, message) {
|
|
@@ -15,7 +15,7 @@ export class SandboxRouteError extends Error {
|
|
|
15
15
|
function resolveSpec(session, lookup) {
|
|
16
16
|
const ownerId = owningSandboxSessionId(session, lookup);
|
|
17
17
|
const owner = ownerId === session.id ? session : lookup(ownerId) ?? session;
|
|
18
|
-
const spec =
|
|
18
|
+
const spec = sessionSandboxSpec(owner) ?? sessionSandboxSpec(session);
|
|
19
19
|
if (!spec)
|
|
20
20
|
throw new SandboxRouteError(404, "Session has no sandbox");
|
|
21
21
|
return spec;
|
|
@@ -31,7 +31,7 @@ function toolInput(body) {
|
|
|
31
31
|
if (typeof body !== "object" || Array.isArray(body))
|
|
32
32
|
throw new SandboxRouteError(400, "Sandbox tool body must be an object");
|
|
33
33
|
const record = body;
|
|
34
|
-
const {
|
|
34
|
+
const { requestId: _r, ...input } = record;
|
|
35
35
|
return input;
|
|
36
36
|
}
|
|
37
37
|
async function runTool(deps, { session, lookup }, tool, input, signal) {
|
|
@@ -56,39 +56,36 @@ export async function handleSessionSandboxTool(deps, sessionId, toolName, body,
|
|
|
56
56
|
return runTool(deps, read, tool, toolInput(body), signal);
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
* `POST /v1/actions/:id/sandbox/:tool` —
|
|
60
|
-
*
|
|
59
|
+
* `POST /v1/actions/:id/sandbox/:tool` — the Action endpoint, authorized by the delivery token
|
|
60
|
+
* of the Action's current delivery (`delivery.generation`). The body is the tool's input.
|
|
61
61
|
*/
|
|
62
|
-
export async function handleActionSandboxTool(deps, actionId, toolName, body, signal) {
|
|
62
|
+
export async function handleActionSandboxTool(deps, actionId, toolName, body, signal, delivery) {
|
|
63
63
|
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
64
64
|
throw new SandboxRouteError(400, "Sandbox tool body must be an object");
|
|
65
|
-
const record = body;
|
|
66
|
-
const claimId = record.claimId;
|
|
67
|
-
const generation = record.generation;
|
|
68
|
-
if (typeof claimId !== "string" || claimId.length === 0)
|
|
69
|
-
throw new SandboxRouteError(400, "claimId is required");
|
|
70
|
-
if (typeof generation !== "number" || !Number.isInteger(generation) || generation < 1)
|
|
71
|
-
throw new SandboxRouteError(400, "generation must be a positive integer");
|
|
72
65
|
const action = await deps.getAction(actionId);
|
|
73
66
|
if (!action)
|
|
74
67
|
throw new SandboxRouteError(404, "Action not found");
|
|
75
|
-
if (action.status !== "
|
|
76
|
-
action.
|
|
77
|
-
action.
|
|
78
|
-
|
|
79
|
-
Date.parse(action.leaseExpiresAt) <= Date.now())
|
|
80
|
-
throw new SandboxRouteError(409, "Stale or expired claim");
|
|
68
|
+
if (action.status !== "delivering" ||
|
|
69
|
+
action.generation !== delivery.generation ||
|
|
70
|
+
Date.parse(action.deadlineAt ?? "") <= Date.now())
|
|
71
|
+
throw new SandboxRouteError(409, "The delivery was cancelled, lost or delivered again");
|
|
81
72
|
const read = await deps.session(action.sessionId);
|
|
82
73
|
if (read.session.activeTurnId !== action.turnId)
|
|
83
74
|
throw new SandboxRouteError(409, "Action unavailable");
|
|
84
|
-
|
|
85
|
-
return runTool(deps, read, tool, toolInput(body), signal);
|
|
75
|
+
return runTool(deps, read, parseTool(toolName), toolInput(body), signal);
|
|
86
76
|
}
|
|
87
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Validate PutSession.sandbox attach: same owner, and a sandbox to share. A session whose
|
|
79
|
+
* definition declares a sandbox must declare the owner's spec; one whose definition declares
|
|
80
|
+
* none inherits it (`inherit`).
|
|
81
|
+
*/
|
|
88
82
|
export function validateSandboxAttach(body, newManifest, lookup,
|
|
89
83
|
/** Acting for a subject: another owner's session is the 404 of a missing one. */
|
|
90
84
|
options = {}) {
|
|
91
|
-
const
|
|
85
|
+
const request = body.sandbox;
|
|
86
|
+
const share = typeof request === "object" && request !== null && "session" in request
|
|
87
|
+
? String(request.session)
|
|
88
|
+
: undefined;
|
|
92
89
|
if (!share)
|
|
93
90
|
return undefined;
|
|
94
91
|
const owner = lookup(share);
|
|
@@ -96,10 +93,12 @@ options = {}) {
|
|
|
96
93
|
throw new SandboxRouteError(404, "Sandbox session not found");
|
|
97
94
|
if (owner.ownerUserId !== body.ownerUserId)
|
|
98
95
|
throw new SandboxRouteError(403, "Sandbox session must belong to the same owner");
|
|
99
|
-
const ownerSpec =
|
|
100
|
-
const newSpec = sandboxSpecOf(newManifest);
|
|
96
|
+
const ownerSpec = sessionSandboxSpec(owner);
|
|
101
97
|
if (!ownerSpec)
|
|
102
98
|
throw new SandboxRouteError(400, "Target session has no sandbox to share");
|
|
99
|
+
if (options.inherit)
|
|
100
|
+
return owningSandboxSessionId(owner, lookup);
|
|
101
|
+
const newSpec = sandboxSpecOf(newManifest);
|
|
103
102
|
if (!newSpec)
|
|
104
103
|
throw new SandboxRouteError(400, "Session must declare a sandbox identical to the shared session");
|
|
105
104
|
if (!sandboxSpecsEqual(ownerSpec, newSpec))
|
|
@@ -2,9 +2,9 @@ import { type DurableExecution, type Wake, type WorkerHandlers } from "./types.j
|
|
|
2
2
|
export interface MemoryExecutionOptions {
|
|
3
3
|
/** Delay between sweep passes of one Tenant. Default 5000. */
|
|
4
4
|
sweepIntervalMs?: number;
|
|
5
|
-
/** First retry delay after `advance` throws; doubles per attempt. Default 50. */
|
|
5
|
+
/** First retry delay after `advance` or `deliver` throws; doubles per attempt. Default 50. */
|
|
6
6
|
retryDelayMs?: number;
|
|
7
|
-
/** Attempts before giving up on a throwing
|
|
7
|
+
/** Attempts before giving up on a throwing handler and reporting it. Default 5. */
|
|
8
8
|
maxAttempts?: number;
|
|
9
9
|
/** How long a dedupe key merges repeated wakes. Default 24 hours. */
|
|
10
10
|
dedupeRetentionMs?: number;
|
|
@@ -32,21 +32,24 @@ export declare class MemoryExecution implements DurableExecution {
|
|
|
32
32
|
private readonly onError;
|
|
33
33
|
constructor(options?: MemoryExecutionOptions);
|
|
34
34
|
wake(tenantId: string, sessionId: string, wake: Wake): Promise<void>;
|
|
35
|
+
deliver(tenantId: string, actionId: string): Promise<void>;
|
|
35
36
|
timer(tenantId: string, key: string, at: Date): Promise<void>;
|
|
36
37
|
armSweep(tenantId: string): Promise<void>;
|
|
37
38
|
disarmSweep(tenantId: string): Promise<void>;
|
|
38
39
|
start(handlers: WorkerHandlers): Promise<void>;
|
|
39
40
|
stop(): Promise<void>;
|
|
40
41
|
/**
|
|
41
|
-
* Resolves once no advance is running or queued and no busy or
|
|
42
|
-
* re-
|
|
42
|
+
* Resolves once no advance or delivery is running or queued and no busy or
|
|
43
|
+
* retry re-run is pending. Timers and sweeps are not waited for. Test helper.
|
|
43
44
|
*/
|
|
44
45
|
idle(): Promise<void>;
|
|
45
46
|
private enqueue;
|
|
46
47
|
private drain;
|
|
47
48
|
/** Drops dedupe keys older than the retention window. */
|
|
48
49
|
private forgetDedupeKeys;
|
|
49
|
-
private
|
|
50
|
+
private runOnce;
|
|
51
|
+
/** Runs the key's handler; resolves to a delay when the key must run again. */
|
|
52
|
+
private call;
|
|
50
53
|
private armTimer;
|
|
51
54
|
private scheduleSweep;
|
|
52
55
|
private later;
|
package/dist/execution/memory.js
CHANGED
|
@@ -41,7 +41,10 @@ export class MemoryExecution {
|
|
|
41
41
|
return;
|
|
42
42
|
this.seen.set(dedupe, now);
|
|
43
43
|
}
|
|
44
|
-
this.enqueue(tenantId, sessionId);
|
|
44
|
+
this.enqueue("advance", tenantId, sessionId);
|
|
45
|
+
}
|
|
46
|
+
async deliver(tenantId, actionId) {
|
|
47
|
+
this.enqueue("deliver", tenantId, actionId);
|
|
45
48
|
}
|
|
46
49
|
async timer(tenantId, key, at) {
|
|
47
50
|
const id = `${tenantId}\u0000${key}`;
|
|
@@ -96,8 +99,8 @@ export class MemoryExecution {
|
|
|
96
99
|
await Promise.allSettled(this.inflight);
|
|
97
100
|
}
|
|
98
101
|
/**
|
|
99
|
-
* Resolves once no advance is running or queued and no busy or
|
|
100
|
-
* re-
|
|
102
|
+
* Resolves once no advance or delivery is running or queued and no busy or
|
|
103
|
+
* retry re-run is pending. Timers and sweeps are not waited for. Test helper.
|
|
101
104
|
*/
|
|
102
105
|
async idle() {
|
|
103
106
|
while (this.delayed.size > 0 ||
|
|
@@ -105,13 +108,14 @@ export class MemoryExecution {
|
|
|
105
108
|
[...this.keys.values()].some((s) => s.running || (s.queued && this.handlers)))
|
|
106
109
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
107
110
|
}
|
|
108
|
-
enqueue(tenantId,
|
|
109
|
-
const key =
|
|
111
|
+
enqueue(kind, tenantId, id) {
|
|
112
|
+
const key = stateKey(kind, tenantId, id);
|
|
110
113
|
let state = this.keys.get(key);
|
|
111
114
|
if (!state)
|
|
112
115
|
this.keys.set(key, (state = {
|
|
116
|
+
kind,
|
|
113
117
|
tenantId,
|
|
114
|
-
|
|
118
|
+
id,
|
|
115
119
|
running: false,
|
|
116
120
|
queued: false,
|
|
117
121
|
attempts: 0,
|
|
@@ -126,14 +130,14 @@ export class MemoryExecution {
|
|
|
126
130
|
try {
|
|
127
131
|
while (state.queued && this.handlers) {
|
|
128
132
|
state.queued = false;
|
|
129
|
-
await this.
|
|
133
|
+
await this.runOnce(state, this.handlers);
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
finally {
|
|
133
137
|
state.running = false;
|
|
134
138
|
// Idle keys hold nothing (a retry keeps its attempt count); forget them so
|
|
135
139
|
// long-lived processes stay small.
|
|
136
|
-
const key =
|
|
140
|
+
const key = stateKey(state.kind, state.tenantId, state.id);
|
|
137
141
|
if (!state.queued && state.attempts === 0 && this.keys.get(key) === state)
|
|
138
142
|
this.keys.delete(key);
|
|
139
143
|
}
|
|
@@ -147,14 +151,15 @@ export class MemoryExecution {
|
|
|
147
151
|
this.seen.delete(dedupe);
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
|
-
async
|
|
154
|
+
async runOnce(state, handlers) {
|
|
151
155
|
const controller = new AbortController();
|
|
152
156
|
state.controller = controller;
|
|
157
|
+
const again = () => this.enqueue(state.kind, state.tenantId, state.id);
|
|
153
158
|
try {
|
|
154
|
-
const
|
|
159
|
+
const retryAfterMs = await this.call(state, handlers, controller.signal);
|
|
155
160
|
state.attempts = 0;
|
|
156
|
-
if (
|
|
157
|
-
this.later(
|
|
161
|
+
if (retryAfterMs !== undefined)
|
|
162
|
+
this.later(retryAfterMs, again);
|
|
158
163
|
}
|
|
159
164
|
catch (error) {
|
|
160
165
|
state.attempts += 1;
|
|
@@ -163,12 +168,23 @@ export class MemoryExecution {
|
|
|
163
168
|
this.onError(error);
|
|
164
169
|
}
|
|
165
170
|
else
|
|
166
|
-
this.later(this.retryDelayMs * 2 ** (state.attempts - 1),
|
|
171
|
+
this.later(this.retryDelayMs * 2 ** (state.attempts - 1), again);
|
|
167
172
|
}
|
|
168
173
|
finally {
|
|
169
174
|
state.controller = undefined;
|
|
170
175
|
}
|
|
171
176
|
}
|
|
177
|
+
/** Runs the key's handler; resolves to a delay when the key must run again. */
|
|
178
|
+
async call(state, handlers, signal) {
|
|
179
|
+
if (state.kind === "advance") {
|
|
180
|
+
const result = await handlers.advance(state.tenantId, state.id, signal);
|
|
181
|
+
return result.status === "busy" ? result.retryAfterMs : undefined;
|
|
182
|
+
}
|
|
183
|
+
if (!handlers.deliver)
|
|
184
|
+
throw new Error("WorkerHandlers.deliver is required for deliveries");
|
|
185
|
+
const result = await handlers.deliver(state.tenantId, state.id, signal);
|
|
186
|
+
return result.status === "retry" ? result.retryAfterMs : undefined;
|
|
187
|
+
}
|
|
172
188
|
armTimer(id, timer) {
|
|
173
189
|
timer.handle = setTimeout(() => {
|
|
174
190
|
if (this.timers.get(id) !== timer)
|
|
@@ -215,3 +231,7 @@ export class MemoryExecution {
|
|
|
215
231
|
void promise.finally(() => this.inflight.delete(promise));
|
|
216
232
|
}
|
|
217
233
|
}
|
|
234
|
+
/** Advances and deliveries are queued apart: a session id and an Action id may be equal. */
|
|
235
|
+
function stateKey(kind, tenantId, id) {
|
|
236
|
+
return `${kind}\u0000${sessionKey(tenantId, id)}`;
|
|
237
|
+
}
|