@junctionpanel/server 0.1.29 → 0.1.31

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.
@@ -218,6 +218,9 @@ function parseUpdatedQuestionAnswers(updatedInput) {
218
218
  }
219
219
  return parsed;
220
220
  }
221
+ function toPendingPermissionId(requestId) {
222
+ return `permission-${String(requestId)}`;
223
+ }
221
224
  async function listCodexCustomPrompts() {
222
225
  const codexHome = resolveCodexHomeDir();
223
226
  const promptsDir = path.join(codexHome, "prompts");
@@ -524,7 +527,7 @@ class CodexAppServerClient {
524
527
  const request = msg;
525
528
  const handler = this.requestHandlers.get(request.method);
526
529
  try {
527
- const result = handler ? await handler(request.params) : {};
530
+ const result = handler ? await handler(request.params, request.id) : {};
528
531
  const response = { id: request.id, result };
529
532
  this.child.stdin.write(`${JSON.stringify(response)}\n`);
530
533
  }
@@ -581,12 +584,73 @@ function parsePlanTextToTodoItems(text) {
581
584
  completed: false,
582
585
  }));
583
586
  }
587
+ function formatProposedPlanBlock(text) {
588
+ return `<proposed_plan>\n${text}\n</proposed_plan>`;
589
+ }
590
+ function formatProposedPlanChunk(text, options) {
591
+ const parts = [];
592
+ if (options?.open) {
593
+ parts.push("<proposed_plan>\n");
594
+ }
595
+ parts.push(text);
596
+ if (options?.close) {
597
+ parts.push("\n</proposed_plan>");
598
+ }
599
+ return parts.join("");
600
+ }
584
601
  function planStepsToTodoItems(steps) {
585
602
  return steps.map((entry) => ({
586
603
  text: entry.step,
587
604
  completed: entry.status === "completed",
588
605
  }));
589
606
  }
607
+ function supportsPlanCollaborationMode(response) {
608
+ const candidateArrays = [
609
+ Array.isArray(response) ? response : null,
610
+ Array.isArray(response?.data)
611
+ ? (response.data)
612
+ : null,
613
+ Array.isArray(response?.modes)
614
+ ? (response.modes)
615
+ : null,
616
+ Array.isArray(response?.collaborationModes)
617
+ ? (response.collaborationModes)
618
+ : null,
619
+ Array.isArray(response?.items)
620
+ ? (response.items)
621
+ : null,
622
+ ];
623
+ for (const entries of candidateArrays) {
624
+ if (!entries)
625
+ continue;
626
+ for (const entry of entries) {
627
+ const record = toRecord(entry);
628
+ const modeName = (typeof record?.mode === "string" ? record.mode : null) ??
629
+ (typeof record?.name === "string" ? record.name : null) ??
630
+ (typeof record?.id === "string" ? record.id : null) ??
631
+ (typeof entry === "string" ? entry : null);
632
+ if (modeName?.trim().toLowerCase() === "plan") {
633
+ return true;
634
+ }
635
+ }
636
+ }
637
+ return false;
638
+ }
639
+ function shouldRetryInitializeWithoutExperimentalApi(error) {
640
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
641
+ return (message.includes("experimentalapi") ||
642
+ message.includes("experimental api") ||
643
+ message.includes("capabilities") ||
644
+ message.includes("unknown field") ||
645
+ message.includes("invalid params"));
646
+ }
647
+ function shouldRetryTurnStartWithoutCollaborationMode(error) {
648
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
649
+ return (message.includes("collaborationmode") ||
650
+ message.includes("collaboration mode") ||
651
+ message.includes("experimentalapi") ||
652
+ message.includes("experimental api"));
653
+ }
590
654
  function extractPatchLikeText(value) {
591
655
  if (!value || typeof value !== "object") {
592
656
  return undefined;
@@ -896,8 +960,11 @@ function threadItemToTimeline(item, options) {
896
960
  }
897
961
  case "plan": {
898
962
  const text = normalizedItem.text ?? "";
963
+ if (typeof text === "string" && text.trim().length > 0) {
964
+ return { type: "assistant_message", text: formatProposedPlanBlock(text) };
965
+ }
899
966
  const items = parsePlanTextToTodoItems(text);
900
- return { type: "todo", items };
967
+ return items.length > 0 ? { type: "todo", items } : null;
901
968
  }
902
969
  case "reasoning": {
903
970
  const summary = Array.isArray(normalizedItem.summary)
@@ -958,6 +1025,13 @@ const TurnPlanUpdatedNotificationSchema = z.object({
958
1025
  })
959
1026
  .passthrough()),
960
1027
  }).passthrough();
1028
+ const ItemPlanDeltaNotificationSchema = z.object({
1029
+ itemId: z.string(),
1030
+ delta: z.string(),
1031
+ }).passthrough();
1032
+ const ServerRequestResolvedNotificationSchema = z.object({
1033
+ requestId: z.union([z.string(), z.number()]),
1034
+ }).passthrough();
961
1035
  const TurnDiffUpdatedNotificationSchema = z.object({
962
1036
  diff: z.string(),
963
1037
  }).passthrough();
@@ -1097,6 +1171,12 @@ const CodexNotificationSchema = z.union([
1097
1171
  })),
1098
1172
  })),
1099
1173
  z.object({ method: z.literal("turn/plan/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
1174
+ z.object({ method: z.literal("item/plan/delta"), params: ItemPlanDeltaNotificationSchema }).transform(({ params }) => ({
1175
+ kind: "plan_delta",
1176
+ itemId: params.itemId,
1177
+ delta: params.delta,
1178
+ })),
1179
+ z.object({ method: z.literal("item/plan/delta"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
1100
1180
  z.object({ method: z.literal("turn/diff/updated"), params: TurnDiffUpdatedNotificationSchema }).transform(({ params }) => ({ kind: "diff_updated", diff: params.diff })),
1101
1181
  z.object({ method: z.literal("turn/diff/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
1102
1182
  z.object({
@@ -1104,6 +1184,14 @@ const CodexNotificationSchema = z.union([
1104
1184
  params: ThreadTokenUsageUpdatedNotificationSchema,
1105
1185
  }).transform(({ params }) => ({ kind: "token_usage_updated", tokenUsage: params.tokenUsage })),
1106
1186
  z.object({ method: z.literal("thread/tokenUsage/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
1187
+ z.object({
1188
+ method: z.literal("serverRequest/resolved"),
1189
+ params: ServerRequestResolvedNotificationSchema,
1190
+ }).transform(({ params }) => ({
1191
+ kind: "server_request_resolved",
1192
+ requestId: String(params.requestId),
1193
+ })),
1194
+ z.object({ method: z.literal("serverRequest/resolved"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
1107
1195
  z.object({ method: z.literal("item/agentMessage/delta"), params: ItemTextDeltaNotificationSchema }).transform(({ params }) => ({
1108
1196
  kind: "agent_message_delta",
1109
1197
  itemId: params.itemId,
@@ -1303,6 +1391,10 @@ export async function codexAppServerTurnInputFromPrompt(prompt, logger) {
1303
1391
  }
1304
1392
  export const __codexAppServerInternals = {
1305
1393
  mapCodexPatchNotificationToToolCall,
1394
+ supportsPlanCollaborationMode,
1395
+ shouldRetryInitializeWithoutExperimentalApi,
1396
+ shouldRetryTurnStartWithoutCollaborationMode,
1397
+ formatProposedPlanBlock,
1306
1398
  };
1307
1399
  class CodexAppServerAgentSession {
1308
1400
  constructor(config, resumeHandle, logger, spawnAppServer) {
@@ -1332,8 +1424,8 @@ class CodexAppServerAgentSession {
1332
1424
  this.warnedInvalidNotificationPayloads = new Set();
1333
1425
  this.warnedIncompleteEditToolCallIds = new Set();
1334
1426
  this.connected = false;
1335
- this.collaborationModes = [];
1336
- this.resolvedCollaborationMode = null;
1427
+ this.nativePlanModeSupported = null;
1428
+ this.pendingPlanTexts = new Map();
1337
1429
  this.cachedSkills = [];
1338
1430
  this.logger = logger.child({ module: "agent", provider: CODEX_PROVIDER });
1339
1431
  if (config.modeId === undefined) {
@@ -1358,13 +1450,26 @@ class CodexAppServerAgentSession {
1358
1450
  this.client = new CodexAppServerClient(child, this.logger);
1359
1451
  this.client.setNotificationHandler((method, params) => this.handleNotification(method, params));
1360
1452
  this.registerRequestHandlers();
1361
- await this.client.request("initialize", {
1362
- clientInfo: {
1363
- name: "junction",
1364
- title: "Junction",
1365
- version: "0.0.0",
1366
- },
1367
- });
1453
+ const clientInfo = {
1454
+ name: "junction",
1455
+ title: "Junction",
1456
+ version: "0.0.0",
1457
+ };
1458
+ try {
1459
+ await this.client.request("initialize", {
1460
+ clientInfo,
1461
+ capabilities: {
1462
+ experimentalApi: true,
1463
+ },
1464
+ });
1465
+ }
1466
+ catch (error) {
1467
+ if (!shouldRetryInitializeWithoutExperimentalApi(error)) {
1468
+ throw error;
1469
+ }
1470
+ await this.client.request("initialize", { clientInfo });
1471
+ this.nativePlanModeSupported = false;
1472
+ }
1368
1473
  this.client.notify("initialized", {});
1369
1474
  await this.loadCollaborationModes();
1370
1475
  await this.loadSkills();
@@ -1377,22 +1482,17 @@ class CodexAppServerAgentSession {
1377
1482
  async loadCollaborationModes() {
1378
1483
  if (!this.client)
1379
1484
  return;
1485
+ if (this.nativePlanModeSupported === false) {
1486
+ return;
1487
+ }
1380
1488
  try {
1381
- const response = (await this.client.request("collaborationMode/list", {}));
1382
- const data = Array.isArray(response?.data) ? response.data : [];
1383
- this.collaborationModes = data.map((entry) => ({
1384
- name: String(entry.name ?? ""),
1385
- mode: entry.mode ?? null,
1386
- model: entry.model ?? null,
1387
- reasoning_effort: entry.reasoning_effort ?? null,
1388
- developer_instructions: entry.developer_instructions ?? null,
1389
- }));
1489
+ const response = await this.client.request("collaborationMode/list", {});
1490
+ this.nativePlanModeSupported = supportsPlanCollaborationMode(response);
1390
1491
  }
1391
1492
  catch (error) {
1392
1493
  this.logger.trace({ error }, "Failed to load collaboration modes");
1393
- this.collaborationModes = [];
1494
+ this.nativePlanModeSupported = false;
1394
1495
  }
1395
- this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
1396
1496
  }
1397
1497
  async loadSkills() {
1398
1498
  if (!this.client)
@@ -1422,59 +1522,12 @@ class CodexAppServerAgentSession {
1422
1522
  this.cachedSkills = [];
1423
1523
  }
1424
1524
  }
1425
- resolveCollaborationMode(modeId) {
1426
- if (this.collaborationModes.length === 0)
1427
- return null;
1428
- const normalized = modeId.toLowerCase();
1429
- const findByName = (predicate) => this.collaborationModes.find((entry) => predicate(entry.name.toLowerCase()));
1430
- let match;
1431
- if (normalized === "read-only") {
1432
- // Prefer explicit plan collaboration modes over generic read-only modes.
1433
- match =
1434
- findByName((name) => name.includes("plan")) ??
1435
- findByName((name) => name.includes("read"));
1436
- }
1437
- else if (normalized === "full-access") {
1438
- match = findByName((name) => name.includes("full") || name.includes("exec"));
1439
- }
1440
- else {
1441
- match = findByName((name) => name.includes("auto") || name.includes("code"));
1442
- }
1443
- if (!match) {
1444
- match = this.collaborationModes[0] ?? null;
1445
- }
1446
- if (!match)
1447
- return null;
1448
- const settings = {};
1449
- if (match.model)
1450
- settings.model = match.model;
1451
- if (match.reasoning_effort)
1452
- settings.reasoning_effort = match.reasoning_effort;
1453
- const modeSpecificInstruction = normalized === "read-only"
1454
- ? "Plan mode is enabled. Do not execute commands, edit files, or perform write operations. Provide analysis and a step-by-step plan only."
1455
- : "";
1456
- const developerInstructions = [
1457
- modeSpecificInstruction,
1458
- match.developer_instructions?.trim(),
1459
- this.config.systemPrompt?.trim(),
1460
- ]
1461
- .filter((entry) => typeof entry === "string" && entry.length > 0)
1462
- .join("\n\n");
1463
- if (developerInstructions)
1464
- settings.developer_instructions = developerInstructions;
1465
- if (this.config.model)
1466
- settings.model = this.config.model;
1467
- const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
1468
- if (thinkingOptionId)
1469
- settings.reasoning_effort = thinkingOptionId;
1470
- return { mode: match.mode ?? "code", settings, name: match.name };
1471
- }
1472
1525
  registerRequestHandlers() {
1473
1526
  if (!this.client)
1474
1527
  return;
1475
- this.client.setRequestHandler("item/commandExecution/requestApproval", (params) => this.handleCommandApprovalRequest(params));
1476
- this.client.setRequestHandler("item/fileChange/requestApproval", (params) => this.handleFileChangeApprovalRequest(params));
1477
- this.client.setRequestHandler("tool/requestUserInput", (params) => this.handleToolApprovalRequest(params));
1528
+ this.client.setRequestHandler("item/commandExecution/requestApproval", (params, requestId) => this.handleCommandApprovalRequest(params, requestId));
1529
+ this.client.setRequestHandler("item/fileChange/requestApproval", (params, requestId) => this.handleFileChangeApprovalRequest(params, requestId));
1530
+ this.client.setRequestHandler("tool/requestUserInput", (params, requestId) => this.handleToolApprovalRequest(params, requestId));
1478
1531
  }
1479
1532
  async loadPersistedHistory() {
1480
1533
  if (!this.client || !this.currentThreadId)
@@ -1672,6 +1725,8 @@ class CodexAppServerAgentSession {
1672
1725
  const preset = MODE_PRESETS[this.currentMode] ?? MODE_PRESETS[DEFAULT_CODEX_MODE_ID];
1673
1726
  const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
1674
1727
  const sandboxPolicyType = this.config.sandboxMode ?? preset.sandbox;
1728
+ const planModeRequested = options?.extra?.codex?.planMode === true;
1729
+ const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
1675
1730
  const params = {
1676
1731
  threadId: this.currentThreadId,
1677
1732
  input,
@@ -1683,16 +1738,9 @@ class CodexAppServerAgentSession {
1683
1738
  if (this.config.model) {
1684
1739
  params.model = this.config.model;
1685
1740
  }
1686
- const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
1687
1741
  if (thinkingOptionId) {
1688
1742
  params.effort = thinkingOptionId;
1689
1743
  }
1690
- if (this.resolvedCollaborationMode) {
1691
- params.collaborationMode = {
1692
- mode: this.resolvedCollaborationMode.mode,
1693
- settings: this.resolvedCollaborationMode.settings,
1694
- };
1695
- }
1696
1744
  if (this.config.cwd) {
1697
1745
  params.cwd = this.config.cwd;
1698
1746
  }
@@ -1706,7 +1754,38 @@ class CodexAppServerAgentSession {
1706
1754
  if (codexConfig) {
1707
1755
  params.config = codexConfig;
1708
1756
  }
1709
- await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
1757
+ let downgradedFromPlanMode = false;
1758
+ if (this.nativePlanModeSupported !== false) {
1759
+ const collaborationMode = this.buildCollaborationModePayload(planModeRequested ? "plan" : "default");
1760
+ if (collaborationMode) {
1761
+ params.collaborationMode = collaborationMode;
1762
+ }
1763
+ }
1764
+ try {
1765
+ await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
1766
+ }
1767
+ catch (error) {
1768
+ const canRetryWithoutPlanMode = Object.prototype.hasOwnProperty.call(params, "collaborationMode") &&
1769
+ shouldRetryTurnStartWithoutCollaborationMode(error);
1770
+ if (!canRetryWithoutPlanMode) {
1771
+ throw error;
1772
+ }
1773
+ delete params.collaborationMode;
1774
+ this.nativePlanModeSupported = false;
1775
+ this.cachedRuntimeInfo = null;
1776
+ downgradedFromPlanMode = planModeRequested;
1777
+ await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
1778
+ }
1779
+ if (downgradedFromPlanMode) {
1780
+ this.emitEvent({
1781
+ type: "timeline",
1782
+ provider: CODEX_PROVIDER,
1783
+ item: {
1784
+ type: "assistant_message",
1785
+ text: "Plan mode is not supported by this Codex runtime. Sent as a normal turn instead.",
1786
+ },
1787
+ });
1788
+ }
1710
1789
  let sawTurnStarted = false;
1711
1790
  for await (const event of queue) {
1712
1791
  // Drop pre-start timeline noise that can leak from the previous turn.
@@ -1763,9 +1842,9 @@ class CodexAppServerAgentSession {
1763
1842
  model: this.config.model ?? null,
1764
1843
  thinkingOptionId: normalizeCodexThinkingOptionId(this.config.thinkingOptionId) ?? null,
1765
1844
  modeId: this.currentMode ?? null,
1766
- extra: this.resolvedCollaborationMode
1767
- ? { collaborationMode: this.resolvedCollaborationMode.name }
1768
- : undefined,
1845
+ extra: this.nativePlanModeSupported === null
1846
+ ? undefined
1847
+ : { planModeSupported: this.nativePlanModeSupported },
1769
1848
  };
1770
1849
  this.cachedRuntimeInfo = info;
1771
1850
  return { ...info };
@@ -1779,17 +1858,14 @@ class CodexAppServerAgentSession {
1779
1858
  async setMode(modeId) {
1780
1859
  validateCodexMode(modeId);
1781
1860
  this.currentMode = modeId;
1782
- this.resolvedCollaborationMode = this.resolveCollaborationMode(modeId);
1783
1861
  this.cachedRuntimeInfo = null;
1784
1862
  }
1785
1863
  async setModel(modelId) {
1786
1864
  this.config.model = modelId ?? undefined;
1787
- this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
1788
1865
  this.cachedRuntimeInfo = null;
1789
1866
  }
1790
1867
  async setThinkingOption(thinkingOptionId) {
1791
1868
  this.config.thinkingOptionId = normalizeCodexThinkingOptionId(thinkingOptionId);
1792
- this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
1793
1869
  this.cachedRuntimeInfo = null;
1794
1870
  }
1795
1871
  getPendingPermissions() {
@@ -2019,6 +2095,24 @@ class CodexAppServerAgentSession {
2019
2095
  }
2020
2096
  return Object.keys(innerConfig).length > 0 ? innerConfig : null;
2021
2097
  }
2098
+ buildCollaborationModePayload(mode) {
2099
+ if (this.nativePlanModeSupported !== true) {
2100
+ return null;
2101
+ }
2102
+ const model = normalizeCodexModelId(this.config.model);
2103
+ if (!model) {
2104
+ return null;
2105
+ }
2106
+ const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId) ?? null;
2107
+ return {
2108
+ mode,
2109
+ settings: {
2110
+ model,
2111
+ reasoning_effort: thinkingOptionId,
2112
+ developer_instructions: null,
2113
+ },
2114
+ };
2115
+ }
2022
2116
  async buildUserInput(prompt) {
2023
2117
  if (typeof prompt === "string") {
2024
2118
  return [{ type: "text", text: prompt }];
@@ -2073,6 +2167,7 @@ class CodexAppServerAgentSession {
2073
2167
  this.emittedExecCommandCompletedCallIds.clear();
2074
2168
  this.pendingCommandOutputDeltas.clear();
2075
2169
  this.pendingFileChangeOutputDeltas.clear();
2170
+ this.pendingPlanTexts.clear();
2076
2171
  this.warnedIncompleteEditToolCallIds.clear();
2077
2172
  this.eventQueue?.end();
2078
2173
  return;
@@ -2089,6 +2184,22 @@ class CodexAppServerAgentSession {
2089
2184
  });
2090
2185
  return;
2091
2186
  }
2187
+ if (parsed.kind === "plan_delta") {
2188
+ const previous = this.pendingPlanTexts.get(parsed.itemId) ?? "";
2189
+ const next = previous + parsed.delta;
2190
+ this.pendingPlanTexts.set(parsed.itemId, next);
2191
+ this.emitEvent({
2192
+ type: "timeline",
2193
+ provider: CODEX_PROVIDER,
2194
+ item: {
2195
+ type: "assistant_message",
2196
+ text: formatProposedPlanChunk(parsed.delta, {
2197
+ open: previous.length === 0,
2198
+ }),
2199
+ },
2200
+ });
2201
+ return;
2202
+ }
2092
2203
  if (parsed.kind === "diff_updated") {
2093
2204
  // NOTE: Codex app-server emits frequent `turn/diff/updated` notifications
2094
2205
  // containing a full accumulated unified diff for the *entire turn*.
@@ -2096,6 +2207,20 @@ class CodexAppServerAgentSession {
2096
2207
  // We intentionally do NOT store every diff update in the timeline.
2097
2208
  return;
2098
2209
  }
2210
+ if (parsed.kind === "server_request_resolved") {
2211
+ const pendingRequestId = toPendingPermissionId(parsed.requestId);
2212
+ if (this.resolvedPermissionRequests.has(pendingRequestId)) {
2213
+ return;
2214
+ }
2215
+ this.pendingPermissions.delete(pendingRequestId);
2216
+ this.emitEvent({
2217
+ type: "permission_resolved",
2218
+ provider: CODEX_PROVIDER,
2219
+ requestId: pendingRequestId,
2220
+ resolution: { behavior: "allow" },
2221
+ });
2222
+ return;
2223
+ }
2099
2224
  if (parsed.kind === "token_usage_updated") {
2100
2225
  this.latestUsage = toAgentUsage(parsed.tokenUsage);
2101
2226
  return;
@@ -2225,6 +2350,24 @@ class CodexAppServerAgentSession {
2225
2350
  timelineItem.text = buffered;
2226
2351
  }
2227
2352
  }
2353
+ if (timelineItem.type === "assistant_message" &&
2354
+ normalizedItemType === "plan" &&
2355
+ itemId) {
2356
+ const bufferedPlanText = this.pendingPlanTexts.get(itemId) ?? "";
2357
+ const finalPlanText = typeof parsed.item.text === "string" ? parsed.item.text : "";
2358
+ if (bufferedPlanText.length > 0) {
2359
+ const trailingText = finalPlanText.startsWith(bufferedPlanText)
2360
+ ? finalPlanText.slice(bufferedPlanText.length)
2361
+ : "";
2362
+ timelineItem.text = formatProposedPlanChunk(trailingText, {
2363
+ close: true,
2364
+ });
2365
+ this.pendingPlanTexts.delete(itemId);
2366
+ }
2367
+ else if (finalPlanText.trim().length > 0) {
2368
+ timelineItem.text = formatProposedPlanBlock(finalPlanText);
2369
+ }
2370
+ }
2228
2371
  if (timelineItem.type === "reasoning" && itemId) {
2229
2372
  const buffered = this.pendingReasoning.get(itemId);
2230
2373
  if (buffered && buffered.length > 0) {
@@ -2240,6 +2383,7 @@ class CodexAppServerAgentSession {
2240
2383
  this.emittedItemStartedIds.delete(itemId);
2241
2384
  this.pendingCommandOutputDeltas.delete(itemId);
2242
2385
  this.pendingFileChangeOutputDeltas.delete(itemId);
2386
+ this.pendingPlanTexts.delete(itemId);
2243
2387
  }
2244
2388
  }
2245
2389
  return;
@@ -2336,7 +2480,7 @@ class CodexAppServerAgentSession {
2336
2480
  payload,
2337
2481
  }, "Codex edit tool call is missing diff/content fields");
2338
2482
  }
2339
- handleCommandApprovalRequest(params) {
2483
+ handleCommandApprovalRequest(params, rawRequestId) {
2340
2484
  const parsed = params;
2341
2485
  const commandPreview = mapCodexExecNotificationToToolCall({
2342
2486
  callId: parsed.itemId,
@@ -2344,7 +2488,7 @@ class CodexAppServerAgentSession {
2344
2488
  cwd: parsed.cwd ?? this.config.cwd ?? null,
2345
2489
  running: true,
2346
2490
  });
2347
- const requestId = `permission-${parsed.itemId}`;
2491
+ const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
2348
2492
  const title = parsed.command ? `Run command: ${parsed.command}` : "Run command";
2349
2493
  const request = {
2350
2494
  id: requestId,
@@ -2377,9 +2521,9 @@ class CodexAppServerAgentSession {
2377
2521
  this.pendingPermissionHandlers.set(requestId, { resolve, kind: "command" });
2378
2522
  });
2379
2523
  }
2380
- handleFileChangeApprovalRequest(params) {
2524
+ handleFileChangeApprovalRequest(params, rawRequestId) {
2381
2525
  const parsed = params;
2382
- const requestId = `permission-${parsed.itemId}`;
2526
+ const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
2383
2527
  const request = {
2384
2528
  id: requestId,
2385
2529
  provider: CODEX_PROVIDER,
@@ -2406,16 +2550,19 @@ class CodexAppServerAgentSession {
2406
2550
  this.pendingPermissionHandlers.set(requestId, { resolve, kind: "file" });
2407
2551
  });
2408
2552
  }
2409
- handleToolApprovalRequest(params) {
2553
+ handleToolApprovalRequest(params, rawRequestId) {
2410
2554
  const parsed = params;
2411
- const requestId = `permission-${parsed.itemId}`;
2555
+ const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
2412
2556
  const request = {
2413
2557
  id: requestId,
2414
2558
  provider: CODEX_PROVIDER,
2415
- name: "CodexTool",
2416
- kind: "tool",
2417
- title: "Tool action requires approval",
2559
+ name: "CodexQuestion",
2560
+ kind: "question",
2561
+ title: "Answer question",
2418
2562
  description: undefined,
2563
+ input: {
2564
+ questions: Array.isArray(parsed.questions) ? parsed.questions : [],
2565
+ },
2419
2566
  detail: {
2420
2567
  type: "unknown",
2421
2568
  input: {
@@ -2435,7 +2582,7 @@ class CodexAppServerAgentSession {
2435
2582
  return new Promise((resolve) => {
2436
2583
  this.pendingPermissionHandlers.set(requestId, {
2437
2584
  resolve,
2438
- kind: "tool",
2585
+ kind: "question",
2439
2586
  questions: Array.isArray(parsed.questions) ? parsed.questions : [],
2440
2587
  });
2441
2588
  });