@nylorun/runtime 0.27.0-beta → 0.29.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +1 -1
  3. package/dist/api/a2a/card.d.ts +4 -3
  4. package/dist/api/a2a/card.js +3 -3
  5. package/dist/api/a2a/routes.js +18 -7
  6. package/dist/api/ag-ui/routes.js +4 -7
  7. package/dist/api/components.d.ts +5854 -2718
  8. package/dist/api/components.js +39 -30
  9. package/dist/api/http/app.js +9 -4
  10. package/dist/api/http/body.d.ts +2 -0
  11. package/dist/api/http/body.js +20 -0
  12. package/dist/api/http/define.d.ts +4 -0
  13. package/dist/api/http/define.js +20 -9
  14. package/dist/api/http/routes/agent-manifests.d.ts +10 -0
  15. package/dist/api/http/routes/agent-manifests.js +135 -0
  16. package/dist/api/http/routes/agents.d.ts +11 -0
  17. package/dist/api/http/routes/agents.js +418 -0
  18. package/dist/api/http/routes/artifacts.js +2 -2
  19. package/dist/api/http/routes/coverage.d.ts +4 -4
  20. package/dist/api/http/routes/coverage.js +12 -6
  21. package/dist/api/http/routes/files.js +1 -1
  22. package/dist/api/http/routes/mcp.js +2 -2
  23. package/dist/api/http/routes/me.d.ts +1 -1
  24. package/dist/api/http/routes/me.js +1 -2
  25. package/dist/api/http/routes/memories.d.ts +9 -0
  26. package/dist/api/http/routes/memories.js +152 -0
  27. package/dist/api/http/routes/reads.d.ts +1 -137
  28. package/dist/api/http/routes/reads.js +31 -12
  29. package/dist/api/http/routes/sessions.js +21 -50
  30. package/dist/api/http/routes/tenant.js +13 -10
  31. package/dist/api/http/routes/triggers.d.ts +10 -0
  32. package/dist/api/http/routes/triggers.js +183 -0
  33. package/dist/api/openapi.js +69 -47
  34. package/dist/artifacts/disk.d.ts +10 -10
  35. package/dist/artifacts/disk.js +27 -27
  36. package/dist/artifacts/quota.d.ts +1 -1
  37. package/dist/artifacts/quota.js +2 -2
  38. package/dist/artifacts/service.js +2 -2
  39. package/dist/core/flow-host.d.ts +0 -1
  40. package/dist/gates/egress-credentials.js +1 -1
  41. package/dist/gates/egress.d.ts +5 -5
  42. package/dist/gates/egress.js +6 -6
  43. package/dist/gates/http-tool.js +2 -2
  44. package/dist/gates/meter.js +22 -5
  45. package/dist/gates/policy.d.ts +8 -3
  46. package/dist/gates/policy.js +7 -2
  47. package/dist/gates/tenant-vaults.d.ts +1 -1
  48. package/dist/harness/pod.d.ts +2 -0
  49. package/dist/harness/setup.js +17 -6
  50. package/dist/harness-api/record.js +11 -0
  51. package/dist/harness-api/server.js +4 -0
  52. package/dist/harness-api/start.d.ts +11 -7
  53. package/dist/harness-api/start.js +49 -5
  54. package/dist/harness-api/workspace.js +2 -2
  55. package/dist/host/config-for.d.ts +2 -0
  56. package/dist/host/config-for.js +1 -0
  57. package/dist/host/http.d.ts +2 -2
  58. package/dist/host/http.js +5 -2
  59. package/dist/host/main.js +1 -0
  60. package/dist/keys/keys.d.ts +15 -4
  61. package/dist/keys/keys.js +16 -0
  62. package/dist/keys/webhooks.d.ts +25 -0
  63. package/dist/keys/webhooks.js +62 -0
  64. package/dist/management-openapi.json +313 -133
  65. package/dist/mcp/connect.d.ts +2 -2
  66. package/dist/mcp/connect.js +3 -3
  67. package/dist/mcp/preview.js +7 -7
  68. package/dist/memory/service.d.ts +97 -0
  69. package/dist/memory/service.js +335 -0
  70. package/dist/memory/tool.d.ts +36 -0
  71. package/dist/memory/tool.js +84 -0
  72. package/dist/model/pi-model.js +7 -0
  73. package/dist/openapi.json +13120 -9034
  74. package/dist/reads/types.d.ts +3 -3
  75. package/dist/record/agent.d.ts +27 -0
  76. package/dist/record/agent.js +46 -0
  77. package/dist/record/index.d.ts +1 -1
  78. package/dist/record/index.js +1 -1
  79. package/dist/sandbox/join.js +5 -5
  80. package/dist/sandbox/placement.d.ts +1 -1
  81. package/dist/sandbox/placement.js +3 -3
  82. package/dist/sandbox/pods/lifecycle.d.ts +6 -6
  83. package/dist/sandbox/pods/lifecycle.js +6 -6
  84. package/dist/sandbox/pods/reconcile.js +10 -2
  85. package/dist/sandbox/resolve.d.ts +3 -3
  86. package/dist/sandbox/resolve.js +7 -7
  87. package/dist/sandbox/setup.d.ts +3 -1
  88. package/dist/sandbox/setup.js +14 -4
  89. package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
  90. package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
  91. package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
  92. package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
  93. package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
  94. package/dist/store/postgres/drizzle/0037_model_usage_cost_split.sql +4 -0
  95. package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
  96. package/dist/store/postgres/reads.js +44 -87
  97. package/dist/store/postgres/record-writer.d.ts +4 -4
  98. package/dist/store/postgres/record-writer.js +9 -9
  99. package/dist/store/postgres/schema.d.ts +1408 -123
  100. package/dist/store/postgres/schema.js +194 -38
  101. package/dist/store/postgres/store.js +329 -79
  102. package/dist/store/postgres/tenant.js +1 -2
  103. package/dist/store/types.d.ts +155 -50
  104. package/dist/store/types.js +0 -1
  105. package/dist/tenant/advance.js +20 -8
  106. package/dist/tenant/agent-manifests.d.ts +72 -0
  107. package/dist/tenant/agent-manifests.js +359 -0
  108. package/dist/tenant/agent-versions.d.ts +126 -0
  109. package/dist/tenant/agent-versions.js +389 -0
  110. package/dist/tenant/agents.d.ts +230 -0
  111. package/dist/tenant/{environments.js → agents.js} +464 -320
  112. package/dist/tenant/auth.d.ts +1 -1
  113. package/dist/tenant/auth.js +3 -3
  114. package/dist/tenant/commands.d.ts +63 -2
  115. package/dist/tenant/commands.js +240 -83
  116. package/dist/tenant/context.d.ts +46 -19
  117. package/dist/tenant/context.js +6 -2
  118. package/dist/tenant/effects.js +7 -16
  119. package/dist/tenant/identity-file.d.ts +6 -6
  120. package/dist/tenant/identity-file.js +12 -10
  121. package/dist/tenant/issuers.js +3 -3
  122. package/dist/tenant/limits.d.ts +0 -3
  123. package/dist/tenant/limits.js +0 -3
  124. package/dist/tenant/memories.d.ts +38 -0
  125. package/dist/tenant/memories.js +119 -0
  126. package/dist/tenant/reset.js +2 -2
  127. package/dist/tenant/run-grants.d.ts +6 -0
  128. package/dist/tenant/run-grants.js +4 -0
  129. package/dist/tenant/runtime.d.ts +1 -1
  130. package/dist/tenant/runtime.js +8 -0
  131. package/dist/tenant/sessions.d.ts +28 -45
  132. package/dist/tenant/sessions.js +85 -208
  133. package/dist/tenant/status.js +6 -7
  134. package/dist/tenant/sweep.js +12 -3
  135. package/dist/tenant/transcript.d.ts +16 -16
  136. package/dist/tenant/triggers.d.ts +143 -0
  137. package/dist/tenant/triggers.js +715 -0
  138. package/dist/tenant/turn-timing.d.ts +11 -1
  139. package/dist/tenant/turn-timing.js +3 -1
  140. package/dist/tenant/types.d.ts +5 -0
  141. package/dist/tenant/worker.d.ts +2 -0
  142. package/dist/tenant/worker.js +5 -0
  143. package/dist/traces/map.d.ts +1 -1
  144. package/dist/traces/map.js +7 -1
  145. package/dist/tuning.d.ts +7 -0
  146. package/dist/tuning.js +19 -0
  147. package/dist/vault/service.d.ts +13 -8
  148. package/dist/vault/service.js +61 -17
  149. package/dist/version.d.ts +1 -1
  150. package/dist/version.js +1 -1
  151. package/package.json +13 -12
  152. package/dist/api/http/routes/environment-manifests.d.ts +0 -9
  153. package/dist/api/http/routes/environment-manifests.js +0 -118
  154. package/dist/api/http/routes/environments.d.ts +0 -11
  155. package/dist/api/http/routes/environments.js +0 -332
  156. package/dist/record/environment.d.ts +0 -27
  157. package/dist/record/environment.js +0 -46
  158. package/dist/tenant/environment-manifests.d.ts +0 -57
  159. package/dist/tenant/environment-manifests.js +0 -208
  160. package/dist/tenant/environments.d.ts +0 -172
@@ -13,6 +13,8 @@ export interface TurnTiming {
13
13
  turnStartedAt?: string;
14
14
  /** The trace context the turn's message arrived with (R7.4); it ends with the turn. */
15
15
  turnTrace?: unknown;
16
+ /** What started the turn when not a client (track T4); it ends with the turn. */
17
+ turnCause?: unknown;
16
18
  pausedMs?: number;
17
19
  pausedAt?: string;
18
20
  }
@@ -25,16 +27,24 @@ export declare function resumeTurn(s: TurnTiming, now?: Date): void;
25
27
  /**
26
28
  * The facts the event that ends turn `turnId` carries: `durationMs` from its start to now, waits
27
29
  * included, so the turn's span holds every crossing in it; `pausedMs`, how long it waited paused
28
- * (a wait still open ends now, as for a cancel); the session's agent;
30
+ * (a wait still open ends now, as for a cancel); the session's Agent and the manifest version it runs;
29
31
  * and the turn's totals from the usage ledger (one read, as `/v1/tenant/usage` totals a turn;
30
32
  * `steps` are the model calls the gate metered). Clears the turn's timing from `s`.
31
33
  */
32
34
  export declare function endTurn(t: Tx, s: TurnTiming & {
33
35
  agentId: string;
36
+ definition?: {
37
+ name: string;
38
+ version: number;
39
+ };
34
40
  }, turnId: string | null, now?: Date): Promise<{
35
41
  durationMs?: number;
36
42
  pausedMs?: number;
37
43
  agentId: string;
44
+ manifest?: {
45
+ name: string;
46
+ version: number;
47
+ };
38
48
  totals?: {
39
49
  steps: number;
40
50
  tokens: number;
@@ -19,7 +19,7 @@ export function resumeTurn(s, now = new Date()) {
19
19
  /**
20
20
  * The facts the event that ends turn `turnId` carries: `durationMs` from its start to now, waits
21
21
  * included, so the turn's span holds every crossing in it; `pausedMs`, how long it waited paused
22
- * (a wait still open ends now, as for a cancel); the session's agent;
22
+ * (a wait still open ends now, as for a cancel); the session's Agent and the manifest version it runs;
23
23
  * and the turn's totals from the usage ledger (one read, as `/v1/tenant/usage` totals a turn;
24
24
  * `steps` are the model calls the gate metered). Clears the turn's timing from `s`.
25
25
  */
@@ -30,10 +30,12 @@ export async function endTurn(t, s, turnId, now = new Date()) {
30
30
  const facts = {
31
31
  ...(elapsed === undefined ? {} : { durationMs: Math.max(0, elapsed), pausedMs: s.pausedMs ?? 0 }),
32
32
  agentId: s.agentId,
33
+ ...(s.definition ? { manifest: { name: s.definition.name, version: s.definition.version } } : {}),
33
34
  ...(totals ? { totals: { steps: totals.calls, tokens: totals.tokens, costUsd: totals.costUsd } } : {}),
34
35
  };
35
36
  delete s.turnStartedAt;
36
37
  delete s.pausedMs;
37
38
  delete s.turnTrace;
39
+ delete s.turnCause;
38
40
  return facts;
39
41
  }
@@ -74,6 +74,11 @@ export interface TenantConfig {
74
74
  * the identity file (`NYLORUN_IDENTITY_FILE`). Absent: none.
75
75
  */
76
76
  issuers?: import("./issuers.js").TrustedIssuers;
77
+ /**
78
+ * The URL clients reach the Runtime API at (`NYLORUN_PUBLIC_URL`, no trailing slash): the base
79
+ * of webhook URLs and the OAuth resource. Absent: the origin each request reached.
80
+ */
81
+ publicUrl?: string;
77
82
  logger: Logger;
78
83
  }
79
84
  export interface Logger {
@@ -36,6 +36,8 @@ export interface TenantWorker {
36
36
  sweep(): Promise<void>;
37
37
  /** Reconciles one pod sandbox (F7.2). Only infrastructure errors throw. */
38
38
  sandbox?(sandboxId: string, trigger: SandboxTrigger, signal: AbortSignal): Promise<SandboxResult>;
39
+ /** A durable timer it armed fired (track T4's triggers). Only infrastructure errors throw. */
40
+ fire?(key: string): Promise<void>;
39
41
  }
40
42
  export interface TenantWorkersOptions {
41
43
  /** Finds or opens a Tenant that is not registered on this process. */
@@ -132,6 +132,11 @@ export class TenantWorkers {
132
132
  const worker = await this.find(tenantId);
133
133
  return worker?.sandbox ? worker.sandbox(sandboxId, trigger, signal) : {};
134
134
  },
135
+ // A timer of a Tenant that is not open here does nothing: the Tenant's sweep arms what is due
136
+ // once it opens.
137
+ fire: async (tenantId, key) => {
138
+ await (await this.find(tenantId))?.fire?.(key);
139
+ },
135
140
  };
136
141
  /** Runs one advance under the deadline and the grace period (see the module comment). */
137
142
  async bounded(tenantId, sessionId, worker, signal) {
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * | Row | Span |
7
7
  * | --- | --- |
8
- * | `turn.completed`, `turn.failed`, `turn.cancelled` | `invoke_agent {agentId}`, the turn's root (INTERNAL) |
8
+ * | `turn.completed`, `turn.failed`, `turn.cancelled` | `invoke_agent {manifest name}`, the turn's root (INTERNAL) |
9
9
  * | `message.assistant`, `model.failed`, `context.compacted` | `chat {model}` (CLIENT) |
10
10
  * | `tool.completed` | `execute_tool {toolName}` (INTERNAL) |
11
11
  * | `node.completed` | `execute_tool {key}`, a flow's HTTP stage (INTERNAL) |
@@ -56,11 +56,17 @@ export function spanOf(row, turn, settings) {
56
56
  case "turn.failed":
57
57
  case "turn.cancelled": {
58
58
  const status = row.type.slice("turn.".length);
59
+ // The agent the turn ran: its manifest's name (protocol 16), else the Agent's id.
60
+ const manifest = p.manifest;
61
+ const agentName = typeof manifest?.name === "string" ? manifest.name : typeof p.agentId === "string" ? p.agentId : undefined;
59
62
  const totals = p.totals;
60
63
  const error = row.type === "turn.failed" ? (p.error ?? {}) : undefined;
61
64
  const attributes = base
62
65
  .str("gen_ai.operation.name", "invoke_agent")
63
66
  .str("gen_ai.agent.id", p.agentId)
67
+ .str("gen_ai.agent.name", agentName)
68
+ // The agent manifest version the turn ran (T3): an Agent moves between versions.
69
+ .int("nylorun.manifest.version", typeof manifest?.version === "number" ? manifest.version : undefined)
64
70
  .str("nylorun.turn.status", status)
65
71
  .int("nylorun.turn.paused_ms", p.pausedMs)
66
72
  .int("nylorun.turn.steps", totals?.steps)
@@ -76,7 +82,7 @@ export function spanOf(row, turn, settings) {
76
82
  traceId: turn.traceId,
77
83
  spanId: turn.spanId,
78
84
  ...(turn.parentSpanId ? { parentSpanId: turn.parentSpanId } : {}),
79
- name: typeof p.agentId === "string" ? `invoke_agent ${p.agentId}` : "invoke_agent",
85
+ name: agentName === undefined ? "invoke_agent" : `invoke_agent ${agentName}`,
80
86
  kind: SPAN_KIND.internal,
81
87
  ...window(row.time, p.durationMs),
82
88
  attributes: attributes.list,
package/dist/tuning.d.ts CHANGED
@@ -104,6 +104,8 @@ export declare const HostTuningSchema: z.ZodObject<{
104
104
  signingKeyRotationGraceMs: z.ZodDefault<z.ZodNumber>;
105
105
  retryAfterSeconds: z.ZodDefault<z.ZodNumber>;
106
106
  seedLeaseMs: z.ZodDefault<z.ZodNumber>;
107
+ upgradeIdleMs: z.ZodDefault<z.ZodNumber>;
108
+ upgradeStartMs: z.ZodDefault<z.ZodNumber>;
107
109
  }, z.core.$strict>>;
108
110
  sweep: z.ZodPrefault<z.ZodObject<{
109
111
  intervalMs: z.ZodDefault<z.ZodNumber>;
@@ -120,6 +122,11 @@ export declare const HostTuningSchema: z.ZodObject<{
120
122
  }, z.core.$strict>>;
121
123
  stalePodMs: z.ZodDefault<z.ZodNumber>;
122
124
  }, z.core.$strict>>;
125
+ triggers: z.ZodPrefault<z.ZodObject<{
126
+ lateGraceMs: z.ZodDefault<z.ZodNumber>;
127
+ intervalCheckFires: z.ZodDefault<z.ZodNumber>;
128
+ webhookToleranceMs: z.ZodDefault<z.ZodNumber>;
129
+ }, z.core.$strict>>;
123
130
  execution: z.ZodPrefault<z.ZodObject<{
124
131
  advanceDeadlineMs: z.ZodDefault<z.ZodNumber>;
125
132
  advanceGraceMs: z.ZodDefault<z.ZodNumber>;
package/dist/tuning.js CHANGED
@@ -177,6 +177,13 @@ export const HostTuningSchema = z
177
177
  retryAfterSeconds: count(5),
178
178
  /** How long a pending Environment seed holds the disk before another process may take it over. */
179
179
  seedLeaseMs: ms(10 * MINUTE),
180
+ /**
181
+ * The longest a pod Agent waiting to start on a new version (track T3) stays idle before it
182
+ * stops, so its next start applies the version: its own `lifecycle.idle` when shorter.
183
+ */
184
+ upgradeIdleMs: ms(MINUTE),
185
+ /** How long a pod may take to start on a new version before it goes back to the one it ran (T3). */
186
+ upgradeStartMs: ms(10 * MINUTE),
180
187
  }),
181
188
  /** The Tenant's sweep (`tenant/sweep.ts`): outbox, orphaned sessions, stale pods. */
182
189
  sweep: group({
@@ -201,6 +208,18 @@ export const HostTuningSchema = z
201
208
  /** How long a pod row may go unchanged before the sweep checks it. */
202
209
  stalePodMs: ms(MINUTE),
203
210
  }),
211
+ /** Triggers (track T4, D74): server timing; the Tenant's own bounds are its Runtime limits. */
212
+ triggers: group({
213
+ /**
214
+ * How long after it was due a fire counts as late (the server was down or asleep), and how
215
+ * overdue an active trigger is before the sweep arms it again.
216
+ */
217
+ lateGraceMs: ms(2 * MINUTE),
218
+ /** How many upcoming fires the save check reads to find a cron's smallest gap. */
219
+ intervalCheckFires: count(100, 2, 10_000),
220
+ /** The clock skew a webhook delivery's `webhook-timestamp` may have, either way (D75). */
221
+ webhookToleranceMs: ms(5 * MINUTE),
222
+ }),
204
223
  /** Durable Session Execution: Restate, or the in-process execution. */
205
224
  execution: group({
206
225
  /**
@@ -1,4 +1,4 @@
1
- import type { CredentialCoverage, CredentialCoverageRequest, CredentialIdentity, EnvironmentSecretView, HostModelProviderInfo, HostModelView, PutEnvironmentSecretRequest, PutHostModelRequest, CustomModelSettings, ModelInput, SelectHostModelRequest } from "@nylorun/core/contracts";
1
+ import type { CredentialCoverage, CredentialCoverageRequest, CredentialIdentity, EnvironmentSecretSlot, AgentSecretView, HostModelProviderInfo, HostModelView, PutAgentSecretRequest, PutHostModelRequest, CustomModelSettings, ModelInput, SelectHostModelRequest } from "@nylorun/core/contracts";
2
2
  import type { AgentManifest, WorkflowManifest } from "@nylorun/core/define";
3
3
  import type { SessionStore, Tx, VaultCredentialRow } from "../store/types.js";
4
4
  export declare const HOST_VAULT_ID = "host";
@@ -101,16 +101,16 @@ export declare class VaultService {
101
101
  dropped: string[];
102
102
  }>;
103
103
  /**
104
- * `PUT /v1/environments/{id}/secrets/{name}`: creates or replaces one credential. The same
104
+ * `PUT /v1/agents/{id}/secrets/{name}`: creates or replaces one credential. The same
105
105
  * name and type keeps the credential's id and re-seals its value and binding; another type
106
106
  * replaces it. Refused (`409 credential_conflict`) when another credential of the vault is
107
107
  * bound to one of its hosts or to its URL. Runs under the Environment row's lock, which also
108
108
  * orders it with session opens and deletes, and counts as the Environment's use.
109
109
  */
110
- putEnvironmentSecret(environmentId: string, name: string, body: PutEnvironmentSecretRequest, actor: string): Promise<EnvironmentSecretView>;
110
+ putEnvironmentSecret(environmentId: string, name: string, body: PutAgentSecretRequest, actor: string): Promise<AgentSecretView>;
111
111
  /** The Environment's secrets: names, types and bindings, never values. */
112
- listEnvironmentSecrets(environmentId: string): Promise<EnvironmentSecretView[]>;
113
- getEnvironmentSecret(environmentId: string, name: string): Promise<EnvironmentSecretView>;
112
+ listEnvironmentSecrets(environmentId: string): Promise<AgentSecretView[]>;
113
+ getEnvironmentSecret(environmentId: string, name: string): Promise<AgentSecretView>;
114
114
  deleteEnvironmentSecret(environmentId: string, name: string, actor: string): Promise<{
115
115
  id: string;
116
116
  }>;
@@ -154,9 +154,9 @@ export declare class VaultService {
154
154
  url: string;
155
155
  }): Promise<AuthorizeResult>;
156
156
  /**
157
- * What a session that names `request.environmentId` would send for each URL `manifest` names
158
- * (`credentialNeeds`), decided as `authorize` decides a call: the credential of the
159
- * Environment's vault bound to the URL. Reads no secret, writes no audit row.
157
+ * What a session on Agent `request.agentId` would send for each URL `manifest` (its manifest
158
+ * version's loop manifest) names (`credentialNeeds`), decided as `authorize` decides a call:
159
+ * the credential of the Agent's vault bound to the URL. Reads no secret, writes no audit row.
160
160
  */
161
161
  coverage(manifest: AgentManifest | WorkflowManifest, request: CredentialCoverageRequest): Promise<CredentialCoverage>;
162
162
  getHostModel(): Promise<HostModelView>;
@@ -204,4 +204,9 @@ type HostSecretPayload = {
204
204
  };
205
205
  export declare function modelBinding(row: VaultCredentialRow): ModelBinding;
206
206
  export declare function hostModelAad(provider: string, model: string): Buffer;
207
+ /**
208
+ * Whether the credential in `row` fills secret slot `slot` (D64): of the slot's type, with its
209
+ * binding (the hosts and header of an `environment_secret`, the URL of a `bearer` or `headers`).
210
+ */
211
+ export declare function slotHolds(slot: EnvironmentSecretSlot, row: Pick<VaultCredentialRow, "type" | "bindingJson">): boolean;
207
212
  export {};
@@ -18,11 +18,11 @@
18
18
  * sealEnvironmentSecretCopies(from, vaultId, slots | null): Promise<SealedSecretCopies>
19
19
  * insertEnvironmentSecretCopies(t: Tx, vaultId, from, actor, sealed): Promise<{ copied; dropped }>
20
20
  *
21
- * // Its secrets (`/v1/environments/{id}/secrets`): each opens its own transaction and writes its
21
+ * // Its secrets (`/v1/agents/{id}/secrets`): each opens its own transaction and writes its
22
22
  * // audit rows in it. A put needs the vault key, so callers reach it through `Keys`.
23
- * putEnvironmentSecret(environmentId, name, body: PutEnvironmentSecretRequest, actor): Promise<EnvironmentSecretView>
24
- * listEnvironmentSecrets(environmentId): Promise<EnvironmentSecretView[]>
25
- * getEnvironmentSecret(environmentId, name): Promise<EnvironmentSecretView>
23
+ * putEnvironmentSecret(environmentId, name, body: PutAgentSecretRequest, actor): Promise<AgentSecretView>
24
+ * listEnvironmentSecrets(environmentId): Promise<AgentSecretView[]>
25
+ * getEnvironmentSecret(environmentId, name): Promise<AgentSecretView>
26
26
  * deleteEnvironmentSecret(environmentId, name, actor): Promise<{ id: string }>
27
27
  *
28
28
  * // The shell's variables of a session's sandbox, in the caller's transaction (R2c).
@@ -231,7 +231,7 @@ export class VaultService {
231
231
  }
232
232
  // --- an Environment's secrets ----------------------------------------------------
233
233
  /**
234
- * `PUT /v1/environments/{id}/secrets/{name}`: creates or replaces one credential. The same
234
+ * `PUT /v1/agents/{id}/secrets/{name}`: creates or replaces one credential. The same
235
235
  * name and type keeps the credential's id and re-seals its value and binding; another type
236
236
  * replaces it. Refused (`409 credential_conflict`) when another credential of the vault is
237
237
  * bound to one of its hosts or to its URL. Runs under the Environment row's lock, which also
@@ -333,9 +333,12 @@ export class VaultService {
333
333
  if (!vault)
334
334
  return {};
335
335
  const environment = {};
336
+ const live = await liveFilter(t, environmentId);
336
337
  for (const row of await t.credentialsForVault(vault.id)) {
337
338
  if (row.type !== "environment_secret" && row.type !== "environment_variable")
338
339
  continue;
340
+ if (!live(row))
341
+ continue;
339
342
  const binding = JSON.parse(row.bindingJson);
340
343
  if (row.type === "environment_secret")
341
344
  environment[binding.secretName] = ENVIRONMENT_SECRET_SENTINEL;
@@ -398,8 +401,10 @@ export class VaultService {
398
401
  });
399
402
  return { result: { status: "refused", url, credentialIds: [], reason: "vault_missing" } };
400
403
  }
404
+ const live = await liveFilter(t, environmentId);
401
405
  const matches = (await t.credentialsForVault(vault.id))
402
406
  .filter(isUserCredential)
407
+ .filter(live)
403
408
  .filter((row) => bindingUrl(row) === url)
404
409
  .sort((a, b) => a.id.localeCompare(b.id));
405
410
  if (matches.length === 0)
@@ -466,18 +471,19 @@ export class VaultService {
466
471
  }
467
472
  // --- coverage --------------------------------------------------------------------
468
473
  /**
469
- * What a session that names `request.environmentId` would send for each URL `manifest` names
470
- * (`credentialNeeds`), decided as `authorize` decides a call: the credential of the
471
- * Environment's vault bound to the URL. Reads no secret, writes no audit row.
474
+ * What a session on Agent `request.agentId` would send for each URL `manifest` (its manifest
475
+ * version's loop manifest) names (`credentialNeeds`), decided as `authorize` decides a call:
476
+ * the credential of the Agent's vault bound to the URL. Reads no secret, writes no audit row.
472
477
  */
473
478
  async coverage(manifest, request) {
474
- const environmentId = request.environmentId;
479
+ const environmentId = request.agentId;
475
480
  return this.store.tx(async (t) => {
476
481
  const credentials = [];
477
- if (environmentId !== undefined) {
482
+ {
478
483
  const vault = await this.environmentVault(t, environmentId);
484
+ const live = await liveFilter(t, environmentId);
479
485
  for (const row of await t.credentialsForVault(vault.id))
480
- if (isUserCredential(row))
486
+ if (isUserCredential(row) && live(row))
481
487
  credentials.push({ row, url: bindingUrl(row) });
482
488
  }
483
489
  const entries = credentialNeeds(manifest).map((need) => {
@@ -495,7 +501,6 @@ export class VaultService {
495
501
  });
496
502
  return {
497
503
  agentId: request.agentId,
498
- environmentId: environmentId ?? null,
499
504
  complete: entries.every((entry) => entry.status === "covered"),
500
505
  entries,
501
506
  };
@@ -634,7 +639,7 @@ export class VaultService {
634
639
  ? await t.vaultOfEnvironment(environmentId)
635
640
  : undefined;
636
641
  if (!vault)
637
- throw new VaultError(404, "Environment not found");
642
+ throw new VaultError(404, "Agent not found");
638
643
  return vault;
639
644
  }
640
645
  async ensureHostVault(t) {
@@ -749,18 +754,18 @@ function bindingUrl(row) {
749
754
  /** Where a credential is, for coverage: never its value. */
750
755
  function coverageCredential(environmentId, row) {
751
756
  const via = JSON.parse(row.bindingJson).via;
752
- return { environmentId, name: row.name, ...(via === undefined ? {} : { via }) };
757
+ return { agentId: environmentId, name: row.name, ...(via === undefined ? {} : { via }) };
753
758
  }
754
759
  /** One coverage entry, with the sentence that says what the session would do. */
755
760
  function coverageEntry(need, url, credential, environmentId, invalid) {
756
761
  const required = need.kind === "http";
757
762
  const where = environmentId === undefined
758
- ? "Without an Environment, no credential is sent"
759
- : `Environment ${environmentId} holds no credential for this URL`;
763
+ ? "Without an Agent, no credential is sent"
764
+ : `Agent ${environmentId} holds no credential for this URL`;
760
765
  const message = invalid !== undefined
761
766
  ? `No credential can be bound to this URL (${invalid}).`
762
767
  : credential !== undefined
763
- ? `Sends '${credential.name}' from Environment ${credential.environmentId}${credential.via ? ` through ${credential.via}` : ""}.`
768
+ ? `Sends '${credential.name}' from Agent ${credential.agentId}${credential.via ? ` through ${credential.via}` : ""}.`
764
769
  : required
765
770
  ? `${where}: each call fails with http.credential.`
766
771
  : `${where}: the server is called without one, which only a public server accepts.`;
@@ -1027,6 +1032,45 @@ async function environmentSecretsFor(t, environmentId, host) {
1027
1032
  const vault = await t.vaultOfEnvironment(environmentId);
1028
1033
  if (!vault)
1029
1034
  return [];
1035
+ const live = await liveFilter(t, environmentId);
1030
1036
  return (await t.credentialsForVault(vault.id)).filter((row) => row.type === "environment_secret" &&
1037
+ live(row) &&
1031
1038
  (credentialBinding(row).allowedHosts ?? []).includes(lower));
1032
1039
  }
1040
+ /**
1041
+ * Whether the credential in `row` fills secret slot `slot` (D64): of the slot's type, with its
1042
+ * binding (the hosts and header of an `environment_secret`, the URL of a `bearer` or `headers`).
1043
+ */
1044
+ export function slotHolds(slot, row) {
1045
+ if (row.type !== slot.type)
1046
+ return false;
1047
+ const binding = JSON.parse(row.bindingJson);
1048
+ if (slot.type === "environment_variable")
1049
+ return true;
1050
+ if (slot.type === "environment_secret") {
1051
+ const hosts = (list) => canonical([...(list ?? [])].map((host) => host.toLowerCase()).sort());
1052
+ const inject = slot.inject ? { header: slot.inject.header, format: slot.inject.format } : null;
1053
+ return hosts(slot.allowedHosts) === hosts(binding.allowedHosts) && canonical(inject) === canonical(binding.inject ?? null);
1054
+ }
1055
+ try {
1056
+ return normalizeVaultUrl(slot.url) === binding.url;
1057
+ }
1058
+ catch {
1059
+ return false;
1060
+ }
1061
+ }
1062
+ /**
1063
+ * Which of Agent `environmentId`'s credentials are live (track T3, D73): with secret slots in the
1064
+ * runtime it applies, only those that fill a slot of the name (`slotHolds`). Another stays in the
1065
+ * vault, dormant: a slot the version dropped, or one whose binding changed until it gets a value
1066
+ * again. Without slots, every one.
1067
+ */
1068
+ async function liveFilter(t, environmentId) {
1069
+ const slots = (await t.environment(environmentId))?.applied.secrets;
1070
+ if (slots === undefined)
1071
+ return () => true;
1072
+ return (row) => {
1073
+ const slot = slots[row.name];
1074
+ return slot !== undefined && slotHolds(slot, row);
1075
+ };
1076
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.27.0-beta";
1
+ export declare const RUNTIME_VERSION = "0.29.0-beta";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in step with package.json by runtime/server/scripts/check-package.mjs, which runs in npm run check.
2
2
  // release:prepare also rewrites this when it bumps @nylorun/runtime.
3
- export const RUNTIME_VERSION = "0.27.0-beta";
3
+ export const RUNTIME_VERSION = "0.29.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.27.0-beta",
3
+ "version": "0.29.0-beta",
4
4
  "description": "Nylorun Runtime: the agent execution Host on Postgres, Restate and S2, and model providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -61,17 +61,18 @@
61
61
  "dependencies": {
62
62
  "@ag-ui/core": "~1.0.2",
63
63
  "@asteasolutions/zod-to-openapi": "^9.1.0",
64
- "@earendil-works/pi-ai": "0.99.1",
65
- "@hono/node-server": "^2.1.3",
64
+ "@earendil-works/pi-ai": "1.2.0",
65
+ "@hono/node-server": "^2.1.5",
66
66
  "@hono/zod-openapi": "^1.6.3",
67
67
  "@modelcontextprotocol/sdk": "^1.32.1",
68
- "@nylorun/core": "0.23.0-beta",
69
- "@nylorun/harness": "0.28.0-beta",
70
- "@restatedev/restate-sdk": "1.17.2",
71
- "@restatedev/restate-sdk-clients": "1.17.2",
68
+ "@nylorun/core": "0.25.0-beta",
69
+ "@nylorun/harness": "0.29.1-beta",
70
+ "@restatedev/restate-sdk": "1.17.3",
71
+ "@restatedev/restate-sdk-clients": "1.17.3",
72
72
  "@s2-dev/streamstore": "0.28.0",
73
73
  "aws4fetch": "1.0.20",
74
- "drizzle-orm": "^0.45.3",
74
+ "croner": "^9.1.0",
75
+ "drizzle-orm": "^0.45.4",
75
76
  "hono": "^4.13.13",
76
77
  "jose": "^6.2.12",
77
78
  "just-bash": "3.6.0",
@@ -80,18 +81,18 @@
80
81
  "postgres": "^3.4.9",
81
82
  "ws": "^8.22.0",
82
83
  "yaml": "^2.9.1",
83
- "zod": "^4.6.5"
84
+ "zod": "^4.6.7"
84
85
  },
85
86
  "devDependencies": {
86
87
  "@a2a-js/sdk": "~1.3.0",
87
88
  "@ag-ui/client": "1.0.2",
88
- "@scalar/openapi-parser": "^0.29.11",
89
- "@types/node": "^26.6.4",
89
+ "@scalar/openapi-parser": "^0.29.13",
90
+ "@types/node": "^26.6.5",
90
91
  "@types/pg": "^8.23.1",
91
92
  "@types/ws": "^8.18.2",
92
93
  "@typescript/native": "npm:typescript@^7.0.2",
93
94
  "drizzle-kit": "^0.31.11",
94
95
  "typescript": "npm:@typescript/typescript6@^6.0.2",
95
- "vitest": "^5.0.2"
96
+ "vitest": "^5.0.4"
96
97
  }
97
98
  }
@@ -1,9 +0,0 @@
1
- /**
2
- * Environment manifests (`/v1/environment-manifests/**`, protocol 14, D63): save, read, list and
3
- * delete the named, versioned definitions Environments are made from. A save never changes an
4
- * Environment. Reading needs what reading an Environment needs; saving and deleting need
5
- * `environments:write`, from any client (Q12).
6
- */
7
- import type { OpenAPIHono } from "@hono/zod-openapi";
8
- import type { TenantEnv } from "../app.js";
9
- export declare function environmentManifestRoutes(api: OpenAPIHono<TenantEnv>): void;
@@ -1,118 +0,0 @@
1
- import { z } from "zod";
2
- import { isEnvironmentManifestName, PutEnvironmentManifestRequestSchema } from "@nylorun/core/contracts";
3
- import { DeleteEnvironmentManifestResponse, EnvironmentManifestPlan, EnvironmentManifestVersions, EnvironmentManifestView, ListEnvironmentManifestsResponse, PutEnvironmentManifestRequest, SaveEnvironmentManifestResponse, } from "../../components.js";
4
- import { fail } from "../../../tenant/http.js";
5
- import { deleteEnvironmentManifest, environmentManifestVersions, getEnvironmentManifest, listEnvironmentManifests, saveEnvironmentManifest, } from "../../../tenant/environment-manifests.js";
6
- import { readJson } from "../body.js";
7
- import { tenantRoute } from "../define.js";
8
- import { jsonResponse } from "../respond.js";
9
- const READ = {
10
- credentials: ["application", "subject", "token"],
11
- scopes: ["sessions:own", "environments:write"],
12
- };
13
- const WRITE = {
14
- credentials: ["application", "subject", "token"],
15
- scopes: ["environments:write"],
16
- };
17
- const json = (schema, description) => ({
18
- description,
19
- content: { "application/json": { schema } },
20
- });
21
- const namePath = z.object({
22
- name: z.string().meta({ description: "The environment manifest's name, such as `analyst-box`" }),
23
- });
24
- function nameOf(value) {
25
- if (!isEnvironmentManifestName(value))
26
- return fail(400, "An environment manifest's name is 1-64 letters, digits, '.', '_' and '-', starting with a letter or digit");
27
- return value;
28
- }
29
- function versionOf(value) {
30
- const version = Number(value);
31
- if (!Number.isInteger(version) || version < 1)
32
- return fail(400, "version is a positive integer");
33
- return version;
34
- }
35
- /** `If-Match: <version>` (or `"<version>"`), when sent. */
36
- function ifMatchOf(value) {
37
- if (value === undefined || value.trim() === "")
38
- return undefined;
39
- const version = Number(value.trim().replace(/^W\//, "").replace(/^"|"$/g, ""));
40
- if (!Number.isInteger(version) || version < 0)
41
- return fail(400, "If-Match is the version you read: a whole number, 0 for none yet");
42
- return version;
43
- }
44
- export function environmentManifestRoutes(api) {
45
- tenantRoute(api, READ, {
46
- method: "get",
47
- path: "/v1/environment-manifests",
48
- tags: ["Environment manifests"],
49
- summary: "List environment manifests",
50
- description: "Each manifest's latest version, by name, with how many Environments were made from it.",
51
- responses: { 200: json(ListEnvironmentManifestsResponse, "The manifests") },
52
- }, async (c) => jsonResponse(200, await listEnvironmentManifests(c.env.tenant)));
53
- tenantRoute(api, WRITE, {
54
- method: "put",
55
- path: "/v1/environment-manifests/{name}",
56
- tags: ["Environment manifests"],
57
- summary: "Save an environment manifest",
58
- description: "Saves the manifest as its next version, or answers the latest unchanged when nothing differs. No Environment changes: an Environment keeps the version it was made from, and a fork moves it to another (D63). `If-Match: <version>` refuses the save with `412 manifest_changed` when the latest version is no longer the one you read (`0` for a manifest that does not exist yet). With `?dryRun=true` the answer is the plan: the changes, every reason a save would be refused (`errors`), warnings, and how many Environments stay on older versions; nothing is saved. Clients drop a file's `$schema` before they send it.",
59
- request: {
60
- params: namePath,
61
- query: z.object({
62
- dryRun: z.enum(["true", "false"]).optional().meta({ description: "`true`: answer the plan and save nothing" }),
63
- }),
64
- body: { required: true, content: { "application/json": { schema: PutEnvironmentManifestRequest } } },
65
- },
66
- responses: {
67
- 200: json(z.union([SaveEnvironmentManifestResponse, EnvironmentManifestPlan]), "The version saved, or the plan with dryRun"),
68
- 400: { description: "The manifest breaks the Tenant's limits or names files the Runtime cannot use" },
69
- 412: { description: "If-Match named a version that is no longer the latest (`manifest_changed`)" },
70
- },
71
- }, async (c) => {
72
- const name = nameOf(c.req.param("name"));
73
- const body = PutEnvironmentManifestRequestSchema.parse(await readJson(c));
74
- const ifMatch = ifMatchOf(c.req.header("if-match"));
75
- const options = ifMatch === undefined ? {} : { ifMatch };
76
- return jsonResponse(200, c.req.query("dryRun") === "true"
77
- ? await saveEnvironmentManifest(c.env.tenant, name, body, c.get("scope"), { ...options, dryRun: true })
78
- : await saveEnvironmentManifest(c.env.tenant, name, body, c.get("scope"), options));
79
- });
80
- tenantRoute(api, READ, {
81
- method: "get",
82
- path: "/v1/environment-manifests/{name}",
83
- tags: ["Environment manifests"],
84
- summary: "Get an environment manifest",
85
- description: "Its latest version.",
86
- request: { params: namePath },
87
- responses: { 200: json(EnvironmentManifestView, "The latest version") },
88
- }, async (c) => jsonResponse(200, await getEnvironmentManifest(c.env.tenant, nameOf(c.req.param("name")))));
89
- tenantRoute(api, READ, {
90
- method: "get",
91
- path: "/v1/environment-manifests/{name}/versions",
92
- tags: ["Environment manifests"],
93
- summary: "List an environment manifest's versions",
94
- description: "Every version, newest first, with who saved it and how many Environments were made from it.",
95
- request: { params: namePath },
96
- responses: { 200: json(EnvironmentManifestVersions, "The versions") },
97
- }, async (c) => jsonResponse(200, await environmentManifestVersions(c.env.tenant, nameOf(c.req.param("name")))));
98
- tenantRoute(api, READ, {
99
- method: "get",
100
- path: "/v1/environment-manifests/{name}/versions/{version}",
101
- tags: ["Environment manifests"],
102
- summary: "Get one version of an environment manifest",
103
- request: { params: namePath.extend({ version: z.string().meta({ description: "The version number" }) }) },
104
- responses: { 200: json(EnvironmentManifestView, "The version") },
105
- }, async (c) => jsonResponse(200, await getEnvironmentManifest(c.env.tenant, nameOf(c.req.param("name")), versionOf(c.req.param("version")))));
106
- tenantRoute(api, WRITE, {
107
- method: "delete",
108
- path: "/v1/environment-manifests/{name}",
109
- tags: ["Environment manifests"],
110
- summary: "Delete an environment manifest",
111
- description: "Deletes every version. Refused while any Environment was made from one (`409 manifest_in_use`).",
112
- request: { params: namePath },
113
- responses: {
114
- 200: json(DeleteEnvironmentManifestResponse, "Deleted, or there was none"),
115
- 409: { description: "An Environment was made from one of its versions (`manifest_in_use`)" },
116
- },
117
- }, async (c) => jsonResponse(200, await deleteEnvironmentManifest(c.env.tenant, nameOf(c.req.param("name")))));
118
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * Environments (`/v1/environments/**`, protocol 11, D55, D56): create, find or change one by id,
3
- * read it, list them by label, read its lifecycle stream, stop, reset and delete it, and write
4
- * its vault's secrets. Ids may hold `/`, sent percent-encoded as one path segment
5
- * (`users%2Falice%2Fweb`). A token caller reaches only the ids its Environment grants match,
6
- * and changes them only with `environments:write`. Secret values are write-only: no route reads
7
- * one back.
8
- */
9
- import type { OpenAPIHono } from "@hono/zod-openapi";
10
- import type { TenantEnv } from "../app.js";
11
- export declare function environmentRoutes(api: OpenAPIHono<TenantEnv>): void;