@wix/pathgrade 1.0.20 → 1.0.21

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 (35) hide show
  1. package/README.md +4 -2
  2. package/dist/agents/codex-app-server/agent.d.ts +0 -9
  3. package/dist/agents/codex-app-server/agent.js +73 -57
  4. package/dist/agents/codex-app-server/mcp-approval-correlator.d.ts +57 -4
  5. package/dist/agents/codex-app-server/mcp-approval-correlator.js +66 -9
  6. package/dist/agents/codex-app-server/protocol/ClientRequest.js +1 -1
  7. package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.js +1 -1
  8. package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.js +1 -1
  9. package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.d.ts +5 -0
  10. package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.js +1 -1
  11. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.d.ts +3 -0
  12. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.js +1 -1
  13. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.d.ts +1 -1
  14. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.js +1 -1
  15. package/dist/agents/codex-app-server/protocol/SandboxMode.js +1 -1
  16. package/dist/agents/codex-app-server/protocol/ServerRequest.js +2 -2
  17. package/dist/agents/codex-app-server/protocol/ThreadStartParams.d.ts +14 -18
  18. package/dist/agents/codex-app-server/protocol/ThreadStartParams.js +3 -3
  19. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.js +1 -1
  20. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.js +1 -1
  21. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.d.ts +3 -0
  22. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.js +1 -1
  23. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.js +1 -1
  24. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.js +1 -1
  25. package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.js +1 -1
  26. package/dist/agents/codex-app-server/protocol/index.js +1 -1
  27. package/dist/providers/mcp-config.d.ts +2 -0
  28. package/dist/providers/mcp-config.js +5 -2
  29. package/dist/providers/workspace.d.ts +2 -1
  30. package/dist/providers/workspace.js +2 -1
  31. package/dist/sdk/judge-prompt-builder.js +13 -1
  32. package/dist/sdk/managed-session.js +1 -0
  33. package/dist/sdk/mcp-mock-approvals.js +2 -2
  34. package/dist/types.d.ts +3 -0
  35. package/package.json +2 -2
package/README.md CHANGED
@@ -350,8 +350,10 @@ Read-only metadata is explicit: add `annotations.readOnlyHint: true` only when
350
350
  the operation being modeled is read-only. Unannotated and explicitly mutating
351
351
  mock tools retain the runtime's normal approval behavior.
352
352
 
353
- To evaluate mutation approval behavior with Claude or Codex app-server, pass a
354
- non-empty ordered `mcpMockApprovalRules` array. The first matching rule wins:
353
+ To evaluate through the receipt-backed mock host with Claude or Codex
354
+ app-server, pass an ordered `mcpMockApprovalRules` array. Use an empty array for
355
+ read-only-only mocks. Mutation scenarios require explicit rules; the first
356
+ matching rule wins:
355
357
 
356
358
  ```ts
357
359
  const agent = await createAgent({
@@ -2,13 +2,6 @@ import { AgentCommandRunner, AgentSession, AgentSessionOptions, BaseAgent, Envir
2
2
  import { type AppServerSessionHandle } from './transport.js';
3
3
  import { type LifecycleClock } from './item-lifecycle.js';
4
4
  type SandboxMode = 'workspace-write' | 'danger-full-access';
5
- export interface PermissionGrantLogEntry {
6
- type: 'permissions_granted';
7
- turnNumber: number;
8
- requested: unknown;
9
- scope: 'turn';
10
- strictAutoReview: false;
11
- }
12
5
  export interface CodexAppServerAgentDeps {
13
6
  /**
14
7
  * Inject a transport factory for tests. Default: spawn `codex app-server`
@@ -24,8 +17,6 @@ export interface CodexAppServerAgentDeps {
24
17
  }) => Promise<AppServerSessionHandle>;
25
18
  /** Sandbox mode for `thread/start`. Default: 'workspace-write'. */
26
19
  sandboxMode?: SandboxMode;
27
- /** Observer for per-grant audit entries (§7 of design decisions). */
28
- onPermissionGrant?: (entry: PermissionGrantLogEntry) => void;
29
20
  /** Injectable clocks keep wall timestamps and monotonic durations independently testable. */
30
21
  clock?: LifecycleClock;
31
22
  /** Inject Codex-managed ChatGPT access sessions for deterministic tests. */
@@ -1,5 +1,5 @@
1
1
  import { BaseAgent, getRuntimeEnv, getWorkspacePath, } from '../../types.js';
2
- import { mountMcpForCodexAppServer } from '../../providers/mcp-runtime-mounting.js';
2
+ import { assertUnsupportedLiveMcpSafetyRuntime, mountMcpForCodexAppServer, } from '../../providers/mcp-runtime-mounting.js';
3
3
  import { assertMcpSecretReferencesReady } from '../../providers/mcp-config.js';
4
4
  import { enrichSkillEvents, } from '../../tool-events.js';
5
5
  import { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../../tool-event-results.js';
@@ -7,12 +7,11 @@ import { requireAskBusForLiveBatches } from '../../sdk/ask-bus/bus.js';
7
7
  import { attachTurnResultSensitiveValues } from '../../sdk/turn-result-secrets.js';
8
8
  import { attachToolEventSensitiveValues } from '../../sdk/tool-event-secrets.js';
9
9
  import { toAskUserToolEvent } from '../../sdk/ask-bus/projection.js';
10
- import { decideMcpToolCall } from '../../sdk/mcp-safety.js';
11
10
  import { spawnAppServerTransport, } from './transport.js';
12
11
  import { normalizeUpstreamQuestion, toWireAnswerMap, } from './wire-translators.js';
13
12
  import { extractTurnCompletionFailure } from './turn-completion.js';
14
13
  import { resolveCodexModel } from '../codex-model.js';
15
- import { CodexMcpApprovalCorrelator, extractMcpToolApprovalRequest, hasScriptedApprovalPolicy, isMcpToolCallApprovalRequest, recordPolicyDeniedMcpToolCall } from './mcp-approval-correlator.js';
14
+ import { buildLiveMcpProtocolErrorEvent, buildPolicyDeniedMcpToolEvent, CodexMcpApprovalCorrelator, consumeMatchingMcpDenial, evaluateLiveMcpApproval, hasScriptedApprovalPolicy, isMcpToolCallApprovalRequest, queuePolicyDeniedMcpToolCall, } from './mcp-approval-correlator.js';
16
15
  import { projectItemIntoTurn } from './item-projection.js';
17
16
  import { CodexItemLifecycle, } from './item-lifecycle.js';
18
17
  import { createCodexManagedAuth } from './managed-auth.js';
@@ -45,6 +44,26 @@ function failTurn(turn, message) {
45
44
  turn.failureMessage = message;
46
45
  turn.signalFailure?.(message);
47
46
  }
47
+ function recordFromUnknown(value) {
48
+ return value && typeof value === 'object' && !Array.isArray(value)
49
+ ? value
50
+ : {};
51
+ }
52
+ function projectCompletedItem(item, timing, params, turn, correlator, sensitiveValues) {
53
+ if (correlator?.completed(params) === false)
54
+ return;
55
+ const mcpItem = item.type === 'mcpToolCall'
56
+ ? item
57
+ : undefined;
58
+ const pendingDenial = mcpItem?.status === 'failed'
59
+ ? consumeMatchingMcpDenial(turn, mcpItem.server, mcpItem.tool, recordFromUnknown(mcpItem.arguments))
60
+ : undefined;
61
+ if (!pendingDenial || !mcpItem) {
62
+ projectItemIntoTurn(item, turn, sensitiveValues, timing);
63
+ return;
64
+ }
65
+ turn.nonAskToolEvents.push(buildPolicyDeniedMcpToolEvent(turn.turnNumber, pendingDenial, mcpItem));
66
+ }
48
67
  function extractTurnCompletionIdentity(params) {
49
68
  if (!params || typeof params !== 'object' || Array.isArray(params))
50
69
  return {};
@@ -74,6 +93,12 @@ export class CodexAppServerAgent extends BaseAgent {
74
93
  const sensitiveValues = collectSensitiveEnvValues(runtimeEnv);
75
94
  const model = resolveCodexModel(options?.model);
76
95
  const sandboxMode = this.deps.sandboxMode ?? 'workspace-write';
96
+ await assertUnsupportedLiveMcpSafetyRuntime({
97
+ runtimeName: 'Codex app-server',
98
+ workspacePath,
99
+ mcpConfigPath: options?.mcpConfigPath,
100
+ mcpSafety: options?.mcpSafety,
101
+ });
77
102
  let handle = null;
78
103
  let threadId = null;
79
104
  let turnCounter = 0;
@@ -106,7 +131,6 @@ export class CodexAppServerAgent extends BaseAgent {
106
131
  transport,
107
132
  askBus,
108
133
  activeTurn: () => activeTurn,
109
- onPermissionGrant: this.deps.onPermissionGrant,
110
134
  mcpSafety: options?.mcpSafety,
111
135
  scriptedHost,
112
136
  correlator,
@@ -150,7 +174,7 @@ export class CodexAppServerAgent extends BaseAgent {
150
174
  codexUserAgent = initialized.userAgent.slice(0, 200);
151
175
  }
152
176
  // Upstream ClientNotification = { method: "initialized" }: send it
153
- // before any thread/start so the handshake matches the v0.124
177
+ // before any thread/start so the handshake matches the v0.149
154
178
  // contract and is forward-compatible with servers that enforce it.
155
179
  transport.sendNotification('initialized', null);
156
180
  await managedAuth.login(transport);
@@ -170,6 +194,7 @@ export class CodexAppServerAgent extends BaseAgent {
170
194
  turnNumber: turnCounter,
171
195
  askBatchIds: [],
172
196
  nonAskToolEvents: [],
197
+ pendingMcpDenials: [],
173
198
  assistantMessageParts: [],
174
199
  turnFailed: false,
175
200
  pendingTurnCompletions: [],
@@ -191,7 +216,13 @@ export class CodexAppServerAgent extends BaseAgent {
191
216
  mcpConfigPath: options.mcpConfigPath,
192
217
  })
193
218
  : undefined);
194
- const resp = await t.sendRequest('thread/start', buildThreadStartParams({ cwd: workspacePath, model, sandboxMode, mcpConfig, scripted: !!scriptedHost }));
219
+ const resp = await t.sendRequest('thread/start', buildThreadStartParams({
220
+ cwd: workspacePath,
221
+ model,
222
+ sandboxMode,
223
+ mcpConfig,
224
+ enableMcpElicitations: !!scriptedHost || shouldEnableMcpElicitations(options),
225
+ }));
195
226
  if (scriptedHost && !hasScriptedApprovalPolicy(resp)) {
196
227
  throw new Error(`Codex app-server scripted MCP approval policy drift (userAgent=${codexUserAgent})`);
197
228
  }
@@ -213,9 +244,7 @@ export class CodexAppServerAgent extends BaseAgent {
213
244
  },
214
245
  onCompleted: (item, timing, params) => {
215
246
  try {
216
- if (correlator?.completed(params) !== false) {
217
- projectItemIntoTurn(item, turn, sensitiveValues, timing);
218
- }
247
+ projectCompletedItem(item, timing, params, turn, correlator, sensitiveValues);
219
248
  }
220
249
  catch (error) {
221
250
  failTurn(turn, error instanceof Error ? error.message : String(error));
@@ -404,41 +433,25 @@ export class CodexAppServerAgent extends BaseAgent {
404
433
  };
405
434
  }
406
435
  dispatchServerRequest(req, ctx) {
407
- const { transport, askBus, activeTurn, onPermissionGrant, mcpSafety, scriptedHost, correlator } = ctx;
436
+ const { transport, askBus, activeTurn, mcpSafety, scriptedHost, correlator } = ctx;
408
437
  switch (req.method) {
409
438
  case 'item/tool/requestUserInput':
410
439
  void handleRequestUserInput(req, { transport, askBus, activeTurn });
411
440
  return;
412
441
  case 'item/permissions/requestApproval': {
413
- if (scriptedHost) {
414
- transport.sendResponse(req.id, { permissions: {}, scope: 'turn', strictAutoReview: false });
415
- return;
416
- }
417
- const params = (req.params ?? {});
418
- transport.sendResponse(req.id, {
419
- permissions: params.permissions ?? {},
420
- scope: 'turn',
421
- strictAutoReview: false,
422
- });
423
- const turn = activeTurn();
424
- if (onPermissionGrant) {
425
- onPermissionGrant({
426
- type: 'permissions_granted',
427
- turnNumber: turn?.turnNumber ?? 0,
428
- requested: params.permissions ?? {},
429
- scope: 'turn',
430
- strictAutoReview: false,
431
- });
432
- }
442
+ transport.sendErrorResponse(req.id, -32000, 'pathgrade: permission grants are not authorized by the MCP-only approval policy');
433
443
  return;
434
444
  }
435
445
  case 'item/commandExecution/requestApproval':
436
446
  case 'item/fileChange/requestApproval':
447
+ transport.sendResponse(req.id, { decision: 'decline' });
448
+ return;
437
449
  case 'applyPatchApproval':
438
450
  case 'execCommandApproval':
439
- transport.sendResponse(req.id, scriptedHost
440
- ? { decision: 'denied' }
441
- : { decision: 'approved', scope: 'turn' });
451
+ transport.sendResponse(req.id, { decision: { denied: {
452
+ rejection: 'Pathgrade does not authorize legacy command or patch approvals.',
453
+ } },
454
+ });
442
455
  return;
443
456
  case 'item/tool/call':
444
457
  transport.sendResponse(req.id, { status: 'declined' });
@@ -456,25 +469,17 @@ export class CodexAppServerAgent extends BaseAgent {
456
469
  return;
457
470
  }
458
471
  if (isMcpToolCallApprovalRequest(req.params)) {
459
- const toolRequest = extractMcpToolApprovalRequest(req.params);
460
- if (toolRequest) {
461
- const decision = decideMcpToolCall(mcpSafety, toolRequest);
462
- if (decision.action === 'deny') {
463
- recordPolicyDeniedMcpToolCall(activeTurn(), toolRequest, decision, req.params);
464
- transport.sendResponse(req.id, {
465
- action: 'decline',
466
- content: null,
467
- _meta: {
468
- pathgrade_policy_denial: {
469
- reason: decision.reason,
470
- message: decision.message,
471
- },
472
- },
473
- });
474
- return;
472
+ const evaluation = evaluateLiveMcpApproval(mcpSafety, req.params);
473
+ if (evaluation.denial) {
474
+ queuePolicyDeniedMcpToolCall(activeTurn(), evaluation.denial.request, evaluation.denial.decision, req.params);
475
+ }
476
+ if (evaluation.protocolError) {
477
+ const turn = activeTurn();
478
+ if (turn) {
479
+ turn.nonAskToolEvents.push(buildLiveMcpProtocolErrorEvent(turn.turnNumber, evaluation.protocolError, req.params));
475
480
  }
476
481
  }
477
- transport.sendResponse(req.id, { action: 'accept', content: {}, _meta: null });
482
+ transport.sendResponse(req.id, evaluation.response);
478
483
  }
479
484
  else {
480
485
  transport.sendResponse(req.id, { action: 'decline', content: null, _meta: null });
@@ -528,21 +533,32 @@ async function handleRequestUserInput(req, ctx) {
528
533
  function buildThreadStartParams(opts) {
529
534
  return {
530
535
  cwd: opts.cwd,
531
- approvalPolicy: opts.scripted ? { granular: {
532
- sandbox_approval: false, rules: false, skill_approval: false,
533
- request_permissions: false, mcp_elicitations: true,
534
- } } : 'never',
535
- ...(opts.scripted ? { approvalsReviewer: 'user' } : {}),
536
+ approvalPolicy: opts.enableMcpElicitations
537
+ ? {
538
+ granular: {
539
+ sandbox_approval: false,
540
+ rules: false,
541
+ skill_approval: false,
542
+ request_permissions: false,
543
+ mcp_elicitations: true,
544
+ },
545
+ }
546
+ : 'never',
547
+ ...(opts.enableMcpElicitations ? { approvalsReviewer: 'user' } : {}),
536
548
  sandbox: opts.sandboxMode,
537
549
  ephemeral: true,
538
- experimentalRawEvents: false,
539
- persistExtendedHistory: false,
540
550
  model: opts.model,
541
551
  ...(opts.mcpConfig ? { config: opts.mcpConfig } : {}),
542
552
  };
543
553
  }
554
+ function shouldEnableMcpElicitations(options) {
555
+ return options?.mcpConfigOrigin === 'generated_mock';
556
+ }
544
557
  function assembleTurnResult(args) {
545
558
  const { askBus, activeTurn, exitCode, message, pid, signal, sensitiveValues = [] } = args;
559
+ for (const pending of activeTurn.pendingMcpDenials.splice(0)) {
560
+ activeTurn.nonAskToolEvents.push(buildPolicyDeniedMcpToolEvent(activeTurn.turnNumber, pending));
561
+ }
546
562
  const askBatchIds = new Set(activeTurn.askBatchIds);
547
563
  const askEvents = askBus
548
564
  .snapshot()
@@ -1,6 +1,30 @@
1
1
  import type { ToolEvent } from '../../tool-events.js';
2
2
  import type { ScriptedMcpMockHost } from '../../providers/scripted-mcp-mock-host.js';
3
- import { type McpToolPolicyDecision } from '../../sdk/mcp-safety.js';
3
+ import { type McpSafetyOptions, type McpToolPolicyDecision } from '../../sdk/mcp-safety.js';
4
+ export interface PendingMcpDenial {
5
+ request: {
6
+ serverName: string;
7
+ toolName: string;
8
+ arguments: Record<string, unknown>;
9
+ };
10
+ decision: Extract<McpToolPolicyDecision, {
11
+ action: 'deny';
12
+ }>;
13
+ rawParams: unknown;
14
+ }
15
+ export interface CodexMcpTerminalItem {
16
+ type: 'mcpToolCall';
17
+ id: string;
18
+ server: string;
19
+ tool: string;
20
+ status?: string;
21
+ arguments?: unknown;
22
+ result?: unknown;
23
+ error?: {
24
+ message?: string;
25
+ } | null;
26
+ durationMs?: number | null;
27
+ }
4
28
  export interface CorrelatedApprovalResponse {
5
29
  action: 'accept' | 'decline';
6
30
  events: ToolEvent[];
@@ -15,9 +39,30 @@ export declare function extractMcpToolApprovalRequest(params: unknown): {
15
39
  toolName: string;
16
40
  arguments: Record<string, unknown>;
17
41
  } | undefined;
18
- export declare function recordPolicyDeniedMcpToolCall(turn: {
19
- turnNumber: number;
20
- nonAskToolEvents: ToolEvent[];
42
+ export declare function evaluateLiveMcpApproval(safety: McpSafetyOptions | undefined, params: unknown): {
43
+ response: {
44
+ action: 'accept' | 'decline';
45
+ content: Record<string, never> | null;
46
+ _meta: {
47
+ pathgrade_policy_denial: {
48
+ reason: string;
49
+ message: string;
50
+ };
51
+ } | null;
52
+ };
53
+ denial?: {
54
+ request: NonNullable<ReturnType<typeof extractMcpToolApprovalRequest>>;
55
+ decision: Extract<McpToolPolicyDecision, {
56
+ action: 'deny';
57
+ }>;
58
+ };
59
+ protocolError?: {
60
+ reason: string;
61
+ message: string;
62
+ };
63
+ };
64
+ export declare function queuePolicyDeniedMcpToolCall(turn: {
65
+ pendingMcpDenials: PendingMcpDenial[];
21
66
  } | null, request: {
22
67
  serverName: string;
23
68
  toolName: string;
@@ -25,6 +70,14 @@ export declare function recordPolicyDeniedMcpToolCall(turn: {
25
70
  }, decision: Extract<McpToolPolicyDecision, {
26
71
  action: 'deny';
27
72
  }>, rawParams: unknown): void;
73
+ export declare function buildPolicyDeniedMcpToolEvent(turnNumber: number, pending: PendingMcpDenial, terminal?: CodexMcpTerminalItem): ToolEvent;
74
+ export declare function consumeMatchingMcpDenial(turn: {
75
+ pendingMcpDenials: PendingMcpDenial[];
76
+ }, serverName: string, toolName: string, args: Record<string, unknown>): PendingMcpDenial | undefined;
77
+ export declare function buildLiveMcpProtocolErrorEvent(turnNumber: number, error: {
78
+ reason: string;
79
+ message: string;
80
+ }, rawParams: unknown): ToolEvent;
28
81
  export declare class CodexMcpApprovalCorrelator {
29
82
  private readonly host;
30
83
  private readonly getTurnNumber;
@@ -1,4 +1,4 @@
1
- import { redactMcpSecrets, } from '../../sdk/mcp-safety.js';
1
+ import { decideMcpToolCall, redactMcpSecrets, } from '../../sdk/mcp-safety.js';
2
2
  import { canonicalizeJson } from '../../core/canonical-json.js';
3
3
  import { buildScriptedMcpApprovalEvent, buildScriptedMcpDeniedCallEvent, } from '../../sdk/scripted-mcp-events.js';
4
4
  function isRecord(value) {
@@ -30,18 +30,75 @@ export function extractMcpToolApprovalRequest(params) {
30
30
  return undefined;
31
31
  return { serverName, toolName, arguments: isRecord(meta.tool_params) ? meta.tool_params : {} };
32
32
  }
33
- export function recordPolicyDeniedMcpToolCall(turn, request, decision, rawParams) {
33
+ export function evaluateLiveMcpApproval(safety, params) {
34
+ const request = extractMcpToolApprovalRequest(params);
35
+ if (!request) {
36
+ const protocolError = {
37
+ reason: 'unrecognized_mcp_tool_name',
38
+ message: 'Live MCP tool identity could not be established.',
39
+ };
40
+ return {
41
+ response: {
42
+ action: 'decline', content: null,
43
+ _meta: { pathgrade_policy_denial: protocolError },
44
+ },
45
+ protocolError,
46
+ };
47
+ }
48
+ const decision = decideMcpToolCall(safety, request);
49
+ if (decision.action === 'allow') {
50
+ return { response: { action: 'accept', content: {}, _meta: null } };
51
+ }
52
+ return {
53
+ response: { action: 'decline', content: null, _meta: {
54
+ pathgrade_policy_denial: { reason: decision.reason, message: decision.message },
55
+ } },
56
+ denial: { request, decision },
57
+ };
58
+ }
59
+ export function queuePolicyDeniedMcpToolCall(turn, request, decision, rawParams) {
34
60
  if (!turn)
35
61
  return;
36
- const args = redactMcpSecrets(request.arguments);
37
- const providerToolName = `${request.serverName}.${request.toolName}`;
38
- turn.nonAskToolEvents.push({
39
- action: 'mcp_tool_call', provider: 'codex', providerToolName, turnNumber: turn.turnNumber,
40
- arguments: { ...args, server: request.serverName, tool: request.toolName, status: 'policy_denied',
41
- policyResult: { action: 'deny', reason: decision.reason, message: decision.message } },
62
+ turn.pendingMcpDenials.push({ request, decision, rawParams });
63
+ }
64
+ export function buildPolicyDeniedMcpToolEvent(turnNumber, pending, terminal) {
65
+ const args = redactMcpSecrets(pending.request.arguments);
66
+ const providerToolName = `${pending.request.serverName}.${pending.request.toolName}`;
67
+ return {
68
+ action: 'mcp_tool_call', provider: 'codex', providerToolName,
69
+ ...(terminal ? { toolUseId: terminal.id } : {}),
70
+ turnNumber, status: 'error',
71
+ mcp: { serverName: pending.request.serverName, toolName: pending.request.toolName,
72
+ invocation: 'not_invoked', outcome: 'policy_denied' },
73
+ arguments: {
74
+ ...args,
75
+ server: pending.request.serverName,
76
+ tool: pending.request.toolName,
77
+ status: 'policy_denied',
78
+ policyResult: { action: 'deny', reason: pending.decision.reason, message: pending.decision.message },
79
+ },
42
80
  summary: `MCP tool ${providerToolName} policy_denied`, confidence: 'high',
81
+ rawSnippet: JSON.stringify(redactMcpSecrets(terminal ? { approval: pending.rawParams, terminal } : pending.rawParams)),
82
+ };
83
+ }
84
+ export function consumeMatchingMcpDenial(turn, serverName, toolName, args) {
85
+ const argsKey = canonicalizeJson(args);
86
+ const index = turn.pendingMcpDenials.findIndex((pending) => pending.request.serverName === serverName
87
+ && pending.request.toolName === toolName
88
+ && canonicalizeJson(pending.request.arguments) === argsKey);
89
+ if (index < 0)
90
+ return undefined;
91
+ return turn.pendingMcpDenials.splice(index, 1)[0];
92
+ }
93
+ export function buildLiveMcpProtocolErrorEvent(turnNumber, error, rawParams) {
94
+ return {
95
+ action: 'mcp_approval', provider: 'codex', providerToolName: 'mcp.protocol_error',
96
+ turnNumber, status: 'error',
97
+ arguments: { decision: 'deny', outcome: 'protocol_error', reason: error.reason,
98
+ message: error.message, decisionSource: 'live_policy' },
99
+ summary: 'MCP approval protocol error', confidence: 'high',
43
100
  rawSnippet: JSON.stringify(redactMcpSecrets(rawParams)),
44
- });
101
+ };
45
102
  }
46
103
  export class CodexMcpApprovalCorrelator {
47
104
  host;
@@ -3,5 +3,5 @@
3
3
  // `codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts`
4
4
  // (top-level, NOT `typescript/v2/`). This module enumerates the subset of
5
5
  // method names the pathgrade Codex app-server driver actually sends. Refresh
6
- // from openai/codex@rust-v0.124.0 when bumping the vendored version.
6
+ // Refreshed against openai/codex@rust-v0.149.0-alpha.4.3.
7
7
  export {};
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallParams.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/GrantedPermissionProfile.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  //
@@ -14,6 +14,11 @@ export type McpElicitationRequestParams = {
14
14
  _meta: JsonValue | null;
15
15
  message: string;
16
16
  requestedSchema: McpElicitationSchema;
17
+ } | {
18
+ mode: 'openai/form';
19
+ _meta: JsonValue | null;
20
+ message: string;
21
+ requestedSchema: JsonValue;
17
22
  } | {
18
23
  mode: 'url';
19
24
  _meta: JsonValue | null;
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/McpServerElicitationRequestParams.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  //
@@ -4,6 +4,9 @@ export type PermissionsRequestApprovalParams = {
4
4
  threadId: string;
5
5
  turnId: string;
6
6
  itemId: string;
7
+ environmentId: string | null;
8
+ /** Unix timestamp (in milliseconds) when this approval request started. */
9
+ startedAtMs: number;
7
10
  cwd: AbsolutePathBuf;
8
11
  reason: string | null;
9
12
  permissions: RequestPermissionProfile;
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/PermissionsRequestApprovalParams.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  //
@@ -4,7 +4,7 @@ import type { GrantedPermissionProfile } from './GrantedPermissionProfile.js';
4
4
  * turn only; `'thread'` persists until the thread ends. Upstream union is
5
5
  * represented as a string literal set here.
6
6
  */
7
- export type PermissionGrantScope = 'turn' | 'thread';
7
+ export type PermissionGrantScope = 'turn' | 'session';
8
8
  export type PermissionsRequestApprovalResponse = {
9
9
  permissions: GrantedPermissionProfile;
10
10
  scope: PermissionGrantScope;
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/PermissionsRequestApprovalResponse.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/SandboxMode.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -1,6 +1,6 @@
1
1
  // Pathgrade-local composition; not a single upstream file.
2
- // Enumerates the 9 server-request variants the driver observes over the wire
3
- // under rust-v0.124.0. Upstream ships the discriminated union at
2
+ // Enumerates the 9 server-request variants the driver supports from
3
+ // rust-v0.149.0-alpha.4.3. Upstream ships the discriminated union at
4
4
  // `codex-rs/app-server-protocol/schema/typescript/ServerRequest.ts`
5
5
  // (top-level, NOT `typescript/v2/`); the per-variant params files this module
6
6
  // imports live under the `v2/` subdirectory.
@@ -1,15 +1,22 @@
1
1
  import type { SandboxMode } from './SandboxMode.js';
2
2
  export type Personality = unknown;
3
- export type ServiceTier = unknown;
4
3
  export type JsonValue = unknown;
5
- export type ApprovalsReviewer = unknown;
6
- export type AskForApproval = unknown;
7
- export type PermissionProfile = unknown;
4
+ export type ApprovalsReviewer = 'user' | 'auto_review' | 'guardian_subagent';
5
+ export type AskForApproval = 'untrusted' | 'on-request' | {
6
+ granular: {
7
+ sandbox_approval: boolean;
8
+ rules: boolean;
9
+ skill_approval: boolean;
10
+ request_permissions: boolean;
11
+ mcp_elicitations: boolean;
12
+ };
13
+ } | 'never';
8
14
  export type ThreadStartSource = unknown;
15
+ export type ThreadSource = unknown;
9
16
  export type ThreadStartParams = {
10
17
  model?: string | null;
11
18
  modelProvider?: string | null;
12
- serviceTier?: ServiceTier | null | null;
19
+ serviceTier?: string | null | null;
13
20
  cwd?: string | null;
14
21
  approvalPolicy?: AskForApproval | null;
15
22
  /**
@@ -18,11 +25,6 @@ export type ThreadStartParams = {
18
25
  */
19
26
  approvalsReviewer?: ApprovalsReviewer | null;
20
27
  sandbox?: SandboxMode | null;
21
- /**
22
- * Full permissions override for this thread. Cannot be combined with
23
- * `sandbox`.
24
- */
25
- permissionProfile?: PermissionProfile | null;
26
28
  config?: {
27
29
  [key: string]: JsonValue | undefined;
28
30
  } | null;
@@ -33,13 +35,7 @@ export type ThreadStartParams = {
33
35
  ephemeral?: boolean | null;
34
36
  sessionStartSource?: ThreadStartSource | null;
35
37
  /**
36
- * If true, opt into emitting raw Responses API items on the event stream.
37
- * This is for internal use only (e.g. Codex Cloud).
38
- */
39
- experimentalRawEvents: boolean;
40
- /**
41
- * If true, persist additional rollout EventMsg variants required to
42
- * reconstruct a richer thread history on resume/fork/read.
38
+ * Optional client-supplied analytics source classification for this thread.
43
39
  */
44
- persistExtendedHistory: boolean;
40
+ threadSource?: ThreadSource | null;
45
41
  };
@@ -1,9 +1,9 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartParams.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  //
5
- // NOTE: upstream imports additional shared types (Personality, ServiceTier, JsonValue,
6
- // ApprovalsReviewer, AskForApproval, PermissionProfile, ThreadStartSource) that are not
5
+ // NOTE: upstream imports additional shared types (Personality, JsonValue,
6
+ // ThreadSource, ThreadStartSource) that are not
7
7
  // required by the pathgrade Codex driver. They are modelled here as opaque aliases so the
8
8
  // vendored surface compiles standalone. Refresh policy: when a new upstream version lands,
9
9
  // replace each alias with the vendored definition if the driver starts consuming it.
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputAnswer.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputOption.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -7,4 +7,7 @@ export type ToolRequestUserInputParams = {
7
7
  turnId: string;
8
8
  itemId: string;
9
9
  questions: Array<ToolRequestUserInputQuestion>;
10
+ isBlocking: boolean;
11
+ /** @deprecated Use `isBlocking` to decide whether the request should block. */
12
+ autoResolutionMs: number | null;
10
13
  };
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputQuestion.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputResponse.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -1,4 +1,4 @@
1
- // Vendored from openai/codex@rust-v0.124.0
1
+ // Vendored from openai/codex@rust-v0.149.0-alpha.4.3
2
2
  // Source: codex-rs/app-server-protocol/schema/typescript/v2/TurnCompletedNotification.ts
3
3
  // GENERATED CODE in upstream; do not modify locally either.
4
4
  export {};
@@ -1,6 +1,6 @@
1
1
  // Curated re-exports for the pathgrade Codex app-server driver and fixture
2
2
  // tests. Every re-exported symbol is either a curated upstream shape from
3
- // openai/codex@rust-v0.124.0 (with an upstream-citation header in its file) or
3
+ // openai/codex@rust-v0.149.0-alpha.4.3 (with an upstream-citation header in its file) or
4
4
  // a pathgrade-local composition (ClientRequest, ServerRequest, Op) whose header
5
5
  // spells out the composition rationale.
6
6
  //
@@ -1,4 +1,5 @@
1
1
  import type { MockMcpServerDescriptor } from '../core/mcp-mock.types.js';
2
+ import type { McpConfigOrigin } from '../types.js';
2
3
  /**
3
4
  * Stdio-server shape pathgrade writes to `.pathgrade-mcp.json`. Lines up
4
5
  * directly with the SDK's `McpStdioServerConfig` (`sdk.d.ts:1005`) — the
@@ -38,5 +39,6 @@ export declare function assertMcpSecretReferencesReady(opts: {
38
39
  }): Promise<void>;
39
40
  export interface McpConfigResult {
40
41
  mcpConfigPath: string | undefined;
42
+ mcpConfigOrigin: McpConfigOrigin | undefined;
41
43
  }
42
44
  export declare function stageMcpConfig(workspacePath: string, mcp: McpDeclaration | undefined): Promise<McpConfigResult>;
@@ -130,7 +130,7 @@ async function resolveMockServerScript(workspacePath) {
130
130
  }
131
131
  export async function stageMcpConfig(workspacePath, mcp) {
132
132
  if (!mcp)
133
- return { mcpConfigPath: undefined };
133
+ return { mcpConfigPath: undefined, mcpConfigOrigin: undefined };
134
134
  const mcpConfigPath = MCP_CONFIG_FILENAME;
135
135
  if ('configFile' in mcp) {
136
136
  const mcpSrc = path.resolve(mcp.configFile);
@@ -161,5 +161,8 @@ export async function stageMcpConfig(workspacePath, mcp) {
161
161
  }
162
162
  await fs.writeJson(path.join(workspacePath, mcpConfigPath), { mcpServers }, { spaces: 2 });
163
163
  }
164
- return { mcpConfigPath };
164
+ return {
165
+ mcpConfigPath,
166
+ mcpConfigOrigin: 'mock' in mcp ? 'generated_mock' : 'caller_config',
167
+ };
165
168
  }
@@ -1,9 +1,10 @@
1
1
  import { type SandboxConfig } from './sandbox.js';
2
- import type { CommandResult } from '../types.js';
2
+ import type { CommandResult, McpConfigOrigin } from '../types.js';
3
3
  export type { McpDeclaration } from './mcp-config.js';
4
4
  export interface Workspace {
5
5
  readonly path: string;
6
6
  readonly mcpConfigPath: string | undefined;
7
+ readonly mcpConfigOrigin: McpConfigOrigin | undefined;
7
8
  readonly env: Record<string, string>;
8
9
  readonly setupCommands: string[];
9
10
  readonly sensitiveValues?: readonly string[];
@@ -56,11 +56,12 @@ export async function prepareWorkspace(spec) {
56
56
  await copyPathsFromHostHome(creds.copyFromHome, homePath);
57
57
  await linkPathsFromHostHome(creds.linkFromHome ?? [], homePath);
58
58
  await stageSensitiveHomeFiles(creds.sensitiveHomeFiles ?? [], homePath);
59
- const { mcpConfigPath } = await stageMcpConfig(workspacePath, mcp);
59
+ const { mcpConfigPath, mcpConfigOrigin } = await stageMcpConfig(workspacePath, mcp);
60
60
  let disposed = false;
61
61
  return {
62
62
  path: workspacePath,
63
63
  mcpConfigPath,
64
+ mcpConfigOrigin,
64
65
  env: sandboxEnv,
65
66
  setupCommands: creds.setupCommands,
66
67
  sensitiveValues: [...new Set([
@@ -1,3 +1,5 @@
1
+ import { sanitizePersistenceValue } from '../tool-event-results.js';
2
+ import { getToolEventSensitiveValues } from './tool-event-secrets.js';
1
3
  export function buildJudgePrompt(scorer, ctx, input) {
2
4
  const sections = [];
3
5
  sections.push(`## Session Transcript\n${ctx.transcript}`);
@@ -23,7 +25,17 @@ function formatToolEvents(ctx) {
23
25
  return ctx.toolEvents
24
26
  .map((event) => {
25
27
  const turn = event.turnNumber ? `turn ${event.turnNumber}` : 'instruction';
26
- return `- ${turn}: ${event.action} via ${event.providerToolName} (${event.provider})`;
28
+ const sensitiveValues = getToolEventSensitiveValues(event);
29
+ const details = [
30
+ event.status ? ` status: ${event.status}` : undefined,
31
+ event.mcp ? ` mcp: ${JSON.stringify(event.mcp)}` : undefined,
32
+ event.arguments ? ` arguments: ${JSON.stringify(sanitizePersistenceValue(event.arguments, sensitiveValues))}` : undefined,
33
+ event.result ? ` result: ${JSON.stringify(sanitizePersistenceValue(event.result, sensitiveValues))}` : undefined,
34
+ ].filter((line) => line !== undefined);
35
+ return [
36
+ `- ${turn}: ${event.action} via ${event.providerToolName} (${event.provider})`,
37
+ ...details,
38
+ ].join('\n');
27
39
  })
28
40
  .join('\n');
29
41
  }
@@ -22,6 +22,7 @@ export function createManagedSession(deps) {
22
22
  ?? createAskBus({ askUserTimeoutMs: deps.askUserTimeoutMs ?? 30_000 });
23
23
  const sessionOptions = {
24
24
  ...(ws.mcpConfigPath ? { mcpConfigPath: ws.mcpConfigPath } : {}),
25
+ ...(ws.mcpConfigOrigin ? { mcpConfigOrigin: ws.mcpConfigOrigin } : {}),
25
26
  ...(model ? { model } : {}),
26
27
  ...(conversationWindow !== undefined ? { conversationWindow } : {}),
27
28
  ...(runtimePolicies.length > 0 ? { runtimePolicies } : {}),
@@ -171,8 +171,8 @@ function assertClaudeNamesUnambiguous(names) {
171
171
  }
172
172
  }
173
173
  export function compileMcpMockApprovalSession(opts) {
174
- if (!Array.isArray(opts.rules) || opts.rules.length === 0) {
175
- throw new Error('mcpMockApprovalRules must be a non-empty array');
174
+ if (!Array.isArray(opts.rules)) {
175
+ throw new Error('mcpMockApprovalRules must be an array');
176
176
  }
177
177
  const descriptors = (Array.isArray(opts.mcpMock) ? opts.mcpMock : [opts.mcpMock])
178
178
  .map((descriptor, index) => cloneDescriptor(descriptor, index));
package/dist/types.d.ts CHANGED
@@ -352,8 +352,11 @@ export interface AgentSession {
352
352
  export declare function getWorkspacePath(handle: EnvironmentHandle): string;
353
353
  export declare function getRuntimeHandle(handle: EnvironmentHandle): string;
354
354
  export declare function getRuntimeEnv(handle: EnvironmentHandle): Record<string, string>;
355
+ export type McpConfigOrigin = 'generated_mock' | 'caller_config';
355
356
  export interface AgentSessionOptions {
356
357
  mcpConfigPath?: string;
358
+ /** Authority provenance for the staged MCP configuration. */
359
+ mcpConfigOrigin?: McpConfigOrigin;
357
360
  model?: string;
358
361
  conversationWindow?: import('./sdk/types.js').ConversationWindowConfig | false;
359
362
  runtimePolicies?: RuntimePolicyDescriptor[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/pathgrade",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "packageManager": "yarn@4.12.0",
5
5
  "description": "Evaluate whether AI agents discover and use your skills correctly",
6
6
  "exports": {
@@ -144,5 +144,5 @@
144
144
  "typescript": "^5.9.3",
145
145
  "zod": "4.3.6"
146
146
  },
147
- "falconPackageHash": "4b55cfec1102172799601d1b5a3a8089a73b9c5e093a4533316f1e7c"
147
+ "falconPackageHash": "20beff94d0506c770c82c813225aca2ab4c0b4785199afb176f72286"
148
148
  }