@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
package/dist/tenant/advance.js
CHANGED
|
@@ -47,15 +47,16 @@ import { endAdvance } from "./scheduler.js";
|
|
|
47
47
|
import { harnessOf } from "./context.js";
|
|
48
48
|
import { HARNESS_ID } from "../sandbox/placement.js";
|
|
49
49
|
import { recoversModelCalls } from "./effects.js";
|
|
50
|
+
import { contextCompacted } from "./transcript.js";
|
|
50
51
|
import { slimModelEffects } from "./slim.js";
|
|
51
52
|
import { applyUpdates, leanState, transcriptShadow, withTranscript, } from "./history.js";
|
|
52
|
-
import { command } from "./commands.js";
|
|
53
|
-
import {
|
|
54
|
-
import { dropRunGrant, grantRun } from "./run-grants.js";
|
|
53
|
+
import { command, startQueuedTurn } from "./commands.js";
|
|
54
|
+
import { dropRunGrant, grantRun, rootAgentOf } from "./run-grants.js";
|
|
55
55
|
import { startHeartbeat } from "../harness-api/renew.js";
|
|
56
56
|
import { buildTurnStart, startSegment } from "../harness-api/start.js";
|
|
57
|
-
import {
|
|
57
|
+
import { touchAgent } from "./agents.js";
|
|
58
58
|
import { endTurn, pauseTurn } from "./turn-timing.js";
|
|
59
|
+
import { hostTuning } from "../tuning.js";
|
|
59
60
|
import { AdvanceAbort, AdvanceDeadlineError, abortKind, } from "./worker.js";
|
|
60
61
|
/**
|
|
61
62
|
* A segment stopped by a `shutdown` or `ownership.lost` abort before it settled. Never
|
|
@@ -95,15 +96,6 @@ class HarnessUnavailable extends Error {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
const DONE = { status: "done" };
|
|
98
|
-
/** How soon an advance whose segment no harness took is tried again. */
|
|
99
|
-
const UNAVAILABLE_RETRY_MS = 1000;
|
|
100
|
-
/**
|
|
101
|
-
* How long a pod sandbox's segment waits for its engine (F7.2): a pod that is starting, or
|
|
102
|
-
* resuming, takes longer than a harness that is already connected.
|
|
103
|
-
*/
|
|
104
|
-
const POD_OFFER_WAIT_MS = 60_000;
|
|
105
|
-
/** Shortest `busy` retry, so a lease about to lapse is not polled in a tight loop. */
|
|
106
|
-
const MIN_RETRY_MS = 25;
|
|
107
99
|
/**
|
|
108
100
|
* Advance session `id` if it has a runnable checkpoint (`WorkerHandlers.advance`). Returns
|
|
109
101
|
* `busy` when another advance holds a live lease, `done` otherwise.
|
|
@@ -115,7 +107,10 @@ export async function advance(ctx, id, signal) {
|
|
|
115
107
|
if (taken.status === "busy")
|
|
116
108
|
return {
|
|
117
109
|
status: "busy",
|
|
118
|
-
retryAfterMs: Math.min(
|
|
110
|
+
retryAfterMs: Math.min(
|
|
111
|
+
// The shortest `busy` retry (`tenant.busyRetryMinMs`), so a lease about to lapse is not
|
|
112
|
+
// polled in a tight loop.
|
|
113
|
+
Math.max(Date.parse(taken.ownerExpiresAt) - Date.now(), hostTuning().tenant.busyRetryMinMs), ctx.ownerLeaseMs),
|
|
119
114
|
};
|
|
120
115
|
if (taken.status === "idle")
|
|
121
116
|
return DONE;
|
|
@@ -129,7 +124,10 @@ export async function advance(ctx, id, signal) {
|
|
|
129
124
|
ctx.work.running.set(id, controller);
|
|
130
125
|
ctx.work.runningTurns.set(id, taken.session.activeTurnId ?? null);
|
|
131
126
|
// A harness renews the lease once it holds the run; until then, and without one, core does.
|
|
132
|
-
|
|
127
|
+
// The run token names the root agent as the engine does: the loop manifest's id, i.e. the
|
|
128
|
+
// agent manifest's name (protocol 16), never the Agent the session runs on. The model gate's
|
|
129
|
+
// usage rows and `manifest` budgets key on it.
|
|
130
|
+
const run = { agentId: rootAgentOf(taken.session), activeTurnId: taken.session.activeTurnId };
|
|
133
131
|
const heartbeat = startHeartbeat(ctx, lease, controller, run);
|
|
134
132
|
let result = DONE;
|
|
135
133
|
let release = true;
|
|
@@ -152,7 +150,7 @@ export async function advance(ctx, id, signal) {
|
|
|
152
150
|
sessionId: id,
|
|
153
151
|
epoch: lease.epoch,
|
|
154
152
|
});
|
|
155
|
-
result = { status: "busy", retryAfterMs:
|
|
153
|
+
result = { status: "busy", retryAfterMs: hostTuning().tenant.unavailableRetryMs };
|
|
156
154
|
}
|
|
157
155
|
else if (error instanceof SegmentStopped && error.kind === "shutdown") {
|
|
158
156
|
// Left for the next advance, which resumes from the checkpoint once this one releases.
|
|
@@ -186,7 +184,7 @@ export async function advance(ctx, id, signal) {
|
|
|
186
184
|
// The Environment was used: `deleteAfter`, and a pod's idle time (D34), count from the end
|
|
187
185
|
// of each segment.
|
|
188
186
|
if (placed.environment !== undefined)
|
|
189
|
-
await
|
|
187
|
+
await touchAgent(ctx, placed.environment);
|
|
190
188
|
}
|
|
191
189
|
return result;
|
|
192
190
|
}
|
|
@@ -199,11 +197,11 @@ function podUnusable(ctx, segment) {
|
|
|
199
197
|
return `Environment ${pod.id} is a pod Environment, and this Runtime has no sandbox pods`;
|
|
200
198
|
const state = pod.pod;
|
|
201
199
|
if (state.desired === "deleted")
|
|
202
|
-
return `
|
|
200
|
+
return `Agent ${pod.id} was deleted`;
|
|
203
201
|
if (state.observed === "lost")
|
|
204
|
-
return `
|
|
202
|
+
return `Agent ${pod.id} was lost: ${state.reason ?? "its volume is gone"}`;
|
|
205
203
|
if (state.observed === "failed")
|
|
206
|
-
return `
|
|
204
|
+
return `Agent ${pod.id} failed to start: ${state.reason ?? "its pod was not ready"}`;
|
|
207
205
|
return undefined;
|
|
208
206
|
}
|
|
209
207
|
/**
|
|
@@ -290,6 +288,10 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
|
|
|
290
288
|
// A new volume being seeded from a disk version takes no turn until it holds it (R2d step 3).
|
|
291
289
|
if (segment.pod?.seeding)
|
|
292
290
|
throw new HarnessUnavailable();
|
|
291
|
+
// A pod starting on a new version takes no turn until its harness joins (track T3): a start
|
|
292
|
+
// that fails goes back to the version it ran, and the turn's first segment re-pins to it.
|
|
293
|
+
if (segment.pod?.starting)
|
|
294
|
+
throw new HarnessUnavailable();
|
|
293
295
|
const pod = segment.pod?.id;
|
|
294
296
|
if (started.environmentId !== undefined)
|
|
295
297
|
placed.environment = started.environmentId;
|
|
@@ -302,7 +304,8 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
|
|
|
302
304
|
onTaken,
|
|
303
305
|
// Only a connection of the session's harness takes its runs (R4.1).
|
|
304
306
|
harness: harnessOf(segment.current),
|
|
305
|
-
|
|
307
|
+
// A pod that is starting, or resuming, takes longer than a harness already connected.
|
|
308
|
+
...(pod === undefined ? {} : { pod, waitMs: hostTuning().harnessApi.podOfferWaitMs }),
|
|
306
309
|
});
|
|
307
310
|
if (end.kind === "unavailable") {
|
|
308
311
|
// A pod stopped, or not yet started: make sure it is asked to run.
|
|
@@ -313,9 +316,6 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
|
|
|
313
316
|
// Aborted before a harness took it: settled as an abort of the engine would be.
|
|
314
317
|
if (end.kind === "aborted")
|
|
315
318
|
throw signal.reason;
|
|
316
|
-
// The harness's own tools take names of this turn's tools (Harness API 3): the turn fails.
|
|
317
|
-
if (end.kind === "refused")
|
|
318
|
-
throw new Error(end.message);
|
|
319
319
|
// Checked before the abort: whatever core's signal says, the run's effects are unaccounted for.
|
|
320
320
|
if (end.kind === "released" && end.reason === "connection.lost")
|
|
321
321
|
throw new RunLost();
|
|
@@ -333,7 +333,7 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
|
|
|
333
333
|
await settleFailure(ctx, lease, started, signal.reason);
|
|
334
334
|
else {
|
|
335
335
|
const recorded = result.status === "yielded" || result.status === "completed" || result.status === "paused";
|
|
336
|
-
cursor = await
|
|
336
|
+
cursor = await settle(ctx, lease, started, result, recorded ? output.transcript ?? [] : [], segment.cursor,
|
|
337
337
|
// The turn's end and its transcript are what the harness reported (R4.9).
|
|
338
338
|
{ source: { kind: "harness", id: harnessOf(started) }, evidence: "claimed", runId: output.runId });
|
|
339
339
|
}
|
|
@@ -419,17 +419,6 @@ async function wakeWorkflowOf(ctx, t, agent, turnId, type, payload) {
|
|
|
419
419
|
error: "Agent turn was cancelled",
|
|
420
420
|
});
|
|
421
421
|
}
|
|
422
|
-
/**
|
|
423
|
-
* `settle`, then, for a completed turn, the turn-end export of its sandbox outputs (F8.2): after
|
|
424
|
-
* the turn has committed and while the advance still holds the lease. The harness hears back
|
|
425
|
-
* after the export. The export never throws.
|
|
426
|
-
*/
|
|
427
|
-
async function settleTurn(ctx, lease, s, result, updates, signal, cursor, writer) {
|
|
428
|
-
const kept = await settle(ctx, lease, s, result, updates, cursor, writer);
|
|
429
|
-
if (result.status === "completed")
|
|
430
|
-
await exportOutputs(ctx, lease.sessionId, s.activeTurnId, signal);
|
|
431
|
-
return kept;
|
|
432
|
-
}
|
|
433
422
|
/**
|
|
434
423
|
* Record a segment result, then cancel failed siblings and wake a linked workflow. Returns the
|
|
435
424
|
* cursor of the transcript it recorded (`cursor` when it recorded no edit), or undefined when
|
|
@@ -580,6 +569,9 @@ writer) {
|
|
|
580
569
|
await t.event(id, s.activeTurnId, type, payload, writer);
|
|
581
570
|
await t.put("sessions", id, current);
|
|
582
571
|
await wakeWorkflowOf(ctx, t, current, s.activeTurnId, type, payload);
|
|
572
|
+
// Messages that waited for this turn start the next one (protocol 17).
|
|
573
|
+
if (await startQueuedTurn(ctx, t, current, id))
|
|
574
|
+
await t.put("sessions", id, current);
|
|
583
575
|
return siblings;
|
|
584
576
|
});
|
|
585
577
|
for (const agentId of siblingCancelIds) {
|
|
@@ -613,6 +605,10 @@ async function heldCalls(t, effectIds) {
|
|
|
613
605
|
const request = row.request;
|
|
614
606
|
const context = (request.context ?? {});
|
|
615
607
|
const toolName = request.toolName ?? request.key ?? effectId;
|
|
608
|
+
// A person approves the tool by its identity (protocol 15): an MCP tool by server and tool.
|
|
609
|
+
const what = row.server !== undefined && row.tool !== undefined && row.toolKind === "mcp"
|
|
610
|
+
? `${row.server} / ${row.tool}`
|
|
611
|
+
: (row.tool ?? toolName);
|
|
616
612
|
const reason = row.hold?.reason ?? "The Tenant's tool policy asks a person about this call";
|
|
617
613
|
held.push({
|
|
618
614
|
invocationId: typeof context.invocationId === "string" ? context.invocationId : effectId,
|
|
@@ -620,9 +616,10 @@ async function heldCalls(t, effectIds) {
|
|
|
620
616
|
interaction: {
|
|
621
617
|
id: effectId,
|
|
622
618
|
kind: "approval",
|
|
623
|
-
prompt: `Approve ${
|
|
619
|
+
prompt: `Approve ${what}?`,
|
|
624
620
|
reason,
|
|
625
621
|
...(row.server === undefined ? {} : { server: row.server }),
|
|
622
|
+
...(row.tool === undefined ? {} : { tool: row.tool }),
|
|
626
623
|
input: request.input ?? null,
|
|
627
624
|
},
|
|
628
625
|
status: "interaction",
|
|
@@ -649,6 +646,8 @@ async function settleFailure(ctx, lease, s, error) {
|
|
|
649
646
|
current.activeTurnId = null;
|
|
650
647
|
await t.put("sessions", id, current);
|
|
651
648
|
await wakeWorkflowOf(ctx, t, current, s.activeTurnId, "turn.failed", payload);
|
|
649
|
+
if (await startQueuedTurn(ctx, t, current, id))
|
|
650
|
+
await t.put("sessions", id, current);
|
|
652
651
|
});
|
|
653
652
|
}
|
|
654
653
|
/** Whether the session runs our harness, whose state core may store lean (R4.2). */
|
|
@@ -668,13 +667,16 @@ function leanCheckpoint(session, checkpoint) {
|
|
|
668
667
|
return { ...checkpoint, state: leanOf(session, state) };
|
|
669
668
|
}
|
|
670
669
|
/**
|
|
671
|
-
* Appends a segment's transcript edits; a snapshot moves `history.snapshot`.
|
|
672
|
-
*
|
|
670
|
+
* Appends a segment's transcript edits; a snapshot moves `history.snapshot`. An edit the harness
|
|
671
|
+
* marks as a compaction (`kind: "compaction"`, Q4) also writes `context.compacted`, from its facts
|
|
672
|
+
* and the summary calls it names. Returns the seq of the last edit, if any.
|
|
673
673
|
*/
|
|
674
674
|
async function recordTranscript(t, id, turnId, s, updates, writer) {
|
|
675
675
|
let last;
|
|
676
676
|
for (const update of updates) {
|
|
677
677
|
const event = await t.event(id, turnId, "transcript.updated", update, writer);
|
|
678
|
+
if ("compaction" in update)
|
|
679
|
+
await t.event(id, turnId, "context.compacted", await contextCompacted(t, id, turnId, update.compaction), writer);
|
|
678
680
|
if (update.keep === 0)
|
|
679
681
|
s.history = { from: s.history?.from ?? event.seq, snapshot: event.seq };
|
|
680
682
|
last = event.seq;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent manifests (protocol 16, track T2): `/v1/agent-manifests/{name}`, named and versioned
|
|
3
|
+
* documents with what runs inside an agent's loop (`harness`) and what the Runtime provides
|
|
4
|
+
* around it (`runtime`). They replace agent definitions (`PUT /v1/agents/:id` before protocol
|
|
5
|
+
* 16) and environment manifests.
|
|
6
|
+
*
|
|
7
|
+
* - **Versions.** A save whose manifest hashes the same as the latest answers it unchanged; any
|
|
8
|
+
* other writes the next version. A version is never changed or deleted alone.
|
|
9
|
+
* - **Tracking Agents move.** Saving a version moves every Agent that tracks the manifest to it
|
|
10
|
+
* (track T3, `moveAgent`), in the save's transaction: at once, at its pod's next start, or once
|
|
11
|
+
* its required secrets are set. A pinned Agent stays, and so does one the version cannot move
|
|
12
|
+
* (a fixed field) or whose start failed on it. The plan counts both.
|
|
13
|
+
* - **Both sections at once.** The Runtime sees both sections, so it checks them against each
|
|
14
|
+
* other (`sectionGaps`): every gap is named in one `422 manifest_inconsistent`, which is what
|
|
15
|
+
* `requires` checked when a session opened before protocol 16. Each section's own checks run
|
|
16
|
+
* too: the harness's (no code tools, no approvals, HTTP tool timeouts, the skill files) and the
|
|
17
|
+
* runtime's (the Tenant's limits, its files).
|
|
18
|
+
* - **Plan and drift.** `?dryRun=true` answers what a save would do and saves nothing: the
|
|
19
|
+
* changes from the latest by section, every reason the Runtime would refuse it, warnings, the
|
|
20
|
+
* Agents it moves and how many stay on older versions. `If-Match: <version>` refuses
|
|
21
|
+
* (`412 manifest_changed`) a save whose latest version moved since the client read it; `0` asks
|
|
22
|
+
* for a manifest that does not exist yet.
|
|
23
|
+
* - **Every client is equal** (Q12): a version records who saved it (`savedBy`).
|
|
24
|
+
*/
|
|
25
|
+
import { type AgentManifestChange, type AgentManifestPlan, type AgentManifestV8, type AgentManifestVersions, type AgentManifestView, type PutAgentManifestRequest, type SaveAgentManifestResponse } from "@nylorun/core/contracts";
|
|
26
|
+
import { type RecipeRequest } from "../sandbox/resolve.js";
|
|
27
|
+
import type { AuthScope, TenantContext } from "./context.js";
|
|
28
|
+
/** What a manifest's runtime section asks of an Agent's recipe, with the harness its loop names. */
|
|
29
|
+
export declare function manifestRecipeRequest(manifest: Pick<AgentManifestV8, "harness" | "runtime">): RecipeRequest;
|
|
30
|
+
/** The hash a manifest's version records: equal manifests hash the same. */
|
|
31
|
+
export declare function agentManifestHash(manifest: AgentManifestV8): string;
|
|
32
|
+
/** The owner a version's definition files are recorded under, for the sweep. */
|
|
33
|
+
export declare function manifestFileOwner(name: string): string;
|
|
34
|
+
/** Each field that differs between `before` and `after`, by path; arrays compare whole. */
|
|
35
|
+
export declare function manifestChanges(before: unknown, after: unknown, path?: string): {
|
|
36
|
+
path: string;
|
|
37
|
+
from?: unknown;
|
|
38
|
+
to?: unknown;
|
|
39
|
+
}[];
|
|
40
|
+
/**
|
|
41
|
+
* The changes from `before` to `after` by section: `manifest` for the top-level fields, else
|
|
42
|
+
* `harness` or `runtime` with the path inside it. A section added or removed whole is one change.
|
|
43
|
+
*/
|
|
44
|
+
export declare function sectionChanges(before: Partial<AgentManifestV8> | undefined, after: AgentManifestV8): AgentManifestChange[];
|
|
45
|
+
/**
|
|
46
|
+
* `PUT /v1/agent-manifests/{name}`: saves `body` as manifest `name`'s next version, or answers
|
|
47
|
+
* the latest unchanged; with `dryRun`, the plan of that save and nothing saved.
|
|
48
|
+
*/
|
|
49
|
+
export declare function saveAgentManifest(ctx: TenantContext, name: string, body: PutAgentManifestRequest, scope: AuthScope, options: {
|
|
50
|
+
dryRun: true;
|
|
51
|
+
ifMatch?: number;
|
|
52
|
+
}): Promise<AgentManifestPlan>;
|
|
53
|
+
export declare function saveAgentManifest(ctx: TenantContext, name: string, body: PutAgentManifestRequest, scope: AuthScope, options?: {
|
|
54
|
+
dryRun?: false;
|
|
55
|
+
ifMatch?: number;
|
|
56
|
+
}): Promise<SaveAgentManifestResponse>;
|
|
57
|
+
/** `GET /v1/agent-manifests/{name}` (the latest) and `.../versions/{n}`. */
|
|
58
|
+
export declare function getAgentManifest(ctx: TenantContext, name: string, version?: number): Promise<AgentManifestView>;
|
|
59
|
+
/** `GET /v1/agent-manifests`: each manifest's latest version. */
|
|
60
|
+
export declare function listAgentManifests(ctx: TenantContext): Promise<{
|
|
61
|
+
manifests: AgentManifestView[];
|
|
62
|
+
}>;
|
|
63
|
+
/** `GET /v1/agent-manifests/{name}/versions`: every version, newest first. */
|
|
64
|
+
export declare function agentManifestVersions(ctx: TenantContext, name: string): Promise<AgentManifestVersions>;
|
|
65
|
+
/**
|
|
66
|
+
* `DELETE /v1/agent-manifests/{name}`: deletes every version, refused while any Agent was made
|
|
67
|
+
* from one (`409 manifest_in_use`): the Agent names the version it runs.
|
|
68
|
+
*/
|
|
69
|
+
export declare function deleteAgentManifest(ctx: TenantContext, name: string): Promise<{
|
|
70
|
+
name: string;
|
|
71
|
+
deleted: boolean;
|
|
72
|
+
}>;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent manifests (protocol 16, track T2): `/v1/agent-manifests/{name}`, named and versioned
|
|
3
|
+
* documents with what runs inside an agent's loop (`harness`) and what the Runtime provides
|
|
4
|
+
* around it (`runtime`). They replace agent definitions (`PUT /v1/agents/:id` before protocol
|
|
5
|
+
* 16) and environment manifests.
|
|
6
|
+
*
|
|
7
|
+
* - **Versions.** A save whose manifest hashes the same as the latest answers it unchanged; any
|
|
8
|
+
* other writes the next version. A version is never changed or deleted alone.
|
|
9
|
+
* - **Tracking Agents move.** Saving a version moves every Agent that tracks the manifest to it
|
|
10
|
+
* (track T3, `moveAgent`), in the save's transaction: at once, at its pod's next start, or once
|
|
11
|
+
* its required secrets are set. A pinned Agent stays, and so does one the version cannot move
|
|
12
|
+
* (a fixed field) or whose start failed on it. The plan counts both.
|
|
13
|
+
* - **Both sections at once.** The Runtime sees both sections, so it checks them against each
|
|
14
|
+
* other (`sectionGaps`): every gap is named in one `422 manifest_inconsistent`, which is what
|
|
15
|
+
* `requires` checked when a session opened before protocol 16. Each section's own checks run
|
|
16
|
+
* too: the harness's (no code tools, no approvals, HTTP tool timeouts, the skill files) and the
|
|
17
|
+
* runtime's (the Tenant's limits, its files).
|
|
18
|
+
* - **Plan and drift.** `?dryRun=true` answers what a save would do and saves nothing: the
|
|
19
|
+
* changes from the latest by section, every reason the Runtime would refuse it, warnings, the
|
|
20
|
+
* Agents it moves and how many stay on older versions. `If-Match: <version>` refuses
|
|
21
|
+
* (`412 manifest_changed`) a save whose latest version moved since the client read it; `0` asks
|
|
22
|
+
* for a manifest that does not exist yet.
|
|
23
|
+
* - **Every client is equal** (Q12): a version records who saved it (`savedBy`).
|
|
24
|
+
*/
|
|
25
|
+
import { definitionFilesOf, } from "@nylorun/core/contracts";
|
|
26
|
+
import { hashManifest } from "@nylorun/core/compatibility";
|
|
27
|
+
import { approvalRefusal, codeToolRefusal, harnessIdOf, loopManifestOf, runtimeOf, sectionGaps, } from "@nylorun/core/define";
|
|
28
|
+
import { canonical } from "../store/canonical.js";
|
|
29
|
+
import { resolveRecipe } from "../sandbox/resolve.js";
|
|
30
|
+
import { declaredSandboxes } from "../sandbox/share.js";
|
|
31
|
+
import { httpToolTimeoutIssues } from "../gates/http-tool.js";
|
|
32
|
+
import { limitIssues, limitsInForce, manifestShapeIssues, readTenantLimits } from "./limits.js";
|
|
33
|
+
import { triggerIssues } from "./triggers.js";
|
|
34
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
35
|
+
import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
|
|
36
|
+
import { actorOf, fileErrors } from "./agents.js";
|
|
37
|
+
import { moveAgent, planMove } from "./agent-versions.js";
|
|
38
|
+
import { definitionFileIssues } from "./definition-files.js";
|
|
39
|
+
import { fail } from "./http.js";
|
|
40
|
+
/** What a manifest's runtime section asks of an Agent's recipe, with the harness its loop names. */
|
|
41
|
+
export function manifestRecipeRequest(manifest) {
|
|
42
|
+
const runtime = runtimeOf(manifest);
|
|
43
|
+
return {
|
|
44
|
+
...(runtime.image === undefined ? {} : { image: runtime.image }),
|
|
45
|
+
...(runtime.resources === undefined ? {} : { resources: runtime.resources }),
|
|
46
|
+
...(runtime.storage === undefined ? {} : { storage: runtime.storage }),
|
|
47
|
+
...(runtime.network === undefined ? {} : { network: runtime.network }),
|
|
48
|
+
harness: harnessIdOf(manifest.harness),
|
|
49
|
+
...(runtime.lifecycle === undefined ? {} : { lifecycle: runtime.lifecycle }),
|
|
50
|
+
...(runtime.setup === undefined ? {} : { setup: runtime.setup }),
|
|
51
|
+
...(runtime.files === undefined ? {} : { files: runtime.files }),
|
|
52
|
+
...(runtime.limits === undefined ? {} : { limits: runtime.limits }),
|
|
53
|
+
...(runtime.secrets === undefined ? {} : { secrets: runtime.secrets }),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** The hash a manifest's version records: equal manifests hash the same. */
|
|
57
|
+
export function agentManifestHash(manifest) {
|
|
58
|
+
return hashManifest(manifest);
|
|
59
|
+
}
|
|
60
|
+
/** The owner a version's definition files are recorded under, for the sweep. */
|
|
61
|
+
export function manifestFileOwner(name) {
|
|
62
|
+
return `agent-manifest:${name}`;
|
|
63
|
+
}
|
|
64
|
+
const notFound = (name, version) => fail(404, version === undefined ? `No agent manifest is named ${name}.` : `Agent manifest ${name} has no version ${version}.`, {
|
|
65
|
+
code: "manifest_not_found",
|
|
66
|
+
});
|
|
67
|
+
function viewOf(row, counts) {
|
|
68
|
+
return {
|
|
69
|
+
name: row.name,
|
|
70
|
+
version: row.version,
|
|
71
|
+
hash: row.hash,
|
|
72
|
+
manifest: row.body,
|
|
73
|
+
savedBy: row.savedBy,
|
|
74
|
+
createdAt: row.createdAt,
|
|
75
|
+
agents: counts.get(row.version) ?? 0,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Each field that differs between `before` and `after`, by path; arrays compare whole. */
|
|
79
|
+
export function manifestChanges(before, after, path = "") {
|
|
80
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
81
|
+
if (object(before) && object(after)) {
|
|
82
|
+
const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();
|
|
83
|
+
return keys.flatMap((key) => manifestChanges(before[key], after[key], path ? `${path}.${key}` : key));
|
|
84
|
+
}
|
|
85
|
+
if (canonical(before) === canonical(after))
|
|
86
|
+
return [];
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
path,
|
|
90
|
+
...(before === undefined ? {} : { from: before }),
|
|
91
|
+
...(after === undefined ? {} : { to: after }),
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The changes from `before` to `after` by section: `manifest` for the top-level fields, else
|
|
97
|
+
* `harness` or `runtime` with the path inside it. A section added or removed whole is one change.
|
|
98
|
+
*/
|
|
99
|
+
export function sectionChanges(before, after) {
|
|
100
|
+
const op = (from, to) => from === undefined ? "add" : to === undefined ? "remove" : "replace";
|
|
101
|
+
const changes = [];
|
|
102
|
+
const { harness: beforeHarness, runtime: beforeRuntime, ...beforeTop } = before ?? {};
|
|
103
|
+
const { harness, runtime, ...top } = after;
|
|
104
|
+
for (const change of manifestChanges(beforeTop, top))
|
|
105
|
+
changes.push({ section: "manifest", ...change, op: op(change.from, change.to) });
|
|
106
|
+
for (const [section, from, to] of [
|
|
107
|
+
["harness", beforeHarness, harness],
|
|
108
|
+
["runtime", beforeRuntime, runtime],
|
|
109
|
+
]) {
|
|
110
|
+
if (from === undefined || to === undefined) {
|
|
111
|
+
if (from !== to)
|
|
112
|
+
changes.push({
|
|
113
|
+
section,
|
|
114
|
+
path: "",
|
|
115
|
+
op: op(from, to),
|
|
116
|
+
...(from === undefined ? {} : { from }),
|
|
117
|
+
...(to === undefined ? {} : { to }),
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
for (const change of manifestChanges(from, to))
|
|
122
|
+
changes.push({ section, ...change, op: op(change.from, change.to) });
|
|
123
|
+
}
|
|
124
|
+
return changes;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Each live Agent of manifest `name` on another version than `to`, and whether a save of `to`
|
|
128
|
+
* moves it: it tracks the manifest, is not moving to `to` already, and `to` did not fail to start on it.
|
|
129
|
+
*/
|
|
130
|
+
async function agentsBehind(t, name, to) {
|
|
131
|
+
return (await t.listEnvironments())
|
|
132
|
+
.filter((agent) => agent.manifestName === name && agent.pod?.desired !== "deleted" && agent.manifestVersion !== to)
|
|
133
|
+
.map((agent) => ({
|
|
134
|
+
agent,
|
|
135
|
+
moves: agent.manifestRef === "tracking" && agent.targetVersion !== to && agent.failedVersion !== to,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Plans (or with `apply`, makes) the move of each Agent that tracks manifest `version.name` to
|
|
140
|
+
* `version` (track T3): each under its row lock, in the save's transaction.
|
|
141
|
+
*/
|
|
142
|
+
async function moveTracking(t, version, apply) {
|
|
143
|
+
const moves = { now: 0, nextStart: 0, secrets: 0, behind: 0 };
|
|
144
|
+
const now = new Date().toISOString();
|
|
145
|
+
for (const { agent: listed, moves: tracks } of await agentsBehind(t, version.name, version.version)) {
|
|
146
|
+
const agent = tracks && apply ? await t.environment(listed.id, { lock: true }) : listed;
|
|
147
|
+
if (!agent || !tracks) {
|
|
148
|
+
if (agent && agent.targetVersion !== version.version)
|
|
149
|
+
moves.behind++;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const move = await planMove(t, agent, version, "tracking");
|
|
153
|
+
if (move.plan.applies === "refused") {
|
|
154
|
+
moves.behind++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const applies = apply ? await moveAgent(t, agent, move, now) : move.plan.applies;
|
|
158
|
+
if (applies === "now")
|
|
159
|
+
moves.now++;
|
|
160
|
+
else if (applies === "next_start")
|
|
161
|
+
moves.nextStart++;
|
|
162
|
+
else if (applies === "secrets")
|
|
163
|
+
moves.secrets++;
|
|
164
|
+
}
|
|
165
|
+
return moves;
|
|
166
|
+
}
|
|
167
|
+
/** What a save of `manifest` should tell its saver, though it would be saved. */
|
|
168
|
+
function warningsOf(ctx, manifest, to, moves) {
|
|
169
|
+
const warnings = [];
|
|
170
|
+
const runtime = runtimeOf(manifest);
|
|
171
|
+
if (runtime.image !== undefined && !runtime.image.includes("@sha256:"))
|
|
172
|
+
warnings.push({
|
|
173
|
+
code: "image_tag",
|
|
174
|
+
message: `runtime.image ${runtime.image} is a tag, not a digest: each Agent pins what its first pod pulls, so two Tenants may run different bytes.`,
|
|
175
|
+
});
|
|
176
|
+
if (runtime.kind === "pod" && !ctx.pods)
|
|
177
|
+
warnings.push({
|
|
178
|
+
code: "pods_unavailable",
|
|
179
|
+
message: "runtime.kind pod needs sandbox pods, which this Tenant does not have: an Agent made from it is refused until `nylorun local sandbox enable`.",
|
|
180
|
+
});
|
|
181
|
+
if (moves === undefined)
|
|
182
|
+
return warnings;
|
|
183
|
+
const moved = moves.now + moves.nextStart + moves.secrets;
|
|
184
|
+
if (moved > 0) {
|
|
185
|
+
const parts = [
|
|
186
|
+
...(moves.now > 0 ? [`${moves.now} at once`] : []),
|
|
187
|
+
...(moves.nextStart > 0 ? [`${moves.nextStart} at their pod's next start`] : []),
|
|
188
|
+
...(moves.secrets > 0 ? [`${moves.secrets} once their required secrets are set`] : []),
|
|
189
|
+
];
|
|
190
|
+
warnings.push({
|
|
191
|
+
code: "agents_move",
|
|
192
|
+
message: `${moved} Agent${moved === 1 ? " tracks" : "s track"} ${manifest.name} and move${moved === 1 ? "s" : ""} to version ${to}: ${parts.join(", ")}.`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (moves.behind > 0)
|
|
196
|
+
warnings.push({
|
|
197
|
+
code: "agents_behind",
|
|
198
|
+
message: `${moves.behind} Agent${moves.behind === 1 ? " stays" : "s stay"} on an older version of ${manifest.name}: pinned, or ${moves.behind === 1 ? "it" : "they"} cannot move to version ${to} (move or fork ${moves.behind === 1 ? "it" : "them"}).`,
|
|
199
|
+
});
|
|
200
|
+
return warnings;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Every reason the Runtime would refuse `manifest` now, apart from the gaps between its sections:
|
|
204
|
+
* its harness section's own checks and its runtime section's (the Tenant's limits, its files).
|
|
205
|
+
*/
|
|
206
|
+
async function errorsOf(t, manifest) {
|
|
207
|
+
const loop = loopManifestOf(manifest);
|
|
208
|
+
const errors = [];
|
|
209
|
+
// The Runtime runs agents from their manifests alone (track R2): no tool may need it.
|
|
210
|
+
const code = codeToolRefusal(loop);
|
|
211
|
+
if (code)
|
|
212
|
+
errors.push(code);
|
|
213
|
+
// Tool approvals are the Tenant's tool policy, decided at the Tool Gate (R5 step 2).
|
|
214
|
+
const approval = approvalRefusal(loop);
|
|
215
|
+
if (approval)
|
|
216
|
+
errors.push(approval);
|
|
217
|
+
const declared = declaredSandboxes(loop);
|
|
218
|
+
if (declared.length > 0)
|
|
219
|
+
errors.push(`${declared.join(", ")} declares a sandbox: what an agent runs on is the manifest's runtime section.`);
|
|
220
|
+
const runtimeLimits = await readRuntimeLimits(t);
|
|
221
|
+
errors.push(...httpToolTimeoutIssues(loop, runtimeLimits.httpToolTimeoutMs));
|
|
222
|
+
const runtime = runtimeOf(manifest);
|
|
223
|
+
const config = effectiveSandboxConfig(await readSandboxConfig(t));
|
|
224
|
+
const resolved = resolveRecipe(runtime.kind, manifestRecipeRequest(manifest), config);
|
|
225
|
+
const tenantLimits = await readTenantLimits(t);
|
|
226
|
+
errors.push(...(resolved.kind === "error" ? resolved.errors : []), ...limitIssues(runtime.limits, tenantLimits), ...manifestShapeIssues({ ...runtime, ...(manifest.description === undefined ? {} : { description: manifest.description }) }, runtimeLimits), ...(await fileErrors(t, runtime.files)), ...triggerIssues(runtime.triggers, runtimeLimits));
|
|
227
|
+
// A skill's files count against the manifest's own skill limit, never above the Tenant's.
|
|
228
|
+
const limitBytes = limitsInForce(tenantLimits, runtime.limits).skills.totalBytes;
|
|
229
|
+
const { missing, oversized } = await definitionFileIssues(t, loop, limitBytes);
|
|
230
|
+
if (missing.length > 0)
|
|
231
|
+
errors.push(`The manifest names ${missing.length} file(s) the Runtime does not hold; upload each with PUT /v1/files/<sha256> first: ${missing.join(", ")}`);
|
|
232
|
+
if (oversized.length > 0)
|
|
233
|
+
errors.push(`A skill's files may hold at most ${limitBytes} bytes together: ${oversized.map((skill) => `'${skill.name}' holds ${skill.bytes}`).join(", ")}`);
|
|
234
|
+
return { errors, missing, oversized, limitBytes };
|
|
235
|
+
}
|
|
236
|
+
export async function saveAgentManifest(ctx, name, body, scope, options = {}) {
|
|
237
|
+
const { requestId: _, ...manifest } = body;
|
|
238
|
+
if (manifest.name !== name)
|
|
239
|
+
fail(400, `The manifest is named ${manifest.name}, and the path names ${name}: they must match.`);
|
|
240
|
+
const hash = agentManifestHash(manifest);
|
|
241
|
+
const gaps = sectionGaps(manifest);
|
|
242
|
+
return ctx.store.tx(async (t) => {
|
|
243
|
+
await t.lockAgentManifest(name);
|
|
244
|
+
const latest = await t.agentManifest(name);
|
|
245
|
+
if (options.ifMatch !== undefined && (latest?.version ?? 0) !== options.ifMatch)
|
|
246
|
+
fail(412, latest === undefined
|
|
247
|
+
? `Agent manifest ${name} does not exist, and If-Match named version ${options.ifMatch}.`
|
|
248
|
+
: `Agent manifest ${name} is at version ${latest.version}, not ${options.ifMatch}: someone saved it since you read it. Read it again, or save without If-Match to replace it.`, { code: "manifest_changed", details: { latest: latest?.version ?? 0 } });
|
|
249
|
+
const unchanged = latest?.hash === hash;
|
|
250
|
+
const to = unchanged ? latest.version : (latest?.version ?? 0) + 1;
|
|
251
|
+
const counts = await t.agentsOfManifest(name);
|
|
252
|
+
const checked = await errorsOf(t, manifest);
|
|
253
|
+
// The version as it would be saved, for the plan of the Agents it moves.
|
|
254
|
+
const row = unchanged
|
|
255
|
+
? latest
|
|
256
|
+
: { name, version: to, body: manifest, hash, savedBy: { actor: actorOf(scope) }, createdAt: new Date().toISOString() };
|
|
257
|
+
if (options.dryRun) {
|
|
258
|
+
const errors = [...gaps, ...checked.errors];
|
|
259
|
+
// A save with errors moves nothing; one that changes nothing moves no Agent either.
|
|
260
|
+
const moves = errors.length > 0 || unchanged ? undefined : await moveTracking(t, row, false);
|
|
261
|
+
return {
|
|
262
|
+
name,
|
|
263
|
+
from: latest?.version ?? null,
|
|
264
|
+
to,
|
|
265
|
+
unchanged,
|
|
266
|
+
changes: unchanged ? [] : sectionChanges(latest?.body, manifest),
|
|
267
|
+
warnings: warningsOf(ctx, manifest, to, moves),
|
|
268
|
+
errors,
|
|
269
|
+
behind: moves?.behind ?? (await agentsBehind(t, name, to)).length,
|
|
270
|
+
moves: { now: moves?.now ?? 0, nextStart: moves?.nextStart ?? 0, secrets: moves?.secrets ?? 0 },
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (gaps.length > 0)
|
|
274
|
+
fail(422, `Agent manifest ${name}'s harness and runtime sections do not fit: ${gaps.join("; ")}.`, {
|
|
275
|
+
code: "manifest_inconsistent",
|
|
276
|
+
details: { gaps },
|
|
277
|
+
});
|
|
278
|
+
if (checked.missing.length > 0)
|
|
279
|
+
fail(400, checked.errors.find((error) => error.startsWith("The manifest names")), {
|
|
280
|
+
code: "definition_files_missing",
|
|
281
|
+
details: { missing: checked.missing },
|
|
282
|
+
});
|
|
283
|
+
if (checked.oversized.length > 0)
|
|
284
|
+
fail(413, `This manifest allows a skill's files at most ${checked.limitBytes} bytes together: ${checked.oversized.map((skill) => `'${skill.name}' holds ${skill.bytes}`).join(", ")}`, {
|
|
285
|
+
code: "limit_exceeded",
|
|
286
|
+
details: { limitBytes: checked.limitBytes, skills: checked.oversized },
|
|
287
|
+
});
|
|
288
|
+
if (checked.errors.length > 0)
|
|
289
|
+
fail(400, checked.errors.join(" "));
|
|
290
|
+
if (unchanged)
|
|
291
|
+
return { manifest: viewOf(latest, counts), unchanged: true, warnings: warningsOf(ctx, manifest, to, undefined) };
|
|
292
|
+
await t.insertAgentManifest(row);
|
|
293
|
+
// Its files by hash, skills' and runtime files' alike, for the sweep that deletes unused ones.
|
|
294
|
+
const shas = new Set(definitionFilesOf(loopManifestOf(manifest)));
|
|
295
|
+
for (const file of runtimeOf(manifest).files ?? [])
|
|
296
|
+
if ("files" in file)
|
|
297
|
+
for (const sha of Object.values(file.files))
|
|
298
|
+
shas.add(sha);
|
|
299
|
+
if (shas.size > 0)
|
|
300
|
+
await t.insertDefinitionFileUses(manifestFileOwner(name), hash, [...shas]);
|
|
301
|
+
// Every Agent that tracks the manifest moves to the new version (track T3).
|
|
302
|
+
const moves = await moveTracking(t, row, true);
|
|
303
|
+
return { manifest: viewOf(row, await t.agentsOfManifest(name)), unchanged: false, warnings: warningsOf(ctx, manifest, to, moves) };
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/** `GET /v1/agent-manifests/{name}` (the latest) and `.../versions/{n}`. */
|
|
307
|
+
export async function getAgentManifest(ctx, name, version) {
|
|
308
|
+
return ctx.store.tx(async (t) => {
|
|
309
|
+
const row = (await t.agentManifest(name, version)) ?? notFound(name, version);
|
|
310
|
+
return viewOf(row, await t.agentsOfManifest(name));
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/** `GET /v1/agent-manifests`: each manifest's latest version. */
|
|
314
|
+
export async function listAgentManifests(ctx) {
|
|
315
|
+
return ctx.store.tx(async (t) => {
|
|
316
|
+
const manifests = [];
|
|
317
|
+
for (const row of await t.listAgentManifests())
|
|
318
|
+
manifests.push(viewOf(row, await t.agentsOfManifest(row.name)));
|
|
319
|
+
return { manifests };
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/** `GET /v1/agent-manifests/{name}/versions`: every version, newest first. */
|
|
323
|
+
export async function agentManifestVersions(ctx, name) {
|
|
324
|
+
return ctx.store.tx(async (t) => {
|
|
325
|
+
const rows = await t.agentManifestVersions(name);
|
|
326
|
+
if (rows.length === 0)
|
|
327
|
+
notFound(name);
|
|
328
|
+
const counts = await t.agentsOfManifest(name);
|
|
329
|
+
return {
|
|
330
|
+
name,
|
|
331
|
+
latest: rows[0].version,
|
|
332
|
+
versions: rows.map((row) => ({
|
|
333
|
+
version: row.version,
|
|
334
|
+
hash: row.hash,
|
|
335
|
+
savedBy: row.savedBy,
|
|
336
|
+
createdAt: row.createdAt,
|
|
337
|
+
agents: counts.get(row.version) ?? 0,
|
|
338
|
+
})),
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* `DELETE /v1/agent-manifests/{name}`: deletes every version, refused while any Agent was made
|
|
344
|
+
* from one (`409 manifest_in_use`): the Agent names the version it runs.
|
|
345
|
+
*/
|
|
346
|
+
export async function deleteAgentManifest(ctx, name) {
|
|
347
|
+
return ctx.store.tx(async (t) => {
|
|
348
|
+
await t.lockAgentManifest(name);
|
|
349
|
+
let made = 0;
|
|
350
|
+
for (const n of (await t.agentsOfManifest(name)).values())
|
|
351
|
+
made += n;
|
|
352
|
+
if (made > 0)
|
|
353
|
+
fail(409, `${made} Agent(s) were made from agent manifest ${name}: delete them first, or keep the manifest.`, {
|
|
354
|
+
code: "manifest_in_use",
|
|
355
|
+
details: { agents: made },
|
|
356
|
+
});
|
|
357
|
+
return { name, deleted: (await t.deleteAgentManifest(name)) > 0 };
|
|
358
|
+
});
|
|
359
|
+
}
|