@agentclientprotocol/codex-acp 1.1.14 → 1.3.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 +640 -95
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -3453,7 +3453,7 @@ 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 path6 = __importStar(__require("path"));
3456
+ var path7 = __importStar(__require("path"));
3457
3457
  var os2 = __importStar(__require("os"));
3458
3458
  var fs7 = __importStar(__require("fs"));
3459
3459
  var crypto_1 = __require("crypto");
@@ -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 path6.join(tmpDir, `lsp-${randomSuffix}.sock`);
3608
+ return path7.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, path6) {
4513
- if (!path6)
4512
+ function getElementAtPath(obj, path7) {
4513
+ if (!path7)
4514
4514
  return obj;
4515
- return path6.reduce((acc, key) => acc?.[key], obj);
4515
+ return path7.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(path6, issues) {
4924
+ function prefixIssues(path7, issues) {
4925
4925
  return issues.map((iss) => {
4926
4926
  var _a3;
4927
4927
  (_a3 = iss).path ?? (_a3.path = []);
4928
- iss.path.unshift(path6);
4928
+ iss.path.unshift(path7);
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, path6 = []) => {
5075
+ const processError = (error52, path7 = []) => {
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 }, [...path6, ...issue2.path]));
5078
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
5079
5079
  } else if (issue2.code === "invalid_key") {
5080
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
5080
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
5081
5081
  } else if (issue2.code === "invalid_element") {
5082
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
5082
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
5083
5083
  } else {
5084
- const fullpath = [...path6, ...issue2.path];
5084
+ const fullpath = [...path7, ...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, path6 = []) => {
5111
+ const processError = (error52, path7 = []) => {
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 }, [...path6, ...issue2.path]));
5115
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
5116
5116
  } else if (issue2.code === "invalid_key") {
5117
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
5117
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
5118
5118
  } else if (issue2.code === "invalid_element") {
5119
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
5119
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
5120
5120
  } else {
5121
- const fullpath = [...path6, ...issue2.path];
5121
+ const fullpath = [...path7, ...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 path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5154
- for (const seg of path6) {
5153
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5154
+ for (const seg of path7) {
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 path6 = ref.slice(1).split("/").filter(Boolean);
17847
- if (path6.length === 0) {
17846
+ const path7 = ref.slice(1).split("/").filter(Boolean);
17847
+ if (path7.length === 0) {
17848
17848
  return ctx.rootSchema;
17849
17849
  }
17850
17850
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17851
- if (path6[0] === defsKey) {
17852
- const key = path6[1];
17851
+ if (path7[0] === defsKey) {
17852
+ const key = path7[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, path6) {
23239
- if (query && path6) {
23240
- return `Search for '${query}' in ${path6}`;
23250
+ function createSearchTitle(query, path7) {
23251
+ if (query && path7) {
23252
+ return `Search for '${query}' in ${path7}`;
23241
23253
  } else if (query) {
23242
23254
  return `Search for '${query}'`;
23243
- } else if (path6) {
23244
- return `Search in '${path6}'`;
23255
+ } else if (path7) {
23256
+ return `Search in '${path7}'`;
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(path6, oldText, newText) {
23482
+ function createUpdateDiffContent(path7, oldText, newText) {
23471
23483
  return {
23472
23484
  type: "diff",
23473
23485
  oldText,
23474
23486
  newText,
23475
- path: path6,
23487
+ path: path7,
23476
23488
  _meta: {
23477
23489
  kind: "update"
23478
23490
  }
@@ -23597,10 +23609,104 @@ function sameThreadGoalSnapshot(left, right) {
23597
23609
  }
23598
23610
 
23599
23611
  // src/CodexEventHandler.ts
23612
+ import { randomUUID } from "node:crypto";
23613
+
23614
+ // src/AirExtension.ts
23615
+ var JETBRAINS_META_KEY = "jetbrains";
23616
+ var AIR_META_KEY = "air";
23617
+ var AIR_EXTENSION_VERSION_KEY = "version";
23618
+ var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
23619
+ var AIR_SESSION_FAILURE_KEY = "sessionFailure";
23620
+ var AIR_EXTENSION_VERSION = 1;
23621
+
23622
+ // src/CodexEventHandler.ts
23623
+ var MAX_SESSION_FAILURE_TITLE_LENGTH = 240;
23624
+ var SESSION_FAILURE_POLICY = {
23625
+ transport_lost: {
23626
+ category: "connection",
23627
+ actions: ["retry", "new_session"]
23628
+ },
23629
+ auth_required: {
23630
+ category: "access",
23631
+ actions: ["login"]
23632
+ },
23633
+ rate_limited: {
23634
+ category: "limit",
23635
+ actions: ["retry"]
23636
+ },
23637
+ quota_exhausted: {
23638
+ category: "limit",
23639
+ actions: []
23640
+ },
23641
+ overloaded: {
23642
+ category: "service",
23643
+ actions: ["retry"]
23644
+ },
23645
+ context_exhausted: {
23646
+ category: "limit",
23647
+ actions: ["new_session"]
23648
+ },
23649
+ budget_exhausted: {
23650
+ category: "limit",
23651
+ actions: ["new_session"]
23652
+ },
23653
+ policy_denied: {
23654
+ category: "request",
23655
+ actions: []
23656
+ },
23657
+ bad_request: {
23658
+ category: "request",
23659
+ actions: []
23660
+ },
23661
+ provider_error: {
23662
+ category: "service",
23663
+ actions: ["retry"]
23664
+ },
23665
+ internal_error: {
23666
+ category: "service",
23667
+ actions: ["retry", "new_session"]
23668
+ }
23669
+ };
23670
+ var SYNTHETIC_FAILURE_TITLE = {
23671
+ transport_lost: "Connection to Codex was lost.",
23672
+ internal_error: "Codex encountered an internal error."
23673
+ };
23674
+ function nextSessionFailureRevision(previous, id) {
23675
+ return previous?.id === id ? previous.revision + 1 : 1;
23676
+ }
23677
+ var STRING_CODEX_ERROR_CATEGORIES = {
23678
+ contextWindowExceeded: "context_exhausted",
23679
+ sessionBudgetExceeded: "budget_exhausted",
23680
+ usageLimitExceeded: "quota_exhausted",
23681
+ serverOverloaded: "overloaded",
23682
+ cyberPolicy: "policy_denied",
23683
+ internalServerError: "internal_error",
23684
+ unauthorized: "auth_required",
23685
+ badRequest: "bad_request",
23686
+ threadRollbackFailed: "provider_error",
23687
+ sandboxError: "provider_error",
23688
+ other: "provider_error"
23689
+ };
23690
+ var STRUCTURED_CODEX_ERROR_CATEGORIES = {
23691
+ httpConnectionFailed: "transport_lost",
23692
+ responseStreamConnectionFailed: "transport_lost",
23693
+ responseStreamDisconnected: "transport_lost",
23694
+ responseTooManyFailedAttempts: "transport_lost",
23695
+ activeTurnNotSteerable: "provider_error"
23696
+ };
23600
23697
  var CodexEventHandler = class _CodexEventHandler {
23601
23698
  static PLAN_UPDATE_INTERVAL_MS = 150;
23602
23699
  sessionState;
23603
23700
  supportsPlanUpdates;
23701
+ supportsTypedSessionFailures;
23702
+ sessionFailureEpoch;
23703
+ pendingErrors = [];
23704
+ failuresById = /* @__PURE__ */ new Map();
23705
+ activeFailureIdByScope = /* @__PURE__ */ new Map();
23706
+ failureTurnIdById = /* @__PURE__ */ new Map();
23707
+ allocatedFailureScopes = /* @__PURE__ */ new Set();
23708
+ lastSessionNotice;
23709
+ nextNoticeId = 1;
23604
23710
  failure = null;
23605
23711
  completedPlan = null;
23606
23712
  activeFuzzyFileSearchSessions = /* @__PURE__ */ new Set();
@@ -23619,20 +23725,113 @@ var CodexEventHandler = class _CodexEventHandler {
23619
23725
  terminalCommandOutputIds = /* @__PURE__ */ new Set();
23620
23726
  agentMessagePhases = /* @__PURE__ */ new Map();
23621
23727
  activeSubAgentActivities = /* @__PURE__ */ new Set();
23622
- constructor(connection, sessionState, supportsPlanUpdates = false) {
23728
+ constructor(connection, sessionState, supportsPlanUpdates = false, supportsTypedSessionFailures = false, sessionFailureEpoch = randomUUID()) {
23623
23729
  this.sessionState = sessionState;
23624
23730
  this.supportsPlanUpdates = supportsPlanUpdates;
23731
+ this.supportsTypedSessionFailures = supportsTypedSessionFailures;
23732
+ this.sessionFailureEpoch = sessionFailureEpoch;
23625
23733
  this.session = new ACPSessionConnection(connection, sessionState.sessionId);
23734
+ if (sessionState.sessionFailure !== void 0) {
23735
+ this.failuresById.set(sessionState.sessionFailure.id, sessionState.sessionFailure);
23736
+ }
23626
23737
  }
23627
23738
  getFailure() {
23628
23739
  return this.failure;
23629
23740
  }
23741
+ getTerminalSessionFailureMeta(turnId, allowUnattributed = false) {
23742
+ const failure = this.sessionState.sessionFailure;
23743
+ if (!this.supportsTypedSessionFailures || failure === void 0 || (this.failureTurnIdById.get(failure.id) === void 0 ? !allowUnattributed : turnId === null || this.failureTurnIdById.get(failure.id) !== turnId)) {
23744
+ return null;
23745
+ }
23746
+ return this.createSessionFailureMeta(failure);
23747
+ }
23748
+ recordSyntheticTerminalFailure(kind, turnId) {
23749
+ this.recordSessionFailure(kind, turnId ?? void 0, "error", SYNTHETIC_FAILURE_TITLE[kind]);
23750
+ }
23751
+ /**
23752
+ * Handles notifications after the prompt-local handler has been disposed. The app-server subscription
23753
+ * remains installed until the ACP session closes, so terminal errors need a durable session-level path
23754
+ * instead of entering a turn buffer that will never be flushed.
23755
+ */
23756
+ async handleSessionScopedNotification(notification) {
23757
+ if (notification.method !== "error") {
23758
+ await this.handleNotification(notification);
23759
+ return;
23760
+ }
23761
+ if (!this.supportsTypedSessionFailures) {
23762
+ await this.handleNotification(notification);
23763
+ return;
23764
+ }
23765
+ if (notification.params.willRetry) {
23766
+ await this.session.update(this.createSessionFailureUpdate(this.recordRetryWarning(notification.params, false)));
23767
+ return;
23768
+ }
23769
+ const failure = this.recordSessionFailure(
23770
+ this.sessionFailureKind(notification.params.error.codexErrorInfo),
23771
+ notification.params.turnId,
23772
+ "error",
23773
+ notification.params.error.message,
23774
+ void 0,
23775
+ false
23776
+ );
23777
+ await this.session.update(this.createSessionFailureUpdate(failure));
23778
+ }
23779
+ async flushPendingErrors() {
23780
+ if (this.sessionState.currentTurnId === null || this.pendingErrors.length === 0) {
23781
+ return;
23782
+ }
23783
+ const errors = this.pendingErrors.splice(0);
23784
+ for (const error51 of errors) {
23785
+ const update = await this.createErrorEvent(error51);
23786
+ if (update) {
23787
+ await this.session.update(update);
23788
+ }
23789
+ }
23790
+ }
23791
+ async flushPendingErrorsAsSessionScoped() {
23792
+ if (!this.supportsTypedSessionFailures || this.pendingErrors.length === 0) {
23793
+ return;
23794
+ }
23795
+ const errors = this.pendingErrors.splice(0);
23796
+ for (const error51 of errors) {
23797
+ await this.handleSessionScopedNotification({ method: "error", params: error51 });
23798
+ }
23799
+ }
23800
+ async clearSessionFailure() {
23801
+ delete this.sessionState.sessionFailure;
23802
+ }
23803
+ async completeSuccessfulTurn(turnId) {
23804
+ this.lastSessionNotice = void 0;
23805
+ if (!this.supportsTypedSessionFailures || turnId === null) return;
23806
+ const active = this.sessionState.sessionFailure;
23807
+ if (active?.id !== this.activeFailureIdByScope.get(turnId) || active?.severity !== "warning") return;
23808
+ this.activeFailureIdByScope.delete(turnId);
23809
+ delete this.sessionState.sessionFailure;
23810
+ }
23811
+ async handleFailedTurn(turn) {
23812
+ const activeFailure = this.sessionState.sessionFailure;
23813
+ if (!this.supportsTypedSessionFailures || turn.status !== "failed" || this.failure !== null || this.failureTurnIdById.get(activeFailure?.id ?? "") === turn.id && activeFailure?.severity === "error") {
23814
+ return;
23815
+ }
23816
+ const error51 = turn.error ?? {
23817
+ message: "Turn failed",
23818
+ codexErrorInfo: null,
23819
+ additionalDetails: null
23820
+ };
23821
+ this.recordTypedSessionFailure({
23822
+ threadId: this.sessionState.sessionId,
23823
+ turnId: turn.id,
23824
+ willRetry: false,
23825
+ error: error51
23826
+ });
23827
+ }
23630
23828
  takeCompletedPlan() {
23631
23829
  const plan = this.completedPlan;
23632
23830
  this.completedPlan = null;
23633
23831
  return plan;
23634
23832
  }
23635
23833
  async handleNotification(notification) {
23834
+ await this.flushPendingErrors();
23636
23835
  const updateEvent = await this.createUpdateEvent(notification);
23637
23836
  if (updateEvent) {
23638
23837
  await this.session.update(updateEvent);
@@ -23653,6 +23852,14 @@ var CodexEventHandler = class _CodexEventHandler {
23653
23852
  async dispose() {
23654
23853
  if (this.disposed) return;
23655
23854
  await this.flushPendingPlanUpdates();
23855
+ if (this.pendingErrors.length > 0) {
23856
+ logger.log("Discarding app-server errors that arrived before a turn started", {
23857
+ sessionId: this.sessionState.sessionId,
23858
+ count: this.pendingErrors.length,
23859
+ turnIds: this.pendingErrors.map((error51) => error51.turnId)
23860
+ });
23861
+ this.pendingErrors.splice(0);
23862
+ }
23656
23863
  this.disposed = true;
23657
23864
  this.cancelPlanUpdateTimer();
23658
23865
  this.pendingPlanItemIds.clear();
@@ -23662,19 +23869,25 @@ var CodexEventHandler = class _CodexEventHandler {
23662
23869
  async createUpdateEvent(notification) {
23663
23870
  switch (notification.method) {
23664
23871
  case "item/agentMessage/delta":
23872
+ this.completeRetryIncidentOnTurnProgress();
23665
23873
  return await this.createTextEvent(notification.params);
23666
23874
  case "item/plan/delta":
23875
+ this.completeRetryIncidentOnTurnProgress();
23667
23876
  return this.createPlanDeltaEvent(notification.params);
23668
23877
  case "item/started":
23878
+ this.completeRetryIncidentOnTurnProgress();
23669
23879
  return await this.createItemEvent(notification.params);
23670
23880
  case "item/completed":
23881
+ this.completeRetryIncidentOnTurnProgress();
23671
23882
  return await this.completeItemEvent(notification.params);
23672
23883
  case "turn/plan/updated":
23884
+ this.completeRetryIncidentOnTurnProgress();
23673
23885
  return await this.updatePlan(notification.params);
23674
23886
  case "error":
23675
23887
  return await this.createErrorEvent(notification.params);
23676
23888
  case "turn/started":
23677
23889
  this.sessionState.currentTurnId = notification.params.turn.id;
23890
+ await this.flushPendingErrors();
23678
23891
  return null;
23679
23892
  case "turn/completed":
23680
23893
  await this.flushPendingPlanUpdates();
@@ -23707,8 +23920,10 @@ var CodexEventHandler = class _CodexEventHandler {
23707
23920
  closed: true
23708
23921
  });
23709
23922
  case "item/commandExecution/outputDelta":
23923
+ this.completeRetryIncidentOnTurnProgress();
23710
23924
  return this.createCommandOutputDeltaEvent(notification.params);
23711
23925
  case "item/mcpToolCall/progress":
23926
+ this.completeRetryIncidentOnTurnProgress();
23712
23927
  return this.createMcpToolProgressEvent(notification.params);
23713
23928
  case "account/rateLimits/updated":
23714
23929
  this.handleRateLimitsUpdated(notification.params);
@@ -23719,6 +23934,8 @@ var CodexEventHandler = class _CodexEventHandler {
23719
23934
  return this.createWarningEvent(notification.params);
23720
23935
  case "guardianWarning":
23721
23936
  return null;
23937
+ case "deprecationNotice":
23938
+ return this.createDeprecationNoticeEvent(notification.params);
23722
23939
  case "item/autoApprovalReview/started":
23723
23940
  return this.handleGuardianApprovalReviewStarted(notification.params);
23724
23941
  case "item/autoApprovalReview/completed":
@@ -23726,10 +23943,13 @@ var CodexEventHandler = class _CodexEventHandler {
23726
23943
  case "thread/compacted":
23727
23944
  return this.createContextCompactedEvent();
23728
23945
  case "item/reasoning/summaryTextDelta":
23946
+ this.completeRetryIncidentOnTurnProgress();
23729
23947
  return this.createReasoningDeltaEvent(notification.params);
23730
23948
  case "item/reasoning/textDelta":
23949
+ this.completeRetryIncidentOnTurnProgress();
23731
23950
  return this.createReasoningDeltaEvent(notification.params);
23732
23951
  case "item/reasoning/summaryPartAdded":
23952
+ this.completeRetryIncidentOnTurnProgress();
23733
23953
  return this.createReasoningSectionBreakEvent(notification.params);
23734
23954
  case "model/rerouted":
23735
23955
  return this.createModelReroutedEvent(notification.params);
@@ -23772,7 +23992,6 @@ var CodexEventHandler = class _CodexEventHandler {
23772
23992
  case "windowsSandbox/setupCompleted":
23773
23993
  case "account/login/completed":
23774
23994
  case "skills/changed":
23775
- case "deprecationNotice":
23776
23995
  case "mcpServer/oauthLogin/completed":
23777
23996
  case "externalAgentConfig/import/completed":
23778
23997
  case "rawResponseItem/completed":
@@ -23800,14 +24019,31 @@ var CodexEventHandler = class _CodexEventHandler {
23800
24019
  return createAgentTextMessageChunk(event.delta, event.itemId, createCodexMessagePhaseMeta(phase));
23801
24020
  }
23802
24021
  async createConfigWarningEvent(event) {
23803
- const detailsText = event.details ? `
24022
+ if (this.supportsTypedSessionFailures) {
24023
+ return this.createSessionFailureUpdate(this.recordSessionNotice(...this.sessionNoticeContent(event.summary, event.details)));
24024
+ }
24025
+ const text = event.details ? `${event.summary}
23804
24026
 
23805
- ${event.details}` : "";
23806
- return createAgentTextMessageChunk(`Config warning: ${event.summary}${detailsText}
24027
+ ${event.details}` : event.summary;
24028
+ return createAgentTextMessageChunk(`Config warning: ${text}
23807
24029
 
23808
24030
  `);
23809
24031
  }
24032
+ /**
24033
+ * Unlike `warning` and `configWarning`, this notification was dropped outright, so there is no
24034
+ * legacy rendering to preserve. It is surfaced only to clients that negotiated typed records;
24035
+ * every other client keeps seeing exactly what it sees today, which is nothing.
24036
+ */
24037
+ createDeprecationNoticeEvent(event) {
24038
+ if (!this.supportsTypedSessionFailures) return null;
24039
+ return this.createSessionFailureUpdate(
24040
+ this.recordSessionNotice(...this.sessionNoticeContent(event.summary, event.details))
24041
+ );
24042
+ }
23810
24043
  createWarningEvent(event) {
24044
+ if (this.supportsTypedSessionFailures) {
24045
+ return this.createSessionFailureUpdate(this.recordSessionNotice(event.message));
24046
+ }
23811
24047
  return createAgentTextMessageChunk(`Warning: ${event.message}
23812
24048
 
23813
24049
  `);
@@ -24174,7 +24410,33 @@ ${event.stdin}
24174
24410
  }
24175
24411
  async createErrorEvent(params) {
24176
24412
  const error51 = params.error.codexErrorInfo;
24413
+ if (this.sessionState.currentTurnId === null) {
24414
+ this.pendingErrors.push(params);
24415
+ logger.log("Buffered app-server error until the active turn is known", {
24416
+ sessionId: this.sessionState.sessionId,
24417
+ turnId: params.turnId,
24418
+ willRetry: params.willRetry
24419
+ });
24420
+ return null;
24421
+ }
24422
+ if (params.turnId !== this.sessionState.currentTurnId) {
24423
+ if (this.supportsTypedSessionFailures) {
24424
+ const failure = params.willRetry ? this.recordRetryWarning(params) : this.recordSessionFailure(
24425
+ this.sessionFailureKind(params.error.codexErrorInfo),
24426
+ params.turnId,
24427
+ "error",
24428
+ params.error.message
24429
+ );
24430
+ return this.createSessionFailureUpdate(failure);
24431
+ }
24432
+ return this.createCodexSessionInfoUpdate({
24433
+ error: { ...params.error, turnId: params.turnId, willRetry: params.willRetry }
24434
+ });
24435
+ }
24177
24436
  if (params.willRetry) {
24437
+ if (this.supportsTypedSessionFailures) {
24438
+ return this.createSessionFailureUpdate(this.recordRetryWarning(params));
24439
+ }
24178
24440
  return this.createCodexSessionInfoUpdate({
24179
24441
  error: {
24180
24442
  ...params.error,
@@ -24182,7 +24444,12 @@ ${event.stdin}
24182
24444
  willRetry: true
24183
24445
  }
24184
24446
  });
24185
- } else if (error51 === "usageLimitExceeded") {
24447
+ }
24448
+ if (this.supportsTypedSessionFailures) {
24449
+ this.recordTypedSessionFailure(params);
24450
+ return null;
24451
+ }
24452
+ if (error51 === "usageLimitExceeded") {
24186
24453
  this.failure = RequestError.internalError(
24187
24454
  this.createTurnErrorData(params.error)
24188
24455
  );
@@ -24193,22 +24460,125 @@ ${event.stdin}
24193
24460
 
24194
24461
  `);
24195
24462
  }
24463
+ recordTypedSessionFailure(params) {
24464
+ const kind = this.sessionFailureKind(params.error.codexErrorInfo);
24465
+ this.recordSessionFailure(kind, params.turnId, "error", params.error.message);
24466
+ }
24467
+ recordSessionFailure(kind, turnId, severity, title, actionsOverride, attributeToTurn = true) {
24468
+ const policy = SESSION_FAILURE_POLICY[kind];
24469
+ const scope = turnId ?? this.sessionState.sessionId;
24470
+ const id = this.activeFailureIdByScope.get(scope) ?? this.allocateFailureId(scope, turnId);
24471
+ const previous = this.failuresById.get(id);
24472
+ const failure = {
24473
+ id,
24474
+ revision: nextSessionFailureRevision(previous, id),
24475
+ category: policy.category,
24476
+ severity,
24477
+ title,
24478
+ actions: actionsOverride ?? policy.actions
24479
+ };
24480
+ this.failuresById.set(id, failure);
24481
+ this.failureTurnIdById.set(id, attributeToTurn ? turnId : void 0);
24482
+ this.activeFailureIdByScope.set(scope, id);
24483
+ this.sessionState.sessionFailure = failure;
24484
+ this.lastSessionNotice = void 0;
24485
+ return failure;
24486
+ }
24487
+ allocateFailureId(scope, turnId) {
24488
+ if (turnId !== void 0 && !this.allocatedFailureScopes.has(scope)) {
24489
+ this.allocatedFailureScopes.add(scope);
24490
+ return `${turnId}:error`;
24491
+ }
24492
+ this.allocatedFailureScopes.add(scope);
24493
+ return `${scope}:error:${this.sessionFailureEpoch}:${this.nextNoticeId++}`;
24494
+ }
24495
+ /**
24496
+ * A retry warning remains the active incident until Codex produces turn content again. That content is
24497
+ * the only positive signal available from app-server that the turn recovered; a later error then starts
24498
+ * a new incident instead of overwriting the historical reconnect entry. Terminal errors remain active so
24499
+ * duplicate late notifications cannot append duplicate transcript rows.
24500
+ */
24501
+ completeRetryIncidentOnTurnProgress() {
24502
+ const turnId = this.sessionState.currentTurnId;
24503
+ if (turnId === null) return;
24504
+ const activeId = this.activeFailureIdByScope.get(turnId);
24505
+ if (activeId === void 0 || this.failuresById.get(activeId)?.severity !== "warning") return;
24506
+ this.activeFailureIdByScope.delete(turnId);
24507
+ if (this.sessionState.sessionFailure?.id === activeId) {
24508
+ delete this.sessionState.sessionFailure;
24509
+ }
24510
+ }
24511
+ recordRetryWarning(params, attributeToTurn = true) {
24512
+ const kind = this.sessionFailureKind(params.error.codexErrorInfo);
24513
+ return this.recordSessionFailure(
24514
+ kind,
24515
+ params.turnId,
24516
+ "warning",
24517
+ params.error.message,
24518
+ [],
24519
+ attributeToTurn
24520
+ );
24521
+ }
24522
+ recordSessionNotice(title, details) {
24523
+ const key = `${title}\0${details ?? ""}`;
24524
+ const previous = this.lastSessionNotice?.key === key ? this.lastSessionNotice.failure : void 0;
24525
+ const id = previous?.id ?? `${this.sessionState.sessionId}:notice:${this.sessionFailureEpoch}:${this.nextNoticeId++}`;
24526
+ const notice = {
24527
+ id,
24528
+ revision: nextSessionFailureRevision(previous, id),
24529
+ category: "unknown",
24530
+ severity: "warning",
24531
+ title,
24532
+ ...details === void 0 ? {} : { details },
24533
+ actions: []
24534
+ };
24535
+ this.lastSessionNotice = { key, failure: notice };
24536
+ return notice;
24537
+ }
24538
+ sessionNoticeContent(summary, details) {
24539
+ if (details === null) return [summary];
24540
+ const combinedTitle = `${summary} \u2014 ${details}`;
24541
+ return combinedTitle.length <= MAX_SESSION_FAILURE_TITLE_LENGTH ? [combinedTitle] : [summary, details];
24542
+ }
24543
+ createSessionFailureMeta(failure) {
24544
+ return {
24545
+ [JETBRAINS_META_KEY]: {
24546
+ [AIR_META_KEY]: {
24547
+ [AIR_EXTENSION_VERSION_KEY]: AIR_EXTENSION_VERSION,
24548
+ [AIR_SESSION_FAILURE_KEY]: failure
24549
+ }
24550
+ }
24551
+ };
24552
+ }
24553
+ createSessionFailureUpdate(failure) {
24554
+ return {
24555
+ sessionUpdate: "session_info_update",
24556
+ _meta: this.createSessionFailureMeta(failure)
24557
+ };
24558
+ }
24559
+ sessionFailureKind(error51) {
24560
+ if (this.isAuthenticationRequiredError(error51)) return "auth_required";
24561
+ if (this.getHttpStatusCode(error51) === 429) return "rate_limited";
24562
+ if (typeof error51 === "string") {
24563
+ return STRING_CODEX_ERROR_CATEGORIES[error51] ?? "provider_error";
24564
+ }
24565
+ if (error51 !== null) {
24566
+ for (const kind of Object.keys(STRUCTURED_CODEX_ERROR_CATEGORIES)) {
24567
+ if (kind in error51) {
24568
+ return STRUCTURED_CODEX_ERROR_CATEGORIES[kind] ?? "provider_error";
24569
+ }
24570
+ }
24571
+ }
24572
+ return "provider_error";
24573
+ }
24196
24574
  isAuthenticationRequiredError(error51) {
24197
24575
  return error51 === "unauthorized" || this.getHttpStatusCode(error51) === 401;
24198
24576
  }
24199
24577
  getHttpStatusCode(error51) {
24200
- if (error51 !== null && typeof error51 === "object") {
24201
- if ("httpConnectionFailed" in error51) {
24202
- return error51.httpConnectionFailed.httpStatusCode;
24203
- } else if ("responseStreamConnectionFailed" in error51) {
24204
- return error51.responseStreamConnectionFailed.httpStatusCode;
24205
- } else if ("responseStreamDisconnected" in error51) {
24206
- return error51.responseStreamDisconnected.httpStatusCode;
24207
- } else if ("responseTooManyFailedAttempts" in error51) {
24208
- return error51.responseTooManyFailedAttempts.httpStatusCode;
24209
- }
24210
- }
24211
- return null;
24578
+ if (error51 === null || typeof error51 !== "object") return null;
24579
+ const details = Object.values(error51)[0];
24580
+ if (details === null || typeof details !== "object" || !("httpStatusCode" in details)) return null;
24581
+ return typeof details.httpStatusCode === "number" ? details.httpStatusCode : null;
24212
24582
  }
24213
24583
  createTurnErrorData(error51) {
24214
24584
  const data = {
@@ -24637,11 +25007,11 @@ var CodexApprovalHandler = class {
24637
25007
  });
24638
25008
  }
24639
25009
  const fileSystem = permissions.fileSystem;
24640
- for (const path6 of fileSystem?.read ?? []) {
24641
- changes.push(this.fileSystemGrantChange(path6, "read", lifetime, suffix));
25010
+ for (const path7 of fileSystem?.read ?? []) {
25011
+ changes.push(this.fileSystemGrantChange(path7, "read", lifetime, suffix));
24642
25012
  }
24643
- for (const path6 of fileSystem?.write ?? []) {
24644
- changes.push(this.fileSystemGrantChange(path6, "write", lifetime, suffix));
25013
+ for (const path7 of fileSystem?.write ?? []) {
25014
+ changes.push(this.fileSystemGrantChange(path7, "write", lifetime, suffix));
24645
25015
  }
24646
25016
  for (const entry of fileSystem?.entries ?? []) {
24647
25017
  const matcher = (() => {
@@ -24670,16 +25040,16 @@ var CodexApprovalHandler = class {
24670
25040
  }
24671
25041
  return changes.length > 0 ? { version: 1, changes } : void 0;
24672
25042
  }
24673
- fileSystemGrantChange(path6, access, lifetime, suffix) {
25043
+ fileSystemGrantChange(path7, access, lifetime, suffix) {
24674
25044
  return {
24675
25045
  type: "grant",
24676
25046
  operation: "grant",
24677
- description: `Allow ${access} access to ${path6}${suffix}`,
25047
+ description: `Allow ${access} access to ${path7}${suffix}`,
24678
25048
  lifetime,
24679
25049
  targets: [{
24680
25050
  type: "filesystem",
24681
25051
  access: [access],
24682
- matcher: { type: "exact_path", path: path6 }
25052
+ matcher: { type: "exact_path", path: path7 }
24683
25053
  }]
24684
25054
  };
24685
25055
  }
@@ -25589,15 +25959,15 @@ var wslDefaultBrowser = async () => {
25589
25959
  const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
25590
25960
  return stdout.trim();
25591
25961
  };
25592
- var convertWslPathToWindows = async (path6) => {
25593
- if (/^[a-z]+:\/\//i.test(path6)) {
25594
- return path6;
25962
+ var convertWslPathToWindows = async (path7) => {
25963
+ if (/^[a-z]+:\/\//i.test(path7)) {
25964
+ return path7;
25595
25965
  }
25596
25966
  try {
25597
- const { stdout } = await execFile2("wslpath", ["-aw", path6], { encoding: "utf8" });
25967
+ const { stdout } = await execFile2("wslpath", ["-aw", path7], { encoding: "utf8" });
25598
25968
  return stdout.trim();
25599
25969
  } catch {
25600
- return path6;
25970
+ return path7;
25601
25971
  }
25602
25972
  };
25603
25973
 
@@ -26147,7 +26517,7 @@ var AgentMode = class _AgentMode {
26147
26517
  };
26148
26518
 
26149
26519
  // src/CodexAcpClient.ts
26150
- import path4 from "node:path";
26520
+ import path5 from "node:path";
26151
26521
 
26152
26522
  // src/McpServerName.ts
26153
26523
  var MCP_SERVER_NAME_WHITESPACE = /\p{White_Space}/gu;
@@ -26161,7 +26531,7 @@ var package_default = {
26161
26531
  publishConfig: {
26162
26532
  access: "public"
26163
26533
  },
26164
- version: "1.1.14",
26534
+ version: "1.3.0",
26165
26535
  description: "",
26166
26536
  main: "dist/index.js",
26167
26537
  bin: {
@@ -26194,6 +26564,7 @@ var package_default = {
26194
26564
  "example:steering": "node --import tsx examples/steering.ts",
26195
26565
  "example:steering:multistep": "node --import tsx examples/steering.ts",
26196
26566
  "generate-types": "./node_modules/.bin/codex app-server generate-ts --out src/app-server",
26567
+ "release:preflight": "bash scripts/release-preflight.sh",
26197
26568
  test: "vitest run",
26198
26569
  "test:e2e": "npm run build && RUN_E2E_TESTS=true vitest run src/__tests__/CodexACPAgent/e2e",
26199
26570
  "test:watch": "vitest",
@@ -26265,8 +26636,58 @@ function createCodexCollaborationMode(mode, currentModelId) {
26265
26636
  };
26266
26637
  }
26267
26638
 
26639
+ // src/PathUtils.ts
26640
+ import path4 from "node:path";
26641
+ function isAbsolutePathLike(value) {
26642
+ const trimmed = value.trim();
26643
+ return path4.isAbsolute(trimmed) || isWindowsAbsolutePath(trimmed);
26644
+ }
26645
+ function arePathsEqual(left, right) {
26646
+ return normalizePathForComparison(left) === normalizePathForComparison(right);
26647
+ }
26648
+ function arePathBasenamesEqual(left, right) {
26649
+ const leftBase = path4.posix.basename(normalizePathForComparison(left));
26650
+ const rightBase = path4.posix.basename(normalizePathForComparison(right));
26651
+ if (shouldComparePathCaseInsensitive(left) || shouldComparePathCaseInsensitive(right)) {
26652
+ return leftBase.toLowerCase() === rightBase.toLowerCase();
26653
+ }
26654
+ return leftBase === rightBase;
26655
+ }
26656
+ function normalizePathForComparison(value) {
26657
+ const trimmed = value.trim();
26658
+ if (trimmed.length === 0) {
26659
+ return "";
26660
+ }
26661
+ if (isWindowsAbsolutePath(trimmed)) {
26662
+ const normalized2 = path4.win32.normalize(trimmed).replace(/\\/g, "/");
26663
+ return trimTrailingPathSeparators(normalized2).toLowerCase();
26664
+ }
26665
+ const pathForComparison = path4.isAbsolute(trimmed) ? trimmed : trimmed.replace(/\\/g, "/");
26666
+ const normalized = path4.posix.normalize(pathForComparison);
26667
+ return trimTrailingPathSeparators(normalized);
26668
+ }
26669
+ function isWindowsAbsolutePath(value) {
26670
+ const portableValue = value.replace(/\\/g, "/");
26671
+ return /^[A-Za-z]:\//.test(portableValue) || /^\/\/[^/]+\/[^/]+/.test(portableValue);
26672
+ }
26673
+ function shouldComparePathCaseInsensitive(value) {
26674
+ return isWindowsAbsolutePath(value) || /^[A-Za-z]:/.test(value) || value.includes("\\");
26675
+ }
26676
+ function trimTrailingPathSeparators(value) {
26677
+ let trimmed = value;
26678
+ while (trimmed.endsWith("/") && !isRootPath(trimmed)) {
26679
+ trimmed = trimmed.slice(0, -1);
26680
+ }
26681
+ return trimmed;
26682
+ }
26683
+ function isRootPath(value) {
26684
+ return value === "/" || /^[A-Za-z]:\/$/.test(value) || /^\/\/[^/]+\/[^/]+\/$/.test(value);
26685
+ }
26686
+
26268
26687
  // src/CodexAcpClient.ts
26269
26688
  var CUSTOM_GATEWAY_PROVIDER_ID = "custom-gateway";
26689
+ var OPENAI_PROVIDER_ID = "openai";
26690
+ var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
26270
26691
  var SUPPORTED_GATEWAY_PROTOCOLS = {
26271
26692
  openai: "responses"
26272
26693
  };
@@ -26480,34 +26901,47 @@ var CodexAcpClient = class {
26480
26901
  };
26481
26902
  }
26482
26903
  /**
26483
- * `providers/list`: returns the single client-configurable custom gateway
26484
- * provider. `current` carries only non-secret routing (never headers), and is
26485
- * `null` when the provider is not configured/disabled.
26904
+ * `providers/list`: returns Codex's OpenAI slot. With no ACP override, the
26905
+ * slot reports native OpenAI routing; headers are never exposed.
26486
26906
  */
26487
26907
  listProviders() {
26488
26908
  const gatewayConfig = this.gatewayConfig;
26489
26909
  const current = gatewayConfig ? {
26490
26910
  apiType: gatewayApiTypeFromConfig(gatewayConfig),
26491
26911
  baseUrl: gatewayConfig.config.base_url
26492
- } : null;
26912
+ } : this.getNativeProviderConfig();
26493
26913
  return [
26494
26914
  {
26495
- providerId: CUSTOM_GATEWAY_PROVIDER_ID,
26915
+ providerId: OPENAI_PROVIDER_ID,
26496
26916
  supported: Object.keys(SUPPORTED_GATEWAY_PROTOCOLS),
26497
26917
  required: false,
26498
26918
  current
26499
26919
  }
26500
26920
  ];
26501
26921
  }
26922
+ getNativeProviderConfig() {
26923
+ const configuredProviderId = this.modelProvider ?? (typeof this.config["model_provider"] === "string" ? this.config["model_provider"] : null);
26924
+ const configuredProviders = this.config["model_providers"];
26925
+ if (configuredProviderId && configuredProviders && typeof configuredProviders === "object" && !Array.isArray(configuredProviders)) {
26926
+ const configuredProvider = configuredProviders[configuredProviderId];
26927
+ if (configuredProvider && typeof configuredProvider === "object" && !Array.isArray(configuredProvider)) {
26928
+ const baseUrl = configuredProvider["base_url"];
26929
+ if (typeof baseUrl === "string" && baseUrl.length > 0) {
26930
+ return { apiType: "openai", baseUrl };
26931
+ }
26932
+ }
26933
+ }
26934
+ return { apiType: "openai", baseUrl: DEFAULT_OPENAI_BASE_URL };
26935
+ }
26502
26936
  /**
26503
26937
  * `providers/set`: replaces the full configuration for the custom gateway
26504
26938
  * provider. Rejects unknown provider ids with `invalid_params`.
26505
26939
  */
26506
26940
  setProvider(request) {
26507
- if (request.providerId !== CUSTOM_GATEWAY_PROVIDER_ID) {
26941
+ if (request.providerId !== OPENAI_PROVIDER_ID) {
26508
26942
  throw RequestError.invalidParams(
26509
26943
  { providerId: request.providerId },
26510
- `Unknown providerId "${request.providerId}"; only "${CUSTOM_GATEWAY_PROVIDER_ID}" is configurable`
26944
+ `Unknown providerId "${request.providerId}"; only "${OPENAI_PROVIDER_ID}" is configurable`
26511
26945
  );
26512
26946
  }
26513
26947
  this.applyGatewayConfig({
@@ -26521,7 +26955,7 @@ var CodexAcpClient = class {
26521
26955
  * unknown provider id is idempotent success (RFD behavior §7).
26522
26956
  */
26523
26957
  disableProvider(request) {
26524
- if (request.providerId === CUSTOM_GATEWAY_PROVIDER_ID) {
26958
+ if (request.providerId === OPENAI_PROVIDER_ID) {
26525
26959
  this.gatewayConfig = null;
26526
26960
  }
26527
26961
  }
@@ -26719,7 +27153,7 @@ var CodexAcpClient = class {
26719
27153
  if (!cwd) {
26720
27154
  return;
26721
27155
  }
26722
- const skillExtraRoots = additionalRoots.map((root) => path4.join(root, ".agents", "skills"));
27156
+ const skillExtraRoots = additionalRoots.map((root) => path5.join(root, ".agents", "skills"));
26723
27157
  if (!arraysEqual(this.skillExtraRoots, skillExtraRoots)) {
26724
27158
  await this.codexClient.skillsExtraRootsSet({ extraRoots: skillExtraRoots });
26725
27159
  this.skillExtraRoots = skillExtraRoots;
@@ -26910,11 +27344,10 @@ var CodexAcpClient = class {
26910
27344
  const requestedCwd = request.cwd?.trim() ?? null;
26911
27345
  const filterByCwd = (thread) => {
26912
27346
  if (!requestedCwd) return true;
26913
- if (path4.isAbsolute(requestedCwd)) {
26914
- return thread.cwd === requestedCwd;
27347
+ if (isAbsolutePathLike(requestedCwd)) {
27348
+ return arePathsEqual(thread.cwd, requestedCwd);
26915
27349
  }
26916
- const requestedBase = path4.basename(requestedCwd);
26917
- return path4.basename(thread.cwd) === requestedBase;
27350
+ return arePathBasenamesEqual(thread.cwd, requestedCwd);
26918
27351
  };
26919
27352
  const preferredProvider = this.getModelProvider();
26920
27353
  const modelProviders = preferredProvider ? [preferredProvider] : [];
@@ -26936,7 +27369,7 @@ var CodexAcpClient = class {
26936
27369
  let sessions = listResponse.data.map(mapThreadToSession);
26937
27370
  if (requestedCwd) {
26938
27371
  const filtered = listResponse.data.filter(filterByCwd).map(mapThreadToSession);
26939
- if (filtered.length > 0 || path4.isAbsolute(requestedCwd)) {
27372
+ if (filtered.length > 0 || isAbsolutePathLike(requestedCwd)) {
26940
27373
  sessions = filtered;
26941
27374
  } else {
26942
27375
  logger.log("Ignoring non-absolute cwd filter for session/list", { cwd: requestedCwd });
@@ -27051,8 +27484,8 @@ function formatUriAsLink(name, uri) {
27051
27484
  return `[@${name}](${uri})`;
27052
27485
  }
27053
27486
  if (uri.startsWith("file://")) {
27054
- const path6 = uri.replace("file://", "");
27055
- const fileName = path6.split("/").pop() ?? path6;
27487
+ const path7 = uri.replace("file://", "");
27488
+ const fileName = path7.split("/").pop() ?? path7;
27056
27489
  return `[@${fileName}](${uri})`;
27057
27490
  }
27058
27491
  return uri;
@@ -27082,7 +27515,7 @@ function readAdditionalDirectories(cwd, additionalDirectories, meta3) {
27082
27515
  if (directory.length === 0) {
27083
27516
  throw RequestError.invalidParams(void 0, "additionalDirectories entries must not be empty");
27084
27517
  }
27085
- if (!path4.isAbsolute(directory)) {
27518
+ if (!path5.isAbsolute(directory)) {
27086
27519
  throw RequestError.invalidParams(void 0, "additionalDirectories entries must be absolute paths");
27087
27520
  }
27088
27521
  if (!seen.has(directory)) {
@@ -27685,7 +28118,7 @@ var SteeringQueue = class {
27685
28118
 
27686
28119
  // src/ResponseItemHistoryFallback.ts
27687
28120
  import { readFile as readFile2 } from "node:fs/promises";
27688
- import path5 from "node:path";
28121
+ import path6 from "node:path";
27689
28122
  function historyFallbackUpdateKey(update) {
27690
28123
  switch (update.sessionUpdate) {
27691
28124
  case "user_message_chunk":
@@ -27925,8 +28358,8 @@ function imageBlocks(images) {
27925
28358
  return [{ type: "text", text: `[@image](${image})` }];
27926
28359
  }
27927
28360
  const record2 = asRecord(image);
27928
- const path6 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
27929
- return path6 ? [{ type: "text", text: `[@image](${path6})` }] : [];
28361
+ const path7 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
28362
+ return path7 ? [{ type: "text", text: `[@image](${path7})` }] : [];
27930
28363
  });
27931
28364
  }
27932
28365
  function contentBlocksFromResponseContent(content) {
@@ -28558,10 +28991,10 @@ function baseCommandName(commandName) {
28558
28991
  return commandName.split(/[\\/]/).pop() ?? commandName;
28559
28992
  }
28560
28993
  function absolutizePath(cwd, targetPath) {
28561
- if (path5.isAbsolute(targetPath) || cwd.length === 0) {
28994
+ if (path6.isAbsolute(targetPath) || cwd.length === 0) {
28562
28995
  return targetPath;
28563
28996
  }
28564
- return path5.join(cwd, targetPath);
28997
+ return path6.join(cwd, targetPath);
28565
28998
  }
28566
28999
  function withTerminalContent(event, terminalId, cwd) {
28567
29000
  const { rawInput, ...eventWithoutRawInput } = event;
@@ -28717,8 +29150,17 @@ function clientSupportsPlanUpdates(clientCapabilities) {
28717
29150
  }
28718
29151
 
28719
29152
  // src/CodexAcpServer.ts
29153
+ import { randomUUID as randomUUID2 } from "node:crypto";
28720
29154
  var IMPLEMENT_PLAN_OPTION_ID = "implement_plan";
28721
29155
  var REVISE_PLAN_OPTION_ID = "revise_plan";
29156
+ var CODEX_PROCESS_EXITED_ERROR_CODE = 1001;
29157
+ function clientSupportsTypedSessionFailures(capabilities) {
29158
+ const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
29159
+ const air = jetbrains?.[AIR_META_KEY];
29160
+ const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
29161
+ 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);
29163
+ }
28722
29164
  var CodexAcpServer = class _CodexAcpServer {
28723
29165
  static MODEL_NAME_TOKEN_OVERRIDES = {
28724
29166
  gpt: "GPT",
@@ -28730,6 +29172,7 @@ var CodexAcpServer = class _CodexAcpServer {
28730
29172
  defaultAuthRequest;
28731
29173
  getExitCode;
28732
29174
  getRecentStderr;
29175
+ sessionFailureEpoch;
28733
29176
  availableCommands;
28734
29177
  clientInfo;
28735
29178
  clientCapabilities;
@@ -28759,6 +29202,7 @@ var CodexAcpServer = class _CodexAcpServer {
28759
29202
  this.defaultAuthRequest = defaultAuthRequest ?? null;
28760
29203
  this.getExitCode = getExitCode ?? (() => null);
28761
29204
  this.getRecentStderr = getRecentStderr ?? (() => "");
29205
+ this.sessionFailureEpoch = randomUUID2();
28762
29206
  this.clientInfo = null;
28763
29207
  this.clientCapabilities = null;
28764
29208
  this.terminalOutputMode = "terminal_output_delta";
@@ -28816,6 +29260,12 @@ var CodexAcpServer = class _CodexAcpServer {
28816
29260
  version: GOAL_EXTENSION_VERSION,
28817
29261
  controlMethod: GOAL_CONTROL_METHOD,
28818
29262
  actions: [...GOAL_CONTROL_ACTIONS]
29263
+ },
29264
+ [JETBRAINS_META_KEY]: {
29265
+ [AIR_META_KEY]: {
29266
+ [AIR_EXTENSION_VERSION_KEY]: AIR_EXTENSION_VERSION,
29267
+ [AIR_EXTENSION_CAPABILITIES_KEY]: [AIR_SESSION_FAILURE_KEY]
29268
+ }
28819
29269
  }
28820
29270
  }
28821
29271
  };
@@ -29999,8 +30449,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
29999
30449
  return `[@${name}](${uri})`;
30000
30450
  }
30001
30451
  if (uri.startsWith("file://")) {
30002
- const path6 = uri.replace("file://", "");
30003
- const fileName = path6.split("/").pop() ?? path6;
30452
+ const path7 = uri.replace("file://", "");
30453
+ const fileName = path7.split("/").pop() ?? path7;
30004
30454
  return `[@${fileName}](${uri})`;
30005
30455
  }
30006
30456
  return uri;
@@ -30253,6 +30703,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30253
30703
  prompt: params.prompt
30254
30704
  });
30255
30705
  const sessionState = this.getSessionState(params.sessionId);
30706
+ let recoverableSessionFailure = sessionState.sessionFailure;
30256
30707
  sessionState.currentTurnId = null;
30257
30708
  sessionState.lastTokenUsage = null;
30258
30709
  const activePrompt = this.trackActivePrompt(params.sessionId);
@@ -30266,11 +30717,21 @@ Check ${configPath} and project .codex directories, especially their config.toml
30266
30717
  };
30267
30718
  const disposePromptRequestCancellation = this.observePromptRequestCancellation(signal, sessionState, activePrompt);
30268
30719
  let eventHandler = null;
30720
+ let promptNotificationsActive = true;
30721
+ const clearRecoveredSessionFailure = async (handler) => {
30722
+ await handler.completeSuccessfulTurn(sessionState.currentTurnId);
30723
+ const current = sessionState.sessionFailure;
30724
+ if (recoverableSessionFailure !== void 0 && current !== void 0 && current.id === recoverableSessionFailure.id && current.revision === recoverableSessionFailure.revision) {
30725
+ await handler.clearSessionFailure();
30726
+ }
30727
+ };
30269
30728
  try {
30270
30729
  const promptEventHandler = new CodexEventHandler(
30271
30730
  this.connection,
30272
30731
  sessionState,
30273
- clientSupportsPlanUpdates(this.clientCapabilities)
30732
+ clientSupportsPlanUpdates(this.clientCapabilities),
30733
+ clientSupportsTypedSessionFailures(this.clientCapabilities),
30734
+ this.sessionFailureEpoch
30274
30735
  );
30275
30736
  eventHandler = promptEventHandler;
30276
30737
  const approvalHandler = new CodexApprovalHandler(this.connection, sessionState, activePrompt.signal);
@@ -30283,8 +30744,16 @@ Check ${configPath} and project .codex directories, especially their config.toml
30283
30744
  await this.codexAcpClient.subscribeToSessionEvents(
30284
30745
  params.sessionId,
30285
30746
  async (event) => {
30747
+ if (!promptNotificationsActive) {
30748
+ await promptEventHandler.handleSessionScopedNotification(event);
30749
+ return;
30750
+ }
30751
+ const completesActiveTurn = event.method === "turn/completed" && event.params.turn.id === sessionState.currentTurnId;
30286
30752
  await elicitationHandler.handleNotification(event);
30287
- return promptEventHandler.handleNotification(event);
30753
+ await promptEventHandler.handleNotification(event);
30754
+ if (completesActiveTurn) {
30755
+ promptNotificationsActive = false;
30756
+ }
30288
30757
  },
30289
30758
  approvalHandler,
30290
30759
  elicitationHandler
@@ -30334,8 +30803,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
30334
30803
  return this.cancelledPromptResponse(sessionState);
30335
30804
  }
30336
30805
  if (commandResult.handled) {
30806
+ promptNotificationsActive = false;
30337
30807
  logger.log("Prompt handled by a command");
30338
30808
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30809
+ await eventHandler.flushPendingErrors();
30810
+ await eventHandler.flushPendingErrorsAsSessionScoped();
30811
+ if (commandResult.turnCompleted) {
30812
+ await eventHandler.handleFailedTurn(commandResult.turnCompleted.turn);
30813
+ }
30339
30814
  if (commandResult.turnCompleted?.turn.status === "interrupted") {
30340
30815
  return this.cancelledPromptResponse(sessionState);
30341
30816
  }
@@ -30343,6 +30818,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
30343
30818
  if (error52) {
30344
30819
  throw error52;
30345
30820
  }
30821
+ const terminalFailure2 = this.terminalFailurePromptResponse(
30822
+ sessionState,
30823
+ eventHandler,
30824
+ commandResult.turnCompleted?.turn.id ?? sessionState.currentTurnId
30825
+ );
30826
+ if (terminalFailure2) {
30827
+ return terminalFailure2;
30828
+ }
30829
+ await clearRecoveredSessionFailure(eventHandler);
30346
30830
  return {
30347
30831
  stopReason: "end_turn",
30348
30832
  usage: this.buildPromptUsage(sessionState.lastTokenUsage),
@@ -30408,6 +30892,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
30408
30892
  return this.cancelledPromptResponse(sessionState);
30409
30893
  }
30410
30894
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30895
+ await eventHandler.flushPendingErrors();
30896
+ await eventHandler.handleFailedTurn(turnCompleted.turn);
30897
+ promptNotificationsActive = false;
30411
30898
  if (turnCompleted.turn.status === "interrupted") {
30412
30899
  await eventHandler.flushPendingPlanUpdates();
30413
30900
  return this.cancelledPromptResponse(sessionState);
@@ -30416,6 +30903,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
30416
30903
  if (error51) {
30417
30904
  throw error51;
30418
30905
  }
30906
+ const terminalFailure = this.terminalFailurePromptResponse(
30907
+ sessionState,
30908
+ eventHandler,
30909
+ turnCompleted.turn.id
30910
+ );
30911
+ if (terminalFailure) {
30912
+ return terminalFailure;
30913
+ }
30419
30914
  await eventHandler.flushPendingPlanUpdates();
30420
30915
  const completedPlan = eventHandler.takeCompletedPlan();
30421
30916
  if (completedPlan !== null && sessionState.collaborationMode === PLAN_COLLABORATION_MODE && !this.promptShouldStop(params.sessionId, activePrompt)) {
@@ -30439,6 +30934,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30439
30934
  prompt: [{ type: "text", text: "Implement the approved plan." }]
30440
30935
  };
30441
30936
  activePrompt.currentTurn = null;
30937
+ sessionState.currentTurnId = null;
30442
30938
  const implementationPromise = this.runWithProcessCheck(
30443
30939
  () => this.codexAcpClient.sendPrompt(
30444
30940
  implementationRequest,
@@ -30456,6 +30952,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
30456
30952
  return;
30457
30953
  }
30458
30954
  sessionState.currentTurnId = turnId;
30955
+ recoverableSessionFailure = sessionState.sessionFailure;
30956
+ promptNotificationsActive = true;
30459
30957
  },
30460
30958
  () => this.promptShouldStop(params.sessionId, activePrompt)
30461
30959
  )
@@ -30474,6 +30972,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
30474
30972
  return this.cancelledPromptResponse(sessionState);
30475
30973
  }
30476
30974
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30975
+ await eventHandler.flushPendingErrors();
30976
+ await eventHandler.handleFailedTurn(turnCompleted.turn);
30977
+ promptNotificationsActive = false;
30477
30978
  if (turnCompleted.turn.status === "interrupted") {
30478
30979
  await eventHandler.flushPendingPlanUpdates();
30479
30980
  return this.cancelledPromptResponse(sessionState);
@@ -30482,8 +30983,17 @@ Check ${configPath} and project .codex directories, especially their config.toml
30482
30983
  if (implementationError) {
30483
30984
  throw implementationError;
30484
30985
  }
30986
+ const implementationFailure = this.terminalFailurePromptResponse(
30987
+ sessionState,
30988
+ eventHandler,
30989
+ turnCompleted.turn.id
30990
+ );
30991
+ if (implementationFailure) {
30992
+ return implementationFailure;
30993
+ }
30485
30994
  }
30486
30995
  }
30996
+ await clearRecoveredSessionFailure(eventHandler);
30487
30997
  await this.publishFallbackSessionTitle(
30488
30998
  sessionState,
30489
30999
  this.createPromptFallbackTitle(params.prompt)
@@ -30495,8 +31005,29 @@ Check ${configPath} and project .codex directories, especially their config.toml
30495
31005
  };
30496
31006
  } catch (err) {
30497
31007
  logger.error(`Prompt for session ${params.sessionId} failed`, err);
31008
+ if (activePrompt.signal.aborted || this.sessionIsClosing(params.sessionId)) {
31009
+ return this.cancelledPromptResponse(sessionState);
31010
+ }
31011
+ const isProcessExit = err instanceof RequestError && err.code === CODEX_PROCESS_EXITED_ERROR_CODE;
31012
+ const isUnexpectedFailure = !(err instanceof RequestError);
31013
+ if (eventHandler !== null && clientSupportsTypedSessionFailures(this.clientCapabilities) && (isProcessExit || isUnexpectedFailure)) {
31014
+ eventHandler.recordSyntheticTerminalFailure(
31015
+ isProcessExit ? "transport_lost" : "internal_error",
31016
+ sessionState.currentTurnId
31017
+ );
31018
+ const failureResponse = this.terminalFailurePromptResponse(
31019
+ sessionState,
31020
+ eventHandler,
31021
+ sessionState.currentTurnId,
31022
+ true
31023
+ );
31024
+ if (failureResponse !== null) {
31025
+ return failureResponse;
31026
+ }
31027
+ }
30498
31028
  throw err;
30499
31029
  } finally {
31030
+ promptNotificationsActive = false;
30500
31031
  logger.log("Prompt completed", { sessionId: params.sessionId });
30501
31032
  await eventHandler?.dispose();
30502
31033
  disposePromptRequestCancellation();
@@ -30567,6 +31098,20 @@ Check ${configPath} and project .codex directories, especially their config.toml
30567
31098
  _meta: this.buildQuotaMeta(sessionState)
30568
31099
  };
30569
31100
  }
31101
+ terminalFailurePromptResponse(sessionState, eventHandler, turnId, allowUnattributed = false) {
31102
+ const failureMeta = eventHandler.getTerminalSessionFailureMeta(turnId, allowUnattributed);
31103
+ if (failureMeta === null) {
31104
+ return null;
31105
+ }
31106
+ return {
31107
+ stopReason: "end_turn",
31108
+ usage: this.buildPromptUsage(sessionState.lastTokenUsage),
31109
+ _meta: {
31110
+ ...this.buildQuotaMeta(sessionState),
31111
+ ...failureMeta
31112
+ }
31113
+ };
31114
+ }
30570
31115
  buildQuotaMeta(sessionState) {
30571
31116
  const lastTokenUsage = sessionState.lastTokenUsage;
30572
31117
  const modelName = sessionState.currentModelId.replace(/\[.*?]$/, "");
@@ -30589,7 +31134,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30589
31134
  return await operation();
30590
31135
  } catch (err) {
30591
31136
  const exitCode = this.getExitCode();
30592
- const requestErrorCode = 1001;
31137
+ const requestErrorCode = CODEX_PROCESS_EXITED_ERROR_CODE;
30593
31138
  if (exitCode == 3221225781) {
30594
31139
  throw new RequestError(requestErrorCode, `VC++ redistributable should be installed`);
30595
31140
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.14",
6
+ "version": "1.3.0",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
@@ -36,6 +36,7 @@
36
36
  "example:steering": "node --import tsx examples/steering.ts",
37
37
  "example:steering:multistep": "node --import tsx examples/steering.ts",
38
38
  "generate-types": "./node_modules/.bin/codex app-server generate-ts --out src/app-server",
39
+ "release:preflight": "bash scripts/release-preflight.sh",
39
40
  "test": "vitest run",
40
41
  "test:e2e": "npm run build && RUN_E2E_TESTS=true vitest run src/__tests__/CodexACPAgent/e2e",
41
42
  "test:watch": "vitest",