@nylorun/runtime 0.26.0-beta → 0.28.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 (379) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +18 -14
  3. package/dist/adapters/execution/restate.d.ts +15 -18
  4. package/dist/adapters/execution/restate.js +31 -27
  5. package/dist/adapters/replication/pgoutput.d.ts +1 -1
  6. package/dist/adapters/replication/pgoutput.js +3 -2
  7. package/dist/adapters/sandbox/local.js +8 -5
  8. package/dist/adapters/sandbox/virtual.js +2 -2
  9. package/dist/adapters/streams/s2.d.ts +1 -1
  10. package/dist/adapters/streams/s2.js +18 -14
  11. package/dist/api/a2a/card.d.ts +4 -3
  12. package/dist/api/a2a/card.js +4 -4
  13. package/dist/api/a2a/endpoint.js +3 -4
  14. package/dist/api/a2a/protocol.js +4 -0
  15. package/dist/api/a2a/routes.d.ts +2 -6
  16. package/dist/api/a2a/routes.js +30 -25
  17. package/dist/api/ag-ui/endpoint.js +2 -3
  18. package/dist/api/ag-ui/routes.js +8 -11
  19. package/dist/api/components.d.ts +6789 -3405
  20. package/dist/api/components.js +43 -30
  21. package/dist/api/gate/keys.js +7 -4
  22. package/dist/api/gate/routes.d.ts +6 -2
  23. package/dist/api/gate/routes.js +15 -11
  24. package/dist/api/http/app.js +9 -4
  25. package/dist/api/http/body.d.ts +17 -3
  26. package/dist/api/http/body.js +35 -7
  27. package/dist/api/http/respond.js +6 -2
  28. package/dist/api/http/routes/access.js +5 -4
  29. package/dist/api/http/routes/agent-manifests.d.ts +10 -0
  30. package/dist/api/http/routes/agent-manifests.js +135 -0
  31. package/dist/api/http/routes/agents.d.ts +11 -0
  32. package/dist/api/http/routes/agents.js +418 -0
  33. package/dist/api/http/routes/artifacts.js +21 -9
  34. package/dist/api/http/routes/coverage.d.ts +4 -4
  35. package/dist/api/http/routes/coverage.js +13 -7
  36. package/dist/api/http/routes/files.d.ts +2 -1
  37. package/dist/api/http/routes/files.js +31 -5
  38. package/dist/api/http/routes/mcp.js +3 -3
  39. package/dist/api/http/routes/me.d.ts +1 -1
  40. package/dist/api/http/routes/me.js +1 -2
  41. package/dist/api/http/routes/memories.d.ts +9 -0
  42. package/dist/api/http/routes/memories.js +152 -0
  43. package/dist/api/http/routes/reads.d.ts +10 -111
  44. package/dist/api/http/routes/reads.js +51 -29
  45. package/dist/api/http/routes/sessions.js +26 -54
  46. package/dist/api/http/routes/tenant.d.ts +2 -2
  47. package/dist/api/http/routes/tenant.js +77 -24
  48. package/dist/api/http/routes/triggers.d.ts +10 -0
  49. package/dist/api/http/routes/triggers.js +180 -0
  50. package/dist/api/inbound-files.d.ts +3 -9
  51. package/dist/api/inbound-files.js +18 -24
  52. package/dist/api/openapi.js +73 -47
  53. package/dist/artifacts/disk.d.ts +10 -14
  54. package/dist/artifacts/disk.js +99 -95
  55. package/dist/artifacts/files.d.ts +13 -4
  56. package/dist/artifacts/files.js +27 -9
  57. package/dist/artifacts/folder-upload.d.ts +75 -0
  58. package/dist/artifacts/folder-upload.js +263 -0
  59. package/dist/artifacts/folders.d.ts +14 -8
  60. package/dist/artifacts/folders.js +19 -11
  61. package/dist/artifacts/links.js +7 -6
  62. package/dist/artifacts/parts.d.ts +9 -14
  63. package/dist/artifacts/parts.js +11 -9
  64. package/dist/artifacts/pipe.d.ts +15 -0
  65. package/dist/artifacts/pipe.js +71 -0
  66. package/dist/artifacts/quota.d.ts +36 -0
  67. package/dist/artifacts/quota.js +44 -0
  68. package/dist/artifacts/service.d.ts +25 -1
  69. package/dist/artifacts/service.js +72 -27
  70. package/dist/blob/fs.js +2 -1
  71. package/dist/blob/s3.d.ts +6 -2
  72. package/dist/blob/s3.js +9 -7
  73. package/dist/blob/types.d.ts +1 -1
  74. package/dist/blob/types.js +1 -1
  75. package/dist/core/flow-host.d.ts +0 -1
  76. package/dist/core/limits.d.ts +11 -35
  77. package/dist/core/limits.js +5 -73
  78. package/dist/core/sandbox-routes.js +0 -1
  79. package/dist/core/turn-manifest.d.ts +1 -2
  80. package/dist/core/turn-manifest.js +4 -28
  81. package/dist/execution/memory.d.ts +8 -5
  82. package/dist/execution/memory.js +8 -6
  83. package/dist/execution/types.d.ts +1 -6
  84. package/dist/execution/types.js +0 -5
  85. package/dist/gates/contract.d.ts +3 -2
  86. package/dist/gates/contract.js +1 -1
  87. package/dist/gates/egress-credentials.js +5 -4
  88. package/dist/gates/egress.d.ts +13 -13
  89. package/dist/gates/egress.js +19 -21
  90. package/dist/gates/fixture-model.js +13 -2
  91. package/dist/gates/handler.d.ts +1 -3
  92. package/dist/gates/handler.js +4 -3
  93. package/dist/gates/http-client.d.ts +4 -3
  94. package/dist/gates/http-client.js +5 -7
  95. package/dist/gates/http-tool.d.ts +24 -4
  96. package/dist/gates/http-tool.js +41 -13
  97. package/dist/gates/in-process.d.ts +2 -1
  98. package/dist/gates/in-process.js +17 -9
  99. package/dist/gates/inflight.d.ts +2 -2
  100. package/dist/gates/inflight.js +3 -2
  101. package/dist/gates/journal.js +5 -7
  102. package/dist/gates/mcp-handler.d.ts +4 -3
  103. package/dist/gates/mcp-handler.js +15 -5
  104. package/dist/gates/meter.js +6 -5
  105. package/dist/gates/model-gate.d.ts +11 -2
  106. package/dist/gates/policy.d.ts +34 -6
  107. package/dist/gates/policy.js +66 -18
  108. package/dist/gates/provider-gate.d.ts +22 -0
  109. package/dist/gates/provider-gate.js +30 -0
  110. package/dist/gates/tool-calls.d.ts +1 -3
  111. package/dist/gates/tool-calls.js +2 -3
  112. package/dist/gates/tool-client.d.ts +1 -1
  113. package/dist/gates/tool-client.js +5 -6
  114. package/dist/gates/tool-contract.d.ts +8 -5
  115. package/dist/gates/tool-contract.js +1 -1
  116. package/dist/gates/tool-gate.d.ts +18 -7
  117. package/dist/gates/tool-gate.js +49 -7
  118. package/dist/harness/calls.d.ts +27 -25
  119. package/dist/harness/calls.js +40 -46
  120. package/dist/harness/executors.d.ts +10 -3
  121. package/dist/harness/executors.js +53 -23
  122. package/dist/harness/main.js +2 -2
  123. package/dist/harness/pod.d.ts +2 -2
  124. package/dist/harness/pod.js +7 -6
  125. package/dist/harness/run-tokens.d.ts +4 -1
  126. package/dist/harness/run-tokens.js +6 -2
  127. package/dist/harness/service.d.ts +17 -14
  128. package/dist/harness/service.js +21 -35
  129. package/dist/harness/setup.d.ts +0 -2
  130. package/dist/harness/setup.js +21 -15
  131. package/dist/harness/ws-client.d.ts +2 -2
  132. package/dist/harness/ws-client.js +7 -5
  133. package/dist/harness/ws-port.d.ts +3 -4
  134. package/dist/harness/ws-port.js +3 -4
  135. package/dist/harness-api/artifacts.d.ts +20 -0
  136. package/dist/harness-api/artifacts.js +478 -0
  137. package/dist/harness-api/in-process.d.ts +2 -2
  138. package/dist/harness-api/in-process.js +18 -16
  139. package/dist/harness-api/record.d.ts +10 -9
  140. package/dist/harness-api/record.js +113 -157
  141. package/dist/harness-api/renew.d.ts +1 -1
  142. package/dist/harness-api/renew.js +2 -2
  143. package/dist/harness-api/server.d.ts +1 -16
  144. package/dist/harness-api/server.js +86 -73
  145. package/dist/harness-api/start.d.ts +27 -5
  146. package/dist/harness-api/start.js +102 -19
  147. package/dist/harness-api/tool-journal.d.ts +15 -10
  148. package/dist/harness-api/tool-journal.js +26 -24
  149. package/dist/harness-api/workspace.d.ts +0 -4
  150. package/dist/harness-api/workspace.js +4 -47
  151. package/dist/harness-api/ws-server.js +10 -10
  152. package/dist/host/create-host.js +2 -3
  153. package/dist/host/execution.js +2 -3
  154. package/dist/host/gates.d.ts +2 -9
  155. package/dist/host/gates.js +15 -12
  156. package/dist/host/http.d.ts +2 -4
  157. package/dist/host/http.js +5 -29
  158. package/dist/host/main.js +10 -3
  159. package/dist/host/operate.js +12 -3
  160. package/dist/host/stack-config.d.ts +11 -0
  161. package/dist/host/stack-config.js +21 -1
  162. package/dist/index.d.ts +0 -1
  163. package/dist/index.js +0 -1
  164. package/dist/infra/index.js +2 -1
  165. package/dist/infra/readiness.d.ts +0 -2
  166. package/dist/infra/readiness.js +4 -3
  167. package/dist/keys/client.d.ts +1 -0
  168. package/dist/keys/client.js +2 -3
  169. package/dist/keys/contract.d.ts +0 -2
  170. package/dist/keys/contract.js +0 -2
  171. package/dist/keys/egress-ca.d.ts +0 -2
  172. package/dist/keys/egress-ca.js +2 -3
  173. package/dist/keys/keys.d.ts +15 -4
  174. package/dist/keys/keys.js +22 -1
  175. package/dist/keys/webhooks.d.ts +25 -0
  176. package/dist/keys/webhooks.js +62 -0
  177. package/dist/keys/x509.d.ts +8 -4
  178. package/dist/keys/x509.js +14 -5
  179. package/dist/management-openapi.json +3043 -1954
  180. package/dist/mcp/connect.d.ts +35 -26
  181. package/dist/mcp/connect.js +112 -141
  182. package/dist/mcp/pool.d.ts +27 -22
  183. package/dist/mcp/pool.js +34 -48
  184. package/dist/mcp/preview.d.ts +12 -5
  185. package/dist/mcp/preview.js +43 -26
  186. package/dist/mcp/snapshot.d.ts +35 -86
  187. package/dist/mcp/snapshot.js +93 -135
  188. package/dist/memory/service.d.ts +97 -0
  189. package/dist/memory/service.js +335 -0
  190. package/dist/memory/tool.d.ts +36 -0
  191. package/dist/memory/tool.js +84 -0
  192. package/dist/model/auth-store.js +5 -3
  193. package/dist/model/capabilities.d.ts +1 -1
  194. package/dist/model/catalog.d.ts +9 -36
  195. package/dist/model/catalog.js +6 -33
  196. package/dist/model/descriptor.d.ts +51 -0
  197. package/dist/model/descriptor.js +83 -0
  198. package/dist/model/models.d.ts +0 -3
  199. package/dist/model/models.js +3 -5
  200. package/dist/model/native-blocks.js +1 -1
  201. package/dist/model/pi-model.d.ts +16 -5
  202. package/dist/model/pi-model.js +86 -105
  203. package/dist/openapi.json +13653 -9986
  204. package/dist/reads/cursor.js +3 -1
  205. package/dist/reads/types.d.ts +3 -3
  206. package/dist/record/agent.d.ts +27 -0
  207. package/dist/record/agent.js +46 -0
  208. package/dist/record/append.d.ts +34 -2
  209. package/dist/record/append.js +50 -1
  210. package/dist/record/index.d.ts +2 -2
  211. package/dist/record/index.js +2 -2
  212. package/dist/redact.d.ts +1 -1
  213. package/dist/redact.js +3 -2
  214. package/dist/sandbox/egress-token.d.ts +3 -3
  215. package/dist/sandbox/egress-token.js +3 -3
  216. package/dist/sandbox/join.js +8 -7
  217. package/dist/sandbox/manager.d.ts +41 -41
  218. package/dist/sandbox/manager.js +112 -44
  219. package/dist/sandbox/placement.d.ts +2 -2
  220. package/dist/sandbox/placement.js +7 -10
  221. package/dist/sandbox/pods/client.d.ts +1 -10
  222. package/dist/sandbox/pods/client.js +8 -4
  223. package/dist/sandbox/pods/lifecycle.d.ts +25 -17
  224. package/dist/sandbox/pods/lifecycle.js +51 -29
  225. package/dist/sandbox/pods/network-gate.d.ts +4 -5
  226. package/dist/sandbox/pods/network-gate.js +7 -7
  227. package/dist/sandbox/pods/reconcile.js +14 -3
  228. package/dist/sandbox/pods/spec.js +8 -5
  229. package/dist/sandbox/policy.d.ts +7 -1
  230. package/dist/sandbox/policy.js +10 -3
  231. package/dist/sandbox/resolve.d.ts +11 -6
  232. package/dist/sandbox/resolve.js +19 -9
  233. package/dist/sandbox/setup.d.ts +6 -2
  234. package/dist/sandbox/setup.js +47 -14
  235. package/dist/sandbox/share.d.ts +5 -14
  236. package/dist/sandbox/share.js +5 -34
  237. package/dist/sandbox/skills.d.ts +7 -13
  238. package/dist/sandbox/skills.js +16 -31
  239. package/dist/sandbox/tenant-config.d.ts +2 -2
  240. package/dist/sandbox/tenant-config.js +4 -3
  241. package/dist/sandbox/tools.d.ts +7 -1
  242. package/dist/sandbox/tools.js +26 -26
  243. package/dist/sandbox/types.d.ts +10 -3
  244. package/dist/sandbox/types.js +9 -0
  245. package/dist/store/postgres/connect.d.ts +6 -5
  246. package/dist/store/postgres/connect.js +14 -11
  247. package/dist/store/postgres/control.js +13 -13
  248. package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
  249. package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
  250. package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
  251. package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
  252. package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
  253. package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
  254. package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
  255. package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
  256. package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
  257. package/dist/store/postgres/drizzle/meta/_journal.json +63 -0
  258. package/dist/store/postgres/reads.js +17 -82
  259. package/dist/store/postgres/record-writer.d.ts +4 -4
  260. package/dist/store/postgres/record-writer.js +9 -9
  261. package/dist/store/postgres/schema.d.ts +1382 -151
  262. package/dist/store/postgres/schema.js +191 -37
  263. package/dist/store/postgres/store.js +384 -105
  264. package/dist/store/postgres/tenant.js +1 -2
  265. package/dist/store/types.d.ts +172 -57
  266. package/dist/store/types.js +0 -1
  267. package/dist/streams/relay/core.d.ts +3 -3
  268. package/dist/streams/relay/core.js +9 -10
  269. package/dist/tenant/advance.js +41 -39
  270. package/dist/tenant/agent-manifests.d.ts +72 -0
  271. package/dist/tenant/agent-manifests.js +359 -0
  272. package/dist/tenant/agent-versions.d.ts +126 -0
  273. package/dist/tenant/agent-versions.js +389 -0
  274. package/dist/tenant/agents.d.ts +230 -0
  275. package/dist/tenant/{environments.js → agents.js} +493 -328
  276. package/dist/tenant/auth.d.ts +1 -1
  277. package/dist/tenant/auth.js +3 -3
  278. package/dist/tenant/commands.d.ts +63 -2
  279. package/dist/tenant/commands.js +240 -90
  280. package/dist/tenant/context.d.ts +57 -33
  281. package/dist/tenant/context.js +6 -2
  282. package/dist/tenant/definition-files.d.ts +25 -4
  283. package/dist/tenant/definition-files.js +85 -47
  284. package/dist/tenant/effects.js +15 -51
  285. package/dist/tenant/ephemeral.js +2 -1
  286. package/dist/tenant/history.d.ts +1 -1
  287. package/dist/tenant/history.js +1 -1
  288. package/dist/tenant/host-token.d.ts +3 -3
  289. package/dist/tenant/host-token.js +11 -10
  290. package/dist/tenant/http.d.ts +5 -8
  291. package/dist/tenant/http.js +9 -0
  292. package/dist/tenant/identity-file.d.ts +29 -6
  293. package/dist/tenant/identity-file.js +45 -15
  294. package/dist/tenant/issuers.d.ts +5 -5
  295. package/dist/tenant/issuers.js +34 -26
  296. package/dist/tenant/jwt.d.ts +8 -2
  297. package/dist/tenant/jwt.js +10 -2
  298. package/dist/tenant/limits.d.ts +87 -0
  299. package/dist/tenant/limits.js +172 -0
  300. package/dist/tenant/memories.d.ts +38 -0
  301. package/dist/tenant/memories.js +119 -0
  302. package/dist/tenant/module.d.ts +4 -1
  303. package/dist/tenant/module.js +9 -8
  304. package/dist/tenant/outbound.d.ts +11 -14
  305. package/dist/tenant/outbound.js +10 -15
  306. package/dist/tenant/reset.js +2 -2
  307. package/dist/tenant/run-grants.d.ts +6 -0
  308. package/dist/tenant/run-grants.js +7 -2
  309. package/dist/tenant/run-token.d.ts +0 -4
  310. package/dist/tenant/run-token.js +6 -11
  311. package/dist/tenant/runtime-limits.d.ts +24 -0
  312. package/dist/tenant/runtime-limits.js +27 -0
  313. package/dist/tenant/runtime.d.ts +1 -1
  314. package/dist/tenant/runtime.js +38 -43
  315. package/dist/tenant/session-streams.js +7 -6
  316. package/dist/tenant/session.d.ts +1 -1
  317. package/dist/tenant/session.js +1 -1
  318. package/dist/tenant/sessions.d.ts +28 -45
  319. package/dist/tenant/sessions.js +89 -209
  320. package/dist/tenant/signing-keys.d.ts +1 -1
  321. package/dist/tenant/signing-keys.js +5 -6
  322. package/dist/tenant/status.js +10 -10
  323. package/dist/tenant/streams.js +18 -20
  324. package/dist/tenant/sweep.d.ts +1 -21
  325. package/dist/tenant/sweep.js +51 -51
  326. package/dist/tenant/transcript.d.ts +70 -23
  327. package/dist/tenant/transcript.js +28 -22
  328. package/dist/tenant/triggers.d.ts +143 -0
  329. package/dist/tenant/triggers.js +715 -0
  330. package/dist/tenant/turn-timing.d.ts +11 -1
  331. package/dist/tenant/turn-timing.js +3 -1
  332. package/dist/tenant/types.d.ts +9 -14
  333. package/dist/tenant/worker.d.ts +4 -10
  334. package/dist/tenant/worker.js +10 -14
  335. package/dist/traces/ids.js +5 -1
  336. package/dist/traces/map.d.ts +10 -7
  337. package/dist/traces/map.js +16 -12
  338. package/dist/traces/otlp.d.ts +3 -3
  339. package/dist/traces/otlp.js +5 -3
  340. package/dist/traces/projector.d.ts +9 -4
  341. package/dist/traces/projector.js +13 -7
  342. package/dist/traces/read.js +6 -1
  343. package/dist/tuning.d.ts +390 -0
  344. package/dist/tuning.js +677 -0
  345. package/dist/vault/headers.js +4 -2
  346. package/dist/vault/service.d.ts +13 -8
  347. package/dist/vault/service.js +61 -17
  348. package/dist/version.d.ts +1 -1
  349. package/dist/version.js +1 -1
  350. package/package.json +13 -12
  351. package/dist/api/http/routes/environment-manifests.d.ts +0 -9
  352. package/dist/api/http/routes/environment-manifests.js +0 -118
  353. package/dist/api/http/routes/environments.d.ts +0 -11
  354. package/dist/api/http/routes/environments.js +0 -332
  355. package/dist/artifacts/export.d.ts +0 -8
  356. package/dist/artifacts/export.js +0 -204
  357. package/dist/artifacts/media-types.d.ts +0 -30
  358. package/dist/artifacts/media-types.js +0 -137
  359. package/dist/artifacts/workspace.d.ts +0 -60
  360. package/dist/artifacts/workspace.js +0 -45
  361. package/dist/media.d.ts +0 -10
  362. package/dist/media.js +0 -58
  363. package/dist/record/environment.d.ts +0 -25
  364. package/dist/record/environment.js +0 -44
  365. package/dist/sandbox/runtime-tools.d.ts +0 -9
  366. package/dist/sandbox/runtime-tools.js +0 -16
  367. package/dist/sandbox/session-sandbox.d.ts +0 -16
  368. package/dist/sandbox/session-sandbox.js +0 -77
  369. package/dist/tenant/artifact-tool.d.ts +0 -13
  370. package/dist/tenant/artifact-tool.js +0 -243
  371. package/dist/tenant/environment-manifests.d.ts +0 -57
  372. package/dist/tenant/environment-manifests.js +0 -200
  373. package/dist/tenant/environments.d.ts +0 -174
  374. package/dist/tenant/skill-tool.d.ts +0 -20
  375. package/dist/tenant/skill-tool.js +0 -67
  376. package/dist/tenant/tool-results.d.ts +0 -57
  377. package/dist/tenant/tool-results.js +0 -298
  378. package/dist/tenant/tool-search.d.ts +0 -39
  379. package/dist/tenant/tool-search.js +0 -103
@@ -0,0 +1,418 @@
1
+ import { z } from "zod";
2
+ import { AGENT_EVENT_TYPES, ForkAgentRequestSchema, AGENT_STATES, isAgentId, PutAgentRequestSchema, RestartAgentRequestSchema, RestoreAgentRequestSchema, } from "@nylorun/core/contracts";
3
+ import { deleteEnvironmentVersion, environmentVersions, restoreEnvironment, snapshotEnvironment, } from "../../../artifacts/disk.js";
4
+ import { DeleteAgentResponse, DeletedResponse, AgentPage, AgentRestore, AgentSnapshot, AgentDiskVersions, ForkAgentRequest, ForkAgentResponse, RestoreAgentRequest, RestartAgentRequest, AgentSecretView, AgentView, ListAgentEventsResponse, ListAgentSecretsResponse, ListAgentsResponse, PutAgentDryRunResponse, PutAgentRequest, PutAgentSecretRequest, PutAgentSecretValueRequest, } from "../../components.js";
5
+ import { fail } from "../../../tenant/http.js";
6
+ import { canonical } from "../../../store/canonical.js";
7
+ import { actorOf, assertReaches, deleteAgent, agentEventsOf, forkAgent, getAgent, listAgents, planAgentPut, putAgent, resetAgent, restartAgent, secretRequestFor, secretWritten, stopAgent, } from "../../../tenant/agents.js";
8
+ import { bodyLimit, readJson, readText } from "../body.js";
9
+ import { tenantRoute } from "../define.js";
10
+ import { pageQuery, pageSize } from "./reads.js";
11
+ import { jsonResponse } from "../respond.js";
12
+ /** Reading an Agent: any caller that may read Agents, use sessions, or change Agents. */
13
+ const READ = {
14
+ credentials: ["application", "subject", "token"],
15
+ scopes: ["sessions:own", "agents:read", "agents:write"],
16
+ };
17
+ /** Creating, changing or deleting one, and writing its secrets. */
18
+ const WRITE = {
19
+ credentials: ["application", "subject", "token"],
20
+ scopes: ["agents:write"],
21
+ };
22
+ const json = (schema, description) => ({
23
+ description,
24
+ content: { "application/json": { schema } },
25
+ });
26
+ const agentId = z.object({
27
+ agentId: z
28
+ .string()
29
+ .meta({ description: "The Agent id, percent-encoded: `users%2Falice%2Ftriage` for `users/alice/triage`" }),
30
+ });
31
+ const secretPath = agentId.extend({
32
+ name: z.string().meta({
33
+ description: "The secret's name: the variable for `environment_secret` and `environment_variable`, a label for `bearer` and `headers`",
34
+ }),
35
+ });
36
+ /** The path's Agent id, or a 400 naming the rule. */
37
+ function idOf(value) {
38
+ if (!isAgentId(value))
39
+ return fail(400, "An Agent id is up to 200 characters: /-separated segments of letters, digits, '.', '_' and '-'");
40
+ return value;
41
+ }
42
+ /**
43
+ * A page cursor: the last Agent id of the page before and the filters it was read with, opaque to
44
+ * the client; a cursor read with other filters is refused.
45
+ */
46
+ const cursor = {
47
+ encode: (id, filters) => Buffer.from(JSON.stringify({ after: id, filters: canonical(filters) })).toString("base64url"),
48
+ decode: (value, filters) => {
49
+ try {
50
+ const read = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
51
+ if (typeof read.after === "string" && read.filters === canonical(filters))
52
+ return read.after;
53
+ }
54
+ catch { }
55
+ return fail(400, "Invalid Agent cursor", { code: "cursor_invalid" });
56
+ },
57
+ };
58
+ /** `label=k=v`, repeated: every one must match. */
59
+ function labelsOf(values) {
60
+ const labels = {};
61
+ for (const value of values ?? []) {
62
+ const at = value.indexOf("=");
63
+ if (at <= 0)
64
+ fail(400, `label must be key=value, not ${value}`);
65
+ labels[value.slice(0, at)] = value.slice(at + 1);
66
+ }
67
+ return labels;
68
+ }
69
+ export function agentRoutes(api) {
70
+ tenantRoute(api, READ, {
71
+ method: "get",
72
+ path: "/v1/agents",
73
+ tags: ["Agents"],
74
+ summary: "List Agents",
75
+ description: "Every Agent with all the labels asked for, in `state`, made from `manifest` and `outdated` (moving to another version, track T3) when given, by id. A token caller sees only the Agents its grants reach, and a caller acting for a person only that person's sessions on each. `limit` opts into pages.",
76
+ request: {
77
+ query: pageQuery.extend({
78
+ label: z
79
+ .union([z.string(), z.array(z.string())])
80
+ .optional()
81
+ .meta({ description: "`key=value`; repeat it to require several labels" }),
82
+ state: z.string().optional().meta({ description: "Only Agents in this state (`input_required`, …)" }),
83
+ manifest: z.string().optional().meta({ description: "Only Agents made from this agent manifest" }),
84
+ outdated: z
85
+ .enum(["true", "false"])
86
+ .optional()
87
+ .meta({ description: "`true`: only Agents moving to another version of their manifest (waiting for secrets or a start); `false`: only the others" }),
88
+ }),
89
+ },
90
+ responses: {
91
+ 200: json(z.union([ListAgentsResponse, AgentPage]), "The Agents; limit opts into pagination"),
92
+ },
93
+ }, async (c) => {
94
+ const state = c.req.query("state");
95
+ if (state !== undefined && !AGENT_STATES.includes(state))
96
+ fail(400, `state must be one of ${AGENT_STATES.join(", ")}`);
97
+ const outdated = c.req.query("outdated");
98
+ if (outdated !== undefined && outdated !== "true" && outdated !== "false")
99
+ fail(400, "outdated must be true or false");
100
+ const filters = {
101
+ labels: labelsOf(c.req.queries("label")),
102
+ ...(state === undefined ? {} : { state }),
103
+ ...(c.req.query("manifest") === undefined ? {} : { manifest: c.req.query("manifest") }),
104
+ ...(outdated === undefined ? {} : { outdated: outdated === "true" }),
105
+ };
106
+ const limit = c.req.query("limit");
107
+ if (limit === undefined) {
108
+ const { agents } = await listAgents(c.env.tenant, filters, c.get("scope"));
109
+ return jsonResponse(200, { agents });
110
+ }
111
+ if (!/^[1-9]\d*$/.test(limit))
112
+ fail(400, "limit must be a positive integer", { code: "query_invalid" });
113
+ const after = c.req.query("cursor");
114
+ const page = await listAgents(c.env.tenant, filters, c.get("scope"), {
115
+ limit: await pageSize(c.env.tenant, Number(limit)),
116
+ ...(after === undefined ? {} : { after: cursor.decode(after, filters) }),
117
+ });
118
+ return jsonResponse(200, { agents: page.agents, nextCursor: page.next === null ? null : cursor.encode(page.next, filters) });
119
+ });
120
+ tenantRoute(api, WRITE, {
121
+ method: "put",
122
+ path: "/v1/agents/{agentId}",
123
+ tags: ["Agents"],
124
+ summary: "Create, find or move an Agent",
125
+ description: "Creates the Agent, its vault and its compute from a saved agent manifest, within the Tenant's limits, or answers the one with this id, so get-or-create is one call. `manifest: \"name\"` records that it tracks the manifest (its latest version, and each version saved later) and `\"name@n\"` that it is pinned; the version it runs is `resolvedVersion`. On an existing one, labels replace, `owner` sets or (with `null`) clears the owner guard, and `manifest` moves it (track T3) with its id, disk, vault and sessions: a harness-only change applies at once (each session's next turn runs it), a runtime change of a pod at its next start (`targetVersion`, `waitingFor: start`), and a version that adds a required secret once a value is set (`waitingFor: secrets`). Its manifest's name, kind, harness and storage are fixed (`409 agent_fixed_field`): fork it to change them. `?dryRun=true` answers the move's plan and changes nothing. Creating an Agent opens no session (D71). Kind `pod` needs sandbox pods (`nylorun local sandbox enable`) and is created at once.",
126
+ request: {
127
+ params: agentId,
128
+ query: z.object({
129
+ dryRun: z.enum(["true", "false"]).optional().meta({ description: "`true`: the plan of moving the existing Agent, with nothing changed" }),
130
+ }),
131
+ body: { required: true, content: { "application/json": { schema: PutAgentRequest } } },
132
+ },
133
+ responses: {
134
+ 200: json(z.union([AgentView, PutAgentDryRunResponse]), "The Agent, or with `dryRun` the plan of its move"),
135
+ 404: { description: "No agent manifest has this name or version (`manifest_not_found`), or a dry run names no Agent" },
136
+ 409: {
137
+ description: "A move would change its manifest's name, its kind, harness or storage (`agent_fixed_field`), the Tenant holds as many Agents as it allows (`limit_exceeded`), kind pod without sandbox pods (`agent_unavailable`), or it is being deleted (`agent_busy`)",
138
+ },
139
+ },
140
+ }, async (c) => {
141
+ const id = idOf(c.req.param("agentId"));
142
+ const body = PutAgentRequestSchema.parse(await readJson(c));
143
+ const dryRun = c.req.query("dryRun");
144
+ if (dryRun !== undefined && dryRun !== "true" && dryRun !== "false")
145
+ fail(400, "dryRun must be true or false");
146
+ if (dryRun === "true")
147
+ return jsonResponse(200, await planAgentPut(c.env.tenant, id, body, c.get("scope")));
148
+ return jsonResponse(200, await putAgent(c.env.tenant, id, body, c.get("scope")));
149
+ });
150
+ tenantRoute(api, READ, {
151
+ method: "get",
152
+ path: "/v1/agents/{agentId}",
153
+ tags: ["Agents"],
154
+ summary: "Get an Agent",
155
+ description: "Its manifest version, what its environment runs, labels, owner guard, derived state with the sessions behind it (`attention`), compute, secret slots, disk, and its sessions (acting for a person, only theirs).",
156
+ request: { params: agentId },
157
+ responses: { 200: json(AgentView, "The Agent") },
158
+ }, async (c) => jsonResponse(200, await getAgent(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
159
+ tenantRoute(api, READ, {
160
+ method: "get",
161
+ path: "/v1/agents/{agentId}/events",
162
+ tags: ["Agents"],
163
+ summary: "Read an Agent's events",
164
+ description: "`agent.created`, `.attached`, `.detached`, `.deleted`, a pod's `.running`, `.suspended`, `.relaunched`, `.lost`, `.reset` and `.failed`, `.forked`, a move's `.outdated`, `.version_changed` and `.start_failed` (track T3), and `agent.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.",
165
+ request: {
166
+ params: agentId,
167
+ query: z.object({
168
+ from: z.string().optional().meta({ description: "The first `seq` to return" }),
169
+ types: z.string().optional().meta({ description: "Only these event types, comma-separated (`agent.egress`)" }),
170
+ since: z.string().optional().meta({ description: "Only events recorded at or after this ISO time" }),
171
+ until: z.string().optional().meta({ description: "Only events recorded before this ISO time" }),
172
+ }),
173
+ },
174
+ responses: { 200: json(ListAgentEventsResponse, "The events") },
175
+ }, async (c) => {
176
+ const from = c.req.query("from");
177
+ if (from !== undefined && !/^\d+$/.test(from))
178
+ fail(400, "from must be a seq");
179
+ const types = c.req.query("types")?.split(",").map((type) => type.trim()).filter(Boolean);
180
+ const unknown = types?.find((type) => !AGENT_EVENT_TYPES.includes(type));
181
+ if (unknown !== undefined)
182
+ fail(400, `types: ${unknown} is not an Agent event type`);
183
+ const time = (name) => {
184
+ const value = c.req.query(name);
185
+ if (value === undefined)
186
+ return undefined;
187
+ const at = Date.parse(value);
188
+ if (!Number.isFinite(at))
189
+ fail(400, `${name} must be an ISO time`);
190
+ return new Date(at).toISOString();
191
+ };
192
+ const since = time("since");
193
+ const until = time("until");
194
+ return jsonResponse(200, await agentEventsOf(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"), {
195
+ ...(from === undefined ? {} : { fromSeq: Number(from) }),
196
+ ...(types === undefined || types.length === 0 ? {} : { types }),
197
+ ...(since === undefined ? {} : { since }),
198
+ ...(until === undefined ? {} : { until }),
199
+ }));
200
+ });
201
+ tenantRoute(api, WRITE, {
202
+ method: "delete",
203
+ path: "/v1/agents/{agentId}",
204
+ tags: ["Agents"],
205
+ summary: "Delete an Agent",
206
+ description: "Deletes the Agent, its vault with every secret, and its disk. Its sessions keep its id; their next turn is refused (`agent_unavailable`) until an Agent with this id exists again.",
207
+ request: { params: agentId },
208
+ responses: {
209
+ 200: json(DeleteAgentResponse, "Deleted, or there was none"),
210
+ 409: { description: "One of its sessions has a turn running (`agent_busy`)" },
211
+ },
212
+ }, async (c) => jsonResponse(200, await deleteAgent(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
213
+ for (const [action, summary, description, run] of [
214
+ [
215
+ "stop",
216
+ "Stop a pod Agent",
217
+ "Suspends the pod: its volume is kept, and the next turn of one of its sessions starts it again. Refused during a turn (`agent_busy`).",
218
+ stopAgent,
219
+ ],
220
+ [
221
+ "reset",
222
+ "Reset a pod Agent",
223
+ "A new pod on a new, empty volume with the recipe applied; the old pod and volume are deleted. The way out of `agent_lost`. Refused during a turn (`agent_busy`).",
224
+ resetAgent,
225
+ ],
226
+ ])
227
+ tenantRoute(api, WRITE, {
228
+ method: "post",
229
+ path: `/v1/agents/{agentId}/${action}`,
230
+ tags: ["Agents"],
231
+ summary,
232
+ description,
233
+ request: { params: agentId },
234
+ responses: {
235
+ 200: json(AgentView, "The Agent"),
236
+ 400: { description: "The Agent is not of kind pod" },
237
+ 409: {
238
+ description: "A turn holds it (`agent_busy`), or no sandbox pods (`agent_unavailable`)",
239
+ },
240
+ },
241
+ }, async (c) => jsonResponse(200, await run(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
242
+ tenantRoute(api, WRITE, {
243
+ method: "post",
244
+ path: "/v1/agents/{agentId}/restart",
245
+ tags: ["Agents"],
246
+ summary: "Restart an Agent on its pending version",
247
+ description: "Applies a version the Agent waits to apply (track T3): a running pod stops, so the next turn starts it on the version, on the same volume; a stopped or failed one is asked to run at once. `retry: true` first moves it to the version that failed to start (`failedVersion`) again. Kinds `virtual` and `none` apply a version at once, so there is nothing to wait for. Refused during a turn (`agent_busy`). The body is optional.",
248
+ request: {
249
+ params: agentId,
250
+ body: { required: false, content: { "application/json": { schema: RestartAgentRequest } } },
251
+ },
252
+ responses: {
253
+ 200: json(AgentView, "The Agent"),
254
+ 409: { description: "A turn holds it (`agent_busy`), its pod was lost (`agent_lost`), or no sandbox pods (`agent_unavailable`)" },
255
+ },
256
+ }, async (c) => {
257
+ const text = await readText(c.req.raw, await bodyLimit(c.env.tenant));
258
+ let raw = {};
259
+ if (text.trim() !== "")
260
+ try {
261
+ raw = JSON.parse(text);
262
+ }
263
+ catch {
264
+ fail(400, "Invalid JSON");
265
+ }
266
+ const body = RestartAgentRequestSchema.parse(raw);
267
+ return jsonResponse(200, await restartAgent(c.env.tenant, idOf(c.req.param("agentId")), { ...(body.retry ? { retry: true } : {}) }, c.get("scope")));
268
+ });
269
+ tenantRoute(api, WRITE, {
270
+ method: "post",
271
+ path: "/v1/agents/{agentId}/snapshot",
272
+ tags: ["Agents"],
273
+ summary: "Snapshot an Agent's disk",
274
+ description: "Between turns, a new version of the Agent'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 Agent 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 (`agent_busy`).",
275
+ request: { params: agentId },
276
+ responses: {
277
+ 201: json(AgentSnapshot, "The version"),
278
+ 400: { description: "The Agent is of kind none" },
279
+ 409: { description: "A turn or another copy holds the disk (`agent_busy`), or the pod could not start (`agent_unavailable`)" },
280
+ 413: { description: "Past the entries, bytes or Tenant total a version may hold (`limit_exceeded`)" },
281
+ },
282
+ }, async (c) => jsonResponse(201, await snapshotEnvironment(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
283
+ tenantRoute(api, WRITE, {
284
+ method: "post",
285
+ path: "/v1/agents/{agentId}/restore",
286
+ tags: ["Agents"],
287
+ summary: "Restore a version of an Agent's disk",
288
+ description: "Puts disk version `version` (or `latest`) back. A virtual Agent'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 Agent keeps its id, vault and sessions. Refused during a turn or another copy (`agent_busy`).",
289
+ request: {
290
+ params: agentId,
291
+ body: { required: true, content: { "application/json": { schema: RestoreAgentRequest } } },
292
+ },
293
+ responses: {
294
+ 200: json(AgentRestore, "Restored, or being restored"),
295
+ 404: { description: "No such Agent, or no such version" },
296
+ 409: { description: "A turn or another copy holds the disk (`agent_busy`)" },
297
+ },
298
+ }, async (c) => {
299
+ const body = RestoreAgentRequestSchema.parse(await readJson(c));
300
+ return jsonResponse(200, await restoreEnvironment(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"), body.version));
301
+ });
302
+ tenantRoute(api, WRITE, {
303
+ method: "post",
304
+ path: "/v1/agents/{agentId}/fork",
305
+ tags: ["Agents"],
306
+ summary: "Fork an Agent",
307
+ description: "Copies the Agent into a new one (Q15), which is how an Agent changes what is fixed for its life: a new Agent `id`, made from `manifest` (default the source's manifest name, tracking, 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 Agent keeps what it runs, its disk and its sessions. `dryRun` answers the plan and makes nothing. To move an Agent to another version of the same manifest, with the same kind, harness and storage, `PUT` it instead (track T3).",
308
+ request: {
309
+ params: agentId,
310
+ body: { required: true, content: { "application/json": { schema: ForkAgentRequest } } },
311
+ },
312
+ responses: {
313
+ 200: json(ForkAgentResponse, "The plan (`dryRun`)"),
314
+ 201: json(ForkAgentResponse, "The new Agent, and the plan it ran"),
315
+ 400: { description: "The manifest does not resolve against the Tenant's limits" },
316
+ 404: { description: "No such Agent, manifest version (`manifest_not_found`) or disk version" },
317
+ 409: { description: "The id exists, or a turn or another copy holds the source's disk (`agent_busy`)" },
318
+ },
319
+ }, async (c) => {
320
+ const body = ForkAgentRequestSchema.parse(await readJson(c));
321
+ const answer = await forkAgent(c.env.tenant, idOf(c.req.param("agentId")), body, c.get("scope"));
322
+ return jsonResponse("dryRun" in answer ? 200 : 201, answer);
323
+ });
324
+ tenantRoute(api, READ, {
325
+ method: "get",
326
+ path: "/v1/agents/{agentId}/versions",
327
+ tags: ["Agents"],
328
+ summary: "List an Agent's disk versions",
329
+ description: "Newest first. Read a version's tree, files, diff and zip through `/v1/artifacts/{artifactId}/versions/{version}/…`.",
330
+ request: { params: agentId },
331
+ responses: { 200: json(AgentDiskVersions, "The versions") },
332
+ }, async (c) => jsonResponse(200, await environmentVersions(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
333
+ tenantRoute(api, WRITE, {
334
+ method: "delete",
335
+ path: "/v1/agents/{agentId}/versions/{version}",
336
+ tags: ["Agents"],
337
+ summary: "Delete an Agent's disk version",
338
+ description: "Deletes the version, and the stored files no other version names. Refused while a seed reads it.",
339
+ request: { params: agentId.extend({ version: z.string() }) },
340
+ responses: {
341
+ 200: json(z.object({ agentId: z.string(), version: z.number(), deleted: z.boolean() }), "Deleted, or there was none"),
342
+ 409: { description: "A new volume is being seeded from it (`agent_busy`)" },
343
+ },
344
+ }, async (c) => {
345
+ const version = c.req.param("version") ?? "";
346
+ if (!/^[1-9]\d{0,8}$/.test(version))
347
+ fail(400, "version must be a version number");
348
+ return jsonResponse(200, await deleteEnvironmentVersion(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"), Number(version)));
349
+ });
350
+ tenantRoute(api, READ, {
351
+ method: "get",
352
+ path: "/v1/agents/{agentId}/secrets",
353
+ tags: ["Agents"],
354
+ summary: "List an Agent's secrets",
355
+ description: "Each secret's name, type and binding (its URL, or its hosts and header). Never a value.",
356
+ request: { params: agentId },
357
+ responses: { 200: json(ListAgentSecretsResponse, "The secrets") },
358
+ }, async (c) => {
359
+ const id = idOf(c.req.param("agentId"));
360
+ assertReaches(c.get("scope"), id);
361
+ return jsonResponse(200, { secrets: await c.env.tenant.vault.listEnvironmentSecrets(id) });
362
+ });
363
+ tenantRoute(api, READ, {
364
+ method: "get",
365
+ path: "/v1/agents/{agentId}/secrets/{name}",
366
+ tags: ["Agents"],
367
+ summary: "Get an Agent's secret",
368
+ description: "Its name, type and binding. Never its value.",
369
+ request: { params: secretPath },
370
+ responses: { 200: json(AgentSecretView, "The secret") },
371
+ }, async (c) => {
372
+ const id = idOf(c.req.param("agentId"));
373
+ assertReaches(c.get("scope"), id);
374
+ return jsonResponse(200, await c.env.tenant.vault.getEnvironmentSecret(id, c.req.param("name") ?? ""));
375
+ });
376
+ tenantRoute(api, WRITE, {
377
+ method: "put",
378
+ path: "/v1/agents/{agentId}/secrets/{name}",
379
+ tags: ["Agents"],
380
+ summary: "Write an Agent's secret",
381
+ description: "Creates or replaces one credential of the Agent'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 the Agent's manifest declares secret slots (D64; while it moves to another version, that version's), 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`.",
382
+ request: {
383
+ params: secretPath,
384
+ body: {
385
+ required: true,
386
+ content: { "application/json": { schema: z.union([PutAgentSecretRequest, PutAgentSecretValueRequest]) } },
387
+ },
388
+ },
389
+ responses: {
390
+ 200: json(AgentSecretView, "The secret, without its value"),
391
+ 409: { description: "Another credential is bound to one of its hosts or to its URL (`credential_conflict`)" },
392
+ },
393
+ }, async (c) => {
394
+ const id = idOf(c.req.param("agentId"));
395
+ const scope = c.get("scope");
396
+ assertReaches(scope, id);
397
+ const name = c.req.param("name") ?? "";
398
+ const body = await secretRequestFor(c.env.tenant, id, name, await readJson(c));
399
+ const secret = await c.env.tenant.keys.putEnvironmentSecret(id, name, body, actorOf(scope));
400
+ // A move that waited for this secret goes on (track T3).
401
+ await secretWritten(c.env.tenant, id);
402
+ return jsonResponse(200, secret);
403
+ });
404
+ tenantRoute(api, WRITE, {
405
+ method: "delete",
406
+ path: "/v1/agents/{agentId}/secrets/{name}",
407
+ tags: ["Agents"],
408
+ summary: "Delete an Agent's secret",
409
+ description: "The next request that would have used it gets none.",
410
+ request: { params: secretPath },
411
+ responses: { 200: json(DeletedResponse, "Deleted") },
412
+ }, async (c) => {
413
+ const id = idOf(c.req.param("agentId"));
414
+ const scope = c.get("scope");
415
+ assertReaches(scope, id);
416
+ return jsonResponse(200, await c.env.tenant.vault.deleteEnvironmentSecret(id, c.req.param("name") ?? "", actorOf(scope)));
417
+ });
418
+ }
@@ -21,7 +21,7 @@ import { effectiveArtifactLimits, readArtifactsConfig, writeArtifactsConfig, } f
21
21
  import { contentKey, diffManifests, entryAt, readManifest } from "../../../artifacts/folders.js";
22
22
  import { mintArtifactLink, verifyArtifactLink } from "../../../artifacts/links.js";
23
23
  import { zipStream, zipTooLarge } from "../../../artifacts/zip.js";
24
- import { normalizeContentType } from "../../../artifacts/media-types.js";
24
+ import { normalizeContentType } from "@nylorun/core/contracts";
25
25
  import { artifactContent, deleteArtifact, folderContent, getArtifact, listArtifacts, readableArtifact, uploadArtifact, versionOf, } from "../../../artifacts/service.js";
26
26
  import { accessOf } from "../../../tenant/auth.js";
27
27
  import { readAccess, readStoreOf } from "../../../reads/access.js";
@@ -29,7 +29,9 @@ import { fail } from "../../../tenant/http.js";
29
29
  import { readJson } from "../body.js";
30
30
  import { tenantRoute } from "../define.js";
31
31
  import { jsonResponse } from "../respond.js";
32
- import { pageQuery, parseQuery } from "./reads.js";
32
+ import { labelIssues } from "../../../tenant/limits.js";
33
+ import { readRuntimeLimits } from "../../../tenant/runtime-limits.js";
34
+ import { pageQuery, pageSize, parseQuery } from "./reads.js";
33
35
  /** Reading and writing artifacts: whoever may use sessions, a person only in their own. */
34
36
  const OWN = {
35
37
  credentials: ["application", "subject", "token"],
@@ -105,6 +107,15 @@ function labelsOf(values) {
105
107
  fail(400, parsed.error.issues[0]?.message ?? "Invalid labels");
106
108
  return labels;
107
109
  }
110
+ /** `labels` to set on an artifact, within the Tenant's Runtime limits (read for the request). */
111
+ async function settableLabels(ctx, labels) {
112
+ if (labels === undefined)
113
+ return undefined;
114
+ const issues = labelIssues(labels, await ctx.store.tx((t) => readRuntimeLimits(t)), "artifactLabels");
115
+ if (issues.length > 0)
116
+ fail(400, issues.join(" "));
117
+ return labels;
118
+ }
108
119
  function versionNumber(value) {
109
120
  if (value === "latest")
110
121
  return undefined;
@@ -285,7 +296,7 @@ export function artifactRoutes(api) {
285
296
  const request = c.req.raw;
286
297
  const name = c.req.query("name");
287
298
  const sessionId = c.req.query("sessionId");
288
- const labels = labelsOf(c.req.queries("label"));
299
+ const labels = await settableLabels(c.env.tenant, labelsOf(c.req.queries("label")));
289
300
  const contentType = declaredType(request);
290
301
  const uploaded = await uploadArtifact(c.env.tenant, {
291
302
  ...(name === undefined ? {} : { name }),
@@ -337,9 +348,10 @@ export function artifactRoutes(api) {
337
348
  }, async (c) => {
338
349
  const sessionId = c.req.query("sessionId");
339
350
  if (c.req.query("limit") !== undefined) {
340
- const { limit, cursor, kind } = parseQuery(artifactPageQuery, c.req.query());
341
- if (limit === undefined)
351
+ const { limit: asked, cursor, kind } = parseQuery(artifactPageQuery, c.req.query());
352
+ if (asked === undefined)
342
353
  return fail(400, "Pagination requires limit");
354
+ const limit = await pageSize(c.env.tenant, asked);
343
355
  return jsonResponse(200, await readStoreOf(c.env.tenant).artifacts({ sessionId, kind, labels: labelsOf(c.req.queries("label")) ?? {} }, { limit, cursor }, readAccess(c.get("scope"))));
344
356
  }
345
357
  return jsonResponse(200, await listArtifacts(c.env.tenant, sessionId === undefined ? {} : { sessionId }, accessOf(c.get("scope"))));
@@ -457,7 +469,7 @@ export function artifactRoutes(api) {
457
469
  responses: { 200: json(ArtifactLink, "The link") },
458
470
  }, async (c) => {
459
471
  const ctx = c.env.tenant;
460
- const request = CreateArtifactLinkRequestSchema.parse(await readJson(c.req.raw));
472
+ const request = CreateArtifactLinkRequestSchema.parse(await readJson(c));
461
473
  const id = c.req.param("artifactId");
462
474
  const { artifact, version } = await ctx.store.tx(async (t) => {
463
475
  const artifact = await readableArtifact(t, id, accessOf(c.get("scope")));
@@ -480,11 +492,11 @@ export function artifactRoutes(api) {
480
492
  path: "/v1/artifacts/{artifactId}",
481
493
  tags: ["Artifacts"],
482
494
  summary: "Delete an artifact",
483
- description: "Deletes every version and its bytes; its links stop working. A session's artifact gets `artifact.deleted` in its history. An Environment's disk versions are deleted through `/v1/environments/{environmentId}/versions/{version}` (`409` here).",
495
+ description: "Deletes every version and its bytes; its links stop working. A session's artifact gets `artifact.deleted` in its history. An Agent's disk versions are deleted through `/v1/agents/{agentId}/versions/{version}` (`409` here).",
484
496
  request: { params: artifactId },
485
497
  responses: {
486
498
  200: json(DeleteArtifactResponse, "Deleted"),
487
- 409: { description: "The artifact holds an Environment's disk versions" },
499
+ 409: { description: "The artifact holds an Agent's disk versions" },
488
500
  },
489
501
  }, async (c) => jsonResponse(200, await deleteArtifact(c.env.tenant, c.req.param("artifactId"), accessOf(c.get("scope")))));
490
502
  tenantRoute(api, LINK, {
@@ -528,7 +540,7 @@ export function artifactRoutes(api) {
528
540
  responses: { 200: json(TenantArtifactsView, "The limits now in force") },
529
541
  }, async (c) => {
530
542
  const ctx = c.env.tenant;
531
- const { requestId: _requestId, ...config } = PutTenantArtifactsRequestSchema.parse(await readJson(c.req.raw));
543
+ const { requestId: _requestId, ...config } = PutTenantArtifactsRequestSchema.parse(await readJson(c));
532
544
  await ctx.store.tx((t) => writeArtifactsConfig(t, config));
533
545
  return jsonResponse(200, await artifactsView(ctx));
534
546
  });
@@ -1,8 +1,8 @@
1
1
  /**
2
- * `POST /v1/tenant/credential-coverage` (the Management API): what a session of a saved agent
3
- * would send for each URL it names, in a given Environment. A dry run of each call's credential
4
- * choice (`VaultService.coverage`); no secret is read. An Environment's secrets themselves are
5
- * written on the Runtime API (`/v1/environments/{id}/secrets`, `routes/environments.ts`).
2
+ * `POST /v1/tenant/credential-coverage` (the Management API): what a session on an Agent would
3
+ * send for each URL its manifest version names, from the Agent's vault. A dry run of each call's
4
+ * credential choice (`VaultService.coverage`); no secret is read. An Agent's secrets themselves
5
+ * are written on the Runtime API (`/v1/agents/{id}/secrets`, `routes/agents.ts`).
6
6
  */
7
7
  import type { OpenAPIHono } from "@hono/zod-openapi";
8
8
  import type { TenantEnv } from "../app.js";
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { CredentialCoverageRequestSchema } from "@nylorun/core/contracts";
3
+ import { loopManifestOf } from "@nylorun/core/define";
4
+ import { liveEnvironment } from "../../../tenant/agents.js";
3
5
  import { CredentialCoverage, CredentialCoverageRequest, Rejected } from "../../components.js";
4
6
  import { readJson } from "../body.js";
5
7
  import { tenantRoute } from "../define.js";
@@ -15,20 +17,24 @@ export function coverageRoutes(api) {
15
17
  tenantRoute(api, MANAGEMENT, {
16
18
  method: "post",
17
19
  path: "/v1/tenant/credential-coverage",
18
- tags: ["Environments"],
19
- summary: "Check an agent's credentials against an Environment",
20
- description: "For each remote MCP server and each HTTP tool with a `credential` that the saved agent `agentId` declares (its own, its agents used as tools' and a flow's), the credential a session that names `environmentId` would send: the Environment's secret bound to the URL (`covered`), or none (`missing`). No secret is read and no server is called.",
20
+ tags: ["Agents"],
21
+ summary: "Check an Agent's credentials",
22
+ description: "For each remote MCP server and each HTTP tool with a `credential` that Agent `agentId`'s manifest version declares (its own, its agents used as tools' and a flow's), the credential its sessions would send: the Agent's secret bound to the URL (`covered`), or none (`missing`). No secret is read and no server is called.",
21
23
  request: {
22
24
  body: { required: true, content: { "application/json": { schema: CredentialCoverageRequest } } },
23
25
  },
24
26
  responses: {
25
27
  200: json(CredentialCoverage, "One entry per declared URL"),
26
- 404: json(Rejected, "No such agent, or no such Environment"),
28
+ 404: json(Rejected, "No such Agent"),
27
29
  },
28
30
  }, async (c) => {
29
- const request = CredentialCoverageRequestSchema.parse(await readJson(c.req.raw));
31
+ const request = CredentialCoverageRequestSchema.parse(await readJson(c));
30
32
  const tenant = c.env.tenant;
31
- const definition = (await tenant.store.tx((t) => t.get("definitions", request.agentId))) ?? fail(404, "Definition not found");
32
- return jsonResponse(200, await tenant.vault.coverage(definition.manifest, request));
33
+ const manifest = await tenant.store.tx(async (t) => {
34
+ const agent = (await liveEnvironment(t, request.agentId)) ?? fail(404, "Agent not found");
35
+ const version = (await t.agentManifest(agent.manifestName, agent.manifestVersion)) ?? fail(404, "Agent not found");
36
+ return loopManifestOf(version.body);
37
+ });
38
+ return jsonResponse(200, await tenant.vault.coverage(manifest, request));
33
39
  });
34
40
  }
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Definition files (track R2 M4): `PUT /v1/files/sha256:{hex}` uploads one file a definition
3
3
  * names by its content hash (a skill's), `HEAD` says whether the Tenant holds it. Application
4
- * key only: definitions are the application's, not a person's.
4
+ * key only: definitions are the application's, not a person's. The Tenant's skill limit is
5
+ * `GET`/`PUT /v1/tenant/skills`, a Management API route.
5
6
  */
6
7
  import type { OpenAPIHono } from "@hono/zod-openapi";
7
8
  import type { TenantEnv } from "../app.js";
@@ -1,13 +1,17 @@
1
1
  import { z } from "zod";
2
- import { DEFINITION_FILE_MAX_BYTES } from "@nylorun/core/contracts";
3
- import { DefinitionFileView } from "../../components.js";
4
- import { normalizeContentType } from "../../../artifacts/media-types.js";
2
+ import { PutTenantSkillsRequestSchema } from "@nylorun/core/contracts";
3
+ import { DefinitionFileView, PutTenantSkillsRequest, TenantSkillsView } from "../../components.js";
4
+ import { readJson } from "../body.js";
5
+ import { normalizeContentType } from "@nylorun/core/contracts";
5
6
  import { hasDefinitionFile, putDefinitionFile } from "../../../tenant/definition-files.js";
7
+ import { effectiveSkillLimits, readSkillsConfig, writeSkillsConfig } from "../../../tenant/limits.js";
6
8
  import { fail } from "../../../tenant/http.js";
7
9
  import { tenantRoute } from "../define.js";
8
10
  import { jsonResponse } from "../respond.js";
9
11
  const APPLICATION = { credentials: ["application"], scopes: "never" };
10
12
  const UPLOAD = { ...APPLICATION, bytes: true };
13
+ /** The Tenant's skill limit: a Management API route (`/v1/tenant/skills`). */
14
+ const SETTINGS = { credentials: ["management"], scopes: "never" };
11
15
  const file = z.object({
12
16
  file: z.string().meta({ description: "`sha256:` and the SHA-256 of the file's bytes, 64 lowercase hex digits" }),
13
17
  });
@@ -36,7 +40,7 @@ export function fileRoutes(api) {
36
40
  path: "/v1/files/{file}",
37
41
  tags: ["Definition files"],
38
42
  summary: "Upload a definition file",
39
- description: `Stores the body, at most ${DEFINITION_FILE_MAX_BYTES} bytes, at the SHA-256 the path names; a body with another hash stores nothing (\`400\`). A file the Tenant holds already is kept as it is (\`200\`).`,
43
+ description: "Streams the body, at most the Tenant's skill limit (`/v1/tenant/skills`, default 2 GiB: one file may be as large as a whole skill), to the SHA-256 the path names; a body with another hash stores nothing (`400`). A file the Tenant holds already is kept as it is (`200`).",
40
44
  request: {
41
45
  params: file,
42
46
  body: {
@@ -48,7 +52,7 @@ export function fileRoutes(api) {
48
52
  responses: {
49
53
  200: view("The Tenant held the file already"),
50
54
  201: view("The file, stored"),
51
- 413: { description: `Larger than ${DEFINITION_FILE_MAX_BYTES} bytes (\`limit_exceeded\`)` },
55
+ 413: { description: "Larger than the Tenant's skill limit (`limit_exceeded`)" },
52
56
  },
53
57
  }, async (c) => {
54
58
  const request = c.req.raw;
@@ -74,4 +78,26 @@ export function fileRoutes(api) {
74
78
  return fail(404, "The Runtime does not hold this file");
75
79
  return new Response(null, { status: 200 });
76
80
  });
81
+ tenantRoute(api, SETTINGS, {
82
+ method: "get",
83
+ path: "/v1/tenant/skills",
84
+ tags: ["Tenant"],
85
+ summary: "Get the Tenant's skill limit",
86
+ responses: { 200: { description: "The limit in force", content: { "application/json": { schema: TenantSkillsView } } } },
87
+ }, async (c) => jsonResponse(200, { limits: effectiveSkillLimits(await c.env.tenant.store.tx((t) => readSkillsConfig(t))) }));
88
+ tenantRoute(api, SETTINGS, {
89
+ method: "put",
90
+ path: "/v1/tenant/skills",
91
+ tags: ["Tenant"],
92
+ summary: "Set the Tenant's skill limit",
93
+ description: "The most bytes one skill's files may hold together, for every agent manifest the Tenant stores, whose runtime section may set a lower one (`limits.skills`). Unset, the limit takes the default (2 GiB). Manifests already saved keep their skills; a session opens only while its Agent's skills fit.",
94
+ request: {
95
+ body: { required: true, content: { "application/json": { schema: PutTenantSkillsRequest } } },
96
+ },
97
+ responses: { 200: { description: "The limit now in force", content: { "application/json": { schema: TenantSkillsView } } } },
98
+ }, async (c) => {
99
+ const { requestId: _requestId, ...config } = PutTenantSkillsRequestSchema.parse(await readJson(c));
100
+ await c.env.tenant.store.tx((t) => writeSkillsConfig(t, config));
101
+ return jsonResponse(200, { limits: effectiveSkillLimits(config) });
102
+ });
77
103
  }