@nylorun/runtime 0.23.0-beta → 0.25.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 +252 -0
- package/README.md +87 -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/card.d.ts +11 -1
- package/dist/api/a2a/card.js +26 -2
- package/dist/api/a2a/endpoint.js +9 -3
- package/dist/api/a2a/protocol.d.ts +25 -2
- package/dist/api/a2a/protocol.js +20 -3
- package/dist/api/a2a/routes.d.ts +6 -1
- package/dist/api/a2a/routes.js +67 -9
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +8 -3
- package/dist/api/ag-ui/history.d.ts +6 -1
- package/dist/api/ag-ui/history.js +39 -3
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +63 -20
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +5327 -1091
- package/dist/api/components.js +40 -16
- package/dist/api/gate/keys.d.ts +2 -1
- package/dist/api/gate/keys.js +14 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +52 -3
- package/dist/api/http/app.js +17 -6
- package/dist/api/http/body.d.ts +3 -3
- package/dist/api/http/body.js +7 -7
- 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/environment-manifests.d.ts +9 -0
- package/dist/api/http/routes/environment-manifests.js +118 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +332 -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 +94 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +24 -9
- package/dist/api/http/routes/tenant.js +65 -15
- package/dist/api/inbound-files.d.ts +65 -0
- package/dist/api/inbound-files.js +115 -0
- package/dist/api/openapi.js +48 -28
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +569 -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/media-types.d.ts +15 -0
- package/dist/artifacts/media-types.js +56 -4
- package/dist/artifacts/parts.d.ts +12 -3
- package/dist/artifacts/parts.js +15 -4
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +5 -5
- package/dist/artifacts/workspace.js +7 -19
- package/dist/core/flow-host.d.ts +3 -4
- package/dist/core/flow-host.js +5 -2
- 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 +11 -44
- package/dist/core/turn-manifest.d.ts +3 -2
- package/dist/core/turn-manifest.js +29 -5
- 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 +22 -1
- package/dist/gates/egress.js +116 -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 +9 -6
- package/dist/gates/http-tool.js +11 -17
- 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 +51 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +7 -4
- 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 +132 -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 +11 -69
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +14 -7
- package/dist/harness/calls.js +30 -24
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +24 -5
- package/dist/harness/pod.d.ts +39 -0
- package/dist/harness/pod.js +46 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +17 -9
- package/dist/harness/setup.d.ts +29 -0
- package/dist/harness/setup.js +174 -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 +180 -50
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +35 -4
- package/dist/harness-api/server.js +130 -17
- 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 +105 -0
- package/dist/harness-api/tool-journal.js +132 -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 +12 -7
- package/dist/harness-api/workspace.js +47 -25
- 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 +62 -2
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +13 -6
- package/dist/keys/keys.js +9 -8
- package/dist/management-openapi.json +1264 -2171
- package/dist/mcp/connect.d.ts +26 -22
- package/dist/mcp/connect.js +30 -34
- package/dist/mcp/pool.d.ts +3 -6
- package/dist/mcp/pool.js +14 -28
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -21
- package/dist/mcp/snapshot.d.ts +16 -24
- package/dist/mcp/snapshot.js +33 -45
- package/dist/model/capabilities.d.ts +26 -0
- package/dist/model/capabilities.js +44 -0
- package/dist/model/catalog.d.ts +34 -3
- package/dist/model/catalog.js +38 -2
- package/dist/model/models.d.ts +2 -0
- package/dist/model/models.js +1 -1
- package/dist/model/native-blocks.d.ts +35 -0
- package/dist/model/native-blocks.js +170 -0
- package/dist/model/pi-model.js +114 -94
- package/dist/openapi.json +12822 -3943
- 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 +46 -15
- package/dist/sandbox/manager.d.ts +47 -21
- package/dist/sandbox/manager.js +162 -107
- package/dist/sandbox/placement.d.ts +14 -13
- package/dist/sandbox/placement.js +31 -14
- package/dist/sandbox/pods/client.d.ts +9 -2
- package/dist/sandbox/pods/lifecycle.d.ts +36 -27
- package/dist/sandbox/pods/lifecycle.js +29 -26
- package/dist/sandbox/pods/reconcile.js +18 -12
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +17 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +37 -27
- package/dist/sandbox/resolve.js +73 -41
- package/dist/sandbox/runtime-tools.d.ts +9 -0
- package/dist/sandbox/runtime-tools.js +16 -0
- package/dist/sandbox/session-sandbox.d.ts +4 -6
- package/dist/sandbox/session-sandbox.js +47 -94
- package/dist/sandbox/setup.d.ts +64 -0
- package/dist/sandbox/setup.js +160 -0
- package/dist/sandbox/share.d.ts +21 -26
- package/dist/sandbox/share.js +36 -63
- package/dist/sandbox/skills.d.ts +8 -6
- package/dist/sandbox/skills.js +24 -23
- 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/0024_flat_manifest.sql +7 -0
- package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
- package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
- package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
- package/dist/store/postgres/reads.js +123 -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 +1126 -736
- package/dist/store/postgres/schema.js +136 -73
- package/dist/store/postgres/store.js +195 -67
- package/dist/store/types.d.ts +165 -50
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +118 -42
- package/dist/tenant/artifact-tool.d.ts +1 -1
- package/dist/tenant/artifact-tool.js +46 -26
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +115 -14
- package/dist/tenant/context.d.ts +56 -24
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +44 -29
- package/dist/tenant/environment-manifests.d.ts +57 -0
- package/dist/tenant/environment-manifests.js +200 -0
- package/dist/tenant/environments.d.ts +174 -0
- package/dist/tenant/environments.js +1044 -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/model-setting.d.ts +14 -0
- package/dist/tenant/model-setting.js +51 -0
- 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 +18 -14
- package/dist/tenant/sessions.js +99 -143
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/skill-tool.d.ts +3 -3
- package/dist/tenant/skill-tool.js +6 -11
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/sweep.js +13 -7
- package/dist/tenant/tool-results.js +1 -1
- package/dist/tenant/tool-search.d.ts +7 -11
- package/dist/tenant/tool-search.js +8 -5
- package/dist/tenant/transcript.d.ts +245 -36
- package/dist/tenant/transcript.js +64 -12
- 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 +41 -0
- package/dist/traces/ids.js +81 -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 +289 -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 +62 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/host-model.d.ts +4 -1
- package/dist/vault/host-model.js +10 -2
- package/dist/vault/service.d.ts +92 -48
- package/dist/vault/service.js +462 -376
- 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/sandbox/capability.d.ts +0 -3
- package/dist/sandbox/capability.js +0 -10
- package/dist/tenant/sandboxes.d.ts +0 -123
- package/dist/tenant/sandboxes.js +0 -566
|
@@ -11,8 +11,15 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Rows store the request's hash (`requestHash`) and, for a model call, the request without its
|
|
13
13
|
* prompt: drift is a hash compare. Rows written before carry their full request, hashed on read.
|
|
14
|
+
*
|
|
15
|
+
* Every row a run journals keeps when it was journaled (`startedAt`, R7.1), and a rejoin keeps
|
|
16
|
+
* it: an outcome's `durationMs` is the journal's window, from the intent to the outcome. A model
|
|
17
|
+
* call's outcome event also carries what the meter recorded for it (`modelOutcomeFacts`). A tool
|
|
18
|
+
* row keeps its span's context (`traceparent`, R7.4), which the `execute` answer carries to the
|
|
19
|
+
* harness and the Tool Gate reads with the row.
|
|
14
20
|
*/
|
|
15
21
|
import { type EffectIntent, type IntentAnswer, type OutcomeAnswer, type RecordedOutcome } from "@nylorun/core/harness-api";
|
|
22
|
+
import type { EventWriter } from "@nylorun/core/contracts";
|
|
16
23
|
import { type Lease, type Session, type TenantContext } from "../tenant/context.js";
|
|
17
24
|
import type { Tx } from "../store/types.js";
|
|
18
25
|
/** What a run's journal writes are scoped by: the advance's lease and its signal. */
|
|
@@ -20,6 +27,13 @@ export interface RecordScope {
|
|
|
20
27
|
readonly ctx: TenantContext;
|
|
21
28
|
readonly lease: Lease;
|
|
22
29
|
readonly signal: AbortSignal;
|
|
30
|
+
/** The tools the run's harness declared it runs itself (`hello` `features.tools`, R4.2), by name. */
|
|
31
|
+
readonly nativeTools?: ReadonlySet<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Who reported what this scope records (R4.9): the run's harness, so the events derived from
|
|
34
|
+
* its intents and outcomes are its claim. Absent, core recorded what it saw itself.
|
|
35
|
+
*/
|
|
36
|
+
readonly writer?: EventWriter;
|
|
23
37
|
}
|
|
24
38
|
/** The hash a journal row's request was recorded with. */
|
|
25
39
|
export declare function hashOf(row: {
|
|
@@ -41,7 +55,7 @@ export declare function recordIntent(scope: RecordScope, effect: EffectIntent, r
|
|
|
41
55
|
* `error` that left it uncertain. Only a cancel discards an outcome in hand (§10.7): after any
|
|
42
56
|
* other abort (shutdown, deadline) it is recorded, so the next advance replays it.
|
|
43
57
|
*/
|
|
44
|
-
export declare function recordOutcome(scope: RecordScope, effectId: string,
|
|
58
|
+
export declare function recordOutcome(scope: RecordScope, effectId: string, reported: {
|
|
45
59
|
value: unknown;
|
|
46
60
|
} | {
|
|
47
61
|
error: string;
|
|
@@ -11,10 +11,16 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Rows store the request's hash (`requestHash`) and, for a model call, the request without its
|
|
13
13
|
* prompt: drift is a hash compare. Rows written before carry their full request, hashed on read.
|
|
14
|
+
*
|
|
15
|
+
* Every row a run journals keeps when it was journaled (`startedAt`, R7.1), and a rejoin keeps
|
|
16
|
+
* it: an outcome's `durationMs` is the journal's window, from the intent to the outcome. A model
|
|
17
|
+
* call's outcome event also carries what the meter recorded for it (`modelOutcomeFacts`). A tool
|
|
18
|
+
* row keeps its span's context (`traceparent`, R7.4), which the `execute` answer carries to the
|
|
19
|
+
* harness and the Tool Gate reads with the row.
|
|
14
20
|
*/
|
|
15
|
-
import { HarnessApiError, effectRequestHash, } from "@nylorun/core/harness-api";
|
|
21
|
+
import { HarnessApiError, effectRequestHash, isEffectIdOf, } from "@nylorun/core/harness-api";
|
|
16
22
|
import { isFlowEffect, isFlowToolEffect, recordVerdict, settleAgentEffect, } from "../core/flow-host.js";
|
|
17
|
-
import {
|
|
23
|
+
import { sandboxToolSpec } from "../sandbox/runtime-tools.js";
|
|
18
24
|
import { abortOn, isReadArtifactCall, isSaveArtifactCall } from "../harness/calls.js";
|
|
19
25
|
import { callReadArtifact, callSaveArtifact } from "../tenant/artifact-tool.js";
|
|
20
26
|
import { mayNeedShaping, resultBytes, resultLimit, shapeToolOutcome, } from "../tenant/tool-results.js";
|
|
@@ -26,8 +32,13 @@ import { manifestFor, mcpToolOf } from "../mcp/snapshot.js";
|
|
|
26
32
|
import { isHttpToolCall } from "../gates/http-tool.js";
|
|
27
33
|
import { ownedSession } from "../tenant/context.js";
|
|
28
34
|
import { isGateToolEffect, linkedOutcome, recoversModelCalls, recoversToolCalls, resolveNewFlowEffect, } from "../tenant/effects.js";
|
|
29
|
-
import { assistantMessage, contextCompacted, modelFailed,
|
|
35
|
+
import { assistantMessage, contextCompacted, modelFailed, modelOutcomeFacts, } from "../tenant/transcript.js";
|
|
30
36
|
import { abortKind } from "../tenant/worker.js";
|
|
37
|
+
import { redactTokens } from "./redact.js";
|
|
38
|
+
import { toolCallDecision } from "../gates/policy.js";
|
|
39
|
+
import { traceKey } from "../traces/key.js";
|
|
40
|
+
import { effectSpanOf, traceparentOf, turnTraceIdOf } from "../traces/ids.js";
|
|
41
|
+
import { MAX_TOOL_ATTEMPTS, firstSend, interrupted, settleToolRow, toolKindOf, } from "./tool-journal.js";
|
|
31
42
|
const TURN_CANCELLED = () => new HarnessApiError("turn_cancelled", "Turn cancelled");
|
|
32
43
|
/** The hash a journal row's request was recorded with. */
|
|
33
44
|
export function hashOf(row) {
|
|
@@ -35,10 +46,9 @@ export function hashOf(row) {
|
|
|
35
46
|
}
|
|
36
47
|
/** The completed outcomes of a segment, which a run replays without asking. */
|
|
37
48
|
export async function bulkOutcomes(t, session, checkpoint) {
|
|
38
|
-
const prefix = `${checkpoint.turnId}:${checkpoint.segment}:`;
|
|
39
49
|
const rows = await t.effectsForTurn(session.id, checkpoint.turnId, ["completed"]);
|
|
40
50
|
return rows
|
|
41
|
-
.filter((row) => row.request.effectId.
|
|
51
|
+
.filter((row) => isEffectIdOf(row.request.effectId, checkpoint.turnId, checkpoint.segment) && row.outcome)
|
|
42
52
|
.map((row) => ({ effectId: row.request.effectId, requestHash: hashOf(row), outcome: row.outcome }));
|
|
43
53
|
}
|
|
44
54
|
/**
|
|
@@ -48,10 +58,14 @@ export async function bulkOutcomes(t, session, checkpoint) {
|
|
|
48
58
|
export async function recordIntent(scope, effect, requestHash) {
|
|
49
59
|
const { ctx, lease, signal } = scope;
|
|
50
60
|
const request = effect;
|
|
61
|
+
// Read before the transaction: the first read of the Tenant may create the key.
|
|
62
|
+
const key = await traceKey(ctx.store);
|
|
51
63
|
const answer = await ctx.store.tx(async (t) => {
|
|
52
64
|
const s = await ownedSession(t, lease, request.sessionId);
|
|
53
65
|
if (s.status === "cancelled" || s.activeTurnId !== request.turnId)
|
|
54
66
|
throw TURN_CANCELLED();
|
|
67
|
+
// A tool call's span context (R7.4), kept on its row.
|
|
68
|
+
const traced = () => ({ traceparent: toolTraceparent(key, ctx.store.tenantId, s, request) });
|
|
55
69
|
// An aborted advance starts no effect; the advance decides what the abort means.
|
|
56
70
|
signal.throwIfAborted();
|
|
57
71
|
const existing = await t.get("effects", request.effectId);
|
|
@@ -61,12 +75,12 @@ export async function recordIntent(scope, effect, requestHash) {
|
|
|
61
75
|
if (existing.status === "completed")
|
|
62
76
|
return { status: "completed", outcome: existing.outcome };
|
|
63
77
|
// A call its previous owner left running at the gate: re-send it, same key and request,
|
|
64
|
-
// to join it or collect its outcome (P1.2
|
|
65
|
-
// tool calls).
|
|
78
|
+
// to join it or collect its outcome (P1.2).
|
|
66
79
|
if (existing.status === "invoking" && request.kind === "model" && recoversModelCalls(ctx))
|
|
67
80
|
return { status: "execute", rejoin: true };
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
// A tool call a crash, a shutdown or a lost gate hop interrupted (R5.3, R5.4).
|
|
82
|
+
if (existing.status === "invoking" && existing.request.kind === "tool")
|
|
83
|
+
return resend(t, s, existing, recoversToolCalls(ctx));
|
|
70
84
|
if (request.kind === "agent" && existing.status === "pending" && existing.agentSessionId) {
|
|
71
85
|
const agent = await t.get("sessions", existing.agentSessionId);
|
|
72
86
|
const outcome = await linkedOutcome(t, existing, agent);
|
|
@@ -86,26 +100,38 @@ export async function recordIntent(scope, effect, requestHash) {
|
|
|
86
100
|
if (request.kind !== "model" && effectRequestHash(request) !== requestHash)
|
|
87
101
|
throw new HarnessApiError("invalid", "The request hash does not match the request");
|
|
88
102
|
if (flowHttp) {
|
|
89
|
-
|
|
103
|
+
const row = { request, requestHash, status: "invoking", ...firstSend({ toolKind: "http" }), ...traced() };
|
|
90
104
|
await t.event(s.id, s.activeTurnId, "node.started", {
|
|
91
105
|
path: request.path,
|
|
92
106
|
kind: "http",
|
|
93
107
|
key: request.key,
|
|
94
108
|
...(request.iterations !== undefined ? { iterations: request.iterations } : {}),
|
|
95
|
-
});
|
|
96
|
-
|
|
109
|
+
}, scope.writer);
|
|
110
|
+
const decided = await bypolicy(t, s, row, "http");
|
|
111
|
+
if (decided)
|
|
112
|
+
return decided;
|
|
113
|
+
await t.put("effects", request.effectId, row);
|
|
114
|
+
return { status: "execute", traceparent: row.traceparent };
|
|
97
115
|
}
|
|
98
116
|
if (request.kind === "delegation") {
|
|
99
117
|
// Lifecycle points of an agent used as a tool: journaled once, so replays never re-emit.
|
|
100
118
|
const outcome = { value: null };
|
|
101
|
-
|
|
119
|
+
const at = new Date().toISOString();
|
|
120
|
+
await t.put("effects", request.effectId, {
|
|
121
|
+
request,
|
|
122
|
+
requestHash,
|
|
123
|
+
status: "completed",
|
|
124
|
+
outcome,
|
|
125
|
+
startedAt: at,
|
|
126
|
+
settledAt: at,
|
|
127
|
+
});
|
|
102
128
|
const settled = request.effectId.endsWith(":settled");
|
|
103
129
|
const callId = request.context?.callId;
|
|
104
130
|
await t.event(s.id, s.activeTurnId, settled ? "delegation.completed" : "delegation.started", {
|
|
105
131
|
agent: request.agent,
|
|
106
132
|
...(typeof callId === "string" ? { callId } : {}),
|
|
107
133
|
...request.input,
|
|
108
|
-
});
|
|
134
|
+
}, scope.writer);
|
|
109
135
|
return { status: "completed", outcome };
|
|
110
136
|
}
|
|
111
137
|
const agentManifest = manifestFor(s.manifest, request.agent);
|
|
@@ -117,33 +143,50 @@ export async function recordIntent(scope, effect, requestHash) {
|
|
|
117
143
|
(mcpToolOf(s.mcpSnapshot, request, turnManifestOf(s)) !== undefined ||
|
|
118
144
|
// HTTP tools (R2 M3) cross the Tool Gate.
|
|
119
145
|
isHttpToolCall(s.manifest, request) ||
|
|
120
|
-
|
|
146
|
+
sandboxToolSpec(agentManifest, request.toolName) !== undefined ||
|
|
121
147
|
// `save_artifact` (F8.1) runs beside the sandbox tools, as do `read_artifact` (R2b
|
|
122
148
|
// C11) and the skill tools (R2 M4).
|
|
123
149
|
isSaveArtifactCall(agentManifest, request) ||
|
|
124
150
|
isReadArtifactCall(agentManifest, request) ||
|
|
125
151
|
isSkillToolCall(agentManifest, request) ||
|
|
126
152
|
// `tool_search` (R2b C10) reads the session's pinned MCP snapshot: core serves it.
|
|
127
|
-
isToolSearchCall(
|
|
153
|
+
isToolSearchCall(s.mcpSnapshot, request) ||
|
|
154
|
+
// A tool the run's harness declared and runs itself (R4.2, Q3), by its name: the turn
|
|
155
|
+
// refuses a native tool whose name another tool of the agent's has (`harness-api/server.ts`).
|
|
156
|
+
(request.toolName !== undefined && scope.nativeTools?.has(request.toolName) === true)));
|
|
128
157
|
if (!executed) {
|
|
129
158
|
// A tool that would run the developer's code: refused at save (a turn's manifest only
|
|
130
159
|
// removes tools), so this is a backstop, as is an MCP tool the turn disabled, which the
|
|
131
160
|
// model was never offered. The model sees the failure; nothing runs.
|
|
132
161
|
const disabled = request.kind === "tool" && mcpToolOf(s.mcpSnapshot, request) !== undefined;
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
const row = {
|
|
163
|
+
request,
|
|
164
|
+
requestHash,
|
|
165
|
+
status: "invoking",
|
|
166
|
+
...toolKindOf(s, agentManifest, request, scope.nativeTools),
|
|
167
|
+
attempts: 0,
|
|
168
|
+
};
|
|
169
|
+
const outcome = await settleToolRow(t, s, row, disabled ? disabledTool(request) : unrunnableTool(request), { writer: coreIn(scope) });
|
|
138
170
|
return { status: "completed", outcome };
|
|
139
171
|
}
|
|
140
|
-
|
|
172
|
+
const row = {
|
|
173
|
+
// A model row keeps no prompt; a tool row its whole request.
|
|
141
174
|
request: storedRequest(request),
|
|
142
175
|
requestHash,
|
|
143
176
|
status: "invoking",
|
|
144
|
-
|
|
177
|
+
...(request.kind === "tool"
|
|
178
|
+
? { ...firstSend(toolKindOf(s, agentManifest, request, scope.nativeTools)), ...traced() }
|
|
179
|
+
: { startedAt: new Date().toISOString() }),
|
|
180
|
+
};
|
|
181
|
+
// The Tenant's tool policy decides an MCP or HTTP tool call as it is journaled (R5 step 2).
|
|
182
|
+
if (row.toolKind === "mcp" || row.toolKind === "http") {
|
|
183
|
+
const decided = await bypolicy(t, s, row, row.toolKind);
|
|
184
|
+
if (decided)
|
|
185
|
+
return decided;
|
|
186
|
+
}
|
|
187
|
+
await t.put("effects", request.effectId, row);
|
|
145
188
|
// `save_artifact` writes the Tenant's artifacts: core runs it, reading the file through the
|
|
146
|
-
// workspace
|
|
189
|
+
// workspace feature, wherever the sandbox is.
|
|
147
190
|
if (request.kind === "tool" && isSaveArtifactCall(agentManifest, request))
|
|
148
191
|
return "save";
|
|
149
192
|
// `read_artifact` reads the session's artifacts: core serves it, with or without a sandbox.
|
|
@@ -152,9 +195,9 @@ export async function recordIntent(scope, effect, requestHash) {
|
|
|
152
195
|
// The skill tools read the agent's definition files: core serves them.
|
|
153
196
|
if (isSkillToolCall(agentManifest, request))
|
|
154
197
|
return { skill: agentManifest };
|
|
155
|
-
if (isToolSearchCall(
|
|
198
|
+
if (isToolSearchCall(s.mcpSnapshot, request))
|
|
156
199
|
return "search";
|
|
157
|
-
return { status: "execute" };
|
|
200
|
+
return { status: "execute", ...(row.traceparent ? { traceparent: row.traceparent } : {}) };
|
|
158
201
|
});
|
|
159
202
|
if (answer === "save")
|
|
160
203
|
return runInCore(scope, request, () => callSaveArtifact(scope.ctx, request, scope.signal));
|
|
@@ -177,6 +220,9 @@ export async function recordIntent(scope, effect, requestHash) {
|
|
|
177
220
|
* the outcome as the intent's answer.
|
|
178
221
|
*/
|
|
179
222
|
async function runInCore(scope, request, call) {
|
|
223
|
+
// Core ran the call: its outcome is what core saw, not the harness's claim (R4.9).
|
|
224
|
+
const writer = coreIn(scope);
|
|
225
|
+
const core = { ctx: scope.ctx, lease: scope.lease, signal: scope.signal, ...(writer ? { writer } : {}) };
|
|
180
226
|
let value;
|
|
181
227
|
try {
|
|
182
228
|
value = await call();
|
|
@@ -184,11 +230,70 @@ async function runInCore(scope, request, call) {
|
|
|
184
230
|
catch (error) {
|
|
185
231
|
if (isOwnershipLost(error))
|
|
186
232
|
throw error;
|
|
187
|
-
return recordOutcome(
|
|
233
|
+
return recordOutcome(core, request.effectId, {
|
|
188
234
|
error: error instanceof Error ? error.message : String(error),
|
|
189
235
|
});
|
|
190
236
|
}
|
|
191
|
-
return recordOutcome(
|
|
237
|
+
return recordOutcome(core, request.effectId, { value });
|
|
238
|
+
}
|
|
239
|
+
/** Core's own write during the scope's run (R4.9): core saw it, in that run. */
|
|
240
|
+
function coreIn(scope) {
|
|
241
|
+
const runId = scope.writer?.runId;
|
|
242
|
+
return runId === undefined ? undefined : { source: { kind: "core", id: "runtime" }, runId };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The answer to a tool row still `invoking` (R5.3, R5.4): a crash, a shutdown or a lost gate
|
|
246
|
+
* hop interrupted its send. An MCP or HTTP call (it crosses the Tool Gate, which hands the tool
|
|
247
|
+
* its key) is sent again, one more attempt, until `MAX_TOOL_ATTEMPTS`; then it fails to the
|
|
248
|
+
* model. Any other call (sandbox, core-served) has no key to dedupe on: it fails to the model
|
|
249
|
+
* now, and is not run again (Q2).
|
|
250
|
+
*/
|
|
251
|
+
async function resend(t, s, row, rejoin) {
|
|
252
|
+
if (!isGateToolEffect(s, row.request)) {
|
|
253
|
+
const outcome = await settleToolRow(t, s, row, interrupted("A crash interrupted this call before its outcome was recorded. It may have run; it is not run again."));
|
|
254
|
+
return { status: "completed", outcome };
|
|
255
|
+
}
|
|
256
|
+
const attempts = (row.attempts ?? 1) + 1;
|
|
257
|
+
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
258
|
+
const outcome = await settleToolRow(t, s, row, interrupted(`The call was interrupted ${MAX_TOOL_ATTEMPTS} times and may have run.`));
|
|
259
|
+
return { status: "completed", outcome };
|
|
260
|
+
}
|
|
261
|
+
row.attempts = attempts;
|
|
262
|
+
await t.put("effects", row.request.effectId, row);
|
|
263
|
+
const traced = row.traceparent ? { traceparent: row.traceparent } : {};
|
|
264
|
+
return rejoin ? { status: "execute", rejoin: true, ...traced } : { status: "execute", ...traced };
|
|
265
|
+
}
|
|
266
|
+
/** The span context of tool call `request` (R7.4): its turn's trace, and its own span. */
|
|
267
|
+
function toolTraceparent(key, tenantId, s, request) {
|
|
268
|
+
const traceId = turnTraceIdOf(key, { tenantId, sessionId: s.id, turnId: request.turnId }, s.turnTrace);
|
|
269
|
+
return traceparentOf(traceId, effectSpanOf(key, request.effectId));
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The Tenant's tool policy on a tool call being journaled (R5 step 2, `gates/policy.ts`), or
|
|
273
|
+
* undefined when it runs. `deny`: the call fails to the model as `tool.denied`, unsent. `ask`:
|
|
274
|
+
* the row waits as `held` and the effect is pending; the segment ends waiting, the turn pauses,
|
|
275
|
+
* and `approve` answers it (`tenant/commands.ts`). The Tool Gate refuses a row not `invoking`, so
|
|
276
|
+
* neither runs whichever harness sends it. Writes `row`.
|
|
277
|
+
*/
|
|
278
|
+
async function bypolicy(t, s, row, kind) {
|
|
279
|
+
const decision = await toolCallDecision(t, s, row.request, kind);
|
|
280
|
+
if (decision.effect === "allow")
|
|
281
|
+
return undefined;
|
|
282
|
+
// Nothing was sent.
|
|
283
|
+
row.attempts = 0;
|
|
284
|
+
if (decision.effect === "deny") {
|
|
285
|
+
const outcome = await settleToolRow(t, s, row, {
|
|
286
|
+
kind: "failed",
|
|
287
|
+
code: "tool.denied",
|
|
288
|
+
message: decision.reason,
|
|
289
|
+
retryable: false,
|
|
290
|
+
});
|
|
291
|
+
return { status: "completed", outcome };
|
|
292
|
+
}
|
|
293
|
+
row.status = "held";
|
|
294
|
+
row.hold = { reason: decision.reason, at: new Date().toISOString() };
|
|
295
|
+
await t.put("effects", row.request.effectId, row);
|
|
296
|
+
return { status: "pending" };
|
|
192
297
|
}
|
|
193
298
|
/** The failed outcome of an MCP tool the turn's manifest disables (R2b C9). */
|
|
194
299
|
function disabledTool(request) {
|
|
@@ -203,7 +308,7 @@ function unrunnableTool(request) {
|
|
|
203
308
|
return {
|
|
204
309
|
kind: "failed",
|
|
205
310
|
code: "tool.unavailable",
|
|
206
|
-
message: `Tool '${request.toolName ?? ""}' runs your code,
|
|
311
|
+
message: `Tool '${request.toolName ?? ""}' runs your code, but the Runtime runs agents from their manifests alone. Make it an http() tool or serve it from a remote MCP server.`,
|
|
207
312
|
};
|
|
208
313
|
}
|
|
209
314
|
/**
|
|
@@ -211,24 +316,38 @@ function unrunnableTool(request) {
|
|
|
211
316
|
* `error` that left it uncertain. Only a cancel discards an outcome in hand (§10.7): after any
|
|
212
317
|
* other abort (shutdown, deadline) it is recorded, so the next advance replays it.
|
|
213
318
|
*/
|
|
214
|
-
export async function recordOutcome(scope, effectId,
|
|
319
|
+
export async function recordOutcome(scope, effectId, reported) {
|
|
215
320
|
const { ctx, lease, signal } = scope;
|
|
321
|
+
const result = "error" in reported ? { error: redactTokens(reported.error) } : reported;
|
|
216
322
|
if ("error" in result) {
|
|
217
|
-
|
|
323
|
+
return ctx.store.tx(async (t) => {
|
|
218
324
|
const s = await t.assertEpoch(lease.sessionId, lease.epoch);
|
|
219
325
|
const effect = await t.get("effects", effectId);
|
|
220
326
|
if (!effect)
|
|
221
|
-
return;
|
|
327
|
+
return { status: "uncertain" };
|
|
222
328
|
const { request } = effect;
|
|
329
|
+
const active = s && s.status !== "cancelled" && s.activeTurnId === request.turnId;
|
|
330
|
+
// A tool call whose fate the harness could not learn fails to the model (R5.3): it may
|
|
331
|
+
// have run, and nothing sends it again. A cancel already settled it.
|
|
332
|
+
if (request.kind === "tool") {
|
|
333
|
+
if (effect.status === "completed")
|
|
334
|
+
return { status: "completed", outcome: effect.outcome };
|
|
335
|
+
if (effect.status !== "invoking" || !active)
|
|
336
|
+
return { status: "uncertain" };
|
|
337
|
+
effect.error = result.error;
|
|
338
|
+
const outcome = await settleToolRow(t, s, effect, interrupted(`${result.error} (the call may have run; it is not sent again)`), { writer: coreIn(scope) });
|
|
339
|
+
return { status: "completed", outcome };
|
|
340
|
+
}
|
|
223
341
|
effect.status = "uncertain";
|
|
224
342
|
effect.error = result.error;
|
|
225
343
|
await t.put("effects", effectId, effect);
|
|
226
|
-
if (
|
|
227
|
-
await t.event(s.id, request.turnId, "effect.uncertain", { effectId, message: result.error });
|
|
344
|
+
if (active)
|
|
345
|
+
await t.event(s.id, request.turnId, "effect.uncertain", { effectId, message: result.error }, scope.writer);
|
|
346
|
+
return { status: "uncertain" };
|
|
228
347
|
});
|
|
229
|
-
return { status: "uncertain" };
|
|
230
348
|
}
|
|
231
|
-
|
|
349
|
+
// What a harness reports never carries the Runtime's own tokens into the record (R4).
|
|
350
|
+
const value = await shaped(scope, effectId, redactTokens(result.value));
|
|
232
351
|
return ctx.store.tx(async (t) => {
|
|
233
352
|
const s = await t.assertEpoch(lease.sessionId, lease.epoch);
|
|
234
353
|
const effect = await t.get("effects", effectId);
|
|
@@ -237,29 +356,40 @@ export async function recordOutcome(scope, effectId, result) {
|
|
|
237
356
|
const { request } = effect;
|
|
238
357
|
if (s.status === "cancelled" || s.activeTurnId !== request.turnId || abortKind(signal) === "cancel")
|
|
239
358
|
throw TURN_CANCELLED();
|
|
359
|
+
if (request.kind === "tool") {
|
|
360
|
+
// Settled already: a re-send after a lost hop collected what the first send recorded.
|
|
361
|
+
if (effect.status === "completed")
|
|
362
|
+
return { status: "completed", outcome: effect.outcome };
|
|
363
|
+
// What the call returned is the harness's claim, or, for a tool core ran, core's (R4.9).
|
|
364
|
+
const outcome = await settleToolRow(t, s, effect, value, { writer: scope.writer });
|
|
365
|
+
if (request.context?.role === "verify-http")
|
|
366
|
+
await recordVerdict(t, request, value);
|
|
367
|
+
return { status: "completed", outcome };
|
|
368
|
+
}
|
|
240
369
|
effect.status = "completed";
|
|
241
370
|
effect.outcome = { value };
|
|
371
|
+
effect.settledAt = new Date().toISOString();
|
|
242
372
|
await t.put("effects", effectId, effect);
|
|
243
373
|
const model = request.kind === "model";
|
|
244
374
|
const failed = model ? modelFailed(request, value) : undefined;
|
|
245
|
-
// A summary call is never an assistant message;
|
|
375
|
+
// A summary call is never an assistant message; it publishes context.compacted, `partial`
|
|
376
|
+
// unless it is the last.
|
|
246
377
|
const summarizing = model && Boolean(request.context.compaction);
|
|
247
378
|
const compacted = summarizing && !failed ? contextCompacted(request, value) : undefined;
|
|
248
|
-
const transcript = failed || summarizing
|
|
249
|
-
? undefined
|
|
250
|
-
: model
|
|
251
|
-
? assistantMessage(request, value)
|
|
252
|
-
: toolCompleted(request, value);
|
|
379
|
+
const transcript = failed || summarizing || !model ? undefined : assistantMessage(request, value);
|
|
253
380
|
if (request.context?.role === "verify-http")
|
|
254
381
|
await recordVerdict(t, request, value);
|
|
382
|
+
const writer = scope.writer;
|
|
383
|
+
// The call's span (R7.1): the journal's window, and the meter's row for it when a gate ran it.
|
|
384
|
+
const facts = failed || compacted || transcript
|
|
385
|
+
? modelOutcomeFacts(effectId, effect, await t.modelUsageOf(effectId))
|
|
386
|
+
: undefined;
|
|
255
387
|
if (failed)
|
|
256
|
-
await t.event(s.id, request.turnId, "model.failed", failed);
|
|
388
|
+
await t.event(s.id, request.turnId, "model.failed", { ...failed, ...facts }, writer);
|
|
257
389
|
if (compacted)
|
|
258
|
-
await t.event(s.id, request.turnId, "context.compacted", compacted);
|
|
259
|
-
if (transcript
|
|
260
|
-
await t.event(s.id, request.turnId, "message.assistant", transcript);
|
|
261
|
-
else if (transcript)
|
|
262
|
-
await t.event(s.id, request.turnId, "tool.completed", transcript);
|
|
390
|
+
await t.event(s.id, request.turnId, "context.compacted", { ...compacted, ...facts }, writer);
|
|
391
|
+
if (transcript)
|
|
392
|
+
await t.event(s.id, request.turnId, "message.assistant", { ...transcript, ...facts }, writer);
|
|
263
393
|
return { status: "completed", outcome: effect.outcome };
|
|
264
394
|
});
|
|
265
395
|
}
|
|
@@ -283,8 +413,8 @@ async function shaped(scope, effectId, value) {
|
|
|
283
413
|
if (!s || !effect || effect.status === "completed")
|
|
284
414
|
return undefined;
|
|
285
415
|
const request = effect.request;
|
|
286
|
-
// An agent's tool; a flow's HTTP stage or verifier names no
|
|
287
|
-
if (request.kind !== "tool" || request.
|
|
416
|
+
// An agent's tool; a flow's HTTP stage or verifier names no tool, and its output is data.
|
|
417
|
+
if (request.kind !== "tool" || request.toolName === undefined)
|
|
288
418
|
return undefined;
|
|
289
419
|
const mcp = mcpToolOf(s.mcpSnapshot, request) !== undefined;
|
|
290
420
|
if (!mcp && !isHttpToolCall(s.manifest, request))
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps the Runtime's own tokens out of the record (track R4). A harness runs
|
|
3
|
+
* model-written commands with the egress token in their proxy variables, and holds its run and
|
|
4
|
+
* host tokens; a command that prints its environment, or a model that repeats it, would put
|
|
5
|
+
* them into a tool result, an assistant message or a transcript edit. Core replaces every
|
|
6
|
+
* Nylorun-signed token (a compact JWT whose `typ` is `nylorun-…`) in what a harness reports with
|
|
7
|
+
* `[nylorun token redacted]` before recording it, whichever harness reported it. Other values
|
|
8
|
+
* pass through unchanged, and the walk keeps the value's shape.
|
|
9
|
+
*/
|
|
10
|
+
import { tokenType } from "../tenant/jwt.js";
|
|
11
|
+
export const REDACTED_TOKEN = "[nylorun token redacted]";
|
|
12
|
+
/** A compact JWT: three base64url parts, the first two JSON objects. */
|
|
13
|
+
const COMPACT_JWT = /eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{16,}/g;
|
|
14
|
+
function redactString(text) {
|
|
15
|
+
if (!text.includes("eyJ"))
|
|
16
|
+
return text;
|
|
17
|
+
return text.replace(COMPACT_JWT, (token) => (tokenType(token)?.startsWith("nylorun-") ? REDACTED_TOKEN : token));
|
|
18
|
+
}
|
|
19
|
+
/** `value` with every Nylorun-signed token in its strings replaced; the same value when there is none. */
|
|
20
|
+
export function redactTokens(value) {
|
|
21
|
+
if (typeof value === "string")
|
|
22
|
+
return redactString(value);
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
let changed = false;
|
|
25
|
+
const next = value.map((item) => {
|
|
26
|
+
const redacted = redactTokens(item);
|
|
27
|
+
if (redacted !== item)
|
|
28
|
+
changed = true;
|
|
29
|
+
return redacted;
|
|
30
|
+
});
|
|
31
|
+
return (changed ? next : value);
|
|
32
|
+
}
|
|
33
|
+
if (value !== null && typeof value === "object") {
|
|
34
|
+
let changed = false;
|
|
35
|
+
const next = {};
|
|
36
|
+
for (const [key, item] of Object.entries(value)) {
|
|
37
|
+
const redacted = redactTokens(item);
|
|
38
|
+
if (redacted !== item)
|
|
39
|
+
changed = true;
|
|
40
|
+
next[key] = redacted;
|
|
41
|
+
}
|
|
42
|
+
return (changed ? next : value);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
@@ -5,6 +5,12 @@ import type { RunOf } from "../tenant/run-grants.js";
|
|
|
5
5
|
export interface HarnessPeer {
|
|
6
6
|
/** For logs. */
|
|
7
7
|
readonly name: string;
|
|
8
|
+
/**
|
|
9
|
+
* The harness id its credential is for (R4.1): `nylorun` for the harness token, a custom
|
|
10
|
+
* harness's id for its own token. Its `hello` must name it. Absent for an in-process
|
|
11
|
+
* harness, which may name any id; a pod's host is checked against its Environment's harness.
|
|
12
|
+
*/
|
|
13
|
+
readonly harness?: string;
|
|
8
14
|
/** A pod sandbox's engine (F7.2): the sandbox it hosts, at the host epoch of its token. */
|
|
9
15
|
readonly host?: {
|
|
10
16
|
readonly sandboxId: string;
|
|
@@ -28,6 +34,8 @@ export interface RunOffer {
|
|
|
28
34
|
* only a harness that hosts no sandbox does.
|
|
29
35
|
*/
|
|
30
36
|
readonly pod?: string;
|
|
37
|
+
/** The session's harness (R4.1): only a connection that said this id takes it. Default ours. */
|
|
38
|
+
readonly harness?: string;
|
|
31
39
|
/** How long to wait for a harness to take it. Default the server's `offerWaitMs`. */
|
|
32
40
|
readonly waitMs?: number;
|
|
33
41
|
}
|
|
@@ -51,6 +59,14 @@ export type RunEnd = {
|
|
|
51
59
|
/** No harness took the run within `offerWaitMs`. */
|
|
52
60
|
| {
|
|
53
61
|
readonly kind: "unavailable";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The harness that would take the run cannot: a tool it runs itself (`features.tools`) has the
|
|
65
|
+
* name of a tool of the turn's agents. The turn fails with `message`.
|
|
66
|
+
*/
|
|
67
|
+
| {
|
|
68
|
+
readonly kind: "refused";
|
|
69
|
+
readonly message: string;
|
|
54
70
|
};
|
|
55
71
|
export interface HarnessApiServer {
|
|
56
72
|
/** Serves a harness's channel until it closes. Returns a function that detaches it. */
|
|
@@ -58,19 +74,26 @@ export interface HarnessApiServer {
|
|
|
58
74
|
offer(offer: RunOffer): Promise<RunEnd>;
|
|
59
75
|
/**
|
|
60
76
|
* Sends a `workspace.*` request to a harness that serves workspaces: with `pod`, the host of
|
|
61
|
-
* that pod sandbox
|
|
62
|
-
*
|
|
77
|
+
* that pod sandbox (with `minEpoch`, one that joined at that host epoch or later, so a pod's
|
|
78
|
+
* new volume is never written through its old host); otherwise one that hosts no sandbox.
|
|
79
|
+
* Throws `NoWorkspaceHarness` when none is attached.
|
|
63
80
|
*/
|
|
64
81
|
workspace<M extends CoreMethod>(method: M, params: ParamsOf<M>, signal?: AbortSignal, target?: {
|
|
65
82
|
readonly pod?: string;
|
|
83
|
+
readonly minEpoch?: number;
|
|
66
84
|
}): Promise<ResultOf<M>>;
|
|
67
85
|
/**
|
|
68
86
|
* Pod sandbox `sandboxId`'s host epoch moved to `epoch` (F7.2): connections hosting it at an
|
|
69
87
|
* older epoch are closed.
|
|
70
88
|
*/
|
|
71
89
|
revokeHost(sandboxId: string, epoch: number): void;
|
|
72
|
-
/**
|
|
73
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Whether a host of pod sandbox `sandboxId` is connected: with `minEpoch`, one that joined at
|
|
92
|
+
* that host epoch or later. A restore or a lost volume moves the epoch before the old host's
|
|
93
|
+
* connection is closed (`host.revoked` is delivered after the commit), so a caller that writes
|
|
94
|
+
* the new volume passes the Environment's epoch.
|
|
95
|
+
*/
|
|
96
|
+
hosting(sandboxId: string, minEpoch?: number): boolean;
|
|
74
97
|
/** Harnesses attached now, and whether one serves workspaces. */
|
|
75
98
|
status(): HarnessStatus;
|
|
76
99
|
/** Harnesses attached now. */
|
|
@@ -94,3 +117,11 @@ export interface HarnessApiServerOptions {
|
|
|
94
117
|
sandboxPreference?: string;
|
|
95
118
|
}
|
|
96
119
|
export declare function createHarnessApiServer(ctx: TenantContext, options?: HarnessApiServerOptions): HarnessApiServer;
|
|
120
|
+
/**
|
|
121
|
+
* The tools a harness runs itself (`features.tools`) whose names the turn already uses: a tool of
|
|
122
|
+
* the turn's agents (their own and the Runtime's `runtime` tools), a session tool, `tool_search` or
|
|
123
|
+
* `tool_call` (session tools a later step may get). Tools are known by name alone, so a native
|
|
124
|
+
* tool must not take one (Harness API 3); `hello` already refuses a name with `__`, which names an
|
|
125
|
+
* MCP server's tools. A flow turn runs no native tools.
|
|
126
|
+
*/
|
|
127
|
+
export declare function nativeToolClashes(tools: ReadonlySet<string> | undefined, start: TurnStart): string[];
|