@nylorun/runtime 0.17.0-beta → 0.19.1-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 +155 -0
  2. package/README.md +89 -58
  3. package/dist/adapters/execution/restate.d.ts +1 -3
  4. package/dist/adapters/execution/restate.js +4 -31
  5. package/dist/adapters/replication/pgoutput.js +1 -1
  6. package/dist/adapters/sandbox/local.js +1 -1
  7. package/dist/adapters/sandbox/virtual.js +1 -1
  8. package/dist/api/ag-ui/routes.js +1 -1
  9. package/dist/api/ag-ui/translate.d.ts +1 -3
  10. package/dist/api/ag-ui/translate.js +2 -17
  11. package/dist/api/components.d.ts +4871 -2614
  12. package/dist/api/components.js +12 -11
  13. package/dist/api/gate/routes.d.ts +7 -8
  14. package/dist/api/gate/routes.js +58 -52
  15. package/dist/api/http/app.d.ts +6 -0
  16. package/dist/api/http/app.js +18 -14
  17. package/dist/api/http/define.d.ts +14 -2
  18. package/dist/api/http/define.js +34 -15
  19. package/dist/api/http/routes/access.d.ts +3 -3
  20. package/dist/api/http/routes/access.js +18 -17
  21. package/dist/api/http/routes/artifacts.js +2 -4
  22. package/dist/api/http/routes/files.d.ts +8 -0
  23. package/dist/api/http/routes/files.js +77 -0
  24. package/dist/api/http/routes/keys.d.ts +10 -0
  25. package/dist/api/http/routes/keys.js +64 -0
  26. package/dist/api/http/routes/me.js +5 -2
  27. package/dist/api/http/routes/reads.d.ts +66 -0
  28. package/dist/api/http/routes/reads.js +127 -0
  29. package/dist/api/http/routes/sandboxes.js +7 -4
  30. package/dist/api/http/routes/sessions.js +25 -8
  31. package/dist/api/http/routes/tenant.js +15 -18
  32. package/dist/api/http/routes/vaults.d.ts +10 -9
  33. package/dist/api/http/routes/vaults.js +27 -23
  34. package/dist/api/openapi.d.ts +10 -11
  35. package/dist/api/openapi.js +288 -63
  36. package/dist/artifacts/links.js +1 -1
  37. package/dist/core/flow-host.d.ts +44 -23
  38. package/dist/core/flow-host.js +88 -106
  39. package/dist/core/provider.d.ts +4 -1
  40. package/dist/core/provider.js +6 -1
  41. package/dist/core/sandbox-routes.d.ts +0 -9
  42. package/dist/core/sandbox-routes.js +2 -21
  43. package/dist/execution/memory.d.ts +4 -5
  44. package/dist/execution/memory.js +11 -25
  45. package/dist/execution/types.d.ts +3 -33
  46. package/dist/execution/types.js +2 -12
  47. package/dist/gates/http-tool.d.ts +86 -0
  48. package/dist/gates/http-tool.js +197 -0
  49. package/dist/gates/mcp-handler.d.ts +3 -0
  50. package/dist/gates/mcp-handler.js +1 -3
  51. package/dist/gates/meter.d.ts +2 -2
  52. package/dist/gates/meter.js +6 -0
  53. package/dist/gates/tenant-vaults.d.ts +5 -9
  54. package/dist/gates/tool-calls.d.ts +7 -15
  55. package/dist/gates/tool-calls.js +4 -4
  56. package/dist/gates/tool-client.d.ts +1 -1
  57. package/dist/gates/tool-client.js +23 -47
  58. package/dist/gates/tool-contract.d.ts +27 -39
  59. package/dist/gates/tool-contract.js +30 -21
  60. package/dist/gates/tool-gate.d.ts +25 -24
  61. package/dist/gates/tool-gate.js +11 -7
  62. package/dist/harness/calls.d.ts +10 -4
  63. package/dist/harness/calls.js +15 -6
  64. package/dist/harness/executors.d.ts +1 -1
  65. package/dist/harness/executors.js +17 -12
  66. package/dist/harness/main.d.ts +2 -2
  67. package/dist/harness/main.js +5 -13
  68. package/dist/harness/service.d.ts +4 -5
  69. package/dist/harness/service.js +9 -2
  70. package/dist/harness-api/in-process.js +2 -2
  71. package/dist/harness-api/record.d.ts +6 -3
  72. package/dist/harness-api/record.js +65 -72
  73. package/dist/harness-api/server.d.ts +1 -9
  74. package/dist/harness-api/server.js +19 -17
  75. package/dist/harness-api/start.js +1 -5
  76. package/dist/harness-api/workspace.js +2 -3
  77. package/dist/harness-api/ws-server.js +1 -1
  78. package/dist/host/app.d.ts +2 -9
  79. package/dist/host/app.js +15 -25
  80. package/dist/host/config-for.d.ts +1 -5
  81. package/dist/host/config-for.js +0 -3
  82. package/dist/host/config.d.ts +0 -5
  83. package/dist/host/create-host.d.ts +3 -33
  84. package/dist/host/create-host.js +18 -94
  85. package/dist/host/environment.d.ts +0 -3
  86. package/dist/host/environment.js +0 -9
  87. package/dist/host/gates.d.ts +4 -1
  88. package/dist/host/gates.js +3 -2
  89. package/dist/host/http.d.ts +1 -2
  90. package/dist/host/http.js +1 -7
  91. package/dist/host/index.d.ts +2 -2
  92. package/dist/host/index.js +2 -2
  93. package/dist/host/main.js +27 -34
  94. package/dist/host/operate.d.ts +29 -0
  95. package/dist/host/operate.js +223 -0
  96. package/dist/host/stack-config.d.ts +12 -12
  97. package/dist/host/stack-config.js +5 -32
  98. package/dist/index.d.ts +1 -1
  99. package/dist/index.js +1 -1
  100. package/dist/infra/workers.d.ts +1 -1
  101. package/dist/keys/keys.js +2 -2
  102. package/dist/management-openapi.json +6491 -0
  103. package/dist/mcp/connect.d.ts +8 -4
  104. package/dist/mcp/connect.js +6 -26
  105. package/dist/mcp/pool.d.ts +5 -9
  106. package/dist/mcp/pool.js +8 -19
  107. package/dist/mcp/snapshot.d.ts +8 -2
  108. package/dist/mcp/snapshot.js +29 -2
  109. package/dist/media.d.ts +1 -1
  110. package/dist/media.js +1 -1
  111. package/dist/model/pi-model.js +3 -0
  112. package/dist/openapi.json +9372 -17030
  113. package/dist/reads/access.d.ts +4 -0
  114. package/dist/reads/access.js +12 -0
  115. package/dist/reads/cursor.d.ts +11 -0
  116. package/dist/reads/cursor.js +80 -0
  117. package/dist/reads/types.d.ts +25 -0
  118. package/dist/reads/types.js +1 -0
  119. package/dist/sandbox/manager.d.ts +16 -0
  120. package/dist/sandbox/manager.js +40 -0
  121. package/dist/sandbox/session-sandbox.js +13 -3
  122. package/dist/sandbox/skills.d.ts +13 -0
  123. package/dist/sandbox/skills.js +46 -0
  124. package/dist/sandbox/types.d.ts +2 -2
  125. package/dist/store/ownership.d.ts +9 -0
  126. package/dist/store/ownership.js +13 -0
  127. package/dist/store/postgres/connect.d.ts +2 -0
  128. package/dist/store/postgres/connect.js +29 -0
  129. package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
  130. package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
  131. package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
  132. package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
  133. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  134. package/dist/store/postgres/reads.d.ts +3 -0
  135. package/dist/store/postgres/reads.js +272 -0
  136. package/dist/store/postgres/schema.d.ts +294 -489
  137. package/dist/store/postgres/schema.js +41 -76
  138. package/dist/store/postgres/store.js +51 -154
  139. package/dist/store/postgres/tenant.d.ts +34 -2
  140. package/dist/store/postgres/tenant.js +16 -32
  141. package/dist/store/types.d.ts +27 -96
  142. package/dist/store/types.js +0 -1
  143. package/dist/streams/control.d.ts +0 -2
  144. package/dist/streams/control.js +0 -5
  145. package/dist/streams/types.d.ts +1 -10
  146. package/dist/tenant/advance.d.ts +3 -3
  147. package/dist/tenant/advance.js +10 -7
  148. package/dist/tenant/auth.d.ts +14 -9
  149. package/dist/tenant/auth.js +24 -28
  150. package/dist/tenant/cause.d.ts +3 -3
  151. package/dist/tenant/cause.js +1 -1
  152. package/dist/tenant/commands.d.ts +2 -18
  153. package/dist/tenant/commands.js +35 -172
  154. package/dist/tenant/context.d.ts +14 -24
  155. package/dist/tenant/definition-files.d.ts +24 -0
  156. package/dist/tenant/definition-files.js +103 -0
  157. package/dist/tenant/effects.d.ts +18 -20
  158. package/dist/tenant/effects.js +56 -119
  159. package/dist/tenant/ephemeral.d.ts +7 -6
  160. package/dist/tenant/ephemeral.js +3 -11
  161. package/dist/tenant/host-token.js +1 -1
  162. package/dist/tenant/jwt.js +1 -1
  163. package/dist/tenant/module.d.ts +1 -4
  164. package/dist/tenant/module.js +3 -12
  165. package/dist/tenant/operator-keys.d.ts +34 -8
  166. package/dist/tenant/operator-keys.js +50 -18
  167. package/dist/tenant/outbound.d.ts +10 -1
  168. package/dist/tenant/outbound.js +44 -27
  169. package/dist/tenant/paths.d.ts +1 -1
  170. package/dist/tenant/paths.js +1 -3
  171. package/dist/tenant/principals.d.ts +11 -2
  172. package/dist/tenant/principals.js +27 -8
  173. package/dist/tenant/run-token.js +3 -3
  174. package/dist/tenant/runtime.d.ts +4 -6
  175. package/dist/tenant/runtime.js +19 -18
  176. package/dist/tenant/sandboxes.d.ts +4 -1
  177. package/dist/tenant/sandboxes.js +11 -0
  178. package/dist/tenant/scheduler.d.ts +1 -5
  179. package/dist/tenant/scheduler.js +2 -5
  180. package/dist/tenant/session-streams.d.ts +2 -1
  181. package/dist/tenant/session-streams.js +39 -5
  182. package/dist/tenant/session.d.ts +8 -41
  183. package/dist/tenant/session.js +0 -37
  184. package/dist/tenant/sessions.js +16 -8
  185. package/dist/tenant/signing-keys.d.ts +3 -3
  186. package/dist/tenant/signing-keys.js +5 -5
  187. package/dist/tenant/skill-tool.d.ts +20 -0
  188. package/dist/tenant/skill-tool.js +72 -0
  189. package/dist/tenant/slim.d.ts +1 -1
  190. package/dist/tenant/slim.js +1 -1
  191. package/dist/tenant/status.js +7 -12
  192. package/dist/tenant/store-pg.d.ts +15 -0
  193. package/dist/tenant/store-pg.js +7 -3
  194. package/dist/tenant/streams.d.ts +3 -10
  195. package/dist/tenant/streams.js +1 -24
  196. package/dist/tenant/sweep.js +0 -5
  197. package/dist/tenant/transcript.d.ts +2 -2
  198. package/dist/tenant/transcript.js +2 -2
  199. package/dist/tenant/types.d.ts +15 -22
  200. package/dist/tenant/worker.d.ts +1 -3
  201. package/dist/tenant/worker.js +3 -8
  202. package/dist/vault/sources.d.ts +7 -1
  203. package/dist/vault/sources.js +22 -11
  204. package/dist/version.d.ts +1 -1
  205. package/dist/version.js +1 -1
  206. package/package.json +4 -4
  207. package/dist/admin-openapi.json +0 -904
  208. package/dist/api/http/routes/actions.d.ts +0 -8
  209. package/dist/api/http/routes/actions.js +0 -75
  210. package/dist/api/http/routes/endpoints.d.ts +0 -7
  211. package/dist/api/http/routes/endpoints.js +0 -100
  212. package/dist/host/admin-api.d.ts +0 -30
  213. package/dist/host/admin-api.js +0 -140
  214. package/dist/mcp/stdio.d.ts +0 -17
  215. package/dist/mcp/stdio.js +0 -82
  216. package/dist/tenant/actions.d.ts +0 -14
  217. package/dist/tenant/actions.js +0 -29
  218. package/dist/tenant/delivery-token.d.ts +0 -41
  219. package/dist/tenant/delivery-token.js +0 -104
  220. package/dist/tenant/delivery.d.ts +0 -62
  221. package/dist/tenant/delivery.js +0 -481
  222. package/dist/tenant/endpoints.d.ts +0 -67
  223. package/dist/tenant/endpoints.js +0 -90
@@ -1,5 +1,11 @@
1
- import type { Action } from "@nylorun/core/contracts";
2
- import type { AgentManifest, JsonValue } from "@nylorun/core/define";
1
+ /**
2
+ * Pure helpers over the session document: turn-manifest pins and variants, and state rebasing.
3
+ * No store access and no I/O. (The tools of an MCP snapshot
4
+ * live in `mcp/snapshot.ts`.)
5
+ *
6
+ * Later waves: stable; the async store conversion (Wave 1 / A) does not change these.
7
+ */
8
+ import type { AgentManifest } from "@nylorun/core/define";
3
9
  import { type TurnManifestStore } from "../core/turn-manifest.js";
4
10
  import type { Session } from "./context.js";
5
11
  export { manifestFor, sessionToolsOf } from "../mcp/snapshot.js";
@@ -7,43 +13,4 @@ export { manifestFor, sessionToolsOf } from "../mcp/snapshot.js";
7
13
  export declare function variantStore(session: Session): TurnManifestStore;
8
14
  /** Manifest this turn's checkpoint is pinned to (session pin or a stored variant). */
9
15
  export declare function turnManifestOf(session: Session): AgentManifest;
10
- /** Wrap a linked agent turn so the Loop learns the turn's manifest (SD-I4 pin stays on session). */
11
- export declare function linkedAgentOutput(session: Session, output: JsonValue | undefined): JsonValue;
12
16
  export declare function rebaseSessionState(session: Session, turnHash: string): void;
13
- export declare function pinnedTool(manifest: AgentManifest, capabilityId?: string, toolName?: string): import("@nylorun/core/define").ToolManifest | undefined;
14
- /** What an action runs, for events: a tool name, or a hook point and its capabilities. */
15
- export declare function actionTarget(action: Action): {
16
- hook: {
17
- at: "after" | "before";
18
- scope: "step" | "turn";
19
- capabilityIds: string[];
20
- };
21
- agent?: {
22
- id: string;
23
- path: string;
24
- delegationId?: string | undefined;
25
- } | undefined;
26
- } | {
27
- toolName: string;
28
- agent?: {
29
- id: string;
30
- path: string;
31
- delegationId?: string | undefined;
32
- } | undefined;
33
- } | {
34
- path: string;
35
- key: string;
36
- agent?: {
37
- id: string;
38
- path: string;
39
- delegationId?: string | undefined;
40
- } | {
41
- id: string;
42
- path: string;
43
- delegationId?: string | undefined;
44
- } | {
45
- id: string;
46
- path: string;
47
- delegationId?: string | undefined;
48
- } | undefined;
49
- };
@@ -1,12 +1,3 @@
1
- /**
2
- * Pure helpers over the session document: turn-manifest pins and variants, state rebasing,
3
- * and the event target of an Action. No store access and no I/O. (The tools of an MCP snapshot
4
- * live in `mcp/snapshot.ts`.)
5
- *
6
- * Later waves: stable; the async store conversion (Wave 1 / A) does not change these.
7
- */
8
- import { agentTurnValue } from "@nylorun/harness/run";
9
- import { isWorkflowManifest } from "../core/flow-host.js";
10
1
  import { allowedManifestHashes, rebaseTurnState, } from "../core/turn-manifest.js";
11
2
  export { manifestFor, sessionToolsOf } from "../mcp/snapshot.js";
12
3
  /** Turn-manifest store backed by the session's `variants` map (no schema change). */
@@ -27,12 +18,6 @@ export function turnManifestOf(session) {
27
18
  return session.manifest;
28
19
  return session.variants?.[hash] ?? session.manifest;
29
20
  }
30
- /** Wrap a linked agent turn so the Loop learns the turn's manifest (SD-I4 pin stays on session). */
31
- export function linkedAgentOutput(session, output) {
32
- if (isWorkflowManifest(session.manifest))
33
- return output ?? null;
34
- return agentTurnValue(output ?? null, turnManifestOf(session));
35
- }
36
21
  export function rebaseSessionState(session, turnHash) {
37
22
  const allowed = allowedManifestHashes({
38
23
  pinnedHash: session.manifestHash,
@@ -46,25 +31,3 @@ export function rebaseSessionState(session, turnHash) {
46
31
  if (next !== session.state)
47
32
  session.state = next;
48
33
  }
49
- export function pinnedTool(manifest, capabilityId, toolName) {
50
- const capability = manifest.capabilities.find((item) => item.id === capabilityId);
51
- return capability?.tools?.find((tool) => tool.name === toolName);
52
- }
53
- /** What an action runs, for events: a tool name, or a hook point and its capabilities. */
54
- export function actionTarget(action) {
55
- if (action.kind === "hook")
56
- return {
57
- hook: action.hook,
58
- ...(action.agent ? { agent: action.agent } : {}),
59
- };
60
- if (action.kind === "tool" && "toolName" in action)
61
- return {
62
- toolName: action.toolName,
63
- ...(action.agent ? { agent: action.agent } : {}),
64
- };
65
- return {
66
- path: action.path,
67
- key: action.key,
68
- ...(action.agent ? { agent: action.agent } : {}),
69
- };
70
- }
@@ -3,8 +3,9 @@
3
3
  * re-attach vaults), the session list and the session view. Reads use the store's typed
4
4
  * queries; `PUT /v1/sessions/:id` runs in one transaction with the vault attachment checks.
5
5
  */
6
- import { DefinitionDocumentSchema, } from "@nylorun/core/contracts";
6
+ import { DefinitionDocumentSchema, definitionFilesOf, } from "@nylorun/core/contracts";
7
7
  import { hashManifest } from "@nylorun/core/compatibility";
8
+ import { codeToolRefusal } from "@nylorun/core/define";
8
9
  import { aggregateWaits, isWorkflowManifest } from "../core/flow-host.js";
9
10
  import { canonical } from "../store/canonical.js";
10
11
  import { validateSandboxAttach } from "../core/sandbox-routes.js";
@@ -16,6 +17,7 @@ import { checkPlacement, requirePods } from "../sandbox/placement.js";
16
17
  import { attachSandbox, recordAttachment } from "./sandboxes.js";
17
18
  import { sandboxLookup, } from "./context.js";
18
19
  import { fail } from "./http.js";
20
+ import { missingDefinitionFiles } from "./definition-files.js";
19
21
  /** A session's creation identity: everything but the request id and vault attachments. */
20
22
  const sessionIdentity = (value) => {
21
23
  const { requestId: _requestId, vaultIds: _vaultIds, credentialSelections: _selections, ...body } = value ?? {};
@@ -54,6 +56,10 @@ export async function putDefinition(ctx, agentId, body) {
54
56
  if (body.manifest.id !== agentId)
55
57
  fail(400, "Agent id mismatch");
56
58
  DefinitionDocumentSchema.parse(body.manifest);
59
+ // The Runtime runs no code of yours during a session (track R2): no tool may need it.
60
+ const code = codeToolRefusal(body.manifest);
61
+ if (code)
62
+ fail(400, code);
57
63
  const declared = declaredSandboxes(body.manifest);
58
64
  if (declared.length > 0)
59
65
  fail(400, `${declared.join(", ")} declares a sandbox. Agents no longer declare one: remove .sandbox() and open the session with it, createSession({ sandbox: { image, network: { allow }, resources } }), or set the Tenant's default sandbox. See MIGRATION.md.`);
@@ -61,7 +67,14 @@ export async function putDefinition(ctx, agentId, body) {
61
67
  ...body,
62
68
  manifestHash: hashManifest(body.manifest),
63
69
  };
64
- await ctx.store.tx((t) => t.put("definitions", agentId, definition));
70
+ const files = [...definitionFilesOf(body.manifest)];
71
+ await ctx.store.tx(async (t) => {
72
+ const missing = await missingDefinitionFiles(t, body.manifest);
73
+ if (missing.length > 0)
74
+ fail(400, `The definition names ${missing.length} file(s) the Runtime does not hold; upload each with PUT /v1/files/<sha256> first: ${missing.join(", ")}`, { code: "definition_files_missing", details: { missing } });
75
+ await t.put("definitions", agentId, definition);
76
+ await t.insertDefinitionFileUses(agentId, definition.manifestHash, files);
77
+ });
65
78
  return {
66
79
  agentId,
67
80
  manifestHash: definition.manifestHash,
@@ -156,7 +169,6 @@ export async function putSession(ctx, id, body, access, options = {}) {
156
169
  creation: body,
157
170
  vaultIds,
158
171
  credentialSelections,
159
- pluginRoots: definition.pluginRoots ?? {},
160
172
  ...(sandbox.sandboxOwnerId !== undefined
161
173
  ? { sandboxOwnerId: sandbox.sandboxOwnerId }
162
174
  : {}),
@@ -221,7 +233,7 @@ async function sessionSandbox(t, body, definition, options) {
221
233
  return pin(definition, resolved.spec, resolved.source);
222
234
  }
223
235
  async function pin(definition, spec, source) {
224
- // A workflow's manifest stays as registered: Action endpoints match workflow actions by its hash.
236
+ // A workflow's manifest stays as registered: its agents inherit the sandbox when they start.
225
237
  if (isWorkflowManifest(definition.manifest))
226
238
  return { spec, source };
227
239
  const sandboxed = withSandboxCapability(definition.manifest, spec);
@@ -249,9 +261,6 @@ export async function sessionView(t, s) {
249
261
  if (aggregated.length > 0)
250
262
  waits = aggregated;
251
263
  }
252
- const actions = await t.actionsForSession(s.id, {
253
- statuses: ["pending", "delivering", "uncertain"],
254
- });
255
264
  const uncertain = await t.effectsForSession(s.id, {
256
265
  statuses: ["uncertain"],
257
266
  });
@@ -273,7 +282,6 @@ export async function sessionView(t, s) {
273
282
  mcpDiagnostics: s.mcpDiagnostics ?? [],
274
283
  waits,
275
284
  error: s.error,
276
- actions,
277
285
  uncertainEffects: uncertain.map((e) => ({
278
286
  effectId: e.request.effectId,
279
287
  turnId: e.request.turnId,
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Tenant signing keys for the tokens the Runtime signs itself (delivery tokens, capability
3
- * links, run and host tokens). Each key is an ES256 pair: the public JWK is stored
2
+ * Tenant signing keys for the tokens the Runtime signs itself (capability links, run and host
3
+ * tokens). Each key is an ES256 pair: the public JWK is stored
4
4
  * as is, the PKCS#8 private key is sealed with the vault KEK (AAD bound to the Tenant and key
5
5
  * id), like a credential.
6
6
  *
@@ -44,7 +44,7 @@ export declare class SigningKeys {
44
44
  /**
45
45
  * Rotates: previous → revoked, current → previous, standby → current, new standby. Refused
46
46
  * with 409 while the previous key may still verify live tokens (retired less than the
47
- * longest token lifetime, `maxTtlSeconds` or a delivery token's, plus a minute ago), unless
47
+ * longest token lifetime, `maxTtlSeconds` or `TOKEN_TTL_MAX_SECONDS`, plus a minute ago), unless
48
48
  * `force`.
49
49
  */
50
50
  rotate(t: Tx, kek: Buffer, maxTtlSeconds: number, force: boolean): Promise<SigningKeyRow[]>;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Tenant signing keys for the tokens the Runtime signs itself (delivery tokens, capability
3
- * links, run and host tokens). Each key is an ES256 pair: the public JWK is stored
2
+ * Tenant signing keys for the tokens the Runtime signs itself (capability links, run and host
3
+ * tokens). Each key is an ES256 pair: the public JWK is stored
4
4
  * as is, the PKCS#8 private key is sealed with the vault KEK (AAD bound to the Tenant and key
5
5
  * id), like a credential.
6
6
  *
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { exportJWK, exportPKCS8, generateKeyPair, importJWK, importPKCS8, } from "jose";
17
17
  import { newSigningKeyId, SIGNING_KEY_ID_PATTERN, } from "@nylorun/core/compatibility";
18
- import { DELIVERY_TOKEN_MAX_TTL_SECONDS } from "@nylorun/core/contracts";
18
+ import { TOKEN_TTL_MAX_SECONDS } from "@nylorun/core/contracts";
19
19
  import { decryptSecret, encryptSecret } from "../vault/crypto.js";
20
20
  import { fail } from "./http.js";
21
21
  const ALG = "ES256";
@@ -107,7 +107,7 @@ export class SigningKeys {
107
107
  /**
108
108
  * Rotates: previous → revoked, current → previous, standby → current, new standby. Refused
109
109
  * with 409 while the previous key may still verify live tokens (retired less than the
110
- * longest token lifetime, `maxTtlSeconds` or a delivery token's, plus a minute ago), unless
110
+ * longest token lifetime, `maxTtlSeconds` or `TOKEN_TTL_MAX_SECONDS`, plus a minute ago), unless
111
111
  * `force`.
112
112
  */
113
113
  async rotate(t, kek, maxTtlSeconds, force) {
@@ -118,7 +118,7 @@ export class SigningKeys {
118
118
  const [previous] = await t.signingKeys(["previous"]);
119
119
  if (previous && !force) {
120
120
  const retired = Date.parse(previous.retiredAt ?? previous.createdAt);
121
- const longest = Math.max(maxTtlSeconds, DELIVERY_TOKEN_MAX_TTL_SECONDS);
121
+ const longest = Math.max(maxTtlSeconds, TOKEN_TTL_MAX_SECONDS);
122
122
  const until = retired + longest * 1000 + ROTATION_GRACE_MS;
123
123
  if (until > now.getTime())
124
124
  fail(409, "The previous key may still verify live tokens", {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The skill tools (track R2 M4): `load_skill` and `read_skill_resource`, which the build gives
3
+ * the first capability with skills. The Runtime runs them itself, like `save_artifact`, from the
4
+ * definition files uploaded with the agent: no developer process is involved.
5
+ *
6
+ * - `load_skill`: the skill's instructions (its `SKILL.md` after the frontmatter), the paths of
7
+ * its other files, and, when the session has a sandbox, where they are in it
8
+ * (`/skills/<name>/`, read-only).
9
+ * - `read_skill_resource`: one text file of the skill. A binary file is refused: the model reads
10
+ * it in the sandbox, when there is one.
11
+ *
12
+ * Expected problems are failed outcomes the model sees; only infrastructure errors throw.
13
+ */
14
+ import type { SandboxToolOutcome } from "@nylorun/core/contracts";
15
+ import { type AgentManifest } from "@nylorun/core/define";
16
+ import type { HostEffect } from "@nylorun/harness/run";
17
+ import type { TenantContext } from "./context.js";
18
+ /** True when `request` calls a skill tool the Runtime serves for `manifest`'s agent. */
19
+ export declare function isSkillToolCall(manifest: AgentManifest | undefined, request: HostEffect): boolean;
20
+ export declare function callSkillTool(ctx: TenantContext, manifest: AgentManifest, request: HostEffect, signal: AbortSignal): Promise<SandboxToolOutcome>;
@@ -0,0 +1,72 @@
1
+ import { LOAD_SKILL_TOOL, SANDBOX_CAPABILITY_ID, SKILL_ENTRY, SKILLS_MOUNT, isSkillTool, skillInstructions, } from "@nylorun/core/define";
2
+ import { sandboxCapabilityOf } from "../sandbox/capability.js";
3
+ import { readDefinitionFile } from "./definition-files.js";
4
+ const failed = (code, message) => ({ kind: "failed", code, message });
5
+ /** True when `request` calls a skill tool the Runtime serves for `manifest`'s agent. */
6
+ export function isSkillToolCall(manifest, request) {
7
+ return (request.kind === "tool" &&
8
+ isSkillTool(manifest?.capabilities.find((capability) => capability.id === request.capabilityId), request.toolName));
9
+ }
10
+ /** Every skill of the agent, by name. */
11
+ function skillsOf(manifest) {
12
+ const skills = new Map();
13
+ for (const capability of manifest.capabilities)
14
+ for (const skill of Object.values(capability.skills ?? {}))
15
+ skills.set(skill.name, skill);
16
+ return skills;
17
+ }
18
+ /** UTF-8 text, or undefined for bytes that are not (a NUL byte, or invalid UTF-8). */
19
+ function textOf(bytes) {
20
+ if (bytes.includes(0))
21
+ return undefined;
22
+ try {
23
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ export async function callSkillTool(ctx, manifest, request, signal) {
30
+ const input = (request.input ?? {});
31
+ const skills = skillsOf(manifest);
32
+ const name = typeof input.name === "string" ? input.name : "";
33
+ const skill = skills.get(name);
34
+ if (!skill)
35
+ return request.toolName === LOAD_SKILL_TOOL
36
+ ? { kind: "completed", output: { unknown: name, available: [...skills.keys()].sort() } }
37
+ : failed("skill.unknown", `There is no skill '${name}'.`);
38
+ const sandboxed = sandboxCapabilityOf(manifest, SANDBOX_CAPABILITY_ID, "bash") !== undefined;
39
+ const directory = `${SKILLS_MOUNT}/${skill.name}/`;
40
+ const read = async (path) => {
41
+ const bytes = await readDefinitionFile(ctx.blobs, skill.files[path], signal);
42
+ if (!bytes)
43
+ throw new Error(`The bytes of ${skill.files[path]} (${skill.name}/${path}) are missing from the Object store`);
44
+ return bytes;
45
+ };
46
+ if (request.toolName === LOAD_SKILL_TOOL) {
47
+ const text = textOf(await read(SKILL_ENTRY));
48
+ if (text === undefined)
49
+ return failed("skill.binary_resource", `${SKILL_ENTRY} of '${skill.name}' is not text.`);
50
+ const resources = Object.keys(skill.files)
51
+ .filter((path) => path !== SKILL_ENTRY)
52
+ .sort();
53
+ return {
54
+ kind: "completed",
55
+ output: {
56
+ name: skill.name,
57
+ content: skillInstructions(text),
58
+ ...(resources.length === 0 ? {} : { resources }),
59
+ ...(sandboxed ? { sandboxPath: directory } : {}),
60
+ },
61
+ };
62
+ }
63
+ const path = typeof input.path === "string" ? input.path : "";
64
+ if (skill.files[path] === undefined)
65
+ return failed("skill.unknown_resource", `Skill '${skill.name}' has no resource '${path}'.`);
66
+ const content = textOf(await read(path));
67
+ if (content === undefined)
68
+ return failed("skill.binary_resource", sandboxed
69
+ ? `${path} is not a text file. It is in the sandbox at ${directory}${path}; use it there.`
70
+ : `${path} is not a text file; only text files can be read without a sandbox.`);
71
+ return { kind: "completed", output: { name: skill.name, path, content } };
72
+ }
@@ -3,6 +3,6 @@ import type { Tx } from "../store/types.js";
3
3
  * Slim the completed model effects of a turn that ended (Model Calls §9): drop the prompt
4
4
  * and the answer, which the transcript already holds, and keep the identity and status.
5
5
  * Replay only ever reads the effects of a running segment. Tool, flow and uncertain rows
6
- * are never touched: completed actions return their receipts, and status lists uncertain work.
6
+ * are never touched: status lists uncertain work.
7
7
  */
8
8
  export declare function slimModelEffects(t: Tx, sessionId: string, turnId: string | null): Promise<void>;
@@ -2,7 +2,7 @@
2
2
  * Slim the completed model effects of a turn that ended (Model Calls §9): drop the prompt
3
3
  * and the answer, which the transcript already holds, and keep the identity and status.
4
4
  * Replay only ever reads the effects of a running segment. Tool, flow and uncertain rows
5
- * are never touched: completed actions return their receipts, and status lists uncertain work.
5
+ * are never touched: status lists uncertain work.
6
6
  */
7
7
  export async function slimModelEffects(t, sessionId, turnId) {
8
8
  if (!turnId)
@@ -56,16 +56,13 @@ export async function buildTenantStatus(ctx) {
56
56
  ctx.stuckInvocations ? executionStatus(ctx.stuckInvocations) : undefined,
57
57
  ctx.streamsStatus?.(),
58
58
  ]);
59
- const definitionIds = new Set(definitions
60
- .map((d) => d.manifest?.id)
61
- .filter((id) => typeof id === "string"));
62
- const endpointIds = new Set((await ctx.store.tx((t) => t.listEndpoints())).map((e) => e.agentId));
63
- const agentIds = new Set([...definitionIds, ...endpointIds]);
64
- const agents = [...agentIds].sort().map((agentId) => ({
65
- agentId,
66
- registered: definitionIds.has(agentId),
67
- endpoint: endpointIds.has(agentId),
68
- }));
59
+ const agents = [
60
+ ...new Set(definitions
61
+ .map((d) => d.manifest?.id)
62
+ .filter((id) => typeof id === "string")),
63
+ ]
64
+ .sort()
65
+ .map((agentId) => ({ agentId }));
69
66
  return {
70
67
  tenant: ctx.envelope,
71
68
  path: ctx.config.paths.root,
@@ -73,7 +70,6 @@ export async function buildTenantStatus(ctx) {
73
70
  store: health.schemaVersion > 0,
74
71
  scheduler: !ctx.closing,
75
72
  model: ctx.modelConfigured || modelView.configured,
76
- endpoints: true,
77
73
  schema: health.ok,
78
74
  },
79
75
  model: modelView,
@@ -81,7 +77,6 @@ export async function buildTenantStatus(ctx) {
81
77
  counts: {
82
78
  sessions: counts.sessions,
83
79
  runningSessions: counts.runningSessions,
84
- pendingActions: counts.pendingActions,
85
80
  uncertainEffects: counts.uncertainEffects,
86
81
  },
87
82
  sandbox: {
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Opens the Host's Tenant on its Postgres database (tenancy.md §4–§5, plan P9).
3
+ *
4
+ * The database holds one Tenant (`store/postgres/tenant.ts`): opening migrates it, creates
5
+ * the Tenant on first start (from the Host's configuration: id, name, Studio and derived
6
+ * principals), and opens the Tenant Runtime on its Session Store. What stays on the Host root
7
+ * is the Tenant directory `tenant/`: the vault key (`vault-kek`), `logs/`, and the private
8
+ * `home/` and `sandboxes/`.
9
+ *
10
+ * - A failure in the Tenant (an old layout, a database newer than this Runtime, a failed
11
+ * migration, an unreadable Tenant row, vault ciphertext without its key) rejects with that
12
+ * `TenantOpenError`; the Host reports it and fails readiness.
13
+ * - A failure outside the Tenant (Postgres unreachable, its sweep not armed in Restate) is
14
+ * `TenantUnavailableError`; the Tenant module retries.
15
+ */
1
16
  import type { PostgresClient } from "../store/postgres/connect.js";
2
17
  import type { Migration } from "../store/postgres/migrate.js";
3
18
  import { type TenantCreation } from "../store/postgres/tenant.js";
@@ -4,8 +4,8 @@
4
4
  * The database holds one Tenant (`store/postgres/tenant.ts`): opening migrates it, creates
5
5
  * the Tenant on first start (from the Host's configuration: id, name, Studio and derived
6
6
  * principals), and opens the Tenant Runtime on its Session Store. What stays on the Host root
7
- * is the Tenant directory `tenant/`: the vault key (`vault-kek`), `plugin-data/`, `logs/`,
8
- * and the private `home/`, `tmp/` and `sandboxes/`.
7
+ * is the Tenant directory `tenant/`: the vault key (`vault-kek`), `logs/`, and the private
8
+ * `home/` and `sandboxes/`.
9
9
  *
10
10
  * - A failure in the Tenant (an old layout, a database newer than this Runtime, a failed
11
11
  * migration, an unreadable Tenant row, vault ciphertext without its key) rejects with that
@@ -13,6 +13,7 @@
13
13
  * - A failure outside the Tenant (Postgres unreachable, its sweep not armed in Restate) is
14
14
  * `TenantUnavailableError`; the Tenant module retries.
15
15
  */
16
+ import { createPostgresReadStore } from "../store/postgres/reads.js";
16
17
  import { mkdirSync } from "node:fs";
17
18
  import { openTenantDatabase, } from "../store/postgres/tenant.js";
18
19
  import { TenantOpenError } from "./cause.js";
@@ -54,13 +55,16 @@ export function createPostgresTenantOpener(options) {
54
55
  from: opened.migrated.from,
55
56
  to: opened.migrated.to,
56
57
  });
58
+ let reads;
57
59
  try {
60
+ reads = createPostgresReadStore(options.sql, tenantId);
58
61
  const config = options.configFor(tenantId);
59
62
  const paths = tenantPaths(options.hostRoot);
60
63
  mkdirSync(paths.root, { recursive: true, mode: 0o700 });
61
- return await options.openRuntime({ ...config, tenantId }, { store, envelope });
64
+ return await options.openRuntime({ ...config, tenantId }, { store, envelope, reads });
62
65
  }
63
66
  catch (error) {
67
+ await reads?.close().catch(() => undefined);
64
68
  await store.close().catch(() => undefined);
65
69
  if (error instanceof TenantOpenError) {
66
70
  // The Tenant's own failure (`kek-missing`): report which Tenant it is.
@@ -16,9 +16,8 @@
16
16
  * and `streamsStatus` reports the basin's state.
17
17
  * - **Readers.** History and session SSE read the session's stream
18
18
  * (`tenant/session-streams.ts`). This module runs one `tenant/control` reader per Tenant per
19
- * process, on the current basin, which calls `ctx.abortLocal` for each `session.cancel`,
20
- * passes an Action's outcome to a run held here for each `action.resolved` (F6.2), and
21
- * checks the session streams for each `sessions.reset`.
19
+ * process, on the current basin, which calls `ctx.abortLocal` for each `session.cancel`
20
+ * and checks the session streams for each `sessions.reset`.
22
21
  *
23
22
  * The caller passes the streams: the Host's S2 streams, or `MemoryStreams` for tests and a
24
23
  * local development Host (not durable).
@@ -78,12 +77,6 @@ export declare function wireStreams(ctx: TenantContext, options: WireStreamsOpti
78
77
  * effect), so failures are logged, never thrown.
79
78
  */
80
79
  export declare function signalSessionCancel(ctx: TenantContext, sessionId: string, turnId: string | null): void;
81
- /**
82
- * Appends `action.resolved` for an Action whose outcome just committed, so a run held for it
83
- * by a harness of another process goes on (F6.2). Call it from `t.afterCommit`. A lost signal
84
- * costs the hold's time only: the segment then ends as waiting and resumes by replay.
85
- */
86
- export declare function signalActionOutcome(ctx: TenantContext, sessionId: string, actionId: string): void;
87
80
  /**
88
81
  * After a reset deleted the Tenant's sessions and moved it to a new basin generation: tells
89
82
  * every process with the Tenant open (`sessions.reset` on the old basin's control stream),
@@ -91,7 +84,7 @@ export declare function signalActionOutcome(ctx: TenantContext, sessionId: strin
91
84
  * are logged; the periodic check and the next open finish the job.
92
85
  */
93
86
  export declare function tenantReset(ctx: TenantContext): Promise<void>;
94
- /** Tenant status of the streams seam (for `GET /v1/tenant` and the Admin API). */
87
+ /** Tenant status of the streams seam (for `GET /v1/tenant`). */
95
88
  export interface StreamsStatus {
96
89
  /** The streams' service answered a probe (`probeStreams`) within the timeout. */
97
90
  reachable: boolean;
@@ -1,5 +1,5 @@
1
1
  import { basinOf } from "../streams/basin.js";
2
- import { signalActionResolved, signalCancel, signalSessionsReset } from "../streams/control.js";
2
+ import { signalCancel, signalSessionsReset } from "../streams/control.js";
3
3
  import { createStreamRelay, } from "../streams/relay/core.js";
4
4
  import { CONTROL_STREAM, } from "../streams/types.js";
5
5
  import { checkSessionStreams, currentBasin, sleep, } from "./session-streams.js";
@@ -59,10 +59,6 @@ export async function wireStreams(ctx, options) {
59
59
  const signal = body;
60
60
  if (signal?.type === "session.cancel" && typeof signal.sessionId === "string")
61
61
  ctx.abortLocal(signal.sessionId, typeof signal.turnId === "string" ? signal.turnId : undefined);
62
- else if (signal?.type === "action.resolved" &&
63
- typeof signal.sessionId === "string" &&
64
- typeof signal.actionId === "string")
65
- void resolveHeld(ctx, signal.sessionId, signal.actionId).catch(report("held run resolution failed"));
66
62
  else if (signal?.type === "sessions.reset")
67
63
  void checkSessionStreams(ctx).catch(report("session stream check failed"));
68
64
  }, report("control stream read failed; retrying"));
@@ -171,25 +167,6 @@ export function signalSessionCancel(ctx, sessionId, turnId) {
171
167
  message: messageOf(error),
172
168
  }));
173
169
  }
174
- /**
175
- * Appends `action.resolved` for an Action whose outcome just committed, so a run held for it
176
- * by a harness of another process goes on (F6.2). Call it from `t.afterCommit`. A lost signal
177
- * costs the hold's time only: the segment then ends as waiting and resumes by replay.
178
- */
179
- export function signalActionOutcome(ctx, sessionId, actionId) {
180
- const streams = ctx.sessionStreams.wiring?.streams;
181
- if (!streams)
182
- return;
183
- void signalActionResolved(streams, currentBasin(ctx), sessionId, actionId).catch((error) => ctx.config.logger.warn("action resolved signal failed", { sessionId, message: messageOf(error) }));
184
- }
185
- /** Passes an Action's recorded outcome to the run of `sessionId` held here, if any. */
186
- async function resolveHeld(ctx, sessionId, actionId) {
187
- if (!ctx.harness.holds(sessionId))
188
- return;
189
- const effect = await ctx.store.tx((t) => t.get("effects", actionId));
190
- if (effect?.status === "completed" && effect.outcome)
191
- ctx.harness.resolved(sessionId, actionId, effect.outcome);
192
- }
193
170
  /**
194
171
  * After a reset deleted the Tenant's sessions and moved it to a new basin generation: tells
195
172
  * every process with the Tenant open (`sessions.reset` on the old basin's control stream),
@@ -14,9 +14,6 @@
14
14
  * workspace capability (`harness-api/workspace.ts`: here, or in the harness that serves
15
15
  * workspaces). Idle MCP connections of the in-process harness are closed.
16
16
  * 4. **Hooks.** Callbacks registered with `ctx.onSweep`.
17
- * 5. **Deliveries.** Deliveries to Action endpoints whose deadline passed without an answer are
18
- * lost (`delivery.ts` `loseAction`), and pending Actions of agents with an endpoint are sent
19
- * again, in case a send was lost between a commit and the execution.
20
17
  *
21
18
  * Every step runs one transaction per session it changes (a linked agent and its workflow
22
19
  * share one, child first), so the sweep follows the lock order in `store/types.ts`. A failing
@@ -24,14 +21,12 @@
24
21
  * reports it.
25
22
  */
26
23
  import { pendingAgentEffects, reconcilePendingAgentEffect, } from "../core/flow-host.js";
27
- import { sweepDeliveries } from "./delivery.js";
28
24
  const BATCH = 100;
29
25
  export async function sweep(ctx) {
30
26
  if (ctx.closing || ctx.closed)
31
27
  return;
32
28
  const now = new Date();
33
29
  const steps = [
34
- ["deliveries", () => sweepDeliveries(ctx, now)],
35
30
  ["linked", () => reconcileLinkedAgents(ctx)],
36
31
  ["orphans", () => wakeOrphanedSessions(ctx, now)],
37
32
  [
@@ -4,7 +4,7 @@
4
4
  * Payload schemas: `@nylorun/core/contracts` (`parseTranscriptEvent`).
5
5
  */
6
6
  import type { HostEffect } from "@nylorun/harness/run";
7
- /** The model's tool call id and the harness invocation, for tool effects and tool actions. */
7
+ /** The model's tool call id and the harness invocation, for tool effects. */
8
8
  export declare function toolIds(context: unknown): {
9
9
  callId: string;
10
10
  invocationId: string;
@@ -75,7 +75,7 @@ export declare function modelFailed(request: HostEffect, value: unknown): {
75
75
  message: string;
76
76
  retryable: boolean;
77
77
  } | undefined;
78
- /** `tool.completed` for an MCP or sandbox tool: its output, or the tool error it reported. */
78
+ /** `tool.completed` for a tool the Runtime ran: its output, or the tool error it reported. */
79
79
  export declare function toolCompleted(request: HostEffect, value: unknown): {
80
80
  agent: import("@nylorun/core/define").AgentRef;
81
81
  error: {
@@ -1,5 +1,5 @@
1
1
  import { isModelFailureOutcome } from "@nylorun/core/define";
2
- /** The model's tool call id and the harness invocation, for tool effects and tool actions. */
2
+ /** The model's tool call id and the harness invocation, for tool effects. */
3
3
  export function toolIds(context) {
4
4
  const c = context;
5
5
  return typeof c?.callId === "string" && typeof c.invocationId === "string"
@@ -91,7 +91,7 @@ export function modelFailed(request, value) {
91
91
  ...agentOf(request),
92
92
  };
93
93
  }
94
- /** `tool.completed` for an MCP or sandbox tool: its output, or the tool error it reported. */
94
+ /** `tool.completed` for a tool the Runtime ran: its output, or the tool error it reported. */
95
95
  export function toolCompleted(request, value) {
96
96
  const ids = toolIds(request.context);
97
97
  if (!("callId" in ids))