@agentconnect.md/codex-acp 1.10.0-agentconnect.2 → 1.12.0-agentconnect.1

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 (3) hide show
  1. package/README.md +11 -0
  2. package/dist/index.js +973 -863
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -22782,6 +22782,111 @@ function reversePatch(structuredPatch) {
22782
22782
  return reversed;
22783
22783
  }
22784
22784
 
22785
+ // src/DiffStats.ts
22786
+ var DiffStatsCalculator = class {
22787
+ addedFile(text) {
22788
+ return { version: 1, added: this.lineCount(text), removed: 0 };
22789
+ }
22790
+ deletedFile(text) {
22791
+ return { version: 1, added: 0, removed: this.lineCount(text) };
22792
+ }
22793
+ update(patch) {
22794
+ if (patch.isBinary || patch.hunks.length === 0) return null;
22795
+ let added = 0;
22796
+ let removed = 0;
22797
+ let previousOldEnd = 1;
22798
+ let previousNewEnd = 1;
22799
+ for (const hunk of patch.hunks) {
22800
+ const { oldStart, oldLines, newStart, newLines } = hunk;
22801
+ if (![oldStart, oldLines, newStart, newLines].every(Number.isSafeInteger) || oldStart < previousOldEnd || newStart < previousNewEnd || oldLines < 0 || newLines < 0 || newStart - oldStart !== added - removed) return null;
22802
+ let oldConsumed = 0;
22803
+ let newConsumed = 0;
22804
+ let previousWasContent = false;
22805
+ for (const line of hunk.lines) {
22806
+ switch (line[0]) {
22807
+ case "+":
22808
+ added++;
22809
+ newConsumed++;
22810
+ previousWasContent = true;
22811
+ break;
22812
+ case "-":
22813
+ removed++;
22814
+ oldConsumed++;
22815
+ previousWasContent = true;
22816
+ break;
22817
+ case " ":
22818
+ case void 0:
22819
+ oldConsumed++;
22820
+ newConsumed++;
22821
+ previousWasContent = true;
22822
+ break;
22823
+ case "\\":
22824
+ if (!previousWasContent || line.replace(/\r$/, "") !== "\") return null;
22825
+ previousWasContent = false;
22826
+ break;
22827
+ default:
22828
+ return null;
22829
+ }
22830
+ }
22831
+ if (oldConsumed !== oldLines || newConsumed !== newLines) return null;
22832
+ previousOldEnd = oldStart + oldLines;
22833
+ previousNewEnd = newStart + newLines;
22834
+ }
22835
+ return { version: 1, added, removed };
22836
+ }
22837
+ lineCount(text) {
22838
+ let count = 0;
22839
+ for (let offset = text.indexOf("\n"); offset >= 0; offset = text.indexOf("\n", offset + 1)) count++;
22840
+ for (let offset = text.indexOf("\r"); offset >= 0; offset = text.indexOf("\r", offset + 1)) {
22841
+ if (text[offset + 1] !== "\n") count++;
22842
+ }
22843
+ if (text.length > 0 && !text.endsWith("\n") && !text.endsWith("\r")) count++;
22844
+ return count;
22845
+ }
22846
+ };
22847
+
22848
+ // src/AirExtension.ts
22849
+ var JETBRAINS_META_KEY = "jetbrains";
22850
+ var AIR_META_KEY = "air";
22851
+ var AIR_EXTENSION_VERSION_KEY = "version";
22852
+ var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
22853
+ var AIR_DIFF_STATS_KEY = "diffStats";
22854
+ var AIR_SESSION_FAILURE_KEY = "sessionFailure";
22855
+ var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
22856
+ var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
22857
+ var AIR_ASYNC_TASKS_KEY = "asyncTasks";
22858
+ var AIR_RECOMMENDED_CONFIG_VALUE_KEY = "recommendedValue";
22859
+ var AIR_ASYNC_TASKS_BACKGROUNDED_KEY = "backgrounded";
22860
+ var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
22861
+ var AIR_EXTENSION_VERSION = 1;
22862
+ function withAirMeta(meta3, key, value) {
22863
+ const root = asRecord(meta3);
22864
+ const jetbrains = asRecord(root[JETBRAINS_META_KEY]);
22865
+ const air = asRecord(jetbrains[AIR_META_KEY]);
22866
+ return {
22867
+ ...root,
22868
+ [JETBRAINS_META_KEY]: {
22869
+ ...jetbrains,
22870
+ [AIR_META_KEY]: {
22871
+ ...air,
22872
+ [AIR_EXTENSION_VERSION_KEY]: AIR_EXTENSION_VERSION,
22873
+ [key]: value
22874
+ }
22875
+ }
22876
+ };
22877
+ }
22878
+ function clientSupportsAirCapability(capabilities, capability) {
22879
+ const meta3 = asRecord(capabilities?._meta);
22880
+ const jetbrains = asRecord(meta3[JETBRAINS_META_KEY]);
22881
+ const air = asRecord(jetbrains[AIR_META_KEY]);
22882
+ const version2 = air[AIR_EXTENSION_VERSION_KEY];
22883
+ const supported = air[AIR_EXTENSION_CAPABILITIES_KEY];
22884
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
22885
+ }
22886
+ function asRecord(value) {
22887
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
22888
+ }
22889
+
22785
22890
  // src/CodexToolCallMapper.ts
22786
22891
  import { readFile } from "node:fs/promises";
22787
22892
  import path2 from "node:path";
@@ -22834,8 +22939,26 @@ function createContextCompactionMeta(metadata = {}) {
22834
22939
  };
22835
22940
  }
22836
22941
 
22942
+ // src/ToolCallName.ts
22943
+ function functionToolName(name, namespace) {
22944
+ return `${namespace ?? ""}${name}`;
22945
+ }
22946
+ function commandToolName(source) {
22947
+ switch (source) {
22948
+ case "unifiedExecStartup":
22949
+ return "exec_command";
22950
+ case "unifiedExecInteraction":
22951
+ return "write_stdin";
22952
+ // These sources do not identify a unique model tool.
22953
+ case "agent":
22954
+ case "userShell":
22955
+ return void 0;
22956
+ }
22957
+ }
22958
+
22837
22959
  // src/CodexToolCallMapper.ts
22838
22960
  var CONTEXT_COMPACTION_META = createContextCompactionMeta();
22961
+ var DIFF_STATS = new DiffStatsCalculator();
22839
22962
  function toAcpStatus(status) {
22840
22963
  switch (status) {
22841
22964
  case "inProgress":
@@ -22864,14 +22987,19 @@ async function createFileChangeUpdate(item) {
22864
22987
  };
22865
22988
  }
22866
22989
  async function createCommandExecutionUpdate(item) {
22990
+ const name = commandToolName(item.source);
22867
22991
  const commandAction = item.commandActions.length === 1 ? item.commandActions[0] : void 0;
22868
22992
  if (commandAction) {
22869
- return createCommandActionEvent(item.id, item.status, item.cwd, commandAction);
22993
+ return {
22994
+ ...createCommandActionEvent(item.id, item.status, item.cwd, commandAction),
22995
+ ...name === void 0 ? {} : { name }
22996
+ };
22870
22997
  }
22871
22998
  const command = stripShellPrefix(item.command);
22872
22999
  return createTerminalCommandEvent({
22873
23000
  sessionUpdate: "tool_call",
22874
23001
  toolCallId: item.id,
23002
+ ...name === void 0 ? {} : { name },
22875
23003
  kind: "execute",
22876
23004
  title: command,
22877
23005
  status: toAcpStatus(item.status),
@@ -22926,7 +23054,10 @@ async function createMcpToolCallUpdate(item) {
22926
23054
  };
22927
23055
  }
22928
23056
  async function createDynamicToolCallUpdate(item) {
22929
- return createExecuteToolCallUpdate(item, item.tool, { arguments: item.arguments });
23057
+ return {
23058
+ ...await createExecuteToolCallUpdate(item, item.tool, { arguments: item.arguments }),
23059
+ name: functionToolName(item.tool, item.namespace)
23060
+ };
22930
23061
  }
22931
23062
  function createImageViewUpdate(item) {
22932
23063
  const displayPath = item.path;
@@ -22934,6 +23065,7 @@ function createImageViewUpdate(item) {
22934
23065
  sessionUpdate: "tool_call",
22935
23066
  toolCallId: item.id,
22936
23067
  kind: "read",
23068
+ name: "view_image",
22937
23069
  title: `View Image ${displayPath}`,
22938
23070
  status: "completed",
22939
23071
  content: [createContent({
@@ -23491,53 +23623,43 @@ async function createAddFileContent(change) {
23491
23623
  newText: change.diff,
23492
23624
  // app-server always returns file content instead of diff
23493
23625
  path: change.path,
23494
- _meta: {
23495
- kind: "add"
23496
- }
23626
+ _meta: withAirMeta({ kind: "add" }, AIR_DIFF_STATS_KEY, DIFF_STATS.addedFile(change.diff))
23497
23627
  };
23498
23628
  }
23499
23629
  async function createUpdateFileContent(change) {
23500
23630
  if (change.kind.type !== "update") return null;
23501
23631
  const unifiedDiff = recoverCorruptedDiff(change.diff);
23632
+ const patches = parsePatch(unifiedDiff);
23633
+ if (patches.length !== 1) return null;
23634
+ const patch = patches[0];
23502
23635
  const movePath = change.kind.move_path;
23503
23636
  const oldContent = await readFileContent(change.path);
23504
23637
  if (oldContent !== null) {
23505
- const patchedContent = applyPatch(oldContent, unifiedDiff);
23638
+ const patchedContent = applyPatch(oldContent, patch);
23506
23639
  if (patchedContent === false) {
23507
- const revertedPatch2 = revertPatch(unifiedDiff);
23508
- if (revertedPatch2) {
23509
- const revertedContent2 = applyPatch(oldContent, revertedPatch2);
23510
- if (revertedContent2 !== false) {
23511
- return createUpdateDiffContent(change.path, revertedContent2, oldContent);
23512
- }
23640
+ const revertedContent2 = applyPatch(oldContent, reversePatch(patch));
23641
+ if (revertedContent2 !== false) {
23642
+ return createUpdateDiffContent(change.path, revertedContent2, oldContent, patch);
23513
23643
  }
23514
23644
  return null;
23515
23645
  }
23516
- return createUpdateDiffContent(movePath ?? change.path, oldContent, patchedContent);
23646
+ return createUpdateDiffContent(movePath ?? change.path, oldContent, patchedContent, patch);
23517
23647
  }
23518
23648
  if (!movePath) return null;
23519
23649
  const newContent = await readFileContent(movePath);
23520
23650
  if (newContent === null) return null;
23521
- const revertedPatch = revertPatch(unifiedDiff);
23522
- if (!revertedPatch) return null;
23523
- const revertedContent = applyPatch(newContent, revertedPatch);
23651
+ const revertedContent = applyPatch(newContent, reversePatch(patch));
23524
23652
  if (revertedContent === false) return null;
23525
- return createUpdateDiffContent(movePath, revertedContent, newContent);
23526
- }
23527
- function revertPatch(unifiedDiff) {
23528
- const [patch] = parsePatch(unifiedDiff);
23529
- if (!patch) return null;
23530
- return reversePatch(patch);
23653
+ return createUpdateDiffContent(movePath, revertedContent, newContent, patch);
23531
23654
  }
23532
- function createUpdateDiffContent(path9, oldText, newText) {
23655
+ function createUpdateDiffContent(path9, oldText, newText, patch) {
23656
+ const stats = DIFF_STATS.update(patch);
23533
23657
  return {
23534
23658
  type: "diff",
23535
23659
  oldText,
23536
23660
  newText,
23537
23661
  path: path9,
23538
- _meta: {
23539
- kind: "update"
23540
- }
23662
+ _meta: stats ? withAirMeta({ kind: "update" }, AIR_DIFF_STATS_KEY, stats) : { kind: "update" }
23541
23663
  };
23542
23664
  }
23543
23665
  async function createDeleteFileContent(change) {
@@ -23547,9 +23669,7 @@ async function createDeleteFileContent(change) {
23547
23669
  // app-server always returns file content instead of diff
23548
23670
  newText: "",
23549
23671
  path: change.path,
23550
- _meta: {
23551
- kind: "delete"
23552
- }
23672
+ _meta: withAirMeta({ kind: "delete" }, AIR_DIFF_STATS_KEY, DIFF_STATS.deletedFile(change.diff))
23553
23673
  };
23554
23674
  }
23555
23675
  async function readFileContent(filePath) {
@@ -23661,26 +23781,6 @@ function sameThreadGoalSnapshot(left, right) {
23661
23781
  // src/CodexEventHandler.ts
23662
23782
  import { randomUUID } from "node:crypto";
23663
23783
 
23664
- // src/AirExtension.ts
23665
- var JETBRAINS_META_KEY = "jetbrains";
23666
- var AIR_META_KEY = "air";
23667
- var AIR_EXTENSION_VERSION_KEY = "version";
23668
- var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
23669
- var AIR_SESSION_FAILURE_KEY = "sessionFailure";
23670
- var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
23671
- var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
23672
- var AIR_ASYNC_TASKS_KEY = "asyncTasks";
23673
- var AIR_ASYNC_TASKS_BACKGROUNDED_KEY = "backgrounded";
23674
- var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
23675
- var AIR_EXTENSION_VERSION = 1;
23676
- function clientSupportsAirCapability(capabilities, capability) {
23677
- const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
23678
- const air = jetbrains?.[AIR_META_KEY];
23679
- const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
23680
- const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
23681
- return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
23682
- }
23683
-
23684
23784
  // src/subagents/CodexAgentPath.ts
23685
23785
  function normalizeAgentPath(path9) {
23686
23786
  const normalized = path9.trim().replace(/\/+$/, "");
@@ -24110,153 +24210,462 @@ function mergeRateLimitSnapshot(previous, update) {
24110
24210
  };
24111
24211
  }
24112
24212
 
24113
- // src/CodexEventHandler.ts
24114
- var MAX_SESSION_FAILURE_TITLE_LENGTH = 240;
24115
- var SESSION_FAILURE_POLICY = {
24116
- transport_lost: {
24117
- category: "connection",
24118
- actions: ["retry", "new_session"]
24119
- },
24120
- auth_required: {
24121
- category: "access",
24122
- actions: ["login"]
24123
- },
24124
- rate_limited: {
24125
- category: "limit",
24126
- actions: ["retry"]
24127
- },
24128
- quota_exhausted: {
24129
- category: "limit",
24130
- actions: []
24131
- },
24132
- overloaded: {
24133
- category: "service",
24134
- actions: ["retry"]
24135
- },
24136
- context_exhausted: {
24137
- category: "limit",
24138
- actions: ["new_session"]
24139
- },
24140
- budget_exhausted: {
24141
- category: "limit",
24142
- actions: ["new_session"]
24143
- },
24144
- policy_denied: {
24145
- category: "request",
24146
- actions: []
24147
- },
24148
- bad_request: {
24149
- category: "request",
24150
- actions: []
24151
- },
24152
- provider_error: {
24153
- category: "service",
24154
- actions: ["retry"]
24155
- },
24156
- internal_error: {
24157
- category: "service",
24158
- actions: ["retry", "new_session"]
24213
+ // src/AgentFileChangeReport.ts
24214
+ import fs2 from "node:fs";
24215
+ import path3 from "node:path";
24216
+ var AGENT_FILE_CHANGE_REPORT_VERSION = 1;
24217
+ var AGENT_FILE_CHANGE_REPORT_MAX_PATHS = 1024;
24218
+ var AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH = 4096;
24219
+ var AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES = 256 * 1024;
24220
+ var AGENT_FILE_CHANGE_REPORT_MAX_DIFF_BYTES = 8 * 1024 * 1024;
24221
+ var TURN_DIFF_UNCERTAINTY = "Codex turn diffs may omit same-content renames and changes made outside apply_patch, including shell commands, version-control commands, generators, and child processes.";
24222
+ function captureAgentFileChangeWorkspace(cwd, additionalDirectories) {
24223
+ const lexicalCwd = parseWorkspaceRoot(cwd);
24224
+ return {
24225
+ cwd,
24226
+ additionalDirectories: [...additionalDirectories],
24227
+ diffRoot: lexicalCwd === null ? cwd : findDiffDisplayRoot(lexicalCwd).value
24228
+ };
24229
+ }
24230
+ function parseAgentFileChangeReportRequest(meta3) {
24231
+ const jetbrains = asRecord2(meta3?.[JETBRAINS_META_KEY]);
24232
+ const air = asRecord2(jetbrains?.[AIR_META_KEY]);
24233
+ const request = asRecord2(air?.[AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY]);
24234
+ if (request === null || !hasOnlyKeys(request, ["version", "requestId"]) || request["version"] !== AGENT_FILE_CHANGE_REPORT_VERSION) {
24235
+ return null;
24159
24236
  }
24160
- };
24161
- var SYNTHETIC_FAILURE_TITLE = {
24162
- transport_lost: "Connection to Codex was lost.",
24163
- internal_error: "Codex encountered an internal error."
24164
- };
24165
- function nextSessionFailureRevision(previous, id) {
24166
- return previous?.id === id ? previous.revision + 1 : 1;
24237
+ const requestId = request["requestId"];
24238
+ if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) {
24239
+ return null;
24240
+ }
24241
+ return { version: AGENT_FILE_CHANGE_REPORT_VERSION, requestId };
24167
24242
  }
24168
- var STRING_CODEX_ERROR_CATEGORIES = {
24169
- contextWindowExceeded: "context_exhausted",
24170
- sessionBudgetExceeded: "budget_exhausted",
24171
- usageLimitExceeded: "quota_exhausted",
24172
- rateLimitExceeded: "rate_limited",
24173
- serverOverloaded: "overloaded",
24174
- cyberPolicy: "policy_denied",
24175
- misalignmentPolicyViolation: "policy_denied",
24176
- internalServerError: "internal_error",
24177
- unauthorized: "auth_required",
24178
- badRequest: "bad_request",
24179
- threadRollbackFailed: "provider_error",
24180
- sandboxError: "provider_error",
24181
- other: "provider_error"
24182
- };
24183
- var STRUCTURED_CODEX_ERROR_CATEGORIES = {
24184
- httpConnectionFailed: "transport_lost",
24185
- responseStreamConnectionFailed: "transport_lost",
24186
- responseStreamDisconnected: "transport_lost",
24187
- responseTooManyFailedAttempts: "transport_lost",
24188
- activeTurnNotSteerable: "provider_error"
24189
- };
24190
- var CodexEventHandler = class _CodexEventHandler {
24191
- static PLAN_UPDATE_INTERVAL_MS = 150;
24192
- sessionState;
24193
- supportsPlanUpdates;
24194
- supportsTypedSessionFailures;
24195
- sessionFailureEpoch;
24196
- pendingErrors = [];
24197
- failuresById = /* @__PURE__ */ new Map();
24198
- activeFailureIdByScope = /* @__PURE__ */ new Map();
24199
- failureTurnIdById = /* @__PURE__ */ new Map();
24200
- allocatedFailureScopes = /* @__PURE__ */ new Set();
24201
- lastSessionNotice;
24202
- nextNoticeId = 1;
24203
- failure = null;
24204
- completedPlan = null;
24205
- activeFuzzyFileSearchSessions = /* @__PURE__ */ new Set();
24206
- activeGuardianApprovalReviews = /* @__PURE__ */ new Set();
24207
- activeImageGenerationItems = /* @__PURE__ */ new Set();
24208
- emittedImageViewItems = /* @__PURE__ */ new Set();
24209
- planDeltaTextByItemId = /* @__PURE__ */ new Map();
24210
- pendingPlanItemIds = /* @__PURE__ */ new Set();
24211
- lastEmittedPlanTextByItemId = /* @__PURE__ */ new Map();
24212
- session;
24213
- planUpdateTimer = null;
24214
- planUpdateChain = Promise.resolve();
24215
- disposed = false;
24216
- seenReasoningDeltaItemIds = /* @__PURE__ */ new Set();
24217
- terminalCommandIds = /* @__PURE__ */ new Set();
24218
- terminalCommandOutputIds = /* @__PURE__ */ new Set();
24219
- agentMessagePhases = /* @__PURE__ */ new Map();
24220
- subagents;
24221
- /** Connection-level `authStatus` sink; the app-server account push feeds it. */
24222
- onAccountUpdated;
24223
- constructor(connection, sessionState, supportsPlanUpdates = false, supportsTypedSessionFailures = false, sessionFailureEpoch = randomUUID(), subagents = new CodexSubagentEventRouter(
24224
- sessionState.sessionId,
24225
- false,
24226
- new ACPSessionConnection(connection, sessionState.sessionId)
24227
- ), onAccountUpdated) {
24228
- this.onAccountUpdated = onAccountUpdated;
24229
- this.sessionState = sessionState;
24230
- this.supportsPlanUpdates = supportsPlanUpdates;
24231
- this.supportsTypedSessionFailures = supportsTypedSessionFailures;
24232
- this.sessionFailureEpoch = sessionFailureEpoch;
24233
- this.session = new ACPSessionConnection(connection, sessionState.sessionId);
24234
- this.subagents = subagents;
24235
- if (sessionState.sessionFailure !== void 0) {
24236
- this.failuresById.set(sessionState.sessionFailure.id, sessionState.sessionFailure);
24237
- }
24243
+ function createReportedAgentFileChangeReport(requestId, diff, workspace) {
24244
+ if (Buffer.byteLength(diff, "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_DIFF_BYTES) {
24245
+ throw new AgentFileChangeReportError("invalidOutput", "The turn diff exceeds the parser input limit");
24238
24246
  }
24239
- getFailure() {
24240
- return this.failure;
24247
+ const parsedReport = parseTurnDiff(diff);
24248
+ const normalized = normalizeFileChangeReport(parsedReport, workspace);
24249
+ return fitReportedAgentFileChangeReport({
24250
+ version: AGENT_FILE_CHANGE_REPORT_VERSION,
24251
+ requestId,
24252
+ status: "reported",
24253
+ ...normalized
24254
+ });
24255
+ }
24256
+ function createUnavailableAgentFileChangeReport(requestId, reason) {
24257
+ return {
24258
+ version: AGENT_FILE_CHANGE_REPORT_VERSION,
24259
+ requestId,
24260
+ status: "unavailable",
24261
+ reason
24262
+ };
24263
+ }
24264
+ var AgentFileChangeReportError = class extends Error {
24265
+ reason;
24266
+ constructor(reason, message) {
24267
+ super(message);
24268
+ this.name = "AgentFileChangeReportError";
24269
+ this.reason = reason;
24241
24270
  }
24242
- getTerminalSessionFailureMeta(turnId, allowUnattributed = false) {
24243
- const failure = this.sessionState.sessionFailure;
24244
- if (!this.supportsTypedSessionFailures || failure === void 0 || (this.failureTurnIdById.get(failure.id) === void 0 ? !allowUnattributed : turnId === null || this.failureTurnIdById.get(failure.id) !== turnId)) {
24245
- return null;
24246
- }
24247
- return this.createSessionFailureMeta(failure);
24271
+ };
24272
+ function parseTurnDiff(diff) {
24273
+ if (diff.trim() === "") {
24274
+ return { paths: [], complete: false, uncertainty: TURN_DIFF_UNCERTAINTY };
24248
24275
  }
24249
- recordSyntheticTerminalFailure(kind, turnId) {
24250
- this.recordSessionFailure(kind, turnId ?? void 0, "error", SYNTHETIC_FAILURE_TITLE[kind]);
24276
+ let patches;
24277
+ try {
24278
+ patches = parsePatch(diff);
24279
+ } catch (error51) {
24280
+ throw new AgentFileChangeReportError(
24281
+ "invalidOutput",
24282
+ `The turn diff is not valid unified diff: ${error51 instanceof Error ? error51.message : String(error51)}`
24283
+ );
24251
24284
  }
24252
- /**
24253
- * Handles notifications after the prompt-local handler has been disposed. The app-server subscription
24254
- * remains installed until the ACP session closes, so terminal errors need a durable session-level path
24255
- * instead of entering a turn buffer that will never be flushed.
24256
- */
24257
- async handleSessionScopedNotification(notification) {
24258
- if (notification.method !== "error") {
24259
- await this.handleNotification(notification);
24285
+ if (patches.length === 0) {
24286
+ throw new AgentFileChangeReportError("invalidOutput", "The turn diff contains no parseable file patches");
24287
+ }
24288
+ const rawFileHeaders = extractRawFileHeaders(diff);
24289
+ const paths = [];
24290
+ for (const [index, patch] of patches.entries()) {
24291
+ const rawHeaders = rawFileHeaders[index];
24292
+ const oldPath = normalizeDiffPath(selectLosslessFileName(rawHeaders?.oldFileName, patch.oldFileName));
24293
+ const newPath = normalizeDiffPath(selectLosslessFileName(rawHeaders?.newFileName, patch.newFileName));
24294
+ if (oldPath === null && newPath === null) {
24295
+ throw new AgentFileChangeReportError("invalidOutput", "The turn diff contains a patch without a file path");
24296
+ }
24297
+ if (oldPath !== null) paths.push(oldPath);
24298
+ if (newPath !== null) paths.push(newPath);
24299
+ }
24300
+ return { paths, complete: false, uncertainty: TURN_DIFF_UNCERTAINTY };
24301
+ }
24302
+ function extractRawFileHeaders(diff) {
24303
+ return diff.split(/(?=^diff --git )/m).filter((section) => section.trim() !== "").map((section) => {
24304
+ const lines = section.split(/\r?\n/);
24305
+ for (let index = 0; index < lines.length; index += 1) {
24306
+ const line = lines[index];
24307
+ if (line?.startsWith("@@ ")) return null;
24308
+ const nextLine = lines[index + 1];
24309
+ if (line?.startsWith("--- ") && nextLine?.startsWith("+++ ")) {
24310
+ return {
24311
+ oldFileName: line.slice(4),
24312
+ newFileName: nextLine.slice(4)
24313
+ };
24314
+ }
24315
+ }
24316
+ return null;
24317
+ });
24318
+ }
24319
+ function selectLosslessFileName(rawFileName, parsedFileName) {
24320
+ return rawFileName === void 0 || rawFileName.startsWith('"') ? parsedFileName : rawFileName;
24321
+ }
24322
+ function normalizeDiffPath(value) {
24323
+ if (value === void 0 || value === "/dev/null") return null;
24324
+ return value.startsWith("a/") || value.startsWith("b/") ? value.slice(2) : value;
24325
+ }
24326
+ function fitReportedAgentFileChangeReport(report) {
24327
+ const paths = [...report.paths];
24328
+ let truncated = report.truncated;
24329
+ let fitted = report;
24330
+ while (Buffer.byteLength(JSON.stringify(fitted), "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
24331
+ if (paths.length === 0) {
24332
+ throw new AgentFileChangeReportError("invalidOutput", "The audit report exceeds the wire limit");
24333
+ }
24334
+ paths.pop();
24335
+ truncated = true;
24336
+ fitted = {
24337
+ ...report,
24338
+ paths: [...paths],
24339
+ declaredComplete: false,
24340
+ truncated
24341
+ };
24342
+ }
24343
+ return fitted;
24344
+ }
24345
+ function normalizeFileChangeReport(report, workspace) {
24346
+ const lexicalCwd = parseWorkspaceRoot(workspace.cwd);
24347
+ if (lexicalCwd === null) {
24348
+ throw new AgentFileChangeReportError("providerError", "The session working directory is not absolute");
24349
+ }
24350
+ const cwd = canonicalizeWorkspaceRoot(lexicalCwd);
24351
+ const diffRoot = parseWorkspaceRoot(workspace.diffRoot);
24352
+ if (diffRoot === null || diffRoot.flavor !== cwd.flavor) {
24353
+ throw new AgentFileChangeReportError("providerError", "The captured turn-diff root is invalid");
24354
+ }
24355
+ const roots = [cwd, ...workspace.additionalDirectories.flatMap((directory) => {
24356
+ const root = normalizeWorkspaceRoot(directory);
24357
+ return root === null || root.flavor !== cwd.flavor ? [] : [root];
24358
+ })];
24359
+ const paths = [];
24360
+ const seen = /* @__PURE__ */ new Set();
24361
+ let totalBytes = 0;
24362
+ let truncated = false;
24363
+ for (const reportedPath of report.paths) {
24364
+ if (reportedPath.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
24365
+ truncated = true;
24366
+ continue;
24367
+ }
24368
+ const normalized = normalizeReportedPath(reportedPath, diffRoot, roots);
24369
+ if (normalized === null || normalized.value.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
24370
+ truncated = true;
24371
+ continue;
24372
+ }
24373
+ const key = normalized.flavor === "windows" ? normalized.value.toLowerCase() : normalized.value;
24374
+ if (seen.has(key)) {
24375
+ continue;
24376
+ }
24377
+ const bytes = Buffer.byteLength(normalized.value, "utf8");
24378
+ if (paths.length >= AGENT_FILE_CHANGE_REPORT_MAX_PATHS || totalBytes + bytes > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
24379
+ truncated = true;
24380
+ continue;
24381
+ }
24382
+ seen.add(key);
24383
+ paths.push(normalized.value);
24384
+ totalBytes += bytes;
24385
+ }
24386
+ return {
24387
+ paths,
24388
+ declaredComplete: report.complete && !truncated,
24389
+ truncated,
24390
+ ...report.uncertainty ? { uncertainty: report.uncertainty } : {}
24391
+ };
24392
+ }
24393
+ function normalizeWorkspaceRoot(value) {
24394
+ const root = parseWorkspaceRoot(value);
24395
+ return root === null ? null : canonicalizeWorkspaceRoot(root);
24396
+ }
24397
+ function parseWorkspaceRoot(value) {
24398
+ const trimmed = value.trim();
24399
+ if (!isValidPathText(trimmed)) {
24400
+ return null;
24401
+ }
24402
+ if (isWindowsAbsolutePath(trimmed)) {
24403
+ return {
24404
+ value: path3.win32.normalize(trimmed.replace(/\//g, "\\")),
24405
+ flavor: "windows"
24406
+ };
24407
+ }
24408
+ if (path3.posix.isAbsolute(trimmed)) {
24409
+ return {
24410
+ value: path3.posix.normalize(trimmed.replace(/\\/g, "/")),
24411
+ flavor: "posix"
24412
+ };
24413
+ }
24414
+ return null;
24415
+ }
24416
+ function normalizeReportedPath(value, relativeRoot, roots) {
24417
+ if (!isValidPathText(value) || /^[A-Za-z]:[^\\/]/.test(value) || /^\\\\[?.]\\/.test(value) || /^(?:\\\\|\/\/)/.test(value) && !isWindowsAbsolutePath(value) || relativeRoot.flavor === "windows" && /^\\(?!\\)/.test(value) || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(value)) {
24418
+ return null;
24419
+ }
24420
+ let candidate;
24421
+ if (isWindowsAbsolutePath(value)) {
24422
+ candidate = { value: path3.win32.normalize(value.replace(/\//g, "\\")), flavor: "windows" };
24423
+ } else if (path3.posix.isAbsolute(value)) {
24424
+ candidate = { value: path3.posix.normalize(value.replace(/\\/g, "/")), flavor: "posix" };
24425
+ } else if (relativeRoot.flavor === "windows") {
24426
+ candidate = {
24427
+ value: path3.win32.resolve(relativeRoot.value, value.replace(/\//g, "\\")),
24428
+ flavor: "windows"
24429
+ };
24430
+ } else {
24431
+ candidate = {
24432
+ value: path3.posix.resolve(relativeRoot.value, value.replace(/\\/g, "/")),
24433
+ flavor: "posix"
24434
+ };
24435
+ }
24436
+ candidate = canonicalizeReportedPath(candidate);
24437
+ return roots.some((root) => pathIsStrictlyInside(root, candidate)) ? candidate : null;
24438
+ }
24439
+ function findDiffDisplayRoot(cwd) {
24440
+ if (!isNativePathFlavor(cwd.flavor)) return cwd;
24441
+ const pathImplementation = cwd.flavor === "windows" ? path3.win32 : path3.posix;
24442
+ let current = cwd.value;
24443
+ while (true) {
24444
+ if (fs2.existsSync(pathImplementation.join(current, ".git"))) {
24445
+ return canonicalizeWorkspaceRoot({ value: current, flavor: cwd.flavor });
24446
+ }
24447
+ const parent = pathImplementation.dirname(current);
24448
+ if (parent === current) return cwd;
24449
+ current = parent;
24450
+ }
24451
+ }
24452
+ function canonicalizeWorkspaceRoot(root) {
24453
+ if (!isNativePathFlavor(root.flavor)) {
24454
+ return root;
24455
+ }
24456
+ return { ...root, value: canonicalizeFromExistingAncestor(root.value) };
24457
+ }
24458
+ function canonicalizeReportedPath(candidate) {
24459
+ if (!isNativePathFlavor(candidate.flavor)) {
24460
+ return candidate;
24461
+ }
24462
+ const parent = canonicalizeFromExistingAncestor(path3.dirname(candidate.value));
24463
+ return { ...candidate, value: path3.resolve(parent, path3.basename(candidate.value)) };
24464
+ }
24465
+ function canonicalizeFromExistingAncestor(value) {
24466
+ const original = path3.resolve(value);
24467
+ let current = original;
24468
+ const missingSegments = [];
24469
+ while (true) {
24470
+ try {
24471
+ const canonical = fs2.realpathSync.native(current);
24472
+ return path3.resolve(canonical, ...missingSegments.reverse());
24473
+ } catch (error51) {
24474
+ if (!isMissingPathError(error51)) return original;
24475
+ }
24476
+ const parent = path3.dirname(current);
24477
+ if (parent === current) return original;
24478
+ missingSegments.push(path3.basename(current));
24479
+ current = parent;
24480
+ }
24481
+ }
24482
+ function isMissingPathError(error51) {
24483
+ if (typeof error51 !== "object" || error51 === null || !("code" in error51)) return false;
24484
+ return error51.code === "ENOENT" || error51.code === "ENOTDIR";
24485
+ }
24486
+ function isNativePathFlavor(flavor) {
24487
+ return process.platform === "win32" ? flavor === "windows" : flavor === "posix";
24488
+ }
24489
+ function pathIsStrictlyInside(root, candidate) {
24490
+ if (root.flavor !== candidate.flavor) {
24491
+ return false;
24492
+ }
24493
+ const pathImplementation = root.flavor === "windows" ? path3.win32 : path3.posix;
24494
+ const relative = pathImplementation.relative(root.value, candidate.value);
24495
+ return relative.length > 0 && !pathImplementation.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${pathImplementation.sep}`);
24496
+ }
24497
+ function isWindowsAbsolutePath(value) {
24498
+ const portable = value.replace(/\\/g, "/");
24499
+ return /^[A-Za-z]:\//.test(portable) || /^\/\/[^/]+\/[^/]+(?:\/|$)/.test(portable);
24500
+ }
24501
+ function isValidPathText(value) {
24502
+ return value.length > 0 && !/[\u0000-\u001F\u007F-\u009F]/.test(value);
24503
+ }
24504
+ function asRecord2(value) {
24505
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
24506
+ }
24507
+ function hasOnlyKeys(value, allowed) {
24508
+ const allowedKeys = new Set(allowed);
24509
+ return Object.keys(value).every((key) => allowedKeys.has(key));
24510
+ }
24511
+
24512
+ // src/CodexEventHandler.ts
24513
+ var MAX_SESSION_FAILURE_TITLE_LENGTH = 240;
24514
+ var SESSION_FAILURE_POLICY = {
24515
+ transport_lost: {
24516
+ category: "connection",
24517
+ actions: ["retry", "new_session"]
24518
+ },
24519
+ auth_required: {
24520
+ category: "access",
24521
+ actions: ["login"]
24522
+ },
24523
+ rate_limited: {
24524
+ category: "limit",
24525
+ actions: ["retry"]
24526
+ },
24527
+ quota_exhausted: {
24528
+ category: "limit",
24529
+ actions: []
24530
+ },
24531
+ overloaded: {
24532
+ category: "service",
24533
+ actions: ["retry"]
24534
+ },
24535
+ context_exhausted: {
24536
+ category: "limit",
24537
+ actions: ["new_session"]
24538
+ },
24539
+ budget_exhausted: {
24540
+ category: "limit",
24541
+ actions: ["new_session"]
24542
+ },
24543
+ policy_denied: {
24544
+ category: "request",
24545
+ actions: []
24546
+ },
24547
+ bad_request: {
24548
+ category: "request",
24549
+ actions: []
24550
+ },
24551
+ provider_error: {
24552
+ category: "service",
24553
+ actions: ["retry"]
24554
+ },
24555
+ internal_error: {
24556
+ category: "service",
24557
+ actions: ["retry", "new_session"]
24558
+ }
24559
+ };
24560
+ var SYNTHETIC_FAILURE_TITLE = {
24561
+ transport_lost: "Connection to Codex was lost.",
24562
+ internal_error: "Codex encountered an internal error."
24563
+ };
24564
+ function nextSessionFailureRevision(previous, id) {
24565
+ return previous?.id === id ? previous.revision + 1 : 1;
24566
+ }
24567
+ var STRING_CODEX_ERROR_CATEGORIES = {
24568
+ contextWindowExceeded: "context_exhausted",
24569
+ sessionBudgetExceeded: "budget_exhausted",
24570
+ usageLimitExceeded: "quota_exhausted",
24571
+ rateLimitExceeded: "rate_limited",
24572
+ serverOverloaded: "overloaded",
24573
+ cyberPolicy: "policy_denied",
24574
+ misalignmentPolicyViolation: "policy_denied",
24575
+ internalServerError: "internal_error",
24576
+ unauthorized: "auth_required",
24577
+ badRequest: "bad_request",
24578
+ threadRollbackFailed: "provider_error",
24579
+ sandboxError: "provider_error",
24580
+ other: "provider_error"
24581
+ };
24582
+ var STRUCTURED_CODEX_ERROR_CATEGORIES = {
24583
+ httpConnectionFailed: "transport_lost",
24584
+ responseStreamConnectionFailed: "transport_lost",
24585
+ responseStreamDisconnected: "transport_lost",
24586
+ responseTooManyFailedAttempts: "transport_lost",
24587
+ activeTurnNotSteerable: "provider_error"
24588
+ };
24589
+ var CodexEventHandler = class _CodexEventHandler {
24590
+ static PLAN_UPDATE_INTERVAL_MS = 150;
24591
+ sessionState;
24592
+ supportsPlanUpdates;
24593
+ supportsTypedSessionFailures;
24594
+ sessionFailureEpoch;
24595
+ pendingErrors = [];
24596
+ failuresById = /* @__PURE__ */ new Map();
24597
+ activeFailureIdByScope = /* @__PURE__ */ new Map();
24598
+ failureTurnIdById = /* @__PURE__ */ new Map();
24599
+ allocatedFailureScopes = /* @__PURE__ */ new Set();
24600
+ lastSessionNotice;
24601
+ nextNoticeId = 1;
24602
+ failure = null;
24603
+ completedPlan = null;
24604
+ activeFuzzyFileSearchSessions = /* @__PURE__ */ new Set();
24605
+ activeGuardianApprovalReviews = /* @__PURE__ */ new Set();
24606
+ activeImageGenerationItems = /* @__PURE__ */ new Set();
24607
+ emittedImageViewItems = /* @__PURE__ */ new Set();
24608
+ planDeltaTextByItemId = /* @__PURE__ */ new Map();
24609
+ pendingPlanItemIds = /* @__PURE__ */ new Set();
24610
+ lastEmittedPlanTextByItemId = /* @__PURE__ */ new Map();
24611
+ session;
24612
+ planUpdateTimer = null;
24613
+ planUpdateChain = Promise.resolve();
24614
+ disposed = false;
24615
+ seenReasoningDeltaItemIds = /* @__PURE__ */ new Set();
24616
+ terminalCommandIds = /* @__PURE__ */ new Set();
24617
+ terminalCommandOutputIds = /* @__PURE__ */ new Set();
24618
+ agentMessagePhases = /* @__PURE__ */ new Map();
24619
+ turnDiffs = /* @__PURE__ */ new Map();
24620
+ oversizedTurnDiffs = /* @__PURE__ */ new Set();
24621
+ collectTurnDiffs;
24622
+ subagents;
24623
+ /** Connection-level `authStatus` sink; the app-server account push feeds it. */
24624
+ onAccountUpdated;
24625
+ constructor(connection, sessionState, supportsPlanUpdates = false, supportsTypedSessionFailures = false, sessionFailureEpoch = randomUUID(), subagents = new CodexSubagentEventRouter(
24626
+ sessionState.sessionId,
24627
+ false,
24628
+ new ACPSessionConnection(connection, sessionState.sessionId)
24629
+ ), onAccountUpdated, collectTurnDiffs = false) {
24630
+ this.onAccountUpdated = onAccountUpdated;
24631
+ this.sessionState = sessionState;
24632
+ this.supportsPlanUpdates = supportsPlanUpdates;
24633
+ this.supportsTypedSessionFailures = supportsTypedSessionFailures;
24634
+ this.sessionFailureEpoch = sessionFailureEpoch;
24635
+ this.session = new ACPSessionConnection(connection, sessionState.sessionId);
24636
+ this.subagents = subagents;
24637
+ this.collectTurnDiffs = collectTurnDiffs;
24638
+ if (sessionState.sessionFailure !== void 0) {
24639
+ this.failuresById.set(sessionState.sessionFailure.id, sessionState.sessionFailure);
24640
+ }
24641
+ }
24642
+ getFailure() {
24643
+ return this.failure;
24644
+ }
24645
+ getTurnDiff(turnId) {
24646
+ return this.turnDiffs.get(turnId) ?? "";
24647
+ }
24648
+ isTurnDiffOversized(turnId) {
24649
+ return this.oversizedTurnDiffs.has(turnId);
24650
+ }
24651
+ getTerminalSessionFailureMeta(turnId, allowUnattributed = false) {
24652
+ const failure = this.sessionState.sessionFailure;
24653
+ if (!this.supportsTypedSessionFailures || failure === void 0 || (this.failureTurnIdById.get(failure.id) === void 0 ? !allowUnattributed : turnId === null || this.failureTurnIdById.get(failure.id) !== turnId)) {
24654
+ return null;
24655
+ }
24656
+ return this.createSessionFailureMeta(failure);
24657
+ }
24658
+ recordSyntheticTerminalFailure(kind, turnId) {
24659
+ this.recordSessionFailure(kind, turnId ?? void 0, "error", SYNTHETIC_FAILURE_TITLE[kind]);
24660
+ }
24661
+ /**
24662
+ * Handles notifications after the prompt-local handler has been disposed. The app-server subscription
24663
+ * remains installed until the ACP session closes, so terminal errors need a durable session-level path
24664
+ * instead of entering a turn buffer that will never be flushed.
24665
+ */
24666
+ async handleSessionScopedNotification(notification) {
24667
+ if (notification.method !== "error") {
24668
+ await this.handleNotification(notification);
24260
24669
  return;
24261
24670
  }
24262
24671
  if (!this.supportsTypedSessionFailures) {
@@ -24398,6 +24807,8 @@ var CodexEventHandler = class _CodexEventHandler {
24398
24807
  this.pendingPlanItemIds.clear();
24399
24808
  this.planDeltaTextByItemId.clear();
24400
24809
  this.lastEmittedPlanTextByItemId.clear();
24810
+ this.turnDiffs.clear();
24811
+ this.oversizedTurnDiffs.clear();
24401
24812
  }
24402
24813
  async createUpdateEvent(notification) {
24403
24814
  switch (notification.method) {
@@ -24416,6 +24827,20 @@ var CodexEventHandler = class _CodexEventHandler {
24416
24827
  case "turn/plan/updated":
24417
24828
  this.completeRetryIncidentOnTurnProgress();
24418
24829
  return await this.updatePlan(notification.params);
24830
+ case "turn/diff/updated":
24831
+ if (notification.params.threadId === this.sessionState.sessionId) {
24832
+ this.completeRetryIncidentOnTurnProgress();
24833
+ if (!this.disposed && this.collectTurnDiffs) {
24834
+ if (Buffer.byteLength(notification.params.diff, "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_DIFF_BYTES) {
24835
+ this.turnDiffs.delete(notification.params.turnId);
24836
+ this.oversizedTurnDiffs.add(notification.params.turnId);
24837
+ } else {
24838
+ this.oversizedTurnDiffs.delete(notification.params.turnId);
24839
+ this.turnDiffs.set(notification.params.turnId, notification.params.diff);
24840
+ }
24841
+ }
24842
+ }
24843
+ return null;
24419
24844
  case "error":
24420
24845
  return await this.createErrorEvent(notification.params);
24421
24846
  case "turn/started":
@@ -24510,7 +24935,6 @@ var CodexEventHandler = class _CodexEventHandler {
24510
24935
  case "command/exec/outputDelta":
24511
24936
  case "hook/started":
24512
24937
  case "hook/completed":
24513
- case "turn/diff/updated":
24514
24938
  case "turn/moderationMetadata":
24515
24939
  case "item/fileChange/outputDelta":
24516
24940
  case "item/fileChange/patchUpdated":
@@ -24694,10 +25118,16 @@ ${event.details}` : event.summary;
24694
25118
  async completeItemEvent(event) {
24695
25119
  switch (event.item.type) {
24696
25120
  case "fileChange":
25121
+ return {
25122
+ sessionUpdate: "tool_call_update",
25123
+ toolCallId: event.item.id,
25124
+ status: event.item.status === "completed" ? "completed" : "failed"
25125
+ };
24697
25126
  case "dynamicToolCall":
24698
25127
  return {
24699
25128
  sessionUpdate: "tool_call_update",
24700
25129
  toolCallId: event.item.id,
25130
+ name: functionToolName(event.item.tool, event.item.namespace),
24701
25131
  status: event.item.status === "completed" ? "completed" : "failed"
24702
25132
  };
24703
25133
  case "mcpToolCall":
@@ -24909,9 +25339,11 @@ ${event.stdin}
24909
25339
  return `mcp_startup.${encodeURIComponent(serverName)}`;
24910
25340
  }
24911
25341
  completeCommandExecutionEvent(item) {
25342
+ const name = commandToolName(item.source);
24912
25343
  const update = {
24913
25344
  sessionUpdate: "tool_call_update",
24914
25345
  toolCallId: item.id,
25346
+ ...name === void 0 ? {} : { name },
24915
25347
  status: item.status === "completed" ? "completed" : "failed",
24916
25348
  rawOutput: {
24917
25349
  formatted_output: item.aggregatedOutput ?? "",
@@ -25527,7 +25959,8 @@ function nonBlank(value) {
25527
25959
  }
25528
25960
 
25529
25961
  // src/permissions/presentation.ts
25530
- function commandToolCall(params) {
25962
+ function commandToolCall(params, permissionContext) {
25963
+ const name = permissionContext.commandName(params.threadId, params.itemId);
25531
25964
  const network = params.networkApprovalContext;
25532
25965
  const rawInput = {
25533
25966
  ...params.command ? { command: stripShellPrefix(params.command) } : {},
@@ -25538,6 +25971,7 @@ function commandToolCall(params) {
25538
25971
  const additionalPermissionContent = params.additionalPermissions ? permissionProfileContent(params.additionalPermissions) : [];
25539
25972
  return {
25540
25973
  toolCallId: params.itemId,
25974
+ ...name !== void 0 ? { name } : {},
25541
25975
  kind: "execute",
25542
25976
  status: "pending",
25543
25977
  title: network ? `${network.protocol} network access to ${network.host}` : commandTitle(params.commandActions),
@@ -25563,6 +25997,7 @@ function additionalPermissionsToolCall(itemId, cwd, environmentId, permissions)
25563
25997
  const content = permissionProfileContent(permissions);
25564
25998
  return {
25565
25999
  toolCallId: itemId,
26000
+ name: "request_permissions",
25566
26001
  kind: "other",
25567
26002
  status: "pending",
25568
26003
  title: "Additional sandbox permissions",
@@ -25661,7 +26096,7 @@ var CodexApprovalHandler = class {
25661
26096
  try {
25662
26097
  const response = await this.requestPermission({
25663
26098
  sessionId: params.threadId,
25664
- toolCall: commandToolCall(authoritativeParams),
26099
+ toolCall: commandToolCall(authoritativeParams, this.permissionContext),
25665
26100
  options: decisions.map(({ option }) => option),
25666
26101
  _meta: requestPermissionMeta(
25667
26102
  params.networkApprovalContext ? CODEX_NETWORK_PERMISSION_TITLE : CODEX_COMMAND_PERMISSION_TITLE,
@@ -25769,6 +26204,7 @@ var PermissionPromptContext = class {
25769
26204
  this.nextStandaloneId = nextStandaloneId;
25770
26205
  }
25771
26206
  nextStandaloneId;
26207
+ commandNames = /* @__PURE__ */ new Map();
25772
26208
  fileChanges = /* @__PURE__ */ new Map();
25773
26209
  pendingMcpApprovals = /* @__PURE__ */ new Map();
25774
26210
  handleNotification(notification) {
@@ -25792,6 +26228,9 @@ var PermissionPromptContext = class {
25792
26228
  fileChange(threadId, itemId) {
25793
26229
  return this.fileChanges.get(threadId)?.get(itemId);
25794
26230
  }
26231
+ commandName(threadId, itemId) {
26232
+ return this.commandNames.get(threadId)?.get(itemId);
26233
+ }
25795
26234
  popPendingMcpApproval(threadId, serverName) {
25796
26235
  const byServer = this.pendingMcpApprovals.get(threadId);
25797
26236
  if (!byServer) return void 0;
@@ -25806,6 +26245,15 @@ var PermissionPromptContext = class {
25806
26245
  return this.nextStandaloneId(serverName);
25807
26246
  }
25808
26247
  handleItemStarted(threadId, item) {
26248
+ if (item.type === "commandExecution") {
26249
+ const name = commandToolName(item.source);
26250
+ if (name !== void 0) {
26251
+ const byItem = this.commandNames.get(threadId) ?? /* @__PURE__ */ new Map();
26252
+ byItem.set(item.id, name);
26253
+ this.commandNames.set(threadId, byItem);
26254
+ }
26255
+ return;
26256
+ }
25809
26257
  if (item.type === "fileChange") {
25810
26258
  const byItem = this.fileChanges.get(threadId) ?? /* @__PURE__ */ new Map();
25811
26259
  byItem.set(item.id, item);
@@ -25820,6 +26268,12 @@ var PermissionPromptContext = class {
25820
26268
  this.pendingMcpApprovals.set(threadId, byServer);
25821
26269
  }
25822
26270
  handleItemCompleted(threadId, item) {
26271
+ if (item.type === "commandExecution") {
26272
+ const byItem = this.commandNames.get(threadId);
26273
+ byItem?.delete(item.id);
26274
+ if (byItem?.size === 0) this.commandNames.delete(threadId);
26275
+ return;
26276
+ }
25823
26277
  if (item.type === "fileChange") {
25824
26278
  const byItem = this.fileChanges.get(threadId);
25825
26279
  byItem?.delete(item.id);
@@ -25837,6 +26291,7 @@ var PermissionPromptContext = class {
25837
26291
  if (byServer.size === 0) this.pendingMcpApprovals.delete(threadId);
25838
26292
  }
25839
26293
  clearTransientState(threadId) {
26294
+ this.commandNames.delete(threadId);
25840
26295
  this.fileChanges.delete(threadId);
25841
26296
  this.pendingMcpApprovals.delete(threadId);
25842
26297
  }
@@ -25969,8 +26424,9 @@ function buildMcpPermissionRequest(sessionId, params, context, nextStandaloneToo
25969
26424
  toolCallId: nextStandaloneToolCallId(),
25970
26425
  kind: context.isToolApproval ? "execute" : "other",
25971
26426
  status: "pending",
26427
+ title: context.isToolApproval ? "MCP tool call approval" : "Question from MCP server",
25972
26428
  content: [messageContent],
25973
- rawInput: { serverName: params.serverName, schema: params.requestedSchema }
26429
+ rawInput: { serverName: params.serverName, description: params.message, schema: params.requestedSchema }
25974
26430
  },
25975
26431
  ...context.isToolApproval ? { _meta: { is_mcp_tool_approval: true } } : {},
25976
26432
  options
@@ -25988,8 +26444,9 @@ function buildMcpPermissionRequest(sessionId, params, context, nextStandaloneToo
25988
26444
  toolCallId: `elicitation-${params.elicitationId}`,
25989
26445
  kind: "fetch",
25990
26446
  status: "pending",
26447
+ title: "MCP server requests to open a URL",
25991
26448
  content: [messageContent],
25992
- rawInput: { serverName: params.serverName, url: params.url }
26449
+ rawInput: { serverName: params.serverName, description: params.message, url: params.url }
25993
26450
  },
25994
26451
  options
25995
26452
  },
@@ -26023,7 +26480,9 @@ function cancelledResponse() {
26023
26480
  }
26024
26481
 
26025
26482
  // src/CodexElicitationHandler.ts
26026
- var USER_INPUT_OTHER_FIELD_SUFFIX = "__other";
26483
+ var USER_INPUT_NOTE_FIELD_SUFFIX = "_note";
26484
+ var USER_INPUT_OTHER_OPTION = "None of the above";
26485
+ var USER_INPUT_NOTE_PREFIX = "user_note: ";
26027
26486
  function normalizeElicitationSchema(value) {
26028
26487
  const normalized = normalizeElicitationSchemaValue(value);
26029
26488
  if (!isRecord2(normalized)) {
@@ -26080,16 +26539,14 @@ function elicitationResponseMeta(response, context, persist = void 0) {
26080
26539
  }
26081
26540
  return Object.keys(meta3).length === 0 ? null : meta3;
26082
26541
  }
26083
- function userInputOtherFieldId(questionId, questionIds) {
26084
- const base = `${questionId}${USER_INPUT_OTHER_FIELD_SUFFIX}`;
26085
- if (!questionIds.has(base)) {
26086
- return base;
26087
- }
26542
+ function userInputNoteFieldId(questionId, questionIds) {
26543
+ const base = `${questionId}${USER_INPUT_NOTE_FIELD_SUFFIX}`;
26544
+ let fieldId = base;
26088
26545
  let index = 1;
26089
- while (questionIds.has(`${base}${index}`)) {
26090
- index += 1;
26546
+ while (questionIds.has(fieldId)) {
26547
+ fieldId = `${base}${index++}`;
26091
26548
  }
26092
- return `${base}${index}`;
26549
+ return fieldId;
26093
26550
  }
26094
26551
  function userInputResponseValue(content, fieldId) {
26095
26552
  const value = content[fieldId];
@@ -26166,11 +26623,29 @@ var CodexElicitationHandler = class {
26166
26623
  context.isToolApproval,
26167
26624
  context.persistOptions
26168
26625
  );
26169
- if (correlatedCallId !== void 0 && result.action === "accept") {
26170
- await this.connection.notify(methods.client.session.update, {
26171
- sessionId: params.threadId,
26172
- update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }
26173
- });
26626
+ if (correlatedCallId !== void 0) {
26627
+ if (result.action === "accept") {
26628
+ await this.connection.notify(methods.client.session.update, {
26629
+ sessionId: params.threadId,
26630
+ update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }
26631
+ });
26632
+ }
26633
+ } else {
26634
+ try {
26635
+ await this.connection.notify(methods.client.session.update, {
26636
+ sessionId: params.threadId,
26637
+ update: {
26638
+ sessionUpdate: "tool_call_update",
26639
+ toolCallId: request.toolCall.toolCallId,
26640
+ status: "completed",
26641
+ title: request.toolCall.title,
26642
+ content: request.toolCall.content,
26643
+ rawOutput: { action: result.action }
26644
+ }
26645
+ });
26646
+ } catch (error51) {
26647
+ logger.error("Failed to finalize standalone MCP elicitation tool call", error51);
26648
+ }
26174
26649
  }
26175
26650
  return result;
26176
26651
  } catch (error51) {
@@ -26308,8 +26783,8 @@ var CodexElicitationHandler = class {
26308
26783
  const hasOptions = options.length > 0;
26309
26784
  const hasOtherAnswer = question.isOther && hasOptions;
26310
26785
  const base = {
26311
- title: question.header || question.id,
26312
- description: question.question,
26786
+ title: question.question || question.header || question.id,
26787
+ ...question.header ? { description: question.header } : {},
26313
26788
  _meta: {
26314
26789
  codex: {
26315
26790
  isOther: question.isOther,
@@ -26317,42 +26792,45 @@ var CodexElicitationHandler = class {
26317
26792
  }
26318
26793
  }
26319
26794
  };
26320
- if (!hasOtherAnswer) {
26321
- required2.push(question.id);
26322
- }
26795
+ required2.push(question.id);
26323
26796
  properties[question.id] = hasOptions ? {
26324
26797
  ...base,
26325
26798
  type: "string",
26326
- oneOf: options.map((option) => ({
26327
- const: option.label,
26328
- title: option.label,
26329
- description: option.description
26330
- }))
26799
+ oneOf: [
26800
+ ...options.map((option) => ({
26801
+ const: option.label,
26802
+ title: option.label,
26803
+ ...option.description ? { description: option.description } : {}
26804
+ })),
26805
+ ...hasOtherAnswer && !options.some((option) => option.label === USER_INPUT_OTHER_OPTION) ? [{
26806
+ const: USER_INPUT_OTHER_OPTION,
26807
+ title: USER_INPUT_OTHER_OPTION,
26808
+ description: "Provide a different answer in the note field."
26809
+ }] : []
26810
+ ]
26331
26811
  } : {
26332
26812
  ...base,
26333
26813
  type: "string"
26334
26814
  };
26335
26815
  if (hasOtherAnswer) {
26336
- properties[userInputOtherFieldId(question.id, questionIds)] = {
26816
+ properties[userInputNoteFieldId(question.id, questionIds)] = {
26337
26817
  type: "string",
26338
- title: "Other",
26339
- description: "Type your own answer instead of choosing an option above.",
26818
+ title: "Additional answer or note",
26340
26819
  _meta: {
26341
26820
  codex: {
26342
26821
  questionId: question.id,
26343
- isOtherAnswer: true,
26822
+ role: "user_note",
26344
26823
  isSecret: question.isSecret
26345
26824
  }
26346
26825
  }
26347
26826
  };
26348
26827
  }
26349
26828
  }
26350
- const firstQuestion = params.questions[0];
26351
26829
  return {
26352
26830
  sessionId: params.threadId,
26353
26831
  toolCallId: params.itemId,
26354
26832
  mode: "form",
26355
- message: params.questions.length === 1 && firstQuestion ? firstQuestion.question : "Input requested",
26833
+ message: "Codex needs your input to continue.",
26356
26834
  requestedSchema: {
26357
26835
  type: "object",
26358
26836
  properties,
@@ -26403,12 +26881,23 @@ var CodexElicitationHandler = class {
26403
26881
  const content = contentRecord(response.content);
26404
26882
  const questionIds = new Set(params.questions.map((question) => question.id));
26405
26883
  for (const question of params.questions) {
26406
- const value = question.isOther && question.options != null && question.options.length > 0 ? userInputResponseValue(content, userInputOtherFieldId(question.id, questionIds)) ?? userInputResponseValue(content, question.id) : userInputResponseValue(content, question.id);
26407
- if (value === void 0) {
26884
+ const answerValues = [];
26885
+ const value = userInputResponseValue(content, question.id);
26886
+ if (value !== void 0) {
26887
+ answerValues.push(...Array.isArray(value) ? value.map(String) : [String(value)]);
26888
+ }
26889
+ if (question.isOther && question.options != null && question.options.length > 0) {
26890
+ const note = userInputResponseValue(content, userInputNoteFieldId(question.id, questionIds));
26891
+ if (note !== void 0) {
26892
+ const notes = Array.isArray(note) ? note : [note];
26893
+ answerValues.push(...notes.map((item) => `${USER_INPUT_NOTE_PREFIX}${String(item).trim()}`));
26894
+ }
26895
+ }
26896
+ if (answerValues.length === 0) {
26408
26897
  continue;
26409
26898
  }
26410
26899
  answers[question.id] = {
26411
- answers: Array.isArray(value) ? value.map(String) : [String(value)]
26900
+ answers: answerValues
26412
26901
  };
26413
26902
  }
26414
26903
  return { answers };
@@ -26498,31 +26987,31 @@ function isCodexAuthRequest(request) {
26498
26987
 
26499
26988
  // node_modules/open/index.js
26500
26989
  import process11 from "node:process";
26501
- import path4 from "node:path";
26990
+ import path5 from "node:path";
26502
26991
  import { fileURLToPath } from "node:url";
26503
26992
  import childProcess4 from "node:child_process";
26504
- import fs6, { constants as fsConstants2 } from "node:fs/promises";
26993
+ import fs7, { constants as fsConstants2 } from "node:fs/promises";
26505
26994
 
26506
26995
  // node_modules/wsl-utils/index.js
26507
- import path3 from "node:path";
26996
+ import path4 from "node:path";
26508
26997
  import { promisify as promisify2 } from "node:util";
26509
26998
  import childProcess2 from "node:child_process";
26510
- import fs5, { constants as fsConstants } from "node:fs/promises";
26999
+ import fs6, { constants as fsConstants } from "node:fs/promises";
26511
27000
 
26512
27001
  // node_modules/is-wsl/index.js
26513
27002
  import process3 from "node:process";
26514
27003
  import os from "node:os";
26515
- import fs4 from "node:fs";
27004
+ import fs5 from "node:fs";
26516
27005
 
26517
27006
  // node_modules/is-inside-container/index.js
26518
- import fs3 from "node:fs";
27007
+ import fs4 from "node:fs";
26519
27008
 
26520
27009
  // node_modules/is-docker/index.js
26521
- import fs2 from "node:fs";
27010
+ import fs3 from "node:fs";
26522
27011
  var isDockerCached;
26523
27012
  function hasDockerEnv() {
26524
27013
  try {
26525
- fs2.statSync("/.dockerenv");
27014
+ fs3.statSync("/.dockerenv");
26526
27015
  return true;
26527
27016
  } catch {
26528
27017
  return false;
@@ -26530,7 +27019,7 @@ function hasDockerEnv() {
26530
27019
  }
26531
27020
  function hasDockerCGroup() {
26532
27021
  try {
26533
- return fs2.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
27022
+ return fs3.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
26534
27023
  } catch {
26535
27024
  return false;
26536
27025
  }
@@ -26546,7 +27035,7 @@ function isDocker() {
26546
27035
  var cachedResult;
26547
27036
  var hasContainerEnv = () => {
26548
27037
  try {
26549
- fs3.statSync("/run/.containerenv");
27038
+ fs4.statSync("/run/.containerenv");
26550
27039
  return true;
26551
27040
  } catch {
26552
27041
  return false;
@@ -26571,12 +27060,12 @@ var isWsl = () => {
26571
27060
  return true;
26572
27061
  }
26573
27062
  try {
26574
- if (fs4.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
27063
+ if (fs5.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
26575
27064
  return !isInsideContainer();
26576
27065
  }
26577
27066
  } catch {
26578
27067
  }
26579
- if (fs4.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs4.existsSync("/run/WSL")) {
27068
+ if (fs5.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs5.existsSync("/run/WSL")) {
26580
27069
  return !isInsideContainer();
26581
27070
  }
26582
27071
  return false;
@@ -26644,14 +27133,14 @@ var wslDrivesMountPoint = /* @__PURE__ */ (() => {
26644
27133
  const configFilePath = "/etc/wsl.conf";
26645
27134
  let isConfigFileExists = false;
26646
27135
  try {
26647
- await fs5.access(configFilePath, fsConstants.F_OK);
27136
+ await fs6.access(configFilePath, fsConstants.F_OK);
26648
27137
  isConfigFileExists = true;
26649
27138
  } catch {
26650
27139
  }
26651
27140
  if (!isConfigFileExists) {
26652
27141
  return defaultMountPoint;
26653
27142
  }
26654
- const configContent = await fs5.readFile(configFilePath, { encoding: "utf8" });
27143
+ const configContent = await fs6.readFile(configFilePath, { encoding: "utf8" });
26655
27144
  const parsedMountPoint = parseMountPointFromConfig(configContent);
26656
27145
  if (parsedMountPoint === void 0) {
26657
27146
  return defaultMountPoint;
@@ -26671,7 +27160,7 @@ var canAccessPowerShell = async () => {
26671
27160
  canAccessPowerShellPromise ??= (async () => {
26672
27161
  try {
26673
27162
  const psPath = await powerShellPath2();
26674
- await fs5.access(psPath, fsConstants.X_OK);
27163
+ await fs6.access(psPath, fsConstants.X_OK);
26675
27164
  return true;
26676
27165
  } catch {
26677
27166
  return false;
@@ -26684,7 +27173,7 @@ var wslDefaultBrowser = async () => {
26684
27173
  const command = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
26685
27174
  const { stdout } = await executePowerShell(command, {
26686
27175
  powerShellPath: psPath,
26687
- cwd: path3.dirname(psPath)
27176
+ cwd: path4.dirname(psPath)
26688
27177
  });
26689
27178
  return stdout.trim();
26690
27179
  };
@@ -26890,8 +27379,8 @@ var is_in_ssh_default = isInSsh;
26890
27379
 
26891
27380
  // node_modules/open/index.js
26892
27381
  var fallbackAttemptSymbol = /* @__PURE__ */ Symbol("fallbackAttempt");
26893
- var __dirname = import.meta.url ? path4.dirname(fileURLToPath(import.meta.url)) : "";
26894
- var localXdgOpenPath = path4.join(__dirname, "xdg-open");
27382
+ var __dirname = import.meta.url ? path5.dirname(fileURLToPath(import.meta.url)) : "";
27383
+ var localXdgOpenPath = path5.join(__dirname, "xdg-open");
26895
27384
  var { platform, arch } = process11;
26896
27385
  var tryEachApp = async (apps2, opener) => {
26897
27386
  if (apps2.length === 0) {
@@ -27033,7 +27522,7 @@ var baseOpen = async (options) => {
27033
27522
  childProcessOptions.stdio = "ignore";
27034
27523
  }
27035
27524
  if (is_wsl_default) {
27036
- childProcessOptions.cwd = path4.dirname(command);
27525
+ childProcessOptions.cwd = path5.dirname(command);
27037
27526
  }
27038
27527
  } else {
27039
27528
  if (app) {
@@ -27042,7 +27531,7 @@ var baseOpen = async (options) => {
27042
27531
  const isBundled = !__dirname || __dirname === "/";
27043
27532
  let exeLocalXdgOpen = false;
27044
27533
  try {
27045
- await fs6.access(localXdgOpenPath, fsConstants2.X_OK);
27534
+ await fs7.access(localXdgOpenPath, fsConstants2.X_OK);
27046
27535
  exeLocalXdgOpen = true;
27047
27536
  } catch {
27048
27537
  }
@@ -27330,7 +27819,7 @@ var package_default = {
27330
27819
  publishConfig: {
27331
27820
  access: "public"
27332
27821
  },
27333
- version: "1.10.0-agentconnect.2",
27822
+ version: "1.12.0-agentconnect.1",
27334
27823
  description: "",
27335
27824
  main: "dist/index.js",
27336
27825
  bin: {
@@ -27355,428 +27844,132 @@ var package_default = {
27355
27844
  "package:linux-x64": "cd dist/bin && zip codex-acp-x64-linux.zip codex-acp-x64-linux",
27356
27845
  "package:linux-arm64": "cd dist/bin && zip codex-acp-arm64-linux.zip codex-acp-arm64-linux",
27357
27846
  "package:darwin-x64": "cd dist/bin && zip codex-acp-x64-darwin.zip codex-acp-x64-darwin",
27358
- "package:darwin-arm64": "cd dist/bin && zip codex-acp-arm64-darwin.zip codex-acp-arm64-darwin",
27359
- "package:win-x64": "cd dist/bin && zip codex-acp-x64-windows.zip codex-acp-x64-windows.exe",
27360
- "package:win-arm64": "cd dist/bin && zip codex-acp-arm64-windows.zip codex-acp-arm64-windows.exe",
27361
- start: "node --import tsx src/index.ts",
27362
- "example:simple-client": "node --import tsx examples/simple-client.ts",
27363
- "example:steering": "node --import tsx examples/steering.ts",
27364
- "example:steering:multistep": "node --import tsx examples/steering.ts",
27365
- "generate-types": "./node_modules/.bin/codex app-server generate-ts --out src/app-server",
27366
- "release:preflight": "bash scripts/release-preflight.sh",
27367
- test: "vitest run --no-file-parallelism --retry=2",
27368
- "test:e2e": "npm run build && RUN_E2E_TESTS=true vitest run --no-file-parallelism --retry=2 src/__tests__/CodexACPAgent/e2e",
27369
- "test:watch": "vitest",
27370
- typecheck: "tsc --noEmit && tsc --noEmit -p examples/tsconfig.json",
27371
- "codex-test": "tsx .claude/skills/run-codex/scripts/run-codex-test.ts"
27372
- },
27373
- homepage: "https://github.com/agentconnect-md/codex-acp#readme",
27374
- bugs: {
27375
- url: "https://github.com/agentconnect-md/codex-acp/issues"
27376
- },
27377
- repository: {
27378
- type: "git",
27379
- url: "git+https://github.com/agentconnect-md/codex-acp.git"
27380
- },
27381
- keywords: [],
27382
- author: "Agent Client Protocol",
27383
- license: "Apache-2.0",
27384
- type: "module",
27385
- devDependencies: {
27386
- "@types/node": "^26.1.0",
27387
- esbuild: "^0.28.2",
27388
- "mcp-hello-world": "^1.1.2",
27389
- tsx: "^4.23.12",
27390
- typescript: "^7.0.2",
27391
- vitest: "^4.1.11"
27392
- },
27393
- dependencies: {
27394
- "@agentclientprotocol/sdk": "^1.4.0",
27395
- "@openai/codex": "^0.153.3",
27396
- diff: "^9.0.0",
27397
- open: "^11.0.1",
27398
- "vscode-jsonrpc": "^9.0.1",
27399
- zod: "^4.0.0"
27400
- }
27401
- };
27402
-
27403
- // src/CollaborationModeConfig.ts
27404
- var COLLABORATION_MODE_CONFIG_ID = "collaboration_mode";
27405
- var DEFAULT_COLLABORATION_MODE = "default";
27406
- var PLAN_COLLABORATION_MODE = "plan";
27407
- function createCollaborationModeConfigOption(currentValue) {
27408
- return {
27409
- id: COLLABORATION_MODE_CONFIG_ID,
27410
- name: "Collaboration mode",
27411
- description: "How Codex collaborates for subsequent turns",
27412
- category: "collaboration_mode",
27413
- type: "select",
27414
- currentValue,
27415
- options: [
27416
- { value: DEFAULT_COLLABORATION_MODE, name: "Default" },
27417
- { value: PLAN_COLLABORATION_MODE, name: "Plan", description: "Plan before making changes" }
27418
- ]
27419
- };
27420
- }
27421
- function parseCollaborationMode(value) {
27422
- if (value === DEFAULT_COLLABORATION_MODE) return DEFAULT_COLLABORATION_MODE;
27423
- if (value === PLAN_COLLABORATION_MODE) return PLAN_COLLABORATION_MODE;
27424
- return null;
27425
- }
27426
- function createCodexCollaborationMode(mode, currentModelId) {
27427
- const modelId = ModelId.fromString(currentModelId);
27428
- return {
27429
- mode,
27430
- settings: {
27431
- model: modelId.model,
27432
- reasoning_effort: modelId.effort,
27433
- developer_instructions: null
27434
- }
27435
- };
27436
- }
27437
-
27438
- // src/PathUtils.ts
27439
- import path5 from "node:path";
27440
- function isAbsolutePathLike(value) {
27441
- const trimmed = value.trim();
27442
- return path5.isAbsolute(trimmed) || isWindowsAbsolutePath(trimmed);
27443
- }
27444
- function arePathsEqual(left, right) {
27445
- return normalizePathForComparison(left) === normalizePathForComparison(right);
27446
- }
27447
- function arePathBasenamesEqual(left, right) {
27448
- const leftBase = path5.posix.basename(normalizePathForComparison(left));
27449
- const rightBase = path5.posix.basename(normalizePathForComparison(right));
27450
- if (shouldComparePathCaseInsensitive(left) || shouldComparePathCaseInsensitive(right)) {
27451
- return leftBase.toLowerCase() === rightBase.toLowerCase();
27452
- }
27453
- return leftBase === rightBase;
27454
- }
27455
- function normalizePathForComparison(value) {
27456
- const trimmed = value.trim();
27457
- if (trimmed.length === 0) {
27458
- return "";
27459
- }
27460
- if (isWindowsAbsolutePath(trimmed)) {
27461
- const normalized2 = path5.win32.normalize(trimmed).replace(/\\/g, "/");
27462
- return trimTrailingPathSeparators(normalized2).toLowerCase();
27463
- }
27464
- const pathForComparison = path5.isAbsolute(trimmed) ? trimmed : trimmed.replace(/\\/g, "/");
27465
- const normalized = path5.posix.normalize(pathForComparison);
27466
- return trimTrailingPathSeparators(normalized);
27467
- }
27468
- function isWindowsAbsolutePath(value) {
27469
- const portableValue = value.replace(/\\/g, "/");
27470
- return /^[A-Za-z]:\//.test(portableValue) || /^\/\/[^/]+\/[^/]+/.test(portableValue);
27471
- }
27472
- function shouldComparePathCaseInsensitive(value) {
27473
- return isWindowsAbsolutePath(value) || /^[A-Za-z]:/.test(value) || value.includes("\\");
27474
- }
27475
- function trimTrailingPathSeparators(value) {
27476
- let trimmed = value;
27477
- while (trimmed.endsWith("/") && !isRootPath(trimmed)) {
27478
- trimmed = trimmed.slice(0, -1);
27479
- }
27480
- return trimmed;
27481
- }
27482
- function isRootPath(value) {
27483
- return value === "/" || /^[A-Za-z]:\/$/.test(value) || /^\/\/[^/]+\/[^/]+\/$/.test(value);
27484
- }
27485
-
27486
- // src/AgentFileChangeReport.ts
27487
- import fs7 from "node:fs";
27488
- import path6 from "node:path";
27489
- var AGENT_FILE_CHANGE_REPORT_VERSION = 1;
27490
- var AGENT_FILE_CHANGE_REPORT_TIMEOUT_MS = 3e4;
27491
- var AGENT_FILE_CHANGE_REPORT_MAX_PATHS = 1024;
27492
- var AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH = 4096;
27493
- var AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES = 256 * 1024;
27494
- var AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH = 2e3;
27495
- var AGENT_FILE_CHANGE_REPORT_OUTPUT_SCHEMA = {
27496
- type: "object",
27497
- additionalProperties: false,
27498
- required: ["paths", "complete", "uncertainty"],
27499
- properties: {
27500
- paths: {
27501
- type: "array",
27502
- items: { type: "string" }
27503
- },
27504
- complete: { type: "boolean" },
27505
- uncertainty: {
27506
- anyOf: [{
27507
- type: "string",
27508
- maxLength: AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH
27509
- }, {
27510
- type: "null"
27511
- }]
27512
- }
27513
- }
27514
- };
27515
- var AGENT_FILE_CHANGE_REPORT_DEVELOPER_INSTRUCTIONS = `You are running an internal, read-only file-change audit for the immediately preceding turn.
27516
- Do not modify files, run commands that can modify files, or ask the user questions.
27517
- Report files that the preceding turn causally created, modified, deleted, or moved, including changes made by shell commands, version-control commands, generators, and child processes.
27518
- Do not report files that were only read or inspected.
27519
- You may use read-only inspection when needed. If the list may be incomplete, set complete to false and briefly explain why in uncertainty.`;
27520
- function createAgentFileChangeReportPrompt(workspace) {
27521
- return `List the paths changed by the immediately preceding turn.
27522
- Return only the structured result required by the output schema.
27523
- Relative paths are resolved against the working directory.
27524
- Working directory: ${JSON.stringify(workspace.cwd)}
27525
- Additional allowed directories: ${JSON.stringify(workspace.additionalDirectories)}`;
27526
- }
27527
- function parseAgentFileChangeReportRequest(meta3) {
27528
- const jetbrains = asRecord(meta3?.[JETBRAINS_META_KEY]);
27529
- const air = asRecord(jetbrains?.[AIR_META_KEY]);
27530
- const request = asRecord(air?.[AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY]);
27531
- if (request === null || !hasOnlyKeys(request, ["version", "requestId"]) || request["version"] !== AGENT_FILE_CHANGE_REPORT_VERSION) {
27532
- return null;
27533
- }
27534
- const requestId = request["requestId"];
27535
- if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) {
27536
- return null;
27537
- }
27538
- return { version: AGENT_FILE_CHANGE_REPORT_VERSION, requestId };
27539
- }
27540
- function createReportedAgentFileChangeReport(requestId, turn, workspace) {
27541
- const modelReport = parseModelFileChangeReport(turn);
27542
- const normalized = normalizeModelFileChangeReport(modelReport, workspace);
27543
- return fitReportedAgentFileChangeReport({
27544
- version: AGENT_FILE_CHANGE_REPORT_VERSION,
27545
- requestId,
27546
- status: "reported",
27547
- ...normalized
27548
- });
27549
- }
27550
- function createUnavailableAgentFileChangeReport(requestId, reason) {
27551
- return {
27552
- version: AGENT_FILE_CHANGE_REPORT_VERSION,
27553
- requestId,
27554
- status: "unavailable",
27555
- reason
27556
- };
27557
- }
27558
- var AgentFileChangeReportError = class extends Error {
27559
- reason;
27560
- constructor(reason, message) {
27561
- super(message);
27562
- this.name = "AgentFileChangeReportError";
27563
- this.reason = reason;
27564
- }
27565
- };
27566
- function parseModelFileChangeReport(turn) {
27567
- switch (turn.status) {
27568
- case "interrupted":
27569
- throw new AgentFileChangeReportError("cancelled", "The audit turn was interrupted");
27570
- case "failed":
27571
- throw new AgentFileChangeReportError(
27572
- "providerError",
27573
- `The audit turn failed${turn.error?.message ? `: ${turn.error.message}` : ""}`
27574
- );
27575
- case "inProgress":
27576
- throw new AgentFileChangeReportError("notReported", "The audit turn did not complete");
27577
- case "completed":
27578
- break;
27579
- }
27580
- let text = null;
27581
- for (let index = turn.items.length - 1; index >= 0; index -= 1) {
27582
- const item = turn.items[index];
27583
- if (item?.type === "agentMessage") {
27584
- text = item.text;
27585
- break;
27586
- }
27587
- }
27588
- if (text === null) {
27589
- throw new AgentFileChangeReportError("notReported", "The audit turn returned no agent message");
27590
- }
27591
- let value;
27592
- try {
27593
- value = JSON.parse(text);
27594
- } catch {
27595
- throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned invalid JSON");
27596
- }
27597
- const report = asRecord(value);
27598
- if (report === null || !hasOnlyKeys(report, ["paths", "complete", "uncertainty"])) {
27599
- throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned an invalid object");
27600
- }
27601
- const paths = report["paths"];
27602
- const complete = report["complete"];
27603
- const uncertainty = report["uncertainty"];
27604
- if (!Array.isArray(paths) || !paths.every((item) => typeof item === "string") || typeof complete !== "boolean" || uncertainty !== void 0 && uncertainty !== null && typeof uncertainty !== "string") {
27605
- throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned invalid fields");
27606
- }
27607
- const normalizedUncertainty = typeof uncertainty === "string" ? uncertainty.trim() : void 0;
27608
- if (normalizedUncertainty !== void 0 && normalizedUncertainty.length > AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH) {
27609
- throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned oversized uncertainty");
27610
- }
27611
- return {
27612
- paths,
27613
- complete,
27614
- ...normalizedUncertainty ? { uncertainty: normalizedUncertainty } : {}
27615
- };
27616
- }
27617
- function fitReportedAgentFileChangeReport(report) {
27618
- const paths = [...report.paths];
27619
- let truncated = report.truncated;
27620
- let fitted = report;
27621
- while (Buffer.byteLength(JSON.stringify(fitted), "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
27622
- if (paths.length === 0) {
27623
- throw new AgentFileChangeReportError("invalidOutput", "The audit report exceeds the wire limit");
27624
- }
27625
- paths.pop();
27626
- truncated = true;
27627
- fitted = {
27628
- ...report,
27629
- paths: [...paths],
27630
- declaredComplete: false,
27631
- truncated
27632
- };
27633
- }
27634
- return fitted;
27635
- }
27636
- function normalizeModelFileChangeReport(report, workspace) {
27637
- const cwd = normalizeWorkspaceRoot(workspace.cwd);
27638
- if (cwd === null) {
27639
- throw new AgentFileChangeReportError("providerError", "The session working directory is not absolute");
27640
- }
27641
- const roots = [cwd, ...workspace.additionalDirectories.flatMap((directory) => {
27642
- const root = normalizeWorkspaceRoot(directory);
27643
- return root === null || root.flavor !== cwd.flavor ? [] : [root];
27644
- })];
27645
- const paths = [];
27646
- const seen = /* @__PURE__ */ new Set();
27647
- let totalBytes = 0;
27648
- let truncated = false;
27649
- for (const reportedPath of report.paths) {
27650
- if (reportedPath.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
27651
- truncated = true;
27652
- continue;
27653
- }
27654
- const normalized = normalizeReportedPath(reportedPath, cwd, roots);
27655
- if (normalized === null || normalized.value.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
27656
- truncated = true;
27657
- continue;
27658
- }
27659
- const key = normalized.flavor === "windows" ? normalized.value.toLowerCase() : normalized.value;
27660
- if (seen.has(key)) {
27661
- continue;
27662
- }
27663
- const bytes = Buffer.byteLength(normalized.value, "utf8");
27664
- if (paths.length >= AGENT_FILE_CHANGE_REPORT_MAX_PATHS || totalBytes + bytes > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
27665
- truncated = true;
27666
- continue;
27667
- }
27668
- seen.add(key);
27669
- paths.push(normalized.value);
27670
- totalBytes += bytes;
27847
+ "package:darwin-arm64": "cd dist/bin && zip codex-acp-arm64-darwin.zip codex-acp-arm64-darwin",
27848
+ "package:win-x64": "cd dist/bin && zip codex-acp-x64-windows.zip codex-acp-x64-windows.exe",
27849
+ "package:win-arm64": "cd dist/bin && zip codex-acp-arm64-windows.zip codex-acp-arm64-windows.exe",
27850
+ start: "node --import tsx src/index.ts",
27851
+ "example:simple-client": "node --import tsx examples/simple-client.ts",
27852
+ "example:steering": "node --import tsx examples/steering.ts",
27853
+ "example:steering:multistep": "node --import tsx examples/steering.ts",
27854
+ "generate-types": "./node_modules/.bin/codex app-server generate-ts --out src/app-server",
27855
+ "release:preflight": "bash scripts/release-preflight.sh",
27856
+ test: "vitest run --no-file-parallelism --retry=2",
27857
+ "test:e2e": "npm run build && RUN_E2E_TESTS=true vitest run --no-file-parallelism --retry=2 src/__tests__/CodexACPAgent/e2e",
27858
+ "test:watch": "vitest",
27859
+ typecheck: "tsc --noEmit && tsc --noEmit -p examples/tsconfig.json",
27860
+ "codex-test": "tsx .claude/skills/run-codex/scripts/run-codex-test.ts"
27861
+ },
27862
+ homepage: "https://github.com/agentconnect-md/codex-acp#readme",
27863
+ bugs: {
27864
+ url: "https://github.com/agentconnect-md/codex-acp/issues"
27865
+ },
27866
+ repository: {
27867
+ type: "git",
27868
+ url: "git+https://github.com/agentconnect-md/codex-acp.git"
27869
+ },
27870
+ keywords: [],
27871
+ author: "Agent Client Protocol",
27872
+ license: "Apache-2.0",
27873
+ type: "module",
27874
+ devDependencies: {
27875
+ "@types/node": "^26.1.0",
27876
+ esbuild: "^0.28.2",
27877
+ "mcp-hello-world": "^1.1.2",
27878
+ tsx: "^4.23.12",
27879
+ typescript: "^7.0.2",
27880
+ vitest: "^4.1.11"
27881
+ },
27882
+ dependencies: {
27883
+ "@agentclientprotocol/sdk": "^1.4.0",
27884
+ "@openai/codex": "^0.154.0",
27885
+ diff: "^9.0.0",
27886
+ open: "^11.0.1",
27887
+ "vscode-jsonrpc": "^9.0.1",
27888
+ zod: "^4.0.0"
27671
27889
  }
27890
+ };
27891
+
27892
+ // src/CollaborationModeConfig.ts
27893
+ var COLLABORATION_MODE_CONFIG_ID = "collaboration_mode";
27894
+ var DEFAULT_COLLABORATION_MODE = "default";
27895
+ var PLAN_COLLABORATION_MODE = "plan";
27896
+ function createCollaborationModeConfigOption(currentValue) {
27672
27897
  return {
27673
- paths,
27674
- declaredComplete: report.complete && !truncated,
27675
- truncated,
27676
- ...report.uncertainty ? { uncertainty: report.uncertainty } : {}
27898
+ id: COLLABORATION_MODE_CONFIG_ID,
27899
+ name: "Collaboration mode",
27900
+ description: "How Codex collaborates for subsequent turns",
27901
+ category: "collaboration_mode",
27902
+ type: "select",
27903
+ currentValue,
27904
+ options: [
27905
+ { value: DEFAULT_COLLABORATION_MODE, name: "Default" },
27906
+ { value: PLAN_COLLABORATION_MODE, name: "Plan", description: "Plan before making changes" }
27907
+ ]
27677
27908
  };
27678
27909
  }
27679
- function normalizeWorkspaceRoot(value) {
27680
- const trimmed = value.trim();
27681
- if (!isValidPathText(trimmed)) {
27682
- return null;
27683
- }
27684
- if (isWindowsAbsolutePath2(trimmed)) {
27685
- return canonicalizeWorkspaceRoot({
27686
- value: path6.win32.normalize(trimmed.replace(/\//g, "\\")),
27687
- flavor: "windows"
27688
- });
27689
- }
27690
- if (path6.posix.isAbsolute(trimmed)) {
27691
- return canonicalizeWorkspaceRoot({
27692
- value: path6.posix.normalize(trimmed.replace(/\\/g, "/")),
27693
- flavor: "posix"
27694
- });
27695
- }
27910
+ function parseCollaborationMode(value) {
27911
+ if (value === DEFAULT_COLLABORATION_MODE) return DEFAULT_COLLABORATION_MODE;
27912
+ if (value === PLAN_COLLABORATION_MODE) return PLAN_COLLABORATION_MODE;
27696
27913
  return null;
27697
27914
  }
27698
- function normalizeReportedPath(value, cwd, roots) {
27915
+ function createCodexCollaborationMode(mode, currentModelId) {
27916
+ const modelId = ModelId.fromString(currentModelId);
27917
+ return {
27918
+ mode,
27919
+ settings: {
27920
+ model: modelId.model,
27921
+ reasoning_effort: modelId.effort,
27922
+ developer_instructions: null
27923
+ }
27924
+ };
27925
+ }
27926
+
27927
+ // src/PathUtils.ts
27928
+ import path6 from "node:path";
27929
+ function isAbsolutePathLike(value) {
27699
27930
  const trimmed = value.trim();
27700
- if (!isValidPathText(trimmed) || /^[A-Za-z]:[^\\/]/.test(trimmed) || /^\\\\[?.]\\/.test(trimmed) || /^(?:\\\\|\/\/)/.test(trimmed) && !isWindowsAbsolutePath2(trimmed) || cwd.flavor === "windows" && /^\\(?!\\)/.test(trimmed) || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(trimmed)) {
27701
- return null;
27702
- }
27703
- let candidate;
27704
- if (isWindowsAbsolutePath2(trimmed)) {
27705
- candidate = { value: path6.win32.normalize(trimmed.replace(/\//g, "\\")), flavor: "windows" };
27706
- } else if (path6.posix.isAbsolute(trimmed)) {
27707
- candidate = { value: path6.posix.normalize(trimmed.replace(/\\/g, "/")), flavor: "posix" };
27708
- } else if (cwd.flavor === "windows") {
27709
- candidate = {
27710
- value: path6.win32.resolve(cwd.value, trimmed.replace(/\//g, "\\")),
27711
- flavor: "windows"
27712
- };
27713
- } else {
27714
- candidate = {
27715
- value: path6.posix.resolve(cwd.value, trimmed.replace(/\\/g, "/")),
27716
- flavor: "posix"
27717
- };
27718
- }
27719
- candidate = canonicalizeReportedPath(candidate);
27720
- return roots.some((root) => pathIsStrictlyInside(root, candidate)) ? candidate : null;
27931
+ return path6.isAbsolute(trimmed) || isWindowsAbsolutePath2(trimmed);
27721
27932
  }
27722
- function canonicalizeWorkspaceRoot(root) {
27723
- if (!isNativePathFlavor(root.flavor)) {
27724
- return root;
27725
- }
27726
- return { ...root, value: canonicalizeFromExistingAncestor(root.value) };
27933
+ function arePathsEqual(left, right) {
27934
+ return normalizePathForComparison(left) === normalizePathForComparison(right);
27727
27935
  }
27728
- function canonicalizeReportedPath(candidate) {
27729
- if (!isNativePathFlavor(candidate.flavor)) {
27730
- return candidate;
27936
+ function arePathBasenamesEqual(left, right) {
27937
+ const leftBase = path6.posix.basename(normalizePathForComparison(left));
27938
+ const rightBase = path6.posix.basename(normalizePathForComparison(right));
27939
+ if (shouldComparePathCaseInsensitive(left) || shouldComparePathCaseInsensitive(right)) {
27940
+ return leftBase.toLowerCase() === rightBase.toLowerCase();
27731
27941
  }
27732
- const parent = canonicalizeFromExistingAncestor(path6.dirname(candidate.value));
27733
- return { ...candidate, value: path6.resolve(parent, path6.basename(candidate.value)) };
27942
+ return leftBase === rightBase;
27734
27943
  }
27735
- function canonicalizeFromExistingAncestor(value) {
27736
- const original = path6.resolve(value);
27737
- let current = original;
27738
- const missingSegments = [];
27739
- while (true) {
27740
- try {
27741
- const canonical = fs7.realpathSync.native(current);
27742
- return path6.resolve(canonical, ...missingSegments.reverse());
27743
- } catch (error51) {
27744
- if (!isMissingPathError(error51)) return original;
27745
- }
27746
- const parent = path6.dirname(current);
27747
- if (parent === current) return original;
27748
- missingSegments.push(path6.basename(current));
27749
- current = parent;
27944
+ function normalizePathForComparison(value) {
27945
+ const trimmed = value.trim();
27946
+ if (trimmed.length === 0) {
27947
+ return "";
27750
27948
  }
27751
- }
27752
- function isMissingPathError(error51) {
27753
- if (typeof error51 !== "object" || error51 === null || !("code" in error51)) return false;
27754
- return error51.code === "ENOENT" || error51.code === "ENOTDIR";
27755
- }
27756
- function isNativePathFlavor(flavor) {
27757
- return process.platform === "win32" ? flavor === "windows" : flavor === "posix";
27758
- }
27759
- function pathIsStrictlyInside(root, candidate) {
27760
- if (root.flavor !== candidate.flavor) {
27761
- return false;
27949
+ if (isWindowsAbsolutePath2(trimmed)) {
27950
+ const normalized2 = path6.win32.normalize(trimmed).replace(/\\/g, "/");
27951
+ return trimTrailingPathSeparators(normalized2).toLowerCase();
27762
27952
  }
27763
- const pathImplementation = root.flavor === "windows" ? path6.win32 : path6.posix;
27764
- const relative = pathImplementation.relative(root.value, candidate.value);
27765
- return relative.length > 0 && !pathImplementation.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${pathImplementation.sep}`);
27953
+ const pathForComparison = path6.isAbsolute(trimmed) ? trimmed : trimmed.replace(/\\/g, "/");
27954
+ const normalized = path6.posix.normalize(pathForComparison);
27955
+ return trimTrailingPathSeparators(normalized);
27766
27956
  }
27767
27957
  function isWindowsAbsolutePath2(value) {
27768
- const portable = value.replace(/\\/g, "/");
27769
- return /^[A-Za-z]:\//.test(portable) || /^\/\/[^/]+\/[^/]+(?:\/|$)/.test(portable);
27958
+ const portableValue = value.replace(/\\/g, "/");
27959
+ return /^[A-Za-z]:\//.test(portableValue) || /^\/\/[^/]+\/[^/]+/.test(portableValue);
27770
27960
  }
27771
- function isValidPathText(value) {
27772
- return value.length > 0 && !/[\u0000-\u001F\u007F-\u009F]/.test(value);
27961
+ function shouldComparePathCaseInsensitive(value) {
27962
+ return isWindowsAbsolutePath2(value) || /^[A-Za-z]:/.test(value) || value.includes("\\");
27773
27963
  }
27774
- function asRecord(value) {
27775
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
27964
+ function trimTrailingPathSeparators(value) {
27965
+ let trimmed = value;
27966
+ while (trimmed.endsWith("/") && !isRootPath(trimmed)) {
27967
+ trimmed = trimmed.slice(0, -1);
27968
+ }
27969
+ return trimmed;
27776
27970
  }
27777
- function hasOnlyKeys(value, allowed) {
27778
- const allowedKeys = new Set(allowed);
27779
- return Object.keys(value).every((key) => allowedKeys.has(key));
27971
+ function isRootPath(value) {
27972
+ return value === "/" || /^[A-Za-z]:\/$/.test(value) || /^\/\/[^/]+\/[^/]+\/$/.test(value);
27780
27973
  }
27781
27974
 
27782
27975
  // src/subagents/CodexSubagentSubscriptions.ts
@@ -27900,6 +28093,7 @@ async function forkSession(request, additionalDirectories, dependencies) {
27900
28093
  const lastTurnId = await resolveForkTurnId(request, dependencies.codexClient);
27901
28094
  const prepared = await dependencies.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []);
27902
28095
  const response = await dependencies.codexClient.threadFork({
28096
+ excludeTurns: true,
27903
28097
  config: prepared.config,
27904
28098
  cwd: request.cwd,
27905
28099
  ...lastTurnId !== void 0 && { lastTurnId },
@@ -27922,10 +28116,7 @@ async function forkSession(request, additionalDirectories, dependencies) {
27922
28116
  async function resolveForkTurnId(request, codexClient) {
27923
28117
  const forkPoint = readAirForkPoint(request._meta);
27924
28118
  if (!forkPoint) return void 0;
27925
- const history = await codexClient.threadRead({
27926
- threadId: request.sessionId,
27927
- includeTurns: true
27928
- });
28119
+ const history = await codexClient.threadReadWithHistory(request.sessionId);
27929
28120
  const candidateIds = airForkMessageIdCandidates(forkPoint.messageId);
27930
28121
  const itemTurnId = candidateIds.map((candidateId) => history.thread.turns.find((turn) => turn.items.some((item) => item.id === candidateId))?.id).find((turnId) => turnId !== void 0);
27931
28122
  if (itemTurnId) return itemTurnId;
@@ -28380,6 +28571,7 @@ var CodexAcpClient = class {
28380
28571
  const prepared = await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []);
28381
28572
  await this.restrictResumedHttpMcpServers(request.sessionId, prepared);
28382
28573
  const response = await this.codexClient.threadResume({
28574
+ excludeTurns: true,
28383
28575
  config: prepared.config,
28384
28576
  cwd: request.cwd,
28385
28577
  modelProvider: await this.getResumeModelProvider(),
@@ -28419,6 +28611,7 @@ var CodexAcpClient = class {
28419
28611
  const prepared = await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []);
28420
28612
  await this.restrictResumedHttpMcpServers(request.sessionId, prepared);
28421
28613
  const response = await this.codexClient.threadResume({
28614
+ excludeTurns: true,
28422
28615
  config: prepared.config,
28423
28616
  cwd: request.cwd,
28424
28617
  modelProvider: await this.getResumeModelProvider(),
@@ -28426,10 +28619,10 @@ var CodexAcpClient = class {
28426
28619
  ...this.permissionProfileSelection(initialAgentMode, request.cwd, additionalDirectories)
28427
28620
  });
28428
28621
  onSubscribed?.();
28429
- const historyResponse = await this.codexClient.threadRead({
28430
- threadId: response.thread.id,
28431
- includeTurns: true
28432
- });
28622
+ const thread = response.thread.historyMode === "paginated" ? {
28623
+ ...response.thread,
28624
+ turns: response.turnsBackwardsCursor === null ? [] : await this.codexClient.threadReadHistory(response.thread.id, response.turnsBackwardsCursor)
28625
+ } : (await this.codexClient.threadReadWithHistory(response.thread.id)).thread;
28433
28626
  const codexModels = await this.fetchAvailableModels();
28434
28627
  const currentModelId = this.createModelId(codexModels, response.model, response.reasoningEffort).toString();
28435
28628
  return {
@@ -28439,16 +28632,13 @@ var CodexAcpClient = class {
28439
28632
  collaborationMode: this.getCollaborationMode(response.thread.id),
28440
28633
  modelProvider: response.modelProvider,
28441
28634
  currentServiceTier: response.serviceTier ?? null,
28442
- thread: historyResponse.thread,
28635
+ thread,
28443
28636
  additionalDirectories,
28444
28637
  fullAccessHttpMcpServers: prepared.fullAccessHttpMcpServers
28445
28638
  };
28446
28639
  }
28447
28640
  async readSessionThread(sessionId) {
28448
- return (await this.codexClient.threadRead({
28449
- threadId: sessionId,
28450
- includeTurns: true
28451
- })).thread;
28641
+ return (await this.codexClient.threadReadWithHistory(sessionId)).thread;
28452
28642
  }
28453
28643
  async newSession(request) {
28454
28644
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
@@ -28583,7 +28773,7 @@ var CodexAcpClient = class {
28583
28773
  baseUrl: activeProvider.baseUrl
28584
28774
  });
28585
28775
  const mergedConfig = {
28586
- ...mergeGatewayConfig(this.config, this.gatewayConfig),
28776
+ ...forceGitRootTurnDiffPaths(mergeGatewayConfig(this.config, this.gatewayConfig)),
28587
28777
  projects: Object.fromEntries(sessionRoots2.map((root) => [root, {
28588
28778
  trust_level: "trusted"
28589
28779
  }]))
@@ -28750,100 +28940,6 @@ var CodexAcpClient = class {
28750
28940
  serviceTier
28751
28941
  }, onTurnStarted);
28752
28942
  }
28753
- async runAgentFileChangeReport(params) {
28754
- if (params.signal?.aborted) {
28755
- return createUnavailableAgentFileChangeReport(params.requestId, "cancelled");
28756
- }
28757
- const budget = new AgentFileChangeReportBudget(params.signal);
28758
- let forkThreadId = null;
28759
- let auditTurnId = null;
28760
- let auditTurnCompleted = false;
28761
- let lateStopReason = null;
28762
- try {
28763
- const forkPromise = this.codexClient.threadFork({
28764
- threadId: params.sessionId,
28765
- lastTurnId: params.turnId,
28766
- cwd: params.workspace.cwd,
28767
- approvalPolicy: "never",
28768
- sandbox: "read-only",
28769
- developerInstructions: AGENT_FILE_CHANGE_REPORT_DEVELOPER_INSTRUCTIONS,
28770
- ephemeral: true
28771
- });
28772
- void forkPromise.then((fork2) => {
28773
- if (lateStopReason !== null && forkThreadId === null) {
28774
- void this.unsubscribeAgentFileChangeReportThread(fork2.thread.id, budget);
28775
- }
28776
- }, () => {
28777
- });
28778
- const fork = await budget.wait(forkPromise);
28779
- forkThreadId = fork.thread.id;
28780
- const turnPromise = this.codexClient.runTurn({
28781
- threadId: forkThreadId,
28782
- input: [{
28783
- type: "text",
28784
- text: createAgentFileChangeReportPrompt(params.workspace),
28785
- text_elements: []
28786
- }],
28787
- cwd: params.workspace.cwd,
28788
- approvalPolicy: "never",
28789
- sandboxPolicy: { type: "readOnly", networkAccess: false },
28790
- summary: "none",
28791
- outputSchema: AGENT_FILE_CHANGE_REPORT_OUTPUT_SCHEMA
28792
- }, (turnId) => {
28793
- auditTurnId = turnId;
28794
- if (lateStopReason !== null && forkThreadId !== null) {
28795
- void this.interruptAgentFileChangeReport(forkThreadId, turnId, lateStopReason, budget);
28796
- }
28797
- });
28798
- const outcome = await budget.wait(turnPromise);
28799
- auditTurnCompleted = true;
28800
- return createReportedAgentFileChangeReport(
28801
- params.requestId,
28802
- outcome.turn,
28803
- params.workspace
28804
- );
28805
- } catch (error51) {
28806
- if (error51 instanceof AgentFileChangeReportBudgetError) {
28807
- lateStopReason = error51.reason;
28808
- if (!auditTurnCompleted && forkThreadId !== null && auditTurnId !== null) {
28809
- await this.interruptAgentFileChangeReport(
28810
- forkThreadId,
28811
- auditTurnId,
28812
- error51.reason,
28813
- budget
28814
- );
28815
- }
28816
- return createUnavailableAgentFileChangeReport(params.requestId, error51.reason);
28817
- }
28818
- if (error51 instanceof AgentFileChangeReportError) {
28819
- logger.error("Agent file-change report unavailable", error51);
28820
- return createUnavailableAgentFileChangeReport(params.requestId, error51.reason);
28821
- }
28822
- logger.error("Agent file-change report failed", error51);
28823
- return createUnavailableAgentFileChangeReport(params.requestId, "providerError");
28824
- } finally {
28825
- if (forkThreadId !== null) {
28826
- await this.unsubscribeAgentFileChangeReportThread(forkThreadId, budget);
28827
- }
28828
- }
28829
- }
28830
- async interruptAgentFileChangeReport(threadId, turnId, reason, budget) {
28831
- this.codexClient.markTurnStale(threadId, turnId);
28832
- try {
28833
- await budget.wait(this.codexClient.turnInterrupt({ threadId, turnId }));
28834
- } catch (error51) {
28835
- logger.error(`Failed to interrupt ${reason} agent file-change report`, error51);
28836
- } finally {
28837
- this.codexClient.resolveTurnInterrupted(threadId, turnId);
28838
- }
28839
- }
28840
- async unsubscribeAgentFileChangeReportThread(threadId, budget) {
28841
- try {
28842
- await budget.wait(this.codexClient.threadUnsubscribe({ threadId }));
28843
- } catch (error51) {
28844
- logger.error("Failed to unsubscribe the agent file-change report thread", error51);
28845
- }
28846
- }
28847
28943
  async setAgentMode(sessionId, agentMode) {
28848
28944
  const config2 = this.permissionProfileConfig;
28849
28945
  if (!config2) return;
@@ -29009,58 +29105,6 @@ var CodexAcpClient = class {
29009
29105
  };
29010
29106
  }
29011
29107
  };
29012
- var AgentFileChangeReportBudgetError = class extends Error {
29013
- constructor(reason) {
29014
- super(`Agent file-change report ${reason}`);
29015
- this.reason = reason;
29016
- this.name = "AgentFileChangeReportBudgetError";
29017
- }
29018
- reason;
29019
- };
29020
- var AgentFileChangeReportBudget = class {
29021
- constructor(signal) {
29022
- this.signal = signal;
29023
- }
29024
- signal;
29025
- deadline = Date.now() + AGENT_FILE_CHANGE_REPORT_TIMEOUT_MS;
29026
- async wait(operation) {
29027
- void operation.catch(() => {
29028
- });
29029
- const immediateReason = this.stopReason();
29030
- if (immediateReason !== null) {
29031
- throw new AgentFileChangeReportBudgetError(immediateReason);
29032
- }
29033
- return await new Promise((resolve, reject) => {
29034
- let settled = false;
29035
- const finish = (action) => {
29036
- if (settled) return;
29037
- settled = true;
29038
- clearTimeout(timeout);
29039
- this.signal?.removeEventListener("abort", onAbort);
29040
- action();
29041
- };
29042
- const onAbort = () => finish(() => reject(new AgentFileChangeReportBudgetError("cancelled")));
29043
- const timeout = setTimeout(
29044
- () => finish(() => reject(new AgentFileChangeReportBudgetError("timeout"))),
29045
- Math.max(1, this.deadline - Date.now())
29046
- );
29047
- timeout.unref();
29048
- this.signal?.addEventListener("abort", onAbort, { once: true });
29049
- if (this.signal?.aborted) {
29050
- onAbort();
29051
- }
29052
- void operation.then(
29053
- (value) => finish(() => resolve(value)),
29054
- (error51) => finish(() => reject(error51))
29055
- );
29056
- });
29057
- }
29058
- stopReason() {
29059
- if (this.signal?.aborted) return "cancelled";
29060
- if (Date.now() >= this.deadline) return "timeout";
29061
- return null;
29062
- }
29063
- };
29064
29108
  function buildPromptItems(prompt) {
29065
29109
  return prompt.map((block) => {
29066
29110
  switch (block.type) {
@@ -29175,6 +29219,16 @@ function mergeSandboxWorkspaceWriteRoots(config2, roots) {
29175
29219
  }
29176
29220
  };
29177
29221
  }
29222
+ function forceGitRootTurnDiffPaths(config2) {
29223
+ const features = isJsonObject(config2["features"]) ? config2["features"] : {};
29224
+ return {
29225
+ ...config2,
29226
+ features: {
29227
+ ...features,
29228
+ cwd_relative_turn_diffs: false
29229
+ }
29230
+ };
29231
+ }
29178
29232
  function addAdditionalDirectoriesToSandboxPolicy(sandboxPolicy, additionalDirectories) {
29179
29233
  if (additionalDirectories.length === 0 || sandboxPolicy.type !== "workspaceWrite") {
29180
29234
  return sandboxPolicy;
@@ -29607,6 +29661,41 @@ var CodexAppServerClient = class {
29607
29661
  async threadRead(params) {
29608
29662
  return await this.sendRequest({ method: "thread/read", params });
29609
29663
  }
29664
+ async threadTurnsList(params) {
29665
+ return await this.sendRequest({ method: "thread/turns/list", params });
29666
+ }
29667
+ async threadReadWithHistory(threadId) {
29668
+ const response = await this.threadRead({ threadId });
29669
+ if (response.thread.historyMode === "legacy") {
29670
+ return await this.threadRead({ threadId, includeTurns: true });
29671
+ }
29672
+ const turns = await this.threadReadHistory(threadId);
29673
+ return { ...response, thread: { ...response.thread, turns } };
29674
+ }
29675
+ async threadReadHistory(threadId, initialCursor = null) {
29676
+ const turns = [];
29677
+ const seenCursors = /* @__PURE__ */ new Set();
29678
+ if (initialCursor !== null) seenCursors.add(initialCursor);
29679
+ let cursor = initialCursor;
29680
+ do {
29681
+ const page = await this.threadTurnsList({
29682
+ threadId,
29683
+ cursor,
29684
+ limit: 50,
29685
+ sortDirection: "desc",
29686
+ itemsView: "full"
29687
+ });
29688
+ turns.push(...page.data);
29689
+ cursor = page.nextCursor;
29690
+ if (cursor !== null) {
29691
+ if (seenCursors.has(cursor)) {
29692
+ throw new Error("Codex returned a repeated thread history cursor");
29693
+ }
29694
+ seenCursors.add(cursor);
29695
+ }
29696
+ } while (cursor !== null);
29697
+ return turns.reverse();
29698
+ }
29610
29699
  async threadArchive(params) {
29611
29700
  return await this.sendRequest({ method: "thread/archive", params });
29612
29701
  }
@@ -30045,6 +30134,10 @@ function extractTurnRouting(notification) {
30045
30134
  // src/ModelConfigOption.ts
30046
30135
  var MODEL_CONFIG_ID = "model";
30047
30136
  var REASONING_EFFORT_CONFIG_ID = "reasoning_effort";
30137
+ function formatModelDisplayName(displayName) {
30138
+ if (!/^gpt-/i.test(displayName)) return displayName;
30139
+ return displayName.replace(/^gpt-/i, "").split(/[-/]+/).filter(Boolean).map(capitalize).join(" ");
30140
+ }
30048
30141
  function capitalize(value) {
30049
30142
  return value.charAt(0).toUpperCase() + value.slice(1);
30050
30143
  }
@@ -30052,19 +30145,20 @@ function findSupportedEffort(options, effort) {
30052
30145
  if (!effort) return void 0;
30053
30146
  return options.find((o) => o.reasoningEffort === effort)?.reasoningEffort;
30054
30147
  }
30055
- function createModelConfigOption(availableModels, currentBaseModelId) {
30148
+ function createModelConfigOption(availableModels, currentBaseModelId, recommendedModelId) {
30056
30149
  const options = availableModels.map((model) => ({
30057
30150
  value: model.id,
30058
- name: model.displayName,
30151
+ name: formatModelDisplayName(model.displayName),
30059
30152
  description: model.description
30060
30153
  }));
30061
30154
  if (!availableModels.some((model) => model.id === currentBaseModelId)) {
30062
30155
  options.unshift({
30063
30156
  value: currentBaseModelId,
30064
- name: currentBaseModelId,
30157
+ name: formatModelDisplayName(currentBaseModelId),
30065
30158
  description: null
30066
30159
  });
30067
30160
  }
30161
+ const recommendation = recommendedModelId && options.some((option) => option.value === recommendedModelId) ? recommendedModelId : void 0;
30068
30162
  return {
30069
30163
  id: MODEL_CONFIG_ID,
30070
30164
  name: "Model",
@@ -30072,10 +30166,12 @@ function createModelConfigOption(availableModels, currentBaseModelId) {
30072
30166
  category: "model",
30073
30167
  type: "select",
30074
30168
  currentValue: currentBaseModelId,
30075
- options
30169
+ options,
30170
+ ...recommendation ? { _meta: withAirMeta(void 0, AIR_RECOMMENDED_CONFIG_VALUE_KEY, recommendation) } : {}
30076
30171
  };
30077
30172
  }
30078
- function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEffort) {
30173
+ function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEffort, recommendedEffort) {
30174
+ const recommendation = findSupportedEffort(supportedReasoningEfforts, recommendedEffort);
30079
30175
  return {
30080
30176
  id: REASONING_EFFORT_CONFIG_ID,
30081
30177
  name: "Reasoning effort",
@@ -30087,7 +30183,8 @@ function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEff
30087
30183
  value: option.reasoningEffort,
30088
30184
  name: capitalize(option.reasoningEffort),
30089
30185
  description: option.description
30090
- }))
30186
+ })),
30187
+ ...recommendation ? { _meta: withAirMeta(void 0, AIR_RECOMMENDED_CONFIG_VALUE_KEY, recommendation) } : {}
30091
30188
  };
30092
30189
  }
30093
30190
 
@@ -30787,14 +30884,14 @@ function parseJsonRecord(line) {
30787
30884
  }
30788
30885
  try {
30789
30886
  const value = JSON.parse(line);
30790
- return asRecord2(value);
30887
+ return asRecord3(value);
30791
30888
  } catch {
30792
30889
  return null;
30793
30890
  }
30794
30891
  }
30795
30892
  function extractResponseItem(record2) {
30796
30893
  if (record2["type"] === "response_item") {
30797
- return asRecord2(record2["payload"]);
30894
+ return asRecord3(record2["payload"]);
30798
30895
  }
30799
30896
  const itemType = record2["type"];
30800
30897
  if (typeof itemType === "string" && isLegacyResponseItemType(itemType)) {
@@ -30828,7 +30925,7 @@ function createEventMsgUpdates(record2) {
30828
30925
  if (record2["type"] !== "event_msg") {
30829
30926
  return null;
30830
30927
  }
30831
- const payload = asRecord2(record2["payload"]);
30928
+ const payload = asRecord3(record2["payload"]);
30832
30929
  if (!payload) {
30833
30930
  return [];
30834
30931
  }
@@ -30872,7 +30969,7 @@ function imageBlocks(images) {
30872
30969
  if (typeof image === "string") {
30873
30970
  return [{ type: "text", text: `[@image](${image})` }];
30874
30971
  }
30875
- const record2 = asRecord2(image);
30972
+ const record2 = asRecord3(image);
30876
30973
  const path9 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
30877
30974
  return path9 ? [{ type: "text", text: `[@image](${path9})` }] : [];
30878
30975
  });
@@ -30882,7 +30979,7 @@ function contentBlocksFromResponseContent(content) {
30882
30979
  return [];
30883
30980
  }
30884
30981
  return content.flatMap((entry) => {
30885
- const record2 = asRecord2(entry);
30982
+ const record2 = asRecord3(entry);
30886
30983
  if (!record2) {
30887
30984
  return [];
30888
30985
  }
@@ -30915,7 +31012,7 @@ function textParts(value) {
30915
31012
  if (typeof entry === "string") {
30916
31013
  return entry.length > 0 ? [entry] : [];
30917
31014
  }
30918
- const record2 = asRecord2(entry);
31015
+ const record2 = asRecord3(entry);
30919
31016
  if (!record2) {
30920
31017
  return [];
30921
31018
  }
@@ -30929,6 +31026,7 @@ function createFunctionCallUpdate(item) {
30929
31026
  if (!toolCallId || !name) {
30930
31027
  return null;
30931
31028
  }
31029
+ const toolName = functionToolName(name, stringValue(item["namespace"]));
30932
31030
  const isExecCommand = name === "exec_command";
30933
31031
  const args = parseFunctionArguments(item["arguments"]);
30934
31032
  const command = isExecCommand ? commandFromFunctionArguments(args) : null;
@@ -30936,7 +31034,10 @@ function createFunctionCallUpdate(item) {
30936
31034
  const commandAction = command ? inferCommandAction(command, cwd) : null;
30937
31035
  if (commandAction) {
30938
31036
  return {
30939
- update: createCommandActionEvent(toolCallId, "inProgress", cwd, commandAction),
31037
+ update: {
31038
+ ...createCommandActionEvent(toolCallId, "inProgress", cwd, commandAction),
31039
+ name: toolName
31040
+ },
30940
31041
  usesTerminal: false,
30941
31042
  isExecCommand
30942
31043
  };
@@ -30944,6 +31045,7 @@ function createFunctionCallUpdate(item) {
30944
31045
  const update = {
30945
31046
  sessionUpdate: "tool_call",
30946
31047
  toolCallId,
31048
+ name: toolName,
30947
31049
  kind: toolKindForFunctionCall(name),
30948
31050
  title: titleForFunctionCall(name, args),
30949
31051
  status: "in_progress",
@@ -31007,7 +31109,7 @@ function parseFunctionArguments(value) {
31007
31109
  }
31008
31110
  function rawInputForFunctionCall(name, args) {
31009
31111
  if (name === "exec_command") {
31010
- const record2 = asRecord2(args);
31112
+ const record2 = asRecord3(args);
31011
31113
  if (record2) {
31012
31114
  return {
31013
31115
  command: stringValue(record2["cmd"]) ?? stringValue(record2["command"]),
@@ -31054,14 +31156,14 @@ function functionCallUsesTerminal(item) {
31054
31156
  return item["name"] === "exec_command";
31055
31157
  }
31056
31158
  function commandFromFunctionArguments(args) {
31057
- const record2 = asRecord2(args);
31159
+ const record2 = asRecord3(args);
31058
31160
  if (!record2) {
31059
31161
  return null;
31060
31162
  }
31061
31163
  return stringValue(record2["cmd"]) ?? stringValue(record2["command"]);
31062
31164
  }
31063
31165
  function cwdFromFunctionArguments(args) {
31064
- const record2 = asRecord2(args);
31166
+ const record2 = asRecord3(args);
31065
31167
  if (!record2) {
31066
31168
  return "";
31067
31169
  }
@@ -31533,7 +31635,7 @@ function outputText(output) {
31533
31635
  return "";
31534
31636
  }
31535
31637
  return output.flatMap((entry) => {
31536
- const record2 = asRecord2(entry);
31638
+ const record2 = asRecord3(entry);
31537
31639
  if (!record2) {
31538
31640
  return [];
31539
31641
  }
@@ -31549,7 +31651,7 @@ function outputText(output) {
31549
31651
  }).join("\n");
31550
31652
  }
31551
31653
  function parseExitCode(rawOutput, output) {
31552
- const record2 = asRecord2(rawOutput);
31654
+ const record2 = asRecord3(rawOutput);
31553
31655
  if (record2) {
31554
31656
  const exitCode2 = numberValue(record2["exit_code"]) ?? numberValue(record2["exitCode"]);
31555
31657
  if (exitCode2 !== null) {
@@ -31580,7 +31682,7 @@ function looksLikeCommandFailure(output) {
31580
31682
  }
31581
31683
  return /(^|\n)(Error|Failed|Command failed|Sandbox error|No such file or directory|Permission denied|Operation not permitted|ENOENT|EACCES)(:|\b)/i.test(trimmed);
31582
31684
  }
31583
- function asRecord2(value) {
31685
+ function asRecord3(value) {
31584
31686
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
31585
31687
  return null;
31586
31688
  }
@@ -32230,12 +32332,7 @@ function clientSupportsTypedSessionFailures(capabilities) {
32230
32332
  function clientSupportsAgentFileChangeReports(capabilities) {
32231
32333
  return clientSupportsAirCapability(capabilities, AIR_AGENT_FILE_CHANGE_REPORT_KEY);
32232
32334
  }
32233
- var CodexAcpServer = class _CodexAcpServer {
32234
- static MODEL_NAME_TOKEN_OVERRIDES = {
32235
- gpt: "GPT",
32236
- mini: "Mini",
32237
- codex: "Codex"
32238
- };
32335
+ var CodexAcpServer = class {
32239
32336
  codexAcpClient;
32240
32337
  connection;
32241
32338
  defaultAuthRequest;
@@ -32362,7 +32459,8 @@ var CodexAcpServer = class _CodexAcpServer {
32362
32459
  AIR_SESSION_FAILURE_KEY,
32363
32460
  AIR_AGENT_FILE_CHANGE_REPORT_KEY,
32364
32461
  AIR_NATIVE_SUBAGENT_SESSIONS_KEY,
32365
- AIR_ASYNC_TASKS_KEY
32462
+ AIR_ASYNC_TASKS_KEY,
32463
+ AIR_RECOMMENDED_CONFIG_VALUE_KEY
32366
32464
  ]
32367
32465
  }
32368
32466
  }
@@ -33535,14 +33633,24 @@ Check ${configPath} and project .codex directories, especially their config.toml
33535
33633
  }
33536
33634
  createSessionConfigOptions(sessionState) {
33537
33635
  const currentModelId = ModelId.fromString(sessionState.currentModelId);
33636
+ const useRecommendedValue = clientSupportsAirCapability(
33637
+ this.clientCapabilities,
33638
+ AIR_RECOMMENDED_CONFIG_VALUE_KEY
33639
+ );
33640
+ const currentModel = this.findCurrentModel(sessionState.availableModels, sessionState.currentModelId);
33641
+ const recommendedModelId = useRecommendedValue ? sessionState.availableModels.find((model) => model.isDefault)?.id : void 0;
33538
33642
  const configOptions = [
33539
33643
  sessionState.agentMode.toConfigOption(),
33540
33644
  createCollaborationModeConfigOption(sessionState.collaborationMode),
33541
- createModelConfigOption(sessionState.availableModels, currentModelId.model)
33645
+ createModelConfigOption(sessionState.availableModels, currentModelId.model, recommendedModelId)
33542
33646
  ];
33543
33647
  if (sessionState.supportedReasoningEfforts.length > 0) {
33544
33648
  configOptions.push(
33545
- createReasoningEffortConfigOption(sessionState.supportedReasoningEfforts, currentModelId.effort)
33649
+ createReasoningEffortConfigOption(
33650
+ sessionState.supportedReasoningEfforts,
33651
+ currentModelId.effort,
33652
+ useRecommendedValue ? currentModel?.defaultReasoningEffort : void 0
33653
+ )
33546
33654
  );
33547
33655
  }
33548
33656
  if (sessionState.currentModelSupportsFast) {
@@ -33619,14 +33727,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
33619
33727
  const modelId = ModelId.fromString(currentModelId);
33620
33728
  return models.find((m) => m.id === modelId.model);
33621
33729
  }
33622
- normalizeModelDisplayName(displayName) {
33623
- return displayName.split("-").map((token) => _CodexAcpServer.MODEL_NAME_TOKEN_OVERRIDES[token.toLowerCase()] ?? token).join("-");
33624
- }
33625
33730
  createModelState(availableModels, selectedModelId) {
33626
33731
  const allowedModels = availableModels.flatMap(
33627
33732
  (model) => model.supportedReasoningEfforts.map((effort) => ({
33628
33733
  modelId: ModelId.fromComponents(model, effort.reasoningEffort).toString(),
33629
- name: `${this.normalizeModelDisplayName(model.displayName)} (${effort.reasoningEffort})`,
33734
+ name: `${formatModelDisplayName(model.displayName)} (${effort.reasoningEffort})`,
33630
33735
  description: `${model.description} ${effort.description}`
33631
33736
  }))
33632
33737
  );
@@ -33894,24 +33999,19 @@ Check ${configPath} and project .codex directories, especially their config.toml
33894
33999
  title
33895
34000
  });
33896
34001
  }
33897
- async publishAgentFileChangeReport(sessionState, turnId, request, unavailableReason, signal) {
34002
+ async publishAgentFileChangeReport(sessionState, turnId, request, unavailableReason, turnDiff, workspace) {
33898
34003
  let report;
33899
34004
  try {
33900
- report = turnId === null ? createUnavailableAgentFileChangeReport(request.requestId, unavailableReason) : await this.codexAcpClient.runAgentFileChangeReport({
33901
- sessionId: sessionState.sessionId,
33902
- turnId,
33903
- // The client owns request-id correlation and duplicate suppression. The wrapper
33904
- // stays stateless so a retried ACP prompt still receives a terminal report.
33905
- requestId: request.requestId,
33906
- workspace: {
33907
- cwd: sessionState.cwd,
33908
- additionalDirectories: sessionState.additionalDirectories
33909
- },
33910
- signal
33911
- });
34005
+ report = turnId === null ? createUnavailableAgentFileChangeReport(request.requestId, unavailableReason) : createReportedAgentFileChangeReport(request.requestId, turnDiff, workspace);
33912
34006
  } catch (error51) {
33913
- logger.error("Agent file-change report failed unexpectedly", error51);
33914
- report = createUnavailableAgentFileChangeReport(request.requestId, "providerError");
34007
+ logger.error(
34008
+ error51 instanceof AgentFileChangeReportError ? "Agent file-change report unavailable" : "Agent file-change report failed unexpectedly",
34009
+ error51
34010
+ );
34011
+ report = createUnavailableAgentFileChangeReport(
34012
+ request.requestId,
34013
+ error51 instanceof AgentFileChangeReportError ? error51.reason : "providerError"
34014
+ );
33915
34015
  }
33916
34016
  try {
33917
34017
  const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
@@ -34381,6 +34481,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
34381
34481
  });
34382
34482
  const sessionState = this.getSessionState(params.sessionId);
34383
34483
  const agentFileChangeReportRequest = clientSupportsAgentFileChangeReports(this.clientCapabilities) ? parseAgentFileChangeReportRequest(params._meta) : null;
34484
+ const agentFileChangeWorkspace = agentFileChangeReportRequest === null ? null : captureAgentFileChangeWorkspace(sessionState.cwd, sessionState.additionalDirectories);
34384
34485
  let agentFileChangeReportTurnId = null;
34385
34486
  let agentFileChangeReportUnavailableReason = "providerError";
34386
34487
  let promptWasCancelled = false;
@@ -34420,7 +34521,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
34420
34521
  clientSupportsTypedSessionFailures(this.clientCapabilities),
34421
34522
  this.sessionFailureEpoch,
34422
34523
  sessionState.subagents,
34423
- (accountUpdated) => this.handleAccountUpdated(accountUpdated)
34524
+ (accountUpdated) => this.handleAccountUpdated(accountUpdated),
34525
+ agentFileChangeReportRequest !== null
34424
34526
  );
34425
34527
  eventHandler = promptEventHandler;
34426
34528
  const permissionLifecycle = this.permissionLifecycleContext(sessionState);
@@ -34791,13 +34893,21 @@ Check ${configPath} and project .codex directories, especially their config.toml
34791
34893
  } catch (error51) {
34792
34894
  logger.error("Failed to publish terminal subagent state during prompt cleanup", error51);
34793
34895
  }
34794
- if (agentFileChangeReportRequest !== null) {
34896
+ if (agentFileChangeReportRequest !== null && agentFileChangeWorkspace !== null) {
34897
+ if (promptWasCancelled || activePrompt.signal.aborted || this.sessionIsClosing(params.sessionId)) {
34898
+ agentFileChangeReportTurnId = null;
34899
+ agentFileChangeReportUnavailableReason = "cancelled";
34900
+ } else if (agentFileChangeReportTurnId !== null && eventHandler?.isTurnDiffOversized(agentFileChangeReportTurnId)) {
34901
+ agentFileChangeReportTurnId = null;
34902
+ agentFileChangeReportUnavailableReason = "invalidOutput";
34903
+ }
34795
34904
  await this.publishAgentFileChangeReport(
34796
34905
  sessionState,
34797
34906
  agentFileChangeReportTurnId,
34798
34907
  agentFileChangeReportRequest,
34799
34908
  agentFileChangeReportUnavailableReason,
34800
- activePrompt.signal
34909
+ agentFileChangeReportTurnId === null || eventHandler === null ? "" : eventHandler.getTurnDiff(agentFileChangeReportTurnId),
34910
+ agentFileChangeWorkspace
34801
34911
  );
34802
34912
  }
34803
34913
  logger.log("Prompt completed", { sessionId: params.sessionId });