@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.
Files changed (89) hide show
  1. package/README.md +44 -2
  2. package/dist/agents/claude/sdk-message-projector.js +5 -0
  3. package/dist/agents/claude/sdk-options.d.ts +1 -2
  4. package/dist/agents/claude.js +2 -1
  5. package/dist/agents/codex-app-server/agent.js +89 -52
  6. package/dist/agents/codex-app-server/scenario-mount.d.ts +40 -0
  7. package/dist/agents/codex-app-server/scenario-mount.js +238 -0
  8. package/dist/agents/codex.js +1 -0
  9. package/dist/agents/cursor-scenario.d.ts +20 -0
  10. package/dist/agents/cursor-scenario.js +96 -0
  11. package/dist/agents/cursor.js +21 -0
  12. package/dist/agents/opencode/scenario.d.ts +25 -0
  13. package/dist/agents/opencode/scenario.js +195 -0
  14. package/dist/agents/opencode.js +54 -2
  15. package/dist/commands/report.d.ts +10 -2
  16. package/dist/commands/report.js +25 -4
  17. package/dist/internal/direct-mcp-v2/acp-author-projector.d.ts +20 -0
  18. package/dist/internal/direct-mcp-v2/acp-author-projector.js +147 -0
  19. package/dist/internal/direct-mcp-v2/acp-author-session.d.ts +39 -0
  20. package/dist/internal/direct-mcp-v2/acp-author-session.js +201 -0
  21. package/dist/internal/direct-mcp-v2/acp-client.d.ts +70 -0
  22. package/dist/internal/direct-mcp-v2/acp-client.js +310 -0
  23. package/dist/internal/direct-mcp-v2/acp-direct-mcp.d.ts +38 -0
  24. package/dist/internal/direct-mcp-v2/acp-direct-mcp.js +326 -0
  25. package/dist/internal/direct-mcp-v2/claude-direct-mcp.d.ts +68 -0
  26. package/dist/internal/direct-mcp-v2/claude-direct-mcp.js +456 -0
  27. package/dist/internal/direct-mcp-v2/claude-profile.d.ts +9 -0
  28. package/dist/internal/direct-mcp-v2/claude-profile.js +97 -0
  29. package/dist/internal/direct-mcp-v2/cleanup.d.ts +62 -0
  30. package/dist/internal/direct-mcp-v2/cleanup.js +182 -0
  31. package/dist/internal/direct-mcp-v2/codex-direct-mcp.d.ts +41 -0
  32. package/dist/internal/direct-mcp-v2/codex-direct-mcp.js +436 -0
  33. package/dist/internal/direct-mcp-v2/codex-profile.d.ts +12 -0
  34. package/dist/internal/direct-mcp-v2/codex-profile.js +81 -0
  35. package/dist/internal/direct-mcp-v2/compiler.d.ts +12 -0
  36. package/dist/internal/direct-mcp-v2/compiler.js +449 -0
  37. package/dist/internal/direct-mcp-v2/cursor-direct-mcp.d.ts +14 -0
  38. package/dist/internal/direct-mcp-v2/cursor-direct-mcp.js +60 -0
  39. package/dist/internal/direct-mcp-v2/cursor-profile.d.ts +38 -0
  40. package/dist/internal/direct-mcp-v2/cursor-profile.js +79 -0
  41. package/dist/internal/direct-mcp-v2/evidence.d.ts +50 -0
  42. package/dist/internal/direct-mcp-v2/evidence.js +147 -0
  43. package/dist/internal/direct-mcp-v2/host-reducer.d.ts +61 -0
  44. package/dist/internal/direct-mcp-v2/host-reducer.js +214 -0
  45. package/dist/internal/direct-mcp-v2/json.d.ts +9 -0
  46. package/dist/internal/direct-mcp-v2/json.js +220 -0
  47. package/dist/internal/direct-mcp-v2/materialize.d.ts +39 -0
  48. package/dist/internal/direct-mcp-v2/materialize.js +22 -0
  49. package/dist/internal/direct-mcp-v2/opencode-direct-mcp.d.ts +14 -0
  50. package/dist/internal/direct-mcp-v2/opencode-direct-mcp.js +71 -0
  51. package/dist/internal/direct-mcp-v2/opencode-profile.d.ts +8 -0
  52. package/dist/internal/direct-mcp-v2/opencode-profile.js +52 -0
  53. package/dist/internal/direct-mcp-v2/projection.d.ts +40 -0
  54. package/dist/internal/direct-mcp-v2/projection.js +46 -0
  55. package/dist/internal/direct-mcp-v2/public-scenario-runtime.d.ts +51 -0
  56. package/dist/internal/direct-mcp-v2/public-scenario-runtime.js +353 -0
  57. package/dist/internal/direct-mcp-v2/runtime-lock.d.ts +51 -0
  58. package/dist/internal/direct-mcp-v2/runtime-lock.js +203 -0
  59. package/dist/internal/direct-mcp-v2/runtime-profile.d.ts +28 -0
  60. package/dist/internal/direct-mcp-v2/runtime-profile.js +163 -0
  61. package/dist/internal/direct-mcp-v2/scenario-http-host.d.ts +24 -0
  62. package/dist/internal/direct-mcp-v2/scenario-http-host.js +254 -0
  63. package/dist/internal/direct-mcp-v2/types.d.ts +233 -0
  64. package/dist/internal/direct-mcp-v2/types.js +1 -0
  65. package/dist/pathgrade.js +22 -1
  66. package/dist/reporters/github-comment.d.ts +12 -3
  67. package/dist/reporters/github-comment.js +80 -9
  68. package/dist/reporting/artifacts.js +5 -2
  69. package/dist/sdk/agent.js +56 -4
  70. package/dist/sdk/evaluate.d.ts +2 -0
  71. package/dist/sdk/evaluate.js +20 -12
  72. package/dist/sdk/index.d.ts +6 -0
  73. package/dist/sdk/index.js +4 -0
  74. package/dist/sdk/lifecycle.js +8 -3
  75. package/dist/sdk/managed-session.d.ts +6 -0
  76. package/dist/sdk/managed-session.js +157 -50
  77. package/dist/sdk/scenario-evidence.d.ts +27 -0
  78. package/dist/sdk/scenario-evidence.js +94 -0
  79. package/dist/sdk/scenario-machine-v2.d.ts +9 -0
  80. package/dist/sdk/scenario-machine-v2.js +37 -0
  81. package/dist/sdk/snapshots.d.ts +3 -0
  82. package/dist/sdk/snapshots.js +2 -0
  83. package/dist/sdk/types.d.ts +11 -1
  84. package/dist/tool-event-results.d.ts +1 -1
  85. package/dist/tool-event-results.js +2 -1
  86. package/dist/tool-events.d.ts +5 -0
  87. package/dist/tool-events.js +5 -0
  88. package/dist/types.d.ts +8 -0
  89. 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
- currentSignal = signal;
68
- if (!session) {
69
- // Run auth setup commands before first agent turn
70
- if (!setupDone) {
71
- for (const cmd of ws.setupCommands) {
72
- await ws.exec(cmd, { signal });
73
- signal.throwIfAborted();
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
- session = await createAgentSession(agent, runtime, runCommand, sessionOptions);
143
+ result = turnNumber === 1
144
+ ? await session.start({ message })
145
+ : await session.reply({ message });
93
146
  }
94
147
  catch (error) {
95
- await scriptedHost?.dispose();
96
- scriptedHost = undefined;
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
- const turnNumber = ++nextTurnNumber;
101
- scriptedHost?.beginTurn(turnNumber);
102
- let result = turnNumber === 1
103
- ? await session.start({ message })
104
- : await session.reply({ message });
105
- if (scriptedHost) {
106
- const settled = scriptedHost.settleEvents(result.toolEvents);
107
- result.toolEvents = settled.events;
108
- if (settled.error) {
109
- result = cloneTurnResultWithSensitiveValues(result, {
110
- exitCode: 1,
111
- rawOutput: settled.error.message,
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
- const sensitiveValues = [...new Set([
116
- ...(deps.sensitiveValues ?? []),
117
- ...getTurnResultSensitiveValues(result),
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
- finally {
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
+ }
@@ -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?: {
@@ -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
  }
@@ -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
  }
@@ -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
  */
@@ -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.25",
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": "b936e0a54540b195856329c07c81e71818b9e4de30b90e768e9058d6"
143
+ "falconPackageHash": "5c7c088ce2f8c51308dde9f92d3f259465bbf34021afddb3af39d2d9"
144
144
  }