@nylorun/runtime 0.27.0-beta → 0.28.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/api/a2a/card.d.ts +4 -3
- package/dist/api/a2a/card.js +3 -3
- package/dist/api/a2a/routes.js +18 -7
- package/dist/api/ag-ui/routes.js +4 -7
- package/dist/api/components.d.ts +5823 -2720
- package/dist/api/components.js +39 -30
- package/dist/api/http/app.js +9 -4
- package/dist/api/http/body.d.ts +2 -0
- package/dist/api/http/body.js +20 -0
- package/dist/api/http/routes/agent-manifests.d.ts +10 -0
- package/dist/api/http/routes/agent-manifests.js +135 -0
- package/dist/api/http/routes/agents.d.ts +11 -0
- package/dist/api/http/routes/agents.js +418 -0
- package/dist/api/http/routes/artifacts.js +2 -2
- package/dist/api/http/routes/coverage.d.ts +4 -4
- package/dist/api/http/routes/coverage.js +12 -6
- package/dist/api/http/routes/files.js +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.d.ts +1 -1
- package/dist/api/http/routes/me.js +1 -2
- package/dist/api/http/routes/memories.d.ts +9 -0
- package/dist/api/http/routes/memories.js +152 -0
- package/dist/api/http/routes/reads.d.ts +1 -137
- package/dist/api/http/routes/reads.js +2 -8
- package/dist/api/http/routes/sessions.js +21 -50
- package/dist/api/http/routes/tenant.js +8 -8
- package/dist/api/http/routes/triggers.d.ts +10 -0
- package/dist/api/http/routes/triggers.js +180 -0
- package/dist/api/openapi.js +69 -47
- package/dist/artifacts/disk.d.ts +10 -10
- package/dist/artifacts/disk.js +27 -27
- package/dist/artifacts/quota.d.ts +1 -1
- package/dist/artifacts/quota.js +2 -2
- package/dist/artifacts/service.js +2 -2
- package/dist/core/flow-host.d.ts +0 -1
- package/dist/gates/egress-credentials.js +1 -1
- package/dist/gates/egress.d.ts +5 -5
- package/dist/gates/egress.js +6 -6
- package/dist/gates/http-tool.js +1 -1
- package/dist/gates/meter.js +6 -5
- package/dist/gates/policy.d.ts +8 -3
- package/dist/gates/policy.js +7 -2
- package/dist/harness/pod.d.ts +2 -0
- package/dist/harness/setup.js +17 -6
- package/dist/harness-api/record.js +11 -0
- package/dist/harness-api/server.js +4 -0
- package/dist/harness-api/start.d.ts +11 -7
- package/dist/harness-api/start.js +49 -5
- package/dist/harness-api/workspace.js +2 -2
- package/dist/host/http.d.ts +2 -2
- package/dist/host/http.js +5 -2
- package/dist/keys/keys.d.ts +15 -4
- package/dist/keys/keys.js +16 -0
- package/dist/keys/webhooks.d.ts +25 -0
- package/dist/keys/webhooks.js +62 -0
- package/dist/management-openapi.json +245 -123
- package/dist/mcp/preview.js +7 -7
- package/dist/memory/service.d.ts +97 -0
- package/dist/memory/service.js +335 -0
- package/dist/memory/tool.d.ts +36 -0
- package/dist/memory/tool.js +84 -0
- package/dist/openapi.json +12837 -8790
- package/dist/reads/types.d.ts +3 -3
- package/dist/record/agent.d.ts +27 -0
- package/dist/record/agent.js +46 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/sandbox/join.js +5 -5
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +3 -3
- package/dist/sandbox/pods/lifecycle.d.ts +6 -6
- package/dist/sandbox/pods/lifecycle.js +6 -6
- package/dist/sandbox/pods/reconcile.js +10 -2
- package/dist/sandbox/resolve.d.ts +3 -3
- package/dist/sandbox/resolve.js +7 -7
- package/dist/sandbox/setup.d.ts +3 -1
- package/dist/sandbox/setup.js +14 -4
- package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
- package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
- package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
- package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +35 -0
- package/dist/store/postgres/reads.js +13 -86
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/schema.d.ts +1373 -161
- package/dist/store/postgres/schema.js +184 -37
- package/dist/store/postgres/store.js +329 -79
- package/dist/store/postgres/tenant.js +1 -2
- package/dist/store/types.d.ts +155 -50
- package/dist/store/types.js +0 -1
- package/dist/tenant/advance.js +20 -8
- package/dist/tenant/agent-manifests.d.ts +72 -0
- package/dist/tenant/agent-manifests.js +359 -0
- package/dist/tenant/agent-versions.d.ts +126 -0
- package/dist/tenant/agent-versions.js +389 -0
- package/dist/tenant/agents.d.ts +230 -0
- package/dist/tenant/{environments.js → agents.js} +464 -320
- package/dist/tenant/auth.d.ts +1 -1
- package/dist/tenant/auth.js +3 -3
- package/dist/tenant/commands.d.ts +63 -2
- package/dist/tenant/commands.js +240 -83
- package/dist/tenant/context.d.ts +46 -19
- package/dist/tenant/context.js +6 -2
- package/dist/tenant/effects.js +7 -16
- package/dist/tenant/identity-file.d.ts +6 -6
- package/dist/tenant/identity-file.js +12 -10
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/limits.d.ts +0 -3
- package/dist/tenant/limits.js +0 -3
- package/dist/tenant/memories.d.ts +38 -0
- package/dist/tenant/memories.js +119 -0
- package/dist/tenant/reset.js +2 -2
- package/dist/tenant/run-grants.d.ts +6 -0
- package/dist/tenant/run-grants.js +4 -0
- package/dist/tenant/runtime.d.ts +1 -1
- package/dist/tenant/runtime.js +8 -0
- package/dist/tenant/sessions.d.ts +28 -45
- package/dist/tenant/sessions.js +85 -208
- package/dist/tenant/status.js +6 -7
- package/dist/tenant/sweep.js +12 -3
- package/dist/tenant/transcript.d.ts +16 -16
- package/dist/tenant/triggers.d.ts +143 -0
- package/dist/tenant/triggers.js +715 -0
- package/dist/tenant/turn-timing.d.ts +11 -1
- package/dist/tenant/turn-timing.js +3 -1
- package/dist/tenant/worker.d.ts +2 -0
- package/dist/tenant/worker.js +5 -0
- package/dist/traces/map.d.ts +1 -1
- package/dist/traces/map.js +7 -1
- package/dist/tuning.d.ts +7 -0
- package/dist/tuning.js +19 -0
- package/dist/vault/service.d.ts +13 -8
- package/dist/vault/service.js +61 -17
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -12
- package/dist/api/http/routes/environment-manifests.d.ts +0 -9
- package/dist/api/http/routes/environment-manifests.js +0 -118
- package/dist/api/http/routes/environments.d.ts +0 -11
- package/dist/api/http/routes/environments.js +0 -332
- package/dist/record/environment.d.ts +0 -27
- package/dist/record/environment.js +0 -46
- package/dist/tenant/environment-manifests.d.ts +0 -57
- package/dist/tenant/environment-manifests.js +0 -208
- package/dist/tenant/environments.d.ts +0 -172
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AGENT_EVENT_TYPES, ForkAgentRequestSchema, AGENT_STATES, isAgentId, PutAgentRequestSchema, RestartAgentRequestSchema, RestoreAgentRequestSchema, } from "@nylorun/core/contracts";
|
|
3
|
+
import { deleteEnvironmentVersion, environmentVersions, restoreEnvironment, snapshotEnvironment, } from "../../../artifacts/disk.js";
|
|
4
|
+
import { DeleteAgentResponse, DeletedResponse, AgentPage, AgentRestore, AgentSnapshot, AgentDiskVersions, ForkAgentRequest, ForkAgentResponse, RestoreAgentRequest, RestartAgentRequest, AgentSecretView, AgentView, ListAgentEventsResponse, ListAgentSecretsResponse, ListAgentsResponse, PutAgentDryRunResponse, PutAgentRequest, PutAgentSecretRequest, PutAgentSecretValueRequest, } from "../../components.js";
|
|
5
|
+
import { fail } from "../../../tenant/http.js";
|
|
6
|
+
import { canonical } from "../../../store/canonical.js";
|
|
7
|
+
import { actorOf, assertReaches, deleteAgent, agentEventsOf, forkAgent, getAgent, listAgents, planAgentPut, putAgent, resetAgent, restartAgent, secretRequestFor, secretWritten, stopAgent, } from "../../../tenant/agents.js";
|
|
8
|
+
import { bodyLimit, readJson, readText } from "../body.js";
|
|
9
|
+
import { tenantRoute } from "../define.js";
|
|
10
|
+
import { pageQuery, pageSize } from "./reads.js";
|
|
11
|
+
import { jsonResponse } from "../respond.js";
|
|
12
|
+
/** Reading an Agent: any caller that may read Agents, use sessions, or change Agents. */
|
|
13
|
+
const READ = {
|
|
14
|
+
credentials: ["application", "subject", "token"],
|
|
15
|
+
scopes: ["sessions:own", "agents:read", "agents:write"],
|
|
16
|
+
};
|
|
17
|
+
/** Creating, changing or deleting one, and writing its secrets. */
|
|
18
|
+
const WRITE = {
|
|
19
|
+
credentials: ["application", "subject", "token"],
|
|
20
|
+
scopes: ["agents:write"],
|
|
21
|
+
};
|
|
22
|
+
const json = (schema, description) => ({
|
|
23
|
+
description,
|
|
24
|
+
content: { "application/json": { schema } },
|
|
25
|
+
});
|
|
26
|
+
const agentId = z.object({
|
|
27
|
+
agentId: z
|
|
28
|
+
.string()
|
|
29
|
+
.meta({ description: "The Agent id, percent-encoded: `users%2Falice%2Ftriage` for `users/alice/triage`" }),
|
|
30
|
+
});
|
|
31
|
+
const secretPath = agentId.extend({
|
|
32
|
+
name: z.string().meta({
|
|
33
|
+
description: "The secret's name: the variable for `environment_secret` and `environment_variable`, a label for `bearer` and `headers`",
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
/** The path's Agent id, or a 400 naming the rule. */
|
|
37
|
+
function idOf(value) {
|
|
38
|
+
if (!isAgentId(value))
|
|
39
|
+
return fail(400, "An Agent id is up to 200 characters: /-separated segments of letters, digits, '.', '_' and '-'");
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A page cursor: the last Agent id of the page before and the filters it was read with, opaque to
|
|
44
|
+
* the client; a cursor read with other filters is refused.
|
|
45
|
+
*/
|
|
46
|
+
const cursor = {
|
|
47
|
+
encode: (id, filters) => Buffer.from(JSON.stringify({ after: id, filters: canonical(filters) })).toString("base64url"),
|
|
48
|
+
decode: (value, filters) => {
|
|
49
|
+
try {
|
|
50
|
+
const read = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
51
|
+
if (typeof read.after === "string" && read.filters === canonical(filters))
|
|
52
|
+
return read.after;
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
return fail(400, "Invalid Agent cursor", { code: "cursor_invalid" });
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
/** `label=k=v`, repeated: every one must match. */
|
|
59
|
+
function labelsOf(values) {
|
|
60
|
+
const labels = {};
|
|
61
|
+
for (const value of values ?? []) {
|
|
62
|
+
const at = value.indexOf("=");
|
|
63
|
+
if (at <= 0)
|
|
64
|
+
fail(400, `label must be key=value, not ${value}`);
|
|
65
|
+
labels[value.slice(0, at)] = value.slice(at + 1);
|
|
66
|
+
}
|
|
67
|
+
return labels;
|
|
68
|
+
}
|
|
69
|
+
export function agentRoutes(api) {
|
|
70
|
+
tenantRoute(api, READ, {
|
|
71
|
+
method: "get",
|
|
72
|
+
path: "/v1/agents",
|
|
73
|
+
tags: ["Agents"],
|
|
74
|
+
summary: "List Agents",
|
|
75
|
+
description: "Every Agent with all the labels asked for, in `state`, made from `manifest` and `outdated` (moving to another version, track T3) when given, by id. A token caller sees only the Agents its grants reach, and a caller acting for a person only that person's sessions on each. `limit` opts into pages.",
|
|
76
|
+
request: {
|
|
77
|
+
query: pageQuery.extend({
|
|
78
|
+
label: z
|
|
79
|
+
.union([z.string(), z.array(z.string())])
|
|
80
|
+
.optional()
|
|
81
|
+
.meta({ description: "`key=value`; repeat it to require several labels" }),
|
|
82
|
+
state: z.string().optional().meta({ description: "Only Agents in this state (`input_required`, …)" }),
|
|
83
|
+
manifest: z.string().optional().meta({ description: "Only Agents made from this agent manifest" }),
|
|
84
|
+
outdated: z
|
|
85
|
+
.enum(["true", "false"])
|
|
86
|
+
.optional()
|
|
87
|
+
.meta({ description: "`true`: only Agents moving to another version of their manifest (waiting for secrets or a start); `false`: only the others" }),
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
responses: {
|
|
91
|
+
200: json(z.union([ListAgentsResponse, AgentPage]), "The Agents; limit opts into pagination"),
|
|
92
|
+
},
|
|
93
|
+
}, async (c) => {
|
|
94
|
+
const state = c.req.query("state");
|
|
95
|
+
if (state !== undefined && !AGENT_STATES.includes(state))
|
|
96
|
+
fail(400, `state must be one of ${AGENT_STATES.join(", ")}`);
|
|
97
|
+
const outdated = c.req.query("outdated");
|
|
98
|
+
if (outdated !== undefined && outdated !== "true" && outdated !== "false")
|
|
99
|
+
fail(400, "outdated must be true or false");
|
|
100
|
+
const filters = {
|
|
101
|
+
labels: labelsOf(c.req.queries("label")),
|
|
102
|
+
...(state === undefined ? {} : { state }),
|
|
103
|
+
...(c.req.query("manifest") === undefined ? {} : { manifest: c.req.query("manifest") }),
|
|
104
|
+
...(outdated === undefined ? {} : { outdated: outdated === "true" }),
|
|
105
|
+
};
|
|
106
|
+
const limit = c.req.query("limit");
|
|
107
|
+
if (limit === undefined) {
|
|
108
|
+
const { agents } = await listAgents(c.env.tenant, filters, c.get("scope"));
|
|
109
|
+
return jsonResponse(200, { agents });
|
|
110
|
+
}
|
|
111
|
+
if (!/^[1-9]\d*$/.test(limit))
|
|
112
|
+
fail(400, "limit must be a positive integer", { code: "query_invalid" });
|
|
113
|
+
const after = c.req.query("cursor");
|
|
114
|
+
const page = await listAgents(c.env.tenant, filters, c.get("scope"), {
|
|
115
|
+
limit: await pageSize(c.env.tenant, Number(limit)),
|
|
116
|
+
...(after === undefined ? {} : { after: cursor.decode(after, filters) }),
|
|
117
|
+
});
|
|
118
|
+
return jsonResponse(200, { agents: page.agents, nextCursor: page.next === null ? null : cursor.encode(page.next, filters) });
|
|
119
|
+
});
|
|
120
|
+
tenantRoute(api, WRITE, {
|
|
121
|
+
method: "put",
|
|
122
|
+
path: "/v1/agents/{agentId}",
|
|
123
|
+
tags: ["Agents"],
|
|
124
|
+
summary: "Create, find or move an Agent",
|
|
125
|
+
description: "Creates the Agent, its vault and its compute from a saved agent manifest, within the Tenant's limits, or answers the one with this id, so get-or-create is one call. `manifest: \"name\"` records that it tracks the manifest (its latest version, and each version saved later) and `\"name@n\"` that it is pinned; the version it runs is `resolvedVersion`. On an existing one, labels replace, `owner` sets or (with `null`) clears the owner guard, and `manifest` moves it (track T3) with its id, disk, vault and sessions: a harness-only change applies at once (each session's next turn runs it), a runtime change of a pod at its next start (`targetVersion`, `waitingFor: start`), and a version that adds a required secret once a value is set (`waitingFor: secrets`). Its manifest's name, kind, harness and storage are fixed (`409 agent_fixed_field`): fork it to change them. `?dryRun=true` answers the move's plan and changes nothing. Creating an Agent opens no session (D71). Kind `pod` needs sandbox pods (`nylorun local sandbox enable`) and is created at once.",
|
|
126
|
+
request: {
|
|
127
|
+
params: agentId,
|
|
128
|
+
query: z.object({
|
|
129
|
+
dryRun: z.enum(["true", "false"]).optional().meta({ description: "`true`: the plan of moving the existing Agent, with nothing changed" }),
|
|
130
|
+
}),
|
|
131
|
+
body: { required: true, content: { "application/json": { schema: PutAgentRequest } } },
|
|
132
|
+
},
|
|
133
|
+
responses: {
|
|
134
|
+
200: json(z.union([AgentView, PutAgentDryRunResponse]), "The Agent, or with `dryRun` the plan of its move"),
|
|
135
|
+
404: { description: "No agent manifest has this name or version (`manifest_not_found`), or a dry run names no Agent" },
|
|
136
|
+
409: {
|
|
137
|
+
description: "A move would change its manifest's name, its kind, harness or storage (`agent_fixed_field`), the Tenant holds as many Agents as it allows (`limit_exceeded`), kind pod without sandbox pods (`agent_unavailable`), or it is being deleted (`agent_busy`)",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}, async (c) => {
|
|
141
|
+
const id = idOf(c.req.param("agentId"));
|
|
142
|
+
const body = PutAgentRequestSchema.parse(await readJson(c));
|
|
143
|
+
const dryRun = c.req.query("dryRun");
|
|
144
|
+
if (dryRun !== undefined && dryRun !== "true" && dryRun !== "false")
|
|
145
|
+
fail(400, "dryRun must be true or false");
|
|
146
|
+
if (dryRun === "true")
|
|
147
|
+
return jsonResponse(200, await planAgentPut(c.env.tenant, id, body, c.get("scope")));
|
|
148
|
+
return jsonResponse(200, await putAgent(c.env.tenant, id, body, c.get("scope")));
|
|
149
|
+
});
|
|
150
|
+
tenantRoute(api, READ, {
|
|
151
|
+
method: "get",
|
|
152
|
+
path: "/v1/agents/{agentId}",
|
|
153
|
+
tags: ["Agents"],
|
|
154
|
+
summary: "Get an Agent",
|
|
155
|
+
description: "Its manifest version, what its environment runs, labels, owner guard, derived state with the sessions behind it (`attention`), compute, secret slots, disk, and its sessions (acting for a person, only theirs).",
|
|
156
|
+
request: { params: agentId },
|
|
157
|
+
responses: { 200: json(AgentView, "The Agent") },
|
|
158
|
+
}, async (c) => jsonResponse(200, await getAgent(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
|
|
159
|
+
tenantRoute(api, READ, {
|
|
160
|
+
method: "get",
|
|
161
|
+
path: "/v1/agents/{agentId}/events",
|
|
162
|
+
tags: ["Agents"],
|
|
163
|
+
summary: "Read an Agent's events",
|
|
164
|
+
description: "`agent.created`, `.attached`, `.detached`, `.deleted`, a pod's `.running`, `.suspended`, `.relaunched`, `.lost`, `.reset` and `.failed`, `.forked`, a move's `.outdated`, `.version_changed` and `.start_failed` (track T3), and `agent.egress` for every connection its sandbox opens through egress-gate, naming the turns that were running (D67), in order, numbered from 0, at most the Tenant's Runtime limit `environmentEventItems` (default 1000) per call: `from` reads on.",
|
|
165
|
+
request: {
|
|
166
|
+
params: agentId,
|
|
167
|
+
query: z.object({
|
|
168
|
+
from: z.string().optional().meta({ description: "The first `seq` to return" }),
|
|
169
|
+
types: z.string().optional().meta({ description: "Only these event types, comma-separated (`agent.egress`)" }),
|
|
170
|
+
since: z.string().optional().meta({ description: "Only events recorded at or after this ISO time" }),
|
|
171
|
+
until: z.string().optional().meta({ description: "Only events recorded before this ISO time" }),
|
|
172
|
+
}),
|
|
173
|
+
},
|
|
174
|
+
responses: { 200: json(ListAgentEventsResponse, "The events") },
|
|
175
|
+
}, async (c) => {
|
|
176
|
+
const from = c.req.query("from");
|
|
177
|
+
if (from !== undefined && !/^\d+$/.test(from))
|
|
178
|
+
fail(400, "from must be a seq");
|
|
179
|
+
const types = c.req.query("types")?.split(",").map((type) => type.trim()).filter(Boolean);
|
|
180
|
+
const unknown = types?.find((type) => !AGENT_EVENT_TYPES.includes(type));
|
|
181
|
+
if (unknown !== undefined)
|
|
182
|
+
fail(400, `types: ${unknown} is not an Agent event type`);
|
|
183
|
+
const time = (name) => {
|
|
184
|
+
const value = c.req.query(name);
|
|
185
|
+
if (value === undefined)
|
|
186
|
+
return undefined;
|
|
187
|
+
const at = Date.parse(value);
|
|
188
|
+
if (!Number.isFinite(at))
|
|
189
|
+
fail(400, `${name} must be an ISO time`);
|
|
190
|
+
return new Date(at).toISOString();
|
|
191
|
+
};
|
|
192
|
+
const since = time("since");
|
|
193
|
+
const until = time("until");
|
|
194
|
+
return jsonResponse(200, await agentEventsOf(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"), {
|
|
195
|
+
...(from === undefined ? {} : { fromSeq: Number(from) }),
|
|
196
|
+
...(types === undefined || types.length === 0 ? {} : { types }),
|
|
197
|
+
...(since === undefined ? {} : { since }),
|
|
198
|
+
...(until === undefined ? {} : { until }),
|
|
199
|
+
}));
|
|
200
|
+
});
|
|
201
|
+
tenantRoute(api, WRITE, {
|
|
202
|
+
method: "delete",
|
|
203
|
+
path: "/v1/agents/{agentId}",
|
|
204
|
+
tags: ["Agents"],
|
|
205
|
+
summary: "Delete an Agent",
|
|
206
|
+
description: "Deletes the Agent, its vault with every secret, and its disk. Its sessions keep its id; their next turn is refused (`agent_unavailable`) until an Agent with this id exists again.",
|
|
207
|
+
request: { params: agentId },
|
|
208
|
+
responses: {
|
|
209
|
+
200: json(DeleteAgentResponse, "Deleted, or there was none"),
|
|
210
|
+
409: { description: "One of its sessions has a turn running (`agent_busy`)" },
|
|
211
|
+
},
|
|
212
|
+
}, async (c) => jsonResponse(200, await deleteAgent(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
|
|
213
|
+
for (const [action, summary, description, run] of [
|
|
214
|
+
[
|
|
215
|
+
"stop",
|
|
216
|
+
"Stop a pod Agent",
|
|
217
|
+
"Suspends the pod: its volume is kept, and the next turn of one of its sessions starts it again. Refused during a turn (`agent_busy`).",
|
|
218
|
+
stopAgent,
|
|
219
|
+
],
|
|
220
|
+
[
|
|
221
|
+
"reset",
|
|
222
|
+
"Reset a pod Agent",
|
|
223
|
+
"A new pod on a new, empty volume with the recipe applied; the old pod and volume are deleted. The way out of `agent_lost`. Refused during a turn (`agent_busy`).",
|
|
224
|
+
resetAgent,
|
|
225
|
+
],
|
|
226
|
+
])
|
|
227
|
+
tenantRoute(api, WRITE, {
|
|
228
|
+
method: "post",
|
|
229
|
+
path: `/v1/agents/{agentId}/${action}`,
|
|
230
|
+
tags: ["Agents"],
|
|
231
|
+
summary,
|
|
232
|
+
description,
|
|
233
|
+
request: { params: agentId },
|
|
234
|
+
responses: {
|
|
235
|
+
200: json(AgentView, "The Agent"),
|
|
236
|
+
400: { description: "The Agent is not of kind pod" },
|
|
237
|
+
409: {
|
|
238
|
+
description: "A turn holds it (`agent_busy`), or no sandbox pods (`agent_unavailable`)",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
}, async (c) => jsonResponse(200, await run(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
|
|
242
|
+
tenantRoute(api, WRITE, {
|
|
243
|
+
method: "post",
|
|
244
|
+
path: "/v1/agents/{agentId}/restart",
|
|
245
|
+
tags: ["Agents"],
|
|
246
|
+
summary: "Restart an Agent on its pending version",
|
|
247
|
+
description: "Applies a version the Agent waits to apply (track T3): a running pod stops, so the next turn starts it on the version, on the same volume; a stopped or failed one is asked to run at once. `retry: true` first moves it to the version that failed to start (`failedVersion`) again. Kinds `virtual` and `none` apply a version at once, so there is nothing to wait for. Refused during a turn (`agent_busy`). The body is optional.",
|
|
248
|
+
request: {
|
|
249
|
+
params: agentId,
|
|
250
|
+
body: { required: false, content: { "application/json": { schema: RestartAgentRequest } } },
|
|
251
|
+
},
|
|
252
|
+
responses: {
|
|
253
|
+
200: json(AgentView, "The Agent"),
|
|
254
|
+
409: { description: "A turn holds it (`agent_busy`), its pod was lost (`agent_lost`), or no sandbox pods (`agent_unavailable`)" },
|
|
255
|
+
},
|
|
256
|
+
}, async (c) => {
|
|
257
|
+
const text = await readText(c.req.raw, await bodyLimit(c.env.tenant));
|
|
258
|
+
let raw = {};
|
|
259
|
+
if (text.trim() !== "")
|
|
260
|
+
try {
|
|
261
|
+
raw = JSON.parse(text);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
fail(400, "Invalid JSON");
|
|
265
|
+
}
|
|
266
|
+
const body = RestartAgentRequestSchema.parse(raw);
|
|
267
|
+
return jsonResponse(200, await restartAgent(c.env.tenant, idOf(c.req.param("agentId")), { ...(body.retry ? { retry: true } : {}) }, c.get("scope")));
|
|
268
|
+
});
|
|
269
|
+
tenantRoute(api, WRITE, {
|
|
270
|
+
method: "post",
|
|
271
|
+
path: "/v1/agents/{agentId}/snapshot",
|
|
272
|
+
tags: ["Agents"],
|
|
273
|
+
summary: "Snapshot an Agent's disk",
|
|
274
|
+
description: "Between turns, a new version of the Agent's disk (`/workspace`, and a pod's `/harness` but the engine's own files): files with their modes and modification times, links and empty directories, as a version of the folder artifact the Agent owns. Unchanged files are stored once; an unchanged disk makes no version (`unchanged`). A stopped pod is started first. Refused during a turn or another copy (`agent_busy`).",
|
|
275
|
+
request: { params: agentId },
|
|
276
|
+
responses: {
|
|
277
|
+
201: json(AgentSnapshot, "The version"),
|
|
278
|
+
400: { description: "The Agent is of kind none" },
|
|
279
|
+
409: { description: "A turn or another copy holds the disk (`agent_busy`), or the pod could not start (`agent_unavailable`)" },
|
|
280
|
+
413: { description: "Past the entries, bytes or Tenant total a version may hold (`limit_exceeded`)" },
|
|
281
|
+
},
|
|
282
|
+
}, async (c) => jsonResponse(201, await snapshotEnvironment(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
|
|
283
|
+
tenantRoute(api, WRITE, {
|
|
284
|
+
method: "post",
|
|
285
|
+
path: "/v1/agents/{agentId}/restore",
|
|
286
|
+
tags: ["Agents"],
|
|
287
|
+
summary: "Restore a version of an Agent's disk",
|
|
288
|
+
description: "Puts disk version `version` (or `latest`) back. A virtual Agent's workspace is rewritten at once (`done`); a pod gets a new volume seeded from the version before any turn (`pending`, with `disk.operation` in the view until it ends). The Agent keeps its id, vault and sessions. Refused during a turn or another copy (`agent_busy`).",
|
|
289
|
+
request: {
|
|
290
|
+
params: agentId,
|
|
291
|
+
body: { required: true, content: { "application/json": { schema: RestoreAgentRequest } } },
|
|
292
|
+
},
|
|
293
|
+
responses: {
|
|
294
|
+
200: json(AgentRestore, "Restored, or being restored"),
|
|
295
|
+
404: { description: "No such Agent, or no such version" },
|
|
296
|
+
409: { description: "A turn or another copy holds the disk (`agent_busy`)" },
|
|
297
|
+
},
|
|
298
|
+
}, async (c) => {
|
|
299
|
+
const body = RestoreAgentRequestSchema.parse(await readJson(c));
|
|
300
|
+
return jsonResponse(200, await restoreEnvironment(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"), body.version));
|
|
301
|
+
});
|
|
302
|
+
tenantRoute(api, WRITE, {
|
|
303
|
+
method: "post",
|
|
304
|
+
path: "/v1/agents/{agentId}/fork",
|
|
305
|
+
tags: ["Agents"],
|
|
306
|
+
summary: "Fork an Agent",
|
|
307
|
+
description: "Copies the Agent into a new one (Q15), which is how an Agent changes what is fixed for its life: a new Agent `id`, made from `manifest` (default the source's manifest name, tracking, at its latest version), its disk started from a version of this one's (`disk`: `latest`, the default, takes a snapshot first; a version number; or `empty`) and its secrets copied inside the Runtime (`secrets`: `copy`, the default, copies those the new manifest declares; `none`). On a fork onto other setup the new manifest's setup runs on the copied disk before its first turn. This Agent keeps what it runs, its disk and its sessions. `dryRun` answers the plan and makes nothing. To move an Agent to another version of the same manifest, with the same kind, harness and storage, `PUT` it instead (track T3).",
|
|
308
|
+
request: {
|
|
309
|
+
params: agentId,
|
|
310
|
+
body: { required: true, content: { "application/json": { schema: ForkAgentRequest } } },
|
|
311
|
+
},
|
|
312
|
+
responses: {
|
|
313
|
+
200: json(ForkAgentResponse, "The plan (`dryRun`)"),
|
|
314
|
+
201: json(ForkAgentResponse, "The new Agent, and the plan it ran"),
|
|
315
|
+
400: { description: "The manifest does not resolve against the Tenant's limits" },
|
|
316
|
+
404: { description: "No such Agent, manifest version (`manifest_not_found`) or disk version" },
|
|
317
|
+
409: { description: "The id exists, or a turn or another copy holds the source's disk (`agent_busy`)" },
|
|
318
|
+
},
|
|
319
|
+
}, async (c) => {
|
|
320
|
+
const body = ForkAgentRequestSchema.parse(await readJson(c));
|
|
321
|
+
const answer = await forkAgent(c.env.tenant, idOf(c.req.param("agentId")), body, c.get("scope"));
|
|
322
|
+
return jsonResponse("dryRun" in answer ? 200 : 201, answer);
|
|
323
|
+
});
|
|
324
|
+
tenantRoute(api, READ, {
|
|
325
|
+
method: "get",
|
|
326
|
+
path: "/v1/agents/{agentId}/versions",
|
|
327
|
+
tags: ["Agents"],
|
|
328
|
+
summary: "List an Agent's disk versions",
|
|
329
|
+
description: "Newest first. Read a version's tree, files, diff and zip through `/v1/artifacts/{artifactId}/versions/{version}/…`.",
|
|
330
|
+
request: { params: agentId },
|
|
331
|
+
responses: { 200: json(AgentDiskVersions, "The versions") },
|
|
332
|
+
}, async (c) => jsonResponse(200, await environmentVersions(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"))));
|
|
333
|
+
tenantRoute(api, WRITE, {
|
|
334
|
+
method: "delete",
|
|
335
|
+
path: "/v1/agents/{agentId}/versions/{version}",
|
|
336
|
+
tags: ["Agents"],
|
|
337
|
+
summary: "Delete an Agent's disk version",
|
|
338
|
+
description: "Deletes the version, and the stored files no other version names. Refused while a seed reads it.",
|
|
339
|
+
request: { params: agentId.extend({ version: z.string() }) },
|
|
340
|
+
responses: {
|
|
341
|
+
200: json(z.object({ agentId: z.string(), version: z.number(), deleted: z.boolean() }), "Deleted, or there was none"),
|
|
342
|
+
409: { description: "A new volume is being seeded from it (`agent_busy`)" },
|
|
343
|
+
},
|
|
344
|
+
}, async (c) => {
|
|
345
|
+
const version = c.req.param("version") ?? "";
|
|
346
|
+
if (!/^[1-9]\d{0,8}$/.test(version))
|
|
347
|
+
fail(400, "version must be a version number");
|
|
348
|
+
return jsonResponse(200, await deleteEnvironmentVersion(c.env.tenant, idOf(c.req.param("agentId")), c.get("scope"), Number(version)));
|
|
349
|
+
});
|
|
350
|
+
tenantRoute(api, READ, {
|
|
351
|
+
method: "get",
|
|
352
|
+
path: "/v1/agents/{agentId}/secrets",
|
|
353
|
+
tags: ["Agents"],
|
|
354
|
+
summary: "List an Agent's secrets",
|
|
355
|
+
description: "Each secret's name, type and binding (its URL, or its hosts and header). Never a value.",
|
|
356
|
+
request: { params: agentId },
|
|
357
|
+
responses: { 200: json(ListAgentSecretsResponse, "The secrets") },
|
|
358
|
+
}, async (c) => {
|
|
359
|
+
const id = idOf(c.req.param("agentId"));
|
|
360
|
+
assertReaches(c.get("scope"), id);
|
|
361
|
+
return jsonResponse(200, { secrets: await c.env.tenant.vault.listEnvironmentSecrets(id) });
|
|
362
|
+
});
|
|
363
|
+
tenantRoute(api, READ, {
|
|
364
|
+
method: "get",
|
|
365
|
+
path: "/v1/agents/{agentId}/secrets/{name}",
|
|
366
|
+
tags: ["Agents"],
|
|
367
|
+
summary: "Get an Agent's secret",
|
|
368
|
+
description: "Its name, type and binding. Never its value.",
|
|
369
|
+
request: { params: secretPath },
|
|
370
|
+
responses: { 200: json(AgentSecretView, "The secret") },
|
|
371
|
+
}, async (c) => {
|
|
372
|
+
const id = idOf(c.req.param("agentId"));
|
|
373
|
+
assertReaches(c.get("scope"), id);
|
|
374
|
+
return jsonResponse(200, await c.env.tenant.vault.getEnvironmentSecret(id, c.req.param("name") ?? ""));
|
|
375
|
+
});
|
|
376
|
+
tenantRoute(api, WRITE, {
|
|
377
|
+
method: "put",
|
|
378
|
+
path: "/v1/agents/{agentId}/secrets/{name}",
|
|
379
|
+
tags: ["Agents"],
|
|
380
|
+
summary: "Write an Agent's secret",
|
|
381
|
+
description: "Creates or replaces one credential of the Agent's vault: an `environment_secret` (the variable `name` reads the sentinel `nylorun_managed` in the sandbox, and egress-gate sets the value as a header on requests to `allowedHosts`), an `environment_variable` (a plain variable), or a `bearer` token or `headers` map for the MCP servers and HTTP tools at `url`. The same name and type re-seals the value and binding; another type replaces it. A rotated value is used from the next request. A second credential for a host or a URL is `409 credential_conflict`. When the Agent's manifest declares secret slots (D64; while it moves to another version, that version's), send only the value (`{ value }`, or `{ headers }` for a `headers` slot): the type and binding come from the slot, and a name it does not declare is `409 secret_not_in_manifest`.",
|
|
382
|
+
request: {
|
|
383
|
+
params: secretPath,
|
|
384
|
+
body: {
|
|
385
|
+
required: true,
|
|
386
|
+
content: { "application/json": { schema: z.union([PutAgentSecretRequest, PutAgentSecretValueRequest]) } },
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
responses: {
|
|
390
|
+
200: json(AgentSecretView, "The secret, without its value"),
|
|
391
|
+
409: { description: "Another credential is bound to one of its hosts or to its URL (`credential_conflict`)" },
|
|
392
|
+
},
|
|
393
|
+
}, async (c) => {
|
|
394
|
+
const id = idOf(c.req.param("agentId"));
|
|
395
|
+
const scope = c.get("scope");
|
|
396
|
+
assertReaches(scope, id);
|
|
397
|
+
const name = c.req.param("name") ?? "";
|
|
398
|
+
const body = await secretRequestFor(c.env.tenant, id, name, await readJson(c));
|
|
399
|
+
const secret = await c.env.tenant.keys.putEnvironmentSecret(id, name, body, actorOf(scope));
|
|
400
|
+
// A move that waited for this secret goes on (track T3).
|
|
401
|
+
await secretWritten(c.env.tenant, id);
|
|
402
|
+
return jsonResponse(200, secret);
|
|
403
|
+
});
|
|
404
|
+
tenantRoute(api, WRITE, {
|
|
405
|
+
method: "delete",
|
|
406
|
+
path: "/v1/agents/{agentId}/secrets/{name}",
|
|
407
|
+
tags: ["Agents"],
|
|
408
|
+
summary: "Delete an Agent's secret",
|
|
409
|
+
description: "The next request that would have used it gets none.",
|
|
410
|
+
request: { params: secretPath },
|
|
411
|
+
responses: { 200: json(DeletedResponse, "Deleted") },
|
|
412
|
+
}, async (c) => {
|
|
413
|
+
const id = idOf(c.req.param("agentId"));
|
|
414
|
+
const scope = c.get("scope");
|
|
415
|
+
assertReaches(scope, id);
|
|
416
|
+
return jsonResponse(200, await c.env.tenant.vault.deleteEnvironmentSecret(id, c.req.param("name") ?? "", actorOf(scope)));
|
|
417
|
+
});
|
|
418
|
+
}
|
|
@@ -492,11 +492,11 @@ export function artifactRoutes(api) {
|
|
|
492
492
|
path: "/v1/artifacts/{artifactId}",
|
|
493
493
|
tags: ["Artifacts"],
|
|
494
494
|
summary: "Delete an artifact",
|
|
495
|
-
description: "Deletes every version and its bytes; its links stop working. A session's artifact gets `artifact.deleted` in its history. An
|
|
495
|
+
description: "Deletes every version and its bytes; its links stop working. A session's artifact gets `artifact.deleted` in its history. An Agent's disk versions are deleted through `/v1/agents/{agentId}/versions/{version}` (`409` here).",
|
|
496
496
|
request: { params: artifactId },
|
|
497
497
|
responses: {
|
|
498
498
|
200: json(DeleteArtifactResponse, "Deleted"),
|
|
499
|
-
409: { description: "The artifact holds an
|
|
499
|
+
409: { description: "The artifact holds an Agent's disk versions" },
|
|
500
500
|
},
|
|
501
501
|
}, async (c) => jsonResponse(200, await deleteArtifact(c.env.tenant, c.req.param("artifactId"), accessOf(c.get("scope")))));
|
|
502
502
|
tenantRoute(api, LINK, {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `POST /v1/tenant/credential-coverage` (the Management API): what a session
|
|
3
|
-
*
|
|
4
|
-
* choice (`VaultService.coverage`); no secret is read. An
|
|
5
|
-
* written on the Runtime API (`/v1/
|
|
2
|
+
* `POST /v1/tenant/credential-coverage` (the Management API): what a session on an Agent would
|
|
3
|
+
* send for each URL its manifest version names, from the Agent's vault. A dry run of each call's
|
|
4
|
+
* credential choice (`VaultService.coverage`); no secret is read. An Agent's secrets themselves
|
|
5
|
+
* are written on the Runtime API (`/v1/agents/{id}/secrets`, `routes/agents.ts`).
|
|
6
6
|
*/
|
|
7
7
|
import type { OpenAPIHono } from "@hono/zod-openapi";
|
|
8
8
|
import type { TenantEnv } from "../app.js";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CredentialCoverageRequestSchema } from "@nylorun/core/contracts";
|
|
3
|
+
import { loopManifestOf } from "@nylorun/core/define";
|
|
4
|
+
import { liveEnvironment } from "../../../tenant/agents.js";
|
|
3
5
|
import { CredentialCoverage, CredentialCoverageRequest, Rejected } from "../../components.js";
|
|
4
6
|
import { readJson } from "../body.js";
|
|
5
7
|
import { tenantRoute } from "../define.js";
|
|
@@ -15,20 +17,24 @@ export function coverageRoutes(api) {
|
|
|
15
17
|
tenantRoute(api, MANAGEMENT, {
|
|
16
18
|
method: "post",
|
|
17
19
|
path: "/v1/tenant/credential-coverage",
|
|
18
|
-
tags: ["
|
|
19
|
-
summary: "Check an
|
|
20
|
-
description: "For each remote MCP server and each HTTP tool with a `credential` that
|
|
20
|
+
tags: ["Agents"],
|
|
21
|
+
summary: "Check an Agent's credentials",
|
|
22
|
+
description: "For each remote MCP server and each HTTP tool with a `credential` that Agent `agentId`'s manifest version declares (its own, its agents used as tools' and a flow's), the credential its sessions would send: the Agent's secret bound to the URL (`covered`), or none (`missing`). No secret is read and no server is called.",
|
|
21
23
|
request: {
|
|
22
24
|
body: { required: true, content: { "application/json": { schema: CredentialCoverageRequest } } },
|
|
23
25
|
},
|
|
24
26
|
responses: {
|
|
25
27
|
200: json(CredentialCoverage, "One entry per declared URL"),
|
|
26
|
-
404: json(Rejected, "No such
|
|
28
|
+
404: json(Rejected, "No such Agent"),
|
|
27
29
|
},
|
|
28
30
|
}, async (c) => {
|
|
29
31
|
const request = CredentialCoverageRequestSchema.parse(await readJson(c));
|
|
30
32
|
const tenant = c.env.tenant;
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
+
const manifest = await tenant.store.tx(async (t) => {
|
|
34
|
+
const agent = (await liveEnvironment(t, request.agentId)) ?? fail(404, "Agent not found");
|
|
35
|
+
const version = (await t.agentManifest(agent.manifestName, agent.manifestVersion)) ?? fail(404, "Agent not found");
|
|
36
|
+
return loopManifestOf(version.body);
|
|
37
|
+
});
|
|
38
|
+
return jsonResponse(200, await tenant.vault.coverage(manifest, request));
|
|
33
39
|
});
|
|
34
40
|
}
|
|
@@ -90,7 +90,7 @@ export function fileRoutes(api) {
|
|
|
90
90
|
path: "/v1/tenant/skills",
|
|
91
91
|
tags: ["Tenant"],
|
|
92
92
|
summary: "Set the Tenant's skill limit",
|
|
93
|
-
description: "The most bytes one skill's files may hold together, for every agent the Tenant stores
|
|
93
|
+
description: "The most bytes one skill's files may hold together, for every agent manifest the Tenant stores, whose runtime section may set a lower one (`limits.skills`). Unset, the limit takes the default (2 GiB). Manifests already saved keep their skills; a session opens only while its Agent's skills fit.",
|
|
94
94
|
request: {
|
|
95
95
|
body: { required: true, content: { "application/json": { schema: PutTenantSkillsRequest } } },
|
|
96
96
|
},
|
|
@@ -16,13 +16,13 @@ export function mcpRoutes(api) {
|
|
|
16
16
|
path: "/v1/tenant/mcp/preview",
|
|
17
17
|
tags: ["MCP servers"],
|
|
18
18
|
summary: "Preview an MCP server's tools",
|
|
19
|
-
description: "Connects to the remote MCP server at `url` with the credential for that URL in the vault of `
|
|
19
|
+
description: "Connects to the remote MCP server at `url` with the credential for that URL in the vault of the Agent `agentId`, if given (its headers and `via`; never an identity header), under the Host's address policy, lists its tools within 15 s and closes the connection. It never calls a tool. Each tool comes with the name the model would call it (`modelName`, made with `name`), its annotations and its input schema's size. A server that answers `401` is `authRequired`, with its RFC 9728 protected-resource metadata when it publishes any: it needs a person's sign-in, so reach it through a gateway (`via`), or with a key. No credential value is in the answer.",
|
|
20
20
|
request: {
|
|
21
21
|
body: { required: true, content: { "application/json": { schema: McpPreviewRequest } } },
|
|
22
22
|
},
|
|
23
23
|
responses: {
|
|
24
24
|
200: json(McpPreview, "The server's tools, or that it needs a credential (`authRequired`)"),
|
|
25
|
-
409: json(Rejected, "The
|
|
25
|
+
409: json(Rejected, "The Agent's credential for the URL cannot be used"),
|
|
26
26
|
502: json(Rejected, "The server could not be listed (`mcp_preview_failed`): `details.failure` is the code a tool call would have failed with, such as `mcp.unreachable` for an address the Host refuses"),
|
|
27
27
|
},
|
|
28
28
|
}, async (c) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `GET /v1/me` (Host feature `trusted-issuers`): who the Runtime takes the caller to be, for any
|
|
3
3
|
* credential that acts on the Tenant. It shows what a trusted issuer's token renders to (its
|
|
4
|
-
* subject, scopes
|
|
4
|
+
* subject, scopes and Agent grants), so an operator can check an identity file, and
|
|
5
5
|
* Studio can check a forwarded token for the `studio` scope.
|
|
6
6
|
*/
|
|
7
7
|
import type { OpenAPIHono } from "@hono/zod-openapi";
|
|
@@ -19,7 +19,6 @@ export function describeCaller(scope) {
|
|
|
19
19
|
subject: scope.subject,
|
|
20
20
|
scopes: ordered(scope.scopes),
|
|
21
21
|
agents: scope.agents === "*" ? "*" : [...scope.agents].sort(),
|
|
22
|
-
environments: [...(scope.environments ?? [])],
|
|
23
22
|
via: `issuer:${scope.issuer}`,
|
|
24
23
|
};
|
|
25
24
|
case "management":
|
|
@@ -34,7 +33,7 @@ export function meRoutes(api) {
|
|
|
34
33
|
path: "/v1/me",
|
|
35
34
|
tags: ["Access"],
|
|
36
35
|
summary: "Get who the caller is",
|
|
37
|
-
description: "The subject, scopes
|
|
36
|
+
description: "The subject, scopes and Agent grants of the credential that sends it: an " +
|
|
38
37
|
"application key (alone or acting for a subject), a management key " +
|
|
39
38
|
"(`via: management:<principalId>`, no scopes, no agents), or a trusted issuer's token " +
|
|
40
39
|
"(`via: issuer:<name>`).",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Agent's memories (track T5, D72), shaped like Anthropic's memory stores: list, read, write
|
|
3
|
+
* whole, delete, and the versions every change leaves. A memory's path is relative to
|
|
4
|
+
* `/memories`, with `/` percent-encoded (`conventions%2Frelease.md`), as an Agent id is. The
|
|
5
|
+
* caller reaches an Agent's memories as it reaches the Agent; writes are `agents:write`.
|
|
6
|
+
*/
|
|
7
|
+
import type { OpenAPIHono } from "@hono/zod-openapi";
|
|
8
|
+
import type { TenantEnv } from "../app.js";
|
|
9
|
+
export declare function memoryRoutes(api: OpenAPIHono<TenantEnv>): void;
|