@nylorun/runtime 0.27.0-beta → 0.29.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 +67 -0
- package/README.md +1 -1
- package/dist/api/a2a/card.d.ts +4 -3
- package/dist/api/a2a/card.js +3 -3
- package/dist/api/a2a/routes.js +18 -7
- package/dist/api/ag-ui/routes.js +4 -7
- package/dist/api/components.d.ts +5854 -2718
- package/dist/api/components.js +39 -30
- package/dist/api/http/app.js +9 -4
- package/dist/api/http/body.d.ts +2 -0
- package/dist/api/http/body.js +20 -0
- package/dist/api/http/define.d.ts +4 -0
- package/dist/api/http/define.js +20 -9
- package/dist/api/http/routes/agent-manifests.d.ts +10 -0
- package/dist/api/http/routes/agent-manifests.js +135 -0
- package/dist/api/http/routes/agents.d.ts +11 -0
- package/dist/api/http/routes/agents.js +418 -0
- package/dist/api/http/routes/artifacts.js +2 -2
- package/dist/api/http/routes/coverage.d.ts +4 -4
- package/dist/api/http/routes/coverage.js +12 -6
- package/dist/api/http/routes/files.js +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.d.ts +1 -1
- package/dist/api/http/routes/me.js +1 -2
- package/dist/api/http/routes/memories.d.ts +9 -0
- package/dist/api/http/routes/memories.js +152 -0
- package/dist/api/http/routes/reads.d.ts +1 -137
- package/dist/api/http/routes/reads.js +31 -12
- package/dist/api/http/routes/sessions.js +21 -50
- package/dist/api/http/routes/tenant.js +13 -10
- package/dist/api/http/routes/triggers.d.ts +10 -0
- package/dist/api/http/routes/triggers.js +183 -0
- package/dist/api/openapi.js +69 -47
- package/dist/artifacts/disk.d.ts +10 -10
- package/dist/artifacts/disk.js +27 -27
- package/dist/artifacts/quota.d.ts +1 -1
- package/dist/artifacts/quota.js +2 -2
- package/dist/artifacts/service.js +2 -2
- package/dist/core/flow-host.d.ts +0 -1
- package/dist/gates/egress-credentials.js +1 -1
- package/dist/gates/egress.d.ts +5 -5
- package/dist/gates/egress.js +6 -6
- package/dist/gates/http-tool.js +2 -2
- package/dist/gates/meter.js +22 -5
- package/dist/gates/policy.d.ts +8 -3
- package/dist/gates/policy.js +7 -2
- package/dist/gates/tenant-vaults.d.ts +1 -1
- package/dist/harness/pod.d.ts +2 -0
- package/dist/harness/setup.js +17 -6
- package/dist/harness-api/record.js +11 -0
- package/dist/harness-api/server.js +4 -0
- package/dist/harness-api/start.d.ts +11 -7
- package/dist/harness-api/start.js +49 -5
- package/dist/harness-api/workspace.js +2 -2
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/http.d.ts +2 -2
- package/dist/host/http.js +5 -2
- package/dist/host/main.js +1 -0
- package/dist/keys/keys.d.ts +15 -4
- package/dist/keys/keys.js +16 -0
- package/dist/keys/webhooks.d.ts +25 -0
- package/dist/keys/webhooks.js +62 -0
- package/dist/management-openapi.json +313 -133
- package/dist/mcp/connect.d.ts +2 -2
- package/dist/mcp/connect.js +3 -3
- package/dist/mcp/preview.js +7 -7
- package/dist/memory/service.d.ts +97 -0
- package/dist/memory/service.js +335 -0
- package/dist/memory/tool.d.ts +36 -0
- package/dist/memory/tool.js +84 -0
- package/dist/model/pi-model.js +7 -0
- package/dist/openapi.json +13120 -9034
- package/dist/reads/types.d.ts +3 -3
- package/dist/record/agent.d.ts +27 -0
- package/dist/record/agent.js +46 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/sandbox/join.js +5 -5
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +3 -3
- package/dist/sandbox/pods/lifecycle.d.ts +6 -6
- package/dist/sandbox/pods/lifecycle.js +6 -6
- package/dist/sandbox/pods/reconcile.js +10 -2
- package/dist/sandbox/resolve.d.ts +3 -3
- package/dist/sandbox/resolve.js +7 -7
- package/dist/sandbox/setup.d.ts +3 -1
- package/dist/sandbox/setup.js +14 -4
- package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
- package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
- package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
- package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
- package/dist/store/postgres/drizzle/0037_model_usage_cost_split.sql +4 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
- package/dist/store/postgres/reads.js +44 -87
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/schema.d.ts +1408 -123
- package/dist/store/postgres/schema.js +194 -38
- package/dist/store/postgres/store.js +329 -79
- package/dist/store/postgres/tenant.js +1 -2
- package/dist/store/types.d.ts +155 -50
- package/dist/store/types.js +0 -1
- package/dist/tenant/advance.js +20 -8
- package/dist/tenant/agent-manifests.d.ts +72 -0
- package/dist/tenant/agent-manifests.js +359 -0
- package/dist/tenant/agent-versions.d.ts +126 -0
- package/dist/tenant/agent-versions.js +389 -0
- package/dist/tenant/agents.d.ts +230 -0
- package/dist/tenant/{environments.js → agents.js} +464 -320
- package/dist/tenant/auth.d.ts +1 -1
- package/dist/tenant/auth.js +3 -3
- package/dist/tenant/commands.d.ts +63 -2
- package/dist/tenant/commands.js +240 -83
- package/dist/tenant/context.d.ts +46 -19
- package/dist/tenant/context.js +6 -2
- package/dist/tenant/effects.js +7 -16
- package/dist/tenant/identity-file.d.ts +6 -6
- package/dist/tenant/identity-file.js +12 -10
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/limits.d.ts +0 -3
- package/dist/tenant/limits.js +0 -3
- package/dist/tenant/memories.d.ts +38 -0
- package/dist/tenant/memories.js +119 -0
- package/dist/tenant/reset.js +2 -2
- package/dist/tenant/run-grants.d.ts +6 -0
- package/dist/tenant/run-grants.js +4 -0
- package/dist/tenant/runtime.d.ts +1 -1
- package/dist/tenant/runtime.js +8 -0
- package/dist/tenant/sessions.d.ts +28 -45
- package/dist/tenant/sessions.js +85 -208
- package/dist/tenant/status.js +6 -7
- package/dist/tenant/sweep.js +12 -3
- package/dist/tenant/transcript.d.ts +16 -16
- package/dist/tenant/triggers.d.ts +143 -0
- package/dist/tenant/triggers.js +715 -0
- package/dist/tenant/turn-timing.d.ts +11 -1
- package/dist/tenant/turn-timing.js +3 -1
- package/dist/tenant/types.d.ts +5 -0
- package/dist/tenant/worker.d.ts +2 -0
- package/dist/tenant/worker.js +5 -0
- package/dist/traces/map.d.ts +1 -1
- package/dist/traces/map.js +7 -1
- package/dist/tuning.d.ts +7 -0
- package/dist/tuning.js +19 -0
- package/dist/vault/service.d.ts +13 -8
- package/dist/vault/service.js +61 -17
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -12
- package/dist/api/http/routes/environment-manifests.d.ts +0 -9
- package/dist/api/http/routes/environment-manifests.js +0 -118
- package/dist/api/http/routes/environments.d.ts +0 -11
- package/dist/api/http/routes/environments.js +0 -332
- package/dist/record/environment.d.ts +0 -27
- package/dist/record/environment.js +0 -46
- package/dist/tenant/environment-manifests.d.ts +0 -57
- package/dist/tenant/environment-manifests.js +0 -208
- package/dist/tenant/environments.d.ts +0 -172
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { ENVIRONMENT_EVENT_TYPES, ForkEnvironmentRequestSchema, isEnvironmentId, PutEnvironmentRequestSchema, RestoreEnvironmentRequestSchema, } from "@nylorun/core/contracts";
|
|
3
|
-
import { deleteEnvironmentVersion, environmentVersions, restoreEnvironment, snapshotEnvironment, } from "../../../artifacts/disk.js";
|
|
4
|
-
import { DeleteEnvironmentResponse, DeletedResponse, EnvironmentPage, EnvironmentRestore, EnvironmentSnapshot, EnvironmentVersions, ForkEnvironmentRequest, ForkEnvironmentResponse, RestoreEnvironmentRequest, EnvironmentSecretView, EnvironmentView, ListEnvironmentEventsResponse, ListEnvironmentSecretsResponse, ListEnvironmentsResponse, PutEnvironmentRequest, PutEnvironmentSecretRequest, PutEnvironmentSecretValueRequest, } from "../../components.js";
|
|
5
|
-
import { fail } from "../../../tenant/http.js";
|
|
6
|
-
import { actorOf, assertReaches, deleteEnvironment, environmentEventsOf, forkEnvironment, getEnvironment, listEnvironments, putEnvironment, resetEnvironment, secretRequestFor, stopEnvironment, } from "../../../tenant/environments.js";
|
|
7
|
-
import { readJson } from "../body.js";
|
|
8
|
-
import { tenantRoute } from "../define.js";
|
|
9
|
-
import { environmentPage, pageQuery } from "./reads.js";
|
|
10
|
-
import { jsonResponse } from "../respond.js";
|
|
11
|
-
/** Reading an Environment: any caller that may use sessions, or change Environments. */
|
|
12
|
-
const READ = {
|
|
13
|
-
credentials: ["application", "subject", "token"],
|
|
14
|
-
scopes: ["sessions:own", "environments:write"],
|
|
15
|
-
};
|
|
16
|
-
/** Creating, changing or deleting one, and writing its secrets. */
|
|
17
|
-
const WRITE = {
|
|
18
|
-
credentials: ["application", "subject", "token"],
|
|
19
|
-
scopes: ["environments:write"],
|
|
20
|
-
};
|
|
21
|
-
const json = (schema, description) => ({
|
|
22
|
-
description,
|
|
23
|
-
content: { "application/json": { schema } },
|
|
24
|
-
});
|
|
25
|
-
const environmentId = z.object({
|
|
26
|
-
environmentId: z
|
|
27
|
-
.string()
|
|
28
|
-
.meta({ description: "The Environment id, percent-encoded: `users%2Falice%2Fweb` for `users/alice/web`" }),
|
|
29
|
-
});
|
|
30
|
-
const secretPath = environmentId.extend({
|
|
31
|
-
name: z.string().meta({
|
|
32
|
-
description: "The secret's name: the variable for `environment_secret` and `environment_variable`, a label for `bearer` and `headers`",
|
|
33
|
-
}),
|
|
34
|
-
});
|
|
35
|
-
/** The path's Environment id, or a 400 naming the rule. */
|
|
36
|
-
function idOf(value) {
|
|
37
|
-
if (!isEnvironmentId(value))
|
|
38
|
-
return fail(400, "An Environment id is up to 200 characters: /-separated segments of letters, digits, '.', '_' and '-'");
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
41
|
-
/** `label=k=v`, repeated: every one must match. */
|
|
42
|
-
function labelsOf(values) {
|
|
43
|
-
const labels = {};
|
|
44
|
-
for (const value of values ?? []) {
|
|
45
|
-
const at = value.indexOf("=");
|
|
46
|
-
if (at <= 0)
|
|
47
|
-
fail(400, `label must be key=value, not ${value}`);
|
|
48
|
-
labels[value.slice(0, at)] = value.slice(at + 1);
|
|
49
|
-
}
|
|
50
|
-
return labels;
|
|
51
|
-
}
|
|
52
|
-
export function environmentRoutes(api) {
|
|
53
|
-
tenantRoute(api, READ, {
|
|
54
|
-
method: "get",
|
|
55
|
-
path: "/v1/environments",
|
|
56
|
-
tags: ["Environments"],
|
|
57
|
-
summary: "List Environments",
|
|
58
|
-
description: "Every Environment with all the labels asked for. A token caller sees only the Environments its grants reach.",
|
|
59
|
-
request: {
|
|
60
|
-
query: pageQuery.extend({
|
|
61
|
-
label: z
|
|
62
|
-
.union([z.string(), z.array(z.string())])
|
|
63
|
-
.optional()
|
|
64
|
-
.meta({ description: "`key=value`; repeat it to require several labels" }),
|
|
65
|
-
}),
|
|
66
|
-
},
|
|
67
|
-
responses: {
|
|
68
|
-
200: json(z.union([ListEnvironmentsResponse, EnvironmentPage]), "The Environments; limit opts into pagination"),
|
|
69
|
-
},
|
|
70
|
-
}, async (c) => jsonResponse(200, c.req.query("limit") !== undefined
|
|
71
|
-
? await environmentPage(c.env.tenant, c.get("scope"), c.req.query(), labelsOf(c.req.queries("label")))
|
|
72
|
-
: await listEnvironments(c.env.tenant, labelsOf(c.req.queries("label")), c.get("scope"))));
|
|
73
|
-
tenantRoute(api, WRITE, {
|
|
74
|
-
method: "put",
|
|
75
|
-
path: "/v1/environments/{environmentId}",
|
|
76
|
-
tags: ["Environments"],
|
|
77
|
-
summary: "Create, find or change an Environment",
|
|
78
|
-
description: "Creates the Environment and its vault within the Tenant's limits, or answers the one with this id, so get-or-create is one call. It is made from one version of an environment manifest (`manifest: \"name\"` takes the latest, `\"name@n\"` version n) or from the inline fields, and never changes what it runs after that (protocol 14, D63): on an existing one, labels replace, `ownerUserId` sets or (with `null`) clears the owner guard, fields that match what it runs are accepted, and anything else is `409 environment_immutable`, which names the fork that makes a new Environment instead. Kind `pod` needs sandbox pods (`nylorun local sandbox enable`) and is created at once.",
|
|
79
|
-
request: {
|
|
80
|
-
params: environmentId,
|
|
81
|
-
body: { required: true, content: { "application/json": { schema: PutEnvironmentRequest } } },
|
|
82
|
-
},
|
|
83
|
-
responses: {
|
|
84
|
-
200: json(EnvironmentView, "The Environment"),
|
|
85
|
-
409: {
|
|
86
|
-
description: "An Environment with this id runs something else (`environment_immutable`), the Tenant holds as many Environments as it allows (`limit_exceeded`), kind pod without sandbox pods (`environment_unavailable`), or it is being deleted (`environment_busy`)",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
}, async (c) => {
|
|
90
|
-
const id = idOf(c.req.param("environmentId"));
|
|
91
|
-
const body = PutEnvironmentRequestSchema.parse(await readJson(c));
|
|
92
|
-
return jsonResponse(200, await putEnvironment(c.env.tenant, id, body, c.get("scope")));
|
|
93
|
-
});
|
|
94
|
-
tenantRoute(api, READ, {
|
|
95
|
-
method: "get",
|
|
96
|
-
path: "/v1/environments/{environmentId}",
|
|
97
|
-
tags: ["Environments"],
|
|
98
|
-
summary: "Get an Environment",
|
|
99
|
-
description: "Its recipe, versions, labels, owner guard, state and the sessions that name it (acting for a person, only theirs).",
|
|
100
|
-
request: { params: environmentId },
|
|
101
|
-
responses: { 200: json(EnvironmentView, "The Environment") },
|
|
102
|
-
}, async (c) => jsonResponse(200, await getEnvironment(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"))));
|
|
103
|
-
tenantRoute(api, READ, {
|
|
104
|
-
method: "get",
|
|
105
|
-
path: "/v1/environments/{environmentId}/events",
|
|
106
|
-
tags: ["Environments"],
|
|
107
|
-
summary: "Read an Environment's events",
|
|
108
|
-
description: "`environment.created`, `.attached`, `.detached`, `.deleted`, a pod's `.running`, `.suspended`, `.relaunched`, `.lost`, `.reset` and `.failed`, and `environment.egress` for every connection its sandbox opens through egress-gate, naming the turns that were running (D67), in order, numbered from 0, at most the Tenant's Runtime limit `environmentEventItems` (default 1000) per call: `from` reads on.",
|
|
109
|
-
request: {
|
|
110
|
-
params: environmentId,
|
|
111
|
-
query: z.object({
|
|
112
|
-
from: z.string().optional().meta({ description: "The first `seq` to return" }),
|
|
113
|
-
types: z.string().optional().meta({ description: "Only these event types, comma-separated (`environment.egress`)" }),
|
|
114
|
-
since: z.string().optional().meta({ description: "Only events recorded at or after this ISO time" }),
|
|
115
|
-
until: z.string().optional().meta({ description: "Only events recorded before this ISO time" }),
|
|
116
|
-
}),
|
|
117
|
-
},
|
|
118
|
-
responses: { 200: json(ListEnvironmentEventsResponse, "The events") },
|
|
119
|
-
}, async (c) => {
|
|
120
|
-
const from = c.req.query("from");
|
|
121
|
-
if (from !== undefined && !/^\d+$/.test(from))
|
|
122
|
-
fail(400, "from must be a seq");
|
|
123
|
-
const types = c.req.query("types")?.split(",").map((type) => type.trim()).filter(Boolean);
|
|
124
|
-
const unknown = types?.find((type) => !ENVIRONMENT_EVENT_TYPES.includes(type));
|
|
125
|
-
if (unknown !== undefined)
|
|
126
|
-
fail(400, `types: ${unknown} is not an Environment event type`);
|
|
127
|
-
const time = (name) => {
|
|
128
|
-
const value = c.req.query(name);
|
|
129
|
-
if (value === undefined)
|
|
130
|
-
return undefined;
|
|
131
|
-
const at = Date.parse(value);
|
|
132
|
-
if (!Number.isFinite(at))
|
|
133
|
-
fail(400, `${name} must be an ISO time`);
|
|
134
|
-
return new Date(at).toISOString();
|
|
135
|
-
};
|
|
136
|
-
const since = time("since");
|
|
137
|
-
const until = time("until");
|
|
138
|
-
return jsonResponse(200, await environmentEventsOf(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"), {
|
|
139
|
-
...(from === undefined ? {} : { fromSeq: Number(from) }),
|
|
140
|
-
...(types === undefined || types.length === 0 ? {} : { types }),
|
|
141
|
-
...(since === undefined ? {} : { since }),
|
|
142
|
-
...(until === undefined ? {} : { until }),
|
|
143
|
-
}));
|
|
144
|
-
});
|
|
145
|
-
tenantRoute(api, WRITE, {
|
|
146
|
-
method: "delete",
|
|
147
|
-
path: "/v1/environments/{environmentId}",
|
|
148
|
-
tags: ["Environments"],
|
|
149
|
-
summary: "Delete an Environment",
|
|
150
|
-
description: "Deletes the Environment, its vault with every secret, and its files. Sessions that name it keep its id; their next turn is refused (`environment_unavailable`) until an Environment with this id exists again.",
|
|
151
|
-
request: { params: environmentId },
|
|
152
|
-
responses: {
|
|
153
|
-
200: json(DeleteEnvironmentResponse, "Deleted, or there was none"),
|
|
154
|
-
409: { description: "A session that names it has a turn running (`environment_busy`)" },
|
|
155
|
-
},
|
|
156
|
-
}, async (c) => jsonResponse(200, await deleteEnvironment(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"))));
|
|
157
|
-
for (const [action, summary, description, run] of [
|
|
158
|
-
[
|
|
159
|
-
"stop",
|
|
160
|
-
"Stop a pod Environment",
|
|
161
|
-
"Suspends the pod: its volume is kept, and the next turn of a session that names it starts it again. Refused during a turn (`environment_busy`).",
|
|
162
|
-
stopEnvironment,
|
|
163
|
-
],
|
|
164
|
-
[
|
|
165
|
-
"reset",
|
|
166
|
-
"Reset a pod Environment",
|
|
167
|
-
"A new pod on a new, empty volume with the recipe applied; the old pod and volume are deleted. The way out of `environment_lost`. Refused during a turn (`environment_busy`).",
|
|
168
|
-
resetEnvironment,
|
|
169
|
-
],
|
|
170
|
-
])
|
|
171
|
-
tenantRoute(api, WRITE, {
|
|
172
|
-
method: "post",
|
|
173
|
-
path: `/v1/environments/{environmentId}/${action}`,
|
|
174
|
-
tags: ["Environments"],
|
|
175
|
-
summary,
|
|
176
|
-
description,
|
|
177
|
-
request: { params: environmentId },
|
|
178
|
-
responses: {
|
|
179
|
-
200: json(EnvironmentView, "The Environment"),
|
|
180
|
-
400: { description: "The Environment is not of kind pod" },
|
|
181
|
-
409: {
|
|
182
|
-
description: "A turn holds it (`environment_busy`), or no sandbox pods (`environment_unavailable`)",
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
}, async (c) => jsonResponse(200, await run(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"))));
|
|
186
|
-
tenantRoute(api, WRITE, {
|
|
187
|
-
method: "post",
|
|
188
|
-
path: "/v1/environments/{environmentId}/snapshot",
|
|
189
|
-
tags: ["Environments"],
|
|
190
|
-
summary: "Snapshot an Environment's disk",
|
|
191
|
-
description: "Between turns, a new version of the Environment's disk (`/workspace`, and a pod's `/harness` but the engine's own files): files with their modes and modification times, links and empty directories, as a version of the folder artifact the Environment owns. Unchanged files are stored once; an unchanged disk makes no version (`unchanged`). A stopped pod is started first. Refused during a turn or another copy (`environment_busy`).",
|
|
192
|
-
request: { params: environmentId },
|
|
193
|
-
responses: {
|
|
194
|
-
201: json(EnvironmentSnapshot, "The version"),
|
|
195
|
-
400: { description: "The Environment is of kind none" },
|
|
196
|
-
409: { description: "A turn or another copy holds the disk (`environment_busy`), or the pod could not start (`environment_unavailable`)" },
|
|
197
|
-
413: { description: "Past the entries, bytes or Tenant total a version may hold (`limit_exceeded`)" },
|
|
198
|
-
},
|
|
199
|
-
}, async (c) => jsonResponse(201, await snapshotEnvironment(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"))));
|
|
200
|
-
tenantRoute(api, WRITE, {
|
|
201
|
-
method: "post",
|
|
202
|
-
path: "/v1/environments/{environmentId}/restore",
|
|
203
|
-
tags: ["Environments"],
|
|
204
|
-
summary: "Restore a version of an Environment's disk",
|
|
205
|
-
description: "Puts disk version `version` (or `latest`) back. A virtual Environment's workspace is rewritten at once (`done`); a pod gets a new volume seeded from the version before any turn (`pending`, with `disk.operation` in the view until it ends). The Environment keeps its id, vault and sessions. Refused during a turn or another copy (`environment_busy`).",
|
|
206
|
-
request: {
|
|
207
|
-
params: environmentId,
|
|
208
|
-
body: { required: true, content: { "application/json": { schema: RestoreEnvironmentRequest } } },
|
|
209
|
-
},
|
|
210
|
-
responses: {
|
|
211
|
-
200: json(EnvironmentRestore, "Restored, or being restored"),
|
|
212
|
-
404: { description: "No such Environment, or no such version" },
|
|
213
|
-
409: { description: "A turn or another copy holds the disk (`environment_busy`)" },
|
|
214
|
-
},
|
|
215
|
-
}, async (c) => {
|
|
216
|
-
const body = RestoreEnvironmentRequestSchema.parse(await readJson(c));
|
|
217
|
-
return jsonResponse(200, await restoreEnvironment(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"), body.version));
|
|
218
|
-
});
|
|
219
|
-
tenantRoute(api, WRITE, {
|
|
220
|
-
method: "post",
|
|
221
|
-
path: "/v1/environments/{environmentId}/fork",
|
|
222
|
-
tags: ["Environments"],
|
|
223
|
-
summary: "Fork an Environment",
|
|
224
|
-
description: "How an Environment gets a new manifest version (Q15): a new Environment `id`, made from `manifest` (default the source's manifest at its latest version), its disk started from a version of this one's (`disk`: `latest`, the default, takes a snapshot first; a version number; or `empty`) and its secrets copied inside the Runtime (`secrets`: `copy`, the default, copies those the new manifest declares; `none`). On a fork onto other setup the new manifest's setup runs on the copied disk before its first turn. This Environment keeps what it runs, its disk and its sessions. `dryRun` answers the plan and makes nothing.",
|
|
225
|
-
request: {
|
|
226
|
-
params: environmentId,
|
|
227
|
-
body: { required: true, content: { "application/json": { schema: ForkEnvironmentRequest } } },
|
|
228
|
-
},
|
|
229
|
-
responses: {
|
|
230
|
-
200: json(ForkEnvironmentResponse, "The plan (`dryRun`)"),
|
|
231
|
-
201: json(ForkEnvironmentResponse, "The new Environment, and the plan it ran"),
|
|
232
|
-
400: { description: "The source was made from inline fields and no manifest is named, or the manifest does not resolve" },
|
|
233
|
-
404: { description: "No such Environment, manifest version (`manifest_not_found`) or disk version" },
|
|
234
|
-
409: { description: "The id exists, or a turn or another copy holds the source's disk (`environment_busy`)" },
|
|
235
|
-
},
|
|
236
|
-
}, async (c) => {
|
|
237
|
-
const body = ForkEnvironmentRequestSchema.parse(await readJson(c));
|
|
238
|
-
const answer = await forkEnvironment(c.env.tenant, idOf(c.req.param("environmentId")), body, c.get("scope"));
|
|
239
|
-
return jsonResponse("dryRun" in answer ? 200 : 201, answer);
|
|
240
|
-
});
|
|
241
|
-
tenantRoute(api, READ, {
|
|
242
|
-
method: "get",
|
|
243
|
-
path: "/v1/environments/{environmentId}/versions",
|
|
244
|
-
tags: ["Environments"],
|
|
245
|
-
summary: "List an Environment's disk versions",
|
|
246
|
-
description: "Newest first. Read a version's tree, files, diff and zip through `/v1/artifacts/{artifactId}/versions/{version}/…`.",
|
|
247
|
-
request: { params: environmentId },
|
|
248
|
-
responses: { 200: json(EnvironmentVersions, "The versions") },
|
|
249
|
-
}, async (c) => jsonResponse(200, await environmentVersions(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"))));
|
|
250
|
-
tenantRoute(api, WRITE, {
|
|
251
|
-
method: "delete",
|
|
252
|
-
path: "/v1/environments/{environmentId}/versions/{version}",
|
|
253
|
-
tags: ["Environments"],
|
|
254
|
-
summary: "Delete an Environment's disk version",
|
|
255
|
-
description: "Deletes the version, and the stored files no other version names. Refused while a seed reads it.",
|
|
256
|
-
request: { params: environmentId.extend({ version: z.string() }) },
|
|
257
|
-
responses: {
|
|
258
|
-
200: json(z.object({ environmentId: z.string(), version: z.number(), deleted: z.boolean() }), "Deleted, or there was none"),
|
|
259
|
-
409: { description: "A new volume is being seeded from it (`environment_busy`)" },
|
|
260
|
-
},
|
|
261
|
-
}, async (c) => {
|
|
262
|
-
const version = c.req.param("version") ?? "";
|
|
263
|
-
if (!/^[1-9]\d{0,8}$/.test(version))
|
|
264
|
-
fail(400, "version must be a version number");
|
|
265
|
-
return jsonResponse(200, await deleteEnvironmentVersion(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"), Number(version)));
|
|
266
|
-
});
|
|
267
|
-
tenantRoute(api, READ, {
|
|
268
|
-
method: "get",
|
|
269
|
-
path: "/v1/environments/{environmentId}/secrets",
|
|
270
|
-
tags: ["Environments"],
|
|
271
|
-
summary: "List an Environment's secrets",
|
|
272
|
-
description: "Each secret's name, type and binding (its URL, or its hosts and header). Never a value.",
|
|
273
|
-
request: { params: environmentId },
|
|
274
|
-
responses: { 200: json(ListEnvironmentSecretsResponse, "The secrets") },
|
|
275
|
-
}, async (c) => {
|
|
276
|
-
const id = idOf(c.req.param("environmentId"));
|
|
277
|
-
assertReaches(c.get("scope"), id);
|
|
278
|
-
return jsonResponse(200, { secrets: await c.env.tenant.vault.listEnvironmentSecrets(id) });
|
|
279
|
-
});
|
|
280
|
-
tenantRoute(api, READ, {
|
|
281
|
-
method: "get",
|
|
282
|
-
path: "/v1/environments/{environmentId}/secrets/{name}",
|
|
283
|
-
tags: ["Environments"],
|
|
284
|
-
summary: "Get an Environment's secret",
|
|
285
|
-
description: "Its name, type and binding. Never its value.",
|
|
286
|
-
request: { params: secretPath },
|
|
287
|
-
responses: { 200: json(EnvironmentSecretView, "The secret") },
|
|
288
|
-
}, async (c) => {
|
|
289
|
-
const id = idOf(c.req.param("environmentId"));
|
|
290
|
-
assertReaches(c.get("scope"), id);
|
|
291
|
-
return jsonResponse(200, await c.env.tenant.vault.getEnvironmentSecret(id, c.req.param("name") ?? ""));
|
|
292
|
-
});
|
|
293
|
-
tenantRoute(api, WRITE, {
|
|
294
|
-
method: "put",
|
|
295
|
-
path: "/v1/environments/{environmentId}/secrets/{name}",
|
|
296
|
-
tags: ["Environments"],
|
|
297
|
-
summary: "Write an Environment's secret",
|
|
298
|
-
description: "Creates or replaces one credential of the Environment's vault: an `environment_secret` (the variable `name` reads the sentinel `nylorun_managed` in the sandbox, and egress-gate sets the value as a header on requests to `allowedHosts`), an `environment_variable` (a plain variable), or a `bearer` token or `headers` map for the MCP servers and HTTP tools at `url`. The same name and type re-seals the value and binding; another type replaces it. A rotated value is used from the next request. A second credential for a host or a URL is `409 credential_conflict`. When what the Environment runs declares secret slots (protocol 14, D64), send only the value (`{ value }`, or `{ headers }` for a `headers` slot): the type and binding come from the slot, and a name it does not declare is `409 secret_not_in_manifest`.",
|
|
299
|
-
request: {
|
|
300
|
-
params: secretPath,
|
|
301
|
-
body: {
|
|
302
|
-
required: true,
|
|
303
|
-
content: { "application/json": { schema: z.union([PutEnvironmentSecretRequest, PutEnvironmentSecretValueRequest]) } },
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
responses: {
|
|
307
|
-
200: json(EnvironmentSecretView, "The secret, without its value"),
|
|
308
|
-
409: { description: "Another credential is bound to one of its hosts or to its URL (`credential_conflict`)" },
|
|
309
|
-
},
|
|
310
|
-
}, async (c) => {
|
|
311
|
-
const id = idOf(c.req.param("environmentId"));
|
|
312
|
-
const scope = c.get("scope");
|
|
313
|
-
assertReaches(scope, id);
|
|
314
|
-
const name = c.req.param("name") ?? "";
|
|
315
|
-
const body = await secretRequestFor(c.env.tenant, id, name, await readJson(c));
|
|
316
|
-
return jsonResponse(200, await c.env.tenant.keys.putEnvironmentSecret(id, name, body, actorOf(scope)));
|
|
317
|
-
});
|
|
318
|
-
tenantRoute(api, WRITE, {
|
|
319
|
-
method: "delete",
|
|
320
|
-
path: "/v1/environments/{environmentId}/secrets/{name}",
|
|
321
|
-
tags: ["Environments"],
|
|
322
|
-
summary: "Delete an Environment's secret",
|
|
323
|
-
description: "The next request that would have used it gets none.",
|
|
324
|
-
request: { params: secretPath },
|
|
325
|
-
responses: { 200: json(DeletedResponse, "Deleted") },
|
|
326
|
-
}, async (c) => {
|
|
327
|
-
const id = idOf(c.req.param("environmentId"));
|
|
328
|
-
const scope = c.get("scope");
|
|
329
|
-
assertReaches(scope, id);
|
|
330
|
-
return jsonResponse(200, await c.env.tenant.vault.deleteEnvironmentSecret(id, c.req.param("name") ?? "", actorOf(scope)));
|
|
331
|
-
});
|
|
332
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { type EnvironmentEvent, type EnvironmentEventPayload, type EnvironmentEventType } from "@nylorun/core/contracts";
|
|
2
|
-
/** The Environment stream's two statements, in the caller's transaction. */
|
|
3
|
-
export interface EnvironmentRecordWriter {
|
|
4
|
-
/** The seq the Environment's next event takes: one past its last, or 0. */
|
|
5
|
-
nextSeq(environmentId: string): Promise<number>;
|
|
6
|
-
insert(row: {
|
|
7
|
-
environmentId: string;
|
|
8
|
-
seq: number;
|
|
9
|
-
type: string;
|
|
10
|
-
body: EnvironmentEvent;
|
|
11
|
-
}): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
export interface EnvironmentAppendInput<T extends EnvironmentEventType> {
|
|
14
|
-
tenantId: string;
|
|
15
|
-
environmentId: string;
|
|
16
|
-
time: Date;
|
|
17
|
-
type: T;
|
|
18
|
-
payload: EnvironmentEventPayload<T>;
|
|
19
|
-
/** The Tenant's Runtime limit `recordEntryBytes`, as for a session's events. */
|
|
20
|
-
maxEntryBytes: number;
|
|
21
|
-
}
|
|
22
|
-
/** Builds the event and checks it against the Environment event catalog. */
|
|
23
|
-
export declare function buildEnvironmentEvent<T extends EnvironmentEventType>(input: EnvironmentAppendInput<T> & {
|
|
24
|
-
seq: number;
|
|
25
|
-
}): EnvironmentEvent;
|
|
26
|
-
/** Appends one event to the Environment's stream through `writer`. */
|
|
27
|
-
export declare function appendEnvironmentEvent<T extends EnvironmentEventType>(writer: EnvironmentRecordWriter, input: EnvironmentAppendInput<T>): Promise<EnvironmentEvent>;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A environment's lifecycle stream (blueprint D39, F7.1): `environment.created`, `environment.attached`,
|
|
3
|
-
* `environment.detached` and `environment.deleted` (and the rest of `ENVIRONMENT_EVENT_TYPES`, egress-gate's
|
|
4
|
-
* `environment.egress` for every connection among them, D67), on the `nylorun.environment-event/1` envelope, numbered
|
|
5
|
-
* from 0 per environment id in `nylorun_streams.environment_events`. The record module is its only
|
|
6
|
-
* writer, as for session events; the store runs the two statements through
|
|
7
|
-
* `EnvironmentRecordWriter` in the caller's transaction, under the environment row's lock (or, for a
|
|
8
|
-
* environment being created, its new row).
|
|
9
|
-
*
|
|
10
|
-
* The stream lives in the record only: it is not published to the stream relay, so no S2
|
|
11
|
-
* stream follows it yet. Clients read it with `GET /v1/environmentes/{id}/events`.
|
|
12
|
-
*/
|
|
13
|
-
import { randomUUID } from "node:crypto";
|
|
14
|
-
import { z } from "zod";
|
|
15
|
-
import { ENVIRONMENT_EVENT_PAYLOADS, ENVIRONMENT_EVENT_SCHEMA, EnvironmentEventSchema, } from "@nylorun/core/contracts";
|
|
16
|
-
import { InvalidEventError } from "./envelope.js";
|
|
17
|
-
import { checkEntry } from "./append.js";
|
|
18
|
-
/** Builds the event and checks it against the Environment event catalog. */
|
|
19
|
-
export function buildEnvironmentEvent(input) {
|
|
20
|
-
const payload = ENVIRONMENT_EVENT_PAYLOADS[input.type];
|
|
21
|
-
const checkedPayload = payload.safeParse(input.payload);
|
|
22
|
-
if (!checkedPayload.success)
|
|
23
|
-
throw new InvalidEventError(input.type, z.prettifyError(checkedPayload.error));
|
|
24
|
-
const event = JSON.parse(JSON.stringify({
|
|
25
|
-
schema: ENVIRONMENT_EVENT_SCHEMA,
|
|
26
|
-
eventId: randomUUID(),
|
|
27
|
-
tenantId: input.tenantId,
|
|
28
|
-
environmentId: input.environmentId,
|
|
29
|
-
seq: input.seq,
|
|
30
|
-
time: input.time.toISOString(),
|
|
31
|
-
type: input.type,
|
|
32
|
-
payload: input.payload,
|
|
33
|
-
}));
|
|
34
|
-
const checked = EnvironmentEventSchema.safeParse(event);
|
|
35
|
-
if (!checked.success)
|
|
36
|
-
throw new InvalidEventError(input.type, z.prettifyError(checked.error));
|
|
37
|
-
return event;
|
|
38
|
-
}
|
|
39
|
-
/** Appends one event to the Environment's stream through `writer`. */
|
|
40
|
-
export async function appendEnvironmentEvent(writer, input) {
|
|
41
|
-
checkEntry(`The ${input.type} event's payload`, input.payload, input.maxEntryBytes);
|
|
42
|
-
const seq = await writer.nextSeq(input.environmentId);
|
|
43
|
-
const event = buildEnvironmentEvent({ ...input, seq });
|
|
44
|
-
await writer.insert({ environmentId: input.environmentId, seq, type: input.type, body: event });
|
|
45
|
-
return event;
|
|
46
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment manifests (protocol 14, D63): `/v1/environment-manifests/{name}`, named and
|
|
3
|
-
* versioned definitions of a kind of Environment, saved like agents.
|
|
4
|
-
*
|
|
5
|
-
* - **Versions.** A save whose manifest hashes the same as the latest answers it unchanged;
|
|
6
|
-
* any other writes the next version. A version is never changed or deleted alone.
|
|
7
|
-
* - **No Environment changes.** Saving a version reaches no Environment: an Environment is made
|
|
8
|
-
* from one version and keeps it for life (`putEnvironment`). The plan counts those behind.
|
|
9
|
-
* - **Plan and drift.** `?dryRun=true` answers what a save would do and saves nothing: the
|
|
10
|
-
* changes from the latest, every reason the Runtime would refuse it (the Tenant's limits,
|
|
11
|
-
* missing artifacts), warnings, and how many Environments stay on older versions.
|
|
12
|
-
* `If-Match: <version>` refuses (`412 manifest_changed`) a save whose latest version moved
|
|
13
|
-
* since the client read it; `0` asks for a manifest that does not exist yet.
|
|
14
|
-
* - **Every client is equal** (Q12): a version records who saved it (`savedBy`), and nothing is
|
|
15
|
-
* read-only by where it was saved from.
|
|
16
|
-
*/
|
|
17
|
-
import { type EnvironmentManifest, type EnvironmentManifestPlan, type EnvironmentManifestVersions, type EnvironmentManifestView, type PutEnvironmentManifestRequest, type SaveEnvironmentManifestResponse } from "@nylorun/core/contracts";
|
|
18
|
-
import { type RecipeRequest } from "../sandbox/resolve.js";
|
|
19
|
-
import type { AuthScope, TenantContext } from "./context.js";
|
|
20
|
-
/** What an environment manifest asks of an Environment's recipe. */
|
|
21
|
-
export declare function manifestRecipeRequest(manifest: EnvironmentManifest): RecipeRequest;
|
|
22
|
-
/** The hash a manifest's version records: equal manifests hash the same. */
|
|
23
|
-
export declare function environmentManifestHash(manifest: EnvironmentManifest): string;
|
|
24
|
-
/** Each field that differs between `before` and `after`, by path; arrays compare whole. */
|
|
25
|
-
export declare function manifestChanges(before: unknown, after: unknown, path?: string): {
|
|
26
|
-
path: string;
|
|
27
|
-
from?: unknown;
|
|
28
|
-
to?: unknown;
|
|
29
|
-
}[];
|
|
30
|
-
/**
|
|
31
|
-
* `PUT /v1/environment-manifests/{name}`: saves `body` as manifest `name`'s next version, or
|
|
32
|
-
* answers the latest unchanged; with `dryRun`, the plan of that save and nothing saved.
|
|
33
|
-
*/
|
|
34
|
-
export declare function saveEnvironmentManifest(ctx: TenantContext, name: string, body: PutEnvironmentManifestRequest, scope: AuthScope, options: {
|
|
35
|
-
dryRun: true;
|
|
36
|
-
ifMatch?: number;
|
|
37
|
-
}): Promise<EnvironmentManifestPlan>;
|
|
38
|
-
export declare function saveEnvironmentManifest(ctx: TenantContext, name: string, body: PutEnvironmentManifestRequest, scope: AuthScope, options?: {
|
|
39
|
-
dryRun?: false;
|
|
40
|
-
ifMatch?: number;
|
|
41
|
-
}): Promise<SaveEnvironmentManifestResponse>;
|
|
42
|
-
/** `GET /v1/environment-manifests/{name}` (the latest) and `.../versions/{n}`. */
|
|
43
|
-
export declare function getEnvironmentManifest(ctx: TenantContext, name: string, version?: number): Promise<EnvironmentManifestView>;
|
|
44
|
-
/** `GET /v1/environment-manifests`: each manifest's latest version. */
|
|
45
|
-
export declare function listEnvironmentManifests(ctx: TenantContext): Promise<{
|
|
46
|
-
manifests: EnvironmentManifestView[];
|
|
47
|
-
}>;
|
|
48
|
-
/** `GET /v1/environment-manifests/{name}/versions`: every version, newest first. */
|
|
49
|
-
export declare function environmentManifestVersions(ctx: TenantContext, name: string): Promise<EnvironmentManifestVersions>;
|
|
50
|
-
/**
|
|
51
|
-
* `DELETE /v1/environment-manifests/{name}`: deletes every version, refused while any Environment
|
|
52
|
-
* was made from one (`409 manifest_in_use`): the Environment names the version it runs.
|
|
53
|
-
*/
|
|
54
|
-
export declare function deleteEnvironmentManifest(ctx: TenantContext, name: string): Promise<{
|
|
55
|
-
name: string;
|
|
56
|
-
deleted: boolean;
|
|
57
|
-
}>;
|