@wix/pathgrade 1.0.25 → 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.
- package/README.md +42 -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 +83 -46
- 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/cursor-scenario.d.ts +20 -0
- package/dist/agents/cursor-scenario.js +96 -0
- package/dist/agents/cursor.js +20 -0
- package/dist/agents/opencode/scenario.d.ts +25 -0
- package/dist/agents/opencode/scenario.js +195 -0
- package/dist/agents/opencode.js +50 -0
- 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/sdk/agent.js +49 -2
- package/dist/sdk/evaluate.js +6 -3
- package/dist/sdk/index.d.ts +4 -0
- package/dist/sdk/index.js +2 -0
- 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 +9 -1
- package/dist/types.d.ts +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/* oxlint-disable anti-slop/no-unknown-parameters, anti-slop/no-runtime-typeof -- SDK messages and JSON-RPC responses are external trust boundaries. */
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
4
|
+
import { mkdir, mkdtemp, realpath, rm } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { createSdkMcpServer, query as sdkQuery, tool, } from '@anthropic-ai/claude-agent-sdk';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { createSandboxedClaudeSpawn } from '../../providers/sandboxed-claude-spawn.js';
|
|
10
|
+
import { compileScenario } from './compiler.js';
|
|
11
|
+
import { IdempotentTrialCleaner, persistTrialArtifact, TrialExecutionError, TrialResourceRegistry, scanBearerSurfaces } from './cleanup.js';
|
|
12
|
+
import { PrivateReplayVault, replayPrivateEvidence, verifyEvidenceChain } from './evidence.js';
|
|
13
|
+
import { ScenarioHostReducer } from './host-reducer.js';
|
|
14
|
+
import { materializeProjection } from './materialize.js';
|
|
15
|
+
import { projectEndpoints } from './projection.js';
|
|
16
|
+
import { admitRuntimeProfile } from './runtime-profile.js';
|
|
17
|
+
import { createInvocationBarrier, startScenarioHttpHost } from './scenario-http-host.js';
|
|
18
|
+
import { CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE, CLAUDE_DIRECT_MCP_NORMALIZED_HANDSHAKE } from './claude-profile.js';
|
|
19
|
+
export const CLAUDE_DIRECT_MCP_FLAG = 'PATHGRADE_INTERNAL_CLAUDE_DIRECT_MCP_V2';
|
|
20
|
+
const disabledProfiles = new Map();
|
|
21
|
+
export function isClaudeDirectMcpSelected(environment = process.env) {
|
|
22
|
+
const fingerprint = CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.profileFingerprint;
|
|
23
|
+
return environment[CLAUDE_DIRECT_MCP_FLAG] === '1' && !disabledProfiles.has(fingerprint);
|
|
24
|
+
}
|
|
25
|
+
export function claudeDirectMcpDisableReason() {
|
|
26
|
+
return disabledProfiles.get(CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.profileFingerprint);
|
|
27
|
+
}
|
|
28
|
+
/** Test-only reset for proving the fail-closed process latch. */
|
|
29
|
+
// oxlint-disable-next-line inhuman/no-empty-wrappers -- This test seam exposes Map.clear without exposing mutable production state.
|
|
30
|
+
export function __resetClaudeDirectMcpDisablementForTesting() {
|
|
31
|
+
disabledProfiles.clear();
|
|
32
|
+
}
|
|
33
|
+
export function mapClaudeProviderEnvironment(environment) {
|
|
34
|
+
const { APP_ANTHROPIC_API_KEY: appApiKey, APP_ANTHROPIC_BASE_URL: appBaseUrl, ...providerEnvironment } = environment;
|
|
35
|
+
const result = { ...providerEnvironment };
|
|
36
|
+
const apiKey = environment.ANTHROPIC_API_KEY ?? appApiKey;
|
|
37
|
+
const baseUrl = environment.ANTHROPIC_BASE_URL ?? appBaseUrl;
|
|
38
|
+
if (apiKey)
|
|
39
|
+
result.ANTHROPIC_API_KEY = apiKey;
|
|
40
|
+
if (baseUrl)
|
|
41
|
+
result.ANTHROPIC_BASE_URL = baseUrl;
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
export function assertClaudeProjectionIsolation(options) {
|
|
45
|
+
for (const record of options.records) {
|
|
46
|
+
const argv = record.args.join('\u0000');
|
|
47
|
+
const environment = Object.entries(record.environment).map(([name, value]) => `${name}=${value ?? ''}`).join('\u0000');
|
|
48
|
+
if (record.args.includes('--mcp-config'))
|
|
49
|
+
throw new Error('Claude child argv contained --mcp-config');
|
|
50
|
+
if (options.protectedValues.some((value) => value.length > 0 && (argv.includes(value) || environment.includes(value)))) {
|
|
51
|
+
throw new Error('Claude authenticated HTTP projection reached child argv or environment');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (options.scans.some((entry) => entry.status !== 'clean'))
|
|
55
|
+
throw new Error('Claude authenticated HTTP projection reached a writable or unreadable surface');
|
|
56
|
+
}
|
|
57
|
+
export function createClaudeParentBridges(options) {
|
|
58
|
+
const bridges = {};
|
|
59
|
+
for (const projection of options.projections) {
|
|
60
|
+
if (projection.kind !== 'claude-parent-bridge')
|
|
61
|
+
throw new Error(`Unsupported Claude projection kind: ${projection.kind}`);
|
|
62
|
+
const { name, endpoint } = projection.server;
|
|
63
|
+
const declaredServer = options.artifact.canonicalIr.servers[name];
|
|
64
|
+
if (!declaredServer)
|
|
65
|
+
throw new Error(`Claude projection references undeclared server: ${name}`);
|
|
66
|
+
const tools = Object.entries(declaredServer.tools).map(([toolId, definition]) => {
|
|
67
|
+
const schema = z.fromJSONSchema(definition.inputSchema);
|
|
68
|
+
if (!(schema instanceof z.ZodObject))
|
|
69
|
+
throw new Error(`Claude tool schema is not an object: ${name}/${toolId}`);
|
|
70
|
+
// oxlint-disable-next-line anti-slop/no-shape-in-symbol-names -- `shape` is Zod's owned API for the field map required by the SDK tool helper.
|
|
71
|
+
return tool(toolId, definition.description, schema.shape, async (args, extra) => {
|
|
72
|
+
const signal = readAbortSignal(extra);
|
|
73
|
+
// SAFETY: Zod validated the SDK handler argument against the compiler-approved object schema immediately before invoking this handler.
|
|
74
|
+
const result = await forwardToolCall({ endpoint, serverId: name, toolId, args: args, signal });
|
|
75
|
+
options.observe?.({ serverId: name, toolId, result });
|
|
76
|
+
return result;
|
|
77
|
+
}, { alwaysLoad: true });
|
|
78
|
+
});
|
|
79
|
+
bridges[name] = createSdkMcpServer({ name, version: '2.0.0', alwaysLoad: true, tools });
|
|
80
|
+
}
|
|
81
|
+
return bridges;
|
|
82
|
+
}
|
|
83
|
+
export async function runClaudeDirectMcpTrial(options) {
|
|
84
|
+
const environment = options.environment ?? process.env;
|
|
85
|
+
const profile = CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE;
|
|
86
|
+
if (!isClaudeDirectMcpSelected(environment)) {
|
|
87
|
+
const reason = disabledProfiles.get(profile.profileFingerprint);
|
|
88
|
+
throw new Error(reason ? `Claude direct MCP disabled for admitted profile: ${reason}` : `${CLAUDE_DIRECT_MCP_FLAG} is disabled`);
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return await executeTrial({ ...options, environment });
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
95
|
+
disabledProfiles.set(profile.profileFingerprint, reason.slice(0, 500));
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function executeTrial(options) {
|
|
100
|
+
const compiled = compileScenario(options.scenario);
|
|
101
|
+
if (!compiled.ok)
|
|
102
|
+
throw new Error(`Scenario compilation failed: ${JSON.stringify(compiled.diagnostics)}`);
|
|
103
|
+
const effectfulCase = findEffectfulSuccessCase(compiled.artifact);
|
|
104
|
+
const createdScratchParent = await mkdtemp(join(tmpdir(), 'pathgrade-claude-direct-mcp-'));
|
|
105
|
+
let scratchParent;
|
|
106
|
+
try {
|
|
107
|
+
scratchParent = await realpath(createdScratchParent);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
await rm(createdScratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
const scratchPath = join(scratchParent, 'trial');
|
|
114
|
+
const workspacePath = join(scratchPath, 'workspace');
|
|
115
|
+
const homePath = join(scratchPath, 'home');
|
|
116
|
+
const tempPath = join(scratchPath, 'tmp');
|
|
117
|
+
try {
|
|
118
|
+
await mkdir(scratchPath);
|
|
119
|
+
await Promise.all([mkdir(workspacePath), mkdir(homePath), mkdir(tempPath)]);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
const runtimeEnv = mapClaudeProviderEnvironment({ ...options.environment, HOME: homePath, TMPDIR: tempPath });
|
|
126
|
+
if (!runtimeEnv.ANTHROPIC_API_KEY) {
|
|
127
|
+
await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
128
|
+
throw new Error('Claude live direct MCP requires ANTHROPIC_API_KEY (or APP_ANTHROPIC_API_KEY)');
|
|
129
|
+
}
|
|
130
|
+
const trialId = `claude-${randomBytes(12).toString('hex')}`;
|
|
131
|
+
const macKey = randomBytes(32);
|
|
132
|
+
const replayVault = new PrivateReplayVault(randomBytes(32));
|
|
133
|
+
const reducer = new ScenarioHostReducer(trialId, compiled.artifact, macKey, replayVault);
|
|
134
|
+
reducer.markCompiled();
|
|
135
|
+
const barrier = createInvocationBarrier(effectfulCase.caseId);
|
|
136
|
+
const host = await startScenarioHttpHost({ artifact: compiled.artifact, reducer, barrier, secretRef: 'claude-direct-mcp-trial-bearer' }).catch(async (error) => {
|
|
137
|
+
macKey.fill(0);
|
|
138
|
+
replayVault.dispose();
|
|
139
|
+
reducer.dispose();
|
|
140
|
+
await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
141
|
+
throw error;
|
|
142
|
+
});
|
|
143
|
+
const registry = new TrialResourceRegistry();
|
|
144
|
+
let hostOpen = true;
|
|
145
|
+
let hostQuiescentBeforeCleanup = false;
|
|
146
|
+
registry.register({ opaqueId: 'scenario-host', kind: 'listener', close: async () => { await host.dispose(); hostOpen = false; }, isOpen: () => hostOpen });
|
|
147
|
+
registry.register({ opaqueId: 'scenario-transports', kind: 'transport', close: () => host.dispose(), isOpen: () => host.activeTransportCount() !== 0 });
|
|
148
|
+
const children = [];
|
|
149
|
+
const childRecords = [];
|
|
150
|
+
const childSpawn = (command, args, spawnOptions) => {
|
|
151
|
+
const child = nodeSpawn(command, args, { cwd: spawnOptions.cwd, env: spawnOptions.env, signal: spawnOptions.signal, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
152
|
+
const index = children.push(child) - 1;
|
|
153
|
+
childRecords.push({ args: [command, ...args], environment: spawnOptions.env });
|
|
154
|
+
registry.register({ opaqueId: `claude-child-${index}`, kind: 'process', close: () => terminateChild(child), isOpen: () => child.exitCode === null });
|
|
155
|
+
return child;
|
|
156
|
+
};
|
|
157
|
+
const spawnClaudeCodeProcess = createSandboxedClaudeSpawn({ platform: process.platform, hostEnv: runtimeEnv, spawn: childSpawn });
|
|
158
|
+
const observations = [];
|
|
159
|
+
let persisted = false;
|
|
160
|
+
const cleaner = new IdempotentTrialCleaner({
|
|
161
|
+
registry,
|
|
162
|
+
scanRoots: [workspacePath, homePath, tempPath],
|
|
163
|
+
scratchPath,
|
|
164
|
+
approvedScratchParent: scratchParent,
|
|
165
|
+
bearer: host.bearer.toString(),
|
|
166
|
+
persistRedacted: async (report, phase) => {
|
|
167
|
+
if (!options.artifactDirectory) {
|
|
168
|
+
if (phase === 'final')
|
|
169
|
+
persisted = true;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const path = join(options.artifactDirectory, 'trial-evidence.json');
|
|
173
|
+
await mkdir(dirname(path), { recursive: true });
|
|
174
|
+
const value = { artifactDigest: compiled.artifact.artifactDigest, evidence: reducer.ledger.events(), cleanup: { ...report, scans: report.scans.map((entry) => ({ ...entry, path: entry.path.replace(scratchParent, '<scratch>') })) } };
|
|
175
|
+
await persistTrialArtifact(path, `${JSON.stringify(value)}\n`, phase);
|
|
176
|
+
if (phase === 'final')
|
|
177
|
+
persisted = true;
|
|
178
|
+
},
|
|
179
|
+
zeroBearer: () => { host.bearer.fill(0); macKey.fill(0); replayVault.dispose(); },
|
|
180
|
+
});
|
|
181
|
+
try {
|
|
182
|
+
const admission = await admitRuntimeProfile(CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE, {
|
|
183
|
+
provider: 'claude',
|
|
184
|
+
platform: process.platform,
|
|
185
|
+
architecture: process.arch,
|
|
186
|
+
environment: runtimeEnv,
|
|
187
|
+
forbiddenSecrets: [host.bearer.toString(), `Bearer ${host.bearer.toString()}`],
|
|
188
|
+
initializeControlPlane: async () => {
|
|
189
|
+
await options.initializeControlPlane?.();
|
|
190
|
+
if (typeof createSdkMcpServer !== 'function' || typeof (options.query ?? sdkQuery) !== 'function')
|
|
191
|
+
throw new Error('Claude SDK parent bridge methods unavailable');
|
|
192
|
+
return { normalizedHandshake: CLAUDE_DIRECT_MCP_NORMALIZED_HANDSHAKE, capabilities: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.requiredCapabilities, close: async () => undefined };
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
if (!admission.ok)
|
|
196
|
+
throw new Error(`RUNTIME_NOT_ADMITTED: ${admission.reason}`);
|
|
197
|
+
reducer.markAdmitted();
|
|
198
|
+
const projected = projectEndpoints(host.endpoints, admission.profile);
|
|
199
|
+
if (!Array.isArray(projected))
|
|
200
|
+
throw new Error(`Projection rejected: ${projected.reason}`);
|
|
201
|
+
const materialized = projected.map((plan) => materializeProjection(plan, {
|
|
202
|
+
resolve(secretRef) {
|
|
203
|
+
if (secretRef !== host.secretRef)
|
|
204
|
+
throw new Error(`Unknown secret reference: ${secretRef}`);
|
|
205
|
+
return `Bearer ${host.bearer.toString()}`;
|
|
206
|
+
},
|
|
207
|
+
}));
|
|
208
|
+
const bridges = createClaudeParentBridges({ artifact: compiled.artifact, projections: materialized, observe: (entry) => observations.push(entry) });
|
|
209
|
+
for (const [name, bridge] of Object.entries(bridges)) {
|
|
210
|
+
let open = true;
|
|
211
|
+
registry.register({ opaqueId: `claude-sdk-bridge-${name}`, kind: 'transport', close: async () => { await bridge.instance.close(); open = false; }, isOpen: () => open });
|
|
212
|
+
}
|
|
213
|
+
reducer.markReady();
|
|
214
|
+
const query = options.query ?? sdkQuery;
|
|
215
|
+
const queryOptions = { query, runtimeEnv, workspacePath, model: options.model, profilePath: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.command.absolutePath, mcpServers: bridges, spawnClaudeCodeProcess };
|
|
216
|
+
reducer.beginTurn('direct-call', 'claude');
|
|
217
|
+
const normal = await runQuery(queryOptions, `Invoke mcp__demo__${effectfulCase.pureToolId} exactly once with ${JSON.stringify(effectfulCase.pureArgs)}, then invoke mcp__demo__${effectfulCase.effectfulToolId} exactly once with ${JSON.stringify(effectfulCase.errorArgs)}. Wait for both results and do not use any other tool.`);
|
|
218
|
+
if (normal.status !== 'success')
|
|
219
|
+
throw new Error(`Claude direct MCP success/error turn ended as ${normal.status}`);
|
|
220
|
+
if (!observations.some((entry) => entry.toolId === effectfulCase.pureToolId && entry.result.isError !== true))
|
|
221
|
+
throw new Error('Claude parent bridge did not preserve direct MCP success');
|
|
222
|
+
if (!observations.some((entry) => entry.toolId === effectfulCase.effectfulToolId && entry.result.isError === true && readPathgradeCode(entry.result) !== undefined))
|
|
223
|
+
throw new Error('Claude parent bridge did not preserve structured scenario tool error');
|
|
224
|
+
reducer.finalize('settled');
|
|
225
|
+
reducer.beginTurn('cancelled-turn', 'claude');
|
|
226
|
+
const controller = new AbortController();
|
|
227
|
+
const cancelled = runQuery({ ...queryOptions, abortController: controller, resume: normal.sessionId }, `Invoke mcp__demo__${effectfulCase.effectfulToolId} exactly once with ${JSON.stringify(effectfulCase.successArgs)}. Wait for its result and do not use any other tool.`);
|
|
228
|
+
const entered = await withTimeout(Promise.race([barrier.reached().then(() => 'barrier'), cancelled.then((result) => `terminal:${result.status}`)]), 90_000, 'Claude produced neither a blocking MCP invocation nor a terminal');
|
|
229
|
+
if (entered !== 'barrier')
|
|
230
|
+
throw new Error(`Claude reached ${entered} before the blocking MCP transition`);
|
|
231
|
+
reducer.fence();
|
|
232
|
+
controller.abort();
|
|
233
|
+
reducer.abortUncommitted('provider-cancelled');
|
|
234
|
+
barrier.release();
|
|
235
|
+
const cancellation = await withTimeout(cancelled, 45_000, 'Claude cancelled query');
|
|
236
|
+
await host.waitForQuiescence(30_000);
|
|
237
|
+
hostQuiescentBeforeCleanup = host.activeTransportCount() === 0;
|
|
238
|
+
if (!hostQuiescentBeforeCleanup)
|
|
239
|
+
throw new Error('Scenario host was not quiescent after Claude cancellation');
|
|
240
|
+
if (!cancellation.cancelled)
|
|
241
|
+
throw new Error(`Claude cancellation terminal was ${cancellation.status}`);
|
|
242
|
+
reducer.observeProviderTerminal();
|
|
243
|
+
reducer.finalize('cancelled-clean');
|
|
244
|
+
reducer.beginTurn('post-cancel-reuse', 'claude');
|
|
245
|
+
const reused = await runQuery({ ...queryOptions, resume: normal.sessionId }, `Invoke mcp__demo__${effectfulCase.effectfulToolId} exactly once with ${JSON.stringify(effectfulCase.successArgs)}. Wait for its result and do not use any other tool.`);
|
|
246
|
+
if (reused.status !== 'success' || !observations.some((entry) => entry.toolId === effectfulCase.effectfulToolId && entry.result.isError !== true))
|
|
247
|
+
throw new Error('Post-cancel Claude parent bridge reuse failed');
|
|
248
|
+
reducer.finalize('settled');
|
|
249
|
+
const replay = replayPrivateEvidence({ artifact: compiled.artifact, evidence: reducer.ledger.events(), vault: replayVault, macKey });
|
|
250
|
+
if (!replay.ok || replay.poisoned || replay.finalState !== reducer.state)
|
|
251
|
+
throw new Error(`Private replay failed: ${replay.ok ? 'state mismatch' : replay.reason}`);
|
|
252
|
+
const chain = verifyEvidenceChain(reducer.ledger.events(), compiled.artifact.artifactDigest);
|
|
253
|
+
if (!chain.ok)
|
|
254
|
+
throw new Error(`Evidence chain failed: ${chain.reason}`);
|
|
255
|
+
const protectedValues = [host.bearer.toString(), `Bearer ${host.bearer.toString()}`, ...host.endpoints.map((endpoint) => endpoint.url)];
|
|
256
|
+
const projectionScans = (await Promise.all(protectedValues.map((value) => scanBearerSurfaces([workspacePath, homePath, tempPath], value)))).flat();
|
|
257
|
+
assertClaudeProjectionIsolation({ records: childRecords, scans: projectionScans, protectedValues });
|
|
258
|
+
reducer.dispose();
|
|
259
|
+
const cleanup = await cleaner.dispose();
|
|
260
|
+
await rm(scratchParent, { recursive: true, force: true });
|
|
261
|
+
if (!persisted || cleanup.failures.length > 0 || cleanup.bearerFound || !cleanup.scratchDeleted)
|
|
262
|
+
throw new Error(`CLEANUP_FAILED: ${JSON.stringify(cleanup.failures)}`);
|
|
263
|
+
return {
|
|
264
|
+
selected: true,
|
|
265
|
+
profileFingerprint: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.profileFingerprint,
|
|
266
|
+
artifact: compiled.artifact,
|
|
267
|
+
evidence: reducer.ledger.events(),
|
|
268
|
+
checks: { runtimeAdmitted: true, secretFreeProject: true, directCall: true, structuredToolError: true, fencedCancellation: true, providerTerminal: true, hostQuiescent: hostQuiescentBeforeCleanup, postCancelReuse: true, privateReplay: true, cleanup: true },
|
|
269
|
+
cleanup,
|
|
270
|
+
cancellationStatus: cancellation.status,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
barrier.release();
|
|
275
|
+
try {
|
|
276
|
+
if (reducer.phase === 'ACTIVE')
|
|
277
|
+
reducer.fence();
|
|
278
|
+
if (reducer.phase === 'FENCING')
|
|
279
|
+
reducer.abortUncommitted('trial-failed');
|
|
280
|
+
if (!['DISPOSED', 'NEW', 'COMPILED', 'ADMITTED'].includes(reducer.phase))
|
|
281
|
+
reducer.finalize('poisoned', 'trial-failed');
|
|
282
|
+
}
|
|
283
|
+
catch (lifecycleFailure) {
|
|
284
|
+
void lifecycleFailure;
|
|
285
|
+
}
|
|
286
|
+
reducer.dispose();
|
|
287
|
+
let cleanupError;
|
|
288
|
+
try {
|
|
289
|
+
const report = await cleaner.dispose();
|
|
290
|
+
if (report.failures.length > 0 || report.bearerFound || !report.scratchDeleted)
|
|
291
|
+
cleanupError = new Error(`CLEANUP_FAILED: ${JSON.stringify(report.failures)}`);
|
|
292
|
+
}
|
|
293
|
+
catch (failure) {
|
|
294
|
+
cleanupError = failure instanceof Error ? failure : new Error(String(failure));
|
|
295
|
+
}
|
|
296
|
+
await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
297
|
+
if (cleanupError)
|
|
298
|
+
throw new TrialExecutionError(error instanceof Error ? error.message : 'Claude direct MCP trial failed', { primary: error, secondary: [cleanupError] });
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function forwardToolCall(options) {
|
|
303
|
+
const response = await fetch(options.endpoint.url, {
|
|
304
|
+
method: 'POST',
|
|
305
|
+
headers: { Authorization: options.endpoint.headers[0].value, accept: 'application/json, text/event-stream', 'content-type': 'application/json' },
|
|
306
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: `${options.serverId}:${options.toolId}:${randomBytes(8).toString('hex')}`, method: 'tools/call', params: { name: options.toolId, arguments: options.args } }),
|
|
307
|
+
signal: options.signal,
|
|
308
|
+
});
|
|
309
|
+
if (!response.ok)
|
|
310
|
+
throw new Error(`Scenario host returned HTTP ${response.status}`);
|
|
311
|
+
const message = await readJsonRpcResponse(response);
|
|
312
|
+
if (message.error)
|
|
313
|
+
throw new Error(`Scenario host JSON-RPC ${String(message.error.code)}: ${String(message.error.message)}`);
|
|
314
|
+
if (!message.result)
|
|
315
|
+
throw new Error('Scenario host returned an invalid MCP tool result');
|
|
316
|
+
return message.result;
|
|
317
|
+
}
|
|
318
|
+
async function readJsonRpcResponse(response) {
|
|
319
|
+
if (!response.headers.get('content-type')?.includes('text/event-stream'))
|
|
320
|
+
return parseJsonRpcToolResponse(await response.json());
|
|
321
|
+
const messages = [];
|
|
322
|
+
for (const line of (await response.text()).split(/\r?\n/)) {
|
|
323
|
+
if (line.startsWith('data:'))
|
|
324
|
+
messages.push(parseJsonRpcToolResponse(JSON.parse(line.slice(5).trim())));
|
|
325
|
+
}
|
|
326
|
+
const message = messages.find((entry) => entry.result !== undefined || entry.error !== undefined);
|
|
327
|
+
if (message === undefined)
|
|
328
|
+
throw new Error('Scenario host returned no JSON-RPC message in its event stream');
|
|
329
|
+
return message;
|
|
330
|
+
}
|
|
331
|
+
function parseJsonRpcToolResponse(value) {
|
|
332
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
333
|
+
throw new Error('Scenario host returned an invalid JSON-RPC response');
|
|
334
|
+
if ('error' in value) {
|
|
335
|
+
const error = value.error;
|
|
336
|
+
if (error === null || typeof error !== 'object' || Array.isArray(error))
|
|
337
|
+
throw new Error('Scenario host returned an invalid JSON-RPC error');
|
|
338
|
+
return { error: { code: 'code' in error && (typeof error.code === 'string' || typeof error.code === 'number') ? error.code : undefined, message: 'message' in error && typeof error.message === 'string' ? error.message : undefined } };
|
|
339
|
+
}
|
|
340
|
+
if (!('result' in value) || value.result === null || typeof value.result !== 'object' || Array.isArray(value.result) || !('content' in value.result) || !Array.isArray(value.result.content)) {
|
|
341
|
+
throw new Error('Scenario host returned an invalid MCP tool result');
|
|
342
|
+
}
|
|
343
|
+
// SAFETY: the MCP boundary check above establishes the required CallToolResult content array; optional fields remain provider-owned JSON.
|
|
344
|
+
return { result: value.result };
|
|
345
|
+
}
|
|
346
|
+
async function runQuery(options, prompt) {
|
|
347
|
+
const allow = async (_toolName, input) => ({ behavior: 'allow', updatedInput: input });
|
|
348
|
+
const sdkOptions = {
|
|
349
|
+
cwd: options.workspacePath,
|
|
350
|
+
env: options.runtimeEnv,
|
|
351
|
+
maxTurns: 8,
|
|
352
|
+
maxBudgetUsd: 0.25,
|
|
353
|
+
systemPrompt: 'Follow the direct MCP contract instruction exactly. Do not use shell, file, web, or unrelated tools.',
|
|
354
|
+
settingSources: [],
|
|
355
|
+
permissionMode: 'default',
|
|
356
|
+
allowedTools: Object.keys(options.mcpServers).flatMap((serverId) => [`mcp__${serverId}__inspect`, `mcp__${serverId}__advance`]),
|
|
357
|
+
canUseTool: allow,
|
|
358
|
+
mcpServers: options.mcpServers,
|
|
359
|
+
strictMcpConfig: true,
|
|
360
|
+
persistSession: true,
|
|
361
|
+
abortController: options.abortController,
|
|
362
|
+
pathToClaudeCodeExecutable: options.profilePath,
|
|
363
|
+
spawnClaudeCodeProcess: options.spawnClaudeCodeProcess,
|
|
364
|
+
};
|
|
365
|
+
if (options.resume)
|
|
366
|
+
sdkOptions.resume = options.resume;
|
|
367
|
+
const model = options.model ?? options.runtimeEnv.PATHGRADE_DIRECT_MCP_CLAUDE_MODEL ?? 'claude-haiku-4-5-20251001';
|
|
368
|
+
if (model)
|
|
369
|
+
sdkOptions.model = model;
|
|
370
|
+
let sessionId;
|
|
371
|
+
let status = 'missing-result';
|
|
372
|
+
try {
|
|
373
|
+
for await (const message of options.query({ prompt, options: sdkOptions })) {
|
|
374
|
+
if (message.session_id)
|
|
375
|
+
sessionId = message.session_id;
|
|
376
|
+
if (message.type === 'result')
|
|
377
|
+
status = message.subtype;
|
|
378
|
+
}
|
|
379
|
+
return { status, cancelled: status === 'error_during_execution' || status === 'error_max_turns', sessionId };
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
const name = error instanceof Error ? error.name : '';
|
|
383
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
384
|
+
const cancelled = name === 'AbortError' || name === 'CLAUDE_CODE_PROCESS_ERROR' || text === 'Claude Code process aborted by user';
|
|
385
|
+
if (!cancelled)
|
|
386
|
+
throw error;
|
|
387
|
+
return { status: text || name || 'aborted', cancelled: true, sessionId };
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function readAbortSignal(extra) {
|
|
391
|
+
if (extra === null || typeof extra !== 'object' || !('signal' in extra) || !(extra.signal instanceof AbortSignal))
|
|
392
|
+
return undefined;
|
|
393
|
+
return extra.signal;
|
|
394
|
+
}
|
|
395
|
+
function readPathgradeCode(result) {
|
|
396
|
+
const structured = result.structuredContent;
|
|
397
|
+
if (!structured || typeof structured.pathgrade !== 'object' || structured.pathgrade === null || Array.isArray(structured.pathgrade) || !('code' in structured.pathgrade))
|
|
398
|
+
return undefined;
|
|
399
|
+
return typeof structured.pathgrade.code === 'string' ? structured.pathgrade.code : undefined;
|
|
400
|
+
}
|
|
401
|
+
function findEffectfulSuccessCase(artifact) {
|
|
402
|
+
const state = artifact.canonicalIr.states[artifact.canonicalIr.initialState];
|
|
403
|
+
const entries = Object.entries(state.tools);
|
|
404
|
+
const effectful = entries.find(([, candidate]) => candidate.effectful && candidate.cases.some((entry) => entry.transition && entry.outcome.kind === 'success' && 'exactArgs' in entry.when));
|
|
405
|
+
const error = entries.find(([, candidate]) => candidate.cases.some((entry) => entry.outcome.kind === 'tool-error' && 'exactArgs' in entry.when));
|
|
406
|
+
const pure = entries.find(([, candidate]) => !candidate.effectful && candidate.cases.some((entry) => entry.outcome.kind === 'success'));
|
|
407
|
+
if (!effectful || !error || !pure)
|
|
408
|
+
throw new Error('Claude vertical requires an effectful success, structured error, and pure success in the initial state');
|
|
409
|
+
const successCase = effectful[1].cases.find((entry) => entry.transition && entry.outcome.kind === 'success' && 'exactArgs' in entry.when);
|
|
410
|
+
const errorCase = error[1].cases.find((entry) => entry.outcome.kind === 'tool-error' && 'exactArgs' in entry.when);
|
|
411
|
+
const pureCase = pure[1].cases.find((entry) => entry.outcome.kind === 'success');
|
|
412
|
+
return {
|
|
413
|
+
caseId: successCase.id,
|
|
414
|
+
effectfulToolId: effectful[0].split('/')[1],
|
|
415
|
+
successArgs: 'exactArgs' in successCase.when ? successCase.when.exactArgs : {},
|
|
416
|
+
errorArgs: 'exactArgs' in errorCase.when ? errorCase.when.exactArgs : {},
|
|
417
|
+
pureToolId: pure[0].split('/')[1],
|
|
418
|
+
pureArgs: 'exactArgs' in pureCase.when ? pureCase.when.exactArgs : deriveArgs(pureCase.when.all),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function deriveArgs(predicates) {
|
|
422
|
+
const result = {};
|
|
423
|
+
for (const predicate of predicates)
|
|
424
|
+
if (predicate.pointer.startsWith('/') && 'equals' in predicate)
|
|
425
|
+
result[predicate.pointer.slice(1)] = predicate.equals;
|
|
426
|
+
return result;
|
|
427
|
+
}
|
|
428
|
+
async function terminateChild(child) {
|
|
429
|
+
if (child.exitCode !== null)
|
|
430
|
+
return;
|
|
431
|
+
child.kill('SIGTERM');
|
|
432
|
+
if (await waitForExit(child, 2_000))
|
|
433
|
+
return;
|
|
434
|
+
child.kill('SIGKILL');
|
|
435
|
+
if (!await waitForExit(child, 2_000))
|
|
436
|
+
throw new Error('Claude child did not exit after SIGKILL');
|
|
437
|
+
}
|
|
438
|
+
async function waitForExit(child, timeoutMs) {
|
|
439
|
+
if (child.exitCode !== null)
|
|
440
|
+
return true;
|
|
441
|
+
return new Promise((resolve) => {
|
|
442
|
+
const onExit = () => { clearTimeout(timer); resolve(true); };
|
|
443
|
+
const timer = setTimeout(() => { child.off('exit', onExit); resolve(false); }, timeoutMs);
|
|
444
|
+
child.once('exit', onExit);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
async function withTimeout(promise, timeoutMs, message) {
|
|
448
|
+
let timer;
|
|
449
|
+
try {
|
|
450
|
+
return await Promise.race([promise, new Promise((_resolve, reject) => { timer = setTimeout(() => reject(new Error(message)), timeoutMs); })]);
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
if (timer)
|
|
454
|
+
clearTimeout(timer);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RuntimeLock } from './runtime-lock.js';
|
|
2
|
+
import type { JsonObject, RuntimeCapabilityProfile } from './types.js';
|
|
3
|
+
export declare const CLAUDE_DIRECT_MCP_NORMALIZED_HANDSHAKE: JsonObject;
|
|
4
|
+
/** Reviewed macOS arm64 pin captured by RFC 0006. Other platforms fail closed. */
|
|
5
|
+
export declare const CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE: RuntimeCapabilityProfile;
|
|
6
|
+
/** Code-owned relocatable identity lock for the admitted public Claude SDK slice. */
|
|
7
|
+
export declare const CLAUDE_DIRECT_MCP_DARWIN_ARM64_LOCK: RuntimeLock;
|
|
8
|
+
/** Resolve the bundled platform binary relative to the installed SDK package. */
|
|
9
|
+
export declare function resolveClaudeScenarioExecutable(): Promise<string>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { access, realpath } from 'node:fs/promises';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
export const CLAUDE_DIRECT_MCP_NORMALIZED_HANDSHAKE = {
|
|
6
|
+
normalizerVersion: 1,
|
|
7
|
+
provider: 'claude',
|
|
8
|
+
sdkVersion: '0.2.141',
|
|
9
|
+
controlPlane: 'native-sdk-parent-bridge',
|
|
10
|
+
streamableHttp: true,
|
|
11
|
+
controlPlaneHeaders: true,
|
|
12
|
+
parentOwnedSdkMcp: true,
|
|
13
|
+
cancellation: 'abort-controller',
|
|
14
|
+
sdkMethods: ['createSdkMcpServer', 'query'],
|
|
15
|
+
};
|
|
16
|
+
/** Reviewed macOS arm64 pin captured by RFC 0006. Other platforms fail closed. */
|
|
17
|
+
export const CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE = {
|
|
18
|
+
format: 'pathgrade-direct-mcp-runtime-profile-v1',
|
|
19
|
+
provider: 'claude',
|
|
20
|
+
platform: 'darwin',
|
|
21
|
+
architecture: 'arm64',
|
|
22
|
+
controlPlane: 'native-sdk-parent-bridge',
|
|
23
|
+
command: {
|
|
24
|
+
absolutePath: '/Users/nadavlac/projects/agent-evals/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64/claude',
|
|
25
|
+
args: [],
|
|
26
|
+
versionArgs: ['--version'],
|
|
27
|
+
},
|
|
28
|
+
expectedVersion: '2.1.141 (Claude Code)',
|
|
29
|
+
artifacts: [
|
|
30
|
+
{
|
|
31
|
+
label: 'claude-agent-sdk-0.2.141',
|
|
32
|
+
absolutePath: '/Users/nadavlac/projects/agent-evals/node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs',
|
|
33
|
+
sha256: '48bde6aeabf7e71ad5528bf52c8feb1642c21f505ea2495c70f39db7df226d97',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: 'claude-code-bundled-2.1.141',
|
|
37
|
+
absolutePath: '/Users/nadavlac/projects/agent-evals/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64/claude',
|
|
38
|
+
sha256: '31ac95bb19a33b1d0cddd3f3ff594bf8bfd2be5051cd2af7867109641cab705e',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: 'zod-4.3.6-commonjs-entry',
|
|
42
|
+
absolutePath: '/Users/nadavlac/projects/agent-evals/node_modules/zod/index.cjs',
|
|
43
|
+
sha256: '2a3455cecff4f7021c92d0a3e2e5fc170c0448dc5cc261160127e08f4888a9c2',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
requiredCapabilities: {
|
|
47
|
+
streamableHttp: true,
|
|
48
|
+
controlPlaneHeaders: true,
|
|
49
|
+
parentOwnedSdkMcp: true,
|
|
50
|
+
cancellation: 'abort-controller',
|
|
51
|
+
},
|
|
52
|
+
profileFingerprint: '463d1485d70e542cf2a356cae007469a7d94d0e848a26d9e9b7470dbc5262104',
|
|
53
|
+
runtimeCapabilityFingerprint: '990b779b75145c8d7bcc8cfebcb5b9c1aae30eb1436393b38e7e997941ecc162',
|
|
54
|
+
};
|
|
55
|
+
/** Code-owned relocatable identity lock for the admitted public Claude SDK slice. */
|
|
56
|
+
export const CLAUDE_DIRECT_MCP_DARWIN_ARM64_LOCK = {
|
|
57
|
+
format: 'pathgrade-direct-mcp-runtime-lock-v1',
|
|
58
|
+
provider: 'claude',
|
|
59
|
+
platform: 'darwin',
|
|
60
|
+
architecture: 'arm64',
|
|
61
|
+
controlPlane: 'native-sdk-parent-bridge',
|
|
62
|
+
version: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.expectedVersion,
|
|
63
|
+
commandArtifact: 'claude-code-bundled-2.1.141',
|
|
64
|
+
args: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.command.args,
|
|
65
|
+
versionArgs: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.command.versionArgs,
|
|
66
|
+
artifacts: [
|
|
67
|
+
{
|
|
68
|
+
id: 'claude-agent-sdk-0.2.141',
|
|
69
|
+
relativePath: '@anthropic-ai/claude-agent-sdk/sdk.mjs',
|
|
70
|
+
sha256: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.artifacts[0].sha256,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'claude-code-bundled-2.1.141',
|
|
74
|
+
relativePath: '@anthropic-ai/claude-agent-sdk-darwin-arm64/claude',
|
|
75
|
+
sha256: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.artifacts[1].sha256,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'zod-4.3.6-commonjs-entry',
|
|
79
|
+
relativePath: 'zod/index.cjs',
|
|
80
|
+
sha256: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.artifacts[2].sha256,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
capabilities: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.requiredCapabilities,
|
|
84
|
+
normalizedHandshakeSha256: CLAUDE_DIRECT_MCP_DARWIN_ARM64_PROFILE.runtimeCapabilityFingerprint,
|
|
85
|
+
};
|
|
86
|
+
/** Resolve the bundled platform binary relative to the installed SDK package. */
|
|
87
|
+
export async function resolveClaudeScenarioExecutable() {
|
|
88
|
+
const sdkEntry = createRequire(import.meta.url).resolve('@anthropic-ai/claude-agent-sdk');
|
|
89
|
+
const candidate = join(dirname(sdkEntry), '..', 'claude-agent-sdk-darwin-arm64', 'claude');
|
|
90
|
+
try {
|
|
91
|
+
await access(candidate, constants.X_OK);
|
|
92
|
+
return realpath(candidate);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new Error('RUNTIME_NOT_ADMITTED: no bundled Claude executable was discovered');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type TrackedResource = {
|
|
2
|
+
opaqueId: string;
|
|
3
|
+
kind: 'process' | 'descriptor' | 'socket' | 'listener' | 'transport' | 'invocation' | 'auth-copy';
|
|
4
|
+
close(): Promise<void>;
|
|
5
|
+
isOpen(): boolean;
|
|
6
|
+
};
|
|
7
|
+
export type ScanEntry = {
|
|
8
|
+
path: string;
|
|
9
|
+
status: 'clean' | 'bearer-found' | 'unreadable' | 'skipped';
|
|
10
|
+
reason?: string;
|
|
11
|
+
};
|
|
12
|
+
export type CleanupReport = {
|
|
13
|
+
scans: ScanEntry[];
|
|
14
|
+
released: string[];
|
|
15
|
+
failures: {
|
|
16
|
+
opaqueId: string;
|
|
17
|
+
reason: string;
|
|
18
|
+
}[];
|
|
19
|
+
scratchDeleted: boolean;
|
|
20
|
+
bearerFound: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type TrialArtifactPhase = 'provisional' | 'final';
|
|
23
|
+
export declare function persistTrialArtifact(path: string, content: string, phase: TrialArtifactPhase): Promise<void>;
|
|
24
|
+
export declare class TrialResourceRegistry {
|
|
25
|
+
private readonly resources;
|
|
26
|
+
register(resource: TrackedResource): void;
|
|
27
|
+
entries(): readonly TrackedResource[];
|
|
28
|
+
}
|
|
29
|
+
export declare function scanBearerSurfaces(roots: readonly string[], bearer: string): Promise<ScanEntry[]>;
|
|
30
|
+
export declare function cleanupTrial(options: {
|
|
31
|
+
registry: TrialResourceRegistry;
|
|
32
|
+
scanRoots: readonly string[];
|
|
33
|
+
scratchPath: string;
|
|
34
|
+
approvedScratchParent: string;
|
|
35
|
+
bearer: string;
|
|
36
|
+
persistRedacted(report: CleanupReport, phase: TrialArtifactPhase): Promise<void>;
|
|
37
|
+
zeroBearer?(): void;
|
|
38
|
+
removeScratch?(path: string): Promise<void>;
|
|
39
|
+
}): Promise<CleanupReport>;
|
|
40
|
+
export declare class IdempotentTrialCleaner {
|
|
41
|
+
private readonly options;
|
|
42
|
+
private result?;
|
|
43
|
+
constructor(options: Parameters<typeof cleanupTrial>[0]);
|
|
44
|
+
dispose(): Promise<CleanupReport>;
|
|
45
|
+
}
|
|
46
|
+
export declare class TrialExecutionError extends Error {
|
|
47
|
+
constructor(message: string, options: {
|
|
48
|
+
primary?: unknown;
|
|
49
|
+
secondary: readonly unknown[];
|
|
50
|
+
});
|
|
51
|
+
readonly secondary: readonly unknown[];
|
|
52
|
+
}
|
|
53
|
+
export declare function runWithUnconditionalSettlement<T>(options: {
|
|
54
|
+
sendTurn(): Promise<T>;
|
|
55
|
+
finalize(input: {
|
|
56
|
+
providerResult?: T;
|
|
57
|
+
providerError?: unknown;
|
|
58
|
+
cancellationRequested: boolean;
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
cleanup(): Promise<void>;
|
|
61
|
+
cancellationRequested?: boolean;
|
|
62
|
+
}): Promise<T>;
|