@tangle-network/agent-runtime 0.266.0 → 0.273.3
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/dist/{activation-BERiJjx5.js → activation-Cx2dKA_l.js} +2 -2
- package/dist/{activation-BERiJjx5.js.map → activation-Cx2dKA_l.js.map} +1 -1
- package/dist/{activation-VpxSbDKJ.d.ts → activation-IfDlhR3J.d.ts} +4 -4
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +2 -2
- package/dist/candidate-execution/index.js +2 -2
- package/dist/{coordination-driver-DdZ-nh_4.js → coordination-driver-CkcW4dxU.js} +173 -92
- package/dist/coordination-driver-CkcW4dxU.js.map +1 -0
- package/dist/{delegate-Ajx_PVyu.js → delegate-Dvb1-mE-.js} +2 -2
- package/dist/{delegate-Ajx_PVyu.js.map → delegate-Dvb1-mE-.js.map} +1 -1
- package/dist/durable.d.ts +3 -571
- package/dist/durable.js +93 -19
- package/dist/durable.js.map +1 -1
- package/dist/{graph-CK50N9vG.js → graph-C69Uxtzt.js} +17 -8
- package/dist/graph-C69Uxtzt.js.map +1 -0
- package/dist/{improvement-cycle-GRPoHLm1.js → improvement-cycle-CO2HRCQq.js} +10 -10
- package/dist/improvement-cycle-CO2HRCQq.js.map +1 -0
- package/dist/{index-CU2mlMfc.d.ts → index-D2ipSqkP.d.ts} +4335 -3055
- package/dist/index.d.ts +7 -451
- package/dist/index.js +14 -11
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +9 -3
- package/dist/intelligence.js +13 -7
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +9 -9
- package/dist/{loop-runner-bin-cC16EPUq.js → loop-runner-bin-B9MIq9O7.js} +3 -3
- package/dist/{loop-runner-bin-cC16EPUq.js.map → loop-runner-bin-B9MIq9O7.js.map} +1 -1
- package/dist/{loop-runner-bin-XKZXM08Q.d.ts → loop-runner-bin-ThlwVkvo.d.ts} +3 -3
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +12 -6
- package/dist/mcp/index.js +7 -7
- package/dist/mcp/memory-bin.js +1 -1
- package/dist/{memory-server-Cn8LbAxO.js → memory-server-DXQ8IxGR.js} +2 -2
- package/dist/{memory-server-Cn8LbAxO.js.map → memory-server-DXQ8IxGR.js.map} +1 -1
- package/dist/{openai-tools-D3ajI_D2.d.ts → openai-tools-Bb0mAr5z.d.ts} +2 -2
- package/dist/{prepare-oHUQdC5-.js → prepare-DVlvGP7V.js} +2 -2
- package/dist/{prepare-oHUQdC5-.js.map → prepare-DVlvGP7V.js.map} +1 -1
- package/dist/profiles.d.ts +2 -2
- package/dist/{protected-model-port-DqF0qDU6.js → protected-model-port-BrWgsCnl.js} +2 -2
- package/dist/{protected-model-port-DqF0qDU6.js.map → protected-model-port-BrWgsCnl.js.map} +1 -1
- package/dist/{protected-redaction--F3v1oo8.js → protected-redaction-DylCC_ot.js} +14 -20
- package/dist/protected-redaction-DylCC_ot.js.map +1 -0
- package/dist/{provision-supervisor-Bf_bPaCv.js → provision-supervisor-BKAvsDtP.js} +3 -3
- package/dist/{provision-supervisor-Bf_bPaCv.js.map → provision-supervisor-BKAvsDtP.js.map} +1 -1
- package/dist/{runtime-hOLlAc1i.js → runtime-CHxXrUeZ.js} +1714 -854
- package/dist/runtime-CHxXrUeZ.js.map +1 -0
- package/dist/{server-DOb67ohd.js → server-2OeFrEVG.js} +4 -4
- package/dist/{server-DOb67ohd.js.map → server-2OeFrEVG.js.map} +1 -1
- package/dist/{stream-agent-turn-Gw9hLpBn.d.ts → stream-agent-turn-ewm3BZAp.d.ts} +34 -17
- package/dist/{structural-rollout-DV9YoQF6.js → structural-rollout-DCUprNpH.js} +2 -2
- package/dist/{structural-rollout-DV9YoQF6.js.map → structural-rollout-DCUprNpH.js.map} +1 -1
- package/dist/{substrate-CLt9S9ym.d.ts → substrate-VKjmrksz.d.ts} +2 -2
- package/dist/{supervise--njTtSL3.js → supervise-Dej5aSDa.js} +220 -52
- package/dist/supervise-Dej5aSDa.js.map +1 -0
- package/dist/{supervisor-B8UMAXya.js → supervisor-DdKPGluv.js} +1233 -303
- package/dist/supervisor-DdKPGluv.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +62 -62
- package/dist/testing.js.map +1 -1
- package/dist/{tool-server-x9NEhvHZ.js → tool-server-Dqer2M4_.js} +30 -12
- package/dist/tool-server-Dqer2M4_.js.map +1 -0
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{types-CrnwLVYQ.d.ts → types-DMRc3jru.d.ts} +463 -105
- package/package.json +6 -5
- package/dist/coordination-driver-DdZ-nh_4.js.map +0 -1
- package/dist/graph-CK50N9vG.js.map +0 -1
- package/dist/improvement-cycle-GRPoHLm1.js.map +0 -1
- package/dist/protected-redaction--F3v1oo8.js.map +0 -1
- package/dist/runtime-hOLlAc1i.js.map +0 -1
- package/dist/supervise--njTtSL3.js.map +0 -1
- package/dist/supervisor-B8UMAXya.js.map +0 -1
- package/dist/tool-server-x9NEhvHZ.js.map +0 -1
|
@@ -14,7 +14,8 @@ import { access, chmod, copyFile, mkdir, mkdtemp, open, readFile, readdir, realp
|
|
|
14
14
|
import { applyWorkspacePlan, hashWorkspacePlan, materializeProfile, resolveAgentProfileResources } from "@tangle-network/agent-profile-materialize";
|
|
15
15
|
import { homedir, hostname, tmpdir } from "node:os";
|
|
16
16
|
import { stripVTControlCharacters } from "node:util";
|
|
17
|
-
import { deriveHexId, isW3CSpanId, isW3CTraceId } from "@tangle-network/agent-trace-contract";
|
|
17
|
+
import { ATTR, deriveHexId, isW3CSpanId, isW3CTraceId } from "@tangle-network/agent-trace-contract";
|
|
18
|
+
import { REDACTION_VERSION as REDACTION_VERSION$1, redact } from "@tangle-network/agent-eval/traces";
|
|
18
19
|
import { SDKError, generateIdempotencyKey, withRetry } from "@tangle-network/agent-core";
|
|
19
20
|
import { collectAgentFinalMessageText, collectAgentResponseText } from "@tangle-network/sandbox";
|
|
20
21
|
import { request } from "node:http";
|
|
@@ -4797,7 +4798,7 @@ function sanitizeAgentRuntimeEvent(event, options = {}) {
|
|
|
4797
4798
|
if (event.type === "questions_end") return {
|
|
4798
4799
|
...base,
|
|
4799
4800
|
questions: event.questions.map((question) => sanitizeQuestion(question, options)),
|
|
4800
|
-
userAnswers: options.includeUserAnswers ? event.userAnswers : redactRecord(event.userAnswers)
|
|
4801
|
+
userAnswers: options.includeUserAnswers ? included(event.userAnswers) : redactRecord(event.userAnswers)
|
|
4801
4802
|
};
|
|
4802
4803
|
if (event.type === "acquisition_start") return {
|
|
4803
4804
|
...base,
|
|
@@ -4845,7 +4846,7 @@ function sanitizeRuntimeStreamEvent(event, options = {}) {
|
|
|
4845
4846
|
...withTask,
|
|
4846
4847
|
timestamp: event.timestamp,
|
|
4847
4848
|
questions: event.questions.map((question) => sanitizeQuestion(question, options)),
|
|
4848
|
-
userAnswers: options.includeUserAnswers ? event.userAnswers : redactRecord(event.userAnswers)
|
|
4849
|
+
userAnswers: options.includeUserAnswers ? included(event.userAnswers) : redactRecord(event.userAnswers)
|
|
4849
4850
|
};
|
|
4850
4851
|
if (event.type === "acquisition_start") return {
|
|
4851
4852
|
type: event.type,
|
|
@@ -4868,7 +4869,7 @@ function sanitizeRuntimeStreamEvent(event, options = {}) {
|
|
|
4868
4869
|
timestamp: event.timestamp,
|
|
4869
4870
|
toolName: event.toolName,
|
|
4870
4871
|
toolCallId: event.toolCallId,
|
|
4871
|
-
args: options.includeControlPayloads ? event.args : void 0
|
|
4872
|
+
args: options.includeControlPayloads ? included(event.args) : void 0
|
|
4872
4873
|
};
|
|
4873
4874
|
if (event.type === "tool_result") return {
|
|
4874
4875
|
type: event.type,
|
|
@@ -4877,7 +4878,7 @@ function sanitizeRuntimeStreamEvent(event, options = {}) {
|
|
|
4877
4878
|
timestamp: event.timestamp,
|
|
4878
4879
|
toolName: event.toolName,
|
|
4879
4880
|
toolCallId: event.toolCallId,
|
|
4880
|
-
result: options.includeControlPayloads ? event.result : void 0
|
|
4881
|
+
result: options.includeControlPayloads ? included(event.result) : void 0
|
|
4881
4882
|
};
|
|
4882
4883
|
if (event.type === "llm_call") return {
|
|
4883
4884
|
type: event.type,
|
|
@@ -4900,8 +4901,8 @@ function sanitizeRuntimeStreamEvent(event, options = {}) {
|
|
|
4900
4901
|
name: event.name,
|
|
4901
4902
|
mimeType: event.mimeType,
|
|
4902
4903
|
uri: options.includeEvidenceIds ? event.uri : void 0,
|
|
4903
|
-
content: options.includeControlPayloads ? event.content : void 0,
|
|
4904
|
-
metadata: options.includeMetadata ? event.metadata : void 0
|
|
4904
|
+
content: options.includeControlPayloads ? included(event.content) : void 0,
|
|
4905
|
+
metadata: options.includeMetadata ? included(event.metadata) : void 0
|
|
4905
4906
|
};
|
|
4906
4907
|
if (event.type === "proposal_created") return {
|
|
4907
4908
|
type: event.type,
|
|
@@ -4909,16 +4910,16 @@ function sanitizeRuntimeStreamEvent(event, options = {}) {
|
|
|
4909
4910
|
...withSession,
|
|
4910
4911
|
timestamp: event.timestamp,
|
|
4911
4912
|
proposalId: event.proposalId,
|
|
4912
|
-
title: options.includeControlPayloads ? event.title : void 0,
|
|
4913
|
-
content: options.includeControlPayloads ? event.content : void 0,
|
|
4913
|
+
title: options.includeControlPayloads ? included(event.title) : void 0,
|
|
4914
|
+
content: options.includeControlPayloads ? included(event.content) : void 0,
|
|
4914
4915
|
status: event.status
|
|
4915
4916
|
};
|
|
4916
4917
|
if (event.type === "final") {
|
|
4917
4918
|
const sanitizedError = event.error !== void 0 ? {
|
|
4918
4919
|
kind: event.error.kind,
|
|
4919
|
-
message: event.error.message,
|
|
4920
|
+
message: included(event.error.message),
|
|
4920
4921
|
status: event.error.status,
|
|
4921
|
-
body: options.includeControlPayloads ? event.error.body : void 0
|
|
4922
|
+
body: options.includeControlPayloads ? included(event.error.body) : void 0
|
|
4922
4923
|
} : void 0;
|
|
4923
4924
|
return {
|
|
4924
4925
|
type: event.type,
|
|
@@ -4927,8 +4928,8 @@ function sanitizeRuntimeStreamEvent(event, options = {}) {
|
|
|
4927
4928
|
timestamp: event.timestamp,
|
|
4928
4929
|
status: event.status,
|
|
4929
4930
|
reason: event.reason,
|
|
4930
|
-
text: options.includeControlPayloads ? event.text : void 0,
|
|
4931
|
-
metadata: options.includeMetadata ? event.metadata : void 0,
|
|
4931
|
+
text: options.includeControlPayloads ? included(event.text) : void 0,
|
|
4932
|
+
metadata: options.includeMetadata ? included(event.metadata) : void 0,
|
|
4932
4933
|
...sanitizedError !== void 0 ? { error: sanitizedError } : {}
|
|
4933
4934
|
};
|
|
4934
4935
|
}
|
|
@@ -4945,9 +4946,9 @@ function sanitizeTask(task, options) {
|
|
|
4945
4946
|
id: task.id,
|
|
4946
4947
|
intent: task.intent,
|
|
4947
4948
|
domain: task.domain,
|
|
4948
|
-
inputs: options.includeInputs ? task.inputs : task.inputs ? "[redacted]" : void 0,
|
|
4949
|
+
inputs: options.includeInputs ? included(task.inputs) : task.inputs ? "[redacted]" : void 0,
|
|
4949
4950
|
requiredKnowledge: task.requiredKnowledge?.map((requirement) => sanitizeKnowledgeRequirement(requirement, options)),
|
|
4950
|
-
metadata: options.includeMetadata ? task.metadata : task.metadata ? "[redacted]" : void 0
|
|
4951
|
+
metadata: options.includeMetadata ? included(task.metadata) : task.metadata ? "[redacted]" : void 0
|
|
4951
4952
|
};
|
|
4952
4953
|
}
|
|
4953
4954
|
function sanitizeRuntimeSession(session, options) {
|
|
@@ -4958,7 +4959,7 @@ function sanitizeRuntimeSession(session, options) {
|
|
|
4958
4959
|
hasResumeToken: Boolean(session.resumeToken),
|
|
4959
4960
|
createdAt: session.createdAt,
|
|
4960
4961
|
updatedAt: session.updatedAt,
|
|
4961
|
-
metadata: options.includeMetadata ? session.metadata : session.metadata ? "[redacted]" : void 0
|
|
4962
|
+
metadata: options.includeMetadata ? included(session.metadata) : session.metadata ? "[redacted]" : void 0
|
|
4962
4963
|
};
|
|
4963
4964
|
}
|
|
4964
4965
|
function sanitizeKnowledgeRequirement(requirement, options) {
|
|
@@ -5007,8 +5008,8 @@ function sanitizeControlStep(step, options) {
|
|
|
5007
5008
|
index: step.index,
|
|
5008
5009
|
decisionType: step.decision.type,
|
|
5009
5010
|
reason: step.decision.reason,
|
|
5010
|
-
action: options.includeControlPayloads && step.decision.type === "continue" ? step.decision.action : void 0,
|
|
5011
|
-
result: options.includeControlPayloads && actionOutcome?.ok ? actionOutcome.result : void 0,
|
|
5011
|
+
action: options.includeControlPayloads && step.decision.type === "continue" ? included(step.decision.action) : void 0,
|
|
5012
|
+
result: options.includeControlPayloads && actionOutcome?.ok ? included(actionOutcome.result) : void 0,
|
|
5012
5013
|
actionOk: actionOutcome?.ok,
|
|
5013
5014
|
actionError: actionOutcome?.ok === false ? actionOutcome.error : void 0,
|
|
5014
5015
|
durationMs: actionOutcome?.durationMs,
|
|
@@ -5041,10 +5042,17 @@ function summarizeEvals(evals, options) {
|
|
|
5041
5042
|
score: evalResult.score,
|
|
5042
5043
|
severity: evalResult.severity,
|
|
5043
5044
|
objective: evalResult.objective,
|
|
5044
|
-
detail: options.includeEvalDetails ? evalResult.detail : void 0,
|
|
5045
|
-
evidence: options.includeEvalDetails ? evalResult.evidence : void 0
|
|
5045
|
+
detail: options.includeEvalDetails ? included(evalResult.detail) : void 0,
|
|
5046
|
+
evidence: options.includeEvalDetails ? included(evalResult.evidence) : void 0
|
|
5046
5047
|
}));
|
|
5047
5048
|
}
|
|
5049
|
+
/**
|
|
5050
|
+
* An opted-in payload still goes through agent-eval's redaction core: the
|
|
5051
|
+
* flags decide whether content is shared, never whether credentials are.
|
|
5052
|
+
*/
|
|
5053
|
+
function included(value) {
|
|
5054
|
+
return value === void 0 ? value : redact(value).value;
|
|
5055
|
+
}
|
|
5048
5056
|
function redactRecord(record) {
|
|
5049
5057
|
return Object.fromEntries(Object.keys(record).map((key) => [key, "[redacted]"]));
|
|
5050
5058
|
}
|
|
@@ -5058,7 +5066,7 @@ function pickPublicStreamFields(event) {
|
|
|
5058
5066
|
} : void 0;
|
|
5059
5067
|
return {
|
|
5060
5068
|
backend: event.backend,
|
|
5061
|
-
message: event.message,
|
|
5069
|
+
message: included(event.message),
|
|
5062
5070
|
recoverable: event.recoverable,
|
|
5063
5071
|
...sanitizedError !== void 0 ? { error: sanitizedError } : {}
|
|
5064
5072
|
};
|
|
@@ -5171,25 +5179,30 @@ const GEN_AI = {
|
|
|
5171
5179
|
* useless for the runs you most want to look at.
|
|
5172
5180
|
*/
|
|
5173
5181
|
function createOpenInferenceFileExporter(filePath) {
|
|
5174
|
-
|
|
5182
|
+
let written = 0;
|
|
5183
|
+
let dropped = 0;
|
|
5175
5184
|
let failed;
|
|
5176
|
-
const append = (line) => {
|
|
5177
|
-
try {
|
|
5178
|
-
appendFileSync(filePath, `${line}\n`, "utf8");
|
|
5179
|
-
} catch (error) {
|
|
5180
|
-
failed ??= error instanceof Error ? error : new Error(String(error));
|
|
5181
|
-
}
|
|
5182
|
-
};
|
|
5183
5185
|
return {
|
|
5184
5186
|
exportSpan(span) {
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
+
try {
|
|
5188
|
+
appendFileSync(filePath, `${JSON.stringify(toOpenInferenceLine(span))}\n`, "utf8");
|
|
5189
|
+
written += 1;
|
|
5190
|
+
} catch (error) {
|
|
5191
|
+
dropped += 1;
|
|
5192
|
+
failed ??= error instanceof Error ? error : new Error(String(error));
|
|
5193
|
+
}
|
|
5187
5194
|
},
|
|
5188
5195
|
async flush() {
|
|
5189
|
-
if (failed) throw new Error(`OpenInference file exporter failed writing ${filePath}: ${failed.message}`);
|
|
5196
|
+
if (failed) throw new Error(`OpenInference file exporter failed writing ${filePath} (${dropped} spans dropped): ${failed.message}`);
|
|
5190
5197
|
},
|
|
5191
|
-
async shutdown() {
|
|
5192
|
-
|
|
5198
|
+
async shutdown() {},
|
|
5199
|
+
stats() {
|
|
5200
|
+
return {
|
|
5201
|
+
written,
|
|
5202
|
+
dropped,
|
|
5203
|
+
pending: 0,
|
|
5204
|
+
...failed ? { lastError: failed.message } : {}
|
|
5205
|
+
};
|
|
5193
5206
|
}
|
|
5194
5207
|
};
|
|
5195
5208
|
}
|
|
@@ -5240,68 +5253,172 @@ function toOpenInferenceLine(span) {
|
|
|
5240
5253
|
};
|
|
5241
5254
|
}
|
|
5242
5255
|
/**
|
|
5243
|
-
* Create an
|
|
5256
|
+
* Create an OTLP/HTTP exporter. Returns undefined when no endpoint is configured.
|
|
5257
|
+
*
|
|
5258
|
+
* One batch is in flight at a time; spans that arrive meanwhile wait in a queue bounded by
|
|
5259
|
+
* `maxQueueSize`. Every span ends in exactly one of `written` or `dropped`: a non-2xx response, a
|
|
5260
|
+
* network error, a timeout, an OTLP `partialSuccess.rejectedSpans` count and a full queue each count
|
|
5261
|
+
* as drops and set `lastError`. Nothing here throws into the caller's run; `flush()` reports the
|
|
5262
|
+
* loss, the same rule {@link createOpenInferenceFileExporter} follows.
|
|
5244
5263
|
*/
|
|
5245
5264
|
function createOtelExporter(config) {
|
|
5246
5265
|
const resolvedEndpoint = config?.endpoint ?? (typeof process !== "undefined" ? process.env.OTEL_EXPORTER_OTLP_ENDPOINT : void 0);
|
|
5247
5266
|
if (!resolvedEndpoint) return void 0;
|
|
5248
|
-
const
|
|
5267
|
+
const url = `${resolvedEndpoint.replace(/\/+$/, "")}/v1/traces`;
|
|
5249
5268
|
const headers = config?.headers ?? parseHeadersFromEnv();
|
|
5250
|
-
const batchSize = config?.batchSize
|
|
5269
|
+
const batchSize = positiveInteger(config?.batchSize, 64);
|
|
5251
5270
|
const flushIntervalMs = config?.flushIntervalMs ?? 5e3;
|
|
5252
|
-
const
|
|
5253
|
-
const
|
|
5254
|
-
const
|
|
5255
|
-
|
|
5271
|
+
const maxQueueSize = Math.max(positiveInteger(config?.maxQueueSize, 2048), batchSize);
|
|
5272
|
+
const timeoutMs = positiveInteger(config?.timeoutMs, 1e4);
|
|
5273
|
+
const resource = { attributes: toOtelAttributes({
|
|
5274
|
+
"service.name": config?.serviceName ?? "agent-runtime",
|
|
5275
|
+
...config?.resourceAttributes ?? {}
|
|
5276
|
+
}) };
|
|
5277
|
+
const queue = [];
|
|
5278
|
+
let inFlight = 0;
|
|
5279
|
+
let written = 0;
|
|
5280
|
+
let dropped = 0;
|
|
5281
|
+
let droppedAtLastFlush = 0;
|
|
5282
|
+
let lastError;
|
|
5283
|
+
let sending;
|
|
5256
5284
|
let stopped = false;
|
|
5257
|
-
const
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
pending.push(span);
|
|
5261
|
-
if (pending.length >= batchSize) doFlush();
|
|
5262
|
-
},
|
|
5263
|
-
async flush() {
|
|
5264
|
-
await doFlush();
|
|
5265
|
-
},
|
|
5266
|
-
async shutdown() {
|
|
5267
|
-
stopped = true;
|
|
5268
|
-
if (timer !== void 0) {
|
|
5269
|
-
clearInterval(timer);
|
|
5270
|
-
timer = void 0;
|
|
5271
|
-
}
|
|
5272
|
-
await doFlush();
|
|
5273
|
-
}
|
|
5285
|
+
const drop = (count, reason) => {
|
|
5286
|
+
dropped += count;
|
|
5287
|
+
lastError = reason;
|
|
5274
5288
|
};
|
|
5275
|
-
|
|
5276
|
-
if (pending.length > 0) doFlush();
|
|
5277
|
-
}, flushIntervalMs);
|
|
5278
|
-
if (typeof timer === "object" && "unref" in timer) timer.unref();
|
|
5279
|
-
async function doFlush() {
|
|
5280
|
-
if (pending.length === 0) return;
|
|
5281
|
-
const batch = pending.splice(0);
|
|
5289
|
+
async function send(batch) {
|
|
5282
5290
|
const body = { resourceSpans: [{
|
|
5283
|
-
resource
|
|
5284
|
-
"service.name": serviceName,
|
|
5285
|
-
...resourceAttrs
|
|
5286
|
-
}) },
|
|
5291
|
+
resource,
|
|
5287
5292
|
scopeSpans: [{
|
|
5288
5293
|
scope: SCOPE,
|
|
5289
5294
|
spans: batch
|
|
5290
5295
|
}]
|
|
5291
5296
|
}] };
|
|
5292
|
-
const url = `${endpoint.replace(/\/+$/, "")}/v1/traces`;
|
|
5293
5297
|
try {
|
|
5294
|
-
await fetch(url, {
|
|
5298
|
+
const response = await fetch(url, {
|
|
5295
5299
|
method: "POST",
|
|
5296
5300
|
headers: {
|
|
5297
5301
|
"content-type": "application/json",
|
|
5298
5302
|
...headers
|
|
5299
5303
|
},
|
|
5300
|
-
body: JSON.stringify(body)
|
|
5304
|
+
body: JSON.stringify(body),
|
|
5305
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
5301
5306
|
});
|
|
5302
|
-
|
|
5307
|
+
const text = await response.text().catch(() => "");
|
|
5308
|
+
if (!response.ok) {
|
|
5309
|
+
const detail = text ? `: ${text.slice(0, 200)}` : "";
|
|
5310
|
+
drop(batch.length, `HTTP ${response.status} from ${url}${detail}`);
|
|
5311
|
+
return;
|
|
5312
|
+
}
|
|
5313
|
+
const rejected = rejectedSpans(text, batch.length);
|
|
5314
|
+
if (rejected.count > 0) drop(rejected.count, `collector rejected ${rejected.count} spans: ${rejected.message}`);
|
|
5315
|
+
written += batch.length - rejected.count;
|
|
5316
|
+
} catch (error) {
|
|
5317
|
+
drop(batch.length, `POST ${url} failed: ${describeFetchError(error)}`);
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
/** The single sender: it drains the queue batch by batch until the queue is empty. */
|
|
5321
|
+
function drain() {
|
|
5322
|
+
sending ??= (async () => {
|
|
5323
|
+
try {
|
|
5324
|
+
while (queue.length > 0) {
|
|
5325
|
+
const batch = queue.splice(0, batchSize);
|
|
5326
|
+
inFlight = batch.length;
|
|
5327
|
+
await send(batch);
|
|
5328
|
+
inFlight = 0;
|
|
5329
|
+
}
|
|
5330
|
+
} finally {
|
|
5331
|
+
sending = void 0;
|
|
5332
|
+
}
|
|
5333
|
+
})();
|
|
5334
|
+
return sending;
|
|
5335
|
+
}
|
|
5336
|
+
const timer = setInterval(() => {
|
|
5337
|
+
if (queue.length > 0) drain();
|
|
5338
|
+
}, flushIntervalMs);
|
|
5339
|
+
if (typeof timer === "object" && "unref" in timer) timer.unref();
|
|
5340
|
+
return {
|
|
5341
|
+
exportSpan(span) {
|
|
5342
|
+
if (stopped) {
|
|
5343
|
+
drop(1, "span exported after the exporter stopped");
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
if (queue.length + inFlight >= maxQueueSize) {
|
|
5347
|
+
drop(1, `queue full: ${maxQueueSize} spans already waiting on ${url}`);
|
|
5348
|
+
return;
|
|
5349
|
+
}
|
|
5350
|
+
queue.push(span);
|
|
5351
|
+
if (queue.length >= batchSize) drain();
|
|
5352
|
+
},
|
|
5353
|
+
async flush() {
|
|
5354
|
+
await drain();
|
|
5355
|
+
const lost = dropped - droppedAtLastFlush;
|
|
5356
|
+
droppedAtLastFlush = dropped;
|
|
5357
|
+
if (lost > 0) throw new Error(`OTLP exporter dropped ${lost} spans since the last flush: ${lastError}`);
|
|
5358
|
+
},
|
|
5359
|
+
async shutdown() {
|
|
5360
|
+
stopped = true;
|
|
5361
|
+
clearInterval(timer);
|
|
5362
|
+
await drain();
|
|
5363
|
+
},
|
|
5364
|
+
stats() {
|
|
5365
|
+
return {
|
|
5366
|
+
written,
|
|
5367
|
+
dropped,
|
|
5368
|
+
pending: queue.length + inFlight,
|
|
5369
|
+
...lastError !== void 0 ? { lastError } : {}
|
|
5370
|
+
};
|
|
5371
|
+
}
|
|
5372
|
+
};
|
|
5373
|
+
}
|
|
5374
|
+
/**
|
|
5375
|
+
* Spans a 2xx OTLP/HTTP response still refused. The spec lets a collector accept a request and
|
|
5376
|
+
* reject part of it through `partialSuccess.rejectedSpans`; reading only the status would count
|
|
5377
|
+
* those spans as delivered. An empty or non-JSON body is a full success.
|
|
5378
|
+
*/
|
|
5379
|
+
function rejectedSpans(text, batchLength) {
|
|
5380
|
+
if (!text.trim()) return {
|
|
5381
|
+
count: 0,
|
|
5382
|
+
message: ""
|
|
5383
|
+
};
|
|
5384
|
+
let parsed;
|
|
5385
|
+
try {
|
|
5386
|
+
parsed = JSON.parse(text);
|
|
5387
|
+
} catch {
|
|
5388
|
+
return {
|
|
5389
|
+
count: 0,
|
|
5390
|
+
message: ""
|
|
5391
|
+
};
|
|
5303
5392
|
}
|
|
5304
|
-
return
|
|
5393
|
+
if (parsed === null || typeof parsed !== "object") return {
|
|
5394
|
+
count: 0,
|
|
5395
|
+
message: ""
|
|
5396
|
+
};
|
|
5397
|
+
const partial = parsed.partialSuccess;
|
|
5398
|
+
if (partial === null || typeof partial !== "object") return {
|
|
5399
|
+
count: 0,
|
|
5400
|
+
message: ""
|
|
5401
|
+
};
|
|
5402
|
+
const { rejectedSpans: raw, errorMessage } = partial;
|
|
5403
|
+
const count = typeof raw === "string" ? Number(raw) : typeof raw === "number" ? raw : 0;
|
|
5404
|
+
if (!Number.isSafeInteger(count) || count <= 0) return {
|
|
5405
|
+
count: 0,
|
|
5406
|
+
message: ""
|
|
5407
|
+
};
|
|
5408
|
+
return {
|
|
5409
|
+
count: Math.min(count, batchLength),
|
|
5410
|
+
message: typeof errorMessage === "string" && errorMessage ? errorMessage : "no message"
|
|
5411
|
+
};
|
|
5412
|
+
}
|
|
5413
|
+
/** Node's fetch reports every socket failure as "fetch failed"; the cause names which one. */
|
|
5414
|
+
function describeFetchError(error) {
|
|
5415
|
+
if (!(error instanceof Error)) return String(error);
|
|
5416
|
+
const cause = error.cause;
|
|
5417
|
+
const code = cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string" ? cause.code : void 0;
|
|
5418
|
+
return code ? `${error.message} (${code})` : error.message;
|
|
5419
|
+
}
|
|
5420
|
+
function positiveInteger(value, fallback) {
|
|
5421
|
+
return value !== void 0 && Number.isSafeInteger(value) && value > 0 ? value : fallback;
|
|
5305
5422
|
}
|
|
5306
5423
|
/**
|
|
5307
5424
|
* Convert a LoopTraceEvent into an OtelSpan for export.
|
|
@@ -5382,8 +5499,10 @@ function buildRuntimeEventOtelSpans(events, traceId, parentSpanId, options = {})
|
|
|
5382
5499
|
"tangle.runtime.event": serialized(record)
|
|
5383
5500
|
};
|
|
5384
5501
|
let name = `tangle.runtime.${event.type}`;
|
|
5502
|
+
if (parentSpanId) attrs[ATTR.parentConfidence] = "explicit";
|
|
5385
5503
|
if (event.type === "tool_call" || event.type === "tool_result") {
|
|
5386
5504
|
name = `agent.${event.type}`;
|
|
5505
|
+
if (event.type === "tool_call") attrs[ATTR.spanKind] = "TOOL";
|
|
5387
5506
|
attrs["tool.name"] = event.toolName;
|
|
5388
5507
|
if (event.toolCallId) attrs["tool.call_id"] = event.toolCallId;
|
|
5389
5508
|
const mcp = mcpIdentity(event.toolName);
|
|
@@ -5393,6 +5512,7 @@ function buildRuntimeEventOtelSpans(events, traceId, parentSpanId, options = {})
|
|
|
5393
5512
|
if (payload !== void 0) attrs[event.type === "tool_call" ? "tool.input" : "tool.output"] = serialized(payload);
|
|
5394
5513
|
} else if (event.type === "llm_call") {
|
|
5395
5514
|
name = "gen_ai.client.inference";
|
|
5515
|
+
attrs[ATTR.spanKind] = "LLM";
|
|
5396
5516
|
attrs["gen_ai.request.model"] = event.model;
|
|
5397
5517
|
if (event.tokensIn !== void 0) attrs["gen_ai.usage.input_tokens"] = event.tokensIn;
|
|
5398
5518
|
if (event.tokensOut !== void 0) attrs["gen_ai.usage.output_tokens"] = event.tokensOut;
|
|
@@ -5446,11 +5566,25 @@ function buildLoopOtelSpans(events, traceId, rootParentSpanId) {
|
|
|
5446
5566
|
kind: 1,
|
|
5447
5567
|
startTimeUnixNano: msToNs(node.startMs),
|
|
5448
5568
|
endTimeUnixNano: msToNs(node.endMs),
|
|
5449
|
-
attributes: toOtelAttributes(
|
|
5569
|
+
attributes: toOtelAttributes({
|
|
5570
|
+
...node.attrs,
|
|
5571
|
+
[ATTR.spanKind]: LOOP_SPAN_KIND[node.kind],
|
|
5572
|
+
...node.parentSpanId === void 0 && rootParentSpanId ? { [ATTR.parentConfidence]: "explicit" } : {}
|
|
5573
|
+
}),
|
|
5450
5574
|
status: { code: node.error ? 2 : 1 }
|
|
5451
5575
|
}));
|
|
5452
5576
|
}
|
|
5453
5577
|
/**
|
|
5578
|
+
* The declared kind of each loop level. An iteration span carries its own token total, and an
|
|
5579
|
+
* undeclared span with tokens reads as an LLM call, so declaring AGENT keeps those tokens from
|
|
5580
|
+
* being counted a second time beside the model-call spans beneath it.
|
|
5581
|
+
*/
|
|
5582
|
+
const LOOP_SPAN_KIND = {
|
|
5583
|
+
loop: "CHAIN",
|
|
5584
|
+
round: "CHAIN",
|
|
5585
|
+
branch: "AGENT"
|
|
5586
|
+
};
|
|
5587
|
+
/**
|
|
5454
5588
|
* Sink-neutral core behind {@link buildLoopOtelSpans}: reconstruct the
|
|
5455
5589
|
* loop → round → branch span tree from one run's ordered `LoopTraceEvent`
|
|
5456
5590
|
* stream. Consumed by the OTEL mapper above and by the MCP delegation
|
|
@@ -5506,10 +5640,15 @@ function buildLoopSpanNodes(events) {
|
|
|
5506
5640
|
const iterStartTs = /* @__PURE__ */ new Map();
|
|
5507
5641
|
const placementByIdx = /* @__PURE__ */ new Map();
|
|
5508
5642
|
let currentRoundId;
|
|
5643
|
+
/** Plan round index → its span id, so an iteration joins the round it records (`groupId`). */
|
|
5644
|
+
const roundIdByIndex = /* @__PURE__ */ new Map();
|
|
5509
5645
|
let pendingRound;
|
|
5510
5646
|
const flushRound = (endMs) => {
|
|
5511
5647
|
if (!pendingRound) return;
|
|
5512
|
-
out.push(make(pendingRound.id, rootId, "loop.round", "round", pendingRound.start, endMs,
|
|
5648
|
+
out.push(make(pendingRound.id, rootId, "loop.round", "round", pendingRound.start, endMs, {
|
|
5649
|
+
...pendingRound.attrs,
|
|
5650
|
+
[ATTR.parentConfidence]: "explicit"
|
|
5651
|
+
}));
|
|
5513
5652
|
pendingRound = void 0;
|
|
5514
5653
|
};
|
|
5515
5654
|
for (const e of events) {
|
|
@@ -5537,6 +5676,7 @@ function buildLoopSpanNodes(events) {
|
|
|
5537
5676
|
attrs
|
|
5538
5677
|
};
|
|
5539
5678
|
currentRoundId = id;
|
|
5679
|
+
roundIdByIndex.set(roundIdx, id);
|
|
5540
5680
|
break;
|
|
5541
5681
|
}
|
|
5542
5682
|
case "loop.iteration.started": {
|
|
@@ -5583,6 +5723,8 @@ function buildLoopSpanNodes(events) {
|
|
|
5583
5723
|
if (err) attrs["tangle.loop.error"] = err;
|
|
5584
5724
|
const gid = num(p.groupId);
|
|
5585
5725
|
if (gid !== void 0) attrs["tangle.loop.iteration.group_id"] = gid;
|
|
5726
|
+
const recordedRoundId = gid === void 0 ? void 0 : roundIdByIndex.get(gid);
|
|
5727
|
+
attrs[ATTR.parentConfidence] = recordedRoundId ? "explicit" : "heuristic";
|
|
5586
5728
|
const par = num(p.parentIndex);
|
|
5587
5729
|
if (par !== void 0) attrs["tangle.loop.iteration.parent_index"] = par;
|
|
5588
5730
|
const dur = num(p.durationMs);
|
|
@@ -5590,7 +5732,7 @@ function buildLoopSpanNodes(events) {
|
|
|
5590
5732
|
const preview = str(p.outputPreview);
|
|
5591
5733
|
if (preview) attrs["tangle.loop.iteration.output_preview"] = preview;
|
|
5592
5734
|
Object.assign(attrs, placementByIdx.get(idx) ?? {});
|
|
5593
|
-
out.push(make(generateSpanId(), currentRoundId ?? rootId, "loop.iteration", "branch", start, e.timestamp, attrs, err !== void 0));
|
|
5735
|
+
out.push(make(generateSpanId(), recordedRoundId ?? currentRoundId ?? rootId, "loop.iteration", "branch", start, e.timestamp, attrs, err !== void 0));
|
|
5594
5736
|
break;
|
|
5595
5737
|
}
|
|
5596
5738
|
case "loop.decision":
|
|
@@ -5931,11 +6073,11 @@ const HARNESS_ROOTS = Object.freeze({
|
|
|
5931
6073
|
]),
|
|
5932
6074
|
codex: Object.freeze([".codex/sessions", ".codex/history.jsonl"]),
|
|
5933
6075
|
opencode: Object.freeze([
|
|
5934
|
-
".local/share/opencode/storage",
|
|
5935
|
-
".local/share/opencode/export",
|
|
5936
|
-
".local/share/opencode/log",
|
|
5937
6076
|
".opencode/sessions",
|
|
5938
|
-
".opencode/messages"
|
|
6077
|
+
".opencode/messages",
|
|
6078
|
+
".local/share/opencode/export",
|
|
6079
|
+
".local/share/opencode/storage",
|
|
6080
|
+
".local/share/opencode/log"
|
|
5939
6081
|
]),
|
|
5940
6082
|
pi: Object.freeze([".pi/agent/sessions"])
|
|
5941
6083
|
});
|
|
@@ -5973,6 +6115,126 @@ function harnessTranscriptUnavailable(reason) {
|
|
|
5973
6115
|
return unavailable$2(reason);
|
|
5974
6116
|
}
|
|
5975
6117
|
/**
|
|
6118
|
+
* Read one executor's harness-transcript receipt without letting a broken port escape.
|
|
6119
|
+
*
|
|
6120
|
+
* The three answers this has to keep apart, because #1214 and #1244 are both about an artifact
|
|
6121
|
+
* that reads as coverage without being coverage:
|
|
6122
|
+
* - `available` the transcript survived;
|
|
6123
|
+
* - a reason the CAPTURE produced a box existed and could not be read;
|
|
6124
|
+
* - `executor-exposes-no-transcript` this runtime has no transcript to offer at all;
|
|
6125
|
+
* - `execution-never-started` no environment was ever created (the executor's
|
|
6126
|
+
* own seed, set before `create`);
|
|
6127
|
+
* - `capture-did-not-run` the port answered nothing, so the capture was
|
|
6128
|
+
* skipped rather than attempted and failed.
|
|
6129
|
+
*
|
|
6130
|
+
* Mirrors the supervise scope's `readInteractiveSession`: an absence is always a named reason,
|
|
6131
|
+
* never `undefined`. The scope reads each child with it, and `supervise` reads the root with it.
|
|
6132
|
+
*/
|
|
6133
|
+
function readHarnessTranscript(executor) {
|
|
6134
|
+
if (!executor.harnessTranscript) return harnessTranscriptUnavailable("executor-exposes-no-transcript");
|
|
6135
|
+
let reported;
|
|
6136
|
+
try {
|
|
6137
|
+
reported = executor.harnessTranscript();
|
|
6138
|
+
} catch {
|
|
6139
|
+
return harnessTranscriptUnavailable("executor-exposes-no-transcript");
|
|
6140
|
+
}
|
|
6141
|
+
if (reported === void 0) return harnessTranscriptUnavailable("capture-did-not-run");
|
|
6142
|
+
const capture = reported;
|
|
6143
|
+
if (capture.status === "captured" && capture.artifact) return capture;
|
|
6144
|
+
if (capture.status === "unavailable" && capture.reason) return capture;
|
|
6145
|
+
return harnessTranscriptUnavailable("executor-exposes-no-transcript");
|
|
6146
|
+
}
|
|
6147
|
+
/**
|
|
6148
|
+
* Bounds on the subagent export, per capture. The capture runs after every turn and again at
|
|
6149
|
+
* release, so they bound how long a turn's end or a teardown can wait on it; each session they
|
|
6150
|
+
* leave out is named in `skipped`.
|
|
6151
|
+
*/
|
|
6152
|
+
const MAX_SUBAGENT_EXPORTS = 32;
|
|
6153
|
+
const SUBAGENT_EXPORT_SECONDS = 120;
|
|
6154
|
+
/** The script starts no export after SUBAGENT_EXPORT_SECONDS and bounds each one at 30 s, so it
|
|
6155
|
+
* ends inside this. It is passed to `exec`, because the Sandbox SDK's default is 30 s. */
|
|
6156
|
+
const SUBAGENT_EXPORT_EXEC_MS = 180 * 1e3;
|
|
6157
|
+
const SUBAGENT_EXPORT_DIR = "~/.local/share/opencode/export";
|
|
6158
|
+
/**
|
|
6159
|
+
* The export, run in the box. For each sidecar record it reads the runtime home and the parent
|
|
6160
|
+
* session, collects the subagent sessions its messages name, and writes `opencode export` of each
|
|
6161
|
+
* one under `export`, where the capture reads it. It prints one `<state> <sessionId>` line per
|
|
6162
|
+
* subagent: `exported`, `failed`, or `unexported` (over a bound), then `done`. Every capture exports again,
|
|
6163
|
+
* because a later turn can continue a subagent and the store changes with every turn; an export
|
|
6164
|
+
* takes about 2 s in a Tangle box. Each export goes to a temporary file first, so a failed or
|
|
6165
|
+
* interrupted export never leaves a partial file for the capture to read.
|
|
6166
|
+
*/
|
|
6167
|
+
const SUBAGENT_EXPORT_SCRIPT = String.raw`cd "$HOME" 2>/dev/null || exit 0
|
|
6168
|
+
out="$HOME/.local/share/opencode/export"
|
|
6169
|
+
t=""; command -v timeout >/dev/null 2>&1 && t="timeout 30"
|
|
6170
|
+
start=$(date +%s); n=0
|
|
6171
|
+
for rec in .opencode/sessions/*.json; do
|
|
6172
|
+
[ -f "$rec" ] || continue
|
|
6173
|
+
h=$(sed -n 's/.*"providerSessionHome"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$rec" | head -n 1)
|
|
6174
|
+
p=$(sed -n 's/.*"providerSessionId"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$rec" | head -n 1)
|
|
6175
|
+
db="$h/.local/share/opencode/opencode.db"
|
|
6176
|
+
case "$h" in /*) [ -f "$db" ] || continue ;; *) continue ;; esac
|
|
6177
|
+
ids=$(grep -rhoE '<task id=\\?"ses_[A-Za-z0-9]+|task_id: ses_[A-Za-z0-9]+|"sessionId"[[:space:]]*:[[:space:]]*"ses_[A-Za-z0-9]+' ".opencode/messages/$(basename "$rec" .json)" 2>/dev/null | grep -oE 'ses_[A-Za-z0-9]+' | sort -u)
|
|
6178
|
+
for id in $ids; do
|
|
6179
|
+
[ "$id" = "$p" ] && continue
|
|
6180
|
+
n=$((n + 1))
|
|
6181
|
+
if [ "$n" -gt ${MAX_SUBAGENT_EXPORTS} ] || [ $(($(date +%s) - start)) -ge ${SUBAGENT_EXPORT_SECONDS} ]; then echo "unexported $id"; continue; fi
|
|
6182
|
+
mkdir -p "$out" && tmp=$(mktemp) || { echo "failed $id"; continue; }
|
|
6183
|
+
if HOME="$h" XDG_DATA_HOME="$h/.local/share" XDG_CONFIG_HOME="$h/.config" XDG_STATE_HOME="$h/.local/state" XDG_CACHE_HOME="$h/.cache" OPENCODE_DISABLE_AUTOUPDATE=1 OPENCODE_DISABLE_MODELS_FETCH=1 $t opencode export "$id" > "$tmp" 2>/dev/null && [ -s "$tmp" ]; then
|
|
6184
|
+
mv "$tmp" "$out/$id.json" && echo "exported $id"
|
|
6185
|
+
else
|
|
6186
|
+
rm -f "$tmp"; echo "failed $id"
|
|
6187
|
+
fi
|
|
6188
|
+
done
|
|
6189
|
+
done
|
|
6190
|
+
echo done`;
|
|
6191
|
+
/**
|
|
6192
|
+
* Export each opencode subagent session a Tangle box's sidecar names, so the capture carries the
|
|
6193
|
+
* subagent's own steps (#1264).
|
|
6194
|
+
*
|
|
6195
|
+
* The sidecar runs opencode under a private runtime home and records the parent session under
|
|
6196
|
+
* `.opencode/sessions` and `.opencode/messages`. A `task` subagent runs in a child session, and
|
|
6197
|
+
* those records name it only in the tool's result (`<task id="ses_…">`, or `task_id: ses_…` on a
|
|
6198
|
+
* failure). Its steps are only in the runtime home's `opencode.db`, which a text capture cannot
|
|
6199
|
+
* carry. Each sidecar record names that home as `providerSessionHome`, so `opencode export` reads
|
|
6200
|
+
* the child from there. A shared-box worker exports its own subagents after each turn (#1371) and
|
|
6201
|
+
* keeps no sidecar record, so the export finds nothing to do for it.
|
|
6202
|
+
*
|
|
6203
|
+
* Returns the sessions it could not export, as `skipped` entries. An export that did not run, or
|
|
6204
|
+
* stopped before its `done` line, is named once, because the subagents it did not reach are
|
|
6205
|
+
* unknown.
|
|
6206
|
+
*/
|
|
6207
|
+
async function exportSubagentSessions(environment, signal) {
|
|
6208
|
+
if (!environment.exec || signal?.aborted) return [];
|
|
6209
|
+
let stdout;
|
|
6210
|
+
try {
|
|
6211
|
+
stdout = (await environment.exec(SUBAGENT_EXPORT_SCRIPT, {
|
|
6212
|
+
timeoutMs: SUBAGENT_EXPORT_EXEC_MS,
|
|
6213
|
+
...signal ? { signal } : {}
|
|
6214
|
+
})).stdout ?? "";
|
|
6215
|
+
} catch {
|
|
6216
|
+
return [{
|
|
6217
|
+
path: SUBAGENT_EXPORT_DIR,
|
|
6218
|
+
reason: "subagent-export-did-not-run"
|
|
6219
|
+
}];
|
|
6220
|
+
}
|
|
6221
|
+
const lines = stdout.split("\n").map((line) => line.trim());
|
|
6222
|
+
const gaps = [];
|
|
6223
|
+
for (const line of lines) {
|
|
6224
|
+
const match = /^(failed|unexported) (ses_[A-Za-z0-9]+)$/u.exec(line);
|
|
6225
|
+
if (match === null) continue;
|
|
6226
|
+
gaps.push({
|
|
6227
|
+
path: `${SUBAGENT_EXPORT_DIR}/${match[2]}.json`,
|
|
6228
|
+
reason: match[1] === "failed" ? "subagent-export-failed" : "subagent-export-over-bound"
|
|
6229
|
+
});
|
|
6230
|
+
}
|
|
6231
|
+
if (!lines.includes("done")) gaps.push({
|
|
6232
|
+
path: SUBAGENT_EXPORT_DIR,
|
|
6233
|
+
reason: "subagent-export-incomplete"
|
|
6234
|
+
});
|
|
6235
|
+
return gaps;
|
|
6236
|
+
}
|
|
6237
|
+
/**
|
|
5976
6238
|
* Enumerate candidate transcript paths inside the environment.
|
|
5977
6239
|
*
|
|
5978
6240
|
* `find -printf` is given the roots and prints one `size<TAB>path` line per file. A missing root
|
|
@@ -6033,12 +6295,13 @@ async function captureHarnessTranscript(environment, harness, signal) {
|
|
|
6033
6295
|
if (harness === void 0) return unavailable$2("unknown-harness");
|
|
6034
6296
|
const roots = HARNESS_ROOTS[harness];
|
|
6035
6297
|
if (!roots) return unavailable$2("unknown-harness");
|
|
6298
|
+
const unexported = harness === "opencode" ? await exportSubagentSessions(environment, signal) : [];
|
|
6036
6299
|
const enumerated = await enumerate(environment, roots, signal);
|
|
6037
6300
|
if (enumerated === void 0) return unavailable$2("enumeration-failed");
|
|
6038
6301
|
const { paths } = enumerated;
|
|
6039
|
-
if (paths.length === 0 && enumerated.omitted.length === 0) return unavailable$2("no-transcript");
|
|
6302
|
+
if (paths.length === 0 && enumerated.omitted.length === 0 && unexported.length === 0) return unavailable$2("no-transcript");
|
|
6040
6303
|
const files = [];
|
|
6041
|
-
const skipped = [...enumerated.omitted];
|
|
6304
|
+
const skipped = [...unexported, ...enumerated.omitted];
|
|
6042
6305
|
let total = 0;
|
|
6043
6306
|
for (const path of paths) {
|
|
6044
6307
|
if (signal?.aborted) {
|
|
@@ -6112,6 +6375,73 @@ async function captureHarnessTranscript(environment, harness, signal) {
|
|
|
6112
6375
|
});
|
|
6113
6376
|
}
|
|
6114
6377
|
/**
|
|
6378
|
+
* A transcript this process holds in memory, one JSON line per record, as a capture under the same
|
|
6379
|
+
* bounds a box's session files get: each file at most MAX_FILE_BYTES, all files together at most
|
|
6380
|
+
* MAX_TOTAL_BYTES. Lines fill `<name>-000.jsonl`, `<name>-001.jsonl`, ... in order, and a line is
|
|
6381
|
+
* never split. A line longer than one file, and every line past the total, is named in `skipped`
|
|
6382
|
+
* with its line number, so a cut transcript never reads as the whole one.
|
|
6383
|
+
*/
|
|
6384
|
+
function harnessTranscriptFromLines(harness, name, lines) {
|
|
6385
|
+
if (lines.length === 0) return unavailable$2("no-transcript");
|
|
6386
|
+
const files = [];
|
|
6387
|
+
const skipped = [];
|
|
6388
|
+
let total = 0;
|
|
6389
|
+
let chunk = [];
|
|
6390
|
+
let chunkBytes = 0;
|
|
6391
|
+
const flush = () => {
|
|
6392
|
+
if (chunk.length === 0) return;
|
|
6393
|
+
const content = `${chunk.join("\n")}\n`;
|
|
6394
|
+
files.push(Object.freeze({
|
|
6395
|
+
path: `${name}-${String(files.length).padStart(3, "0")}.jsonl`,
|
|
6396
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
6397
|
+
content
|
|
6398
|
+
}));
|
|
6399
|
+
chunk = [];
|
|
6400
|
+
chunkBytes = 0;
|
|
6401
|
+
};
|
|
6402
|
+
lines.forEach((line, index) => {
|
|
6403
|
+
const bytes = Buffer.byteLength(line, "utf8") + 1;
|
|
6404
|
+
const where = `${name} line ${index + 1}`;
|
|
6405
|
+
if (bytes > MAX_FILE_BYTES) {
|
|
6406
|
+
skipped.push({
|
|
6407
|
+
path: where,
|
|
6408
|
+
reason: "file-exceeds-byte-bound"
|
|
6409
|
+
});
|
|
6410
|
+
return;
|
|
6411
|
+
}
|
|
6412
|
+
if (total + bytes > MAX_TOTAL_BYTES) {
|
|
6413
|
+
skipped.push({
|
|
6414
|
+
path: where,
|
|
6415
|
+
reason: "total-byte-budget-exhausted"
|
|
6416
|
+
});
|
|
6417
|
+
return;
|
|
6418
|
+
}
|
|
6419
|
+
if (chunkBytes + bytes > MAX_FILE_BYTES) flush();
|
|
6420
|
+
chunk.push(line);
|
|
6421
|
+
chunkBytes += bytes;
|
|
6422
|
+
total += bytes;
|
|
6423
|
+
});
|
|
6424
|
+
flush();
|
|
6425
|
+
if (files.length === 0) return Object.freeze({
|
|
6426
|
+
status: "unavailable",
|
|
6427
|
+
reason: "nothing-carried",
|
|
6428
|
+
skipped: Object.freeze(skipped)
|
|
6429
|
+
});
|
|
6430
|
+
const artifact = Object.freeze({
|
|
6431
|
+
schemaVersion: 1,
|
|
6432
|
+
harness,
|
|
6433
|
+
files: Object.freeze(files),
|
|
6434
|
+
skipped: Object.freeze(skipped)
|
|
6435
|
+
});
|
|
6436
|
+
return Object.freeze({
|
|
6437
|
+
status: "captured",
|
|
6438
|
+
artifact,
|
|
6439
|
+
fileCount: files.length,
|
|
6440
|
+
totalBytes: total,
|
|
6441
|
+
skippedCount: skipped.length
|
|
6442
|
+
});
|
|
6443
|
+
}
|
|
6444
|
+
/**
|
|
6115
6445
|
* Persist a capture under its own content ref and return the receipt a settlement carries.
|
|
6116
6446
|
*
|
|
6117
6447
|
* The scope calls this, not the executor: storage stays out of every provider and destroy site,
|
|
@@ -6267,81 +6597,40 @@ function publicCreateOptions(create) {
|
|
|
6267
6597
|
}
|
|
6268
6598
|
//#endregion
|
|
6269
6599
|
//#region src/redact.ts
|
|
6270
|
-
const redactedMarker = "[redacted]";
|
|
6271
|
-
/** Secret-bearing key names: when an object key matches, its value is fully
|
|
6272
|
-
* replaced regardless of the value's own content. Lowercased compare. */
|
|
6273
|
-
const secretKeyPattern = /(api[-_]?key|secret|token|password|passwd|authorization|auth|private[-_]?key|credential|access[-_]?key|client[-_]?secret|session[-_]?(id|token)|cookie|bearer)/i;
|
|
6274
|
-
/** AgentProfile's numeric model limits describe compute, not credentials. */
|
|
6275
|
-
const tokenLimitKeyPattern = /^max(?:VisibleOutput|Reasoning|TotalOutput)Tokens$/;
|
|
6276
|
-
/** In-value secret/PII patterns scrubbed from any string, regardless of key. */
|
|
6277
|
-
const valuePatterns = [
|
|
6278
|
-
/\bBearer\s+[A-Za-z0-9._\-+/=]{8,}/gi,
|
|
6279
|
-
/\b(?:sk|pk|rk|ak)[-_][A-Za-z0-9_-]{12,}/g,
|
|
6280
|
-
/\bgh[pousr]_[A-Za-z0-9]{20,}/g,
|
|
6281
|
-
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
6282
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
6283
|
-
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g
|
|
6284
|
-
];
|
|
6285
|
-
/** Secret assignments embedded inside an otherwise opaque string, including
|
|
6286
|
-
* serialized JSON and log-style `key=value` text. */
|
|
6287
|
-
const secretAssignmentPattern = /((?:"|')?(?:api[-_]?key|secret|token|password|passwd|authorization|auth|private[-_]?key|credential|access[-_]?key|client[-_]?secret|session[-_]?(?:id|token)|cookie|bearer)(?:"|')?\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;}]+)/gi;
|
|
6288
|
-
/** Scrub a single string of in-value secret/PII patterns. */
|
|
6289
|
-
function scrubString(input) {
|
|
6290
|
-
let out = input;
|
|
6291
|
-
for (const pattern of valuePatterns) out = out.replace(pattern, redactedMarker);
|
|
6292
|
-
return out.replace(secretAssignmentPattern, `$1${redactedMarker}`);
|
|
6293
|
-
}
|
|
6294
6600
|
/**
|
|
6295
|
-
*
|
|
6296
|
-
*
|
|
6297
|
-
*
|
|
6298
|
-
*
|
|
6601
|
+
* Redaction for values that may leave the Runtime process. The built-in
|
|
6602
|
+
* redactor is agent-eval's redaction core (`@tangle-network/agent-eval/traces`,
|
|
6603
|
+
* default profile): credentials found by field name or value shape are
|
|
6604
|
+
* replaced whole, email, card, SSN and phone values in place, and token counts
|
|
6605
|
+
* such as `inputTokens` or `max_tokens` are kept. A customer with
|
|
6606
|
+
* domain-specific PII supplies their own `redact` hook.
|
|
6607
|
+
*
|
|
6608
|
+
* This is narrower than `src/sanitize.ts`, which drops fields of the runtime's
|
|
6609
|
+
* *event envelope* unless the caller opts in: here the value is opaque customer
|
|
6610
|
+
* payload, so the scrub is value-shaped, not schema-shaped.
|
|
6611
|
+
*
|
|
6612
|
+
* @experimental
|
|
6613
|
+
*/
|
|
6614
|
+
/**
|
|
6615
|
+
* The built-in redactor. Cycle-safe, depth-bounded and total: it never throws
|
|
6616
|
+
* on customer input and never mutates it.
|
|
6299
6617
|
*/
|
|
6300
6618
|
function defaultRedactor(value) {
|
|
6301
|
-
return
|
|
6302
|
-
}
|
|
6303
|
-
const maxDepth = 32;
|
|
6304
|
-
function walk(value, seen, depth) {
|
|
6305
|
-
if (depth > maxDepth) return "[truncated]";
|
|
6306
|
-
if (typeof value === "string") return scrubString(value);
|
|
6307
|
-
if (value === null || typeof value !== "object") return value;
|
|
6308
|
-
if (seen.has(value)) return "[circular]";
|
|
6309
|
-
seen.add(value);
|
|
6310
|
-
if (Array.isArray(value)) return value.map((item) => walk(item, seen, depth + 1));
|
|
6311
|
-
const out = {};
|
|
6312
|
-
for (const [key, v] of Object.entries(value)) {
|
|
6313
|
-
const tokenLimit = tokenLimitKeyPattern.test(key) && typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
6314
|
-
if (secretKeyPattern.test(key) && !tokenLimit) {
|
|
6315
|
-
out[key] = redactedMarker;
|
|
6316
|
-
continue;
|
|
6317
|
-
}
|
|
6318
|
-
out[key] = walk(v, seen, depth + 1);
|
|
6319
|
-
}
|
|
6320
|
-
return out;
|
|
6619
|
+
return redact(value).value;
|
|
6321
6620
|
}
|
|
6322
6621
|
/** Stable identity input for saved work that depends on built-in redaction behavior. */
|
|
6323
6622
|
function defaultRedactorIdentityMaterial() {
|
|
6324
|
-
const patternIdentity = (pattern) => ({
|
|
6325
|
-
source: pattern.source,
|
|
6326
|
-
flags: pattern.flags
|
|
6327
|
-
});
|
|
6328
6623
|
return {
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
valuePatterns: valuePatterns.map(patternIdentity),
|
|
6333
|
-
secretAssignmentPattern: patternIdentity(secretAssignmentPattern),
|
|
6334
|
-
maxDepth,
|
|
6335
|
-
scrubString: Function.prototype.toString.call(scrubString),
|
|
6336
|
-
walk: Function.prototype.toString.call(walk),
|
|
6337
|
-
defaultRedactor: Function.prototype.toString.call(defaultRedactor)
|
|
6624
|
+
core: "@tangle-network/agent-eval/traces redact",
|
|
6625
|
+
profile: "default",
|
|
6626
|
+
version: REDACTION_VERSION$1
|
|
6338
6627
|
};
|
|
6339
6628
|
}
|
|
6340
6629
|
/**
|
|
6341
6630
|
* Resolve the redactor a client uses. A caller-supplied hook handles
|
|
6342
6631
|
* domain-specific values first, then the built-in scrubber still removes
|
|
6343
|
-
*
|
|
6344
|
-
*
|
|
6632
|
+
* credentials and personal data. Returning `false` is the explicit opt-out for
|
|
6633
|
+
* already-reviewed public values.
|
|
6345
6634
|
*/
|
|
6346
6635
|
function resolveRedactor(redact) {
|
|
6347
6636
|
if (redact === false) return (value) => value;
|
|
@@ -7404,7 +7693,8 @@ function assertRetainedRunReplayMaterial(provider, replay, admission) {
|
|
|
7404
7693
|
}, identity, retainedContextTransfer(replay.turn.contextTransfer)));
|
|
7405
7694
|
}
|
|
7406
7695
|
function retainedRunIntent(options, identity, contextTransfer) {
|
|
7407
|
-
const
|
|
7696
|
+
const { profile } = options.environment;
|
|
7697
|
+
const requestedProfileDigest = typeof profile === "string" ? canonicalCandidateDigest(profile) : canonicalAgentProfileDigest(profile);
|
|
7408
7698
|
const requestDigest = canonicalCandidateDigest({
|
|
7409
7699
|
kind: "retained-run-intent.v1",
|
|
7410
7700
|
provider: options.provider.name,
|
|
@@ -8263,6 +8553,7 @@ function createProviderExecutor(provider, profile, ctx, options, placement) {
|
|
|
8263
8553
|
let workspaceCapturePromise;
|
|
8264
8554
|
let workspaceCleanupPromise;
|
|
8265
8555
|
let retainedReleasePromise;
|
|
8556
|
+
let releaseReadTried = false;
|
|
8266
8557
|
let workspaceOutcome;
|
|
8267
8558
|
let workspacePreservationRequired = false;
|
|
8268
8559
|
let workspaceRunActive = false;
|
|
@@ -8615,6 +8906,11 @@ function createProviderExecutor(provider, profile, ctx, options, placement) {
|
|
|
8615
8906
|
environment = target;
|
|
8616
8907
|
resetWorkspaceState(target);
|
|
8617
8908
|
}
|
|
8909
|
+
if (harnessTranscript.status !== "captured" && !releaseReadTried) {
|
|
8910
|
+
releaseReadTried = true;
|
|
8911
|
+
const capture = await captureHarnessTranscript(target, profile.harness, signal);
|
|
8912
|
+
if (capture.status === "captured") harnessTranscript = capture;
|
|
8913
|
+
}
|
|
8618
8914
|
const result = await destroyEnvironment(signal);
|
|
8619
8915
|
if (!result.destroyed) return [receipt(false, result.detail, result.permanent)];
|
|
8620
8916
|
pending = false;
|
|
@@ -8966,7 +9262,7 @@ async function providerExecutionSource(args, turn, signal) {
|
|
|
8966
9262
|
const dispatched = [...admissions].reverse().find((admission) => admission.phase === "dispatched");
|
|
8967
9263
|
const intent = admissions.find((admission) => admission.phase === "intent");
|
|
8968
9264
|
const environmentAdmission = admissions.find((admission) => admission.phase === "environment");
|
|
8969
|
-
if (args.recovering) {
|
|
9265
|
+
if (args.recovering && admissions.length > 0) {
|
|
8970
9266
|
if (!intent) throw new Error("retained provider execution has no original intent");
|
|
8971
9267
|
assertRetainedRunReplayMaterial(args.provider, material, intent);
|
|
8972
9268
|
}
|
|
@@ -9014,7 +9310,7 @@ async function providerExecutionSource(args, turn, signal) {
|
|
|
9014
9310
|
if (recovered.outcome !== "recovered") throw new Error(`retained provider execution is ${recovered.outcome}`);
|
|
9015
9311
|
handle = recovered.handle;
|
|
9016
9312
|
} else {
|
|
9017
|
-
if (args.recovering) throw new Error("retained provider execution has no durable admission");
|
|
9313
|
+
if (args.recovering && admissions.length > 0) throw new Error("retained provider execution has no durable admission");
|
|
9018
9314
|
handle = await startRetainedRun({
|
|
9019
9315
|
provider: args.provider,
|
|
9020
9316
|
...material,
|
|
@@ -16636,6 +16932,7 @@ var FileResultBlobStore = class {
|
|
|
16636
16932
|
return `${this.dir}/${outRef.replace(/:/g, "-")}.json`;
|
|
16637
16933
|
}
|
|
16638
16934
|
};
|
|
16935
|
+
/** @internal shared with the SQL-backed store. */
|
|
16639
16936
|
function encodeResultBlob(outRef, artifact) {
|
|
16640
16937
|
const detached = detachedSnapshot(artifact, "result blob");
|
|
16641
16938
|
assertContentAddress(outRef, detached);
|
|
@@ -16648,11 +16945,21 @@ function encodeResultBlob(outRef, artifact) {
|
|
|
16648
16945
|
text
|
|
16649
16946
|
};
|
|
16650
16947
|
}
|
|
16948
|
+
/** @internal shared with the SQL-backed store. */
|
|
16651
16949
|
function assertContentAddress(outRef, artifact) {
|
|
16652
16950
|
const expected = contentAddress(artifact);
|
|
16653
16951
|
if (outRef !== expected) throw new Error(`blob outRef '${outRef}' does not match the artifact content hash '${expected}'; a content-addressed store refuses a mismatched ref (breaks the replay invariant)`);
|
|
16654
16952
|
}
|
|
16655
16953
|
/**
|
|
16954
|
+
* Append every event as one grouped publication where the journal supports it (`appendEvents`),
|
|
16955
|
+
* preserving legacy per-event appends otherwise — the SQL contexts' fenced head advances once.
|
|
16956
|
+
* @internal
|
|
16957
|
+
*/
|
|
16958
|
+
async function appendSpawnEvents(journal, root, events) {
|
|
16959
|
+
if (journal.appendEvents !== void 0) return journal.appendEvents(root, events);
|
|
16960
|
+
for (const event of events) await journal.appendEvent(root, event);
|
|
16961
|
+
}
|
|
16962
|
+
/**
|
|
16656
16963
|
* In-memory `SpawnJournal`. Appends are observed-committed only; the impl enforces
|
|
16657
16964
|
* the corruption guards a durable replay rests on:
|
|
16658
16965
|
* - an event before `beginTree` is a corrupted tree (fail loud),
|
|
@@ -17085,6 +17392,7 @@ function servedIdentityParts(model) {
|
|
|
17085
17392
|
};
|
|
17086
17393
|
}
|
|
17087
17394
|
/** One validation implementation for memory, durable append, and cold replay. */
|
|
17395
|
+
/** @internal shared with the SQL-backed store. */
|
|
17088
17396
|
var SpawnEventIndex = class {
|
|
17089
17397
|
root;
|
|
17090
17398
|
nodes = /* @__PURE__ */ new Map();
|
|
@@ -17752,6 +18060,96 @@ function executionTaskPrompt$1(task, configuredPrompt) {
|
|
|
17752
18060
|
return prompt;
|
|
17753
18061
|
}
|
|
17754
18062
|
//#endregion
|
|
18063
|
+
//#region src/runtime/supervise/router-transcript.ts
|
|
18064
|
+
/**
|
|
18065
|
+
* The conversation of a router-brained agent, kept as it happens.
|
|
18066
|
+
*
|
|
18067
|
+
* A router-brained agent (`harness` omitted or `cli-base`) runs its tool loop in this process, so
|
|
18068
|
+
* its whole conversation is in the loop's `messages` and nowhere else. Before this, Runtime
|
|
18069
|
+
* metered each turn's usage and tool-call names and kept nothing the agent said, saw, or asked a
|
|
18070
|
+
* tool to do: a 316-agent tree on 2026-09-24 settled with 0 of 316 transcripts, and the 37 agents
|
|
18071
|
+
* still running when its driver process stopped left no record at all (agent-runtime#1377).
|
|
18072
|
+
*
|
|
18073
|
+
* The recorder keeps each message once, in the order the model saw it, and serves two readers:
|
|
18074
|
+
* - each turn's `agent.turn` event carries the messages that turn added (the loop's inputs since
|
|
18075
|
+
* the previous turn, then the reply), so an observer journal holds every agent's conversation
|
|
18076
|
+
* while the run is live;
|
|
18077
|
+
* - `capture()` returns the whole conversation as a harness transcript, so the agent settles
|
|
18078
|
+
* with an `available` receipt instead of `executor-exposes-no-transcript`.
|
|
18079
|
+
*
|
|
18080
|
+
* The loop mutates `messages` in place: it appends the reply it was given, each tool result, and
|
|
18081
|
+
* any inbox message, and a compaction replaces the middle with a distilled note. The recorder
|
|
18082
|
+
* therefore reads new messages twice per turn, before compaction can remove them and again when
|
|
18083
|
+
* the model is called, and it identifies a message by object identity. The loop's own copy of a
|
|
18084
|
+
* reply is skipped, because the recorder already kept the reply when the model returned it.
|
|
18085
|
+
*/
|
|
18086
|
+
/** The harness name a router-brained agent's transcript carries. */
|
|
18087
|
+
const ROUTER_TRANSCRIPT_HARNESS = "router";
|
|
18088
|
+
/**
|
|
18089
|
+
* The most bytes of one message's `content` a turn event carries. A longer content is cut, and
|
|
18090
|
+
* the message says so with its full length, so a reader never takes a cut message for the whole.
|
|
18091
|
+
* The settled transcript keeps the full content, within the bounds of `harnessTranscriptFromLines`.
|
|
18092
|
+
*/
|
|
18093
|
+
const TURN_CONTENT_BYTES = 256 * 1024;
|
|
18094
|
+
function createRouterTranscript() {
|
|
18095
|
+
const kept = [];
|
|
18096
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
18097
|
+
let turnStart = 0;
|
|
18098
|
+
let replyEchoPending = false;
|
|
18099
|
+
let live;
|
|
18100
|
+
const observe = (messages) => {
|
|
18101
|
+
live = messages;
|
|
18102
|
+
for (const message of messages) {
|
|
18103
|
+
if (message === null || typeof message !== "object" || seen.has(message)) continue;
|
|
18104
|
+
seen.add(message);
|
|
18105
|
+
if (replyEchoPending) {
|
|
18106
|
+
replyEchoPending = false;
|
|
18107
|
+
if (message.role === "assistant") continue;
|
|
18108
|
+
}
|
|
18109
|
+
kept.push(message);
|
|
18110
|
+
}
|
|
18111
|
+
};
|
|
18112
|
+
return {
|
|
18113
|
+
observe,
|
|
18114
|
+
reply(reply) {
|
|
18115
|
+
const message = {
|
|
18116
|
+
role: "assistant",
|
|
18117
|
+
content: reply.content ?? "",
|
|
18118
|
+
...reply.toolCalls && reply.toolCalls.length > 0 ? { tool_calls: reply.toolCalls.map((call) => ({
|
|
18119
|
+
id: call.id,
|
|
18120
|
+
type: "function",
|
|
18121
|
+
function: {
|
|
18122
|
+
name: call.name,
|
|
18123
|
+
arguments: call.arguments
|
|
18124
|
+
}
|
|
18125
|
+
})) } : {}
|
|
18126
|
+
};
|
|
18127
|
+
kept.push(message);
|
|
18128
|
+
replyEchoPending = true;
|
|
18129
|
+
const added = kept.slice(turnStart).map(forTurnEvent);
|
|
18130
|
+
turnStart = kept.length;
|
|
18131
|
+
return added;
|
|
18132
|
+
},
|
|
18133
|
+
capture() {
|
|
18134
|
+
if (live !== void 0) observe(live);
|
|
18135
|
+
if (kept.length === 0) return harnessTranscriptUnavailable("execution-never-started");
|
|
18136
|
+
return harnessTranscriptFromLines(ROUTER_TRANSCRIPT_HARNESS, "conversation", kept.map((message) => JSON.stringify(message)));
|
|
18137
|
+
}
|
|
18138
|
+
};
|
|
18139
|
+
}
|
|
18140
|
+
function forTurnEvent(message) {
|
|
18141
|
+
const content = message.content;
|
|
18142
|
+
if (typeof content !== "string") return message;
|
|
18143
|
+
const bytes = Buffer.byteLength(content);
|
|
18144
|
+
if (bytes <= 262144) return message;
|
|
18145
|
+
return {
|
|
18146
|
+
...message,
|
|
18147
|
+
content: Buffer.from(content).subarray(0, TURN_CONTENT_BYTES).toString("utf8"),
|
|
18148
|
+
contentBytes: bytes,
|
|
18149
|
+
contentCut: true
|
|
18150
|
+
};
|
|
18151
|
+
}
|
|
18152
|
+
//#endregion
|
|
17755
18153
|
//#region src/runtime/supervise/sandbox-session.ts
|
|
17756
18154
|
/** Ceiling on continuation turns. Turn 0 is the task; every later turn is a folded steer, so
|
|
17757
18155
|
* this bounds how many times a supervisor may redirect ONE worker before it must respawn. */
|
|
@@ -18363,6 +18761,7 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
18363
18761
|
const requestIdentity = routerRequestIdentity(ctx);
|
|
18364
18762
|
const controller = linkAbort(ctx.signal);
|
|
18365
18763
|
let artifact;
|
|
18764
|
+
const transcript = createRouterTranscript();
|
|
18366
18765
|
const executionId = ctx.node?.nodeId ?? `router-request-${randomUUID()}`;
|
|
18367
18766
|
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
18368
18767
|
let executor;
|
|
@@ -18370,6 +18769,7 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
18370
18769
|
runtime: "router",
|
|
18371
18770
|
async execute(task, signal) {
|
|
18372
18771
|
const messages = taskToMessages(task, spec, profileExecution.systemPrompt);
|
|
18772
|
+
transcript.observe(messages);
|
|
18373
18773
|
const started = Date.now();
|
|
18374
18774
|
const linked = linkAbort(signal, controller.signal).signal;
|
|
18375
18775
|
const extraBody = {
|
|
@@ -18407,6 +18807,10 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
18407
18807
|
...requestIdentity
|
|
18408
18808
|
}));
|
|
18409
18809
|
if (r.model !== void 0) recordRuntimeOwnedProviderModel(executor, r.model);
|
|
18810
|
+
transcript.reply({
|
|
18811
|
+
content: r.content,
|
|
18812
|
+
toolCalls: "toolCalls" in r ? r.toolCalls : []
|
|
18813
|
+
});
|
|
18410
18814
|
const spent = {
|
|
18411
18815
|
...addResourceSpend(r.resources),
|
|
18412
18816
|
iterations: 1,
|
|
@@ -18442,6 +18846,7 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
18442
18846
|
};
|
|
18443
18847
|
return artifact;
|
|
18444
18848
|
},
|
|
18849
|
+
harnessTranscript: () => transcript.capture(),
|
|
18445
18850
|
cancel(_request) {
|
|
18446
18851
|
controller.abort("executor cancelled");
|
|
18447
18852
|
return Promise.resolve(localAbortCancellation("the Router chat-completions API exposes no cancel operation; the local request was aborted and the provider may still bill the completion"));
|
|
@@ -18542,12 +18947,14 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
18542
18947
|
const controller = linkAbort(ctx.signal);
|
|
18543
18948
|
const inbox = createInbox();
|
|
18544
18949
|
let artifact;
|
|
18950
|
+
const transcript = createRouterTranscript();
|
|
18545
18951
|
const executionId = ctx.node?.nodeId ?? `router-tools-run-${randomUUID()}`;
|
|
18546
18952
|
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
18547
18953
|
let executor;
|
|
18548
18954
|
executor = attestRuntimeOwnedExecutor({
|
|
18549
18955
|
runtime: "router",
|
|
18550
18956
|
deliver: (m) => inbox.deliver(m),
|
|
18957
|
+
harnessTranscript: () => transcript.capture(),
|
|
18551
18958
|
async execute(task, signal) {
|
|
18552
18959
|
const started = Date.now();
|
|
18553
18960
|
const messages = seam.initialMessages ? taskToMessages({ messages: [...seam.initialMessages, {
|
|
@@ -18586,6 +18993,7 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
18586
18993
|
interruptSig.addEventListener("abort", abortTurn, { once: true });
|
|
18587
18994
|
const cleanup = () => external.removeEventListener("abort", abortTurn);
|
|
18588
18995
|
let res;
|
|
18996
|
+
transcript.observe(messages);
|
|
18589
18997
|
try {
|
|
18590
18998
|
recordRuntimeOwnedProviderAttemptStart(executor);
|
|
18591
18999
|
res = await (profileExecution.stream === true ? streamRouterChatWithTools : routerChatWithTools)({
|
|
@@ -18622,6 +19030,10 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
18622
19030
|
}
|
|
18623
19031
|
cleanup();
|
|
18624
19032
|
turns += 1;
|
|
19033
|
+
transcript.reply({
|
|
19034
|
+
content: res.content,
|
|
19035
|
+
toolCalls: res.toolCalls
|
|
19036
|
+
});
|
|
18625
19037
|
const priorResources = resources;
|
|
18626
19038
|
resources = addResourceSpend(resources, res.resources).resources;
|
|
18627
19039
|
resources = addResourceSpend(Object.fromEntries(Object.entries(resources ?? {}).map(([name, value]) => [name, {
|
|
@@ -21025,14 +21437,15 @@ function settledNodeEvidence(subject, settlement, settledAt) {
|
|
|
21025
21437
|
trace: detachedSnapshot(settlement.trace, "worker trace evidence")
|
|
21026
21438
|
};
|
|
21027
21439
|
}
|
|
21028
|
-
/** The `agent.child` payload of
|
|
21029
|
-
* kept at the settlement instant and `metered` omitted so the
|
|
21030
|
-
* twice. The sweep and the resume heal emit
|
|
21031
|
-
|
|
21440
|
+
/** The `agent.child` payload of a closed retained slot: the settlement restated with the state
|
|
21441
|
+
* the slot closed in, `settledAt` kept at the settlement instant and `metered` omitted so the
|
|
21442
|
+
* driver's inference is not summed twice. The sweep, the final close and the resume heal emit
|
|
21443
|
+
* it from the same builder. */
|
|
21444
|
+
function releasedChildPayload(subject, settlement, settledAt, releasedAt, retainedExecution = "released") {
|
|
21032
21445
|
return {
|
|
21033
21446
|
childId: subject.id,
|
|
21034
21447
|
status: "down",
|
|
21035
|
-
retainedExecution
|
|
21448
|
+
retainedExecution,
|
|
21036
21449
|
...settlement.retainedPendingCause === void 0 ? {} : { retainedPendingCause: settlement.retainedPendingCause },
|
|
21037
21450
|
releasedAt,
|
|
21038
21451
|
...settlement.outRef === void 0 ? {} : { outRef: settlement.outRef },
|
|
@@ -21180,7 +21593,7 @@ async function prepareInterruptedExecutors(opts, events, signal, now, parentId =
|
|
|
21180
21593
|
continue;
|
|
21181
21594
|
}
|
|
21182
21595
|
const admissions = current.flatMap((event) => event.kind === "execution-admitted" ? [event.admission] : []);
|
|
21183
|
-
if (!opts.recoverExecutor || admissions.length === 0 && !node.ownedTreeRoot) continue;
|
|
21596
|
+
if (!opts.recoverExecutor || admissions.length === 0 && !node.ownedTreeRoot && (opts.journal.appendEvents === void 0 || latestInput === void 0)) continue;
|
|
21184
21597
|
const taskRef = inputs[0]?.taskRef;
|
|
21185
21598
|
if (!node.profileRef || !taskRef || !node.identity?.profileDigest || !node.identity.taskDigest) throw new RuntimeRunStateError(`cannot recover '${node.id}' without exact profile and task bytes`);
|
|
21186
21599
|
const profile = agentProfileSchema.parse(await opts.blobs.get(node.profileRef));
|
|
@@ -21273,7 +21686,11 @@ async function assertRecordedResult(node, result, events, opts) {
|
|
|
21273
21686
|
* The keyed assignments a prior journal proves: every `spawned` event carrying a `key`, resolved
|
|
21274
21687
|
* against the replayed settlements. A key spawned more than once (a retry chain) resolves to its
|
|
21275
21688
|
* LATEST attempt — iterate in ordinal order so later spawns overwrite earlier ones. A spawned
|
|
21276
|
-
* event with no matching settlement is `in-doubt`: the process died with it in flight
|
|
21689
|
+
* event with no matching settlement is `in-doubt`: the process died with it in flight — UNLESS
|
|
21690
|
+
* the spawn's own runtime proves the execution cannot have outlived the process (`inline`, the
|
|
21691
|
+
* same predicate the budget layer charges no uncertain reservation for), in which case the resume
|
|
21692
|
+
* itself is the terminal evidence and the key resolves `down` with a derived reason, so a re-spawn
|
|
21693
|
+
* under the same key retries instead of wedging in-doubt.
|
|
21277
21694
|
*/
|
|
21278
21695
|
function keyedAssignments(events, settled) {
|
|
21279
21696
|
const byId = new Map(settled.map((s) => [s.handle.id, s]));
|
|
@@ -21283,7 +21700,13 @@ function keyedAssignments(events, settled) {
|
|
|
21283
21700
|
if (ev.key === void 0) continue;
|
|
21284
21701
|
if (ev.identity === void 0) throw new RuntimeRunStateError(`supervisor: keyed node '${ev.id}' has no durable execution identity`);
|
|
21285
21702
|
const s = byId.get(ev.id);
|
|
21286
|
-
keys.set(ev.key, s === void 0 ? {
|
|
21703
|
+
keys.set(ev.key, s === void 0 ? ev.runtime === "inline" ? {
|
|
21704
|
+
id: ev.id,
|
|
21705
|
+
label: ev.label,
|
|
21706
|
+
identity: ev.identity,
|
|
21707
|
+
state: "down",
|
|
21708
|
+
reason: `interrupted: the process died with '${ev.label}' in flight and its '${ev.runtime}' runtime cannot outlive that process, so the resume proves the attempt dead`
|
|
21709
|
+
} : {
|
|
21287
21710
|
id: ev.id,
|
|
21288
21711
|
label: ev.label,
|
|
21289
21712
|
identity: ev.identity,
|
|
@@ -22620,6 +23043,7 @@ const runtimeOwnedProviderMeters = /* @__PURE__ */ new WeakMap();
|
|
|
22620
23043
|
const pauseRecorders = /* @__PURE__ */ new WeakMap();
|
|
22621
23044
|
const recoveryStarters = /* @__PURE__ */ new WeakMap();
|
|
22622
23045
|
const retainedReleasers = /* @__PURE__ */ new WeakMap();
|
|
23046
|
+
const retainedSlotClosers = /* @__PURE__ */ new WeakMap();
|
|
22623
23047
|
const teardownRetriers = /* @__PURE__ */ new WeakMap();
|
|
22624
23048
|
const teardownRetriable = /* @__PURE__ */ new WeakMap();
|
|
22625
23049
|
/** The retained owner environments a scope's last release could not confirm destroyed. */
|
|
@@ -22707,6 +23131,25 @@ async function releaseRetainedEnvironments(scope) {
|
|
|
22707
23131
|
return unconfirmedTeardowns(scope);
|
|
22708
23132
|
}
|
|
22709
23133
|
/**
|
|
23134
|
+
* @internal Close the cursor slot of every retained child this scope and its nested scopes still
|
|
23135
|
+
* hold open, once the settlement is final and the release and its retries are done.
|
|
23136
|
+
*
|
|
23137
|
+
* The release sweep closes a slot only when it confirms the environment destroyed, and nothing
|
|
23138
|
+
* else ever closed the rest: a refused delete, a teardown probe that failed after a destroy, or an
|
|
23139
|
+
* admission that timed out before `create` named an environment. Measured on the Discovery fleet
|
|
23140
|
+
* of 2026-09-23/24 (Runtime 0.249.1 to 0.261.0): 338 of 1,620 agents never settled, so they had no
|
|
23141
|
+
* terminal record, no spend but the reservation ceiling, and no transcript receipt (#1301). A
|
|
23142
|
+
* settlement under `retainedAtSettlement: 'release'` is final, so no process will recover these
|
|
23143
|
+
* executions. Each slot closes with the settlement the driver already received, under the seq it
|
|
23144
|
+
* saw, marked `retainedExecution: 'release-unconfirmed'`. The environment it may still hold stays
|
|
23145
|
+
* named by its `teardown-unconfirmed` record for a sweeper. Never call this on a run a later
|
|
23146
|
+
* process may resume.
|
|
23147
|
+
*/
|
|
23148
|
+
async function closeRetainedSlots(scope) {
|
|
23149
|
+
const close = retainedSlotClosers.get(scope);
|
|
23150
|
+
if (close) await close();
|
|
23151
|
+
}
|
|
23152
|
+
/**
|
|
22710
23153
|
* @internal Everything this scope cannot prove destroyed: its settled children whose teardown is
|
|
22711
23154
|
* unconfirmed and, after a release, the retained owner environment that release could not
|
|
22712
23155
|
* confirm. Read live, so a later retry that confirms a node removes it.
|
|
@@ -22877,6 +23320,14 @@ function createScope(args) {
|
|
|
22877
23320
|
identity: prior.identity,
|
|
22878
23321
|
reason: prior.settled.reason
|
|
22879
23322
|
});
|
|
23323
|
+
} else if (prior.state === "down" && prior.settled === void 0) {
|
|
23324
|
+
if (prior.identity === void 0) continue;
|
|
23325
|
+
keyed.set(key, {
|
|
23326
|
+
state: "down",
|
|
23327
|
+
id: prior.id,
|
|
23328
|
+
identity: prior.identity,
|
|
23329
|
+
reason: prior.reason ?? "interrupted: resume proved the prior attempt dead"
|
|
23330
|
+
});
|
|
22880
23331
|
} else if (prior.identity !== void 0) keyed.set(key, {
|
|
22881
23332
|
state: "in-doubt",
|
|
22882
23333
|
id: prior.id,
|
|
@@ -23225,6 +23676,7 @@ function createScope(args) {
|
|
|
23225
23676
|
askTeardown,
|
|
23226
23677
|
...executor.heldEnvironments ? { readHeldEnvironments: executor.heldEnvironments.bind(executor) } : {},
|
|
23227
23678
|
...nestedTeardownScopes.has(executor) ? { readNestedScope: nestedTeardownScopes.get(executor) } : {},
|
|
23679
|
+
...executor.harnessTranscript ? { readTranscriptAtRelease: () => persistHarnessTranscript(readHarnessTranscript(executor), args.blobs) } : {},
|
|
23228
23680
|
teardownAttempts: 0,
|
|
23229
23681
|
abortChild: (reason) => controller.abort(reason)
|
|
23230
23682
|
};
|
|
@@ -23241,29 +23693,31 @@ function createScope(args) {
|
|
|
23241
23693
|
});
|
|
23242
23694
|
const profileRef = contentAddress(spec.profile);
|
|
23243
23695
|
const taskRef = identity?.taskDigest === void 0 ? void 0 : contentAddress(task);
|
|
23244
|
-
const spawnCommitted = recovery ? Promise.resolve() : args.blobs.put(profileRef, spec.profile).then(() => taskRef === void 0 ? void 0 : args.blobs.put(taskRef, task)).then(() =>
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
|
|
23254
|
-
|
|
23255
|
-
|
|
23256
|
-
|
|
23257
|
-
|
|
23258
|
-
|
|
23259
|
-
|
|
23260
|
-
|
|
23696
|
+
const spawnCommitted = recovery ? Promise.resolve() : args.blobs.put(profileRef, spec.profile).then(() => taskRef === void 0 ? void 0 : args.blobs.put(taskRef, task)).then(async () => {
|
|
23697
|
+
const events = [{
|
|
23698
|
+
kind: "spawned",
|
|
23699
|
+
id,
|
|
23700
|
+
parent: args.parentId,
|
|
23701
|
+
label: opts.label,
|
|
23702
|
+
...opts.key !== void 0 ? { key: opts.key } : {},
|
|
23703
|
+
...opts.assignmentId === void 0 ? {} : { assignmentId: opts.assignmentId },
|
|
23704
|
+
...opts.successorOf === void 0 ? {} : { successorOf: opts.successorOf },
|
|
23705
|
+
budget: opts.budget,
|
|
23706
|
+
runtime: executor.runtime,
|
|
23707
|
+
...ownedTreeRoot === void 0 ? {} : { ownedTreeRoot },
|
|
23708
|
+
...identity ? { identity } : {},
|
|
23709
|
+
profileRef,
|
|
23710
|
+
seq: ordinal,
|
|
23711
|
+
at: new Date(now()).toISOString()
|
|
23712
|
+
}];
|
|
23713
|
+
if (taskRef !== void 0) events.push({
|
|
23261
23714
|
kind: "execution-input",
|
|
23262
23715
|
id,
|
|
23263
23716
|
taskRef,
|
|
23264
23717
|
seq: ordinal,
|
|
23265
23718
|
at: new Date(now()).toISOString()
|
|
23266
23719
|
});
|
|
23720
|
+
await appendSpawnEvents(args.journal, args.root, events);
|
|
23267
23721
|
}).then(async () => {
|
|
23268
23722
|
if (args.workerTraceUnpropagated === void 0 || workerTrace === void 0) return;
|
|
23269
23723
|
await args.journal.appendEvent(args.root, {
|
|
@@ -23393,6 +23847,7 @@ function createScope(args) {
|
|
|
23393
23847
|
childId: id,
|
|
23394
23848
|
label: opts.label,
|
|
23395
23849
|
...opts.assignmentId === void 0 ? {} : { assignmentId: opts.assignmentId },
|
|
23850
|
+
...opts.key === void 0 ? {} : { key: opts.key },
|
|
23396
23851
|
...opts.successorOf === void 0 ? {} : { successorOf: opts.successorOf },
|
|
23397
23852
|
runtime: executor.runtime,
|
|
23398
23853
|
...identity ? { identity } : {},
|
|
@@ -23916,24 +24371,44 @@ function createScope(args) {
|
|
|
23916
24371
|
const prior = released.length > 0 ? await args.journal.loadTree(args.root) ?? [] : [];
|
|
23917
24372
|
for (const { child, receipts } of released) {
|
|
23918
24373
|
for (const receipt of receipts) await appendEnvironmentTeardown(args.journal, args.root, child.id, receipt, new Date(now()).toISOString());
|
|
23919
|
-
if (child.cleanupConfirmed
|
|
23920
|
-
|
|
23921
|
-
|
|
23922
|
-
|
|
23923
|
-
|
|
23924
|
-
|
|
23925
|
-
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
|
|
23929
|
-
|
|
23930
|
-
|
|
23931
|
-
|
|
23932
|
-
|
|
23933
|
-
|
|
23934
|
-
|
|
23935
|
-
|
|
24374
|
+
if (child.cleanupConfirmed) await closeRetainedSlot(child, "released", prior);
|
|
24375
|
+
}
|
|
24376
|
+
});
|
|
24377
|
+
const closeRetainedSlot = async (child, state, prior) => {
|
|
24378
|
+
if (child.recoveryPending !== true || child.resolved?.kind !== "down" || child.settledSeq === void 0 || child.settledAt === void 0 || child.retainedExecution === "released" || child.retainedExecution === "release-unconfirmed" || prior.some((event) => event.id === child.id && closesCursorSlot(event))) return;
|
|
24379
|
+
child.budgetViolation = child.retainedViolation;
|
|
24380
|
+
const settledAt = child.settledAt;
|
|
24381
|
+
let settlement = child.resolved;
|
|
24382
|
+
if (settlement.harnessTranscript?.status !== "available" && child.readTranscriptAtRelease) {
|
|
24383
|
+
const atRelease = await child.readTranscriptAtRelease();
|
|
24384
|
+
if (atRelease.status === "available") settlement = {
|
|
24385
|
+
...settlement,
|
|
24386
|
+
harnessTranscript: atRelease
|
|
24387
|
+
};
|
|
24388
|
+
}
|
|
24389
|
+
await args.journal.appendEvent(args.root, terminalDownEvent(child, settlement, child.settledSeq, new Date(settledAt).toISOString(), state));
|
|
24390
|
+
child.retainedExecution = state;
|
|
24391
|
+
const closedAt = now();
|
|
24392
|
+
notifyRuntimeHookEvent(args.hooks, {
|
|
24393
|
+
id: `${child.id}:${state}`,
|
|
24394
|
+
runId: args.root,
|
|
24395
|
+
target: "agent.child",
|
|
24396
|
+
phase: "after",
|
|
24397
|
+
timestamp: closedAt,
|
|
24398
|
+
stepIndex: child.settledSeq,
|
|
24399
|
+
parentId: args.parentId,
|
|
24400
|
+
payload: releasedChildPayload(child, settlement, settledAt, closedAt, state)
|
|
24401
|
+
}, { signal: args.signal });
|
|
24402
|
+
};
|
|
24403
|
+
retainedSlotClosers.set(scope, async () => {
|
|
24404
|
+
for (const child of children.values()) {
|
|
24405
|
+
const nested = child.readNestedScope?.();
|
|
24406
|
+
if (nested !== void 0) await closeRetainedSlots(nested);
|
|
23936
24407
|
}
|
|
24408
|
+
const open = [...children.values()].filter((child) => child.recoveryPending === true && !child.cleanupConfirmed && isTerminalNodeStatus(child.status) && child.retainedExecution !== "released" && child.retainedExecution !== "release-unconfirmed");
|
|
24409
|
+
if (open.length === 0) return;
|
|
24410
|
+
const prior = await args.journal.loadTree(args.root) ?? [];
|
|
24411
|
+
for (const child of open) await closeRetainedSlot(child, "release-unconfirmed", prior);
|
|
23937
24412
|
});
|
|
23938
24413
|
registerScopeRetainedOwner(scope, {
|
|
23939
24414
|
rootId: args.root,
|
|
@@ -24033,7 +24508,7 @@ async function recordScopeOwnerMaterialization(scope, runtime, declaration, bind
|
|
|
24033
24508
|
/** @internal Kernel identity for constructing the exact deferred owner executor. */
|
|
24034
24509
|
/**
|
|
24035
24510
|
* @internal Each driver attempt of the scope owner is its own execution attempt. A driver retry
|
|
24036
|
-
* after a failed drive, and the
|
|
24511
|
+
* after a failed drive, and the continuation re-entry after an unmet completion check, both
|
|
24037
24512
|
* run the harness again and report a new execution binding; the journal keys owner bindings by
|
|
24038
24513
|
* attempt id, so every attempt after the first is given a fresh kernel-minted id and its
|
|
24039
24514
|
* published flags are cleared, and the new binding lands beside the earlier ones. The first
|
|
@@ -25039,35 +25514,6 @@ function downRecord(reason, infra, trace, metered, providerModel, outRef, harnes
|
|
|
25039
25514
|
...outRef === void 0 ? {} : { outRef }
|
|
25040
25515
|
};
|
|
25041
25516
|
}
|
|
25042
|
-
/**
|
|
25043
|
-
* Read one executor's harness-transcript receipt without letting a broken port escape.
|
|
25044
|
-
*
|
|
25045
|
-
* The three answers this has to keep apart, because #1214 and #1244 are both about an artifact
|
|
25046
|
-
* that reads as coverage without being coverage:
|
|
25047
|
-
* - `available` the transcript survived;
|
|
25048
|
-
* - a reason the CAPTURE produced a box existed and could not be read;
|
|
25049
|
-
* - `executor-exposes-no-transcript` this runtime has no transcript to offer at all;
|
|
25050
|
-
* - `execution-never-started` no environment was ever created (the executor's
|
|
25051
|
-
* own seed, set before `create`);
|
|
25052
|
-
* - `capture-did-not-run` the port answered nothing, so the capture was
|
|
25053
|
-
* skipped rather than attempted and failed.
|
|
25054
|
-
*
|
|
25055
|
-
* Mirrors `readInteractiveSession` above: an absence is always a named reason, never `undefined`.
|
|
25056
|
-
*/
|
|
25057
|
-
function readHarnessTranscript(executor) {
|
|
25058
|
-
if (!executor.harnessTranscript) return harnessTranscriptUnavailable("executor-exposes-no-transcript");
|
|
25059
|
-
let reported;
|
|
25060
|
-
try {
|
|
25061
|
-
reported = executor.harnessTranscript();
|
|
25062
|
-
} catch {
|
|
25063
|
-
return harnessTranscriptUnavailable("executor-exposes-no-transcript");
|
|
25064
|
-
}
|
|
25065
|
-
if (reported === void 0) return harnessTranscriptUnavailable("capture-did-not-run");
|
|
25066
|
-
const capture = reported;
|
|
25067
|
-
if (capture.status === "captured" && capture.artifact) return capture;
|
|
25068
|
-
if (capture.status === "unavailable" && capture.reason) return capture;
|
|
25069
|
-
return harnessTranscriptUnavailable("executor-exposes-no-transcript");
|
|
25070
|
-
}
|
|
25071
25517
|
/** The one place an absent interactive process is spelled, so every refusal reads the same. */
|
|
25072
25518
|
function noInteractiveSession(reason) {
|
|
25073
25519
|
return Object.freeze({
|
|
@@ -25487,6 +25933,493 @@ function unreportedSpend(total, prior) {
|
|
|
25487
25933
|
};
|
|
25488
25934
|
}
|
|
25489
25935
|
//#endregion
|
|
25936
|
+
//#region src/runtime/supervise/continuation.ts
|
|
25937
|
+
/**
|
|
25938
|
+
* The continuation: what a manager with a completion check is told when its turn ends and the
|
|
25939
|
+
* check is unmet, and the one rule that decides whether it is sent back.
|
|
25940
|
+
*
|
|
25941
|
+
* Measured on discovery-lab before this module (2026-09-24, 670 settled runs, 1,596 re-entries):
|
|
25942
|
+
* 1,565 re-entries followed a failed turn and resent the original task, and 31 carried note text.
|
|
25943
|
+
* The note Runtime wrote itself held seven fixed lines and no field for the check's verdict. Of 40
|
|
25944
|
+
* episodes where the director ended on its own and the outside check disagreed, 33 were told
|
|
25945
|
+
* nothing before they ended. 100 of 236 resubmits sent the identical packet. So the director
|
|
25946
|
+
* mostly graded itself, and when it was wrong it heard nothing it could use.
|
|
25947
|
+
*
|
|
25948
|
+
* The design (discovery `docs/38-one-loop-and-continuation.md`) makes three things single, and
|
|
25949
|
+
* this module holds the part Runtime owns:
|
|
25950
|
+
*
|
|
25951
|
+
* - ONE CHECK. A check reading is a {@link CheckVerdict}: pass, the items it read, the composite,
|
|
25952
|
+
* and one `FAIL <item> <where>: <reason>` line per failed item. The same check decides
|
|
25953
|
+
* `submit_result`, runs when a turn ends without an accepted result, scores the finished run,
|
|
25954
|
+
* and ranks versions. A check that could not run gives no verdict ({@link CheckUnavailableError}),
|
|
25955
|
+
* and the loop pauses instead of blaming the director.
|
|
25956
|
+
* - ONE RESEND RULE. {@link ContinuationPolicy} is required for a manager with a check. It holds a
|
|
25957
|
+
* deadline and `maxBarren`, the re-entered turns in a row that may end without progress. There
|
|
25958
|
+
* is no re-prompt count and no default: a retry default in source is a research decision.
|
|
25959
|
+
* - ONE NOTE. {@link composeContinuationNote} writes it. Runtime owns the facts and their order;
|
|
25960
|
+
* the {@link ContinuationProfile} owns every instruction word, so an optimizer can change the
|
|
25961
|
+
* words without touching code. A play may append a section, never remove or rewrite one.
|
|
25962
|
+
*
|
|
25963
|
+
* Evidence behind the note's shape: located errors help (given the first mistake's true location,
|
|
25964
|
+
* fix rates rose 23.5 to 43.9 points, Tyen 2023); unsupported pressure does not (threats and tips
|
|
25965
|
+
* had no significant effect, Wharton 2025; challenges without evidence flipped correct answers,
|
|
25966
|
+
* FlipFlop); repeated summaries drop detail while itemized additions do not (ACE). So the note is
|
|
25967
|
+
* a list of located facts, findings are added and marked resolved rather than rewritten, and the
|
|
25968
|
+
* profile, not this file, decides how blunt the words are.
|
|
25969
|
+
*/
|
|
25970
|
+
/**
|
|
25971
|
+
* The check could not run: its box did not start, its judge's provider refused, its program
|
|
25972
|
+
* crashed. It is not a verdict on the result. Inside a run the loop pauses, as it does for an
|
|
25973
|
+
* unavailable upstream, and the refusal tells the director its result was not judged.
|
|
25974
|
+
*/
|
|
25975
|
+
var CheckUnavailableError = class extends Error {
|
|
25976
|
+
constructor(message, options) {
|
|
25977
|
+
super(message, options);
|
|
25978
|
+
this.name = "CheckUnavailableError";
|
|
25979
|
+
}
|
|
25980
|
+
};
|
|
25981
|
+
/** Read a check function's return value into a verdict. Anything but `true` or a passing verdict
|
|
25982
|
+
* is a failure: a check fails closed. */
|
|
25983
|
+
function checkVerdictOf(outcome) {
|
|
25984
|
+
if (outcome === true) return { pass: true };
|
|
25985
|
+
if (typeof outcome !== "object" || outcome === null || Array.isArray(outcome)) return { pass: false };
|
|
25986
|
+
const raw = outcome;
|
|
25987
|
+
const items = typeof raw.items === "object" && raw.items !== null && !Array.isArray(raw.items) ? Object.fromEntries(Object.entries(raw.items).filter((entry) => typeof entry[1] === "number" && Number.isFinite(entry[1]))) : void 0;
|
|
25988
|
+
const failures = Array.isArray(raw.failures) ? raw.failures.filter((line) => typeof line === "string" && line.trim() !== "") : void 0;
|
|
25989
|
+
return Object.freeze({
|
|
25990
|
+
pass: raw.pass === true,
|
|
25991
|
+
...items === void 0 ? {} : { items: Object.freeze(items) },
|
|
25992
|
+
...finite(raw.composite) ? { composite: raw.composite } : {},
|
|
25993
|
+
...finite(raw.threshold) ? { threshold: raw.threshold } : {},
|
|
25994
|
+
...failures === void 0 ? {} : { failures: Object.freeze(failures) },
|
|
25995
|
+
...typeof raw.review === "string" && raw.review.trim() !== "" ? { review: raw.review.trim() } : {}
|
|
25996
|
+
});
|
|
25997
|
+
}
|
|
25998
|
+
/**
|
|
25999
|
+
* Read an agent-eval `JudgeScore` from a check program into a verdict.
|
|
26000
|
+
*
|
|
26001
|
+
* Each dimension is a checked item. `notes` lines that start with `FAIL ` are the failures; the
|
|
26002
|
+
* remaining lines are the check's review. The verdict passes when `composite >= threshold` and the
|
|
26003
|
+
* score is not marked `failed`; a `failed` score means the judge itself did not run, which is
|
|
26004
|
+
* {@link CheckUnavailableError}, not a verdict.
|
|
26005
|
+
*/
|
|
26006
|
+
function verdictFromJudgeScore(score, threshold) {
|
|
26007
|
+
if (score.failed === true) throw new CheckUnavailableError(`the check's judge did not run: ${score.notes}`);
|
|
26008
|
+
const lines = score.notes.split("\n").map((line) => line.trimEnd());
|
|
26009
|
+
const failures = lines.filter((line) => line.startsWith("FAIL "));
|
|
26010
|
+
const review = lines.filter((line) => !line.startsWith("FAIL ") && line.trim() !== "").join("\n");
|
|
26011
|
+
return checkVerdictOf({
|
|
26012
|
+
pass: score.composite >= threshold,
|
|
26013
|
+
items: score.dimensions,
|
|
26014
|
+
composite: score.composite,
|
|
26015
|
+
threshold,
|
|
26016
|
+
failures,
|
|
26017
|
+
...review === "" ? {} : { review }
|
|
26018
|
+
});
|
|
26019
|
+
}
|
|
26020
|
+
/** Items that fail in `verdict`: every item below 1, or the named items of its FAIL lines. */
|
|
26021
|
+
function failedItems(verdict) {
|
|
26022
|
+
if (verdict.items !== void 0) return Object.entries(verdict.items).filter(([, value]) => value < 1).map(([name]) => name);
|
|
26023
|
+
return (verdict.failures ?? []).map((line) => line.slice(5).split(/[\s:]/u)[0] ?? "");
|
|
26024
|
+
}
|
|
26025
|
+
/** Items that pass in `verdict`. */
|
|
26026
|
+
function passedItems(verdict) {
|
|
26027
|
+
return Object.entries(verdict.items ?? {}).filter(([, value]) => value >= 1).map(([name]) => name);
|
|
26028
|
+
}
|
|
26029
|
+
/** Resolve and check a policy before any compute. Returns the deadline in epoch ms. */
|
|
26030
|
+
function admitContinuationPolicy(policy, context) {
|
|
26031
|
+
if (typeof policy !== "object" || policy === null) throw new ValidationError$1(`${context}: continuation must be an object`);
|
|
26032
|
+
const deadlineMs = typeof policy.deadline === "string" ? Date.parse(policy.deadline) : policy.deadline;
|
|
26033
|
+
if (typeof deadlineMs !== "number" || !Number.isFinite(deadlineMs) || deadlineMs <= 0) throw new ValidationError$1(`${context}: continuation.deadline must be a finite time (epoch ms or ISO 8601)`);
|
|
26034
|
+
if (!Number.isInteger(policy.maxBarren) || policy.maxBarren < 1) throw new ValidationError$1(`${context}: continuation.maxBarren must be an integer >= 1`);
|
|
26035
|
+
if (policy.failures !== "verbatim" && policy.failures !== "off") throw new ValidationError$1(`${context}: continuation.failures must be 'verbatim' or 'off'`);
|
|
26036
|
+
if (policy.panel !== "on" && policy.panel !== "off") throw new ValidationError$1(`${context}: continuation.panel must be 'on' or 'off'`);
|
|
26037
|
+
if (policy.bar !== "on" && policy.bar !== "off") throw new ValidationError$1(`${context}: continuation.bar must be 'on' or 'off'`);
|
|
26038
|
+
if (policy.panel === "on") {
|
|
26039
|
+
if (typeof policy.runPanel !== "function") throw new ValidationError$1(`${context}: continuation.panel 'on' needs runPanel`);
|
|
26040
|
+
const usd = policy.panelUsd;
|
|
26041
|
+
if (usd === void 0 || !finite(usd.perContinuation) || !finite(usd.perRun) || usd.perContinuation <= 0 || usd.perRun < usd.perContinuation) throw new ValidationError$1(`${context}: continuation.panel 'on' needs panelUsd with 0 < perContinuation <= perRun`);
|
|
26042
|
+
}
|
|
26043
|
+
if (policy.append !== void 0 && typeof policy.append !== "function") throw new ValidationError$1(`${context}: continuation.append must be a function`);
|
|
26044
|
+
admitContinuationProfile(policy.profile, context);
|
|
26045
|
+
return deadlineMs;
|
|
26046
|
+
}
|
|
26047
|
+
/** The facts a profile template may name. */
|
|
26048
|
+
const CONTINUATION_FACTS = [
|
|
26049
|
+
"composite",
|
|
26050
|
+
"threshold",
|
|
26051
|
+
"failed",
|
|
26052
|
+
"total",
|
|
26053
|
+
"reads",
|
|
26054
|
+
"owed",
|
|
26055
|
+
"settled",
|
|
26056
|
+
"delivered",
|
|
26057
|
+
"continuation"
|
|
26058
|
+
];
|
|
26059
|
+
const factNames = new Set(CONTINUATION_FACTS);
|
|
26060
|
+
const placeholder = /\{([a-zA-Z]+)\}/gu;
|
|
26061
|
+
function admitContinuationProfile(profile, context) {
|
|
26062
|
+
if (typeof profile !== "object" || profile === null) throw new ValidationError$1(`${context}: continuation.profile must be an object`);
|
|
26063
|
+
if (typeof profile.id !== "string" || profile.id.trim() === "") throw new ValidationError$1(`${context}: continuation.profile.id must be non-empty`);
|
|
26064
|
+
const texts = [
|
|
26065
|
+
["opening", profile.opening],
|
|
26066
|
+
["plan", profile.plan],
|
|
26067
|
+
["rules", profile.rules],
|
|
26068
|
+
...Object.entries(profile.headings ?? {}).map(([key, value]) => [`headings.${key}`, value]),
|
|
26069
|
+
...profile.barRule === void 0 ? [] : [["barRule", profile.barRule]]
|
|
26070
|
+
];
|
|
26071
|
+
for (const key of [
|
|
26072
|
+
"failures",
|
|
26073
|
+
"protected",
|
|
26074
|
+
"changed",
|
|
26075
|
+
"findings",
|
|
26076
|
+
"bar",
|
|
26077
|
+
"plan",
|
|
26078
|
+
"rules"
|
|
26079
|
+
]) if (typeof profile.headings?.[key] !== "string") throw new ValidationError$1(`${context}: continuation.profile.headings.${key} must be a string`);
|
|
26080
|
+
for (const [key, text] of texts) {
|
|
26081
|
+
if (typeof text !== "string") throw new ValidationError$1(`${context}: continuation.profile.${key} must be a string`);
|
|
26082
|
+
for (const match of text.matchAll(placeholder)) if (!factNames.has(match[1] ?? "")) throw new ValidationError$1(`${context}: continuation.profile.${key} names {${match[1]}}, which is not a fact Runtime supplies (${CONTINUATION_FACTS.join(", ")})`);
|
|
26083
|
+
}
|
|
26084
|
+
if (profile.review !== void 0) {
|
|
26085
|
+
if (typeof profile.review !== "string" || profile.review.trim() === "") throw new ValidationError$1(`${context}: continuation.profile.review must be non-empty`);
|
|
26086
|
+
for (const match of profile.review.matchAll(placeholder)) if (![
|
|
26087
|
+
"version",
|
|
26088
|
+
"path",
|
|
26089
|
+
"score"
|
|
26090
|
+
].includes(match[1] ?? "")) throw new ValidationError$1(`${context}: continuation.profile.review names {${match[1]}}; it may name {version}, {path} and {score}`);
|
|
26091
|
+
}
|
|
26092
|
+
if (profile.opening.trim() === "") throw new ValidationError$1(`${context}: continuation.profile.opening must be non-empty`);
|
|
26093
|
+
for (const [index, question] of (profile.questions ?? []).entries()) if (typeof question !== "string" || question.trim() === "") throw new ValidationError$1(`${context}: continuation.profile.questions[${index}] must be a non-empty string`);
|
|
26094
|
+
}
|
|
26095
|
+
/** Findings reach the note only when every citation resolves, at least two distinct spans are
|
|
26096
|
+
* cited, and the independent verifier agreed. The best method in Who&When found the failing step
|
|
26097
|
+
* 14.2% of the time, so an unverified finding is noise with a citation. */
|
|
26098
|
+
function admitFinding(finding) {
|
|
26099
|
+
if (!finding.verified) return false;
|
|
26100
|
+
if (finding.citations.length === 0) return false;
|
|
26101
|
+
if (!finding.citations.every((citation) => citation.resolved)) return false;
|
|
26102
|
+
return new Set(finding.citations.map((citation) => citation.uri)).size >= 2;
|
|
26103
|
+
}
|
|
26104
|
+
/** About 1,500 tokens of findings. */
|
|
26105
|
+
const FINDINGS_CHAR_CAP = 6e3;
|
|
26106
|
+
/**
|
|
26107
|
+
* Rank the admitted findings by how many failed items they explain, drop repeats, and keep them
|
|
26108
|
+
* within the note's cap. Earlier findings stay as they were written (ACE: itemized additions keep
|
|
26109
|
+
* detail that repeated rewrites lose); a finding whose items all pass now is marked resolved.
|
|
26110
|
+
*/
|
|
26111
|
+
function distillFindings(prior, proposed, failing, continuation) {
|
|
26112
|
+
const key = (claim) => claim.toLowerCase().replace(/\s+/gu, " ").trim();
|
|
26113
|
+
const known = new Set(prior.map((finding) => key(finding.claim)));
|
|
26114
|
+
const carried = prior.map((finding) => finding.resolvedAt === void 0 && (finding.items?.length ?? 0) > 0 && (finding.items ?? []).every((item) => !failing.has(item)) ? {
|
|
26115
|
+
...finding,
|
|
26116
|
+
resolvedAt: continuation
|
|
26117
|
+
} : finding);
|
|
26118
|
+
const fresh = proposed.filter(admitFinding).filter((finding) => {
|
|
26119
|
+
const k = key(finding.claim);
|
|
26120
|
+
if (known.has(k)) return false;
|
|
26121
|
+
known.add(k);
|
|
26122
|
+
return true;
|
|
26123
|
+
}).map((finding) => ({
|
|
26124
|
+
finding,
|
|
26125
|
+
links: (finding.items ?? []).filter((item) => failing.has(item)).length
|
|
26126
|
+
})).sort((a, b) => b.links - a.links);
|
|
26127
|
+
const out = [...carried];
|
|
26128
|
+
let used = carried.filter((finding) => finding.resolvedAt === void 0).reduce((sum, finding) => sum + finding.claim.length, 0);
|
|
26129
|
+
for (const { finding } of fresh) {
|
|
26130
|
+
if (used + finding.claim.length > FINDINGS_CHAR_CAP) break;
|
|
26131
|
+
used += finding.claim.length;
|
|
26132
|
+
out.push({
|
|
26133
|
+
...finding,
|
|
26134
|
+
admittedAt: continuation
|
|
26135
|
+
});
|
|
26136
|
+
}
|
|
26137
|
+
return out;
|
|
26138
|
+
}
|
|
26139
|
+
/** Expand the profile's question templates over the failed items and the settled workers. */
|
|
26140
|
+
function expandQuestions(templates, failing, workers) {
|
|
26141
|
+
const out = [];
|
|
26142
|
+
for (const template of templates) {
|
|
26143
|
+
const hasItem = template.includes("{item}");
|
|
26144
|
+
const hasWorker = template.includes("{worker}");
|
|
26145
|
+
if (!hasItem && !hasWorker) {
|
|
26146
|
+
out.push(template);
|
|
26147
|
+
continue;
|
|
26148
|
+
}
|
|
26149
|
+
const items = hasItem ? failing : [""];
|
|
26150
|
+
const each = hasWorker ? workers.map((worker) => `${worker.id} (${worker.label})`) : [""];
|
|
26151
|
+
for (const item of items) for (const worker of each) out.push(template.replaceAll("{item}", item).replaceAll("{worker}", worker));
|
|
26152
|
+
}
|
|
26153
|
+
return out;
|
|
26154
|
+
}
|
|
26155
|
+
/**
|
|
26156
|
+
* Write one continuation note, sections 1 to 8 of docs/38. Section 9, the run's state, is
|
|
26157
|
+
* `composeReentryTask`, which wraps this text. Pure: the same input writes the same note.
|
|
26158
|
+
*/
|
|
26159
|
+
function composeContinuationNote(input) {
|
|
26160
|
+
const { profile, verdict } = input;
|
|
26161
|
+
const items = verdict?.items ?? {};
|
|
26162
|
+
const total = Object.keys(items).length;
|
|
26163
|
+
const failing = verdict === void 0 ? [] : failedItems(verdict);
|
|
26164
|
+
const facts = {
|
|
26165
|
+
composite: verdict?.composite === void 0 ? "unknown" : format(verdict.composite),
|
|
26166
|
+
threshold: verdict?.threshold === void 0 ? "unknown" : format(verdict.threshold),
|
|
26167
|
+
failed: verdict === void 0 || total === 0 && (verdict.failures?.length ?? 0) === 0 ? "unknown" : String(failing.length),
|
|
26168
|
+
total: total === 0 ? "unknown" : String(total),
|
|
26169
|
+
reads: String(input.reads),
|
|
26170
|
+
owed: input.owed?.trim() || "not described",
|
|
26171
|
+
settled: String(input.progress.settledCount),
|
|
26172
|
+
delivered: String(input.progress.deliveredCount ?? 0),
|
|
26173
|
+
continuation: String(input.continuation)
|
|
26174
|
+
};
|
|
26175
|
+
const fill = (text) => text.replace(placeholder, (_, name) => facts[name] ?? `{${name}}`);
|
|
26176
|
+
const sections = [verdict === void 0 ? "No result has reached the check yet. The run ends only when a result passes it through submit_result." : verdict.pass ? "The check passes on the current state, but no result was submitted. Call submit_result to end the run." : fill(profile.opening).trim()];
|
|
26177
|
+
const section = (heading, body) => {
|
|
26178
|
+
if (body.length === 0) return;
|
|
26179
|
+
sections.push([...heading.trim() === "" ? [] : [`## ${fill(heading)}`], ...body].join("\n"));
|
|
26180
|
+
};
|
|
26181
|
+
if (input.failures === "verbatim" && verdict !== void 0) {
|
|
26182
|
+
const lines = verdict.failures ?? [];
|
|
26183
|
+
const shown = lines.slice(0, 40);
|
|
26184
|
+
const more = lines.length - shown.length;
|
|
26185
|
+
section(profile.headings.failures, [...shown, ...more > 0 ? [input.canReadMore ? `${more} more failure lines: call read_continuation with continuation ${input.continuation}.` : `${more} more failure lines are not shown.`] : []]);
|
|
26186
|
+
const passing = passedItems(verdict);
|
|
26187
|
+
section(profile.headings.protected, passing.length === 0 ? [] : [passing.join(", ")]);
|
|
26188
|
+
if (input.previous !== void 0) {
|
|
26189
|
+
const before = new Set(failedItems(input.previous));
|
|
26190
|
+
const now = new Set(failing);
|
|
26191
|
+
const fixed = [...before].filter((item) => !now.has(item));
|
|
26192
|
+
const broke = [...now].filter((item) => !before.has(item));
|
|
26193
|
+
section(profile.headings.changed, [
|
|
26194
|
+
...fixed.length === 0 ? [] : [`Now passing: ${fixed.join(", ")}.`],
|
|
26195
|
+
...broke.length === 0 ? [] : [`Newly failing: ${broke.join(", ")}.`],
|
|
26196
|
+
...fixed.length === 0 && broke.length === 0 ? ["No item changed since the last note."] : [],
|
|
26197
|
+
...input.previous.composite !== void 0 && verdict.composite !== void 0 ? [`Composite: ${format(input.previous.composite)} then ${format(verdict.composite)}.`] : []
|
|
26198
|
+
]);
|
|
26199
|
+
}
|
|
26200
|
+
}
|
|
26201
|
+
if (input.findings !== void 0) {
|
|
26202
|
+
const open = input.findings.filter((finding) => finding.resolvedAt === void 0);
|
|
26203
|
+
const resolved = input.findings.filter((finding) => finding.resolvedAt === input.continuation);
|
|
26204
|
+
section(profile.headings.findings, [...open.map((finding) => `- [hypothesis] ${finding.claim}` + (finding.items?.length ? ` (items: ${finding.items.join(", ")})` : "") + ` Evidence: ${finding.citations.map((citation) => citation.uri).join(", ")}`), ...resolved.map((finding) => `- [resolved] ${finding.claim}`)]);
|
|
26205
|
+
}
|
|
26206
|
+
if (input.bar !== void 0 && verdict !== void 0) {
|
|
26207
|
+
const body = [];
|
|
26208
|
+
if (profile.barRule?.trim()) body.push(fill(profile.barRule).trim());
|
|
26209
|
+
for (const [item, value] of Object.entries(items)) {
|
|
26210
|
+
const best = input.bar.best?.verdict.items?.[item];
|
|
26211
|
+
body.push(`- ${item}: ${value >= 1 ? "passes" : `fails (${format(value)})`}` + (best === void 0 ? "" : ` / ${input.bar.best?.label}: ${best >= 1 ? "passes" : `fails (${format(best)})`}`));
|
|
26212
|
+
}
|
|
26213
|
+
if (input.bar.best?.verdict.composite !== void 0) body.push(`Best version ${input.bar.best.label}: composite ${format(input.bar.best.verdict.composite)}.`);
|
|
26214
|
+
if (input.bar.reference?.trim()) body.push(`Reference result: ${input.bar.reference.trim()}`);
|
|
26215
|
+
if (verdict.review?.trim()) body.push(`The check's review: ${verdict.review.trim()}`);
|
|
26216
|
+
section(profile.headings.bar, body);
|
|
26217
|
+
}
|
|
26218
|
+
section(profile.headings.plan, profile.plan.trim() === "" ? [] : [fill(profile.plan).trim()]);
|
|
26219
|
+
section(profile.headings.rules, [...profile.rules.trim() === "" ? [] : [fill(profile.rules).trim()], ...input.sealed ? ["The score comes from cases you cannot see."] : []]);
|
|
26220
|
+
for (const appended of input.appended ?? []) section(appended.heading, appended.text.trim() === "" ? [] : [appended.text.trim()]);
|
|
26221
|
+
return sections.join("\n\n");
|
|
26222
|
+
}
|
|
26223
|
+
function summarizeVerdict(verdict) {
|
|
26224
|
+
if (verdict === void 0) return null;
|
|
26225
|
+
const total = Object.keys(verdict.items ?? {}).length;
|
|
26226
|
+
return {
|
|
26227
|
+
pass: verdict.pass,
|
|
26228
|
+
...verdict.composite === void 0 ? {} : { composite: verdict.composite },
|
|
26229
|
+
...total === 0 ? {} : {
|
|
26230
|
+
failed: failedItems(verdict).length,
|
|
26231
|
+
total
|
|
26232
|
+
}
|
|
26233
|
+
};
|
|
26234
|
+
}
|
|
26235
|
+
function noteDigest(note) {
|
|
26236
|
+
return `sha256:${createHash("sha256").update(note).digest("hex")}`;
|
|
26237
|
+
}
|
|
26238
|
+
/** A run directory's continuation files: the root's at `<runDir>/continuations/<n>/`, a nested
|
|
26239
|
+
* manager's at `<runDir>/continuations/managers/<owner>/<n>/`. */
|
|
26240
|
+
const CONTINUATIONS_DIR = "continuations";
|
|
26241
|
+
/**
|
|
26242
|
+
* Write one continuation's files under `<dir>/<n>/`: `note.md`, `verdict.json`, and
|
|
26243
|
+
* `panel.jsonl` when the panel ran. The director reads them through `read_continuation`, because
|
|
26244
|
+
* its box cannot read the driver's run directory.
|
|
26245
|
+
*/
|
|
26246
|
+
async function writeContinuationFiles(dir, continuation, files) {
|
|
26247
|
+
const at = join(dir, String(continuation));
|
|
26248
|
+
await mkdir(at, { recursive: true });
|
|
26249
|
+
await writeFile(join(at, "note.md"), `${files.note}\n`, "utf8");
|
|
26250
|
+
await writeFile(join(at, "verdict.json"), `${JSON.stringify(files.verdict ?? null, null, 2)}\n`, "utf8");
|
|
26251
|
+
if (files.panel !== void 0) await writeFile(join(at, "panel.jsonl"), files.panel.map((line) => JSON.stringify(line)).join("\n") + (files.panel.length ? "\n" : ""), "utf8");
|
|
26252
|
+
}
|
|
26253
|
+
/** The best composite across `reads`, or `undefined` when no read scored. */
|
|
26254
|
+
function bestComposite(reads) {
|
|
26255
|
+
let best;
|
|
26256
|
+
for (const read of reads) {
|
|
26257
|
+
const value = read.verdict?.composite;
|
|
26258
|
+
if (value !== void 0 && (best === void 0 || value > best)) best = value;
|
|
26259
|
+
}
|
|
26260
|
+
return best;
|
|
26261
|
+
}
|
|
26262
|
+
function finite(value) {
|
|
26263
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
26264
|
+
}
|
|
26265
|
+
function format(value) {
|
|
26266
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/u, "");
|
|
26267
|
+
}
|
|
26268
|
+
/** Build the continuation state for one manager with a check. */
|
|
26269
|
+
function createContinuationKeeper(input) {
|
|
26270
|
+
const { policy } = input;
|
|
26271
|
+
const failures = input.feedback === "pass-only" ? "off" : policy.failures;
|
|
26272
|
+
const entries = [];
|
|
26273
|
+
const notes = [];
|
|
26274
|
+
let findings = [];
|
|
26275
|
+
let panelUsd = 0;
|
|
26276
|
+
let panelUsdKnown = true;
|
|
26277
|
+
const latestVerdict = () => {
|
|
26278
|
+
const reads = input.reads();
|
|
26279
|
+
for (let i = reads.length - 1; i >= 0; i -= 1) {
|
|
26280
|
+
const read = reads[i];
|
|
26281
|
+
if (read?.verdict !== void 0) return {
|
|
26282
|
+
verdict: read.verdict,
|
|
26283
|
+
read: read.read
|
|
26284
|
+
};
|
|
26285
|
+
}
|
|
26286
|
+
return { read: 0 };
|
|
26287
|
+
};
|
|
26288
|
+
const closePrevious = () => {
|
|
26289
|
+
const last = entries.at(-1);
|
|
26290
|
+
const lastNote = notes.at(-1);
|
|
26291
|
+
if (last === void 0 || lastNote === void 0) return;
|
|
26292
|
+
const latest = latestVerdict();
|
|
26293
|
+
entries[entries.length - 1] = {
|
|
26294
|
+
...last,
|
|
26295
|
+
after: latest.read > lastNote.read ? summarizeVerdict(latest.verdict) : null
|
|
26296
|
+
};
|
|
26297
|
+
};
|
|
26298
|
+
return {
|
|
26299
|
+
async compose(context) {
|
|
26300
|
+
closePrevious();
|
|
26301
|
+
const continuation = entries.length + 1;
|
|
26302
|
+
const { verdict, read } = latestVerdict();
|
|
26303
|
+
const failing = verdict === void 0 ? [] : failedItems(verdict);
|
|
26304
|
+
let panelRecord;
|
|
26305
|
+
let panelLines;
|
|
26306
|
+
if (policy.panel === "on" && policy.runPanel !== void 0 && policy.panelUsd !== void 0) {
|
|
26307
|
+
const workers = input.workers();
|
|
26308
|
+
const questions = expandQuestions(policy.profile.questions ?? [], failing, workers);
|
|
26309
|
+
const left = policy.panelUsd.perRun - panelUsd;
|
|
26310
|
+
const usdCap = Math.min(policy.panelUsd.perContinuation, left);
|
|
26311
|
+
if (questions.length > 0 && usdCap > 0 && panelUsdKnown) {
|
|
26312
|
+
const result = await policy.runPanel({
|
|
26313
|
+
continuation,
|
|
26314
|
+
task: input.task,
|
|
26315
|
+
...verdict === void 0 ? {} : { verdict },
|
|
26316
|
+
reads: input.reads(),
|
|
26317
|
+
questions,
|
|
26318
|
+
...input.rootStreamPath === void 0 ? {} : { rootStreamPath: input.rootStreamPath },
|
|
26319
|
+
workers,
|
|
26320
|
+
...policy.best === void 0 ? {} : { bar: policy.best },
|
|
26321
|
+
...policy.reference === void 0 ? {} : { reference: policy.reference },
|
|
26322
|
+
usdCap,
|
|
26323
|
+
signal: context.signal
|
|
26324
|
+
});
|
|
26325
|
+
if (result.usd === null) panelUsdKnown = false;
|
|
26326
|
+
else panelUsd += result.usd;
|
|
26327
|
+
findings = distillFindings(findings, result.findings, new Set(failing), continuation);
|
|
26328
|
+
panelRecord = {
|
|
26329
|
+
asked: questions.length,
|
|
26330
|
+
proposed: result.findings.length,
|
|
26331
|
+
admitted: findings.filter((finding) => finding.admittedAt === continuation).length,
|
|
26332
|
+
usd: result.usd
|
|
26333
|
+
};
|
|
26334
|
+
panelLines = [...result.findings.map((finding) => ({
|
|
26335
|
+
kind: "finding",
|
|
26336
|
+
admitted: admitFinding(finding),
|
|
26337
|
+
...finding
|
|
26338
|
+
})), ...result.receipts === void 0 ? [] : [{
|
|
26339
|
+
kind: "receipts",
|
|
26340
|
+
receipts: result.receipts
|
|
26341
|
+
}]];
|
|
26342
|
+
} else panelRecord = {
|
|
26343
|
+
asked: 0,
|
|
26344
|
+
proposed: 0,
|
|
26345
|
+
admitted: 0,
|
|
26346
|
+
usd: 0
|
|
26347
|
+
};
|
|
26348
|
+
}
|
|
26349
|
+
const appended = policy.append ? await policy.append({
|
|
26350
|
+
continuation,
|
|
26351
|
+
...verdict === void 0 ? {} : { verdict },
|
|
26352
|
+
reads: input.reads(),
|
|
26353
|
+
signal: context.signal
|
|
26354
|
+
}) : void 0;
|
|
26355
|
+
const note = composeContinuationNote({
|
|
26356
|
+
profile: policy.profile,
|
|
26357
|
+
continuation,
|
|
26358
|
+
...verdict === void 0 ? {} : { verdict },
|
|
26359
|
+
...notes.at(-1)?.verdict === void 0 ? {} : { previous: notes.at(-1)?.verdict },
|
|
26360
|
+
reads: input.reads().length,
|
|
26361
|
+
failures,
|
|
26362
|
+
...policy.panel === "on" ? { findings } : {},
|
|
26363
|
+
...policy.bar === "on" ? { bar: {
|
|
26364
|
+
...policy.best === void 0 ? {} : { best: policy.best },
|
|
26365
|
+
...policy.reference === void 0 ? {} : { reference: policy.reference }
|
|
26366
|
+
} } : {},
|
|
26367
|
+
...appended === void 0 ? {} : { appended: [appended] },
|
|
26368
|
+
...input.owed === void 0 ? {} : { owed: input.owed },
|
|
26369
|
+
progress: context.progress,
|
|
26370
|
+
canReadMore: input.canReadMore,
|
|
26371
|
+
...input.sealed === true ? { sealed: true } : {}
|
|
26372
|
+
});
|
|
26373
|
+
notes.push({
|
|
26374
|
+
note,
|
|
26375
|
+
...verdict === void 0 ? {} : { verdict },
|
|
26376
|
+
...panelLines === void 0 ? {} : { panel: panelLines },
|
|
26377
|
+
read
|
|
26378
|
+
});
|
|
26379
|
+
entries.push({
|
|
26380
|
+
continuation,
|
|
26381
|
+
attempt: context.attempt,
|
|
26382
|
+
noteDigest: noteDigest(note),
|
|
26383
|
+
profile: policy.profile.id,
|
|
26384
|
+
switches: {
|
|
26385
|
+
failures,
|
|
26386
|
+
panel: policy.panel,
|
|
26387
|
+
bar: policy.bar
|
|
26388
|
+
},
|
|
26389
|
+
before: summarizeVerdict(verdict),
|
|
26390
|
+
after: null,
|
|
26391
|
+
...panelRecord === void 0 ? {} : { panel: panelRecord },
|
|
26392
|
+
appended: appended === void 0 ? 0 : 1
|
|
26393
|
+
});
|
|
26394
|
+
if (input.dir !== void 0) await writeContinuationFiles(input.dir, continuation, {
|
|
26395
|
+
note,
|
|
26396
|
+
...verdict === void 0 ? {} : { verdict },
|
|
26397
|
+
...panelLines === void 0 ? {} : { panel: panelLines }
|
|
26398
|
+
});
|
|
26399
|
+
return note;
|
|
26400
|
+
},
|
|
26401
|
+
read(continuation) {
|
|
26402
|
+
const index = continuation === void 0 ? notes.length - 1 : continuation - 1;
|
|
26403
|
+
const entry = notes[index];
|
|
26404
|
+
if (entry === void 0) return {
|
|
26405
|
+
found: false,
|
|
26406
|
+
reason: notes.length === 0 ? "no continuation has been sent in this run" : `continuations run from 1 to ${notes.length}`
|
|
26407
|
+
};
|
|
26408
|
+
return {
|
|
26409
|
+
found: true,
|
|
26410
|
+
continuation: index + 1,
|
|
26411
|
+
note: entry.note,
|
|
26412
|
+
verdict: failures === "off" && entry.verdict !== void 0 ? { pass: entry.verdict.pass } : entry.verdict ?? null,
|
|
26413
|
+
findings: findings.filter((finding) => finding.admittedAt <= index + 1)
|
|
26414
|
+
};
|
|
26415
|
+
},
|
|
26416
|
+
entries() {
|
|
26417
|
+
closePrevious();
|
|
26418
|
+
return entries.map((entry) => ({ ...entry }));
|
|
26419
|
+
}
|
|
26420
|
+
};
|
|
26421
|
+
}
|
|
26422
|
+
//#endregion
|
|
25490
26423
|
//#region src/runtime/supervise/driver-retry.ts
|
|
25491
26424
|
/**
|
|
25492
26425
|
* Root-driver retry — the root gets the same second chance a worker's transport already has.
|
|
@@ -25534,22 +26467,21 @@ function unreportedSpend(total, prior) {
|
|
|
25534
26467
|
* driver that RETURNED with the contract unmet ended the run silently — 376 of 376 winning lab runs
|
|
25535
26468
|
* ended on this loop's own `stop: 'completed'`, with the completion gate left to label the result
|
|
25536
26469
|
* rather than to change it. A completed drive whose contract is unmet is now a first-class moment:
|
|
25537
|
-
* `
|
|
25538
|
-
* same budget, deadline, and abort bounds.
|
|
25539
|
-
* retry allowance.
|
|
26470
|
+
* the manager's `continuation` policy (`./continuation.ts`) re-enters the driver with Runtime's
|
|
26471
|
+
* continuation note, and every re-entry crosses the same budget, deadline, and abort bounds.
|
|
26472
|
+
* Successful continuations do not consume the failure retry allowance.
|
|
25540
26473
|
*
|
|
25541
26474
|
* This loop decides WHETHER to re-enter; it does not know WHERE the next drive runs. Whether the
|
|
25542
26475
|
* re-entered driver continues the same harness session, the same environment, or a replacement is
|
|
25543
26476
|
* the drive harness's fact, and the caller composes the re-entry task from it (see
|
|
25544
|
-
* `./reentry.ts`). A
|
|
26477
|
+
* `./reentry.ts`). A continuation re-enters the same session only on a backend that proves it.
|
|
25545
26478
|
*
|
|
25546
|
-
* Progress bounds the
|
|
25547
|
-
* counter decides whether a
|
|
25548
|
-
* re-entered drives that completed without
|
|
25549
|
-
* 'no-progress'`.
|
|
25550
|
-
*
|
|
25551
|
-
*
|
|
25552
|
-
* the old prompt-owned loop logged 896 of 1,011 rounds with no progress.
|
|
26479
|
+
* Progress bounds the continuations, and nothing else counts them. The same delivery reading that
|
|
26480
|
+
* resets the barren failure counter decides whether a continued drive earned another:
|
|
26481
|
+
* `continuation.maxBarren` consecutive re-entered drives that completed without progress end the
|
|
26482
|
+
* loop with `repromptRefusedBy: 'no-progress'`. Progress is a delivery or a rise in the check's
|
|
26483
|
+
* best composite. There is no count cap and no caller veto: the old prompt-owned loop logged 896
|
|
26484
|
+
* of 1,011 rounds with no progress, and 650 recorded inputs chose seven different re-prompt counts.
|
|
25553
26485
|
*
|
|
25554
26486
|
* A THIRD CLASS sits between the two: the upstream said "not now". A model provider's exhausted
|
|
25555
26487
|
* quota, a rate limit, or an overloaded service (HTTP 429, 503, 529, or the router's own codes for
|
|
@@ -25561,28 +26493,8 @@ function unreportedSpend(total, prior) {
|
|
|
25561
26493
|
* the router answered some turns in between (one lead spent 1.7M and 3.3M input tokens on two of
|
|
25562
26494
|
* them) and each run's budget and 8-hour deadline were almost untouched.
|
|
25563
26495
|
*/
|
|
25564
|
-
/**
|
|
25565
|
-
* The instruction a completed-but-undelivered drive is re-entered with when the caller supplies no
|
|
25566
|
-
* `onUnmetContract`. It states the verdict, names what is owed, reports the ledger, and gives the
|
|
25567
|
-
* three steps — the same shape `depthStrategy` re-prompts a resumed session with, said in the
|
|
25568
|
-
* driver's own terms.
|
|
25569
|
-
*/
|
|
25570
|
-
function defaultUnmetContractSteer(context) {
|
|
25571
|
-
const owed = context.describe?.trim();
|
|
25572
|
-
return [
|
|
25573
|
-
"The completion check has not passed. This run has delivered nothing yet.",
|
|
25574
|
-
owed === void 0 || owed.length === 0 ? "The deliverable this run owes is still missing." : `The deliverable this run owes: ${owed}`,
|
|
25575
|
-
`Workers settled: ${context.progress.settledCount}. Workers that passed the check: ${context.progress.deliveredCount ?? 0}.`,
|
|
25576
|
-
"Do the unfinished work with the tools.",
|
|
25577
|
-
"Verify that the check passes.",
|
|
25578
|
-
"Then submit the result.",
|
|
25579
|
-
"Do not restate work you already did."
|
|
25580
|
-
].join("\n");
|
|
25581
|
-
}
|
|
25582
26496
|
const DEFAULT_MAX_CONSECUTIVE_FAILURES = 3;
|
|
25583
26497
|
const DEFAULT_MAX_ATTEMPTS = 8;
|
|
25584
|
-
/** Two re-prompted drives in a row that deliver nothing end the re-prompts. */
|
|
25585
|
-
const DEFAULT_MAX_BARREN_REPROMPTS = 2;
|
|
25586
26498
|
const DEFAULT_INITIAL_BACKOFF_MS = 2e3;
|
|
25587
26499
|
const DEFAULT_MAX_BACKOFF_MS = 3e4;
|
|
25588
26500
|
/**
|
|
@@ -25628,6 +26540,7 @@ var HarnessTurnFailedError = class extends Error {
|
|
|
25628
26540
|
*/
|
|
25629
26541
|
function upstreamUnavailableSignal(error) {
|
|
25630
26542
|
if (!(error instanceof Error)) return void 0;
|
|
26543
|
+
if (error instanceof CheckUnavailableError) return "check-unavailable";
|
|
25631
26544
|
if (error instanceof HarnessTurnFailedError) return unavailableSignalOfFailure({
|
|
25632
26545
|
error: error.message,
|
|
25633
26546
|
...error.errorCode === void 0 ? {} : { errorCode: error.errorCode }
|
|
@@ -25711,13 +26624,15 @@ function contractOf(mark) {
|
|
|
25711
26624
|
/**
|
|
25712
26625
|
* Did this attempt move the run toward its DELIVERABLE?
|
|
25713
26626
|
*
|
|
25714
|
-
* A delivery always counts: an accepted submission, one more child that passed the check,
|
|
25715
|
-
* contract turning met
|
|
25716
|
-
* with a check unmet they are the burn-rate reading this
|
|
26627
|
+
* A delivery always counts: an accepted submission, one more child that passed the check, the
|
|
26628
|
+
* contract turning met, or a rise in the check's best composite. Spend and settlements count only
|
|
26629
|
+
* while no declared check is outstanding — with a check unmet they are the burn-rate reading this
|
|
26630
|
+
* module's header measures and rejects.
|
|
25717
26631
|
*/
|
|
25718
26632
|
function madeProgress(before, after) {
|
|
25719
26633
|
if (after.submitted && !before.submitted) return true;
|
|
25720
26634
|
if ((after.deliveredCount ?? 0) > (before.deliveredCount ?? 0)) return true;
|
|
26635
|
+
if (after.composite !== void 0 && after.composite > (before.composite ?? -Infinity)) return true;
|
|
25721
26636
|
if (contractOf(before) !== "met" && contractOf(after) === "met") return true;
|
|
25722
26637
|
if (contractOf(after) === "unmet") return false;
|
|
25723
26638
|
return after.poolTokensSpent > before.poolTokensSpent || after.settledCount > before.settledCount;
|
|
@@ -25763,10 +26678,11 @@ async function defaultSleep(ms, signal) {
|
|
|
25763
26678
|
/**
|
|
25764
26679
|
* Run the driver until it completes WITH ITS CONTRACT MET, or until the budget, the deadline, an
|
|
25765
26680
|
* abort, a terminal error, or a ceiling stops it. Transient failures are retried. A drive that
|
|
25766
|
-
* returns with its completion check unmet is re-entered
|
|
25767
|
-
*
|
|
25768
|
-
* real failure) when a FAILURE ends the loop; a
|
|
25769
|
-
* because deciding what an undelivered run is worth
|
|
26681
|
+
* returns with its completion check unmet is re-entered with Runtime's continuation note until the
|
|
26682
|
+
* continuation's deadline or `maxBarren` turns without progress. Throws
|
|
26683
|
+
* `DriverAttemptsExhaustedError` (cause = the last real failure) when a FAILURE ends the loop; a
|
|
26684
|
+
* completed drive returns, met contract or not, because deciding what an undelivered run is worth
|
|
26685
|
+
* belongs to the finalizer, not to this loop.
|
|
25770
26686
|
*/
|
|
25771
26687
|
async function runDriverWithRetry(run) {
|
|
25772
26688
|
const now = run.now ?? Date.now;
|
|
@@ -25777,19 +26693,18 @@ async function runDriverWithRetry(run) {
|
|
|
25777
26693
|
const maxAttempts = Math.max(1, policy.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
|
|
25778
26694
|
const initialBackoff = Math.max(0, policy.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS);
|
|
25779
26695
|
const maxBackoff = Math.max(initialBackoff, policy.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS);
|
|
25780
|
-
const
|
|
25781
|
-
if (
|
|
25782
|
-
|
|
25783
|
-
if (!Number.
|
|
26696
|
+
const continuation = run.continuation;
|
|
26697
|
+
if (continuation !== void 0) {
|
|
26698
|
+
if (!Number.isFinite(continuation.deadlineMs) || continuation.deadlineMs <= 0) throw new ValidationError$1("runDriverWithRetry: a continuation needs a finite positive deadline");
|
|
26699
|
+
if (!Number.isInteger(continuation.maxBarren) || continuation.maxBarren < 1) throw new ValidationError$1("runDriverWithRetry: continuation.maxBarren must be an integer >= 1");
|
|
25784
26700
|
}
|
|
25785
|
-
const maxBarren = Math.max(1, run.reprompt?.maxBarren ?? 2);
|
|
25786
26701
|
const attempts = [];
|
|
25787
26702
|
let consecutiveBarren = 0;
|
|
25788
26703
|
let barrenReentries = 0;
|
|
25789
26704
|
let failures = 0;
|
|
25790
26705
|
let consecutivePauses = 0;
|
|
25791
26706
|
let pauses = 0;
|
|
25792
|
-
let
|
|
26707
|
+
let continuations = 0;
|
|
25793
26708
|
let reentry;
|
|
25794
26709
|
const emit = async (record) => {
|
|
25795
26710
|
const entered = reentry === void 0 ? {} : { reentry: reentry.reason };
|
|
@@ -25801,32 +26716,33 @@ async function runDriverWithRetry(run) {
|
|
|
25801
26716
|
await run.onAttempt?.(stamped);
|
|
25802
26717
|
};
|
|
25803
26718
|
/**
|
|
25804
|
-
* Decide what a completed
|
|
25805
|
-
* is applied here too — that is the fix for `budgetStop` having lived only in the catch
|
|
25806
|
-
*
|
|
26719
|
+
* Decide what a completed drive with its check unmet does next. Every bound the FAILURE path
|
|
26720
|
+
* applies is applied here too — that is the fix for `budgetStop` having lived only in the catch
|
|
26721
|
+
* arm — and they are read again after the note is composed, because composing may run the
|
|
26722
|
+
* question panel.
|
|
25807
26723
|
*/
|
|
25808
|
-
const
|
|
25809
|
-
|
|
25810
|
-
|
|
25811
|
-
|
|
25812
|
-
|
|
25813
|
-
|
|
25814
|
-
|
|
26724
|
+
const decideContinuation = async (policy, attempt, after) => {
|
|
26725
|
+
const bounds = () => {
|
|
26726
|
+
if (policy.closed()) return "closed";
|
|
26727
|
+
if (run.signal.aborted) return "aborted";
|
|
26728
|
+
if (now() >= policy.deadlineMs) return "deadline";
|
|
26729
|
+
const byBudget = budgetStop(run.budget(), now());
|
|
26730
|
+
return byBudget === "deadline" || byBudget === "budget-exhausted" ? byBudget : void 0;
|
|
26731
|
+
};
|
|
26732
|
+
if (barrenReentries >= policy.maxBarren) return { refusedBy: "no-progress" };
|
|
26733
|
+
const before = bounds();
|
|
26734
|
+
if (before !== void 0) return { refusedBy: before };
|
|
26735
|
+
const steer = (await policy.compose({
|
|
25815
26736
|
attempt,
|
|
25816
|
-
|
|
25817
|
-
maxReprompts,
|
|
26737
|
+
continuations,
|
|
25818
26738
|
progress: after,
|
|
25819
26739
|
budget: run.budget(),
|
|
25820
|
-
|
|
25821
|
-
|
|
25822
|
-
};
|
|
25823
|
-
const
|
|
25824
|
-
if (
|
|
25825
|
-
if (
|
|
25826
|
-
const afterHook = budgetStop(run.budget(), now());
|
|
25827
|
-
if (afterHook === "deadline" || afterHook === "budget-exhausted") return { refusedBy: afterHook };
|
|
25828
|
-
const steer = typeof decision.steer === "string" ? decision.steer.trim() : "";
|
|
25829
|
-
if (steer.length === 0) throw new ValidationError$1("runDriverWithRetry: onUnmetContract returned an empty steer — return a non-empty instruction or 'stop'");
|
|
26740
|
+
barrenReentries,
|
|
26741
|
+
signal: run.signal
|
|
26742
|
+
})).trim();
|
|
26743
|
+
const afterCompose = bounds();
|
|
26744
|
+
if (afterCompose !== void 0) return { refusedBy: afterCompose };
|
|
26745
|
+
if (steer.length === 0) throw new ValidationError$1("runDriverWithRetry: the continuation note is empty");
|
|
25830
26746
|
return { steer };
|
|
25831
26747
|
};
|
|
25832
26748
|
for (let attempt = 1;; attempt += 1) {
|
|
@@ -25940,10 +26856,10 @@ async function runDriverWithRetry(run) {
|
|
|
25940
26856
|
const contractField = contract === "none" ? {} : { contract };
|
|
25941
26857
|
if (progressed) barrenReentries = 0;
|
|
25942
26858
|
else if (reentry !== void 0 && contract === "unmet") barrenReentries += 1;
|
|
25943
|
-
if (contract === "unmet" &&
|
|
25944
|
-
const decision = await
|
|
26859
|
+
if (contract === "unmet" && continuation !== void 0) {
|
|
26860
|
+
const decision = await decideContinuation(continuation, attempt, after);
|
|
25945
26861
|
if ("steer" in decision) {
|
|
25946
|
-
|
|
26862
|
+
continuations += 1;
|
|
25947
26863
|
await emit({
|
|
25948
26864
|
attempt,
|
|
25949
26865
|
durationMs,
|
|
@@ -25955,7 +26871,7 @@ async function runDriverWithRetry(run) {
|
|
|
25955
26871
|
reentry = {
|
|
25956
26872
|
reason: "unmet-contract",
|
|
25957
26873
|
steer: decision.steer,
|
|
25958
|
-
|
|
26874
|
+
continuation: continuations
|
|
25959
26875
|
};
|
|
25960
26876
|
continue;
|
|
25961
26877
|
}
|
|
@@ -26083,8 +26999,9 @@ async function runFinalizer(finalizer, args) {
|
|
|
26083
26999
|
if (candidate === void 0) return false;
|
|
26084
27000
|
if (args.deliverable === void 0) return true;
|
|
26085
27001
|
try {
|
|
26086
|
-
return await args.deliverable.check(candidate)
|
|
27002
|
+
return checkVerdictOf(await args.deliverable.check(candidate)).pass;
|
|
26087
27003
|
} catch (error) {
|
|
27004
|
+
if (error instanceof CheckUnavailableError) throw error;
|
|
26088
27005
|
throw new ValidationError$1(`finalizer: parent completion check threw: ${error instanceof Error ? error.message : String(error)}`);
|
|
26089
27006
|
}
|
|
26090
27007
|
};
|
|
@@ -26349,10 +27266,10 @@ function createSupervisor() {
|
|
|
26349
27266
|
pool = createBudgetPool(opts.budget, runEpochMs, restored.poolRestore);
|
|
26350
27267
|
} else {
|
|
26351
27268
|
pool = createBudgetPool(opts.budget, runStartedAtMs);
|
|
26352
|
-
await opts.journal.beginTree(opts.runId, runStartedAt);
|
|
27269
|
+
if (existing === void 0) await opts.journal.beginTree(opts.runId, runStartedAt);
|
|
26353
27270
|
const rootReceipt = rootMaterializationReceipt(opts);
|
|
26354
27271
|
const rootRuntime = opts.rootMaterialization?.runtime ?? "inline";
|
|
26355
|
-
|
|
27272
|
+
const rootEvents = [{
|
|
26356
27273
|
kind: "spawned",
|
|
26357
27274
|
id: opts.runId,
|
|
26358
27275
|
label: "root",
|
|
@@ -26362,14 +27279,15 @@ function createSupervisor() {
|
|
|
26362
27279
|
...opts.rootIdentity ? { identity: opts.rootIdentity } : {},
|
|
26363
27280
|
seq: 0,
|
|
26364
27281
|
at: runStartedAt
|
|
26365
|
-
}
|
|
26366
|
-
if (rootReceipt !== void 0)
|
|
27282
|
+
}];
|
|
27283
|
+
if (rootReceipt !== void 0) rootEvents.push({
|
|
26367
27284
|
kind: "materialized",
|
|
26368
27285
|
id: opts.runId,
|
|
26369
27286
|
receipt: rootReceipt,
|
|
26370
27287
|
seq: 0,
|
|
26371
27288
|
at: runStartedAt
|
|
26372
27289
|
});
|
|
27290
|
+
await appendSpawnEvents(opts.journal, opts.runId, rootEvents);
|
|
26373
27291
|
}
|
|
26374
27292
|
const stableRootReceipt = resuming ? prior?.find((event) => event.kind === "materialized" && event.id === opts.runId)?.receipt : rootMaterializationReceipt(opts);
|
|
26375
27293
|
if (stableRootReceipt !== void 0) {
|
|
@@ -26501,6 +27419,7 @@ function createSupervisor() {
|
|
|
26501
27419
|
seq: teardownSeq++,
|
|
26502
27420
|
at: new Date(now()).toISOString()
|
|
26503
27421
|
});
|
|
27422
|
+
if (releasing) await closeRetainedSlots(openScope);
|
|
26504
27423
|
} catch (error) {
|
|
26505
27424
|
if (actOutcome?.ok !== false) actOutcome = {
|
|
26506
27425
|
ok: false,
|
|
@@ -26706,11 +27625,18 @@ function createIntensityBreaker(opts, trip) {
|
|
|
26706
27625
|
* settlement either (it writes no `settled` record then), so `downCount` stays what it was:
|
|
26707
27626
|
* ordinary downs only; `fleetYield.down` is where a retained child is counted. */
|
|
26708
27627
|
function wrapJournalForBreaker(journal, breaker) {
|
|
27628
|
+
const recordDown = (ev) => {
|
|
27629
|
+
if (ev.kind === "settled" && ev.status === "down" && ev.retainedExecution === void 0) breaker.recordDown(Date.parse(ev.at));
|
|
27630
|
+
};
|
|
26709
27631
|
return {
|
|
26710
27632
|
loadTree: (root) => journal.loadTree(root),
|
|
26711
27633
|
beginTree: (root, at) => journal.beginTree(root, at),
|
|
27634
|
+
...journal.appendEvents === void 0 ? {} : { appendEvents: (root, events) => {
|
|
27635
|
+
for (const event of events) recordDown(event);
|
|
27636
|
+
return journal.appendEvents?.(root, events) ?? Promise.resolve();
|
|
27637
|
+
} },
|
|
26712
27638
|
appendEvent: (root, ev) => {
|
|
26713
|
-
|
|
27639
|
+
recordDown(ev);
|
|
26714
27640
|
return journal.appendEvent(root, ev);
|
|
26715
27641
|
}
|
|
26716
27642
|
};
|
|
@@ -26917,13 +27843,16 @@ function fleetYieldFromForest(forest) {
|
|
|
26917
27843
|
const spawned = /* @__PURE__ */ new Set();
|
|
26918
27844
|
const terminal = /* @__PURE__ */ new Map();
|
|
26919
27845
|
const released = /* @__PURE__ */ new Set();
|
|
27846
|
+
const unconfirmed = /* @__PURE__ */ new Set();
|
|
26920
27847
|
for (const { event } of forest.events) if (event.kind === "spawned") {
|
|
26921
27848
|
if (event.parent !== void 0) spawned.add(event.id);
|
|
26922
27849
|
} else if (event.kind === "settled" || event.kind === "cancelled") {
|
|
26923
27850
|
const bucket = event.kind === "cancelled" ? "cancelled" : event.status;
|
|
26924
27851
|
terminal.set(event.id, bucket);
|
|
26925
|
-
|
|
26926
|
-
|
|
27852
|
+
released.delete(event.id);
|
|
27853
|
+
unconfirmed.delete(event.id);
|
|
27854
|
+
if (bucket !== "done" && event.retainedExecution === "released") released.add(event.id);
|
|
27855
|
+
if (bucket !== "done" && event.retainedExecution === "release-unconfirmed") unconfirmed.add(event.id);
|
|
26927
27856
|
}
|
|
26928
27857
|
const count = (bucket) => [...terminal.values()].filter((value) => value === bucket).length;
|
|
26929
27858
|
const fleetYield = {
|
|
@@ -26932,10 +27861,11 @@ function fleetYieldFromForest(forest) {
|
|
|
26932
27861
|
down: count("down"),
|
|
26933
27862
|
cancelled: count("cancelled"),
|
|
26934
27863
|
neverSettled: forest.inDoubt.length,
|
|
26935
|
-
releasedUnrecovered: released.size
|
|
27864
|
+
releasedUnrecovered: released.size,
|
|
27865
|
+
releaseUnconfirmed: unconfirmed.size
|
|
26936
27866
|
};
|
|
26937
27867
|
if (fleetYield.done + fleetYield.down + fleetYield.cancelled + fleetYield.neverSettled !== fleetYield.spawned) throw new RuntimeRunStateError(`supervisor: fleet yield does not partition the spawned children of '${forest.root}' (${JSON.stringify(fleetYield)})`);
|
|
26938
|
-
if (fleetYield.releasedUnrecovered > fleetYield.down + fleetYield.cancelled) throw new RuntimeRunStateError(`supervisor: released children exceed down + cancelled for '${forest.root}' (${JSON.stringify(fleetYield)})`);
|
|
27868
|
+
if (fleetYield.releasedUnrecovered + fleetYield.releaseUnconfirmed > fleetYield.down + fleetYield.cancelled) throw new RuntimeRunStateError(`supervisor: released children exceed down + cancelled for '${forest.root}' (${JSON.stringify(fleetYield)})`);
|
|
26939
27869
|
return fleetYield;
|
|
26940
27870
|
}
|
|
26941
27871
|
/** The journaled nodes whose usage accounting is incomplete — one `SpendGap` per node+kind,
|
|
@@ -27000,6 +27930,6 @@ function isNonEmptySpend(s) {
|
|
|
27000
27930
|
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0 || s.tokensKnown === false || s.usdKnown === false || Object.values(s.resources ?? {}).some((resource) => resource.amount > 0 || !resource.known);
|
|
27001
27931
|
}
|
|
27002
27932
|
//#endregion
|
|
27003
|
-
export {
|
|
27933
|
+
export { bindScopeRetainedOwnerProvider as $, detachedSnapshot as $i, providerAsExecutor as $n, runWorktreeHarness as $r, BudgetReconcileFault as $t, driverExecutorFactory as A, resourceTelemetry as Aa, readCommittedJsonLines as Ai, addHarnessUsage as An, createPropagatingTraceEmitter as Ar, snapshotExecutorConfig as At, recordScopeOwnerMaterialization as B, promptOptionsFromAgentTurnInput as Ba, providerAttemptEvidence as Bi, runAgentRounds as Bn, exportEvalRuns as Br, SpawnEventIndex as Bt, createContinuationKeeper as C, stringifySafe as Ca, sandboxActProfileMaterialization as Ci, peerMailTools as Cn, defaultRedactorIdentityMaterial as Cr, rollingDispatch as Ct, passedItems as D, withTimeout as Da, isNoEntError as Di, executorFailure as Dn, persistHarnessTranscript as Dr, cliWorktreeExecutor as Dt, failedItems as E, unmeteredSpend as Ea, worktreeCliProfileMaterialization as Ei, isTerminalNodeStatus as En, harnessTranscriptArtifact as Er, cliInPlaceExecutor as Et, beginScopeOwnerAttempt as F, DEFAULT_SUCCESSFUL_SHUTDOWN_MS as Fa, finalizeRuntimeOwnedPendingExecutor as Fi, bridgeStopSignalKey as Fn, buildLoopOtelSpans as Fr, createInPlaceCliExecutor as Ft, createWorkerSlots as G, runtimeOwnedExecutorProviderEvidence as Gi, readPromptOptions as Gn, padTraceId as Gr, materializeTreeView as Gt, restoreScopeOwnerAcceptedExecution as H, runtimeOwnedDriveHarnessProviderEvidence as Hi, probeSandboxCapabilities as Hn, generateSpanId as Hr, encodeResultBlob as Ht, createScope as I, armDeadlineTimer as Ia, inheritRuntimeOwnedExecutorAttestation as Ii, TERMINAL_DECISIONS as In, buildLoopSpanNodes as Ir, FileResultBlobStore as It, pollFor as J, unknownExecutionBindingReceipt as Ji, canonicalObservedModelParts as Jn, createRuntimeStreamEventCollector as Jr, WORKER_TOOL_TRACE_SCHEMA_VERSION as Jt, createWaitProbes as K, runtimeOwnedPendingExecutorMaterialization as Ki, routerBrain as Kn, toOtelAttributes as Kr, pendingWaits as Kt, deriveNodeExecutionIdentity as L, teardownExecutor as La, knownExecutionBindingReceipt as Li, createSandboxForSpec as Ln, buildRuntimeEventOtelSpans as Lr, FileSpawnJournal as Lt, withDriverExecutor as M, decodeHarnessUsage as Ma, attestRuntimeOwnedPendingExecutor as Mi, harnessUsageIsEmpty as Mn, readTraceContextFromEnv as Mr, DEFAULT_SANDBOX_STEERING_MAX_TURNS as Mt, SUBTREE_RESULT_LIMIT as N, abortError$2 as Na, attestRuntimeOwnedScopeOwner as Ni, readCodexRolloutSession as Nn, traceContextToEnv as Nr, createSteerableSandboxSession as Nt, verdictFromJudgeScore as O, zeroSpend as Oa, parseCommittedJsonLines as Oi, bridgeAdmissionRefusal as On, readHarnessTranscript as Or, createExecutor as Ot, assertRecursiveReservationPolicy as P, awaitAbortable$1 as Pa, authoredProfileDigest as Pi, bridgeRuntimeAttachmentsKey as Pn, INTELLIGENCE_WIRE_VERSION as Pr, createRouterTranscript as Pt, bindScopeRetainedOwnerEnvironmentId as Q, detachedFrozen as Qi, createAgentEnvironmentProviderRegistry as Qn, runSettledCommand as Qr, workerTraceAnalysisStore as Qt, meterRuntimeOwnedAccounting as R, teardownSurfaces as Ra, knownMaterializationReceipt as Ri, defaultSelectWinner as Rn, createOpenInferenceFileExporter as Rr, InMemoryResultBlobStore as Rt, composeContinuationNote as S, sleep as Sa, renderUnsupported as Si, isPeerMailEnvelope as Sn, defaultRedactor as Sr, queueOf as St, expandQuestions as T, throwIfAborted as Ta, validateProfileMaterialization as Ti, isLiveNodeStatus as Tn, captureHarnessTranscript as Tr, captureReusableExecutorConfig as Tt, scopeOwnerExecutorNodeContext as U, runtimeOwnedExecutorExecutionBinding as Ui, acquireSandbox as Un, loopEventToOtelSpan as Ur, insideCursorNamespace as Ut, recordScopeOwnerPause as V, providerMessageText as Va, recordRuntimeOwnedDriveHarnessProviderEvidence as Vi, createSandboxLineage as Vn, flatOtelSpan as Vr, assertContentAddress as Vt, settledToIteration as W, runtimeOwnedExecutorMaterialization as Wi, assertBoxlessPromptOptions as Wn, padSpanId as Wr, loadSpawnForest as Wt, validateWaitSpec as X, executableAgentProfileSnapshot as Xi, DEFAULT_SANDBOX_IDLE_TIMEOUT_SECONDS as Xn, sanitizeKnowledgeReadinessReport as Xr, isTraceAnalysisStore as Xt, timerAt as Y, unknownMaterializationReceipt as Yi, observedModelMatchesDeclared as Yn, sanitizeAgentRuntimeEvent as Yr, captureWorkerTraceEvidence as Yt, waitUntil as Z, executableAgentSpecSnapshot as Zi, LEAF_CONTINUATION_TASK as Zn, sanitizeRuntimeStreamEvent as Zr, parseWorkerToolTraceArtifact as Zt, CheckUnavailableError as _, deleteBoxSafe as _a, promptControlProfileMaterialization as _i, AUTHORITY_MARKERS as _n, environmentGone as _r, workerInteractiveBindingsDir as _t, collectDelivered as a, createSandboxUsageLedger as aa, harnessInvocation as ai, spendFromUsageEvents as an, createPushTraceSource as ar, scopeRetainedOwnerResourceReader as at, bestComposite as b, promptCacheTokenClasses as ba, promptResourceProfileMaterialization as bi, claimsAuthority as bn, linkAbort as br, startRetainedInteractiveRun as bt, runTree as c, mapSandboxEvent as ca, parseCodexTokenUsage as ci, readWorkerTraceContext as cn, registerRetainedExecutorPreparation as cr, environmentReader as ct, classifyDriverFailure as d, sandboxEventServedBackend as da, AGENT_PROFILE_MATERIALIZATION_AXES as di, workerTraceSeamKey as dn, reconnectRetainedRun as dr, interactiveAdmissionSeamKey as dt, projectSandboxOutcome as ea, captureWorktreeDiff as ei, ReservationWaitRefused as en, providerAsSandboxClient as er, consumeScopeRetainedOwnerResult as et, runDriverWithRetry as f, sandboxProgressEvents as fa, assertProfileMaterialization as fi, taskToPrompt as fn, recoverRetainedRun as fr, readWorkerInteractiveAdmissions as ft, CONTINUATION_FACTS as g, cloneSpend as ga, profileMaterializationAxes$1 as gi, createInbox as gn, destroyInteractiveEnvironment as gr, workerInteractiveBindingFile as gt, CONTINUATIONS_DIR as h, chargedTokens as ha, fullProfileMaterialization as hi, readWorkerProgress as hn, retainedCreateMaterial as hr, readWorkerInteractiveBinding as ht, bestDelivered as i, createSandboxToolPartState as ia, LOCAL_HARNESSES as ii, newUsageTotals as in, unavailableSignalOfFailure as ir, scopeRetainedOwnerPriorSpend as it, isDriverSpec as j, withBudgetResources as ja, writeAllBytes as ji, createCodexRolloutStoreReader as jn, mergeTraceEnv as jr, createWorktreeCliExecutor as jt, driverChild as k, addResourceSpend as ka, prepareJsonlAppend as ki, bridgeModelRouteRefusal as kn, contentAddress as kr, createExecutorRegistry as kt, DriverAttemptsExhaustedError as l, mapSandboxToolEvent as la, CodexExecutionDiagnosticError as li, workerTraceEnv as ln, retainedExecutorSeamKey as lr, hostDirectoryReader as lt, upstreamUnavailableSignal as m, addSpend as ma, defineProfileMaterializationContract as mi, createActivityLog as mn, startRetainedRunInEnvironment as mr, attachWorker as mt, createRootHandle as n, assertSandboxServedModel as na, removeWorktree as ni, createBudgetPool as nn, sandboxClientAsProvider as nr, reconcileScopeRetainedOwnerEnvironment as nt, pickBestDelivered as o, extractLlmCallEvent as oa, harnessSupportsReasoningEffort as oi, spendFromUsageTotals as on, decodeToolPart as or, scopeRetainedOwnerResult as ot, summarizeDriverAttempts as p, sumSandboxUsage as pa, controlProfileMaterialization as pi, DEFAULT_STALL_AFTER_MS as pn, startRetainedRun as pr, workerInteractiveAdmissionFile as pt, isWaitOutcome as q, runtimeOwnedScopeOwnerRuntime as qi, runBrainLoop as qn, createRuntimeEventCollector as qr, replaySpawnTree as qt, createSupervisor as r, canonicalStreamEventFromSandboxEvent as ra, DEFAULT_LOCAL_HARNESS as ri, meterUsageEvent as rn, unavailablePauseMs as rr, scopeRetainedOwnerContext as rt, runFinalizer as s, isSandboxTerminalEvent as sa, localHarnessExecutable as si, WORKER_TRACE_PROPAGATION as sn, sandboxSessionTraceSource as sr, SPAWN_RESOURCE_PATH_MAX_BYTES as st, DEFAULT_MAX_DEPTH as t, readSandboxOutcome as ta, createWorktree as ti, assertValidBudget as tn, resolveAgentEnvironmentProvider as tr, prepareScopeRetainedOwnerTask as tt, HarnessTurnFailedError as u, notifySandboxEventObserver as ua, terminateProcessTreeAndConfirm as ui, workerTraceHeaders as un, errMessage as ur, resolveSpawnResourcePaths as ut, admitContinuationPolicy as v, hasCompleteCacheBreakdown as va, promptModelProfileMaterialization as vi, DEFAULT_PEER_MAIL_LIMITS as vn, RunCancellationReason as vr, reconnectRetainedInteractiveRun as vt, distillFindings as w, throwAbort as wa, unsupportedProfileDimensions as wi, peerMailVerbNames as wn, resolveRedactor as wr, bindReusableExecutorExecutionId as wt, checkVerdictOf as x, randomSuffix as xa, renderProfileMaterializationIssues as xi, createPeerMailbox as xn, runAbortable as xr, freeSlots as xt, admitFinding as y, isAbortError$2 as ya, promptOnlyProfileMaterialization as yi, PEER_MAIL_WIRE_KEY as yn, abortError as yr, recoverRetainedInteractiveRun as yt, meterRuntimeOwnedProviderAttempt as z, promptFromAgentTurnInput as za, newExecutionAttemptId as zi, isTerminalDecision as zn, createOtelExporter as zr, InMemorySpawnJournal as zt };
|
|
27004
27934
|
|
|
27005
|
-
//# sourceMappingURL=supervisor-
|
|
27935
|
+
//# sourceMappingURL=supervisor-DdKPGluv.js.map
|