@agentclientprotocol/codex-acp 1.2.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 +849 -161
  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
  }
@@ -22777,8 +22777,20 @@ function createTerminalOutputMeta(mode, terminalId, data) {
22777
22777
  }
22778
22778
  }
22779
22779
 
22780
+ // src/ContextCompactionMeta.ts
22781
+ var CONTEXT_COMPACTION_META_KEY = "contextCompaction";
22782
+ var CONTEXT_COMPACTION_META_VERSION = 1;
22783
+ function createContextCompactionMeta(metadata = {}) {
22784
+ return {
22785
+ [CONTEXT_COMPACTION_META_KEY]: {
22786
+ version: CONTEXT_COMPACTION_META_VERSION,
22787
+ ...metadata
22788
+ }
22789
+ };
22790
+ }
22791
+
22780
22792
  // src/CodexToolCallMapper.ts
22781
- var CONTEXT_COMPACTION_META = { contextCompaction: true };
22793
+ var CONTEXT_COMPACTION_META = createContextCompactionMeta();
22782
22794
  function toAcpStatus(status) {
22783
22795
  switch (status) {
22784
22796
  case "inProgress":
@@ -22925,8 +22937,8 @@ function createContextCompactionStartUpdate(item) {
22925
22937
  return {
22926
22938
  sessionUpdate: "tool_call",
22927
22939
  toolCallId: item.id,
22928
- kind: "other",
22929
- title: "Context compacting",
22940
+ kind: "think",
22941
+ title: "Compact conversation",
22930
22942
  status: "in_progress",
22931
22943
  _meta: CONTEXT_COMPACTION_META
22932
22944
  };
@@ -22935,7 +22947,7 @@ function createContextCompactionCompleteUpdate(item) {
22935
22947
  return {
22936
22948
  sessionUpdate: "tool_call_update",
22937
22949
  toolCallId: item.id,
22938
- title: "Context compacted",
22950
+ title: "Compact conversation",
22939
22951
  status: "completed",
22940
22952
  _meta: CONTEXT_COMPACTION_META
22941
22953
  };
@@ -22944,8 +22956,8 @@ function createCompletedContextCompactionUpdate(item) {
22944
22956
  return {
22945
22957
  sessionUpdate: "tool_call",
22946
22958
  toolCallId: item.id,
22947
- kind: "other",
22948
- title: "Context compacted",
22959
+ kind: "think",
22960
+ title: "Compact conversation",
22949
22961
  status: "completed",
22950
22962
  _meta: CONTEXT_COMPACTION_META
22951
22963
  };
@@ -23235,13 +23247,13 @@ function createTerminalCommandEvent(event, terminalId, cwd) {
23235
23247
  }
23236
23248
  };
23237
23249
  }
23238
- function createSearchTitle(query, path7) {
23239
- if (query && path7) {
23240
- return `Search for '${query}' in ${path7}`;
23250
+ function createSearchTitle(query, path8) {
23251
+ if (query && path8) {
23252
+ return `Search for '${query}' in ${path8}`;
23241
23253
  } else if (query) {
23242
23254
  return `Search for '${query}'`;
23243
- } else if (path7) {
23244
- return `Search in '${path7}'`;
23255
+ } else if (path8) {
23256
+ return `Search in '${path8}'`;
23245
23257
  }
23246
23258
  return "Search";
23247
23259
  }
@@ -23467,12 +23479,12 @@ function revertPatch(unifiedDiff) {
23467
23479
  if (!patch) return null;
23468
23480
  return reversePatch(patch);
23469
23481
  }
23470
- function createUpdateDiffContent(path7, oldText, newText) {
23482
+ function createUpdateDiffContent(path8, oldText, newText) {
23471
23483
  return {
23472
23484
  type: "diff",
23473
23485
  oldText,
23474
23486
  newText,
23475
- path: path7,
23487
+ path: path8,
23476
23488
  _meta: {
23477
23489
  kind: "update"
23478
23490
  }
@@ -23605,22 +23617,65 @@ var AIR_META_KEY = "air";
23605
23617
  var AIR_EXTENSION_VERSION_KEY = "version";
23606
23618
  var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
23607
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";
23608
23622
  var AIR_EXTENSION_VERSION = 1;
23609
23623
 
23610
23624
  // src/CodexEventHandler.ts
23611
- var SESSION_FAILURE_PRESENTATION = {
23612
- transport_lost: { message: "Connection to Codex was lost.", retryable: true, actions: ["reconnect", "retry"] },
23613
- auth_required: { message: "Sign in to continue using Codex.", retryable: false, actions: ["login"] },
23614
- rate_limited: { message: "The Codex rate limit was reached.", retryable: true, actions: ["retry"] },
23615
- quota_exhausted: { message: "The Codex usage quota is exhausted.", retryable: false, actions: ["new_session"] },
23616
- overloaded: { message: "Codex is temporarily overloaded.", retryable: true, actions: ["retry"] },
23617
- context_exhausted: { message: "This conversation has reached its context limit.", retryable: false, actions: ["new_turn"] },
23618
- budget_exhausted: { message: "This session has reached its usage budget.", retryable: false, actions: ["new_session"] },
23619
- policy_denied: { message: "The request was blocked by provider policy.", retryable: false, actions: [] },
23620
- bad_request: { message: "Codex could not process this request.", retryable: false, actions: ["new_turn"] },
23621
- provider_error: { message: "The model provider reported an error.", retryable: true, actions: ["retry"] },
23622
- internal_error: { message: "Codex encountered an internal error.", retryable: true, actions: ["retry"] }
23625
+ var MAX_SESSION_FAILURE_TITLE_LENGTH = 240;
23626
+ var SESSION_FAILURE_POLICY = {
23627
+ transport_lost: {
23628
+ category: "connection",
23629
+ actions: ["retry", "new_session"]
23630
+ },
23631
+ auth_required: {
23632
+ category: "access",
23633
+ actions: ["login"]
23634
+ },
23635
+ rate_limited: {
23636
+ category: "limit",
23637
+ actions: ["retry"]
23638
+ },
23639
+ quota_exhausted: {
23640
+ category: "limit",
23641
+ actions: []
23642
+ },
23643
+ overloaded: {
23644
+ category: "service",
23645
+ actions: ["retry"]
23646
+ },
23647
+ context_exhausted: {
23648
+ category: "limit",
23649
+ actions: ["new_session"]
23650
+ },
23651
+ budget_exhausted: {
23652
+ category: "limit",
23653
+ actions: ["new_session"]
23654
+ },
23655
+ policy_denied: {
23656
+ category: "request",
23657
+ actions: []
23658
+ },
23659
+ bad_request: {
23660
+ category: "request",
23661
+ actions: []
23662
+ },
23663
+ provider_error: {
23664
+ category: "service",
23665
+ actions: ["retry"]
23666
+ },
23667
+ internal_error: {
23668
+ category: "service",
23669
+ actions: ["retry", "new_session"]
23670
+ }
23671
+ };
23672
+ var SYNTHETIC_FAILURE_TITLE = {
23673
+ transport_lost: "Connection to Codex was lost.",
23674
+ internal_error: "Codex encountered an internal error."
23623
23675
  };
23676
+ function nextSessionFailureRevision(previous, id) {
23677
+ return previous?.id === id ? previous.revision + 1 : 1;
23678
+ }
23624
23679
  var STRING_CODEX_ERROR_CATEGORIES = {
23625
23680
  contextWindowExceeded: "context_exhausted",
23626
23681
  sessionBudgetExceeded: "budget_exhausted",
@@ -23648,6 +23703,12 @@ var CodexEventHandler = class _CodexEventHandler {
23648
23703
  supportsTypedSessionFailures;
23649
23704
  sessionFailureEpoch;
23650
23705
  pendingErrors = [];
23706
+ failuresById = /* @__PURE__ */ new Map();
23707
+ activeFailureIdByScope = /* @__PURE__ */ new Map();
23708
+ failureTurnIdById = /* @__PURE__ */ new Map();
23709
+ allocatedFailureScopes = /* @__PURE__ */ new Set();
23710
+ lastSessionNotice;
23711
+ nextNoticeId = 1;
23651
23712
  failure = null;
23652
23713
  completedPlan = null;
23653
23714
  activeFuzzyFileSearchSessions = /* @__PURE__ */ new Set();
@@ -23672,19 +23733,22 @@ var CodexEventHandler = class _CodexEventHandler {
23672
23733
  this.supportsTypedSessionFailures = supportsTypedSessionFailures;
23673
23734
  this.sessionFailureEpoch = sessionFailureEpoch;
23674
23735
  this.session = new ACPSessionConnection(connection, sessionState.sessionId);
23736
+ if (sessionState.sessionFailure !== void 0) {
23737
+ this.failuresById.set(sessionState.sessionFailure.id, sessionState.sessionFailure);
23738
+ }
23675
23739
  }
23676
23740
  getFailure() {
23677
23741
  return this.failure;
23678
23742
  }
23679
23743
  getTerminalSessionFailureMeta(turnId, allowUnattributed = false) {
23680
23744
  const failure = this.sessionState.sessionFailure;
23681
- if (!this.supportsTypedSessionFailures || failure?.phase !== "active" || (failure.turnId === void 0 ? !allowUnattributed : turnId === null || failure.turnId !== turnId)) {
23745
+ if (!this.supportsTypedSessionFailures || failure === void 0 || (this.failureTurnIdById.get(failure.id) === void 0 ? !allowUnattributed : turnId === null || this.failureTurnIdById.get(failure.id) !== turnId)) {
23682
23746
  return null;
23683
23747
  }
23684
23748
  return this.createSessionFailureMeta(failure);
23685
23749
  }
23686
- recordSyntheticTerminalFailure(category, turnId) {
23687
- this.recordSessionFailure(category, turnId ?? void 0);
23750
+ recordSyntheticTerminalFailure(kind, turnId) {
23751
+ this.recordSessionFailure(kind, turnId ?? void 0, "error", SYNTHETIC_FAILURE_TITLE[kind]);
23688
23752
  }
23689
23753
  /**
23690
23754
  * Handles notifications after the prompt-local handler has been disposed. The app-server subscription
@@ -23701,12 +23765,16 @@ var CodexEventHandler = class _CodexEventHandler {
23701
23765
  return;
23702
23766
  }
23703
23767
  if (notification.params.willRetry) {
23704
- await this.session.update(this.createSafeErrorDiagnostic(notification.params));
23768
+ await this.session.update(this.createSessionFailureUpdate(this.recordRetryWarning(notification.params, false)));
23705
23769
  return;
23706
23770
  }
23707
23771
  const failure = this.recordSessionFailure(
23708
- this.sessionFailureCategory(notification.params.error.codexErrorInfo),
23709
- void 0
23772
+ this.sessionFailureKind(notification.params.error.codexErrorInfo),
23773
+ notification.params.turnId,
23774
+ "error",
23775
+ notification.params.error.message,
23776
+ void 0,
23777
+ false
23710
23778
  );
23711
23779
  await this.session.update(this.createSessionFailureUpdate(failure));
23712
23780
  }
@@ -23732,21 +23800,19 @@ var CodexEventHandler = class _CodexEventHandler {
23732
23800
  }
23733
23801
  }
23734
23802
  async clearSessionFailure() {
23803
+ delete this.sessionState.sessionFailure;
23804
+ }
23805
+ async completeSuccessfulTurn(turnId) {
23806
+ this.lastSessionNotice = void 0;
23807
+ if (!this.supportsTypedSessionFailures || turnId === null) return;
23735
23808
  const active = this.sessionState.sessionFailure;
23736
- if (!this.supportsTypedSessionFailures || active?.phase !== "active") {
23737
- return;
23738
- }
23739
- const cleared = {
23740
- ...active,
23741
- revision: active.revision + 1,
23742
- phase: "cleared"
23743
- };
23744
- await this.session.update(this.createSessionFailureUpdate(cleared));
23745
- this.sessionState.sessionFailure = cleared;
23809
+ if (active?.id !== this.activeFailureIdByScope.get(turnId) || active?.severity !== "warning") return;
23810
+ this.activeFailureIdByScope.delete(turnId);
23811
+ delete this.sessionState.sessionFailure;
23746
23812
  }
23747
23813
  async handleFailedTurn(turn) {
23748
23814
  const activeFailure = this.sessionState.sessionFailure;
23749
- if (!this.supportsTypedSessionFailures || turn.status !== "failed" || this.failure !== null || activeFailure?.phase === "active" && activeFailure.turnId === turn.id) {
23815
+ if (!this.supportsTypedSessionFailures || turn.status !== "failed" || this.failure !== null || this.failureTurnIdById.get(activeFailure?.id ?? "") === turn.id && activeFailure?.severity === "error") {
23750
23816
  return;
23751
23817
  }
23752
23818
  const error51 = turn.error ?? {
@@ -23805,14 +23871,19 @@ var CodexEventHandler = class _CodexEventHandler {
23805
23871
  async createUpdateEvent(notification) {
23806
23872
  switch (notification.method) {
23807
23873
  case "item/agentMessage/delta":
23874
+ this.completeRetryIncidentOnTurnProgress();
23808
23875
  return await this.createTextEvent(notification.params);
23809
23876
  case "item/plan/delta":
23877
+ this.completeRetryIncidentOnTurnProgress();
23810
23878
  return this.createPlanDeltaEvent(notification.params);
23811
23879
  case "item/started":
23880
+ this.completeRetryIncidentOnTurnProgress();
23812
23881
  return await this.createItemEvent(notification.params);
23813
23882
  case "item/completed":
23883
+ this.completeRetryIncidentOnTurnProgress();
23814
23884
  return await this.completeItemEvent(notification.params);
23815
23885
  case "turn/plan/updated":
23886
+ this.completeRetryIncidentOnTurnProgress();
23816
23887
  return await this.updatePlan(notification.params);
23817
23888
  case "error":
23818
23889
  return await this.createErrorEvent(notification.params);
@@ -23851,8 +23922,10 @@ var CodexEventHandler = class _CodexEventHandler {
23851
23922
  closed: true
23852
23923
  });
23853
23924
  case "item/commandExecution/outputDelta":
23925
+ this.completeRetryIncidentOnTurnProgress();
23854
23926
  return this.createCommandOutputDeltaEvent(notification.params);
23855
23927
  case "item/mcpToolCall/progress":
23928
+ this.completeRetryIncidentOnTurnProgress();
23856
23929
  return this.createMcpToolProgressEvent(notification.params);
23857
23930
  case "account/rateLimits/updated":
23858
23931
  this.handleRateLimitsUpdated(notification.params);
@@ -23863,6 +23936,8 @@ var CodexEventHandler = class _CodexEventHandler {
23863
23936
  return this.createWarningEvent(notification.params);
23864
23937
  case "guardianWarning":
23865
23938
  return null;
23939
+ case "deprecationNotice":
23940
+ return this.createDeprecationNoticeEvent(notification.params);
23866
23941
  case "item/autoApprovalReview/started":
23867
23942
  return this.handleGuardianApprovalReviewStarted(notification.params);
23868
23943
  case "item/autoApprovalReview/completed":
@@ -23870,10 +23945,13 @@ var CodexEventHandler = class _CodexEventHandler {
23870
23945
  case "thread/compacted":
23871
23946
  return this.createContextCompactedEvent();
23872
23947
  case "item/reasoning/summaryTextDelta":
23948
+ this.completeRetryIncidentOnTurnProgress();
23873
23949
  return this.createReasoningDeltaEvent(notification.params);
23874
23950
  case "item/reasoning/textDelta":
23951
+ this.completeRetryIncidentOnTurnProgress();
23875
23952
  return this.createReasoningDeltaEvent(notification.params);
23876
23953
  case "item/reasoning/summaryPartAdded":
23954
+ this.completeRetryIncidentOnTurnProgress();
23877
23955
  return this.createReasoningSectionBreakEvent(notification.params);
23878
23956
  case "model/rerouted":
23879
23957
  return this.createModelReroutedEvent(notification.params);
@@ -23916,7 +23994,6 @@ var CodexEventHandler = class _CodexEventHandler {
23916
23994
  case "windowsSandbox/setupCompleted":
23917
23995
  case "account/login/completed":
23918
23996
  case "skills/changed":
23919
- case "deprecationNotice":
23920
23997
  case "mcpServer/oauthLogin/completed":
23921
23998
  case "externalAgentConfig/import/completed":
23922
23999
  case "rawResponseItem/completed":
@@ -23944,14 +24021,31 @@ var CodexEventHandler = class _CodexEventHandler {
23944
24021
  return createAgentTextMessageChunk(event.delta, event.itemId, createCodexMessagePhaseMeta(phase));
23945
24022
  }
23946
24023
  async createConfigWarningEvent(event) {
23947
- const detailsText = event.details ? `
24024
+ if (this.supportsTypedSessionFailures) {
24025
+ return this.createSessionFailureUpdate(this.recordSessionNotice(...this.sessionNoticeContent(event.summary, event.details)));
24026
+ }
24027
+ const text = event.details ? `${event.summary}
23948
24028
 
23949
- ${event.details}` : "";
23950
- return createAgentTextMessageChunk(`Config warning: ${event.summary}${detailsText}
24029
+ ${event.details}` : event.summary;
24030
+ return createAgentTextMessageChunk(`Config warning: ${text}
23951
24031
 
23952
24032
  `);
23953
24033
  }
24034
+ /**
24035
+ * Unlike `warning` and `configWarning`, this notification was dropped outright, so there is no
24036
+ * legacy rendering to preserve. It is surfaced only to clients that negotiated typed records;
24037
+ * every other client keeps seeing exactly what it sees today, which is nothing.
24038
+ */
24039
+ createDeprecationNoticeEvent(event) {
24040
+ if (!this.supportsTypedSessionFailures) return null;
24041
+ return this.createSessionFailureUpdate(
24042
+ this.recordSessionNotice(...this.sessionNoticeContent(event.summary, event.details))
24043
+ );
24044
+ }
23954
24045
  createWarningEvent(event) {
24046
+ if (this.supportsTypedSessionFailures) {
24047
+ return this.createSessionFailureUpdate(this.recordSessionNotice(event.message));
24048
+ }
23955
24049
  return createAgentTextMessageChunk(`Warning: ${event.message}
23956
24050
 
23957
24051
  `);
@@ -24329,7 +24423,13 @@ ${event.stdin}
24329
24423
  }
24330
24424
  if (params.turnId !== this.sessionState.currentTurnId) {
24331
24425
  if (this.supportsTypedSessionFailures) {
24332
- return this.createSafeErrorDiagnostic(params);
24426
+ const failure = params.willRetry ? this.recordRetryWarning(params) : this.recordSessionFailure(
24427
+ this.sessionFailureKind(params.error.codexErrorInfo),
24428
+ params.turnId,
24429
+ "error",
24430
+ params.error.message
24431
+ );
24432
+ return this.createSessionFailureUpdate(failure);
24333
24433
  }
24334
24434
  return this.createCodexSessionInfoUpdate({
24335
24435
  error: { ...params.error, turnId: params.turnId, willRetry: params.willRetry }
@@ -24337,7 +24437,7 @@ ${event.stdin}
24337
24437
  }
24338
24438
  if (params.willRetry) {
24339
24439
  if (this.supportsTypedSessionFailures) {
24340
- return this.createSafeErrorDiagnostic(params);
24440
+ return this.createSessionFailureUpdate(this.recordRetryWarning(params));
24341
24441
  }
24342
24442
  return this.createCodexSessionInfoUpdate({
24343
24443
  error: {
@@ -24362,39 +24462,86 @@ ${event.stdin}
24362
24462
 
24363
24463
  `);
24364
24464
  }
24365
- createSafeErrorDiagnostic(params) {
24366
- const category = this.sessionFailureCategory(params.error.codexErrorInfo);
24367
- return this.createCodexSessionInfoUpdate({
24368
- error: {
24369
- category,
24370
- message: SESSION_FAILURE_PRESENTATION[category].message,
24371
- turnId: params.turnId,
24372
- willRetry: params.willRetry
24373
- }
24374
- });
24375
- }
24376
24465
  recordTypedSessionFailure(params) {
24377
- const category = this.sessionFailureCategory(params.error.codexErrorInfo);
24378
- this.recordSessionFailure(category, params.turnId);
24379
- }
24380
- recordSessionFailure(category, turnId) {
24381
- const presentation = SESSION_FAILURE_PRESENTATION[category];
24382
- const previous = this.sessionState.sessionFailure;
24383
- const id = previous?.phase === "active" ? previous.id : turnId === void 0 ? `${this.sessionState.sessionId}:error:${this.sessionFailureEpoch}` : `${turnId}:error`;
24466
+ const kind = this.sessionFailureKind(params.error.codexErrorInfo);
24467
+ this.recordSessionFailure(kind, params.turnId, "error", params.error.message);
24468
+ }
24469
+ recordSessionFailure(kind, turnId, severity, title, actionsOverride, attributeToTurn = true) {
24470
+ const policy = SESSION_FAILURE_POLICY[kind];
24471
+ const scope = turnId ?? this.sessionState.sessionId;
24472
+ const id = this.activeFailureIdByScope.get(scope) ?? this.allocateFailureId(scope, turnId);
24473
+ const previous = this.failuresById.get(id);
24384
24474
  const failure = {
24385
24475
  id,
24386
- revision: previous?.id === id ? previous.revision + 1 : 1,
24387
- phase: "active",
24388
- category,
24389
- source: "codex",
24390
- safeMessage: presentation.message,
24391
- retryable: presentation.retryable,
24392
- actions: presentation.actions,
24393
- ...turnId === void 0 ? {} : { turnId }
24476
+ revision: nextSessionFailureRevision(previous, id),
24477
+ category: policy.category,
24478
+ severity,
24479
+ title,
24480
+ actions: actionsOverride ?? policy.actions
24394
24481
  };
24482
+ this.failuresById.set(id, failure);
24483
+ this.failureTurnIdById.set(id, attributeToTurn ? turnId : void 0);
24484
+ this.activeFailureIdByScope.set(scope, id);
24395
24485
  this.sessionState.sessionFailure = failure;
24486
+ this.lastSessionNotice = void 0;
24396
24487
  return failure;
24397
24488
  }
24489
+ allocateFailureId(scope, turnId) {
24490
+ if (turnId !== void 0 && !this.allocatedFailureScopes.has(scope)) {
24491
+ this.allocatedFailureScopes.add(scope);
24492
+ return `${turnId}:error`;
24493
+ }
24494
+ this.allocatedFailureScopes.add(scope);
24495
+ return `${scope}:error:${this.sessionFailureEpoch}:${this.nextNoticeId++}`;
24496
+ }
24497
+ /**
24498
+ * A retry warning remains the active incident until Codex produces turn content again. That content is
24499
+ * the only positive signal available from app-server that the turn recovered; a later error then starts
24500
+ * a new incident instead of overwriting the historical reconnect entry. Terminal errors remain active so
24501
+ * duplicate late notifications cannot append duplicate transcript rows.
24502
+ */
24503
+ completeRetryIncidentOnTurnProgress() {
24504
+ const turnId = this.sessionState.currentTurnId;
24505
+ if (turnId === null) return;
24506
+ const activeId = this.activeFailureIdByScope.get(turnId);
24507
+ if (activeId === void 0 || this.failuresById.get(activeId)?.severity !== "warning") return;
24508
+ this.activeFailureIdByScope.delete(turnId);
24509
+ if (this.sessionState.sessionFailure?.id === activeId) {
24510
+ delete this.sessionState.sessionFailure;
24511
+ }
24512
+ }
24513
+ recordRetryWarning(params, attributeToTurn = true) {
24514
+ const kind = this.sessionFailureKind(params.error.codexErrorInfo);
24515
+ return this.recordSessionFailure(
24516
+ kind,
24517
+ params.turnId,
24518
+ "warning",
24519
+ params.error.message,
24520
+ [],
24521
+ attributeToTurn
24522
+ );
24523
+ }
24524
+ recordSessionNotice(title, details) {
24525
+ const key = `${title}\0${details ?? ""}`;
24526
+ const previous = this.lastSessionNotice?.key === key ? this.lastSessionNotice.failure : void 0;
24527
+ const id = previous?.id ?? `${this.sessionState.sessionId}:notice:${this.sessionFailureEpoch}:${this.nextNoticeId++}`;
24528
+ const notice = {
24529
+ id,
24530
+ revision: nextSessionFailureRevision(previous, id),
24531
+ category: "unknown",
24532
+ severity: "warning",
24533
+ title,
24534
+ ...details === void 0 ? {} : { details },
24535
+ actions: []
24536
+ };
24537
+ this.lastSessionNotice = { key, failure: notice };
24538
+ return notice;
24539
+ }
24540
+ sessionNoticeContent(summary, details) {
24541
+ if (details === null) return [summary];
24542
+ const combinedTitle = `${summary} \u2014 ${details}`;
24543
+ return combinedTitle.length <= MAX_SESSION_FAILURE_TITLE_LENGTH ? [combinedTitle] : [summary, details];
24544
+ }
24398
24545
  createSessionFailureMeta(failure) {
24399
24546
  return {
24400
24547
  [JETBRAINS_META_KEY]: {
@@ -24411,7 +24558,7 @@ ${event.stdin}
24411
24558
  _meta: this.createSessionFailureMeta(failure)
24412
24559
  };
24413
24560
  }
24414
- sessionFailureCategory(error51) {
24561
+ sessionFailureKind(error51) {
24415
24562
  if (this.isAuthenticationRequiredError(error51)) return "auth_required";
24416
24563
  if (this.getHttpStatusCode(error51) === 429) return "rate_limited";
24417
24564
  if (typeof error51 === "string") {
@@ -24862,11 +25009,11 @@ var CodexApprovalHandler = class {
24862
25009
  });
24863
25010
  }
24864
25011
  const fileSystem = permissions.fileSystem;
24865
- for (const path7 of fileSystem?.read ?? []) {
24866
- changes.push(this.fileSystemGrantChange(path7, "read", lifetime, suffix));
25012
+ for (const path8 of fileSystem?.read ?? []) {
25013
+ changes.push(this.fileSystemGrantChange(path8, "read", lifetime, suffix));
24867
25014
  }
24868
- for (const path7 of fileSystem?.write ?? []) {
24869
- changes.push(this.fileSystemGrantChange(path7, "write", lifetime, suffix));
25015
+ for (const path8 of fileSystem?.write ?? []) {
25016
+ changes.push(this.fileSystemGrantChange(path8, "write", lifetime, suffix));
24870
25017
  }
24871
25018
  for (const entry of fileSystem?.entries ?? []) {
24872
25019
  const matcher = (() => {
@@ -24895,16 +25042,16 @@ var CodexApprovalHandler = class {
24895
25042
  }
24896
25043
  return changes.length > 0 ? { version: 1, changes } : void 0;
24897
25044
  }
24898
- fileSystemGrantChange(path7, access, lifetime, suffix) {
25045
+ fileSystemGrantChange(path8, access, lifetime, suffix) {
24899
25046
  return {
24900
25047
  type: "grant",
24901
25048
  operation: "grant",
24902
- description: `Allow ${access} access to ${path7}${suffix}`,
25049
+ description: `Allow ${access} access to ${path8}${suffix}`,
24903
25050
  lifetime,
24904
25051
  targets: [{
24905
25052
  type: "filesystem",
24906
25053
  access: [access],
24907
- matcher: { type: "exact_path", path: path7 }
25054
+ matcher: { type: "exact_path", path: path8 }
24908
25055
  }]
24909
25056
  };
24910
25057
  }
@@ -25814,15 +25961,15 @@ var wslDefaultBrowser = async () => {
25814
25961
  const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
25815
25962
  return stdout.trim();
25816
25963
  };
25817
- var convertWslPathToWindows = async (path7) => {
25818
- if (/^[a-z]+:\/\//i.test(path7)) {
25819
- return path7;
25964
+ var convertWslPathToWindows = async (path8) => {
25965
+ if (/^[a-z]+:\/\//i.test(path8)) {
25966
+ return path8;
25820
25967
  }
25821
25968
  try {
25822
- const { stdout } = await execFile2("wslpath", ["-aw", path7], { encoding: "utf8" });
25969
+ const { stdout } = await execFile2("wslpath", ["-aw", path8], { encoding: "utf8" });
25823
25970
  return stdout.trim();
25824
25971
  } catch {
25825
- return path7;
25972
+ return path8;
25826
25973
  }
25827
25974
  };
25828
25975
 
@@ -26372,7 +26519,7 @@ var AgentMode = class _AgentMode {
26372
26519
  };
26373
26520
 
26374
26521
  // src/CodexAcpClient.ts
26375
- import path5 from "node:path";
26522
+ import path6 from "node:path";
26376
26523
 
26377
26524
  // src/McpServerName.ts
26378
26525
  var MCP_SERVER_NAME_WHITESPACE = /\p{White_Space}/gu;
@@ -26386,7 +26533,7 @@ var package_default = {
26386
26533
  publishConfig: {
26387
26534
  access: "public"
26388
26535
  },
26389
- version: "1.2.0",
26536
+ version: "1.4.0",
26390
26537
  description: "",
26391
26538
  main: "dist/index.js",
26392
26539
  bin: {
@@ -26539,8 +26686,299 @@ function isRootPath(value) {
26539
26686
  return value === "/" || /^[A-Za-z]:\/$/.test(value) || /^\/\/[^/]+\/[^/]+\/$/.test(value);
26540
26687
  }
26541
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
+
26542
26978
  // src/CodexAcpClient.ts
26543
26979
  var CUSTOM_GATEWAY_PROVIDER_ID = "custom-gateway";
26980
+ var OPENAI_PROVIDER_ID = "openai";
26981
+ var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
26544
26982
  var SUPPORTED_GATEWAY_PROTOCOLS = {
26545
26983
  openai: "responses"
26546
26984
  };
@@ -26754,34 +27192,47 @@ var CodexAcpClient = class {
26754
27192
  };
26755
27193
  }
26756
27194
  /**
26757
- * `providers/list`: returns the single client-configurable custom gateway
26758
- * provider. `current` carries only non-secret routing (never headers), and is
26759
- * `null` when the provider is not configured/disabled.
27195
+ * `providers/list`: returns Codex's OpenAI slot. With no ACP override, the
27196
+ * slot reports native OpenAI routing; headers are never exposed.
26760
27197
  */
26761
27198
  listProviders() {
26762
27199
  const gatewayConfig = this.gatewayConfig;
26763
27200
  const current = gatewayConfig ? {
26764
27201
  apiType: gatewayApiTypeFromConfig(gatewayConfig),
26765
27202
  baseUrl: gatewayConfig.config.base_url
26766
- } : null;
27203
+ } : this.getNativeProviderConfig();
26767
27204
  return [
26768
27205
  {
26769
- providerId: CUSTOM_GATEWAY_PROVIDER_ID,
27206
+ providerId: OPENAI_PROVIDER_ID,
26770
27207
  supported: Object.keys(SUPPORTED_GATEWAY_PROTOCOLS),
26771
27208
  required: false,
26772
27209
  current
26773
27210
  }
26774
27211
  ];
26775
27212
  }
27213
+ getNativeProviderConfig() {
27214
+ const configuredProviderId = this.modelProvider ?? (typeof this.config["model_provider"] === "string" ? this.config["model_provider"] : null);
27215
+ const configuredProviders = this.config["model_providers"];
27216
+ if (configuredProviderId && configuredProviders && typeof configuredProviders === "object" && !Array.isArray(configuredProviders)) {
27217
+ const configuredProvider = configuredProviders[configuredProviderId];
27218
+ if (configuredProvider && typeof configuredProvider === "object" && !Array.isArray(configuredProvider)) {
27219
+ const baseUrl = configuredProvider["base_url"];
27220
+ if (typeof baseUrl === "string" && baseUrl.length > 0) {
27221
+ return { apiType: "openai", baseUrl };
27222
+ }
27223
+ }
27224
+ }
27225
+ return { apiType: "openai", baseUrl: DEFAULT_OPENAI_BASE_URL };
27226
+ }
26776
27227
  /**
26777
27228
  * `providers/set`: replaces the full configuration for the custom gateway
26778
27229
  * provider. Rejects unknown provider ids with `invalid_params`.
26779
27230
  */
26780
27231
  setProvider(request) {
26781
- if (request.providerId !== CUSTOM_GATEWAY_PROVIDER_ID) {
27232
+ if (request.providerId !== OPENAI_PROVIDER_ID) {
26782
27233
  throw RequestError.invalidParams(
26783
27234
  { providerId: request.providerId },
26784
- `Unknown providerId "${request.providerId}"; only "${CUSTOM_GATEWAY_PROVIDER_ID}" is configurable`
27235
+ `Unknown providerId "${request.providerId}"; only "${OPENAI_PROVIDER_ID}" is configurable`
26785
27236
  );
26786
27237
  }
26787
27238
  this.applyGatewayConfig({
@@ -26795,7 +27246,7 @@ var CodexAcpClient = class {
26795
27246
  * unknown provider id is idempotent success (RFD behavior §7).
26796
27247
  */
26797
27248
  disableProvider(request) {
26798
- if (request.providerId === CUSTOM_GATEWAY_PROVIDER_ID) {
27249
+ if (request.providerId === OPENAI_PROVIDER_ID) {
26799
27250
  this.gatewayConfig = null;
26800
27251
  }
26801
27252
  }
@@ -26993,7 +27444,7 @@ var CodexAcpClient = class {
26993
27444
  if (!cwd) {
26994
27445
  return;
26995
27446
  }
26996
- const skillExtraRoots = additionalRoots.map((root) => path5.join(root, ".agents", "skills"));
27447
+ const skillExtraRoots = additionalRoots.map((root) => path6.join(root, ".agents", "skills"));
26997
27448
  if (!arraysEqual(this.skillExtraRoots, skillExtraRoots)) {
26998
27449
  await this.codexClient.skillsExtraRootsSet({ extraRoots: skillExtraRoots });
26999
27450
  this.skillExtraRoots = skillExtraRoots;
@@ -27115,6 +27566,113 @@ var CodexAcpClient = class {
27115
27566
  serviceTier
27116
27567
  }, onTurnStarted);
27117
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
+ }
27118
27676
  async setCollaborationMode(sessionId, mode, currentModelId) {
27119
27677
  await this.codexClient.threadSettingsUpdate({
27120
27678
  threadId: sessionId,
@@ -27265,6 +27823,58 @@ var CodexAcpClient = class {
27265
27823
  };
27266
27824
  }
27267
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
+ };
27268
27878
  function buildPromptItems(prompt) {
27269
27879
  return prompt.map((block) => {
27270
27880
  switch (block.type) {
@@ -27324,8 +27934,8 @@ function formatUriAsLink(name, uri) {
27324
27934
  return `[@${name}](${uri})`;
27325
27935
  }
27326
27936
  if (uri.startsWith("file://")) {
27327
- const path7 = uri.replace("file://", "");
27328
- const fileName = path7.split("/").pop() ?? path7;
27937
+ const path8 = uri.replace("file://", "");
27938
+ const fileName = path8.split("/").pop() ?? path8;
27329
27939
  return `[@${fileName}](${uri})`;
27330
27940
  }
27331
27941
  return uri;
@@ -27355,7 +27965,7 @@ function readAdditionalDirectories(cwd, additionalDirectories, meta3) {
27355
27965
  if (directory.length === 0) {
27356
27966
  throw RequestError.invalidParams(void 0, "additionalDirectories entries must not be empty");
27357
27967
  }
27358
- if (!path5.isAbsolute(directory)) {
27968
+ if (!path6.isAbsolute(directory)) {
27359
27969
  throw RequestError.invalidParams(void 0, "additionalDirectories entries must be absolute paths");
27360
27970
  }
27361
27971
  if (!seen.has(directory)) {
@@ -27958,7 +28568,7 @@ var SteeringQueue = class {
27958
28568
 
27959
28569
  // src/ResponseItemHistoryFallback.ts
27960
28570
  import { readFile as readFile2 } from "node:fs/promises";
27961
- import path6 from "node:path";
28571
+ import path7 from "node:path";
27962
28572
  function historyFallbackUpdateKey(update) {
27963
28573
  switch (update.sessionUpdate) {
27964
28574
  case "user_message_chunk":
@@ -28112,14 +28722,14 @@ function parseJsonRecord(line) {
28112
28722
  }
28113
28723
  try {
28114
28724
  const value = JSON.parse(line);
28115
- return asRecord(value);
28725
+ return asRecord2(value);
28116
28726
  } catch {
28117
28727
  return null;
28118
28728
  }
28119
28729
  }
28120
28730
  function extractResponseItem(record2) {
28121
28731
  if (record2["type"] === "response_item") {
28122
- return asRecord(record2["payload"]);
28732
+ return asRecord2(record2["payload"]);
28123
28733
  }
28124
28734
  const itemType = record2["type"];
28125
28735
  if (typeof itemType === "string" && isLegacyResponseItemType(itemType)) {
@@ -28153,7 +28763,7 @@ function createEventMsgUpdates(record2) {
28153
28763
  if (record2["type"] !== "event_msg") {
28154
28764
  return null;
28155
28765
  }
28156
- const payload = asRecord(record2["payload"]);
28766
+ const payload = asRecord2(record2["payload"]);
28157
28767
  if (!payload) {
28158
28768
  return [];
28159
28769
  }
@@ -28197,9 +28807,9 @@ function imageBlocks(images) {
28197
28807
  if (typeof image === "string") {
28198
28808
  return [{ type: "text", text: `[@image](${image})` }];
28199
28809
  }
28200
- const record2 = asRecord(image);
28201
- const path7 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
28202
- 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})` }] : [];
28203
28813
  });
28204
28814
  }
28205
28815
  function contentBlocksFromResponseContent(content) {
@@ -28207,7 +28817,7 @@ function contentBlocksFromResponseContent(content) {
28207
28817
  return [];
28208
28818
  }
28209
28819
  return content.flatMap((entry) => {
28210
- const record2 = asRecord(entry);
28820
+ const record2 = asRecord2(entry);
28211
28821
  if (!record2) {
28212
28822
  return [];
28213
28823
  }
@@ -28240,7 +28850,7 @@ function textParts(value) {
28240
28850
  if (typeof entry === "string") {
28241
28851
  return entry.length > 0 ? [entry] : [];
28242
28852
  }
28243
- const record2 = asRecord(entry);
28853
+ const record2 = asRecord2(entry);
28244
28854
  if (!record2) {
28245
28855
  return [];
28246
28856
  }
@@ -28332,7 +28942,7 @@ function parseFunctionArguments(value) {
28332
28942
  }
28333
28943
  function rawInputForFunctionCall(name, args) {
28334
28944
  if (name === "exec_command") {
28335
- const record2 = asRecord(args);
28945
+ const record2 = asRecord2(args);
28336
28946
  if (record2) {
28337
28947
  return {
28338
28948
  command: stringValue(record2["cmd"]) ?? stringValue(record2["command"]),
@@ -28379,14 +28989,14 @@ function functionCallUsesTerminal(item) {
28379
28989
  return item["name"] === "exec_command";
28380
28990
  }
28381
28991
  function commandFromFunctionArguments(args) {
28382
- const record2 = asRecord(args);
28992
+ const record2 = asRecord2(args);
28383
28993
  if (!record2) {
28384
28994
  return null;
28385
28995
  }
28386
28996
  return stringValue(record2["cmd"]) ?? stringValue(record2["command"]);
28387
28997
  }
28388
28998
  function cwdFromFunctionArguments(args) {
28389
- const record2 = asRecord(args);
28999
+ const record2 = asRecord2(args);
28390
29000
  if (!record2) {
28391
29001
  return "";
28392
29002
  }
@@ -28831,10 +29441,10 @@ function baseCommandName(commandName) {
28831
29441
  return commandName.split(/[\\/]/).pop() ?? commandName;
28832
29442
  }
28833
29443
  function absolutizePath(cwd, targetPath) {
28834
- if (path6.isAbsolute(targetPath) || cwd.length === 0) {
29444
+ if (path7.isAbsolute(targetPath) || cwd.length === 0) {
28835
29445
  return targetPath;
28836
29446
  }
28837
- return path6.join(cwd, targetPath);
29447
+ return path7.join(cwd, targetPath);
28838
29448
  }
28839
29449
  function withTerminalContent(event, terminalId, cwd) {
28840
29450
  const { rawInput, ...eventWithoutRawInput } = event;
@@ -28858,7 +29468,7 @@ function outputText(output) {
28858
29468
  return "";
28859
29469
  }
28860
29470
  return output.flatMap((entry) => {
28861
- const record2 = asRecord(entry);
29471
+ const record2 = asRecord2(entry);
28862
29472
  if (!record2) {
28863
29473
  return [];
28864
29474
  }
@@ -28874,7 +29484,7 @@ function outputText(output) {
28874
29484
  }).join("\n");
28875
29485
  }
28876
29486
  function parseExitCode(rawOutput, output) {
28877
- const record2 = asRecord(rawOutput);
29487
+ const record2 = asRecord2(rawOutput);
28878
29488
  if (record2) {
28879
29489
  const exitCode2 = numberValue(record2["exit_code"]) ?? numberValue(record2["exitCode"]);
28880
29490
  if (exitCode2 !== null) {
@@ -28905,7 +29515,7 @@ function looksLikeCommandFailure(output) {
28905
29515
  }
28906
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);
28907
29517
  }
28908
- function asRecord(value) {
29518
+ function asRecord2(value) {
28909
29519
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
28910
29520
  return null;
28911
29521
  }
@@ -28994,12 +29604,18 @@ import { randomUUID as randomUUID2 } from "node:crypto";
28994
29604
  var IMPLEMENT_PLAN_OPTION_ID = "implement_plan";
28995
29605
  var REVISE_PLAN_OPTION_ID = "revise_plan";
28996
29606
  var CODEX_PROCESS_EXITED_ERROR_CODE = 1001;
28997
- function clientSupportsTypedSessionFailures(capabilities) {
29607
+ function clientSupportsAirCapability(capabilities, capability) {
28998
29608
  const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
28999
29609
  const air = jetbrains?.[AIR_META_KEY];
29000
29610
  const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
29001
29611
  const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
29002
- 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);
29003
29619
  }
29004
29620
  var CodexAcpServer = class _CodexAcpServer {
29005
29621
  static MODEL_NAME_TOKEN_OVERRIDES = {
@@ -29104,7 +29720,10 @@ var CodexAcpServer = class _CodexAcpServer {
29104
29720
  [JETBRAINS_META_KEY]: {
29105
29721
  [AIR_META_KEY]: {
29106
29722
  [AIR_EXTENSION_VERSION_KEY]: AIR_EXTENSION_VERSION,
29107
- [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
+ ]
29108
29727
  }
29109
29728
  }
29110
29729
  }
@@ -30155,6 +30774,42 @@ Check ${configPath} and project .codex directories, especially their config.toml
30155
30774
  title
30156
30775
  });
30157
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
+ }
30158
30813
  createPromptFallbackTitle(prompt) {
30159
30814
  return this.normalizeSessionTitle(prompt.filter((block) => block.type === "text").map((block) => block.text).join(" "));
30160
30815
  }
@@ -30289,8 +30944,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
30289
30944
  return `[@${name}](${uri})`;
30290
30945
  }
30291
30946
  if (uri.startsWith("file://")) {
30292
- const path7 = uri.replace("file://", "");
30293
- const fileName = path7.split("/").pop() ?? path7;
30947
+ const path8 = uri.replace("file://", "");
30948
+ const fileName = path8.split("/").pop() ?? path8;
30294
30949
  return `[@${fileName}](${uri})`;
30295
30950
  }
30296
30951
  return uri;
@@ -30543,6 +31198,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
30543
31198
  prompt: params.prompt
30544
31199
  });
30545
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";
30546
31204
  let recoverableSessionFailure = sessionState.sessionFailure;
30547
31205
  sessionState.currentTurnId = null;
30548
31206
  sessionState.lastTokenUsage = null;
@@ -30559,11 +31217,17 @@ Check ${configPath} and project .codex directories, especially their config.toml
30559
31217
  let eventHandler = null;
30560
31218
  let promptNotificationsActive = true;
30561
31219
  const clearRecoveredSessionFailure = async (handler) => {
31220
+ await handler.completeSuccessfulTurn(sessionState.currentTurnId);
30562
31221
  const current = sessionState.sessionFailure;
30563
- if (recoverableSessionFailure?.phase === "active" && current?.phase === "active" && current.id === recoverableSessionFailure.id && current.revision === recoverableSessionFailure.revision) {
31222
+ if (recoverableSessionFailure !== void 0 && current !== void 0 && current.id === recoverableSessionFailure.id && current.revision === recoverableSessionFailure.revision) {
30564
31223
  await handler.clearSessionFailure();
30565
31224
  }
30566
31225
  };
31226
+ const cancelledPromptResponse = () => {
31227
+ agentFileChangeReportTurnId = null;
31228
+ agentFileChangeReportUnavailableReason = "cancelled";
31229
+ return this.cancelledPromptResponse(sessionState);
31230
+ };
30567
31231
  try {
30568
31232
  const promptEventHandler = new CodexEventHandler(
30569
31233
  this.connection,
@@ -30598,7 +31262,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30598
31262
  elicitationHandler
30599
31263
  );
30600
31264
  if (activePrompt.signal.aborted) {
30601
- return this.cancelledPromptResponse(sessionState);
31265
+ return cancelledPromptResponse();
30602
31266
  }
30603
31267
  const commandPromise = this.availableCommands.tryHandleCommand(params.prompt, sessionState, {
30604
31268
  onTurnStartPending: () => {
@@ -30639,7 +31303,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30639
31303
  this.cancelBeforeTurnStarted(activePrompt)
30640
31304
  ]);
30641
31305
  if (commandResult === null) {
30642
- return this.cancelledPromptResponse(sessionState);
31306
+ return cancelledPromptResponse();
30643
31307
  }
30644
31308
  if (commandResult.handled) {
30645
31309
  promptNotificationsActive = false;
@@ -30651,7 +31315,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30651
31315
  await eventHandler.handleFailedTurn(commandResult.turnCompleted.turn);
30652
31316
  }
30653
31317
  if (commandResult.turnCompleted?.turn.status === "interrupted") {
30654
- return this.cancelledPromptResponse(sessionState);
31318
+ return cancelledPromptResponse();
30655
31319
  }
30656
31320
  const error52 = eventHandler.getFailure();
30657
31321
  if (error52) {
@@ -30665,6 +31329,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
30665
31329
  if (terminalFailure2) {
30666
31330
  return terminalFailure2;
30667
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
+ }
30668
31337
  await clearRecoveredSessionFailure(eventHandler);
30669
31338
  return {
30670
31339
  stopReason: "end_turn",
@@ -30674,7 +31343,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30674
31343
  }
30675
31344
  const effectiveParams = commandResult.prompt === void 0 ? params : { ...params, prompt: commandResult.prompt };
30676
31345
  if (this.sessionIsClosing(params.sessionId)) {
30677
- return this.cancelledPromptResponse(sessionState);
31346
+ return cancelledPromptResponse();
30678
31347
  }
30679
31348
  const modelId = ModelId.fromString(sessionState.currentModelId);
30680
31349
  const modelLacksReasoning = sessionState.supportedReasoningEfforts.length > 0 && sessionState.supportedReasoningEfforts.every((e) => e.reasoningEffort === "none");
@@ -30728,7 +31397,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30728
31397
  this.cancelBeforeTurnStarted(activePrompt)
30729
31398
  ]);
30730
31399
  if (turnCompleted === null) {
30731
- return this.cancelledPromptResponse(sessionState);
31400
+ return cancelledPromptResponse();
30732
31401
  }
30733
31402
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30734
31403
  await eventHandler.flushPendingErrors();
@@ -30736,7 +31405,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30736
31405
  promptNotificationsActive = false;
30737
31406
  if (turnCompleted.turn.status === "interrupted") {
30738
31407
  await eventHandler.flushPendingPlanUpdates();
30739
- return this.cancelledPromptResponse(sessionState);
31408
+ return cancelledPromptResponse();
30740
31409
  }
30741
31410
  const error51 = eventHandler.getFailure();
30742
31411
  if (error51) {
@@ -30759,7 +31428,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30759
31428
  activePrompt.signal
30760
31429
  );
30761
31430
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
30762
- return this.cancelledPromptResponse(sessionState);
31431
+ return cancelledPromptResponse();
30763
31432
  }
30764
31433
  if (approved && !this.promptShouldStop(params.sessionId, activePrompt)) {
30765
31434
  await this.applyCollaborationModeChange(sessionState, DEFAULT_COLLABORATION_MODE);
@@ -30808,7 +31477,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30808
31477
  this.cancelBeforeTurnStarted(activePrompt)
30809
31478
  ]);
30810
31479
  if (turnCompleted === null) {
30811
- return this.cancelledPromptResponse(sessionState);
31480
+ return cancelledPromptResponse();
30812
31481
  }
30813
31482
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30814
31483
  await eventHandler.flushPendingErrors();
@@ -30816,7 +31485,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30816
31485
  promptNotificationsActive = false;
30817
31486
  if (turnCompleted.turn.status === "interrupted") {
30818
31487
  await eventHandler.flushPendingPlanUpdates();
30819
- return this.cancelledPromptResponse(sessionState);
31488
+ return cancelledPromptResponse();
30820
31489
  }
30821
31490
  const implementationError = eventHandler.getFailure();
30822
31491
  if (implementationError) {
@@ -30832,6 +31501,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
30832
31501
  }
30833
31502
  }
30834
31503
  }
31504
+ if (turnCompleted.turn.status === "completed") {
31505
+ agentFileChangeReportTurnId = turnCompleted.turn.id;
31506
+ }
30835
31507
  await clearRecoveredSessionFailure(eventHandler);
30836
31508
  await this.publishFallbackSessionTitle(
30837
31509
  sessionState,
@@ -30845,13 +31517,17 @@ Check ${configPath} and project .codex directories, especially their config.toml
30845
31517
  } catch (err) {
30846
31518
  logger.error(`Prompt for session ${params.sessionId} failed`, err);
30847
31519
  if (activePrompt.signal.aborted || this.sessionIsClosing(params.sessionId)) {
30848
- return this.cancelledPromptResponse(sessionState);
31520
+ return cancelledPromptResponse();
30849
31521
  }
31522
+ agentFileChangeReportTurnId = null;
31523
+ agentFileChangeReportUnavailableReason = "providerError";
30850
31524
  const isProcessExit = err instanceof RequestError && err.code === CODEX_PROCESS_EXITED_ERROR_CODE;
30851
31525
  const isUnexpectedFailure = !(err instanceof RequestError);
30852
31526
  if (eventHandler !== null && clientSupportsTypedSessionFailures(this.clientCapabilities) && (isProcessExit || isUnexpectedFailure)) {
30853
- const category = isProcessExit ? "transport_lost" : "internal_error";
30854
- eventHandler.recordSyntheticTerminalFailure(category, sessionState.currentTurnId);
31527
+ eventHandler.recordSyntheticTerminalFailure(
31528
+ isProcessExit ? "transport_lost" : "internal_error",
31529
+ sessionState.currentTurnId
31530
+ );
30855
31531
  const failureResponse = this.terminalFailurePromptResponse(
30856
31532
  sessionState,
30857
31533
  eventHandler,
@@ -30865,6 +31541,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
30865
31541
  throw err;
30866
31542
  } finally {
30867
31543
  promptNotificationsActive = false;
31544
+ if (agentFileChangeReportRequest !== null) {
31545
+ await this.publishAgentFileChangeReport(
31546
+ sessionState,
31547
+ agentFileChangeReportTurnId,
31548
+ agentFileChangeReportRequest,
31549
+ agentFileChangeReportUnavailableReason,
31550
+ activePrompt.signal
31551
+ );
31552
+ }
30868
31553
  logger.log("Prompt completed", { sessionId: params.sessionId });
30869
31554
  await eventHandler?.dispose();
30870
31555
  disposePromptRequestCancellation();
@@ -31427,6 +32112,9 @@ var CodexAppServerClient = class {
31427
32112
  async threadResume(params) {
31428
32113
  return await this.sendRequest({ method: "thread/resume", params });
31429
32114
  }
32115
+ async threadFork(params) {
32116
+ return await this.sendRequest({ method: "thread/fork", params });
32117
+ }
31430
32118
  getThreadSettings(threadId) {
31431
32119
  return this.threadSettings.get(threadId);
31432
32120
  }