@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.
Files changed (160) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +1 -1
  3. package/dist/api/a2a/card.d.ts +4 -3
  4. package/dist/api/a2a/card.js +3 -3
  5. package/dist/api/a2a/routes.js +18 -7
  6. package/dist/api/ag-ui/routes.js +4 -7
  7. package/dist/api/components.d.ts +5854 -2718
  8. package/dist/api/components.js +39 -30
  9. package/dist/api/http/app.js +9 -4
  10. package/dist/api/http/body.d.ts +2 -0
  11. package/dist/api/http/body.js +20 -0
  12. package/dist/api/http/define.d.ts +4 -0
  13. package/dist/api/http/define.js +20 -9
  14. package/dist/api/http/routes/agent-manifests.d.ts +10 -0
  15. package/dist/api/http/routes/agent-manifests.js +135 -0
  16. package/dist/api/http/routes/agents.d.ts +11 -0
  17. package/dist/api/http/routes/agents.js +418 -0
  18. package/dist/api/http/routes/artifacts.js +2 -2
  19. package/dist/api/http/routes/coverage.d.ts +4 -4
  20. package/dist/api/http/routes/coverage.js +12 -6
  21. package/dist/api/http/routes/files.js +1 -1
  22. package/dist/api/http/routes/mcp.js +2 -2
  23. package/dist/api/http/routes/me.d.ts +1 -1
  24. package/dist/api/http/routes/me.js +1 -2
  25. package/dist/api/http/routes/memories.d.ts +9 -0
  26. package/dist/api/http/routes/memories.js +152 -0
  27. package/dist/api/http/routes/reads.d.ts +1 -137
  28. package/dist/api/http/routes/reads.js +31 -12
  29. package/dist/api/http/routes/sessions.js +21 -50
  30. package/dist/api/http/routes/tenant.js +13 -10
  31. package/dist/api/http/routes/triggers.d.ts +10 -0
  32. package/dist/api/http/routes/triggers.js +183 -0
  33. package/dist/api/openapi.js +69 -47
  34. package/dist/artifacts/disk.d.ts +10 -10
  35. package/dist/artifacts/disk.js +27 -27
  36. package/dist/artifacts/quota.d.ts +1 -1
  37. package/dist/artifacts/quota.js +2 -2
  38. package/dist/artifacts/service.js +2 -2
  39. package/dist/core/flow-host.d.ts +0 -1
  40. package/dist/gates/egress-credentials.js +1 -1
  41. package/dist/gates/egress.d.ts +5 -5
  42. package/dist/gates/egress.js +6 -6
  43. package/dist/gates/http-tool.js +2 -2
  44. package/dist/gates/meter.js +22 -5
  45. package/dist/gates/policy.d.ts +8 -3
  46. package/dist/gates/policy.js +7 -2
  47. package/dist/gates/tenant-vaults.d.ts +1 -1
  48. package/dist/harness/pod.d.ts +2 -0
  49. package/dist/harness/setup.js +17 -6
  50. package/dist/harness-api/record.js +11 -0
  51. package/dist/harness-api/server.js +4 -0
  52. package/dist/harness-api/start.d.ts +11 -7
  53. package/dist/harness-api/start.js +49 -5
  54. package/dist/harness-api/workspace.js +2 -2
  55. package/dist/host/config-for.d.ts +2 -0
  56. package/dist/host/config-for.js +1 -0
  57. package/dist/host/http.d.ts +2 -2
  58. package/dist/host/http.js +5 -2
  59. package/dist/host/main.js +1 -0
  60. package/dist/keys/keys.d.ts +15 -4
  61. package/dist/keys/keys.js +16 -0
  62. package/dist/keys/webhooks.d.ts +25 -0
  63. package/dist/keys/webhooks.js +62 -0
  64. package/dist/management-openapi.json +313 -133
  65. package/dist/mcp/connect.d.ts +2 -2
  66. package/dist/mcp/connect.js +3 -3
  67. package/dist/mcp/preview.js +7 -7
  68. package/dist/memory/service.d.ts +97 -0
  69. package/dist/memory/service.js +335 -0
  70. package/dist/memory/tool.d.ts +36 -0
  71. package/dist/memory/tool.js +84 -0
  72. package/dist/model/pi-model.js +7 -0
  73. package/dist/openapi.json +13120 -9034
  74. package/dist/reads/types.d.ts +3 -3
  75. package/dist/record/agent.d.ts +27 -0
  76. package/dist/record/agent.js +46 -0
  77. package/dist/record/index.d.ts +1 -1
  78. package/dist/record/index.js +1 -1
  79. package/dist/sandbox/join.js +5 -5
  80. package/dist/sandbox/placement.d.ts +1 -1
  81. package/dist/sandbox/placement.js +3 -3
  82. package/dist/sandbox/pods/lifecycle.d.ts +6 -6
  83. package/dist/sandbox/pods/lifecycle.js +6 -6
  84. package/dist/sandbox/pods/reconcile.js +10 -2
  85. package/dist/sandbox/resolve.d.ts +3 -3
  86. package/dist/sandbox/resolve.js +7 -7
  87. package/dist/sandbox/setup.d.ts +3 -1
  88. package/dist/sandbox/setup.js +14 -4
  89. package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
  90. package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
  91. package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
  92. package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
  93. package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
  94. package/dist/store/postgres/drizzle/0037_model_usage_cost_split.sql +4 -0
  95. package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
  96. package/dist/store/postgres/reads.js +44 -87
  97. package/dist/store/postgres/record-writer.d.ts +4 -4
  98. package/dist/store/postgres/record-writer.js +9 -9
  99. package/dist/store/postgres/schema.d.ts +1408 -123
  100. package/dist/store/postgres/schema.js +194 -38
  101. package/dist/store/postgres/store.js +329 -79
  102. package/dist/store/postgres/tenant.js +1 -2
  103. package/dist/store/types.d.ts +155 -50
  104. package/dist/store/types.js +0 -1
  105. package/dist/tenant/advance.js +20 -8
  106. package/dist/tenant/agent-manifests.d.ts +72 -0
  107. package/dist/tenant/agent-manifests.js +359 -0
  108. package/dist/tenant/agent-versions.d.ts +126 -0
  109. package/dist/tenant/agent-versions.js +389 -0
  110. package/dist/tenant/agents.d.ts +230 -0
  111. package/dist/tenant/{environments.js → agents.js} +464 -320
  112. package/dist/tenant/auth.d.ts +1 -1
  113. package/dist/tenant/auth.js +3 -3
  114. package/dist/tenant/commands.d.ts +63 -2
  115. package/dist/tenant/commands.js +240 -83
  116. package/dist/tenant/context.d.ts +46 -19
  117. package/dist/tenant/context.js +6 -2
  118. package/dist/tenant/effects.js +7 -16
  119. package/dist/tenant/identity-file.d.ts +6 -6
  120. package/dist/tenant/identity-file.js +12 -10
  121. package/dist/tenant/issuers.js +3 -3
  122. package/dist/tenant/limits.d.ts +0 -3
  123. package/dist/tenant/limits.js +0 -3
  124. package/dist/tenant/memories.d.ts +38 -0
  125. package/dist/tenant/memories.js +119 -0
  126. package/dist/tenant/reset.js +2 -2
  127. package/dist/tenant/run-grants.d.ts +6 -0
  128. package/dist/tenant/run-grants.js +4 -0
  129. package/dist/tenant/runtime.d.ts +1 -1
  130. package/dist/tenant/runtime.js +8 -0
  131. package/dist/tenant/sessions.d.ts +28 -45
  132. package/dist/tenant/sessions.js +85 -208
  133. package/dist/tenant/status.js +6 -7
  134. package/dist/tenant/sweep.js +12 -3
  135. package/dist/tenant/transcript.d.ts +16 -16
  136. package/dist/tenant/triggers.d.ts +143 -0
  137. package/dist/tenant/triggers.js +715 -0
  138. package/dist/tenant/turn-timing.d.ts +11 -1
  139. package/dist/tenant/turn-timing.js +3 -1
  140. package/dist/tenant/types.d.ts +5 -0
  141. package/dist/tenant/worker.d.ts +2 -0
  142. package/dist/tenant/worker.js +5 -0
  143. package/dist/traces/map.d.ts +1 -1
  144. package/dist/traces/map.js +7 -1
  145. package/dist/tuning.d.ts +7 -0
  146. package/dist/tuning.js +19 -0
  147. package/dist/vault/service.d.ts +13 -8
  148. package/dist/vault/service.js +61 -17
  149. package/dist/version.d.ts +1 -1
  150. package/dist/version.js +1 -1
  151. package/package.json +13 -12
  152. package/dist/api/http/routes/environment-manifests.d.ts +0 -9
  153. package/dist/api/http/routes/environment-manifests.js +0 -118
  154. package/dist/api/http/routes/environments.d.ts +0 -11
  155. package/dist/api/http/routes/environments.js +0 -332
  156. package/dist/record/environment.d.ts +0 -27
  157. package/dist/record/environment.js +0 -46
  158. package/dist/tenant/environment-manifests.d.ts +0 -57
  159. package/dist/tenant/environment-manifests.js +0 -208
  160. package/dist/tenant/environments.d.ts +0 -172
@@ -0,0 +1,183 @@
1
+ import { z } from "zod";
2
+ import { isAgentId } from "@nylorun/core/contracts";
3
+ import { RunTriggerResponse, TriggerList, TriggerRunPage, WebhookAcceptedResponse, WebhookSecretResponse, } from "../../components.js";
4
+ import { assertReaches } from "../../../tenant/agents.js";
5
+ import { fail } from "../../../tenant/http.js";
6
+ import { listRuns, listTriggers, receiveWebhook, rotateWebhookSecret, runTriggerNow, setTriggerStatus, } from "../../../tenant/triggers.js";
7
+ import { readRuntimeLimits } from "../../../tenant/runtime-limits.js";
8
+ import { readBytes } from "../body.js";
9
+ import { tenantRoute } from "../define.js";
10
+ import { pageQuery, pageSize, parseQuery } from "./reads.js";
11
+ import { jsonResponse } from "../respond.js";
12
+ const READ = {
13
+ credentials: ["application", "subject", "token"],
14
+ scopes: ["sessions:own", "agents:read", "agents:write"],
15
+ };
16
+ const WRITE = {
17
+ credentials: ["application", "subject", "token"],
18
+ scopes: ["agents:write"],
19
+ };
20
+ const json = (schema, description) => ({
21
+ description,
22
+ content: { "application/json": { schema } },
23
+ });
24
+ const agentId = z.object({
25
+ agentId: z.string().meta({ description: "The Agent id, percent-encoded" }),
26
+ });
27
+ const triggerPath = agentId.extend({
28
+ triggerId: z.string().meta({ description: "The trigger's id in the manifest's `runtime.triggers`" }),
29
+ });
30
+ function idOf(value) {
31
+ if (!isAgentId(value))
32
+ return fail(400, "An Agent id is up to 200 characters: /-separated segments of letters, digits, '.', '_' and '-'");
33
+ return value;
34
+ }
35
+ export function triggerRoutes(api) {
36
+ tenantRoute(api, READ, {
37
+ method: "get",
38
+ path: "/v1/agents/{agentId}/triggers",
39
+ tags: ["Triggers"],
40
+ summary: "List an Agent's triggers",
41
+ description: "Each trigger its manifest declares, with what the Agent holds of it: its status (`active`, `paused` with a reason, `completed` for an `at` that ran), when it runs next and last ran.",
42
+ request: { params: agentId },
43
+ responses: { 200: json(TriggerList, "The triggers") },
44
+ }, async (c) => {
45
+ const id = idOf(c.req.param("agentId"));
46
+ assertReaches(c.get("scope"), id);
47
+ return jsonResponse(200, await listTriggers(c.env.tenant, id, originOf(c)));
48
+ });
49
+ for (const action of ["pause", "resume"])
50
+ tenantRoute(api, WRITE, {
51
+ method: "post",
52
+ path: `/v1/agents/{agentId}/triggers/{triggerId}/${action}`,
53
+ tags: ["Triggers"],
54
+ summary: action === "pause" ? "Pause a trigger" : "Resume a trigger",
55
+ description: action === "pause"
56
+ ? "It stops firing until it is resumed; a run already started or queued goes on."
57
+ : "It fires again from its next time after now; missed times are not run. An `at` whose time has passed is refused (`trigger_expired`).",
58
+ request: { params: triggerPath },
59
+ responses: {
60
+ 200: json(TriggerList, "The Agent's triggers"),
61
+ 409: { description: "An `at` whose time has passed (`trigger_expired`)" },
62
+ },
63
+ }, async (c) => {
64
+ const id = idOf(c.req.param("agentId"));
65
+ assertReaches(c.get("scope"), id);
66
+ return jsonResponse(200, await setTriggerStatus(c.env.tenant, id, c.req.param("triggerId"), action, originOf(c)));
67
+ });
68
+ tenantRoute(api, WRITE, {
69
+ method: "post",
70
+ path: "/v1/agents/{agentId}/triggers/{triggerId}/run",
71
+ tags: ["Triggers"],
72
+ summary: "Run a trigger now",
73
+ description: "Runs it once now, paused or not, as if it had fired: a turn on its session, or a queued message when a turn runs there. A retried request with the same `Idempotency-Key` answers the same run.",
74
+ request: {
75
+ params: triggerPath,
76
+ headers: z.object({ "Idempotency-Key": z.string().min(1).max(200) }),
77
+ },
78
+ responses: { 200: json(RunTriggerResponse, "The run") },
79
+ }, async (c) => {
80
+ const id = idOf(c.req.param("agentId"));
81
+ assertReaches(c.get("scope"), id);
82
+ const key = c.req.header("idempotency-key")?.trim();
83
+ if (!key || key.length > 200)
84
+ fail(400, "Idempotency-Key is required: 1-200 characters");
85
+ return jsonResponse(200, { run: await runTriggerNow(c.env.tenant, id, c.req.param("triggerId"), key) });
86
+ });
87
+ tenantRoute(api, READ, {
88
+ method: "get",
89
+ path: "/v1/agents/{agentId}/runs",
90
+ tags: ["Triggers"],
91
+ summary: "List an Agent's runs",
92
+ description: "Each time one of its triggers fired or was run by hand, newest first: when it was due, whether it was late, and what came of it (`started`, `queued`, `skipped` or `failed`, with a reason), with the session and turn.",
93
+ request: {
94
+ params: agentId,
95
+ query: pageQuery.extend({
96
+ triggerId: z.string().optional().meta({ description: "Only this trigger's runs" }),
97
+ }),
98
+ },
99
+ responses: { 200: json(TriggerRunPage, "A page of runs") },
100
+ }, async (c) => {
101
+ const id = idOf(c.req.param("agentId"));
102
+ assertReaches(c.get("scope"), id);
103
+ const triggerId = c.req.query("triggerId");
104
+ const { limit, cursor } = parseQuery(pageQuery, { limit: c.req.query("limit"), cursor: c.req.query("cursor") });
105
+ return jsonResponse(200, await listRuns(c.env.tenant, id, {
106
+ ...(triggerId ? { triggerId } : {}),
107
+ ...(cursor ? { cursor } : {}),
108
+ limit: await pageSize(c.env.tenant, limit, { fallback: 50 }),
109
+ }));
110
+ });
111
+ }
112
+ /** A delivery: no credential, no protocol header, any body; the endpoint id and the signature are the grant. */
113
+ const DELIVERY = {
114
+ credentials: [],
115
+ scopes: "any",
116
+ anonymous: true,
117
+ unversioned: true,
118
+ bytes: true,
119
+ };
120
+ export function webhookRoutes(api) {
121
+ tenantRoute(api, WRITE, {
122
+ method: "post",
123
+ path: "/v1/agents/{agentId}/triggers/{triggerId}/secret",
124
+ tags: ["Triggers"],
125
+ summary: "Make a webhook trigger's signing secret",
126
+ description: "A new Standard Webhooks secret (`whsec_…`) for a webhook trigger, shown this once; the one before stops working at once. Give it to the sender with the trigger's `webhookUrl`.",
127
+ request: { params: triggerPath },
128
+ responses: {
129
+ 200: json(WebhookSecretResponse, "The secret, this once"),
130
+ 400: { description: "Not a webhook trigger" },
131
+ },
132
+ }, async (c) => {
133
+ const id = idOf(c.req.param("agentId"));
134
+ assertReaches(c.get("scope"), id);
135
+ return jsonResponse(200, await rotateWebhookSecret(c.env.tenant, id, c.req.param("triggerId"), originOf(c)));
136
+ });
137
+ tenantRoute(api, DELIVERY, {
138
+ method: "post",
139
+ path: "/v1/webhooks/{endpointId}",
140
+ tags: ["Triggers"],
141
+ summary: "Deliver to a webhook trigger",
142
+ description: "A sender's delivery, signed with Standard Webhooks (`webhook-id`, `webhook-timestamp` within 5 minutes by default, `webhook-signature`): no credential (an `Authorization` header is ignored) and no `Nylorun-Protocol`. The body, any media type, at most the Runtime limit `webhookBodyBytes`, is read as bytes and verified before anything else. It runs the trigger once per `webhook-id`: a retry answers the first run. The body reaches the model as data under the trigger's prompt. Other senders (GitHub, Stripe, Slack) are relayed by an app.",
143
+ request: {
144
+ params: z.object({ endpointId: z.string().meta({ description: "The trigger's endpoint id, `wh_…`" }) }),
145
+ headers: z.object({
146
+ "webhook-id": z.string().meta({ description: "The delivery's id; a retry sends the same" }),
147
+ "webhook-timestamp": z.string().meta({ description: "Seconds since the epoch" }),
148
+ "webhook-signature": z.string().meta({ description: "`v1,<base64>`, space-separated" }),
149
+ }),
150
+ body: {
151
+ required: true,
152
+ description: "The payload, of any media type",
153
+ content: { "*/*": { schema: z.string().meta({ format: "binary" }) } },
154
+ },
155
+ },
156
+ responses: {
157
+ 202: json(WebhookAcceptedResponse, "The run it made: started, queued, skipped or failed"),
158
+ 401: { description: "Unsigned, not signed with the endpoint's secret, or a timestamp out of tolerance" },
159
+ 404: { description: "No endpoint has this id" },
160
+ 409: { description: "The trigger is paused (`trigger_paused`)" },
161
+ 413: { description: "Over `webhookBodyBytes`" },
162
+ },
163
+ }, async (c) => {
164
+ const tenant = c.env.tenant;
165
+ const { webhookBodyBytes } = await tenant.store.tx((t) => readRuntimeLimits(t));
166
+ const body = await readBytes(c.req.raw, webhookBodyBytes, "webhookBodyBytes");
167
+ const header = (name) => c.req.header(name)?.trim() || undefined;
168
+ return jsonResponse(202, await receiveWebhook(tenant, c.req.param("endpointId"), {
169
+ id: header("webhook-id"),
170
+ timestamp: header("webhook-timestamp"),
171
+ signature: header("webhook-signature"),
172
+ contentType: header("content-type"),
173
+ body,
174
+ }));
175
+ });
176
+ }
177
+ /**
178
+ * Where its webhook URLs live: the Runtime's public URL (`NYLORUN_PUBLIC_URL`), else the origin
179
+ * the request reached, which behind a proxy that rewrites `Host` is the Runtime's own.
180
+ */
181
+ function originOf(c) {
182
+ return c.env.tenant.config.publicUrl ?? new URL(c.req.url).origin;
183
+ }
@@ -110,21 +110,79 @@ function tenantSchemes(registry) {
110
110
  }
111
111
  const RUNTIME_TAGS = [
112
112
  {
113
- name: "Agents",
113
+ name: "Agent manifests",
114
114
  group: "Agents",
115
- description: "The agents a Tenant can run: put a definition, list what is there. In `@nylorun/agents`.",
116
- operations: ["PUT /v1/agents/{agentId}", "GET /v1/agents"],
115
+ description: "Named, versioned agent manifests (protocol 16): one document with what runs inside an agent's loop (`harness`) and what the Runtime provides around it (`runtime`), checked against each other when it is saved. Save one with a plan first (`?dryRun=true`) and `If-Match`; an Agent runs one version at a time, and a save moves every Agent that tracks the manifest to the new version, while a pinned one stays. `client.agentManifests` in `@nylorun/agents`, and `nylorun apply`.",
116
+ operations: [
117
+ "PUT /v1/agent-manifests/{name}",
118
+ "GET /v1/agent-manifests",
119
+ "GET /v1/agent-manifests/{name}",
120
+ "GET /v1/agent-manifests/{name}/versions",
121
+ "GET /v1/agent-manifests/{name}/versions/{version}",
122
+ "DELETE /v1/agent-manifests/{name}",
123
+ ],
117
124
  },
118
125
  {
119
126
  name: "Definition files",
120
127
  group: "Agents",
121
- description: "The files a definition names by the SHA-256 of their bytes (each skill's folder): upload each once before putting the definition, which is refused while it names one the Runtime does not hold (`definition_files_missing`) or a skill whose files hold more than the Tenant's skill limit together (`limit_exceeded`; `/v1/tenant/skills`, default 2 GiB). No file and no count of files has a limit of its own. `saveAgent` in `@nylorun/agents` does both.",
128
+ description: "The files an agent manifest names by the SHA-256 of their bytes (each skill's folder, a runtime section's input files): upload each once before saving the manifest, which is refused while it names one the Runtime does not hold (`definition_files_missing`) or a skill whose files hold more than its skill limit together (`limit_exceeded`; `/v1/tenant/skills`, default 2 GiB). No file and no count of files has a limit of its own. `client.agentManifests.save` in `@nylorun/agents` does both.",
122
129
  operations: ["PUT /v1/files/{file}", "HEAD /v1/files/{file}"],
123
130
  },
131
+ {
132
+ name: "Agents",
133
+ group: "Agents",
134
+ description: "The live instances of agent manifests: an Agent has a stable id, its manifest version, one sandbox (a virtual workspace, a pod with its own volume, or none), one vault of secrets for its shell, MCP servers and HTTP tools, its disk versions, and its sessions. Its `state` says whether it is working, sleeping, needs a person (`input_required`), is failing or paused. Create or find one from a saved manifest, watch its lifecycle, stop, reset, fork or delete it, and write its secrets. In `@nylorun/agents`.",
135
+ operations: [
136
+ "PUT /v1/agents/{agentId}",
137
+ "GET /v1/agents",
138
+ "GET /v1/agents/{agentId}",
139
+ "GET /v1/agents/{agentId}/events",
140
+ "POST /v1/agents/{agentId}/stop",
141
+ "POST /v1/agents/{agentId}/reset",
142
+ "POST /v1/agents/{agentId}/restart",
143
+ "DELETE /v1/agents/{agentId}",
144
+ "GET /v1/agents/{agentId}/secrets",
145
+ "GET /v1/agents/{agentId}/secrets/{name}",
146
+ "PUT /v1/agents/{agentId}/secrets/{name}",
147
+ "DELETE /v1/agents/{agentId}/secrets/{name}",
148
+ "POST /v1/agents/{agentId}/snapshot",
149
+ "POST /v1/agents/{agentId}/restore",
150
+ "POST /v1/agents/{agentId}/fork",
151
+ "GET /v1/agents/{agentId}/versions",
152
+ "DELETE /v1/agents/{agentId}/versions/{version}",
153
+ ],
154
+ },
155
+ {
156
+ name: "Triggers",
157
+ group: "Agents",
158
+ description: "What starts an Agent's turns with no client connected: the schedules, one-shot times and webhook endpoints its manifest's `runtime.triggers` declare. Each trigger's status is the Agent's: pause and resume it, run it now, make a webhook trigger's signing secret, and read its runs (`started`, `queued`, `skipped` or `failed`, with a reason). In `@nylorun/agents` and `nylorun agents triggers`.",
159
+ operations: [
160
+ "GET /v1/agents/{agentId}/triggers",
161
+ "POST /v1/agents/{agentId}/triggers/{triggerId}/pause",
162
+ "POST /v1/agents/{agentId}/triggers/{triggerId}/resume",
163
+ "POST /v1/agents/{agentId}/triggers/{triggerId}/run",
164
+ "GET /v1/agents/{agentId}/runs",
165
+ "POST /v1/agents/{agentId}/triggers/{triggerId}/secret",
166
+ "POST /v1/webhooks/{endpointId}",
167
+ ],
168
+ },
169
+ {
170
+ name: "Memory",
171
+ group: "Agents",
172
+ description: "An Agent's memories: Markdown files under `/memories` it keeps across sessions, wakes and upgrades, shaped like Anthropic's memory stores. List and read them, write one whole (with a `content_sha256` precondition), delete one or a folder, and read the version every change leaves, with who made it. In `@nylorun/agents` and `nylorun agents memories`.",
173
+ operations: [
174
+ "GET /v1/agents/{agentId}/memories",
175
+ "GET /v1/agents/{agentId}/memories/{path}",
176
+ "PUT /v1/agents/{agentId}/memories/{path}",
177
+ "DELETE /v1/agents/{agentId}/memories/{path}",
178
+ "GET /v1/agents/{agentId}/memory_versions",
179
+ "GET /v1/agents/{agentId}/memory_versions/{versionId}",
180
+ ],
181
+ },
124
182
  {
125
183
  name: "Sessions API",
126
184
  group: "Sessions",
127
- description: "A conversation with an agent: open a session, send commands, follow its events, read its history. In `@nylorun/agents`.",
185
+ description: "A conversation with an Agent: open a session on it, send commands, follow its events, read its history. No session is special: an app that keeps one conversation going reuses its id. In `@nylorun/agents`.",
128
186
  operations: [
129
187
  "PUT /v1/sessions/{sessionId}",
130
188
  "GET /v1/sessions",
@@ -143,7 +201,7 @@ const RUNTIME_TAGS = [
143
201
  {
144
202
  name: "AG-UI",
145
203
  group: "Sessions",
146
- description: "The same sessions through AG-UI: a thread is one session per person, agent and thread, and each run is a turn.",
204
+ description: "The same sessions through AG-UI: a thread is one session per person, Agent and thread, and each run is a turn.",
147
205
  operations: [
148
206
  "POST /v1/ag-ui/agents/{agentId}",
149
207
  "GET /v1/ag-ui/agents/{agentId}/threads/{threadId}/events",
@@ -154,45 +212,9 @@ const RUNTIME_TAGS = [
154
212
  {
155
213
  name: "A2A",
156
214
  group: "Sessions",
157
- description: "The same sessions through Agent2Agent: a context is one session per subject, agent and `contextId`, and a task is one turn. The card is how an A2A client finds the agent.",
215
+ description: "The same sessions through Agent2Agent: a context is one session per subject, Agent and `contextId`, and a task is one turn; every caller's session runs on the Agent's disk and vault, so an app that serves several audiences makes an Agent for each. The card is how an A2A client finds the Agent.",
158
216
  operations: ["GET /v1/a2a/agents/{agentId}/card", "POST /v1/a2a/agents/{agentId}"],
159
217
  },
160
- {
161
- name: "Environments",
162
- group: "Environments",
163
- description: "Where sessions run: one sandbox (a virtual workspace, a pod with its own volume, or none) made from an environment manifest or inline fields and never changed after that, and one vault of secrets for its shell, MCP servers and HTTP tools. Create or find one, watch its lifecycle, stop, reset or delete it, and write its secrets. In `@nylorun/agents`.",
164
- operations: [
165
- "PUT /v1/environments/{environmentId}",
166
- "GET /v1/environments",
167
- "GET /v1/environments/{environmentId}",
168
- "GET /v1/environments/{environmentId}/events",
169
- "POST /v1/environments/{environmentId}/stop",
170
- "POST /v1/environments/{environmentId}/reset",
171
- "DELETE /v1/environments/{environmentId}",
172
- "GET /v1/environments/{environmentId}/secrets",
173
- "GET /v1/environments/{environmentId}/secrets/{name}",
174
- "PUT /v1/environments/{environmentId}/secrets/{name}",
175
- "DELETE /v1/environments/{environmentId}/secrets/{name}",
176
- "POST /v1/environments/{environmentId}/snapshot",
177
- "POST /v1/environments/{environmentId}/restore",
178
- "POST /v1/environments/{environmentId}/fork",
179
- "GET /v1/environments/{environmentId}/versions",
180
- "DELETE /v1/environments/{environmentId}/versions/{version}",
181
- ],
182
- },
183
- {
184
- name: "Environment manifests",
185
- group: "Environments",
186
- description: "Named, versioned definitions of a kind of Environment, saved like agents (protocol 14). Save one with a plan first (`?dryRun=true`) and `If-Match`; an Environment is made from one version and keeps it, so a save changes no Environment. In `@nylorun/agents`.",
187
- operations: [
188
- "PUT /v1/environment-manifests/{name}",
189
- "GET /v1/environment-manifests",
190
- "GET /v1/environment-manifests/{name}",
191
- "GET /v1/environment-manifests/{name}/versions",
192
- "GET /v1/environment-manifests/{name}/versions/{version}",
193
- "DELETE /v1/environment-manifests/{name}",
194
- ],
195
- },
196
218
  {
197
219
  name: "Artifacts",
198
220
  group: "Artifacts",
@@ -266,12 +288,12 @@ const MANAGEMENT_TAGS = [
266
288
  },
267
289
  {
268
290
  name: "Credential coverage",
269
- description: "Which secret of an Environment each of an agent's MCP servers and HTTP tools would get, before a session opens. The secrets themselves are written on the Runtime API. `admin.coverage`.",
291
+ description: "Which secret of an Agent's vault each of its manifest's MCP servers and HTTP tools would get, before a session opens. The secrets themselves are written on the Runtime API. `admin.coverage`.",
270
292
  operations: ["POST /v1/tenant/credential-coverage"],
271
293
  },
272
294
  {
273
295
  name: "MCP servers",
274
- description: "See a remote MCP server's tools before an agent names it, with an Environment's credential for its URL: their model names, sizes and annotations, or that it needs a person's sign-in. `admin.mcp`.",
296
+ description: "See a remote MCP server's tools before an agent names it, with an Agent's credential for its URL: their model names, sizes and annotations, or that it needs a person's sign-in. `admin.mcp`.",
275
297
  operations: ["POST /v1/tenant/mcp/preview"],
276
298
  },
277
299
  {
@@ -285,7 +307,7 @@ const MANAGEMENT_TAGS = [
285
307
  },
286
308
  {
287
309
  name: "Settings",
288
- description: "The Tenant's sandbox backend, Environment, artifact and skill settings, and its Runtime limits. `admin.settings`.",
310
+ description: "The Tenant's sandbox backend, Agent compute, artifact and skill settings, and its Runtime limits. `admin.settings`.",
289
311
  operations: [
290
312
  "GET /v1/tenant/sandbox",
291
313
  "PUT /v1/tenant/sandbox",
@@ -392,7 +414,7 @@ function documents() {
392
414
  built = {
393
415
  runtime: document(all, RUNTIME_TAGS, {
394
416
  title: "Nylorun Runtime API",
395
- description: "For developers: agents, sessions (with AG-UI and A2A), Environments and artifacts, through `@nylorun/agents`. Every Tenant serves it on its one URL, beside the Management API (`/openapi/management.json`). Every request sends `Nylorun-Protocol`. Who may call each operation is its `security` and its `x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields: an application key (servers) or a trusted issuer's token (browsers and apps).",
417
+ description: "For developers: agent manifests, Agents, sessions (with AG-UI and A2A) and artifacts, through `@nylorun/agents`. Every Tenant serves it on its one URL, beside the Management API (`/openapi/management.json`). Every request sends `Nylorun-Protocol`. Who may call each operation is its `security` and its `x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields: an application key (servers) or a trusted issuer's token (browsers and apps).",
396
418
  }),
397
419
  management: document(all, MANAGEMENT_TAGS, {
398
420
  title: "Nylorun Management API",
@@ -1,20 +1,20 @@
1
- import { type EnvironmentRestore, type EnvironmentSnapshot, type EnvironmentVersions } from "@nylorun/core/contracts";
1
+ import { type AgentRestore, type AgentSnapshot, type AgentDiskVersions } from "@nylorun/core/contracts";
2
2
  import type { Tx } from "../store/types.js";
3
3
  import type { TenantContext } from "../tenant/context.js";
4
- import { assertReaches } from "../tenant/environments.js";
4
+ import { assertReaches } from "../tenant/agents.js";
5
5
  /** The name of an Environment's disk artifact. */
6
6
  export declare const DISK_ARTIFACT_NAME = "disk";
7
7
  /**
8
- * `POST /v1/environments/{id}/snapshot`: a new version of the Environment's disk, between turns.
8
+ * `POST /v1/agents/{id}/snapshot`: a new version of the Environment's disk, between turns.
9
9
  * A pod that is stopped is started first. An unchanged disk makes no version.
10
10
  */
11
- export declare function snapshotEnvironment(ctx: TenantContext, id: string, scope: Parameters<typeof assertReaches>[0]): Promise<EnvironmentSnapshot>;
11
+ export declare function snapshotEnvironment(ctx: TenantContext, id: string, scope: Parameters<typeof assertReaches>[0]): Promise<AgentSnapshot>;
12
12
  /**
13
- * `POST /v1/environments/{id}/restore`: puts disk version `version` back. A virtual
13
+ * `POST /v1/agents/{id}/restore`: puts disk version `version` back. A virtual
14
14
  * Environment's workspace is rewritten at once; a pod gets a new volume, seeded from the version
15
15
  * before any turn (`status: pending` until then).
16
16
  */
17
- export declare function restoreEnvironment(ctx: TenantContext, id: string, scope: Parameters<typeof assertReaches>[0], version: number | "latest"): Promise<EnvironmentRestore>;
17
+ export declare function restoreEnvironment(ctx: TenantContext, id: string, scope: Parameters<typeof assertReaches>[0], version: number | "latest"): Promise<AgentRestore>;
18
18
  /**
19
19
  * Writes Environment `id`'s pending seed (`disk_seed`) into its disk, once its pod's engine is
20
20
  * connected (or at once for a virtual one), then clears the seed and the lease. Started after a
@@ -37,14 +37,14 @@ export declare function forkSeedOf(t: Tx, from: {
37
37
  artifactId: string;
38
38
  version: number;
39
39
  }>;
40
- /** `GET /v1/environments/{id}/versions`: its disk versions, newest first. */
41
- export declare function environmentVersions(ctx: TenantContext, id: string, scope: Parameters<typeof assertReaches>[0]): Promise<EnvironmentVersions>;
40
+ /** `GET /v1/agents/{id}/versions`: its disk versions, newest first. */
41
+ export declare function environmentVersions(ctx: TenantContext, id: string, scope: Parameters<typeof assertReaches>[0]): Promise<AgentDiskVersions>;
42
42
  /**
43
- * `DELETE /v1/environments/{id}/versions/{n}`: deletes one disk version, and the stored files no
43
+ * `DELETE /v1/agents/{id}/versions/{n}`: deletes one disk version, and the stored files no
44
44
  * other version names. Refused while a seed reads it.
45
45
  */
46
46
  export declare function deleteEnvironmentVersion(ctx: TenantContext, id: string, scope: Parameters<typeof assertReaches>[0], version: number): Promise<{
47
- environmentId: string;
47
+ agentId: string;
48
48
  version: number;
49
49
  deleted: boolean;
50
50
  }>;
@@ -18,14 +18,14 @@
18
18
  * until the engine has run that setup on the written disk; turns wait for both.
19
19
  *
20
20
  * Nothing here runs inside a transaction but the lease and the commit. No event type is new:
21
- * a restore or recovery is `environment.reset` with `reason` and `from`.
21
+ * a restore or recovery is `agent.reset` with `reason` and `from`.
22
22
  */
23
23
  import { createHash, randomUUID } from "node:crypto";
24
24
  import { newArtifactId } from "@nylorun/core/compatibility";
25
25
  import { FOLDER_CONTENT_TYPE, FOLDER_MANIFEST_FORMAT_V2, } from "@nylorun/core/contracts";
26
26
  import { sandboxSpecOf } from "../sandbox/resolve.js";
27
27
  import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
28
- import { assertReaches, diskHeld, liveEnvironment, newVolume, wakePod, } from "../tenant/environments.js";
28
+ import { assertReaches, diskHeld, liveEnvironment, newVolume, wakePod, } from "../tenant/agents.js";
29
29
  import { fail, failRetryLater } from "../tenant/http.js";
30
30
  import { hostTuning } from "../tuning.js";
31
31
  import { artifactQuota, totalSubject } from "./quota.js";
@@ -63,25 +63,25 @@ async function diskTarget(ctx, environment) {
63
63
  spec,
64
64
  };
65
65
  }
66
- /** One disk operation through the workspace, or a `409 environment_unavailable`. */
66
+ /** One disk operation through the workspace, or a `409 agent_unavailable`. */
67
67
  async function diskOp(ctx, target, op) {
68
68
  const answer = await ctx.sandbox.disk(target.ref, target.spec, op, new AbortController().signal);
69
69
  if ("code" in answer)
70
- return fail(409, `The disk could not be reached: ${answer.message}`, { code: "environment_unavailable" });
70
+ return fail(409, `The disk could not be reached: ${answer.message}`, { code: "agent_unavailable" });
71
71
  return answer;
72
72
  }
73
73
  /**
74
- * Takes the disk lease of Environment `id` for `kind`: refused (`409 environment_busy`) while a
74
+ * Takes the disk lease of Environment `id` for `kind`: refused (`409 agent_busy`) while a
75
75
  * turn runs or another copy holds it; `400` for kind `none`, which has no disk.
76
76
  */
77
77
  async function lease(ctx, id, kind, leaseMs, version) {
78
78
  return ctx.store.tx(async (t) => {
79
- const environment = (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Environment not found");
79
+ const environment = (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Agent not found");
80
80
  if (environment.kind === "none")
81
- fail(400, `Environment ${id} is of kind none: it has no disk.`);
81
+ fail(400, `Agent ${id} is of kind none: it has no disk.`);
82
82
  const busy = (await t.sessionsOnEnvironment(id)).some((session) => session.activeTurnId);
83
83
  if (busy || diskHeld(environment))
84
- failRetryLater(`Environment ${id} has a turn running or its disk is being copied. Try again shortly.`, "environment_busy");
84
+ failRetryLater(`Agent ${id} has a turn running or its disk is being copied. Try again shortly.`, "agent_busy");
85
85
  const now = new Date();
86
86
  const operation = {
87
87
  kind,
@@ -134,7 +134,7 @@ async function awaitEngine(ctx, id, waitMs) {
134
134
  const deadline = Date.now() + waitMs;
135
135
  while (!ctx.harness.hosting(id)) {
136
136
  if (Date.now() > deadline)
137
- failRetryLater(`Environment ${id}'s pod did not start within ${waitMs / 1000} s (the Runtime limit diskPodWaitMs).`, "environment_unavailable");
137
+ failRetryLater(`Agent ${id}'s pod did not start within ${waitMs / 1000} s (the Runtime limit diskPodWaitMs).`, "agent_unavailable");
138
138
  await new Promise((resolve) => setTimeout(resolve, hostTuning().pods.enginePollMs));
139
139
  }
140
140
  }
@@ -158,7 +158,7 @@ async function readFile(ctx, target, path, rangeBytes) {
158
158
  return { bytes: Buffer.concat(chunks), sha256: hash.digest("hex") };
159
159
  }
160
160
  /**
161
- * `POST /v1/environments/{id}/snapshot`: a new version of the Environment's disk, between turns.
161
+ * `POST /v1/agents/{id}/snapshot`: a new version of the Environment's disk, between turns.
162
162
  * A pod that is stopped is started first. An unchanged disk makes no version.
163
163
  */
164
164
  export async function snapshotEnvironment(ctx, id, scope) {
@@ -226,7 +226,7 @@ export async function snapshotEnvironment(ctx, id, scope) {
226
226
  durationMs: Date.now() - started,
227
227
  });
228
228
  return {
229
- environmentId: id,
229
+ agentId: id,
230
230
  artifactId: committed.artifact.id,
231
231
  version: committed.version.version,
232
232
  format: FOLDER_MANIFEST_FORMAT_V2,
@@ -265,7 +265,7 @@ async function commitDiskVersion(ctx, id, entries, epoch) {
265
265
  const key = `artifacts/${artifactId}/${randomUUID()}`;
266
266
  await ctx.blobs.put(key, manifest, { contentType: FOLDER_CONTENT_TYPE });
267
267
  return ctx.store.tx(async (t) => {
268
- (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Environment not found");
268
+ (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Agent not found");
269
269
  const quota = await artifactQuota(t, id);
270
270
  await t.lockArtifactQuota();
271
271
  const shas = [...files.keys()];
@@ -274,7 +274,7 @@ async function commitDiskVersion(ctx, id, entries, epoch) {
274
274
  if ((await contentEpoch(t)) !== epoch)
275
275
  for (const sha of fresh)
276
276
  if (!(await ctx.blobs.head(contentKey(sha))))
277
- fail(409, "A file's bytes were removed while the snapshot ran; try again.", { code: "environment_busy" });
277
+ fail(409, "A file's bytes were removed while the snapshot ran; try again.", { code: "agent_busy" });
278
278
  for (const total of quota.totals) {
279
279
  // A file adds bytes to a total unless an artifact it counts already holds the file.
280
280
  const scope = total.environmentId === undefined ? undefined : { environmentId: total.environmentId };
@@ -397,11 +397,11 @@ async function versionOf(t, id, version) {
397
397
  const artifact = await t.diskArtifact(id);
398
398
  const number = version === "latest" ? artifact?.latestVersion : version;
399
399
  if (!artifact || !number || !(await t.artifactVersion(artifact.id, number)))
400
- return fail(404, `Environment ${id} has no disk version ${version}`);
400
+ return fail(404, `Agent ${id} has no disk version ${version}`);
401
401
  return { artifactId: artifact.id, version: number };
402
402
  }
403
403
  /**
404
- * `POST /v1/environments/{id}/restore`: puts disk version `version` back. A virtual
404
+ * `POST /v1/agents/{id}/restore`: puts disk version `version` back. A virtual
405
405
  * Environment's workspace is rewritten at once; a pod gets a new volume, seeded from the version
406
406
  * before any turn (`status: pending` until then).
407
407
  */
@@ -410,16 +410,16 @@ export async function restoreEnvironment(ctx, id, scope, version) {
410
410
  const seed = await ctx.store.tx((t) => versionOf(t, id, version));
411
411
  const found = await ctx.store.tx((t) => liveEnvironment(t, id));
412
412
  if (!found)
413
- return fail(404, "Environment not found");
413
+ return fail(404, "Agent not found");
414
414
  if (found.kind === "pod") {
415
415
  const volumeGeneration = await ctx.store.tx(async (t) => {
416
- const environment = (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Environment not found");
416
+ const environment = (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Agent not found");
417
417
  const busy = (await t.sessionsOnEnvironment(id)).some((session) => session.activeTurnId);
418
418
  if (busy || diskHeld(environment) || environment.pod.retiring !== undefined)
419
- failRetryLater(`Environment ${id} has a turn running, its disk is being copied or its last volume is still being deleted.`, "environment_busy");
419
+ failRetryLater(`Agent ${id} has a turn running, its disk is being copied or its last volume is still being deleted.`, "agent_busy");
420
420
  return newVolume(ctx, t, environment, { seed, reason: "restore" });
421
421
  });
422
- return { environmentId: id, restored: seed.version, status: "pending", volumeGeneration };
422
+ return { agentId: id, restored: seed.version, status: "pending", volumeGeneration };
423
423
  }
424
424
  const limits = await copyLimits(ctx, found);
425
425
  const environment = await lease(ctx, id, "restore", limits.leaseMs, seed.version);
@@ -430,7 +430,7 @@ export async function restoreEnvironment(ctx, id, scope, version) {
430
430
  finally {
431
431
  await beat.stop();
432
432
  }
433
- return { environmentId: id, restored: seed.version, status: "done" };
433
+ return { agentId: id, restored: seed.version, status: "done" };
434
434
  }
435
435
  /** Seeds that run in this process, so the sweep starts no second copy. */
436
436
  const running = new Set();
@@ -516,15 +516,15 @@ export async function recoverLostVolume(ctx, id) {
516
516
  export async function forkSeedOf(t, from) {
517
517
  return versionOf(t, from.environment, from.version);
518
518
  }
519
- /** `GET /v1/environments/{id}/versions`: its disk versions, newest first. */
519
+ /** `GET /v1/agents/{id}/versions`: its disk versions, newest first. */
520
520
  export async function environmentVersions(ctx, id, scope) {
521
521
  assertReaches(scope, id);
522
522
  return ctx.store.tx(async (t) => {
523
- (await liveEnvironment(t, id)) ?? fail(404, "Environment not found");
523
+ (await liveEnvironment(t, id)) ?? fail(404, "Agent not found");
524
524
  const artifact = await t.diskArtifact(id);
525
525
  const versions = artifact ? await t.artifactVersions(artifact.id) : [];
526
526
  return {
527
- environmentId: id,
527
+ agentId: id,
528
528
  artifactId: artifact?.id ?? null,
529
529
  latest: artifact && artifact.latestVersion > 0 ? artifact.latestVersion : null,
530
530
  versions: versions
@@ -534,18 +534,18 @@ export async function environmentVersions(ctx, id, scope) {
534
534
  });
535
535
  }
536
536
  /**
537
- * `DELETE /v1/environments/{id}/versions/{n}`: deletes one disk version, and the stored files no
537
+ * `DELETE /v1/agents/{id}/versions/{n}`: deletes one disk version, and the stored files no
538
538
  * other version names. Refused while a seed reads it.
539
539
  */
540
540
  export async function deleteEnvironmentVersion(ctx, id, scope, version) {
541
541
  assertReaches(scope, id);
542
542
  const blobKey = await ctx.store.tx(async (t) => {
543
- const environment = (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Environment not found");
543
+ const environment = (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Agent not found");
544
544
  const artifact = await t.diskArtifact(id, { lock: true });
545
545
  if (!artifact)
546
546
  return undefined;
547
547
  if (environment.diskSeed?.artifactId === artifact.id && environment.diskSeed.version === version)
548
- failRetryLater(`A new volume of Environment ${id} is being seeded from version ${version}.`, "environment_busy");
548
+ failRetryLater(`A new volume of Agent ${id} is being seeded from version ${version}.`, "agent_busy");
549
549
  await t.lockArtifactQuota();
550
550
  const content = await t.artifactContentShas({ artifactId: artifact.id });
551
551
  const key = await t.deleteArtifactVersion(artifact.id, version);
@@ -555,7 +555,7 @@ export async function deleteEnvironmentVersion(ctx, id, scope, version) {
555
555
  });
556
556
  if (blobKey !== undefined)
557
557
  await ctx.blobs.delete(blobKey).catch(() => undefined);
558
- return { environmentId: id, version, deleted: blobKey !== undefined };
558
+ return { agentId: id, version, deleted: blobKey !== undefined };
559
559
  }
560
560
  /**
561
561
  * Deletes Environment `id`'s disk artifact with its versions, in the transaction that deletes the
@@ -30,7 +30,7 @@ export declare function artifactUsage(t: Tx, quota: ArtifactQuota): Promise<Arti
30
30
  export declare function tightest(usage: readonly ArtifactUsage[]): ArtifactUsage & {
31
31
  readonly roomBytes: number;
32
32
  };
33
- /** Whose artifacts a total counts: `The Tenant's artifacts`, or `Environment <id>'s artifacts`. */
33
+ /** Whose artifacts a total counts: `The Tenant's artifacts`, or `Agent <id>'s artifacts`. */
34
34
  export declare function totalSubject(total: ArtifactTotal): string;
35
35
  /** The Environment a session's artifacts belong to, if it names one. */
36
36
  export declare function environmentOfSession(t: Tx, sessionId: string | null): Promise<string | undefined>;
@@ -32,9 +32,9 @@ export function tightest(usage) {
32
32
  best = entry;
33
33
  return { ...best, roomBytes: best.total.limitBytes - best.usedBytes };
34
34
  }
35
- /** Whose artifacts a total counts: `The Tenant's artifacts`, or `Environment <id>'s artifacts`. */
35
+ /** Whose artifacts a total counts: `The Tenant's artifacts`, or `Agent <id>'s artifacts`. */
36
36
  export function totalSubject(total) {
37
- return total.environmentId === undefined ? "The Tenant's artifacts" : `Environment ${total.environmentId}'s artifacts`;
37
+ return total.environmentId === undefined ? "The Tenant's artifacts" : `Agent ${total.environmentId}'s artifacts`;
38
38
  }
39
39
  /** The Environment a session's artifacts belong to, if it names one. */
40
40
  export async function environmentOfSession(t, sessionId) {
@@ -82,7 +82,7 @@ export function artifactView(row, versions) {
82
82
  name: row.name,
83
83
  contentType: row.contentType,
84
84
  ...(row.sessionId === null ? {} : { sessionId: row.sessionId }),
85
- ...(row.environmentId === null ? {} : { environmentId: row.environmentId }),
85
+ ...(row.environmentId === null ? {} : { agentId: row.environmentId }),
86
86
  latestVersion: row.latestVersion,
87
87
  ...(row.labelsJson === null ? {} : { labels: JSON.parse(row.labelsJson) }),
88
88
  createdAt: row.createdAt,
@@ -332,7 +332,7 @@ export async function deleteArtifact(ctx, id, access) {
332
332
  const keys = await ctx.store.tx(async (t) => {
333
333
  const found = await readableArtifact(t, id, access);
334
334
  if (found.environmentId !== null)
335
- fail(409, `Artifact ${id} holds Environment ${found.environmentId}'s disk versions: delete them with DELETE /v1/environments/{id}/versions/{version}, or delete the Environment.`);
335
+ fail(409, `Artifact ${id} holds Agent ${found.environmentId}'s disk versions: delete them with DELETE /v1/agents/{id}/versions/{version}, or delete the Agent.`);
336
336
  if (found.sessionId !== null)
337
337
  await lockedSession(t, found.sessionId, access);
338
338
  if (found.kind === "folder")