@nylorun/runtime 0.13.0-beta → 0.14.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 (223) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/README.md +69 -15
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +38 -8
  5. package/dist/adapters/replication/pgoutput.d.ts +16 -0
  6. package/dist/adapters/replication/pgoutput.js +169 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +1071 -0
  9. package/dist/api/a2a/card.d.ts +15 -0
  10. package/dist/api/a2a/card.js +24 -0
  11. package/dist/api/a2a/endpoint.d.ts +8 -0
  12. package/dist/api/a2a/endpoint.js +81 -0
  13. package/dist/api/a2a/protocol.d.ts +144 -0
  14. package/dist/api/a2a/protocol.js +188 -0
  15. package/dist/api/a2a/routes.d.ts +19 -0
  16. package/dist/api/a2a/routes.js +289 -0
  17. package/dist/api/a2a/tasks.d.ts +50 -0
  18. package/dist/api/a2a/tasks.js +186 -0
  19. package/dist/api/ag-ui/endpoint.d.ts +8 -0
  20. package/dist/api/ag-ui/endpoint.js +126 -0
  21. package/dist/api/ag-ui/history.d.ts +7 -0
  22. package/dist/api/ag-ui/history.js +60 -0
  23. package/dist/api/ag-ui/routes.d.ts +32 -0
  24. package/dist/api/ag-ui/routes.js +305 -0
  25. package/dist/api/ag-ui/session-id.d.ts +1 -0
  26. package/dist/api/ag-ui/session-id.js +12 -0
  27. package/dist/api/ag-ui/sse.d.ts +9 -0
  28. package/dist/api/ag-ui/sse.js +7 -0
  29. package/dist/api/ag-ui/translate.d.ts +38 -0
  30. package/dist/api/ag-ui/translate.js +222 -0
  31. package/dist/api/components.d.ts +7825 -0
  32. package/dist/api/components.js +83 -0
  33. package/dist/api/http/app.d.ts +18 -0
  34. package/dist/api/http/app.js +58 -0
  35. package/dist/api/http/body.d.ts +4 -0
  36. package/dist/api/http/body.js +37 -0
  37. package/dist/api/http/define.d.ts +39 -0
  38. package/dist/api/http/define.js +130 -0
  39. package/dist/api/http/respond.d.ts +9 -0
  40. package/dist/api/http/respond.js +44 -0
  41. package/dist/api/http/routes/access.d.ts +24 -0
  42. package/dist/api/http/routes/access.js +259 -0
  43. package/dist/api/http/routes/actions.d.ts +8 -0
  44. package/dist/api/http/routes/actions.js +76 -0
  45. package/dist/api/http/routes/endpoints.d.ts +7 -0
  46. package/dist/api/http/routes/endpoints.js +100 -0
  47. package/dist/api/http/routes/sessions.d.ts +3 -0
  48. package/dist/api/http/routes/sessions.js +205 -0
  49. package/dist/api/http/routes/tenant.d.ts +10 -0
  50. package/dist/api/http/routes/tenant.js +172 -0
  51. package/dist/api/http/routes/vaults.d.ts +8 -0
  52. package/dist/api/http/routes/vaults.js +167 -0
  53. package/dist/api/openapi.d.ts +19 -0
  54. package/dist/api/openapi.js +138 -0
  55. package/dist/api/route.d.ts +12 -0
  56. package/dist/api/route.js +4 -0
  57. package/dist/core/bearer.d.ts +8 -0
  58. package/dist/core/bearer.js +11 -0
  59. package/dist/core/flow-host.d.ts +7 -15
  60. package/dist/core/flow-host.js +16 -41
  61. package/dist/core/sandbox-routes.d.ts +12 -7
  62. package/dist/core/sandbox-routes.js +24 -25
  63. package/dist/execution/memory.d.ts +8 -5
  64. package/dist/execution/memory.js +33 -13
  65. package/dist/execution/types.d.ts +38 -6
  66. package/dist/execution/types.js +12 -3
  67. package/dist/host/admin-api.d.ts +22 -0
  68. package/dist/host/admin-api.js +178 -0
  69. package/dist/host/app.d.ts +34 -0
  70. package/dist/host/app.js +185 -0
  71. package/dist/host/config-for.d.ts +2 -0
  72. package/dist/host/config-for.js +1 -0
  73. package/dist/host/config.d.ts +11 -0
  74. package/dist/host/cors.d.ts +27 -0
  75. package/dist/host/cors.js +64 -0
  76. package/dist/host/create-host.d.ts +31 -3
  77. package/dist/host/create-host.js +168 -327
  78. package/dist/host/http.d.ts +14 -3
  79. package/dist/host/http.js +64 -23
  80. package/dist/host/main.js +55 -4
  81. package/dist/host/stack-config.d.ts +20 -0
  82. package/dist/host/stack-config.js +55 -0
  83. package/dist/mcp/pool.d.ts +19 -1
  84. package/dist/mcp/pool.js +60 -10
  85. package/dist/model/catalog.d.ts +2 -10
  86. package/dist/model/classify.d.ts +13 -0
  87. package/dist/model/classify.js +61 -0
  88. package/dist/model/models.d.ts +10 -1
  89. package/dist/model/models.js +14 -6
  90. package/dist/model/pi-model.d.ts +17 -2
  91. package/dist/model/pi-model.js +366 -222
  92. package/dist/openapi.json +25696 -0
  93. package/dist/sandbox/manager.d.ts +13 -1
  94. package/dist/sandbox/manager.js +8 -11
  95. package/dist/sandbox/resolve.d.ts +33 -0
  96. package/dist/sandbox/resolve.js +81 -0
  97. package/dist/sandbox/session-sandbox.d.ts +11 -0
  98. package/dist/sandbox/session-sandbox.js +51 -0
  99. package/dist/sandbox/share.d.ts +14 -2
  100. package/dist/sandbox/share.js +33 -3
  101. package/dist/sandbox/tenant-config.d.ts +37 -0
  102. package/dist/sandbox/tenant-config.js +51 -0
  103. package/dist/sandbox/tools.d.ts +7 -9
  104. package/dist/sandbox/tools.js +11 -3
  105. package/dist/sandbox/types.d.ts +8 -2
  106. package/dist/store/event.d.ts +19 -0
  107. package/dist/store/event.js +49 -0
  108. package/dist/store/memory.d.ts +20 -8
  109. package/dist/store/memory.js +265 -100
  110. package/dist/store/postgres/connect.d.ts +13 -0
  111. package/dist/store/postgres/connect.js +5 -0
  112. package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
  113. package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
  114. package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
  115. package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
  116. package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
  117. package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
  118. package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
  119. package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
  120. package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
  121. package/dist/store/postgres/migrations/007_event_time.js +14 -0
  122. package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
  123. package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
  124. package/dist/store/postgres/migrations/index.d.ts +5 -1
  125. package/dist/store/postgres/migrations/index.js +22 -2
  126. package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
  127. package/dist/store/postgres/migrations/shared/index.js +92 -0
  128. package/dist/store/postgres/record.d.ts +11 -0
  129. package/dist/store/postgres/record.js +64 -0
  130. package/dist/store/postgres/store.js +337 -95
  131. package/dist/store/postgres/tenants.js +14 -0
  132. package/dist/store/types.d.ts +176 -81
  133. package/dist/streams/basin.d.ts +18 -1
  134. package/dist/streams/basin.js +34 -2
  135. package/dist/streams/control.d.ts +18 -0
  136. package/dist/streams/control.js +29 -0
  137. package/dist/streams/memory.d.ts +3 -1
  138. package/dist/streams/memory.js +14 -8
  139. package/dist/streams/relay/core.d.ts +35 -0
  140. package/dist/streams/relay/core.js +366 -0
  141. package/dist/streams/relay/memory.d.ts +69 -0
  142. package/dist/streams/relay/memory.js +176 -0
  143. package/dist/streams/relay/types.d.ts +77 -0
  144. package/dist/streams/relay/types.js +7 -0
  145. package/dist/streams/types.d.ts +66 -36
  146. package/dist/streams/types.js +18 -53
  147. package/dist/tenant/access-policy.d.ts +30 -0
  148. package/dist/tenant/access-policy.js +52 -0
  149. package/dist/tenant/actions.d.ts +10 -149
  150. package/dist/tenant/actions.js +5 -167
  151. package/dist/tenant/advance.js +32 -0
  152. package/dist/tenant/auth.d.ts +19 -17
  153. package/dist/tenant/auth.js +90 -93
  154. package/dist/tenant/browser.d.ts +15 -0
  155. package/dist/tenant/browser.js +39 -0
  156. package/dist/tenant/commands.d.ts +18 -2
  157. package/dist/tenant/commands.js +107 -113
  158. package/dist/tenant/context.d.ts +75 -18
  159. package/dist/tenant/context.js +14 -10
  160. package/dist/tenant/delivery-token.d.ts +41 -0
  161. package/dist/tenant/delivery-token.js +105 -0
  162. package/dist/tenant/delivery.d.ts +62 -0
  163. package/dist/tenant/delivery.js +476 -0
  164. package/dist/tenant/effects.d.ts +5 -0
  165. package/dist/tenant/effects.js +78 -37
  166. package/dist/tenant/endpoints.d.ts +67 -0
  167. package/dist/tenant/endpoints.js +90 -0
  168. package/dist/tenant/ephemeral.d.ts +6 -0
  169. package/dist/tenant/ephemeral.js +60 -36
  170. package/dist/tenant/http.d.ts +8 -9
  171. package/dist/tenant/http.js +13 -27
  172. package/dist/tenant/jwt.d.ts +16 -0
  173. package/dist/tenant/jwt.js +50 -0
  174. package/dist/tenant/module.d.ts +3 -0
  175. package/dist/tenant/module.js +6 -12
  176. package/dist/tenant/outbound.d.ts +44 -0
  177. package/dist/tenant/outbound.js +127 -0
  178. package/dist/tenant/pool.d.ts +0 -2
  179. package/dist/tenant/pool.js +0 -18
  180. package/dist/tenant/reset.d.ts +0 -4
  181. package/dist/tenant/reset.js +0 -5
  182. package/dist/tenant/runtime.d.ts +10 -20
  183. package/dist/tenant/runtime.js +59 -42
  184. package/dist/tenant/scheduler.d.ts +7 -2
  185. package/dist/tenant/scheduler.js +7 -3
  186. package/dist/tenant/session-streams.d.ts +125 -0
  187. package/dist/tenant/session-streams.js +399 -0
  188. package/dist/tenant/sessions.d.ts +28 -4
  189. package/dist/tenant/sessions.js +118 -24
  190. package/dist/tenant/signing-keys.d.ts +55 -0
  191. package/dist/tenant/signing-keys.js +172 -0
  192. package/dist/tenant/slim.d.ts +8 -0
  193. package/dist/tenant/slim.js +21 -0
  194. package/dist/tenant/status.d.ts +0 -3
  195. package/dist/tenant/status.js +19 -12
  196. package/dist/tenant/streams.d.ts +60 -67
  197. package/dist/tenant/streams.js +140 -208
  198. package/dist/tenant/subject-limits.d.ts +16 -0
  199. package/dist/tenant/subject-limits.js +45 -0
  200. package/dist/tenant/sweep.d.ts +1 -10
  201. package/dist/tenant/sweep.js +29 -69
  202. package/dist/tenant/tokens.d.ts +14 -0
  203. package/dist/tenant/tokens.js +178 -0
  204. package/dist/tenant/transcript.d.ts +48 -5
  205. package/dist/tenant/transcript.js +58 -1
  206. package/dist/tenant/types.d.ts +29 -7
  207. package/dist/tenant/worker.d.ts +3 -1
  208. package/dist/tenant/worker.js +9 -3
  209. package/dist/vault/service.d.ts +18 -2
  210. package/dist/vault/service.js +57 -13
  211. package/dist/version.d.ts +1 -1
  212. package/dist/version.js +1 -1
  213. package/package.json +20 -8
  214. package/dist/core/executors.d.ts +0 -44
  215. package/dist/core/executors.js +0 -95
  216. package/dist/streams/relay.d.ts +0 -37
  217. package/dist/streams/relay.js +0 -254
  218. package/dist/tenant/live.d.ts +0 -92
  219. package/dist/tenant/live.js +0 -290
  220. package/dist/tenant/routes-tenant.d.ts +0 -12
  221. package/dist/tenant/routes-tenant.js +0 -137
  222. package/dist/tenant/routes.d.ts +0 -9
  223. package/dist/tenant/routes.js +0 -151
@@ -0,0 +1,259 @@
1
+ import { z } from "zod";
2
+ import { newPublishableKey, newPublishableKeyId } from "@nylorun/core/compatibility";
3
+ import { CreatePublishableKeyRequestSchema, CreateTokenRequestSchema, PutAccessPolicyRequestSchema, RevokeSigningKeyRequestSchema, RevokeSubjectRequestSchema, RotateSigningKeysRequestSchema, UpdatePublishableKeyRequestSchema, } from "@nylorun/core/contracts";
4
+ import { AccessPolicyResponse, CreatePublishableKeyRequest, CreateTokenRequest, CreateTokenResponse, Jwks, ListPublishableKeysResponse, PublishableKey, PutAccessPolicyRequest, RevokeSigningKeyRequest, RevokeSubjectRequest, RevokeSubjectResponse, RotateSigningKeysRequest, SigningKeyList, SigningKeyView, UpdatePublishableKeyRequest, } from "../../components.js";
5
+ import { signalSubjectRevoked } from "../../../streams/control.js";
6
+ import { readPolicy, writePolicy } from "../../../tenant/access-policy.js";
7
+ import { currentBasin, endSubjectStreams } from "../../../tenant/session-streams.js";
8
+ import { requireApplication } from "../../../tenant/auth.js";
9
+ import { fail } from "../../../tenant/http.js";
10
+ import { publicJwk, signingKeyView } from "../../../tenant/signing-keys.js";
11
+ import { mintToken } from "../../../tenant/tokens.js";
12
+ import { readJson } from "../body.js";
13
+ import { tenantRoute } from "../define.js";
14
+ import { jsonResponse } from "../respond.js";
15
+ const APPLICATION = { credentials: ["application"], scopes: "never" };
16
+ const json = (schema, description) => ({
17
+ description,
18
+ content: { "application/json": { schema } },
19
+ });
20
+ const body = (schema) => ({
21
+ required: true,
22
+ content: { "application/json": { schema } },
23
+ });
24
+ const keyId = z.object({ keyId: z.string() });
25
+ export function accessRoutes(api) {
26
+ tenantRoute(api, {
27
+ credentials: ["application", "subject", "token", "publishable"],
28
+ scopes: "any",
29
+ browser: true,
30
+ // Public keys: an Action endpoint verifies delivery tokens with them and holds no key.
31
+ anonymous: true,
32
+ }, {
33
+ method: "get",
34
+ path: "/v1/access/jwks",
35
+ tags: ["Access"],
36
+ summary: "Get the public keys subject and delivery tokens are signed with",
37
+ description: "A JSON Web Key Set, to verify a subject token or a delivery token without calling the " +
38
+ "Runtime. No credential is needed: `Nylorun-Tenant` alone names the Tenant.",
39
+ responses: { 200: json(Jwks, "The public keys") },
40
+ }, async (c) => {
41
+ const keys = c.env.tenant.signingKeys;
42
+ const kek = keys.kek();
43
+ const rows = await c.env.tenant.store.tx(async (t) => {
44
+ await keys.ensure(t, kek);
45
+ return t.signingKeys(["standby", "current", "previous"]);
46
+ });
47
+ return jsonResponse(200, { keys: rows.map(publicJwk) });
48
+ });
49
+ tenantRoute(api, APPLICATION, {
50
+ method: "post",
51
+ path: "/v1/tokens",
52
+ tags: ["Access"],
53
+ summary: "Mint a subject token",
54
+ description: "A token for one person and one role of the access policy, for their browser or app to call the Runtime with. At most 15 minutes.",
55
+ request: { body: body(CreateTokenRequest) },
56
+ responses: { 200: json(CreateTokenResponse, "The token, its scopes and expiry") },
57
+ }, async (c) => {
58
+ requireApplication(c.get("scope"));
59
+ return jsonResponse(200, await mintToken(c.env.tenant, CreateTokenRequestSchema.parse(await readJson(c.req.raw))));
60
+ });
61
+ tenantRoute(api, APPLICATION, {
62
+ method: "get",
63
+ path: "/v1/access/policy",
64
+ tags: ["Access"],
65
+ summary: "Get the access policy",
66
+ responses: { 200: json(AccessPolicyResponse, "Roles, the anonymous grant and token lifetime") },
67
+ }, async (c) => {
68
+ requireApplication(c.get("scope"));
69
+ return jsonResponse(200, { policy: await c.env.tenant.store.tx((t) => readPolicy(t)) });
70
+ });
71
+ tenantRoute(api, APPLICATION, {
72
+ method: "put",
73
+ path: "/v1/access/policy",
74
+ tags: ["Access"],
75
+ summary: "Set the access policy",
76
+ request: { body: body(PutAccessPolicyRequest) },
77
+ responses: { 200: json(AccessPolicyResponse, "The policy now in force") },
78
+ }, async (c) => {
79
+ requireApplication(c.get("scope"));
80
+ const request = PutAccessPolicyRequestSchema.parse(await readJson(c.req.raw));
81
+ await c.env.tenant.store.tx((t) => writePolicy(t, request.policy));
82
+ return jsonResponse(200, { policy: request.policy });
83
+ });
84
+ tenantRoute(api, APPLICATION, {
85
+ method: "get",
86
+ path: "/v1/access/signing-keys",
87
+ tags: ["Access"],
88
+ summary: "List signing keys",
89
+ responses: { 200: json(SigningKeyList, "Standby, current, previous and revoked keys") },
90
+ }, async (c) => {
91
+ requireApplication(c.get("scope"));
92
+ const keys = c.env.tenant.signingKeys;
93
+ const kek = keys.kek();
94
+ const rows = await c.env.tenant.store.tx(async (t) => {
95
+ await keys.ensure(t, kek);
96
+ return t.signingKeys();
97
+ });
98
+ return jsonResponse(200, { keys: rows.map(signingKeyView) });
99
+ });
100
+ tenantRoute(api, APPLICATION, {
101
+ method: "post",
102
+ path: "/v1/access/signing-keys/rotate",
103
+ tags: ["Access"],
104
+ summary: "Rotate the signing keys",
105
+ description: "The standby key signs from now on; the current one still verifies tokens it signed. `force` also ends every outstanding token.",
106
+ request: { body: body(RotateSigningKeysRequest) },
107
+ responses: {
108
+ 200: json(SigningKeyList, "The keys after rotation"),
109
+ 409: { description: "The previous key may still verify outstanding tokens" },
110
+ },
111
+ }, async (c) => {
112
+ requireApplication(c.get("scope"));
113
+ const ctx = c.env.tenant;
114
+ const request = RotateSigningKeysRequestSchema.parse(await readJson(c.req.raw));
115
+ const keys = ctx.signingKeys;
116
+ const kek = keys.kek();
117
+ const rows = await ctx.store.tx(async (t) => keys.rotate(t, kek, (await readPolicy(t)).tokens.maxTtlSeconds, request.force === true));
118
+ ctx.config.logger.info("signing keys rotated", { force: request.force === true });
119
+ return jsonResponse(200, { keys: rows.map(signingKeyView) });
120
+ });
121
+ tenantRoute(api, APPLICATION, {
122
+ method: "post",
123
+ path: "/v1/access/signing-keys/{kid}/revoke",
124
+ tags: ["Access"],
125
+ summary: "Revoke a signing key",
126
+ request: { params: z.object({ kid: z.string() }), body: body(RevokeSigningKeyRequest) },
127
+ responses: {
128
+ 200: json(SigningKeyView, "The key, revoked"),
129
+ 409: { description: "Rotate before revoking the current key" },
130
+ },
131
+ }, async (c) => {
132
+ requireApplication(c.get("scope"));
133
+ const ctx = c.env.tenant;
134
+ RevokeSigningKeyRequestSchema.parse(await readJson(c.req.raw));
135
+ const row = await ctx.store.tx((t) => ctx.signingKeys.revoke(t, c.req.param("kid")));
136
+ ctx.config.logger.info("signing key revoked", { kid: row.id });
137
+ return jsonResponse(200, signingKeyView(row));
138
+ });
139
+ tenantRoute(api, APPLICATION, {
140
+ method: "get",
141
+ path: "/v1/access/publishable-keys",
142
+ tags: ["Access"],
143
+ summary: "List publishable keys",
144
+ responses: { 200: json(ListPublishableKeysResponse, "The keys, revoked ones included") },
145
+ }, async (c) => {
146
+ requireApplication(c.get("scope"));
147
+ return jsonResponse(200, {
148
+ keys: (await c.env.tenant.store.tx((t) => t.publishableKeys())).map(publishableKeyView),
149
+ });
150
+ });
151
+ tenantRoute(api, APPLICATION, {
152
+ method: "post",
153
+ path: "/v1/access/publishable-keys",
154
+ tags: ["Access"],
155
+ summary: "Create a publishable key",
156
+ description: "A key a browser page on one of the origins sends as `Nylorun-Key`. Public by design; it names the Tenant and grants the policy's anonymous scopes.",
157
+ request: { body: body(CreatePublishableKeyRequest) },
158
+ responses: {
159
+ 200: json(PublishableKey, "The key"),
160
+ 409: { description: "A key with this name exists" },
161
+ },
162
+ }, async (c) => {
163
+ requireApplication(c.get("scope"));
164
+ const ctx = c.env.tenant;
165
+ const request = CreatePublishableKeyRequestSchema.parse(await readJson(c.req.raw));
166
+ const row = {
167
+ id: newPublishableKeyId(),
168
+ key: newPublishableKey(ctx.config.tenantId),
169
+ name: request.name,
170
+ originsJson: JSON.stringify(request.origins),
171
+ createdAt: new Date().toISOString(),
172
+ revokedAt: null,
173
+ };
174
+ await ctx.store.tx(async (t) => {
175
+ if ((await t.publishableKeys()).some((other) => other.name === request.name))
176
+ fail(409, `A publishable key named ${request.name} exists`);
177
+ await t.insertPublishableKey(row);
178
+ });
179
+ ctx.config.logger.info("publishable key created", { keyId: row.id });
180
+ return jsonResponse(200, publishableKeyView(row));
181
+ });
182
+ for (const method of ["put", "delete"])
183
+ tenantRoute(api, APPLICATION, {
184
+ method,
185
+ path: "/v1/access/publishable-keys/{keyId}",
186
+ tags: ["Access"],
187
+ summary: method === "put" ? "Set a publishable key's origins" : "Revoke a publishable key",
188
+ request: {
189
+ params: keyId,
190
+ ...(method === "put" ? { body: body(UpdatePublishableKeyRequest) } : {}),
191
+ },
192
+ responses: {
193
+ 200: json(PublishableKey, method === "put" ? "The key" : "The key, revoked (again: unchanged)"),
194
+ },
195
+ }, async (c) => {
196
+ requireApplication(c.get("scope"));
197
+ const ctx = c.env.tenant;
198
+ const id = c.req.param("keyId");
199
+ const patch = method === "put"
200
+ ? {
201
+ originsJson: JSON.stringify(UpdatePublishableKeyRequestSchema.parse(await readJson(c.req.raw)).origins),
202
+ }
203
+ : { revokedAt: new Date().toISOString() };
204
+ const row = await ctx.store.tx(async (t) => {
205
+ const existing = (await t.publishableKey(id)) ?? fail(404, "Publishable key not found");
206
+ // Revoking twice keeps the first time.
207
+ if (method === "delete" && existing.revokedAt !== null)
208
+ return existing;
209
+ await t.updatePublishableKey(id, patch);
210
+ return { ...existing, ...patch };
211
+ });
212
+ ctx.config.logger.info(method === "put" ? "publishable key updated" : "publishable key revoked", { keyId: row.id });
213
+ return jsonResponse(200, publishableKeyView(row));
214
+ });
215
+ tenantRoute(api, APPLICATION, {
216
+ method: "post",
217
+ path: "/v1/access/revocations",
218
+ tags: ["Access"],
219
+ summary: "Revoke a subject",
220
+ description: "Ends every token minted for the subject so far, and their open streams on every process. Turns in progress continue.",
221
+ request: { body: body(RevokeSubjectRequest) },
222
+ responses: { 200: json(RevokeSubjectResponse, "The subject's new epoch") },
223
+ }, async (c) => {
224
+ requireApplication(c.get("scope"));
225
+ const request = RevokeSubjectRequestSchema.parse(await readJson(c.req.raw));
226
+ return jsonResponse(200, await revokeSubject(c.env.tenant, request.subject));
227
+ });
228
+ }
229
+ /** A publishable key as the API shows it. */
230
+ export function publishableKeyView(row) {
231
+ return {
232
+ id: row.id,
233
+ name: row.name,
234
+ key: row.key,
235
+ origins: JSON.parse(row.originsJson),
236
+ createdAt: row.createdAt,
237
+ revokedAt: row.revokedAt,
238
+ };
239
+ }
240
+ /**
241
+ * Ends every token of `subject` minted so far: bumps its epoch, then ends its open streams
242
+ * here and, through `tenant/control`, on every other process. Running turns continue.
243
+ */
244
+ export async function revokeSubject(ctx, subject) {
245
+ const epoch = await ctx.store.tx(async (t) => {
246
+ const next = await t.bumpSubjectEpoch(subject, new Date().toISOString());
247
+ t.afterCommit(async () => {
248
+ endSubjectStreams(ctx.sessionStreams, subject, next);
249
+ const streams = ctx.sessionStreams.wiring?.streams;
250
+ if (streams)
251
+ await signalSubjectRevoked(streams, currentBasin(ctx), subject, next).catch((error) => ctx.config.logger.warn("subject revocation signal failed", {
252
+ message: error instanceof Error ? error.message : String(error),
253
+ }));
254
+ });
255
+ return next;
256
+ });
257
+ ctx.config.logger.info("subject revoked", { epoch });
258
+ return { subject, epoch };
259
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Session commands, and the callbacks an Action endpoint makes about the Action it was
3
+ * delivered: heartbeats and sandbox tools, with the Action's delivery token (Action endpoints).
4
+ * The background result is in `endpoints.ts`.
5
+ */
6
+ import type { OpenAPIHono } from "@hono/zod-openapi";
7
+ import type { TenantEnv } from "../app.js";
8
+ export declare function actionRoutes(api: OpenAPIHono<TenantEnv>): void;
@@ -0,0 +1,76 @@
1
+ import { z } from "zod";
2
+ import { SessionCommandSchema } from "@nylorun/core/contracts";
3
+ import { AcceptedResponse, DeliveryHeartbeatResponse, SandboxToolOutcome, SessionCommand, } from "../../components.js";
4
+ import { actionSandboxTool } from "../../../tenant/actions.js";
5
+ import { command } from "../../../tenant/commands.js";
6
+ import { deliveryHeartbeat } from "../../../tenant/delivery.js";
7
+ import { fail } from "../../../tenant/http.js";
8
+ import { readJson } from "../body.js";
9
+ import { tenantRoute } from "../define.js";
10
+ import { jsonResponse } from "../respond.js";
11
+ /** An Action's callbacks: only the delivery token of its current delivery. */
12
+ const CALLBACK = { credentials: ["delivery"], scopes: "never" };
13
+ const json = (schema, description) => ({
14
+ description,
15
+ content: { "application/json": { schema } },
16
+ });
17
+ const body = (schema) => ({
18
+ required: true,
19
+ content: { "application/json": { schema } },
20
+ });
21
+ const actionId = z.object({ actionId: z.string() });
22
+ const SANDBOX_TOOLS = ["bash", "read", "write", "edit", "grep", "glob"];
23
+ function deliveryOf(scope) {
24
+ if (scope.kind !== "delivery")
25
+ return fail(403, "A delivery token is required");
26
+ return scope;
27
+ }
28
+ export function actionRoutes(api) {
29
+ tenantRoute(api, CALLBACK, {
30
+ method: "post",
31
+ path: "/v1/actions/{actionId}/sandbox/{tool}",
32
+ tags: ["Action endpoints"],
33
+ summary: "Run a sandbox tool for an Action being delivered",
34
+ description: "Called by the Action endpoint with the Action's delivery token. The body is the tool's input.",
35
+ request: {
36
+ params: actionId.extend({ tool: z.enum(SANDBOX_TOOLS) }),
37
+ body: body(z.looseObject({}).meta({ description: "The tool's input" })),
38
+ },
39
+ responses: {
40
+ 200: json(SandboxToolOutcome, "The tool's output, or why it failed"),
41
+ 409: { description: "The delivery was cancelled, lost or sent again" },
42
+ },
43
+ }, async (c) => jsonResponse(200, await actionSandboxTool(c.env.tenant, deliveryOf(c.get("scope")), c.req.param("actionId"), c.req.param("tool"), () => readJson(c.req.raw), c.req.raw.signal)));
44
+ tenantRoute(api, CALLBACK, {
45
+ method: "post",
46
+ path: "/v1/actions/{actionId}/heartbeat",
47
+ tags: ["Action endpoints"],
48
+ summary: "Keep a background delivery alive",
49
+ description: "Called by an Action endpoint that answered a delivery with 202: extends the delivery by a " +
50
+ "lease and returns a fresh delivery token.",
51
+ request: { params: actionId },
52
+ responses: {
53
+ 200: json(DeliveryHeartbeatResponse, "The delivery's new deadline, and a fresh token"),
54
+ 409: { description: "The delivery was cancelled, lost or sent again" },
55
+ },
56
+ }, async (c) => jsonResponse(200, await deliveryHeartbeat(c.env.tenant, deliveryOf(c.get("scope")), c.req.param("actionId"))));
57
+ tenantRoute(api, {
58
+ credentials: ["application", "subject", "token"],
59
+ scopes: ["sessions:own"],
60
+ browser: true,
61
+ }, {
62
+ method: "post",
63
+ path: "/v1/sessions/{sessionId}/commands",
64
+ tags: ["Sessions"],
65
+ summary: "Send a command to a session",
66
+ description: "A message starts a turn; the others answer what the session waits on, or cancel. Idempotent on the command's `idempotencyKey`.",
67
+ request: {
68
+ params: z.object({ sessionId: z.string() }),
69
+ body: body(SessionCommand),
70
+ },
71
+ responses: {
72
+ 200: json(AcceptedResponse, "The command, accepted"),
73
+ 409: { description: "A turn is running, or the session does not wait on this" },
74
+ },
75
+ }, async (c) => jsonResponse(200, await command(c.env.tenant, c.req.param("sessionId"), SessionCommandSchema.parse(await readJson(c.req.raw)), c.get("scope"))));
76
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Action endpoints: where the Runtime delivers each agent's Actions (design: Action endpoints
3
+ * §4.2). Registered, listed and removed with the application key; no subject reaches them.
4
+ */
5
+ import type { OpenAPIHono } from "@hono/zod-openapi";
6
+ import type { TenantEnv } from "../app.js";
7
+ export declare function endpointRoutes(api: OpenAPIHono<TenantEnv>): void;
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+ import { PutEndpointsRequestSchema } from "@nylorun/core/contracts";
3
+ import { DeleteEndpointResponse, EndpointPingResponse, ListEndpointsResponse, PutEndpointsRequest, } from "../../components.js";
4
+ import { ActionOutcomeSchema } from "@nylorun/core/contracts";
5
+ import { ActionOutcome, ActionResultReceipt } from "../../components.js";
6
+ import { deliveryResult, pingEndpoint } from "../../../tenant/delivery.js";
7
+ import { requireApplication } from "../../../tenant/auth.js";
8
+ import { fail } from "../../../tenant/http.js";
9
+ import { deleteEndpoint, listEndpoints, putEndpoints, } from "../../../tenant/endpoints.js";
10
+ import { readJson } from "../body.js";
11
+ import { tenantRoute } from "../define.js";
12
+ import { jsonResponse } from "../respond.js";
13
+ const APPLICATION = { credentials: ["application"], scopes: "never" };
14
+ const json = (schema, description) => ({
15
+ description,
16
+ content: { "application/json": { schema } },
17
+ });
18
+ export function endpointRoutes(api) {
19
+ tenantRoute(api, APPLICATION, {
20
+ method: "get",
21
+ path: "/v1/endpoints",
22
+ tags: ["Action endpoints"],
23
+ summary: "List Action endpoints",
24
+ description: "Each agent's Action endpoint, with what recent deliveries say about it.",
25
+ responses: { 200: json(ListEndpointsResponse, "The endpoints") },
26
+ }, async (c) => {
27
+ requireApplication(c.get("scope"));
28
+ return jsonResponse(200, await listEndpoints(c.env.tenant));
29
+ });
30
+ tenantRoute(api, APPLICATION, {
31
+ method: "put",
32
+ path: "/v1/endpoints",
33
+ tags: ["Action endpoints"],
34
+ summary: "Register Action endpoints",
35
+ description: "Points the Runtime at the URL that runs each agent's Actions. A new URL starts with no " +
36
+ "health.",
37
+ request: {
38
+ body: { required: true, content: { "application/json": { schema: PutEndpointsRequest } } },
39
+ },
40
+ responses: { 200: json(ListEndpointsResponse, "The registered endpoints") },
41
+ }, async (c) => {
42
+ const principalId = requireApplication(c.get("scope"));
43
+ return jsonResponse(200, await putEndpoints(c.env.tenant, principalId, PutEndpointsRequestSchema.parse(await readJson(c.req.raw))));
44
+ });
45
+ tenantRoute(api, APPLICATION, {
46
+ method: "delete",
47
+ path: "/v1/endpoints/{agentId}",
48
+ tags: ["Action endpoints"],
49
+ summary: "Remove an agent's Action endpoint",
50
+ description: "The agent's pending Actions wait until an endpoint is registered again.",
51
+ request: { params: z.object({ agentId: z.string() }) },
52
+ responses: {
53
+ 200: json(DeleteEndpointResponse, "The endpoint was removed"),
54
+ 404: { description: "The agent has no endpoint" },
55
+ },
56
+ }, async (c) => {
57
+ requireApplication(c.get("scope"));
58
+ return jsonResponse(200, await deleteEndpoint(c.env.tenant, c.req.param("agentId")));
59
+ });
60
+ tenantRoute(api, APPLICATION, {
61
+ method: "post",
62
+ path: "/v1/endpoints/{agentId}/ping",
63
+ tags: ["Action endpoints"],
64
+ summary: "Ping an agent's Action endpoint",
65
+ description: "Sends a signed ping through the endpoint and records what it answers it serves. A " +
66
+ "wrong URL, a tunnel that is down or a handler that does not serve the agent answers 502.",
67
+ request: { params: z.object({ agentId: z.string() }) },
68
+ responses: {
69
+ 200: json(EndpointPingResponse, "What the endpoint serves"),
70
+ 404: { description: "The agent has no endpoint" },
71
+ 502: { description: "The endpoint did not answer the ping" },
72
+ },
73
+ }, async (c) => {
74
+ requireApplication(c.get("scope"));
75
+ return jsonResponse(200, await pingEndpoint(c.env.tenant, c.req.param("agentId")));
76
+ });
77
+ tenantRoute(api, { credentials: ["delivery"], scopes: "never" }, {
78
+ method: "post",
79
+ path: "/v1/actions/{actionId}/result",
80
+ tags: ["Action endpoints"],
81
+ summary: "Post the outcome of an Action answered with 202",
82
+ description: "Called by an Action endpoint with the Action's delivery token after it answered the " +
83
+ "delivery with 202. The same result again returns the first receipt.",
84
+ request: {
85
+ params: z.object({ actionId: z.string() }),
86
+ body: { required: true, content: { "application/json": { schema: ActionOutcome } } },
87
+ },
88
+ responses: {
89
+ 200: json(ActionResultReceipt, "The outcome was recorded"),
90
+ 409: {
91
+ description: "The delivery was cancelled, lost or sent again, or the Action has another result",
92
+ },
93
+ },
94
+ }, async (c) => {
95
+ const scope = c.get("scope");
96
+ if (scope.kind !== "delivery")
97
+ return fail(403, "A delivery token is required");
98
+ return jsonResponse(200, await deliveryResult(c.env.tenant, scope, c.req.param("actionId"), ActionOutcomeSchema.parse(await readJson(c.req.raw))));
99
+ });
100
+ }
@@ -0,0 +1,3 @@
1
+ import type { OpenAPIHono } from "@hono/zod-openapi";
2
+ import type { TenantEnv } from "../app.js";
3
+ export declare function sessionRoutes(api: OpenAPIHono<TenantEnv>): void;
@@ -0,0 +1,205 @@
1
+ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
2
+ import { z } from "zod";
3
+ import { PutAgentRequestSchema, PutSessionRequestSchema } from "@nylorun/core/contracts";
4
+ import { handleSessionSandboxTool, SandboxRouteError } from "../../../core/sandbox-routes.js";
5
+ import { ListAgentsResponse, ListPublicAgentsResponse, ListSessionsResponse, SessionEvent, PutAgentRequest, PutAgentResponse, PutSessionRequest, SandboxToolOutcome, SessionItemsResponse, SessionView, StreamClosedFrame, } from "../../components.js";
6
+ import { sandboxRouteDeps } from "../../../tenant/actions.js";
7
+ import { accessOf, requireApplication } from "../../../tenant/auth.js";
8
+ import { loadSession, sessionOf } from "../../../tenant/context.js";
9
+ import { fail } from "../../../tenant/http.js";
10
+ import { readHistory, requestCursor, streamSessionEvents } from "../../../tenant/session-streams.js";
11
+ import { listAgentsPublic, listDefinitions, listSessions, putDefinition, putSession, sessionView, } from "../../../tenant/sessions.js";
12
+ import { readJson } from "../body.js";
13
+ import { tenantRoute } from "../define.js";
14
+ import { jsonResponse } from "../respond.js";
15
+ const OWN_SESSIONS = {
16
+ credentials: ["application", "subject", "token"],
17
+ scopes: ["sessions:own"],
18
+ browser: true,
19
+ };
20
+ const json = (schema, description) => ({
21
+ description,
22
+ content: { "application/json": { schema } },
23
+ });
24
+ const body = (schema) => ({
25
+ required: true,
26
+ content: { "application/json": { schema } },
27
+ });
28
+ const sessionId = z.object({ sessionId: z.string() });
29
+ const SANDBOX_TOOLS = ["bash", "read", "write", "edit", "grep", "glob"];
30
+ const resume = z.object({
31
+ cursor: z
32
+ .string()
33
+ .optional()
34
+ .meta({ description: "Continue after this event's cursor; `Last-Event-ID` works too" }),
35
+ });
36
+ function urlOf(incoming) {
37
+ return new URL(incoming.url ?? "/", "http://runtime");
38
+ }
39
+ /**
40
+ * A session route below the session itself: the session must exist and be the caller's, and a
41
+ * resume cursor must be well formed, before anything else.
42
+ */
43
+ async function sessionBelow(ctx, incoming, id, access) {
44
+ await loadSession(ctx, id, access);
45
+ return requestCursor(incoming, urlOf(incoming));
46
+ }
47
+ export function sessionRoutes(api) {
48
+ tenantRoute(api, {
49
+ credentials: ["application", "subject", "token", "publishable"],
50
+ scopes: ["agents:read", "agents:write"],
51
+ browser: true,
52
+ }, {
53
+ method: "get",
54
+ path: "/v1/agents",
55
+ tags: ["Agents"],
56
+ summary: "List agents",
57
+ description: "With an application key, every definition, manifest included. With a subject token or a publishable key, only the agents it may use, by name.",
58
+ responses: {
59
+ 200: json(z.union([ListAgentsResponse, ListPublicAgentsResponse]), "The agents"),
60
+ },
61
+ }, async (c) => {
62
+ const scope = c.get("scope");
63
+ const ctx = c.env.tenant;
64
+ if (scope.kind === "publishable")
65
+ return jsonResponse(200, await listAgentsPublic(ctx, scope.agents));
66
+ return jsonResponse(200, scope.kind === "token"
67
+ ? await listAgentsPublic(ctx, scope.agents)
68
+ : await listDefinitions(ctx));
69
+ });
70
+ tenantRoute(api, { credentials: ["application", "subject"], scopes: ["agents:write"] }, {
71
+ method: "put",
72
+ path: "/v1/agents/{agentId}",
73
+ tags: ["Agents"],
74
+ summary: "Put an agent's definition",
75
+ request: { params: z.object({ agentId: z.string() }), body: body(PutAgentRequest) },
76
+ responses: { 200: json(PutAgentResponse, "The definition, stored") },
77
+ }, async (c) => {
78
+ return jsonResponse(200, await putDefinition(c.env.tenant, c.req.param("agentId"), PutAgentRequestSchema.parse(await readJson(c.req.raw))));
79
+ });
80
+ tenantRoute(api, OWN_SESSIONS, {
81
+ method: "get",
82
+ path: "/v1/sessions",
83
+ tags: ["Sessions"],
84
+ summary: "List sessions",
85
+ description: "Acting for a person, only theirs.",
86
+ request: { query: z.object({ agentId: z.string().optional() }) },
87
+ responses: { 200: json(ListSessionsResponse, "The sessions") },
88
+ }, async (c) => {
89
+ const scope = c.get("scope");
90
+ return jsonResponse(200, await listSessions(c.env.tenant, c.req.query("agentId") ?? null, accessOf(scope)));
91
+ });
92
+ tenantRoute(api, OWN_SESSIONS, {
93
+ method: "put",
94
+ path: "/v1/sessions/{sessionId}",
95
+ tags: ["Sessions"],
96
+ summary: "Open a session",
97
+ description: "Creates the session, or re-attaches vaults to an identical one. Its sandbox is chosen now and fixed for its life.",
98
+ request: { params: sessionId, body: body(PutSessionRequest) },
99
+ responses: {
100
+ 200: json(SessionView, "The session"),
101
+ 409: { description: "A session with this id was opened with other parameters" },
102
+ },
103
+ }, async (c) => {
104
+ const scope = c.get("scope");
105
+ const ctx = c.env.tenant;
106
+ const request = PutSessionRequestSchema.parse(await readJson(c.req.raw));
107
+ // Agent code may trust `info`: only an app server sets it.
108
+ if (scope.kind === "token" && request.info !== undefined)
109
+ fail(403, "A subject token cannot set session info", { code: "scope_required" });
110
+ const session = await putSession(ctx, c.req.param("sessionId"), request, accessOf(scope));
111
+ return jsonResponse(200, await ctx.store.tx((t) => sessionView(t, session)));
112
+ });
113
+ tenantRoute(api, OWN_SESSIONS, {
114
+ method: "get",
115
+ path: "/v1/sessions/{sessionId}",
116
+ tags: ["Sessions"],
117
+ summary: "Get a session",
118
+ request: { params: sessionId },
119
+ responses: { 200: json(SessionView, "The session") },
120
+ }, async (c) => {
121
+ const scope = c.get("scope");
122
+ const ctx = c.env.tenant;
123
+ const id = c.req.param("sessionId");
124
+ return jsonResponse(200, await ctx.store.tx(async (t) => sessionView(t, await sessionOf(t, id, accessOf(scope)))));
125
+ });
126
+ tenantRoute(api, OWN_SESSIONS, {
127
+ method: "get",
128
+ path: "/v1/sessions/{sessionId}/items",
129
+ tags: ["Sessions"],
130
+ summary: "Read a session's history",
131
+ request: {
132
+ params: sessionId,
133
+ query: resume.extend({
134
+ agent: z
135
+ .string()
136
+ .optional()
137
+ .meta({ description: "Only the events of this agent (the root agent, or one it uses as a tool)" }),
138
+ }),
139
+ },
140
+ responses: { 200: json(SessionItemsResponse, "Events up to now, and the cursor to continue") },
141
+ }, async (c) => {
142
+ const scope = c.get("scope");
143
+ const ctx = c.env.tenant;
144
+ const id = c.req.param("sessionId");
145
+ const cursor = await sessionBelow(ctx, c.env.incoming, id, accessOf(scope));
146
+ return jsonResponse(200, await readHistory(ctx, id, cursor, c.req.query("agent")));
147
+ });
148
+ tenantRoute(api, OWN_SESSIONS, {
149
+ method: "get",
150
+ path: "/v1/sessions/{sessionId}/events",
151
+ tags: ["Sessions"],
152
+ summary: "Follow a session's events",
153
+ description: "Server-sent events, each with its type as `event` and its cursor as `id`, from the cursor on. Events are on the `nylorun.event/2` envelope, typed by `SessionEvent`; a client ignores a type it does not know. A subject token's stream ends with `event: nylorun.closed` when the token expires or the subject is revoked. A `: keepalive` comment every 15 seconds.",
154
+ request: { params: sessionId, query: resume },
155
+ responses: {
156
+ 200: {
157
+ description: "The session's events",
158
+ content: {
159
+ "text/event-stream": {
160
+ itemSchema: z.union([SessionEvent, StreamClosedFrame]),
161
+ },
162
+ },
163
+ },
164
+ },
165
+ }, async (c) => {
166
+ const scope = c.get("scope");
167
+ const ctx = c.env.tenant;
168
+ const { incoming, outgoing } = c.env;
169
+ const id = c.req.param("sessionId");
170
+ const cursor = await sessionBelow(ctx, incoming, id, accessOf(scope));
171
+ await streamSessionEvents(ctx, incoming, outgoing, id, cursor, scope.kind === "token"
172
+ ? { subject: scope.subject, epoch: scope.epoch, expiresAt: scope.expiresAt }
173
+ : undefined);
174
+ return RESPONSE_ALREADY_SENT;
175
+ });
176
+ tenantRoute(api, { credentials: ["application"], scopes: "never" }, {
177
+ method: "post",
178
+ path: "/v1/sessions/{sessionId}/sandbox/{tool}",
179
+ tags: ["Sessions"],
180
+ summary: "Run a sandbox tool in a session",
181
+ description: "For an app's own tools; not while a turn is running.",
182
+ request: {
183
+ params: sessionId.extend({ tool: z.enum(SANDBOX_TOOLS) }),
184
+ body: body(z.looseObject({}).meta({ description: "The tool's input" })),
185
+ },
186
+ responses: {
187
+ 200: json(SandboxToolOutcome, "The tool's output, or why it failed"),
188
+ 409: { description: "A turn is running" },
189
+ },
190
+ }, async (c) => {
191
+ const scope = c.get("scope");
192
+ const ctx = c.env.tenant;
193
+ const id = c.req.param("sessionId");
194
+ await sessionBelow(ctx, c.env.incoming, id, accessOf(scope));
195
+ requireApplication(scope);
196
+ try {
197
+ return jsonResponse(200, await handleSessionSandboxTool(sandboxRouteDeps(ctx), id, c.req.param("tool"), await readJson(c.req.raw), c.req.raw.signal));
198
+ }
199
+ catch (error) {
200
+ if (error instanceof SandboxRouteError)
201
+ fail(error.status, error.message);
202
+ throw error;
203
+ }
204
+ });
205
+ }