@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,220 @@
1
+ import { createHash } from 'node:crypto';
2
+ function assertValidString(value, path) {
3
+ for (let index = 0; index < value.length; index += 1) {
4
+ const code = value.charCodeAt(index);
5
+ if (code >= 0xD800 && code <= 0xDBFF) {
6
+ const next = value.charCodeAt(index + 1);
7
+ if (!(next >= 0xDC00 && next <= 0xDFFF))
8
+ throw new TypeError(`${path}: unpaired high surrogate`);
9
+ index += 1;
10
+ }
11
+ else if (code >= 0xDC00 && code <= 0xDFFF) {
12
+ throw new TypeError(`${path}: unpaired low surrogate`);
13
+ }
14
+ }
15
+ }
16
+ export function assertJsonValue(value, path = '$', ancestors = new Set()) {
17
+ if (value === null || typeof value === 'boolean')
18
+ return;
19
+ if (typeof value === 'string')
20
+ return assertValidString(value, path);
21
+ if (typeof value === 'number') {
22
+ if (!Number.isFinite(value) || Object.is(value, -0))
23
+ throw new TypeError(`${path}: invalid JSON number`);
24
+ if (Number.isInteger(value) && !Number.isSafeInteger(value))
25
+ throw new TypeError(`${path}: unsafe integer`);
26
+ return;
27
+ }
28
+ if (typeof value !== 'object')
29
+ throw new TypeError(`${path}: non-JSON value`);
30
+ if (ancestors.has(value))
31
+ throw new TypeError(`${path}: cyclic value`);
32
+ ancestors.add(value);
33
+ if (Array.isArray(value)) {
34
+ const keys = Reflect.ownKeys(value);
35
+ for (let index = 0; index < value.length; index += 1) {
36
+ if (!Object.prototype.hasOwnProperty.call(value, index))
37
+ throw new TypeError(`${path}: sparse array`);
38
+ assertJsonValue(value[index], `${path}/${index}`, ancestors);
39
+ }
40
+ if (keys.some((key) => key !== 'length' && (typeof key !== 'string' || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length))) {
41
+ throw new TypeError(`${path}: non-index array property`);
42
+ }
43
+ }
44
+ else {
45
+ const prototype = Object.getPrototypeOf(value);
46
+ if (prototype !== Object.prototype && prototype !== null)
47
+ throw new TypeError(`${path}: non-plain object`);
48
+ for (const key of Reflect.ownKeys(value)) {
49
+ if (typeof key !== 'string')
50
+ throw new TypeError(`${path}: symbol key`);
51
+ assertValidString(key, `${path} key`);
52
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
53
+ if (!descriptor.enumerable || !('value' in descriptor))
54
+ throw new TypeError(`${path}/${key}: accessor or hidden property`);
55
+ assertJsonValue(descriptor.value, `${path}/${escapePointerToken(key)}`, ancestors);
56
+ }
57
+ }
58
+ ancestors.delete(value);
59
+ }
60
+ export function canonicalJson(value) {
61
+ assertJsonValue(value);
62
+ if (value === null || typeof value !== 'object')
63
+ return JSON.stringify(value);
64
+ if (Array.isArray(value))
65
+ return `[${value.map(canonicalJson).join(',')}]`;
66
+ const keys = Object.keys(value).sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
67
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
68
+ }
69
+ export function sha256Canonical(value) {
70
+ return createHash('sha256').update(canonicalJson(value)).digest('hex');
71
+ }
72
+ export function escapePointerToken(token) {
73
+ return token.replaceAll('~', '~0').replaceAll('/', '~1');
74
+ }
75
+ export function parsePointer(pointer) {
76
+ if (pointer === '')
77
+ return [];
78
+ if (!pointer.startsWith('/'))
79
+ throw new TypeError(`invalid JSON Pointer: ${pointer}`);
80
+ return pointer.slice(1).split('/').map((token) => {
81
+ if (/~(?:[^01]|$)/.test(token))
82
+ throw new TypeError(`invalid JSON Pointer escape: ${pointer}`);
83
+ return token.replaceAll('~1', '/').replaceAll('~0', '~');
84
+ });
85
+ }
86
+ export const MISSING = Symbol('missing-json-pointer');
87
+ export function resolvePointer(value, pointer) {
88
+ let current = value;
89
+ for (const token of parsePointer(pointer)) {
90
+ if (current === MISSING || current === null || typeof current !== 'object')
91
+ return MISSING;
92
+ if (Array.isArray(current)) {
93
+ if (!/^(0|[1-9]\d*)$/.test(token) || Number(token) >= current.length)
94
+ return MISSING;
95
+ current = current[Number(token)];
96
+ }
97
+ else {
98
+ current = Object.prototype.hasOwnProperty.call(current, token) ? current[token] : MISSING;
99
+ }
100
+ }
101
+ return current;
102
+ }
103
+ class StrictJsonParser {
104
+ text;
105
+ index = 0;
106
+ constructor(text) {
107
+ this.text = text;
108
+ }
109
+ parse() {
110
+ const value = this.value('$');
111
+ this.space();
112
+ if (this.index !== this.text.length)
113
+ this.fail('trailing input');
114
+ assertJsonValue(value);
115
+ return value;
116
+ }
117
+ value(path) {
118
+ this.space();
119
+ const char = this.text[this.index];
120
+ if (char === '{')
121
+ return this.object(path);
122
+ if (char === '[')
123
+ return this.array(path);
124
+ if (char === '"')
125
+ return this.string();
126
+ if (char === 't')
127
+ return this.literal('true', true);
128
+ if (char === 'f')
129
+ return this.literal('false', false);
130
+ if (char === 'n')
131
+ return this.literal('null', null);
132
+ if (char === '-' || (char >= '0' && char <= '9'))
133
+ return this.number();
134
+ return this.fail('expected JSON value');
135
+ }
136
+ object(path) {
137
+ this.index += 1;
138
+ const result = {};
139
+ const keys = new Set();
140
+ this.space();
141
+ if (this.take('}'))
142
+ return result;
143
+ while (true) {
144
+ this.space();
145
+ if (this.text[this.index] !== '"')
146
+ this.fail('expected object key');
147
+ const key = this.string();
148
+ if (keys.has(key))
149
+ this.fail(`duplicate key ${JSON.stringify(key)}`);
150
+ keys.add(key);
151
+ this.space();
152
+ if (!this.take(':'))
153
+ this.fail('expected colon');
154
+ result[key] = this.value(`${path}/${escapePointerToken(key)}`);
155
+ this.space();
156
+ if (this.take('}'))
157
+ return result;
158
+ if (!this.take(','))
159
+ this.fail('expected comma');
160
+ }
161
+ }
162
+ array(path) {
163
+ this.index += 1;
164
+ const result = [];
165
+ this.space();
166
+ if (this.take(']'))
167
+ return result;
168
+ while (true) {
169
+ result.push(this.value(`${path}/${result.length}`));
170
+ this.space();
171
+ if (this.take(']'))
172
+ return result;
173
+ if (!this.take(','))
174
+ this.fail('expected comma');
175
+ }
176
+ }
177
+ string() {
178
+ const start = this.index;
179
+ this.index += 1;
180
+ while (this.index < this.text.length) {
181
+ const char = this.text[this.index++];
182
+ if (char === '"') {
183
+ const value = JSON.parse(this.text.slice(start, this.index));
184
+ assertValidString(value, `offset ${start}`);
185
+ return value;
186
+ }
187
+ if (char === '\\')
188
+ this.index += 1;
189
+ else if (char < ' ')
190
+ this.fail('unescaped control character');
191
+ }
192
+ return this.fail('unterminated string');
193
+ }
194
+ number() {
195
+ const rest = this.text.slice(this.index);
196
+ const match = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(rest);
197
+ if (!match)
198
+ return this.fail('invalid number');
199
+ this.index += match[0].length;
200
+ const value = Number(match[0]);
201
+ if (!Number.isFinite(value) || Object.is(value, -0) || (Number.isInteger(value) && !Number.isSafeInteger(value))) {
202
+ return this.fail('number outside supported JSON domain');
203
+ }
204
+ return value;
205
+ }
206
+ literal(token, value) {
207
+ if (!this.text.startsWith(token, this.index))
208
+ this.fail(`expected ${token}`);
209
+ this.index += token.length;
210
+ return value;
211
+ }
212
+ space() { while (/\s/.test(this.text[this.index] ?? ''))
213
+ this.index += 1; }
214
+ take(char) { if (this.text[this.index] !== char)
215
+ return false; this.index += 1; return true; }
216
+ fail(message) { throw new SyntaxError(`${message} at offset ${this.index}`); }
217
+ }
218
+ export function parseStrictJson(text) {
219
+ return new StrictJsonParser(text).parse();
220
+ }
@@ -0,0 +1,39 @@
1
+ import type { ProjectionPlan } from './projection.js';
2
+ export interface TrialSecretVault {
3
+ resolve(secretRef: string): string;
4
+ }
5
+ type MaterializedHeader = {
6
+ name: 'Authorization';
7
+ value: string;
8
+ };
9
+ export type MaterializedProjection = {
10
+ provider: 'claude';
11
+ kind: 'claude-parent-bridge';
12
+ server: {
13
+ name: string;
14
+ endpoint: {
15
+ transport: 'streamable-http';
16
+ url: string;
17
+ headers: MaterializedHeader[];
18
+ };
19
+ };
20
+ } | {
21
+ provider: 'codex';
22
+ kind: 'codex-app-server';
23
+ mcp_servers: Record<string, {
24
+ url: string;
25
+ http_headers: Record<string, string>;
26
+ enabled: true;
27
+ }>;
28
+ } | {
29
+ provider: 'cursor' | 'opencode';
30
+ kind: 'acp-session';
31
+ mcpServers: {
32
+ type: 'http';
33
+ name: string;
34
+ url: string;
35
+ headers: MaterializedHeader[];
36
+ }[];
37
+ };
38
+ export declare function materializeProjection(plan: ProjectionPlan, vault: TrialSecretVault): MaterializedProjection;
39
+ export {};
@@ -0,0 +1,22 @@
1
+ function resolveHeaders(headers, vault) {
2
+ return headers.map((header) => {
3
+ const value = vault.resolve(header.valueRef);
4
+ if (value.length === 0)
5
+ throw new Error(`secret reference is empty: ${header.valueRef}`);
6
+ return { name: header.name, value };
7
+ });
8
+ }
9
+ export function materializeProjection(plan, vault) {
10
+ if (plan.projection.kind === 'claude-parent-bridge') {
11
+ const { server } = plan.projection;
12
+ return { provider: 'claude', kind: 'claude-parent-bridge', server: { ...server, endpoint: { ...server.endpoint, headers: resolveHeaders(server.endpoint.headers, vault) } } };
13
+ }
14
+ if (plan.projection.kind === 'codex-app-server') {
15
+ const { mcpServer } = plan.projection;
16
+ const headers = Object.fromEntries(resolveHeaders(mcpServer.httpHeaders, vault).map(({ name, value }) => [name, value]));
17
+ return { provider: 'codex', kind: 'codex-app-server', mcp_servers: { [mcpServer.name]: { url: mcpServer.url, http_headers: headers, enabled: true } } };
18
+ }
19
+ const { mcpServer } = plan.projection;
20
+ const provider = plan.provider;
21
+ return { provider, kind: 'acp-session', mcpServers: [{ ...mcpServer, headers: resolveHeaders(mcpServer.headers, vault) }] };
22
+ }
@@ -0,0 +1,14 @@
1
+ import { runAcpDirectMcpTrial, type AcpDirectMcpTrialResult } from './acp-direct-mcp.js';
2
+ import type { ScenarioMachineV2 } from './types.js';
3
+ export declare const OPENCODE_DIRECT_MCP_FLAG = "PATHGRADE_INTERNAL_OPENCODE_DIRECT_MCP_V2";
4
+ export declare function isOpenCodeDirectMcpSelected(environment?: NodeJS.ProcessEnv): boolean;
5
+ export declare function openCodeDirectMcpDisableReason(): string | undefined;
6
+ /** Test-only reset for proving the fail-closed process latch. */
7
+ export declare function __resetOpenCodeDirectMcpDisablementForTesting(): void;
8
+ export declare function mapOpenCodeDirectMcpEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
9
+ export declare function runOpenCodeDirectMcpTrial(options: {
10
+ scenario: ScenarioMachineV2;
11
+ environment?: NodeJS.ProcessEnv;
12
+ artifactDirectory?: string;
13
+ createClient?: Parameters<typeof runAcpDirectMcpTrial>[0]['createClient'];
14
+ }): Promise<AcpDirectMcpTrialResult>;
@@ -0,0 +1,71 @@
1
+ import { curateAcpProcessEnvironment, runAcpDirectMcpTrial } from './acp-direct-mcp.js';
2
+ import { OPENCODE_DIRECT_MCP_DARWIN_ARM64_PROFILE } from './opencode-profile.js';
3
+ export const OPENCODE_DIRECT_MCP_FLAG = 'PATHGRADE_INTERNAL_OPENCODE_DIRECT_MCP_V2';
4
+ const disabledProfiles = new Map();
5
+ const OPENCODE_RUNTIME_ENV = {
6
+ OPENCODE_CLIENT: 'pathgrade',
7
+ OPENCODE_DISABLE_AUTOUPDATE: '1',
8
+ OPENCODE_DISABLE_PRUNE: '1',
9
+ OPENCODE_DISABLE_MODELS_FETCH: '1',
10
+ OPENCODE_DISABLE_EXTERNAL_SKILLS: '1',
11
+ OPENCODE_DISABLE_LSP_DOWNLOAD: '1',
12
+ OPENCODE_DISABLE_SHARE: '1',
13
+ OPENCODE_DISABLE_DEFAULT_PLUGINS: '1',
14
+ OPENCODE_PURE: '1',
15
+ };
16
+ export function isOpenCodeDirectMcpSelected(environment = process.env) {
17
+ return environment[OPENCODE_DIRECT_MCP_FLAG] === '1' && !disabledProfiles.has(OPENCODE_DIRECT_MCP_DARWIN_ARM64_PROFILE.profileFingerprint);
18
+ }
19
+ export function openCodeDirectMcpDisableReason() {
20
+ return disabledProfiles.get(OPENCODE_DIRECT_MCP_DARWIN_ARM64_PROFILE.profileFingerprint);
21
+ }
22
+ /** Test-only reset for proving the fail-closed process latch. */
23
+ // oxlint-disable-next-line inhuman/no-empty-wrappers -- This test seam exposes Map.clear without exposing mutable production state.
24
+ export function __resetOpenCodeDirectMcpDisablementForTesting() {
25
+ disabledProfiles.clear();
26
+ }
27
+ export function mapOpenCodeDirectMcpEnvironment(environment) {
28
+ const result = { ...curateAcpProcessEnvironment(environment), ...OPENCODE_RUNTIME_ENV };
29
+ const appAnthropicKey = environment.APP_ANTHROPIC_API_KEY;
30
+ const appAnthropicBaseUrl = environment.APP_ANTHROPIC_BASE_URL;
31
+ const appOpenAiKey = environment.APP_OPENAI_API_KEY;
32
+ const appOpenAiBaseUrl = environment.APP_OPENAI_BASE_URL;
33
+ const anthropicKey = environment.ANTHROPIC_API_KEY ?? appAnthropicKey;
34
+ const anthropicBaseUrl = environment.ANTHROPIC_BASE_URL ?? appAnthropicBaseUrl;
35
+ const openAiKey = environment.OPENAI_API_KEY ?? appOpenAiKey;
36
+ const openAiBaseUrl = environment.OPENAI_BASE_URL ?? appOpenAiBaseUrl;
37
+ if (anthropicKey)
38
+ result.ANTHROPIC_API_KEY = anthropicKey;
39
+ if (anthropicBaseUrl)
40
+ result.ANTHROPIC_BASE_URL = anthropicBaseUrl;
41
+ if (openAiKey)
42
+ result.OPENAI_API_KEY = openAiKey;
43
+ if (openAiBaseUrl)
44
+ result.OPENAI_BASE_URL = openAiBaseUrl;
45
+ return result;
46
+ }
47
+ export async function runOpenCodeDirectMcpTrial(options) {
48
+ const environment = options.environment ?? process.env;
49
+ if (!isOpenCodeDirectMcpSelected(environment)) {
50
+ const reason = openCodeDirectMcpDisableReason();
51
+ throw new Error(reason ? `OpenCode direct MCP disabled for admitted profile: ${reason}` : `${OPENCODE_DIRECT_MCP_FLAG} is disabled`);
52
+ }
53
+ try {
54
+ const runtimeEnvironment = mapOpenCodeDirectMcpEnvironment(environment);
55
+ if (!runtimeEnvironment.ANTHROPIC_API_KEY && !runtimeEnvironment.OPENAI_API_KEY) {
56
+ throw new Error('RUNTIME_NOT_ADMITTED: OpenCode direct MCP requires ANTHROPIC_API_KEY or OPENAI_API_KEY (APP_ANTHROPIC_API_KEY and APP_OPENAI_API_KEY aliases are accepted)');
57
+ }
58
+ return await runAcpDirectMcpTrial({
59
+ provider: 'opencode',
60
+ scenario: options.scenario,
61
+ environment: runtimeEnvironment,
62
+ profile: OPENCODE_DIRECT_MCP_DARWIN_ARM64_PROFILE,
63
+ artifactDirectory: options.artifactDirectory,
64
+ createClient: options.createClient,
65
+ });
66
+ }
67
+ catch (error) {
68
+ disabledProfiles.set(OPENCODE_DIRECT_MCP_DARWIN_ARM64_PROFILE.profileFingerprint, (error instanceof Error ? error.message : String(error)).slice(0, 500));
69
+ throw error;
70
+ }
71
+ }
@@ -0,0 +1,8 @@
1
+ import type { AcpNormalizedHandshake } from './acp-client.js';
2
+ import type { RuntimeLock } from './runtime-lock.js';
3
+ import type { RuntimeCapabilityProfile } from './types.js';
4
+ export declare const OPENCODE_DIRECT_MCP_NORMALIZED_HANDSHAKE: AcpNormalizedHandshake;
5
+ /** Code-owned public author-session lock. Installation location is verified separately. */
6
+ export declare const OPENCODE_ACP_DARWIN_ARM64_RUNTIME_LOCK: RuntimeLock;
7
+ /** Reviewed macOS arm64 pin captured by RFC 0006. Other platforms fail closed. */
8
+ export declare const OPENCODE_DIRECT_MCP_DARWIN_ARM64_PROFILE: RuntimeCapabilityProfile;
@@ -0,0 +1,52 @@
1
+ export const OPENCODE_DIRECT_MCP_NORMALIZED_HANDSHAKE = {
2
+ normalizerVersion: 'pathgrade-acp-v1-capabilities-v1',
3
+ provider: 'opencode',
4
+ protocolVersion: 1,
5
+ httpMcp: true,
6
+ sessionClose: true,
7
+ };
8
+ /** Code-owned public author-session lock. Installation location is verified separately. */
9
+ export const OPENCODE_ACP_DARWIN_ARM64_RUNTIME_LOCK = {
10
+ format: 'pathgrade-direct-mcp-runtime-lock-v1',
11
+ provider: 'opencode',
12
+ platform: 'darwin',
13
+ architecture: 'arm64',
14
+ controlPlane: 'acp-v1',
15
+ version: '1.18.14',
16
+ commandArtifact: 'opencode',
17
+ args: ['acp', '--pure'],
18
+ versionArgs: ['--version'],
19
+ artifacts: [{
20
+ id: 'opencode',
21
+ relativePath: 'opencode.exe',
22
+ sha256: '8b7c4e116c1ac5163c02fa85eee5a13d4d00c8a08e677dac4100f55aa56532fa',
23
+ }],
24
+ capabilities: {
25
+ streamableHttp: true,
26
+ controlPlaneHeaders: true,
27
+ cancellation: 'session-cancel',
28
+ },
29
+ normalizedHandshakeSha256: '481b61c577a70ab7fae2e78d8c2159379675b1007ede488dcf4159f657cece25',
30
+ };
31
+ /** Reviewed macOS arm64 pin captured by RFC 0006. Other platforms fail closed. */
32
+ export const OPENCODE_DIRECT_MCP_DARWIN_ARM64_PROFILE = {
33
+ format: 'pathgrade-direct-mcp-runtime-profile-v1',
34
+ provider: 'opencode',
35
+ platform: 'darwin',
36
+ architecture: 'arm64',
37
+ controlPlane: 'acp-v1',
38
+ command: {
39
+ absolutePath: '/Users/nadavlac/.nvm/versions/node/v22.18.0/lib/node_modules/opencode-ai/bin/opencode.exe',
40
+ args: ['acp', '--pure'],
41
+ versionArgs: ['--version'],
42
+ },
43
+ expectedVersion: '1.18.14',
44
+ artifacts: [{
45
+ label: 'opencode-1.18.14',
46
+ absolutePath: '/Users/nadavlac/.nvm/versions/node/v22.18.0/lib/node_modules/opencode-ai/bin/opencode.exe',
47
+ sha256: '8b7c4e116c1ac5163c02fa85eee5a13d4d00c8a08e677dac4100f55aa56532fa',
48
+ }],
49
+ requiredCapabilities: { streamableHttp: true, controlPlaneHeaders: true, cancellation: 'session-cancel' },
50
+ profileFingerprint: '91f512964bc05b7a8b0cac90444e30c41bb9b0e319aff2341c2a4b72e6e5e3c5',
51
+ runtimeCapabilityFingerprint: '481b61c577a70ab7fae2e78d8c2159379675b1007ede488dcf4159f657cece25',
52
+ };
@@ -0,0 +1,40 @@
1
+ import type { AdmittedRuntimeProfile, EndpointSpec, HeaderRef, ProviderId } from './types.js';
2
+ export type ProjectionRejection = {
3
+ ok: false;
4
+ code: 'RUNTIME_NOT_ADMITTED';
5
+ reason: string;
6
+ };
7
+ export type ProjectionPlan = {
8
+ ok: true;
9
+ provider: ProviderId;
10
+ serverId: string;
11
+ controlPlane: AdmittedRuntimeProfile['controlPlane'];
12
+ projection: {
13
+ kind: 'claude-parent-bridge';
14
+ server: {
15
+ name: string;
16
+ endpoint: {
17
+ transport: 'streamable-http';
18
+ url: string;
19
+ headers: readonly HeaderRef[];
20
+ };
21
+ };
22
+ } | {
23
+ kind: 'codex-app-server';
24
+ mcpServer: {
25
+ name: string;
26
+ url: string;
27
+ httpHeaders: readonly HeaderRef[];
28
+ };
29
+ } | {
30
+ kind: 'acp-session';
31
+ mcpServer: {
32
+ type: 'http';
33
+ name: string;
34
+ url: string;
35
+ headers: readonly HeaderRef[];
36
+ };
37
+ };
38
+ };
39
+ export declare function projectEndpoint(endpoint: EndpointSpec, profile: AdmittedRuntimeProfile): ProjectionPlan | ProjectionRejection;
40
+ export declare function projectEndpoints(endpoints: readonly EndpointSpec[], profile: AdmittedRuntimeProfile): ProjectionPlan[] | ProjectionRejection;
@@ -0,0 +1,46 @@
1
+ import { canonicalJson } from './json.js';
2
+ const CONTROL_PLANE = {
3
+ claude: 'native-sdk-parent-bridge',
4
+ codex: 'app-server',
5
+ cursor: 'acp-v1',
6
+ opencode: 'acp-v1',
7
+ };
8
+ export function projectEndpoint(endpoint, profile) {
9
+ if (endpoint.transport !== 'streamable-http' || endpoint.authority !== 'scenario-host' || endpoint.lifetime !== 'trial') {
10
+ return { ok: false, code: 'RUNTIME_NOT_ADMITTED', reason: 'unsupported endpoint contract' };
11
+ }
12
+ if (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(endpoint.serverId))
13
+ return { ok: false, code: 'RUNTIME_NOT_ADMITTED', reason: 'projection-unstable server name' };
14
+ if (!/^http:\/\/127\.0\.0\.1:\d+\/[A-Za-z0-9_-]+$/.test(endpoint.url))
15
+ return { ok: false, code: 'RUNTIME_NOT_ADMITTED', reason: 'endpoint must use IPv4 loopback and a private route' };
16
+ if (profile.controlPlane !== CONTROL_PLANE[profile.provider])
17
+ return { ok: false, code: 'RUNTIME_NOT_ADMITTED', reason: 'provider/control-plane mismatch' };
18
+ if (endpoint.headers.length !== 1 || endpoint.headers[0].name !== 'Authorization' || endpoint.headers[0].exposure !== 'control-plane-only' || endpoint.headers[0].valueRef.length === 0) {
19
+ return { ok: false, code: 'RUNTIME_NOT_ADMITTED', reason: 'unsafe header exposure' };
20
+ }
21
+ const headers = endpoint.headers.map((header) => ({ ...header }));
22
+ const base = { ok: true, provider: profile.provider, serverId: endpoint.serverId, controlPlane: profile.controlPlane };
23
+ if (profile.provider === 'claude') {
24
+ if (profile.requiredCapabilities.parentOwnedSdkMcp !== true)
25
+ return { ok: false, code: 'RUNTIME_NOT_ADMITTED', reason: 'Claude requires a parent-owned SDK MCP bridge' };
26
+ return { ...base, projection: { kind: 'claude-parent-bridge', server: { name: endpoint.serverId, endpoint: { transport: endpoint.transport, url: endpoint.url, headers } } } };
27
+ }
28
+ if (profile.provider === 'codex')
29
+ return { ...base, projection: { kind: 'codex-app-server', mcpServer: { name: endpoint.serverId, url: endpoint.url, httpHeaders: headers } } };
30
+ return { ...base, projection: { kind: 'acp-session', mcpServer: { type: 'http', name: endpoint.serverId, url: endpoint.url, headers } } };
31
+ }
32
+ export function projectEndpoints(endpoints, profile) {
33
+ const plans = [];
34
+ const names = new Set();
35
+ for (const endpoint of [...endpoints].sort((left, right) => canonicalJson(left.serverId).localeCompare(canonicalJson(right.serverId), 'en'))) {
36
+ const plan = projectEndpoint(endpoint, profile);
37
+ if (!plan.ok)
38
+ return plan;
39
+ const projectedName = plan.serverId;
40
+ if (names.has(projectedName))
41
+ return { ok: false, code: 'RUNTIME_NOT_ADMITTED', reason: `projection collision: ${projectedName}` };
42
+ names.add(projectedName);
43
+ plans.push(plan);
44
+ }
45
+ return plans;
46
+ }
@@ -0,0 +1,51 @@
1
+ import { type McpSdkServerConfigWithInstance } from '@anthropic-ai/claude-agent-sdk';
2
+ import { type ScanEntry } from './cleanup.js';
3
+ import { type RuntimeLock } from './runtime-lock.js';
4
+ import { startScenarioHttpHost } from './scenario-http-host.js';
5
+ import type { AgentSessionOptions } from '../../types.js';
6
+ import type { EvidenceEnvelope, ProviderId, ScenarioArtifact, ScenarioProviderAttachContext, ScenarioProviderAttachment } from './types.js';
7
+ export type ScenarioCoordinatorHost = typeof startScenarioHttpHost;
8
+ export interface ScenarioCoordinator<TAttachment> {
9
+ readonly attachment: TAttachment;
10
+ beginTurn(turnId: string, signal: AbortSignal): void;
11
+ settleTurn(): Promise<void>;
12
+ failTurn(error: unknown): Promise<void>;
13
+ evidence(): readonly EvidenceEnvelope[];
14
+ dispose(): Promise<void>;
15
+ }
16
+ export type ScenarioCoordinatorOptions<TAttachment> = {
17
+ artifact: ScenarioArtifact;
18
+ provider: ProviderId;
19
+ workspacePath: string;
20
+ environment: Readonly<Record<string, string | undefined>>;
21
+ attach(context: ScenarioProviderAttachContext): Promise<ScenarioProviderAttachment<TAttachment>>;
22
+ onEvidence?(evidence: readonly EvidenceEnvelope[]): void;
23
+ };
24
+ type ScenarioCoordinatorDependencies = {
25
+ startHost: ScenarioCoordinatorHost;
26
+ scanBearerSurfaces(roots: readonly string[], bearer: string): Promise<ScanEntry[]>;
27
+ };
28
+ export declare function startScenarioCoordinator<TAttachment>(options: ScenarioCoordinatorOptions<TAttachment>, dependencies?: Partial<ScenarioCoordinatorDependencies>): Promise<ScenarioCoordinator<TAttachment>>;
29
+ export interface PublicScenarioRuntime {
30
+ /** Adapter-owned, in-memory session projection; never persisted by the coordinator. */
31
+ configureSession(options: AgentSessionOptions): void;
32
+ /** Preserved compatibility view for the existing Claude SDK path. */
33
+ readonly claudeServers?: Record<string, McpSdkServerConfigWithInstance>;
34
+ beginTurn(turnId: string, signal: AbortSignal): void;
35
+ settleTurn(): Promise<void>;
36
+ failTurn(error: unknown): Promise<void>;
37
+ evidence(): readonly EvidenceEnvelope[];
38
+ dispose(): Promise<void>;
39
+ }
40
+ export declare function startPublicClaudeScenarioRuntime(options: {
41
+ artifact: ScenarioArtifact;
42
+ workspacePath: string;
43
+ environment: Readonly<Record<string, string | undefined>>;
44
+ onEvidence?(evidence: readonly EvidenceEnvelope[]): void;
45
+ }, dependencies?: Partial<{
46
+ lock: RuntimeLock;
47
+ resolveExecutable(environment: Readonly<Record<string, string | undefined>>): Promise<string>;
48
+ commandVersion(path: string, args: readonly string[]): Promise<string>;
49
+ }>): Promise<PublicScenarioRuntime>;
50
+ export type PublicScenarioRuntimeFactory = typeof startPublicClaudeScenarioRuntime;
51
+ export {};