@skill-harness/core 0.11.0 → 0.13.0
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/README.md +2 -1
- package/dist/adapters/types.d.ts +19 -0
- package/dist/adjudication.d.ts +9 -1
- package/dist/adjudication.js +41 -33
- package/dist/adoption.d.ts +128 -0
- package/dist/adoption.js +102 -0
- package/dist/arms.d.ts +1 -1
- package/dist/capture-trace-types.d.ts +2 -0
- package/dist/command-cost.d.ts +9 -0
- package/dist/command-cost.js +23 -0
- package/dist/comparison.d.ts +1 -0
- package/dist/comparison.js +9 -3
- package/dist/execution-trace.js +9 -1
- package/dist/factory-calibration.d.ts +63 -0
- package/dist/factory-calibration.js +115 -0
- package/dist/grade.d.ts +2 -0
- package/dist/grade.js +2 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/intervention-results.d.ts +6 -0
- package/dist/intervention-results.js +52 -0
- package/dist/intervention.d.ts +123 -0
- package/dist/intervention.js +218 -0
- package/dist/investigation.d.ts +94 -0
- package/dist/investigation.js +148 -0
- package/dist/journal.d.ts +1 -1
- package/dist/judge-agreement.d.ts +19 -0
- package/dist/judge-agreement.js +31 -0
- package/dist/judge-policy.js +5 -3
- package/dist/lift.js +1 -1
- package/dist/lint.js +1 -1
- package/dist/prompt-normalization.d.ts +6 -0
- package/dist/prompt-normalization.js +14 -0
- package/dist/qualification-capture.d.ts +44 -0
- package/dist/qualification-capture.js +163 -0
- package/dist/qualification-config.d.ts +233 -0
- package/dist/qualification-config.js +694 -0
- package/dist/qualification-lock.d.ts +15 -0
- package/dist/qualification-lock.js +87 -0
- package/dist/qualification-oauth-directory.d.ts +79 -0
- package/dist/qualification-oauth-directory.js +444 -0
- package/dist/qualification-panel-store.d.ts +14 -0
- package/dist/qualification-panel-store.js +188 -0
- package/dist/qualification-panels.d.ts +99 -0
- package/dist/qualification-panels.js +91 -0
- package/dist/qualification-process.d.ts +13 -0
- package/dist/qualification-process.js +190 -0
- package/dist/qualification-runner.d.ts +203 -0
- package/dist/qualification-runner.js +2037 -0
- package/dist/qualification-store.d.ts +187 -0
- package/dist/qualification-store.js +833 -0
- package/dist/qualification-validation.d.ts +12 -0
- package/dist/qualification-validation.js +8 -0
- package/dist/regate.js +42 -10
- package/dist/regrade.d.ts +3 -1
- package/dist/regrade.js +37 -5
- package/dist/reps.d.ts +4 -2
- package/dist/reps.js +13 -4
- package/dist/rescore.js +2 -1
- package/dist/results.d.ts +53 -7
- package/dist/results.js +210 -7
- package/dist/run.js +66 -17
- package/dist/score.d.ts +4 -3
- package/dist/score.js +13 -4
- package/dist/screen.d.ts +31 -0
- package/dist/screen.js +105 -0
- package/dist/seeded.d.ts +2 -1
- package/dist/seeded.js +1 -0
- package/dist/sources.d.ts +7 -1
- package/dist/sources.js +13 -5
- package/dist/spec.d.ts +1 -1
- package/dist/spec.js +1 -1
- package/dist/stability.js +1 -1
- package/dist/trajectory-gates.d.ts +14 -14
- package/dist/trajectory-gates.js +18 -34
- package/dist/trusted-host-supervision.d.ts +25 -0
- package/dist/trusted-host-supervision.js +94 -0
- package/dist/vote-panel.d.ts +33 -0
- package/dist/vote-panel.js +41 -0
- package/dist/work-capture-schema.d.ts +38 -0
- package/dist/work-capture-schema.js +33 -0
- package/dist/work-capture.d.ts +43 -0
- package/dist/work-capture.js +81 -0
- package/dist/work-signals.d.ts +55 -0
- package/dist/work-signals.js +71 -0
- package/dist/workspace.js +4 -1
- package/package.json +3 -3
- package/schemas/qualification-cell-result-v1.schema.json +32 -0
- package/schemas/qualification-config-v1.schema.json +244 -0
- package/schemas/qualification-invocation-request-v1.schema.json +59 -0
- package/schemas/qualification-judge-panel-result-v1.schema.json +42 -0
- package/schemas/results-v2.schema.json +19 -21
- package/schemas/results-v3.schema.json +654 -0
- package/schemas/specification-v1.schema.json +1 -1
- package/schemas/trajectory-event-v1.schema.json +20 -1
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @skill-harness/core
|
|
2
2
|
|
|
3
3
|
The engine behind [skill-harness](https://github.com/mojomanyana/skill-harness):
|
|
4
|
-
spec parsing, discovery, run orchestration, LLM-judge grading, scoring, results
|
|
4
|
+
spec parsing, discovery, run orchestration, LLM-judge grading, scoring, results,
|
|
5
|
+
and the separately versioned durable qualification-runner-v1 primitives.
|
|
5
6
|
|
|
6
7
|
Internal API — use the `skill-harness` CLI. See the repo for docs.
|
|
7
8
|
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -11,6 +11,21 @@ export interface ModelRef {
|
|
|
11
11
|
export declare function parseModelRef(token: string): ModelRef;
|
|
12
12
|
/** Stable, filesystem-safe slug for a model ref (used in run-dir names). */
|
|
13
13
|
export declare function modelSlug(ref: ModelRef): string;
|
|
14
|
+
export type PromptMechanism = "none" | "pi-skill" | "append-system-prompt" | "system-prompt-file";
|
|
15
|
+
export interface PromptProvenance {
|
|
16
|
+
capture_version: "prompt-provenance-v1";
|
|
17
|
+
request_index: number;
|
|
18
|
+
raw_sha256: string;
|
|
19
|
+
normalized_sha256: string;
|
|
20
|
+
normalization_rule: "cwd-line-v1";
|
|
21
|
+
bytes: number;
|
|
22
|
+
contract_sha256: string;
|
|
23
|
+
contract_bytes: number;
|
|
24
|
+
contract_occurrences: number;
|
|
25
|
+
mechanism: PromptMechanism;
|
|
26
|
+
status: "PASS" | "NOT-MEASURED" | "ERROR";
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
14
29
|
export interface RunReq {
|
|
15
30
|
skillDir: string;
|
|
16
31
|
model: ModelRef;
|
|
@@ -50,6 +65,8 @@ export interface RunReq {
|
|
|
50
65
|
* inheriting a caller's copy of them.
|
|
51
66
|
*/
|
|
52
67
|
armEnv?: Record<string, string>;
|
|
68
|
+
/** Adapter-produced observation callback. Callers receive evidence; they never supply delivery status. */
|
|
69
|
+
onPromptObservation?: (observation: PromptProvenance) => void;
|
|
53
70
|
}
|
|
54
71
|
/** A judge request: single prompt, no skills, no session. */
|
|
55
72
|
export interface JudgeReq {
|
|
@@ -74,6 +91,8 @@ export interface StructuredRun {
|
|
|
74
91
|
}
|
|
75
92
|
export interface HarnessAdapter {
|
|
76
93
|
name: string;
|
|
94
|
+
/** True only when the adapter computes final provider-payload observations. */
|
|
95
|
+
observesPrompts?: boolean;
|
|
77
96
|
available(): Promise<boolean>;
|
|
78
97
|
run(req: RunReq): Promise<string>;
|
|
79
98
|
/**
|
package/dist/adjudication.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HarnessAdapter, ModelRef } from "./adapters/types.js";
|
|
2
|
-
import type { ScenarioResult, AdjudicationResult, Judgment, ResultsFile } from "./results.js";
|
|
2
|
+
import type { ScenarioResult, AdjudicationResult, Judgment, ResultsFile, CriterionVote } from "./results.js";
|
|
3
3
|
import type { Scenario, ShipBar, Spec } from "./spec.js";
|
|
4
4
|
import type { Verdict } from "./score.js";
|
|
5
5
|
/**
|
|
@@ -33,6 +33,11 @@ export interface CellState {
|
|
|
33
33
|
suspect: boolean;
|
|
34
34
|
/** Per-rep verdicts, when the cell ran with reps. Empty for a single-rep cell. */
|
|
35
35
|
repVerdicts?: Verdict[];
|
|
36
|
+
criteria?: CriterionVote[];
|
|
37
|
+
/** Delivery prerequisite for schema-3 cells; non-PASS cells are never judged. */
|
|
38
|
+
deliveryStatus?: "PASS" | "NOT-MEASURED" | "ERROR";
|
|
39
|
+
/** The actual selected repetition's primary vote; aggregate fields remain for planning only. */
|
|
40
|
+
primaryJudgment?: Judgment;
|
|
36
41
|
}
|
|
37
42
|
export interface TriggerDecision {
|
|
38
43
|
id: string;
|
|
@@ -105,11 +110,14 @@ export declare function collapseJudgments(judgments: Judgment[], trigger: Trigge
|
|
|
105
110
|
*/
|
|
106
111
|
export declare function boundAdjudicationToRepetitions(result: ScenarioResult, scenario: Scenario, adj: AdjudicationResult): AdjudicationResult;
|
|
107
112
|
export declare function projectAdjudication(result: ScenarioResult, adj: AdjudicationResult): ScenarioResult;
|
|
113
|
+
/** Apply the one-transcript bound and label it only when the bound changed the panel result. */
|
|
114
|
+
export declare function projectAdjudicationForScenario(result: ScenarioResult, scenario: Scenario, adj: AdjudicationResult): ScenarioResult;
|
|
108
115
|
/** Ask one judge about one cell's saved transcript. Supplied by the caller. */
|
|
109
116
|
export type RejudgeFn = (id: string, judge: ModelRef) => Promise<{
|
|
110
117
|
verdict: Verdict;
|
|
111
118
|
reason: string;
|
|
112
119
|
suspect: boolean;
|
|
120
|
+
criteria?: CriterionVote[];
|
|
113
121
|
}>;
|
|
114
122
|
export interface RunAdjudicationOptions {
|
|
115
123
|
plan: AdjudicationPlan;
|
package/dist/adjudication.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { judgeRawPath, writeResults, scoreContextFor, findTranscriptFiles, repIndexOf, rebuildScenarioResult, mergeScenarioMetrics, effectiveThreshold, } from "./results.js";
|
|
3
|
+
import { judgeRawPath, writeResults, scoreContextFor, findTranscriptFiles, repIndexOf, rebuildScenarioResult, mergeScenarioMetrics, effectiveThreshold, completeCriterionVotes, } from "./results.js";
|
|
4
4
|
import { score } from "./score.js";
|
|
5
5
|
import { buildJudgePrompt, judgeInWorkspace, parseVerdict } from "./grade.js";
|
|
6
6
|
import { appendJournal } from "./journal.js";
|
|
7
|
+
import { collapseVotePanel } from "./vote-panel.js";
|
|
7
8
|
/** Maximum judgments per cell, first wave included. Hard cap, not a default. */
|
|
8
9
|
export const MAX_JUDGMENTS = 3;
|
|
9
10
|
/**
|
|
@@ -18,6 +19,9 @@ export function planAdjudication(input) {
|
|
|
18
19
|
const enabled = new Set(input.enabled ?? ["ambiguous", "contradictory", "non_unanimous", "ship_deciding"]);
|
|
19
20
|
const decisions = [];
|
|
20
21
|
for (const cell of input.cells) {
|
|
22
|
+
// Delivery-gated schema-3 cells carry no behavioral claim to adjudicate.
|
|
23
|
+
if (cell.deliveryStatus && cell.deliveryStatus !== "PASS")
|
|
24
|
+
continue;
|
|
21
25
|
const triggers = [];
|
|
22
26
|
// `JUDGE-AMBIGUOUS` is what the parser emits when a judge's verdict blocks
|
|
23
27
|
// disagree. `ERROR` is what it emits when nothing parsed at all — and that
|
|
@@ -92,24 +96,13 @@ function flipsShipDecision(cell, input) {
|
|
|
92
96
|
* cast a deciding vote would launder exactly the unreliability being measured.
|
|
93
97
|
*/
|
|
94
98
|
export function collapseJudgments(judgments, trigger) {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const fails = clean.length - passes;
|
|
103
|
-
if (passes === 0 || fails === 0) {
|
|
104
|
-
return { ...base, state: "confirmed", verdict: clean[0].verdict };
|
|
105
|
-
}
|
|
106
|
-
// A clean strict majority breaks the tie. With exactly 2 clean votes that
|
|
107
|
-
// disagree there is no majority, so this correctly falls through.
|
|
108
|
-
if (passes > fails)
|
|
109
|
-
return { ...base, state: "tie_broken", verdict: "PASS" };
|
|
110
|
-
if (fails > passes)
|
|
111
|
-
return { ...base, state: "tie_broken", verdict: "FAIL" };
|
|
112
|
-
return { ...base, state: "unresolved" };
|
|
99
|
+
const collapsed = collapseVotePanel(judgments);
|
|
100
|
+
return {
|
|
101
|
+
trigger,
|
|
102
|
+
judgments,
|
|
103
|
+
state: collapsed.state,
|
|
104
|
+
...(collapsed.verdict ? { verdict: collapsed.verdict } : {}),
|
|
105
|
+
};
|
|
113
106
|
}
|
|
114
107
|
/**
|
|
115
108
|
* Project an adjudication onto the compatibility fields the scorer reads.
|
|
@@ -149,6 +142,15 @@ export function projectAdjudication(result, adj) {
|
|
|
149
142
|
adjudication: adj,
|
|
150
143
|
};
|
|
151
144
|
}
|
|
145
|
+
/** Apply the one-transcript bound and label it only when the bound changed the panel result. */
|
|
146
|
+
export function projectAdjudicationForScenario(result, scenario, adj) {
|
|
147
|
+
const bounded = boundAdjudicationToRepetitions(result, scenario, adj);
|
|
148
|
+
const projected = projectAdjudication(result, bounded);
|
|
149
|
+
if (bounded !== adj) {
|
|
150
|
+
projected.judge_reason = `${adj.judgments.length} judgments on one transcript cannot replace a critical all-repetitions aggregate`;
|
|
151
|
+
}
|
|
152
|
+
return projected;
|
|
153
|
+
}
|
|
152
154
|
function reasonFor(adj) {
|
|
153
155
|
const n = adj.judgments.length;
|
|
154
156
|
const verb = adj.state === "confirmed" ? "confirmed by" : "resolved by majority of";
|
|
@@ -179,7 +181,9 @@ export async function runAdjudication(opts) {
|
|
|
179
181
|
// in the fixed order `planAdjudication` evaluates them.
|
|
180
182
|
const trigger = decision.triggers[0];
|
|
181
183
|
const judgments = [
|
|
182
|
-
|
|
184
|
+
cell.primaryJudgment
|
|
185
|
+
? { ...cell.primaryJudgment, ordinal: 1 }
|
|
186
|
+
: { ordinal: 1, judge: { ...opts.primaryJudge }, verdict: cell.verdict, reason: cell.reason, suspect: cell.suspect, criteria: cell.criteria ?? [] },
|
|
183
187
|
];
|
|
184
188
|
const second = await opts.rejudge(decision.id, opts.secondaryJudge);
|
|
185
189
|
callsMade++;
|
|
@@ -242,11 +246,8 @@ export async function adjudicateRun(opts) {
|
|
|
242
246
|
if (!adj)
|
|
243
247
|
return s;
|
|
244
248
|
const scenario = byIdScenario.get(s.id);
|
|
245
|
-
const
|
|
246
|
-
const projected = projectAdjudication(s,
|
|
247
|
-
if (boundedAdj !== adj) {
|
|
248
|
-
projected.judge_reason = `${adj.judgments.length} judgments on one transcript cannot replace a critical all-repetitions aggregate`;
|
|
249
|
-
}
|
|
249
|
+
const withRepetition = { ...adj, repetition: s.rep_judgments?.[0]?.repetition ?? 0 };
|
|
250
|
+
const projected = scenario ? projectAdjudicationForScenario(s, scenario, withRepetition) : projectAdjudication(s, withRepetition);
|
|
250
251
|
const extraCalls = adj.judgments.filter((judgment) => judgment.ordinal > 1).length;
|
|
251
252
|
projected.metrics = mergeScenarioMetrics(s.metrics, {
|
|
252
253
|
wall_time_ms: 0,
|
|
@@ -299,7 +300,7 @@ async function judgeCell(opts) {
|
|
|
299
300
|
event: "judge-verdict", ts: opts.now(), id: opts.scenario.id,
|
|
300
301
|
verdict: g.verdict, reason: g.reason, suspect: g.suspect,
|
|
301
302
|
});
|
|
302
|
-
return { verdict: g.verdict, reason: g.reason, suspect: g.suspect };
|
|
303
|
+
return { verdict: g.verdict, reason: g.reason, suspect: g.suspect, criteria: completeCriterionVotes(g.criteria, opts.scenario.checklist.length) };
|
|
303
304
|
}
|
|
304
305
|
/**
|
|
305
306
|
* Build the plan's input cells from a run.
|
|
@@ -311,13 +312,20 @@ async function judgeCell(opts) {
|
|
|
311
312
|
* about — inverting the one invariant this feature actually promises.
|
|
312
313
|
*/
|
|
313
314
|
export function cellsFromResults(runDir, results) {
|
|
314
|
-
return results.scenarios.map((s) =>
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
315
|
+
return results.scenarios.map((s) => {
|
|
316
|
+
const primaryJudgment = s.rep_judgments?.find(panel => panel.repetition === 0)?.judgments[0];
|
|
317
|
+
const deliveryStatus = s.objective?.assertions.find(assertion => assertion.kind === "skill_delivered")?.status;
|
|
318
|
+
return {
|
|
319
|
+
id: s.id,
|
|
320
|
+
verdict: s.judge_verdict,
|
|
321
|
+
reason: s.judge_reason,
|
|
322
|
+
suspect: s.suspect,
|
|
323
|
+
repVerdicts: repVerdictsOf(runDir, s, results.mode),
|
|
324
|
+
criteria: primaryJudgment?.criteria,
|
|
325
|
+
...(deliveryStatus === "PASS" || deliveryStatus === "NOT-MEASURED" || deliveryStatus === "ERROR" ? { deliveryStatus } : {}),
|
|
326
|
+
...(primaryJudgment ? { primaryJudgment } : {}),
|
|
327
|
+
};
|
|
328
|
+
});
|
|
321
329
|
}
|
|
322
330
|
/** Per-rep verdicts from saved judge artifacts, for the non-unanimous trigger. */
|
|
323
331
|
function repVerdictsOf(runDir, s, mode) {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export interface AdoptionInput {
|
|
2
|
+
hypothesisDigest: string;
|
|
3
|
+
experimentDigest: string;
|
|
4
|
+
candidateDigest: string;
|
|
5
|
+
scopeDigest: string;
|
|
6
|
+
assessmentPolicyDigest: string;
|
|
7
|
+
rollbackCandidateDigest: string;
|
|
8
|
+
activationBoundary: "next-orders";
|
|
9
|
+
expiresAt: number;
|
|
10
|
+
}
|
|
11
|
+
export interface AdoptionBinding extends AdoptionInput {
|
|
12
|
+
version: "adoption-binding-v1";
|
|
13
|
+
id: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AdoptionAuthority {
|
|
16
|
+
id: string;
|
|
17
|
+
adoptions: readonly string[];
|
|
18
|
+
rollbacks: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface AdoptionFacts {
|
|
21
|
+
experimentDigest: string;
|
|
22
|
+
candidateDigest: string;
|
|
23
|
+
scopeDigest: string;
|
|
24
|
+
assessmentPolicyDigest: string;
|
|
25
|
+
eligible: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildAdoptionBinding(input: AdoptionInput): AdoptionBinding;
|
|
28
|
+
/** Independent host declarations and independently verified eligible facts are required again at use. */
|
|
29
|
+
export declare function authorizeAdoption(binding: AdoptionBinding, authority: AdoptionAuthority | null, facts: AdoptionFacts, now: number): Readonly<{
|
|
30
|
+
id: string;
|
|
31
|
+
version: "adoption-receipt-v1";
|
|
32
|
+
bindingId: string;
|
|
33
|
+
authorityId: string;
|
|
34
|
+
hypothesisDigest: string;
|
|
35
|
+
experimentDigest: string;
|
|
36
|
+
candidateDigest: string;
|
|
37
|
+
scopeDigest: string;
|
|
38
|
+
assessmentPolicyDigest: string;
|
|
39
|
+
rollbackCandidateDigest: string;
|
|
40
|
+
activationBoundary: "next-orders";
|
|
41
|
+
grantExpansion: false;
|
|
42
|
+
expiresAt: number;
|
|
43
|
+
}>;
|
|
44
|
+
export type AdoptionReceipt = ReturnType<typeof authorizeAdoption>;
|
|
45
|
+
export declare function validateAdoptionReceipt(receipt: AdoptionReceipt, binding: AdoptionBinding, authority: AdoptionAuthority | null, facts: AdoptionFacts, now: number): void;
|
|
46
|
+
export interface ProductionObservation {
|
|
47
|
+
id: string;
|
|
48
|
+
adoptionId: string;
|
|
49
|
+
candidateDigest: string;
|
|
50
|
+
scopeDigest: string;
|
|
51
|
+
originalRequirementDigest: string;
|
|
52
|
+
currentRequirementDigest: string;
|
|
53
|
+
/** Only an independent host may supply confirmed outcomes; observer/model claims are not confirmation. */
|
|
54
|
+
outcome: "confirmed-defect" | "success" | "unknown";
|
|
55
|
+
acceptanceDigest: string | null;
|
|
56
|
+
acceptedArtifactDigest: string | null;
|
|
57
|
+
observedArtifactDigest: string;
|
|
58
|
+
evidence: string[];
|
|
59
|
+
}
|
|
60
|
+
export declare function classifyProductionObservation(adoption: AdoptionReceipt, input: ProductionObservation): Readonly<{
|
|
61
|
+
state: "unknown" | "success" | "out-of-scope" | "changed-requirements" | "changed-artifact" | "caught-defect" | "escape";
|
|
62
|
+
adoptionId: string;
|
|
63
|
+
hypothesisDigest: string;
|
|
64
|
+
experimentDigest: string;
|
|
65
|
+
observationId: string;
|
|
66
|
+
acceptanceDigest: string | null;
|
|
67
|
+
acceptedArtifactDigest: string | null;
|
|
68
|
+
observedArtifactDigest: string;
|
|
69
|
+
automaticRollback: false;
|
|
70
|
+
}>;
|
|
71
|
+
export interface RollbackRequest {
|
|
72
|
+
version: "rollback-request-v1";
|
|
73
|
+
id: string;
|
|
74
|
+
adoptionId: string;
|
|
75
|
+
scopeDigest: string;
|
|
76
|
+
restoreCandidateDigest: string;
|
|
77
|
+
reason: "operator-request" | "confirmed-escape";
|
|
78
|
+
evidence: readonly string[];
|
|
79
|
+
expiresAt: number;
|
|
80
|
+
}
|
|
81
|
+
export declare function buildRollbackRequest(adoption: AdoptionReceipt, reason: RollbackRequest["reason"], evidence: string[], expiresAt?: number): RollbackRequest;
|
|
82
|
+
/** This validates a recorded choice, not an automatic rollback action or a caller-created authority flag. */
|
|
83
|
+
export declare function authorizeRollback(adoption: AdoptionReceipt, request: RollbackRequest, authority: AdoptionAuthority | null, now: number, current: {
|
|
84
|
+
adoptionId: string;
|
|
85
|
+
candidateDigest: string;
|
|
86
|
+
scopeDigest: string;
|
|
87
|
+
}): Readonly<{
|
|
88
|
+
requestId: string;
|
|
89
|
+
adoptionId: string;
|
|
90
|
+
restoreCandidateDigest: string;
|
|
91
|
+
scopeDigest: string;
|
|
92
|
+
grantExpansion: false;
|
|
93
|
+
application: "not-performed";
|
|
94
|
+
}>;
|
|
95
|
+
export interface LeanOutcome {
|
|
96
|
+
population: string;
|
|
97
|
+
complete: boolean;
|
|
98
|
+
accepted: boolean | null;
|
|
99
|
+
repairAttempts: number | null;
|
|
100
|
+
escape: "confirmed" | "none-observed" | "unknown";
|
|
101
|
+
requirementsChanged: boolean;
|
|
102
|
+
decisionWaitMs: number | null;
|
|
103
|
+
questions: number | null;
|
|
104
|
+
}
|
|
105
|
+
export declare function summarizeLeanOutcomes(population: string, rows: readonly LeanOutcome[]): {
|
|
106
|
+
population: string;
|
|
107
|
+
firstPassAccepted: number;
|
|
108
|
+
firstPassDenominator: number;
|
|
109
|
+
incomplete: number;
|
|
110
|
+
confirmedEscapes: number;
|
|
111
|
+
changedRequirements: number;
|
|
112
|
+
recovery: {
|
|
113
|
+
known: number;
|
|
114
|
+
observed: number;
|
|
115
|
+
total: number;
|
|
116
|
+
};
|
|
117
|
+
decisionWait: {
|
|
118
|
+
known: number;
|
|
119
|
+
observed: number;
|
|
120
|
+
total: number;
|
|
121
|
+
};
|
|
122
|
+
questions: {
|
|
123
|
+
known: number;
|
|
124
|
+
observed: number;
|
|
125
|
+
total: number;
|
|
126
|
+
};
|
|
127
|
+
universalScore: null;
|
|
128
|
+
};
|
package/dist/adoption.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const SHA = /^[a-f0-9]{64}$/;
|
|
3
|
+
const text = (x) => typeof x === "string" && x.length > 0 && x.length <= 512 && !/[\u0000-\u001f\u007f]/.test(x);
|
|
4
|
+
const closed = (value, names) => {
|
|
5
|
+
if (!value || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
6
|
+
return false;
|
|
7
|
+
const fields = Object.getOwnPropertyDescriptors(value);
|
|
8
|
+
return Reflect.ownKeys(fields).length === names.length && Reflect.ownKeys(fields).every(key => typeof key === "string" && names.includes(key) && fields[key].enumerable && Object.hasOwn(fields[key], "value"));
|
|
9
|
+
};
|
|
10
|
+
const hash = (value) => createHash("sha256").update(JSON.stringify(Object.keys(value).sort().map(k => [k, value[k]]))).digest("hex");
|
|
11
|
+
export function buildAdoptionBinding(input) {
|
|
12
|
+
if (!input || !closed(input, ["hypothesisDigest", "experimentDigest", "candidateDigest", "scopeDigest", "assessmentPolicyDigest", "rollbackCandidateDigest", "activationBoundary", "expiresAt"])
|
|
13
|
+
|| ![input.hypothesisDigest, input.experimentDigest, input.candidateDigest, input.scopeDigest, input.assessmentPolicyDigest, input.rollbackCandidateDigest].every(s => typeof s === "string" && SHA.test(s))
|
|
14
|
+
|| input.activationBoundary !== "next-orders" || !Number.isSafeInteger(input.expiresAt) || input.expiresAt < 0)
|
|
15
|
+
throw new Error("invalid scoped adoption binding");
|
|
16
|
+
const body = { version: "adoption-binding-v1", ...input };
|
|
17
|
+
return Object.freeze({ ...body, id: hash(body) });
|
|
18
|
+
}
|
|
19
|
+
function bindingValid(binding) {
|
|
20
|
+
const { version, id, ...input } = binding;
|
|
21
|
+
if (version !== "adoption-binding-v1" || buildAdoptionBinding(input).id !== id)
|
|
22
|
+
throw new Error("adoption binding changed");
|
|
23
|
+
}
|
|
24
|
+
function authorityValid(authority) {
|
|
25
|
+
return !!authority && text(authority.id) && Array.isArray(authority.adoptions) && Array.isArray(authority.rollbacks)
|
|
26
|
+
&& authority.adoptions.length <= 256 && authority.rollbacks.length <= 256 && [...authority.adoptions, ...authority.rollbacks].every(id => typeof id === "string" && SHA.test(id));
|
|
27
|
+
}
|
|
28
|
+
/** Independent host declarations and independently verified eligible facts are required again at use. */
|
|
29
|
+
export function authorizeAdoption(binding, authority, facts, now) {
|
|
30
|
+
bindingValid(binding);
|
|
31
|
+
if (!authorityValid(authority) || !authority.adoptions.includes(binding.id))
|
|
32
|
+
throw new Error("independent adoption authority required");
|
|
33
|
+
if (!Number.isSafeInteger(now) || now < 0 || now >= binding.expiresAt || !facts || !closed(facts, ["experimentDigest", "candidateDigest", "scopeDigest", "assessmentPolicyDigest", "eligible"]) || facts.eligible !== true
|
|
34
|
+
|| facts.experimentDigest !== binding.experimentDigest || facts.candidateDigest !== binding.candidateDigest
|
|
35
|
+
|| facts.scopeDigest !== binding.scopeDigest || facts.assessmentPolicyDigest !== binding.assessmentPolicyDigest)
|
|
36
|
+
throw new Error("adoption expired, ineligible or outside verified scope");
|
|
37
|
+
const body = { version: "adoption-receipt-v1", bindingId: binding.id, authorityId: authority.id, hypothesisDigest: binding.hypothesisDigest,
|
|
38
|
+
experimentDigest: binding.experimentDigest, candidateDigest: binding.candidateDigest, scopeDigest: binding.scopeDigest,
|
|
39
|
+
assessmentPolicyDigest: binding.assessmentPolicyDigest, rollbackCandidateDigest: binding.rollbackCandidateDigest,
|
|
40
|
+
activationBoundary: "next-orders", grantExpansion: false, expiresAt: binding.expiresAt };
|
|
41
|
+
return Object.freeze({ ...body, id: hash(body) });
|
|
42
|
+
}
|
|
43
|
+
export function validateAdoptionReceipt(receipt, binding, authority, facts, now) {
|
|
44
|
+
if (authorizeAdoption(binding, authority, facts, now).id !== receipt.id)
|
|
45
|
+
throw new Error("adoption receipt does not match current independent authority/facts");
|
|
46
|
+
receiptValid(receipt);
|
|
47
|
+
}
|
|
48
|
+
function receiptValid(receipt) {
|
|
49
|
+
const { id, ...body } = receipt;
|
|
50
|
+
if (!closed(receipt, ["version", "bindingId", "authorityId", "hypothesisDigest", "experimentDigest", "candidateDigest", "scopeDigest", "assessmentPolicyDigest", "rollbackCandidateDigest", "activationBoundary", "grantExpansion", "expiresAt", "id"])
|
|
51
|
+
|| body.version !== "adoption-receipt-v1" || !text(body.authorityId) || body.activationBoundary !== "next-orders" || body.grantExpansion !== false || hash(body) !== id)
|
|
52
|
+
throw new Error("adoption receipt changed");
|
|
53
|
+
const binding = buildAdoptionBinding({ hypothesisDigest: body.hypothesisDigest, experimentDigest: body.experimentDigest, candidateDigest: body.candidateDigest,
|
|
54
|
+
scopeDigest: body.scopeDigest, assessmentPolicyDigest: body.assessmentPolicyDigest, rollbackCandidateDigest: body.rollbackCandidateDigest, activationBoundary: body.activationBoundary, expiresAt: body.expiresAt });
|
|
55
|
+
if (binding.id !== body.bindingId)
|
|
56
|
+
throw new Error("adoption receipt binding mismatch");
|
|
57
|
+
}
|
|
58
|
+
export function classifyProductionObservation(adoption, input) {
|
|
59
|
+
receiptValid(adoption);
|
|
60
|
+
if (!input || !text(input.id) || input.evidence.length > 256 || input.evidence.some(h => !SHA.test(h))
|
|
61
|
+
|| ![input.originalRequirementDigest, input.currentRequirementDigest, input.candidateDigest, input.scopeDigest, input.observedArtifactDigest].every(h => typeof h === "string" && SHA.test(h))
|
|
62
|
+
|| ![input.acceptanceDigest, input.acceptedArtifactDigest].every(h => h === null || (typeof h === "string" && SHA.test(h)))
|
|
63
|
+
|| (input.acceptanceDigest === null) !== (input.acceptedArtifactDigest === null)
|
|
64
|
+
|| !["confirmed-defect", "success", "unknown"].includes(input.outcome))
|
|
65
|
+
throw new Error("invalid production observation");
|
|
66
|
+
const state = input.adoptionId !== adoption.id || input.candidateDigest !== adoption.candidateDigest || input.scopeDigest !== adoption.scopeDigest ? "out-of-scope"
|
|
67
|
+
: input.originalRequirementDigest !== input.currentRequirementDigest ? "changed-requirements"
|
|
68
|
+
: input.acceptedArtifactDigest !== null && input.acceptedArtifactDigest !== input.observedArtifactDigest ? "changed-artifact"
|
|
69
|
+
: input.outcome === "confirmed-defect" && input.evidence.length ? input.acceptanceDigest === null ? "caught-defect" : "escape" : input.outcome === "success" ? "success" : "unknown";
|
|
70
|
+
return Object.freeze({ state, adoptionId: adoption.id, hypothesisDigest: adoption.hypothesisDigest, experimentDigest: adoption.experimentDigest, observationId: input.id, acceptanceDigest: input.acceptanceDigest, acceptedArtifactDigest: input.acceptedArtifactDigest, observedArtifactDigest: input.observedArtifactDigest, automaticRollback: false });
|
|
71
|
+
}
|
|
72
|
+
export function buildRollbackRequest(adoption, reason, evidence, expiresAt = adoption.expiresAt) {
|
|
73
|
+
receiptValid(adoption);
|
|
74
|
+
if (!Number.isSafeInteger(expiresAt) || expiresAt < 0 || !["operator-request", "confirmed-escape"].includes(reason) || !evidence.length || evidence.length > 256 || evidence.some(h => !SHA.test(h)))
|
|
75
|
+
throw new Error("invalid rollback request");
|
|
76
|
+
const body = { version: "rollback-request-v1", adoptionId: adoption.id, scopeDigest: adoption.scopeDigest, restoreCandidateDigest: adoption.rollbackCandidateDigest, reason, expiresAt, evidence: Object.freeze([...new Set(evidence)].sort()) };
|
|
77
|
+
return Object.freeze({ ...body, id: hash(body) });
|
|
78
|
+
}
|
|
79
|
+
/** This validates a recorded choice, not an automatic rollback action or a caller-created authority flag. */
|
|
80
|
+
export function authorizeRollback(adoption, request, authority, now, current) {
|
|
81
|
+
receiptValid(adoption);
|
|
82
|
+
const { id, ...body } = request;
|
|
83
|
+
if (!authorityValid(authority) || authority.id !== adoption.authorityId || !authority.rollbacks.includes(id))
|
|
84
|
+
throw new Error("independent rollback authority required");
|
|
85
|
+
if (hash(body) !== id || request.version !== "rollback-request-v1" || request.adoptionId !== adoption.id || request.scopeDigest !== adoption.scopeDigest
|
|
86
|
+
|| request.restoreCandidateDigest !== adoption.rollbackCandidateDigest || !Number.isSafeInteger(now) || now < 0 || !Number.isSafeInteger(request.expiresAt) || now >= request.expiresAt
|
|
87
|
+
|| !current || current.adoptionId !== adoption.id || current.candidateDigest !== adoption.candidateDigest || current.scopeDigest !== adoption.scopeDigest)
|
|
88
|
+
throw new Error("rollback binding changed or expired");
|
|
89
|
+
return Object.freeze({ requestId: id, adoptionId: adoption.id, restoreCandidateDigest: request.restoreCandidateDigest, scopeDigest: adoption.scopeDigest, grantExpansion: false, application: "not-performed" });
|
|
90
|
+
}
|
|
91
|
+
export function summarizeLeanOutcomes(population, rows) {
|
|
92
|
+
if (!text(population) || rows.length > 10000 || rows.some(r => r.population !== population || typeof r.complete !== "boolean" || typeof r.requirementsChanged !== "boolean"
|
|
93
|
+
|| ![true, false, null].includes(r.accepted) || !["confirmed", "none-observed", "unknown"].includes(r.escape)
|
|
94
|
+
|| [r.repairAttempts, r.decisionWaitMs, r.questions].some(n => n !== null && (!Number.isSafeInteger(n) || n < 0))))
|
|
95
|
+
throw new Error("invalid or incomparable lean population");
|
|
96
|
+
const complete = rows.filter(r => r.complete && r.accepted !== null && r.repairAttempts !== null);
|
|
97
|
+
const observed = (field) => ({ known: rows.reduce((n, r) => n + (r[field] ?? 0), 0), observed: rows.filter(r => r[field] !== null).length, total: rows.length });
|
|
98
|
+
return { population, firstPassAccepted: complete.filter(r => r.accepted && r.repairAttempts === 0).length, firstPassDenominator: complete.length,
|
|
99
|
+
incomplete: rows.length - complete.length, confirmedEscapes: rows.filter(r => r.escape === "confirmed" && !r.requirementsChanged).length,
|
|
100
|
+
changedRequirements: rows.filter(r => r.requirementsChanged).length, recovery: observed("repairAttempts"), decisionWait: observed("decisionWaitMs"), questions: observed("questions"), universalScore: null };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=adoption.js.map
|
package/dist/arms.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface Arm {
|
|
|
18
18
|
seedSkills: string[];
|
|
19
19
|
/** Minimum definitions the seeding must produce, else the run ERRORs. */
|
|
20
20
|
requireDefinitions: number;
|
|
21
|
-
/** Env for the subject process. `<run-dir>`
|
|
21
|
+
/** Env for the subject process. `<run-dir>` and `<workspace>` are substituted per rep. */
|
|
22
22
|
env: Record<string, string>;
|
|
23
23
|
}
|
|
24
24
|
/** The implicit control: today's behaviour, byte-identical, no extensions. */
|
|
@@ -83,12 +83,14 @@ export interface TraceResultMeta {
|
|
|
83
83
|
* conversation carries in the session dir, not in the event stream). This
|
|
84
84
|
* mirrors the loop the adapter already runs.
|
|
85
85
|
*/
|
|
86
|
+
export type CostSource = "provider-reported" | "subscription" | "unreported";
|
|
86
87
|
export interface TraceMetrics {
|
|
87
88
|
input_tokens: number;
|
|
88
89
|
output_tokens: number;
|
|
89
90
|
cache_read_tokens: number;
|
|
90
91
|
cache_write_tokens: number;
|
|
91
92
|
cost_usd: number;
|
|
93
|
+
cost_source: CostSource;
|
|
92
94
|
tool_calls: number;
|
|
93
95
|
delegated_children: number;
|
|
94
96
|
/** Maximum simultaneously outstanding tool calls observed in the pi stream. */
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI commands guaranteed to make no subject or judge calls.
|
|
3
|
+
*
|
|
4
|
+
* Keep this list narrow: commands that can conditionally spend (notably `regate`)
|
|
5
|
+
* do not belong here even when their common path only replays saved artifacts.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FREE_OFFLINE_COMMANDS: readonly ["affected", "archive", "coverage", "init", "judge-agreement", "lint", "list", "rescore", "restamp", "screen", "stability"];
|
|
8
|
+
export type FreeOfflineCommand = typeof FREE_OFFLINE_COMMANDS[number];
|
|
9
|
+
export declare function isFreeOfflineCommand(command: string): command is FreeOfflineCommand;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI commands guaranteed to make no subject or judge calls.
|
|
3
|
+
*
|
|
4
|
+
* Keep this list narrow: commands that can conditionally spend (notably `regate`)
|
|
5
|
+
* do not belong here even when their common path only replays saved artifacts.
|
|
6
|
+
*/
|
|
7
|
+
export const FREE_OFFLINE_COMMANDS = [
|
|
8
|
+
"affected",
|
|
9
|
+
"archive",
|
|
10
|
+
"coverage",
|
|
11
|
+
"init",
|
|
12
|
+
"judge-agreement",
|
|
13
|
+
"lint",
|
|
14
|
+
"list",
|
|
15
|
+
"rescore",
|
|
16
|
+
"restamp",
|
|
17
|
+
"screen",
|
|
18
|
+
"stability",
|
|
19
|
+
];
|
|
20
|
+
export function isFreeOfflineCommand(command) {
|
|
21
|
+
return FREE_OFFLINE_COMMANDS.some((candidate) => candidate === command);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=command-cost.js.map
|
package/dist/comparison.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export interface AggregateMetrics {
|
|
|
45
45
|
cache_read_tokens: number | null;
|
|
46
46
|
cache_write_tokens: number | null;
|
|
47
47
|
subject_cost_usd: number | null;
|
|
48
|
+
cost_source: "provider-reported" | "subscription" | "unreported" | null;
|
|
48
49
|
tool_calls: number | null;
|
|
49
50
|
delegated_children: number | null;
|
|
50
51
|
max_concurrency: number | null;
|
package/dist/comparison.js
CHANGED
|
@@ -51,8 +51,8 @@ export function buildComparison(input) {
|
|
|
51
51
|
const behavioral = cells.filter((cell) => cell.change === "regression").map((cell) => cell.id);
|
|
52
52
|
const criticalRegressions = cells.filter((cell) => cell.change === "regression" && cell.critical).map((cell) => cell.id);
|
|
53
53
|
const byReference = new Map(reference.scenarios.map((scenario) => [scenario.id, scenario]));
|
|
54
|
-
const infrastructureErrors = cells.filter((cell) => cell.reference === "ERROR" || cell.reference === "JUDGE-AMBIGUOUS" || byReference.get(cell.id)?.suspect ||
|
|
55
|
-
cell.candidate === "ERROR" || cell.candidate === "JUDGE-AMBIGUOUS" || byCandidate.get(cell.id)?.suspect).map((cell) => cell.id);
|
|
54
|
+
const infrastructureErrors = cells.filter((cell) => cell.reference === "ERROR" || cell.reference === "NOT-MEASURED" || cell.reference === "JUDGE-AMBIGUOUS" || byReference.get(cell.id)?.suspect ||
|
|
55
|
+
cell.candidate === "ERROR" || cell.candidate === "NOT-MEASURED" || cell.candidate === "JUDGE-AMBIGUOUS" || byCandidate.get(cell.id)?.suspect).map((cell) => cell.id);
|
|
56
56
|
const metrics = { reference: aggregateMetrics(reference.scenarios), candidate: aggregateMetrics(candidate.scenarios) };
|
|
57
57
|
const costRegressions = thresholdFindings(metrics.reference, metrics.candidate, input.thresholds);
|
|
58
58
|
const partial = input.partial === true || reference.partial === true || candidate.partial === true;
|
|
@@ -137,6 +137,10 @@ export function aggregateMetrics(scenarios) {
|
|
|
137
137
|
cache_read_tokens: sumOptional("cache_read_tokens"),
|
|
138
138
|
cache_write_tokens: sumOptional("cache_write_tokens"),
|
|
139
139
|
subject_cost_usd: sumOptional("subject_cost_usd"),
|
|
140
|
+
cost_source: (() => {
|
|
141
|
+
const sources = scenarios.map((scenario) => scenario.metrics?.cost_source).filter((source) => source !== undefined);
|
|
142
|
+
return sources.length === 0 ? null : sources.every((source) => source === sources[0]) ? sources[0] : "unreported";
|
|
143
|
+
})(),
|
|
140
144
|
tool_calls: sumOptional("tool_calls"),
|
|
141
145
|
delegated_children: sumOptional("delegated_children"),
|
|
142
146
|
max_concurrency: maxValues.length ? Math.max(...maxValues) : null,
|
|
@@ -147,11 +151,13 @@ function effective(result) {
|
|
|
147
151
|
return result.override;
|
|
148
152
|
if (result.objective?.status === "ERROR")
|
|
149
153
|
return "ERROR";
|
|
154
|
+
if (result.objective?.status === "NOT-MEASURED")
|
|
155
|
+
return "NOT-MEASURED";
|
|
150
156
|
if (result.objective?.status === "FAIL")
|
|
151
157
|
return "FAIL";
|
|
152
158
|
return result.judge_verdict;
|
|
153
159
|
}
|
|
154
|
-
function untrustworthy(result, verdict) { return result.suspect || verdict === "ERROR" || verdict === "JUDGE-AMBIGUOUS"; }
|
|
160
|
+
function untrustworthy(result, verdict) { return result.suspect || verdict === "ERROR" || verdict === "NOT-MEASURED" || verdict === "JUDGE-AMBIGUOUS"; }
|
|
155
161
|
function sorted(value) { return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b))); }
|
|
156
162
|
function increase(reference, candidate) { return reference === null || candidate === null || reference <= 0 ? null : (candidate - reference) / reference; }
|
|
157
163
|
function thresholdFindings(reference, candidate, thresholds) {
|
package/dist/execution-trace.js
CHANGED
|
@@ -157,12 +157,19 @@ export function parseTrace(lines, meta) {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
const toolCalls = [...calls.values()].sort((a, b) => a.issueIndex - b.issueIndex);
|
|
160
|
+
const subscription = meta.subject.provider === "openai-codex" || meta.subject.provider === "claude-code";
|
|
161
|
+
const costSource = subscription
|
|
162
|
+
? "subscription"
|
|
163
|
+
: cost !== null
|
|
164
|
+
? "provider-reported"
|
|
165
|
+
: "unreported";
|
|
160
166
|
const metrics = {
|
|
161
167
|
input_tokens: inputTokens,
|
|
162
168
|
output_tokens: outputTokens,
|
|
163
169
|
cache_read_tokens: cacheReadTokens,
|
|
164
170
|
cache_write_tokens: cacheWriteTokens,
|
|
165
171
|
cost_usd: cost ?? 0,
|
|
172
|
+
cost_source: costSource,
|
|
166
173
|
tool_calls: toolCalls.length,
|
|
167
174
|
delegated_children: toolCalls
|
|
168
175
|
.filter((call) => call.name === "Agent")
|
|
@@ -348,12 +355,13 @@ export function mergeTraces(traces) {
|
|
|
348
355
|
cache_read_tokens: sum.cache_read_tokens + trace.metrics.cache_read_tokens,
|
|
349
356
|
cache_write_tokens: sum.cache_write_tokens + trace.metrics.cache_write_tokens,
|
|
350
357
|
cost_usd: sum.cost_usd + trace.metrics.cost_usd,
|
|
358
|
+
cost_source: sum.cost_source === trace.metrics.cost_source ? sum.cost_source : "unreported",
|
|
351
359
|
tool_calls: sum.tool_calls + trace.metrics.tool_calls,
|
|
352
360
|
delegated_children: sum.delegated_children + trace.metrics.delegated_children,
|
|
353
361
|
max_concurrency: Math.max(sum.max_concurrency, trace.metrics.max_concurrency),
|
|
354
362
|
}), {
|
|
355
363
|
input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_write_tokens: 0,
|
|
356
|
-
cost_usd: 0, tool_calls: 0, delegated_children: 0, max_concurrency: 0,
|
|
364
|
+
cost_usd: 0, cost_source: traces[0].metrics.cost_source, tool_calls: 0, delegated_children: 0, max_concurrency: 0,
|
|
357
365
|
})
|
|
358
366
|
: undefined;
|
|
359
367
|
const last = traces[traces.length - 1];
|