@wix/pathgrade 1.0.24 → 1.0.26

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 (75) hide show
  1. package/README.md +42 -0
  2. package/dist/agents/claude/sdk-options.d.ts +1 -2
  3. package/dist/agents/claude.js +2 -1
  4. package/dist/agents/codex-app-server/agent.js +83 -46
  5. package/dist/agents/codex-app-server/scenario-mount.d.ts +40 -0
  6. package/dist/agents/codex-app-server/scenario-mount.js +238 -0
  7. package/dist/agents/cursor-scenario.d.ts +20 -0
  8. package/dist/agents/cursor-scenario.js +96 -0
  9. package/dist/agents/cursor.js +20 -0
  10. package/dist/agents/opencode/scenario.d.ts +25 -0
  11. package/dist/agents/opencode/scenario.js +195 -0
  12. package/dist/agents/opencode.js +50 -0
  13. package/dist/internal/direct-mcp-v2/acp-author-projector.d.ts +20 -0
  14. package/dist/internal/direct-mcp-v2/acp-author-projector.js +147 -0
  15. package/dist/internal/direct-mcp-v2/acp-author-session.d.ts +39 -0
  16. package/dist/internal/direct-mcp-v2/acp-author-session.js +201 -0
  17. package/dist/internal/direct-mcp-v2/acp-client.d.ts +70 -0
  18. package/dist/internal/direct-mcp-v2/acp-client.js +310 -0
  19. package/dist/internal/direct-mcp-v2/acp-direct-mcp.d.ts +38 -0
  20. package/dist/internal/direct-mcp-v2/acp-direct-mcp.js +326 -0
  21. package/dist/internal/direct-mcp-v2/claude-direct-mcp.d.ts +68 -0
  22. package/dist/internal/direct-mcp-v2/claude-direct-mcp.js +456 -0
  23. package/dist/internal/direct-mcp-v2/claude-profile.d.ts +9 -0
  24. package/dist/internal/direct-mcp-v2/claude-profile.js +97 -0
  25. package/dist/internal/direct-mcp-v2/cleanup.d.ts +62 -0
  26. package/dist/internal/direct-mcp-v2/cleanup.js +182 -0
  27. package/dist/internal/direct-mcp-v2/codex-direct-mcp.d.ts +41 -0
  28. package/dist/internal/direct-mcp-v2/codex-direct-mcp.js +436 -0
  29. package/dist/internal/direct-mcp-v2/codex-profile.d.ts +12 -0
  30. package/dist/internal/direct-mcp-v2/codex-profile.js +81 -0
  31. package/dist/internal/direct-mcp-v2/compiler.d.ts +12 -0
  32. package/dist/internal/direct-mcp-v2/compiler.js +449 -0
  33. package/dist/internal/direct-mcp-v2/cursor-direct-mcp.d.ts +14 -0
  34. package/dist/internal/direct-mcp-v2/cursor-direct-mcp.js +60 -0
  35. package/dist/internal/direct-mcp-v2/cursor-profile.d.ts +38 -0
  36. package/dist/internal/direct-mcp-v2/cursor-profile.js +79 -0
  37. package/dist/internal/direct-mcp-v2/evidence.d.ts +50 -0
  38. package/dist/internal/direct-mcp-v2/evidence.js +147 -0
  39. package/dist/internal/direct-mcp-v2/host-reducer.d.ts +61 -0
  40. package/dist/internal/direct-mcp-v2/host-reducer.js +214 -0
  41. package/dist/internal/direct-mcp-v2/json.d.ts +9 -0
  42. package/dist/internal/direct-mcp-v2/json.js +220 -0
  43. package/dist/internal/direct-mcp-v2/materialize.d.ts +39 -0
  44. package/dist/internal/direct-mcp-v2/materialize.js +22 -0
  45. package/dist/internal/direct-mcp-v2/opencode-direct-mcp.d.ts +14 -0
  46. package/dist/internal/direct-mcp-v2/opencode-direct-mcp.js +71 -0
  47. package/dist/internal/direct-mcp-v2/opencode-profile.d.ts +8 -0
  48. package/dist/internal/direct-mcp-v2/opencode-profile.js +52 -0
  49. package/dist/internal/direct-mcp-v2/projection.d.ts +40 -0
  50. package/dist/internal/direct-mcp-v2/projection.js +46 -0
  51. package/dist/internal/direct-mcp-v2/public-scenario-runtime.d.ts +51 -0
  52. package/dist/internal/direct-mcp-v2/public-scenario-runtime.js +353 -0
  53. package/dist/internal/direct-mcp-v2/runtime-lock.d.ts +51 -0
  54. package/dist/internal/direct-mcp-v2/runtime-lock.js +203 -0
  55. package/dist/internal/direct-mcp-v2/runtime-profile.d.ts +28 -0
  56. package/dist/internal/direct-mcp-v2/runtime-profile.js +163 -0
  57. package/dist/internal/direct-mcp-v2/scenario-http-host.d.ts +24 -0
  58. package/dist/internal/direct-mcp-v2/scenario-http-host.js +254 -0
  59. package/dist/internal/direct-mcp-v2/types.d.ts +233 -0
  60. package/dist/internal/direct-mcp-v2/types.js +1 -0
  61. package/dist/sdk/agent.js +57 -2
  62. package/dist/sdk/evaluate.js +6 -3
  63. package/dist/sdk/index.d.ts +4 -0
  64. package/dist/sdk/index.js +2 -0
  65. package/dist/sdk/managed-session.d.ts +6 -0
  66. package/dist/sdk/managed-session.js +157 -50
  67. package/dist/sdk/scenario-evidence.d.ts +27 -0
  68. package/dist/sdk/scenario-evidence.js +94 -0
  69. package/dist/sdk/scenario-machine-v2.d.ts +9 -0
  70. package/dist/sdk/scenario-machine-v2.js +37 -0
  71. package/dist/sdk/snapshots.d.ts +3 -0
  72. package/dist/sdk/snapshots.js +2 -0
  73. package/dist/sdk/types.d.ts +9 -1
  74. package/dist/types.d.ts +6 -0
  75. package/package.json +2 -2
@@ -0,0 +1,326 @@
1
+ /* oxlint-disable anti-slop/no-unknown-parameters -- Provider promise rejections are normalized at the ACP boundary. */
2
+ import { randomBytes } from 'node:crypto';
3
+ import { mkdir, mkdtemp, realpath, rm, symlink } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { dirname, isAbsolute, join, relative } from 'node:path';
6
+ import { SAFE_HOST_VARS } from '../../providers/sandbox.js';
7
+ import { spawnAcpClient } from './acp-client.js';
8
+ import { IdempotentTrialCleaner, persistTrialArtifact, TrialExecutionError, TrialResourceRegistry, scanBearerSurfaces } from './cleanup.js';
9
+ import { compileScenario } from './compiler.js';
10
+ import { PrivateReplayVault, replayPrivateEvidence, verifyEvidenceChain } from './evidence.js';
11
+ import { ScenarioHostReducer } from './host-reducer.js';
12
+ import { materializeProjection } from './materialize.js';
13
+ import { projectEndpoints } from './projection.js';
14
+ import { admitRuntimeProfile } from './runtime-profile.js';
15
+ import { createInvocationBarrier, startScenarioHttpHost } from './scenario-http-host.js';
16
+ export function curateAcpProcessEnvironment(environment) {
17
+ const result = {};
18
+ for (const name of SAFE_HOST_VARS)
19
+ if (environment[name])
20
+ result[name] = environment[name];
21
+ return result;
22
+ }
23
+ export async function runAcpDirectMcpTrial(options) {
24
+ const compiled = compileScenario(options.scenario);
25
+ if (!compiled.ok)
26
+ throw new Error(`Scenario compilation failed: ${JSON.stringify(compiled.diagnostics)}`);
27
+ const cases = findContractCases(compiled.artifact);
28
+ const createdScratchParent = await mkdtemp(join(tmpdir(), `pathgrade-${options.provider}-direct-mcp-`));
29
+ let scratchParent;
30
+ try {
31
+ scratchParent = await realpath(createdScratchParent);
32
+ }
33
+ catch (error) {
34
+ await rm(createdScratchParent, { recursive: true, force: true }).catch(() => undefined);
35
+ throw error;
36
+ }
37
+ const scratchPath = join(scratchParent, 'trial');
38
+ const workspacePath = join(scratchPath, 'workspace');
39
+ const homePath = join(scratchPath, 'home');
40
+ const dataPath = join(scratchPath, 'data');
41
+ const configPath = join(scratchPath, 'config');
42
+ const cachePath = join(scratchPath, 'cache');
43
+ const tempPath = join(scratchPath, 'tmp');
44
+ try {
45
+ await Promise.all([workspacePath, homePath, dataPath, configPath, cachePath, tempPath].map((path) => mkdir(path, { recursive: true })));
46
+ for (const link of options.localHomeLinks ?? []) {
47
+ const difference = relative(homePath, join(homePath, link.relativePath));
48
+ if (link.relativePath.length === 0 || isAbsolute(link.relativePath) || difference.startsWith('..'))
49
+ throw new Error(`unsafe local HOME link: ${link.relativePath}`);
50
+ const source = await realpath(link.hostPath);
51
+ const destination = join(homePath, link.relativePath);
52
+ await mkdir(dirname(destination), { recursive: true });
53
+ await symlink(source, destination, 'dir');
54
+ }
55
+ }
56
+ catch (error) {
57
+ await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
58
+ throw error;
59
+ }
60
+ const runtimeEnvironment = {
61
+ ...options.environment,
62
+ HOME: homePath,
63
+ TMPDIR: tempPath,
64
+ TMP: tempPath,
65
+ TEMP: tempPath,
66
+ XDG_DATA_HOME: dataPath,
67
+ XDG_CONFIG_HOME: configPath,
68
+ XDG_CACHE_HOME: cachePath,
69
+ };
70
+ delete runtimeEnvironment.OPENCODE_CONFIG_CONTENT;
71
+ const trialId = `${options.provider}-${randomBytes(12).toString('hex')}`;
72
+ const macKey = randomBytes(32);
73
+ const replayVault = new PrivateReplayVault(randomBytes(32));
74
+ const reducer = new ScenarioHostReducer(trialId, compiled.artifact, macKey, replayVault);
75
+ reducer.markCompiled();
76
+ const barrier = createInvocationBarrier(cases.effectfulCaseId);
77
+ const host = await startScenarioHttpHost({ artifact: compiled.artifact, reducer, barrier, secretRef: `${options.provider}-direct-mcp-trial-bearer` }).catch(async (error) => {
78
+ macKey.fill(0);
79
+ replayVault.dispose();
80
+ reducer.dispose();
81
+ await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
82
+ throw error;
83
+ });
84
+ const registry = new TrialResourceRegistry();
85
+ let hostOpen = true;
86
+ let client;
87
+ let hostQuiescent = false;
88
+ let persisted = false;
89
+ registry.register({ opaqueId: 'scenario-host', kind: 'listener', close: async () => { await host.dispose(); hostOpen = false; }, isOpen: () => hostOpen });
90
+ registry.register({ opaqueId: 'scenario-transports', kind: 'transport', close: () => host.dispose(), isOpen: () => host.activeTransportCount() !== 0 });
91
+ const cleaner = new IdempotentTrialCleaner({
92
+ registry,
93
+ scanRoots: [scratchPath],
94
+ scratchPath,
95
+ approvedScratchParent: scratchParent,
96
+ bearer: host.bearer.toString(),
97
+ persistRedacted: async (report, phase) => {
98
+ if (!options.artifactDirectory) {
99
+ if (phase === 'final')
100
+ persisted = true;
101
+ return;
102
+ }
103
+ const path = join(options.artifactDirectory, 'trial-evidence.json');
104
+ await mkdir(dirname(path), { recursive: true });
105
+ const value = {
106
+ artifactDigest: compiled.artifact.artifactDigest,
107
+ scenarioArtifact: compiled.artifact,
108
+ evidence: reducer.ledger.events(),
109
+ replayVaultReference: {
110
+ format: 'pathgrade-private-replay-vault-reference-v1',
111
+ trialId,
112
+ artifactDigest: compiled.artifact.artifactDigest,
113
+ },
114
+ admissionProfile: options.profile,
115
+ cleanup: { ...report, scans: report.scans.map((entry) => ({ ...entry, path: entry.path.replace(scratchParent, '<scratch>') })) },
116
+ };
117
+ await persistTrialArtifact(path, `${JSON.stringify(value)}\n`, phase);
118
+ if (phase === 'final')
119
+ persisted = true;
120
+ },
121
+ zeroBearer: () => { host.bearer.fill(0); macKey.fill(0); replayVault.dispose(); },
122
+ });
123
+ try {
124
+ const protectedValues = [host.bearer.toString(), `Bearer ${host.bearer.toString()}`];
125
+ const createClient = options.createClient ?? spawnAcpClient;
126
+ const admission = await admitRuntimeProfile(options.profile, {
127
+ provider: options.provider,
128
+ platform: process.platform,
129
+ architecture: process.arch,
130
+ environment: runtimeEnvironment,
131
+ forbiddenSecrets: protectedValues,
132
+ initializeControlPlane: async () => {
133
+ const initializedClient = createClient({
134
+ provider: options.provider,
135
+ command: options.profile.command.absolutePath,
136
+ args: options.profile.command.args,
137
+ cwd: workspacePath,
138
+ environment: runtimeEnvironment,
139
+ protectedValues,
140
+ });
141
+ client = initializedClient;
142
+ registry.register({ opaqueId: `${options.provider}-acp`, kind: 'process', close: () => initializedClient.close(), isOpen: () => initializedClient.isOpen() });
143
+ const normalizedHandshake = await initializedClient.initialize();
144
+ return { normalizedHandshake, capabilities: options.profile.requiredCapabilities, close: () => initializedClient.close() };
145
+ },
146
+ });
147
+ if (!admission.ok || !client)
148
+ throw new Error(`RUNTIME_NOT_ADMITTED: ${admission.ok ? 'ACP client unavailable' : admission.reason}`);
149
+ const admittedClient = client;
150
+ const projected = projectEndpoints(host.endpoints, admission.profile);
151
+ if (!Array.isArray(projected))
152
+ throw new Error(`RUNTIME_NOT_ADMITTED: ${projected.reason}`);
153
+ const mcpServers = projected.flatMap((plan) => {
154
+ const materialized = materializeProjection(plan, {
155
+ resolve(secretRef) {
156
+ if (secretRef !== host.secretRef)
157
+ throw new Error(`Unknown secret reference: ${secretRef}`);
158
+ return `Bearer ${host.bearer.toString()}`;
159
+ },
160
+ });
161
+ if (materialized.kind !== 'acp-session' || materialized.provider !== options.provider)
162
+ throw new Error('ACP projection materialized for another provider');
163
+ return materialized.mcpServers.map((server) => ({
164
+ type: server.type,
165
+ name: server.name,
166
+ url: server.url,
167
+ headers: server.headers.map((header) => ({ name: header.name, value: header.value })),
168
+ }));
169
+ });
170
+ const protectedSurfaces = [...protectedValues, ...host.endpoints.map((endpoint) => endpoint.url)];
171
+ const sessionId = await admittedClient.newSession(mcpServers);
172
+ if (admittedClient.controlPlaneSecretFrames() !== 1 || admittedClient.inboundSecretSeen())
173
+ throw new Error('RUNTIME_NOT_ADMITTED: ACP secret isolation failed during session admission');
174
+ await assertProtectedSurfacesClean([scratchPath], protectedSurfaces, 'RUNTIME_NOT_ADMITTED: ACP protected projection reached a writable or unreadable surface');
175
+ reducer.markAdmitted();
176
+ reducer.markReady();
177
+ reducer.beginTurn('direct-call', options.provider);
178
+ await admittedClient.prompt(sessionId, normalPrompt(options.provider, cases));
179
+ await host.waitForQuiescence(30_000);
180
+ reducer.finalize('settled');
181
+ const normalEvidence = reducer.ledger.events();
182
+ if (!hasDecision(normalEvidence, cases.pureCaseId) || !hasDecision(normalEvidence, cases.errorCaseId))
183
+ throw new Error(`${options.provider} did not exercise direct success and structured tool error`);
184
+ reducer.beginTurn('cancelled-turn', options.provider);
185
+ const cancelled = admittedClient.prompt(sessionId, cancellationPrompt(cases)).then((result) => ({ kind: 'result', result }), (error) => ({ kind: 'error', error }));
186
+ const entered = await withTimeout(Promise.race([barrier.reached().then(() => 'barrier'), cancelled.then((result) => `terminal:${result.kind}`)]), 90_000, `${options.provider} blocking invocation`);
187
+ if (entered !== 'barrier')
188
+ throw new Error(`${options.provider} reached ${entered} before the blocking MCP transition`);
189
+ reducer.fence();
190
+ admittedClient.setCancelling(true);
191
+ admittedClient.cancel(sessionId);
192
+ reducer.abortUncommitted('provider-cancelled');
193
+ barrier.release();
194
+ const cancellation = await withTimeout(cancelled, 45_000, `${options.provider} cancelled prompt`);
195
+ admittedClient.setCancelling(false);
196
+ if (cancellation.kind === 'error')
197
+ throw cancellation.error;
198
+ await host.waitForQuiescence(30_000);
199
+ hostQuiescent = host.activeTransportCount() === 0;
200
+ if (!hostQuiescent)
201
+ throw new Error(`Scenario host was not quiescent after ${options.provider} cancellation`);
202
+ if (cancellation.result.stopReason !== 'cancelled')
203
+ throw new Error(`${options.provider} cancellation terminal was ${cancellation.result.stopReason}`);
204
+ reducer.observeProviderTerminal();
205
+ reducer.finalize('cancelled-clean');
206
+ reducer.beginTurn('post-cancel-reuse', options.provider);
207
+ await admittedClient.prompt(sessionId, reusePrompt(cases));
208
+ await host.waitForQuiescence(30_000);
209
+ reducer.finalize('settled');
210
+ if (!hasDecision(reducer.ledger.events(), cases.effectfulCaseId))
211
+ throw new Error(`${options.provider} post-cancel reuse did not reach the scenario host`);
212
+ const replay = replayPrivateEvidence({ artifact: compiled.artifact, evidence: reducer.ledger.events(), vault: replayVault, macKey });
213
+ if (!replay.ok || replay.poisoned || replay.finalState !== reducer.state)
214
+ throw new Error(`Private replay failed: ${replay.ok ? 'state mismatch' : replay.reason}`);
215
+ const chain = verifyEvidenceChain(reducer.ledger.events(), compiled.artifact.artifactDigest);
216
+ if (!chain.ok)
217
+ throw new Error(`Evidence chain failed: ${chain.reason}`);
218
+ await assertProtectedSurfacesClean([scratchPath], protectedSurfaces, 'ACP protected projection reached a writable or unreadable surface');
219
+ reducer.dispose();
220
+ const cleanup = await cleaner.dispose();
221
+ await rm(scratchParent, { recursive: true, force: true });
222
+ if (!persisted || cleanup.failures.length > 0 || cleanup.bearerFound || !cleanup.scratchDeleted)
223
+ throw new Error(`CLEANUP_FAILED: ${JSON.stringify(cleanup.failures)}`);
224
+ return {
225
+ selected: true,
226
+ profileFingerprint: options.profile.profileFingerprint,
227
+ artifact: compiled.artifact,
228
+ evidence: reducer.ledger.events(),
229
+ checks: { runtimeAdmitted: true, secretFreeProject: true, directCall: true, structuredToolError: true, fencedCancellation: true, providerTerminal: true, hostQuiescent, postCancelReuse: true, privateReplay: true, cleanup: true },
230
+ cleanup,
231
+ cancellationStatus: cancellation.result.stopReason,
232
+ };
233
+ }
234
+ catch (error) {
235
+ barrier.release();
236
+ client?.setCancelling(true);
237
+ try {
238
+ if (reducer.phase === 'ACTIVE')
239
+ reducer.fence();
240
+ if (reducer.phase === 'FENCING')
241
+ reducer.abortUncommitted('trial-failed');
242
+ if (!['DISPOSED', 'NEW', 'COMPILED', 'ADMITTED'].includes(reducer.phase))
243
+ reducer.finalize('poisoned', 'trial-failed');
244
+ }
245
+ catch (lifecycleFailure) {
246
+ void lifecycleFailure;
247
+ }
248
+ reducer.dispose();
249
+ let cleanupError;
250
+ try {
251
+ const report = await cleaner.dispose();
252
+ if (report.failures.length > 0 || report.bearerFound || !report.scratchDeleted)
253
+ cleanupError = new Error(`CLEANUP_FAILED: ${JSON.stringify(report.failures)}`);
254
+ }
255
+ catch (failure) {
256
+ cleanupError = failure instanceof Error ? failure : new Error(String(failure));
257
+ }
258
+ await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
259
+ if (cleanupError)
260
+ throw new TrialExecutionError(error instanceof Error ? error.message : `${options.provider} direct MCP trial failed`, { primary: error, secondary: [cleanupError] });
261
+ throw error;
262
+ }
263
+ }
264
+ async function assertProtectedSurfacesClean(roots, protectedValues, message) {
265
+ for (const value of protectedValues) {
266
+ const scans = await scanBearerSurfaces(roots, value);
267
+ if (scans.some((entry) => entry.status !== 'clean'))
268
+ throw new Error(message);
269
+ }
270
+ }
271
+ function findContractCases(artifact) {
272
+ const state = artifact.canonicalIr.states[artifact.canonicalIr.initialState];
273
+ const entries = Object.entries(state.tools);
274
+ const effectful = entries.find(([, tool]) => tool.effectful && tool.cases.some((entry) => entry.transition && entry.outcome.kind === 'success' && 'exactArgs' in entry.when));
275
+ const error = entries.find(([, tool]) => tool.cases.some((entry) => entry.outcome.kind === 'tool-error' && 'exactArgs' in entry.when));
276
+ const pure = entries.find(([, tool]) => !tool.effectful && tool.cases.some((entry) => entry.outcome.kind === 'success'));
277
+ if (!effectful || !error || !pure)
278
+ throw new Error('ACP vertical requires an effectful success, structured error, and pure success in the initial state');
279
+ const successCase = effectful[1].cases.find((entry) => entry.transition && entry.outcome.kind === 'success' && 'exactArgs' in entry.when);
280
+ const errorCase = error[1].cases.find((entry) => entry.outcome.kind === 'tool-error' && 'exactArgs' in entry.when);
281
+ const pureCase = pure[1].cases.find((entry) => entry.outcome.kind === 'success');
282
+ const [serverId, effectfulToolId] = effectful[0].split('/');
283
+ return {
284
+ serverId,
285
+ effectfulToolId,
286
+ effectfulCaseId: successCase.id,
287
+ successArgs: exactArgs(successCase),
288
+ errorCaseId: errorCase.id,
289
+ errorArgs: exactArgs(errorCase),
290
+ pureToolId: pure[0].split('/')[1],
291
+ pureCaseId: pureCase.id,
292
+ pureArgs: 'exactArgs' in pureCase.when ? pureCase.when.exactArgs : deriveArgs(pureCase.when.all),
293
+ };
294
+ }
295
+ function exactArgs(scenarioCase) {
296
+ if (!('exactArgs' in scenarioCase.when))
297
+ throw new Error('ACP contract case requires exact arguments');
298
+ return scenarioCase.when.exactArgs;
299
+ }
300
+ function deriveArgs(predicates) {
301
+ const result = {};
302
+ for (const predicate of predicates)
303
+ if (predicate.pointer.startsWith('/') && 'equals' in predicate)
304
+ result[predicate.pointer.slice(1)] = predicate.equals;
305
+ return result;
306
+ }
307
+ function normalPrompt(provider, cases) {
308
+ return `Use only MCP server ${cases.serverId}. Invoke ${cases.pureToolId} exactly once with ${JSON.stringify(cases.pureArgs)}, then invoke ${cases.effectfulToolId} exactly once with ${JSON.stringify(cases.errorArgs)}. The second result is an expected tool error; do not retry it. Do not use shell or any other tool. Then answer ${provider.toUpperCase()}_DIRECT_DONE.`;
309
+ }
310
+ function cancellationPrompt(cases) {
311
+ return `Use only MCP server ${cases.serverId}. Invoke ${cases.effectfulToolId} exactly once with ${JSON.stringify(cases.successArgs)}. Wait for its result and do not use any other tool.`;
312
+ }
313
+ function reusePrompt(cases) {
314
+ return `Use only MCP server ${cases.serverId}. Invoke ${cases.effectfulToolId} exactly once with ${JSON.stringify(cases.successArgs)}. Wait for its result and do not use any other tool.`;
315
+ }
316
+ function hasDecision(evidence, caseId) {
317
+ return evidence.some((event) => event.payload.type === 'mcp.decision' && event.payload.caseId === caseId && event.payload.authority === 'scenario-host');
318
+ }
319
+ async function withTimeout(promise, timeoutMs, label) {
320
+ let timer;
321
+ return Promise.race([
322
+ promise,
323
+ new Promise((_resolve, reject) => { timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs); }),
324
+ ]).finally(() => { if (timer)
325
+ clearTimeout(timer); });
326
+ }
@@ -0,0 +1,68 @@
1
+ import { type McpSdkServerConfigWithInstance, type Options as ClaudeSdkOptions, type Query } from '@anthropic-ai/claude-agent-sdk';
2
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
+ import { type CleanupReport } from './cleanup.js';
4
+ import { type MaterializedProjection } from './materialize.js';
5
+ import type { EvidenceEnvelope, ScenarioArtifact, ScenarioMachineV2 } from './types.js';
6
+ export declare const CLAUDE_DIRECT_MCP_FLAG = "PATHGRADE_INTERNAL_CLAUDE_DIRECT_MCP_V2";
7
+ export type ClaudeDirectMcpChecks = {
8
+ runtimeAdmitted: boolean;
9
+ secretFreeProject: boolean;
10
+ directCall: boolean;
11
+ structuredToolError: boolean;
12
+ fencedCancellation: boolean;
13
+ providerTerminal: boolean;
14
+ hostQuiescent: boolean;
15
+ postCancelReuse: boolean;
16
+ privateReplay: boolean;
17
+ cleanup: boolean;
18
+ };
19
+ export type ClaudeDirectMcpTrialResult = {
20
+ selected: true;
21
+ profileFingerprint: string;
22
+ artifact: ScenarioArtifact;
23
+ evidence: readonly EvidenceEnvelope[];
24
+ checks: ClaudeDirectMcpChecks;
25
+ cleanup: CleanupReport;
26
+ cancellationStatus: string;
27
+ };
28
+ export type ClaudeSdkQueryFn = (args: {
29
+ prompt: string;
30
+ options?: ClaudeSdkOptions;
31
+ }) => Query;
32
+ type BridgeObservation = {
33
+ serverId: string;
34
+ toolId: string;
35
+ result: CallToolResult;
36
+ };
37
+ type ChildProjectionRecord = {
38
+ args: readonly string[];
39
+ environment: Readonly<Record<string, string | undefined>>;
40
+ };
41
+ type ClaudeParentBridges = Record<string, McpSdkServerConfigWithInstance>;
42
+ export declare function isClaudeDirectMcpSelected(environment?: NodeJS.ProcessEnv): boolean;
43
+ export declare function claudeDirectMcpDisableReason(): string | undefined;
44
+ /** Test-only reset for proving the fail-closed process latch. */
45
+ export declare function __resetClaudeDirectMcpDisablementForTesting(): void;
46
+ export declare function mapClaudeProviderEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
47
+ export declare function assertClaudeProjectionIsolation(options: {
48
+ records: readonly ChildProjectionRecord[];
49
+ scans: readonly {
50
+ path: string;
51
+ status: string;
52
+ }[];
53
+ protectedValues: readonly string[];
54
+ }): void;
55
+ export declare function createClaudeParentBridges(options: {
56
+ artifact: ScenarioArtifact;
57
+ projections: readonly MaterializedProjection[];
58
+ observe?(observation: BridgeObservation): void;
59
+ }): ClaudeParentBridges;
60
+ export declare function runClaudeDirectMcpTrial(options: {
61
+ scenario: ScenarioMachineV2;
62
+ environment?: NodeJS.ProcessEnv;
63
+ model?: string;
64
+ artifactDirectory?: string;
65
+ query?: ClaudeSdkQueryFn;
66
+ initializeControlPlane?: () => Promise<void>;
67
+ }): Promise<ClaudeDirectMcpTrialResult>;
68
+ export {};