@wix/pathgrade 1.0.13 → 1.0.15
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 +30 -0
- package/dist/adapters/jest/results.js +1 -1
- package/dist/adapters/node-test/index.js +1 -1
- package/dist/agents/claude/sdk-message-projector.js +3 -2
- package/dist/agents/claude/tool-permission-bridge.d.ts +4 -0
- package/dist/agents/claude/tool-permission-bridge.js +68 -1
- package/dist/agents/claude.d.ts +2 -0
- package/dist/agents/claude.js +55 -10
- package/dist/agents/codex-app-server/agent.js +94 -76
- package/dist/agents/codex-app-server/mcp-approval-correlator.d.ts +55 -0
- package/dist/agents/codex-app-server/mcp-approval-correlator.js +299 -0
- package/dist/core/canonical-json.d.ts +2 -0
- package/dist/core/canonical-json.js +51 -0
- package/dist/core/generated-mcp-protocol.d.ts +19 -0
- package/dist/core/generated-mcp-protocol.js +26 -0
- package/dist/core/mcp-mock.d.ts +1 -1
- package/dist/core/mcp-mock.js +24 -0
- package/dist/core/mcp-mock.types.d.ts +6 -0
- package/dist/core/mcp-schema-profile.d.ts +10 -0
- package/dist/core/mcp-schema-profile.js +91 -0
- package/dist/mcp-mock-server.js +29 -13
- package/dist/providers/mcp-config.js +4 -2
- package/dist/providers/sandbox.js +6 -0
- package/dist/providers/scripted-mcp-mock-host.d.ts +39 -0
- package/dist/providers/scripted-mcp-mock-host.js +368 -0
- package/dist/reporters/cli.js +4 -3
- package/dist/reporters/github-comment.js +1 -1
- package/dist/reporters/report-summary.js +1 -0
- package/dist/reporting/core.js +19 -5
- package/dist/reporting/types.d.ts +2 -1
- package/dist/runners/model-builders.js +1 -1
- package/dist/runners/model-validation.js +4 -1
- package/dist/runners/model.d.ts +1 -1
- package/dist/runners/report-projection.js +2 -2
- package/dist/runners/vitest-adapter.js +1 -1
- package/dist/sdk/agent.js +64 -19
- package/dist/sdk/diagnostics.d.ts +1 -0
- package/dist/sdk/diagnostics.js +6 -3
- package/dist/sdk/index.d.ts +4 -3
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/lifecycle.js +3 -3
- package/dist/sdk/managed-session.d.ts +3 -0
- package/dist/sdk/managed-session.js +71 -26
- package/dist/sdk/mcp-event-input.d.ts +3 -0
- package/dist/sdk/mcp-event-input.js +8 -0
- package/dist/sdk/mcp-evidence.d.ts +39 -0
- package/dist/sdk/mcp-evidence.js +71 -12
- package/dist/sdk/mcp-mock-approvals.d.ts +40 -0
- package/dist/sdk/mcp-mock-approvals.js +235 -0
- package/dist/sdk/scripted-mcp-events.d.ts +24 -0
- package/dist/sdk/scripted-mcp-events.js +30 -0
- package/dist/sdk/types.d.ts +6 -2
- package/dist/tool-events.d.ts +16 -1
- package/dist/types.d.ts +3 -1
- package/dist/viewer.html +4 -4
- package/package.json +3 -2
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { createHmac, randomBytes } from 'node:crypto';
|
|
2
|
+
import { assertScriptedMcpSchemaProfile } from '../core/mcp-schema-profile.js';
|
|
3
|
+
import { canonicalizeJson } from '../core/canonical-json.js';
|
|
4
|
+
const RULE_KEYS = new Set(['serverName', 'toolName', 'argumentsContaining', 'decision', 'label']);
|
|
5
|
+
const INSTRUCTIONS_MAX_BYTES = 32 * 1024;
|
|
6
|
+
const WHEN_MAX_BYTES = 8 * 1024;
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
9
|
+
return false;
|
|
10
|
+
const prototype = Object.getPrototypeOf(value);
|
|
11
|
+
return prototype === Object.prototype || prototype === null;
|
|
12
|
+
}
|
|
13
|
+
function cloneJson(value, path, ancestors = new Set()) {
|
|
14
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
15
|
+
return value;
|
|
16
|
+
if (typeof value === 'number') {
|
|
17
|
+
if (!Number.isFinite(value))
|
|
18
|
+
throw new Error(`${path} must contain only finite JSON numbers`);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
if (ancestors.has(value))
|
|
23
|
+
throw new Error(`${path} must be acyclic JSON`);
|
|
24
|
+
if (Object.keys(value).some((key) => !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length)) {
|
|
25
|
+
throw new Error(`${path} must not contain non-index array properties`);
|
|
26
|
+
}
|
|
27
|
+
ancestors.add(value);
|
|
28
|
+
const cloned = Array.from({ length: value.length }, (_, index) => {
|
|
29
|
+
if (!Object.prototype.hasOwnProperty.call(value, index))
|
|
30
|
+
throw new Error(`${path}[${index}] must not be sparse`);
|
|
31
|
+
return cloneJson(value[index], `${path}[${index}]`, ancestors);
|
|
32
|
+
});
|
|
33
|
+
ancestors.delete(value);
|
|
34
|
+
return cloned;
|
|
35
|
+
}
|
|
36
|
+
if (!isPlainObject(value))
|
|
37
|
+
throw new Error(`${path} must contain only plain JSON values`);
|
|
38
|
+
if (ancestors.has(value))
|
|
39
|
+
throw new Error(`${path} must be acyclic JSON`);
|
|
40
|
+
ancestors.add(value);
|
|
41
|
+
const cloned = {};
|
|
42
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
43
|
+
if (typeof key !== 'string')
|
|
44
|
+
throw new Error(`${path} must not contain symbol keys`);
|
|
45
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
46
|
+
if (!descriptor.enumerable || !('value' in descriptor)) {
|
|
47
|
+
throw new Error(`${path}.${key} must be an enumerable data property`);
|
|
48
|
+
}
|
|
49
|
+
cloned[key] = cloneJson(descriptor.value, `${path}.${key}`, ancestors);
|
|
50
|
+
}
|
|
51
|
+
ancestors.delete(value);
|
|
52
|
+
return cloned;
|
|
53
|
+
}
|
|
54
|
+
function deepFreeze(value, seen = new Set()) {
|
|
55
|
+
if (value === null || typeof value !== 'object' || seen.has(value))
|
|
56
|
+
return value;
|
|
57
|
+
seen.add(value);
|
|
58
|
+
if (value instanceof Map) {
|
|
59
|
+
for (const [key, entry] of value) {
|
|
60
|
+
deepFreeze(key, seen);
|
|
61
|
+
deepFreeze(entry, seen);
|
|
62
|
+
}
|
|
63
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'set')) {
|
|
64
|
+
const immutable = () => { throw new TypeError('Compiled MCP manifest is immutable'); };
|
|
65
|
+
Object.defineProperties(value, {
|
|
66
|
+
set: { value: immutable },
|
|
67
|
+
delete: { value: immutable },
|
|
68
|
+
clear: { value: immutable },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
for (const entry of Object.values(value))
|
|
74
|
+
deepFreeze(entry, seen);
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze(value);
|
|
77
|
+
}
|
|
78
|
+
function nonEmptyString(value, path) {
|
|
79
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
80
|
+
throw new Error(`${path} must be a non-empty string`);
|
|
81
|
+
}
|
|
82
|
+
function cloneDescriptor(value, index) {
|
|
83
|
+
const cloned = cloneJson(value, `mcpMock[${index}]`);
|
|
84
|
+
if (cloned.__type !== 'mock_mcp_server' || !isPlainObject(cloned.config)) {
|
|
85
|
+
throw new Error(`mcpMock[${index}] must be a generated mock descriptor`);
|
|
86
|
+
}
|
|
87
|
+
nonEmptyString(cloned.config.name, `mcpMock[${index}].config.name`);
|
|
88
|
+
if (!Array.isArray(cloned.config.tools) || cloned.config.tools.length === 0) {
|
|
89
|
+
throw new Error(`mcpMock[${index}].config.tools must be a non-empty array`);
|
|
90
|
+
}
|
|
91
|
+
if (cloned.config.instructions !== undefined) {
|
|
92
|
+
nonEmptyString(cloned.config.instructions, `mcpMock[${index}].config.instructions`);
|
|
93
|
+
if (Buffer.byteLength(cloned.config.instructions, 'utf8') > INSTRUCTIONS_MAX_BYTES) {
|
|
94
|
+
throw new Error(`mcpMock[${index}].config.instructions exceeds 32 KiB`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return cloned;
|
|
98
|
+
}
|
|
99
|
+
function buildServer(descriptor) {
|
|
100
|
+
const byName = new Map();
|
|
101
|
+
for (const [index, tool] of descriptor.config.tools.entries()) {
|
|
102
|
+
nonEmptyString(tool.name, `${descriptor.config.name}.tools[${index}].name`);
|
|
103
|
+
if (tool.when !== undefined) {
|
|
104
|
+
nonEmptyString(tool.when, `${descriptor.config.name}.${tool.name}.when`);
|
|
105
|
+
if (Buffer.byteLength(tool.when, 'utf8') > WHEN_MAX_BYTES) {
|
|
106
|
+
throw new Error(`${descriptor.config.name}.${tool.name}.when exceeds 8 KiB`);
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
new RegExp(tool.when, 'i');
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw new Error(`${descriptor.config.name}.${tool.name}.when is invalid`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const schema = tool.inputSchema ?? { type: 'object' };
|
|
116
|
+
if (!isPlainObject(schema))
|
|
117
|
+
throw new Error(`${descriptor.config.name}.${tool.name}.inputSchema must be an object`);
|
|
118
|
+
assertScriptedMcpSchemaProfile(schema, { serverName: descriptor.config.name, toolName: tool.name });
|
|
119
|
+
const cases = byName.get(tool.name) ?? [];
|
|
120
|
+
cases.push(tool);
|
|
121
|
+
byName.set(tool.name, cases);
|
|
122
|
+
}
|
|
123
|
+
const tools = new Map();
|
|
124
|
+
for (const [name, cases] of byName) {
|
|
125
|
+
const hints = new Set(cases.flatMap((entry) => entry.annotations?.readOnlyHint === undefined
|
|
126
|
+
? []
|
|
127
|
+
: [entry.annotations.readOnlyHint]));
|
|
128
|
+
if (hints.size > 1)
|
|
129
|
+
throw new Error(`Conflicting readOnlyHint values for tool "${name}"`);
|
|
130
|
+
const first = cases[0];
|
|
131
|
+
tools.set(name, deepFreeze({
|
|
132
|
+
name,
|
|
133
|
+
...(first.description !== undefined ? { description: first.description } : {}),
|
|
134
|
+
inputSchema: first.inputSchema ?? { type: 'object' },
|
|
135
|
+
...(hints.size === 1 ? { readOnlyHint: [...hints][0] } : {}),
|
|
136
|
+
cases: cases.map((entry) => ({
|
|
137
|
+
...(entry.when !== undefined ? { when: entry.when } : {}),
|
|
138
|
+
response: entry.response,
|
|
139
|
+
})),
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
return deepFreeze({
|
|
143
|
+
name: descriptor.config.name,
|
|
144
|
+
...(descriptor.config.instructions !== undefined ? { instructions: descriptor.config.instructions } : {}),
|
|
145
|
+
tools,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function selectorSubsumes(earlier, later) {
|
|
149
|
+
if (Object.is(earlier, later))
|
|
150
|
+
return true;
|
|
151
|
+
if (Array.isArray(earlier) || Array.isArray(later)) {
|
|
152
|
+
return Array.isArray(earlier) && Array.isArray(later)
|
|
153
|
+
&& earlier.length === later.length
|
|
154
|
+
&& earlier.every((entry, index) => selectorSubsumes(entry, later[index]));
|
|
155
|
+
}
|
|
156
|
+
if (!isPlainObject(earlier) || !isPlainObject(later))
|
|
157
|
+
return false;
|
|
158
|
+
return Object.entries(earlier).every(([key, value]) => Object.prototype.hasOwnProperty.call(later, key) && selectorSubsumes(value, later[key]));
|
|
159
|
+
}
|
|
160
|
+
function assertClaudeNamesUnambiguous(names) {
|
|
161
|
+
for (const name of names) {
|
|
162
|
+
if (name.includes('__'))
|
|
163
|
+
throw new Error(`Claude MCP server name "${name}" contains reserved delimiter "__"`);
|
|
164
|
+
}
|
|
165
|
+
for (let left = 0; left < names.length; left++) {
|
|
166
|
+
for (let right = left + 1; right < names.length; right++) {
|
|
167
|
+
if (names[left].startsWith(`${names[right]}__`) || names[right].startsWith(`${names[left]}__`)) {
|
|
168
|
+
throw new Error(`Claude MCP server names "${names[left]}" and "${names[right]}" are ambiguous`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export function compileMcpMockApprovalSession(opts) {
|
|
174
|
+
if (!Array.isArray(opts.rules) || opts.rules.length === 0) {
|
|
175
|
+
throw new Error('mcpMockApprovalRules must be a non-empty array');
|
|
176
|
+
}
|
|
177
|
+
const descriptors = (Array.isArray(opts.mcpMock) ? opts.mcpMock : [opts.mcpMock])
|
|
178
|
+
.map((descriptor, index) => cloneDescriptor(descriptor, index));
|
|
179
|
+
const servers = new Map();
|
|
180
|
+
for (const descriptor of descriptors) {
|
|
181
|
+
if (servers.has(descriptor.config.name))
|
|
182
|
+
throw new Error(`Duplicate mock MCP server name: "${descriptor.config.name}"`);
|
|
183
|
+
servers.set(descriptor.config.name, buildServer(descriptor));
|
|
184
|
+
}
|
|
185
|
+
if (opts.provider === 'claude')
|
|
186
|
+
assertClaudeNamesUnambiguous([...servers.keys()]);
|
|
187
|
+
const rules = [];
|
|
188
|
+
for (const [index, source] of opts.rules.entries()) {
|
|
189
|
+
if (!isPlainObject(source))
|
|
190
|
+
throw new Error(`mcpMockApprovalRules[${index}] must be a plain object`);
|
|
191
|
+
for (const key of Object.keys(source)) {
|
|
192
|
+
if (!RULE_KEYS.has(key))
|
|
193
|
+
throw new Error(`mcpMockApprovalRules[${index}] has unknown key "${key}"`);
|
|
194
|
+
}
|
|
195
|
+
const rule = cloneJson(source, `mcpMockApprovalRules[${index}]`);
|
|
196
|
+
nonEmptyString(rule.serverName, `mcpMockApprovalRules[${index}].serverName`);
|
|
197
|
+
nonEmptyString(rule.toolName, `mcpMockApprovalRules[${index}].toolName`);
|
|
198
|
+
if (rule.decision !== 'approve' && rule.decision !== 'deny') {
|
|
199
|
+
throw new Error(`mcpMockApprovalRules[${index}].decision must be approve or deny`);
|
|
200
|
+
}
|
|
201
|
+
if (rule.label !== undefined)
|
|
202
|
+
nonEmptyString(rule.label, `mcpMockApprovalRules[${index}].label`);
|
|
203
|
+
if (rule.argumentsContaining !== undefined && !isPlainObject(rule.argumentsContaining)) {
|
|
204
|
+
throw new Error(`mcpMockApprovalRules[${index}].argumentsContaining must be a plain JSON object`);
|
|
205
|
+
}
|
|
206
|
+
const tool = servers.get(rule.serverName)?.tools.get(rule.toolName);
|
|
207
|
+
if (!tool)
|
|
208
|
+
throw new Error(`mcpMockApprovalRules[${index}] targets unknown generated tool ${rule.serverName}.${rule.toolName}`);
|
|
209
|
+
if (tool.readOnlyHint === true)
|
|
210
|
+
throw new Error(`mcpMockApprovalRules[${index}] targets read-only tool ${rule.serverName}.${rule.toolName}`);
|
|
211
|
+
for (let previous = 0; previous < index; previous++) {
|
|
212
|
+
const earlier = rules[previous];
|
|
213
|
+
if (earlier.serverName === rule.serverName && earlier.toolName === rule.toolName
|
|
214
|
+
&& (earlier.argumentsContaining === undefined
|
|
215
|
+
|| (rule.argumentsContaining !== undefined
|
|
216
|
+
&& selectorSubsumes(earlier.argumentsContaining, rule.argumentsContaining)))) {
|
|
217
|
+
throw new Error(`mcpMockApprovalRules[${index}] is shadowed by rule ${previous}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
rules.push(deepFreeze(rule));
|
|
221
|
+
}
|
|
222
|
+
return deepFreeze({ servers, rules });
|
|
223
|
+
}
|
|
224
|
+
export function matchesMcpApprovalArguments(actual, expected) {
|
|
225
|
+
return expected === undefined || selectorSubsumes(expected, actual);
|
|
226
|
+
}
|
|
227
|
+
export function canonicalizeMcpArguments(value) {
|
|
228
|
+
return canonicalizeJson(value);
|
|
229
|
+
}
|
|
230
|
+
export function createMcpArgumentDigest(key, value) {
|
|
231
|
+
return createHmac('sha256', key).update(canonicalizeMcpArguments(value)).digest('base64url');
|
|
232
|
+
}
|
|
233
|
+
export function createMcpReceiptKey() {
|
|
234
|
+
return randomBytes(32);
|
|
235
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ToolEvent } from '../tool-events.js';
|
|
2
|
+
interface ScriptedMcpDecisionEvidence {
|
|
3
|
+
decision: 'approve' | 'deny';
|
|
4
|
+
outcome: 'matched' | 'unmatched' | 'protocol_error';
|
|
5
|
+
reason: string;
|
|
6
|
+
ruleIndex?: number;
|
|
7
|
+
ruleLabel?: string;
|
|
8
|
+
}
|
|
9
|
+
interface ScriptedMcpEventIdentity {
|
|
10
|
+
provider: 'claude' | 'codex';
|
|
11
|
+
serverName: string;
|
|
12
|
+
toolName: string;
|
|
13
|
+
toolUseId: string;
|
|
14
|
+
turnNumber: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function buildScriptedMcpApprovalEvent(opts: ScriptedMcpEventIdentity & {
|
|
17
|
+
decision: ScriptedMcpDecisionEvidence;
|
|
18
|
+
status?: ToolEvent['status'];
|
|
19
|
+
}): ToolEvent;
|
|
20
|
+
export declare function buildScriptedMcpDeniedCallEvent(opts: ScriptedMcpEventIdentity & {
|
|
21
|
+
args: Record<string, unknown>;
|
|
22
|
+
outcome: 'user_denied' | 'protocol_error';
|
|
23
|
+
}): ToolEvent;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { redactMcpSecrets } from './mcp-safety.js';
|
|
2
|
+
export function buildScriptedMcpApprovalEvent(opts) {
|
|
3
|
+
const providerToolName = `${opts.serverName}.${opts.toolName}`;
|
|
4
|
+
return {
|
|
5
|
+
action: 'mcp_approval', provider: opts.provider, providerToolName,
|
|
6
|
+
toolUseId: opts.toolUseId, turnNumber: opts.turnNumber, status: opts.status ?? 'completed',
|
|
7
|
+
arguments: {
|
|
8
|
+
server: opts.serverName, tool: opts.toolName, decision: opts.decision.decision,
|
|
9
|
+
outcome: opts.decision.outcome, reason: opts.decision.reason, decisionSource: 'scripted_user',
|
|
10
|
+
...(opts.decision.ruleIndex !== undefined ? { ruleIndex: opts.decision.ruleIndex } : {}),
|
|
11
|
+
...(opts.decision.ruleLabel ? { ruleLabel: opts.decision.ruleLabel } : {}),
|
|
12
|
+
},
|
|
13
|
+
summary: `MCP approval ${providerToolName} ${opts.decision.decision} ${opts.decision.outcome}`,
|
|
14
|
+
confidence: 'high',
|
|
15
|
+
rawSnippet: JSON.stringify({ decision: opts.decision.decision, outcome: opts.decision.outcome }),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function buildScriptedMcpDeniedCallEvent(opts) {
|
|
19
|
+
const providerToolName = `${opts.serverName}.${opts.toolName}`;
|
|
20
|
+
const args = redactMcpSecrets(opts.args);
|
|
21
|
+
return {
|
|
22
|
+
action: 'mcp_tool_call', provider: opts.provider, providerToolName,
|
|
23
|
+
toolUseId: opts.toolUseId, turnNumber: opts.turnNumber, status: 'error',
|
|
24
|
+
mcp: { serverName: opts.serverName, toolName: opts.toolName, invocation: 'not_invoked', outcome: opts.outcome },
|
|
25
|
+
arguments: { ...args, server: opts.serverName, tool: opts.toolName, status: opts.outcome },
|
|
26
|
+
summary: `MCP tool ${providerToolName} ${opts.outcome}`,
|
|
27
|
+
confidence: 'high',
|
|
28
|
+
rawSnippet: JSON.stringify({ status: opts.outcome }),
|
|
29
|
+
};
|
|
30
|
+
}
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { TrialResult } from '../types.js';
|
|
|
5
5
|
import type { DiagnosticsReport } from './diagnostics.js';
|
|
6
6
|
import type { LLMPort } from '../utils/llm-types.js';
|
|
7
7
|
import type { McpSafetyOptions } from './mcp-safety.js';
|
|
8
|
+
import type { McpMockApprovalRule } from './mcp-mock-approvals.js';
|
|
8
9
|
export type AgentName = 'claude' | 'codex' | 'cursor' | 'opencode';
|
|
9
10
|
export type AgentInteractionMode = 'prompt' | 'start_chat' | 'conversation';
|
|
10
11
|
/** Runtime channel that actually executed the agent. */
|
|
@@ -28,6 +29,8 @@ export interface AgentOptions {
|
|
|
28
29
|
copyFromHome?: string[];
|
|
29
30
|
env?: Record<string, string>;
|
|
30
31
|
mcpMock?: MockMcpServerDescriptor | MockMcpServerDescriptor[];
|
|
32
|
+
/** Ordered, persistent simulated-user decisions for approval-required generated MCP tools. */
|
|
33
|
+
mcpMockApprovalRules?: McpMockApprovalRule[];
|
|
31
34
|
mcpConfigFile?: string;
|
|
32
35
|
/** Configure the conversation window for transcript-based agents. Set false to disable. */
|
|
33
36
|
conversationWindow?: ConversationWindowConfig | false;
|
|
@@ -352,7 +355,8 @@ export interface EvalResult {
|
|
|
352
355
|
scorers: ScorerResultEntry[];
|
|
353
356
|
tokenUsage?: TokenUsage;
|
|
354
357
|
}
|
|
355
|
-
export interface RecordedEvalResult extends EvalResult {
|
|
358
|
+
export interface RecordedEvalResult extends Omit<EvalResult, 'score'> {
|
|
359
|
+
score?: number;
|
|
356
360
|
trial?: TrialResult;
|
|
357
361
|
resultKind?: EvaluationResultKind;
|
|
358
362
|
scoringDurationMs?: number;
|
|
@@ -375,7 +379,7 @@ export interface ScorerResultEntry {
|
|
|
375
379
|
errorCode?: string;
|
|
376
380
|
}
|
|
377
381
|
export interface PathgradeTestMeta {
|
|
378
|
-
score
|
|
382
|
+
score?: number;
|
|
379
383
|
scorers: ScorerResultEntry[];
|
|
380
384
|
/** Stable identity for this evaluation definition across separate runs. */
|
|
381
385
|
evaluationDefinitionKey?: string;
|
package/dist/tool-events.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
export type ToolAction = 'run_shell' | 'read_file' | 'write_file' | 'edit_file' | 'search_code' | 'list_files' | 'ask_user' | 'web_fetch' | 'use_skill' | 'update_todos' | 'mcp_tool_call' | 'unknown';
|
|
1
|
+
export type ToolAction = 'run_shell' | 'read_file' | 'write_file' | 'edit_file' | 'search_code' | 'list_files' | 'ask_user' | 'web_fetch' | 'use_skill' | 'update_todos' | 'mcp_approval' | 'mcp_tool_call' | 'unknown';
|
|
2
|
+
export type McpToolCallClassification = {
|
|
3
|
+
serverName: string;
|
|
4
|
+
toolName: string;
|
|
5
|
+
} & ({
|
|
6
|
+
invocation: 'confirmed';
|
|
7
|
+
outcome: 'completed' | 'tool_error';
|
|
8
|
+
} | {
|
|
9
|
+
invocation: 'not_invoked';
|
|
10
|
+
outcome: 'user_denied' | 'policy_denied' | 'protocol_error';
|
|
11
|
+
} | {
|
|
12
|
+
invocation: 'unknown';
|
|
13
|
+
outcome: 'protocol_error' | 'unknown';
|
|
14
|
+
});
|
|
2
15
|
export interface ToolEvent {
|
|
3
16
|
action: ToolAction;
|
|
4
17
|
provider: 'claude' | 'codex' | 'cursor' | 'opencode';
|
|
@@ -9,6 +22,8 @@ export interface ToolEvent {
|
|
|
9
22
|
arguments?: Record<string, unknown>;
|
|
10
23
|
/** Lifecycle state observed by PathGrade. Absent on legacy provider events. */
|
|
11
24
|
status?: 'completed' | 'error' | 'incomplete';
|
|
25
|
+
/** Receipt/enforcement-backed classification for canonical MCP calls. */
|
|
26
|
+
mcp?: McpToolCallClassification;
|
|
12
27
|
/** Local wall-clock receive time for the invocation boundary. */
|
|
13
28
|
startedAt?: string;
|
|
14
29
|
/** Local wall-clock receive time for the matching result boundary. */
|
package/dist/types.d.ts
CHANGED
|
@@ -151,7 +151,7 @@ export interface LogEntry {
|
|
|
151
151
|
export interface TrialResult {
|
|
152
152
|
trial_id: number;
|
|
153
153
|
name?: string;
|
|
154
|
-
reward
|
|
154
|
+
reward?: number;
|
|
155
155
|
scorer_results: ScorerResult[];
|
|
156
156
|
duration_ms: number;
|
|
157
157
|
n_commands: number;
|
|
@@ -385,6 +385,8 @@ export interface AgentSessionOptions {
|
|
|
385
385
|
opencodeExecutable?: string;
|
|
386
386
|
/** Exact generated MCP tool names accepted by the OpenCode event normalizer. */
|
|
387
387
|
opencodeMcpToolNames?: string[];
|
|
388
|
+
/** Managed-session-owned runtime for scripted generated MCP. */
|
|
389
|
+
scriptedMcpHost?: import('./providers/scripted-mcp-mock-host.js').ScriptedMcpMockHost;
|
|
388
390
|
}
|
|
389
391
|
export declare abstract class BaseAgent {
|
|
390
392
|
createSession(runtime: EnvironmentHandle, runCommand: AgentCommandRunner, options?: AgentSessionOptions): Promise<AgentSession>;
|
package/dist/viewer.html
CHANGED
|
@@ -997,9 +997,9 @@
|
|
|
997
997
|
${trials}
|
|
998
998
|
`;
|
|
999
999
|
}
|
|
1000
|
-
|
|
1001
1000
|
function renderTrial(t, idx) {
|
|
1002
|
-
const
|
|
1001
|
+
const evaluated = t.reward !== undefined;
|
|
1002
|
+
const pass = evaluated && t.reward >= 0.5;
|
|
1003
1003
|
const dur = ((t.duration_ms || 0) / 1000).toFixed(1);
|
|
1004
1004
|
const tokens = (t.input_tokens || 0) + (t.output_tokens || 0);
|
|
1005
1005
|
const convTokens = (t.conversation_input_tokens || 0) + (t.conversation_output_tokens || 0);
|
|
@@ -1026,8 +1026,8 @@
|
|
|
1026
1026
|
<div class="trial-card" id="trial-${idx}">
|
|
1027
1027
|
<div class="trial-header" onclick="toggleTrial(${idx})">
|
|
1028
1028
|
<span class="trial-number">${t.name ? esc(t.name) : 'Trial ' + t.trial_id}</span>
|
|
1029
|
-
<span class="trial-reward ${pass ? 'pass' : 'fail'}">${t.reward.toFixed(2)}</span>
|
|
1030
|
-
<span class="badge ${pass ? 'badge-pass' : 'badge-fail'}">${pass ? 'PASS' : 'FAIL'}</span>
|
|
1029
|
+
<span class="trial-reward ${pass ? 'pass' : 'fail'}">${evaluated ? t.reward.toFixed(2) : 'n/a'}</span>
|
|
1030
|
+
<span class="badge ${pass ? 'badge-pass' : 'badge-fail'}">${evaluated ? (pass ? 'PASS' : 'FAIL') : 'N/A'}</span>
|
|
1031
1031
|
<span class="trial-meta">
|
|
1032
1032
|
<span>${dur}s</span>
|
|
1033
1033
|
<span>${t.n_commands || 0} cmds</span>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"packageManager": "yarn@4.12.0",
|
|
5
5
|
"description": "Evaluate whether AI agents discover and use your skills correctly",
|
|
6
6
|
"exports": {
|
|
@@ -125,6 +125,7 @@
|
|
|
125
125
|
"@anthropic-ai/claude-agent-sdk": "0.2.141",
|
|
126
126
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
127
127
|
"@types/node": "25.6.0",
|
|
128
|
+
"ajv": "8.20.0",
|
|
128
129
|
"fs-extra": "11.3.3",
|
|
129
130
|
"jiti": "2.6.1",
|
|
130
131
|
"picomatch": "^4.0.4",
|
|
@@ -132,5 +133,5 @@
|
|
|
132
133
|
"typescript": "^5.9.3",
|
|
133
134
|
"zod": "4.3.6"
|
|
134
135
|
},
|
|
135
|
-
"falconPackageHash": "
|
|
136
|
+
"falconPackageHash": "7c3967f299416b4436afa2b39609adc26ca8dbd4079ffeb875453235"
|
|
136
137
|
}
|