@nylorun/runtime 0.26.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 +171 -0
- package/README.md +18 -14
- package/dist/adapters/execution/restate.d.ts +15 -18
- package/dist/adapters/execution/restate.js +31 -27
- package/dist/adapters/replication/pgoutput.d.ts +1 -1
- package/dist/adapters/replication/pgoutput.js +3 -2
- package/dist/adapters/sandbox/local.js +8 -5
- package/dist/adapters/sandbox/virtual.js +2 -2
- package/dist/adapters/streams/s2.d.ts +1 -1
- package/dist/adapters/streams/s2.js +18 -14
- package/dist/api/a2a/card.d.ts +4 -3
- package/dist/api/a2a/card.js +4 -4
- package/dist/api/a2a/endpoint.js +3 -4
- package/dist/api/a2a/protocol.js +4 -0
- package/dist/api/a2a/routes.d.ts +2 -6
- package/dist/api/a2a/routes.js +30 -25
- package/dist/api/ag-ui/endpoint.js +2 -3
- package/dist/api/ag-ui/routes.js +8 -11
- package/dist/api/components.d.ts +6789 -3405
- package/dist/api/components.js +43 -30
- package/dist/api/gate/keys.js +7 -4
- package/dist/api/gate/routes.d.ts +6 -2
- package/dist/api/gate/routes.js +15 -11
- package/dist/api/http/app.js +9 -4
- package/dist/api/http/body.d.ts +17 -3
- package/dist/api/http/body.js +35 -7
- package/dist/api/http/respond.js +6 -2
- package/dist/api/http/routes/access.js +5 -4
- 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 +21 -9
- package/dist/api/http/routes/coverage.d.ts +4 -4
- package/dist/api/http/routes/coverage.js +13 -7
- package/dist/api/http/routes/files.d.ts +2 -1
- package/dist/api/http/routes/files.js +31 -5
- package/dist/api/http/routes/mcp.js +3 -3
- 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 +10 -111
- package/dist/api/http/routes/reads.js +51 -29
- package/dist/api/http/routes/sessions.js +26 -54
- package/dist/api/http/routes/tenant.d.ts +2 -2
- package/dist/api/http/routes/tenant.js +77 -24
- package/dist/api/http/routes/triggers.d.ts +10 -0
- package/dist/api/http/routes/triggers.js +180 -0
- package/dist/api/inbound-files.d.ts +3 -9
- package/dist/api/inbound-files.js +18 -24
- package/dist/api/openapi.js +73 -47
- package/dist/artifacts/disk.d.ts +10 -14
- package/dist/artifacts/disk.js +99 -95
- package/dist/artifacts/files.d.ts +13 -4
- package/dist/artifacts/files.js +27 -9
- package/dist/artifacts/folder-upload.d.ts +75 -0
- package/dist/artifacts/folder-upload.js +263 -0
- package/dist/artifacts/folders.d.ts +14 -8
- package/dist/artifacts/folders.js +19 -11
- package/dist/artifacts/links.js +7 -6
- package/dist/artifacts/parts.d.ts +9 -14
- package/dist/artifacts/parts.js +11 -9
- package/dist/artifacts/pipe.d.ts +15 -0
- package/dist/artifacts/pipe.js +71 -0
- package/dist/artifacts/quota.d.ts +36 -0
- package/dist/artifacts/quota.js +44 -0
- package/dist/artifacts/service.d.ts +25 -1
- package/dist/artifacts/service.js +72 -27
- package/dist/blob/fs.js +2 -1
- package/dist/blob/s3.d.ts +6 -2
- package/dist/blob/s3.js +9 -7
- package/dist/blob/types.d.ts +1 -1
- package/dist/blob/types.js +1 -1
- package/dist/core/flow-host.d.ts +0 -1
- package/dist/core/limits.d.ts +11 -35
- package/dist/core/limits.js +5 -73
- package/dist/core/sandbox-routes.js +0 -1
- package/dist/core/turn-manifest.d.ts +1 -2
- package/dist/core/turn-manifest.js +4 -28
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +8 -6
- package/dist/execution/types.d.ts +1 -6
- package/dist/execution/types.js +0 -5
- package/dist/gates/contract.d.ts +3 -2
- package/dist/gates/contract.js +1 -1
- package/dist/gates/egress-credentials.js +5 -4
- package/dist/gates/egress.d.ts +13 -13
- package/dist/gates/egress.js +19 -21
- package/dist/gates/fixture-model.js +13 -2
- package/dist/gates/handler.d.ts +1 -3
- package/dist/gates/handler.js +4 -3
- package/dist/gates/http-client.d.ts +4 -3
- package/dist/gates/http-client.js +5 -7
- package/dist/gates/http-tool.d.ts +24 -4
- package/dist/gates/http-tool.js +41 -13
- package/dist/gates/in-process.d.ts +2 -1
- package/dist/gates/in-process.js +17 -9
- package/dist/gates/inflight.d.ts +2 -2
- package/dist/gates/inflight.js +3 -2
- package/dist/gates/journal.js +5 -7
- package/dist/gates/mcp-handler.d.ts +4 -3
- package/dist/gates/mcp-handler.js +15 -5
- package/dist/gates/meter.js +6 -5
- package/dist/gates/model-gate.d.ts +11 -2
- package/dist/gates/policy.d.ts +34 -6
- package/dist/gates/policy.js +66 -18
- package/dist/gates/provider-gate.d.ts +22 -0
- package/dist/gates/provider-gate.js +30 -0
- package/dist/gates/tool-calls.d.ts +1 -3
- package/dist/gates/tool-calls.js +2 -3
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +5 -6
- package/dist/gates/tool-contract.d.ts +8 -5
- package/dist/gates/tool-contract.js +1 -1
- package/dist/gates/tool-gate.d.ts +18 -7
- package/dist/gates/tool-gate.js +49 -7
- package/dist/harness/calls.d.ts +27 -25
- package/dist/harness/calls.js +40 -46
- package/dist/harness/executors.d.ts +10 -3
- package/dist/harness/executors.js +53 -23
- package/dist/harness/main.js +2 -2
- package/dist/harness/pod.d.ts +2 -2
- package/dist/harness/pod.js +7 -6
- package/dist/harness/run-tokens.d.ts +4 -1
- package/dist/harness/run-tokens.js +6 -2
- package/dist/harness/service.d.ts +17 -14
- package/dist/harness/service.js +21 -35
- package/dist/harness/setup.d.ts +0 -2
- package/dist/harness/setup.js +21 -15
- package/dist/harness/ws-client.d.ts +2 -2
- package/dist/harness/ws-client.js +7 -5
- package/dist/harness/ws-port.d.ts +3 -4
- package/dist/harness/ws-port.js +3 -4
- package/dist/harness-api/artifacts.d.ts +20 -0
- package/dist/harness-api/artifacts.js +478 -0
- package/dist/harness-api/in-process.d.ts +2 -2
- package/dist/harness-api/in-process.js +18 -16
- package/dist/harness-api/record.d.ts +10 -9
- package/dist/harness-api/record.js +113 -157
- package/dist/harness-api/renew.d.ts +1 -1
- package/dist/harness-api/renew.js +2 -2
- package/dist/harness-api/server.d.ts +1 -16
- package/dist/harness-api/server.js +86 -73
- package/dist/harness-api/start.d.ts +27 -5
- package/dist/harness-api/start.js +102 -19
- package/dist/harness-api/tool-journal.d.ts +15 -10
- package/dist/harness-api/tool-journal.js +26 -24
- package/dist/harness-api/workspace.d.ts +0 -4
- package/dist/harness-api/workspace.js +4 -47
- package/dist/harness-api/ws-server.js +10 -10
- package/dist/host/create-host.js +2 -3
- package/dist/host/execution.js +2 -3
- package/dist/host/gates.d.ts +2 -9
- package/dist/host/gates.js +15 -12
- package/dist/host/http.d.ts +2 -4
- package/dist/host/http.js +5 -29
- package/dist/host/main.js +10 -3
- package/dist/host/operate.js +12 -3
- package/dist/host/stack-config.d.ts +11 -0
- package/dist/host/stack-config.js +21 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/index.js +2 -1
- package/dist/infra/readiness.d.ts +0 -2
- package/dist/infra/readiness.js +4 -3
- package/dist/keys/client.d.ts +1 -0
- package/dist/keys/client.js +2 -3
- package/dist/keys/contract.d.ts +0 -2
- package/dist/keys/contract.js +0 -2
- package/dist/keys/egress-ca.d.ts +0 -2
- package/dist/keys/egress-ca.js +2 -3
- package/dist/keys/keys.d.ts +15 -4
- package/dist/keys/keys.js +22 -1
- package/dist/keys/webhooks.d.ts +25 -0
- package/dist/keys/webhooks.js +62 -0
- package/dist/keys/x509.d.ts +8 -4
- package/dist/keys/x509.js +14 -5
- package/dist/management-openapi.json +3043 -1954
- package/dist/mcp/connect.d.ts +35 -26
- package/dist/mcp/connect.js +112 -141
- package/dist/mcp/pool.d.ts +27 -22
- package/dist/mcp/pool.js +34 -48
- package/dist/mcp/preview.d.ts +12 -5
- package/dist/mcp/preview.js +43 -26
- package/dist/mcp/snapshot.d.ts +35 -86
- package/dist/mcp/snapshot.js +93 -135
- 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/auth-store.js +5 -3
- package/dist/model/capabilities.d.ts +1 -1
- package/dist/model/catalog.d.ts +9 -36
- package/dist/model/catalog.js +6 -33
- package/dist/model/descriptor.d.ts +51 -0
- package/dist/model/descriptor.js +83 -0
- package/dist/model/models.d.ts +0 -3
- package/dist/model/models.js +3 -5
- package/dist/model/native-blocks.js +1 -1
- package/dist/model/pi-model.d.ts +16 -5
- package/dist/model/pi-model.js +86 -105
- package/dist/openapi.json +13653 -9986
- package/dist/reads/cursor.js +3 -1
- 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/append.d.ts +34 -2
- package/dist/record/append.js +50 -1
- package/dist/record/index.d.ts +2 -2
- package/dist/record/index.js +2 -2
- package/dist/redact.d.ts +1 -1
- package/dist/redact.js +3 -2
- package/dist/sandbox/egress-token.d.ts +3 -3
- package/dist/sandbox/egress-token.js +3 -3
- package/dist/sandbox/join.js +8 -7
- package/dist/sandbox/manager.d.ts +41 -41
- package/dist/sandbox/manager.js +112 -44
- package/dist/sandbox/placement.d.ts +2 -2
- package/dist/sandbox/placement.js +7 -10
- package/dist/sandbox/pods/client.d.ts +1 -10
- package/dist/sandbox/pods/client.js +8 -4
- package/dist/sandbox/pods/lifecycle.d.ts +25 -17
- package/dist/sandbox/pods/lifecycle.js +51 -29
- package/dist/sandbox/pods/network-gate.d.ts +4 -5
- package/dist/sandbox/pods/network-gate.js +7 -7
- package/dist/sandbox/pods/reconcile.js +14 -3
- package/dist/sandbox/pods/spec.js +8 -5
- package/dist/sandbox/policy.d.ts +7 -1
- package/dist/sandbox/policy.js +10 -3
- package/dist/sandbox/resolve.d.ts +11 -6
- package/dist/sandbox/resolve.js +19 -9
- package/dist/sandbox/setup.d.ts +6 -2
- package/dist/sandbox/setup.js +47 -14
- package/dist/sandbox/share.d.ts +5 -14
- package/dist/sandbox/share.js +5 -34
- package/dist/sandbox/skills.d.ts +7 -13
- package/dist/sandbox/skills.js +16 -31
- package/dist/sandbox/tenant-config.d.ts +2 -2
- package/dist/sandbox/tenant-config.js +4 -3
- package/dist/sandbox/tools.d.ts +7 -1
- package/dist/sandbox/tools.js +26 -26
- package/dist/sandbox/types.d.ts +10 -3
- package/dist/sandbox/types.js +9 -0
- package/dist/store/postgres/connect.d.ts +6 -5
- package/dist/store/postgres/connect.js +14 -11
- package/dist/store/postgres/control.js +13 -13
- package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
- package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
- package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
- package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
- 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 +63 -0
- package/dist/store/postgres/reads.js +17 -82
- 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 +1382 -151
- package/dist/store/postgres/schema.js +191 -37
- package/dist/store/postgres/store.js +384 -105
- package/dist/store/postgres/tenant.js +1 -2
- package/dist/store/types.d.ts +172 -57
- package/dist/store/types.js +0 -1
- package/dist/streams/relay/core.d.ts +3 -3
- package/dist/streams/relay/core.js +9 -10
- package/dist/tenant/advance.js +41 -39
- 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} +493 -328
- 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 -90
- package/dist/tenant/context.d.ts +57 -33
- package/dist/tenant/context.js +6 -2
- package/dist/tenant/definition-files.d.ts +25 -4
- package/dist/tenant/definition-files.js +85 -47
- package/dist/tenant/effects.js +15 -51
- package/dist/tenant/ephemeral.js +2 -1
- package/dist/tenant/history.d.ts +1 -1
- package/dist/tenant/history.js +1 -1
- package/dist/tenant/host-token.d.ts +3 -3
- package/dist/tenant/host-token.js +11 -10
- package/dist/tenant/http.d.ts +5 -8
- package/dist/tenant/http.js +9 -0
- package/dist/tenant/identity-file.d.ts +29 -6
- package/dist/tenant/identity-file.js +45 -15
- package/dist/tenant/issuers.d.ts +5 -5
- package/dist/tenant/issuers.js +34 -26
- package/dist/tenant/jwt.d.ts +8 -2
- package/dist/tenant/jwt.js +10 -2
- package/dist/tenant/limits.d.ts +87 -0
- package/dist/tenant/limits.js +172 -0
- package/dist/tenant/memories.d.ts +38 -0
- package/dist/tenant/memories.js +119 -0
- package/dist/tenant/module.d.ts +4 -1
- package/dist/tenant/module.js +9 -8
- package/dist/tenant/outbound.d.ts +11 -14
- package/dist/tenant/outbound.js +10 -15
- package/dist/tenant/reset.js +2 -2
- package/dist/tenant/run-grants.d.ts +6 -0
- package/dist/tenant/run-grants.js +7 -2
- package/dist/tenant/run-token.d.ts +0 -4
- package/dist/tenant/run-token.js +6 -11
- package/dist/tenant/runtime-limits.d.ts +24 -0
- package/dist/tenant/runtime-limits.js +27 -0
- package/dist/tenant/runtime.d.ts +1 -1
- package/dist/tenant/runtime.js +38 -43
- package/dist/tenant/session-streams.js +7 -6
- package/dist/tenant/session.d.ts +1 -1
- package/dist/tenant/session.js +1 -1
- package/dist/tenant/sessions.d.ts +28 -45
- package/dist/tenant/sessions.js +89 -209
- package/dist/tenant/signing-keys.d.ts +1 -1
- package/dist/tenant/signing-keys.js +5 -6
- package/dist/tenant/status.js +10 -10
- package/dist/tenant/streams.js +18 -20
- package/dist/tenant/sweep.d.ts +1 -21
- package/dist/tenant/sweep.js +51 -51
- package/dist/tenant/transcript.d.ts +70 -23
- package/dist/tenant/transcript.js +28 -22
- 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 +9 -14
- package/dist/tenant/worker.d.ts +4 -10
- package/dist/tenant/worker.js +10 -14
- package/dist/traces/ids.js +5 -1
- package/dist/traces/map.d.ts +10 -7
- package/dist/traces/map.js +16 -12
- package/dist/traces/otlp.d.ts +3 -3
- package/dist/traces/otlp.js +5 -3
- package/dist/traces/projector.d.ts +9 -4
- package/dist/traces/projector.js +13 -7
- package/dist/traces/read.js +6 -1
- package/dist/tuning.d.ts +390 -0
- package/dist/tuning.js +677 -0
- package/dist/vault/headers.js +4 -2
- 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/artifacts/export.d.ts +0 -8
- package/dist/artifacts/export.js +0 -204
- package/dist/artifacts/media-types.d.ts +0 -30
- package/dist/artifacts/media-types.js +0 -137
- package/dist/artifacts/workspace.d.ts +0 -60
- package/dist/artifacts/workspace.js +0 -45
- package/dist/media.d.ts +0 -10
- package/dist/media.js +0 -58
- package/dist/record/environment.d.ts +0 -25
- package/dist/record/environment.js +0 -44
- package/dist/sandbox/runtime-tools.d.ts +0 -9
- package/dist/sandbox/runtime-tools.js +0 -16
- package/dist/sandbox/session-sandbox.d.ts +0 -16
- package/dist/sandbox/session-sandbox.js +0 -77
- package/dist/tenant/artifact-tool.d.ts +0 -13
- package/dist/tenant/artifact-tool.js +0 -243
- package/dist/tenant/environment-manifests.d.ts +0 -57
- package/dist/tenant/environment-manifests.js +0 -200
- package/dist/tenant/environments.d.ts +0 -174
- package/dist/tenant/skill-tool.d.ts +0 -20
- package/dist/tenant/skill-tool.js +0 -67
- package/dist/tenant/tool-results.d.ts +0 -57
- package/dist/tenant/tool-results.js +0 -298
- package/dist/tenant/tool-search.d.ts +0 -39
- package/dist/tenant/tool-search.js +0 -103
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent memory (track T5, D72): the one place memories change. The `memory` tool's commands
|
|
3
|
+
* (Anthropic's memory tool: `create`, `str_replace`, `insert`, `delete`, `rename`) and the API's
|
|
4
|
+
* writes both go through `applyMemoryCommand`, so paths, limits, versions and locks are the same
|
|
5
|
+
* for a model and for a person. Reads are `viewMemory` (the tool's `view`) and the API's.
|
|
6
|
+
*
|
|
7
|
+
* Every change is a new row in `memory_versions` attributed to who made it; the Runtime never
|
|
8
|
+
* rewrites what was written. A write takes a lock on each path it touches, in path order, so
|
|
9
|
+
* writes to different memories never wait on each other. Answers to the model are Anthropic's
|
|
10
|
+
* result strings; a refusal also carries a `memory.*` code.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
13
|
+
import { MEMORY_ROOT, memoryDescriptionOf, memoryPathOf, memoryPathWithin, } from "@nylorun/core/contracts";
|
|
14
|
+
/** A new version id: increasing with time, so versions page newest first by id. */
|
|
15
|
+
export function newMemoryVersionId(now = Date.now()) {
|
|
16
|
+
return `mv_${now.toString(36).padStart(9, "0")}${randomBytes(8).toString("hex")}`;
|
|
17
|
+
}
|
|
18
|
+
export function sha256(text) {
|
|
19
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
20
|
+
}
|
|
21
|
+
const refused = (code, text) => ({ ok: false, code, text });
|
|
22
|
+
const missing = (path) => refused("memory.not_found", `The path ${path} does not exist. Please provide a valid path.`);
|
|
23
|
+
function pathOf(raw, kind) {
|
|
24
|
+
const parsed = memoryPathOf(raw, kind);
|
|
25
|
+
return "error" in parsed ? refused("memory.path_invalid", parsed.error) : parsed;
|
|
26
|
+
}
|
|
27
|
+
/** A file's text with Anthropic's line numbers: six columns, a tab, the line. */
|
|
28
|
+
function numbered(text, start = 1) {
|
|
29
|
+
return text
|
|
30
|
+
.split("\n")
|
|
31
|
+
.map((line, index) => `${String(index + start).padStart(6, " ")}\t${line}`)
|
|
32
|
+
.join("\n");
|
|
33
|
+
}
|
|
34
|
+
function sizeOf(bytes) {
|
|
35
|
+
if (bytes < 1024)
|
|
36
|
+
return `${bytes}B`;
|
|
37
|
+
if (bytes < 1024 * 1024)
|
|
38
|
+
return `${(bytes / 1024).toFixed(1)}K`;
|
|
39
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The tool's `view`: a memory's text with line numbers (`viewRange` `[start, end]`, 1-based,
|
|
43
|
+
* `end` -1 for the last line), or a folder's listing two levels deep with sizes. `/memories`
|
|
44
|
+
* lists even when the Agent has no memories.
|
|
45
|
+
*/
|
|
46
|
+
export async function viewMemory(t, agentId, rawPath, viewRange) {
|
|
47
|
+
const parsed = pathOf(rawPath, "any");
|
|
48
|
+
if (!("path" in parsed))
|
|
49
|
+
return parsed;
|
|
50
|
+
const path = parsed.path;
|
|
51
|
+
const row = path.endsWith(".md") ? await t.memory(agentId, path) : undefined;
|
|
52
|
+
if (row) {
|
|
53
|
+
const lines = row.content.split("\n");
|
|
54
|
+
if (viewRange === undefined)
|
|
55
|
+
return { ok: true, text: `Here's the content of ${path} with line numbers:\n${numbered(row.content)}` };
|
|
56
|
+
const [start, end] = viewRange;
|
|
57
|
+
const last = end === -1 ? lines.length : end;
|
|
58
|
+
if (!Number.isInteger(start) || !Number.isInteger(last) || start < 1 || start > lines.length || last < start || last > lines.length)
|
|
59
|
+
return refused("memory.invalid_line", `Invalid view_range [${start}, ${end}]: ${path} has ${lines.length} lines.`);
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
text: `Here's the content of ${path} with line numbers:\n${numbered(lines.slice(start - 1, last).join("\n"), start)}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const rows = await t.memories(agentId, { folder: path });
|
|
66
|
+
if (rows.length === 0 && path !== MEMORY_ROOT)
|
|
67
|
+
return missing(path);
|
|
68
|
+
// Folders are the prefixes of memory paths: list what is one or two levels below `path`.
|
|
69
|
+
const sizes = new Map();
|
|
70
|
+
for (const memory of rows) {
|
|
71
|
+
const rest = memory.path.slice(path.length + 1).split("/");
|
|
72
|
+
for (let depth = 1; depth <= Math.min(2, rest.length); depth++) {
|
|
73
|
+
const entry = `${path}/${rest.slice(0, depth).join("/")}`;
|
|
74
|
+
sizes.set(entry, (sizes.get(entry) ?? 0) + memory.bytes);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const total = rows.reduce((sum, memory) => sum + memory.bytes, 0);
|
|
78
|
+
const lines = [`${sizeOf(total)}\t${path}`, ...[...sizes.keys()].sort().map((entry) => `${sizeOf(sizes.get(entry))}\t${entry}`)];
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
text: `Here're the files and directories up to 2 levels deep in ${path}, excluding hidden items and node_modules:\n${lines.join("\n")}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function tooLarge(content, limits) {
|
|
85
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
86
|
+
return bytes > limits.memoryBytes
|
|
87
|
+
? refused("memory.too_large", `A memory is at most ${limits.memoryBytes} bytes (the Runtime limit memoryBytes); this one would be ${bytes}. Split it into smaller files.`)
|
|
88
|
+
: undefined;
|
|
89
|
+
}
|
|
90
|
+
/** Writes `content` at `path` (row `before`, locked, when it exists) and records the version. */
|
|
91
|
+
async function write(a, path, before, content, operation, fromPath) {
|
|
92
|
+
const contentSha256 = sha256(content);
|
|
93
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
94
|
+
const version = (before?.version ?? 0) + 1;
|
|
95
|
+
await a.t.putMemory({
|
|
96
|
+
agentId: a.agentId,
|
|
97
|
+
path,
|
|
98
|
+
content,
|
|
99
|
+
contentSha256,
|
|
100
|
+
bytes,
|
|
101
|
+
description: memoryDescriptionOf(content),
|
|
102
|
+
version,
|
|
103
|
+
updatedAt: a.now,
|
|
104
|
+
});
|
|
105
|
+
await a.t.insertMemoryVersion(versionRow(a, { path, fromPath: fromPath ?? null, operation, content, contentSha256, bytes }));
|
|
106
|
+
return { path, operation, version };
|
|
107
|
+
}
|
|
108
|
+
function versionRow(a, change) {
|
|
109
|
+
return {
|
|
110
|
+
id: newMemoryVersionId(Date.parse(a.now)),
|
|
111
|
+
agentId: a.agentId,
|
|
112
|
+
...change,
|
|
113
|
+
actor: a.author.actor,
|
|
114
|
+
sessionId: a.author.sessionId ?? null,
|
|
115
|
+
turnId: a.author.turnId ?? null,
|
|
116
|
+
triggerRunId: a.author.triggerRunId ?? null,
|
|
117
|
+
createdAt: a.now,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Deletes `row` (locked) and records the delete. */
|
|
121
|
+
async function remove(a, row) {
|
|
122
|
+
await a.t.deleteMemory(a.agentId, row.path);
|
|
123
|
+
await a.t.insertMemoryVersion(versionRow(a, { path: row.path, fromPath: null, operation: "delete", content: null, contentSha256: null, bytes: null }));
|
|
124
|
+
return { path: row.path, operation: "delete", version: row.version + 1 };
|
|
125
|
+
}
|
|
126
|
+
/** Locks the memories at `paths` (in path order, so two writes never wait on each other's locks). */
|
|
127
|
+
async function lockPaths(t, agentId, paths) {
|
|
128
|
+
const rows = new Map();
|
|
129
|
+
for (const path of [...new Set(paths)].sort())
|
|
130
|
+
rows.set(path, await t.memory(agentId, path, { lock: true }));
|
|
131
|
+
return rows;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Applies one write command to Agent `agentId`'s memories in `t`, as `author`, within the
|
|
135
|
+
* Runtime limits. Access is the caller's to check. A command that changes nothing (a `create`
|
|
136
|
+
* with the same text) makes no version.
|
|
137
|
+
*/
|
|
138
|
+
export async function applyMemoryCommand(t, agentId, command, author, limits, now = new Date().toISOString()) {
|
|
139
|
+
const a = { t, agentId, author, limits, now };
|
|
140
|
+
switch (command.command) {
|
|
141
|
+
case "create":
|
|
142
|
+
return create(a, command.path, command.fileText);
|
|
143
|
+
case "str_replace":
|
|
144
|
+
return strReplace(a, command.path, command.oldStr, command.newStr ?? "");
|
|
145
|
+
case "insert":
|
|
146
|
+
return insert(a, command.path, command.insertLine, command.insertText);
|
|
147
|
+
case "delete":
|
|
148
|
+
return del(a, command.path);
|
|
149
|
+
case "rename":
|
|
150
|
+
return rename(a, command.oldPath, command.newPath);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function create(a, rawPath, content) {
|
|
154
|
+
const parsed = pathOf(rawPath, "file");
|
|
155
|
+
if (!("path" in parsed))
|
|
156
|
+
return parsed;
|
|
157
|
+
const path = parsed.path;
|
|
158
|
+
const big = tooLarge(content, a.limits);
|
|
159
|
+
if (big)
|
|
160
|
+
return big;
|
|
161
|
+
const before = (await lockPaths(a.t, a.agentId, [path])).get(path);
|
|
162
|
+
// A memory can't share its path with a folder of memories.
|
|
163
|
+
if (!before && (await a.t.memories(a.agentId, { folder: path, limit: 1 })).length > 0)
|
|
164
|
+
return refused("memory.exists", `${path} is a folder of memories`);
|
|
165
|
+
if (before?.content === content)
|
|
166
|
+
return { ok: true, text: `File created successfully at: ${path}`, changes: [] };
|
|
167
|
+
if (!before && (await a.t.countMemories(a.agentId)) >= a.limits.memoriesPerAgent)
|
|
168
|
+
return refused("memory.limit", `This Agent holds ${a.limits.memoriesPerAgent} memories, the most the Runtime limit memoriesPerAgent allows. Delete or merge some first.`);
|
|
169
|
+
const change = await write(a, path, before, content, "create");
|
|
170
|
+
return { ok: true, text: `File created successfully at: ${path}`, changes: [change] };
|
|
171
|
+
}
|
|
172
|
+
async function strReplace(a, rawPath, oldStr, newStr) {
|
|
173
|
+
const parsed = pathOf(rawPath, "file");
|
|
174
|
+
if (!("path" in parsed))
|
|
175
|
+
return parsed;
|
|
176
|
+
const path = parsed.path;
|
|
177
|
+
const before = (await lockPaths(a.t, a.agentId, [path])).get(path);
|
|
178
|
+
if (!before)
|
|
179
|
+
return missing(path);
|
|
180
|
+
if (oldStr === "")
|
|
181
|
+
return refused("memory.no_match", "old_str is empty: give the text to replace.");
|
|
182
|
+
const lines = [];
|
|
183
|
+
for (let at = before.content.indexOf(oldStr); at !== -1; at = before.content.indexOf(oldStr, at + 1))
|
|
184
|
+
lines.push(before.content.slice(0, at).split("\n").length);
|
|
185
|
+
if (lines.length === 0)
|
|
186
|
+
return refused("memory.no_match", `No replacement was performed, old_str \`${oldStr}\` did not appear verbatim in ${path}.`);
|
|
187
|
+
if (lines.length > 1)
|
|
188
|
+
return refused("memory.not_unique", `No replacement was performed. Multiple occurrences of old_str \`${oldStr}\` in lines: ${lines.join(", ")}. Please ensure it is unique`);
|
|
189
|
+
const content = before.content.replace(oldStr, () => newStr);
|
|
190
|
+
const big = tooLarge(content, a.limits);
|
|
191
|
+
if (big)
|
|
192
|
+
return big;
|
|
193
|
+
const change = await write(a, path, before, content, "str_replace");
|
|
194
|
+
// Anthropic answers with the edited lines around the change.
|
|
195
|
+
const line = lines[0];
|
|
196
|
+
const all = content.split("\n");
|
|
197
|
+
const from = Math.max(1, line - 4);
|
|
198
|
+
const to = Math.min(all.length, line + newStr.split("\n").length + 3);
|
|
199
|
+
return {
|
|
200
|
+
ok: true,
|
|
201
|
+
text: `The memory file has been edited.\n${numbered(all.slice(from - 1, to).join("\n"), from)}`,
|
|
202
|
+
changes: [change],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function insert(a, rawPath, insertLine, insertText) {
|
|
206
|
+
const parsed = pathOf(rawPath, "file");
|
|
207
|
+
if (!("path" in parsed))
|
|
208
|
+
return parsed;
|
|
209
|
+
const path = parsed.path;
|
|
210
|
+
const before = (await lockPaths(a.t, a.agentId, [path])).get(path);
|
|
211
|
+
if (!before)
|
|
212
|
+
return missing(path);
|
|
213
|
+
const lines = before.content.split("\n");
|
|
214
|
+
if (!Number.isInteger(insertLine) || insertLine < 0 || insertLine > lines.length)
|
|
215
|
+
return refused("memory.invalid_line", `Invalid \`insert_line\` parameter: ${insertLine}. It should be within the range of lines of the file: [0, ${lines.length}]`);
|
|
216
|
+
const added = insertText.endsWith("\n") ? insertText.slice(0, -1) : insertText;
|
|
217
|
+
lines.splice(insertLine, 0, ...added.split("\n"));
|
|
218
|
+
const content = lines.join("\n");
|
|
219
|
+
const big = tooLarge(content, a.limits);
|
|
220
|
+
if (big)
|
|
221
|
+
return big;
|
|
222
|
+
const change = await write(a, path, before, content, "insert");
|
|
223
|
+
return { ok: true, text: `The file ${path} has been edited.`, changes: [change] };
|
|
224
|
+
}
|
|
225
|
+
async function del(a, rawPath) {
|
|
226
|
+
const parsed = pathOf(rawPath, "any");
|
|
227
|
+
if (!("path" in parsed))
|
|
228
|
+
return parsed;
|
|
229
|
+
const path = parsed.path;
|
|
230
|
+
if (path === MEMORY_ROOT)
|
|
231
|
+
return refused("memory.path_invalid", `${MEMORY_ROOT} itself can't be deleted: delete what is in it.`);
|
|
232
|
+
const file = path.endsWith(".md") ? (await lockPaths(a.t, a.agentId, [path])).get(path) : undefined;
|
|
233
|
+
const rows = file ? [file] : await a.t.memories(a.agentId, { folder: path, lock: true });
|
|
234
|
+
if (rows.length === 0)
|
|
235
|
+
return missing(path);
|
|
236
|
+
const changes = [];
|
|
237
|
+
for (const row of rows)
|
|
238
|
+
changes.push(await remove(a, row));
|
|
239
|
+
return { ok: true, text: `Successfully deleted ${path}`, changes };
|
|
240
|
+
}
|
|
241
|
+
async function rename(a, rawOld, rawNew) {
|
|
242
|
+
const from = pathOf(rawOld, "any");
|
|
243
|
+
if (!("path" in from))
|
|
244
|
+
return from;
|
|
245
|
+
const oldPath = from.path;
|
|
246
|
+
if (oldPath === MEMORY_ROOT)
|
|
247
|
+
return refused("memory.path_invalid", `${MEMORY_ROOT} itself can't be renamed.`);
|
|
248
|
+
const isFile = oldPath.endsWith(".md") && (await a.t.memory(a.agentId, oldPath)) !== undefined;
|
|
249
|
+
const to = pathOf(rawNew, isFile ? "file" : "any");
|
|
250
|
+
if (!("path" in to))
|
|
251
|
+
return to;
|
|
252
|
+
const newPath = to.path;
|
|
253
|
+
if (newPath === MEMORY_ROOT || memoryPathWithin(newPath, oldPath))
|
|
254
|
+
return refused("memory.path_invalid", `Can't rename ${oldPath} to ${newPath}: it would move into itself.`);
|
|
255
|
+
if (isFile) {
|
|
256
|
+
const rows = await lockPaths(a.t, a.agentId, [oldPath, newPath]);
|
|
257
|
+
const row = rows.get(oldPath);
|
|
258
|
+
if (!row)
|
|
259
|
+
return missing(oldPath);
|
|
260
|
+
if (rows.get(newPath) || (await a.t.memories(a.agentId, { folder: newPath, limit: 1 })).length > 0)
|
|
261
|
+
return refused("memory.exists", `The destination ${newPath} already exists`);
|
|
262
|
+
await a.t.deleteMemory(a.agentId, oldPath);
|
|
263
|
+
const change = await write(a, newPath, { ...row, version: 0 }, row.content, "rename", oldPath);
|
|
264
|
+
return { ok: true, text: `Successfully renamed ${oldPath} to ${newPath}`, changes: [change] };
|
|
265
|
+
}
|
|
266
|
+
const rows = await a.t.memories(a.agentId, { folder: oldPath, lock: true });
|
|
267
|
+
if (rows.length === 0)
|
|
268
|
+
return missing(oldPath);
|
|
269
|
+
const targets = rows.map((row) => `${newPath}${row.path.slice(oldPath.length)}`);
|
|
270
|
+
for (const target of targets)
|
|
271
|
+
if ("error" in memoryPathOf(target, "file"))
|
|
272
|
+
return refused("memory.path_invalid", `Renaming ${oldPath} to ${newPath} would make ${target}, which is too long or invalid.`);
|
|
273
|
+
const taken = await lockPaths(a.t, a.agentId, [newPath, ...targets]);
|
|
274
|
+
if ([...taken.values()].some(Boolean) || (await a.t.memories(a.agentId, { folder: newPath, limit: 1 })).length > 0)
|
|
275
|
+
return refused("memory.exists", `The destination ${newPath} already exists`);
|
|
276
|
+
const changes = [];
|
|
277
|
+
for (const [index, row] of rows.entries()) {
|
|
278
|
+
await a.t.deleteMemory(a.agentId, row.path);
|
|
279
|
+
changes.push(await write(a, targets[index], { ...row, version: 0 }, row.content, "rename", row.path));
|
|
280
|
+
}
|
|
281
|
+
return { ok: true, text: `Successfully renamed ${oldPath} to ${newPath}`, changes };
|
|
282
|
+
}
|
|
283
|
+
// --- the pinned index ------------------------------------------------------------
|
|
284
|
+
/**
|
|
285
|
+
* The index a session pins (`turn.start.memory.index`, design §6.3): a line per memory, newest
|
|
286
|
+
* first, `- <path>`, then ` — <description>` when it has one, within `memoryIndexLines` lines and
|
|
287
|
+
* `memoryIndexBytes` bytes. `truncated` when some memories were left out; the harness tells the
|
|
288
|
+
* model to view folders for the rest.
|
|
289
|
+
*/
|
|
290
|
+
export async function renderMemoryIndex(t, agentId, limits) {
|
|
291
|
+
const rows = await t.recentMemories(agentId, limits.memoryIndexLines + 1);
|
|
292
|
+
const lines = [];
|
|
293
|
+
let bytes = 0;
|
|
294
|
+
let truncated = rows.length > limits.memoryIndexLines;
|
|
295
|
+
for (const row of rows.slice(0, limits.memoryIndexLines)) {
|
|
296
|
+
const line = row.description ? `- ${row.path} — ${row.description}` : `- ${row.path}`;
|
|
297
|
+
const size = Buffer.byteLength(line, "utf8") + 1;
|
|
298
|
+
if (bytes + size > limits.memoryIndexBytes) {
|
|
299
|
+
truncated = true;
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
lines.push(line);
|
|
303
|
+
bytes += size;
|
|
304
|
+
}
|
|
305
|
+
return { index: lines.join("\n"), truncated };
|
|
306
|
+
}
|
|
307
|
+
// --- views ---------------------------------------------------------------------
|
|
308
|
+
export function memorySummaryOf(row) {
|
|
309
|
+
return {
|
|
310
|
+
path: row.path,
|
|
311
|
+
bytes: row.bytes,
|
|
312
|
+
contentSha256: row.contentSha256,
|
|
313
|
+
description: row.description,
|
|
314
|
+
version: row.version,
|
|
315
|
+
updatedAt: row.updatedAt,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
export function memoryVersionOf(row) {
|
|
319
|
+
return {
|
|
320
|
+
id: row.id,
|
|
321
|
+
path: row.path,
|
|
322
|
+
fromPath: row.fromPath,
|
|
323
|
+
operation: row.operation,
|
|
324
|
+
bytes: row.bytes,
|
|
325
|
+
contentSha256: row.contentSha256,
|
|
326
|
+
actor: row.actor,
|
|
327
|
+
sessionId: row.sessionId,
|
|
328
|
+
turnId: row.turnId,
|
|
329
|
+
triggerRunId: row.triggerRunId,
|
|
330
|
+
createdAt: row.createdAt,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
export function memoryVersionDetailOf(row) {
|
|
334
|
+
return { ...memoryVersionOf(row), content: row.content };
|
|
335
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `memory` tool's calls (track T5, D72): a `runtime` effect, operation `memory`, which core
|
|
3
|
+
* serves in the transaction that journals the call, as it does `schedule_wake`. So a replayed turn
|
|
4
|
+
* applies a write once and sees the same `view`, and a BYO harness gets the same answers by sending
|
|
5
|
+
* the same target. The input is Anthropic's memory tool's: `command` and its parameters.
|
|
6
|
+
*
|
|
7
|
+
* Core checks every call, whichever harness sent it (our engine runs the same `checkMemoryCall`
|
|
8
|
+
* before it sends one): a call missing a command's parameter, or with a parameter of the wrong
|
|
9
|
+
* type, fails with Anthropic-style text (`Error: create requires file_text.`) and changes nothing. Writes need `access: "read_write"` in the version the session runs and the
|
|
10
|
+
* root agent: a sub-agent's call (one that names its `agent`) may only view (`memory.forbidden`).
|
|
11
|
+
*/
|
|
12
|
+
import { type AgentMemorySettings } from "@nylorun/core/contracts";
|
|
13
|
+
import type { Tx } from "../store/types.js";
|
|
14
|
+
import type { Session } from "../tenant/context.js";
|
|
15
|
+
type Outcome = {
|
|
16
|
+
kind: "completed";
|
|
17
|
+
output: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "failed";
|
|
20
|
+
code: string;
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
/** `harness.memory` of the agent manifest version session `s` runs, when it sets one. */
|
|
24
|
+
export declare function memorySettingsOf(t: Tx, s: Pick<Session, "definition">): Promise<AgentMemorySettings | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* Serves one `memory` call of session `s`'s active turn in `t`: a view, or a write recorded as a
|
|
27
|
+
* version attributed to the turn (`agent:<manifest>@<version>`, the session, turn and the trigger
|
|
28
|
+
* run that started it), with `memory.updated` in the session and `agent.memory_updated` on the
|
|
29
|
+
* Agent's stream when something changed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function serveMemoryTool(t: Tx, s: Session, request: {
|
|
32
|
+
effectId: string;
|
|
33
|
+
input?: unknown;
|
|
34
|
+
agent?: unknown;
|
|
35
|
+
}): Promise<Outcome>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `memory` tool's calls (track T5, D72): a `runtime` effect, operation `memory`, which core
|
|
3
|
+
* serves in the transaction that journals the call, as it does `schedule_wake`. So a replayed turn
|
|
4
|
+
* applies a write once and sees the same `view`, and a BYO harness gets the same answers by sending
|
|
5
|
+
* the same target. The input is Anthropic's memory tool's: `command` and its parameters.
|
|
6
|
+
*
|
|
7
|
+
* Core checks every call, whichever harness sent it (our engine runs the same `checkMemoryCall`
|
|
8
|
+
* before it sends one): a call missing a command's parameter, or with a parameter of the wrong
|
|
9
|
+
* type, fails with Anthropic-style text (`Error: create requires file_text.`) and changes nothing. Writes need `access: "read_write"` in the version the session runs and the
|
|
10
|
+
* root agent: a sub-agent's call (one that names its `agent`) may only view (`memory.forbidden`).
|
|
11
|
+
*/
|
|
12
|
+
import { checkMemoryCall } from "@nylorun/core/contracts";
|
|
13
|
+
import { readRuntimeLimits } from "../tenant/runtime-limits.js";
|
|
14
|
+
import { applyMemoryCommand, viewMemory } from "./service.js";
|
|
15
|
+
/** `harness.memory` of the agent manifest version session `s` runs, when it sets one. */
|
|
16
|
+
export async function memorySettingsOf(t, s) {
|
|
17
|
+
if (!s.definition)
|
|
18
|
+
return undefined;
|
|
19
|
+
const version = await t.agentManifest(s.definition.name, s.definition.version);
|
|
20
|
+
const harness = version?.body.harness;
|
|
21
|
+
return harness?.kind === "workflow" ? undefined : harness?.memory;
|
|
22
|
+
}
|
|
23
|
+
function commandOf(command, args) {
|
|
24
|
+
switch (command) {
|
|
25
|
+
case "create":
|
|
26
|
+
return { command, path: args.path, fileText: args.file_text };
|
|
27
|
+
case "str_replace":
|
|
28
|
+
return {
|
|
29
|
+
command,
|
|
30
|
+
path: args.path,
|
|
31
|
+
oldStr: args.old_str,
|
|
32
|
+
...(typeof args.new_str === "string" ? { newStr: args.new_str } : {}),
|
|
33
|
+
};
|
|
34
|
+
case "insert":
|
|
35
|
+
return { command, path: args.path, insertLine: Number(args.insert_line), insertText: args.insert_text };
|
|
36
|
+
case "delete":
|
|
37
|
+
return { command, path: args.path };
|
|
38
|
+
case "rename":
|
|
39
|
+
return { command, oldPath: args.old_path, newPath: args.new_path };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Serves one `memory` call of session `s`'s active turn in `t`: a view, or a write recorded as a
|
|
44
|
+
* version attributed to the turn (`agent:<manifest>@<version>`, the session, turn and the trigger
|
|
45
|
+
* run that started it), with `memory.updated` in the session and `agent.memory_updated` on the
|
|
46
|
+
* Agent's stream when something changed.
|
|
47
|
+
*/
|
|
48
|
+
export async function serveMemoryTool(t, s, request) {
|
|
49
|
+
const settings = await memorySettingsOf(t, s);
|
|
50
|
+
if (!settings)
|
|
51
|
+
return { kind: "failed", code: "tool.unavailable", message: "This session's agent has no memory (harness.memory)." };
|
|
52
|
+
const parsed = checkMemoryCall(request.input);
|
|
53
|
+
if ("error" in parsed)
|
|
54
|
+
return { kind: "failed", code: "tool.invalid-input", message: parsed.error };
|
|
55
|
+
const { command, args } = parsed;
|
|
56
|
+
if (command === "view") {
|
|
57
|
+
const range = Array.isArray(args.view_range) ? [Number(args.view_range[0]), Number(args.view_range[1])] : undefined;
|
|
58
|
+
const viewed = await viewMemory(t, s.agentId, args.path, range);
|
|
59
|
+
return viewed.ok ? { kind: "completed", output: viewed.text } : { kind: "failed", code: viewed.code, message: viewed.text };
|
|
60
|
+
}
|
|
61
|
+
if ((settings.access ?? "read_write") === "read_only" || request.agent !== undefined)
|
|
62
|
+
return {
|
|
63
|
+
kind: "failed",
|
|
64
|
+
code: "memory.forbidden",
|
|
65
|
+
message: request.agent !== undefined
|
|
66
|
+
? "Error: memory is read-only for a sub-agent; only the main agent writes it."
|
|
67
|
+
: "Error: this agent's memory is read-only (harness.memory.access); view it, but don't change it.",
|
|
68
|
+
};
|
|
69
|
+
const turnId = s.activeTurnId;
|
|
70
|
+
const result = await applyMemoryCommand(t, s.agentId, commandOf(command, args), {
|
|
71
|
+
actor: `agent:${s.definition.name}@${s.definition.version}`,
|
|
72
|
+
sessionId: s.id,
|
|
73
|
+
turnId,
|
|
74
|
+
...(s.turnCause ? { triggerRunId: s.turnCause.runId } : {}),
|
|
75
|
+
}, await readRuntimeLimits(t));
|
|
76
|
+
if (!result.ok)
|
|
77
|
+
return { kind: "failed", code: result.code, message: result.text };
|
|
78
|
+
if (result.changes.length > 0) {
|
|
79
|
+
const changes = result.changes.map((change) => ({ ...change }));
|
|
80
|
+
await t.event(s.id, turnId, "memory.updated", { effectId: request.effectId, changes });
|
|
81
|
+
await t.agentEvent(s.agentId, "agent.memory_updated", { effectId: request.effectId, changes, sessionId: s.id, turnId });
|
|
82
|
+
}
|
|
83
|
+
return { kind: "completed", output: result.text };
|
|
84
|
+
}
|
package/dist/model/auth-store.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
+
import { hostTuning } from "../tuning.js";
|
|
4
5
|
export class ProjectCredentialStore {
|
|
5
6
|
file;
|
|
6
7
|
legacyFile;
|
|
@@ -68,7 +69,8 @@ export class ProjectCredentialStore {
|
|
|
68
69
|
async #locked(operation) {
|
|
69
70
|
await mkdir(dirname(this.file), { recursive: true });
|
|
70
71
|
const lock = this.file + ".lock";
|
|
71
|
-
|
|
72
|
+
const { credentialLockPollMs: pollMs, credentialLockAttempts: attempts } = hostTuning().host;
|
|
73
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
72
74
|
try {
|
|
73
75
|
await mkdir(lock);
|
|
74
76
|
break;
|
|
@@ -76,9 +78,9 @@ export class ProjectCredentialStore {
|
|
|
76
78
|
catch (error) {
|
|
77
79
|
if (error.code !== "EEXIST")
|
|
78
80
|
throw error;
|
|
79
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
80
82
|
}
|
|
81
|
-
if (attempt ===
|
|
83
|
+
if (attempt === attempts - 1)
|
|
82
84
|
throw new Error("Timed out waiting for the credential store.");
|
|
83
85
|
}
|
|
84
86
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a model reads (protocol 12): its pi-ai catalog's `input` (text, image) and what the
|
|
3
3
|
* operator added. One function, `capabilitiesOf`, serves the catalog, the accept check, the Model
|
|
4
|
-
* Gate
|
|
4
|
+
* Gate.
|
|
5
5
|
*
|
|
6
6
|
* The operator may add only an input our provider path sends natively for the model's API
|
|
7
7
|
* (`NATIVE_INPUTS`): an image on every API; a PDF on the APIs below; Office documents on the
|
package/dist/model/catalog.d.ts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
-
import type { HostModelCatalog,
|
|
3
|
-
import type
|
|
2
|
+
import type { HostModelCatalog, ModelInput } from "@nylorun/core/contracts";
|
|
3
|
+
import { type ModelBindingView } from "./descriptor.js";
|
|
4
4
|
export type { HostModelCatalog };
|
|
5
|
-
/**
|
|
6
|
-
* The context window, in tokens, of the Tenant's model (`GET /v1/tenant/model`'s view): the
|
|
7
|
-
* custom endpoint's setting, else the provider catalog's. A model the catalog does not know, or
|
|
8
|
-
* none configured, is assumed small (`CUSTOM_CONTEXT_WINDOW`), as for a custom endpoint.
|
|
9
|
-
*/
|
|
10
|
-
export declare function contextWindowOf(view: {
|
|
11
|
-
configured: boolean;
|
|
12
|
-
provider?: string;
|
|
13
|
-
model?: string;
|
|
14
|
-
baseUrl?: string;
|
|
15
|
-
settings?: {
|
|
16
|
-
contextWindow?: number;
|
|
17
|
-
};
|
|
18
|
-
}): number;
|
|
19
5
|
/**
|
|
20
6
|
* Public provider and model names, and what each model reads with the operator's additions
|
|
21
7
|
* (`added`, by `<provider>/<model>`) and what may be added (protocol 12). No credentials.
|
|
@@ -25,25 +11,12 @@ export declare function hostModelCatalog(added?: Readonly<Record<string, readonl
|
|
|
25
11
|
export declare function catalogModel(provider: string, model: string): Model<Api> | undefined;
|
|
26
12
|
/**
|
|
27
13
|
* What a configured model reads (protocol 12): a custom endpoint's `settings.inputs` (text only
|
|
28
|
-
* by default), else its catalog's inputs and the operator's additions (`added`)
|
|
29
|
-
* model, or one the catalog does not
|
|
14
|
+
* by default), else its catalog's inputs and the operator's additions (`added`), as its
|
|
15
|
+
* descriptor says (`hostModelDescriptor`). Undefined for no model, or one the catalog does not
|
|
16
|
+
* know.
|
|
30
17
|
*/
|
|
31
18
|
export declare function inputsOfHostModel(view: {
|
|
32
|
-
configured:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
settings?: {
|
|
37
|
-
inputs?: readonly ModelInput[];
|
|
38
|
-
};
|
|
39
|
-
}, added?: Readonly<Record<string, readonly ModelInput[]>>): ModelInput[] | undefined;
|
|
40
|
-
/**
|
|
41
|
-
* What the Tenant's model reads now, for the accept check and `read_artifact`: undefined when no
|
|
42
|
-
* model is in the vault (a test's fake provider, say), and the check is skipped.
|
|
43
|
-
*/
|
|
44
|
-
export declare function currentModelInputs(ctx: {
|
|
45
|
-
readonly store: SessionStore;
|
|
46
|
-
readonly vault: {
|
|
47
|
-
getHostModel(): Promise<HostModelView>;
|
|
48
|
-
};
|
|
49
|
-
}): Promise<ModelInput[] | undefined>;
|
|
19
|
+
readonly configured: false;
|
|
20
|
+
} | ({
|
|
21
|
+
readonly configured: true;
|
|
22
|
+
} & ModelBindingView), added?: Readonly<Record<string, readonly ModelInput[]>>): ModelInput[] | undefined;
|
package/dist/model/catalog.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readAddedInputs } from "../tenant/model-setting.js";
|
|
2
1
|
import { addableInputs, capabilitiesOf } from "./capabilities.js";
|
|
3
|
-
import {
|
|
2
|
+
import { hostModelDescriptor } from "./descriptor.js";
|
|
3
|
+
import { modelsFor } from "./models.js";
|
|
4
4
|
const emptyStore = {
|
|
5
5
|
async read() {
|
|
6
6
|
return undefined;
|
|
@@ -13,19 +13,6 @@ const emptyStore = {
|
|
|
13
13
|
},
|
|
14
14
|
async delete() { },
|
|
15
15
|
};
|
|
16
|
-
/**
|
|
17
|
-
* The context window, in tokens, of the Tenant's model (`GET /v1/tenant/model`'s view): the
|
|
18
|
-
* custom endpoint's setting, else the provider catalog's. A model the catalog does not know, or
|
|
19
|
-
* none configured, is assumed small (`CUSTOM_CONTEXT_WINDOW`), as for a custom endpoint.
|
|
20
|
-
*/
|
|
21
|
-
export function contextWindowOf(view) {
|
|
22
|
-
if (!view.configured || !view.provider || !view.model)
|
|
23
|
-
return CUSTOM_CONTEXT_WINDOW;
|
|
24
|
-
if (view.baseUrl)
|
|
25
|
-
return view.settings?.contextWindow ?? CUSTOM_CONTEXT_WINDOW;
|
|
26
|
-
const models = modelsFor({ provider: "", model: "" }, emptyStore, { environment: false });
|
|
27
|
-
return models.getModel(view.provider, view.model)?.contextWindow ?? CUSTOM_CONTEXT_WINDOW;
|
|
28
|
-
}
|
|
29
16
|
/**
|
|
30
17
|
* Public provider and model names, and what each model reads with the operator's additions
|
|
31
18
|
* (`added`, by `<provider>/<model>`) and what may be added (protocol 12). No credentials.
|
|
@@ -53,26 +40,12 @@ export function catalogModel(provider, model) {
|
|
|
53
40
|
}
|
|
54
41
|
/**
|
|
55
42
|
* What a configured model reads (protocol 12): a custom endpoint's `settings.inputs` (text only
|
|
56
|
-
* by default), else its catalog's inputs and the operator's additions (`added`)
|
|
57
|
-
* model, or one the catalog does not
|
|
43
|
+
* by default), else its catalog's inputs and the operator's additions (`added`), as its
|
|
44
|
+
* descriptor says (`hostModelDescriptor`). Undefined for no model, or one the catalog does not
|
|
45
|
+
* know.
|
|
58
46
|
*/
|
|
59
47
|
export function inputsOfHostModel(view, added = {}) {
|
|
60
|
-
if (!view.configured || !view.provider || !view.model)
|
|
61
|
-
return undefined;
|
|
62
|
-
if (view.baseUrl) {
|
|
63
|
-
const inputs = view.settings?.inputs ?? [];
|
|
64
|
-
return capabilitiesOf({ api: "openai-completions", input: inputs.includes("image") ? ["text", "image"] : ["text"] }, inputs).inputs;
|
|
65
|
-
}
|
|
66
|
-
const model = catalogModel(view.provider, view.model);
|
|
67
|
-
return model && capabilitiesOf(model, added[`${view.provider}/${view.model}`]).inputs;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* What the Tenant's model reads now, for the accept check and `read_artifact`: undefined when no
|
|
71
|
-
* model is in the vault (a test's fake provider, say), and the check is skipped.
|
|
72
|
-
*/
|
|
73
|
-
export async function currentModelInputs(ctx) {
|
|
74
|
-
const view = await ctx.vault.getHostModel();
|
|
75
48
|
if (!view.configured)
|
|
76
49
|
return undefined;
|
|
77
|
-
return
|
|
50
|
+
return hostModelDescriptor(view, added)?.inputs;
|
|
78
51
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model descriptor (protocol 15): the Tenant's model as a harness learns it in
|
|
3
|
+
* `turn.start.model` — its provider and id, what it reads, its context window and output limit.
|
|
4
|
+
* One resolution serves the descriptor, the catalog's inputs and the Model Gate: the binding
|
|
5
|
+
* becomes a pi-ai `Selection` (`selectionOf`), the registry finds the model, and
|
|
6
|
+
* `describeModel` reads it with the operator's additions. So what a harness is told and what the
|
|
7
|
+
* gate takes cannot disagree.
|
|
8
|
+
*/
|
|
9
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
10
|
+
import { type CustomModelSettings, type HostModelView, type ModelInput } from "@nylorun/core/contracts";
|
|
11
|
+
import type { ModelDescriptor } from "@nylorun/core/harness-api";
|
|
12
|
+
import type { SessionStore } from "../store/types.js";
|
|
13
|
+
import { type Selection } from "./models.js";
|
|
14
|
+
/** A configured model: its provider and model, and a custom endpoint's address and settings. */
|
|
15
|
+
export interface ModelBindingView {
|
|
16
|
+
readonly provider: string;
|
|
17
|
+
readonly model: string;
|
|
18
|
+
readonly baseUrl?: string;
|
|
19
|
+
readonly settings?: CustomModelSettings;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The pi-ai selection for `binding`. A call may name another model of the same provider
|
|
23
|
+
* (`requested`, as `<model>` or `<provider>/<model>`); a custom endpoint carries its settings.
|
|
24
|
+
*/
|
|
25
|
+
export declare function selectionOf(binding: ModelBindingView, requested?: string): Selection;
|
|
26
|
+
/** The inputs the operator added that apply to `selection`: a custom endpoint's own, or the Tenant's. */
|
|
27
|
+
export declare function addedInputsOf(selection: Selection, binding: Pick<ModelBindingView, "settings">, added: Readonly<Record<string, readonly ModelInput[]>>): readonly ModelInput[] | undefined;
|
|
28
|
+
/** A pi-ai model as a descriptor, with the inputs the operator added (`capabilitiesOf`). */
|
|
29
|
+
export declare function describeModel(found: Model<Api>, added?: readonly ModelInput[]): ModelDescriptor;
|
|
30
|
+
/** The fixture model (`model.fixture`): it reads every input, since it ignores files. */
|
|
31
|
+
export declare const FIXTURE_DESCRIPTOR: ModelDescriptor;
|
|
32
|
+
/**
|
|
33
|
+
* The descriptor of a configured model (`binding`), with the Tenant's added inputs; undefined
|
|
34
|
+
* for a model the catalog does not know. No credential is read.
|
|
35
|
+
*/
|
|
36
|
+
export declare function hostModelDescriptor(binding: ModelBindingView, added?: Readonly<Record<string, readonly ModelInput[]>>): ModelDescriptor | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* The model the Tenant's turns use now: the fixture model's descriptor when the Tenant uses it,
|
|
39
|
+
* else its active model's; undefined when none is configured (a test's scripted provider, say).
|
|
40
|
+
*/
|
|
41
|
+
export declare function currentModelDescriptor(ctx: {
|
|
42
|
+
readonly store: SessionStore;
|
|
43
|
+
readonly vault: {
|
|
44
|
+
getHostModel(): Promise<HostModelView>;
|
|
45
|
+
};
|
|
46
|
+
}): Promise<ModelDescriptor | undefined>;
|
|
47
|
+
/**
|
|
48
|
+
* The context window, in tokens, of the Tenant's model now; without one, or one the catalog does
|
|
49
|
+
* not know, it is assumed small (`CUSTOM_MODEL_SETTING_DEFAULTS`), as for a custom endpoint.
|
|
50
|
+
*/
|
|
51
|
+
export declare function currentContextWindow(ctx: Parameters<typeof currentModelDescriptor>[0]): Promise<number>;
|