@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,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Agent's versions (track T3, D73): which version of its agent manifest it runs, how it moves
|
|
3
|
+
* to another, and which version a session's turn runs.
|
|
4
|
+
*
|
|
5
|
+
* - **One version at a time.** An Agent runs one version of its manifest (`manifestVersion`),
|
|
6
|
+
* both sections together: the loop manifest its harness section makes, and the runtime its
|
|
7
|
+
* compute applies (`applied`). A session pins all of it (`pinOf`) when it opens.
|
|
8
|
+
* - **A move** (`moveAgent`: `PUT /v1/agents/{id}`, and a save for each Agent that tracks the
|
|
9
|
+
* manifest) changes no data: its id, disk, vault and sessions stay. A version whose runtime a
|
|
10
|
+
* pod applies only when it starts (`START_FIELDS`) makes the Agent outdated (`targetVersion`,
|
|
11
|
+
* with the target's runtime in `recipe`) until its pod's next start switches it (`switchTo`);
|
|
12
|
+
* any other applies at once. A version that adds a required secret with no value the version
|
|
13
|
+
* can use waits for it (`resumeMove` after each secret write). Its manifest's name, kind,
|
|
14
|
+
* harness and storage are fixed for its life (`409 agent_fixed_field`).
|
|
15
|
+
* - **The start check** (pods): a start on a new version records what ran before
|
|
16
|
+
* (`startCheck`) until the new pod's harness joins (`passStartCheck`). A failed setup, a failed
|
|
17
|
+
* pod or a start past the Host's `tenant.upgradeStartMs` goes back to it (`revertStart`): the
|
|
18
|
+
* definition, not the data. The version is the Agent's `failedVersion`, which a tracking Agent
|
|
19
|
+
* does not move to again, until a start on a new version succeeds. While a pod waits to start
|
|
20
|
+
* on a new version, it idles at most `tenant.upgradeIdleMs` (`idleMsOf`).
|
|
21
|
+
* - **A turn takes the Agent's version.** A turn's first segment re-pins its session to the
|
|
22
|
+
* version the Agent runs when that changed since the session's last turn (`repinTurn`), before
|
|
23
|
+
* anything of the turn has run: no effect is journaled for it yet. The turn keeps that version
|
|
24
|
+
* through every later segment, approval and rollover. A turn whose message named a variant,
|
|
25
|
+
* and a flow's sessions, keep the version they pinned.
|
|
26
|
+
*
|
|
27
|
+
* Re-pinning at the first segment, not when the message is accepted, keeps a turn that waits for
|
|
28
|
+
* its compute to start on a version the Agent has at that start.
|
|
29
|
+
*/
|
|
30
|
+
import type { AgentMovePlan, EnvironmentRecipe } from "@nylorun/core/contracts";
|
|
31
|
+
import { type SandboxManifest } from "@nylorun/core/define";
|
|
32
|
+
import type { AgentManifestRow, Environment, Tx } from "../store/types.js";
|
|
33
|
+
import type { TenantSandboxConfig } from "../sandbox/tenant-config.js";
|
|
34
|
+
import { type Session, type TenantContext } from "./context.js";
|
|
35
|
+
/** What a session pins of the version its Agent runs. */
|
|
36
|
+
export interface SessionPin {
|
|
37
|
+
/** The loop manifest the version's harness section makes (`loopManifestOf`). */
|
|
38
|
+
readonly manifest: any;
|
|
39
|
+
readonly manifestHash: string;
|
|
40
|
+
readonly definition: {
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly version: number;
|
|
43
|
+
};
|
|
44
|
+
/** The version's `harness.loop`, when it sets one. */
|
|
45
|
+
readonly loop?: Record<string, unknown>;
|
|
46
|
+
/** The harness that runs its turns: the runtime's, or ours for a flow. */
|
|
47
|
+
readonly harness: string;
|
|
48
|
+
/** The sandbox spec of the runtime the Agent's compute runs (kinds `virtual` and `pod`). */
|
|
49
|
+
readonly sandbox?: SandboxManifest;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The pin of the version Agent `agent` runs: its manifest version's loop manifest, and the
|
|
53
|
+
* harness and sandbox spec of the runtime its compute applies (`applied`). `409 agent_unavailable`
|
|
54
|
+
* when that version is gone.
|
|
55
|
+
*/
|
|
56
|
+
export declare function pinOf(t: Tx, agent: Environment): Promise<{
|
|
57
|
+
pin: SessionPin;
|
|
58
|
+
version: AgentManifestRow;
|
|
59
|
+
}>;
|
|
60
|
+
/** Writes `pin` onto `session` (the caller saves it). */
|
|
61
|
+
export declare function applyPin(session: Session, pin: SessionPin): void;
|
|
62
|
+
/**
|
|
63
|
+
* Re-pins `session` (in the segment's owned transaction) to the version its Agent runs, when its
|
|
64
|
+
* active turn is about to run its first segment and nothing of the turn has run yet: the turn
|
|
65
|
+
* then runs that version. Returns whether it changed the session (the caller saves it).
|
|
66
|
+
*
|
|
67
|
+
* Leaves the session as it is for a flow and a flow's linked session, a turn whose message named
|
|
68
|
+
* a variant, a later segment, a turn with any journaled effect, and a version whose harness is
|
|
69
|
+
* not the session's (an Agent's harness is fixed for its life, D73).
|
|
70
|
+
*/
|
|
71
|
+
export declare function repinTurn(t: Tx, session: Session): Promise<boolean>;
|
|
72
|
+
/** What a move of `agent` to `target` would do, and the target's runtime resolved. */
|
|
73
|
+
export interface Move {
|
|
74
|
+
plan: AgentMovePlan;
|
|
75
|
+
target: AgentManifestRow;
|
|
76
|
+
recipe: EnvironmentRecipe;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The plan of moving `agent` to version `target` of its manifest, named `tracking` after it:
|
|
80
|
+
* the target's runtime resolved against the Tenant's limits (a `400` when it no longer fits),
|
|
81
|
+
* what it changes, the fixed fields it would change, and the required secrets it would wait for.
|
|
82
|
+
*/
|
|
83
|
+
export declare function planMove(t: Tx, agent: Environment, target: AgentManifestRow, tracking: Environment["manifestRef"]): Promise<Move>;
|
|
84
|
+
/** The `409 agent_fixed_field` of a move that would change what is fixed for an Agent's life. */
|
|
85
|
+
export declare function refuseFixed(agentId: string, plan: Pick<AgentMovePlan, "refusals" | "to" | "from">): never;
|
|
86
|
+
/**
|
|
87
|
+
* Moves `agent` (locked, live) per `move` (`planMove`), in the caller's transaction. A move
|
|
88
|
+
* replaces a pending one: the runtime it waited to apply is dropped first. `none` (back to what it
|
|
89
|
+
* runs) only clears the target; `now` switches; `next_start` keeps the target's runtime in
|
|
90
|
+
* `recipe` for the pod's next start (`switchTo`); `secrets` names the target and waits.
|
|
91
|
+
* Returns when it applies.
|
|
92
|
+
*/
|
|
93
|
+
export declare function moveAgent(t: Tx, agent: Environment, move: Move, now: string): Promise<AgentMovePlan["applies"]>;
|
|
94
|
+
/**
|
|
95
|
+
* After a secret write: an Agent that waits for its secrets moves on once the target's required
|
|
96
|
+
* slots are filled (`moveAgent` again, to the same target). In the caller's transaction.
|
|
97
|
+
*/
|
|
98
|
+
export declare function resumeMove(t: Tx, agent: Environment, now: string): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* At a pod Agent's start (`wantRunning`, `newVolume`): switches `agent` (locked) to the version it
|
|
101
|
+
* moves to when its runtime waits for this start. Applies the target's runtime and version, drops
|
|
102
|
+
* the pinned image digest when the image changed, and records what ran before (`startCheck`)
|
|
103
|
+
* until the new pod's harness joins. Returns whether it switched.
|
|
104
|
+
*/
|
|
105
|
+
export declare function switchTo(t: Tx, agent: Environment, now: string): Promise<boolean>;
|
|
106
|
+
/** What an outdated Agent waits for: its secrets, or its pod's next start; `null` when it is not. */
|
|
107
|
+
export declare function waitingFor(agent: Pick<Environment, "targetVersion" | "appliedVersion" | "version">): "secrets" | "start" | null;
|
|
108
|
+
/** How long pod Agent `agent` may idle before it stops: shorter while a start would apply a new version. */
|
|
109
|
+
export declare function idleMsOf(config: TenantSandboxConfig, agent: Environment): number;
|
|
110
|
+
/**
|
|
111
|
+
* The harness of pod Agent `id` joined at host epoch `epoch` (its `hello`): a start on a new
|
|
112
|
+
* version succeeded, setup included. Clears the start check and the failed version.
|
|
113
|
+
*/
|
|
114
|
+
export declare function passStartCheck(ctx: TenantContext, id: string, epoch: number): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Pod Agent `agent` (locked) did not start on the version it switched to (`reason`): back to what
|
|
117
|
+
* it ran before (`startCheck.from`), its definition and not its data, with that version as its
|
|
118
|
+
* `failedVersion`, and its pod asked to run again. A turn waiting for the start runs on the
|
|
119
|
+
* version it went back to. Returns whether there was a start to go back from.
|
|
120
|
+
*/
|
|
121
|
+
export declare function revertStart(t: Tx, agent: Environment, reason: string, now: string): Promise<boolean>;
|
|
122
|
+
/**
|
|
123
|
+
* The Tenant sweep: each pod Agent whose start on a new version has taken longer than the Host's
|
|
124
|
+
* `tenant.upgradeStartMs` goes back to the version it ran (`revertStart`).
|
|
125
|
+
*/
|
|
126
|
+
export declare function sweepStartChecks(ctx: TenantContext, now?: number): Promise<void>;
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { hashManifest } from "@nylorun/core/compatibility";
|
|
2
|
+
import { loopManifestOf, runtimeOf } from "@nylorun/core/define";
|
|
3
|
+
import { isWorkflowManifest } from "../core/flow-host.js";
|
|
4
|
+
import { canonical } from "../store/canonical.js";
|
|
5
|
+
import { resolveRecipe, sandboxSpecOf } from "../sandbox/resolve.js";
|
|
6
|
+
import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
|
|
7
|
+
import { HARNESS_ID } from "../sandbox/placement.js";
|
|
8
|
+
import { slotHolds } from "../vault/service.js";
|
|
9
|
+
import { manifestRecipeRequest, sectionChanges } from "./agent-manifests.js";
|
|
10
|
+
import { podLifecycleConfig } from "../sandbox/pods/spec.js";
|
|
11
|
+
import { hostTuning } from "../tuning.js";
|
|
12
|
+
import { harnessOf } from "./context.js";
|
|
13
|
+
import { fail } from "./http.js";
|
|
14
|
+
import { limitIssues, manifestShapeIssues, readTenantLimits } from "./limits.js";
|
|
15
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
16
|
+
import { mergeAgentTriggers } from "./triggers.js";
|
|
17
|
+
/**
|
|
18
|
+
* The pin of the version Agent `agent` runs: its manifest version's loop manifest, and the
|
|
19
|
+
* harness and sandbox spec of the runtime its compute applies (`applied`). `409 agent_unavailable`
|
|
20
|
+
* when that version is gone.
|
|
21
|
+
*/
|
|
22
|
+
export async function pinOf(t, agent) {
|
|
23
|
+
const version = (await t.agentManifest(agent.manifestName, agent.manifestVersion)) ??
|
|
24
|
+
fail(409, `Agent ${agent.id}'s manifest ${agent.manifestName}@${agent.manifestVersion} is gone.`, {
|
|
25
|
+
code: "agent_unavailable",
|
|
26
|
+
});
|
|
27
|
+
const manifest = loopManifestOf(version.body);
|
|
28
|
+
const config = await readSandboxConfig(t);
|
|
29
|
+
const loop = version.body.harness.loop;
|
|
30
|
+
const sandbox = agent.kind === "none" ? undefined : sandboxSpecOf(agent.applied, effectiveSandboxConfig(config).limits.idle);
|
|
31
|
+
return {
|
|
32
|
+
version,
|
|
33
|
+
pin: {
|
|
34
|
+
manifest,
|
|
35
|
+
manifestHash: hashManifest(manifest),
|
|
36
|
+
definition: { name: version.name, version: version.version },
|
|
37
|
+
...(loop === undefined ? {} : { loop }),
|
|
38
|
+
harness: isWorkflowManifest(manifest) ? HARNESS_ID : agent.applied.harness,
|
|
39
|
+
...(sandbox === undefined ? {} : { sandbox }),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Writes `pin` onto `session` (the caller saves it). */
|
|
44
|
+
export function applyPin(session, pin) {
|
|
45
|
+
session.manifest = pin.manifest;
|
|
46
|
+
session.manifestHash = pin.manifestHash;
|
|
47
|
+
session.definition = { ...pin.definition };
|
|
48
|
+
if (pin.loop === undefined)
|
|
49
|
+
delete session.loop;
|
|
50
|
+
else
|
|
51
|
+
session.loop = pin.loop;
|
|
52
|
+
session.harness = pin.harness;
|
|
53
|
+
if (pin.sandbox === undefined)
|
|
54
|
+
delete session.sandbox;
|
|
55
|
+
else
|
|
56
|
+
session.sandbox = pin.sandbox;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Re-pins `session` (in the segment's owned transaction) to the version its Agent runs, when its
|
|
60
|
+
* active turn is about to run its first segment and nothing of the turn has run yet: the turn
|
|
61
|
+
* then runs that version. Returns whether it changed the session (the caller saves it).
|
|
62
|
+
*
|
|
63
|
+
* Leaves the session as it is for a flow and a flow's linked session, a turn whose message named
|
|
64
|
+
* a variant, a later segment, a turn with any journaled effect, and a version whose harness is
|
|
65
|
+
* not the session's (an Agent's harness is fixed for its life, D73).
|
|
66
|
+
*/
|
|
67
|
+
export async function repinTurn(t, session) {
|
|
68
|
+
const id = session.environmentId;
|
|
69
|
+
const checkpoint = session.checkpoint;
|
|
70
|
+
if (id === undefined || session.definition === undefined || session.flowRootId !== undefined)
|
|
71
|
+
return false;
|
|
72
|
+
if (isWorkflowManifest(session.manifest) || session.activeTurnId === null)
|
|
73
|
+
return false;
|
|
74
|
+
if (!checkpoint || checkpoint.segment !== 0 || checkpoint.turnId !== session.activeTurnId)
|
|
75
|
+
return false;
|
|
76
|
+
// A variant the message named (a turn-manifest variant) is validated against the old pin.
|
|
77
|
+
if (checkpoint.manifestHash !== session.manifestHash)
|
|
78
|
+
return false;
|
|
79
|
+
const agent = await t.environment(id);
|
|
80
|
+
if (!agent)
|
|
81
|
+
return false;
|
|
82
|
+
if (session.definition.name === agent.manifestName && session.definition.version === agent.manifestVersion)
|
|
83
|
+
return false;
|
|
84
|
+
if ((await t.effectsForTurn(session.id, session.activeTurnId)).length > 0)
|
|
85
|
+
return false;
|
|
86
|
+
const { pin } = await pinOf(t, agent);
|
|
87
|
+
if (pin.harness !== harnessOf(session))
|
|
88
|
+
return false;
|
|
89
|
+
const changed = pin.manifestHash !== session.manifestHash;
|
|
90
|
+
applyPin(session, pin);
|
|
91
|
+
session.environmentVersion = agent.version;
|
|
92
|
+
session.checkpoint = { ...session.checkpoint, manifestHash: pin.manifestHash };
|
|
93
|
+
// MCP discovery is per loop manifest: the next one is the new version's.
|
|
94
|
+
if (changed) {
|
|
95
|
+
delete session.mcpSnapshot;
|
|
96
|
+
delete session.mcpDiagnostics;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
/** The runtime fields a pod applies only when it starts: a change waits for its next start. */
|
|
101
|
+
const START_FIELDS = ["image", "resources", "network", "setup", "files"];
|
|
102
|
+
/** Whether pod Agent `agent` applies `recipe` only at its next start. */
|
|
103
|
+
function needsStart(agent, recipe) {
|
|
104
|
+
return agent.kind === "pod" && START_FIELDS.some((field) => canonical(recipe[field]) !== canonical(agent.applied[field]));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The plan of moving `agent` to version `target` of its manifest, named `tracking` after it:
|
|
108
|
+
* the target's runtime resolved against the Tenant's limits (a `400` when it no longer fits),
|
|
109
|
+
* what it changes, the fixed fields it would change, and the required secrets it would wait for.
|
|
110
|
+
*/
|
|
111
|
+
export async function planMove(t, agent, target, tracking) {
|
|
112
|
+
const runtime = runtimeOf(target.body);
|
|
113
|
+
const recipe = target.version === agent.manifestVersion ? agent.applied : await resolvedRuntime(t, target);
|
|
114
|
+
const refusals = [];
|
|
115
|
+
const fixed = (field, from, to) => {
|
|
116
|
+
if (canonical(from) !== canonical(to))
|
|
117
|
+
refusals.push({ field, ...(from === undefined ? {} : { from }), ...(to === undefined ? {} : { to }) });
|
|
118
|
+
};
|
|
119
|
+
fixed("kind", agent.kind, runtime.kind);
|
|
120
|
+
fixed("harness", agent.applied.harness, recipe.harness);
|
|
121
|
+
fixed("storage", agent.applied.storage, recipe.storage);
|
|
122
|
+
const { missing, dormant } = await secretsOf(t, agent.id, recipe.secrets);
|
|
123
|
+
const current = await t.agentManifest(agent.manifestName, agent.manifestVersion);
|
|
124
|
+
const from = agent.manifestVersion;
|
|
125
|
+
const applies = refusals.length > 0
|
|
126
|
+
? "refused"
|
|
127
|
+
: target.version === from
|
|
128
|
+
? "none"
|
|
129
|
+
: missing.length > 0
|
|
130
|
+
? "secrets"
|
|
131
|
+
: needsStart(agent, recipe)
|
|
132
|
+
? "next_start"
|
|
133
|
+
: "now";
|
|
134
|
+
return {
|
|
135
|
+
target,
|
|
136
|
+
recipe,
|
|
137
|
+
plan: {
|
|
138
|
+
agent: agent.id,
|
|
139
|
+
manifest: tracking === "tracking" ? target.name : `${target.name}@${target.version}`,
|
|
140
|
+
from,
|
|
141
|
+
to: target.version,
|
|
142
|
+
tracking,
|
|
143
|
+
applies,
|
|
144
|
+
changes: target.version === from ? [] : sectionChanges(current?.body, target.body),
|
|
145
|
+
missingSecrets: missing,
|
|
146
|
+
dormantSecrets: dormant,
|
|
147
|
+
refusals,
|
|
148
|
+
sessions: (await t.sessionsOnEnvironment(agent.id)).length,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Version `target`'s runtime resolved against the Tenant's limits now, as a create resolves it:
|
|
154
|
+
* `400` when it no longer fits them. (What an Agent runs already stays, whatever its limits.)
|
|
155
|
+
*/
|
|
156
|
+
async function resolvedRuntime(t, target) {
|
|
157
|
+
const runtime = runtimeOf(target.body);
|
|
158
|
+
const resolved = resolveRecipe(runtime.kind, manifestRecipeRequest(target.body), effectiveSandboxConfig(await readSandboxConfig(t)));
|
|
159
|
+
const errors = [
|
|
160
|
+
...(resolved.kind === "error" ? resolved.errors : []),
|
|
161
|
+
...limitIssues(runtime.limits, await readTenantLimits(t)),
|
|
162
|
+
...manifestShapeIssues(runtime, await readRuntimeLimits(t)),
|
|
163
|
+
];
|
|
164
|
+
if (resolved.kind === "error" || errors.length > 0)
|
|
165
|
+
return fail(400, `${target.name}@${target.version} does not fit this Tenant's limits now: ${errors.join(" ")}`);
|
|
166
|
+
return resolved.recipe;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Of Agent `id`'s vault and the secret slots `slots` of a version: each required slot with no
|
|
170
|
+
* value that fills it (`slotHolds`), and each secret that fills none, which stays dormant.
|
|
171
|
+
*/
|
|
172
|
+
async function secretsOf(t, id, slots) {
|
|
173
|
+
const vault = await t.vaultOfEnvironment(id);
|
|
174
|
+
const rows = vault ? (await t.credentialsForVault(vault.id)).filter((row) => row.type !== "model") : [];
|
|
175
|
+
if (slots === undefined)
|
|
176
|
+
return { missing: [], dormant: [] };
|
|
177
|
+
const fills = (name) => rows.some((row) => row.name === name && slotHolds(slots[name], row));
|
|
178
|
+
return {
|
|
179
|
+
missing: Object.entries(slots)
|
|
180
|
+
.filter(([name, slot]) => slot.required === true && !fills(name))
|
|
181
|
+
.map(([name]) => name)
|
|
182
|
+
.sort(),
|
|
183
|
+
dormant: rows
|
|
184
|
+
.filter((row) => slots[row.name] === undefined || !slotHolds(slots[row.name], row))
|
|
185
|
+
.map((row) => row.name)
|
|
186
|
+
.sort(),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** The `409 agent_fixed_field` of a move that would change what is fixed for an Agent's life. */
|
|
190
|
+
export function refuseFixed(agentId, plan) {
|
|
191
|
+
return fail(409, `Agent ${agentId}'s ${plan.refusals.map((refusal) => refusal.field).join(", ")} ${plan.refusals.length === 1 ? "is" : "are"} fixed for its life, and version ${plan.to} changes ${plan.refusals.length === 1 ? "it" : "them"}: fork it into a new Agent with POST /v1/agents/${encodeURIComponent(agentId)}/fork.`, { code: "agent_fixed_field", details: { fields: plan.refusals, from: plan.from, to: plan.to } });
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Moves `agent` (locked, live) per `move` (`planMove`), in the caller's transaction. A move
|
|
195
|
+
* replaces a pending one: the runtime it waited to apply is dropped first. `none` (back to what it
|
|
196
|
+
* runs) only clears the target; `now` switches; `next_start` keeps the target's runtime in
|
|
197
|
+
* `recipe` for the pod's next start (`switchTo`); `secrets` names the target and waits.
|
|
198
|
+
* Returns when it applies.
|
|
199
|
+
*/
|
|
200
|
+
export async function moveAgent(t, agent, move, now) {
|
|
201
|
+
const { plan, target, recipe } = move;
|
|
202
|
+
if (plan.applies === "refused")
|
|
203
|
+
refuseFixed(agent.id, plan);
|
|
204
|
+
// Asked again while it waits for secrets: nothing new.
|
|
205
|
+
if (plan.applies === "secrets" && agent.targetVersion === plan.to && agent.appliedVersion === agent.version) {
|
|
206
|
+
if (agent.manifestRef !== plan.tracking)
|
|
207
|
+
await t.updateEnvironment(agent.id, { manifestRef: plan.tracking }, now);
|
|
208
|
+
return plan.applies;
|
|
209
|
+
}
|
|
210
|
+
const pending = agent.appliedVersion !== agent.version;
|
|
211
|
+
// What it runs, with no pending runtime: the base every move starts from.
|
|
212
|
+
const version = pending ? agent.version + 1 : agent.version;
|
|
213
|
+
const base = pending ? { recipe: agent.applied, version, appliedVersion: version } : {};
|
|
214
|
+
const ref = { manifestRef: plan.tracking };
|
|
215
|
+
switch (plan.applies) {
|
|
216
|
+
case "none":
|
|
217
|
+
await t.updateEnvironment(agent.id, { ...base, ...ref, targetVersion: null }, now);
|
|
218
|
+
break;
|
|
219
|
+
case "secrets":
|
|
220
|
+
await t.updateEnvironment(agent.id, { ...base, ...ref, targetVersion: plan.to }, now);
|
|
221
|
+
await t.agentEvent(agent.id, "agent.outdated", { from: plan.from, to: plan.to, waitingFor: "secrets" });
|
|
222
|
+
break;
|
|
223
|
+
case "next_start":
|
|
224
|
+
// `applied` stays what runs: `recipe` differs from it until the start applies it.
|
|
225
|
+
await t.updateEnvironment(agent.id, { ...ref, recipe, version: agent.version + 1, targetVersion: plan.to }, now);
|
|
226
|
+
await t.agentEvent(agent.id, "agent.outdated", { from: plan.from, to: plan.to, waitingFor: "start" });
|
|
227
|
+
// A running pod stops sooner once idle, so its next start applies the version.
|
|
228
|
+
if (agent.pod?.desired === "running") {
|
|
229
|
+
const idleMs = Math.min(podLifecycleConfig(await readSandboxConfig(t), recipe).idleMs, hostTuning().tenant.upgradeIdleMs);
|
|
230
|
+
const at = Math.max(Date.parse(agent.pod.lastActiveAt ?? now) + idleMs, Date.parse(now));
|
|
231
|
+
await t.signalSandbox(agent.id, { kind: "arm", timer: "idle", at });
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
case "now":
|
|
235
|
+
await t.updateEnvironment(agent.id, {
|
|
236
|
+
...ref,
|
|
237
|
+
recipe,
|
|
238
|
+
applied: recipe,
|
|
239
|
+
version: version + 1,
|
|
240
|
+
appliedVersion: version + 1,
|
|
241
|
+
manifestVersion: target.version,
|
|
242
|
+
manifestHash: target.hash,
|
|
243
|
+
targetVersion: null,
|
|
244
|
+
failedVersion: null,
|
|
245
|
+
}, now);
|
|
246
|
+
await t.agentEvent(agent.id, "agent.version_changed", { from: plan.from, to: plan.to });
|
|
247
|
+
await mergeAgentTriggers(t, { ...agent, manifestVersion: target.version }, agent.manifestVersion);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
return plan.applies;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* After a secret write: an Agent that waits for its secrets moves on once the target's required
|
|
254
|
+
* slots are filled (`moveAgent` again, to the same target). In the caller's transaction.
|
|
255
|
+
*/
|
|
256
|
+
export async function resumeMove(t, agent, now) {
|
|
257
|
+
if (agent.targetVersion === undefined || agent.appliedVersion !== agent.version)
|
|
258
|
+
return;
|
|
259
|
+
const target = await t.agentManifest(agent.manifestName, agent.targetVersion);
|
|
260
|
+
if (!target)
|
|
261
|
+
return void (await t.updateEnvironment(agent.id, { targetVersion: null }, now));
|
|
262
|
+
const move = await planMove(t, agent, target, agent.manifestRef);
|
|
263
|
+
if (move.plan.applies === "refused")
|
|
264
|
+
return;
|
|
265
|
+
await moveAgent(t, agent, move, now);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* At a pod Agent's start (`wantRunning`, `newVolume`): switches `agent` (locked) to the version it
|
|
269
|
+
* moves to when its runtime waits for this start. Applies the target's runtime and version, drops
|
|
270
|
+
* the pinned image digest when the image changed, and records what ran before (`startCheck`)
|
|
271
|
+
* until the new pod's harness joins. Returns whether it switched.
|
|
272
|
+
*/
|
|
273
|
+
export async function switchTo(t, agent, now) {
|
|
274
|
+
if (agent.targetVersion === undefined || agent.appliedVersion === agent.version)
|
|
275
|
+
return false;
|
|
276
|
+
const target = await t.agentManifest(agent.manifestName, agent.targetVersion);
|
|
277
|
+
if (!target)
|
|
278
|
+
return false;
|
|
279
|
+
// A start that replaces one still being checked goes back as far as that one would.
|
|
280
|
+
const from = agent.startCheck?.from ?? {
|
|
281
|
+
manifestVersion: agent.manifestVersion,
|
|
282
|
+
manifestHash: agent.manifestHash,
|
|
283
|
+
applied: agent.applied,
|
|
284
|
+
...(agent.pod?.imageDigest === undefined ? {} : { imageDigest: agent.pod.imageDigest }),
|
|
285
|
+
};
|
|
286
|
+
await t.updateEnvironment(agent.id, {
|
|
287
|
+
applied: agent.recipe,
|
|
288
|
+
appliedVersion: agent.version,
|
|
289
|
+
manifestVersion: target.version,
|
|
290
|
+
manifestHash: target.hash,
|
|
291
|
+
targetVersion: null,
|
|
292
|
+
...(agent.pod ? { startCheck: { from, startedAt: now } } : {}),
|
|
293
|
+
}, now);
|
|
294
|
+
if (agent.pod?.imageDigest !== undefined && canonical(agent.recipe.image) !== canonical(agent.applied.image))
|
|
295
|
+
await t.updateEnvironmentPod(agent.id, { imageDigest: null }, now);
|
|
296
|
+
await t.agentEvent(agent.id, "agent.version_changed", { from: agent.manifestVersion, to: target.version });
|
|
297
|
+
await mergeAgentTriggers(t, { ...agent, manifestVersion: target.version }, agent.manifestVersion);
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
/** What an outdated Agent waits for: its secrets, or its pod's next start; `null` when it is not. */
|
|
301
|
+
export function waitingFor(agent) {
|
|
302
|
+
if (agent.targetVersion === undefined)
|
|
303
|
+
return null;
|
|
304
|
+
return agent.appliedVersion === agent.version ? "secrets" : "start";
|
|
305
|
+
}
|
|
306
|
+
/** How long pod Agent `agent` may idle before it stops: shorter while a start would apply a new version. */
|
|
307
|
+
export function idleMsOf(config, agent) {
|
|
308
|
+
const idleMs = podLifecycleConfig(config, agent.recipe).idleMs;
|
|
309
|
+
return waitingFor(agent) === "start" ? Math.min(idleMs, hostTuning().tenant.upgradeIdleMs) : idleMs;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* The harness of pod Agent `id` joined at host epoch `epoch` (its `hello`): a start on a new
|
|
313
|
+
* version succeeded, setup included. Clears the start check and the failed version.
|
|
314
|
+
*/
|
|
315
|
+
export async function passStartCheck(ctx, id, epoch) {
|
|
316
|
+
await ctx.store.tx(async (t) => {
|
|
317
|
+
const agent = await t.environment(id, { lock: true });
|
|
318
|
+
if (!agent?.startCheck || agent.pod?.hostEpoch !== epoch)
|
|
319
|
+
return;
|
|
320
|
+
await t.updateEnvironment(id, { startCheck: null, failedVersion: null }, new Date().toISOString());
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Pod Agent `agent` (locked) did not start on the version it switched to (`reason`): back to what
|
|
325
|
+
* it ran before (`startCheck.from`), its definition and not its data, with that version as its
|
|
326
|
+
* `failedVersion`, and its pod asked to run again. A turn waiting for the start runs on the
|
|
327
|
+
* version it went back to. Returns whether there was a start to go back from.
|
|
328
|
+
*/
|
|
329
|
+
export async function revertStart(t, agent, reason, now) {
|
|
330
|
+
const check = agent.startCheck;
|
|
331
|
+
const pod = agent.pod;
|
|
332
|
+
if (!check || !pod)
|
|
333
|
+
return false;
|
|
334
|
+
const version = agent.version + 1;
|
|
335
|
+
await t.updateEnvironment(agent.id, {
|
|
336
|
+
recipe: check.from.applied,
|
|
337
|
+
applied: check.from.applied,
|
|
338
|
+
version,
|
|
339
|
+
appliedVersion: version,
|
|
340
|
+
manifestVersion: check.from.manifestVersion,
|
|
341
|
+
manifestHash: check.from.manifestHash,
|
|
342
|
+
targetVersion: null,
|
|
343
|
+
failedVersion: agent.manifestVersion,
|
|
344
|
+
startCheck: null,
|
|
345
|
+
}, now);
|
|
346
|
+
await t.updateEnvironmentPod(agent.id, {
|
|
347
|
+
imageDigest: check.from.imageDigest ?? null,
|
|
348
|
+
desired: "running",
|
|
349
|
+
rev: pod.rev + 1,
|
|
350
|
+
observed: "creating",
|
|
351
|
+
startedAt: now,
|
|
352
|
+
lastActiveAt: now,
|
|
353
|
+
reason: null,
|
|
354
|
+
}, now);
|
|
355
|
+
await t.agentEvent(agent.id, "agent.start_failed", {
|
|
356
|
+
from: check.from.manifestVersion,
|
|
357
|
+
to: agent.manifestVersion,
|
|
358
|
+
reason,
|
|
359
|
+
reverted: true,
|
|
360
|
+
});
|
|
361
|
+
await t.agentEvent(agent.id, "agent.version_changed", { from: agent.manifestVersion, to: check.from.manifestVersion });
|
|
362
|
+
await mergeAgentTriggers(t, { ...agent, manifestVersion: check.from.manifestVersion }, agent.manifestVersion);
|
|
363
|
+
await t.signalSandbox(agent.id, { kind: "reconcile" });
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* The Tenant sweep: each pod Agent whose start on a new version has taken longer than the Host's
|
|
368
|
+
* `tenant.upgradeStartMs` goes back to the version it ran (`revertStart`).
|
|
369
|
+
*/
|
|
370
|
+
export async function sweepStartChecks(ctx, now = Date.now()) {
|
|
371
|
+
const limit = hostTuning().tenant.upgradeStartMs;
|
|
372
|
+
const due = (agent) => agent.startCheck !== undefined && Date.parse(agent.startCheck.startedAt) + limit <= now;
|
|
373
|
+
for (const candidate of (await ctx.store.tx((t) => t.listEnvironments())).filter(due)) {
|
|
374
|
+
try {
|
|
375
|
+
await ctx.store.tx(async (t) => {
|
|
376
|
+
const agent = await t.environment(candidate.id, { lock: true });
|
|
377
|
+
if (!agent || !due(agent))
|
|
378
|
+
return;
|
|
379
|
+
await revertStart(t, agent, `The pod did not start within ${Math.round(limit / 1000)} s`, new Date(now).toISOString());
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
ctx.config.logger.warn("agent start check not swept", {
|
|
384
|
+
environmentId: candidate.id,
|
|
385
|
+
message: error instanceof Error ? error.message : String(error),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|