@wix/pathgrade 1.0.25 → 1.0.27
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 +44 -2
- package/dist/agents/claude/sdk-message-projector.js +5 -0
- package/dist/agents/claude/sdk-options.d.ts +1 -2
- package/dist/agents/claude.js +2 -1
- package/dist/agents/codex-app-server/agent.js +89 -52
- package/dist/agents/codex-app-server/scenario-mount.d.ts +40 -0
- package/dist/agents/codex-app-server/scenario-mount.js +238 -0
- package/dist/agents/codex.js +1 -0
- package/dist/agents/cursor-scenario.d.ts +20 -0
- package/dist/agents/cursor-scenario.js +96 -0
- package/dist/agents/cursor.js +21 -0
- package/dist/agents/opencode/scenario.d.ts +25 -0
- package/dist/agents/opencode/scenario.js +195 -0
- package/dist/agents/opencode.js +54 -2
- package/dist/commands/report.d.ts +10 -2
- package/dist/commands/report.js +25 -4
- package/dist/internal/direct-mcp-v2/acp-author-projector.d.ts +20 -0
- package/dist/internal/direct-mcp-v2/acp-author-projector.js +147 -0
- package/dist/internal/direct-mcp-v2/acp-author-session.d.ts +39 -0
- package/dist/internal/direct-mcp-v2/acp-author-session.js +201 -0
- package/dist/internal/direct-mcp-v2/acp-client.d.ts +70 -0
- package/dist/internal/direct-mcp-v2/acp-client.js +310 -0
- package/dist/internal/direct-mcp-v2/acp-direct-mcp.d.ts +38 -0
- package/dist/internal/direct-mcp-v2/acp-direct-mcp.js +326 -0
- package/dist/internal/direct-mcp-v2/claude-direct-mcp.d.ts +68 -0
- package/dist/internal/direct-mcp-v2/claude-direct-mcp.js +456 -0
- package/dist/internal/direct-mcp-v2/claude-profile.d.ts +9 -0
- package/dist/internal/direct-mcp-v2/claude-profile.js +97 -0
- package/dist/internal/direct-mcp-v2/cleanup.d.ts +62 -0
- package/dist/internal/direct-mcp-v2/cleanup.js +182 -0
- package/dist/internal/direct-mcp-v2/codex-direct-mcp.d.ts +41 -0
- package/dist/internal/direct-mcp-v2/codex-direct-mcp.js +436 -0
- package/dist/internal/direct-mcp-v2/codex-profile.d.ts +12 -0
- package/dist/internal/direct-mcp-v2/codex-profile.js +81 -0
- package/dist/internal/direct-mcp-v2/compiler.d.ts +12 -0
- package/dist/internal/direct-mcp-v2/compiler.js +449 -0
- package/dist/internal/direct-mcp-v2/cursor-direct-mcp.d.ts +14 -0
- package/dist/internal/direct-mcp-v2/cursor-direct-mcp.js +60 -0
- package/dist/internal/direct-mcp-v2/cursor-profile.d.ts +38 -0
- package/dist/internal/direct-mcp-v2/cursor-profile.js +79 -0
- package/dist/internal/direct-mcp-v2/evidence.d.ts +50 -0
- package/dist/internal/direct-mcp-v2/evidence.js +147 -0
- package/dist/internal/direct-mcp-v2/host-reducer.d.ts +61 -0
- package/dist/internal/direct-mcp-v2/host-reducer.js +214 -0
- package/dist/internal/direct-mcp-v2/json.d.ts +9 -0
- package/dist/internal/direct-mcp-v2/json.js +220 -0
- package/dist/internal/direct-mcp-v2/materialize.d.ts +39 -0
- package/dist/internal/direct-mcp-v2/materialize.js +22 -0
- package/dist/internal/direct-mcp-v2/opencode-direct-mcp.d.ts +14 -0
- package/dist/internal/direct-mcp-v2/opencode-direct-mcp.js +71 -0
- package/dist/internal/direct-mcp-v2/opencode-profile.d.ts +8 -0
- package/dist/internal/direct-mcp-v2/opencode-profile.js +52 -0
- package/dist/internal/direct-mcp-v2/projection.d.ts +40 -0
- package/dist/internal/direct-mcp-v2/projection.js +46 -0
- package/dist/internal/direct-mcp-v2/public-scenario-runtime.d.ts +51 -0
- package/dist/internal/direct-mcp-v2/public-scenario-runtime.js +353 -0
- package/dist/internal/direct-mcp-v2/runtime-lock.d.ts +51 -0
- package/dist/internal/direct-mcp-v2/runtime-lock.js +203 -0
- package/dist/internal/direct-mcp-v2/runtime-profile.d.ts +28 -0
- package/dist/internal/direct-mcp-v2/runtime-profile.js +163 -0
- package/dist/internal/direct-mcp-v2/scenario-http-host.d.ts +24 -0
- package/dist/internal/direct-mcp-v2/scenario-http-host.js +254 -0
- package/dist/internal/direct-mcp-v2/types.d.ts +233 -0
- package/dist/internal/direct-mcp-v2/types.js +1 -0
- package/dist/pathgrade.js +22 -1
- package/dist/reporters/github-comment.d.ts +12 -3
- package/dist/reporters/github-comment.js +80 -9
- package/dist/reporting/artifacts.js +5 -2
- package/dist/sdk/agent.js +56 -4
- package/dist/sdk/evaluate.d.ts +2 -0
- package/dist/sdk/evaluate.js +20 -12
- package/dist/sdk/index.d.ts +6 -0
- package/dist/sdk/index.js +4 -0
- package/dist/sdk/lifecycle.js +8 -3
- package/dist/sdk/managed-session.d.ts +6 -0
- package/dist/sdk/managed-session.js +157 -50
- package/dist/sdk/scenario-evidence.d.ts +27 -0
- package/dist/sdk/scenario-evidence.js +94 -0
- package/dist/sdk/scenario-machine-v2.d.ts +9 -0
- package/dist/sdk/scenario-machine-v2.js +37 -0
- package/dist/sdk/snapshots.d.ts +3 -0
- package/dist/sdk/snapshots.js +2 -0
- package/dist/sdk/types.d.ts +11 -1
- package/dist/tool-event-results.d.ts +1 -1
- package/dist/tool-event-results.js +2 -1
- package/dist/tool-events.d.ts +5 -0
- package/dist/tool-events.js +5 -0
- package/dist/types.d.ts +8 -0
- package/package.json +2 -2
|
@@ -10,6 +10,8 @@ import { attachTurnResultSensitiveValues, cloneTurnResultWithSensitiveValues, ge
|
|
|
10
10
|
import { attachToolEventSensitiveValues, getToolEventSensitiveValues } from './tool-event-secrets.js';
|
|
11
11
|
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
12
12
|
import { startScriptedMcpMockHost, } from '../providers/scripted-mcp-mock-host.js';
|
|
13
|
+
import { startPublicClaudeScenarioRuntime, } from '../internal/direct-mcp-v2/public-scenario-runtime.js';
|
|
14
|
+
import { TrialExecutionError } from '../internal/direct-mcp-v2/cleanup.js';
|
|
13
15
|
export function createManagedSession(deps) {
|
|
14
16
|
const { ws, agentName, timeoutSec, messages, log, model, conversationWindow, llm } = deps;
|
|
15
17
|
const agentTimeoutMs = timeoutSec * 1000;
|
|
@@ -40,9 +42,12 @@ export function createManagedSession(deps) {
|
|
|
40
42
|
let setupDone = false;
|
|
41
43
|
let currentSignal;
|
|
42
44
|
let scriptedHost;
|
|
45
|
+
let scenarioRuntime;
|
|
43
46
|
let activeTurn = false;
|
|
44
47
|
let nextTurnNumber = 0;
|
|
45
48
|
let disposePromise;
|
|
49
|
+
let primaryTurnError;
|
|
50
|
+
let inFlightTurn;
|
|
46
51
|
const runCommand = async (cmd) => {
|
|
47
52
|
const result = await ws.exec(cmd, { signal: currentSignal });
|
|
48
53
|
log.push(sanitizePersistenceValue({
|
|
@@ -62,67 +67,145 @@ export function createManagedSession(deps) {
|
|
|
62
67
|
if (remaining <= 0)
|
|
63
68
|
throw new Error(`${label} timed out`);
|
|
64
69
|
activeTurn = true;
|
|
70
|
+
let finishTurnJoin;
|
|
71
|
+
const turnJoin = new Promise((resolve) => { finishTurnJoin = resolve; });
|
|
72
|
+
inFlightTurn = turnJoin;
|
|
65
73
|
try {
|
|
66
74
|
return await withAbortTimeout(async (signal) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
try {
|
|
76
|
+
currentSignal = signal;
|
|
77
|
+
if (!session) {
|
|
78
|
+
// Run auth setup commands before first agent turn
|
|
79
|
+
if (!setupDone) {
|
|
80
|
+
for (const cmd of ws.setupCommands) {
|
|
81
|
+
await ws.exec(cmd, { signal });
|
|
82
|
+
signal.throwIfAborted();
|
|
83
|
+
}
|
|
84
|
+
setupDone = true;
|
|
85
|
+
}
|
|
86
|
+
// Carry the workspace's resolved env (from `prepareWorkspace
|
|
87
|
+
// → resolveCredentials`) into the runtime handle so drivers
|
|
88
|
+
// that auth through `Options.env` (notably the Claude SDK
|
|
89
|
+
// driver) can lift Anthropic keys out. Drivers that only
|
|
90
|
+
// need the workspace path keep using `getWorkspacePath`.
|
|
91
|
+
const runtime = {
|
|
92
|
+
handle: ws.path,
|
|
93
|
+
workspacePath: ws.path,
|
|
94
|
+
env: ws.env,
|
|
95
|
+
};
|
|
96
|
+
if (deps.scriptedMcp) {
|
|
97
|
+
scriptedHost = await startScriptedMcpMockHost(deps.scriptedMcp);
|
|
98
|
+
sessionOptions.scriptedMcpHost = scriptedHost;
|
|
99
|
+
}
|
|
100
|
+
if (deps.scenarioArtifact) {
|
|
101
|
+
const startScenarioRuntime = deps.scenarioRuntimeFactory ?? startPublicClaudeScenarioRuntime;
|
|
102
|
+
scenarioRuntime = await startScenarioRuntime({
|
|
103
|
+
artifact: deps.scenarioArtifact,
|
|
104
|
+
workspacePath: ws.path,
|
|
105
|
+
environment: ws.env,
|
|
106
|
+
onEvidence: deps.onScenarioEvidence,
|
|
107
|
+
});
|
|
108
|
+
try {
|
|
109
|
+
scenarioRuntime.configureSession(sessionOptions);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
try {
|
|
113
|
+
await scenarioRuntime.dispose();
|
|
114
|
+
}
|
|
115
|
+
catch (cleanupError) {
|
|
116
|
+
throw new AggregateError([error, cleanupError], 'Scenario provider projection failed and coordinator cleanup also failed');
|
|
117
|
+
}
|
|
118
|
+
scenarioRuntime = undefined;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
session = await createAgentSession(agent, runtime, runCommand, sessionOptions);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
try {
|
|
127
|
+
await scriptedHost?.dispose();
|
|
128
|
+
await scenarioRuntime?.dispose();
|
|
129
|
+
}
|
|
130
|
+
catch (cleanupError) {
|
|
131
|
+
throw new AggregateError([error, cleanupError], 'Agent session setup failed and scenario cleanup also failed');
|
|
132
|
+
}
|
|
133
|
+
scriptedHost = undefined;
|
|
134
|
+
scenarioRuntime = undefined;
|
|
135
|
+
throw error;
|
|
74
136
|
}
|
|
75
|
-
setupDone = true;
|
|
76
|
-
}
|
|
77
|
-
// Carry the workspace's resolved env (from `prepareWorkspace
|
|
78
|
-
// → resolveCredentials`) into the runtime handle so drivers
|
|
79
|
-
// that auth through `Options.env` (notably the Claude SDK
|
|
80
|
-
// driver) can lift Anthropic keys out. Drivers that only
|
|
81
|
-
// need the workspace path keep using `getWorkspacePath`.
|
|
82
|
-
const runtime = {
|
|
83
|
-
handle: ws.path,
|
|
84
|
-
workspacePath: ws.path,
|
|
85
|
-
env: ws.env,
|
|
86
|
-
};
|
|
87
|
-
if (deps.scriptedMcp) {
|
|
88
|
-
scriptedHost = await startScriptedMcpMockHost(deps.scriptedMcp);
|
|
89
|
-
sessionOptions.scriptedMcpHost = scriptedHost;
|
|
90
137
|
}
|
|
138
|
+
const turnNumber = ++nextTurnNumber;
|
|
139
|
+
scriptedHost?.beginTurn(turnNumber);
|
|
140
|
+
scenarioRuntime?.beginTurn(`turn-${turnNumber}`, signal);
|
|
141
|
+
let result;
|
|
91
142
|
try {
|
|
92
|
-
|
|
143
|
+
result = turnNumber === 1
|
|
144
|
+
? await session.start({ message })
|
|
145
|
+
: await session.reply({ message });
|
|
93
146
|
}
|
|
94
147
|
catch (error) {
|
|
95
|
-
|
|
96
|
-
|
|
148
|
+
try {
|
|
149
|
+
await scenarioRuntime?.failTurn(error);
|
|
150
|
+
}
|
|
151
|
+
catch (lifecycleError) {
|
|
152
|
+
if (lifecycleError instanceof TrialExecutionError) {
|
|
153
|
+
primaryTurnError = lifecycleError;
|
|
154
|
+
throw lifecycleError;
|
|
155
|
+
}
|
|
156
|
+
const combined = new AggregateError([error, lifecycleError], 'Provider turn failed and scenario settlement also failed');
|
|
157
|
+
primaryTurnError = combined;
|
|
158
|
+
throw combined;
|
|
159
|
+
}
|
|
160
|
+
primaryTurnError = error;
|
|
97
161
|
throw error;
|
|
98
162
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
if (result.exitCode !== 0 || result.timedOut) {
|
|
164
|
+
const providerTerminalError = new Error(`Provider terminal: exit ${result.exitCode}`);
|
|
165
|
+
primaryTurnError = providerTerminalError;
|
|
166
|
+
try {
|
|
167
|
+
await scenarioRuntime?.failTurn(providerTerminalError);
|
|
168
|
+
}
|
|
169
|
+
catch (lifecycleError) {
|
|
170
|
+
if (lifecycleError instanceof TrialExecutionError) {
|
|
171
|
+
primaryTurnError = lifecycleError;
|
|
172
|
+
throw lifecycleError;
|
|
173
|
+
}
|
|
174
|
+
const combined = new AggregateError([providerTerminalError, lifecycleError], 'Provider terminal failed and scenario settlement also failed');
|
|
175
|
+
primaryTurnError = combined;
|
|
176
|
+
throw combined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
await scenarioRuntime?.settleTurn();
|
|
181
|
+
}
|
|
182
|
+
if (scriptedHost) {
|
|
183
|
+
const settled = scriptedHost.settleEvents(result.toolEvents);
|
|
184
|
+
result.toolEvents = settled.events;
|
|
185
|
+
if (settled.error) {
|
|
186
|
+
result = cloneTurnResultWithSensitiveValues(result, {
|
|
187
|
+
exitCode: 1,
|
|
188
|
+
rawOutput: settled.error.message,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const sensitiveValues = [...new Set([
|
|
193
|
+
...(deps.sensitiveValues ?? []),
|
|
194
|
+
...getTurnResultSensitiveValues(result),
|
|
195
|
+
])];
|
|
196
|
+
for (const event of result.toolEvents) {
|
|
197
|
+
attachToolEventSensitiveValues(event, [...new Set([
|
|
198
|
+
...sensitiveValues,
|
|
199
|
+
...getToolEventSensitiveValues(event),
|
|
200
|
+
])]);
|
|
113
201
|
}
|
|
202
|
+
return attachTurnResultSensitiveValues(result, sensitiveValues);
|
|
114
203
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
for (const event of result.toolEvents) {
|
|
120
|
-
attachToolEventSensitiveValues(event, [...new Set([
|
|
121
|
-
...sensitiveValues,
|
|
122
|
-
...getToolEventSensitiveValues(event),
|
|
123
|
-
])]);
|
|
204
|
+
finally {
|
|
205
|
+
finishTurnJoin();
|
|
206
|
+
if (inFlightTurn === turnJoin)
|
|
207
|
+
inFlightTurn = undefined;
|
|
124
208
|
}
|
|
125
|
-
return attachTurnResultSensitiveValues(result, sensitiveValues);
|
|
126
209
|
}, remaining, label);
|
|
127
210
|
}
|
|
128
211
|
finally {
|
|
@@ -166,12 +249,36 @@ export function createManagedSession(deps) {
|
|
|
166
249
|
if (disposePromise)
|
|
167
250
|
return disposePromise;
|
|
168
251
|
disposePromise = (async () => {
|
|
252
|
+
const cleanupErrors = [];
|
|
169
253
|
try {
|
|
170
254
|
await session?.dispose?.();
|
|
171
255
|
}
|
|
172
|
-
|
|
256
|
+
catch (error) {
|
|
257
|
+
cleanupErrors.push(error);
|
|
258
|
+
}
|
|
259
|
+
if (inFlightTurn) {
|
|
260
|
+
try {
|
|
261
|
+
await withAbortTimeout(async () => inFlightTurn, Math.min(agentTimeoutMs, 2_000), 'Agent turn cleanup');
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
cleanupErrors.push(error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
173
268
|
await scriptedHost?.dispose();
|
|
174
269
|
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
cleanupErrors.push(error);
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
await scenarioRuntime?.dispose();
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
cleanupErrors.push(error);
|
|
278
|
+
}
|
|
279
|
+
if (cleanupErrors.length > 0) {
|
|
280
|
+
throw new AggregateError(primaryTurnError === undefined ? cleanupErrors : [primaryTurnError, ...cleanupErrors], primaryTurnError === undefined ? 'Agent session cleanup failed' : 'Provider turn failed and cleanup also failed');
|
|
281
|
+
}
|
|
175
282
|
})();
|
|
176
283
|
return disposePromise;
|
|
177
284
|
},
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { EvidenceEnvelope } from '../internal/direct-mcp-v2/types.js';
|
|
2
|
+
export type ExpectedScenarioCaseMatch = {
|
|
3
|
+
caseId?: string;
|
|
4
|
+
serverId?: string;
|
|
5
|
+
toolId?: string;
|
|
6
|
+
turnId?: string;
|
|
7
|
+
};
|
|
8
|
+
export type ScenarioCaseMatchEvidence = {
|
|
9
|
+
trialId: string;
|
|
10
|
+
turnId: string;
|
|
11
|
+
invocationId: string;
|
|
12
|
+
serverId: string;
|
|
13
|
+
toolId: string;
|
|
14
|
+
caseId: string;
|
|
15
|
+
stateBefore: string;
|
|
16
|
+
};
|
|
17
|
+
export type ScenarioStateTransitionEvidence = ScenarioCaseMatchEvidence & {
|
|
18
|
+
stateAfter: string;
|
|
19
|
+
committedEpoch: number;
|
|
20
|
+
};
|
|
21
|
+
export declare class ScenarioEvidenceError extends Error {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
export declare function findScenarioCaseMatches(evidence: readonly EvidenceEnvelope[], expected?: ExpectedScenarioCaseMatch): ScenarioCaseMatchEvidence[];
|
|
25
|
+
export declare function wasScenarioCaseMatched(evidence: readonly EvidenceEnvelope[], expected: ExpectedScenarioCaseMatch): boolean;
|
|
26
|
+
export declare function getScenarioStateTimeline(evidence: readonly EvidenceEnvelope[]): ScenarioStateTransitionEvidence[];
|
|
27
|
+
export declare function getFinalScenarioState(evidence: readonly EvidenceEnvelope[]): string;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { verifyEvidenceChain } from '../internal/direct-mcp-v2/evidence.js';
|
|
2
|
+
export class ScenarioEvidenceError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'ScenarioEvidenceError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function findScenarioCaseMatches(evidence, expected = {}) {
|
|
9
|
+
const invocations = canonicalInvocations(evidence);
|
|
10
|
+
const matches = [];
|
|
11
|
+
for (const envelope of evidence) {
|
|
12
|
+
const decision = envelope.payload;
|
|
13
|
+
if (decision.type !== 'mcp.decision' || decision.outcome !== 'matched' || !decision.caseId)
|
|
14
|
+
continue;
|
|
15
|
+
const invocation = invocations.get(decision.invocationId);
|
|
16
|
+
if (!invocation)
|
|
17
|
+
throw new ScenarioEvidenceError(`matched decision references unknown invocation ${decision.invocationId}`);
|
|
18
|
+
const match = { ...invocation, caseId: decision.caseId };
|
|
19
|
+
if (matchesExpectedCase(match, expected))
|
|
20
|
+
matches.push(match);
|
|
21
|
+
}
|
|
22
|
+
return matches;
|
|
23
|
+
}
|
|
24
|
+
export function wasScenarioCaseMatched(evidence, expected) {
|
|
25
|
+
return findScenarioCaseMatches(evidence, expected).length > 0;
|
|
26
|
+
}
|
|
27
|
+
export function getScenarioStateTimeline(evidence) {
|
|
28
|
+
const matches = new Map(findScenarioCaseMatches(evidence).map((entry) => [entry.invocationId, entry]));
|
|
29
|
+
const timeline = [];
|
|
30
|
+
for (const envelope of evidence) {
|
|
31
|
+
const commit = envelope.payload;
|
|
32
|
+
if (commit.type !== 'mcp.commit')
|
|
33
|
+
continue;
|
|
34
|
+
const match = matches.get(commit.invocationId);
|
|
35
|
+
if (!match)
|
|
36
|
+
throw new ScenarioEvidenceError(`commit references unmatched invocation ${commit.invocationId}`);
|
|
37
|
+
if (match.stateBefore !== commit.stateBefore) {
|
|
38
|
+
throw new ScenarioEvidenceError(`commit state disagrees with invocation ${commit.invocationId}`);
|
|
39
|
+
}
|
|
40
|
+
timeline.push({
|
|
41
|
+
...match,
|
|
42
|
+
stateAfter: commit.stateAfter,
|
|
43
|
+
committedEpoch: commit.committedEpoch,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return timeline;
|
|
47
|
+
}
|
|
48
|
+
export function getFinalScenarioState(evidence) {
|
|
49
|
+
assertCanonicalEvidence(evidence);
|
|
50
|
+
const payload = evidence.at(-1)?.payload;
|
|
51
|
+
if (payload?.type === 'turn.finalized' && payload.state !== undefined)
|
|
52
|
+
return payload.state;
|
|
53
|
+
throw new ScenarioEvidenceError('canonical evidence does not end with a finalized scenario state');
|
|
54
|
+
}
|
|
55
|
+
function canonicalInvocations(evidence) {
|
|
56
|
+
assertCanonicalEvidence(evidence);
|
|
57
|
+
const invocations = new Map();
|
|
58
|
+
for (const envelope of evidence) {
|
|
59
|
+
const invocation = envelope.payload;
|
|
60
|
+
if (invocation.type !== 'mcp.invocation')
|
|
61
|
+
continue;
|
|
62
|
+
if (invocations.has(invocation.invocationId)) {
|
|
63
|
+
throw new ScenarioEvidenceError(`duplicate invocation ${invocation.invocationId}`);
|
|
64
|
+
}
|
|
65
|
+
invocations.set(invocation.invocationId, {
|
|
66
|
+
trialId: envelope.trialId,
|
|
67
|
+
turnId: envelope.turnId,
|
|
68
|
+
invocationId: invocation.invocationId,
|
|
69
|
+
serverId: invocation.serverId,
|
|
70
|
+
toolId: invocation.toolId,
|
|
71
|
+
stateBefore: invocation.stateBefore,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return invocations;
|
|
75
|
+
}
|
|
76
|
+
function assertCanonicalEvidence(evidence) {
|
|
77
|
+
const first = evidence[0];
|
|
78
|
+
if (!first)
|
|
79
|
+
throw new ScenarioEvidenceError('canonical evidence is empty');
|
|
80
|
+
const chain = verifyEvidenceChain(evidence, first.artifactDigest);
|
|
81
|
+
if (!chain.ok)
|
|
82
|
+
throw new ScenarioEvidenceError(`canonical evidence chain failed at index ${chain.index}: ${chain.reason}`);
|
|
83
|
+
for (const [index, envelope] of evidence.entries()) {
|
|
84
|
+
if (envelope.trialId !== first.trialId) {
|
|
85
|
+
throw new ScenarioEvidenceError(`canonical evidence changes trialId at index ${index}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function matchesExpectedCase(match, expected) {
|
|
90
|
+
return (expected.caseId === undefined || match.caseId === expected.caseId)
|
|
91
|
+
&& (expected.serverId === undefined || match.serverId === expected.serverId)
|
|
92
|
+
&& (expected.toolId === undefined || match.toolId === expected.toolId)
|
|
93
|
+
&& (expected.turnId === undefined || match.turnId === expected.turnId);
|
|
94
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CompileScenarioResult } from '../internal/direct-mcp-v2/compiler.js';
|
|
2
|
+
import type { ScenarioMachineV2 as InternalScenarioMachineV2 } from '../internal/direct-mcp-v2/types.js';
|
|
3
|
+
/** Compile the public V2 source contract, whose concurrency safety policy is fixed by the version. */
|
|
4
|
+
export declare function compileScenario(source: unknown): CompileScenarioResult;
|
|
5
|
+
/** Compile strict JSON text using the public V2 source contract. */
|
|
6
|
+
export declare function compileScenarioText(text: string): CompileScenarioResult;
|
|
7
|
+
export type { CompileScenarioResult } from '../internal/direct-mcp-v2/compiler.js';
|
|
8
|
+
export type ScenarioMachineV2 = Pick<InternalScenarioMachineV2, 'version' | 'scenarioId' | 'initialState' | 'servers' | 'states'>;
|
|
9
|
+
export type { EvidenceEnvelope, JsonObject, JsonPrimitive, JsonValue, MatchExpr, MatchPredicate, McpContent, ScenarioArtifact, ScenarioCase, ScenarioDiagnostic, ScenarioEvidence, ScenarioRejection, ScenarioSuccess, ScenarioToolError, } from '../internal/direct-mcp-v2/types.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { compileScenario as compileInternalScenario } from '../internal/direct-mcp-v2/compiler.js';
|
|
2
|
+
import { canonicalJson, parseStrictJson } from '../internal/direct-mcp-v2/json.js';
|
|
3
|
+
const V2_CONCURRENCY_POLICY = 'poison-effectful-overlap';
|
|
4
|
+
function object(value) {
|
|
5
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/** Compile the public V2 source contract, whose concurrency safety policy is fixed by the version. */
|
|
8
|
+
export function compileScenario(source) {
|
|
9
|
+
try {
|
|
10
|
+
canonicalJson(source);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return { ok: false, diagnostics: [{ code: 'INVALID_JSON_VALUE', path: '$', message: error instanceof Error ? error.message : String(error) }] };
|
|
14
|
+
}
|
|
15
|
+
if (!object(source))
|
|
16
|
+
return compileInternalScenario(source);
|
|
17
|
+
const suppliedConcurrency = Object.prototype.hasOwnProperty.call(source, 'concurrency');
|
|
18
|
+
const compiled = compileInternalScenario({ ...source, concurrency: V2_CONCURRENCY_POLICY });
|
|
19
|
+
if (!suppliedConcurrency)
|
|
20
|
+
return compiled;
|
|
21
|
+
const diagnostic = { code: 'UNKNOWN_FIELD', path: '$/concurrency', message: 'unknown field concurrency' };
|
|
22
|
+
if (compiled.ok)
|
|
23
|
+
return { ok: false, diagnostics: [diagnostic] };
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
diagnostics: [...compiled.diagnostics, diagnostic].sort((left, right) => canonicalJson(left).localeCompare(canonicalJson(right), 'en')),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Compile strict JSON text using the public V2 source contract. */
|
|
30
|
+
export function compileScenarioText(text) {
|
|
31
|
+
try {
|
|
32
|
+
return compileScenario(parseStrictJson(text));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return { ok: false, diagnostics: [{ code: 'INVALID_JSON_TEXT', path: '$', message: error instanceof Error ? error.message : String(error) }] };
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/sdk/snapshots.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LogEntry } from '../types.js';
|
|
2
2
|
import type { ToolEvent } from '../tool-events.js';
|
|
3
3
|
import type { AgentName, ConversationResult, Message, TurnTiming } from './types.js';
|
|
4
|
+
import type { EvidenceEnvelope } from '../internal/direct-mcp-v2/types.js';
|
|
4
5
|
export declare const RUN_SNAPSHOT_VERSION = 2;
|
|
5
6
|
export interface RunSnapshot {
|
|
6
7
|
version: 1 | 2;
|
|
@@ -17,6 +18,7 @@ export interface RunSnapshot {
|
|
|
17
18
|
turnTimings: TurnTiming[];
|
|
18
19
|
};
|
|
19
20
|
workspace: string | null;
|
|
21
|
+
scenarioEvidence?: readonly EvidenceEnvelope[];
|
|
20
22
|
}
|
|
21
23
|
export declare function buildRunSnapshot(params: {
|
|
22
24
|
agent: AgentName;
|
|
@@ -26,6 +28,7 @@ export declare function buildRunSnapshot(params: {
|
|
|
26
28
|
workspace: string | null;
|
|
27
29
|
timestamp?: string;
|
|
28
30
|
sensitiveValues?: readonly string[];
|
|
31
|
+
scenarioEvidence?: readonly EvidenceEnvelope[];
|
|
29
32
|
}): RunSnapshot;
|
|
30
33
|
export declare class SnapshotParseError extends Error {
|
|
31
34
|
constructor(message: string, options?: {
|
package/dist/sdk/snapshots.js
CHANGED
|
@@ -20,6 +20,7 @@ export function buildRunSnapshot(params) {
|
|
|
20
20
|
turnTimings: [...conversationResult.turnTimings],
|
|
21
21
|
},
|
|
22
22
|
workspace,
|
|
23
|
+
...(params.scenarioEvidence ? { scenarioEvidence: structuredClone(params.scenarioEvidence) } : {}),
|
|
23
24
|
}, params.sensitiveValues);
|
|
24
25
|
}
|
|
25
26
|
export class SnapshotParseError extends Error {
|
|
@@ -153,5 +154,6 @@ function validateRunSnapshot(input) {
|
|
|
153
154
|
turnTimings: snapshot.conversationResult.turnTimings,
|
|
154
155
|
},
|
|
155
156
|
workspace: typeof snapshot.workspace === 'string' ? snapshot.workspace : null,
|
|
157
|
+
scenarioEvidence: Array.isArray(snapshot.scenarioEvidence) ? snapshot.scenarioEvidence : [],
|
|
156
158
|
};
|
|
157
159
|
}
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -6,15 +6,19 @@ 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
8
|
import type { McpMockApprovalRule } from './mcp-mock-approvals.js';
|
|
9
|
+
import type { EvidenceEnvelope } from '../internal/direct-mcp-v2/types.js';
|
|
10
|
+
import type { ScenarioMachineV2 } from './scenario-machine-v2.js';
|
|
9
11
|
export type AgentName = 'claude' | 'codex' | 'cursor' | 'opencode';
|
|
10
12
|
export type AgentInteractionMode = 'prompt' | 'start_chat' | 'conversation';
|
|
11
13
|
/** Runtime channel that actually executed the agent. */
|
|
12
|
-
export type AgentExecutionTransport = AgentTransport | 'claude-agent-sdk' | 'cursor-agent';
|
|
14
|
+
export type AgentExecutionTransport = AgentTransport | 'claude-agent-sdk' | 'cursor-agent' | 'acp';
|
|
13
15
|
/** Privacy-safe execution dimensions exposed to result observers. */
|
|
14
16
|
export interface AgentExecutionMetadata {
|
|
15
17
|
name: AgentName;
|
|
16
18
|
/** Model override requested by the caller; not necessarily the provider-resolved model. */
|
|
17
19
|
requestedModel?: string;
|
|
20
|
+
/** Concrete model observed or selected for the executed turn. */
|
|
21
|
+
resolvedModel?: string;
|
|
18
22
|
transport?: AgentExecutionTransport;
|
|
19
23
|
interactionMode?: AgentInteractionMode;
|
|
20
24
|
}
|
|
@@ -29,6 +33,8 @@ export interface AgentOptions {
|
|
|
29
33
|
copyFromHome?: string[];
|
|
30
34
|
env?: Record<string, string>;
|
|
31
35
|
mcpMock?: MockMcpServerDescriptor | MockMcpServerDescriptor[];
|
|
36
|
+
/** Host-authoritative direct MCP scenario for explicit Claude, Codex app-server, OpenCode, and admitted Cursor runtimes. */
|
|
37
|
+
mcpScenario?: ScenarioMachineV2;
|
|
32
38
|
/** Ordered, persistent simulated-user decisions for approval-required generated MCP tools. */
|
|
33
39
|
mcpMockApprovalRules?: McpMockApprovalRule[];
|
|
34
40
|
mcpConfigFile?: string;
|
|
@@ -85,6 +91,8 @@ export interface Agent {
|
|
|
85
91
|
readonly workspace: string;
|
|
86
92
|
/** Provider/runtime metadata only; never contains prompts or paths. */
|
|
87
93
|
readonly executionMetadata?: AgentExecutionMetadata;
|
|
94
|
+
/** Immutable canonical ScenarioMachineV2 evidence. Pathgrade agents return an empty array when no scenario is configured. */
|
|
95
|
+
readonly scenarioEvidence?: readonly EvidenceEnvelope[];
|
|
88
96
|
dispose(): Promise<void>;
|
|
89
97
|
}
|
|
90
98
|
export interface ConverseOptions {
|
|
@@ -337,6 +345,8 @@ export interface ScorerContext {
|
|
|
337
345
|
toolEvents: import('../tool-events.js').ToolEvent[];
|
|
338
346
|
runCommand: (cmd: string) => Promise<CommandResult>;
|
|
339
347
|
artifacts: SessionArtifacts;
|
|
348
|
+
/** Canonical host evidence for ScenarioMachineV2 scorers. */
|
|
349
|
+
scenarioEvidence?: readonly EvidenceEnvelope[];
|
|
340
350
|
}
|
|
341
351
|
export interface EvaluateOptions {
|
|
342
352
|
failFast?: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolEventResult } from './tool-events.js';
|
|
2
2
|
export declare const TOOL_RESULT_MAX_CHARS: number;
|
|
3
|
-
export declare function collectSensitiveEnvValues(env?: Readonly<Record<string, string>>): string[];
|
|
3
|
+
export declare function collectSensitiveEnvValues(env?: Readonly<Record<string, string | undefined>>): string[];
|
|
4
4
|
/**
|
|
5
5
|
* Clone a persistence payload while removing secrets from both structured
|
|
6
6
|
* containers and any other strings that repeat their values. The second pass
|
|
@@ -41,6 +41,7 @@ const NON_SECRET_ENVIRONMENT_KEY_NAMES = new Set(['tokencount', 'tokenizersparal
|
|
|
41
41
|
const BOUNDARY_ONLY_SENSITIVE_VALUE_MAX_LENGTH = 1;
|
|
42
42
|
export function collectSensitiveEnvValues(env) {
|
|
43
43
|
return [...new Set(Object.entries(env ?? {})
|
|
44
|
+
.filter((entry) => typeof entry[1] === 'string')
|
|
44
45
|
.filter(([key, value]) => isSecretEnvironmentKey(key) && value.length > 0)
|
|
45
46
|
.map(([, value]) => value))]
|
|
46
47
|
.sort((a, b) => b.length - a.length);
|
|
@@ -153,7 +154,7 @@ function redactCredentialShapes(value, sensitiveValues, explicitSensitiveValues)
|
|
|
153
154
|
redacted = redacted.replace(/([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s/@]+(@)/gi, '$1<redacted>$2');
|
|
154
155
|
}
|
|
155
156
|
if (redacted.includes('=') || redacted.includes(':')) {
|
|
156
|
-
redacted = redacted.replace(/(\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|passwd|session(?:id)?|cookie)\s*[=:]\s*)[^\s,;"'}]+/gi, '$1<redacted>');
|
|
157
|
+
redacted = redacted.replace(/(\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|token|secret|auth|password|passwd|session(?:id)?|cookie)\s*[=:]\s*)[^\s,;"'}]+/gi, '$1<redacted>');
|
|
157
158
|
}
|
|
158
159
|
return redacted;
|
|
159
160
|
}
|
package/dist/tool-events.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ export declare function extractToolEventsFromLog(log: ReadonlyArray<{
|
|
|
64
64
|
type: string;
|
|
65
65
|
tool_event?: ToolEvent;
|
|
66
66
|
}>): ToolEvent[];
|
|
67
|
+
/** Count shell commands across legacy command logs and provider tool events. */
|
|
68
|
+
export declare function countShellCommandsFromLog(log: ReadonlyArray<{
|
|
69
|
+
type: string;
|
|
70
|
+
tool_event?: ToolEvent;
|
|
71
|
+
}>): number;
|
|
67
72
|
/**
|
|
68
73
|
* Extract deduplicated skill names from tool events that have action 'use_skill'.
|
|
69
74
|
*/
|
package/dist/tool-events.js
CHANGED
|
@@ -101,6 +101,11 @@ export function extractToolEventsFromLog(log) {
|
|
|
101
101
|
.filter((e) => e.type === 'tool_event' && e.tool_event != null)
|
|
102
102
|
.map((e) => e.tool_event);
|
|
103
103
|
}
|
|
104
|
+
/** Count shell commands across legacy command logs and provider tool events. */
|
|
105
|
+
export function countShellCommandsFromLog(log) {
|
|
106
|
+
return log.filter((entry) => entry.type === 'command'
|
|
107
|
+
|| (entry.type === 'tool_event' && entry.tool_event?.action === 'run_shell')).length;
|
|
108
|
+
}
|
|
104
109
|
/**
|
|
105
110
|
* Extract deduplicated skill names from tool events that have action 'use_skill'.
|
|
106
111
|
*/
|
package/dist/types.d.ts
CHANGED
|
@@ -175,6 +175,8 @@ export interface TrialResult {
|
|
|
175
175
|
*/
|
|
176
176
|
total_cost_usd?: number;
|
|
177
177
|
session_log: LogEntry[];
|
|
178
|
+
/** Redacted canonical ScenarioMachineV2 evidence for offline scoring and audit. */
|
|
179
|
+
scenario_evidence?: readonly import('./internal/direct-mcp-v2/types.js').EvidenceEnvelope[];
|
|
178
180
|
skills_used?: string[];
|
|
179
181
|
diagnostics?: DiagnosticsReport;
|
|
180
182
|
result_kind?: import('./sdk/types.js').EvaluationResultKind;
|
|
@@ -289,6 +291,8 @@ export interface AgentTurnResult {
|
|
|
289
291
|
timedOut?: boolean;
|
|
290
292
|
toolEvents: import('./tool-events.js').ToolEvent[];
|
|
291
293
|
runtimePoliciesApplied?: RuntimePolicyDescriptor[];
|
|
294
|
+
/** Concrete model that executed this turn, as resolved by the provider or runner. */
|
|
295
|
+
resolvedModel?: string;
|
|
292
296
|
inputTokens?: number;
|
|
293
297
|
outputTokens?: number;
|
|
294
298
|
/**
|
|
@@ -390,6 +394,10 @@ export interface AgentSessionOptions {
|
|
|
390
394
|
opencodeMcpToolNames?: string[];
|
|
391
395
|
/** Managed-session-owned runtime for scripted generated MCP. */
|
|
392
396
|
scriptedMcpHost?: import('./providers/scripted-mcp-mock-host.js').ScriptedMcpMockHost;
|
|
397
|
+
/** Parent-owned public ScenarioMachineV2 bridge; never serialized into child config. */
|
|
398
|
+
scenarioMcpServers?: Record<string, import('@anthropic-ai/claude-agent-sdk').McpSdkServerConfigWithInstance>;
|
|
399
|
+
/** Parent-owned admitted Codex app-server mount; projected only into thread/start memory. */
|
|
400
|
+
codexScenarioMount?: import('./agents/codex-app-server/scenario-mount.js').CodexScenarioMount;
|
|
393
401
|
/** Runtime-only values inherited by drivers and persistence sinks for redaction. */
|
|
394
402
|
sensitiveValues?: readonly string[];
|
|
395
403
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.27",
|
|
4
4
|
"packageManager": "yarn@4.12.0",
|
|
5
5
|
"description": "Evaluate whether AI agents discover and use your skills correctly",
|
|
6
6
|
"exports": {
|
|
@@ -140,5 +140,5 @@
|
|
|
140
140
|
"typescript": "^5.9.3",
|
|
141
141
|
"zod": "4.3.6"
|
|
142
142
|
},
|
|
143
|
-
"falconPackageHash": "
|
|
143
|
+
"falconPackageHash": "5c7c088ce2f8c51308dde9f92d3f259465bbf34021afddb3af39d2d9"
|
|
144
144
|
}
|