@agentclientprotocol/codex-acp 1.3.0 → 1.4.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.
Files changed (2) hide show
  1. package/dist/index.js +607 -82
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3453,9 +3453,9 @@ var require_main = __commonJS({
3453
3453
  exports.createMessageConnection = createMessageConnection2;
3454
3454
  var ril_1 = __importDefault(require_ril());
3455
3455
  ril_1.default.install();
3456
- var path7 = __importStar(__require("path"));
3456
+ var path8 = __importStar(__require("path"));
3457
3457
  var os2 = __importStar(__require("os"));
3458
- var fs7 = __importStar(__require("fs"));
3458
+ var fs8 = __importStar(__require("fs"));
3459
3459
  var crypto_1 = __require("crypto");
3460
3460
  var net_1 = __require("net");
3461
3461
  var api_1 = require_api();
@@ -3596,7 +3596,7 @@ var require_main = __commonJS({
3596
3596
  }
3597
3597
  let randomLength = 32;
3598
3598
  const fixedLength = "/lsp-.sock".length;
3599
- const tmpDir = fs7.realpathSync(XDG_RUNTIME_DIR ?? os2.tmpdir());
3599
+ const tmpDir = fs8.realpathSync(XDG_RUNTIME_DIR ?? os2.tmpdir());
3600
3600
  const limit = safeIpcPathLengths.get(process.platform);
3601
3601
  if (limit !== void 0) {
3602
3602
  randomLength = Math.min(limit - tmpDir.length - fixedLength, randomLength);
@@ -3605,7 +3605,7 @@ var require_main = __commonJS({
3605
3605
  throw new Error(`Unable to generate a random pipe name with ${randomLength} characters.`);
3606
3606
  }
3607
3607
  const randomSuffix = (0, crypto_1.randomBytes)(Math.floor(randomLength / 2)).toString("hex");
3608
- return path7.join(tmpDir, `lsp-${randomSuffix}.sock`);
3608
+ return path8.join(tmpDir, `lsp-${randomSuffix}.sock`);
3609
3609
  }
3610
3610
  function createClientPipeTransport(pipeName, encoding = "utf-8") {
3611
3611
  let connectResolve;
@@ -4509,10 +4509,10 @@ function mergeDefs(...defs) {
4509
4509
  function cloneDef(schema) {
4510
4510
  return mergeDefs(schema._zod.def);
4511
4511
  }
4512
- function getElementAtPath(obj, path7) {
4513
- if (!path7)
4512
+ function getElementAtPath(obj, path8) {
4513
+ if (!path8)
4514
4514
  return obj;
4515
- return path7.reduce((acc, key) => acc?.[key], obj);
4515
+ return path8.reduce((acc, key) => acc?.[key], obj);
4516
4516
  }
4517
4517
  function promiseAllObject(promisesObj) {
4518
4518
  const keys = Object.keys(promisesObj);
@@ -4921,11 +4921,11 @@ function explicitlyAborted(x, startIndex = 0) {
4921
4921
  }
4922
4922
  return false;
4923
4923
  }
4924
- function prefixIssues(path7, issues) {
4924
+ function prefixIssues(path8, issues) {
4925
4925
  return issues.map((iss) => {
4926
4926
  var _a3;
4927
4927
  (_a3 = iss).path ?? (_a3.path = []);
4928
- iss.path.unshift(path7);
4928
+ iss.path.unshift(path8);
4929
4929
  return iss;
4930
4930
  });
4931
4931
  }
@@ -5072,16 +5072,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
5072
5072
  }
5073
5073
  function formatError(error51, mapper = (issue2) => issue2.message) {
5074
5074
  const fieldErrors = { _errors: [] };
5075
- const processError = (error52, path7 = []) => {
5075
+ const processError = (error52, path8 = []) => {
5076
5076
  for (const issue2 of error52.issues) {
5077
5077
  if (issue2.code === "invalid_union" && issue2.errors.length) {
5078
- issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
5078
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
5079
5079
  } else if (issue2.code === "invalid_key") {
5080
- processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
5080
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
5081
5081
  } else if (issue2.code === "invalid_element") {
5082
- processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
5082
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
5083
5083
  } else {
5084
- const fullpath = [...path7, ...issue2.path];
5084
+ const fullpath = [...path8, ...issue2.path];
5085
5085
  if (fullpath.length === 0) {
5086
5086
  fieldErrors._errors.push(mapper(issue2));
5087
5087
  } else {
@@ -5108,17 +5108,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
5108
5108
  }
5109
5109
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
5110
5110
  const result = { errors: [] };
5111
- const processError = (error52, path7 = []) => {
5111
+ const processError = (error52, path8 = []) => {
5112
5112
  var _a3, _b;
5113
5113
  for (const issue2 of error52.issues) {
5114
5114
  if (issue2.code === "invalid_union" && issue2.errors.length) {
5115
- issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
5115
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
5116
5116
  } else if (issue2.code === "invalid_key") {
5117
- processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
5117
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
5118
5118
  } else if (issue2.code === "invalid_element") {
5119
- processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
5119
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
5120
5120
  } else {
5121
- const fullpath = [...path7, ...issue2.path];
5121
+ const fullpath = [...path8, ...issue2.path];
5122
5122
  if (fullpath.length === 0) {
5123
5123
  result.errors.push(mapper(issue2));
5124
5124
  continue;
@@ -5150,8 +5150,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
5150
5150
  }
5151
5151
  function toDotPath(_path) {
5152
5152
  const segs = [];
5153
- const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5154
- for (const seg of path7) {
5153
+ const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5154
+ for (const seg of path8) {
5155
5155
  if (typeof seg === "number")
5156
5156
  segs.push(`[${seg}]`);
5157
5157
  else if (typeof seg === "symbol")
@@ -17843,13 +17843,13 @@ function resolveRef(ref, ctx) {
17843
17843
  if (!ref.startsWith("#")) {
17844
17844
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
17845
17845
  }
17846
- const path7 = ref.slice(1).split("/").filter(Boolean);
17847
- if (path7.length === 0) {
17846
+ const path8 = ref.slice(1).split("/").filter(Boolean);
17847
+ if (path8.length === 0) {
17848
17848
  return ctx.rootSchema;
17849
17849
  }
17850
17850
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17851
- if (path7[0] === defsKey) {
17852
- const key = path7[1];
17851
+ if (path8[0] === defsKey) {
17852
+ const key = path8[1];
17853
17853
  if (!key || !ctx.defs[key]) {
17854
17854
  throw new Error(`Reference not found: ${ref}`);
17855
17855
  }
@@ -23247,13 +23247,13 @@ function createTerminalCommandEvent(event, terminalId, cwd) {
23247
23247
  }
23248
23248
  };
23249
23249
  }
23250
- function createSearchTitle(query, path7) {
23251
- if (query && path7) {
23252
- return `Search for '${query}' in ${path7}`;
23250
+ function createSearchTitle(query, path8) {
23251
+ if (query && path8) {
23252
+ return `Search for '${query}' in ${path8}`;
23253
23253
  } else if (query) {
23254
23254
  return `Search for '${query}'`;
23255
- } else if (path7) {
23256
- return `Search in '${path7}'`;
23255
+ } else if (path8) {
23256
+ return `Search in '${path8}'`;
23257
23257
  }
23258
23258
  return "Search";
23259
23259
  }
@@ -23479,12 +23479,12 @@ function revertPatch(unifiedDiff) {
23479
23479
  if (!patch) return null;
23480
23480
  return reversePatch(patch);
23481
23481
  }
23482
- function createUpdateDiffContent(path7, oldText, newText) {
23482
+ function createUpdateDiffContent(path8, oldText, newText) {
23483
23483
  return {
23484
23484
  type: "diff",
23485
23485
  oldText,
23486
23486
  newText,
23487
- path: path7,
23487
+ path: path8,
23488
23488
  _meta: {
23489
23489
  kind: "update"
23490
23490
  }
@@ -23617,6 +23617,8 @@ var AIR_META_KEY = "air";
23617
23617
  var AIR_EXTENSION_VERSION_KEY = "version";
23618
23618
  var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
23619
23619
  var AIR_SESSION_FAILURE_KEY = "sessionFailure";
23620
+ var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
23621
+ var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
23620
23622
  var AIR_EXTENSION_VERSION = 1;
23621
23623
 
23622
23624
  // src/CodexEventHandler.ts
@@ -25007,11 +25009,11 @@ var CodexApprovalHandler = class {
25007
25009
  });
25008
25010
  }
25009
25011
  const fileSystem = permissions.fileSystem;
25010
- for (const path7 of fileSystem?.read ?? []) {
25011
- changes.push(this.fileSystemGrantChange(path7, "read", lifetime, suffix));
25012
+ for (const path8 of fileSystem?.read ?? []) {
25013
+ changes.push(this.fileSystemGrantChange(path8, "read", lifetime, suffix));
25012
25014
  }
25013
- for (const path7 of fileSystem?.write ?? []) {
25014
- changes.push(this.fileSystemGrantChange(path7, "write", lifetime, suffix));
25015
+ for (const path8 of fileSystem?.write ?? []) {
25016
+ changes.push(this.fileSystemGrantChange(path8, "write", lifetime, suffix));
25015
25017
  }
25016
25018
  for (const entry of fileSystem?.entries ?? []) {
25017
25019
  const matcher = (() => {
@@ -25040,16 +25042,16 @@ var CodexApprovalHandler = class {
25040
25042
  }
25041
25043
  return changes.length > 0 ? { version: 1, changes } : void 0;
25042
25044
  }
25043
- fileSystemGrantChange(path7, access, lifetime, suffix) {
25045
+ fileSystemGrantChange(path8, access, lifetime, suffix) {
25044
25046
  return {
25045
25047
  type: "grant",
25046
25048
  operation: "grant",
25047
- description: `Allow ${access} access to ${path7}${suffix}`,
25049
+ description: `Allow ${access} access to ${path8}${suffix}`,
25048
25050
  lifetime,
25049
25051
  targets: [{
25050
25052
  type: "filesystem",
25051
25053
  access: [access],
25052
- matcher: { type: "exact_path", path: path7 }
25054
+ matcher: { type: "exact_path", path: path8 }
25053
25055
  }]
25054
25056
  };
25055
25057
  }
@@ -25959,15 +25961,15 @@ var wslDefaultBrowser = async () => {
25959
25961
  const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
25960
25962
  return stdout.trim();
25961
25963
  };
25962
- var convertWslPathToWindows = async (path7) => {
25963
- if (/^[a-z]+:\/\//i.test(path7)) {
25964
- return path7;
25964
+ var convertWslPathToWindows = async (path8) => {
25965
+ if (/^[a-z]+:\/\//i.test(path8)) {
25966
+ return path8;
25965
25967
  }
25966
25968
  try {
25967
- const { stdout } = await execFile2("wslpath", ["-aw", path7], { encoding: "utf8" });
25969
+ const { stdout } = await execFile2("wslpath", ["-aw", path8], { encoding: "utf8" });
25968
25970
  return stdout.trim();
25969
25971
  } catch {
25970
- return path7;
25972
+ return path8;
25971
25973
  }
25972
25974
  };
25973
25975
 
@@ -26517,7 +26519,7 @@ var AgentMode = class _AgentMode {
26517
26519
  };
26518
26520
 
26519
26521
  // src/CodexAcpClient.ts
26520
- import path5 from "node:path";
26522
+ import path6 from "node:path";
26521
26523
 
26522
26524
  // src/McpServerName.ts
26523
26525
  var MCP_SERVER_NAME_WHITESPACE = /\p{White_Space}/gu;
@@ -26531,7 +26533,7 @@ var package_default = {
26531
26533
  publishConfig: {
26532
26534
  access: "public"
26533
26535
  },
26534
- version: "1.3.0",
26536
+ version: "1.4.0",
26535
26537
  description: "",
26536
26538
  main: "dist/index.js",
26537
26539
  bin: {
@@ -26684,6 +26686,295 @@ function isRootPath(value) {
26684
26686
  return value === "/" || /^[A-Za-z]:\/$/.test(value) || /^\/\/[^/]+\/[^/]+\/$/.test(value);
26685
26687
  }
26686
26688
 
26689
+ // src/AgentFileChangeReport.ts
26690
+ import fs7 from "node:fs";
26691
+ import path5 from "node:path";
26692
+ var AGENT_FILE_CHANGE_REPORT_VERSION = 1;
26693
+ var AGENT_FILE_CHANGE_REPORT_TIMEOUT_MS = 3e4;
26694
+ var AGENT_FILE_CHANGE_REPORT_MAX_PATHS = 1024;
26695
+ var AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH = 4096;
26696
+ var AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES = 256 * 1024;
26697
+ var AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH = 2e3;
26698
+ var AGENT_FILE_CHANGE_REPORT_OUTPUT_SCHEMA = {
26699
+ type: "object",
26700
+ additionalProperties: false,
26701
+ required: ["paths", "complete"],
26702
+ properties: {
26703
+ paths: {
26704
+ type: "array",
26705
+ items: { type: "string" }
26706
+ },
26707
+ complete: { type: "boolean" },
26708
+ uncertainty: {
26709
+ type: "string",
26710
+ maxLength: AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH
26711
+ }
26712
+ }
26713
+ };
26714
+ var AGENT_FILE_CHANGE_REPORT_DEVELOPER_INSTRUCTIONS = `You are running an internal, read-only file-change audit for the immediately preceding turn.
26715
+ Do not modify files, run commands that can modify files, or ask the user questions.
26716
+ 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.
26717
+ Do not report files that were only read or inspected.
26718
+ You may use read-only inspection when needed. If the list may be incomplete, set complete to false and briefly explain why in uncertainty.`;
26719
+ function createAgentFileChangeReportPrompt(workspace) {
26720
+ return `List the paths changed by the immediately preceding turn.
26721
+ Return only the structured result required by the output schema.
26722
+ Relative paths are resolved against the working directory.
26723
+ Working directory: ${JSON.stringify(workspace.cwd)}
26724
+ Additional allowed directories: ${JSON.stringify(workspace.additionalDirectories)}`;
26725
+ }
26726
+ function parseAgentFileChangeReportRequest(meta3) {
26727
+ const jetbrains = asRecord(meta3?.[JETBRAINS_META_KEY]);
26728
+ const air = asRecord(jetbrains?.[AIR_META_KEY]);
26729
+ const request = asRecord(air?.[AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY]);
26730
+ if (request === null || !hasOnlyKeys(request, ["version", "requestId"]) || request["version"] !== AGENT_FILE_CHANGE_REPORT_VERSION) {
26731
+ return null;
26732
+ }
26733
+ const requestId = request["requestId"];
26734
+ if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) {
26735
+ return null;
26736
+ }
26737
+ return { version: AGENT_FILE_CHANGE_REPORT_VERSION, requestId };
26738
+ }
26739
+ function createReportedAgentFileChangeReport(requestId, turn, workspace) {
26740
+ const modelReport = parseModelFileChangeReport(turn);
26741
+ const normalized = normalizeModelFileChangeReport(modelReport, workspace);
26742
+ return fitReportedAgentFileChangeReport({
26743
+ version: AGENT_FILE_CHANGE_REPORT_VERSION,
26744
+ requestId,
26745
+ status: "reported",
26746
+ ...normalized
26747
+ });
26748
+ }
26749
+ function createUnavailableAgentFileChangeReport(requestId, reason) {
26750
+ return {
26751
+ version: AGENT_FILE_CHANGE_REPORT_VERSION,
26752
+ requestId,
26753
+ status: "unavailable",
26754
+ reason
26755
+ };
26756
+ }
26757
+ var AgentFileChangeReportError = class extends Error {
26758
+ reason;
26759
+ constructor(reason, message) {
26760
+ super(message);
26761
+ this.name = "AgentFileChangeReportError";
26762
+ this.reason = reason;
26763
+ }
26764
+ };
26765
+ function parseModelFileChangeReport(turn) {
26766
+ switch (turn.status) {
26767
+ case "interrupted":
26768
+ throw new AgentFileChangeReportError("cancelled", "The audit turn was interrupted");
26769
+ case "failed":
26770
+ throw new AgentFileChangeReportError("providerError", "The audit turn failed");
26771
+ case "inProgress":
26772
+ throw new AgentFileChangeReportError("notReported", "The audit turn did not complete");
26773
+ case "completed":
26774
+ break;
26775
+ }
26776
+ let text = null;
26777
+ for (let index = turn.items.length - 1; index >= 0; index -= 1) {
26778
+ const item = turn.items[index];
26779
+ if (item?.type === "agentMessage") {
26780
+ text = item.text;
26781
+ break;
26782
+ }
26783
+ }
26784
+ if (text === null) {
26785
+ throw new AgentFileChangeReportError("notReported", "The audit turn returned no agent message");
26786
+ }
26787
+ let value;
26788
+ try {
26789
+ value = JSON.parse(text);
26790
+ } catch {
26791
+ throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned invalid JSON");
26792
+ }
26793
+ const report = asRecord(value);
26794
+ if (report === null || !hasOnlyKeys(report, ["paths", "complete", "uncertainty"])) {
26795
+ throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned an invalid object");
26796
+ }
26797
+ const paths = report["paths"];
26798
+ const complete = report["complete"];
26799
+ const uncertainty = report["uncertainty"];
26800
+ if (!Array.isArray(paths) || !paths.every((item) => typeof item === "string") || typeof complete !== "boolean" || uncertainty !== void 0 && typeof uncertainty !== "string") {
26801
+ throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned invalid fields");
26802
+ }
26803
+ const normalizedUncertainty = uncertainty?.trim();
26804
+ if (normalizedUncertainty !== void 0 && normalizedUncertainty.length > AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH) {
26805
+ throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned oversized uncertainty");
26806
+ }
26807
+ return {
26808
+ paths,
26809
+ complete,
26810
+ ...normalizedUncertainty ? { uncertainty: normalizedUncertainty } : {}
26811
+ };
26812
+ }
26813
+ function fitReportedAgentFileChangeReport(report) {
26814
+ const paths = [...report.paths];
26815
+ let truncated = report.truncated;
26816
+ let fitted = report;
26817
+ while (Buffer.byteLength(JSON.stringify(fitted), "utf8") > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
26818
+ if (paths.length === 0) {
26819
+ throw new AgentFileChangeReportError("invalidOutput", "The audit report exceeds the wire limit");
26820
+ }
26821
+ paths.pop();
26822
+ truncated = true;
26823
+ fitted = {
26824
+ ...report,
26825
+ paths: [...paths],
26826
+ declaredComplete: false,
26827
+ truncated
26828
+ };
26829
+ }
26830
+ return fitted;
26831
+ }
26832
+ function normalizeModelFileChangeReport(report, workspace) {
26833
+ const cwd = normalizeWorkspaceRoot(workspace.cwd);
26834
+ if (cwd === null) {
26835
+ throw new AgentFileChangeReportError("providerError", "The session working directory is not absolute");
26836
+ }
26837
+ const roots = [cwd, ...workspace.additionalDirectories.flatMap((directory) => {
26838
+ const root = normalizeWorkspaceRoot(directory);
26839
+ return root === null || root.flavor !== cwd.flavor ? [] : [root];
26840
+ })];
26841
+ const paths = [];
26842
+ const seen = /* @__PURE__ */ new Set();
26843
+ let totalBytes = 0;
26844
+ let truncated = false;
26845
+ for (const reportedPath of report.paths) {
26846
+ if (reportedPath.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
26847
+ truncated = true;
26848
+ continue;
26849
+ }
26850
+ const normalized = normalizeReportedPath(reportedPath, cwd, roots);
26851
+ if (normalized === null || normalized.value.length > AGENT_FILE_CHANGE_REPORT_MAX_PATH_LENGTH) {
26852
+ truncated = true;
26853
+ continue;
26854
+ }
26855
+ const key = normalized.flavor === "windows" ? normalized.value.toLowerCase() : normalized.value;
26856
+ if (seen.has(key)) {
26857
+ continue;
26858
+ }
26859
+ const bytes = Buffer.byteLength(normalized.value, "utf8");
26860
+ if (paths.length >= AGENT_FILE_CHANGE_REPORT_MAX_PATHS || totalBytes + bytes > AGENT_FILE_CHANGE_REPORT_MAX_TOTAL_BYTES) {
26861
+ truncated = true;
26862
+ continue;
26863
+ }
26864
+ seen.add(key);
26865
+ paths.push(normalized.value);
26866
+ totalBytes += bytes;
26867
+ }
26868
+ return {
26869
+ paths,
26870
+ declaredComplete: report.complete && !truncated,
26871
+ truncated,
26872
+ ...report.uncertainty ? { uncertainty: report.uncertainty } : {}
26873
+ };
26874
+ }
26875
+ function normalizeWorkspaceRoot(value) {
26876
+ const trimmed = value.trim();
26877
+ if (!isValidPathText(trimmed)) {
26878
+ return null;
26879
+ }
26880
+ if (isWindowsAbsolutePath2(trimmed)) {
26881
+ return canonicalizeWorkspaceRoot({
26882
+ value: path5.win32.normalize(trimmed.replace(/\//g, "\\")),
26883
+ flavor: "windows"
26884
+ });
26885
+ }
26886
+ if (path5.posix.isAbsolute(trimmed)) {
26887
+ return canonicalizeWorkspaceRoot({
26888
+ value: path5.posix.normalize(trimmed.replace(/\\/g, "/")),
26889
+ flavor: "posix"
26890
+ });
26891
+ }
26892
+ return null;
26893
+ }
26894
+ function normalizeReportedPath(value, cwd, roots) {
26895
+ const trimmed = value.trim();
26896
+ 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)) {
26897
+ return null;
26898
+ }
26899
+ let candidate;
26900
+ if (isWindowsAbsolutePath2(trimmed)) {
26901
+ candidate = { value: path5.win32.normalize(trimmed.replace(/\//g, "\\")), flavor: "windows" };
26902
+ } else if (path5.posix.isAbsolute(trimmed)) {
26903
+ candidate = { value: path5.posix.normalize(trimmed.replace(/\\/g, "/")), flavor: "posix" };
26904
+ } else if (cwd.flavor === "windows") {
26905
+ candidate = {
26906
+ value: path5.win32.resolve(cwd.value, trimmed.replace(/\//g, "\\")),
26907
+ flavor: "windows"
26908
+ };
26909
+ } else {
26910
+ candidate = {
26911
+ value: path5.posix.resolve(cwd.value, trimmed.replace(/\\/g, "/")),
26912
+ flavor: "posix"
26913
+ };
26914
+ }
26915
+ candidate = canonicalizeReportedPath(candidate);
26916
+ return roots.some((root) => pathIsStrictlyInside(root, candidate)) ? candidate : null;
26917
+ }
26918
+ function canonicalizeWorkspaceRoot(root) {
26919
+ if (!isNativePathFlavor(root.flavor)) {
26920
+ return root;
26921
+ }
26922
+ return { ...root, value: canonicalizeFromExistingAncestor(root.value) };
26923
+ }
26924
+ function canonicalizeReportedPath(candidate) {
26925
+ if (!isNativePathFlavor(candidate.flavor)) {
26926
+ return candidate;
26927
+ }
26928
+ const parent = canonicalizeFromExistingAncestor(path5.dirname(candidate.value));
26929
+ return { ...candidate, value: path5.resolve(parent, path5.basename(candidate.value)) };
26930
+ }
26931
+ function canonicalizeFromExistingAncestor(value) {
26932
+ const original = path5.resolve(value);
26933
+ let current = original;
26934
+ const missingSegments = [];
26935
+ while (true) {
26936
+ try {
26937
+ const canonical = fs7.realpathSync.native(current);
26938
+ return path5.resolve(canonical, ...missingSegments.reverse());
26939
+ } catch (error51) {
26940
+ if (!isMissingPathError(error51)) return original;
26941
+ }
26942
+ const parent = path5.dirname(current);
26943
+ if (parent === current) return original;
26944
+ missingSegments.push(path5.basename(current));
26945
+ current = parent;
26946
+ }
26947
+ }
26948
+ function isMissingPathError(error51) {
26949
+ if (typeof error51 !== "object" || error51 === null || !("code" in error51)) return false;
26950
+ return error51.code === "ENOENT" || error51.code === "ENOTDIR";
26951
+ }
26952
+ function isNativePathFlavor(flavor) {
26953
+ return process.platform === "win32" ? flavor === "windows" : flavor === "posix";
26954
+ }
26955
+ function pathIsStrictlyInside(root, candidate) {
26956
+ if (root.flavor !== candidate.flavor) {
26957
+ return false;
26958
+ }
26959
+ const pathImplementation = root.flavor === "windows" ? path5.win32 : path5.posix;
26960
+ const relative = pathImplementation.relative(root.value, candidate.value);
26961
+ return relative.length > 0 && !pathImplementation.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${pathImplementation.sep}`);
26962
+ }
26963
+ function isWindowsAbsolutePath2(value) {
26964
+ const portable = value.replace(/\\/g, "/");
26965
+ return /^[A-Za-z]:\//.test(portable) || /^\/\/[^/]+\/[^/]+(?:\/|$)/.test(portable);
26966
+ }
26967
+ function isValidPathText(value) {
26968
+ return value.length > 0 && !/[\u0000-\u001F\u007F-\u009F]/.test(value);
26969
+ }
26970
+ function asRecord(value) {
26971
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
26972
+ }
26973
+ function hasOnlyKeys(value, allowed) {
26974
+ const allowedKeys = new Set(allowed);
26975
+ return Object.keys(value).every((key) => allowedKeys.has(key));
26976
+ }
26977
+
26687
26978
  // src/CodexAcpClient.ts
26688
26979
  var CUSTOM_GATEWAY_PROVIDER_ID = "custom-gateway";
26689
26980
  var OPENAI_PROVIDER_ID = "openai";
@@ -27153,7 +27444,7 @@ var CodexAcpClient = class {
27153
27444
  if (!cwd) {
27154
27445
  return;
27155
27446
  }
27156
- const skillExtraRoots = additionalRoots.map((root) => path5.join(root, ".agents", "skills"));
27447
+ const skillExtraRoots = additionalRoots.map((root) => path6.join(root, ".agents", "skills"));
27157
27448
  if (!arraysEqual(this.skillExtraRoots, skillExtraRoots)) {
27158
27449
  await this.codexClient.skillsExtraRootsSet({ extraRoots: skillExtraRoots });
27159
27450
  this.skillExtraRoots = skillExtraRoots;
@@ -27275,6 +27566,113 @@ var CodexAcpClient = class {
27275
27566
  serviceTier
27276
27567
  }, onTurnStarted);
27277
27568
  }
27569
+ async runAgentFileChangeReport(params) {
27570
+ if (params.signal?.aborted) {
27571
+ return createUnavailableAgentFileChangeReport(params.requestId, "cancelled");
27572
+ }
27573
+ const budget = new AgentFileChangeReportBudget(params.signal);
27574
+ let forkThreadId = null;
27575
+ let auditTurnId = null;
27576
+ let auditTurnCompleted = false;
27577
+ let lateStopReason = null;
27578
+ try {
27579
+ const forkPromise = this.codexClient.threadFork({
27580
+ threadId: params.sessionId,
27581
+ lastTurnId: params.turnId,
27582
+ cwd: params.workspace.cwd,
27583
+ approvalPolicy: "never",
27584
+ sandbox: "read-only",
27585
+ developerInstructions: AGENT_FILE_CHANGE_REPORT_DEVELOPER_INSTRUCTIONS,
27586
+ ephemeral: true
27587
+ });
27588
+ void forkPromise.then((fork2) => {
27589
+ if (lateStopReason !== null && forkThreadId === null) {
27590
+ void this.unsubscribeAgentFileChangeReportThread(fork2.thread.id, budget);
27591
+ }
27592
+ }, () => {
27593
+ });
27594
+ const fork = await budget.wait(forkPromise);
27595
+ forkThreadId = fork.thread.id;
27596
+ const turnPromise = this.codexClient.runTurn({
27597
+ threadId: forkThreadId,
27598
+ input: [{
27599
+ type: "text",
27600
+ text: createAgentFileChangeReportPrompt(params.workspace),
27601
+ text_elements: []
27602
+ }],
27603
+ cwd: params.workspace.cwd,
27604
+ approvalPolicy: "never",
27605
+ sandboxPolicy: { type: "readOnly", networkAccess: false },
27606
+ summary: "none",
27607
+ outputSchema: AGENT_FILE_CHANGE_REPORT_OUTPUT_SCHEMA
27608
+ }, (turnId) => {
27609
+ auditTurnId = turnId;
27610
+ if (lateStopReason !== null && forkThreadId !== null) {
27611
+ void this.interruptAgentFileChangeReport(forkThreadId, turnId, lateStopReason, budget);
27612
+ }
27613
+ });
27614
+ const outcome = await budget.wait(turnPromise);
27615
+ auditTurnCompleted = true;
27616
+ const thread = await budget.wait(this.codexClient.threadRead({
27617
+ threadId: forkThreadId,
27618
+ includeTurns: true
27619
+ }));
27620
+ const completedTurn = thread.thread.turns.find(
27621
+ (turn) => turn.id === outcome.turn.id
27622
+ );
27623
+ if (completedTurn === void 0) {
27624
+ throw new AgentFileChangeReportError(
27625
+ "notReported",
27626
+ "The completed audit turn was not present in thread history"
27627
+ );
27628
+ }
27629
+ return createReportedAgentFileChangeReport(
27630
+ params.requestId,
27631
+ completedTurn,
27632
+ params.workspace
27633
+ );
27634
+ } catch (error51) {
27635
+ if (error51 instanceof AgentFileChangeReportBudgetError) {
27636
+ lateStopReason = error51.reason;
27637
+ if (!auditTurnCompleted && forkThreadId !== null && auditTurnId !== null) {
27638
+ await this.interruptAgentFileChangeReport(
27639
+ forkThreadId,
27640
+ auditTurnId,
27641
+ error51.reason,
27642
+ budget
27643
+ );
27644
+ }
27645
+ return createUnavailableAgentFileChangeReport(params.requestId, error51.reason);
27646
+ }
27647
+ if (error51 instanceof AgentFileChangeReportError) {
27648
+ logger.log("Agent file-change report unavailable", { reason: error51.reason });
27649
+ return createUnavailableAgentFileChangeReport(params.requestId, error51.reason);
27650
+ }
27651
+ logger.error("Agent file-change report failed", error51);
27652
+ return createUnavailableAgentFileChangeReport(params.requestId, "providerError");
27653
+ } finally {
27654
+ if (forkThreadId !== null) {
27655
+ await this.unsubscribeAgentFileChangeReportThread(forkThreadId, budget);
27656
+ }
27657
+ }
27658
+ }
27659
+ async interruptAgentFileChangeReport(threadId, turnId, reason, budget) {
27660
+ this.codexClient.markTurnStale(threadId, turnId);
27661
+ try {
27662
+ await budget.wait(this.codexClient.turnInterrupt({ threadId, turnId }));
27663
+ } catch (error51) {
27664
+ logger.error(`Failed to interrupt ${reason} agent file-change report`, error51);
27665
+ } finally {
27666
+ this.codexClient.resolveTurnInterrupted(threadId, turnId);
27667
+ }
27668
+ }
27669
+ async unsubscribeAgentFileChangeReportThread(threadId, budget) {
27670
+ try {
27671
+ await budget.wait(this.codexClient.threadUnsubscribe({ threadId }));
27672
+ } catch (error51) {
27673
+ logger.error("Failed to unsubscribe the agent file-change report thread", error51);
27674
+ }
27675
+ }
27278
27676
  async setCollaborationMode(sessionId, mode, currentModelId) {
27279
27677
  await this.codexClient.threadSettingsUpdate({
27280
27678
  threadId: sessionId,
@@ -27425,6 +27823,58 @@ var CodexAcpClient = class {
27425
27823
  };
27426
27824
  }
27427
27825
  };
27826
+ var AgentFileChangeReportBudgetError = class extends Error {
27827
+ constructor(reason) {
27828
+ super(`Agent file-change report ${reason}`);
27829
+ this.reason = reason;
27830
+ this.name = "AgentFileChangeReportBudgetError";
27831
+ }
27832
+ reason;
27833
+ };
27834
+ var AgentFileChangeReportBudget = class {
27835
+ constructor(signal) {
27836
+ this.signal = signal;
27837
+ }
27838
+ signal;
27839
+ deadline = Date.now() + AGENT_FILE_CHANGE_REPORT_TIMEOUT_MS;
27840
+ async wait(operation) {
27841
+ void operation.catch(() => {
27842
+ });
27843
+ const immediateReason = this.stopReason();
27844
+ if (immediateReason !== null) {
27845
+ throw new AgentFileChangeReportBudgetError(immediateReason);
27846
+ }
27847
+ return await new Promise((resolve, reject) => {
27848
+ let settled = false;
27849
+ const finish = (action) => {
27850
+ if (settled) return;
27851
+ settled = true;
27852
+ clearTimeout(timeout);
27853
+ this.signal?.removeEventListener("abort", onAbort);
27854
+ action();
27855
+ };
27856
+ const onAbort = () => finish(() => reject(new AgentFileChangeReportBudgetError("cancelled")));
27857
+ const timeout = setTimeout(
27858
+ () => finish(() => reject(new AgentFileChangeReportBudgetError("timeout"))),
27859
+ Math.max(1, this.deadline - Date.now())
27860
+ );
27861
+ timeout.unref();
27862
+ this.signal?.addEventListener("abort", onAbort, { once: true });
27863
+ if (this.signal?.aborted) {
27864
+ onAbort();
27865
+ }
27866
+ void operation.then(
27867
+ (value) => finish(() => resolve(value)),
27868
+ (error51) => finish(() => reject(error51))
27869
+ );
27870
+ });
27871
+ }
27872
+ stopReason() {
27873
+ if (this.signal?.aborted) return "cancelled";
27874
+ if (Date.now() >= this.deadline) return "timeout";
27875
+ return null;
27876
+ }
27877
+ };
27428
27878
  function buildPromptItems(prompt) {
27429
27879
  return prompt.map((block) => {
27430
27880
  switch (block.type) {
@@ -27484,8 +27934,8 @@ function formatUriAsLink(name, uri) {
27484
27934
  return `[@${name}](${uri})`;
27485
27935
  }
27486
27936
  if (uri.startsWith("file://")) {
27487
- const path7 = uri.replace("file://", "");
27488
- const fileName = path7.split("/").pop() ?? path7;
27937
+ const path8 = uri.replace("file://", "");
27938
+ const fileName = path8.split("/").pop() ?? path8;
27489
27939
  return `[@${fileName}](${uri})`;
27490
27940
  }
27491
27941
  return uri;
@@ -27515,7 +27965,7 @@ function readAdditionalDirectories(cwd, additionalDirectories, meta3) {
27515
27965
  if (directory.length === 0) {
27516
27966
  throw RequestError.invalidParams(void 0, "additionalDirectories entries must not be empty");
27517
27967
  }
27518
- if (!path5.isAbsolute(directory)) {
27968
+ if (!path6.isAbsolute(directory)) {
27519
27969
  throw RequestError.invalidParams(void 0, "additionalDirectories entries must be absolute paths");
27520
27970
  }
27521
27971
  if (!seen.has(directory)) {
@@ -28118,7 +28568,7 @@ var SteeringQueue = class {
28118
28568
 
28119
28569
  // src/ResponseItemHistoryFallback.ts
28120
28570
  import { readFile as readFile2 } from "node:fs/promises";
28121
- import path6 from "node:path";
28571
+ import path7 from "node:path";
28122
28572
  function historyFallbackUpdateKey(update) {
28123
28573
  switch (update.sessionUpdate) {
28124
28574
  case "user_message_chunk":
@@ -28272,14 +28722,14 @@ function parseJsonRecord(line) {
28272
28722
  }
28273
28723
  try {
28274
28724
  const value = JSON.parse(line);
28275
- return asRecord(value);
28725
+ return asRecord2(value);
28276
28726
  } catch {
28277
28727
  return null;
28278
28728
  }
28279
28729
  }
28280
28730
  function extractResponseItem(record2) {
28281
28731
  if (record2["type"] === "response_item") {
28282
- return asRecord(record2["payload"]);
28732
+ return asRecord2(record2["payload"]);
28283
28733
  }
28284
28734
  const itemType = record2["type"];
28285
28735
  if (typeof itemType === "string" && isLegacyResponseItemType(itemType)) {
@@ -28313,7 +28763,7 @@ function createEventMsgUpdates(record2) {
28313
28763
  if (record2["type"] !== "event_msg") {
28314
28764
  return null;
28315
28765
  }
28316
- const payload = asRecord(record2["payload"]);
28766
+ const payload = asRecord2(record2["payload"]);
28317
28767
  if (!payload) {
28318
28768
  return [];
28319
28769
  }
@@ -28357,9 +28807,9 @@ function imageBlocks(images) {
28357
28807
  if (typeof image === "string") {
28358
28808
  return [{ type: "text", text: `[@image](${image})` }];
28359
28809
  }
28360
- const record2 = asRecord(image);
28361
- const path7 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
28362
- return path7 ? [{ type: "text", text: `[@image](${path7})` }] : [];
28810
+ const record2 = asRecord2(image);
28811
+ const path8 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
28812
+ return path8 ? [{ type: "text", text: `[@image](${path8})` }] : [];
28363
28813
  });
28364
28814
  }
28365
28815
  function contentBlocksFromResponseContent(content) {
@@ -28367,7 +28817,7 @@ function contentBlocksFromResponseContent(content) {
28367
28817
  return [];
28368
28818
  }
28369
28819
  return content.flatMap((entry) => {
28370
- const record2 = asRecord(entry);
28820
+ const record2 = asRecord2(entry);
28371
28821
  if (!record2) {
28372
28822
  return [];
28373
28823
  }
@@ -28400,7 +28850,7 @@ function textParts(value) {
28400
28850
  if (typeof entry === "string") {
28401
28851
  return entry.length > 0 ? [entry] : [];
28402
28852
  }
28403
- const record2 = asRecord(entry);
28853
+ const record2 = asRecord2(entry);
28404
28854
  if (!record2) {
28405
28855
  return [];
28406
28856
  }
@@ -28492,7 +28942,7 @@ function parseFunctionArguments(value) {
28492
28942
  }
28493
28943
  function rawInputForFunctionCall(name, args) {
28494
28944
  if (name === "exec_command") {
28495
- const record2 = asRecord(args);
28945
+ const record2 = asRecord2(args);
28496
28946
  if (record2) {
28497
28947
  return {
28498
28948
  command: stringValue(record2["cmd"]) ?? stringValue(record2["command"]),
@@ -28539,14 +28989,14 @@ function functionCallUsesTerminal(item) {
28539
28989
  return item["name"] === "exec_command";
28540
28990
  }
28541
28991
  function commandFromFunctionArguments(args) {
28542
- const record2 = asRecord(args);
28992
+ const record2 = asRecord2(args);
28543
28993
  if (!record2) {
28544
28994
  return null;
28545
28995
  }
28546
28996
  return stringValue(record2["cmd"]) ?? stringValue(record2["command"]);
28547
28997
  }
28548
28998
  function cwdFromFunctionArguments(args) {
28549
- const record2 = asRecord(args);
28999
+ const record2 = asRecord2(args);
28550
29000
  if (!record2) {
28551
29001
  return "";
28552
29002
  }
@@ -28991,10 +29441,10 @@ function baseCommandName(commandName) {
28991
29441
  return commandName.split(/[\\/]/).pop() ?? commandName;
28992
29442
  }
28993
29443
  function absolutizePath(cwd, targetPath) {
28994
- if (path6.isAbsolute(targetPath) || cwd.length === 0) {
29444
+ if (path7.isAbsolute(targetPath) || cwd.length === 0) {
28995
29445
  return targetPath;
28996
29446
  }
28997
- return path6.join(cwd, targetPath);
29447
+ return path7.join(cwd, targetPath);
28998
29448
  }
28999
29449
  function withTerminalContent(event, terminalId, cwd) {
29000
29450
  const { rawInput, ...eventWithoutRawInput } = event;
@@ -29018,7 +29468,7 @@ function outputText(output) {
29018
29468
  return "";
29019
29469
  }
29020
29470
  return output.flatMap((entry) => {
29021
- const record2 = asRecord(entry);
29471
+ const record2 = asRecord2(entry);
29022
29472
  if (!record2) {
29023
29473
  return [];
29024
29474
  }
@@ -29034,7 +29484,7 @@ function outputText(output) {
29034
29484
  }).join("\n");
29035
29485
  }
29036
29486
  function parseExitCode(rawOutput, output) {
29037
- const record2 = asRecord(rawOutput);
29487
+ const record2 = asRecord2(rawOutput);
29038
29488
  if (record2) {
29039
29489
  const exitCode2 = numberValue(record2["exit_code"]) ?? numberValue(record2["exitCode"]);
29040
29490
  if (exitCode2 !== null) {
@@ -29065,7 +29515,7 @@ function looksLikeCommandFailure(output) {
29065
29515
  }
29066
29516
  return /(^|\n)(Error|Failed|Command failed|Sandbox error|No such file or directory|Permission denied|Operation not permitted|ENOENT|EACCES)(:|\b)/i.test(trimmed);
29067
29517
  }
29068
- function asRecord(value) {
29518
+ function asRecord2(value) {
29069
29519
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
29070
29520
  return null;
29071
29521
  }
@@ -29154,12 +29604,18 @@ import { randomUUID as randomUUID2 } from "node:crypto";
29154
29604
  var IMPLEMENT_PLAN_OPTION_ID = "implement_plan";
29155
29605
  var REVISE_PLAN_OPTION_ID = "revise_plan";
29156
29606
  var CODEX_PROCESS_EXITED_ERROR_CODE = 1001;
29157
- function clientSupportsTypedSessionFailures(capabilities) {
29607
+ function clientSupportsAirCapability(capabilities, capability) {
29158
29608
  const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
29159
29609
  const air = jetbrains?.[AIR_META_KEY];
29160
29610
  const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
29161
29611
  const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
29162
- return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(AIR_SESSION_FAILURE_KEY);
29612
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
29613
+ }
29614
+ function clientSupportsTypedSessionFailures(capabilities) {
29615
+ return clientSupportsAirCapability(capabilities, AIR_SESSION_FAILURE_KEY);
29616
+ }
29617
+ function clientSupportsAgentFileChangeReports(capabilities) {
29618
+ return clientSupportsAirCapability(capabilities, AIR_AGENT_FILE_CHANGE_REPORT_KEY);
29163
29619
  }
29164
29620
  var CodexAcpServer = class _CodexAcpServer {
29165
29621
  static MODEL_NAME_TOKEN_OVERRIDES = {
@@ -29264,7 +29720,10 @@ var CodexAcpServer = class _CodexAcpServer {
29264
29720
  [JETBRAINS_META_KEY]: {
29265
29721
  [AIR_META_KEY]: {
29266
29722
  [AIR_EXTENSION_VERSION_KEY]: AIR_EXTENSION_VERSION,
29267
- [AIR_EXTENSION_CAPABILITIES_KEY]: [AIR_SESSION_FAILURE_KEY]
29723
+ [AIR_EXTENSION_CAPABILITIES_KEY]: [
29724
+ AIR_SESSION_FAILURE_KEY,
29725
+ AIR_AGENT_FILE_CHANGE_REPORT_KEY
29726
+ ]
29268
29727
  }
29269
29728
  }
29270
29729
  }
@@ -30315,6 +30774,42 @@ Check ${configPath} and project .codex directories, especially their config.toml
30315
30774
  title
30316
30775
  });
30317
30776
  }
30777
+ async publishAgentFileChangeReport(sessionState, turnId, request, unavailableReason, signal) {
30778
+ let report;
30779
+ try {
30780
+ report = turnId === null ? createUnavailableAgentFileChangeReport(request.requestId, unavailableReason) : await this.codexAcpClient.runAgentFileChangeReport({
30781
+ sessionId: sessionState.sessionId,
30782
+ turnId,
30783
+ // The client owns request-id correlation and duplicate suppression. The wrapper
30784
+ // stays stateless so a retried ACP prompt still receives a terminal report.
30785
+ requestId: request.requestId,
30786
+ workspace: {
30787
+ cwd: sessionState.cwd,
30788
+ additionalDirectories: sessionState.additionalDirectories
30789
+ },
30790
+ signal
30791
+ });
30792
+ } catch (error51) {
30793
+ logger.error("Agent file-change report failed unexpectedly", error51);
30794
+ report = createUnavailableAgentFileChangeReport(request.requestId, "providerError");
30795
+ }
30796
+ try {
30797
+ const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
30798
+ await session.update({
30799
+ sessionUpdate: "session_info_update",
30800
+ _meta: {
30801
+ [JETBRAINS_META_KEY]: {
30802
+ [AIR_META_KEY]: {
30803
+ [AIR_EXTENSION_VERSION_KEY]: AIR_EXTENSION_VERSION,
30804
+ [AIR_AGENT_FILE_CHANGE_REPORT_KEY]: report
30805
+ }
30806
+ }
30807
+ }
30808
+ });
30809
+ } catch (error51) {
30810
+ logger.error("Failed to publish agent file-change report", error51);
30811
+ }
30812
+ }
30318
30813
  createPromptFallbackTitle(prompt) {
30319
30814
  return this.normalizeSessionTitle(prompt.filter((block) => block.type === "text").map((block) => block.text).join(" "));
30320
30815
  }
@@ -30449,8 +30944,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
30449
30944
  return `[@${name}](${uri})`;
30450
30945
  }
30451
30946
  if (uri.startsWith("file://")) {
30452
- const path7 = uri.replace("file://", "");
30453
- const fileName = path7.split("/").pop() ?? path7;
30947
+ const path8 = uri.replace("file://", "");
30948
+ const fileName = path8.split("/").pop() ?? path8;
30454
30949
  return `[@${fileName}](${uri})`;
30455
30950
  }
30456
30951
  return uri;
@@ -30703,6 +31198,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
30703
31198
  prompt: params.prompt
30704
31199
  });
30705
31200
  const sessionState = this.getSessionState(params.sessionId);
31201
+ const agentFileChangeReportRequest = clientSupportsAgentFileChangeReports(this.clientCapabilities) ? parseAgentFileChangeReportRequest(params._meta) : null;
31202
+ let agentFileChangeReportTurnId = null;
31203
+ let agentFileChangeReportUnavailableReason = "providerError";
30706
31204
  let recoverableSessionFailure = sessionState.sessionFailure;
30707
31205
  sessionState.currentTurnId = null;
30708
31206
  sessionState.lastTokenUsage = null;
@@ -30725,6 +31223,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
30725
31223
  await handler.clearSessionFailure();
30726
31224
  }
30727
31225
  };
31226
+ const cancelledPromptResponse = () => {
31227
+ agentFileChangeReportTurnId = null;
31228
+ agentFileChangeReportUnavailableReason = "cancelled";
31229
+ return this.cancelledPromptResponse(sessionState);
31230
+ };
30728
31231
  try {
30729
31232
  const promptEventHandler = new CodexEventHandler(
30730
31233
  this.connection,
@@ -30759,7 +31262,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30759
31262
  elicitationHandler
30760
31263
  );
30761
31264
  if (activePrompt.signal.aborted) {
30762
- return this.cancelledPromptResponse(sessionState);
31265
+ return cancelledPromptResponse();
30763
31266
  }
30764
31267
  const commandPromise = this.availableCommands.tryHandleCommand(params.prompt, sessionState, {
30765
31268
  onTurnStartPending: () => {
@@ -30800,7 +31303,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30800
31303
  this.cancelBeforeTurnStarted(activePrompt)
30801
31304
  ]);
30802
31305
  if (commandResult === null) {
30803
- return this.cancelledPromptResponse(sessionState);
31306
+ return cancelledPromptResponse();
30804
31307
  }
30805
31308
  if (commandResult.handled) {
30806
31309
  promptNotificationsActive = false;
@@ -30812,7 +31315,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30812
31315
  await eventHandler.handleFailedTurn(commandResult.turnCompleted.turn);
30813
31316
  }
30814
31317
  if (commandResult.turnCompleted?.turn.status === "interrupted") {
30815
- return this.cancelledPromptResponse(sessionState);
31318
+ return cancelledPromptResponse();
30816
31319
  }
30817
31320
  const error52 = eventHandler.getFailure();
30818
31321
  if (error52) {
@@ -30826,6 +31329,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
30826
31329
  if (terminalFailure2) {
30827
31330
  return terminalFailure2;
30828
31331
  }
31332
+ if (commandResult.turnCompleted?.turn.status === "completed") {
31333
+ agentFileChangeReportTurnId = commandResult.turnCompleted.turn.id;
31334
+ } else if (commandResult.turnCompleted === void 0) {
31335
+ agentFileChangeReportUnavailableReason = "notReported";
31336
+ }
30829
31337
  await clearRecoveredSessionFailure(eventHandler);
30830
31338
  return {
30831
31339
  stopReason: "end_turn",
@@ -30835,7 +31343,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30835
31343
  }
30836
31344
  const effectiveParams = commandResult.prompt === void 0 ? params : { ...params, prompt: commandResult.prompt };
30837
31345
  if (this.sessionIsClosing(params.sessionId)) {
30838
- return this.cancelledPromptResponse(sessionState);
31346
+ return cancelledPromptResponse();
30839
31347
  }
30840
31348
  const modelId = ModelId.fromString(sessionState.currentModelId);
30841
31349
  const modelLacksReasoning = sessionState.supportedReasoningEfforts.length > 0 && sessionState.supportedReasoningEfforts.every((e) => e.reasoningEffort === "none");
@@ -30889,7 +31397,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30889
31397
  this.cancelBeforeTurnStarted(activePrompt)
30890
31398
  ]);
30891
31399
  if (turnCompleted === null) {
30892
- return this.cancelledPromptResponse(sessionState);
31400
+ return cancelledPromptResponse();
30893
31401
  }
30894
31402
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30895
31403
  await eventHandler.flushPendingErrors();
@@ -30897,7 +31405,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30897
31405
  promptNotificationsActive = false;
30898
31406
  if (turnCompleted.turn.status === "interrupted") {
30899
31407
  await eventHandler.flushPendingPlanUpdates();
30900
- return this.cancelledPromptResponse(sessionState);
31408
+ return cancelledPromptResponse();
30901
31409
  }
30902
31410
  const error51 = eventHandler.getFailure();
30903
31411
  if (error51) {
@@ -30920,7 +31428,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30920
31428
  activePrompt.signal
30921
31429
  );
30922
31430
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
30923
- return this.cancelledPromptResponse(sessionState);
31431
+ return cancelledPromptResponse();
30924
31432
  }
30925
31433
  if (approved && !this.promptShouldStop(params.sessionId, activePrompt)) {
30926
31434
  await this.applyCollaborationModeChange(sessionState, DEFAULT_COLLABORATION_MODE);
@@ -30969,7 +31477,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30969
31477
  this.cancelBeforeTurnStarted(activePrompt)
30970
31478
  ]);
30971
31479
  if (turnCompleted === null) {
30972
- return this.cancelledPromptResponse(sessionState);
31480
+ return cancelledPromptResponse();
30973
31481
  }
30974
31482
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30975
31483
  await eventHandler.flushPendingErrors();
@@ -30977,7 +31485,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30977
31485
  promptNotificationsActive = false;
30978
31486
  if (turnCompleted.turn.status === "interrupted") {
30979
31487
  await eventHandler.flushPendingPlanUpdates();
30980
- return this.cancelledPromptResponse(sessionState);
31488
+ return cancelledPromptResponse();
30981
31489
  }
30982
31490
  const implementationError = eventHandler.getFailure();
30983
31491
  if (implementationError) {
@@ -30993,6 +31501,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
30993
31501
  }
30994
31502
  }
30995
31503
  }
31504
+ if (turnCompleted.turn.status === "completed") {
31505
+ agentFileChangeReportTurnId = turnCompleted.turn.id;
31506
+ }
30996
31507
  await clearRecoveredSessionFailure(eventHandler);
30997
31508
  await this.publishFallbackSessionTitle(
30998
31509
  sessionState,
@@ -31006,8 +31517,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
31006
31517
  } catch (err) {
31007
31518
  logger.error(`Prompt for session ${params.sessionId} failed`, err);
31008
31519
  if (activePrompt.signal.aborted || this.sessionIsClosing(params.sessionId)) {
31009
- return this.cancelledPromptResponse(sessionState);
31520
+ return cancelledPromptResponse();
31010
31521
  }
31522
+ agentFileChangeReportTurnId = null;
31523
+ agentFileChangeReportUnavailableReason = "providerError";
31011
31524
  const isProcessExit = err instanceof RequestError && err.code === CODEX_PROCESS_EXITED_ERROR_CODE;
31012
31525
  const isUnexpectedFailure = !(err instanceof RequestError);
31013
31526
  if (eventHandler !== null && clientSupportsTypedSessionFailures(this.clientCapabilities) && (isProcessExit || isUnexpectedFailure)) {
@@ -31028,6 +31541,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
31028
31541
  throw err;
31029
31542
  } finally {
31030
31543
  promptNotificationsActive = false;
31544
+ if (agentFileChangeReportRequest !== null) {
31545
+ await this.publishAgentFileChangeReport(
31546
+ sessionState,
31547
+ agentFileChangeReportTurnId,
31548
+ agentFileChangeReportRequest,
31549
+ agentFileChangeReportUnavailableReason,
31550
+ activePrompt.signal
31551
+ );
31552
+ }
31031
31553
  logger.log("Prompt completed", { sessionId: params.sessionId });
31032
31554
  await eventHandler?.dispose();
31033
31555
  disposePromptRequestCancellation();
@@ -31590,6 +32112,9 @@ var CodexAppServerClient = class {
31590
32112
  async threadResume(params) {
31591
32113
  return await this.sendRequest({ method: "thread/resume", params });
31592
32114
  }
32115
+ async threadFork(params) {
32116
+ return await this.sendRequest({ method: "thread/fork", params });
32117
+ }
31593
32118
  getThreadSettings(threadId) {
31594
32119
  return this.threadSettings.get(threadId);
31595
32120
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.0",
6
+ "version": "1.4.0",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {