@wix/pathgrade 1.0.40 → 1.0.42
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 +1 -1
- package/dist/pathgrade.d.ts +1 -0
- package/dist/pathgrade.js +2 -0
- package/dist/sdk/evaluate.js +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/mcp-evidence.d.ts +4 -0
- package/dist/sdk/mcp-evidence.js +39 -14
- package/dist/sdk/run-scorer.js +10 -0
- package/dist/sdk/tool-event-log.js +31 -1
- package/dist/sdk/tool-event-secrets.js +1 -0
- package/dist/sdk/types.d.ts +2 -1
- package/dist/tool-events.d.ts +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -321,7 +321,7 @@ Pathgrade exposes a few useful features that are easy to miss from the basic exa
|
|
|
321
321
|
- `createAgent({ skillDir, workspace })` stages a real skill and a fixture workspace into the sandbox, which is how Pathgrade's skill examples are evaluated.
|
|
322
322
|
- `createAgent({ debug: true })` preserves the final workspace under the backward-compatible `pathgrade-debug/<test-name>/` path; when you use `runConversation()`, it also writes `run-snapshot.json`.
|
|
323
323
|
- `createAgent({ debug: { retainRuns: 5 } })` opts into managed run retention under `pathgrade-debug/runs/<run-id>/<test-name>/`. Use `pathgrade clean --debug`, `--keep=N`, and `--dry-run` to clean marked, inactive debug runs safely.
|
|
324
|
-
- `evaluate.fromSnapshot(snapshotPath, scorers)` re-runs grading against a saved snapshot without re-running the agent.
|
|
324
|
+
- `evaluate.fromSnapshot(snapshotPath, scorers)` re-runs grading against a saved snapshot without re-running the agent. For trusted live deterministic scoring, `evaluate(agent, scorers, { deterministicToolEvidence: 'live' })` exposes exact transient MCP arguments only through `ctx.toolEvents`; `agent.log` stays sanitized, and redaction-blocked argument predicates fail closed.
|
|
325
325
|
- `previewReactions(messages, reactions)` lets you inspect which scripted reactions would fire offline.
|
|
326
326
|
- `conversationWindow` on agents and personas keeps long transcripts bounded with summarization instead of sending the full conversation every turn.
|
|
327
327
|
- `copyIgnore` and `DEFAULT_COPY_IGNORE` let you control what gets copied into the sandbox when seeding from large fixtures or skill directories.
|
package/dist/pathgrade.d.ts
CHANGED
package/dist/pathgrade.js
CHANGED
|
@@ -182,6 +182,8 @@ export async function runPathgradeCli(options = {}) {
|
|
|
182
182
|
if (command === 'run' || !command || command.startsWith('-')) {
|
|
183
183
|
// pathgrade run [--changed [--since=…|--changed-files=…]] [--] [runner-args]
|
|
184
184
|
loadDotenv();
|
|
185
|
+
for (const key of options.blockedEnvironmentKeys ?? [])
|
|
186
|
+
delete process.env[key];
|
|
185
187
|
validateApiKeys();
|
|
186
188
|
const parsed = parsePathgradeRunArgs(command === 'run' ? args.slice(1) : args);
|
|
187
189
|
for (const warning of parsed.warnings ?? []) {
|
package/dist/sdk/evaluate.js
CHANGED
|
@@ -270,7 +270,7 @@ function createLiveDeterministicContext(ctx) {
|
|
|
270
270
|
tool: event.arguments?.tool,
|
|
271
271
|
status: event.arguments?.status,
|
|
272
272
|
...structuredClone(originalInput),
|
|
273
|
-
}, ...(mcp ? { mcp } : {}) })
|
|
273
|
+
}, redactedArgumentPaths: undefined, ...(mcp ? { mcp } : {}) })
|
|
274
274
|
: event;
|
|
275
275
|
}),
|
|
276
276
|
};
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { AgentCrashError } from './agent-crash.js';
|
|
|
4
4
|
export { check, score, judge, toolUsage } from './scorers.js';
|
|
5
5
|
export { compileScenario, compileScenarioText } from './scenario-machine-v2.js';
|
|
6
6
|
export { findScenarioCaseMatches, getFinalScenarioState, getScenarioStateTimeline, ScenarioEvidenceError, wasScenarioCaseMatched, } from './scenario-evidence.js';
|
|
7
|
-
export { getMcpToolCall, isMcpToolCall, findMcpToolCalls, getMcpStartupStatus, isMcpStartupStatus, getMcpApproval, isMcpApproval, findMcpApprovals, getMcpInvocation, wasMcpToolInvoked, } from './mcp-evidence.js';
|
|
7
|
+
export { getMcpToolCall, isMcpToolCall, findMcpToolCalls, McpEvidenceUnavailableError, getMcpStartupStatus, isMcpStartupStatus, getMcpApproval, isMcpApproval, findMcpApprovals, getMcpInvocation, wasMcpToolInvoked, } from './mcp-evidence.js';
|
|
8
8
|
export type { McpMockApprovalRule, McpMockJsonValue } from './mcp-mock-approvals.js';
|
|
9
9
|
export type { CompileScenarioResult, EvidenceEnvelope, JsonObject, JsonPrimitive, JsonValue, MatchExpr, MatchPredicate, McpContent, ScenarioArtifact, ScenarioCase, ScenarioDiagnostic, ScenarioEvidence, ScenarioMachineV2, ScenarioRejection, ScenarioSuccess, ScenarioToolError, } from './scenario-machine-v2.js';
|
|
10
10
|
export type { ExpectedScenarioCaseMatch, ScenarioCaseMatchEvidence, ScenarioStateTransitionEvidence, } from './scenario-evidence.js';
|
package/dist/sdk/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { AgentCrashError } from './agent-crash.js';
|
|
|
5
5
|
export { check, score, judge, toolUsage } from './scorers.js';
|
|
6
6
|
export { compileScenario, compileScenarioText } from './scenario-machine-v2.js';
|
|
7
7
|
export { findScenarioCaseMatches, getFinalScenarioState, getScenarioStateTimeline, ScenarioEvidenceError, wasScenarioCaseMatched, } from './scenario-evidence.js';
|
|
8
|
-
export { getMcpToolCall, isMcpToolCall, findMcpToolCalls, getMcpStartupStatus, isMcpStartupStatus, getMcpApproval, isMcpApproval, findMcpApprovals, getMcpInvocation, wasMcpToolInvoked, } from './mcp-evidence.js';
|
|
8
|
+
export { getMcpToolCall, isMcpToolCall, findMcpToolCalls, McpEvidenceUnavailableError, getMcpStartupStatus, isMcpStartupStatus, getMcpApproval, isMcpApproval, findMcpApprovals, getMcpInvocation, wasMcpToolInvoked, } from './mcp-evidence.js';
|
|
9
9
|
export { decideMcpToolCall, redactMcpSecrets, } from './mcp-safety.js';
|
|
10
10
|
export { evaluate, evaluateFlow, EvalScorerError } from './evaluate.js';
|
|
11
11
|
export { RUN_SNAPSHOT_VERSION, buildRunSnapshot, loadRunSnapshot, SnapshotParseError, SnapshotVersionError, WorkspaceMissingError, } from './snapshots.js';
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ToolEvent } from '../tool-events.js';
|
|
2
|
+
export declare class McpEvidenceUnavailableError extends TypeError {
|
|
3
|
+
readonly name = "McpEvidenceUnavailableError";
|
|
4
|
+
}
|
|
2
5
|
export type McpInvocationResult = {
|
|
3
6
|
serverName: string;
|
|
4
7
|
toolName: string;
|
|
@@ -46,6 +49,7 @@ export interface ExpectedMcpToolCall {
|
|
|
46
49
|
status?: string;
|
|
47
50
|
invocation?: McpInvocationResult['invocation'];
|
|
48
51
|
outcome?: McpInvocationResult['outcome'];
|
|
52
|
+
/** Throws when persisted redaction makes the requested argument match unknowable. */
|
|
49
53
|
argumentsContaining?: Record<string, unknown>;
|
|
50
54
|
}
|
|
51
55
|
export interface McpStartupStatusEvidence {
|
package/dist/sdk/mcp-evidence.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
+
const REDACTED_ARGUMENTS_ERROR = "argumentsContaining cannot be evaluated against redacted MCP arguments; use the scorer's ctx.toolEvents with deterministicToolEvidence: 'live'";
|
|
2
|
+
export class McpEvidenceUnavailableError extends TypeError {
|
|
3
|
+
name = 'McpEvidenceUnavailableError';
|
|
4
|
+
}
|
|
1
5
|
export function getMcpToolCall(event) {
|
|
2
6
|
if (event.action !== 'mcp_tool_call')
|
|
3
7
|
return undefined;
|
|
4
8
|
const args = event.arguments ?? {};
|
|
9
|
+
const separator = event.providerToolName.indexOf('.');
|
|
5
10
|
const serverName = event.mcp?.serverName
|
|
6
|
-
?? (typeof args.server === 'string' ? args.server : undefined)
|
|
11
|
+
?? (typeof args.server === 'string' ? args.server : undefined)
|
|
12
|
+
?? (separator > 0 ? event.providerToolName.slice(0, separator) : undefined);
|
|
7
13
|
const toolName = event.mcp?.toolName
|
|
8
|
-
?? (typeof args.tool === 'string' ? args.tool : undefined)
|
|
14
|
+
?? (typeof args.tool === 'string' ? args.tool : undefined)
|
|
15
|
+
?? (separator > 0 && separator < event.providerToolName.length - 1
|
|
16
|
+
? event.providerToolName.slice(separator + 1) : undefined);
|
|
9
17
|
const argumentStatus = typeof args.status === 'string' ? args.status : undefined;
|
|
10
|
-
const status = mcpClassificationStatus(event.mcp, argumentStatus) ?? argumentStatus;
|
|
18
|
+
const status = mcpClassificationStatus(event.mcp, argumentStatus) ?? argumentStatus ?? event.status;
|
|
11
19
|
if (!serverName || !toolName || !status)
|
|
12
20
|
return undefined;
|
|
13
21
|
return {
|
|
@@ -151,29 +159,46 @@ function matchesMcpToolCall(call, expected) {
|
|
|
151
159
|
if (expected.outcome !== undefined && invocation.outcome !== expected.outcome)
|
|
152
160
|
return false;
|
|
153
161
|
}
|
|
154
|
-
if (expected.argumentsContaining && !containsArguments(call.arguments, expected.argumentsContaining)) {
|
|
162
|
+
if (expected.argumentsContaining && !containsArguments(call.arguments, expected.argumentsContaining, call.event.redactedArgumentPaths)) {
|
|
155
163
|
return false;
|
|
156
164
|
}
|
|
157
165
|
return true;
|
|
158
166
|
}
|
|
159
|
-
function containsArguments(actual, expected) {
|
|
160
|
-
|
|
167
|
+
function containsArguments(actual, expected, redactedPaths = []) {
|
|
168
|
+
const matches = Object.entries(expected).every(([key, expectedValue]) => valuesEqual(actual[key], expectedValue, `/${escapeJsonPointer(key)}`, redactedPaths));
|
|
169
|
+
return matches;
|
|
161
170
|
}
|
|
162
|
-
function valuesEqual(actual, expected) {
|
|
163
|
-
if (Object.is(actual, expected))
|
|
171
|
+
function valuesEqual(actual, expected, path, redactedPaths) {
|
|
172
|
+
if (Object.is(actual, expected)) {
|
|
173
|
+
assertMcpArgumentAvailable(path, redactedPaths);
|
|
164
174
|
return true;
|
|
175
|
+
}
|
|
165
176
|
if (Array.isArray(actual) || Array.isArray(expected)) {
|
|
166
|
-
if (
|
|
167
|
-
return
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return actual.every((value, index) => valuesEqual(value, expected[index]));
|
|
177
|
+
if (Array.isArray(actual) && Array.isArray(expected) && actual.length === expected.length) {
|
|
178
|
+
return actual.every((value, index) => valuesEqual(value, expected[index], `${path}/${index}`, redactedPaths));
|
|
179
|
+
}
|
|
180
|
+
return rejectRedactedMismatch(path, redactedPaths);
|
|
171
181
|
}
|
|
172
182
|
if (isRecord(actual) && isRecord(expected)) {
|
|
173
|
-
return Object.entries(expected).every(([key, value]) => valuesEqual(actual[key], value));
|
|
183
|
+
return Object.entries(expected).every(([key, value]) => valuesEqual(actual[key], value, `${path}/${escapeJsonPointer(key)}`, redactedPaths));
|
|
174
184
|
}
|
|
185
|
+
return rejectRedactedMismatch(path, redactedPaths);
|
|
186
|
+
}
|
|
187
|
+
function rejectRedactedMismatch(path, redactedPaths) {
|
|
188
|
+
assertMcpArgumentAvailable(path, redactedPaths);
|
|
175
189
|
return false;
|
|
176
190
|
}
|
|
191
|
+
function assertMcpArgumentAvailable(path, redactedPaths) {
|
|
192
|
+
if (redactedPaths.some((redactedPath) => redactedPath === ''
|
|
193
|
+
|| redactedPath === path
|
|
194
|
+
|| redactedPath.startsWith(`${path}/`)
|
|
195
|
+
|| path.startsWith(`${redactedPath}/`))) {
|
|
196
|
+
throw new McpEvidenceUnavailableError(REDACTED_ARGUMENTS_ERROR);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function escapeJsonPointer(value) {
|
|
200
|
+
return value.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
201
|
+
}
|
|
177
202
|
function isRecord(value) {
|
|
178
203
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
179
204
|
}
|
package/dist/sdk/run-scorer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { runJudgePipeline } from './judge-pipeline.js';
|
|
2
2
|
import { runJudgeSession } from './judge-tool-session.js';
|
|
3
3
|
import { clamp, makeErroredResult, matchesExpectation } from './scorer-utils.js';
|
|
4
|
+
import { McpEvidenceUnavailableError } from './mcp-evidence.js';
|
|
4
5
|
export async function runScorer(scorer, ctx, opts = {}) {
|
|
5
6
|
switch (scorer.type) {
|
|
6
7
|
case 'check':
|
|
@@ -56,6 +57,9 @@ async function runCheckScorer(scorer, ctx) {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
catch (error) {
|
|
60
|
+
if (error instanceof McpEvidenceUnavailableError) {
|
|
61
|
+
return makeUnavailableEvidenceResult('check', scorer.name, scorer.weight, error);
|
|
62
|
+
}
|
|
59
63
|
return makeErroredResult('check', scorer.name, scorer.weight, error);
|
|
60
64
|
}
|
|
61
65
|
}
|
|
@@ -81,9 +85,15 @@ async function runScoreScorer(scorer, ctx) {
|
|
|
81
85
|
};
|
|
82
86
|
}
|
|
83
87
|
catch (error) {
|
|
88
|
+
if (error instanceof McpEvidenceUnavailableError) {
|
|
89
|
+
return makeUnavailableEvidenceResult('score', scorer.name, scorer.weight, error);
|
|
90
|
+
}
|
|
84
91
|
return makeErroredResult('score', scorer.name, scorer.weight, error);
|
|
85
92
|
}
|
|
86
93
|
}
|
|
94
|
+
function makeUnavailableEvidenceResult(type, name, weight, error) {
|
|
95
|
+
return { name, type, score: 0, weight, details: error.message, status: 'ok' };
|
|
96
|
+
}
|
|
87
97
|
async function runToolUsageScorer(scorer, ctx) {
|
|
88
98
|
try {
|
|
89
99
|
const toolEvents = ctx.toolEvents;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
1
2
|
import { collectStructuredSensitiveValues, isSensitiveValueScanLimitError, sanitizePersistenceValue, } from '../tool-event-results.js';
|
|
2
3
|
import { attachToolEventSensitiveValues, cloneToolEventWithRuntimeMetadata, getToolEventSensitiveValues, redactToolEventPayload, } from './tool-event-secrets.js';
|
|
4
|
+
import { getOriginalMcpInput } from './mcp-event-input.js';
|
|
5
|
+
const MCP_METADATA_ARGUMENTS = new Set(['server', 'tool', 'status']);
|
|
3
6
|
export function buildToolEventLogEntry(toolEvent, fallbackTimestamp) {
|
|
4
7
|
let event = toolEvent;
|
|
5
8
|
let discoveredValues;
|
|
@@ -16,10 +19,37 @@ export function buildToolEventLogEntry(toolEvent, fallbackTimestamp) {
|
|
|
16
19
|
...getToolEventSensitiveValues(toolEvent),
|
|
17
20
|
...discoveredValues,
|
|
18
21
|
])];
|
|
19
|
-
const
|
|
22
|
+
const sanitizedEvent = sanitizePersistenceValue(event, sensitiveValues);
|
|
23
|
+
const originalInput = getOriginalMcpInput(event);
|
|
24
|
+
const originalArguments = originalInput ? {
|
|
25
|
+
...event.arguments,
|
|
26
|
+
...Object.fromEntries(Object.entries(originalInput).filter(([key]) => !MCP_METADATA_ARGUMENTS.has(key))),
|
|
27
|
+
} : event.arguments;
|
|
28
|
+
const redactedArgumentPaths = [...new Set([
|
|
29
|
+
...(event.redactedArgumentPaths ?? []),
|
|
30
|
+
...collectChangedPaths(originalArguments, sanitizedEvent.arguments),
|
|
31
|
+
])];
|
|
32
|
+
const persistedEvent = attachToolEventSensitiveValues(cloneToolEventWithRuntimeMetadata(event, {
|
|
33
|
+
...sanitizedEvent,
|
|
34
|
+
...(redactedArgumentPaths.length > 0 ? { redactedArgumentPaths } : {}),
|
|
35
|
+
}), sensitiveValues);
|
|
20
36
|
return {
|
|
21
37
|
type: 'tool_event',
|
|
22
38
|
timestamp: persistedEvent.startedAt ?? fallbackTimestamp,
|
|
23
39
|
tool_event: persistedEvent,
|
|
24
40
|
};
|
|
25
41
|
}
|
|
42
|
+
function collectChangedPaths(original, sanitized, path = '') {
|
|
43
|
+
if (isDeepStrictEqual(original, sanitized))
|
|
44
|
+
return [];
|
|
45
|
+
if (Array.isArray(original) && Array.isArray(sanitized) && original.length === sanitized.length) {
|
|
46
|
+
return original.flatMap((value, index) => collectChangedPaths(value, sanitized[index], `${path}/${index}`));
|
|
47
|
+
}
|
|
48
|
+
if (isRecord(original) && isRecord(sanitized)) {
|
|
49
|
+
return Object.keys(original).flatMap((key) => collectChangedPaths(original[key], sanitized[key], `${path}/${key.replaceAll('~', '~0').replaceAll('/', '~1')}`));
|
|
50
|
+
}
|
|
51
|
+
return [path];
|
|
52
|
+
}
|
|
53
|
+
function isRecord(value) {
|
|
54
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
55
|
+
}
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -348,6 +348,7 @@ export interface ScorerContext {
|
|
|
348
348
|
workspace: string;
|
|
349
349
|
log: LogEntry[];
|
|
350
350
|
transcript: string;
|
|
351
|
+
/** Use this scorer-context field for live MCP arguments; `log` always remains sanitized. */
|
|
351
352
|
toolEvents: import('../tool-events.js').ToolEvent[];
|
|
352
353
|
runCommand: (cmd: string) => Promise<CommandResult>;
|
|
353
354
|
artifacts: SessionArtifacts;
|
|
@@ -360,7 +361,7 @@ export interface EvaluateOptions {
|
|
|
360
361
|
failFast?: boolean;
|
|
361
362
|
llm?: LLMPort;
|
|
362
363
|
onScorerError?: 'skip' | 'zero' | 'fail';
|
|
363
|
-
/**
|
|
364
|
+
/** Restores exact MCP arguments only on deterministic scorers' `ctx.toolEvents`; `agent.log` remains sanitized. Live mode is unavailable for snapshot replay. */
|
|
364
365
|
deterministicToolEvidence?: 'persisted' | 'live';
|
|
365
366
|
/** Stable identity for this evaluation definition across separate runs. */
|
|
366
367
|
evaluationDefinitionKey?: string;
|
package/dist/tool-events.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface ToolEvent {
|
|
|
20
20
|
toolUseId?: string;
|
|
21
21
|
turnNumber?: number;
|
|
22
22
|
arguments?: Record<string, unknown>;
|
|
23
|
+
/** Producer-controlled JSON Pointer paths identifying persisted arguments that were sanitized. */
|
|
24
|
+
redactedArgumentPaths?: string[];
|
|
23
25
|
/** Lifecycle state observed by PathGrade. Absent on legacy provider events. */
|
|
24
26
|
status?: 'completed' | 'error' | 'incomplete';
|
|
25
27
|
/** Receipt/enforcement-backed classification for canonical MCP calls. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.42",
|
|
4
4
|
"packageManager": "yarn@4.12.0",
|
|
5
5
|
"description": "Evaluate whether AI agents discover and use your skills correctly",
|
|
6
6
|
"exports": {
|
|
@@ -142,5 +142,5 @@
|
|
|
142
142
|
"typescript": "^5.9.3",
|
|
143
143
|
"zod": "4.3.6"
|
|
144
144
|
},
|
|
145
|
-
"falconPackageHash": "
|
|
145
|
+
"falconPackageHash": "9b508a508d9397bfc0a4ac6acd286c9019230468e7fad14edcb26c2c"
|
|
146
146
|
}
|