@nylorun/runtime 0.27.0-beta → 0.29.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +1 -1
  3. package/dist/api/a2a/card.d.ts +4 -3
  4. package/dist/api/a2a/card.js +3 -3
  5. package/dist/api/a2a/routes.js +18 -7
  6. package/dist/api/ag-ui/routes.js +4 -7
  7. package/dist/api/components.d.ts +5854 -2718
  8. package/dist/api/components.js +39 -30
  9. package/dist/api/http/app.js +9 -4
  10. package/dist/api/http/body.d.ts +2 -0
  11. package/dist/api/http/body.js +20 -0
  12. package/dist/api/http/define.d.ts +4 -0
  13. package/dist/api/http/define.js +20 -9
  14. package/dist/api/http/routes/agent-manifests.d.ts +10 -0
  15. package/dist/api/http/routes/agent-manifests.js +135 -0
  16. package/dist/api/http/routes/agents.d.ts +11 -0
  17. package/dist/api/http/routes/agents.js +418 -0
  18. package/dist/api/http/routes/artifacts.js +2 -2
  19. package/dist/api/http/routes/coverage.d.ts +4 -4
  20. package/dist/api/http/routes/coverage.js +12 -6
  21. package/dist/api/http/routes/files.js +1 -1
  22. package/dist/api/http/routes/mcp.js +2 -2
  23. package/dist/api/http/routes/me.d.ts +1 -1
  24. package/dist/api/http/routes/me.js +1 -2
  25. package/dist/api/http/routes/memories.d.ts +9 -0
  26. package/dist/api/http/routes/memories.js +152 -0
  27. package/dist/api/http/routes/reads.d.ts +1 -137
  28. package/dist/api/http/routes/reads.js +31 -12
  29. package/dist/api/http/routes/sessions.js +21 -50
  30. package/dist/api/http/routes/tenant.js +13 -10
  31. package/dist/api/http/routes/triggers.d.ts +10 -0
  32. package/dist/api/http/routes/triggers.js +183 -0
  33. package/dist/api/openapi.js +69 -47
  34. package/dist/artifacts/disk.d.ts +10 -10
  35. package/dist/artifacts/disk.js +27 -27
  36. package/dist/artifacts/quota.d.ts +1 -1
  37. package/dist/artifacts/quota.js +2 -2
  38. package/dist/artifacts/service.js +2 -2
  39. package/dist/core/flow-host.d.ts +0 -1
  40. package/dist/gates/egress-credentials.js +1 -1
  41. package/dist/gates/egress.d.ts +5 -5
  42. package/dist/gates/egress.js +6 -6
  43. package/dist/gates/http-tool.js +2 -2
  44. package/dist/gates/meter.js +22 -5
  45. package/dist/gates/policy.d.ts +8 -3
  46. package/dist/gates/policy.js +7 -2
  47. package/dist/gates/tenant-vaults.d.ts +1 -1
  48. package/dist/harness/pod.d.ts +2 -0
  49. package/dist/harness/setup.js +17 -6
  50. package/dist/harness-api/record.js +11 -0
  51. package/dist/harness-api/server.js +4 -0
  52. package/dist/harness-api/start.d.ts +11 -7
  53. package/dist/harness-api/start.js +49 -5
  54. package/dist/harness-api/workspace.js +2 -2
  55. package/dist/host/config-for.d.ts +2 -0
  56. package/dist/host/config-for.js +1 -0
  57. package/dist/host/http.d.ts +2 -2
  58. package/dist/host/http.js +5 -2
  59. package/dist/host/main.js +1 -0
  60. package/dist/keys/keys.d.ts +15 -4
  61. package/dist/keys/keys.js +16 -0
  62. package/dist/keys/webhooks.d.ts +25 -0
  63. package/dist/keys/webhooks.js +62 -0
  64. package/dist/management-openapi.json +313 -133
  65. package/dist/mcp/connect.d.ts +2 -2
  66. package/dist/mcp/connect.js +3 -3
  67. package/dist/mcp/preview.js +7 -7
  68. package/dist/memory/service.d.ts +97 -0
  69. package/dist/memory/service.js +335 -0
  70. package/dist/memory/tool.d.ts +36 -0
  71. package/dist/memory/tool.js +84 -0
  72. package/dist/model/pi-model.js +7 -0
  73. package/dist/openapi.json +13120 -9034
  74. package/dist/reads/types.d.ts +3 -3
  75. package/dist/record/agent.d.ts +27 -0
  76. package/dist/record/agent.js +46 -0
  77. package/dist/record/index.d.ts +1 -1
  78. package/dist/record/index.js +1 -1
  79. package/dist/sandbox/join.js +5 -5
  80. package/dist/sandbox/placement.d.ts +1 -1
  81. package/dist/sandbox/placement.js +3 -3
  82. package/dist/sandbox/pods/lifecycle.d.ts +6 -6
  83. package/dist/sandbox/pods/lifecycle.js +6 -6
  84. package/dist/sandbox/pods/reconcile.js +10 -2
  85. package/dist/sandbox/resolve.d.ts +3 -3
  86. package/dist/sandbox/resolve.js +7 -7
  87. package/dist/sandbox/setup.d.ts +3 -1
  88. package/dist/sandbox/setup.js +14 -4
  89. package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
  90. package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
  91. package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
  92. package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
  93. package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
  94. package/dist/store/postgres/drizzle/0037_model_usage_cost_split.sql +4 -0
  95. package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
  96. package/dist/store/postgres/reads.js +44 -87
  97. package/dist/store/postgres/record-writer.d.ts +4 -4
  98. package/dist/store/postgres/record-writer.js +9 -9
  99. package/dist/store/postgres/schema.d.ts +1408 -123
  100. package/dist/store/postgres/schema.js +194 -38
  101. package/dist/store/postgres/store.js +329 -79
  102. package/dist/store/postgres/tenant.js +1 -2
  103. package/dist/store/types.d.ts +155 -50
  104. package/dist/store/types.js +0 -1
  105. package/dist/tenant/advance.js +20 -8
  106. package/dist/tenant/agent-manifests.d.ts +72 -0
  107. package/dist/tenant/agent-manifests.js +359 -0
  108. package/dist/tenant/agent-versions.d.ts +126 -0
  109. package/dist/tenant/agent-versions.js +389 -0
  110. package/dist/tenant/agents.d.ts +230 -0
  111. package/dist/tenant/{environments.js → agents.js} +464 -320
  112. package/dist/tenant/auth.d.ts +1 -1
  113. package/dist/tenant/auth.js +3 -3
  114. package/dist/tenant/commands.d.ts +63 -2
  115. package/dist/tenant/commands.js +240 -83
  116. package/dist/tenant/context.d.ts +46 -19
  117. package/dist/tenant/context.js +6 -2
  118. package/dist/tenant/effects.js +7 -16
  119. package/dist/tenant/identity-file.d.ts +6 -6
  120. package/dist/tenant/identity-file.js +12 -10
  121. package/dist/tenant/issuers.js +3 -3
  122. package/dist/tenant/limits.d.ts +0 -3
  123. package/dist/tenant/limits.js +0 -3
  124. package/dist/tenant/memories.d.ts +38 -0
  125. package/dist/tenant/memories.js +119 -0
  126. package/dist/tenant/reset.js +2 -2
  127. package/dist/tenant/run-grants.d.ts +6 -0
  128. package/dist/tenant/run-grants.js +4 -0
  129. package/dist/tenant/runtime.d.ts +1 -1
  130. package/dist/tenant/runtime.js +8 -0
  131. package/dist/tenant/sessions.d.ts +28 -45
  132. package/dist/tenant/sessions.js +85 -208
  133. package/dist/tenant/status.js +6 -7
  134. package/dist/tenant/sweep.js +12 -3
  135. package/dist/tenant/transcript.d.ts +16 -16
  136. package/dist/tenant/triggers.d.ts +143 -0
  137. package/dist/tenant/triggers.js +715 -0
  138. package/dist/tenant/turn-timing.d.ts +11 -1
  139. package/dist/tenant/turn-timing.js +3 -1
  140. package/dist/tenant/types.d.ts +5 -0
  141. package/dist/tenant/worker.d.ts +2 -0
  142. package/dist/tenant/worker.js +5 -0
  143. package/dist/traces/map.d.ts +1 -1
  144. package/dist/traces/map.js +7 -1
  145. package/dist/tuning.d.ts +7 -0
  146. package/dist/tuning.js +19 -0
  147. package/dist/vault/service.d.ts +13 -8
  148. package/dist/vault/service.js +61 -17
  149. package/dist/version.d.ts +1 -1
  150. package/dist/version.js +1 -1
  151. package/package.json +13 -12
  152. package/dist/api/http/routes/environment-manifests.d.ts +0 -9
  153. package/dist/api/http/routes/environment-manifests.js +0 -118
  154. package/dist/api/http/routes/environments.d.ts +0 -11
  155. package/dist/api/http/routes/environments.js +0 -332
  156. package/dist/record/environment.d.ts +0 -27
  157. package/dist/record/environment.js +0 -46
  158. package/dist/tenant/environment-manifests.d.ts +0 -57
  159. package/dist/tenant/environment-manifests.js +0 -208
  160. package/dist/tenant/environments.d.ts +0 -172
@@ -0,0 +1,152 @@
1
+ import { z } from "zod";
2
+ import { isAgentId, PutMemoryRequestSchema } from "@nylorun/core/contracts";
3
+ import { MemoryList, MemoryVersionDetail, MemoryVersionPage, MemoryView, PutMemoryRequest } from "../../components.js";
4
+ import { assertReaches } from "../../../tenant/agents.js";
5
+ import { fail } from "../../../tenant/http.js";
6
+ import { deleteMemoryPath, listMemories, listMemoryVersions, putMemoryContent, readMemory, readMemoryVersion, } from "../../../tenant/memories.js";
7
+ import { readJson } from "../body.js";
8
+ import { tenantRoute } from "../define.js";
9
+ import { pageQuery, pageSize, parseQuery } from "./reads.js";
10
+ import { jsonResponse } from "../respond.js";
11
+ const READ = {
12
+ credentials: ["application", "subject", "token"],
13
+ scopes: ["sessions:own", "agents:read", "agents:write"],
14
+ };
15
+ const WRITE = {
16
+ credentials: ["application", "subject", "token"],
17
+ scopes: ["agents:write"],
18
+ };
19
+ const json = (schema, description) => ({
20
+ description,
21
+ content: { "application/json": { schema } },
22
+ });
23
+ const agentId = z.object({
24
+ agentId: z.string().meta({ description: "The Agent id, percent-encoded" }),
25
+ });
26
+ const memoryPath = agentId.extend({
27
+ path: z.string().meta({
28
+ description: "The memory's path under `/memories`, percent-encoded: `conventions%2Frelease.md` is `/memories/conventions/release.md`",
29
+ }),
30
+ });
31
+ function idOf(value) {
32
+ if (!isAgentId(value))
33
+ return fail(400, "An Agent id is up to 200 characters: /-separated segments of letters, digits, '.', '_' and '-'");
34
+ return value;
35
+ }
36
+ export function memoryRoutes(api) {
37
+ tenantRoute(api, READ, {
38
+ method: "get",
39
+ path: "/v1/agents/{agentId}/memories",
40
+ tags: ["Memory"],
41
+ summary: "List an Agent's memories",
42
+ description: "Its memories by path, each with its size, `content_sha256`, frontmatter `description` and version. `pathPrefix` keeps one folder's (`conventions` for `/memories/conventions/…`).",
43
+ request: {
44
+ params: agentId,
45
+ query: pageQuery.extend({
46
+ pathPrefix: z.string().optional().meta({ description: "Only the memories in this folder, relative to `/memories`" }),
47
+ }),
48
+ },
49
+ responses: { 200: json(MemoryList, "A page of memories") },
50
+ }, async (c) => {
51
+ const id = idOf(c.req.param("agentId"));
52
+ assertReaches(c.get("scope"), id);
53
+ const pathPrefix = c.req.query("pathPrefix");
54
+ const { limit, cursor } = parseQuery(pageQuery, { limit: c.req.query("limit"), cursor: c.req.query("cursor") });
55
+ return jsonResponse(200, await listMemories(c.env.tenant, id, {
56
+ ...(pathPrefix ? { pathPrefix } : {}),
57
+ ...(cursor ? { cursor } : {}),
58
+ limit: await pageSize(c.env.tenant, limit, { fallback: 100 }),
59
+ }));
60
+ });
61
+ tenantRoute(api, READ, {
62
+ method: "get",
63
+ path: "/v1/agents/{agentId}/memories/{path}",
64
+ tags: ["Memory"],
65
+ summary: "Read a memory",
66
+ description: "The memory's content, with its size, `content_sha256`, description and version.",
67
+ request: { params: memoryPath },
68
+ responses: { 200: json(MemoryView, "The memory"), 404: { description: "No memory has this path" } },
69
+ }, async (c) => {
70
+ const id = idOf(c.req.param("agentId"));
71
+ assertReaches(c.get("scope"), id);
72
+ return jsonResponse(200, await readMemory(c.env.tenant, id, c.req.param("path") ?? ""));
73
+ });
74
+ tenantRoute(api, WRITE, {
75
+ method: "put",
76
+ path: "/v1/agents/{agentId}/memories/{path}",
77
+ tags: ["Memory"],
78
+ summary: "Write a memory",
79
+ description: "Creates the memory or replaces its content, as the `memory` tool's `create` does, and records a version with the caller as its actor. This is how an app seeds an Agent's memory. With `contentSha256` the write applies only while the memory still has that content (`\"\"`: only while no memory has the path); otherwise `409 memory_conflict` with the current memory. The path ends in `.md`; the Runtime limits `memoryBytes` (`413`) and `memoriesPerAgent` (`409 limit_exceeded`) hold.",
80
+ request: {
81
+ params: memoryPath,
82
+ body: { required: true, content: { "application/json": { schema: PutMemoryRequest } } },
83
+ },
84
+ responses: {
85
+ 200: json(MemoryView, "The memory as written"),
86
+ 409: { description: "The `contentSha256` precondition no longer holds, or a folder has this path (`memory_conflict`)" },
87
+ 413: { description: "Larger than the Runtime limit `memoryBytes`" },
88
+ },
89
+ }, async (c) => {
90
+ const id = idOf(c.req.param("agentId"));
91
+ const scope = c.get("scope");
92
+ assertReaches(scope, id);
93
+ const body = PutMemoryRequestSchema.safeParse(await readJson(c));
94
+ if (!body.success)
95
+ fail(400, "The body is { content, contentSha256? }", { code: "invalid_request" });
96
+ return jsonResponse(200, await putMemoryContent(c.env.tenant, id, c.req.param("path") ?? "", body.data, scope));
97
+ });
98
+ tenantRoute(api, WRITE, {
99
+ method: "delete",
100
+ path: "/v1/agents/{agentId}/memories/{path}",
101
+ tags: ["Memory"],
102
+ summary: "Delete a memory or a folder",
103
+ description: "Deletes the memory, or every memory in the folder the path names, each with a version that records the delete. Versions stay with the Agent.",
104
+ request: { params: memoryPath },
105
+ responses: {
106
+ 200: json(z.object({ path: z.string(), deleted: z.number().int().nonnegative() }).strict(), "What was deleted"),
107
+ 404: { description: "No memory or folder has this path" },
108
+ },
109
+ }, async (c) => {
110
+ const id = idOf(c.req.param("agentId"));
111
+ const scope = c.get("scope");
112
+ assertReaches(scope, id);
113
+ return jsonResponse(200, await deleteMemoryPath(c.env.tenant, id, c.req.param("path") ?? "", scope));
114
+ });
115
+ tenantRoute(api, READ, {
116
+ method: "get",
117
+ path: "/v1/agents/{agentId}/memory_versions",
118
+ tags: ["Memory"],
119
+ summary: "List an Agent's memory versions",
120
+ description: "Every change to its memories, newest first: the operation, the path (a rename's names its old one), and who made it: `agent:<manifest>@<version>` with the session, turn and trigger run of a turn's write, or the API caller. `path` keeps one memory's.",
121
+ request: {
122
+ params: agentId,
123
+ query: pageQuery.extend({
124
+ path: z.string().optional().meta({ description: "Only this memory's versions, relative to `/memories`" }),
125
+ }),
126
+ },
127
+ responses: { 200: json(MemoryVersionPage, "A page of versions") },
128
+ }, async (c) => {
129
+ const id = idOf(c.req.param("agentId"));
130
+ assertReaches(c.get("scope"), id);
131
+ const path = c.req.query("path");
132
+ const { limit, cursor } = parseQuery(pageQuery, { limit: c.req.query("limit"), cursor: c.req.query("cursor") });
133
+ return jsonResponse(200, await listMemoryVersions(c.env.tenant, id, {
134
+ ...(path ? { path } : {}),
135
+ ...(cursor ? { cursor } : {}),
136
+ limit: await pageSize(c.env.tenant, limit, { fallback: 50 }),
137
+ }));
138
+ });
139
+ tenantRoute(api, READ, {
140
+ method: "get",
141
+ path: "/v1/agents/{agentId}/memory_versions/{versionId}",
142
+ tags: ["Memory"],
143
+ summary: "Read a memory version",
144
+ description: "One version with the content it left (`null` for a delete). To roll a memory back, write this content again.",
145
+ request: { params: agentId.extend({ versionId: z.string().meta({ description: "The version's id (`mv_…`)" }) }) },
146
+ responses: { 200: json(MemoryVersionDetail, "The version"), 404: { description: "No such version" } },
147
+ }, async (c) => {
148
+ const id = idOf(c.req.param("agentId"));
149
+ assertReaches(c.get("scope"), id);
150
+ return jsonResponse(200, await readMemoryVersion(c.env.tenant, id, c.req.param("versionId") ?? ""));
151
+ });
152
+ }
@@ -30,7 +30,7 @@ export declare const sessionPageQuery: z.ZodObject<{
30
30
  uncertain: "uncertain";
31
31
  waiting: "waiting";
32
32
  }>>;
33
- environmentId: z.ZodOptional<z.ZodString>;
33
+ manifest: z.ZodOptional<z.ZodString>;
34
34
  ownerUserId: z.ZodOptional<z.ZodString>;
35
35
  }, z.core.$strict>;
36
36
  export declare function parseQuery<T>(schema: z.ZodType<T>, query: unknown): T;
@@ -42,145 +42,9 @@ export declare function sessionPage(ctx: TenantEnv["Bindings"]["tenant"], scope:
42
42
  status: "cancelled" | "completed" | "failed" | "idle" | "paused" | "runnable" | "running" | "uncertain" | "waiting";
43
43
  activeTurnId: string | null;
44
44
  lastTurnId: string | null;
45
- environmentId: string | null;
46
45
  createdAt: string | null;
47
46
  lastEventAt: string | null;
48
47
  }[];
49
48
  nextCursor: string | null;
50
49
  }>;
51
- export declare function environmentPage(ctx: TenantEnv["Bindings"]["tenant"], scope: TenantEnv["Variables"]["scope"], query: Record<string, string>, labels: Record<string, string>): Promise<{
52
- environments: {
53
- id: string;
54
- kind: "none" | "pod" | "virtual";
55
- labels: Record<string, string>;
56
- manifest: {
57
- image?: string | undefined;
58
- resources?: {
59
- cpus: number;
60
- memory: string;
61
- } | undefined;
62
- storage?: string | undefined;
63
- network?: {
64
- allowedHosts: string[];
65
- } | undefined;
66
- harness: string;
67
- lifecycle: {
68
- idle?: string | undefined;
69
- deleteAfter?: string | undefined;
70
- };
71
- setup?: {
72
- command: string;
73
- cwd?: string | undefined;
74
- timeout?: string | undefined;
75
- }[] | undefined;
76
- files?: ({
77
- artifactId: string;
78
- version: number;
79
- path: string;
80
- } | {
81
- path: string;
82
- files: Record<string, string>;
83
- })[] | undefined;
84
- limits?: {
85
- artifacts?: {
86
- fileBytes?: number | undefined;
87
- totalBytes?: number | undefined;
88
- } | undefined;
89
- skills?: {
90
- totalBytes?: number | undefined;
91
- } | undefined;
92
- disk?: {
93
- versionBytes?: number | undefined;
94
- versionEntries?: number | undefined;
95
- } | undefined;
96
- } | undefined;
97
- secrets?: Record<string, {
98
- required?: boolean | undefined;
99
- description?: string | undefined;
100
- type: "environment_secret";
101
- allowedHosts: string[];
102
- inject?: {
103
- header: string;
104
- format: string;
105
- } | undefined;
106
- } | {
107
- required?: boolean | undefined;
108
- description?: string | undefined;
109
- type: "environment_variable";
110
- } | {
111
- via?: string | undefined;
112
- identity?: {
113
- header: string;
114
- } | undefined;
115
- required?: boolean | undefined;
116
- description?: string | undefined;
117
- type: "bearer";
118
- url: string;
119
- } | {
120
- via?: string | undefined;
121
- identity?: {
122
- header: string;
123
- } | undefined;
124
- required?: boolean | undefined;
125
- description?: string | undefined;
126
- type: "headers";
127
- url: string;
128
- }> | undefined;
129
- };
130
- manifestName?: string | undefined;
131
- manifestVersion?: number | undefined;
132
- manifestHash?: string | undefined;
133
- limits: {
134
- artifacts: {
135
- fileBytes: number;
136
- totalBytes: number;
137
- };
138
- skills: {
139
- totalBytes: number;
140
- };
141
- disk: {
142
- versionBytes: number;
143
- versionEntries: number;
144
- };
145
- };
146
- forkedFrom?: {
147
- environment: string;
148
- disk: number | null;
149
- } | undefined;
150
- slots?: {
151
- name: string;
152
- type: string;
153
- required: boolean;
154
- set: boolean;
155
- }[] | undefined;
156
- version: number;
157
- appliedVersion: number;
158
- ownerUserId: string | null;
159
- state: string;
160
- pod?: {
161
- desired: "deleted" | "running" | "suspended";
162
- observed: "creating" | "deleting" | "failed" | "lost" | "running" | "suspended";
163
- volumeGeneration: number;
164
- hostEpoch: number;
165
- reason?: string | undefined;
166
- imageDigest?: string | undefined;
167
- } | undefined;
168
- lastUsedAt: string;
169
- disk?: {
170
- latestVersion: number | null;
171
- operation?: {
172
- kind: "restore" | "seed" | "snapshot";
173
- version?: number | undefined;
174
- startedAt: string;
175
- } | undefined;
176
- } | undefined;
177
- sessions: {
178
- id: string;
179
- activeTurnId: string | null;
180
- }[];
181
- createdAt: string;
182
- updatedAt: string;
183
- }[];
184
- nextCursor: string | null;
185
- }>;
186
50
  export declare function readRoutes(api: OpenAPIHono<TenantEnv>): void;
@@ -1,14 +1,35 @@
1
1
  import { z } from "zod";
2
2
  import { SESSION_STATUSES } from "@nylorun/core/contracts";
3
+ import { PROTOCOL_HEADER } from "@nylorun/core/compatibility";
3
4
  import { SessionManifestView, SessionUsageTotals, ModelCallsPage, ModelCallExportPage, ToolCallsPage, TurnTrace, } from "../../components.js";
4
5
  import { turnTrace } from "../../../traces/read.js";
5
6
  import { readAccess, readStoreOf } from "../../../reads/access.js";
6
7
  import { requireApplication, requirePrincipal } from "../../../tenant/auth.js";
7
8
  import { fail } from "../../../tenant/http.js";
8
9
  import { readRuntimeLimits } from "../../../tenant/runtime-limits.js";
9
- import { environmentGrantsOf } from "../../../tenant/environments.js";
10
10
  import { tenantRoute } from "../define.js";
11
11
  import { jsonResponse } from "../respond.js";
12
+ /**
13
+ * Whether a client speaks protocol 18, whose usage reads carry the calls' timing and the cost by
14
+ * token kind. An older client's SDK parses these reads strictly, so it gets them without.
15
+ */
16
+ function usageByKind(header) {
17
+ const protocol = Number(header?.trim());
18
+ return Number.isInteger(protocol) && protocol >= 18;
19
+ }
20
+ /** A session's usage as a protocol 16 or 17 client reads it. */
21
+ function totalsBefore18(totals) {
22
+ const { costSplit: _split, splitCalls: _splitCalls, modelTimeMs: _time, timedCalls: _timed, ...rest } = totals;
23
+ return rest;
24
+ }
25
+ /** A model call as a protocol 16 or 17 client reads it. */
26
+ function callBefore18(call) {
27
+ const { startedAt: _started, endedAt: _ended, usage, ...rest } = call;
28
+ if (usage === null)
29
+ return { ...rest, usage };
30
+ const { costSplit: _split, ...counts } = usage;
31
+ return { ...rest, usage: counts };
32
+ }
12
33
  /** A page's `limit`, held to the Tenant's Runtime limit `pageItems` (`pageSize`). */
13
34
  const limitParam = (fallback) => z.coerce
14
35
  .number()
@@ -40,7 +61,8 @@ export const sessionPageQuery = pageQuery
40
61
  .extend({
41
62
  agentId: z.string().optional(),
42
63
  status: z.enum(SESSION_STATUSES).optional(),
43
- environmentId: z.string().optional(),
64
+ /** Sessions that pinned a version of this agent manifest. */
65
+ manifest: z.string().optional(),
44
66
  ownerUserId: z.string().optional(),
45
67
  })
46
68
  .strict();
@@ -56,12 +78,6 @@ export async function sessionPage(ctx, scope, query) {
56
78
  requireApplication(scope);
57
79
  return readStoreOf(ctx).sessions(filters, { limit: await pageSize(ctx, limit), cursor }, readAccess(scope));
58
80
  }
59
- export async function environmentPage(ctx, scope, query, labels) {
60
- const { limit, cursor } = parseQuery(pageQuery.extend({ label: z.string().optional() }).strict(), query);
61
- if (limit === undefined)
62
- return fail(400, "Pagination requires limit");
63
- return readStoreOf(ctx).environments(labels, { limit: await pageSize(ctx, limit), cursor }, readAccess(scope), environmentGrantsOf(scope));
64
- }
65
81
  export function readRoutes(api) {
66
82
  const sessionId = z.object({ sessionId: z.string() });
67
83
  const turnQuery = z.object({ turnId: z.string().optional() }).strict();
@@ -111,14 +127,16 @@ export function readRoutes(api) {
111
127
  },
112
128
  }, async (c) => {
113
129
  requirePrincipal(c.get("scope"));
114
- const store = readStoreOf(c.env.tenant), access = readAccess(c.get("scope")), id = c.req.param("sessionId");
130
+ const store = readStoreOf(c.env.tenant), access = readAccess(c.get("scope")), id = c.req.param("sessionId"), current = usageByKind(c.req.header(PROTOCOL_HEADER));
115
131
  if (kind === "usage") {
116
132
  const q = parseQuery(turnQuery, c.req.query());
117
- return jsonResponse(200, await store.usage(id, q.turnId, access));
133
+ const totals = await store.usage(id, q.turnId, access);
134
+ return jsonResponse(200, current ? totals : totalsBefore18(totals));
118
135
  }
119
136
  const q = parseQuery(callsQuery, c.req.query());
120
137
  const limit = await pageSize(c.env.tenant, q.limit, { fallback: CALLS_PAGE_DEFAULT });
121
- return jsonResponse(200, await store.modelCalls(id, q.turnId, { ...q, limit }, access));
138
+ const page = await store.modelCalls(id, q.turnId, { ...q, limit }, access);
139
+ return jsonResponse(200, current ? page : { ...page, calls: page.calls.map(callBefore18) });
122
140
  });
123
141
  }
124
142
  const toolCallsQuery = turnQuery
@@ -199,6 +217,7 @@ export function readRoutes(api) {
199
217
  }, async (c) => {
200
218
  const q = parseQuery(exportQuery, c.req.query());
201
219
  const limit = await pageSize(c.env.tenant, q.limit, { fallback: EXPORT_PAGE_DEFAULT, name: "exportPageItems" });
202
- return jsonResponse(200, await readStoreOf(c.env.tenant).exportModel(q.after, limit));
220
+ const page = await readStoreOf(c.env.tenant).exportModel(q.after, limit);
221
+ return jsonResponse(200, usageByKind(c.req.header(PROTOCOL_HEADER)) ? page : { ...page, calls: page.calls.map(callBefore18) });
203
222
  });
204
223
  }
@@ -1,16 +1,17 @@
1
1
  import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
2
2
  import { z } from "zod";
3
- import { PutAgentRequestSchema, PutSessionRequestSchema, SessionCommandSchema, } from "@nylorun/core/contracts";
3
+ import { PROTOCOL_HEADER } from "@nylorun/core/compatibility";
4
+ import { PutSessionRequestSchema, SessionCommandSchema, } from "@nylorun/core/contracts";
4
5
  import { handleSessionSandboxTool, SandboxRouteError } from "../../../core/sandbox-routes.js";
5
6
  import { traceContextOf } from "../../../traces/ids.js";
6
- import { AcceptedResponse, ListAgentsResponse, ListPublicAgentsResponse, ListSessionsResponse, SessionPage, HistoryPage, SessionEvent, PutAgentRequest, PutAgentResponse, PutSessionRequest, SandboxToolOutcome, SessionCommand, SessionItemsResponse, SessionView, StreamClosedFrame, } from "../../components.js";
7
+ import { AcceptedResponse, QueuedResponse, ListSessionsResponse, SessionPage, HistoryPage, SessionEvent, PutSessionRequest, SandboxToolOutcome, SessionCommand, SessionItemsResponse, SessionView, StreamClosedFrame, } from "../../components.js";
7
8
  import { accessOf, requireApplication } from "../../../tenant/auth.js";
8
9
  import { command } from "../../../tenant/commands.js";
9
10
  import { loadSession, sessionOf } from "../../../tenant/context.js";
10
11
  import { fail } from "../../../tenant/http.js";
11
- import { environmentGrantsOf, sandboxRouteDeps } from "../../../tenant/environments.js";
12
+ import { agentGrantsOf, sandboxRouteDeps } from "../../../tenant/agents.js";
12
13
  import { readHistory, requestCursor, streamHolderOf, streamSessionEvents } from "../../../tenant/session-streams.js";
13
- import { listAgentsPublic, listDefinitions, listSessions, putDefinition, putSession, sessionView, } from "../../../tenant/sessions.js";
14
+ import { listSessions, putSession, sessionView } from "../../../tenant/sessions.js";
14
15
  import { readJson } from "../body.js";
15
16
  import { TraceHeaders, tenantRoute } from "../define.js";
16
17
  import { pageQuery, pageSize, sessionPage, sessionPageQuery, parseQuery } from "./reads.js";
@@ -46,61 +47,31 @@ async function sessionBelow(ctx, incoming, id, access) {
46
47
  await loadSession(ctx, id, access);
47
48
  return requestCursor(incoming, urlOf(incoming));
48
49
  }
50
+ /**
51
+ * What a message to a busy session does when it says nothing (D76): a protocol 17 client's waits
52
+ * in the queue; a protocol 16 client's is refused, as before.
53
+ */
54
+ function deliveryFor(headers) {
55
+ const protocol = Number(headers.get(PROTOCOL_HEADER)?.trim());
56
+ return Number.isInteger(protocol) && protocol >= 17 ? "queue" : "reject";
57
+ }
49
58
  export function sessionRoutes(api) {
50
59
  tenantRoute(api, OWN_SESSIONS, {
51
60
  method: "post",
52
61
  path: "/v1/sessions/{sessionId}/commands",
53
62
  tags: ["Sessions"],
54
63
  summary: "Send a command to a session",
55
- description: "A message starts a turn; the others answer what the session waits on, or cancel. Idempotent on the command's `idempotencyKey`.",
64
+ description: "A message starts a turn, or waits in the session's queue while a turn runs or waits (protocol 17; `delivery: \"reject\"` is refused with `409 session_busy` instead); the others answer what the session waits on, or cancel. Idempotent on the command's `idempotencyKey`.",
56
65
  request: { params: sessionId, body: body(SessionCommand), headers: TraceHeaders },
57
66
  responses: {
58
- 200: json(AcceptedResponse, "The command, accepted"),
59
- 409: { description: "A turn is running, or the session does not wait on this" },
67
+ 200: json(z.union([AcceptedResponse, QueuedResponse]), "The command, accepted; a message may be queued"),
68
+ 409: { description: "A turn is running (`session_busy`), or the session does not wait on this" },
69
+ 429: { description: "The session's queue is full (`queue_full`)" },
60
70
  },
61
71
  }, async (c) => {
62
72
  // A `traceparent` joins the turn to the caller's trace (R7.4); a malformed one is ignored.
63
73
  const trace = traceContextOf(c.req.raw.headers);
64
- return jsonResponse(200, await command(c.env.tenant, c.req.param("sessionId"), SessionCommandSchema.parse(await readJson(c)), c.get("scope"), trace ? { trace } : {}));
65
- });
66
- tenantRoute(api, {
67
- credentials: ["application", "subject", "token"],
68
- scopes: ["agents:read", "agents:write"],
69
- }, {
70
- method: "get",
71
- path: "/v1/agents",
72
- tags: ["Agents"],
73
- summary: "List agents",
74
- description: "With an application key, every definition, manifest included. With a trusted issuer's token, only the agents it may use, by name.",
75
- responses: {
76
- 200: json(z.union([ListAgentsResponse, ListPublicAgentsResponse]), "The agents"),
77
- },
78
- }, async (c) => {
79
- const scope = c.get("scope");
80
- const ctx = c.env.tenant;
81
- return jsonResponse(200, scope.kind === "token"
82
- ? await listAgentsPublic(ctx, scope.agents)
83
- : await listDefinitions(ctx));
84
- });
85
- tenantRoute(api, { credentials: ["application", "subject"], scopes: ["agents:write"] }, {
86
- method: "put",
87
- path: "/v1/agents/{agentId}",
88
- tags: ["Agents"],
89
- summary: "Put an agent's definition",
90
- description: "Saves the agent's definition. With `?dryRun=true` (protocol 14) every check runs and nothing is saved: the answer says what a save would record.",
91
- request: {
92
- params: z.object({ agentId: z.string() }),
93
- query: z.object({
94
- dryRun: z.enum(["true", "false"]).optional().meta({ description: "`true`: check and save nothing" }),
95
- }),
96
- body: body(PutAgentRequest),
97
- },
98
- responses: {
99
- 200: json(PutAgentResponse, "The definition, stored (or checked, with dryRun)"),
100
- 413: { description: "A skill's files hold more than the Tenant's skill limit together (`limit_exceeded`)" },
101
- },
102
- }, async (c) => {
103
- return jsonResponse(200, await putDefinition(c.env.tenant, c.req.param("agentId"), PutAgentRequestSchema.parse(await readJson(c)), { dryRun: c.req.query("dryRun") === "true" }));
74
+ return jsonResponse(200, await command(c.env.tenant, c.req.param("sessionId"), SessionCommandSchema.parse(await readJson(c)), c.get("scope"), { ...(trace ? { trace } : {}), delivery: deliveryFor(c.req.raw.headers) }));
104
75
  });
105
76
  tenantRoute(api, OWN_SESSIONS, {
106
77
  method: "get",
@@ -121,7 +92,7 @@ export function sessionRoutes(api) {
121
92
  path: "/v1/sessions/{sessionId}",
122
93
  tags: ["Sessions"],
123
94
  summary: "Open a session",
124
- description: "Creates the session, or answers the one already opened with the same agent, owner, info and Environment. Its Environment (its sandbox and vault) is chosen now and fixed for its life.",
95
+ description: "Creates the session on an Agent, or answers the one already opened with the same Agent, owner and info. Its Agent (its sandbox and vault) is fixed for its life, and it pins the manifest version the Agent runs. No session is special: an app that keeps one conversation going reuses its id.",
125
96
  request: { params: sessionId, body: body(PutSessionRequest) },
126
97
  responses: {
127
98
  200: json(SessionView, "The session"),
@@ -134,8 +105,8 @@ export function sessionRoutes(api) {
134
105
  // Agent code may trust `info`: only an app server sets it.
135
106
  if (scope.kind === "token" && request.info !== undefined)
136
107
  fail(403, "A token caller cannot set session info", { code: "scope_required" });
137
- const grants = environmentGrantsOf(scope);
138
- const session = await putSession(ctx, c.req.param("sessionId"), request, accessOf(scope), grants === undefined ? {} : { environmentGrants: grants });
108
+ const grants = agentGrantsOf(scope);
109
+ const session = await putSession(ctx, c.req.param("sessionId"), request, accessOf(scope), grants === undefined ? {} : { agentGrants: grants });
139
110
  return jsonResponse(200, await ctx.store.tx((t) => sessionView(t, session)));
140
111
  });
141
112
  tenantRoute(api, OWN_SESSIONS, {
@@ -18,7 +18,7 @@ import { tenantReset, streamsStatus, } from "../../../tenant/streams.js";
18
18
  import { readJson } from "../body.js";
19
19
  import { pathSegments, tenantRoute } from "../define.js";
20
20
  import { jsonResponse } from "../respond.js";
21
- import { rearmPodTimers } from "../../../tenant/environments.js";
21
+ import { rearmPodTimers } from "../../../tenant/agents.js";
22
22
  import { hostTuning } from "../../../tuning.js";
23
23
  import { budgetIssues, toolPolicyIssues } from "../../../tenant/limits.js";
24
24
  /** Every Management API route (`/v1/tenant/*`): a management key, acting as itself (protocol 8). */
@@ -37,7 +37,7 @@ const body = (schema) => ({
37
37
  });
38
38
  const budgetRow = (budget, updatedAt) => ({
39
39
  scope: budget.scope,
40
- scopeId: budget.scope === "agent" ? budget.id : "*",
40
+ scopeId: budget.scope === "manifest" ? budget.id : "*",
41
41
  period: budget.period ?? null,
42
42
  limitUsd: budget.limitUsd ?? null,
43
43
  limitTokens: budget.limitTokens ?? null,
@@ -45,7 +45,7 @@ const budgetRow = (budget, updatedAt) => ({
45
45
  });
46
46
  const budgetView = (row) => ({
47
47
  scope: row.scope,
48
- ...(row.scope === "agent" ? { id: row.scopeId } : {}),
48
+ ...(row.scope === "manifest" ? { id: row.scopeId } : {}),
49
49
  ...(row.period ? { period: row.period } : {}),
50
50
  ...(row.limitUsd !== null ? { limitUsd: row.limitUsd } : {}),
51
51
  ...(row.limitTokens !== null ? { limitTokens: row.limitTokens } : {}),
@@ -114,7 +114,7 @@ export function tenantRoutes(api) {
114
114
  path: "/v1/tenant/reset",
115
115
  tags: ["Tenant"],
116
116
  summary: "Reset the Tenant",
117
- description: "Deletes its sessions, its Environments (with their vaults), or both, after draining or cancelling the turns in progress. The Tenant stays open.",
117
+ description: "Deletes its sessions, its Agents (with their vaults), or both, after draining or cancelling the turns in progress. The Tenant stays open.",
118
118
  request: { body: body(ResetTenantRequest) },
119
119
  responses: { 200: json(ResetTenantResponse, "Reset") },
120
120
  }, async (c) => {
@@ -123,7 +123,7 @@ export function tenantRoutes(api) {
123
123
  await drain(ctx, request.activeWork, hostTuning().tenant.drainTimeoutMs);
124
124
  // A sessions reset moves the Tenant to a new basin generation, so a session created
125
125
  // again with the same id, during or after the reset, starts in an empty basin at 0.
126
- const sessionsReset = request.scope !== "environments";
126
+ const sessionsReset = request.scope !== "agents";
127
127
  await resetTenant({
128
128
  store: ctx.store,
129
129
  sandbox: ctx.sandbox,
@@ -221,15 +221,15 @@ export function tenantRoutes(api) {
221
221
  method: "get",
222
222
  path: "/v1/tenant/sandbox",
223
223
  tags: ["Tenant"],
224
- summary: "Get the Tenant's sandbox backend and Environment settings",
224
+ summary: "Get the Tenant's sandbox backend and Agent compute settings",
225
225
  responses: { 200: json(TenantSandboxView, "The backend in use and the configuration in force") },
226
226
  }, async (c) => jsonResponse(200, await sandboxView(c.env.tenant)));
227
227
  tenantRoute(api, MANAGEMENT, {
228
228
  method: "put",
229
229
  path: "/v1/tenant/sandbox",
230
230
  tags: ["Tenant"],
231
- summary: "Set the Tenant's Environment settings",
232
- description: "The limits every Environment's recipe must fit, the pods' stop grace and where harnesses may run (Tenant setting `environments.config`).",
231
+ summary: "Set the Tenant's Agent compute settings",
232
+ description: "The limits every Agent's runtime must fit, the pods' stop grace and where harnesses may run (Tenant setting `environments.config`).",
233
233
  request: { body: body(PutTenantEnvironmentsRequest) },
234
234
  responses: { 200: json(TenantSandboxView, "The configuration now in force") },
235
235
  }, async (c) => {
@@ -307,7 +307,10 @@ export function tenantRoutes(api) {
307
307
  path: "/v1/tenant/tool-policy",
308
308
  tags: ["Tenant"],
309
309
  summary: "Get the Tenant's tool policy",
310
- responses: { 200: json(ToolPolicy, "The rules the Tool Gate applies to every MCP and HTTP tool call") },
310
+ responses: {
311
+ 200: json(ToolPolicy, "The rules the Tool Gate applies to every MCP and HTTP tool call"),
312
+ 409: { description: "A stored rule names an MCP tool by its model-facing `server__tool` name: every call is refused until the policy is set again" },
313
+ },
311
314
  }, async (c) => {
312
315
  try {
313
316
  return jsonResponse(200, await c.env.tenant.store.tx((t) => readToolPolicy(t)));
@@ -325,7 +328,7 @@ export function tenantRoutes(api) {
325
328
  path: "/v1/tenant/tool-policy",
326
329
  tags: ["Tenant"],
327
330
  summary: "Set the Tenant's tool policy",
328
- description: "Replaces every rule. The Tool Gate applies them to each MCP and HTTP tool call of every harness, before it sends the call: the strictest rule that matches decides. `deny` refuses the call (the model gets `tool.denied`), `ask` holds it and pauses the turn until a person answers it with an `approve` command on the session, `allow` runs it. A call no rule matches runs. A rule matches when every selector it sets matches: `tool` (the name the model calls it by; `*` matches any run of characters), `server` (an MCP server's name), `kind` (`mcp` or `http`), `agent` (the agent that calls it) and `when` (fields of the call's input by dotted path, each tested with `equals`, `in`, `gt`, `gte`, `lt`, `lte` or `matches`). Sandbox tools never cross the gate. An empty list allows every call.",
331
+ description: "Replaces every rule. The Tool Gate applies them to each MCP and HTTP tool call of every harness, before it sends the call: the strictest rule that matches decides. `deny` refuses the call (the model gets `tool.denied`), `ask` holds it and pauses the turn until a person answers it with an `approve` command on the session, `allow` runs it. A call no rule matches runs. A rule matches when every selector it sets matches: `tool` (the tool's identity: an MCP tool's own name on its `server`, or an HTTP tool's declared name, never the name the model calls it by; `*` matches any run of characters), `server` (an MCP server's name), `kind` (`mcp` or `http`), `agent` (the agent that calls it: the agent manifest's name, or the id of an agent used as a tool or of a flow's agent, never an Agent's id) and `when` (fields of the call's input by dotted path, each tested with `equals`, `in`, `gt`, `gte`, `lt`, `lte` or `matches`). A `tool` with `__` in it, a model-facing `server__tool` name, is refused (`400`): name an MCP tool by `server` and `tool`. A stored policy with such a rule refuses every call until it is set again. Sandbox tools never cross the gate. An empty list allows every call.",
329
332
  request: { body: body(PutToolPolicyRequest) },
330
333
  responses: { 200: json(ToolPolicy, "The policy now in force") },
331
334
  }, async (c) => {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * An Agent's triggers and their runs (track T4, D74): read them, pause and resume one, run one now
3
+ * (keyed by `Idempotency-Key`), and page the runs; make a webhook trigger's signing secret, and
4
+ * take a sender's delivery at its endpoint (D75), an anonymous, unversioned route. What a trigger is lives in the Agent's manifest
5
+ * (`runtime.triggers`); these routes change only what the Agent owns, its status.
6
+ */
7
+ import type { OpenAPIHono } from "@hono/zod-openapi";
8
+ import type { TenantEnv } from "../app.js";
9
+ export declare function triggerRoutes(api: OpenAPIHono<TenantEnv>): void;
10
+ export declare function webhookRoutes(api: OpenAPIHono<TenantEnv>): void;