@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.
- 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 +57 -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,182 @@
|
|
|
1
|
+
import { lstat, opendir, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
import { removeSandboxRoot } from '../../providers/sandbox-lifecycle.js';
|
|
4
|
+
export async function persistTrialArtifact(path, content, phase) {
|
|
5
|
+
const pending = `${path}.pending`;
|
|
6
|
+
const destination = phase === 'provisional' ? pending : path;
|
|
7
|
+
const temporary = `${destination}.${process.pid}.tmp`;
|
|
8
|
+
try {
|
|
9
|
+
await writeFile(temporary, content, { mode: 0o600 });
|
|
10
|
+
await rename(temporary, destination);
|
|
11
|
+
if (phase === 'final')
|
|
12
|
+
await rm(pending, { force: true });
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
16
|
+
if (phase === 'final')
|
|
17
|
+
await rm(pending, { force: true }).catch(() => undefined);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class TrialResourceRegistry {
|
|
22
|
+
resources = new Map();
|
|
23
|
+
register(resource) {
|
|
24
|
+
if (this.resources.has(resource.opaqueId))
|
|
25
|
+
throw new Error(`duplicate resource ID ${resource.opaqueId}`);
|
|
26
|
+
this.resources.set(resource.opaqueId, resource);
|
|
27
|
+
}
|
|
28
|
+
entries() { return [...this.resources.values()]; }
|
|
29
|
+
}
|
|
30
|
+
function within(root, path) {
|
|
31
|
+
const difference = relative(root, path);
|
|
32
|
+
return difference === '' || (!difference.startsWith('..') && !isAbsolute(difference));
|
|
33
|
+
}
|
|
34
|
+
export async function scanBearerSurfaces(roots, bearer) {
|
|
35
|
+
if (bearer.length === 0)
|
|
36
|
+
throw new Error('bearer must be non-empty');
|
|
37
|
+
const entries = [];
|
|
38
|
+
for (const declaredRoot of roots) {
|
|
39
|
+
try {
|
|
40
|
+
const root = await realpath(declaredRoot);
|
|
41
|
+
const pending = [root];
|
|
42
|
+
const visited = new Set();
|
|
43
|
+
while (pending.length > 0) {
|
|
44
|
+
const path = pending.pop();
|
|
45
|
+
try {
|
|
46
|
+
const stat = await lstat(path);
|
|
47
|
+
if (stat.isSymbolicLink()) {
|
|
48
|
+
const target = await realpath(path);
|
|
49
|
+
if (!within(root, target))
|
|
50
|
+
entries.push({ path, status: 'clean', reason: 'symlink target outside approved root was not followed' });
|
|
51
|
+
else if (!visited.has(target))
|
|
52
|
+
pending.push(target);
|
|
53
|
+
}
|
|
54
|
+
else if (stat.isDirectory()) {
|
|
55
|
+
const canonicalPath = await realpath(path);
|
|
56
|
+
if (visited.has(canonicalPath))
|
|
57
|
+
continue;
|
|
58
|
+
visited.add(canonicalPath);
|
|
59
|
+
const directory = await opendir(path);
|
|
60
|
+
for await (const entry of directory)
|
|
61
|
+
pending.push(resolve(path, entry.name));
|
|
62
|
+
}
|
|
63
|
+
else if (stat.isFile()) {
|
|
64
|
+
const content = await readFile(path);
|
|
65
|
+
entries.push({ path, status: content.includes(Buffer.from(bearer)) ? 'bearer-found' : 'clean' });
|
|
66
|
+
}
|
|
67
|
+
else
|
|
68
|
+
entries.push({ path, status: 'skipped', reason: 'not a regular file, directory, or symlink' });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
entries.push({ path, status: 'unreadable', reason: error instanceof Error ? error.message : String(error) });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
entries.push({ path: declaredRoot, status: 'unreadable', reason: error instanceof Error ? error.message : String(error) });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return entries.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
80
|
+
}
|
|
81
|
+
export async function cleanupTrial(options) {
|
|
82
|
+
const released = [];
|
|
83
|
+
const failures = [];
|
|
84
|
+
for (const resource of [...options.registry.entries()].reverse()) {
|
|
85
|
+
try {
|
|
86
|
+
await resource.close();
|
|
87
|
+
if (resource.isOpen())
|
|
88
|
+
throw new Error('resource remained open after close');
|
|
89
|
+
released.push(resource.opaqueId);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
failures.push({ opaqueId: resource.opaqueId, reason: error instanceof Error ? error.message : String(error) });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const scans = await scanBearerSurfaces(options.scanRoots, options.bearer);
|
|
96
|
+
const bearerFound = scans.some((entry) => entry.status === 'bearer-found');
|
|
97
|
+
if (scans.some((entry) => entry.status === 'unreadable' || entry.status === 'skipped'))
|
|
98
|
+
failures.push({ opaqueId: 'surface-scan', reason: 'scan was incomplete' });
|
|
99
|
+
if (bearerFound)
|
|
100
|
+
failures.push({ opaqueId: 'surface-scan', reason: 'bearer found in writable surface' });
|
|
101
|
+
const report = { scans, released: released.sort(), failures, scratchDeleted: false, bearerFound };
|
|
102
|
+
if (!bearerFound) {
|
|
103
|
+
try {
|
|
104
|
+
await options.persistRedacted(report, 'provisional');
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
failures.push({ opaqueId: 'artifact-persistence', reason: error instanceof Error ? error.message : String(error) });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
failures.push({ opaqueId: 'artifact-persistence', reason: 'persistence refused because bearer remained' });
|
|
112
|
+
options.zeroBearer?.();
|
|
113
|
+
try {
|
|
114
|
+
const parent = await realpath(options.approvedScratchParent);
|
|
115
|
+
const scratch = await realpath(options.scratchPath);
|
|
116
|
+
if (scratch === parent || !within(parent, scratch))
|
|
117
|
+
throw new Error('scratch path is outside its approved parent');
|
|
118
|
+
await (options.removeScratch ?? removeSandboxRoot)(scratch);
|
|
119
|
+
report.scratchDeleted = true;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
failures.push({ opaqueId: 'scratch', reason: error instanceof Error ? error.message : String(error) });
|
|
123
|
+
}
|
|
124
|
+
if (!bearerFound) {
|
|
125
|
+
try {
|
|
126
|
+
await options.persistRedacted(report, 'final');
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
failures.push({ opaqueId: 'artifact-finalization', reason: error instanceof Error ? error.message : String(error) });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return report;
|
|
133
|
+
}
|
|
134
|
+
export class IdempotentTrialCleaner {
|
|
135
|
+
options;
|
|
136
|
+
result;
|
|
137
|
+
constructor(options) {
|
|
138
|
+
this.options = options;
|
|
139
|
+
}
|
|
140
|
+
dispose() {
|
|
141
|
+
this.result ??= cleanupTrial(this.options);
|
|
142
|
+
return this.result;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export class TrialExecutionError extends Error {
|
|
146
|
+
constructor(message, options) {
|
|
147
|
+
super(message, { cause: options.primary });
|
|
148
|
+
this.name = 'TrialExecutionError';
|
|
149
|
+
this.secondary = options.secondary;
|
|
150
|
+
}
|
|
151
|
+
secondary;
|
|
152
|
+
}
|
|
153
|
+
export async function runWithUnconditionalSettlement(options) {
|
|
154
|
+
let providerResult;
|
|
155
|
+
let providerError;
|
|
156
|
+
const secondary = [];
|
|
157
|
+
try {
|
|
158
|
+
providerResult = await options.sendTurn();
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
providerError = error;
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
try {
|
|
165
|
+
await options.finalize({ providerResult, providerError, cancellationRequested: options.cancellationRequested ?? false });
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
secondary.push(error);
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
await options.cleanup();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
secondary.push(error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (providerError !== undefined || secondary.length > 0) {
|
|
178
|
+
throw new TrialExecutionError(providerError instanceof Error ? providerError.message : 'trial settlement failed', { primary: providerError, secondary });
|
|
179
|
+
}
|
|
180
|
+
// SAFETY: reaching this line proves sendTurn resolved and neither it nor settlement recorded an error.
|
|
181
|
+
return providerResult;
|
|
182
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type CleanupReport } from './cleanup.js';
|
|
2
|
+
import { type AppServerSessionHandle } from '../../agents/codex-app-server/transport.js';
|
|
3
|
+
import type { EvidenceEnvelope, ScenarioArtifact, ScenarioMachineV2 } from './types.js';
|
|
4
|
+
export declare const CODEX_DIRECT_MCP_FLAG = "PATHGRADE_INTERNAL_CODEX_DIRECT_MCP_V2";
|
|
5
|
+
export type CodexDirectMcpChecks = {
|
|
6
|
+
runtimeAdmitted: boolean;
|
|
7
|
+
secretFreeProject: boolean;
|
|
8
|
+
directCall: boolean;
|
|
9
|
+
structuredToolError: boolean;
|
|
10
|
+
fencedCancellation: boolean;
|
|
11
|
+
providerTerminal: boolean;
|
|
12
|
+
hostQuiescent: boolean;
|
|
13
|
+
postCancelReuse: boolean;
|
|
14
|
+
privateReplay: boolean;
|
|
15
|
+
cleanup: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type CodexDirectMcpTrialResult = {
|
|
18
|
+
selected: true;
|
|
19
|
+
profileFingerprint: string;
|
|
20
|
+
artifact: ScenarioArtifact;
|
|
21
|
+
evidence: readonly EvidenceEnvelope[];
|
|
22
|
+
checks: CodexDirectMcpChecks;
|
|
23
|
+
cleanup: CleanupReport;
|
|
24
|
+
cancellationStatus: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function isCodexDirectMcpSelected(environment?: NodeJS.ProcessEnv): boolean;
|
|
27
|
+
export declare function codexDirectMcpDisableReason(): string | undefined;
|
|
28
|
+
/** Test-only reset for proving the fail-closed process latch. */
|
|
29
|
+
export declare function __resetCodexDirectMcpDisablementForTesting(): void;
|
|
30
|
+
export declare function runCodexDirectMcpTrial(options: {
|
|
31
|
+
scenario: ScenarioMachineV2;
|
|
32
|
+
environment?: NodeJS.ProcessEnv;
|
|
33
|
+
model?: string;
|
|
34
|
+
artifactDirectory?: string;
|
|
35
|
+
createControlPlane?: (context: {
|
|
36
|
+
cwd: string;
|
|
37
|
+
env: NodeJS.ProcessEnv;
|
|
38
|
+
binary: string;
|
|
39
|
+
}) => AppServerSessionHandle;
|
|
40
|
+
}): Promise<CodexDirectMcpTrialResult>;
|
|
41
|
+
export declare function mapCodexProviderEnvironment(environment: NodeJS.ProcessEnv, codexHome: string): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { copyFile, mkdir, mkdtemp, realpath, rm } from 'node:fs/promises';
|
|
2
|
+
import { homedir, tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { compileScenario } from './compiler.js';
|
|
6
|
+
import { IdempotentTrialCleaner, persistTrialArtifact, TrialExecutionError, TrialResourceRegistry } from './cleanup.js';
|
|
7
|
+
import { PrivateReplayVault, replayPrivateEvidence, verifyEvidenceChain } from './evidence.js';
|
|
8
|
+
import { ScenarioHostReducer } from './host-reducer.js';
|
|
9
|
+
import { materializeProjection } from './materialize.js';
|
|
10
|
+
import { projectEndpoints } from './projection.js';
|
|
11
|
+
import { admitRuntimeProfile } from './runtime-profile.js';
|
|
12
|
+
import { CODEX_DIRECT_MCP_DARWIN_ARM64_PROFILE, CODEX_DIRECT_MCP_NORMALIZED_HANDSHAKE } from './codex-profile.js';
|
|
13
|
+
import { createInvocationBarrier, startScenarioHttpHost } from './scenario-http-host.js';
|
|
14
|
+
import { spawnAppServerTransport } from '../../agents/codex-app-server/transport.js';
|
|
15
|
+
import { mergeCodexScenarioConfigs } from '../../agents/codex-app-server/scenario-mount.js';
|
|
16
|
+
export const CODEX_DIRECT_MCP_FLAG = 'PATHGRADE_INTERNAL_CODEX_DIRECT_MCP_V2';
|
|
17
|
+
const disabledProfiles = new Map();
|
|
18
|
+
export function isCodexDirectMcpSelected(environment = process.env) {
|
|
19
|
+
const fingerprint = CODEX_DIRECT_MCP_DARWIN_ARM64_PROFILE.profileFingerprint;
|
|
20
|
+
return environment[CODEX_DIRECT_MCP_FLAG] === '1' && !disabledProfiles.has(fingerprint);
|
|
21
|
+
}
|
|
22
|
+
export function codexDirectMcpDisableReason() {
|
|
23
|
+
return disabledProfiles.get(CODEX_DIRECT_MCP_DARWIN_ARM64_PROFILE.profileFingerprint);
|
|
24
|
+
}
|
|
25
|
+
/** Test-only reset for proving the fail-closed process latch. */
|
|
26
|
+
// oxlint-disable-next-line inhuman/no-empty-wrappers -- This test seam exposes Map.clear without exposing mutable production state.
|
|
27
|
+
export function __resetCodexDirectMcpDisablementForTesting() {
|
|
28
|
+
disabledProfiles.clear();
|
|
29
|
+
}
|
|
30
|
+
export async function runCodexDirectMcpTrial(options) {
|
|
31
|
+
const environment = options.environment ?? process.env;
|
|
32
|
+
const profile = CODEX_DIRECT_MCP_DARWIN_ARM64_PROFILE;
|
|
33
|
+
if (!isCodexDirectMcpSelected(environment)) {
|
|
34
|
+
const reason = disabledProfiles.get(profile.profileFingerprint);
|
|
35
|
+
throw new Error(reason ? `Codex direct MCP disabled for admitted profile: ${reason}` : `${CODEX_DIRECT_MCP_FLAG} is disabled`);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
return await executeTrial({ ...options, environment });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
42
|
+
disabledProfiles.set(profile.profileFingerprint, reason.slice(0, 500));
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function executeTrial(options) {
|
|
47
|
+
const compiled = compileScenario(options.scenario);
|
|
48
|
+
if (!compiled.ok)
|
|
49
|
+
throw new Error(`Scenario compilation failed: ${JSON.stringify(compiled.diagnostics)}`);
|
|
50
|
+
const profile = CODEX_DIRECT_MCP_DARWIN_ARM64_PROFILE;
|
|
51
|
+
const barrierCase = findEffectfulSuccessCase(compiled.artifact);
|
|
52
|
+
const createdScratchParent = await mkdtemp(join(tmpdir(), 'pathgrade-codex-direct-mcp-'));
|
|
53
|
+
let scratchParent;
|
|
54
|
+
try {
|
|
55
|
+
scratchParent = await realpath(createdScratchParent);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
await rm(createdScratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
const scratchPath = join(scratchParent, 'trial');
|
|
62
|
+
const workspacePath = join(scratchPath, 'workspace');
|
|
63
|
+
const codexHome = join(scratchPath, 'codex-home');
|
|
64
|
+
try {
|
|
65
|
+
await mkdir(workspacePath, { recursive: true });
|
|
66
|
+
await mkdir(codexHome, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const trialId = `codex-${randomBytes(12).toString('hex')}`;
|
|
73
|
+
const macKey = randomBytes(32);
|
|
74
|
+
const replayVault = new PrivateReplayVault(randomBytes(32));
|
|
75
|
+
const reducer = new ScenarioHostReducer(trialId, compiled.artifact, macKey, replayVault);
|
|
76
|
+
reducer.markCompiled();
|
|
77
|
+
const barrier = createInvocationBarrier(barrierCase.caseId);
|
|
78
|
+
const host = await startScenarioHttpHost({ artifact: compiled.artifact, reducer, barrier }).catch(async (error) => {
|
|
79
|
+
macKey.fill(0);
|
|
80
|
+
replayVault.dispose();
|
|
81
|
+
reducer.dispose();
|
|
82
|
+
await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
83
|
+
throw error;
|
|
84
|
+
});
|
|
85
|
+
const registry = new TrialResourceRegistry();
|
|
86
|
+
let handle;
|
|
87
|
+
let transport;
|
|
88
|
+
let hostOpen = true;
|
|
89
|
+
let controlPlaneOpen = false;
|
|
90
|
+
let hostQuiescentBeforeCleanup = false;
|
|
91
|
+
registry.register({ opaqueId: 'scenario-host', kind: 'listener', close: async () => { await host.dispose(); hostOpen = false; }, isOpen: () => hostOpen });
|
|
92
|
+
registry.register({ opaqueId: 'scenario-transports', kind: 'transport', close: () => host.dispose(), isOpen: () => host.activeTransportCount() !== 0 });
|
|
93
|
+
let persisted = false;
|
|
94
|
+
const artifactDirectory = options.artifactDirectory;
|
|
95
|
+
const cleaner = new IdempotentTrialCleaner({
|
|
96
|
+
registry,
|
|
97
|
+
scanRoots: [workspacePath, codexHome],
|
|
98
|
+
scratchPath,
|
|
99
|
+
approvedScratchParent: scratchParent,
|
|
100
|
+
bearer: host.bearer.toString(),
|
|
101
|
+
persistRedacted: async (report, phase) => {
|
|
102
|
+
if (!artifactDirectory) {
|
|
103
|
+
if (phase === 'final')
|
|
104
|
+
persisted = true;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const path = join(artifactDirectory, 'trial-evidence.json');
|
|
108
|
+
await mkdir(dirname(path), { recursive: true });
|
|
109
|
+
const value = { artifactDigest: compiled.artifact.artifactDigest, evidence: reducer.ledger.events(), cleanup: { ...report, scans: report.scans.map((entry) => ({ ...entry, path: entry.path.replace(scratchParent, '<scratch>') })) } };
|
|
110
|
+
await persistTrialArtifact(path, `${JSON.stringify(value)}\n`, phase);
|
|
111
|
+
if (phase === 'final')
|
|
112
|
+
persisted = true;
|
|
113
|
+
},
|
|
114
|
+
zeroBearer: () => { host.bearer.fill(0); macKey.fill(0); replayVault.dispose(); },
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
const runtimeEnv = await prepareCodexEnvironment(options.environment, codexHome);
|
|
118
|
+
const admission = await admitRuntimeProfile(profile, {
|
|
119
|
+
provider: 'codex',
|
|
120
|
+
platform: process.platform,
|
|
121
|
+
architecture: process.arch,
|
|
122
|
+
environment: runtimeEnv,
|
|
123
|
+
forbiddenSecrets: [host.bearer.toString(), `Bearer ${host.bearer.toString()}`],
|
|
124
|
+
initializeControlPlane: async () => {
|
|
125
|
+
handle = options.createControlPlane?.({ cwd: workspacePath, env: runtimeEnv, binary: profile.command.absolutePath })
|
|
126
|
+
?? spawnAppServerTransport({
|
|
127
|
+
binary: profile.command.absolutePath,
|
|
128
|
+
args: profile.command.args,
|
|
129
|
+
cwd: workspacePath,
|
|
130
|
+
env: runtimeEnv,
|
|
131
|
+
logStderrOnExit: false,
|
|
132
|
+
});
|
|
133
|
+
controlPlaneOpen = true;
|
|
134
|
+
transport = handle.transport;
|
|
135
|
+
try {
|
|
136
|
+
const initialized = await transport.sendRequest('initialize', {
|
|
137
|
+
clientInfo: { name: 'pathgrade-direct-mcp', version: '2.0.0', title: null },
|
|
138
|
+
capabilities: { experimentalApi: true, optOutNotificationMethods: null },
|
|
139
|
+
});
|
|
140
|
+
const admittedVersion = profile.expectedVersion.replace(/^codex-cli\s+/, '');
|
|
141
|
+
if (!initialized.userAgent?.includes(admittedVersion))
|
|
142
|
+
throw new Error('Codex initialize userAgent did not match admitted version');
|
|
143
|
+
transport.sendNotification('initialized', null);
|
|
144
|
+
return {
|
|
145
|
+
normalizedHandshake: CODEX_DIRECT_MCP_NORMALIZED_HANDSHAKE,
|
|
146
|
+
capabilities: profile.requiredCapabilities,
|
|
147
|
+
close: async () => { await handle?.close(); controlPlaneOpen = false; },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
await handle.close().catch(() => undefined);
|
|
152
|
+
controlPlaneOpen = false;
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
if (!admission.ok || !transport || !handle) {
|
|
158
|
+
throw new Error(`RUNTIME_NOT_ADMITTED: ${admission.ok ? 'control plane unavailable' : admission.reason}`);
|
|
159
|
+
}
|
|
160
|
+
const admittedTransport = transport;
|
|
161
|
+
const admittedHandle = handle;
|
|
162
|
+
registry.register({ opaqueId: 'codex-app-server', kind: 'process', close: async () => { await admittedHandle.close(); controlPlaneOpen = false; }, isOpen: () => controlPlaneOpen });
|
|
163
|
+
reducer.markAdmitted();
|
|
164
|
+
const projected = projectEndpoints(host.endpoints, admission.profile);
|
|
165
|
+
if (!Array.isArray(projected))
|
|
166
|
+
throw new Error(`Projection rejected: ${projected.reason}`);
|
|
167
|
+
const configs = projected.map((plan) => materializeProjection(plan, {
|
|
168
|
+
resolve(secretRef) {
|
|
169
|
+
if (secretRef !== host.secretRef)
|
|
170
|
+
throw new Error(`Unknown secret reference: ${secretRef}`);
|
|
171
|
+
return `Bearer ${host.bearer.toString()}`;
|
|
172
|
+
},
|
|
173
|
+
}));
|
|
174
|
+
const mcpConfig = mergeCodexScenarioConfigs(configs);
|
|
175
|
+
reducer.markReady();
|
|
176
|
+
const started = await admittedTransport.sendRequest('thread/start', {
|
|
177
|
+
cwd: workspacePath,
|
|
178
|
+
approvalPolicy: 'never',
|
|
179
|
+
sandbox: 'read-only',
|
|
180
|
+
ephemeral: true,
|
|
181
|
+
model: options.model ?? options.environment.PATHGRADE_DIRECT_MCP_CODEX_MODEL ?? 'gpt-5.6-luna',
|
|
182
|
+
config: mcpConfig,
|
|
183
|
+
});
|
|
184
|
+
const threadId = started.thread.id;
|
|
185
|
+
const serverId = host.endpoints[0]?.serverId;
|
|
186
|
+
if (!serverId)
|
|
187
|
+
throw new Error('Scenario produced no Codex MCP endpoint');
|
|
188
|
+
reducer.beginTurn('direct-call', 'codex');
|
|
189
|
+
const directResult = await admittedTransport.sendRequest('mcpServer/tool/call', {
|
|
190
|
+
threadId, server: serverId, tool: barrierCase.pureToolId, arguments: barrierCase.pureArgs,
|
|
191
|
+
meta: { source: 'pathgrade-direct-mcp-contract' },
|
|
192
|
+
});
|
|
193
|
+
if (directResult.isError === true)
|
|
194
|
+
throw new Error('Direct MCP success call returned isError');
|
|
195
|
+
reducer.finalize('settled');
|
|
196
|
+
reducer.beginTurn('structured-error', 'codex');
|
|
197
|
+
const errorResult = await admittedTransport.sendRequest('mcpServer/tool/call', {
|
|
198
|
+
threadId, server: serverId, tool: barrierCase.effectfulToolId, arguments: barrierCase.errorArgs,
|
|
199
|
+
meta: { source: 'pathgrade-direct-mcp-contract' },
|
|
200
|
+
});
|
|
201
|
+
if (errorResult.isError !== true || !errorResult.structuredContent?.pathgrade?.code)
|
|
202
|
+
throw new Error('Codex did not preserve the structured scenario tool error');
|
|
203
|
+
reducer.finalize('settled');
|
|
204
|
+
reducer.beginTurn('cancelled-turn', 'codex');
|
|
205
|
+
const providerSignals = [];
|
|
206
|
+
const turnCompletions = createTurnCompletionMonitor(admittedTransport);
|
|
207
|
+
const stopCollectingSignals = admittedTransport.onNotification((notification) => {
|
|
208
|
+
// SAFETY: only optional diagnostic fields are read from the exact-pinned Codex notification schema.
|
|
209
|
+
const params = notification.params;
|
|
210
|
+
if (notification.method === 'item/completed')
|
|
211
|
+
providerSignals.push(`item:${params?.item?.type ?? 'unknown'}:${params?.item?.status ?? 'unknown'}:${params?.item?.server ?? ''}:${params?.item?.tool ?? ''}`);
|
|
212
|
+
if (notification.method === 'turn/completed')
|
|
213
|
+
providerSignals.push(`turn:${params?.turn?.status ?? params?.status ?? 'unknown'}`);
|
|
214
|
+
if (notification.method === 'mcpServer/startupStatus/updated')
|
|
215
|
+
providerSignals.push(`startup:${params?.name ?? 'unknown'}:${params?.status ?? 'unknown'}:${params?.error ? 'error' : 'clean'}`);
|
|
216
|
+
});
|
|
217
|
+
let observingProvider = true;
|
|
218
|
+
registry.register({
|
|
219
|
+
opaqueId: 'codex-provider-observers',
|
|
220
|
+
kind: 'descriptor',
|
|
221
|
+
close: async () => {
|
|
222
|
+
if (!observingProvider)
|
|
223
|
+
return;
|
|
224
|
+
stopCollectingSignals();
|
|
225
|
+
turnCompletions.dispose();
|
|
226
|
+
observingProvider = false;
|
|
227
|
+
},
|
|
228
|
+
isOpen: () => observingProvider,
|
|
229
|
+
});
|
|
230
|
+
const turnStarted = await admittedTransport.sendRequest('turn/start', {
|
|
231
|
+
threadId,
|
|
232
|
+
input: [{
|
|
233
|
+
type: 'text',
|
|
234
|
+
text: `Mandatory contract action: invoke the ${serverId}.${barrierCase.effectfulToolId} MCP tool exactly once with arguments ${JSON.stringify(barrierCase.successArgs)}. Do not answer with text before the tool finishes, do not call another tool, and do not use shell commands or edit files. A response without this MCP invocation is invalid.`,
|
|
235
|
+
text_elements: [],
|
|
236
|
+
}],
|
|
237
|
+
});
|
|
238
|
+
const turnId = turnStarted.turn?.id ?? turnStarted.turnId;
|
|
239
|
+
if (!turnId)
|
|
240
|
+
throw new Error('Codex turn/start returned no turn id');
|
|
241
|
+
const terminal = turnCompletions.waitFor(turnId, 120_000);
|
|
242
|
+
const entered = await withTimeout(Promise.race([
|
|
243
|
+
barrier.reached().then(() => 'barrier'),
|
|
244
|
+
terminal.then((status) => `terminal:${status}`),
|
|
245
|
+
]), 90_000, 'Codex produced neither a blocking MCP invocation nor a terminal');
|
|
246
|
+
if (entered !== 'barrier')
|
|
247
|
+
throw new Error(`Codex reached ${entered} before the blocking MCP transition (${providerSignals.join(',')})`);
|
|
248
|
+
reducer.fence();
|
|
249
|
+
await withTimeout(admittedTransport.sendRequest('turn/interrupt', { threadId, turnId }), 15_000, 'Timed out sending Codex turn interrupt');
|
|
250
|
+
reducer.abortUncommitted('provider-cancelled');
|
|
251
|
+
barrier.release();
|
|
252
|
+
const [cancellationStatus] = await Promise.all([terminal, host.waitForQuiescence(30_000)]);
|
|
253
|
+
hostQuiescentBeforeCleanup = host.activeTransportCount() === 0;
|
|
254
|
+
if (!hostQuiescentBeforeCleanup)
|
|
255
|
+
throw new Error('Scenario host was not quiescent after cancellation');
|
|
256
|
+
stopCollectingSignals();
|
|
257
|
+
turnCompletions.dispose();
|
|
258
|
+
observingProvider = false;
|
|
259
|
+
reducer.observeProviderTerminal();
|
|
260
|
+
if (cancellationStatus !== 'interrupted')
|
|
261
|
+
throw new Error(`Codex cancellation terminal was ${cancellationStatus}`);
|
|
262
|
+
reducer.finalize('cancelled-clean');
|
|
263
|
+
reducer.beginTurn('post-cancel-reuse', 'codex');
|
|
264
|
+
const reused = await admittedTransport.sendRequest('mcpServer/tool/call', {
|
|
265
|
+
threadId, server: serverId, tool: barrierCase.effectfulToolId, arguments: barrierCase.successArgs,
|
|
266
|
+
meta: { source: 'pathgrade-direct-mcp-contract' },
|
|
267
|
+
});
|
|
268
|
+
if (reused.isError === true)
|
|
269
|
+
throw new Error('Post-cancel direct MCP reuse failed');
|
|
270
|
+
reducer.finalize('settled');
|
|
271
|
+
const replay = replayPrivateEvidence({ artifact: compiled.artifact, evidence: reducer.ledger.events(), vault: replayVault, macKey });
|
|
272
|
+
if (!replay.ok || replay.poisoned || replay.finalState !== reducer.state)
|
|
273
|
+
throw new Error(`Private replay failed: ${replay.ok ? 'state mismatch' : replay.reason}`);
|
|
274
|
+
const chain = verifyEvidenceChain(reducer.ledger.events(), compiled.artifact.artifactDigest);
|
|
275
|
+
if (!chain.ok)
|
|
276
|
+
throw new Error(`Evidence chain failed: ${chain.reason}`);
|
|
277
|
+
reducer.dispose();
|
|
278
|
+
const cleanup = await cleaner.dispose();
|
|
279
|
+
await rm(scratchParent, { recursive: true, force: true });
|
|
280
|
+
if (!persisted || cleanup.failures.length > 0 || cleanup.bearerFound || !cleanup.scratchDeleted)
|
|
281
|
+
throw new Error(`CLEANUP_FAILED: ${JSON.stringify(cleanup.failures)}`);
|
|
282
|
+
return {
|
|
283
|
+
selected: true,
|
|
284
|
+
profileFingerprint: profile.profileFingerprint,
|
|
285
|
+
artifact: compiled.artifact,
|
|
286
|
+
evidence: reducer.ledger.events(),
|
|
287
|
+
checks: {
|
|
288
|
+
runtimeAdmitted: true,
|
|
289
|
+
secretFreeProject: true,
|
|
290
|
+
directCall: true,
|
|
291
|
+
structuredToolError: true,
|
|
292
|
+
fencedCancellation: true,
|
|
293
|
+
providerTerminal: true,
|
|
294
|
+
hostQuiescent: hostQuiescentBeforeCleanup,
|
|
295
|
+
postCancelReuse: true,
|
|
296
|
+
privateReplay: true,
|
|
297
|
+
cleanup: true,
|
|
298
|
+
},
|
|
299
|
+
cleanup,
|
|
300
|
+
cancellationStatus,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
barrier.release();
|
|
305
|
+
try {
|
|
306
|
+
if (reducer.phase === 'ACTIVE')
|
|
307
|
+
reducer.fence();
|
|
308
|
+
if (reducer.phase === 'FENCING')
|
|
309
|
+
reducer.abortUncommitted('trial-failed');
|
|
310
|
+
if (reducer.phase !== 'DISPOSED' && reducer.phase !== 'NEW' && reducer.phase !== 'COMPILED' && reducer.phase !== 'ADMITTED')
|
|
311
|
+
reducer.finalize('poisoned', 'trial-failed');
|
|
312
|
+
}
|
|
313
|
+
catch (lifecycleFailure) {
|
|
314
|
+
// Preserve the primary failure; the cleaner reports any remaining resources.
|
|
315
|
+
void lifecycleFailure;
|
|
316
|
+
}
|
|
317
|
+
reducer.dispose();
|
|
318
|
+
let cleanupError;
|
|
319
|
+
try {
|
|
320
|
+
const report = await cleaner.dispose();
|
|
321
|
+
if (report.failures.length > 0 || report.bearerFound || !report.scratchDeleted)
|
|
322
|
+
cleanupError = new Error(`CLEANUP_FAILED: ${JSON.stringify(report.failures)}`);
|
|
323
|
+
}
|
|
324
|
+
catch (failure) {
|
|
325
|
+
cleanupError = failure instanceof Error ? failure : new Error(String(failure));
|
|
326
|
+
}
|
|
327
|
+
await rm(scratchParent, { recursive: true, force: true }).catch(() => undefined);
|
|
328
|
+
if (cleanupError)
|
|
329
|
+
throw new TrialExecutionError(error instanceof Error ? error.message : 'Codex direct MCP trial failed', { primary: error, secondary: [cleanupError] });
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function prepareCodexEnvironment(environment, codexHome) {
|
|
334
|
+
const result = mapCodexProviderEnvironment(environment, codexHome);
|
|
335
|
+
if (!result.OPENAI_API_KEY) {
|
|
336
|
+
const source = join(environment.CODEX_HOME || join(homedir(), '.codex'), 'auth.json');
|
|
337
|
+
const destination = join(codexHome, 'auth.json');
|
|
338
|
+
try {
|
|
339
|
+
await copyFile(source, destination);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
throw new Error(`Codex live cancellation requires OPENAI_API_KEY or readable managed auth at ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
export function mapCodexProviderEnvironment(environment, codexHome) {
|
|
348
|
+
const { APP_OPENAI_API_KEY: appApiKey, APP_OPENAI_BASE_URL: appBaseUrl, ...providerEnvironment } = environment;
|
|
349
|
+
const result = { ...providerEnvironment, CODEX_HOME: codexHome };
|
|
350
|
+
const apiKey = environment.OPENAI_API_KEY ?? appApiKey;
|
|
351
|
+
const baseUrl = environment.OPENAI_BASE_URL ?? appBaseUrl;
|
|
352
|
+
if (apiKey)
|
|
353
|
+
result.OPENAI_API_KEY = apiKey;
|
|
354
|
+
if (baseUrl)
|
|
355
|
+
result.OPENAI_BASE_URL = baseUrl;
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
function findEffectfulSuccessCase(artifact) {
|
|
359
|
+
const state = artifact.canonicalIr.states[artifact.canonicalIr.initialState];
|
|
360
|
+
const entries = Object.entries(state.tools);
|
|
361
|
+
const effectful = entries.find(([, tool]) => tool.effectful && tool.cases.some((entry) => entry.outcome.kind === 'success' && 'exactArgs' in entry.when && entry.transition));
|
|
362
|
+
const error = entries.find(([, tool]) => tool.cases.some((entry) => entry.outcome.kind === 'tool-error' && 'exactArgs' in entry.when));
|
|
363
|
+
const pure = entries.find(([, tool]) => !tool.effectful && tool.cases.some((entry) => entry.outcome.kind === 'success' && 'exactArgs' in entry.when || 'all' in entry.when));
|
|
364
|
+
if (!effectful || !error || !pure)
|
|
365
|
+
throw new Error('Codex vertical requires an effectful success, structured error, and pure success in the initial state');
|
|
366
|
+
const successCase = effectful[1].cases.find((entry) => entry.outcome.kind === 'success' && 'exactArgs' in entry.when && entry.transition);
|
|
367
|
+
const errorCase = error[1].cases.find((entry) => entry.outcome.kind === 'tool-error' && 'exactArgs' in entry.when);
|
|
368
|
+
const pureCase = pure[1].cases.find((entry) => entry.outcome.kind === 'success');
|
|
369
|
+
return {
|
|
370
|
+
caseId: successCase.id,
|
|
371
|
+
effectfulToolId: effectful[0].split('/')[1],
|
|
372
|
+
pureToolId: pure[0].split('/')[1],
|
|
373
|
+
successArgs: 'exactArgs' in successCase.when ? successCase.when.exactArgs : {},
|
|
374
|
+
errorArgs: 'exactArgs' in errorCase.when ? errorCase.when.exactArgs : {},
|
|
375
|
+
pureArgs: 'exactArgs' in pureCase.when ? pureCase.when.exactArgs : deriveArgs(pureCase.when.all),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function deriveArgs(predicates) {
|
|
379
|
+
const result = {};
|
|
380
|
+
for (const predicate of predicates) {
|
|
381
|
+
if (predicate.pointer.startsWith('/') && 'equals' in predicate)
|
|
382
|
+
result[predicate.pointer.slice(1)] = predicate.equals;
|
|
383
|
+
}
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
function createTurnCompletionMonitor(transport) {
|
|
387
|
+
const completed = new Map();
|
|
388
|
+
const waiters = new Map();
|
|
389
|
+
const off = transport.onNotification((notification) => {
|
|
390
|
+
if (notification.method !== 'turn/completed')
|
|
391
|
+
return;
|
|
392
|
+
// SAFETY: the exact-pinned Codex schema permits nested and legacy flattened terminal fields.
|
|
393
|
+
const params = notification.params;
|
|
394
|
+
const turnId = params?.turn?.id ?? params?.turnId;
|
|
395
|
+
if (!turnId)
|
|
396
|
+
return;
|
|
397
|
+
const status = params?.turn?.status ?? params?.status ?? 'unknown';
|
|
398
|
+
const waiter = waiters.get(turnId);
|
|
399
|
+
if (waiter) {
|
|
400
|
+
waiters.delete(turnId);
|
|
401
|
+
clearTimeout(waiter.timer);
|
|
402
|
+
waiter.resolve(status);
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
completed.set(turnId, status);
|
|
406
|
+
});
|
|
407
|
+
return {
|
|
408
|
+
waitFor(turnId, timeoutMs) {
|
|
409
|
+
const status = completed.get(turnId);
|
|
410
|
+
if (status !== undefined) {
|
|
411
|
+
completed.delete(turnId);
|
|
412
|
+
return Promise.resolve(status);
|
|
413
|
+
}
|
|
414
|
+
return new Promise((resolve, reject) => {
|
|
415
|
+
const timer = setTimeout(() => {
|
|
416
|
+
waiters.delete(turnId);
|
|
417
|
+
reject(new Error('Timed out waiting for Codex turn terminal'));
|
|
418
|
+
}, timeoutMs);
|
|
419
|
+
waiters.set(turnId, { resolve, timer });
|
|
420
|
+
});
|
|
421
|
+
},
|
|
422
|
+
dispose() {
|
|
423
|
+
off();
|
|
424
|
+
completed.clear();
|
|
425
|
+
for (const waiter of waiters.values())
|
|
426
|
+
clearTimeout(waiter.timer);
|
|
427
|
+
waiters.clear();
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
async function withTimeout(promise, timeoutMs, message) {
|
|
432
|
+
let timer;
|
|
433
|
+
return Promise.race([promise, new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(message)), timeoutMs); })])
|
|
434
|
+
.finally(() => { if (timer)
|
|
435
|
+
clearTimeout(timer); });
|
|
436
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RuntimeLock } from './runtime-lock.js';
|
|
2
|
+
import type { JsonObject, RuntimeCapabilityProfile } from './types.js';
|
|
3
|
+
export declare const CODEX_DIRECT_MCP_NORMALIZED_HANDSHAKE: JsonObject;
|
|
4
|
+
/** Reviewed macOS arm64 pin captured by RFC 0006. Other platforms fail closed. */
|
|
5
|
+
export declare const CODEX_DIRECT_MCP_DARWIN_ARM64_PROFILE: RuntimeCapabilityProfile;
|
|
6
|
+
/** Code-owned relocatable identity lock for the admitted public Codex slice. */
|
|
7
|
+
export declare const CODEX_DIRECT_MCP_DARWIN_ARM64_LOCK: RuntimeLock;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve one Codex candidate before admission. Selection is deterministic;
|
|
10
|
+
* identity failure never falls through to another process.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveCodexScenarioExecutable(environment: Readonly<Record<string, string | undefined>>): Promise<string>;
|