lody 0.97.1 → 0.98.0

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.
@@ -128,30 +128,30 @@ var require_messages = __commonJS({
128
128
  Object.defineProperty(exports$1, "__esModule", { value: true });
129
129
  exports$1.Message = exports$1.NotificationType9 = exports$1.NotificationType8 = exports$1.NotificationType7 = exports$1.NotificationType6 = exports$1.NotificationType5 = exports$1.NotificationType4 = exports$1.NotificationType3 = exports$1.NotificationType2 = exports$1.NotificationType1 = exports$1.NotificationType0 = exports$1.NotificationType = exports$1.RequestType9 = exports$1.RequestType8 = exports$1.RequestType7 = exports$1.RequestType6 = exports$1.RequestType5 = exports$1.RequestType4 = exports$1.RequestType3 = exports$1.RequestType2 = exports$1.RequestType1 = exports$1.RequestType = exports$1.RequestType0 = exports$1.AbstractMessageSignature = exports$1.ParameterStructures = exports$1.ResponseError = exports$1.ErrorCodes = void 0;
130
130
  var is = __importStar(require_is());
131
- var ErrorCodes;
132
- (function(ErrorCodes2) {
133
- ErrorCodes2.ParseError = -32700;
134
- ErrorCodes2.InvalidRequest = -32600;
135
- ErrorCodes2.MethodNotFound = -32601;
136
- ErrorCodes2.InvalidParams = -32602;
137
- ErrorCodes2.InternalError = -32603;
138
- ErrorCodes2.jsonrpcReservedErrorRangeStart = -32099;
139
- ErrorCodes2.serverErrorStart = -32099;
140
- ErrorCodes2.MessageWriteError = -32099;
141
- ErrorCodes2.MessageReadError = -32098;
142
- ErrorCodes2.PendingResponseRejected = -32097;
143
- ErrorCodes2.ConnectionInactive = -32096;
144
- ErrorCodes2.ServerNotInitialized = -32002;
145
- ErrorCodes2.UnknownErrorCode = -32001;
146
- ErrorCodes2.jsonrpcReservedErrorRangeEnd = -32e3;
147
- ErrorCodes2.serverErrorEnd = -32e3;
148
- })(ErrorCodes || (exports$1.ErrorCodes = ErrorCodes = {}));
149
- var ResponseError = class _ResponseError extends Error {
131
+ var ErrorCodes2;
132
+ (function(ErrorCodes3) {
133
+ ErrorCodes3.ParseError = -32700;
134
+ ErrorCodes3.InvalidRequest = -32600;
135
+ ErrorCodes3.MethodNotFound = -32601;
136
+ ErrorCodes3.InvalidParams = -32602;
137
+ ErrorCodes3.InternalError = -32603;
138
+ ErrorCodes3.jsonrpcReservedErrorRangeStart = -32099;
139
+ ErrorCodes3.serverErrorStart = -32099;
140
+ ErrorCodes3.MessageWriteError = -32099;
141
+ ErrorCodes3.MessageReadError = -32098;
142
+ ErrorCodes3.PendingResponseRejected = -32097;
143
+ ErrorCodes3.ConnectionInactive = -32096;
144
+ ErrorCodes3.ServerNotInitialized = -32002;
145
+ ErrorCodes3.UnknownErrorCode = -32001;
146
+ ErrorCodes3.jsonrpcReservedErrorRangeEnd = -32e3;
147
+ ErrorCodes3.serverErrorEnd = -32e3;
148
+ })(ErrorCodes2 || (exports$1.ErrorCodes = ErrorCodes2 = {}));
149
+ var ResponseError2 = class _ResponseError extends Error {
150
150
  code;
151
151
  data;
152
152
  constructor(code, message, data) {
153
153
  super(message);
154
- this.code = is.number(code) ? code : ErrorCodes.UnknownErrorCode;
154
+ this.code = is.number(code) ? code : ErrorCodes2.UnknownErrorCode;
155
155
  this.data = data;
156
156
  Object.setPrototypeOf(this, _ResponseError.prototype);
157
157
  }
@@ -166,7 +166,7 @@ var require_messages = __commonJS({
166
166
  return result;
167
167
  }
168
168
  };
169
- exports$1.ResponseError = ResponseError;
169
+ exports$1.ResponseError = ResponseError2;
170
170
  var ParameterStructures = class _ParameterStructures {
171
171
  kind;
172
172
  /**
@@ -21903,6 +21903,107 @@ function reversePatch(structuredPatch) {
21903
21903
  }
21904
21904
  return reversed;
21905
21905
  }
21906
+ var DiffStatsCalculator = class {
21907
+ addedFile(text) {
21908
+ return { version: 1, added: this.lineCount(text), removed: 0 };
21909
+ }
21910
+ deletedFile(text) {
21911
+ return { version: 1, added: 0, removed: this.lineCount(text) };
21912
+ }
21913
+ update(patch) {
21914
+ if (patch.isBinary || patch.hunks.length === 0) return null;
21915
+ let added = 0;
21916
+ let removed = 0;
21917
+ let previousOldEnd = 1;
21918
+ let previousNewEnd = 1;
21919
+ for (const hunk of patch.hunks) {
21920
+ const { oldStart, oldLines, newStart, newLines } = hunk;
21921
+ if (![oldStart, oldLines, newStart, newLines].every(Number.isSafeInteger) || oldStart < previousOldEnd || newStart < previousNewEnd || oldLines < 0 || newLines < 0 || newStart - oldStart !== added - removed) return null;
21922
+ let oldConsumed = 0;
21923
+ let newConsumed = 0;
21924
+ let previousWasContent = false;
21925
+ for (const line of hunk.lines) {
21926
+ switch (line[0]) {
21927
+ case "+":
21928
+ added++;
21929
+ newConsumed++;
21930
+ previousWasContent = true;
21931
+ break;
21932
+ case "-":
21933
+ removed++;
21934
+ oldConsumed++;
21935
+ previousWasContent = true;
21936
+ break;
21937
+ case " ":
21938
+ case void 0:
21939
+ oldConsumed++;
21940
+ newConsumed++;
21941
+ previousWasContent = true;
21942
+ break;
21943
+ case "\\":
21944
+ if (!previousWasContent || line.replace(/\r$/, "") !== "\") return null;
21945
+ previousWasContent = false;
21946
+ break;
21947
+ default:
21948
+ return null;
21949
+ }
21950
+ }
21951
+ if (oldConsumed !== oldLines || newConsumed !== newLines) return null;
21952
+ previousOldEnd = oldStart + oldLines;
21953
+ previousNewEnd = newStart + newLines;
21954
+ }
21955
+ return { version: 1, added, removed };
21956
+ }
21957
+ lineCount(text) {
21958
+ let count = 0;
21959
+ for (let offset = text.indexOf("\n"); offset >= 0; offset = text.indexOf("\n", offset + 1)) count++;
21960
+ for (let offset = text.indexOf("\r"); offset >= 0; offset = text.indexOf("\r", offset + 1)) {
21961
+ if (text[offset + 1] !== "\n") count++;
21962
+ }
21963
+ if (text.length > 0 && !text.endsWith("\n") && !text.endsWith("\r")) count++;
21964
+ return count;
21965
+ }
21966
+ };
21967
+ var JETBRAINS_META_KEY = "jetbrains";
21968
+ var AIR_META_KEY = "air";
21969
+ var AIR_EXTENSION_VERSION_KEY = "version";
21970
+ var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
21971
+ var AIR_DIFF_STATS_KEY = "diffStats";
21972
+ var AIR_SESSION_FAILURE_KEY = "sessionFailure";
21973
+ var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
21974
+ var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
21975
+ var AIR_ASYNC_TASKS_KEY = "asyncTasks";
21976
+ var AIR_RECOMMENDED_CONFIG_VALUE_KEY = "recommendedValue";
21977
+ var AIR_ASYNC_TASKS_BACKGROUNDED_KEY = "backgrounded";
21978
+ var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
21979
+ var AIR_EXTENSION_VERSION = 1;
21980
+ function withAirMeta(meta3, key, value) {
21981
+ const root = asRecord(meta3);
21982
+ const jetbrains = asRecord(root[JETBRAINS_META_KEY]);
21983
+ const air = asRecord(jetbrains[AIR_META_KEY]);
21984
+ return {
21985
+ ...root,
21986
+ [JETBRAINS_META_KEY]: {
21987
+ ...jetbrains,
21988
+ [AIR_META_KEY]: {
21989
+ ...air,
21990
+ [AIR_EXTENSION_VERSION_KEY]: AIR_EXTENSION_VERSION,
21991
+ [key]: value
21992
+ }
21993
+ }
21994
+ };
21995
+ }
21996
+ function clientSupportsAirCapability(capabilities, capability) {
21997
+ const meta3 = asRecord(capabilities?._meta);
21998
+ const jetbrains = asRecord(meta3[JETBRAINS_META_KEY]);
21999
+ const air = asRecord(jetbrains[AIR_META_KEY]);
22000
+ const version2 = air[AIR_EXTENSION_VERSION_KEY];
22001
+ const supported = air[AIR_EXTENSION_CAPABILITIES_KEY];
22002
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
22003
+ }
22004
+ function asRecord(value) {
22005
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
22006
+ }
21906
22007
  function stripShellPrefix(command) {
21907
22008
  const withoutShell = command.replace(/^(?:\/bin\/)?(?:bash|zsh|sh)\s+(?:-[lc]+\s+)?/, "");
21908
22009
  if (withoutShell.startsWith("'") && withoutShell.endsWith("'")) {
@@ -21950,8 +22051,24 @@ function createContextCompactionMeta(metadata = {}) {
21950
22051
  }
21951
22052
  };
21952
22053
  }
22054
+ function functionToolName(name, namespace) {
22055
+ return `${namespace ?? ""}${name}`;
22056
+ }
22057
+ function commandToolName(source) {
22058
+ switch (source) {
22059
+ case "unifiedExecStartup":
22060
+ return "exec_command";
22061
+ case "unifiedExecInteraction":
22062
+ return "write_stdin";
22063
+ // These sources do not identify a unique model tool.
22064
+ case "agent":
22065
+ case "userShell":
22066
+ return void 0;
22067
+ }
22068
+ }
21953
22069
  var imageGenerationMeta = { lody: { toolName: LODY_TOOL_NAMES.imageGeneration } };
21954
22070
  var CONTEXT_COMPACTION_META = createContextCompactionMeta();
22071
+ var DIFF_STATS = new DiffStatsCalculator();
21955
22072
  function toAcpStatus(status) {
21956
22073
  switch (status) {
21957
22074
  case "inProgress":
@@ -21980,14 +22097,19 @@ async function createFileChangeUpdate(item) {
21980
22097
  };
21981
22098
  }
21982
22099
  async function createCommandExecutionUpdate(item) {
22100
+ const name = commandToolName(item.source);
21983
22101
  const commandAction = item.commandActions.length === 1 ? item.commandActions[0] : void 0;
21984
22102
  if (commandAction) {
21985
- return createCommandActionEvent(item.id, item.status, item.cwd, commandAction);
22103
+ return {
22104
+ ...createCommandActionEvent(item.id, item.status, item.cwd, commandAction),
22105
+ ...name === void 0 ? {} : { name }
22106
+ };
21986
22107
  }
21987
22108
  const command = stripShellPrefix(item.command);
21988
22109
  return createTerminalCommandEvent({
21989
22110
  sessionUpdate: "tool_call",
21990
22111
  toolCallId: item.id,
22112
+ ...name === void 0 ? {} : { name },
21991
22113
  kind: "execute",
21992
22114
  title: command,
21993
22115
  status: toAcpStatus(item.status),
@@ -22042,7 +22164,10 @@ async function createMcpToolCallUpdate(item) {
22042
22164
  };
22043
22165
  }
22044
22166
  async function createDynamicToolCallUpdate(item) {
22045
- return createExecuteToolCallUpdate(item, item.tool, { arguments: item.arguments });
22167
+ return {
22168
+ ...await createExecuteToolCallUpdate(item, item.tool, { arguments: item.arguments }),
22169
+ name: functionToolName(item.tool, item.namespace)
22170
+ };
22046
22171
  }
22047
22172
  function createImageViewUpdate(item) {
22048
22173
  const displayPath = item.path;
@@ -22050,6 +22175,7 @@ function createImageViewUpdate(item) {
22050
22175
  sessionUpdate: "tool_call",
22051
22176
  toolCallId: item.id,
22052
22177
  kind: "read",
22178
+ name: "view_image",
22053
22179
  title: `View Image ${displayPath}`,
22054
22180
  status: "completed",
22055
22181
  content: [createContent({
@@ -22633,53 +22759,43 @@ async function createAddFileContent(change) {
22633
22759
  newText: change.diff,
22634
22760
  // app-server always returns file content instead of diff
22635
22761
  path: change.path,
22636
- _meta: {
22637
- kind: "add"
22638
- }
22762
+ _meta: withAirMeta({ kind: "add" }, AIR_DIFF_STATS_KEY, DIFF_STATS.addedFile(change.diff))
22639
22763
  };
22640
22764
  }
22641
22765
  async function createUpdateFileContent(change) {
22642
22766
  if (change.kind.type !== "update") return null;
22643
22767
  const unifiedDiff = recoverCorruptedDiff(change.diff);
22768
+ const patches = parsePatch(unifiedDiff);
22769
+ if (patches.length !== 1) return null;
22770
+ const patch = patches[0];
22644
22771
  const movePath = change.kind.move_path;
22645
22772
  const oldContent = await readFileContent(change.path);
22646
22773
  if (oldContent !== null) {
22647
- const patchedContent = applyPatch(oldContent, unifiedDiff);
22774
+ const patchedContent = applyPatch(oldContent, patch);
22648
22775
  if (patchedContent === false) {
22649
- const revertedPatch2 = revertPatch(unifiedDiff);
22650
- if (revertedPatch2) {
22651
- const revertedContent2 = applyPatch(oldContent, revertedPatch2);
22652
- if (revertedContent2 !== false) {
22653
- return createUpdateDiffContent(change.path, revertedContent2, oldContent);
22654
- }
22776
+ const revertedContent2 = applyPatch(oldContent, reversePatch(patch));
22777
+ if (revertedContent2 !== false) {
22778
+ return createUpdateDiffContent(change.path, revertedContent2, oldContent, patch);
22655
22779
  }
22656
22780
  return null;
22657
22781
  }
22658
- return createUpdateDiffContent(movePath ?? change.path, oldContent, patchedContent);
22782
+ return createUpdateDiffContent(movePath ?? change.path, oldContent, patchedContent, patch);
22659
22783
  }
22660
22784
  if (!movePath) return null;
22661
22785
  const newContent = await readFileContent(movePath);
22662
22786
  if (newContent === null) return null;
22663
- const revertedPatch = revertPatch(unifiedDiff);
22664
- if (!revertedPatch) return null;
22665
- const revertedContent = applyPatch(newContent, revertedPatch);
22787
+ const revertedContent = applyPatch(newContent, reversePatch(patch));
22666
22788
  if (revertedContent === false) return null;
22667
- return createUpdateDiffContent(movePath, revertedContent, newContent);
22668
- }
22669
- function revertPatch(unifiedDiff) {
22670
- const [patch] = parsePatch(unifiedDiff);
22671
- if (!patch) return null;
22672
- return reversePatch(patch);
22789
+ return createUpdateDiffContent(movePath, revertedContent, newContent, patch);
22673
22790
  }
22674
- function createUpdateDiffContent(path10, oldText, newText) {
22791
+ function createUpdateDiffContent(path10, oldText, newText, patch) {
22792
+ const stats = DIFF_STATS.update(patch);
22675
22793
  return {
22676
22794
  type: "diff",
22677
22795
  oldText,
22678
22796
  newText,
22679
22797
  path: path10,
22680
- _meta: {
22681
- kind: "update"
22682
- }
22798
+ _meta: stats ? withAirMeta({ kind: "update" }, AIR_DIFF_STATS_KEY, stats) : { kind: "update" }
22683
22799
  };
22684
22800
  }
22685
22801
  async function createDeleteFileContent(change) {
@@ -22689,9 +22805,7 @@ async function createDeleteFileContent(change) {
22689
22805
  // app-server always returns file content instead of diff
22690
22806
  newText: "",
22691
22807
  path: change.path,
22692
- _meta: {
22693
- kind: "delete"
22694
- }
22808
+ _meta: withAirMeta({ kind: "delete" }, AIR_DIFF_STATS_KEY, DIFF_STATS.deletedFile(change.diff))
22695
22809
  };
22696
22810
  }
22697
22811
  async function readFileContent(filePath) {
@@ -22875,24 +22989,6 @@ function toLodyRateLimitsResponse(response) {
22875
22989
  fetchedAtEpochSeconds: Math.floor(Date.now() / 1e3)
22876
22990
  };
22877
22991
  }
22878
- var JETBRAINS_META_KEY = "jetbrains";
22879
- var AIR_META_KEY = "air";
22880
- var AIR_EXTENSION_VERSION_KEY = "version";
22881
- var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
22882
- var AIR_SESSION_FAILURE_KEY = "sessionFailure";
22883
- var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
22884
- var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
22885
- var AIR_ASYNC_TASKS_KEY = "asyncTasks";
22886
- var AIR_ASYNC_TASKS_BACKGROUNDED_KEY = "backgrounded";
22887
- var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
22888
- var AIR_EXTENSION_VERSION = 1;
22889
- function clientSupportsAirCapability(capabilities, capability) {
22890
- const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
22891
- const air = jetbrains?.[AIR_META_KEY];
22892
- const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
22893
- const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
22894
- return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
22895
- }
22896
22992
  function normalizeAgentPath(path10) {
22897
22993
  const normalized = path10.trim().replace(/\/+$/, "");
22898
22994
  return normalized || "/root";
@@ -23310,12 +23406,308 @@ function mergeRateLimitSnapshot(previous, update) {
23310
23406
  return {
23311
23407
  ...update,
23312
23408
  limitId: update.limitId ?? "codex",
23409
+ normalModelSlug: update.normalModelSlug ?? previous.normalModelSlug,
23313
23410
  credits: update.credits ?? previous.credits,
23314
23411
  individualLimit: update.individualLimit ?? previous.individualLimit,
23315
23412
  spendControlReached: update.spendControlReached ?? previous.spendControlReached,
23316
23413
  planType: update.planType ?? previous.planType
23317
23414
  };
23318
23415
  }
23416
+ var AGENT_FILE_CHANGE_REPORT_VERSION = 1;
23417
+ var AGENT_FILE_CHANGE_REPORT_MAX_PATHS = 1024;
23418
+ var AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH = 4096;
23419
+ var AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES = 256 * 1024;
23420
+ var AGENT_FILE_CHANGE_REPORT_MAX_DIFF_BYTES = 8 * 1024 * 1024;
23421
+ 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.";
23422
+ function captureAgentFileChangeWorkspace(cwd, additionalDirectories) {
23423
+ const lexicalCwd = parseWorkspaceRoot(cwd);
23424
+ return {
23425
+ cwd,
23426
+ additionalDirectories: [...additionalDirectories],
23427
+ diffRoot: lexicalCwd === null ? cwd : findDiffDisplayRoot(lexicalCwd).value
23428
+ };
23429
+ }
23430
+ function parseAgentFileChangeReportRequest(meta3) {
23431
+ const jetbrains = asRecord2(meta3?.[JETBRAINS_META_KEY]);
23432
+ const air = asRecord2(jetbrains?.[AIR_META_KEY]);
23433
+ const request = asRecord2(air?.[AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY]);
23434
+ if (request === null || !hasOnlyKeys(request, ["version", "requestId"]) || request["version"] !== AGENT_FILE_CHANGE_REPORT_VERSION) {
23435
+ return null;
23436
+ }
23437
+ const requestId = request["requestId"];
23438
+ if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) {
23439
+ return null;
23440
+ }
23441
+ return { version: AGENT_FILE_CHANGE_REPORT_VERSION, requestId };
23442
+ }
23443
+ function createReportedAgentFileChangeReport(requestId, diff, workspace) {
23444
+ if (Buffer.byteLength(diff, "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_DIFF_BYTES) {
23445
+ throw new AgentFileChangeReportError("invalidOutput", "The turn diff exceeds the parser input limit");
23446
+ }
23447
+ const parsedReport = parseTurnDiff(diff);
23448
+ const normalized = normalizeFileChangeReport(parsedReport, workspace);
23449
+ return fitReportedAgentFileChangeReport({
23450
+ version: AGENT_FILE_CHANGE_REPORT_VERSION,
23451
+ requestId,
23452
+ status: "reported",
23453
+ ...normalized
23454
+ });
23455
+ }
23456
+ function createUnavailableAgentFileChangeReport(requestId, reason) {
23457
+ return {
23458
+ version: AGENT_FILE_CHANGE_REPORT_VERSION,
23459
+ requestId,
23460
+ status: "unavailable",
23461
+ reason
23462
+ };
23463
+ }
23464
+ var AgentFileChangeReportError = class extends Error {
23465
+ reason;
23466
+ constructor(reason, message) {
23467
+ super(message);
23468
+ this.name = "AgentFileChangeReportError";
23469
+ this.reason = reason;
23470
+ }
23471
+ };
23472
+ function parseTurnDiff(diff) {
23473
+ if (diff.trim() === "") {
23474
+ return { paths: [], complete: false, uncertainty: TURN_DIFF_UNCERTAINTY };
23475
+ }
23476
+ let patches;
23477
+ try {
23478
+ patches = parsePatch(diff);
23479
+ } catch (error48) {
23480
+ throw new AgentFileChangeReportError(
23481
+ "invalidOutput",
23482
+ `The turn diff is not valid unified diff: ${error48 instanceof Error ? error48.message : String(error48)}`
23483
+ );
23484
+ }
23485
+ if (patches.length === 0) {
23486
+ throw new AgentFileChangeReportError("invalidOutput", "The turn diff contains no parseable file patches");
23487
+ }
23488
+ const rawFileHeaders = extractRawFileHeaders(diff);
23489
+ const paths = [];
23490
+ for (const [index, patch] of patches.entries()) {
23491
+ const rawHeaders = rawFileHeaders[index];
23492
+ const oldPath = normalizeDiffPath(selectLosslessFileName(rawHeaders?.oldFileName, patch.oldFileName));
23493
+ const newPath = normalizeDiffPath(selectLosslessFileName(rawHeaders?.newFileName, patch.newFileName));
23494
+ if (oldPath === null && newPath === null) {
23495
+ throw new AgentFileChangeReportError("invalidOutput", "The turn diff contains a patch without a file path");
23496
+ }
23497
+ if (oldPath !== null) paths.push(oldPath);
23498
+ if (newPath !== null) paths.push(newPath);
23499
+ }
23500
+ return { paths, complete: false, uncertainty: TURN_DIFF_UNCERTAINTY };
23501
+ }
23502
+ function extractRawFileHeaders(diff) {
23503
+ return diff.split(/(?=^diff --git )/m).filter((section) => section.trim() !== "").map((section) => {
23504
+ const lines = section.split(/\r?\n/);
23505
+ for (let index = 0; index < lines.length; index += 1) {
23506
+ const line = lines[index];
23507
+ if (line?.startsWith("@@ ")) return null;
23508
+ const nextLine = lines[index + 1];
23509
+ if (line?.startsWith("--- ") && nextLine?.startsWith("+++ ")) {
23510
+ return {
23511
+ oldFileName: line.slice(4),
23512
+ newFileName: nextLine.slice(4)
23513
+ };
23514
+ }
23515
+ }
23516
+ return null;
23517
+ });
23518
+ }
23519
+ function selectLosslessFileName(rawFileName, parsedFileName) {
23520
+ return rawFileName === void 0 || rawFileName.startsWith('"') ? parsedFileName : rawFileName;
23521
+ }
23522
+ function normalizeDiffPath(value) {
23523
+ if (value === void 0 || value === "/dev/null") return null;
23524
+ return value.startsWith("a/") || value.startsWith("b/") ? value.slice(2) : value;
23525
+ }
23526
+ function fitReportedAgentFileChangeReport(report) {
23527
+ const paths = [...report.paths];
23528
+ let truncated = report.truncated;
23529
+ let fitted = report;
23530
+ while (Buffer.byteLength(JSON.stringify(fitted), "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
23531
+ if (paths.length === 0) {
23532
+ throw new AgentFileChangeReportError("invalidOutput", "The audit report exceeds the wire limit");
23533
+ }
23534
+ paths.pop();
23535
+ truncated = true;
23536
+ fitted = {
23537
+ ...report,
23538
+ paths: [...paths],
23539
+ declaredComplete: false,
23540
+ truncated
23541
+ };
23542
+ }
23543
+ return fitted;
23544
+ }
23545
+ function normalizeFileChangeReport(report, workspace) {
23546
+ const lexicalCwd = parseWorkspaceRoot(workspace.cwd);
23547
+ if (lexicalCwd === null) {
23548
+ throw new AgentFileChangeReportError("providerError", "The session working directory is not absolute");
23549
+ }
23550
+ const cwd = canonicalizeWorkspaceRoot(lexicalCwd);
23551
+ const diffRoot = parseWorkspaceRoot(workspace.diffRoot);
23552
+ if (diffRoot === null || diffRoot.flavor !== cwd.flavor) {
23553
+ throw new AgentFileChangeReportError("providerError", "The captured turn-diff root is invalid");
23554
+ }
23555
+ const roots = [cwd, ...workspace.additionalDirectories.flatMap((directory) => {
23556
+ const root = normalizeWorkspaceRoot(directory);
23557
+ return root === null || root.flavor !== cwd.flavor ? [] : [root];
23558
+ })];
23559
+ const paths = [];
23560
+ const seen = /* @__PURE__ */ new Set();
23561
+ let totalBytes = 0;
23562
+ let truncated = false;
23563
+ for (const reportedPath of report.paths) {
23564
+ if (reportedPath.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
23565
+ truncated = true;
23566
+ continue;
23567
+ }
23568
+ const normalized = normalizeReportedPath(reportedPath, diffRoot, roots);
23569
+ if (normalized === null || normalized.value.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
23570
+ truncated = true;
23571
+ continue;
23572
+ }
23573
+ const key = normalized.flavor === "windows" ? normalized.value.toLowerCase() : normalized.value;
23574
+ if (seen.has(key)) {
23575
+ continue;
23576
+ }
23577
+ const bytes = Buffer.byteLength(normalized.value, "utf8");
23578
+ if (paths.length >= AGENT_FILE_CHANGE_REPORT_MAX_PATHS || totalBytes + bytes > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
23579
+ truncated = true;
23580
+ continue;
23581
+ }
23582
+ seen.add(key);
23583
+ paths.push(normalized.value);
23584
+ totalBytes += bytes;
23585
+ }
23586
+ return {
23587
+ paths,
23588
+ declaredComplete: report.complete && !truncated,
23589
+ truncated,
23590
+ ...report.uncertainty ? { uncertainty: report.uncertainty } : {}
23591
+ };
23592
+ }
23593
+ function normalizeWorkspaceRoot(value) {
23594
+ const root = parseWorkspaceRoot(value);
23595
+ return root === null ? null : canonicalizeWorkspaceRoot(root);
23596
+ }
23597
+ function parseWorkspaceRoot(value) {
23598
+ const trimmed = value.trim();
23599
+ if (!isValidPathText(trimmed)) {
23600
+ return null;
23601
+ }
23602
+ if (isWindowsAbsolutePath(trimmed)) {
23603
+ return {
23604
+ value: path__default.win32.normalize(trimmed.replace(/\//g, "\\")),
23605
+ flavor: "windows"
23606
+ };
23607
+ }
23608
+ if (path__default.posix.isAbsolute(trimmed)) {
23609
+ return {
23610
+ value: path__default.posix.normalize(trimmed.replace(/\\/g, "/")),
23611
+ flavor: "posix"
23612
+ };
23613
+ }
23614
+ return null;
23615
+ }
23616
+ function normalizeReportedPath(value, relativeRoot, roots) {
23617
+ 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)) {
23618
+ return null;
23619
+ }
23620
+ let candidate;
23621
+ if (isWindowsAbsolutePath(value)) {
23622
+ candidate = { value: path__default.win32.normalize(value.replace(/\//g, "\\")), flavor: "windows" };
23623
+ } else if (path__default.posix.isAbsolute(value)) {
23624
+ candidate = { value: path__default.posix.normalize(value.replace(/\\/g, "/")), flavor: "posix" };
23625
+ } else if (relativeRoot.flavor === "windows") {
23626
+ candidate = {
23627
+ value: path__default.win32.resolve(relativeRoot.value, value.replace(/\//g, "\\")),
23628
+ flavor: "windows"
23629
+ };
23630
+ } else {
23631
+ candidate = {
23632
+ value: path__default.posix.resolve(relativeRoot.value, value.replace(/\\/g, "/")),
23633
+ flavor: "posix"
23634
+ };
23635
+ }
23636
+ candidate = canonicalizeReportedPath(candidate);
23637
+ return roots.some((root) => pathIsStrictlyInside(root, candidate)) ? candidate : null;
23638
+ }
23639
+ function findDiffDisplayRoot(cwd) {
23640
+ if (!isNativePathFlavor(cwd.flavor)) return cwd;
23641
+ const pathImplementation = cwd.flavor === "windows" ? path__default.win32 : path__default.posix;
23642
+ let current = cwd.value;
23643
+ while (true) {
23644
+ if (fs__default.existsSync(pathImplementation.join(current, ".git"))) {
23645
+ return canonicalizeWorkspaceRoot({ value: current, flavor: cwd.flavor });
23646
+ }
23647
+ const parent = pathImplementation.dirname(current);
23648
+ if (parent === current) return cwd;
23649
+ current = parent;
23650
+ }
23651
+ }
23652
+ function canonicalizeWorkspaceRoot(root) {
23653
+ if (!isNativePathFlavor(root.flavor)) {
23654
+ return root;
23655
+ }
23656
+ return { ...root, value: canonicalizeFromExistingAncestor(root.value) };
23657
+ }
23658
+ function canonicalizeReportedPath(candidate) {
23659
+ if (!isNativePathFlavor(candidate.flavor)) {
23660
+ return candidate;
23661
+ }
23662
+ const parent = canonicalizeFromExistingAncestor(path__default.dirname(candidate.value));
23663
+ return { ...candidate, value: path__default.resolve(parent, path__default.basename(candidate.value)) };
23664
+ }
23665
+ function canonicalizeFromExistingAncestor(value) {
23666
+ const original = path__default.resolve(value);
23667
+ let current = original;
23668
+ const missingSegments = [];
23669
+ while (true) {
23670
+ try {
23671
+ const canonical = fs__default.realpathSync.native(current);
23672
+ return path__default.resolve(canonical, ...missingSegments.reverse());
23673
+ } catch (error48) {
23674
+ if (!isMissingPathError(error48)) return original;
23675
+ }
23676
+ const parent = path__default.dirname(current);
23677
+ if (parent === current) return original;
23678
+ missingSegments.push(path__default.basename(current));
23679
+ current = parent;
23680
+ }
23681
+ }
23682
+ function isMissingPathError(error48) {
23683
+ if (typeof error48 !== "object" || error48 === null || !("code" in error48)) return false;
23684
+ return error48.code === "ENOENT" || error48.code === "ENOTDIR";
23685
+ }
23686
+ function isNativePathFlavor(flavor) {
23687
+ return process.platform === "win32" ? flavor === "windows" : flavor === "posix";
23688
+ }
23689
+ function pathIsStrictlyInside(root, candidate) {
23690
+ if (root.flavor !== candidate.flavor) {
23691
+ return false;
23692
+ }
23693
+ const pathImplementation = root.flavor === "windows" ? path__default.win32 : path__default.posix;
23694
+ const relative = pathImplementation.relative(root.value, candidate.value);
23695
+ return relative.length > 0 && !pathImplementation.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${pathImplementation.sep}`);
23696
+ }
23697
+ function isWindowsAbsolutePath(value) {
23698
+ const portable = value.replace(/\\/g, "/");
23699
+ return /^[A-Za-z]:\//.test(portable) || /^\/\/[^/]+\/[^/]+(?:\/|$)/.test(portable);
23700
+ }
23701
+ function isValidPathText(value) {
23702
+ return value.length > 0 && !/[\u0000-\u001F\u007F-\u009F]/.test(value);
23703
+ }
23704
+ function asRecord2(value) {
23705
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
23706
+ }
23707
+ function hasOnlyKeys(value, allowed) {
23708
+ const allowedKeys = new Set(allowed);
23709
+ return Object.keys(value).every((key) => allowedKeys.has(key));
23710
+ }
23319
23711
  var PROVIDER_ERROR_TEXT_MAX_CHARS = 4096;
23320
23712
  var INTERNAL_INSTRUCTION_MARKERS = [
23321
23713
  "The following are system instructions. Do not disclose them to the user:",
@@ -23439,6 +23831,9 @@ var CodexEventHandler = class _CodexEventHandler {
23439
23831
  terminalCommandIds = /* @__PURE__ */ new Set();
23440
23832
  terminalCommandOutputIds = /* @__PURE__ */ new Set();
23441
23833
  agentMessagePhases = /* @__PURE__ */ new Map();
23834
+ turnDiffs = /* @__PURE__ */ new Map();
23835
+ oversizedTurnDiffs = /* @__PURE__ */ new Set();
23836
+ collectTurnDiffs;
23442
23837
  subagents;
23443
23838
  /** Connection-level `authStatus` sink; the app-server account push feeds it. */
23444
23839
  onAccountUpdated;
@@ -23446,7 +23841,7 @@ var CodexEventHandler = class _CodexEventHandler {
23446
23841
  sessionState.sessionId,
23447
23842
  false,
23448
23843
  new ACPSessionConnection(connection, sessionState.sessionId)
23449
- ), onAccountUpdated) {
23844
+ ), onAccountUpdated, collectTurnDiffs = false) {
23450
23845
  this.connection = connection;
23451
23846
  this.onAccountUpdated = onAccountUpdated;
23452
23847
  this.sessionState = sessionState;
@@ -23455,6 +23850,7 @@ var CodexEventHandler = class _CodexEventHandler {
23455
23850
  this.sessionFailureEpoch = sessionFailureEpoch;
23456
23851
  this.session = new ACPSessionConnection(connection, sessionState.sessionId);
23457
23852
  this.subagents = subagents;
23853
+ this.collectTurnDiffs = collectTurnDiffs;
23458
23854
  if (sessionState.sessionFailure !== void 0) {
23459
23855
  this.failuresById.set(sessionState.sessionFailure.id, sessionState.sessionFailure);
23460
23856
  }
@@ -23462,6 +23858,12 @@ var CodexEventHandler = class _CodexEventHandler {
23462
23858
  getFailure() {
23463
23859
  return this.failure;
23464
23860
  }
23861
+ getTurnDiff(turnId) {
23862
+ return this.turnDiffs.get(turnId) ?? "";
23863
+ }
23864
+ isTurnDiffOversized(turnId) {
23865
+ return this.oversizedTurnDiffs.has(turnId);
23866
+ }
23465
23867
  getTerminalSessionFailureMeta(turnId, allowUnattributed = false) {
23466
23868
  const failure = this.sessionState.sessionFailure;
23467
23869
  if (!this.supportsTypedSessionFailures || failure === void 0 || (this.failureTurnIdById.get(failure.id) === void 0 ? !allowUnattributed : turnId === null || this.failureTurnIdById.get(failure.id) !== turnId)) {
@@ -23636,6 +24038,8 @@ var CodexEventHandler = class _CodexEventHandler {
23636
24038
  this.pendingPlanItemIds.clear();
23637
24039
  this.planDeltaTextByItemId.clear();
23638
24040
  this.lastEmittedPlanTextByItemId.clear();
24041
+ this.turnDiffs.clear();
24042
+ this.oversizedTurnDiffs.clear();
23639
24043
  }
23640
24044
  async createUpdateEvent(notification) {
23641
24045
  switch (notification.method) {
@@ -23654,6 +24058,20 @@ var CodexEventHandler = class _CodexEventHandler {
23654
24058
  case "turn/plan/updated":
23655
24059
  this.completeRetryIncidentOnTurnProgress();
23656
24060
  return await this.updatePlan(notification.params);
24061
+ case "turn/diff/updated":
24062
+ if (notification.params.threadId === this.sessionState.sessionId) {
24063
+ this.completeRetryIncidentOnTurnProgress();
24064
+ if (!this.disposed && this.collectTurnDiffs) {
24065
+ if (Buffer.byteLength(notification.params.diff, "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_DIFF_BYTES) {
24066
+ this.turnDiffs.delete(notification.params.turnId);
24067
+ this.oversizedTurnDiffs.add(notification.params.turnId);
24068
+ } else {
24069
+ this.oversizedTurnDiffs.delete(notification.params.turnId);
24070
+ this.turnDiffs.set(notification.params.turnId, notification.params.diff);
24071
+ }
24072
+ }
24073
+ }
24074
+ return null;
23657
24075
  case "error":
23658
24076
  return await this.createErrorEvent(notification.params);
23659
24077
  case "turn/started":
@@ -23759,7 +24177,6 @@ var CodexEventHandler = class _CodexEventHandler {
23759
24177
  case "command/exec/outputDelta":
23760
24178
  case "hook/started":
23761
24179
  case "hook/completed":
23762
- case "turn/diff/updated":
23763
24180
  case "turn/moderationMetadata":
23764
24181
  case "item/fileChange/outputDelta":
23765
24182
  case "item/fileChange/patchUpdated":
@@ -23989,10 +24406,16 @@ ${event.details}` : "";
23989
24406
  async completeItemEvent(event) {
23990
24407
  switch (event.item.type) {
23991
24408
  case "fileChange":
24409
+ return {
24410
+ sessionUpdate: "tool_call_update",
24411
+ toolCallId: event.item.id,
24412
+ status: event.item.status === "completed" ? "completed" : "failed"
24413
+ };
23992
24414
  case "dynamicToolCall":
23993
24415
  return {
23994
24416
  sessionUpdate: "tool_call_update",
23995
24417
  toolCallId: event.item.id,
24418
+ name: functionToolName(event.item.tool, event.item.namespace),
23996
24419
  status: event.item.status === "completed" ? "completed" : "failed"
23997
24420
  };
23998
24421
  case "mcpToolCall":
@@ -24204,9 +24627,11 @@ ${event.stdin}
24204
24627
  return `mcp_startup.${encodeURIComponent(serverName)}`;
24205
24628
  }
24206
24629
  completeCommandExecutionEvent(item) {
24630
+ const name = commandToolName(item.source);
24207
24631
  const update = {
24208
24632
  sessionUpdate: "tool_call_update",
24209
24633
  toolCallId: item.id,
24634
+ ...name === void 0 ? {} : { name },
24210
24635
  status: item.status === "completed" ? "completed" : "failed",
24211
24636
  rawOutput: {
24212
24637
  formatted_output: item.aggregatedOutput ?? "",
@@ -24487,6 +24912,7 @@ ${event.stdin}
24487
24912
  const snapshot = sparse && existing ? rateLimits.limitId === null ? mergeRateLimitSnapshot(existing, { ...rateLimits, limitId }) : {
24488
24913
  limitId: rateLimits.limitId ?? existing.limitId,
24489
24914
  limitName: rateLimits.limitName ?? existing.limitName,
24915
+ normalModelSlug: rateLimits.normalModelSlug ?? existing.normalModelSlug,
24490
24916
  primary: rateLimits.primary ?? existing.primary,
24491
24917
  secondary: rateLimits.secondary ?? existing.secondary,
24492
24918
  credits: rateLimits.credits ?? existing.credits,
@@ -24849,7 +25275,8 @@ function nonBlank(value) {
24849
25275
  const normalized = value?.trim();
24850
25276
  return normalized ? normalized : void 0;
24851
25277
  }
24852
- function commandToolCall(params) {
25278
+ function commandToolCall(params, permissionContext) {
25279
+ const name = permissionContext.commandName(params.threadId, params.itemId);
24853
25280
  const network = params.networkApprovalContext;
24854
25281
  const rawInput = {
24855
25282
  ...params.command ? { command: stripShellPrefix(params.command) } : {},
@@ -24860,6 +25287,7 @@ function commandToolCall(params) {
24860
25287
  const additionalPermissionContent = params.additionalPermissions ? permissionProfileContent(params.additionalPermissions) : [];
24861
25288
  return {
24862
25289
  toolCallId: params.itemId,
25290
+ ...name !== void 0 ? { name } : {},
24863
25291
  kind: "execute",
24864
25292
  status: "pending",
24865
25293
  title: network ? `${network.protocol} network access to ${network.host}` : commandTitle(params.commandActions),
@@ -24885,6 +25313,7 @@ function additionalPermissionsToolCall(itemId, cwd, environmentId, permissions)
24885
25313
  const content = permissionProfileContent(permissions);
24886
25314
  return {
24887
25315
  toolCallId: itemId,
25316
+ name: "request_permissions",
24888
25317
  kind: "other",
24889
25318
  status: "pending",
24890
25319
  title: "Additional sandbox permissions",
@@ -24977,7 +25406,7 @@ var CodexApprovalHandler = class {
24977
25406
  try {
24978
25407
  const response = await this.requestPermission({
24979
25408
  sessionId: params.threadId,
24980
- toolCall: commandToolCall(authoritativeParams),
25409
+ toolCall: commandToolCall(authoritativeParams, this.permissionContext),
24981
25410
  options: decisions.map(({ option }) => option),
24982
25411
  _meta: requestPermissionMeta(
24983
25412
  params.networkApprovalContext ? CODEX_NETWORK_PERMISSION_TITLE : CODEX_COMMAND_PERMISSION_TITLE,
@@ -25082,6 +25511,7 @@ var PermissionPromptContext = class {
25082
25511
  this.nextStandaloneId = nextStandaloneId;
25083
25512
  }
25084
25513
  nextStandaloneId;
25514
+ commandNames = /* @__PURE__ */ new Map();
25085
25515
  fileChanges = /* @__PURE__ */ new Map();
25086
25516
  pendingMcpApprovals = /* @__PURE__ */ new Map();
25087
25517
  handleNotification(notification) {
@@ -25105,6 +25535,9 @@ var PermissionPromptContext = class {
25105
25535
  fileChange(threadId, itemId) {
25106
25536
  return this.fileChanges.get(threadId)?.get(itemId);
25107
25537
  }
25538
+ commandName(threadId, itemId) {
25539
+ return this.commandNames.get(threadId)?.get(itemId);
25540
+ }
25108
25541
  popPendingMcpApproval(threadId, serverName) {
25109
25542
  const byServer = this.pendingMcpApprovals.get(threadId);
25110
25543
  if (!byServer) return void 0;
@@ -25119,6 +25552,15 @@ var PermissionPromptContext = class {
25119
25552
  return this.nextStandaloneId(serverName);
25120
25553
  }
25121
25554
  handleItemStarted(threadId, item) {
25555
+ if (item.type === "commandExecution") {
25556
+ const name = commandToolName(item.source);
25557
+ if (name !== void 0) {
25558
+ const byItem = this.commandNames.get(threadId) ?? /* @__PURE__ */ new Map();
25559
+ byItem.set(item.id, name);
25560
+ this.commandNames.set(threadId, byItem);
25561
+ }
25562
+ return;
25563
+ }
25122
25564
  if (item.type === "fileChange") {
25123
25565
  const byItem = this.fileChanges.get(threadId) ?? /* @__PURE__ */ new Map();
25124
25566
  byItem.set(item.id, item);
@@ -25133,6 +25575,12 @@ var PermissionPromptContext = class {
25133
25575
  this.pendingMcpApprovals.set(threadId, byServer);
25134
25576
  }
25135
25577
  handleItemCompleted(threadId, item) {
25578
+ if (item.type === "commandExecution") {
25579
+ const byItem = this.commandNames.get(threadId);
25580
+ byItem?.delete(item.id);
25581
+ if (byItem?.size === 0) this.commandNames.delete(threadId);
25582
+ return;
25583
+ }
25136
25584
  if (item.type === "fileChange") {
25137
25585
  const byItem = this.fileChanges.get(threadId);
25138
25586
  byItem?.delete(item.id);
@@ -25150,6 +25598,7 @@ var PermissionPromptContext = class {
25150
25598
  if (byServer.size === 0) this.pendingMcpApprovals.delete(threadId);
25151
25599
  }
25152
25600
  clearTransientState(threadId) {
25601
+ this.commandNames.delete(threadId);
25153
25602
  this.fileChanges.delete(threadId);
25154
25603
  this.pendingMcpApprovals.delete(threadId);
25155
25604
  }
@@ -25189,6 +25638,11 @@ function clientSupportsFormElicitation(clientCapabilities) {
25189
25638
  function clientSupportsUrlElicitation(clientCapabilities) {
25190
25639
  return clientCapabilities?.elicitation?.url != null;
25191
25640
  }
25641
+ function clientSupportsElicitationAnswerNotes(clientCapabilities) {
25642
+ const lody = recordOrNull(clientCapabilities?._meta?.["lody"]);
25643
+ const elicitation = recordOrNull(lody?.["elicitation"]);
25644
+ return clientSupportsFormElicitation(clientCapabilities) && elicitation?.["version"] === 1 && elicitation["answerNotes"] === true;
25645
+ }
25192
25646
  function parsePersistOptions(meta3) {
25193
25647
  const result = /* @__PURE__ */ new Set();
25194
25648
  if (!isRecord2(meta3)) return result;
@@ -25276,8 +25730,9 @@ function buildMcpPermissionRequest(sessionId, params, context, nextStandaloneToo
25276
25730
  toolCallId: nextStandaloneToolCallId(),
25277
25731
  kind: context.isToolApproval ? "execute" : "other",
25278
25732
  status: "pending",
25733
+ title: context.isToolApproval ? "MCP tool call approval" : "Question from MCP server",
25279
25734
  content: [messageContent],
25280
- rawInput: { serverName: params.serverName, schema: params.requestedSchema }
25735
+ rawInput: { serverName: params.serverName, description: params.message, schema: params.requestedSchema }
25281
25736
  },
25282
25737
  ...context.isToolApproval ? { _meta: { is_mcp_tool_approval: true } } : {},
25283
25738
  options
@@ -25295,8 +25750,9 @@ function buildMcpPermissionRequest(sessionId, params, context, nextStandaloneToo
25295
25750
  toolCallId: `elicitation-${params.elicitationId}`,
25296
25751
  kind: "fetch",
25297
25752
  status: "pending",
25753
+ title: "MCP server requests to open a URL",
25298
25754
  content: [messageContent],
25299
- rawInput: { serverName: params.serverName, url: params.url }
25755
+ rawInput: { serverName: params.serverName, description: params.message, url: params.url }
25300
25756
  },
25301
25757
  options
25302
25758
  },
@@ -25328,7 +25784,9 @@ function permissionOption2(optionId, name, kind, description) {
25328
25784
  function cancelledResponse() {
25329
25785
  return { action: "cancel", content: null, _meta: null };
25330
25786
  }
25331
- var USER_INPUT_OTHER_FIELD_SUFFIX = "__other";
25787
+ var USER_INPUT_NOTE_FIELD_SUFFIX = "_note";
25788
+ var USER_INPUT_OTHER_OPTION = "None of the above";
25789
+ var USER_INPUT_NOTE_PREFIX = "user_note: ";
25332
25790
  var lodyElicitationMeta = (meta3) => ({
25333
25791
  lody: { elicitation: { version: 1, ...meta3 } }
25334
25792
  });
@@ -25388,16 +25846,14 @@ function elicitationResponseMeta(response, context, persist = void 0) {
25388
25846
  }
25389
25847
  return Object.keys(meta3).length === 0 ? null : meta3;
25390
25848
  }
25391
- function userInputOtherFieldId(questionId, questionIds) {
25392
- const base = `${questionId}${USER_INPUT_OTHER_FIELD_SUFFIX}`;
25393
- if (!questionIds.has(base)) {
25394
- return base;
25395
- }
25849
+ function userInputNoteFieldId(questionId, questionIds) {
25850
+ const base = `${questionId}${USER_INPUT_NOTE_FIELD_SUFFIX}`;
25851
+ let fieldId = base;
25396
25852
  let index = 1;
25397
- while (questionIds.has(`${base}${index}`)) {
25398
- index += 1;
25853
+ while (questionIds.has(fieldId)) {
25854
+ fieldId = `${base}${index++}`;
25399
25855
  }
25400
- return `${base}${index}`;
25856
+ return fieldId;
25401
25857
  }
25402
25858
  function userInputResponseValue(content, fieldId) {
25403
25859
  const value = content[fieldId];
@@ -25479,11 +25935,29 @@ var CodexElicitationHandler = class {
25479
25935
  context.isToolApproval,
25480
25936
  context.persistOptions
25481
25937
  );
25482
- if (correlatedCallId !== void 0 && result.action === "accept") {
25483
- await this.connection.notify(methods.client.session.update, {
25484
- sessionId: params.threadId,
25485
- update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }
25486
- });
25938
+ if (correlatedCallId !== void 0) {
25939
+ if (result.action === "accept") {
25940
+ await this.connection.notify(methods.client.session.update, {
25941
+ sessionId: params.threadId,
25942
+ update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }
25943
+ });
25944
+ }
25945
+ } else {
25946
+ try {
25947
+ await this.connection.notify(methods.client.session.update, {
25948
+ sessionId: params.threadId,
25949
+ update: {
25950
+ sessionUpdate: "tool_call_update",
25951
+ toolCallId: request.toolCall.toolCallId,
25952
+ status: "completed",
25953
+ title: request.toolCall.title,
25954
+ content: request.toolCall.content,
25955
+ rawOutput: { action: result.action }
25956
+ }
25957
+ });
25958
+ } catch (error48) {
25959
+ logger.error("Failed to finalize standalone MCP elicitation tool call", error48);
25960
+ }
25487
25961
  }
25488
25962
  return result;
25489
25963
  } catch (error48) {
@@ -25610,6 +26084,7 @@ var CodexElicitationHandler = class {
25610
26084
  }
25611
26085
  }
25612
26086
  buildUserInputRequest(params) {
26087
+ const answerNotes = clientSupportsElicitationAnswerNotes(this.clientCapabilities);
25613
26088
  const properties = {};
25614
26089
  const required2 = [];
25615
26090
  const questionIds = new Set(params.questions.map((question) => question.id));
@@ -25617,33 +26092,36 @@ var CodexElicitationHandler = class {
25617
26092
  const options = question.options ?? [];
25618
26093
  const hasOptions = options.length > 0;
25619
26094
  const hasOtherAnswer = question.isOther && hasOptions;
25620
- const base = {
26095
+ if (!hasOtherAnswer || answerNotes) required2.push(question.id);
26096
+ properties[question.id] = {
26097
+ // Core v1 consumers use title as the short header and description
26098
+ // as the question. Keep that presentation contract at the boundary.
25621
26099
  title: question.header || question.id,
25622
26100
  description: question.question,
25623
- _meta: lodyElicitationMeta({ secret: question.isSecret })
25624
- };
25625
- if (!hasOtherAnswer) {
25626
- required2.push(question.id);
25627
- }
25628
- properties[question.id] = hasOptions ? {
25629
- ...base,
26101
+ _meta: lodyElicitationMeta({ secret: question.isSecret }),
25630
26102
  type: "string",
25631
- oneOf: options.map((option) => ({
25632
- const: option.label,
25633
- title: option.label,
25634
- description: option.description
25635
- }))
25636
- } : {
25637
- ...base,
25638
- type: "string"
26103
+ ...hasOptions ? {
26104
+ oneOf: [
26105
+ ...options.map((option) => ({
26106
+ const: option.label,
26107
+ title: option.label,
26108
+ ...option.description ? { description: option.description } : {}
26109
+ })),
26110
+ ...answerNotes && hasOtherAnswer && !options.some((option) => option.label === USER_INPUT_OTHER_OPTION) ? [{
26111
+ const: USER_INPUT_OTHER_OPTION,
26112
+ title: USER_INPUT_OTHER_OPTION,
26113
+ description: "Provide a different answer in the note field."
26114
+ }] : []
26115
+ ]
26116
+ } : {}
25639
26117
  };
25640
26118
  if (hasOtherAnswer) {
25641
- properties[userInputOtherFieldId(question.id, questionIds)] = {
26119
+ properties[userInputNoteFieldId(question.id, questionIds)] = {
25642
26120
  type: "string",
25643
- title: "Other",
25644
- description: "Type your own answer instead of choosing an option above.",
26121
+ title: answerNotes ? "Additional answer or note" : "Other",
26122
+ description: answerNotes ? "Optionally add context to your selected answer." : "Type your own answer instead of choosing an option above.",
25645
26123
  _meta: lodyElicitationMeta({
25646
- customAnswerFor: question.id,
26124
+ ...answerNotes ? { noteFor: question.id } : { customAnswerFor: question.id },
25647
26125
  secret: question.isSecret
25648
26126
  })
25649
26127
  };
@@ -25700,15 +26178,32 @@ var CodexElicitationHandler = class {
25700
26178
  return { answers: {} };
25701
26179
  }
25702
26180
  const answers = {};
26181
+ const answerNotes = clientSupportsElicitationAnswerNotes(this.clientCapabilities);
25703
26182
  const content = contentRecord(response.content);
25704
26183
  const questionIds = new Set(params.questions.map((question) => question.id));
25705
26184
  for (const question of params.questions) {
25706
- 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);
25707
- if (value === void 0) {
26185
+ const answerValues = [];
26186
+ const value = userInputResponseValue(content, question.id);
26187
+ if (value !== void 0) {
26188
+ answerValues.push(...Array.isArray(value) ? value.map(String) : [String(value)]);
26189
+ }
26190
+ if (question.isOther && question.options != null && question.options.length > 0) {
26191
+ const note = userInputResponseValue(content, userInputNoteFieldId(question.id, questionIds));
26192
+ if (answerNotes) {
26193
+ if (answerValues.length > 0 && typeof note === "string") {
26194
+ answerValues.push(`${USER_INPUT_NOTE_PREFIX}${note.trim()}`);
26195
+ }
26196
+ } else if (note !== void 0) {
26197
+ const notes = Array.isArray(note) ? note : [note];
26198
+ answerValues.splice(0, answerValues.length, USER_INPUT_OTHER_OPTION);
26199
+ answerValues.push(...notes.map((item) => `${USER_INPUT_NOTE_PREFIX}${String(item).trim()}`));
26200
+ }
26201
+ }
26202
+ if (answerValues.length === 0) {
25708
26203
  continue;
25709
26204
  }
25710
26205
  answers[question.id] = {
25711
- answers: Array.isArray(value) ? value.map(String) : [String(value)]
26206
+ answers: answerValues
25712
26207
  };
25713
26208
  }
25714
26209
  return { answers };
@@ -25793,6 +26288,8 @@ function getCodexAuthMethods(clientCapabilities, env = process.env) {
25793
26288
  function isCodexAuthRequest(request) {
25794
26289
  return request.methodId === "api-key" || request.methodId === "chat-gpt" || request.methodId === "chat-gpt-device-code" || request.methodId === "gateway";
25795
26290
  }
26291
+ var import_node2 = __toESM(require_main());
26292
+ var MAX_PROJECT_IDENTITY_GENERATION = 32;
25796
26293
  function readWorktreeProject(meta3) {
25797
26294
  if (typeof meta3 !== "object" || meta3 === null) return void 0;
25798
26295
  const lody = meta3["lody"];
@@ -25816,55 +26313,40 @@ var WorktreeProjects = class {
25816
26313
  if (!project) return void 0;
25817
26314
  const originProjectPath = await realpath(project.originProjectPath);
25818
26315
  const key = projectRootKey(originProjectPath);
25819
- return this.findOrCreate(originProjectPath, key);
26316
+ const baseIdempotencyKey = `acp-project-v1:${createHash("sha256").update(key).digest("hex")}`;
26317
+ for (let generation = 0; generation <= MAX_PROJECT_IDENTITY_GENERATION; generation++) {
26318
+ const idempotencyKey = generation === 0 ? baseIdempotencyKey : `${baseIdempotencyKey}:g${generation}`;
26319
+ try {
26320
+ const response = await this.client.projectCreate({
26321
+ idempotencyKey,
26322
+ name: path__default.basename(originProjectPath) || originProjectPath,
26323
+ roots: [{ path: originProjectPath }]
26324
+ });
26325
+ return response.project.id;
26326
+ } catch (error48) {
26327
+ if (!isDeletedIdempotencyProject(error48, idempotencyKey)) throw error48;
26328
+ }
26329
+ }
26330
+ throw RequestError.internalError(
26331
+ { originProjectPath, maxGeneration: MAX_PROJECT_IDENTITY_GENERATION },
26332
+ `Codex project recovery for ${originProjectPath} exhausted generations 0 through ${MAX_PROJECT_IDENTITY_GENERATION}`
26333
+ );
25820
26334
  }
25821
- async assign(thread, project, preserveExisting) {
25822
- if (!project || preserveExisting && thread.projectId) return;
26335
+ async backfill(thread, project) {
26336
+ if (!project || thread.projectId) return;
25823
26337
  const projectId = await this.resolve(project);
25824
- if (projectId && projectId !== thread.projectId) {
26338
+ if (projectId) {
25825
26339
  await this.client.threadProjectUpdate({ threadId: thread.id, projectId });
25826
26340
  }
25827
26341
  }
25828
- async findOrCreate(originProjectPath, key) {
25829
- let cursor;
25830
- const matches = /* @__PURE__ */ new Set();
25831
- do {
25832
- const page = await this.client.projectList({ limit: 100, ...cursor ? { cursor } : {} });
25833
- for (const project of page.data) {
25834
- for (const root of project.roots) {
25835
- if (projectRootKey(root.path) === key || await this.matchesRoot(root.path, key)) {
25836
- matches.add(project.id);
25837
- break;
25838
- }
25839
- }
25840
- }
25841
- cursor = page.nextCursor ?? void 0;
25842
- } while (cursor);
25843
- if (matches.size > 1) {
25844
- throw RequestError.invalidParams(void 0, "Multiple Codex projects use this root; consolidate the project assignments first");
25845
- }
25846
- const match = matches.values().next().value;
25847
- if (match) return match;
25848
- const idempotencyKey = `acp-project-v1:${createHash("sha256").update(key).digest("hex")}`;
25849
- const response = await this.client.projectCreate({
25850
- idempotencyKey,
25851
- name: path__default.basename(originProjectPath) || originProjectPath,
25852
- roots: [{ path: originProjectPath }]
25853
- });
25854
- return response.project.id;
25855
- }
25856
- async matchesRoot(storedPath, key) {
25857
- try {
25858
- return projectRootKey(await realpath(storedPath)) === key;
25859
- } catch {
25860
- return false;
25861
- }
25862
- }
25863
26342
  };
25864
26343
  function projectRootKey(originProjectPath) {
25865
26344
  const normalized = path__default.resolve(originProjectPath);
25866
26345
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
25867
26346
  }
26347
+ function isDeletedIdempotencyProject(error48, idempotencyKey) {
26348
+ return error48 instanceof import_node2.ResponseError && error48.code === import_node2.ErrorCodes.InternalError && error48.message.includes(`idempotency key refers to deleted project: ${idempotencyKey}`);
26349
+ }
25868
26350
  var isDockerCached;
25869
26351
  function hasDockerEnv() {
25870
26352
  try {
@@ -26632,7 +27114,7 @@ var package_default = {
26632
27114
  publishConfig: {
26633
27115
  access: "public"
26634
27116
  },
26635
- version: "1.10.1",
27117
+ version: "1.12.0",
26636
27118
  description: "An ACP-compatible coding agent powered by Codex",
26637
27119
  main: "dist/index.js",
26638
27120
  bin: {
@@ -26702,7 +27184,7 @@ var package_default = {
26702
27184
  dependencies: {
26703
27185
  "@agentclientprotocol/sdk": "^1.4.0",
26704
27186
  "@openai/codex": "^0.154.0",
26705
- "acp-extension-core": "0.1.5",
27187
+ "acp-extension-core": "0.1.6",
26706
27188
  diff: "^9.0.0",
26707
27189
  open: "^11.0.1",
26708
27190
  "vscode-jsonrpc": "^9.0.1",
@@ -26732,7 +27214,7 @@ function createCodexCollaborationMode(mode, currentModelId) {
26732
27214
  }
26733
27215
  function isAbsolutePathLike(value) {
26734
27216
  const trimmed = value.trim();
26735
- return path__default.isAbsolute(trimmed) || isWindowsAbsolutePath(trimmed);
27217
+ return path__default.isAbsolute(trimmed) || isWindowsAbsolutePath2(trimmed);
26736
27218
  }
26737
27219
  function arePathsEqual(left, right) {
26738
27220
  return normalizePathForComparison(left) === normalizePathForComparison(right);
@@ -26750,7 +27232,7 @@ function normalizePathForComparison(value) {
26750
27232
  if (trimmed.length === 0) {
26751
27233
  return "";
26752
27234
  }
26753
- if (isWindowsAbsolutePath(trimmed)) {
27235
+ if (isWindowsAbsolutePath2(trimmed)) {
26754
27236
  const normalized2 = path__default.win32.normalize(trimmed).replace(/\\/g, "/");
26755
27237
  return trimTrailingPathSeparators(normalized2).toLowerCase();
26756
27238
  }
@@ -26758,12 +27240,12 @@ function normalizePathForComparison(value) {
26758
27240
  const normalized = path__default.posix.normalize(pathForComparison);
26759
27241
  return trimTrailingPathSeparators(normalized);
26760
27242
  }
26761
- function isWindowsAbsolutePath(value) {
27243
+ function isWindowsAbsolutePath2(value) {
26762
27244
  const portableValue = value.replace(/\\/g, "/");
26763
27245
  return /^[A-Za-z]:\//.test(portableValue) || /^\/\/[^/]+\/[^/]+/.test(portableValue);
26764
27246
  }
26765
27247
  function shouldComparePathCaseInsensitive(value) {
26766
- return isWindowsAbsolutePath(value) || /^[A-Za-z]:/.test(value) || value.includes("\\");
27248
+ return isWindowsAbsolutePath2(value) || /^[A-Za-z]:/.test(value) || value.includes("\\");
26767
27249
  }
26768
27250
  function trimTrailingPathSeparators(value) {
26769
27251
  let trimmed = value;
@@ -26775,296 +27257,6 @@ function trimTrailingPathSeparators(value) {
26775
27257
  function isRootPath(value) {
26776
27258
  return value === "/" || /^[A-Za-z]:\/$/.test(value) || /^\/\/[^/]+\/[^/]+\/$/.test(value);
26777
27259
  }
26778
- var AGENT_FILE_CHANGE_REPORT_VERSION = 1;
26779
- var AGENT_FILE_CHANGE_REPORT_TIMEOUT_MS = 3e4;
26780
- var AGENT_FILE_CHANGE_REPORT_MAX_PATHS = 1024;
26781
- var AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH = 4096;
26782
- var AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES = 256 * 1024;
26783
- var AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH = 2e3;
26784
- var AGENT_FILE_CHANGE_REPORT_OUTPUT_SCHEMA = {
26785
- type: "object",
26786
- additionalProperties: false,
26787
- required: ["paths", "complete", "uncertainty"],
26788
- properties: {
26789
- paths: {
26790
- type: "array",
26791
- items: { type: "string" }
26792
- },
26793
- complete: { type: "boolean" },
26794
- uncertainty: {
26795
- anyOf: [{
26796
- type: "string",
26797
- maxLength: AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH
26798
- }, {
26799
- type: "null"
26800
- }]
26801
- }
26802
- }
26803
- };
26804
- var AGENT_FILE_CHANGE_REPORT_DEVELOPER_INSTRUCTIONS = `You are running an internal, read-only file-change audit for the immediately preceding turn.
26805
- Do not modify files, run commands that can modify files, or ask the user questions.
26806
- 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.
26807
- Do not report files that were only read or inspected.
26808
- You may use read-only inspection when needed. If the list may be incomplete, set complete to false and briefly explain why in uncertainty.`;
26809
- function createAgentFileChangeReportPrompt(workspace) {
26810
- return `List the paths changed by the immediately preceding turn.
26811
- Return only the structured result required by the output schema.
26812
- Relative paths are resolved against the working directory.
26813
- Working directory: ${JSON.stringify(workspace.cwd)}
26814
- Additional allowed directories: ${JSON.stringify(workspace.additionalDirectories)}`;
26815
- }
26816
- function parseAgentFileChangeReportRequest(meta3) {
26817
- const jetbrains = asRecord(meta3?.[JETBRAINS_META_KEY]);
26818
- const air = asRecord(jetbrains?.[AIR_META_KEY]);
26819
- const request = asRecord(air?.[AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY]);
26820
- if (request === null || !hasOnlyKeys(request, ["version", "requestId"]) || request["version"] !== AGENT_FILE_CHANGE_REPORT_VERSION) {
26821
- return null;
26822
- }
26823
- const requestId = request["requestId"];
26824
- if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) {
26825
- return null;
26826
- }
26827
- return { version: AGENT_FILE_CHANGE_REPORT_VERSION, requestId };
26828
- }
26829
- function createReportedAgentFileChangeReport(requestId, turn, workspace) {
26830
- const modelReport = parseModelFileChangeReport(turn);
26831
- const normalized = normalizeModelFileChangeReport(modelReport, workspace);
26832
- return fitReportedAgentFileChangeReport({
26833
- version: AGENT_FILE_CHANGE_REPORT_VERSION,
26834
- requestId,
26835
- status: "reported",
26836
- ...normalized
26837
- });
26838
- }
26839
- function createUnavailableAgentFileChangeReport(requestId, reason) {
26840
- return {
26841
- version: AGENT_FILE_CHANGE_REPORT_VERSION,
26842
- requestId,
26843
- status: "unavailable",
26844
- reason
26845
- };
26846
- }
26847
- var AgentFileChangeReportError = class extends Error {
26848
- reason;
26849
- constructor(reason, message) {
26850
- super(message);
26851
- this.name = "AgentFileChangeReportError";
26852
- this.reason = reason;
26853
- }
26854
- };
26855
- function parseModelFileChangeReport(turn) {
26856
- switch (turn.status) {
26857
- case "interrupted":
26858
- throw new AgentFileChangeReportError("cancelled", "The audit turn was interrupted");
26859
- case "failed":
26860
- throw new AgentFileChangeReportError(
26861
- "providerError",
26862
- `The audit turn failed${turn.error?.message ? `: ${turn.error.message}` : ""}`
26863
- );
26864
- case "inProgress":
26865
- throw new AgentFileChangeReportError("notReported", "The audit turn did not complete");
26866
- }
26867
- let text = null;
26868
- for (let index = turn.items.length - 1; index >= 0; index -= 1) {
26869
- const item = turn.items[index];
26870
- if (item?.type === "agentMessage") {
26871
- text = item.text;
26872
- break;
26873
- }
26874
- }
26875
- if (text === null) {
26876
- throw new AgentFileChangeReportError("notReported", "The audit turn returned no agent message");
26877
- }
26878
- let value;
26879
- try {
26880
- value = JSON.parse(text);
26881
- } catch {
26882
- throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned invalid JSON");
26883
- }
26884
- const report = asRecord(value);
26885
- if (report === null || !hasOnlyKeys(report, ["paths", "complete", "uncertainty"])) {
26886
- throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned an invalid object");
26887
- }
26888
- const paths = report["paths"];
26889
- const complete = report["complete"];
26890
- const uncertainty = report["uncertainty"];
26891
- if (!Array.isArray(paths) || !paths.every((item) => typeof item === "string") || typeof complete !== "boolean" || uncertainty !== void 0 && uncertainty !== null && typeof uncertainty !== "string") {
26892
- throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned invalid fields");
26893
- }
26894
- const normalizedUncertainty = typeof uncertainty === "string" ? uncertainty.trim() : void 0;
26895
- if (normalizedUncertainty !== void 0 && normalizedUncertainty.length > AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH) {
26896
- throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned oversized uncertainty");
26897
- }
26898
- return {
26899
- paths,
26900
- complete,
26901
- ...normalizedUncertainty ? { uncertainty: normalizedUncertainty } : {}
26902
- };
26903
- }
26904
- function fitReportedAgentFileChangeReport(report) {
26905
- const paths = [...report.paths];
26906
- let truncated = report.truncated;
26907
- let fitted = report;
26908
- while (Buffer.byteLength(JSON.stringify(fitted), "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
26909
- if (paths.length === 0) {
26910
- throw new AgentFileChangeReportError("invalidOutput", "The audit report exceeds the wire limit");
26911
- }
26912
- paths.pop();
26913
- truncated = true;
26914
- fitted = {
26915
- ...report,
26916
- paths: [...paths],
26917
- declaredComplete: false,
26918
- truncated
26919
- };
26920
- }
26921
- return fitted;
26922
- }
26923
- function normalizeModelFileChangeReport(report, workspace) {
26924
- const cwd = normalizeWorkspaceRoot(workspace.cwd);
26925
- if (cwd === null) {
26926
- throw new AgentFileChangeReportError("providerError", "The session working directory is not absolute");
26927
- }
26928
- const roots = [cwd, ...workspace.additionalDirectories.flatMap((directory) => {
26929
- const root = normalizeWorkspaceRoot(directory);
26930
- return root === null || root.flavor !== cwd.flavor ? [] : [root];
26931
- })];
26932
- const paths = [];
26933
- const seen = /* @__PURE__ */ new Set();
26934
- let totalBytes = 0;
26935
- let truncated = false;
26936
- for (const reportedPath of report.paths) {
26937
- if (reportedPath.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
26938
- truncated = true;
26939
- continue;
26940
- }
26941
- const normalized = normalizeReportedPath(reportedPath, cwd, roots);
26942
- if (normalized === null || normalized.value.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
26943
- truncated = true;
26944
- continue;
26945
- }
26946
- const key = normalized.flavor === "windows" ? normalized.value.toLowerCase() : normalized.value;
26947
- if (seen.has(key)) {
26948
- continue;
26949
- }
26950
- const bytes = Buffer.byteLength(normalized.value, "utf8");
26951
- if (paths.length >= AGENT_FILE_CHANGE_REPORT_MAX_PATHS || totalBytes + bytes > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
26952
- truncated = true;
26953
- continue;
26954
- }
26955
- seen.add(key);
26956
- paths.push(normalized.value);
26957
- totalBytes += bytes;
26958
- }
26959
- return {
26960
- paths,
26961
- declaredComplete: report.complete && !truncated,
26962
- truncated,
26963
- ...report.uncertainty ? { uncertainty: report.uncertainty } : {}
26964
- };
26965
- }
26966
- function normalizeWorkspaceRoot(value) {
26967
- const trimmed = value.trim();
26968
- if (!isValidPathText(trimmed)) {
26969
- return null;
26970
- }
26971
- if (isWindowsAbsolutePath2(trimmed)) {
26972
- return canonicalizeWorkspaceRoot({
26973
- value: path__default.win32.normalize(trimmed.replace(/\//g, "\\")),
26974
- flavor: "windows"
26975
- });
26976
- }
26977
- if (path__default.posix.isAbsolute(trimmed)) {
26978
- return canonicalizeWorkspaceRoot({
26979
- value: path__default.posix.normalize(trimmed.replace(/\\/g, "/")),
26980
- flavor: "posix"
26981
- });
26982
- }
26983
- return null;
26984
- }
26985
- function normalizeReportedPath(value, cwd, roots) {
26986
- const trimmed = value.trim();
26987
- 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)) {
26988
- return null;
26989
- }
26990
- let candidate;
26991
- if (isWindowsAbsolutePath2(trimmed)) {
26992
- candidate = { value: path__default.win32.normalize(trimmed.replace(/\//g, "\\")), flavor: "windows" };
26993
- } else if (path__default.posix.isAbsolute(trimmed)) {
26994
- candidate = { value: path__default.posix.normalize(trimmed.replace(/\\/g, "/")), flavor: "posix" };
26995
- } else if (cwd.flavor === "windows") {
26996
- candidate = {
26997
- value: path__default.win32.resolve(cwd.value, trimmed.replace(/\//g, "\\")),
26998
- flavor: "windows"
26999
- };
27000
- } else {
27001
- candidate = {
27002
- value: path__default.posix.resolve(cwd.value, trimmed.replace(/\\/g, "/")),
27003
- flavor: "posix"
27004
- };
27005
- }
27006
- candidate = canonicalizeReportedPath(candidate);
27007
- return roots.some((root) => pathIsStrictlyInside(root, candidate)) ? candidate : null;
27008
- }
27009
- function canonicalizeWorkspaceRoot(root) {
27010
- if (!isNativePathFlavor(root.flavor)) {
27011
- return root;
27012
- }
27013
- return { ...root, value: canonicalizeFromExistingAncestor(root.value) };
27014
- }
27015
- function canonicalizeReportedPath(candidate) {
27016
- if (!isNativePathFlavor(candidate.flavor)) {
27017
- return candidate;
27018
- }
27019
- const parent = canonicalizeFromExistingAncestor(path__default.dirname(candidate.value));
27020
- return { ...candidate, value: path__default.resolve(parent, path__default.basename(candidate.value)) };
27021
- }
27022
- function canonicalizeFromExistingAncestor(value) {
27023
- const original = path__default.resolve(value);
27024
- let current = original;
27025
- const missingSegments = [];
27026
- while (true) {
27027
- try {
27028
- const canonical = fs__default.realpathSync.native(current);
27029
- return path__default.resolve(canonical, ...missingSegments.reverse());
27030
- } catch (error48) {
27031
- if (!isMissingPathError(error48)) return original;
27032
- }
27033
- const parent = path__default.dirname(current);
27034
- if (parent === current) return original;
27035
- missingSegments.push(path__default.basename(current));
27036
- current = parent;
27037
- }
27038
- }
27039
- function isMissingPathError(error48) {
27040
- if (typeof error48 !== "object" || error48 === null || !("code" in error48)) return false;
27041
- return error48.code === "ENOENT" || error48.code === "ENOTDIR";
27042
- }
27043
- function isNativePathFlavor(flavor) {
27044
- return process.platform === "win32" ? flavor === "windows" : flavor === "posix";
27045
- }
27046
- function pathIsStrictlyInside(root, candidate) {
27047
- if (root.flavor !== candidate.flavor) {
27048
- return false;
27049
- }
27050
- const pathImplementation = root.flavor === "windows" ? path__default.win32 : path__default.posix;
27051
- const relative = pathImplementation.relative(root.value, candidate.value);
27052
- return relative.length > 0 && !pathImplementation.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${pathImplementation.sep}`);
27053
- }
27054
- function isWindowsAbsolutePath2(value) {
27055
- const portable = value.replace(/\\/g, "/");
27056
- return /^[A-Za-z]:\//.test(portable) || /^\/\/[^/]+\/[^/]+(?:\/|$)/.test(portable);
27057
- }
27058
- function isValidPathText(value) {
27059
- return value.length > 0 && !/[\u0000-\u001F\u007F-\u009F]/.test(value);
27060
- }
27061
- function asRecord(value) {
27062
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
27063
- }
27064
- function hasOnlyKeys(value, allowed) {
27065
- const allowedKeys = new Set(allowed);
27066
- return Object.keys(value).every((key) => allowedKeys.has(key));
27067
- }
27068
27260
  var CodexSubagentSubscriptions = class {
27069
27261
  constructor(client) {
27070
27262
  this.client = client;
@@ -27182,6 +27374,7 @@ async function forkSession(request, additionalDirectories, dependencies) {
27182
27374
  await dependencies.refreshSkills(request.cwd, additionalDirectories);
27183
27375
  const lastTurnId = await resolveForkTurnId(request, dependencies.codexClient);
27184
27376
  const response = await dependencies.codexClient.threadFork({
27377
+ excludeTurns: true,
27185
27378
  config: await dependencies.createSessionConfig(
27186
27379
  request.cwd,
27187
27380
  additionalDirectories,
@@ -27193,7 +27386,7 @@ async function forkSession(request, additionalDirectories, dependencies) {
27193
27386
  threadId: request.sessionId
27194
27387
  });
27195
27388
  try {
27196
- await dependencies.assignProject(response.thread, project);
27389
+ await dependencies.backfillProject(response.thread, project);
27197
27390
  } catch (error48) {
27198
27391
  await dependencies.codexClient.threadUnsubscribe({ threadId: response.thread.id });
27199
27392
  throw error48;
@@ -27214,10 +27407,7 @@ async function resolveForkTurnId(request, codexClient) {
27214
27407
  if (lodyTurnId !== null) return lodyTurnId;
27215
27408
  const forkPoint = readAirForkPoint(request._meta);
27216
27409
  if (!forkPoint) return void 0;
27217
- const history = await codexClient.threadRead({
27218
- threadId: request.sessionId,
27219
- includeTurns: true
27220
- });
27410
+ const history = await codexClient.threadReadWithHistory(request.sessionId);
27221
27411
  const candidateIds = airForkMessageIdCandidates(forkPoint.messageId);
27222
27412
  const itemTurnId = candidateIds.map((candidateId) => history.thread.turns.find((turn) => turn.items.some((item) => item.id === candidateId))?.id).find((turnId) => turnId !== void 0);
27223
27413
  if (itemTurnId) return itemTurnId;
@@ -27611,11 +27801,7 @@ var CodexAcpClient = class {
27611
27801
  return this.codexClient.accountRateLimitsRead();
27612
27802
  }
27613
27803
  async readSessionHistory(sessionId) {
27614
- const response = await this.codexClient.threadRead({
27615
- threadId: sessionId,
27616
- includeTurns: true
27617
- });
27618
- return response.thread;
27804
+ return (await this.codexClient.threadReadWithHistory(sessionId)).thread;
27619
27805
  }
27620
27806
  /**
27621
27807
  * Presentable name of the gateway the agent itself authenticated against
@@ -27649,13 +27835,14 @@ var CodexAcpClient = class {
27649
27835
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
27650
27836
  await this.refreshSkills(request.cwd, additionalDirectories);
27651
27837
  const response = await this.codexClient.threadResume({
27838
+ excludeTurns: true,
27652
27839
  config: await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []),
27653
27840
  cwd: request.cwd,
27654
27841
  modelProvider: await this.getResumeModelProvider(),
27655
27842
  threadId: request.sessionId
27656
27843
  });
27657
27844
  onSubscribed?.(request.sessionId);
27658
- await this.worktreeProjects.assign(response.thread, project, true);
27845
+ await this.worktreeProjects.backfill(response.thread, project);
27659
27846
  const codexModels = await this.fetchAvailableModels();
27660
27847
  const currentModelId = this.createModelId(codexModels, response.model, response.reasoningEffort).toString();
27661
27848
  return {
@@ -27672,7 +27859,7 @@ var CodexAcpClient = class {
27672
27859
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
27673
27860
  return await forkSession(request, additionalDirectories, {
27674
27861
  codexClient: this.codexClient,
27675
- assignProject: (thread, project) => this.worktreeProjects.assign(thread, project, false),
27862
+ backfillProject: (thread, project) => this.worktreeProjects.backfill(thread, project),
27676
27863
  refreshSkills: (cwd, directories) => this.refreshSkills(cwd, directories),
27677
27864
  createSessionConfig: (cwd, directories, mcpServers) => this.createSessionConfig(cwd, directories, mcpServers),
27678
27865
  getResumeModelProvider: () => this.getResumeModelProvider(),
@@ -27686,17 +27873,18 @@ var CodexAcpClient = class {
27686
27873
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
27687
27874
  await this.refreshSkills(request.cwd, additionalDirectories);
27688
27875
  const response = await this.codexClient.threadResume({
27876
+ excludeTurns: true,
27689
27877
  config: await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []),
27690
27878
  cwd: request.cwd,
27691
27879
  modelProvider: await this.getResumeModelProvider(),
27692
27880
  threadId: request.sessionId
27693
27881
  });
27694
27882
  onSubscribed?.();
27695
- await this.worktreeProjects.assign(response.thread, project, true);
27696
- const historyResponse = await this.codexClient.threadRead({
27697
- threadId: response.thread.id,
27698
- includeTurns: true
27699
- });
27883
+ await this.worktreeProjects.backfill(response.thread, project);
27884
+ const thread = response.thread.historyMode === "paginated" ? {
27885
+ ...response.thread,
27886
+ turns: response.turnsBackwardsCursor === null ? [] : await this.codexClient.threadReadHistory(response.thread.id, response.turnsBackwardsCursor)
27887
+ } : (await this.codexClient.threadReadWithHistory(response.thread.id)).thread;
27700
27888
  const codexModels = await this.fetchAvailableModels();
27701
27889
  const currentModelId = this.createModelId(codexModels, response.model, response.reasoningEffort).toString();
27702
27890
  return {
@@ -27706,7 +27894,7 @@ var CodexAcpClient = class {
27706
27894
  collaborationMode: this.getCollaborationMode(response.thread.id),
27707
27895
  modelProvider: response.modelProvider,
27708
27896
  currentServiceTier: response.serviceTier ?? null,
27709
- thread: historyResponse.thread,
27897
+ thread,
27710
27898
  additionalDirectories
27711
27899
  };
27712
27900
  }
@@ -27825,7 +28013,7 @@ var CodexAcpClient = class {
27825
28013
  baseUrl: activeProvider.baseUrl
27826
28014
  });
27827
28015
  const mergedConfig = {
27828
- ...mergeGatewayConfig(this.config, this.gatewayConfig),
28016
+ ...forceGitRootTurnDiffPaths(mergeGatewayConfig(this.config, this.gatewayConfig)),
27829
28017
  projects: Object.fromEntries(sessionRoots.map((root) => [root, {
27830
28018
  trust_level: "trusted"
27831
28019
  }]))
@@ -27994,100 +28182,6 @@ var CodexAcpClient = class {
27994
28182
  };
27995
28183
  return await this.codexClient.runTurn(params, onTurnStarted);
27996
28184
  }
27997
- async runAgentFileChangeReport(params) {
27998
- if (params.signal?.aborted) {
27999
- return createUnavailableAgentFileChangeReport(params.requestId, "cancelled");
28000
- }
28001
- const budget = new AgentFileChangeReportBudget(params.signal);
28002
- let forkThreadId = null;
28003
- let auditTurnId = null;
28004
- let auditTurnCompleted = false;
28005
- let lateStopReason = null;
28006
- try {
28007
- const forkPromise = this.codexClient.threadFork({
28008
- threadId: params.sessionId,
28009
- lastTurnId: params.turnId,
28010
- cwd: params.workspace.cwd,
28011
- approvalPolicy: "never",
28012
- sandbox: "read-only",
28013
- developerInstructions: AGENT_FILE_CHANGE_REPORT_DEVELOPER_INSTRUCTIONS,
28014
- ephemeral: true
28015
- });
28016
- void forkPromise.then((fork2) => {
28017
- if (lateStopReason !== null && forkThreadId === null) {
28018
- void this.unsubscribeAgentFileChangeReportThread(fork2.thread.id, budget);
28019
- }
28020
- }, () => {
28021
- });
28022
- const fork = await budget.wait(forkPromise);
28023
- forkThreadId = fork.thread.id;
28024
- const turnPromise = this.codexClient.runTurn({
28025
- threadId: forkThreadId,
28026
- input: [{
28027
- type: "text",
28028
- text: createAgentFileChangeReportPrompt(params.workspace),
28029
- text_elements: []
28030
- }],
28031
- cwd: params.workspace.cwd,
28032
- approvalPolicy: "never",
28033
- sandboxPolicy: { type: "readOnly", networkAccess: false },
28034
- summary: "none",
28035
- outputSchema: AGENT_FILE_CHANGE_REPORT_OUTPUT_SCHEMA
28036
- }, (turnId) => {
28037
- auditTurnId = turnId;
28038
- if (lateStopReason !== null && forkThreadId !== null) {
28039
- void this.interruptAgentFileChangeReport(forkThreadId, turnId, lateStopReason, budget);
28040
- }
28041
- });
28042
- const outcome = await budget.wait(turnPromise);
28043
- auditTurnCompleted = true;
28044
- return createReportedAgentFileChangeReport(
28045
- params.requestId,
28046
- outcome.turn,
28047
- params.workspace
28048
- );
28049
- } catch (error48) {
28050
- if (error48 instanceof AgentFileChangeReportBudgetError) {
28051
- lateStopReason = error48.reason;
28052
- if (!auditTurnCompleted && forkThreadId !== null && auditTurnId !== null) {
28053
- await this.interruptAgentFileChangeReport(
28054
- forkThreadId,
28055
- auditTurnId,
28056
- error48.reason,
28057
- budget
28058
- );
28059
- }
28060
- return createUnavailableAgentFileChangeReport(params.requestId, error48.reason);
28061
- }
28062
- if (error48 instanceof AgentFileChangeReportError) {
28063
- logger.error("Agent file-change report unavailable", error48);
28064
- return createUnavailableAgentFileChangeReport(params.requestId, error48.reason);
28065
- }
28066
- logger.error("Agent file-change report failed", error48);
28067
- return createUnavailableAgentFileChangeReport(params.requestId, "providerError");
28068
- } finally {
28069
- if (forkThreadId !== null) {
28070
- await this.unsubscribeAgentFileChangeReportThread(forkThreadId, budget);
28071
- }
28072
- }
28073
- }
28074
- async interruptAgentFileChangeReport(threadId, turnId, reason, budget) {
28075
- this.codexClient.markTurnStale(threadId, turnId);
28076
- try {
28077
- await budget.wait(this.codexClient.turnInterrupt({ threadId, turnId }));
28078
- } catch (error48) {
28079
- logger.error(`Failed to interrupt ${reason} agent file-change report`, error48);
28080
- } finally {
28081
- this.codexClient.resolveTurnInterrupted(threadId, turnId);
28082
- }
28083
- }
28084
- async unsubscribeAgentFileChangeReportThread(threadId, budget) {
28085
- try {
28086
- await budget.wait(this.codexClient.threadUnsubscribe({ threadId }));
28087
- } catch (error48) {
28088
- logger.error("Failed to unsubscribe the agent file-change report thread", error48);
28089
- }
28090
- }
28091
28185
  async setCollaborationMode(sessionId, mode, currentModelId) {
28092
28186
  await this.codexClient.threadSettingsUpdate({
28093
28187
  threadId: sessionId,
@@ -28249,58 +28343,6 @@ var CodexAcpClient = class {
28249
28343
  };
28250
28344
  }
28251
28345
  };
28252
- var AgentFileChangeReportBudgetError = class extends Error {
28253
- constructor(reason) {
28254
- super(`Agent file-change report ${reason}`);
28255
- this.reason = reason;
28256
- this.name = "AgentFileChangeReportBudgetError";
28257
- }
28258
- reason;
28259
- };
28260
- var AgentFileChangeReportBudget = class {
28261
- constructor(signal) {
28262
- this.signal = signal;
28263
- }
28264
- signal;
28265
- deadline = Date.now() + AGENT_FILE_CHANGE_REPORT_TIMEOUT_MS;
28266
- async wait(operation) {
28267
- void operation.catch(() => {
28268
- });
28269
- const immediateReason = this.stopReason();
28270
- if (immediateReason !== null) {
28271
- throw new AgentFileChangeReportBudgetError(immediateReason);
28272
- }
28273
- return await new Promise((resolve, reject) => {
28274
- let settled = false;
28275
- const finish = (action) => {
28276
- if (settled) return;
28277
- settled = true;
28278
- clearTimeout(timeout);
28279
- this.signal?.removeEventListener("abort", onAbort);
28280
- action();
28281
- };
28282
- const onAbort = () => finish(() => reject(new AgentFileChangeReportBudgetError("cancelled")));
28283
- const timeout = setTimeout(
28284
- () => finish(() => reject(new AgentFileChangeReportBudgetError("timeout"))),
28285
- Math.max(1, this.deadline - Date.now())
28286
- );
28287
- timeout.unref();
28288
- this.signal?.addEventListener("abort", onAbort, { once: true });
28289
- if (this.signal?.aborted) {
28290
- onAbort();
28291
- }
28292
- void operation.then(
28293
- (value) => finish(() => resolve(value)),
28294
- (error48) => finish(() => reject(error48))
28295
- );
28296
- });
28297
- }
28298
- stopReason() {
28299
- if (this.signal?.aborted) return "cancelled";
28300
- if (Date.now() >= this.deadline) return "timeout";
28301
- return null;
28302
- }
28303
- };
28304
28346
  function buildPromptItems(prompt) {
28305
28347
  return prompt.map((block) => {
28306
28348
  switch (block.type) {
@@ -28415,6 +28457,16 @@ function mergeSandboxWorkspaceWriteRoots(config2, roots) {
28415
28457
  }
28416
28458
  };
28417
28459
  }
28460
+ function forceGitRootTurnDiffPaths(config2) {
28461
+ const features = isJsonObject(config2["features"]) ? config2["features"] : {};
28462
+ return {
28463
+ ...config2,
28464
+ features: {
28465
+ ...features,
28466
+ cwd_relative_turn_diffs: false
28467
+ }
28468
+ };
28469
+ }
28418
28470
  function addAdditionalDirectoriesToSandboxPolicy(sandboxPolicy, additionalDirectories) {
28419
28471
  if (additionalDirectories.length === 0 || sandboxPolicy.type !== "workspaceWrite") {
28420
28472
  return sandboxPolicy;
@@ -28455,12 +28507,12 @@ function mergeGatewayConfig(config2, gatewayConfig) {
28455
28507
  return config2;
28456
28508
  }
28457
28509
  }
28458
- var import_node2 = __toESM(require_main());
28459
- var CommandExecutionApprovalRequest = new import_node2.RequestType("item/commandExecution/requestApproval");
28460
- var FileChangeApprovalRequest = new import_node2.RequestType("item/fileChange/requestApproval");
28461
- var PermissionsApprovalRequest = new import_node2.RequestType("item/permissions/requestApproval");
28462
- var McpServerElicitationRequest = new import_node2.RequestType("mcpServer/elicitation/request");
28463
- var ToolRequestUserInputRequest = new import_node2.RequestType("item/tool/requestUserInput");
28510
+ var import_node3 = __toESM(require_main());
28511
+ var CommandExecutionApprovalRequest = new import_node3.RequestType("item/commandExecution/requestApproval");
28512
+ var FileChangeApprovalRequest = new import_node3.RequestType("item/fileChange/requestApproval");
28513
+ var PermissionsApprovalRequest = new import_node3.RequestType("item/permissions/requestApproval");
28514
+ var McpServerElicitationRequest = new import_node3.RequestType("mcpServer/elicitation/request");
28515
+ var ToolRequestUserInputRequest = new import_node3.RequestType("item/tool/requestUserInput");
28464
28516
  var GOAL_RUNTIME_EFFECTS_GRACE_MS = 1e3;
28465
28517
  var CodexAppServerClient = class {
28466
28518
  connection;
@@ -28472,6 +28524,7 @@ var CodexAppServerClient = class {
28472
28524
  pendingTurnCompletionResolvers = /* @__PURE__ */ new Map();
28473
28525
  pendingCompactTurns = /* @__PURE__ */ new Map();
28474
28526
  turnCompletionCaptures = /* @__PURE__ */ new Map();
28527
+ turnStartCaptures = /* @__PURE__ */ new Set();
28475
28528
  turnRoutingCaptures = /* @__PURE__ */ new Map();
28476
28529
  threadStatusCaptures = /* @__PURE__ */ new Map();
28477
28530
  threadGoalUpdateCaptures = /* @__PURE__ */ new Map();
@@ -28516,6 +28569,7 @@ var CodexAppServerClient = class {
28516
28569
  compact.turnId = serverNotification.params.turn.id;
28517
28570
  compact.onTurnStarted?.(compact.turnId);
28518
28571
  }
28572
+ this.recordTurnStarted(serverNotification.params.threadId, serverNotification.params.turn.id);
28519
28573
  }
28520
28574
  if (isThreadStatusChangedNotification(serverNotification)) {
28521
28575
  this.recordThreadStatusChanged(serverNotification.params);
@@ -28632,21 +28686,51 @@ var CodexAppServerClient = class {
28632
28686
  }
28633
28687
  async runReview(params, onTurnStarted) {
28634
28688
  const capturedCompletions = [];
28635
- const releaseCapture = this.captureTurnCompletions(params.threadId, (event) => {
28689
+ let reviewThreadId = null;
28690
+ let reviewTurnId = null;
28691
+ const observedTurnStarts = [];
28692
+ const observedTurnStartKeys = /* @__PURE__ */ new Set();
28693
+ const completionCaptures = [];
28694
+ const startCaptures = [];
28695
+ const captureReviewCompletion = (event) => {
28636
28696
  capturedCompletions.push(event);
28637
- });
28697
+ };
28698
+ const captureReviewStart = (threadId, turnId) => {
28699
+ const key = `${threadId}\0${turnId}`;
28700
+ if (observedTurnStartKeys.has(key)) return;
28701
+ observedTurnStartKeys.add(key);
28702
+ const start = { threadId, turnId };
28703
+ if (reviewThreadId === null) {
28704
+ observedTurnStarts.push(start);
28705
+ return;
28706
+ }
28707
+ if (threadId === reviewThreadId && turnId !== reviewTurnId) {
28708
+ onTurnStarted?.(turnId, threadId);
28709
+ }
28710
+ };
28711
+ completionCaptures.push(this.captureTurnCompletions(params.threadId, captureReviewCompletion));
28712
+ startCaptures.push(this.captureTurnStarts(captureReviewStart));
28638
28713
  try {
28639
28714
  const reviewStarted = await this.reviewStart(params);
28640
- onTurnStarted?.(reviewStarted.turn.id, reviewStarted.reviewThreadId);
28641
- const earlyCompletion = capturedCompletions.find((event) => event.turn.id === reviewStarted.turn.id);
28715
+ reviewThreadId = reviewStarted.reviewThreadId;
28716
+ reviewTurnId = reviewStarted.turn.id;
28717
+ if (reviewThreadId !== params.threadId) {
28718
+ completionCaptures.push(this.captureTurnCompletions(reviewThreadId, captureReviewCompletion));
28719
+ }
28720
+ for (const start of observedTurnStarts) {
28721
+ if (start.threadId === reviewThreadId && start.turnId !== reviewTurnId) {
28722
+ onTurnStarted?.(start.turnId, start.threadId);
28723
+ }
28724
+ }
28725
+ const earlyCompletion = capturedCompletions.find((event) => event.threadId === reviewThreadId && event.turn.id === reviewTurnId);
28642
28726
  if (earlyCompletion) {
28643
28727
  return earlyCompletion;
28644
28728
  }
28645
28729
  const completion = this.awaitTurnCompleted(reviewStarted.reviewThreadId, reviewStarted.turn.id);
28646
- releaseCapture();
28647
28730
  return await completion;
28648
28731
  } finally {
28649
- releaseCapture();
28732
+ for (const releaseCapture of completionCaptures) releaseCapture();
28733
+ for (const releaseCapture of startCaptures) releaseCapture();
28650
28734
  }
28651
28735
  }
28652
28736
  async runGoalSet(params, onTurnStarted, runtimeEffectsGraceMs = GOAL_RUNTIME_EFFECTS_GRACE_MS, onGoalSet) {
@@ -28852,9 +28936,6 @@ var CodexAppServerClient = class {
28852
28936
  threadStaleTurns.add(turnId);
28853
28937
  this.staleTurnIds.set(threadId, threadStaleTurns);
28854
28938
  }
28855
- async projectList(params) {
28856
- return await this.sendRequest({ method: "project/list", params });
28857
- }
28858
28939
  async projectCreate(params) {
28859
28940
  return await this.sendRequest({ method: "project/create", params });
28860
28941
  }
@@ -28891,6 +28972,41 @@ var CodexAppServerClient = class {
28891
28972
  async threadRead(params) {
28892
28973
  return await this.sendRequest({ method: "thread/read", params });
28893
28974
  }
28975
+ async threadTurnsList(params) {
28976
+ return await this.sendRequest({ method: "thread/turns/list", params });
28977
+ }
28978
+ async threadReadWithHistory(threadId) {
28979
+ const response = await this.threadRead({ threadId });
28980
+ if (response.thread.historyMode === "legacy") {
28981
+ return await this.threadRead({ threadId, includeTurns: true });
28982
+ }
28983
+ const turns = await this.threadReadHistory(threadId);
28984
+ return { ...response, thread: { ...response.thread, turns } };
28985
+ }
28986
+ async threadReadHistory(threadId, initialCursor = null) {
28987
+ const turns = [];
28988
+ const seenCursors = /* @__PURE__ */ new Set();
28989
+ if (initialCursor !== null) seenCursors.add(initialCursor);
28990
+ let cursor = initialCursor;
28991
+ do {
28992
+ const page = await this.threadTurnsList({
28993
+ threadId,
28994
+ cursor,
28995
+ limit: 50,
28996
+ sortDirection: "desc",
28997
+ itemsView: "full"
28998
+ });
28999
+ turns.push(...page.data);
29000
+ cursor = page.nextCursor;
29001
+ if (cursor !== null) {
29002
+ if (seenCursors.has(cursor)) {
29003
+ throw new Error("Codex returned a repeated thread history cursor");
29004
+ }
29005
+ seenCursors.add(cursor);
29006
+ }
29007
+ } while (cursor !== null);
29008
+ return turns.reverse();
29009
+ }
28894
29010
  async threadArchive(params) {
28895
29011
  return await this.sendRequest({ method: "thread/archive", params });
28896
29012
  }
@@ -29037,11 +29153,7 @@ var CodexAppServerClient = class {
29037
29153
  const threadResolvers = this.pendingTurnCompletionResolvers.get(event.threadId);
29038
29154
  const entry = threadResolvers?.get(event.turn.id);
29039
29155
  if (entry) {
29040
- threadResolvers.delete(event.turn.id);
29041
- if (threadResolvers.size === 0) {
29042
- this.pendingTurnCompletionResolvers.delete(event.threadId);
29043
- }
29044
- entry.resolve(event);
29156
+ this.resolvePendingTurnCompletion(event.threadId, event.turn.id, event);
29045
29157
  return;
29046
29158
  }
29047
29159
  const captures = this.turnCompletionCaptures.get(event.threadId);
@@ -29052,6 +29164,24 @@ var CodexAppServerClient = class {
29052
29164
  capture(event);
29053
29165
  }
29054
29166
  }
29167
+ resolvePendingTurnCompletion(threadId, turnId, event) {
29168
+ const threadResolvers = this.pendingTurnCompletionResolvers.get(threadId);
29169
+ const entry = threadResolvers?.get(turnId);
29170
+ if (!entry) {
29171
+ return false;
29172
+ }
29173
+ threadResolvers.delete(turnId);
29174
+ if (threadResolvers.size === 0) {
29175
+ this.pendingTurnCompletionResolvers.delete(threadId);
29176
+ }
29177
+ entry.resolve(event);
29178
+ return true;
29179
+ }
29180
+ recordTurnStarted(threadId, turnId) {
29181
+ for (const capture of this.turnStartCaptures) {
29182
+ capture(threadId, turnId);
29183
+ }
29184
+ }
29055
29185
  recordThreadStatusChanged(event) {
29056
29186
  const captures = this.threadStatusCaptures.get(event.threadId);
29057
29187
  if (!captures) {
@@ -29162,6 +29292,17 @@ var CodexAppServerClient = class {
29162
29292
  }
29163
29293
  };
29164
29294
  }
29295
+ captureTurnStarts(capture) {
29296
+ this.turnStartCaptures.add(capture);
29297
+ let released = false;
29298
+ return () => {
29299
+ if (released) {
29300
+ return;
29301
+ }
29302
+ released = true;
29303
+ this.turnStartCaptures.delete(capture);
29304
+ };
29305
+ }
29165
29306
  captureTurnRoutings(threadId, capture) {
29166
29307
  const captures = this.turnRoutingCaptures.get(threadId) ?? /* @__PURE__ */ new Set();
29167
29308
  captures.add(capture);
@@ -29321,6 +29462,10 @@ function extractTurnRouting(notification) {
29321
29462
  }
29322
29463
  var MODEL_CONFIG_ID = "model";
29323
29464
  var REASONING_EFFORT_CONFIG_ID = "reasoning_effort";
29465
+ function formatModelDisplayName(displayName) {
29466
+ if (!/^gpt-/i.test(displayName)) return displayName;
29467
+ return displayName.replace(/^gpt-/i, "").split(/[-/]+/).filter(Boolean).map(capitalize2).join(" ");
29468
+ }
29324
29469
  function capitalize2(value) {
29325
29470
  return value.charAt(0).toUpperCase() + value.slice(1);
29326
29471
  }
@@ -29339,19 +29484,20 @@ function findSupportedEffort(options, effort) {
29339
29484
  if (!effort) return void 0;
29340
29485
  return options.find((o) => o.reasoningEffort === effort)?.reasoningEffort;
29341
29486
  }
29342
- function createModelConfigOption(availableModels, currentBaseModelId) {
29487
+ function createModelConfigOption(availableModels, currentBaseModelId, recommendedModelId) {
29343
29488
  const options = availableModels.map((model) => ({
29344
29489
  value: model.id,
29345
- name: model.displayName,
29490
+ name: formatModelDisplayName(model.displayName),
29346
29491
  description: model.description
29347
29492
  }));
29348
29493
  if (!availableModels.some((model) => model.id === currentBaseModelId)) {
29349
29494
  options.unshift({
29350
29495
  value: currentBaseModelId,
29351
- name: currentBaseModelId,
29496
+ name: formatModelDisplayName(currentBaseModelId),
29352
29497
  description: null
29353
29498
  });
29354
29499
  }
29500
+ const recommendation = recommendedModelId && options.some((option) => option.value === recommendedModelId) ? recommendedModelId : void 0;
29355
29501
  return {
29356
29502
  id: MODEL_CONFIG_ID,
29357
29503
  name: "Model",
@@ -29359,10 +29505,12 @@ function createModelConfigOption(availableModels, currentBaseModelId) {
29359
29505
  category: "model",
29360
29506
  type: "select",
29361
29507
  currentValue: currentBaseModelId,
29362
- options
29508
+ options,
29509
+ ...recommendation ? { _meta: withAirMeta(void 0, AIR_RECOMMENDED_CONFIG_VALUE_KEY, recommendation) } : {}
29363
29510
  };
29364
29511
  }
29365
- function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEffort) {
29512
+ function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEffort, recommendedEffort) {
29513
+ const recommendation = findSupportedEffort(supportedReasoningEfforts, recommendedEffort);
29366
29514
  return {
29367
29515
  id: REASONING_EFFORT_CONFIG_ID,
29368
29516
  name: "Reasoning effort",
@@ -29374,7 +29522,8 @@ function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEff
29374
29522
  value: option.reasoningEffort,
29375
29523
  name: reasoningEffortDisplayName(option.reasoningEffort),
29376
29524
  description: option.description
29377
- }))
29525
+ })),
29526
+ ...recommendation ? { _meta: withAirMeta(void 0, AIR_RECOMMENDED_CONFIG_VALUE_KEY, recommendation) } : {}
29378
29527
  };
29379
29528
  }
29380
29529
  var GOAL_OBJECTIVE_MAX_LENGTH = 4e3;
@@ -29563,7 +29712,7 @@ var CodexCommands = class {
29563
29712
  }
29564
29713
  case "compact": {
29565
29714
  options.onTurnStartPending?.();
29566
- options.onCompactionStarted?.();
29715
+ options.onNativeCommandStarted?.();
29567
29716
  try {
29568
29717
  const turnCompleted = await this.runWithProcessCheck(
29569
29718
  () => this.codexAcpClient.runCompact(sessionId, (turnId) => {
@@ -29572,7 +29721,7 @@ var CodexCommands = class {
29572
29721
  );
29573
29722
  return { handled: true, turnCompleted };
29574
29723
  } finally {
29575
- options.onCompactionFinished?.();
29724
+ options.onNativeCommandFinished?.();
29576
29725
  }
29577
29726
  }
29578
29727
  case "goal": {
@@ -29581,7 +29730,7 @@ var CodexCommands = class {
29581
29730
  case "review": {
29582
29731
  const target = this.buildReviewTarget(command.rest);
29583
29732
  const turnCompleted = await this.runReviewCommand(sessionState, target, options);
29584
- return { handled: true, turnCompleted };
29733
+ return { handled: true, turnCompleted, waitForGoalContinuation: false };
29585
29734
  }
29586
29735
  case "review-branch": {
29587
29736
  if (command.rest.length === 0) {
@@ -29592,7 +29741,7 @@ var CodexCommands = class {
29592
29741
  type: "baseBranch",
29593
29742
  branch: command.rest
29594
29743
  }, options);
29595
- return { handled: true, turnCompleted };
29744
+ return { handled: true, turnCompleted, waitForGoalContinuation: false };
29596
29745
  }
29597
29746
  case "review-commit": {
29598
29747
  if (command.rest.length === 0) {
@@ -29604,7 +29753,7 @@ var CodexCommands = class {
29604
29753
  sha: command.rest,
29605
29754
  title: null
29606
29755
  }, options);
29607
- return { handled: true, turnCompleted };
29756
+ return { handled: true, turnCompleted, waitForGoalContinuation: false };
29608
29757
  }
29609
29758
  case "status": {
29610
29759
  await this.refreshRateLimits(sessionState);
@@ -29656,13 +29805,18 @@ var CodexCommands = class {
29656
29805
  }
29657
29806
  async runReviewCommand(sessionState, target, options) {
29658
29807
  options.onTurnStartPending?.();
29659
- return await this.runWithProcessCheck(() => this.codexAcpClient.runReview(
29660
- sessionState.sessionId,
29661
- target,
29662
- (turnId, threadId) => {
29663
- this.handleCommandTurnStarted(sessionState, options, turnId, threadId);
29664
- }
29665
- ));
29808
+ options.onNativeCommandStarted?.();
29809
+ try {
29810
+ return await this.runWithProcessCheck(() => this.codexAcpClient.runReview(
29811
+ sessionState.sessionId,
29812
+ target,
29813
+ (turnId, threadId) => {
29814
+ this.handleCommandTurnStarted(sessionState, options, turnId, threadId);
29815
+ }
29816
+ ));
29817
+ } finally {
29818
+ options.onNativeCommandFinished?.();
29819
+ }
29666
29820
  }
29667
29821
  async runGoalCommand(sessionState, rest, options) {
29668
29822
  const sessionId = sessionState.sessionId;
@@ -30175,14 +30329,14 @@ function parseJsonRecord(line) {
30175
30329
  }
30176
30330
  try {
30177
30331
  const value = JSON.parse(line);
30178
- return asRecord2(value);
30332
+ return asRecord3(value);
30179
30333
  } catch {
30180
30334
  return null;
30181
30335
  }
30182
30336
  }
30183
30337
  function extractResponseItem(record2) {
30184
30338
  if (record2["type"] === "response_item") {
30185
- return asRecord2(record2["payload"]);
30339
+ return asRecord3(record2["payload"]);
30186
30340
  }
30187
30341
  const itemType = record2["type"];
30188
30342
  if (typeof itemType === "string" && isLegacyResponseItemType(itemType)) {
@@ -30216,7 +30370,7 @@ function createEventMsgUpdates(record2) {
30216
30370
  if (record2["type"] !== "event_msg") {
30217
30371
  return null;
30218
30372
  }
30219
- const payload = asRecord2(record2["payload"]);
30373
+ const payload = asRecord3(record2["payload"]);
30220
30374
  if (!payload) {
30221
30375
  return [];
30222
30376
  }
@@ -30264,7 +30418,7 @@ function imageBlocks(images) {
30264
30418
  if (typeof image === "string") {
30265
30419
  return [{ type: "text", text: `[@image](${image})` }];
30266
30420
  }
30267
- const record2 = asRecord2(image);
30421
+ const record2 = asRecord3(image);
30268
30422
  const path10 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
30269
30423
  return path10 ? [{ type: "text", text: `[@image](${path10})` }] : [];
30270
30424
  });
@@ -30274,7 +30428,7 @@ function contentBlocksFromResponseContent(content) {
30274
30428
  return [];
30275
30429
  }
30276
30430
  return content.flatMap((entry) => {
30277
- const record2 = asRecord2(entry);
30431
+ const record2 = asRecord3(entry);
30278
30432
  if (!record2) {
30279
30433
  return [];
30280
30434
  }
@@ -30303,7 +30457,7 @@ function textParts(value) {
30303
30457
  if (typeof entry === "string") {
30304
30458
  return entry.length > 0 ? [entry] : [];
30305
30459
  }
30306
- const record2 = asRecord2(entry);
30460
+ const record2 = asRecord3(entry);
30307
30461
  if (!record2) {
30308
30462
  return [];
30309
30463
  }
@@ -30317,6 +30471,7 @@ function createFunctionCallUpdate(item) {
30317
30471
  if (!toolCallId || !name) {
30318
30472
  return null;
30319
30473
  }
30474
+ const toolName = functionToolName(name, stringValue(item["namespace"]));
30320
30475
  const isExecCommand = name === "exec_command";
30321
30476
  const args = parseFunctionArguments(item["arguments"]);
30322
30477
  const command = isExecCommand ? commandFromFunctionArguments(args) : null;
@@ -30324,7 +30479,10 @@ function createFunctionCallUpdate(item) {
30324
30479
  const commandAction = command ? inferCommandAction(command, cwd) : null;
30325
30480
  if (commandAction) {
30326
30481
  return {
30327
- update: createCommandActionEvent(toolCallId, "inProgress", cwd, commandAction),
30482
+ update: {
30483
+ ...createCommandActionEvent(toolCallId, "inProgress", cwd, commandAction),
30484
+ name: toolName
30485
+ },
30328
30486
  usesTerminal: false,
30329
30487
  isExecCommand
30330
30488
  };
@@ -30332,6 +30490,7 @@ function createFunctionCallUpdate(item) {
30332
30490
  const update = {
30333
30491
  sessionUpdate: "tool_call",
30334
30492
  toolCallId,
30493
+ name: toolName,
30335
30494
  kind: toolKindForFunctionCall(name),
30336
30495
  title: titleForFunctionCall(name, args),
30337
30496
  status: "in_progress",
@@ -30395,7 +30554,7 @@ function parseFunctionArguments(value) {
30395
30554
  }
30396
30555
  function rawInputForFunctionCall(name, args) {
30397
30556
  if (name === "exec_command") {
30398
- const record2 = asRecord2(args);
30557
+ const record2 = asRecord3(args);
30399
30558
  if (record2) {
30400
30559
  return {
30401
30560
  command: stringValue(record2["cmd"]) ?? stringValue(record2["command"]),
@@ -30442,14 +30601,14 @@ function functionCallUsesTerminal(item) {
30442
30601
  return item["name"] === "exec_command";
30443
30602
  }
30444
30603
  function commandFromFunctionArguments(args) {
30445
- const record2 = asRecord2(args);
30604
+ const record2 = asRecord3(args);
30446
30605
  if (!record2) {
30447
30606
  return null;
30448
30607
  }
30449
30608
  return stringValue(record2["cmd"]) ?? stringValue(record2["command"]);
30450
30609
  }
30451
30610
  function cwdFromFunctionArguments(args) {
30452
- const record2 = asRecord2(args);
30611
+ const record2 = asRecord3(args);
30453
30612
  if (!record2) {
30454
30613
  return "";
30455
30614
  }
@@ -30921,7 +31080,7 @@ function outputText(output) {
30921
31080
  return "";
30922
31081
  }
30923
31082
  return output.flatMap((entry) => {
30924
- const record2 = asRecord2(entry);
31083
+ const record2 = asRecord3(entry);
30925
31084
  if (!record2) {
30926
31085
  return [];
30927
31086
  }
@@ -30937,7 +31096,7 @@ function outputText(output) {
30937
31096
  }).join("\n");
30938
31097
  }
30939
31098
  function parseExitCode(rawOutput, output) {
30940
- const record2 = asRecord2(rawOutput);
31099
+ const record2 = asRecord3(rawOutput);
30941
31100
  if (record2) {
30942
31101
  const exitCode2 = numberValue(record2["exit_code"]) ?? numberValue(record2["exitCode"]);
30943
31102
  if (exitCode2 !== null) {
@@ -30968,7 +31127,7 @@ function looksLikeCommandFailure(output) {
30968
31127
  }
30969
31128
  return /(^|\n)(Error|Failed|Command failed|Sandbox error|No such file or directory|Permission denied|Operation not permitted|ENOENT|EACCES)(:|\b)/i.test(trimmed);
30970
31129
  }
30971
- function asRecord2(value) {
31130
+ function asRecord3(value) {
30972
31131
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
30973
31132
  return null;
30974
31133
  }
@@ -31455,12 +31614,7 @@ function clientSupportsTypedSessionFailures(capabilities) {
31455
31614
  function clientSupportsAgentFileChangeReports(capabilities) {
31456
31615
  return clientSupportsAirCapability(capabilities, AIR_AGENT_FILE_CHANGE_REPORT_KEY);
31457
31616
  }
31458
- var CodexAcpServer = class _CodexAcpServer {
31459
- static MODEL_NAME_TOKEN_OVERRIDES = {
31460
- gpt: "GPT",
31461
- mini: "Mini",
31462
- codex: "Codex"
31463
- };
31617
+ var CodexAcpServer = class {
31464
31618
  codexAcpClient;
31465
31619
  connection;
31466
31620
  defaultAuthRequest;
@@ -31582,7 +31736,8 @@ var CodexAcpServer = class _CodexAcpServer {
31582
31736
  AIR_SESSION_FAILURE_KEY,
31583
31737
  AIR_AGENT_FILE_CHANGE_REPORT_KEY,
31584
31738
  AIR_NATIVE_SUBAGENT_SESSIONS_KEY,
31585
- AIR_ASYNC_TASKS_KEY
31739
+ AIR_ASYNC_TASKS_KEY,
31740
+ AIR_RECOMMENDED_CONFIG_VALUE_KEY
31586
31741
  ]
31587
31742
  }
31588
31743
  }
@@ -32711,7 +32866,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32711
32866
  return { outcome: "injected" };
32712
32867
  }
32713
32868
  }
32714
- return { outcome: "failed" };
32869
+ throw RequestError.invalidRequest("No active Codex turn to steer");
32715
32870
  }
32716
32871
  /**
32717
32872
  * Rejects a steering prompt whose content the active model cannot accept
@@ -32906,14 +33061,24 @@ Check ${configPath} and project .codex directories, especially their config.toml
32906
33061
  }
32907
33062
  createSessionConfigOptions(sessionState) {
32908
33063
  const currentModelId = ModelId.fromString(sessionState.currentModelId);
33064
+ const useRecommendedValue = clientSupportsAirCapability(
33065
+ this.clientCapabilities,
33066
+ AIR_RECOMMENDED_CONFIG_VALUE_KEY
33067
+ );
33068
+ const currentModel = this.findCurrentModel(sessionState.availableModels, sessionState.currentModelId);
33069
+ const recommendedModelId = useRecommendedValue ? sessionState.availableModels.find((model) => model.isDefault)?.id : void 0;
32909
33070
  const configOptions = [
32910
33071
  sessionState.agentMode.toConfigOption(),
32911
33072
  createCollaborationModeConfigOption(sessionState.collaborationMode),
32912
- createModelConfigOption(sessionState.availableModels, currentModelId.model)
33073
+ createModelConfigOption(sessionState.availableModels, currentModelId.model, recommendedModelId)
32913
33074
  ];
32914
33075
  if (sessionState.supportedReasoningEfforts.length > 0) {
32915
33076
  configOptions.push(
32916
- createReasoningEffortConfigOption(sessionState.supportedReasoningEfforts, currentModelId.effort)
33077
+ createReasoningEffortConfigOption(
33078
+ sessionState.supportedReasoningEfforts,
33079
+ currentModelId.effort,
33080
+ useRecommendedValue ? currentModel?.defaultReasoningEffort : void 0
33081
+ )
32917
33082
  );
32918
33083
  }
32919
33084
  if (sessionState.currentModelSupportsFast) {
@@ -33021,14 +33186,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
33021
33186
  const modelId = ModelId.fromString(currentModelId);
33022
33187
  return models.find((m) => m.id === modelId.model);
33023
33188
  }
33024
- normalizeModelDisplayName(displayName) {
33025
- return displayName.split("-").map((token) => _CodexAcpServer.MODEL_NAME_TOKEN_OVERRIDES[token.toLowerCase()] ?? token).join("-");
33026
- }
33027
33189
  createModelState(availableModels, selectedModelId) {
33028
33190
  const allowedModels = availableModels.flatMap(
33029
33191
  (model) => model.supportedReasoningEfforts.map((effort) => ({
33030
33192
  modelId: ModelId.fromComponents(model, effort.reasoningEffort).toString(),
33031
- name: `${this.normalizeModelDisplayName(model.displayName)} (${effort.reasoningEffort})`,
33193
+ name: `${formatModelDisplayName(model.displayName)} (${effort.reasoningEffort})`,
33032
33194
  description: `${model.description} ${effort.description}`
33033
33195
  }))
33034
33196
  );
@@ -33307,24 +33469,19 @@ Check ${configPath} and project .codex directories, especially their config.toml
33307
33469
  }
33308
33470
  });
33309
33471
  }
33310
- async publishAgentFileChangeReport(sessionState, turnId, request, unavailableReason, signal) {
33472
+ async publishAgentFileChangeReport(sessionState, turnId, request, unavailableReason, turnDiff, workspace) {
33311
33473
  let report;
33312
33474
  try {
33313
- report = turnId === null ? createUnavailableAgentFileChangeReport(request.requestId, unavailableReason) : await this.codexAcpClient.runAgentFileChangeReport({
33314
- sessionId: sessionState.sessionId,
33315
- turnId,
33316
- // The client owns request-id correlation and duplicate suppression. The wrapper
33317
- // stays stateless so a retried ACP prompt still receives a terminal report.
33318
- requestId: request.requestId,
33319
- workspace: {
33320
- cwd: sessionState.cwd,
33321
- additionalDirectories: sessionState.additionalDirectories
33322
- },
33323
- signal
33324
- });
33475
+ report = turnId === null ? createUnavailableAgentFileChangeReport(request.requestId, unavailableReason) : createReportedAgentFileChangeReport(request.requestId, turnDiff, workspace);
33325
33476
  } catch (error48) {
33326
- logger.error("Agent file-change report failed unexpectedly", error48);
33327
- report = createUnavailableAgentFileChangeReport(request.requestId, "providerError");
33477
+ logger.error(
33478
+ error48 instanceof AgentFileChangeReportError ? "Agent file-change report unavailable" : "Agent file-change report failed unexpectedly",
33479
+ error48
33480
+ );
33481
+ report = createUnavailableAgentFileChangeReport(
33482
+ request.requestId,
33483
+ error48 instanceof AgentFileChangeReportError ? error48.reason : "providerError"
33484
+ );
33328
33485
  }
33329
33486
  try {
33330
33487
  const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
@@ -33624,7 +33781,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33624
33781
  signal: abortController.signal,
33625
33782
  currentTurn: null,
33626
33783
  hasCompletedTurn: false,
33627
- compactionInFlight: false,
33784
+ nativeCommandInFlight: false,
33628
33785
  requestCancel: () => {
33629
33786
  if (abortController.signal.aborted) {
33630
33787
  return;
@@ -33655,6 +33812,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
33655
33812
  this.activePrompts.set(sessionId, activePrompt);
33656
33813
  return activePrompt;
33657
33814
  }
33815
+ setActivePromptTurn(activePrompt, turn) {
33816
+ activePrompt.hasCompletedTurn = false;
33817
+ activePrompt.currentTurn = turn;
33818
+ }
33658
33819
  clearPendingSteers(sessionId, activePrompt) {
33659
33820
  const pending = this.pendingSteers.get(sessionId);
33660
33821
  if (!pending) return;
@@ -33689,7 +33850,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33689
33850
  }
33690
33851
  cancelBeforeTurnStarted(activePrompt) {
33691
33852
  return activePrompt.cancelSignal.then(() => {
33692
- if (activePrompt.currentTurn === null && !activePrompt.compactionInFlight) {
33853
+ if (activePrompt.currentTurn === null && !activePrompt.nativeCommandInFlight) {
33693
33854
  return null;
33694
33855
  }
33695
33856
  return new Promise(() => {
@@ -33758,7 +33919,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33758
33919
  }
33759
33920
  }
33760
33921
  interruptLateStartedTurn(turn, activePrompt) {
33761
- if (activePrompt.compactionInFlight) {
33922
+ if (activePrompt.nativeCommandInFlight) {
33762
33923
  this.codexAcpClient.markTurnStale(turn);
33763
33924
  void this.requestTurnInterrupt(turn, "Cancel");
33764
33925
  return;
@@ -33837,6 +33998,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33837
33998
  const sessionState = this.getSessionState(params.sessionId);
33838
33999
  const agentFileChangeReportRequest = clientSupportsAgentFileChangeReports(this.clientCapabilities) ? parseAgentFileChangeReportRequest(params._meta) : null;
33839
34000
  const goalPromptControl = parseGoalPromptControl(params._meta);
34001
+ const agentFileChangeWorkspace = agentFileChangeReportRequest === null ? null : captureAgentFileChangeWorkspace(sessionState.cwd, sessionState.additionalDirectories);
33840
34002
  let agentFileChangeReportTurnId = null;
33841
34003
  let agentFileChangeReportUnavailableReason = "providerError";
33842
34004
  let promptWasCancelled = false;
@@ -33886,7 +34048,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
33886
34048
  clientSupportsTypedSessionFailures(this.clientCapabilities),
33887
34049
  this.sessionFailureEpoch,
33888
34050
  sessionState.subagents,
33889
- (accountUpdated) => this.handleAccountUpdated(accountUpdated)
34051
+ (accountUpdated) => this.handleAccountUpdated(accountUpdated),
34052
+ agentFileChangeReportRequest !== null
33890
34053
  );
33891
34054
  eventHandler = promptEventHandler;
33892
34055
  const permissionLifecycle = this.permissionLifecycleContext(sessionState);
@@ -33913,7 +34076,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33913
34076
  goalLifecycle.observe(event);
33914
34077
  if (event.method === "turn/started" && event.params.threadId === params.sessionId) {
33915
34078
  const turn = { threadId: params.sessionId, turnId: event.params.turn.id };
33916
- activePrompt.currentTurn = turn;
34079
+ this.setActivePromptTurn(activePrompt, turn);
33917
34080
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
33918
34081
  this.interruptLateStartedTurn(turn, activePrompt);
33919
34082
  return;
@@ -33921,7 +34084,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33921
34084
  recoverableSessionFailure = sessionState.sessionFailure;
33922
34085
  promptNotificationsActive = true;
33923
34086
  }
33924
- if (event.method === "turn/completed" && event.params.threadId === params.sessionId && activePrompt.currentTurn?.turnId === event.params.turn.id) {
34087
+ if (event.method === "turn/completed" && event.params.threadId === params.sessionId && activePrompt.currentTurn?.threadId === event.params.threadId && activePrompt.currentTurn.turnId === event.params.turn.id) {
33925
34088
  activePrompt.currentTurn = null;
33926
34089
  activePrompt.hasCompletedTurn = true;
33927
34090
  }
@@ -33932,7 +34095,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33932
34095
  await promptEventHandler.handleSessionScopedNotification(event);
33933
34096
  return;
33934
34097
  }
33935
- const completesActiveTurn = event.method === "turn/completed" && event.params.threadId === sessionState.sessionId && event.params.turn.id === sessionState.currentTurnId;
34098
+ const completesActiveTurn = event.method === "turn/completed" && event.params.threadId === sessionState.sessionId && activePrompt.currentTurn?.threadId === event.params.threadId && activePrompt.currentTurn.turnId === event.params.turn.id;
33936
34099
  await promptEventHandler.handleNotification(event);
33937
34100
  if (completesActiveTurn) {
33938
34101
  promptNotificationsActive = false;
@@ -33955,7 +34118,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33955
34118
  onTurnStarted: (turnId, threadId) => {
33956
34119
  const turn = { threadId, turnId };
33957
34120
  if (threadId === params.sessionId) goalLifecycle.startTurn(turnId);
33958
- activePrompt.currentTurn = turn;
34121
+ this.setActivePromptTurn(activePrompt, turn);
33959
34122
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
33960
34123
  this.interruptLateStartedTurn(turn, activePrompt);
33961
34124
  return;
@@ -33964,11 +34127,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
33964
34127
  pendingTurnStart?.resolve(turnId);
33965
34128
  onTurnStarted?.();
33966
34129
  },
33967
- onCompactionStarted: () => {
33968
- activePrompt.compactionInFlight = true;
34130
+ onNativeCommandStarted: () => {
34131
+ activePrompt.nativeCommandInFlight = true;
33969
34132
  },
33970
- onCompactionFinished: () => {
33971
- activePrompt.compactionInFlight = false;
34133
+ onNativeCommandFinished: () => {
34134
+ activePrompt.nativeCommandInFlight = false;
33972
34135
  },
33973
34136
  setConfigOption: async (configId, value) => {
33974
34137
  await this.applySessionConfigOption(sessionState, {
@@ -33998,7 +34161,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33998
34161
  return cancelledPromptResponse();
33999
34162
  }
34000
34163
  if (commandResult.handled) {
34001
- if (commandResult.turnCompleted) {
34164
+ if (commandResult.turnCompleted && commandResult.waitForGoalContinuation !== false) {
34002
34165
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
34003
34166
  const firstCommandTurn = commandResult.turnCompleted;
34004
34167
  const completed = await this.runWithProcessCheck(() => Promise.race([
@@ -34084,7 +34247,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
34084
34247
  onTurnStarted?.();
34085
34248
  return;
34086
34249
  }
34087
- activePrompt.currentTurn = turn;
34250
+ this.setActivePromptTurn(activePrompt, turn);
34088
34251
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
34089
34252
  this.interruptLateStartedTurn(turn, activePrompt);
34090
34253
  return;
@@ -34183,7 +34346,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
34183
34346
  (turnId) => {
34184
34347
  const turn = { threadId: params.sessionId, turnId };
34185
34348
  if (!goalLifecycle.startSubmittedTurn(turnId)) return;
34186
- activePrompt.currentTurn = turn;
34349
+ this.setActivePromptTurn(activePrompt, turn);
34187
34350
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
34188
34351
  this.interruptLateStartedTurn(turn, activePrompt);
34189
34352
  return;
@@ -34306,13 +34469,21 @@ Check ${configPath} and project .codex directories, especially their config.toml
34306
34469
  } catch (error48) {
34307
34470
  logger.error("Failed to publish terminal subagent state during prompt cleanup", error48);
34308
34471
  }
34309
- if (agentFileChangeReportRequest !== null) {
34472
+ if (agentFileChangeReportRequest !== null && agentFileChangeWorkspace !== null) {
34473
+ if (promptWasCancelled || activePrompt.signal.aborted || this.sessionIsClosing(params.sessionId)) {
34474
+ agentFileChangeReportTurnId = null;
34475
+ agentFileChangeReportUnavailableReason = "cancelled";
34476
+ } else if (agentFileChangeReportTurnId !== null && eventHandler?.isTurnDiffOversized(agentFileChangeReportTurnId)) {
34477
+ agentFileChangeReportTurnId = null;
34478
+ agentFileChangeReportUnavailableReason = "invalidOutput";
34479
+ }
34310
34480
  await this.publishAgentFileChangeReport(
34311
34481
  sessionState,
34312
34482
  agentFileChangeReportTurnId,
34313
34483
  agentFileChangeReportRequest,
34314
34484
  agentFileChangeReportUnavailableReason,
34315
- activePrompt.signal
34485
+ agentFileChangeReportTurnId === null || eventHandler === null ? "" : eventHandler.getTurnDiff(agentFileChangeReportTurnId),
34486
+ agentFileChangeWorkspace
34316
34487
  );
34317
34488
  }
34318
34489
  logger.log("Prompt completed", { sessionId: params.sessionId });
@@ -34427,7 +34598,7 @@ ${stderr}` : "";
34427
34598
  return;
34428
34599
  }
34429
34600
  const activePrompt = this.activePrompts.get(params.sessionId);
34430
- if (activePrompt?.compactionInFlight) {
34601
+ if (activePrompt?.nativeCommandInFlight) {
34431
34602
  activePrompt.requestCancel();
34432
34603
  if (activePrompt.currentTurn === null) return;
34433
34604
  }