@wix/pathgrade 1.0.38 → 1.0.40
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 +5 -5
- package/dist/adapters/jest/results.d.ts +1 -0
- package/dist/adapters/jest/results.js +36 -14
- package/dist/adapters/jest/runner-adapter.d.ts +1 -0
- package/dist/adapters/jest/runner-adapter.js +5 -1
- package/dist/adapters/node-test/runner-adapter.js +6 -5
- package/dist/affected/meta.d.ts +4 -1
- package/dist/affected/meta.js +9 -5
- package/dist/affected/select.js +1 -1
- package/dist/agents/claude/sdk-message-projector.js +4 -5
- package/dist/agents/claude/tool-permission-bridge.js +2 -1
- package/dist/agents/codex-app-server/item-projection.js +2 -2
- package/dist/agents/codex-app-server/mcp-approval-correlator.js +3 -2
- package/dist/agents/opencode.js +4 -5
- package/dist/commands/report.js +5 -26
- package/dist/internal/direct-mcp-v2/acp-author-projector.js +20 -7
- package/dist/reporters/cli.js +20 -1
- package/dist/reporters/github-comment.js +18 -7
- package/dist/reporters/loader.d.ts +4 -0
- package/dist/reporters/loader.js +26 -9
- package/dist/reporting/comparison-contract.d.ts +3 -2
- package/dist/reporting/comparison-contract.js +66 -33
- package/dist/reporting/core.js +35 -8
- package/dist/reporting/reliability-contract.d.ts +1 -0
- package/dist/reporting/report-parser.js +125 -5
- package/dist/reporting/source-metadata.d.ts +3 -0
- package/dist/reporting/source-metadata.js +67 -2
- package/dist/reporting/types.d.ts +5 -1
- package/dist/runners/model-builders.d.ts +1 -0
- package/dist/runners/model-builders.js +36 -12
- package/dist/runners/model.d.ts +2 -0
- package/dist/runners/orchestrator.js +3 -4
- package/dist/runners/repeated-invocation.js +2 -5
- package/dist/runners/report-projection.js +84 -0
- package/dist/runners/vitest-adapter.js +6 -2
- package/dist/sdk/agent.js +10 -1
- package/dist/sdk/evaluate.js +58 -3
- package/dist/sdk/judge-prompt-builder.js +11 -7
- package/dist/sdk/mcp-event-input.d.ts +1 -0
- package/dist/sdk/mcp-event-input.js +3 -0
- package/dist/sdk/mcp-evidence.js +16 -3
- package/dist/sdk/mcp-safety.js +2 -2
- package/dist/sdk/scorers.d.ts +5 -0
- package/dist/sdk/scorers.js +4 -0
- package/dist/sdk/scripted-mcp-events.js +3 -2
- package/dist/sdk/tool-event-log.js +18 -3
- package/dist/sdk/tool-event-secrets.d.ts +4 -0
- package/dist/sdk/tool-event-secrets.js +33 -2
- package/dist/sdk/types.d.ts +2 -0
- package/dist/tool-event-results.d.ts +3 -0
- package/dist/tool-event-results.js +153 -23
- package/dist/types.d.ts +14 -7
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createSourceMetadataResolver } from '../reporting/source-metadata.js';
|
|
2
2
|
export function buildNormalizedRunSnapshotFromReportGroups(run, groups, options = {}) {
|
|
3
3
|
const cwd = options.cwd ?? process.cwd();
|
|
4
|
+
const resolveSourceMetadata = createSourceMetadataResolver(cwd);
|
|
4
5
|
return {
|
|
5
6
|
version: 1,
|
|
6
7
|
completeness: 'final',
|
|
@@ -14,18 +15,41 @@ export function buildNormalizedRunSnapshotFromReportGroups(run, groups, options
|
|
|
14
15
|
message: diagnostic.message,
|
|
15
16
|
})) } : {}),
|
|
16
17
|
},
|
|
17
|
-
units:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
units: [
|
|
19
|
+
...groups.map((group, index) => {
|
|
20
|
+
const source = group.sourceFile && group.sourceRevision
|
|
21
|
+
? {
|
|
22
|
+
sourceFile: group.sourceFile,
|
|
23
|
+
sourceRevision: group.sourceRevision,
|
|
24
|
+
...(group.comparisonInputs ? { comparisonInputs: group.comparisonInputs } : {}),
|
|
25
|
+
}
|
|
26
|
+
: resolveSourceMetadata(group.sourceFile ?? group.cases.map(testCase => testCase.filePath ?? testCase.sourceRef).find(Boolean));
|
|
27
|
+
return {
|
|
28
|
+
id: unitId(group.groupName, index),
|
|
29
|
+
runId: `${run.adapterName}:run`,
|
|
30
|
+
displayName: group.groupName,
|
|
31
|
+
collection: run.status === 'completed' || run.status === 'failed'
|
|
32
|
+
? { state: 'complete' }
|
|
33
|
+
: { state: 'incomplete', reason: 'run-incomplete' },
|
|
34
|
+
...source,
|
|
35
|
+
groupingHints: [{ kind: 'suite', key: group.groupName, label: group.groupName, order: index }],
|
|
36
|
+
};
|
|
37
|
+
}),
|
|
38
|
+
...(options.discoveredFiles ?? [])
|
|
39
|
+
.filter(file => !groups.some(group => group.sourceFile === file
|
|
40
|
+
|| group.cases.some(testCase => testCase.filePath === file || testCase.sourceRef === file)))
|
|
41
|
+
.map((file, index) => ({
|
|
42
|
+
id: unitId(file, groups.length + index),
|
|
23
43
|
runId: `${run.adapterName}:run`,
|
|
24
|
-
displayName:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
displayName: file,
|
|
45
|
+
collection: {
|
|
46
|
+
state: 'incomplete',
|
|
47
|
+
reason: 'adapter-cannot-prove-completeness',
|
|
48
|
+
},
|
|
49
|
+
...resolveSourceMetadata(file),
|
|
50
|
+
groupingHints: [{ kind: 'source', key: file, label: file, order: groups.length + index }],
|
|
51
|
+
})),
|
|
52
|
+
],
|
|
29
53
|
cases: groups.flatMap((group, groupIndex) => group.cases.map((testCase, caseIndex) => {
|
|
30
54
|
const caseId = testCase.caseId ?? `${unitId(group.groupName, groupIndex)}:case-${caseIndex + 1}`;
|
|
31
55
|
const attemptId = `${caseId}:attempt-1`;
|
package/dist/runners/model.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DiagnosticsReport } from '../sdk/diagnostics.js';
|
|
2
2
|
import type { TrialResult } from '../types.js';
|
|
3
3
|
import type { CollectionIncompleteReason } from '../reporting/reliability-contract.js';
|
|
4
|
+
import type { ComparisonInputsState } from '../reporting/reliability-contract.js';
|
|
4
5
|
export interface NormalizedRunSnapshot {
|
|
5
6
|
version: 1;
|
|
6
7
|
completeness: SnapshotCompleteness;
|
|
@@ -29,6 +30,7 @@ export interface EvalUnitRecord {
|
|
|
29
30
|
collection?: EvalUnitCollection;
|
|
30
31
|
sourceFile?: string;
|
|
31
32
|
sourceRevision?: string;
|
|
33
|
+
comparisonInputs?: ComparisonInputsState;
|
|
32
34
|
diagnostics?: Diagnostic[];
|
|
33
35
|
nativeReferences?: NativeReference[];
|
|
34
36
|
groupingHints?: GroupingHint[];
|
|
@@ -43,8 +43,8 @@ export async function runWithAdapter(input) {
|
|
|
43
43
|
if (loadedSelection) {
|
|
44
44
|
built = buildPathgradeReport({
|
|
45
45
|
threshold: options.threshold,
|
|
46
|
+
...reportInput,
|
|
46
47
|
selection: loadedSelection,
|
|
47
|
-
groups: reportInput.groups,
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
const mode = options.reporterMode ?? 'cli';
|
|
@@ -56,14 +56,13 @@ export async function runWithAdapter(input) {
|
|
|
56
56
|
if (mode === 'browser') {
|
|
57
57
|
await options.openBrowser?.();
|
|
58
58
|
}
|
|
59
|
-
if (options.threshold != null && built.report.
|
|
59
|
+
if (options.threshold != null && built.report.threshold_status === 'fail') {
|
|
60
60
|
options.onThresholdFailure?.({
|
|
61
61
|
overallPassRate: built.report.overall_pass_rate,
|
|
62
62
|
threshold: options.threshold,
|
|
63
63
|
});
|
|
64
|
-
return runExitCode === 0 ? 1 : runExitCode;
|
|
65
64
|
}
|
|
66
|
-
return runExitCode;
|
|
65
|
+
return built.report.status === 'fail' && runExitCode === 0 ? 1 : runExitCode;
|
|
67
66
|
}
|
|
68
67
|
finally {
|
|
69
68
|
await lifecycle.cleanupRun();
|
|
@@ -80,13 +80,10 @@ export function withRepeatedAttempts(input) {
|
|
|
80
80
|
if (input.config.reporter === 'browser') {
|
|
81
81
|
await (input.openBrowser ?? openBrowserViewer)();
|
|
82
82
|
}
|
|
83
|
-
if (input.config.ci.threshold != null && built.report.
|
|
83
|
+
if (input.config.ci.threshold != null && built.report.threshold_status === 'fail') {
|
|
84
84
|
process.stdout.write(`\n CI THRESHOLD FAILED avg score ${built.report.overall_pass_rate.toFixed(3)} < threshold ${input.config.ci.threshold}\n\n`);
|
|
85
|
-
resultCode = firstNonzeroExit || 1;
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
resultCode = firstNonzeroExit;
|
|
89
85
|
}
|
|
86
|
+
resultCode = firstNonzeroExit || (built.report.status === 'fail' ? 1 : 0);
|
|
90
87
|
}
|
|
91
88
|
}
|
|
92
89
|
catch (error) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TASK_INVENTORY_VERSION } from '../reporting/reliability-contract.js';
|
|
1
2
|
import { validateNormalizedRunSnapshot } from './model-validation.js';
|
|
2
3
|
export function projectNormalizedRunSnapshotToReportInput(snapshot, options = {}) {
|
|
3
4
|
const validation = validateNormalizedRunSnapshot(snapshot, { completeness: 'final' });
|
|
@@ -20,6 +21,7 @@ export function projectNormalizedRunSnapshotToReportInput(snapshot, options = {}
|
|
|
20
21
|
groupName: key,
|
|
21
22
|
...(unit?.sourceFile ? { sourceFile: unit.sourceFile } : {}),
|
|
22
23
|
...(unit?.sourceRevision ? { sourceRevision: unit.sourceRevision } : {}),
|
|
24
|
+
...(unit?.comparisonInputs ? { comparisonInputs: unit.comparisonInputs } : {}),
|
|
23
25
|
cases: [],
|
|
24
26
|
};
|
|
25
27
|
group.cases.push({
|
|
@@ -41,11 +43,93 @@ export function projectNormalizedRunSnapshotToReportInput(snapshot, options = {}
|
|
|
41
43
|
}
|
|
42
44
|
return {
|
|
43
45
|
...(options.selection ? { selection: options.selection } : {}),
|
|
46
|
+
runStatus: snapshot.model.run.status,
|
|
44
47
|
attemptsRequested: snapshot.model.run.attemptsRequested ?? 1,
|
|
45
48
|
attemptsCompleted: snapshot.model.run.attemptsCompleted ?? 1,
|
|
49
|
+
taskInventory: buildTaskInventory(snapshot, [...groups.values()]),
|
|
46
50
|
groups: [...groups.values()],
|
|
47
51
|
};
|
|
48
52
|
}
|
|
53
|
+
function buildTaskInventory(snapshot, groups) {
|
|
54
|
+
const unitsBySource = new Map(snapshot.model.units
|
|
55
|
+
.filter(unit => unit.sourceFile)
|
|
56
|
+
.map(unit => [unit.sourceFile, unit]));
|
|
57
|
+
const files = new Map();
|
|
58
|
+
for (const unit of snapshot.model.units) {
|
|
59
|
+
if (!unit.sourceFile)
|
|
60
|
+
continue;
|
|
61
|
+
const collection = unit.collection ?? {
|
|
62
|
+
state: 'incomplete',
|
|
63
|
+
reason: 'adapter-cannot-prove-completeness',
|
|
64
|
+
};
|
|
65
|
+
const existing = files.get(unit.sourceFile);
|
|
66
|
+
if (!existing) {
|
|
67
|
+
files.set(unit.sourceFile, {
|
|
68
|
+
tasks: [],
|
|
69
|
+
completeness: collection.state,
|
|
70
|
+
...(collection.state === 'incomplete' ? { reason: collection.reason } : {}),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (collection.state === 'incomplete') {
|
|
74
|
+
existing.completeness = 'incomplete';
|
|
75
|
+
existing.reason = collection.reason;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const group of groups) {
|
|
79
|
+
if (!group.sourceFile)
|
|
80
|
+
continue;
|
|
81
|
+
const unit = unitsBySource.get(group.sourceFile);
|
|
82
|
+
const collection = unit?.collection ?? {
|
|
83
|
+
state: 'incomplete',
|
|
84
|
+
reason: 'adapter-cannot-prove-completeness',
|
|
85
|
+
};
|
|
86
|
+
const file = files.get(group.sourceFile) ?? {
|
|
87
|
+
tasks: [],
|
|
88
|
+
completeness: collection.state,
|
|
89
|
+
...(collection.state === 'incomplete' ? { reason: collection.reason } : {}),
|
|
90
|
+
};
|
|
91
|
+
file.tasks.push(taskInventoryEntry(group));
|
|
92
|
+
if (collection.state === 'incomplete') {
|
|
93
|
+
file.completeness = 'incomplete';
|
|
94
|
+
file.reason = collection.reason;
|
|
95
|
+
}
|
|
96
|
+
files.set(group.sourceFile, file);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
version: TASK_INVENTORY_VERSION,
|
|
100
|
+
files: [...files.entries()].toSorted(([left], [right]) => left.localeCompare(right)).map(([eval_file, file]) => ({
|
|
101
|
+
eval_file,
|
|
102
|
+
tasks: file.tasks.toSorted((left, right) => left.task_key.localeCompare(right.task_key)),
|
|
103
|
+
...(file.completeness === 'complete'
|
|
104
|
+
? { completeness: 'complete' }
|
|
105
|
+
: { completeness: 'incomplete', reason: file.reason ?? 'adapter-cannot-prove-completeness' }),
|
|
106
|
+
})),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function taskInventoryEntry(group) {
|
|
110
|
+
const states = group.cases.map(testCase => testCase.state);
|
|
111
|
+
if (states.every(state => state === 'skipped')) {
|
|
112
|
+
return { task_key: group.groupName, state: 'not-scored', reason: 'skipped' };
|
|
113
|
+
}
|
|
114
|
+
if (states.every(state => state === 'pending')) {
|
|
115
|
+
return { task_key: group.groupName, state: 'not-scored', reason: 'pending' };
|
|
116
|
+
}
|
|
117
|
+
if (group.cases.some(testCase => testCase.attempts?.some(attempt => attempt.outcome.kind === 'timed-out'))) {
|
|
118
|
+
return { task_key: group.groupName, state: 'not-scored', reason: 'timed-out' };
|
|
119
|
+
}
|
|
120
|
+
if (group.cases.some(isPromotableCase))
|
|
121
|
+
return { task_key: group.groupName, state: 'scored' };
|
|
122
|
+
return { task_key: group.groupName, state: 'not-scored', reason: 'failed-before-evaluation' };
|
|
123
|
+
}
|
|
124
|
+
function isPromotableCase(testCase) {
|
|
125
|
+
if (testCase.state === 'skipped' || testCase.state === 'pending')
|
|
126
|
+
return false;
|
|
127
|
+
const evaluations = testCase.attempts?.flatMap(attempt => attempt.evaluations ?? []) ?? testCase.evaluations;
|
|
128
|
+
if (evaluations === undefined)
|
|
129
|
+
return true;
|
|
130
|
+
return evaluations.some(evaluation => evaluation.score !== undefined
|
|
131
|
+
&& evaluation.resultKind !== 'synthetic_no_evaluation');
|
|
132
|
+
}
|
|
49
133
|
function totalDurationMs(runCase) {
|
|
50
134
|
return runCase.attempts.reduce((sum, attempt) => sum + (attempt.durationMs ?? 0), 0);
|
|
51
135
|
}
|
|
@@ -31,11 +31,15 @@ export function createVitestAdapter(options = {}) {
|
|
|
31
31
|
native: {
|
|
32
32
|
testModules: options.testModules ?? [],
|
|
33
33
|
cwd: discoveryCwd,
|
|
34
|
+
discoveredFiles: input.discovered.units.flatMap(unit => unit.sourceRef ? [unit.sourceRef] : []),
|
|
34
35
|
},
|
|
35
36
|
};
|
|
36
37
|
},
|
|
37
38
|
async collectNormalizedRunSnapshot(run) {
|
|
38
|
-
return buildNormalizedRunSnapshotFromReportGroups(run, collectVitestReportGroups(readVitestRunNative(run).testModules), {
|
|
39
|
+
return buildNormalizedRunSnapshotFromReportGroups(run, collectVitestReportGroups(readVitestRunNative(run).testModules), {
|
|
40
|
+
cwd: readVitestRunNative(run).cwd,
|
|
41
|
+
discoveredFiles: readVitestRunNative(run).discoveredFiles,
|
|
42
|
+
});
|
|
39
43
|
},
|
|
40
44
|
};
|
|
41
45
|
}
|
|
@@ -59,7 +63,7 @@ export function collectVitestReportGroups(testModules) {
|
|
|
59
63
|
function readVitestRunNative(run) {
|
|
60
64
|
if (isVitestRunNative(run.native))
|
|
61
65
|
return run.native;
|
|
62
|
-
return { testModules: [], cwd: process.cwd() };
|
|
66
|
+
return { testModules: [], cwd: process.cwd(), discoveredFiles: [] };
|
|
63
67
|
}
|
|
64
68
|
function isVitestRunNative(native) {
|
|
65
69
|
return typeof native === 'object'
|
package/dist/sdk/agent.js
CHANGED
|
@@ -20,6 +20,8 @@ import { collectOpenCodeMcpToolNames } from '../agents/opencode/contract.js';
|
|
|
20
20
|
import { collectSensitiveEnvValues } from '../tool-event-results.js';
|
|
21
21
|
import { createAskUserHandler } from './ask-bus/handler.js';
|
|
22
22
|
import { resolveAgentRuntimeOptions } from './agent-runtime-options.js';
|
|
23
|
+
import { extractToolEventsFromLog } from '../tool-events.js';
|
|
24
|
+
import { clearToolEventRuntimeMetadata } from './tool-event-secrets.js';
|
|
23
25
|
/**
|
|
24
26
|
* Test-only injection point: override the sink used by the next emitter
|
|
25
27
|
* built inside `createAgent`. Pass `null` to restore the default (stderr).
|
|
@@ -392,7 +394,14 @@ class AgentImpl {
|
|
|
392
394
|
}
|
|
393
395
|
}
|
|
394
396
|
finally {
|
|
395
|
-
|
|
397
|
+
try {
|
|
398
|
+
await this.ws.dispose();
|
|
399
|
+
}
|
|
400
|
+
finally {
|
|
401
|
+
for (const event of extractToolEventsFromLog(this._log)) {
|
|
402
|
+
clearToolEventRuntimeMetadata(event);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
396
405
|
}
|
|
397
406
|
}
|
|
398
407
|
}
|
package/dist/sdk/evaluate.js
CHANGED
|
@@ -7,6 +7,9 @@ import { runScorer } from './run-scorer.js';
|
|
|
7
7
|
import { createLLMClient } from '../utils/llm.js';
|
|
8
8
|
import { sandboxExec } from '../providers/sandbox-exec.js';
|
|
9
9
|
import { buildTranscript, loadRunSnapshot, WorkspaceMissingError } from './snapshots.js';
|
|
10
|
+
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
11
|
+
import { getOriginalMcpInput } from './mcp-event-input.js';
|
|
12
|
+
import { cloneToolEventWithRuntimeMetadata, collectToolEventSensitiveValues, } from './tool-event-secrets.js';
|
|
10
13
|
import fs from 'fs-extra';
|
|
11
14
|
import path from 'path';
|
|
12
15
|
import { summarizeFlow } from './agent-flow.js';
|
|
@@ -154,6 +157,9 @@ export function evaluateStepScorers(agent, scorers, opts) {
|
|
|
154
157
|
return evaluateAgent(agent, scorers, opts, false);
|
|
155
158
|
}
|
|
156
159
|
async function fromSnapshot(snapshotPath, scorers, opts) {
|
|
160
|
+
if (opts?.deterministicToolEvidence === 'live') {
|
|
161
|
+
throw new TypeError("evaluate.fromSnapshot() does not support deterministicToolEvidence: 'live'");
|
|
162
|
+
}
|
|
157
163
|
const snapshot = await loadRunSnapshot(snapshotPath);
|
|
158
164
|
const trackedLLM = opts?.llm ?? createLLMClient({ adapters: [{
|
|
159
165
|
name: 'runtime', isAvailable: async () => true,
|
|
@@ -201,6 +207,9 @@ async function evaluateWithContext(ctx, scorers, opts) {
|
|
|
201
207
|
call: (prompt, callOpts) => getRuntime().llm.call(prompt, callOpts),
|
|
202
208
|
}] });
|
|
203
209
|
const onScorerError = opts?.onScorerError ?? 'skip';
|
|
210
|
+
const deterministicCtx = opts?.deterministicToolEvidence === 'live'
|
|
211
|
+
? createLiveDeterministicContext(ctx)
|
|
212
|
+
: ctx;
|
|
204
213
|
const phase1 = [];
|
|
205
214
|
const phase2 = [];
|
|
206
215
|
const phase3 = [];
|
|
@@ -219,7 +228,7 @@ async function evaluateWithContext(ctx, scorers, opts) {
|
|
|
219
228
|
}
|
|
220
229
|
}
|
|
221
230
|
const results = [];
|
|
222
|
-
const phase1Results = await Promise.all(phase1.map((g) => runScorer(g,
|
|
231
|
+
const phase1Results = await Promise.all(phase1.map((g) => runScorer(g, deterministicCtx)));
|
|
223
232
|
results.push(...phase1Results);
|
|
224
233
|
const anyCheckFailed = failFast && phase1Results.some((r) => r.type === 'check' && r.status !== 'error' && r.score === 0);
|
|
225
234
|
if (anyCheckFailed) {
|
|
@@ -230,7 +239,7 @@ async function evaluateWithContext(ctx, scorers, opts) {
|
|
|
230
239
|
else {
|
|
231
240
|
const judgeResults = await runJudgePipeline(phase2, ctx, { llm: trackedLLM });
|
|
232
241
|
results.push(...judgeResults);
|
|
233
|
-
const phase3Results = await Promise.all(phase3.map((g) => runScorer(g,
|
|
242
|
+
const phase3Results = await Promise.all(phase3.map((g) => runScorer(g, deterministicCtx)));
|
|
234
243
|
results.push(...phase3Results);
|
|
235
244
|
}
|
|
236
245
|
const tokenUsage = trackedLLM.tokenUsage ?? { inputTokens: 0, outputTokens: 0 };
|
|
@@ -240,12 +249,58 @@ async function evaluateWithContext(ctx, scorers, opts) {
|
|
|
240
249
|
const totalWeight = scoringResults.reduce((sum, r) => sum + r.weight, 0);
|
|
241
250
|
const weightedSum = scoringResults.reduce((sum, r) => sum + r.score * r.weight, 0);
|
|
242
251
|
const score = totalWeight > 0 ? weightedSum / totalWeight : 0;
|
|
243
|
-
return {
|
|
252
|
+
return sanitizePersistenceValue({
|
|
244
253
|
score,
|
|
245
254
|
scorers: results,
|
|
246
255
|
tokenUsage,
|
|
256
|
+
}, collectToolEventSensitiveValues(ctx.toolEvents));
|
|
257
|
+
}
|
|
258
|
+
function createLiveDeterministicContext(ctx) {
|
|
259
|
+
return {
|
|
260
|
+
...ctx,
|
|
261
|
+
toolEvents: ctx.toolEvents.map((event) => {
|
|
262
|
+
const originalInput = event.action === 'mcp_tool_call'
|
|
263
|
+
? getOriginalMcpInput(event)
|
|
264
|
+
: undefined;
|
|
265
|
+
const mcp = normalizedMcpClassification(event);
|
|
266
|
+
return originalInput
|
|
267
|
+
? cloneToolEventWithRuntimeMetadata(event, { arguments: {
|
|
268
|
+
...event.arguments,
|
|
269
|
+
server: event.arguments?.server,
|
|
270
|
+
tool: event.arguments?.tool,
|
|
271
|
+
status: event.arguments?.status,
|
|
272
|
+
...structuredClone(originalInput),
|
|
273
|
+
}, ...(mcp ? { mcp } : {}) })
|
|
274
|
+
: event;
|
|
275
|
+
}),
|
|
247
276
|
};
|
|
248
277
|
}
|
|
278
|
+
function normalizedMcpClassification(event) {
|
|
279
|
+
if (event.mcp)
|
|
280
|
+
return event.mcp;
|
|
281
|
+
const args = event.arguments ?? {};
|
|
282
|
+
const separator = event.providerToolName.indexOf('.');
|
|
283
|
+
const serverName = typeof args.server === 'string' ? args.server
|
|
284
|
+
: separator > 0 ? event.providerToolName.slice(0, separator) : undefined;
|
|
285
|
+
const toolName = typeof args.tool === 'string' ? args.tool
|
|
286
|
+
: separator > 0 && separator < event.providerToolName.length - 1
|
|
287
|
+
? event.providerToolName.slice(separator + 1) : undefined;
|
|
288
|
+
const status = event.status ?? args.status;
|
|
289
|
+
if (!serverName || !toolName)
|
|
290
|
+
return undefined;
|
|
291
|
+
if (status === 'completed')
|
|
292
|
+
return { serverName, toolName, invocation: 'confirmed', outcome: 'completed' };
|
|
293
|
+
if (status === 'error' || status === 'failed') {
|
|
294
|
+
return { serverName, toolName, invocation: 'confirmed', outcome: 'tool_error' };
|
|
295
|
+
}
|
|
296
|
+
if (status === 'user_denied' || status === 'policy_denied') {
|
|
297
|
+
return { serverName, toolName, invocation: 'not_invoked', outcome: status };
|
|
298
|
+
}
|
|
299
|
+
if (status === 'protocol_error') {
|
|
300
|
+
return { serverName, toolName, invocation: 'unknown', outcome: 'protocol_error' };
|
|
301
|
+
}
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
249
304
|
function maybeThrowOnScorerErrors(result, mode) {
|
|
250
305
|
if (mode !== 'fail')
|
|
251
306
|
return;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
2
|
-
import {
|
|
2
|
+
import { collectToolEventSensitiveValues, } from './tool-event-secrets.js';
|
|
3
3
|
export function buildJudgePrompt(scorer, ctx, input) {
|
|
4
4
|
const sections = [];
|
|
5
|
-
|
|
5
|
+
const sensitiveValues = collectToolEventSensitiveValues(ctx.toolEvents);
|
|
6
|
+
sections.push(`## Session Transcript\n${sanitizePersistenceValue(ctx.transcript, sensitiveValues)}`);
|
|
6
7
|
if (scorer.includeToolEvents && ctx.toolEvents.length > 0) {
|
|
7
8
|
sections.push(`## Tool Events\n${formatToolEvents(ctx)}`);
|
|
8
9
|
}
|
|
9
10
|
if (input) {
|
|
10
|
-
|
|
11
|
+
const sanitizedInput = sanitizePersistenceValue(input, sensitiveValues);
|
|
12
|
+
for (const [key, value] of Object.entries(sanitizedInput)) {
|
|
11
13
|
const body = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
12
14
|
sections.push(`## ${key}\n${body}`);
|
|
13
15
|
}
|
|
@@ -22,10 +24,10 @@ ${scorer.rubric}
|
|
|
22
24
|
Respond with ONLY a JSON object: {"score": <number>, "details": "<brief explanation>"}`;
|
|
23
25
|
}
|
|
24
26
|
function formatToolEvents(ctx) {
|
|
27
|
+
const sensitiveValues = collectToolEventSensitiveValues(ctx.toolEvents);
|
|
25
28
|
return ctx.toolEvents
|
|
26
29
|
.map((event) => {
|
|
27
30
|
const turn = event.turnNumber ? `turn ${event.turnNumber}` : 'instruction';
|
|
28
|
-
const sensitiveValues = getToolEventSensitiveValues(event);
|
|
29
31
|
const details = [
|
|
30
32
|
event.status ? ` status: ${event.status}` : undefined,
|
|
31
33
|
event.mcp ? ` mcp: ${JSON.stringify(event.mcp)}` : undefined,
|
|
@@ -41,7 +43,8 @@ function formatToolEvents(ctx) {
|
|
|
41
43
|
}
|
|
42
44
|
export function buildBatchedJudgePrompt(judges, ctx, inputs) {
|
|
43
45
|
const sections = [];
|
|
44
|
-
|
|
46
|
+
const sensitiveValues = collectToolEventSensitiveValues(ctx.toolEvents);
|
|
47
|
+
sections.push(`## Session Transcript\n${sanitizePersistenceValue(ctx.transcript, sensitiveValues)}`);
|
|
45
48
|
if (judges.some((j) => j.includeToolEvents) && ctx.toolEvents.length > 0) {
|
|
46
49
|
sections.push(`## Tool Events\n${formatToolEvents(ctx)}`);
|
|
47
50
|
}
|
|
@@ -49,7 +52,8 @@ export function buildBatchedJudgePrompt(judges, ctx, inputs) {
|
|
|
49
52
|
const parts = [`### Rubric ${i + 1}: "${j.name}"\n${j.rubric}`];
|
|
50
53
|
const input = inputs[i];
|
|
51
54
|
if (input) {
|
|
52
|
-
|
|
55
|
+
const sanitizedInput = sanitizePersistenceValue(input, sensitiveValues);
|
|
56
|
+
for (const [key, value] of Object.entries(sanitizedInput)) {
|
|
53
57
|
const body = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
54
58
|
parts.push(`#### ${key}\n${body}`);
|
|
55
59
|
}
|
|
@@ -77,7 +81,7 @@ export function buildToolUseJudgePrompt(scorer, ctx) {
|
|
|
77
81
|
].join('\n');
|
|
78
82
|
const parts = [];
|
|
79
83
|
parts.push('## Session Transcript');
|
|
80
|
-
parts.push(ctx.transcript);
|
|
84
|
+
parts.push(sanitizePersistenceValue(ctx.transcript, collectToolEventSensitiveValues(ctx.toolEvents)));
|
|
81
85
|
if (scorer.includeToolEvents && ctx.toolEvents.length > 0) {
|
|
82
86
|
parts.push('## Tool Events');
|
|
83
87
|
parts.push(formatToolEvents(ctx));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ToolEvent } from '../tool-events.js';
|
|
2
2
|
export declare function attachOriginalMcpInput(event: ToolEvent, input: Record<string, unknown>): ToolEvent;
|
|
3
3
|
export declare function getOriginalMcpInput(event: ToolEvent): Record<string, unknown> | undefined;
|
|
4
|
+
export declare function clearOriginalMcpInput(event: ToolEvent): void;
|
package/dist/sdk/mcp-evidence.js
CHANGED
|
@@ -2,9 +2,12 @@ export function getMcpToolCall(event) {
|
|
|
2
2
|
if (event.action !== 'mcp_tool_call')
|
|
3
3
|
return undefined;
|
|
4
4
|
const args = event.arguments ?? {};
|
|
5
|
-
const serverName =
|
|
6
|
-
|
|
7
|
-
const
|
|
5
|
+
const serverName = event.mcp?.serverName
|
|
6
|
+
?? (typeof args.server === 'string' ? args.server : undefined);
|
|
7
|
+
const toolName = event.mcp?.toolName
|
|
8
|
+
?? (typeof args.tool === 'string' ? args.tool : undefined);
|
|
9
|
+
const argumentStatus = typeof args.status === 'string' ? args.status : undefined;
|
|
10
|
+
const status = mcpClassificationStatus(event.mcp, argumentStatus) ?? argumentStatus;
|
|
8
11
|
if (!serverName || !toolName || !status)
|
|
9
12
|
return undefined;
|
|
10
13
|
return {
|
|
@@ -15,6 +18,16 @@ export function getMcpToolCall(event) {
|
|
|
15
18
|
event,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
21
|
+
function mcpClassificationStatus(mcp, argumentStatus) {
|
|
22
|
+
if (!mcp)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (mcp.invocation === 'confirmed') {
|
|
25
|
+
if (mcp.outcome === 'completed')
|
|
26
|
+
return 'completed';
|
|
27
|
+
return argumentStatus === 'failed' || argumentStatus === 'error' ? argumentStatus : 'error';
|
|
28
|
+
}
|
|
29
|
+
return mcp.outcome;
|
|
30
|
+
}
|
|
18
31
|
export function isMcpToolCall(event, expected = {}) {
|
|
19
32
|
const call = getMcpToolCall(event);
|
|
20
33
|
return call !== undefined && matchesMcpToolCall(call, expected);
|
package/dist/sdk/mcp-safety.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sanitizeUntrustedPersistenceValue } from '../tool-event-results.js';
|
|
2
2
|
export function decideMcpToolCall(options, request) {
|
|
3
3
|
const runMode = options?.runMode ?? 'mock';
|
|
4
4
|
if (runMode === 'mock')
|
|
@@ -31,7 +31,7 @@ export function decideMcpToolCall(options, request) {
|
|
|
31
31
|
return { action: 'allow' };
|
|
32
32
|
}
|
|
33
33
|
export function redactMcpSecrets(value) {
|
|
34
|
-
return
|
|
34
|
+
return sanitizeUntrustedPersistenceValue(value);
|
|
35
35
|
}
|
|
36
36
|
function ruleMatches(rule, request) {
|
|
37
37
|
if (rule.serverName !== undefined && rule.serverName !== request.serverName)
|
package/dist/sdk/scorers.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { CheckScorer, CodeJudgeToolName, ScorerContext, JudgeScorer, ScoreScorer, ScoreResult, ToolExpectation, ToolUsageScorer } from './types.js';
|
|
2
|
+
import type { JsonValue } from '../internal/direct-mcp-v2/types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Create a check scorer — a boolean gate that passes (1.0) or fails (0.0).
|
|
4
5
|
*/
|
|
5
6
|
export declare function check(name: string, fn: (ctx: ScorerContext) => boolean | Promise<boolean>, opts?: {
|
|
6
7
|
weight?: number;
|
|
8
|
+
revision?: JsonValue;
|
|
7
9
|
}): CheckScorer;
|
|
8
10
|
/**
|
|
9
11
|
* Create a score scorer — returns a number (0-1) or { score, details } for partial credit.
|
|
10
12
|
*/
|
|
11
13
|
export declare function score(name: string, fn: (ctx: ScorerContext) => number | ScoreResult | Promise<number | ScoreResult>, opts?: {
|
|
12
14
|
weight?: number;
|
|
15
|
+
revision?: JsonValue;
|
|
13
16
|
}): ScoreScorer;
|
|
14
17
|
/**
|
|
15
18
|
* Create a judge scorer — uses an LLM to evaluate against a rubric.
|
|
@@ -55,10 +58,12 @@ export declare function judge(name: string, opts: {
|
|
|
55
58
|
maxRounds?: number;
|
|
56
59
|
/** Anthropic prompt caching. Default: true when tools is set, unchanged otherwise. */
|
|
57
60
|
cacheControl?: boolean;
|
|
61
|
+
revision?: JsonValue;
|
|
58
62
|
}): JudgeScorer;
|
|
59
63
|
/**
|
|
60
64
|
* Create a tool usage scorer — matches tool events against expectations.
|
|
61
65
|
*/
|
|
62
66
|
export declare function toolUsage(name: string, expectations: ToolExpectation[], opts?: {
|
|
63
67
|
weight?: number;
|
|
68
|
+
revision?: JsonValue;
|
|
64
69
|
}): ToolUsageScorer;
|
package/dist/sdk/scorers.js
CHANGED
|
@@ -7,6 +7,7 @@ export function check(name, fn, opts) {
|
|
|
7
7
|
name,
|
|
8
8
|
weight: opts?.weight ?? 1,
|
|
9
9
|
fn,
|
|
10
|
+
...(opts?.revision === undefined ? {} : { revision: opts.revision }),
|
|
10
11
|
};
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
@@ -18,6 +19,7 @@ export function score(name, fn, opts) {
|
|
|
18
19
|
name,
|
|
19
20
|
weight: opts?.weight ?? 1,
|
|
20
21
|
fn,
|
|
22
|
+
...(opts?.revision === undefined ? {} : { revision: opts.revision }),
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
@@ -65,6 +67,7 @@ export function judge(name, opts) {
|
|
|
65
67
|
tools,
|
|
66
68
|
maxRounds: opts.maxRounds,
|
|
67
69
|
cacheControl,
|
|
70
|
+
...(opts.revision === undefined ? {} : { revision: opts.revision }),
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
73
|
/**
|
|
@@ -76,5 +79,6 @@ export function toolUsage(name, expectations, opts) {
|
|
|
76
79
|
name,
|
|
77
80
|
weight: opts?.weight ?? 1,
|
|
78
81
|
expectations,
|
|
82
|
+
...(opts?.revision === undefined ? {} : { revision: opts.revision }),
|
|
79
83
|
};
|
|
80
84
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { redactMcpSecrets } from './mcp-safety.js';
|
|
2
|
+
import { attachLiveMcpInput } from './tool-event-secrets.js';
|
|
2
3
|
export function buildScriptedMcpApprovalEvent(opts) {
|
|
3
4
|
const providerToolName = `${opts.serverName}.${opts.toolName}`;
|
|
4
5
|
return {
|
|
@@ -18,7 +19,7 @@ export function buildScriptedMcpApprovalEvent(opts) {
|
|
|
18
19
|
export function buildScriptedMcpDeniedCallEvent(opts) {
|
|
19
20
|
const providerToolName = `${opts.serverName}.${opts.toolName}`;
|
|
20
21
|
const args = redactMcpSecrets(opts.args);
|
|
21
|
-
return {
|
|
22
|
+
return attachLiveMcpInput({
|
|
22
23
|
action: 'mcp_tool_call', provider: opts.provider, providerToolName,
|
|
23
24
|
toolUseId: opts.toolUseId, turnNumber: opts.turnNumber, status: 'error',
|
|
24
25
|
mcp: { serverName: opts.serverName, toolName: opts.toolName, invocation: 'not_invoked', outcome: opts.outcome },
|
|
@@ -26,5 +27,5 @@ export function buildScriptedMcpDeniedCallEvent(opts) {
|
|
|
26
27
|
summary: `MCP tool ${providerToolName} ${opts.outcome}`,
|
|
27
28
|
confidence: 'high',
|
|
28
29
|
rawSnippet: JSON.stringify({ status: opts.outcome }),
|
|
29
|
-
};
|
|
30
|
+
}, opts.args);
|
|
30
31
|
}
|
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
2
|
-
import { getToolEventSensitiveValues } from './tool-event-secrets.js';
|
|
1
|
+
import { collectStructuredSensitiveValues, isSensitiveValueScanLimitError, sanitizePersistenceValue, } from '../tool-event-results.js';
|
|
2
|
+
import { attachToolEventSensitiveValues, cloneToolEventWithRuntimeMetadata, getToolEventSensitiveValues, redactToolEventPayload, } from './tool-event-secrets.js';
|
|
3
3
|
export function buildToolEventLogEntry(toolEvent, fallbackTimestamp) {
|
|
4
|
-
|
|
4
|
+
let event = toolEvent;
|
|
5
|
+
let discoveredValues;
|
|
6
|
+
try {
|
|
7
|
+
discoveredValues = collectStructuredSensitiveValues(event);
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (!isSensitiveValueScanLimitError(error))
|
|
11
|
+
throw error;
|
|
12
|
+
event = redactToolEventPayload(event);
|
|
13
|
+
discoveredValues = [];
|
|
14
|
+
}
|
|
15
|
+
const sensitiveValues = [...new Set([
|
|
16
|
+
...getToolEventSensitiveValues(toolEvent),
|
|
17
|
+
...discoveredValues,
|
|
18
|
+
])];
|
|
19
|
+
const persistedEvent = attachToolEventSensitiveValues(cloneToolEventWithRuntimeMetadata(event, sanitizePersistenceValue(event, sensitiveValues)), sensitiveValues);
|
|
5
20
|
return {
|
|
6
21
|
type: 'tool_event',
|
|
7
22
|
timestamp: persistedEvent.startedAt ?? fallbackTimestamp,
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ToolEvent } from '../tool-events.js';
|
|
2
2
|
export declare function attachToolEventSensitiveValues(event: ToolEvent, sensitiveValues: readonly string[]): ToolEvent;
|
|
3
3
|
export declare function getToolEventSensitiveValues(event: ToolEvent): readonly string[];
|
|
4
|
+
export declare function attachLiveMcpInput(event: ToolEvent, input: Record<string, unknown>): ToolEvent;
|
|
5
|
+
export declare function redactToolEventPayload(event: ToolEvent): ToolEvent;
|
|
6
|
+
export declare function collectToolEventSensitiveValues(events: readonly ToolEvent[]): string[];
|
|
7
|
+
export declare function clearToolEventRuntimeMetadata(event: ToolEvent): void;
|
|
4
8
|
export declare function cloneToolEventWithRuntimeMetadata(source: ToolEvent, overrides: Partial<ToolEvent>): ToolEvent;
|