@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
package/README.md
CHANGED
|
@@ -375,6 +375,48 @@ Use `getMcpApproval()` for the simulated-user decision and
|
|
|
375
375
|
observed. Mock responses are fixed fixtures; this mode does not model stateful
|
|
376
376
|
mutation or make later reads observe earlier writes.
|
|
377
377
|
|
|
378
|
+
### Direct MCP scenario machine v2
|
|
379
|
+
|
|
380
|
+
`ScenarioMachineV2` is the versioned stateful MCP contract. Its parent-owned
|
|
381
|
+
host controls matching, transitions, cancellation, evidence, and replay. Cases
|
|
382
|
+
are unordered, disjoint JSON constraints; callbacks and regexes are rejected.
|
|
383
|
+
Concurrent calls involving a transition-capable tool poison the scenario. This
|
|
384
|
+
safety behavior is a fixed V2 invariant and is not author-configurable.
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
import {
|
|
388
|
+
compileScenario,
|
|
389
|
+
getFinalScenarioState,
|
|
390
|
+
getScenarioStateTimeline,
|
|
391
|
+
wasScenarioCaseMatched,
|
|
392
|
+
type ScenarioMachineV2,
|
|
393
|
+
} from '@wix/pathgrade';
|
|
394
|
+
|
|
395
|
+
declare const scenario: ScenarioMachineV2;
|
|
396
|
+
const compiled = compileScenario(scenario);
|
|
397
|
+
if (!compiled.ok) throw new Error(JSON.stringify(compiled.diagnostics));
|
|
398
|
+
declare const evidence: Parameters<typeof getFinalScenarioState>[0];
|
|
399
|
+
wasScenarioCaseMatched(evidence, { caseId: 'CreateDocument' });
|
|
400
|
+
getScenarioStateTimeline(evidence);
|
|
401
|
+
getFinalScenarioState(evidence);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Evidence helpers validate the hash chain and reject empty, corrupt, or
|
|
405
|
+
cross-trial evidence. Provider lifecycle events are corroboration only.
|
|
406
|
+
|
|
407
|
+
#### Legacy migration window
|
|
408
|
+
Existing `mcpMock` descriptors remain supported with no scheduled removal. They
|
|
409
|
+
retain ordered regexes, fallback behavior, and receipts. V2 migration is
|
|
410
|
+
explicit; receipt settlement has no v2 authority or new features. Removing the
|
|
411
|
+
legacy executor would require a new owner-approved ADR and a major release.
|
|
412
|
+
|
|
413
|
+
The public live author path requires an explicit `agent`. It supports Claude,
|
|
414
|
+
Codex app-server, OpenCode ACP, and Cursor ACP when the checked-in exact runtime
|
|
415
|
+
lock for the current provider, platform, architecture, and version is admitted.
|
|
416
|
+
Codex `exec` is unsupported. OpenCode requires its existing absolute
|
|
417
|
+
`opencodeExecutable` option and uses the default API-key model for Scenario V2;
|
|
418
|
+
provider-specific scenario flags and profile controls are not public.
|
|
419
|
+
|
|
378
420
|
## CLI
|
|
379
421
|
|
|
380
422
|
```bash
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { CanUseTool, Options, SpawnedProcess, SpawnOptions as SdkSpawnOptions } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
-
import type { McpServersObject } from '../../providers/mcp-runtime-mounting.js';
|
|
3
2
|
export interface ClaudeSdkOptionsInputs {
|
|
4
3
|
/** Per-trial workspace; cwd for the SDK so project-staged skills resolve. */
|
|
5
4
|
workspacePath: string;
|
|
@@ -32,7 +31,7 @@ export interface ClaudeSdkOptionsInputs {
|
|
|
32
31
|
*/
|
|
33
32
|
resume?: string;
|
|
34
33
|
/** MCP servers in the SDK's object form, from `mountMcpForClaudeSdk`. */
|
|
35
|
-
mcpServers?:
|
|
34
|
+
mcpServers?: Options['mcpServers'];
|
|
36
35
|
/** Per-turn cancellation controller linked to PathGrade's turn timeout. */
|
|
37
36
|
abortController?: AbortController;
|
|
38
37
|
}
|
package/dist/agents/claude.js
CHANGED
|
@@ -102,7 +102,8 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
102
102
|
mcpSafety: sessionOptions?.mcpSafety,
|
|
103
103
|
});
|
|
104
104
|
await assertStdioMcpServersStartForClaudeSdk(mcpMountOptions);
|
|
105
|
-
const mcpServers = sessionOptions?.
|
|
105
|
+
const mcpServers = sessionOptions?.scenarioMcpServers
|
|
106
|
+
?? sessionOptions?.scriptedMcpHost?.claudeServers
|
|
106
107
|
?? await mountMcpForClaudeSdk(mcpMountOptions);
|
|
107
108
|
let priorSessionId;
|
|
108
109
|
let turnNumber = 0;
|
|
@@ -40,6 +40,29 @@ function projectMcpStartupStatusIntoTurn(params, turn) {
|
|
|
40
40
|
failTurn(turn, message);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
function dispatchCodexNotification(notification, turn, sensitiveValues) {
|
|
44
|
+
if (process.env.PATHGRADE_CODEX_DEBUG) {
|
|
45
|
+
const debugParams = sanitizePersistenceValue(notification.params, sensitiveValues);
|
|
46
|
+
console.error(`[codex app-server] notification method=${notification.method} params=${JSON.stringify(debugParams).slice(0, 300)}`);
|
|
47
|
+
}
|
|
48
|
+
if (notification.method === 'mcpServer/startupStatus/updated') {
|
|
49
|
+
if (turn) {
|
|
50
|
+
projectMcpStartupStatusIntoTurn((notification.params ?? {}), turn);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!turn)
|
|
55
|
+
return;
|
|
56
|
+
const params = notification.params;
|
|
57
|
+
if (notification.method === 'item/started') {
|
|
58
|
+
if (params)
|
|
59
|
+
turn.itemLifecycle?.receiveStarted(params);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (notification.method === 'item/completed' && params) {
|
|
63
|
+
turn.itemLifecycle?.receiveCompleted(params);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
43
66
|
function failTurn(turn, message) {
|
|
44
67
|
turn.turnFailed = true;
|
|
45
68
|
turn.failureMessage = message;
|
|
@@ -79,7 +102,11 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
79
102
|
const askBus = requireAskBusForLiveBatches(options, 'CodexAppServerAgent');
|
|
80
103
|
const workspacePath = getWorkspacePath(runtime);
|
|
81
104
|
const runtimeEnv = getRuntimeEnv(runtime);
|
|
82
|
-
const
|
|
105
|
+
const scenarioMount = options?.codexScenarioMount;
|
|
106
|
+
const sensitiveValues = [...new Set([
|
|
107
|
+
...collectSensitiveEnvValues(runtimeEnv),
|
|
108
|
+
...(scenarioMount?.sensitiveValues ?? []),
|
|
109
|
+
])];
|
|
83
110
|
const model = resolveCodexModel(options?.model);
|
|
84
111
|
const sandboxMode = this.deps.sandboxMode ?? 'workspace-write';
|
|
85
112
|
await assertUnsupportedLiveMcpSafetyRuntime({
|
|
@@ -112,6 +139,28 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
112
139
|
throw new Error('CodexAppServerAgent session disposed');
|
|
113
140
|
if (handle)
|
|
114
141
|
return handle.transport;
|
|
142
|
+
if (scenarioMount) {
|
|
143
|
+
handle = scenarioMount.handle;
|
|
144
|
+
const transport = handle.transport;
|
|
145
|
+
transport.onServerRequest((req) => this.dispatchServerRequest(req, {
|
|
146
|
+
transport,
|
|
147
|
+
askBus,
|
|
148
|
+
activeTurn: () => activeTurn,
|
|
149
|
+
mcpSafety: options?.mcpSafety,
|
|
150
|
+
scriptedHost,
|
|
151
|
+
correlator,
|
|
152
|
+
managedAuth,
|
|
153
|
+
}));
|
|
154
|
+
transport.onClose((info) => {
|
|
155
|
+
if (handle === scenarioMount.handle)
|
|
156
|
+
closeInfo = info;
|
|
157
|
+
});
|
|
158
|
+
transport.onNotification((n) => {
|
|
159
|
+
dispatchCodexNotification(n, activeTurn, sensitiveValues);
|
|
160
|
+
});
|
|
161
|
+
await managedAuth.login(transport);
|
|
162
|
+
return transport;
|
|
163
|
+
}
|
|
115
164
|
const factory = this.deps.createTransport
|
|
116
165
|
?? (async (ctx) => spawnAppServerTransport({ cwd: ctx.workspacePath, env: ctx.env,
|
|
117
166
|
args: scriptedHost ? ['--enable', 'tool_call_mcp_elicitation'] : [] }));
|
|
@@ -132,30 +181,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
132
181
|
closeInfo = info;
|
|
133
182
|
});
|
|
134
183
|
transport.onNotification((n) => {
|
|
135
|
-
|
|
136
|
-
const debugParams = sanitizePersistenceValue(n.params, sensitiveValues);
|
|
137
|
-
console.error(`[codex app-server] notification method=${n.method} params=${JSON.stringify(debugParams).slice(0, 300)}`);
|
|
138
|
-
}
|
|
139
|
-
if (n.method === 'mcpServer/startupStatus/updated') {
|
|
140
|
-
const turn = activeTurn;
|
|
141
|
-
if (turn) {
|
|
142
|
-
projectMcpStartupStatusIntoTurn((n.params ?? {}), turn);
|
|
143
|
-
}
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const turn = activeTurn;
|
|
147
|
-
if (!turn)
|
|
148
|
-
return;
|
|
149
|
-
const params = n.params;
|
|
150
|
-
if (n.method === 'item/started') {
|
|
151
|
-
if (params)
|
|
152
|
-
turn.itemLifecycle?.receiveStarted(params);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (n.method !== 'item/completed')
|
|
156
|
-
return;
|
|
157
|
-
if (params)
|
|
158
|
-
turn.itemLifecycle?.receiveCompleted(params);
|
|
184
|
+
dispatchCodexNotification(n, activeTurn, sensitiveValues);
|
|
159
185
|
});
|
|
160
186
|
const initialize = async () => {
|
|
161
187
|
const initialized = await transport.sendRequest('initialize', {
|
|
@@ -179,6 +205,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
179
205
|
const runTurn = async (message) => {
|
|
180
206
|
const t = await ensureTransport();
|
|
181
207
|
const turnHandle = handle;
|
|
208
|
+
scenarioMount?.prepareProviderTurn();
|
|
182
209
|
turnCounter += 1;
|
|
183
210
|
const turn = {
|
|
184
211
|
turnNumber: turnCounter,
|
|
@@ -202,7 +229,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
202
229
|
env: runtimeEnv,
|
|
203
230
|
});
|
|
204
231
|
}
|
|
205
|
-
const mcpConfig = scriptedHost?.codexConfig ?? (options?.mcpConfigPath
|
|
232
|
+
const mcpConfig = scenarioMount?.config ?? scriptedHost?.codexConfig ?? (options?.mcpConfigPath
|
|
206
233
|
? await mountMcpForCodexAppServer({
|
|
207
234
|
workspacePath,
|
|
208
235
|
mcpConfigPath: options.mcpConfigPath,
|
|
@@ -335,12 +362,15 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
335
362
|
}
|
|
336
363
|
if (!authoritativeTurnId) {
|
|
337
364
|
if (!turn.authoritativeTurnId) {
|
|
338
|
-
|
|
339
|
-
|
|
365
|
+
const message = 'turn/start did not return an authoritative turn id';
|
|
366
|
+
scriptedHost?.failProtocol(message);
|
|
367
|
+
scenarioMount?.failProviderTurn(new Error(message));
|
|
368
|
+
turn.signalFailure?.(message);
|
|
340
369
|
}
|
|
341
370
|
return;
|
|
342
371
|
}
|
|
343
372
|
turnStartCanReuseTransport = true;
|
|
373
|
+
scenarioMount?.registerProviderTurn(authoritativeThreadId, authoritativeTurnId);
|
|
344
374
|
markAuthoritativeTurn(authoritativeTurnId);
|
|
345
375
|
})
|
|
346
376
|
.catch((err) => {
|
|
@@ -352,6 +382,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
352
382
|
: typeof err === 'string'
|
|
353
383
|
? err
|
|
354
384
|
: 'turn/start failed';
|
|
385
|
+
scenarioMount?.failProviderTurn(new Error(msg));
|
|
355
386
|
turn.signalFailure?.(msg);
|
|
356
387
|
});
|
|
357
388
|
try {
|
|
@@ -399,20 +430,25 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
399
430
|
activeTurn = null;
|
|
400
431
|
correlator?.endTurn();
|
|
401
432
|
const retireTransport = shouldRetireAuthoritativeTurnTransport(authoritativeTurnIds);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
handle
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
433
|
+
if (scenarioMount) {
|
|
434
|
+
sessionRetired ||= retireTransport;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
await closeUncorrelatedTurnTransport({
|
|
438
|
+
turnStartCanReuseTransport,
|
|
439
|
+
authoritativeTurnId: turn.authoritativeTurnId,
|
|
440
|
+
retireTransport,
|
|
441
|
+
currentHandle: handle,
|
|
442
|
+
turnHandle,
|
|
443
|
+
reset: () => {
|
|
444
|
+
sessionRetired ||= retireTransport;
|
|
445
|
+
handle = null;
|
|
446
|
+
threadId = null;
|
|
447
|
+
authoritativeTurnIds.clear();
|
|
448
|
+
closeInfo = null;
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
}
|
|
416
452
|
}
|
|
417
453
|
};
|
|
418
454
|
const dispose = async () => {
|
|
@@ -424,7 +460,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
424
460
|
turn.signalFailure?.('session disposed');
|
|
425
461
|
}
|
|
426
462
|
activeTurn = null;
|
|
427
|
-
if (handle) {
|
|
463
|
+
if (handle && !scenarioMount) {
|
|
428
464
|
try {
|
|
429
465
|
await handle.close();
|
|
430
466
|
}
|
|
@@ -587,8 +623,9 @@ function assembleTurnResult(args) {
|
|
|
587
623
|
.map((s) => toAskUserToolEvent(s));
|
|
588
624
|
const toolEvents = enrichSkillEvents([...askEvents, ...activeTurn.nonAskToolEvents])
|
|
589
625
|
.map((event) => attachToolEventSensitiveValues(event, sensitiveValues));
|
|
626
|
+
const sanitizedMessage = sanitizePersistenceValue(message, sensitiveValues);
|
|
590
627
|
const rawOutput = sanitizePersistenceValue(exitCode === 0
|
|
591
|
-
?
|
|
628
|
+
? sanitizedMessage
|
|
592
629
|
: [
|
|
593
630
|
message,
|
|
594
631
|
pid !== undefined ? `pid=${pid}` : '',
|
|
@@ -599,8 +636,8 @@ function assembleTurnResult(args) {
|
|
|
599
636
|
.join(' '), sensitiveValues);
|
|
600
637
|
return attachTurnResultSensitiveValues({
|
|
601
638
|
rawOutput,
|
|
602
|
-
assistantMessage: exitCode === 0 ?
|
|
603
|
-
visibleAssistantMessage: exitCode === 0 ?
|
|
639
|
+
assistantMessage: exitCode === 0 ? sanitizedMessage : '',
|
|
640
|
+
visibleAssistantMessage: exitCode === 0 ? sanitizedMessage : '',
|
|
604
641
|
visibleAssistantMessageSource: 'assistant_message',
|
|
605
642
|
exitCode,
|
|
606
643
|
traceOutput: rawOutput,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type MaterializedProjection } from '../../internal/direct-mcp-v2/materialize.js';
|
|
2
|
+
import { type PublicScenarioRuntime } from '../../internal/direct-mcp-v2/public-scenario-runtime.js';
|
|
3
|
+
import { type RuntimeLock } from '../../internal/direct-mcp-v2/runtime-lock.js';
|
|
4
|
+
import type { ScenarioArtifact } from '../../internal/direct-mcp-v2/types.js';
|
|
5
|
+
import type { CodexAppServerMcpConfig } from '../../providers/mcp-runtime-mounting.js';
|
|
6
|
+
import { type AppServerSessionHandle } from './transport.js';
|
|
7
|
+
/**
|
|
8
|
+
* Parent-owned, already-admitted Codex app-server attachment for one public
|
|
9
|
+
* ScenarioMachineV2 session. The driver may use this handle, but it must not
|
|
10
|
+
* replace or close it; the scenario coordinator owns provider cleanup.
|
|
11
|
+
*/
|
|
12
|
+
export interface CodexScenarioMount {
|
|
13
|
+
readonly handle: AppServerSessionHandle;
|
|
14
|
+
readonly config: CodexAppServerMcpConfig;
|
|
15
|
+
readonly sensitiveValues: readonly string[];
|
|
16
|
+
prepareProviderTurn(): void;
|
|
17
|
+
registerProviderTurn(threadId: string, turnId: string): void;
|
|
18
|
+
failProviderTurn(error: Error): void;
|
|
19
|
+
}
|
|
20
|
+
type CodexScenarioRuntimeOptions = {
|
|
21
|
+
artifact: ScenarioArtifact;
|
|
22
|
+
workspacePath: string;
|
|
23
|
+
environment: Readonly<Record<string, string | undefined>>;
|
|
24
|
+
onEvidence?(evidence: readonly import('../../internal/direct-mcp-v2/types.js').EvidenceEnvelope[]): void;
|
|
25
|
+
};
|
|
26
|
+
type CodexScenarioRuntimeDependencies = {
|
|
27
|
+
lock: RuntimeLock;
|
|
28
|
+
resolveExecutable(environment: Readonly<Record<string, string | undefined>>): Promise<string>;
|
|
29
|
+
spawn(context: {
|
|
30
|
+
binary: string;
|
|
31
|
+
args: readonly string[];
|
|
32
|
+
cwd: string;
|
|
33
|
+
env: NodeJS.ProcessEnv;
|
|
34
|
+
}): AppServerSessionHandle;
|
|
35
|
+
commandVersion(path: string, args: readonly string[]): Promise<string>;
|
|
36
|
+
};
|
|
37
|
+
/** Coordinator-backed public Codex ScenarioMachineV2 adapter. */
|
|
38
|
+
export declare function startPublicCodexScenarioRuntime(options: CodexScenarioRuntimeOptions, dependencies?: Partial<CodexScenarioRuntimeDependencies>): Promise<PublicScenarioRuntime>;
|
|
39
|
+
export declare function mergeCodexScenarioConfigs(configs: readonly MaterializedProjection[]): CodexAppServerMcpConfig;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { materializeProjection } from '../../internal/direct-mcp-v2/materialize.js';
|
|
2
|
+
import { projectEndpoints } from '../../internal/direct-mcp-v2/projection.js';
|
|
3
|
+
import { startScenarioCoordinator, } from '../../internal/direct-mcp-v2/public-scenario-runtime.js';
|
|
4
|
+
import { admitRuntimeLock } from '../../internal/direct-mcp-v2/runtime-lock.js';
|
|
5
|
+
import { CODEX_DIRECT_MCP_DARWIN_ARM64_LOCK, CODEX_DIRECT_MCP_NORMALIZED_HANDSHAKE, resolveCodexScenarioExecutable, } from '../../internal/direct-mcp-v2/codex-profile.js';
|
|
6
|
+
import { spawnAppServerTransport, } from './transport.js';
|
|
7
|
+
const defaultDependencies = {
|
|
8
|
+
lock: CODEX_DIRECT_MCP_DARWIN_ARM64_LOCK,
|
|
9
|
+
resolveExecutable: resolveCodexScenarioExecutable,
|
|
10
|
+
spawn: ({ binary, args, cwd, env }) => spawnAppServerTransport({
|
|
11
|
+
binary,
|
|
12
|
+
args,
|
|
13
|
+
cwd,
|
|
14
|
+
env,
|
|
15
|
+
logStderrOnExit: false,
|
|
16
|
+
}),
|
|
17
|
+
commandVersion: async (path, args) => {
|
|
18
|
+
const { execFile } = await import('node:child_process');
|
|
19
|
+
return await new Promise((resolve, reject) => {
|
|
20
|
+
execFile(path, [...args], { timeout: 15_000, maxBuffer: 1024 * 1024 }, (error, stdout) => {
|
|
21
|
+
if (error)
|
|
22
|
+
reject(error);
|
|
23
|
+
else
|
|
24
|
+
resolve(stdout.trim());
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
/** Coordinator-backed public Codex ScenarioMachineV2 adapter. */
|
|
30
|
+
export async function startPublicCodexScenarioRuntime(options, dependencies = {}) {
|
|
31
|
+
const deps = { ...defaultDependencies, ...dependencies };
|
|
32
|
+
const coordinator = await startScenarioCoordinator({
|
|
33
|
+
...options,
|
|
34
|
+
provider: 'codex',
|
|
35
|
+
async attach(context) {
|
|
36
|
+
const bearer = context.resolveSecret('public-scenario-v2-bearer');
|
|
37
|
+
let handle;
|
|
38
|
+
const resolvedExecutable = await deps.resolveExecutable(options.environment);
|
|
39
|
+
const admission = await admitRuntimeLock(deps.lock, {
|
|
40
|
+
provider: 'codex',
|
|
41
|
+
platform: process.platform,
|
|
42
|
+
architecture: process.arch,
|
|
43
|
+
resolvedExecutable,
|
|
44
|
+
environment: options.environment,
|
|
45
|
+
forbiddenSecrets: [bearer, bearer.slice('Bearer '.length)],
|
|
46
|
+
commandVersion: deps.commandVersion,
|
|
47
|
+
initializeControlPlane: async (verifiedCommandPath, codeOwnedArgs) => {
|
|
48
|
+
handle = deps.spawn({
|
|
49
|
+
binary: verifiedCommandPath,
|
|
50
|
+
args: codeOwnedArgs,
|
|
51
|
+
cwd: options.workspacePath,
|
|
52
|
+
env: { ...options.environment },
|
|
53
|
+
});
|
|
54
|
+
try {
|
|
55
|
+
const initialized = await handle.transport.sendRequest('initialize', {
|
|
56
|
+
clientInfo: { name: 'pathgrade', version: '0.5.0', title: null },
|
|
57
|
+
capabilities: { experimentalApi: true, optOutNotificationMethods: null },
|
|
58
|
+
});
|
|
59
|
+
assertCodexHandshake(initialized, deps.lock.version);
|
|
60
|
+
handle.transport.sendNotification('initialized', null);
|
|
61
|
+
return {
|
|
62
|
+
normalizedHandshake: CODEX_DIRECT_MCP_NORMALIZED_HANDSHAKE,
|
|
63
|
+
capabilities: deps.lock.capabilities,
|
|
64
|
+
close: () => handle.close(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
await handle.close().catch(() => undefined);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
if (!handle)
|
|
74
|
+
throw new Error('RUNTIME_NOT_ADMITTED: control plane unavailable');
|
|
75
|
+
try {
|
|
76
|
+
const plans = projectEndpoints(context.endpoints, admission.profile);
|
|
77
|
+
if (!Array.isArray(plans))
|
|
78
|
+
throw new Error(`RUNTIME_NOT_ADMITTED: ${plans.reason}`);
|
|
79
|
+
const config = mergeCodexScenarioConfigs(plans.map((plan) => materializeProjection(plan, {
|
|
80
|
+
resolve: context.resolveSecret,
|
|
81
|
+
})));
|
|
82
|
+
const tracker = createCodexScenarioTurnTracker(handle.transport);
|
|
83
|
+
const sensitiveValues = [...new Set([
|
|
84
|
+
bearer,
|
|
85
|
+
bearer.slice('Bearer '.length),
|
|
86
|
+
...context.endpoints.map((endpoint) => endpoint.url),
|
|
87
|
+
])];
|
|
88
|
+
const mount = {
|
|
89
|
+
handle,
|
|
90
|
+
config,
|
|
91
|
+
sensitiveValues,
|
|
92
|
+
prepareProviderTurn: tracker.prepare,
|
|
93
|
+
registerProviderTurn: tracker.register,
|
|
94
|
+
failProviderTurn: tracker.fail,
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
value: mount,
|
|
98
|
+
interrupt: tracker.interrupt,
|
|
99
|
+
waitForTerminal: tracker.waitForTerminal,
|
|
100
|
+
async close() {
|
|
101
|
+
tracker.dispose();
|
|
102
|
+
await handle?.close();
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
await handle.close().catch(() => undefined);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
configureSession(sessionOptions) {
|
|
114
|
+
sessionOptions.codexScenarioMount = coordinator.attachment;
|
|
115
|
+
},
|
|
116
|
+
beginTurn: coordinator.beginTurn,
|
|
117
|
+
settleTurn: coordinator.settleTurn,
|
|
118
|
+
failTurn: coordinator.failTurn,
|
|
119
|
+
evidence: coordinator.evidence,
|
|
120
|
+
dispose: coordinator.dispose,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export function mergeCodexScenarioConfigs(configs) {
|
|
124
|
+
const mcpServers = {};
|
|
125
|
+
for (const config of configs) {
|
|
126
|
+
if (config.kind !== 'codex-app-server') {
|
|
127
|
+
throw new Error('Codex projection materialized as another provider');
|
|
128
|
+
}
|
|
129
|
+
Object.assign(mcpServers, config.mcp_servers);
|
|
130
|
+
}
|
|
131
|
+
return { mcp_servers: mcpServers };
|
|
132
|
+
}
|
|
133
|
+
function assertCodexHandshake(initialized, expectedVersion) {
|
|
134
|
+
const version = expectedVersion.replace(/^codex-cli\s+/, '');
|
|
135
|
+
if (typeof initialized.userAgent !== 'string' || !initialized.userAgent.includes(version)) {
|
|
136
|
+
throw new Error('Codex initialize userAgent did not match admitted version');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function createCodexScenarioTurnTracker(transport) {
|
|
140
|
+
let active;
|
|
141
|
+
let resolveActive;
|
|
142
|
+
let rejectActive;
|
|
143
|
+
let terminalError;
|
|
144
|
+
let activeError;
|
|
145
|
+
const createActiveReady = () => {
|
|
146
|
+
const ready = new Promise((resolve, reject) => {
|
|
147
|
+
resolveActive = resolve;
|
|
148
|
+
rejectActive = reject;
|
|
149
|
+
});
|
|
150
|
+
void ready.catch(() => undefined);
|
|
151
|
+
return ready;
|
|
152
|
+
};
|
|
153
|
+
let activeReady = createActiveReady();
|
|
154
|
+
const completed = new Map();
|
|
155
|
+
const waiters = new Map();
|
|
156
|
+
const off = transport.onNotification((notification) => {
|
|
157
|
+
if (notification.method !== 'turn/completed')
|
|
158
|
+
return;
|
|
159
|
+
const params = notification.params;
|
|
160
|
+
const turnId = params?.turn?.id ?? params?.turnId;
|
|
161
|
+
if (!turnId)
|
|
162
|
+
return;
|
|
163
|
+
const status = params?.turn?.status ?? params?.status ?? 'unknown';
|
|
164
|
+
const waiter = waiters.get(turnId);
|
|
165
|
+
if (waiter) {
|
|
166
|
+
waiters.delete(turnId);
|
|
167
|
+
waiter.resolve(status);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
completed.set(turnId, status);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
const closeOff = transport.onClose((info) => {
|
|
174
|
+
terminalError = Object.assign(new Error('Codex app-server closed before the matching turn terminal'), {
|
|
175
|
+
exitCode: info.exitCode,
|
|
176
|
+
signal: info.signal,
|
|
177
|
+
pid: info.pid,
|
|
178
|
+
});
|
|
179
|
+
rejectActive?.(terminalError);
|
|
180
|
+
for (const waiter of waiters.values())
|
|
181
|
+
waiter.reject(terminalError);
|
|
182
|
+
waiters.clear();
|
|
183
|
+
});
|
|
184
|
+
const providerTurn = async (_turn) => {
|
|
185
|
+
if (active)
|
|
186
|
+
return active;
|
|
187
|
+
if (activeError)
|
|
188
|
+
throw activeError;
|
|
189
|
+
if (terminalError)
|
|
190
|
+
throw terminalError;
|
|
191
|
+
return activeReady;
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
prepare() {
|
|
195
|
+
active = undefined;
|
|
196
|
+
activeError = undefined;
|
|
197
|
+
if (terminalError)
|
|
198
|
+
return;
|
|
199
|
+
activeReady = createActiveReady();
|
|
200
|
+
},
|
|
201
|
+
register(threadId, turnId) {
|
|
202
|
+
const turn = { threadId, turnId };
|
|
203
|
+
active = turn;
|
|
204
|
+
resolveActive?.(turn);
|
|
205
|
+
},
|
|
206
|
+
fail(error) {
|
|
207
|
+
activeError = error;
|
|
208
|
+
rejectActive?.(error);
|
|
209
|
+
},
|
|
210
|
+
async interrupt(turn) {
|
|
211
|
+
if (terminalError)
|
|
212
|
+
throw terminalError;
|
|
213
|
+
const provider = await providerTurn(turn);
|
|
214
|
+
await transport.sendRequest('turn/interrupt', provider);
|
|
215
|
+
},
|
|
216
|
+
async waitForTerminal(turn) {
|
|
217
|
+
const provider = await providerTurn(turn);
|
|
218
|
+
if (completed.has(provider.turnId)) {
|
|
219
|
+
completed.delete(provider.turnId);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (terminalError)
|
|
223
|
+
throw terminalError;
|
|
224
|
+
await new Promise((resolve, reject) => waiters.set(provider.turnId, { resolve, reject }));
|
|
225
|
+
},
|
|
226
|
+
dispose() {
|
|
227
|
+
off();
|
|
228
|
+
closeOff();
|
|
229
|
+
const error = terminalError ?? new Error('Codex scenario turn tracker disposed');
|
|
230
|
+
rejectActive?.(error);
|
|
231
|
+
for (const waiter of waiters.values())
|
|
232
|
+
waiter.reject(error);
|
|
233
|
+
active = undefined;
|
|
234
|
+
completed.clear();
|
|
235
|
+
waiters.clear();
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentSessionOptions } from '../types.js';
|
|
2
|
+
import type { AcpToolNameNormalizer } from '../internal/direct-mcp-v2/acp-author-projector.js';
|
|
3
|
+
import { attachAcpAuthorSession, type AcpAuthorSession } from '../internal/direct-mcp-v2/acp-author-session.js';
|
|
4
|
+
import { startScenarioCoordinator, type PublicScenarioRuntimeFactory } from '../internal/direct-mcp-v2/public-scenario-runtime.js';
|
|
5
|
+
import { resolveCursorExecutable } from '../internal/direct-mcp-v2/cursor-profile.js';
|
|
6
|
+
export declare function setCursorScenarioSession(options: AgentSessionOptions, session: AcpAuthorSession): void;
|
|
7
|
+
export declare function getCursorScenarioSession(options: AgentSessionOptions | undefined): AcpAuthorSession | undefined;
|
|
8
|
+
/** Cursor's admitted ACP build exposes MCP calls only as the stable title `MCP: tool`. */
|
|
9
|
+
export declare const normalizeCursorAcpToolName: AcpToolNameNormalizer;
|
|
10
|
+
type CursorScenarioRuntimeDependencies = {
|
|
11
|
+
attachAcpAuthorSession: typeof attachAcpAuthorSession;
|
|
12
|
+
resolveCursorExecutable: typeof resolveCursorExecutable;
|
|
13
|
+
startScenarioCoordinator: typeof startScenarioCoordinator;
|
|
14
|
+
};
|
|
15
|
+
export type CursorScenarioRuntimeFactoryOptions = {
|
|
16
|
+
requestedModel?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function createPublicCursorScenarioRuntimeFactory(factoryOptions?: CursorScenarioRuntimeFactoryOptions, dependencyOverrides?: Partial<CursorScenarioRuntimeDependencies>): PublicScenarioRuntimeFactory;
|
|
19
|
+
export declare const startPublicCursorScenarioRuntime: typeof import("../internal/direct-mcp-v2/public-scenario-runtime.js").startPublicClaudeScenarioRuntime;
|
|
20
|
+
export {};
|