@nylorun/runtime 0.23.0-beta → 0.24.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 +148 -0
- package/README.md +79 -28
- package/dist/adapters/execution/restate.js +1 -1
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/api/a2a/endpoint.js +5 -1
- package/dist/api/a2a/routes.d.ts +2 -0
- package/dist/api/a2a/routes.js +2 -2
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +4 -2
- package/dist/api/ag-ui/history.js +4 -1
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +11 -10
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +4953 -1204
- package/dist/api/components.js +25 -16
- package/dist/api/gate/keys.js +7 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +39 -3
- package/dist/api/http/app.js +4 -4
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +283 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +45 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +11 -6
- package/dist/api/http/routes/tenant.js +32 -10
- package/dist/api/openapi.js +31 -26
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +563 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +1 -1
- package/dist/artifacts/workspace.js +1 -1
- package/dist/core/flow-host.d.ts +2 -3
- package/dist/core/flow-host.js +3 -0
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +10 -43
- package/dist/execution/types.d.ts +1 -1
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +28 -1
- package/dist/gates/egress.js +115 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +6 -2
- package/dist/gates/http-tool.js +3 -5
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +52 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +134 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +10 -68
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +12 -5
- package/dist/harness/calls.js +21 -12
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +18 -5
- package/dist/harness/pod.d.ts +34 -0
- package/dist/harness/pod.js +41 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +13 -5
- package/dist/harness/setup.d.ts +27 -0
- package/dist/harness/setup.js +150 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +174 -44
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +8 -0
- package/dist/harness-api/server.js +93 -13
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +96 -0
- package/dist/harness-api/tool-journal.js +102 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +6 -3
- package/dist/harness-api/workspace.js +27 -11
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +54 -5
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +64 -0
- package/dist/host/stack-config.js +61 -1
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +5 -5
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +999 -2211
- package/dist/mcp/connect.d.ts +25 -20
- package/dist/mcp/connect.js +29 -30
- package/dist/mcp/pool.d.ts +2 -2
- package/dist/mcp/pool.js +1 -1
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -20
- package/dist/mcp/snapshot.d.ts +5 -11
- package/dist/mcp/snapshot.js +6 -7
- package/dist/model/pi-model.js +8 -0
- package/dist/openapi.json +6955 -2526
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +42 -15
- package/dist/sandbox/manager.d.ts +16 -8
- package/dist/sandbox/manager.js +33 -17
- package/dist/sandbox/placement.d.ts +13 -12
- package/dist/sandbox/placement.js +29 -12
- package/dist/sandbox/pods/client.d.ts +6 -1
- package/dist/sandbox/pods/lifecycle.d.ts +28 -27
- package/dist/sandbox/pods/lifecycle.js +14 -26
- package/dist/sandbox/pods/reconcile.js +18 -12
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +16 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +35 -27
- package/dist/sandbox/resolve.js +66 -41
- package/dist/sandbox/session-sandbox.js +1 -1
- package/dist/sandbox/setup.d.ts +61 -0
- package/dist/sandbox/setup.js +123 -0
- package/dist/sandbox/share.d.ts +11 -21
- package/dist/sandbox/share.js +12 -41
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/reads.js +117 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +934 -761
- package/dist/store/postgres/schema.js +102 -73
- package/dist/store/postgres/store.js +119 -66
- package/dist/store/types.d.ts +109 -48
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +115 -42
- package/dist/tenant/artifact-tool.js +3 -4
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +93 -12
- package/dist/tenant/context.d.ts +48 -22
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +37 -20
- package/dist/tenant/environments.d.ts +167 -0
- package/dist/tenant/environments.js +823 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +7 -1
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +13 -13
- package/dist/tenant/sessions.js +61 -138
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/sweep.js +12 -6
- package/dist/tenant/transcript.d.ts +210 -20
- package/dist/tenant/transcript.js +62 -11
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +42 -0
- package/dist/traces/ids.js +85 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +317 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +64 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/service.d.ts +47 -48
- package/dist/vault/service.js +321 -382
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +13 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/tenant/sandboxes.d.ts +0 -123
- package/dist/tenant/sandboxes.js +0 -566
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,153 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.24.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e917bd6: **Bring your own harness: harness ids, harness-owned state, and who wrote each event (R4.1, R4.2, R4.4, R4.9).**
|
|
8
|
+
|
|
9
|
+
- **Harness ids and routing.** An Environment's `harness` takes any harness id (`^[a-z][a-z0-9-]{1,62}$`). It is no longer only `nylorun`.
|
|
10
|
+
- **The pin.** A session pins its harness when it opens: its Environment's harness, else `nylorun`. A flow always runs `nylorun`. `GET /v1/sessions/{id}` returns the pin as `harness`.
|
|
11
|
+
- **Routing.** Only a connection whose `hello.name` is that id takes the session's runs.
|
|
12
|
+
- **Our harness's name.** It now says `nylorun` in `hello`. A harness that says `@nylorun/harness` is refused with a message naming the new id, so an older harness image needs this Runtime's.
|
|
13
|
+
- **Placement.** It is keyed by the real harness id, and `"*": {hosts: []}` refuses any harness the map does not name.
|
|
14
|
+
- **Drift.** A turn on an Environment that now runs another harness is `409 harness_mismatch`; open a new session. For a pod Environment, a changed `harness` applies at the pod's next wake, like its image.
|
|
15
|
+
- **Per-harness tokens.** `nylorun-operate harness token put <id>` makes a custom harness container's own Harness API credential, bound to its id; `list` and `rm` manage them. `NYLORUN_HARNESS_TOKEN` stays ours, bound to `nylorun`.
|
|
16
|
+
- **A custom harness as a pod's image.** A pod Environment whose harness is not ours runs its `image` as the harness: the image's own entrypoint (`entrypoint: "image"` to the sandboxes service), with nothing copied in. It needs `image`.
|
|
17
|
+
- **Harness-owned state.** Core stores a harness's turn state as sent and never reads it. Our engine now aligns its state with the turn's manifest itself.
|
|
18
|
+
- **Limits for a custom harness.** Its state is at most 256 KiB, or its turn fails. It cannot pause for approvals in Harness API 2.
|
|
19
|
+
- **Native tools.** It may declare the tools it runs itself in `hello` (`capabilities.tools`). Their calls (capability `harness`) are journaled like built-ins, and the log records a real `tool.completed` instead of `tool.unavailable`.
|
|
20
|
+
- **Who wrote each event.** The writer sets an event's `source` and `evidence`; the event's type no longer decides them. Events written before this release still say `loop` or `api`.
|
|
21
|
+
- **`client`**: a command, with a new `principal` taken from its credential.
|
|
22
|
+
- **`harness`**: what a harness reported, with its harness id and `runId`. Evidence `claimed`.
|
|
23
|
+
- **`core`**: core's own logic. Evidence `observed`.
|
|
24
|
+
- **`effectId` on model calls.** `GET /v1/sessions/{id}/calls/model` returns each call's `effectId`, so a metered call joins the effect journal.
|
|
25
|
+
- **The published contract grows.** It adds `HarnessCheckpointSchema` (the checkpoint fields a harness reads), `NativeToolSchema`, the harness id helpers, `HARNESS_TOOLS_CAPABILITY`, `MAX_HARNESS_STATE_BYTES`, and the gate error codes `key_required` and `effect_not_journaled`. It also documents the effect context keys, the model request hash rule and how a turn's status maps to its output method.
|
|
26
|
+
|
|
27
|
+
- 5165451: **A pod's disk can start prepared, and every Environment's disk has versions (R2d steps 2 and 3).** Before, a new pod volume was empty and a disk could only be kept or reset. Now:
|
|
28
|
+
|
|
29
|
+
- **Setup and input files** (pods, Host feature `environment-setup`): `PUT /v1/environments/{id}` takes `files` (versions of Tenant-wide artifacts, copied in: a file at its `path`, a folder under it) and `setup` (commands run in order with `/bin/sh -c`, `cwd` default `/workspace`, `timeout` default 10m and at most 30m, at most 16 of each). They run once per new disk, after the pod's engine joins and before its first turn, with the Environment's variables, secrets (as the sentinel) and egress. A failed command fails the pod (`observed: failed`, reason `setup: command N …`, an `environment.failed` event with the exit code and the end of the output) and the turn waiting for it; the next turn start runs the setup again. A virtual Environment refuses both with a `400`.
|
|
30
|
+
- **Disk versions** (Host feature `environment-disk-versions`): `POST /v1/environments/{id}/snapshot` keeps a version of the disk between turns (`/workspace`, and a pod's `/harness` but the engine's own files) as a version of a folder artifact the Environment owns, with modes, modification times, links and empty directories (folder manifest `nylorun.folder.v2`); each file's bytes are stored once, and an unchanged disk makes no version. `POST .../restore` puts a version back: a virtual Environment at once, a pod on a new volume seeded before its next turn (`disk.operation` in the view meanwhile; turns wait). `PUT` with `from: { environment, version }` creates an Environment from another's version. `GET .../versions` and `DELETE .../versions/{n}` list and delete them; their trees, files, diffs and zips read through `/v1/artifacts`. A lost pod volume with a version is recovered from the latest one instead of refusing turns until a reset. Snapshot and restore during a turn are `409 environment_busy`. `EnvironmentView.disk` gives the latest version and the copy in progress. Migration `0021_disk_versions`.
|
|
31
|
+
- `@nylorun/agents`: `client.environments.snapshot`, `restore`, `versions` and `deleteVersion`; `put` takes `setup`, `files` and `from`.
|
|
32
|
+
|
|
33
|
+
- 35e29b7: **Environments replace sandboxes and vaults (protocol 11). This is a fresh start: upgrading deletes your sandboxes, their disks and every vault except the model's, and closes every open session (its history stays readable).** List what you have before upgrading; see "Environments replace sandboxes and vaults" in MIGRATION.md.
|
|
34
|
+
|
|
35
|
+
An Environment is where sessions run: one sandbox (kind `virtual`, `pod` or `none`) with a recipe that can change, and one vault of secrets for its shell, MCP servers and HTTP tools. The app decides who uses which Environment; the Runtime keeps it by id and holds its lifecycle.
|
|
36
|
+
|
|
37
|
+
- `@nylorun/core`: protocol 11 (the Host accepts 11 only; older clients get `426`), required feature `environments`. `PutEnvironmentRequestSchema`, `EnvironmentViewSchema` (`recipe`, `version`, `appliedVersion`, `ownerUserId`, `lastUsedAt`), `PutEnvironmentSecretRequestSchema`, `EnvironmentSecretViewSchema`, `ENVIRONMENT_EVENT_TYPES`, `TenantEnvironmentsConfigSchema`, `environmentGranted`. `PutSessionRequest.environment` replaces `sandbox`, `vaultIds` and `credentialSelections`; `SessionView.environment` replaces the session's sandbox and vault fields. Error codes `environment_busy`, `environment_unavailable`, `environment_lost`, `environment_not_granted`, `environment_owner_mismatch` and `session_closed` (the `sandbox_*` codes and `sandbox_expired` are gone); session events `environment.state`, `environment.exec` and `environment.attached`; scope `environments:write`. Credential coverage takes `environmentId` and answers `covered` or `missing`; the MCP preview takes `environmentId`. The vault Management schemas are removed.
|
|
38
|
+
- `@nylorun/runtime`: `/v1/environments/{id}` replaces `/v1/sandboxes/{id}`: `PUT` creates, finds or changes one (`kind` and `storage` fixed; a recipe change bumps `version`; a pod gets a new image or resources at its next start from stopped, a new allowlist within seconds), with list by label, events (`nylorun.environment-event/1`), stop, reset and delete. Each Environment owns one vault: `PUT|GET|DELETE /v1/environments/{id}/secrets/{name}` on the Runtime API, write-only, with a second credential for a host or URL refused at the write (`409 credential_conflict`). An optional owner guard (`ownerUserId`) is checked when a session opens and at every turn start. `lifecycle.deleteAfter` deletes an Environment unused that long; TTL and `onExpiry` are gone. Kind `none` holds secrets for MCP-only agents. `/v1/tenant/vaults`, user and installation vaults, the Tenant's default sandbox, inline sandboxes and `{ session }` sharing are removed. The Tenant setting is `environments.config` (`limits.environments`). Identity files grant `environments:` (an old `sandboxes:` key, or `sandboxes:write`, stops the Runtime with a message naming the new key). Migration `0020_environments`.
|
|
39
|
+
- `@nylorun/harness`: the Harness API claims `environment.state` and `environment.exec`, and its workspace fields name `environmentId`.
|
|
40
|
+
- `@nylorun/agents`: `client.environments` (`put`, `get`, `list`, `page`, `stop`, `reset`, `delete`, `events`, `secrets.put|list|get|delete`, `forSession`) replaces `client.sandboxes`; `createSession({ environment })`; `sessions.page({ environmentId })` filters by Environment. The AG-UI handler's session options take `environment`.
|
|
41
|
+
- `@nylorun/admin`: `admin.coverage({ agentId, environmentId })` replaces `admin.vaults`; `admin.mcp.preview({ url, environmentId })`.
|
|
42
|
+
- `@nylorun/studio`: Environments replace Sandboxes, with a create form, stop, reset, delete and their secrets; the Credentials forms write an Environment's secrets; New session picks an Environment and shows its coverage.
|
|
43
|
+
- `nylorun`: `nylorun sandbox ls|rm` list and delete Environments; `nylorun mcp inspect --environment <id>` replaces `--vault`; `nylo reset --environments` replaces `--sandboxes`; `nylo doctor sandbox` reports the Environment limits.
|
|
44
|
+
|
|
45
|
+
- e917bd6: **The Harness API is a published contract (R4.5).** Everything a harness sends or reads now has a schema in `@nylorun/core/harness-api`, and the Runtime validates with those same schemas:
|
|
46
|
+
- the transport and the pod join (`HARNESS_API_PATH`, `HARNESS_API_HEADER`, `HOST_JOIN_PATH`, `HostJoinRequestSchema`, `HostJoinAnswerSchema`);
|
|
47
|
+
- the Model and Tool Gate bodies (`ModelCallBodySchema`, `ToolCallBodySchema`, `HttpCallBodySchema`, the MCP bodies, `GATE_ERROR_CODES`);
|
|
48
|
+
- the MCP snapshot (`McpSnapshotSchema`);
|
|
49
|
+
- transcript entries (`TranscriptEntrySchema`);
|
|
50
|
+
- the effect id rule (`effectIdOf`, `isEffectIdOf`).
|
|
51
|
+
|
|
52
|
+
The build also writes them as one JSON Schema document, `@nylorun/core/harness-api/schema.json`, for harnesses written in other languages.
|
|
53
|
+
|
|
54
|
+
Other changes:
|
|
55
|
+
- `TurnStart` gains an optional `trace` (W3C `traceparent`) for the turn's trace context. Nothing fills it yet.
|
|
56
|
+
- Core refuses a `hello` whose `api` is not `2`.
|
|
57
|
+
- **The fixture model answers at the Model Gate (R4.3).** With a Tenant's `fixtureModel` seeded, the gate answers model calls itself and meters each one, like any model: a `model_usage` row with provider `fixture` and cost 0, readable at `GET /v1/sessions/{id}/calls/model`. Our harness no longer answers it locally.
|
|
58
|
+
- The fixture model also answers scripted markers: `fixture:text`, `fixture:tool <name> <json>`, `fixture:budget` and `fixture:slow <seconds>`.
|
|
59
|
+
- **The sandboxes service can run a harness image (R4.4, Go).** `PUT /v1/pods/{name}` accepts `entrypoint: "image"`: the workload image's own `ENTRYPOINT` and `CMD` run, with no engine copied in and no `harnessImage`. The default, `entrypoint: "engine"`, is unchanged. The Runtime starts sending it with the harness pin in a later change.
|
|
60
|
+
|
|
61
|
+
- 3013501: **Tool calls are delivered at least once, and every one is on the record (R5 step 1).** A gateway restart used to park the sessions it interrupted as `uncertain` until someone cancelled them, a harness could call tools with no record, and nothing listed a session's tool calls. Now every tool call ends in the log with its outcome, a crash sends it again with the same key, and the gateway runs only calls the effect journal recorded.
|
|
62
|
+
|
|
63
|
+
- **Every outcome in the log.** Each tool call ends in one `tool.completed` with `status` (`completed`: the tool answered; `failed`: the Runtime ended it, `tool.disabled`, `tool.unavailable` or `tool.interrupted`; `cancelled`), `kind` (`mcp`, `http`, `sandbox`, `platform`), `server`, `attempts` and `durationMs`. Refused and cancelled calls are in the log for the first time.
|
|
64
|
+
- **`GET /v1/sessions/{id}/calls/tool`** pages a session's tool calls from the effect journal, in the order they were first sent: tool, input, status, sends, error and timing. Same credentials and paging as `/calls/model`; `@nylorun/agents` adds `session.toolCalls()`. Host feature `tool-calls`.
|
|
65
|
+
- **At least once, with the same key.** An MCP or HTTP tool call that a crash, a shutdown or a lost gateway hop interrupted is sent again with its effect id as the key, up to 3 sends; then it fails to the model as `tool.interrupted` ("may have run") and the session continues. A gateway that restarts mid call runs the call again under its key instead of answering `uncertain`. HTTP tools get the key as `Idempotency-Key`, as before; MCP servers now get it in `_meta["nylorun/idempotency-key"]` on `tools/call`. A tool that honors the key acts once; `attempts` above 1 marks where one may have acted twice.
|
|
66
|
+
- **Sandbox commands are never run twice.** A sandbox or core-served call a crash interrupted has no key to dedupe on, so it fails to the model as `tool.interrupted` at once, and the model decides whether to run it again.
|
|
67
|
+
- **No tool call parks a session.** The `uncertain` state for tool calls, its cancel-only exit and R2b's read-only/idempotent `mcp.lost` exception are gone: a lost MCP answer is `mcp.lost` to the model for every tool. Model calls keep their own rules.
|
|
68
|
+
- **The gateway runs only journaled calls.** Under a run token, `/nylorun/v1/tool-calls` and `/nylorun/v1/http-calls` need the call's effect id as `Idempotency-Key` (`400 key_required`), and the effect journal must hold it running, for the run's turn, the same tool and the same input (`409 effect_not_journaled`). A harness that skips the journal gets nothing run.
|
|
69
|
+
- `@nylorun/harness`: `HarnessExecutors.resends(effect, error, run)` says a gate hop lost a call's answer; the harness then asks core again with the same intent and sends the call again.
|
|
70
|
+
- **Approval** stays in the manifest and our harness still asks for it. Only the Runtime's own harness asks: one built from the Harness API can skip it until tool policy moves to the gateway (R5 step 2).
|
|
71
|
+
|
|
72
|
+
- 6061a3a: **Tool approvals are the Tenant's tool policy (R5 step 2).** An agent's manifest used to say which tool calls wait for a person, and only the Runtime's own harness asked; a harness built from the Harness API could skip it. Now the operator sets one tool policy for every agent and every harness, and the Runtime enforces it before anything is sent.
|
|
73
|
+
|
|
74
|
+
- **`GET`/`PUT /v1/tenant/tool-policy`** (the Management API; `admin.toolPolicy.get()`/`.put({ rules })`): rules `{ effect: "allow" | "ask" | "deny", tool?, server?, kind?, agent?, when?, reason? }`. A rule matches a call when every selector it sets matches (`tool` and `server` take `*` globs; `when` tests input fields by dotted path with `equals`, `in`, `gt`, `gte`, `lt`, `lte` or `matches`). The strictest matching rule decides; a call no rule matches runs.
|
|
75
|
+
- **Enforced for every harness.** Core decides an MCP or HTTP tool call as it journals it. `deny` fails it to the model as `tool.denied`, unsent. `ask` holds the call (`held` at `/calls/tool`) and pauses the turn with one `approval` interaction per held call (`turn.paused`, with the tool, its input and the reason). `approve` sends that exact call, or fails it as `tool.denied`. The Tool Gate runs only calls the journal holds running, so a held or refused call cannot run. Nothing holds a process while it waits: an approval survives a restart. A flow's HTTP stages are decided the same way.
|
|
76
|
+
- **`approval` leaves the manifest.** `PUT /v1/agents/:id` and `saveAgent()` refuse a definition that sets `approval` on an HTTP tool, an MCP server or an MCP tool's settings; `http({ approval })` and `.mcp({ … approval })` throw; a turn manifest can no longer add one. The engine never asks for an HTTP or MCP tool's approval itself. A session pinned to a manifest saved with `approval: "always"` keeps it, as an ask.
|
|
77
|
+
- `@nylorun/studio`: a paused session shows an approval card for each held call, with its tool, why it is held and its input, and **Approve** / **Deny**.
|
|
78
|
+
- Migration: move each `approval: "always"` to an `ask` rule (see MIGRATION.md).
|
|
79
|
+
|
|
80
|
+
- 7d496e4: **Every connection a pod Environment makes to the internet is on the record (R7, egress events).** egress-gate used to keep only log lines, so a session's log could not say which hosts its turn reached or what the gate refused. Now each connection through egress-gate is one event, written by the gate itself.
|
|
81
|
+
|
|
82
|
+
- **`egress.connection`** in the session log of the turn that held the Environment when the connection opened, with that turn's id even if the turn has ended by the time it closes. Source `egress-gate`, evidence `observed`.
|
|
83
|
+
- **`environment.egress`**, the same payload, on the Environment's own stream (`GET /v1/environments/{id}/events`) for traffic while no turn holds the Environment.
|
|
84
|
+
- **The payload:** `environmentId`, `host`, `port`, `outcome` (`allowed` or `refused`), `reason` and `status` for a refusal, `mode` (`tunnel` or `credential`), `credentialBound`, `bytesUp` and `bytesDown`, `startedAt` (the gate's clock at CONNECT) and `durationMs`. A host a shell credential is bound to adds `requests` and the `credentialIds` the gate set. It never carries a header value, a URL path or a body.
|
|
85
|
+
- **One event per connection:** written when it closes, or at once when the gate refuses it (a host not in `network.allowedHosts`, a port other than 443 or 80, a private address, too many tunnels, a name that does not resolve). A refusal before the Environment is known (no token, an expired or stale one) stays a log line only.
|
|
86
|
+
- **Never in the way:** a write happens after the connection closes or is answered, and a failed write is logged as `egress_record_failed` without touching the tunnel. The gateway's `egress_refused` and `egress_terminated` log lines stay.
|
|
87
|
+
|
|
88
|
+
- 81c783e: **Every row that ends a call or a turn carries its own timing (R7.1).** The session log said what happened but not how long it took: a model call's outcome had no time at all, a tool call's only core's window, a flow's HTTP stage had no outcome row, and a turn's end had neither its duration nor its cost. Now each one is a span on its own, ready for the traces that follow, with no protocol change.
|
|
89
|
+
|
|
90
|
+
- **Model calls.** `message.assistant`, `model.failed` and `context.compacted` carry the call's `effectId` and `durationMs` (from the intent to the outcome). When the Model Gate metered the call they also carry the gate's own `gate: { startedAt, endedAt }`, `firstTokenMs` (to the provider's first stream event) and `meteredUsage` (`inputTokens`, `outputTokens`, `totalTokens`, and `cachedTokens` and `costUsd` when not zero), beside the `usage` the harness reported. The usage ledger keeps the gate's times (migration 0022: `model_usage.started_at`, `ended_at`, `first_token_ms`).
|
|
91
|
+
- **One row per model call.** A step that said nothing (no text, no tool calls, as a structured answer) now writes `message.assistant` with `text: ""`, and each partial summary of a long compaction writes `context.compacted` with `partial: true`. The AG-UI and A2A histories skip both; `isEmptyAssistantMessage` in `@nylorun/core/contracts` tells your own UI which steps said nothing.
|
|
92
|
+
- **Tool calls.** `tool.completed` carries the call's `effectId`, the Tool Gate's own `gate: { startedAt, endedAt }` for an MCP or HTTP call it ran (absent for sandbox and core-served tools), and `policy: { decision: "ask" | "deny", reason, approvedBy? }` when the Tenant's tool policy held or refused the call.
|
|
93
|
+
- **Flow HTTP stages.** A new event type, `node.completed` (`path`, `kind`, `key`, `iterations`, `effectId`, `status`, `durationMs`, `gate`), ends each HTTP stage and HTTP verifier that `node.started` began, and a tool stage the Runtime refused to run. Clients ignore event types they do not know.
|
|
94
|
+
- **Turns.** `turn.completed`, `turn.failed` and `turn.cancelled` carry `durationMs` (from the turn's start to its end, waits for a person included), `pausedMs` (how much of that it waited paused), `agentId` and `totals: { steps, tokens, costUsd }`, the same totals `GET /v1/tenant/usage?scope=turn` returns. A turn started before this release has no `durationMs`.
|
|
95
|
+
|
|
96
|
+
- 7800358: **Each turn is one OpenTelemetry trace, projected from the session log (R7).** An operator could not see what a turn did or where its time went without grepping container logs. Now every turn is a trace with a span for each model call, tool call and egress tunnel, exported over OTLP to any collector, readable per turn from the Runtime, and joined to the trace of the app that started it. No protocol change.
|
|
97
|
+
|
|
98
|
+
- **Traces from the log.** A turn's end is its root span, `invoke_agent <agent>`; each model call is a `chat <model>` span, each tool call and flow HTTP stage an `execute_tool <tool>` span, each egress-gate tunnel a `CONNECT <host>` span, and each pause and answer a marker. A model or gate tool span is the gate's own start and end; usage and cost are the gate's (`nylorun.usage.mismatch` when the harness reported other numbers). Attribute names follow the OTel GenAI conventions at one pinned commit, recorded as `nylorun.semconv.genai` on the resource. Ids are derived from the log under a key the Tenant keeps (setting `trace.key`), so every export and read of a turn gives the same ids.
|
|
99
|
+
- **OTLP export, off until you set an endpoint.** With `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) the Runtime posts OTLP/HTTP JSON from a replication slot of its own, `nylorun_trace_export`, in batches of up to 512 spans or every 2 s; `OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` work as usual. No turn waits for it. A collector that is down costs only its spans: after three retries a batch is dropped and logged as `trace_export_dropped`, and the slot moves on, so Postgres keeps no WAL for it. `nylorun start` passes these variables to the runtime container.
|
|
100
|
+
- **References by default.** Spans point at log events (`nylorun.event.cursor`) and carry no content unless you opt in per kind with `NYLORUN_TRACE_CONTENT` (`user`, `outputs`, `tool-results`, `errors`), or all of them with `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`, each cut at 8 KiB. A span never carries a credential value, a prompt, a person's id, an identity header or an idempotency key.
|
|
101
|
+
- **`GET /v1/sessions/{id}/turns/{turnId}/trace`** answers a turn's spans as OTLP JSON, the same spans the exporter posts: look at a turn without a collector, or post it to one to backfill. Application keys, as the `/calls` reads. Host feature `traces`; `@nylorun/agents` adds `session.trace(turnId)`, and `@nylorun/core/contracts` `TurnTraceSchema`.
|
|
102
|
+
- **Trace context in and out.** A `traceparent` (and `tracestate`) on `POST /v1/sessions/{id}/commands`, an AG-UI run or an A2A message puts the turn in the caller's trace; `command.message` records it as `trace`. The turn hands its context on: to the harness in `TurnStart.trace`, to HTTP tools as `traceparent` beside `Idempotency-Key`, to MCP servers as `traceparent` on the call's requests, and to sandbox commands as `TRACEPARENT`.
|
|
103
|
+
- **Harness API** (additive): the `effect.intent` answer `execute` may carry the tool call's `traceparent`. `@nylorun/harness`: `HarnessExecutors.tool` takes it as a fourth argument (`ToolCallContext`).
|
|
104
|
+
|
|
105
|
+
### Patch Changes
|
|
106
|
+
|
|
107
|
+
- 17a97e9: **An artifact names the Environment whose disk it holds.** `ArtifactView` (and so `ArtifactListItem` in artifact pages) gains optional `environmentId`: set on the folder artifact that holds an Environment's disk versions (R2d step 3), absent on a session's or the Tenant's own artifacts. Before, such an artifact read as a Tenant-owned one with no way to tell whose disk it was.
|
|
108
|
+
- b6214d9: Add opt-in artifact metadata pagination to `GET /v1/artifacts?limit=…`, shared
|
|
109
|
+
`ArtifactListItem`/`ArtifactPage` contracts and SDK `artifacts.page()` (default 50,
|
|
110
|
+
maximum 200). Hosts advertise `artifact-reads`. Pages order by creation time and
|
|
111
|
+
ID descending, filter by session, kind and labels, bind cursors to the Tenant and
|
|
112
|
+
filters, and reapply ownership and agent grants on each read. Legacy unpaged
|
|
113
|
+
responses remain unchanged. A Drizzle migration adds the creation-order index;
|
|
114
|
+
pages reuse the existing bounded read-only pool. Studio and every other Runtime
|
|
115
|
+
client can consume this public API without database access.
|
|
116
|
+
- a708b27: **The example harness, on harness ids (R4.8).**
|
|
117
|
+
|
|
118
|
+
- **A new fixture marker.** `fixture:unoffered <name> <json>` makes the fixture model call a tool the turn does not offer, so a test can see that such a call runs nothing and the turn goes on. The other markers are unchanged.
|
|
119
|
+
- **The example harness.** `examples/harness-minimal` says `minimal` in `hello`, connects with its own token from `nylorun-operate harness token put minimal`, and runs beside ours: sessions on an Environment whose `harness` is `minimal` go to it. As a pod Environment's `image` it is the pod's harness.
|
|
120
|
+
- **A session's workspace is its own harness's.** Off a pod, a session's sandbox routes, `save_artifact` and turn-end export go only to a connection of the session's harness that serves workspaces. Before, a custom harness's session was served from our harness's workspace, which is not that harness's files. Without such a connection the request is refused. Sweeps and removes still go to ours.
|
|
121
|
+
- **The Harness API reference and guide.** `guides/HARNESS_API.md` covers every section a harness implements, with a walkthrough for building one.
|
|
122
|
+
|
|
123
|
+
- dec813c: **A gateway shutdown no longer leaves an unhandled rejection.** When a keyed MCP or HTTP tool call was re-sent and the re-send's client went away before the gateway joined the running call, closing the gateway mid call rejected that join with nobody listening ("The gateway is shutting down"), which Node reports as an unhandled rejection. The gateway now handles it: the call still stops on shutdown and the next gateway sends it again under its key, as before.
|
|
124
|
+
- 11c3b2a: **The Runtime's own tokens never reach a session's record from what a harness reports.** A harness's commands hold the egress token in their proxy variables, and a command that prints its environment, or a model that repeats it, would put that token into a tool result. Core now replaces every Nylorun-signed token before recording it:
|
|
125
|
+
- where it looks: tool and model outcomes, error messages, claimed sandbox events, and a turn's output, state and transcript edits;
|
|
126
|
+
- what a token is: a compact JWT whose `typ` starts with `nylorun-`, such as egress, run or host tokens;
|
|
127
|
+
- what replaces it: `[nylorun token redacted]`.
|
|
128
|
+
|
|
129
|
+
This applies to every harness, ours included. The leak showed when the example harness ran as a sandbox pod's image. Other values are unchanged.
|
|
130
|
+
|
|
131
|
+
- 02c2d24: **The runtime image keeps the repository's new layout.** The Runtime's code moved to `runtime/server/` and the harness to `runtime/harness/` in the repository, and the image follows it: the entry point is `/app/runtime/server/dist/host/main.js` (was `/app/runtime/dist/host/main.js`). The image's `ENTRYPOINT`, `nylorun-operate` and the sandboxes service (`ghcr.io/nylorun/sandboxes`, released with the Runtime) use the new path; anything that runs a file under `/app/runtime/dist/` directly needs the new one.
|
|
132
|
+
- a0cbca1: **Clearer wording when a definition runs your code.** The refusal for a code tool or a flow's tool stage, the `hooks were removed` error and `nylo endpoints` now say that the Runtime runs agents from their manifests alone, instead of that it runs no code of yours during a session. Only the message text changed; anything matching the old wording (for example `runs no code of yours during a session`) needs the new text.
|
|
133
|
+
- Pin core to the tested release.
|
|
134
|
+
- Pin harness to the tested release.
|
|
135
|
+
- Updated dependencies [17a97e9]
|
|
136
|
+
- Updated dependencies [b6214d9]
|
|
137
|
+
- Updated dependencies [e917bd6]
|
|
138
|
+
- Updated dependencies [5165451]
|
|
139
|
+
- Updated dependencies [35e29b7]
|
|
140
|
+
- Updated dependencies [e917bd6]
|
|
141
|
+
- Updated dependencies [a0cbca1]
|
|
142
|
+
- Updated dependencies [3013501]
|
|
143
|
+
- Updated dependencies [6061a3a]
|
|
144
|
+
- Updated dependencies [7d496e4]
|
|
145
|
+
- Updated dependencies [81c783e]
|
|
146
|
+
- Updated dependencies [7800358]
|
|
147
|
+
- Updated dependencies
|
|
148
|
+
- @nylorun/core@0.20.0-beta
|
|
149
|
+
- @nylorun/harness@0.26.0-beta
|
|
150
|
+
|
|
3
151
|
## 0.23.0-beta
|
|
4
152
|
|
|
5
153
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The independent OSS **Runtime Host** consumes `@nylorun/harness/run` and
|
|
|
4
4
|
`@nylorun/core/contracts`. Cloud installs published `@nylorun/harness` from npm
|
|
5
5
|
and does not import this package. Every Tenant serves two APIs on one URL: the
|
|
6
6
|
**Runtime API** for developers (agents, sessions with AG-UI and
|
|
7
|
-
A2A,
|
|
7
|
+
A2A, Environments and their secrets, artifacts), whose client is `@nylorun/agents`, and the
|
|
8
8
|
**Management API** for operators (`/v1/tenant/*`), whose client is `@nylorun/admin`.
|
|
9
9
|
Vocabulary: [src/CONTEXT.md](./src/CONTEXT.md).
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ Restate and S2: `nylorun start` runs them, as a local Tenant, on a developer mac
|
|
|
24
24
|
package is a library with no bin; its Host entry is `@nylorun/runtime/server`
|
|
25
25
|
(`dist/host/main.js`), which requires `NYLORUN_DATABASE_URL`. For tests and
|
|
26
26
|
ephemeral embeds, use `startEphemeralRuntime()` from `@nylorun/runtime/core`.
|
|
27
|
-
See [MIGRATION.md](
|
|
27
|
+
See [MIGRATION.md](../../guides/MIGRATION.md).
|
|
28
28
|
|
|
29
29
|
A Runtime serves one **Tenant**: an installation is one Runtime with its own
|
|
30
30
|
Postgres database, Restate and S2 basin, and two Tenants are two installations. No
|
|
@@ -45,7 +45,7 @@ database (`tenant_<id>` schemas), or by a pre-release build of one Tenant per da
|
|
|
45
45
|
(`schema_version` tables), is refused: this release starts fresh on a new database. Restate runs one advance of a session at a time, delivers its wakes and holds
|
|
46
46
|
its timers and the Tenant's sweep (Durable Session Execution), and journals no effect:
|
|
47
47
|
checkpoints, effects, events and the outbox are the Session Store's, and a wake or a pod
|
|
48
|
-
|
|
48
|
+
Environment's reconcile is written to the outbox in the transaction that causes it, so a commit
|
|
49
49
|
never loses it ([src/CONTEXT.md](src/CONTEXT.md)); every session's events are relayed from the record to
|
|
50
50
|
its own S2 stream, which history and SSE read (Durable Streams). One image runs every **service**,
|
|
51
51
|
and `--service` picks what a process runs: `core` serves the Runtime API, the
|
|
@@ -66,7 +66,7 @@ core's process. A `harness` process needs `NYLORUN_HARNESS_URL`,
|
|
|
66
66
|
`/harness`), answers `/health` on `127.0.0.1:4300`, and refuses to start with a
|
|
67
67
|
database, Restate, keys or gates credential in its environment. A local Tenant
|
|
68
68
|
runs it in the `harness` container, on a network with only the runtime and the
|
|
69
|
-
gateway ([DEPLOYMENT.md](
|
|
69
|
+
gateway ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#the-harness-agent-turns-mcp-servers-and-workspaces)).
|
|
70
70
|
|
|
71
71
|
The container is configured by its environment, which a local Tenant's Compose file
|
|
72
72
|
sets: `NYLORUN_DATABASE_URL` (required), `NYLORUN_RESTATE_INGRESS_URL`,
|
|
@@ -91,7 +91,7 @@ Host) the registration replaces the deployment at that URL.
|
|
|
91
91
|
`NYLORUN_IDENTITY_FILE` names the identity
|
|
92
92
|
file, a YAML list of the trusted issuers whose JWTs the Runtime API accepts
|
|
93
93
|
(Host feature `trusted-issuers`), read once at boot; a malformed file stops the
|
|
94
|
-
boot ([DEPLOYMENT.md](
|
|
94
|
+
boot ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#trusted-issuers)). A process that runs `loop` sends its model
|
|
95
95
|
calls to the gate at `NYLORUN_GATES_URL`; in a container it refuses to start
|
|
96
96
|
without it and `NYLORUN_GATES_TOKEN`. That token is core's credential: the gate
|
|
97
97
|
accepts only it for vault writes and token signing. Model, HTTP tool and
|
|
@@ -103,16 +103,16 @@ is refused with `409 run_stale`. A `gates` process needs only
|
|
|
103
103
|
(`NYLORUN_GATES_LISTEN_HOST`, `NYLORUN_GATES_LISTEN_PORT`, default 4100, and
|
|
104
104
|
`NYLORUN_GATES_ALLOWED_HOSTS`) and the Host's `tenant/` directory, which it
|
|
105
105
|
never writes; it serves the database's one Tenant. A session's MCP servers and
|
|
106
|
-
HTTP tools get their credentials from its
|
|
106
|
+
HTTP tools get their credentials from the vault of its Environment only: a `bearer` token
|
|
107
107
|
or a `headers` map per URL, sent to an MCP gateway instead when it has `via`,
|
|
108
108
|
with the session owner in its identity header
|
|
109
|
-
([DEPLOYMENT.md](
|
|
109
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#mcp-servers-and-http-tools)); the credential resolver is gone
|
|
110
110
|
(protocol 10), and a process that still sets a `NYLORUN_RESOLVER_*` variable logs
|
|
111
111
|
`resolver_removed` and ignores it. With `egress`
|
|
112
112
|
(`--service gates,keys,egress`, for pod sandboxes) the same process runs
|
|
113
113
|
egress-gate on `NYLORUN_EGRESS_LISTEN_HOST`:`NYLORUN_EGRESS_LISTEN_PORT`
|
|
114
114
|
(default `0.0.0.0:4200`): a CONNECT proxy that admits a pod's egress token and
|
|
115
|
-
tunnels only to the hosts its
|
|
115
|
+
tunnels only to the hosts its Environment allows (`network.allowedHosts`), on 443 or 80, never to a
|
|
116
116
|
private address. `NYLORUN_PACKING` (`combined` or `split`) is logged at startup.
|
|
117
117
|
|
|
118
118
|
The Host has one listener, which serves both APIs. Host work runs on the machine
|
|
@@ -169,9 +169,12 @@ with `415 unsupported_media_type`, except an artifact upload (`POST /v1/artifact
|
|
|
169
169
|
presents a trusted issuer's token; keys (application and management)
|
|
170
170
|
are refused from browsers before they are looked up. The Runtime sends no CORS headers, and answers
|
|
171
171
|
`OPTIONS` with `204` and `Allow` only: the operator's reverse proxy answers preflights
|
|
172
|
-
([DEPLOYMENT.md](
|
|
172
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#calling-the-runtime-from-browsers-and-apps)). Missing or
|
|
173
173
|
unsupported protocol → `426` before authentication, unless the request sends neither
|
|
174
|
-
`Nylorun-Protocol` nor `Authorization` (the Host serves
|
|
174
|
+
`Nylorun-Protocol` nor `Authorization` (the Host serves protocol 11 only; protocol 11 replaces
|
|
175
|
+
sandbox resources, the Tenant's default sandbox, inline sandboxes and user and installation
|
|
176
|
+
vaults with Environments, each one sandbox and one vault, under `/v1/environments`, and
|
|
177
|
+
removes the Management API's vault routes, so a protocol 10 client gets `426`; protocol 10 removes
|
|
175
178
|
the MCP OAuth connect, the vault's `oauth` credentials and the credential resolver, and adds
|
|
176
179
|
`headers` credentials, `via` and the identity header, manifest v6 and
|
|
177
180
|
`POST /v1/tenant/mcp/preview`; protocol 9 makes the Runtime API an OAuth 2.1 resource server: a missing or rejected credential is `401
|
|
@@ -182,8 +185,7 @@ the Admin API, and those old paths answer `404` with no alias; protocol 7 remove
|
|
|
182
185
|
derived principals, whose routes answer `404`; protocol 6 adds file and folder artifacts and message `parts`; at each turn's end the
|
|
183
186
|
Runtime exports `/workspace/outputs` of the session's sandbox as a version of its
|
|
184
187
|
`outputs` folder, read at `/v1/artifacts/{id}/versions/{n|latest}/tree`, `/files/{path}`,
|
|
185
|
-
`/diff?from=` and `/zip`).
|
|
186
|
-
another Tenant (or malformed) and a Tenant that
|
|
188
|
+
`/diff?from=` and `/zip`). Clients name no Tenant (since protocol 5). A Tenant that
|
|
187
189
|
could not be opened → opaque `404` with identical body. A path or method no route serves is `404 Route not found`
|
|
188
190
|
once the caller is known.
|
|
189
191
|
|
|
@@ -207,13 +209,13 @@ https://cdn.jsdelivr.net/npm/@nylorun/runtime@<version>/dist/management-openapi.
|
|
|
207
209
|
|
|
208
210
|
Each document has described tags, in the order a developer uses them. The Runtime API's are
|
|
209
211
|
grouped (`x-tagGroups`): Agents (Agents, Definition files), Sessions (Sessions API, AG-UI,
|
|
210
|
-
A2A),
|
|
212
|
+
A2A), Environments, Artifacts and Service (health, readiness, `/v1/me`, the JWKS and the
|
|
211
213
|
document; the `/openapi.json` alias is served but not listed). The Management API's are
|
|
212
|
-
Tenant, Application keys, Models,
|
|
214
|
+
Tenant, Application keys, Models, Credential coverage, MCP servers, Signing keys and Settings. Each operation's
|
|
213
215
|
`security` says which credentials it takes (application key and trusted issuer's token in
|
|
214
216
|
the Runtime API; management key in the Management API), and its
|
|
215
217
|
`x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields say
|
|
216
|
-
who may call it. Event streams are `text/event-stream` with an `itemSchema`. `runtime/openapi/` holds the committed snapshots: a change to a route changes
|
|
218
|
+
who may call it. Event streams are `text/event-stream` with an `itemSchema`. `runtime/server/openapi/` holds the committed snapshots: a change to a route changes
|
|
217
219
|
them (`node scripts/build-openapi.mjs --write`), and `check-package` fails until they are
|
|
218
220
|
updated.
|
|
219
221
|
|
|
@@ -251,18 +253,22 @@ const runtime = await startEphemeralRuntime({
|
|
|
251
253
|
|
|
252
254
|
The package's own tests run every Session Store on Postgres: `npm test` gives each
|
|
253
255
|
test file a database on the Docker test stack (`test/stack/compose.yaml`) and starts
|
|
254
|
-
the stack when it is down; see [CONTRIBUTING.md](
|
|
256
|
+
the stack when it is down; see [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
|
255
257
|
|
|
256
258
|
Save agents with `PUT /v1/agents/{agentId}` using the application principal (`saveAgent`
|
|
257
259
|
in `@nylorun/agents`). The Runtime runs the agent from its manifest and never calls your
|
|
258
260
|
code during a session: a definition with a tool that would (`tool({ run })`, a flow's tool
|
|
259
261
|
stage) is refused. Your services are reached as tools: HTTP tools, POSTed from the Runtime,
|
|
260
|
-
so their URLs must be reachable from it, and remote MCP servers.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
(
|
|
264
|
-
and
|
|
265
|
-
|
|
262
|
+
so their URLs must be reachable from it, and remote MCP servers. Each URL gets a
|
|
263
|
+
credential in the vault of the Environment a session names (`PUT
|
|
264
|
+
/v1/environments/{id}/secrets/{name}`), the operator previews an MCP server's tools
|
|
265
|
+
(`nylorun mcp inspect --environment <id>`),
|
|
266
|
+
and the manifest says which tools are enabled or deferred
|
|
267
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#mcp-servers-and-http-tools)). Which calls wait for a
|
|
268
|
+
person, or are refused, is the Tenant's tool policy
|
|
269
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#tool-policy)). Model gateway
|
|
270
|
+
and sandbox backend are Tenant configuration (the Tenant's own vault, Tenant
|
|
271
|
+
settings and seed), not Host process env.
|
|
266
272
|
|
|
267
273
|
## Session behaviour
|
|
268
274
|
|
|
@@ -273,11 +279,12 @@ definition/session APIs.
|
|
|
273
279
|
Postgres transactions persist session checkpoints, command receipts, individual
|
|
274
280
|
effects, waits and the record of session events — in the Tenant's database; history is
|
|
275
281
|
read from Durable Streams (S2). An advance owns its session through a lease with
|
|
276
|
-
an epoch; a Worker that takes over after a crash marks in-flight effects
|
|
277
|
-
`uncertain
|
|
282
|
+
an epoch; a Worker that takes over after a crash marks in-flight model effects
|
|
283
|
+
`uncertain`, and sends an interrupted MCP or HTTP tool call again with the same key (R5). Vault ciphertext needs that Tenant's own KEK.
|
|
278
284
|
|
|
279
|
-
|
|
280
|
-
Tenant owns each
|
|
285
|
+
A session that names an Environment of kind `virtual` or `pod` gets Runtime-executed
|
|
286
|
+
sandbox tools; the agent declares nothing. The Tenant owns each Environment; backend
|
|
287
|
+
names are prefixed `nylorun-<tenant-id>-`.
|
|
281
288
|
|
|
282
289
|
## Local Project workflow
|
|
283
290
|
|
|
@@ -307,9 +314,53 @@ Definitions have no `agent.run()`; applications use `@nylorun/agents`.
|
|
|
307
314
|
## Session reads
|
|
308
315
|
|
|
309
316
|
Hosts advertising `session-reads` serve pinned session manifests, recorded usage, model-call
|
|
310
|
-
pages and opt-in session/history/
|
|
317
|
+
pages and opt-in session/history/Environment pagination. `calls-export` adds the Management API's
|
|
311
318
|
unfiltered, resumable model ledger export (`GET /v1/tenant/calls/model`, a management key) in
|
|
312
|
-
safe transaction order.
|
|
319
|
+
safe transaction order. `tool-calls` adds `GET /v1/sessions/{id}/calls/tool`: each tool call of
|
|
320
|
+
the session from the effect journal, with its input, status and sends. Existing list/history
|
|
313
321
|
requests without `limit` retain their response shapes. These are public APIs for Studio,
|
|
314
322
|
CLI and custom clients; clients never need database access. See the
|
|
315
323
|
[API contracts, authorization, SDK examples and Studio handoff](https://github.com/nylorun/agents/blob/main/runtime/docs/session-reads.md).
|
|
324
|
+
|
|
325
|
+
## Artifact pages
|
|
326
|
+
|
|
327
|
+
Hosts advertising `artifact-reads` accept `GET /v1/artifacts?limit=50`, returning
|
|
328
|
+
`{ artifacts: ArtifactListItem[], nextCursor: string | null }`. An explicit `limit`
|
|
329
|
+
of 1–200 opts into paging; without it the existing `{ artifacts: ArtifactView[] }`
|
|
330
|
+
list remains oldest first. Pages order by `(createdAt DESC, artifactId DESC)` and
|
|
331
|
+
contain metadata only: id, kind, name, current content type, latest version number,
|
|
332
|
+
optional session and labels, and creation/update timestamps. Use the existing
|
|
333
|
+
artifact detail, numbered version and download APIs for history and bytes.
|
|
334
|
+
|
|
335
|
+
Paged reads accept equality filters `sessionId`, `kind=file|folder` and repeated
|
|
336
|
+
`label=key=value` (all labels must match). `sessionId` includes only that session's
|
|
337
|
+
artifacts, excluding Tenant-owned artifacts. An application key acting as itself
|
|
338
|
+
sees every artifact; subjects and issuer tokens see only artifacts of their owned
|
|
339
|
+
sessions and permitted agents. Missing or inaccessible session filters return 404.
|
|
340
|
+
Management keys cannot use this Runtime API route.
|
|
341
|
+
|
|
342
|
+
Send `nextCursor` as `cursor` with the same normalized filters until it is null.
|
|
343
|
+
Cursors are bound to the Tenant, route and filters; invalid or mismatched cursors
|
|
344
|
+
return 400 (`cursor_invalid` or `cursor_mismatch`). Each page reapplies current
|
|
345
|
+
authorization and is an independent read: it is not a frozen snapshot. Newer
|
|
346
|
+
inserts require a fresh traversal; deleted rows disappear and versions/metadata
|
|
347
|
+
may change between pages. Creation time and ID remain the paging position.
|
|
348
|
+
|
|
349
|
+
These pages reuse the separate bounded, read-only Drizzle pool (four connections,
|
|
350
|
+
two-second statement timeout). Migration `0023_artifact_pages` adds only the
|
|
351
|
+
creation-order index; the existing session index also serves session pages in
|
|
352
|
+
reverse order. No data backfill, protocol bump or direct client database access is
|
|
353
|
+
required. Studio can discover `artifact-reads` through `/health` before enabling
|
|
354
|
+
a tenant-wide table; `@nylorun/agents` exposes the same API as `artifacts.page()`.
|
|
355
|
+
|
|
356
|
+
[Recorded query plans](docs/artifact-pages-evidence.json) use a disposable local
|
|
357
|
+
Postgres 17 fixture: the public SDK drained 10,006 artifacts in 201 pages of at
|
|
358
|
+
most 50 rows, without gaps or duplicates, in 796 ms. First/deep page queries used
|
|
359
|
+
backward index scans with a tuple index condition on the deep page (0.014/0.028 ms).
|
|
360
|
+
These are fixture timings, not production throughput estimates. Reproduce after
|
|
361
|
+
building dependencies with:
|
|
362
|
+
|
|
363
|
+
```sh
|
|
364
|
+
ARTIFACT_READS_EVIDENCE="$PWD/runtime/server/docs/artifact-pages-evidence.json" \
|
|
365
|
+
npm test --workspace @nylorun/runtime -- test/http/artifact-pages.test.ts
|
|
366
|
+
```
|
|
@@ -314,7 +314,7 @@ export class RestateExecution {
|
|
|
314
314
|
if ((await ctx.get(`at:${input.timer}`)) !== input.at)
|
|
315
315
|
return;
|
|
316
316
|
ctx.clear(`at:${input.timer}`);
|
|
317
|
-
await runSandbox(ctx, input.timer === "
|
|
317
|
+
await runSandbox(ctx, input.timer === "idle" ? "idle" : "reconcile");
|
|
318
318
|
},
|
|
319
319
|
},
|
|
320
320
|
options: serviceOptions,
|
|
@@ -13,6 +13,8 @@ export interface PgoutputSourceOptions {
|
|
|
13
13
|
publication?: string;
|
|
14
14
|
/** The schema of the record tables. Default `nylorun_streams`. */
|
|
15
15
|
schema?: string;
|
|
16
|
+
/** The connections' `application_name`. Default `nylorun-stream-relay`. */
|
|
17
|
+
applicationName?: string;
|
|
16
18
|
/** Wait between attempts to take the slot. Default 5 s. */
|
|
17
19
|
retryMs?: number;
|
|
18
20
|
log?: (message: string, fields?: Record<string, unknown>) => void;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The stream relay's change source on Postgres logical replication (Durable Streams §7.1,
|
|
3
3
|
* §7.4, §7.6): a persistent `pgoutput` slot on the `nylorun_stream_relay` publication, read
|
|
4
|
-
* with `pg-logical-replication`. The only file importing it (with `pg`, which it needs).
|
|
4
|
+
* with `pg-logical-replication`. The only file importing it (with `pg`, which it needs). The
|
|
5
|
+
* trace projector (R7.2) reads the same publication from a slot of its own
|
|
6
|
+
* (`traces/projector.ts`); for it a fresh slot means nothing to reconcile.
|
|
5
7
|
*
|
|
6
8
|
* - **One reader per slot.** Postgres lets one connection hold a slot, so every Runtime
|
|
7
9
|
* process can run a source and exactly one is active; the others retry every few seconds
|
|
@@ -25,12 +27,13 @@ export function createPgoutputSource(options) {
|
|
|
25
27
|
const publication = options.publication ?? "nylorun_stream_relay";
|
|
26
28
|
const schema = options.schema ?? "nylorun_streams";
|
|
27
29
|
const retryMs = options.retryMs ?? 5000;
|
|
30
|
+
const applicationName = options.applicationName ?? "nylorun-stream-relay";
|
|
28
31
|
const { tenantId } = options;
|
|
29
32
|
const log = options.log ?? (() => { });
|
|
30
33
|
const pool = new pg.Pool({
|
|
31
34
|
connectionString: options.connectionString,
|
|
32
35
|
max: 2,
|
|
33
|
-
application_name:
|
|
36
|
+
application_name: applicationName,
|
|
34
37
|
});
|
|
35
38
|
let service;
|
|
36
39
|
let stopped = false;
|
|
@@ -72,7 +75,7 @@ export function createPgoutputSource(options) {
|
|
|
72
75
|
// `stop()` during `prepareSlot` had no service to stop: do not start one it cannot.
|
|
73
76
|
if (stopped)
|
|
74
77
|
return;
|
|
75
|
-
const current = new LogicalReplicationService({ connectionString: options.connectionString, application_name:
|
|
78
|
+
const current = new LogicalReplicationService({ connectionString: options.connectionString, application_name: applicationName }, { acknowledge: { auto: false, timeoutSeconds: 0 }, flowControl: { enabled: false } });
|
|
76
79
|
service = current;
|
|
77
80
|
let decoding;
|
|
78
81
|
current.on("start", () => handlers.onActive({ fresh }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SandboxBackend } from "../../sandbox/types.js";
|
|
1
|
+
import type { ExecRequest, ExecResult, SandboxBackend } from "../../sandbox/types.js";
|
|
2
2
|
export interface LocalBackendOptions {
|
|
3
3
|
/** Default `/workspace`. */
|
|
4
4
|
readonly workspace?: string;
|
|
@@ -6,5 +6,13 @@ export interface LocalBackendOptions {
|
|
|
6
6
|
readonly proxyEnv?: () => Readonly<Record<string, string>>;
|
|
7
7
|
/** The base environment of commands (the engine's allowlisted one); `NYLORUN_*` is dropped. */
|
|
8
8
|
readonly env: Readonly<Record<string, string | undefined>>;
|
|
9
|
+
/** The engine's `/harness` on the same volume, a disk root of its versions (R2d step 3). */
|
|
10
|
+
readonly harnessRoot?: string;
|
|
9
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* How the pod runs a command: `/bin/sh -c` in its own process group, with the base environment,
|
|
14
|
+
* the request's variables and the proxy of the moment. The sandbox tools and the disk's setup
|
|
15
|
+
* (`harness/setup.ts`) both run commands this way.
|
|
16
|
+
*/
|
|
17
|
+
export declare function localExec(options: LocalBackendOptions): (request: ExecRequest, signal: AbortSignal) => Promise<ExecResult>;
|
|
10
18
|
export declare function localBackend(options: LocalBackendOptions): SandboxBackend;
|
|
@@ -49,10 +49,14 @@ function collector() {
|
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
/**
|
|
53
|
+
* How the pod runs a command: `/bin/sh -c` in its own process group, with the base environment,
|
|
54
|
+
* the request's variables and the proxy of the moment. The sandbox tools and the disk's setup
|
|
55
|
+
* (`harness/setup.ts`) both run commands this way.
|
|
56
|
+
*/
|
|
57
|
+
export function localExec(options) {
|
|
58
|
+
const env = baseEnv(options.env, options.workspace ?? SANDBOX_WORKSPACE);
|
|
59
|
+
return (request, signal) => new Promise((resolve, reject) => {
|
|
56
60
|
if (signal.aborted)
|
|
57
61
|
return reject(signal.reason ?? new Error("Turn cancelled"));
|
|
58
62
|
const child = spawn("/bin/sh", ["-c", request.command], {
|
|
@@ -102,10 +106,18 @@ export function localBackend(options) {
|
|
|
102
106
|
});
|
|
103
107
|
});
|
|
104
108
|
});
|
|
109
|
+
}
|
|
110
|
+
export function localBackend(options) {
|
|
111
|
+
const workspace = options.workspace ?? SANDBOX_WORKSPACE;
|
|
112
|
+
const exec = localExec(options);
|
|
105
113
|
const missing = (error) => error?.code === "ENOENT" || error?.code === "ENOTDIR";
|
|
106
114
|
const handle = {
|
|
107
115
|
workspace,
|
|
108
116
|
exec,
|
|
117
|
+
diskRoots: {
|
|
118
|
+
workspace,
|
|
119
|
+
...(options.harnessRoot === undefined ? {} : { harness: options.harnessRoot }),
|
|
120
|
+
},
|
|
109
121
|
async readFile(path) {
|
|
110
122
|
try {
|
|
111
123
|
return await readFile(path, "utf8");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Virtual backend: an emulated bash with a virtual filesystem, running in the
|
|
2
|
+
* Virtual backend: an emulated bash with a virtual filesystem, running in the harness's process
|
|
3
|
+
* (the harness container, or the Runtime process with NYLORUN_HARNESS=in-process).
|
|
3
4
|
* `/workspace` is backed by a host directory so files survive stop and Runtime restarts.
|
|
4
5
|
* It is not a VM boundary; it exists so the first run and CI work on any machine.
|
|
5
6
|
*/
|
|
@@ -67,6 +68,8 @@ export function virtualBackend(options) {
|
|
|
67
68
|
...(network ? { network } : {}),
|
|
68
69
|
});
|
|
69
70
|
return {
|
|
71
|
+
// A virtual workspace's disk is its host directory: `/workspace` only.
|
|
72
|
+
diskRoots: { workspace: root },
|
|
70
73
|
async exec(request, signal) {
|
|
71
74
|
const controller = new AbortController();
|
|
72
75
|
let timedOut = false;
|
package/dist/api/a2a/endpoint.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { readText } from "../http/body.js";
|
|
3
|
-
import { tenantRoute } from "../http/define.js";
|
|
3
|
+
import { TraceHeaders, tenantRoute } from "../http/define.js";
|
|
4
|
+
import { traceContextOf } from "../../traces/ids.js";
|
|
4
5
|
import { jsonResponse } from "../http/respond.js";
|
|
5
6
|
import { a2aCall, a2aCard } from "./routes.js";
|
|
6
7
|
const A2A_SPECIFICATION = {
|
|
@@ -63,6 +64,7 @@ export function a2aRoutes(api) {
|
|
|
63
64
|
"A2A-Version": z.string().optional().meta({ description: "Or the `A2A-Version` header: `1.0`" }),
|
|
64
65
|
}),
|
|
65
66
|
body: { required: true, content: { "application/json": { schema: JsonRpcRequest } } },
|
|
67
|
+
headers: TraceHeaders,
|
|
66
68
|
},
|
|
67
69
|
responses: {
|
|
68
70
|
200: {
|
|
@@ -73,9 +75,11 @@ export function a2aRoutes(api) {
|
|
|
73
75
|
}, async (c) => {
|
|
74
76
|
const scope = c.get("scope");
|
|
75
77
|
const version = c.env.incoming.headers["a2a-version"];
|
|
78
|
+
const trace = traceContextOf(c.req.raw.headers);
|
|
76
79
|
return jsonResponse(200, await a2aCall(c.env.tenant, scope, c.req.param("agentId"), {
|
|
77
80
|
body: () => readText(c.req.raw),
|
|
78
81
|
version: typeof version === "string" ? version : (c.req.query("A2A-Version") ?? null),
|
|
82
|
+
...(trace ? { trace } : {}),
|
|
79
83
|
}, c.env.outgoing));
|
|
80
84
|
});
|
|
81
85
|
}
|
package/dist/api/a2a/routes.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ServerResponse } from "node:http";
|
|
2
2
|
import { type AuthScope, type TenantContext } from "../../tenant/context.js";
|
|
3
|
+
import type { TraceContext } from "../../traces/ids.js";
|
|
3
4
|
/**
|
|
4
5
|
* How long a blocking `SendMessage` waits for the task to end or ask for input. After it, the
|
|
5
6
|
* task is returned `WORKING` and keeps running; the caller polls `GetTask`.
|
|
@@ -16,4 +17,5 @@ export declare function a2aCard(ctx: TenantContext, scope: AuthScope, agentId: s
|
|
|
16
17
|
export declare function a2aCall(ctx: TenantContext, scope: AuthScope, agentId: string, input: {
|
|
17
18
|
body: () => Promise<string>;
|
|
18
19
|
version: string | null;
|
|
20
|
+
trace?: TraceContext;
|
|
19
21
|
}, response: ServerResponse): Promise<unknown>;
|
package/dist/api/a2a/routes.js
CHANGED
|
@@ -50,7 +50,7 @@ export async function a2aCall(ctx, scope, agentId, input, response) {
|
|
|
50
50
|
// A blocking call stops waiting when the caller hangs up; the task keeps running.
|
|
51
51
|
const hangup = new AbortController();
|
|
52
52
|
response.on("close", () => hangup.abort());
|
|
53
|
-
const caller = { ctx, scope, access, agentId };
|
|
53
|
+
const caller = { ctx, scope, access, agentId, ...(input.trace ? { trace: input.trace } : {}) };
|
|
54
54
|
const { id } = envelope.request;
|
|
55
55
|
try {
|
|
56
56
|
checkVersion(input.version);
|
|
@@ -137,7 +137,7 @@ async function run(caller, sessionId, input,
|
|
|
137
137
|
/** The error for a `409` conflict: the session is busy or no longer waiting. */
|
|
138
138
|
conflict) {
|
|
139
139
|
try {
|
|
140
|
-
const accepted = (await command(caller.ctx, sessionId, SessionCommandSchema.parse(input), caller.scope));
|
|
140
|
+
const accepted = (await command(caller.ctx, sessionId, SessionCommandSchema.parse(input), caller.scope, caller.trace ? { trace: caller.trace } : {}));
|
|
141
141
|
return { turnId: accepted.turnId, cursor: accepted.cursor };
|
|
142
142
|
}
|
|
143
143
|
catch (error) {
|
package/dist/api/a2a/tasks.js
CHANGED
|
@@ -97,7 +97,8 @@ export function historyOf(events, contextId, taskId) {
|
|
|
97
97
|
add(clientMessageId(event), "ROLE_USER", [valuePart(payload.value)]);
|
|
98
98
|
break;
|
|
99
99
|
case "message.assistant":
|
|
100
|
-
// Agents used as tools speak inside the turn; only the agent's own words are history.
|
|
100
|
+
// Agents used as tools speak inside the turn; only the agent's own words are history. A
|
|
101
|
+
// step that said nothing (`text: ""`, R7.1) is none.
|
|
101
102
|
if (!payload.agent && typeof payload.text === "string" && payload.text.trim() !== "")
|
|
102
103
|
add(event.eventId, "ROLE_AGENT", [{ text: payload.text }]);
|
|
103
104
|
break;
|