@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.
- package/CHANGELOG.md +67 -0
- package/README.md +1 -1
- 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 +5854 -2718
- 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/define.d.ts +4 -0
- package/dist/api/http/define.js +20 -9
- 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 +31 -12
- package/dist/api/http/routes/sessions.js +21 -50
- package/dist/api/http/routes/tenant.js +13 -10
- package/dist/api/http/routes/triggers.d.ts +10 -0
- package/dist/api/http/routes/triggers.js +183 -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 +2 -2
- package/dist/gates/meter.js +22 -5
- package/dist/gates/policy.d.ts +8 -3
- package/dist/gates/policy.js +7 -2
- package/dist/gates/tenant-vaults.d.ts +1 -1
- 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/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/http.d.ts +2 -2
- package/dist/host/http.js +5 -2
- package/dist/host/main.js +1 -0
- 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 +313 -133
- package/dist/mcp/connect.d.ts +2 -2
- package/dist/mcp/connect.js +3 -3
- 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/model/pi-model.js +7 -0
- package/dist/openapi.json +13120 -9034
- 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/0037_model_usage_cost_split.sql +4 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
- package/dist/store/postgres/reads.js +44 -87
- 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 +1408 -123
- package/dist/store/postgres/schema.js +194 -38
- 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/types.d.ts +5 -0
- 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
package/dist/tenant/context.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
|
|
1
|
+
import { DEFAULT_HARNESS, agentGranted } from "@nylorun/core/contracts";
|
|
2
2
|
import { fail } from "./http.js";
|
|
3
|
+
/** Whether `access` reaches sessions on Agent `agentId`. */
|
|
4
|
+
export function reachesAgent(access, agentId) {
|
|
5
|
+
return access?.agents === undefined || agentGranted(access.agents, agentId);
|
|
6
|
+
}
|
|
3
7
|
/**
|
|
4
8
|
* `access`, when set, limits the request to the owner's sessions of the allowed agents:
|
|
5
9
|
* anything else is the same 404 as a missing session, so a subject cannot learn which session
|
|
@@ -9,7 +13,7 @@ function owned(session, access) {
|
|
|
9
13
|
if (!session ||
|
|
10
14
|
(access !== undefined &&
|
|
11
15
|
(session.ownerUserId !== access.owner ||
|
|
12
|
-
(access
|
|
16
|
+
!reachesAgent(access, session.agentId))))
|
|
13
17
|
return fail(404, "Session not found");
|
|
14
18
|
return session;
|
|
15
19
|
}
|
package/dist/tenant/effects.js
CHANGED
|
@@ -161,23 +161,22 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
161
161
|
if (exists)
|
|
162
162
|
return;
|
|
163
163
|
const definition = leafDefinition(workflow, body, request);
|
|
164
|
-
// The
|
|
164
|
+
// The flow's Agent (Q6) reaches every agent in the tree: its sandbox and its vault. Each leaf
|
|
165
|
+
// session runs on it, with the flow's manifest version, and its leaf agent's loop manifest.
|
|
165
166
|
const sandbox = workflow.sandbox;
|
|
166
167
|
const created = {
|
|
167
168
|
id: agentSessionId,
|
|
168
|
-
agentId:
|
|
169
|
+
agentId: workflow.agentId,
|
|
169
170
|
ownerUserId: workflow.ownerUserId,
|
|
170
171
|
manifest: definition.manifest,
|
|
171
172
|
manifestHash: definition.manifestHash,
|
|
172
|
-
|
|
173
|
-
implementationVersion: definition.implementationVersion,
|
|
173
|
+
...(workflow.definition === undefined ? {} : { definition: workflow.definition }),
|
|
174
174
|
status: "idle",
|
|
175
175
|
activeTurnId: null,
|
|
176
176
|
creation: {
|
|
177
177
|
requestId: `flow-put-${request.effectId}`,
|
|
178
|
-
|
|
178
|
+
agent: workflow.agentId,
|
|
179
179
|
ownerUserId: workflow.ownerUserId,
|
|
180
|
-
...(workflow.environmentId === undefined ? {} : { environment: workflow.environmentId }),
|
|
181
180
|
},
|
|
182
181
|
flowRootId: workflow.flowRootId ?? workflow.id,
|
|
183
182
|
...(workflow.environmentId === undefined
|
|
@@ -262,11 +261,7 @@ function leafDefinition(workflow, body, request) {
|
|
|
262
261
|
const flow = flowDelegateManifest(turnManifestOf(workflow), body.agentId);
|
|
263
262
|
if (!flow)
|
|
264
263
|
return fail(404, `Agent '${body.agentId}' is not a flow agent used as a tool`);
|
|
265
|
-
return {
|
|
266
|
-
manifest: flow,
|
|
267
|
-
manifestHash: hashManifest(flow),
|
|
268
|
-
implementationVersion: workflow.implementationVersion,
|
|
269
|
-
};
|
|
264
|
+
return { manifest: flow, manifestHash: hashManifest(flow) };
|
|
270
265
|
}
|
|
271
266
|
const leaf = isWorkflowManifest(workflow.manifest)
|
|
272
267
|
? embeddedAgent(workflow.manifest, body.flow ?? [], body.agentId)
|
|
@@ -274,9 +269,5 @@ function leafDefinition(workflow, body, request) {
|
|
|
274
269
|
if (!leaf || isWorkflowManifest(leaf))
|
|
275
270
|
return fail(404, `Agent '${body.agentId}' is not embedded in workflow '${workflow.manifest.id}'`);
|
|
276
271
|
const manifest = leaf;
|
|
277
|
-
return {
|
|
278
|
-
manifest,
|
|
279
|
-
manifestHash: hashManifest(manifest),
|
|
280
|
-
implementationVersion: workflow.implementationVersion,
|
|
281
|
-
};
|
|
272
|
+
return { manifest, manifestHash: hashManifest(manifest) };
|
|
282
273
|
}
|
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
* # Optional, with their defaults:
|
|
16
16
|
* subject: "{sub}" # scalar claims only
|
|
17
17
|
* scopes: { claim: scope } # or { fixed: [sessions:own, agents:read] }
|
|
18
|
-
* allowedScopes: [agents:read, sessions:own,
|
|
19
|
-
* agents: [support]
|
|
20
|
-
* environments: ["{org_id}/*"] # absent reaches no Environment
|
|
18
|
+
* allowedScopes: [agents:read, sessions:own, agents:write] # add studio to grant it
|
|
19
|
+
* agents: ["{org_id}/*", support] # Agent grants; absent reaches no Agent
|
|
21
20
|
* jwksFetch: # how the JWKS is fetched and cached
|
|
22
21
|
* timeoutMs: 5000 # one fetch
|
|
23
22
|
* maxBytes: 262144 # the largest JWKS read
|
|
@@ -74,10 +73,11 @@ export interface TrustedIssuerConfig {
|
|
|
74
73
|
};
|
|
75
74
|
/** The scopes its tokens may carry; others are dropped. */
|
|
76
75
|
readonly allowedScopes: readonly IssuerScope[];
|
|
77
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Agent grant templates (protocol 16; `agents` and `environments` before), such as `{org_id}/*`,
|
|
78
|
+
* `support` or `*`: what its tokens reach. Absent reaches no Agent.
|
|
79
|
+
*/
|
|
78
80
|
readonly agents?: readonly string[];
|
|
79
|
-
/** Environment grant templates, such as `{org_id}/*`; absent reaches no Environment. */
|
|
80
|
-
readonly environments?: readonly string[];
|
|
81
81
|
/** How its JWKS is fetched; each setting left out is its default (`JWKS_FETCH_DEFAULTS`). */
|
|
82
82
|
readonly jwksFetch?: Partial<JwksFetchSettings>;
|
|
83
83
|
}
|
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
* # Optional, with their defaults:
|
|
16
16
|
* subject: "{sub}" # scalar claims only
|
|
17
17
|
* scopes: { claim: scope } # or { fixed: [sessions:own, agents:read] }
|
|
18
|
-
* allowedScopes: [agents:read, sessions:own,
|
|
19
|
-
* agents: [support]
|
|
20
|
-
* environments: ["{org_id}/*"] # absent reaches no Environment
|
|
18
|
+
* allowedScopes: [agents:read, sessions:own, agents:write] # add studio to grant it
|
|
19
|
+
* agents: ["{org_id}/*", support] # Agent grants; absent reaches no Agent
|
|
21
20
|
* jwksFetch: # how the JWKS is fetched and cached
|
|
22
21
|
* timeoutMs: 5000 # one fetch
|
|
23
22
|
* maxBytes: 262144 # the largest JWKS read
|
|
@@ -29,7 +28,7 @@
|
|
|
29
28
|
import { createPublicKey } from "node:crypto";
|
|
30
29
|
import { parse as parseYaml } from "yaml";
|
|
31
30
|
import { z } from "zod";
|
|
32
|
-
import { ISSUER_SCOPES,
|
|
31
|
+
import { ISSUER_SCOPES, isAgentGrant, TIMER_MAX_MS, TOKEN_SCOPES, } from "@nylorun/core/contracts";
|
|
33
32
|
/** The JWT algorithms an issuer may sign with. */
|
|
34
33
|
export const ISSUER_ALGORITHMS = ["RS256", "ES256", "EdDSA"];
|
|
35
34
|
/** An entry's defaults: the `sub` claim, the RFC 9068 `scope` claim, every token scope but `studio`. */
|
|
@@ -135,11 +134,10 @@ const IssuerEntrySchema = z
|
|
|
135
134
|
], { error: "must be { claim: <name> } or { fixed: [<scope>, ...] }" })
|
|
136
135
|
.default(DEFAULT_SCOPES),
|
|
137
136
|
allowedScopes: z.array(IssuerScopeSchema).min(1).default([...DEFAULT_ALLOWED_SCOPES]),
|
|
138
|
-
agents: z
|
|
139
|
-
|
|
140
|
-
.array(template("an Environment grant").refine(
|
|
137
|
+
agents: z
|
|
138
|
+
.array(template("an Agent grant").refine(
|
|
141
139
|
// A rendered grant must be a grant: checked here with a sample value per claim.
|
|
142
|
-
(value) =>
|
|
140
|
+
(value) => isAgentGrant(renderTemplate(value, () => "x")), "must render to *, an Agent id or a prefix ending in /*, as {org_id}/*"))
|
|
143
141
|
.optional(),
|
|
144
142
|
jwksFetch: z
|
|
145
143
|
.object({
|
|
@@ -208,7 +206,12 @@ export function parseIdentityFile(text, source = "identity file", options = {})
|
|
|
208
206
|
entry?.scopes,
|
|
209
207
|
]);
|
|
210
208
|
if (scopesNamed.includes('"sandboxes:write"'))
|
|
211
|
-
return fail(`${where}: sandboxes:write: renamed environments:write in protocol 11`);
|
|
209
|
+
return fail(`${where}: sandboxes:write: renamed agents:write (environments:write in protocol 11)`);
|
|
210
|
+
// Protocol 16 renamed these: refused, never read as no grants.
|
|
211
|
+
if (entry && typeof entry === "object" && "environments" in entry)
|
|
212
|
+
return fail(`${where}: environments: Environments are Agents in protocol 16: move the grants to agents`);
|
|
213
|
+
if (scopesNamed.includes('"environments:write"'))
|
|
214
|
+
return fail(`${where}: environments:write: renamed agents:write in protocol 16`);
|
|
212
215
|
const parsed = IssuerEntrySchema.safeParse(entry);
|
|
213
216
|
if (!parsed.success)
|
|
214
217
|
return fail(`${where}: ${issueText(parsed.error.issues)}`);
|
|
@@ -244,7 +247,6 @@ export function parseIdentityFile(text, source = "identity file", options = {})
|
|
|
244
247
|
scopes: value.scopes,
|
|
245
248
|
allowedScopes: [...new Set(value.allowedScopes)],
|
|
246
249
|
...(value.agents ? { agents: [...new Set(value.agents)] } : {}),
|
|
247
|
-
...(value.environments ? { environments: value.environments } : {}),
|
|
248
250
|
});
|
|
249
251
|
});
|
|
250
252
|
return issuers;
|
package/dist/tenant/issuers.js
CHANGED
|
@@ -299,7 +299,7 @@ export async function verifyIssuerToken(ctx, issuer, raw, resourceMetadata) {
|
|
|
299
299
|
const subject = renderTemplate(config.subject, (claim) => scalarClaim(payload, claim));
|
|
300
300
|
if (subject === undefined || !isSubject(subject) || RESERVED.has(subject))
|
|
301
301
|
return rejected("issuer_token_subject");
|
|
302
|
-
const
|
|
302
|
+
const agents = (config.agents ?? []).flatMap((grant) => {
|
|
303
303
|
const rendered = renderTemplate(grant, (claim) => {
|
|
304
304
|
const value = scalarClaim(payload, claim);
|
|
305
305
|
return value !== undefined && GRANT_SEGMENT.test(value) ? value : undefined;
|
|
@@ -316,8 +316,8 @@ export async function verifyIssuerToken(ctx, issuer, raw, resourceMetadata) {
|
|
|
316
316
|
issuer: config.name,
|
|
317
317
|
subject,
|
|
318
318
|
scopes: scopesOf(config, payload),
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
// `*` reaches every Agent; no grant reaches none.
|
|
320
|
+
agents: agents.includes("*") ? "*" : [...new Set(agents)],
|
|
321
321
|
expiresAt: payload.exp * 1000,
|
|
322
322
|
tokenId: jti,
|
|
323
323
|
...(verdict.kid !== undefined ? { keyId: verdict.kid } : {}),
|
package/dist/tenant/limits.d.ts
CHANGED
|
@@ -78,9 +78,6 @@ export declare function budgetIssues(budgets: readonly unknown[], limits: Runtim
|
|
|
78
78
|
* Why an agent's `requires` does not fit: more secrets than an Environment's manifest may declare
|
|
79
79
|
* slots for (the Runtime limit `environmentSecretSlots`).
|
|
80
80
|
*/
|
|
81
|
-
export declare function requiresIssues(requires: {
|
|
82
|
-
readonly secrets?: readonly string[];
|
|
83
|
-
} | undefined, limits: RuntimeLimits): string[];
|
|
84
81
|
/**
|
|
85
82
|
* Why a manifest (inline, or saved) does not fit the Tenant's Runtime limits: its description,
|
|
86
83
|
* its setup commands, their lengths and timeouts, its input file entries and the files of each,
|
package/dist/tenant/limits.js
CHANGED
|
@@ -137,9 +137,6 @@ export function budgetIssues(budgets, limits) {
|
|
|
137
137
|
* Why an agent's `requires` does not fit: more secrets than an Environment's manifest may declare
|
|
138
138
|
* slots for (the Runtime limit `environmentSecretSlots`).
|
|
139
139
|
*/
|
|
140
|
-
export function requiresIssues(requires, limits) {
|
|
141
|
-
return more("requires.secrets", requires?.secrets?.length ?? 0, "environmentSecretSlots", limits);
|
|
142
|
-
}
|
|
143
140
|
/**
|
|
144
141
|
* Why a manifest (inline, or saved) does not fit the Tenant's Runtime limits: its description,
|
|
145
142
|
* its setup commands, their lengths and timeouts, its input file entries and the files of each,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Agent's memories over the Runtime API (track T5, D72), shaped like Anthropic's memory stores:
|
|
3
|
+
* list and read them, write one whole (create or replace, with an optional `content_sha256`
|
|
4
|
+
* precondition), delete one or a folder, and read the versions every change leaves. Writes go
|
|
5
|
+
* through the memory service (`memory/service.ts`), as a model's do, so the same paths, limits
|
|
6
|
+
* and versions hold; the actor is the caller (`human:<subject>`, else `application`).
|
|
7
|
+
*/
|
|
8
|
+
import { type Memory, type MemoryList, type MemoryVersionDetail, type MemoryVersionPage, type PutMemoryRequest } from "@nylorun/core/contracts";
|
|
9
|
+
import type { AuthScope, TenantContext } from "./context.js";
|
|
10
|
+
/** Who an API write names: the person a request acts for, else the application. */
|
|
11
|
+
export declare function memoryActorOf(scope: AuthScope): string;
|
|
12
|
+
/** `GET /v1/agents/{id}/memories`: the Agent's memories by path, under `pathPrefix` when given. */
|
|
13
|
+
export declare function listMemories(ctx: TenantContext, agentId: string, options: {
|
|
14
|
+
pathPrefix?: string;
|
|
15
|
+
cursor?: string;
|
|
16
|
+
limit: number;
|
|
17
|
+
}): Promise<MemoryList>;
|
|
18
|
+
/** `GET /v1/agents/{id}/memories/{path}`: one memory with its content. */
|
|
19
|
+
export declare function readMemory(ctx: TenantContext, agentId: string, rawPath: string): Promise<Memory>;
|
|
20
|
+
/**
|
|
21
|
+
* `PUT /v1/agents/{id}/memories/{path}`: writes the memory whole, as the tool's `create` does. With
|
|
22
|
+
* `contentSha256` the write applies only while the memory still has that content (`""`: only while
|
|
23
|
+
* no memory has the path); otherwise `409 memory_conflict` with the current memory.
|
|
24
|
+
*/
|
|
25
|
+
export declare function putMemoryContent(ctx: TenantContext, agentId: string, rawPath: string, body: PutMemoryRequest, scope: AuthScope): Promise<Memory>;
|
|
26
|
+
/** `DELETE /v1/agents/{id}/memories/{path}`: deletes a memory, or every memory in a folder. */
|
|
27
|
+
export declare function deleteMemoryPath(ctx: TenantContext, agentId: string, rawPath: string, scope: AuthScope): Promise<{
|
|
28
|
+
path: string;
|
|
29
|
+
deleted: number;
|
|
30
|
+
}>;
|
|
31
|
+
/** `GET /v1/agents/{id}/memory_versions`: versions, newest first; `path` keeps one path's. */
|
|
32
|
+
export declare function listMemoryVersions(ctx: TenantContext, agentId: string, options: {
|
|
33
|
+
path?: string;
|
|
34
|
+
cursor?: string;
|
|
35
|
+
limit: number;
|
|
36
|
+
}): Promise<MemoryVersionPage>;
|
|
37
|
+
/** `GET /v1/agents/{id}/memory_versions/{versionId}`: one version with the content it left. */
|
|
38
|
+
export declare function readMemoryVersion(ctx: TenantContext, agentId: string, versionId: string): Promise<MemoryVersionDetail>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Agent's memories over the Runtime API (track T5, D72), shaped like Anthropic's memory stores:
|
|
3
|
+
* list and read them, write one whole (create or replace, with an optional `content_sha256`
|
|
4
|
+
* precondition), delete one or a folder, and read the versions every change leaves. Writes go
|
|
5
|
+
* through the memory service (`memory/service.ts`), as a model's do, so the same paths, limits
|
|
6
|
+
* and versions hold; the actor is the caller (`human:<subject>`, else `application`).
|
|
7
|
+
*/
|
|
8
|
+
import { memoryPathOf, } from "@nylorun/core/contracts";
|
|
9
|
+
import { applyMemoryCommand, memorySummaryOf, memoryVersionDetailOf, memoryVersionOf, } from "../memory/service.js";
|
|
10
|
+
import { liveEnvironment } from "./agents.js";
|
|
11
|
+
import { fail } from "./http.js";
|
|
12
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
13
|
+
/** Who an API write names: the person a request acts for, else the application. */
|
|
14
|
+
export function memoryActorOf(scope) {
|
|
15
|
+
return scope.kind === "token" || scope.kind === "subject" ? `human:${scope.subject}` : "application";
|
|
16
|
+
}
|
|
17
|
+
async function agentOf(t, agentId) {
|
|
18
|
+
if (!(await liveEnvironment(t, agentId)))
|
|
19
|
+
fail(404, `Agent ${agentId} not found`, { code: "not_found" });
|
|
20
|
+
}
|
|
21
|
+
/** A path from a route: relative to `/memories` (as the route's segment), or absolute. */
|
|
22
|
+
function routePath(raw, kind) {
|
|
23
|
+
const trimmed = raw.replace(/^\/+/, "");
|
|
24
|
+
const parsed = memoryPathOf(trimmed.startsWith("memories/") || trimmed === "memories" ? trimmed : `memories/${trimmed}`, kind);
|
|
25
|
+
return "error" in parsed ? fail(400, parsed.error, { code: "invalid_request" }) : parsed.path;
|
|
26
|
+
}
|
|
27
|
+
/** The HTTP answer to a refused write. */
|
|
28
|
+
function refusal(result) {
|
|
29
|
+
switch (result.code) {
|
|
30
|
+
case "memory.not_found":
|
|
31
|
+
return fail(404, result.text, { code: "not_found" });
|
|
32
|
+
case "memory.limit":
|
|
33
|
+
return fail(409, result.text, { code: "limit_exceeded", details: { limit: "memoriesPerAgent" } });
|
|
34
|
+
case "memory.too_large":
|
|
35
|
+
return fail(413, result.text, { code: "limit_exceeded", details: { limit: "memoryBytes" } });
|
|
36
|
+
case "memory.exists":
|
|
37
|
+
return fail(409, result.text, { code: "memory_conflict" });
|
|
38
|
+
default:
|
|
39
|
+
return fail(400, result.text, { code: "invalid_request" });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** `GET /v1/agents/{id}/memories`: the Agent's memories by path, under `pathPrefix` when given. */
|
|
43
|
+
export async function listMemories(ctx, agentId, options) {
|
|
44
|
+
const folder = options.pathPrefix === undefined ? undefined : routePath(options.pathPrefix, "any");
|
|
45
|
+
return ctx.store.tx(async (t) => {
|
|
46
|
+
await agentOf(t, agentId);
|
|
47
|
+
const rows = await t.memories(agentId, {
|
|
48
|
+
...(folder === undefined ? {} : { folder }),
|
|
49
|
+
...(options.cursor === undefined ? {} : { after: options.cursor }),
|
|
50
|
+
limit: options.limit + 1,
|
|
51
|
+
});
|
|
52
|
+
const page = rows.slice(0, options.limit);
|
|
53
|
+
return { memories: page.map(memorySummaryOf), nextCursor: rows.length > options.limit ? page.at(-1).path : null };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** `GET /v1/agents/{id}/memories/{path}`: one memory with its content. */
|
|
57
|
+
export async function readMemory(ctx, agentId, rawPath) {
|
|
58
|
+
const path = routePath(rawPath, "file");
|
|
59
|
+
return ctx.store.tx(async (t) => {
|
|
60
|
+
await agentOf(t, agentId);
|
|
61
|
+
const row = (await t.memory(agentId, path)) ?? fail(404, `The path ${path} does not exist`, { code: "not_found" });
|
|
62
|
+
return { ...memorySummaryOf(row), content: row.content };
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `PUT /v1/agents/{id}/memories/{path}`: writes the memory whole, as the tool's `create` does. With
|
|
67
|
+
* `contentSha256` the write applies only while the memory still has that content (`""`: only while
|
|
68
|
+
* no memory has the path); otherwise `409 memory_conflict` with the current memory.
|
|
69
|
+
*/
|
|
70
|
+
export async function putMemoryContent(ctx, agentId, rawPath, body, scope) {
|
|
71
|
+
const path = routePath(rawPath, "file");
|
|
72
|
+
return ctx.store.tx(async (t) => {
|
|
73
|
+
await agentOf(t, agentId);
|
|
74
|
+
const current = await t.memory(agentId, path, { lock: true });
|
|
75
|
+
if (body.contentSha256 !== undefined && body.contentSha256 !== (current?.contentSha256 ?? ""))
|
|
76
|
+
fail(409, `${path} has changed since that content_sha256: read it again and write on top of what it holds now.`, {
|
|
77
|
+
code: "memory_conflict",
|
|
78
|
+
details: { current: current ? { ...memorySummaryOf(current), content: current.content } : null },
|
|
79
|
+
});
|
|
80
|
+
const result = await applyMemoryCommand(t, agentId, { command: "create", path, fileText: body.content }, { actor: memoryActorOf(scope) }, await readRuntimeLimits(t));
|
|
81
|
+
if (!result.ok)
|
|
82
|
+
refusal(result);
|
|
83
|
+
const row = (await t.memory(agentId, path));
|
|
84
|
+
return { ...memorySummaryOf(row), content: row.content };
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** `DELETE /v1/agents/{id}/memories/{path}`: deletes a memory, or every memory in a folder. */
|
|
88
|
+
export async function deleteMemoryPath(ctx, agentId, rawPath, scope) {
|
|
89
|
+
const path = routePath(rawPath, "any");
|
|
90
|
+
return ctx.store.tx(async (t) => {
|
|
91
|
+
await agentOf(t, agentId);
|
|
92
|
+
const result = await applyMemoryCommand(t, agentId, { command: "delete", path }, { actor: memoryActorOf(scope) }, await readRuntimeLimits(t));
|
|
93
|
+
if (!result.ok)
|
|
94
|
+
refusal(result);
|
|
95
|
+
return { path, deleted: result.changes.length };
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** `GET /v1/agents/{id}/memory_versions`: versions, newest first; `path` keeps one path's. */
|
|
99
|
+
export async function listMemoryVersions(ctx, agentId, options) {
|
|
100
|
+
const path = options.path === undefined ? undefined : routePath(options.path, "file");
|
|
101
|
+
return ctx.store.tx(async (t) => {
|
|
102
|
+
await agentOf(t, agentId);
|
|
103
|
+
const rows = await t.memoryVersions(agentId, {
|
|
104
|
+
...(path === undefined ? {} : { path }),
|
|
105
|
+
...(options.cursor === undefined ? {} : { before: options.cursor }),
|
|
106
|
+
limit: options.limit + 1,
|
|
107
|
+
});
|
|
108
|
+
const page = rows.slice(0, options.limit);
|
|
109
|
+
return { versions: page.map(memoryVersionOf), nextCursor: rows.length > options.limit ? page.at(-1).id : null };
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** `GET /v1/agents/{id}/memory_versions/{versionId}`: one version with the content it left. */
|
|
113
|
+
export async function readMemoryVersion(ctx, agentId, versionId) {
|
|
114
|
+
return ctx.store.tx(async (t) => {
|
|
115
|
+
await agentOf(t, agentId);
|
|
116
|
+
const row = (await t.memoryVersion(agentId, versionId)) ?? fail(404, `Memory version ${versionId} not found`, { code: "not_found" });
|
|
117
|
+
return memoryVersionDetailOf(row);
|
|
118
|
+
});
|
|
119
|
+
}
|
package/dist/tenant/reset.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { detachAllSessions } from "./
|
|
3
|
+
import { detachAllSessions } from "./agents.js";
|
|
4
4
|
import { contentKey } from "../artifacts/folders.js";
|
|
5
5
|
/**
|
|
6
6
|
* Rename `dir` to a sibling, delete the sibling, recreate an empty `dir`.
|
|
@@ -38,7 +38,7 @@ function replaceDirectory(dir) {
|
|
|
38
38
|
*/
|
|
39
39
|
export async function resetTenant(ctx, scope) {
|
|
40
40
|
const clearSessions = scope === "sessions" || scope === "all";
|
|
41
|
-
const clearSandboxes = scope === "
|
|
41
|
+
const clearSandboxes = scope === "agents" || scope === "all";
|
|
42
42
|
const clearAll = scope === "all";
|
|
43
43
|
if (clearSandboxes) {
|
|
44
44
|
await ctx.sandbox.removeAll();
|
|
@@ -23,6 +23,12 @@ export interface RunGrants extends RunTokens {
|
|
|
23
23
|
}
|
|
24
24
|
export declare function createRunGrants(): RunGrants;
|
|
25
25
|
type GrantContext = Pick<TenantContext, "keys" | "config" | "runGrants">;
|
|
26
|
+
/** The root agent a session's run token names: its loop manifest's id (the agent manifest's name). */
|
|
27
|
+
export declare function rootAgentOf(session: {
|
|
28
|
+
readonly manifest: {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
};
|
|
31
|
+
}): string;
|
|
26
32
|
/** What a run token names of the session: its root agent and the turn the advance runs. */
|
|
27
33
|
export interface RunOf {
|
|
28
34
|
readonly agentId: string;
|
|
@@ -18,6 +18,10 @@ export function createRunGrants() {
|
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
/** The root agent a session's run token names: its loop manifest's id (the agent manifest's name). */
|
|
22
|
+
export function rootAgentOf(session) {
|
|
23
|
+
return session.manifest.id;
|
|
24
|
+
}
|
|
21
25
|
/**
|
|
22
26
|
* Mints and registers the run token of the advance that just took `lease`. Nothing when the
|
|
23
27
|
* gates run in this process, or the session has no active turn (it makes no gate call).
|
package/dist/tenant/runtime.d.ts
CHANGED
|
@@ -81,7 +81,7 @@ export type TenantOpenHooks = {
|
|
|
81
81
|
harnessTap?: MemoryPortsOptions["tap"];
|
|
82
82
|
/**
|
|
83
83
|
* Sandbox pods (F7.2): the sandboxes service (`NYLORUN_SANDBOXES_URL`) and the Runtime image
|
|
84
|
-
* pods copy the engine from. Without it, kind `pod` is `
|
|
84
|
+
* pods copy the engine from. Without it, kind `pod` is `agent_unavailable`.
|
|
85
85
|
*/
|
|
86
86
|
pods?: TenantPods;
|
|
87
87
|
/** How the trace route makes spans (R7.5): the Host's `OTEL_*` resource and content opt-ins. */
|
package/dist/tenant/runtime.js
CHANGED
|
@@ -32,6 +32,7 @@ import { localWorkspace, remoteWorkspace, withPodWorkspaces, } from "../harness-
|
|
|
32
32
|
import { hostAuthority } from "../sandbox/join.js";
|
|
33
33
|
import { harnessOfToken } from "./harness-tokens.js";
|
|
34
34
|
import { reconcileSandbox } from "../sandbox/pods/reconcile.js";
|
|
35
|
+
import { fireTimer } from "./triggers.js";
|
|
35
36
|
import { hostTuning } from "../tuning.js";
|
|
36
37
|
function validateConfig(config) {
|
|
37
38
|
for (const key of ["leaseMs", "ownerLeaseMs", "sweepIntervalMs"]) {
|
|
@@ -266,6 +267,12 @@ export class TenantRuntime {
|
|
|
266
267
|
await execution.sandbox?.(config.tenantId, sandboxId, signal);
|
|
267
268
|
return true;
|
|
268
269
|
},
|
|
270
|
+
timer: async (key, at) => {
|
|
271
|
+
if (ctx.closing || ctx.closed)
|
|
272
|
+
return false;
|
|
273
|
+
await execution.timer(config.tenantId, key, at);
|
|
274
|
+
return true;
|
|
275
|
+
},
|
|
269
276
|
send: (request) => request.kind === "wake"
|
|
270
277
|
? ctx.wake(request.sessionId, request.wake)
|
|
271
278
|
: ctx.sandboxSignal(request.sandboxId, request.signal),
|
|
@@ -322,6 +329,7 @@ export class TenantRuntime {
|
|
|
322
329
|
advance: (sessionId, signal) => advance(ctx, sessionId, signal),
|
|
323
330
|
sweep: () => sweep(ctx),
|
|
324
331
|
sandbox: (sandboxId, trigger) => reconcileSandbox(ctx, sandboxId, trigger),
|
|
332
|
+
fire: (key) => fireTimer(ctx, key),
|
|
325
333
|
};
|
|
326
334
|
const unregister = workers.register(config.tenantId, worker);
|
|
327
335
|
detach = async () => {
|
|
@@ -1,42 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Session resources: `PUT /v1/sessions/:id` (create, or find an identical one), the session list
|
|
3
|
+
* and the session view. A session runs on one Agent (protocol 16) and pins the manifest version
|
|
4
|
+
* the Agent runs when it opens; each turn takes the version the Agent runs then
|
|
5
|
+
* (`agent-versions.ts`). Reads use the store's typed queries; `PUT /v1/sessions/:id` runs in
|
|
6
|
+
* one transaction with the Agent's checks.
|
|
5
7
|
*/
|
|
6
|
-
import { type
|
|
8
|
+
import { type PutSessionRequest } from "@nylorun/core/contracts";
|
|
7
9
|
import type { Tx } from "../store/types.js";
|
|
8
10
|
import { type Session, type SessionAccess, type TenantContext } from "./context.js";
|
|
9
|
-
export declare function listDefinitions(ctx: TenantContext): Promise<{
|
|
10
|
-
agents: {
|
|
11
|
-
agentId: any;
|
|
12
|
-
manifest: any;
|
|
13
|
-
manifestHash: string;
|
|
14
|
-
implementationVersion: string;
|
|
15
|
-
}[];
|
|
16
|
-
}>;
|
|
17
|
-
/**
|
|
18
|
-
* The agents a token caller may see: id, name and description only, never
|
|
19
|
-
* instructions, tools or MCP servers, and only the agents allowed.
|
|
20
|
-
*/
|
|
21
|
-
export declare function listAgentsPublic(ctx: TenantContext, agents: ReadonlySet<string> | "*"): Promise<{
|
|
22
|
-
agents: {
|
|
23
|
-
agentId: string;
|
|
24
|
-
name?: any;
|
|
25
|
-
description?: any;
|
|
26
|
-
}[];
|
|
27
|
-
}>;
|
|
28
|
-
export declare function putDefinition(ctx: TenantContext, agentId: string, body: PutAgentRequest, options?: {
|
|
29
|
-
/** Run every check and save nothing (`?dryRun=true`, protocol 14). */
|
|
30
|
-
dryRun?: boolean;
|
|
31
|
-
}): Promise<{
|
|
32
|
-
agentId: string;
|
|
33
|
-
manifestHash: string;
|
|
34
|
-
implementationVersion: string;
|
|
35
|
-
dryRun?: boolean | undefined;
|
|
36
|
-
}>;
|
|
37
11
|
/**
|
|
38
|
-
* `access`, when the request acts for a person, limits the list to that person's sessions
|
|
39
|
-
* the
|
|
12
|
+
* `access`, when the request acts for a person, limits the list to that person's sessions on
|
|
13
|
+
* the Agents they may use.
|
|
40
14
|
*/
|
|
41
15
|
export declare function listSessions(ctx: TenantContext, agentId: string | null, access?: SessionAccess): Promise<{
|
|
42
16
|
sessions: {
|
|
@@ -48,26 +22,35 @@ export declare function listSessions(ctx: TenantContext, agentId: string | null,
|
|
|
48
22
|
}[];
|
|
49
23
|
}>;
|
|
50
24
|
/**
|
|
51
|
-
* Create a session
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* another owner.
|
|
25
|
+
* Create a session on an Agent, or find an identical one. `access`, when the request acts for a
|
|
26
|
+
* subject, must be the session's owner: another owner's session id is the 404 of a missing one
|
|
27
|
+
* (not the 409 of a mismatch), and so is an Agent whose owner guard names another owner.
|
|
55
28
|
*
|
|
56
|
-
* The session
|
|
57
|
-
* the session's, with the spec of the
|
|
58
|
-
*
|
|
29
|
+
* The session runs on its Agent for its life: the Agent's sandbox (kinds `virtual` and `pod`) is
|
|
30
|
+
* the session's, with the spec of what the Agent runs pinned now, and the Agent's vault holds the
|
|
31
|
+
* session's credentials. It pins the manifest version the Agent runs (`definition`), as the loop
|
|
32
|
+
* manifest that version's harness section makes, and the harness that section names (`pinOf`);
|
|
33
|
+
* a turn's first segment pins the version the Agent runs then (`repinTurn`).
|
|
59
34
|
*/
|
|
60
35
|
export declare function putSession(ctx: TenantContext, id: string, body: PutSessionRequest, access?: SessionAccess, options?: {
|
|
61
36
|
/**
|
|
62
37
|
* Create the session if it is missing and otherwise leave it as it is (an AG-UI thread's
|
|
63
|
-
* session): an existing one must have the same owner and
|
|
38
|
+
* session): an existing one must have the same owner and Agent, or it is a 404.
|
|
64
39
|
*/
|
|
65
40
|
createOnly?: boolean;
|
|
66
41
|
/**
|
|
67
|
-
* The
|
|
68
|
-
*
|
|
42
|
+
* The Agents the caller reaches (a token caller's grants, `agentGrantsOf`); undefined
|
|
43
|
+
* reaches every one. `agent` must name one of them.
|
|
69
44
|
*/
|
|
70
|
-
|
|
45
|
+
agentGrants?: readonly string[];
|
|
46
|
+
}): Promise<Session>;
|
|
47
|
+
/**
|
|
48
|
+
* `putSession`'s work in transaction `t`: also how a trigger's first run opens the session it
|
|
49
|
+
* names (track T4), in the run's transaction.
|
|
50
|
+
*/
|
|
51
|
+
export declare function openSessionIn(ctx: TenantContext, t: Tx, id: string, body: PutSessionRequest, access?: SessionAccess, options?: {
|
|
52
|
+
createOnly?: boolean;
|
|
53
|
+
agentGrants?: readonly string[];
|
|
71
54
|
}): Promise<Session>;
|
|
72
55
|
/** The session resource returned by `GET`/`PUT /v1/sessions/:id`. */
|
|
73
56
|
export declare function sessionView(t: Tx, s: Session): Promise<unknown>;
|