@leo000001/codex-mcp 2.0.0 → 2.0.2

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/CHANGELOG.md CHANGED
@@ -25,7 +25,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
25
25
  - Documentation aligned with implementation details for event eviction and e2e guidance
26
26
  - Tool input defaults are now defined in schema (`cursor`, `maxEvents`, `includeSensitive`, `advanced.approvalTimeoutMs`) and client-facing text avoids duplicated default descriptions
27
27
  - `codex_session` adds `clean_background_terminals` action to call `thread/background_terminals/clean`
28
- - Approval action payloads now expose `approvalId` and `networkApprovalContext` when provided by app-server
28
+ - Approval action payloads now expose `approvalId` when provided by app-server
29
+ - Documentation was de-duplicated by splitting responsibilities between `AGENTS.md` (execution handbook) and `docs/DESIGN.md` (single source upgrade playbook), and a one-shot schema refresh runbook/record was added (`2026-02-21`, no schema diff)
30
+ - `src/app-server/protocol.ts` is now aligned to current v2 schema coverage for thread/turn params (`dynamicTools`, `persistExtendedHistory`, `collaborationMode`, richer `SandboxPolicy`, strict `UserInput` union, `turn/steer` params)
31
+ - `codex_check` input validation is now action-aware at schema level (`poll` vs `respond_permission` vs `respond_user_input`), including conditional `execpolicy_amendment` rules and forbidden-field checks
32
+ - Auth refresh request handling now uses explicit unsupported semantics (`-32000`) instead of `-32601` for `account/chatgptAuthTokens/refresh`
33
+ - Compatibility policy is now explicitly strict: removed non-essential alias compatibility (`approval_id`, `network_approval_context`, `questionId`) and documented a single necessary-compatibility whitelist (v1/v2 thread/turn id extraction)
34
+ - Command approval context is now surfaced directly in `actions[]` / `approval_request` payloads (`commandActions`, `proposedExecpolicyAmendment`) for richer client-side approval UX
35
+ - `turn/started` and `turn/completed` notification handling now only uses canonical `turn.id` shape (plus runtime `activeTurnId` fallback), and corresponding v1 top-level `turnId` compatibility tests were removed
36
+ - `compat-report` now correctly advertises `respondApprovalAlias: false` to match strict no-alias behavior
37
+ - Upgrade-policy docs were further de-duplicated: `docs/DESIGN.md` remains the single detailed compatibility source, and `AGENTS.md` now stays as a concise execution gate
29
38
 
30
39
  ## [0.1.0] - 2026-02-15
31
40
 
package/README.md CHANGED
@@ -220,7 +220,7 @@ Query a running session for events, respond to approval requests, or answer user
220
220
  | `decision` | string | For respond_permission | For command approvals: `"accept"`, `"acceptForSession"`, `"acceptWithExecpolicyAmendment"`, `"decline"`, `"cancel"`; for file changes: `"accept"`, `"acceptForSession"`, `"decline"`, `"cancel"` |
221
221
  | `execpolicy_amendment` | string[] | For acceptWithExecpolicyAmendment | Exec policy amendment list (required when `decision="acceptWithExecpolicyAmendment"`) |
222
222
  | `denyMessage` | string | No | Internal note on deny (not sent to app-server) |
223
- | `answers` | object | For respond_user_input | For `respond_user_input`: `questionId -> { answers: string[] }` |
223
+ | `answers` | object | For respond_user_input | For `respond_user_input`: `question-id -> { answers: string[] }` |
224
224
 
225
225
  **Returns (poll and respond_*):** `{ sessionId, status, pollInterval?, cursorResetTo?, events, nextCursor, actions?, result? }`
226
226
 
@@ -267,10 +267,13 @@ When the agent requests approval or user input, `poll` includes an `actions[]` l
267
267
 
268
268
  - `respond_permission`: `decision` is one of `accept`, `acceptForSession`, `decline`, `cancel`.
269
269
  - For command approvals, `acceptWithExecpolicyAmendment` is supported and requires `execpolicy_amendment`.
270
- - `respond_user_input`: send `answers` keyed by `questionId`.
270
+ - `respond_user_input`: send `answers` keyed by the question `id`.
271
+ - For command approvals, `actions[]` may include `commandActions` and `proposedExecpolicyAmendment` for richer review UI.
271
272
 
272
273
  Pending approvals auto-decline after `advanced.approvalTimeoutMs`.
273
274
 
275
+ Auth callback note: if app-server sends `account/chatgptAuthTokens/refresh`, codex-mcp returns JSON-RPC error `-32000` because external ChatGPT token refresh is out of scope for this server.
276
+
274
277
  ## Session Lifecycle & Cleanup
275
278
 
276
279
  Sessions auto-clean up in the background:
package/dist/index.js CHANGED
@@ -233,7 +233,7 @@ var DEFAULT_TERMINAL_CLEANUP_MS = 5 * 60 * 1e3;
233
233
  var CLEANUP_INTERVAL_MS = 6e4;
234
234
 
235
235
  // src/app-server/client.ts
236
- var CLIENT_VERSION = true ? "2.0.0" : "0.0.0-dev";
236
+ var CLIENT_VERSION = true ? "2.0.2" : "0.0.0-dev";
237
237
  var DEFAULT_REQUEST_TIMEOUT = 3e4;
238
238
  var STARTUP_REQUEST_TIMEOUT = 9e4;
239
239
  var MAX_WRITE_QUEUE_BYTES = 5 * 1024 * 1024;
@@ -682,6 +682,9 @@ var COALESCED_PROGRESS_DELTA_METHODS = /* @__PURE__ */ new Set([
682
682
  Methods.REASONING_SUMMARY_DELTA
683
683
  ]);
684
684
  var MAX_COALESCED_DELTA_CHARS = 16384;
685
+ var AUTH_REFRESH_UNSUPPORTED_CODE = -32e3;
686
+ var AUTH_REFRESH_UNSUPPORTED_MESSAGE = "account/chatgptAuthTokens/refresh unsupported: codex-mcp does not manage external ChatGPT auth tokens";
687
+ var AUTH_REFRESH_TERMINAL_MESSAGE = "account/chatgptAuthTokens/refresh unsupported: session is terminal";
685
688
  var NOISE_FILTER_ENABLED = process.env.CODEX_MCP_DISABLE_NOISE_FILTER !== "1";
686
689
  var WINDOWS_TERMINAL_INTEGRATION_PREFIX = `${String.fromCharCode(27)}]633;`;
687
690
  var SHELL_NOISE_LINE_PATTERNS = [
@@ -1114,7 +1117,8 @@ var SessionManager = class {
1114
1117
  itemId: req.itemId,
1115
1118
  reason: req.reason,
1116
1119
  approvalId: req.approvalId,
1117
- networkApprovalContext: req.networkApprovalContext,
1120
+ commandActions: req.commandActions,
1121
+ proposedExecpolicyAmendment: req.proposedExecpolicyAmendment,
1118
1122
  createdAt: req.createdAt
1119
1123
  });
1120
1124
  }
@@ -1156,6 +1160,9 @@ var SessionManager = class {
1156
1160
  while (result.actions.length > 1 && payloadByteSize(result) > normalizedMaxBytes) {
1157
1161
  result.actions.pop();
1158
1162
  }
1163
+ if (payloadByteSize(result) > normalizedMaxBytes) {
1164
+ result.actions = compactActionsToMinimum(result.actions);
1165
+ }
1159
1166
  truncatedFields.push("actions");
1160
1167
  }
1161
1168
  if (typeof result.actions !== "undefined" && payloadByteSize(result) > normalizedMaxBytes) {
@@ -1256,7 +1263,6 @@ var SessionManager = class {
1256
1263
  requestId,
1257
1264
  kind: req.kind,
1258
1265
  approvalId: req.approvalId,
1259
- networkApprovalContext: req.networkApprovalContext,
1260
1266
  decision,
1261
1267
  denyMessage: extra?.denyMessage
1262
1268
  },
@@ -1291,7 +1297,6 @@ var SessionManager = class {
1291
1297
  requestId,
1292
1298
  kind: "user_input",
1293
1299
  approvalId: req.approvalId,
1294
- networkApprovalContext: req.networkApprovalContext,
1295
1300
  answers
1296
1301
  },
1297
1302
  true
@@ -1369,7 +1374,7 @@ var SessionManager = class {
1369
1374
  {
1370
1375
  const turnObj = p.turn;
1371
1376
  const status = normalizeOptionalString(turnObj?.status);
1372
- session.activeTurnId = normalizeOptionalString(turnObj?.id) ?? (typeof p.turnId === "string" ? p.turnId : void 0);
1377
+ session.activeTurnId = normalizeOptionalString(turnObj?.id);
1373
1378
  pushEvent(session.eventBuffer, "progress", {
1374
1379
  method,
1375
1380
  ...p,
@@ -1381,7 +1386,7 @@ var SessionManager = class {
1381
1386
  case Methods.TURN_COMPLETED: {
1382
1387
  if (session.status === "cancelled") break;
1383
1388
  const turnObj = p.turn;
1384
- const completedTurnId = (typeof p.turnId === "string" ? p.turnId : void 0) ?? turnObj?.id ?? session.activeTurnId ?? "";
1389
+ const completedTurnId = turnObj?.id ?? session.activeTurnId ?? "";
1385
1390
  session.status = "idle";
1386
1391
  session.activeTurnId = void 0;
1387
1392
  session.lastResult = {
@@ -1491,10 +1496,11 @@ var SessionManager = class {
1491
1496
  const requestId = `req_${randomUUID().slice(0, 8)}`;
1492
1497
  const approvalParams = params;
1493
1498
  const reason = normalizeOptionalString(approvalParams.reason);
1494
- const approvalId = normalizeOptionalString(
1495
- approvalParams.approvalId ?? approvalParams.approval_id
1499
+ const approvalId = normalizeOptionalString(approvalParams.approvalId);
1500
+ const commandActions = Array.isArray(approvalParams.commandActions) ? approvalParams.commandActions : null;
1501
+ const proposedExecpolicyAmendment = normalizeStringArrayOrNull(
1502
+ approvalParams.proposedExecpolicyAmendment
1496
1503
  );
1497
- const networkApprovalContext = approvalParams.networkApprovalContext ?? approvalParams.network_approval_context;
1498
1504
  const pending = {
1499
1505
  requestId,
1500
1506
  kind: "command",
@@ -1504,7 +1510,8 @@ var SessionManager = class {
1504
1510
  turnId: normalizeOptionalString(approvalParams.turnId) ?? "",
1505
1511
  reason,
1506
1512
  approvalId,
1507
- networkApprovalContext,
1513
+ commandActions,
1514
+ proposedExecpolicyAmendment,
1508
1515
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1509
1516
  resolved: false,
1510
1517
  respond: (result) => client.respondToServer(id, result)
@@ -1551,7 +1558,8 @@ var SessionManager = class {
1551
1558
  command: approvalParams.command,
1552
1559
  cwd: approvalParams.cwd,
1553
1560
  reason,
1554
- networkApprovalContext
1561
+ commandActions,
1562
+ proposedExecpolicyAmendment
1555
1563
  },
1556
1564
  true
1557
1565
  );
@@ -1675,7 +1683,11 @@ var SessionManager = class {
1675
1683
  });
1676
1684
  break;
1677
1685
  case Methods.AUTH_TOKEN_REFRESH:
1678
- client.respondErrorToServer(id, -32601, "Auth token refresh not supported by codex-mcp");
1686
+ client.respondErrorToServer(
1687
+ id,
1688
+ AUTH_REFRESH_UNSUPPORTED_CODE,
1689
+ AUTH_REFRESH_UNSUPPORTED_MESSAGE
1690
+ );
1679
1691
  break;
1680
1692
  case Methods.LEGACY_PATCH_APPROVAL:
1681
1693
  case Methods.LEGACY_EXEC_APPROVAL:
@@ -1824,7 +1836,7 @@ function respondToTerminalSessionRequest(client, id, method) {
1824
1836
  });
1825
1837
  break;
1826
1838
  case Methods.AUTH_TOKEN_REFRESH:
1827
- client.respondErrorToServer(id, -32601, "Session is terminal");
1839
+ client.respondErrorToServer(id, AUTH_REFRESH_UNSUPPORTED_CODE, AUTH_REFRESH_TERMINAL_MESSAGE);
1828
1840
  break;
1829
1841
  case Methods.LEGACY_PATCH_APPROVAL:
1830
1842
  case Methods.LEGACY_EXEC_APPROVAL:
@@ -1838,6 +1850,11 @@ function respondToTerminalSessionRequest(client, id, method) {
1838
1850
  function normalizeOptionalString(value) {
1839
1851
  return typeof value === "string" ? value : void 0;
1840
1852
  }
1853
+ function normalizeStringArrayOrNull(value) {
1854
+ if (!Array.isArray(value)) return null;
1855
+ const normalized = value.filter((entry) => typeof entry === "string");
1856
+ return normalized;
1857
+ }
1841
1858
  function sendPendingRequestResponseOrThrow(req, response, sessionId, requestId) {
1842
1859
  if (!req.respond) {
1843
1860
  throw new Error(
@@ -1859,7 +1876,9 @@ function compactActionsForBudget(actions) {
1859
1876
  kind: action.kind,
1860
1877
  params: compactActionParamsForBudget(action),
1861
1878
  itemId: action.itemId,
1862
- createdAt: action.createdAt
1879
+ createdAt: action.createdAt,
1880
+ commandActions: action.commandActions,
1881
+ proposedExecpolicyAmendment: action.proposedExecpolicyAmendment
1863
1882
  }));
1864
1883
  }
1865
1884
  function compactActionParamsForBudget(action) {
@@ -1872,12 +1891,30 @@ function compactActionParamsForBudget(action) {
1872
1891
  }
1873
1892
  const compactQuestions = [];
1874
1893
  for (const entry of rawQuestions) {
1875
- if (isRecord(entry) && typeof entry.questionId === "string") {
1876
- compactQuestions.push({ questionId: entry.questionId });
1894
+ if (!isRecord(entry)) continue;
1895
+ const id = typeof entry.id === "string" ? entry.id : void 0;
1896
+ if (id) {
1897
+ compactQuestions.push({ id });
1877
1898
  }
1878
1899
  }
1879
1900
  return compactQuestions.length > 0 ? { questions: compactQuestions } : void 0;
1880
1901
  }
1902
+ function compactActionsToMinimum(actions) {
1903
+ if (actions.length === 0) return actions;
1904
+ const first = actions[0];
1905
+ return [
1906
+ {
1907
+ type: first.type,
1908
+ requestId: first.requestId,
1909
+ kind: first.kind,
1910
+ params: void 0,
1911
+ itemId: first.itemId,
1912
+ createdAt: first.createdAt,
1913
+ commandActions: first.commandActions,
1914
+ proposedExecpolicyAmendment: first.proposedExecpolicyAmendment
1915
+ }
1916
+ ];
1917
+ }
1881
1918
  function clampCursorToLatest(cursor, latestCursor) {
1882
1919
  return Math.max(0, Math.min(cursor, latestCursor));
1883
1920
  }
@@ -2235,7 +2272,13 @@ function executeCodexCheck(args, sessionManager) {
2235
2272
  const pollOptions = args.pollOptions;
2236
2273
  switch (args.action) {
2237
2274
  case "poll": {
2238
- const maxEvents = typeof args.maxEvents === "number" ? Math.max(POLL_MIN_MAX_EVENTS, args.maxEvents) : POLL_DEFAULT_MAX_EVENTS;
2275
+ if (args.requestId !== void 0 || args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.denyMessage !== void 0 || args.answers !== void 0) {
2276
+ return {
2277
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: requestId/decision/execpolicy_amendment/denyMessage/answers are only valid for respond_* actions`,
2278
+ isError: true
2279
+ };
2280
+ }
2281
+ const maxEvents = typeof args.maxEvents === "number" ? Math.max(POLL_MIN_MAX_EVENTS, Math.floor(args.maxEvents)) : POLL_DEFAULT_MAX_EVENTS;
2239
2282
  return sessionManager.pollEvents(args.sessionId, args.cursor, maxEvents, {
2240
2283
  responseMode,
2241
2284
  pollOptions
@@ -2248,6 +2291,31 @@ function executeCodexCheck(args, sessionManager) {
2248
2291
  isError: true
2249
2292
  };
2250
2293
  }
2294
+ if (args.answers !== void 0) {
2295
+ return {
2296
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: answers is only valid for respond_user_input`,
2297
+ isError: true
2298
+ };
2299
+ }
2300
+ if (args.decision === "acceptWithExecpolicyAmendment") {
2301
+ if (!args.execpolicy_amendment || args.execpolicy_amendment.length === 0) {
2302
+ return {
2303
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: execpolicy_amendment required for acceptWithExecpolicyAmendment`,
2304
+ isError: true
2305
+ };
2306
+ }
2307
+ } else if (args.execpolicy_amendment !== void 0) {
2308
+ return {
2309
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: execpolicy_amendment is only valid with decision='acceptWithExecpolicyAmendment'`,
2310
+ isError: true
2311
+ };
2312
+ }
2313
+ if (!ALL_DECISIONS.includes(args.decision)) {
2314
+ return {
2315
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown decision '${args.decision}'`,
2316
+ isError: true
2317
+ };
2318
+ }
2251
2319
  try {
2252
2320
  sessionManager.resolveApproval(args.sessionId, args.requestId, args.decision, {
2253
2321
  execpolicy_amendment: args.execpolicy_amendment,
@@ -2257,7 +2325,7 @@ function executeCodexCheck(args, sessionManager) {
2257
2325
  const message = err instanceof Error ? err.message : String(err);
2258
2326
  return { error: message, isError: true };
2259
2327
  }
2260
- const maxEvents = args.maxEvents ?? RESPOND_DEFAULT_MAX_EVENTS;
2328
+ const maxEvents = typeof args.maxEvents === "number" ? Math.max(0, Math.floor(args.maxEvents)) : RESPOND_DEFAULT_MAX_EVENTS;
2261
2329
  return sessionManager.pollEventsMonotonic(args.sessionId, args.cursor, maxEvents, {
2262
2330
  responseMode,
2263
2331
  pollOptions
@@ -2270,13 +2338,19 @@ function executeCodexCheck(args, sessionManager) {
2270
2338
  isError: true
2271
2339
  };
2272
2340
  }
2341
+ if (args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.denyMessage !== void 0) {
2342
+ return {
2343
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision/execpolicy_amendment/denyMessage are only valid for respond_permission`,
2344
+ isError: true
2345
+ };
2346
+ }
2273
2347
  try {
2274
2348
  sessionManager.resolveUserInput(args.sessionId, args.requestId, args.answers);
2275
2349
  } catch (err) {
2276
2350
  const message = err instanceof Error ? err.message : String(err);
2277
2351
  return { error: message, isError: true };
2278
2352
  }
2279
- const maxEvents = args.maxEvents ?? RESPOND_DEFAULT_MAX_EVENTS;
2353
+ const maxEvents = typeof args.maxEvents === "number" ? Math.max(0, Math.floor(args.maxEvents)) : RESPOND_DEFAULT_MAX_EVENTS;
2280
2354
  return sessionManager.pollEventsMonotonic(args.sessionId, args.cursor, maxEvents, {
2281
2355
  responseMode,
2282
2356
  pollOptions
@@ -2563,6 +2637,7 @@ function buildGotchasText() {
2563
2637
  `- Pending approvals/user-input auto-decline after \`approvalTimeoutMs\` (default ${DEFAULT_APPROVAL_TIMEOUT_MS} ms).`,
2564
2638
  "- `untrusted` behavior is enforced by Codex CLI backend and may auto-allow some low-risk commands.",
2565
2639
  "- Do not assume every read-only command will always require approval across CLI versions.",
2640
+ `- **Timeout vs polling conflict**: The recommended polling interval for \`running\` status is >=120 seconds, but the default approval timeout is ${DEFAULT_APPROVAL_TIMEOUT_MS / 1e3} seconds. If a session transitions to \`waiting_approval\` between polls, the approval will auto-decline before the client can respond. Set \`advanced.approvalTimeoutMs\` to at least 300000 (5 minutes) when using \`untrusted\` or \`on-request\` policies.`,
2566
2641
  "",
2567
2642
  "## Event model",
2568
2643
  "",
@@ -2632,6 +2707,7 @@ function buildQuickstartText() {
2632
2707
  "",
2633
2708
  "- Use `pollInterval` as a minimum delay: `running` >=120000ms (and usually longer for big tasks).",
2634
2709
  "- `waiting_approval` is the exception: poll/answer around 1000ms to avoid timeout.",
2710
+ `- When using \`untrusted\` or \`on-request\` policies, set \`advanced.approvalTimeoutMs\` to at least 300000 to prevent approvals from expiring between polling intervals.`,
2635
2711
  "",
2636
2712
  "3. If `actions[]` contains an approval request, respond:",
2637
2713
  "",
@@ -2703,7 +2779,7 @@ function buildCompatReport(deps, codexCliVersion) {
2703
2779
  schemaVersion: "1.0.0",
2704
2780
  features: {
2705
2781
  respondPermission: true,
2706
- respondApprovalAlias: true,
2782
+ respondApprovalAlias: false,
2707
2783
  respondUserInput: true,
2708
2784
  sessionInterrupt: true,
2709
2785
  responseModeMinimal: true,
@@ -2859,7 +2935,7 @@ function registerResources(server, deps) {
2859
2935
  }
2860
2936
 
2861
2937
  // src/server.ts
2862
- var SERVER_VERSION = true ? "2.0.0" : "0.0.0-dev";
2938
+ var SERVER_VERSION = true ? "2.0.2" : "0.0.0-dev";
2863
2939
  function formatErrorMessage(err) {
2864
2940
  const message = err instanceof Error ? err.message : String(err);
2865
2941
  const m = /^Error \[([A-Z_]+)\]:\s*(.*)$/.exec(message);
@@ -2910,6 +2986,119 @@ function createServer(serverCwd) {
2910
2986
  ),
2911
2987
  ...errorOutputShape
2912
2988
  };
2989
+ const codexCheckPollOptionsSchema = z.object({
2990
+ includeEvents: z.boolean().optional().describe("Default: true. Include events[] in response."),
2991
+ includeActions: z.boolean().optional().describe("Default: true. Include actions[] in response."),
2992
+ includeResult: z.boolean().optional().describe("Default: true. Include result in response."),
2993
+ maxBytes: z.number().int().positive().optional().describe("Default: unlimited. Best-effort response payload cap in bytes.")
2994
+ }).optional().describe("Optional poll shaping controls.");
2995
+ const codexCheckInputSchema = z.object({
2996
+ action: z.enum(CHECK_ACTIONS),
2997
+ sessionId: z.string().describe("Target session ID"),
2998
+ cursor: z.number().int().nonnegative().optional().describe("Event cursor (default: session last consumed cursor)."),
2999
+ maxEvents: z.number().int().nonnegative().optional().describe(
3000
+ `Max events. Default: poll=${POLL_DEFAULT_MAX_EVENTS} (min ${POLL_MIN_MAX_EVENTS}), respond_*=${RESPOND_DEFAULT_MAX_EVENTS}.`
3001
+ ),
3002
+ responseMode: z.enum(RESPONSE_MODES).optional().describe("Response mode. Default: minimal. Options: minimal/delta_compact/full."),
3003
+ pollOptions: codexCheckPollOptionsSchema,
3004
+ // respond_permission
3005
+ requestId: z.string().optional().describe("Request ID from actions[]"),
3006
+ decision: z.enum(ALL_DECISIONS).optional().describe(
3007
+ "Approval decision for respond_permission. acceptWithExecpolicyAmendment requires execpolicy_amendment."
3008
+ ),
3009
+ execpolicy_amendment: z.array(z.string()).optional().describe("For acceptWithExecpolicyAmendment only"),
3010
+ denyMessage: z.string().optional().describe("Deny reason (not sent to agent)"),
3011
+ // respond_user_input
3012
+ answers: z.record(
3013
+ z.string(),
3014
+ z.object({
3015
+ answers: z.array(z.string())
3016
+ })
3017
+ ).optional().describe("question-id -> answers map (id from actions[] user_input request).")
3018
+ }).superRefine((value, ctx) => {
3019
+ const addIssue = (path4, message) => {
3020
+ ctx.addIssue({
3021
+ code: z.ZodIssueCode.custom,
3022
+ path: [path4],
3023
+ message
3024
+ });
3025
+ };
3026
+ switch (value.action) {
3027
+ case "poll": {
3028
+ if (value.maxEvents !== void 0 && value.maxEvents < POLL_MIN_MAX_EVENTS) {
3029
+ addIssue(
3030
+ "maxEvents",
3031
+ `poll requires maxEvents >= ${POLL_MIN_MAX_EVENTS} to avoid no-op loops.`
3032
+ );
3033
+ }
3034
+ if (value.requestId !== void 0) {
3035
+ addIssue("requestId", "requestId is only allowed for respond_* actions.");
3036
+ }
3037
+ if (value.decision !== void 0) {
3038
+ addIssue("decision", "decision is only allowed for action='respond_permission'.");
3039
+ }
3040
+ if (value.execpolicy_amendment !== void 0) {
3041
+ addIssue(
3042
+ "execpolicy_amendment",
3043
+ "execpolicy_amendment is only allowed for action='respond_permission'."
3044
+ );
3045
+ }
3046
+ if (value.denyMessage !== void 0) {
3047
+ addIssue("denyMessage", "denyMessage is only allowed for action='respond_permission'.");
3048
+ }
3049
+ if (value.answers !== void 0) {
3050
+ addIssue("answers", "answers is only allowed for action='respond_user_input'.");
3051
+ }
3052
+ break;
3053
+ }
3054
+ case "respond_permission": {
3055
+ if (!value.requestId) {
3056
+ addIssue("requestId", "requestId is required for action='respond_permission'.");
3057
+ }
3058
+ if (!value.decision) {
3059
+ addIssue("decision", "decision is required for action='respond_permission'.");
3060
+ }
3061
+ if (value.answers !== void 0) {
3062
+ addIssue("answers", "answers is only allowed for action='respond_user_input'.");
3063
+ }
3064
+ const needsExecpolicy = value.decision === "acceptWithExecpolicyAmendment";
3065
+ if (needsExecpolicy && (!value.execpolicy_amendment || value.execpolicy_amendment.length === 0)) {
3066
+ addIssue(
3067
+ "execpolicy_amendment",
3068
+ "execpolicy_amendment is required and must be non-empty when decision='acceptWithExecpolicyAmendment'."
3069
+ );
3070
+ }
3071
+ if (!needsExecpolicy && value.execpolicy_amendment !== void 0) {
3072
+ addIssue(
3073
+ "execpolicy_amendment",
3074
+ "execpolicy_amendment is only allowed when decision='acceptWithExecpolicyAmendment'."
3075
+ );
3076
+ }
3077
+ break;
3078
+ }
3079
+ case "respond_user_input": {
3080
+ if (!value.requestId) {
3081
+ addIssue("requestId", "requestId is required for action='respond_user_input'.");
3082
+ }
3083
+ if (!value.answers) {
3084
+ addIssue("answers", "answers is required for action='respond_user_input'.");
3085
+ }
3086
+ if (value.decision !== void 0) {
3087
+ addIssue("decision", "decision is only allowed for action='respond_permission'.");
3088
+ }
3089
+ if (value.execpolicy_amendment !== void 0) {
3090
+ addIssue(
3091
+ "execpolicy_amendment",
3092
+ "execpolicy_amendment is only allowed for action='respond_permission'."
3093
+ );
3094
+ }
3095
+ if (value.denyMessage !== void 0) {
3096
+ addIssue("denyMessage", "denyMessage is only allowed for action='respond_permission'.");
3097
+ }
3098
+ break;
3099
+ }
3100
+ }
3101
+ });
2913
3102
  server.registerTool(
2914
3103
  "codex",
2915
3104
  {
@@ -3093,35 +3282,7 @@ respond_user_input: user-input answers. Default maxEvents=${RESPOND_DEFAULT_MAX_
3093
3282
 
3094
3283
  events[].type is coarse-grained; details are in events[].data.method.
3095
3284
  cursor omitted => use session last cursor. cursorResetTo => reset and continue.`,
3096
- inputSchema: {
3097
- action: z.enum(CHECK_ACTIONS),
3098
- sessionId: z.string().describe("Target session ID"),
3099
- cursor: z.number().int().nonnegative().optional().describe("Event cursor (default: session last consumed cursor)."),
3100
- maxEvents: z.number().int().nonnegative().optional().describe(
3101
- `Max events. Default: poll=${POLL_DEFAULT_MAX_EVENTS} (min ${POLL_MIN_MAX_EVENTS}), respond_*=${RESPOND_DEFAULT_MAX_EVENTS}.`
3102
- ),
3103
- responseMode: z.enum(RESPONSE_MODES).optional().describe("Response mode. Default: minimal. Options: minimal/delta_compact/full."),
3104
- pollOptions: z.object({
3105
- includeEvents: z.boolean().optional().describe("Default: true. Include events[] in response."),
3106
- includeActions: z.boolean().optional().describe("Default: true. Include actions[] in response."),
3107
- includeResult: z.boolean().optional().describe("Default: true. Include result in response."),
3108
- maxBytes: z.number().int().positive().optional().describe("Default: unlimited. Best-effort response payload cap in bytes.")
3109
- }).optional().describe("Optional poll shaping controls."),
3110
- // respond_permission
3111
- requestId: z.string().optional().describe("Request ID from actions[]"),
3112
- decision: z.enum(ALL_DECISIONS).optional().describe(
3113
- "Approval decision for respond_permission. acceptWithExecpolicyAmendment requires execpolicy_amendment."
3114
- ),
3115
- execpolicy_amendment: z.array(z.string()).optional().describe("For acceptWithExecpolicyAmendment only"),
3116
- denyMessage: z.string().optional().describe("Deny reason (not sent to agent)"),
3117
- // respond_user_input
3118
- answers: z.record(
3119
- z.string(),
3120
- z.object({
3121
- answers: z.array(z.string())
3122
- })
3123
- ).optional().describe("questionId -> answers map (questionId from actions[] user_input request).")
3124
- },
3285
+ inputSchema: codexCheckInputSchema,
3125
3286
  outputSchema: {
3126
3287
  sessionId: z.string().optional(),
3127
3288
  status: z.enum(["running", "idle", "waiting_approval", "error", "cancelled"]).optional(),
@@ -3149,10 +3310,13 @@ cursor omitted => use session last cursor. cursorResetTo => reset and continue.`
3149
3310
  z.object({
3150
3311
  type: z.enum(["approval", "user_input"]),
3151
3312
  requestId: z.string(),
3152
- kind: z.string(),
3313
+ kind: z.enum(["command", "fileChange", "user_input"]),
3153
3314
  params: z.unknown(),
3154
3315
  itemId: z.string(),
3155
3316
  reason: z.string().optional(),
3317
+ approvalId: z.string().optional(),
3318
+ commandActions: z.array(z.unknown()).nullable().optional(),
3319
+ proposedExecpolicyAmendment: z.array(z.string()).nullable().optional(),
3156
3320
  createdAt: z.string()
3157
3321
  })
3158
3322
  ).optional(),