@agentconnect.md/codex-acp 1.8.0-agentconnect.1 → 1.10.0-agentconnect.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +925 -62
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -22132,32 +22132,7 @@ function attachLogs(proc) {
22132
22132
  });
22133
22133
  }
22134
22134
 
22135
- // src/AirExtension.ts
22136
- var JETBRAINS_META_KEY = "jetbrains";
22137
- var AIR_META_KEY = "air";
22138
- var AIR_EXTENSION_VERSION_KEY = "version";
22139
- var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
22140
- var AIR_SESSION_FAILURE_KEY = "sessionFailure";
22141
- var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
22142
- var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
22143
- var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
22144
- var AIR_EXTENSION_VERSION = 1;
22145
- function clientSupportsAirCapability(capabilities, capability) {
22146
- const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
22147
- const air = jetbrains?.[AIR_META_KEY];
22148
- const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
22149
- const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
22150
- return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
22151
- }
22152
-
22153
- // src/subagents/AcpSubagents.ts
22154
- function clientSupportsSubagents(capabilities) {
22155
- const subagents = capabilities?.subagents;
22156
- if (typeof subagents === "object" && subagents !== null && !Array.isArray(subagents)) {
22157
- return true;
22158
- }
22159
- return clientSupportsAirCapability(capabilities, AIR_NATIVE_SUBAGENT_SESSIONS_KEY);
22160
- }
22135
+ // src/AcpSessionExtensions.ts
22161
22136
  function asSdkSessionNotification(notification) {
22162
22137
  return notification;
22163
22138
  }
@@ -23686,6 +23661,26 @@ function sameThreadGoalSnapshot(left, right) {
23686
23661
  // src/CodexEventHandler.ts
23687
23662
  import { randomUUID } from "node:crypto";
23688
23663
 
23664
+ // src/AirExtension.ts
23665
+ var JETBRAINS_META_KEY = "jetbrains";
23666
+ var AIR_META_KEY = "air";
23667
+ var AIR_EXTENSION_VERSION_KEY = "version";
23668
+ var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
23669
+ var AIR_SESSION_FAILURE_KEY = "sessionFailure";
23670
+ var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
23671
+ var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
23672
+ var AIR_ASYNC_TASKS_KEY = "asyncTasks";
23673
+ var AIR_ASYNC_TASKS_BACKGROUNDED_KEY = "backgrounded";
23674
+ var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
23675
+ var AIR_EXTENSION_VERSION = 1;
23676
+ function clientSupportsAirCapability(capabilities, capability) {
23677
+ const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
23678
+ const air = jetbrains?.[AIR_META_KEY];
23679
+ const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
23680
+ const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
23681
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
23682
+ }
23683
+
23689
23684
  // src/subagents/CodexAgentPath.ts
23690
23685
  function normalizeAgentPath(path9) {
23691
23686
  const normalized = path9.trim().replace(/\/+$/, "");
@@ -23741,14 +23736,14 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23741
23736
  }
23742
23737
  return childTurn;
23743
23738
  }
23744
- const notificationThreadId = notification.params.threadId;
23745
- if (typeof notificationThreadId === "string" && this.pendingSpawns.has(notificationThreadId)) {
23746
- const pending = this.pendingSpawns.get(notificationThreadId);
23739
+ const notificationThreadId2 = notification.params.threadId;
23740
+ if (typeof notificationThreadId2 === "string" && this.pendingSpawns.has(notificationThreadId2)) {
23741
+ const pending = this.pendingSpawns.get(notificationThreadId2);
23747
23742
  if (pending.buffered.length === _CodexSubagentEventRouter.MAX_PENDING_NOTIFICATIONS) {
23748
23743
  pending.buffered.shift();
23749
23744
  pending.droppedBufferedNotifications += 1;
23750
23745
  if (pending.droppedBufferedNotifications === 1) {
23751
- logger.log(`Pending subagent ${notificationThreadId} exceeded the notification buffer; dropping oldest updates`);
23746
+ logger.log(`Pending subagent ${notificationThreadId2} exceeded the notification buffer; dropping oldest updates`);
23752
23747
  }
23753
23748
  }
23754
23749
  pending.buffered.push(notification);
@@ -23833,6 +23828,25 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23833
23828
  const threadId = notification.params.threadId;
23834
23829
  return typeof threadId === "string" && this.children.has(threadId) ? this.children.get(threadId).sessionId : this.rootSessionId;
23835
23830
  }
23831
+ closingChildSessions(notification) {
23832
+ if (!this.supported) return [];
23833
+ if (notification.method === "turn/completed") {
23834
+ if (terminalStateFromTurn(notification.params.turn.status) === void 0) return [];
23835
+ return this.closingChildSession(notification.params.threadId);
23836
+ }
23837
+ if (notification.method !== "item/started" && notification.method !== "item/completed") return [];
23838
+ const item = notification.params.item;
23839
+ if (item.type === "subAgentActivity") {
23840
+ return item.kind === "interrupted" ? this.closingChildSession(item.agentThreadId) : [];
23841
+ }
23842
+ if (item.type !== "collabAgentToolCall") return [];
23843
+ const closing = /* @__PURE__ */ new Map();
23844
+ for (const [threadId, state] of Object.entries(item.agentsStates)) {
23845
+ if (!state || terminalStateOf(state.status) === void 0) continue;
23846
+ for (const child of this.closingChildSession(threadId)) closing.set(threadId, child);
23847
+ }
23848
+ return [...closing.values()];
23849
+ }
23836
23850
  takeBufferedNotifications() {
23837
23851
  return this.replayQueue.splice(0);
23838
23852
  }
@@ -23908,6 +23922,10 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23908
23922
  isKnownChild(threadId) {
23909
23923
  return threadId !== this.rootSessionId && (this.children.has(threadId) || this.pendingSpawns.has(threadId) || this.terminalPendingSpawns.has(threadId));
23910
23924
  }
23925
+ closingChildSession(threadId) {
23926
+ const child = this.children.get(threadId);
23927
+ return child && child.terminalState === void 0 ? [{ threadId, sessionId: child.sessionId }] : [];
23928
+ }
23911
23929
  async materialize(childSessionId, path9) {
23912
23930
  if (this.children.has(childSessionId)) return;
23913
23931
  const pending = this.pendingSpawns.get(childSessionId);
@@ -24061,6 +24079,37 @@ function fallbackName(sessionId) {
24061
24079
  return `Agent ${suffix}`;
24062
24080
  }
24063
24081
 
24082
+ // src/RateLimitsMap.ts
24083
+ function rateLimitId(snapshot, fallback = "codex") {
24084
+ return snapshot.limitId ?? fallback;
24085
+ }
24086
+ function createRateLimitsMap(response) {
24087
+ const result = /* @__PURE__ */ new Map();
24088
+ const snapshots = Object.entries(response.rateLimitsByLimitId ?? {}).filter((entry) => entry[1] !== void 0);
24089
+ if (snapshots.length === 0) {
24090
+ snapshots.push([rateLimitId(response.rateLimits), response.rateLimits]);
24091
+ }
24092
+ for (const [fallbackId, snapshot] of snapshots) {
24093
+ const limitId = rateLimitId(snapshot, fallbackId);
24094
+ result.set(limitId, {
24095
+ limitId,
24096
+ limitName: snapshot.limitName ?? limitId,
24097
+ snapshot
24098
+ });
24099
+ }
24100
+ return result;
24101
+ }
24102
+ function mergeRateLimitSnapshot(previous, update) {
24103
+ return {
24104
+ ...update,
24105
+ limitId: update.limitId ?? "codex",
24106
+ credits: update.credits ?? previous.credits,
24107
+ individualLimit: update.individualLimit ?? previous.individualLimit,
24108
+ spendControlReached: update.spendControlReached ?? previous.spendControlReached,
24109
+ planType: update.planType ?? previous.planType
24110
+ };
24111
+ }
24112
+
24064
24113
  // src/CodexEventHandler.ts
24065
24114
  var MAX_SESSION_FAILURE_TITLE_LENGTH = 240;
24066
24115
  var SESSION_FAILURE_POLICY = {
@@ -24169,11 +24218,14 @@ var CodexEventHandler = class _CodexEventHandler {
24169
24218
  terminalCommandOutputIds = /* @__PURE__ */ new Set();
24170
24219
  agentMessagePhases = /* @__PURE__ */ new Map();
24171
24220
  subagents;
24221
+ /** Connection-level `authStatus` sink; the app-server account push feeds it. */
24222
+ onAccountUpdated;
24172
24223
  constructor(connection, sessionState, supportsPlanUpdates = false, supportsTypedSessionFailures = false, sessionFailureEpoch = randomUUID(), subagents = new CodexSubagentEventRouter(
24173
24224
  sessionState.sessionId,
24174
24225
  false,
24175
24226
  new ACPSessionConnection(connection, sessionState.sessionId)
24176
- )) {
24227
+ ), onAccountUpdated) {
24228
+ this.onAccountUpdated = onAccountUpdated;
24177
24229
  this.sessionState = sessionState;
24178
24230
  this.supportsPlanUpdates = supportsPlanUpdates;
24179
24231
  this.supportsTypedSessionFailures = supportsTypedSessionFailures;
@@ -24282,17 +24334,29 @@ var CodexEventHandler = class _CodexEventHandler {
24282
24334
  }
24283
24335
  async handleNotification(notification) {
24284
24336
  await this.flushPendingErrors();
24337
+ const closingChildren = this.subagents.closingChildSessions(notification);
24338
+ for (const child of closingChildren) {
24339
+ await this.sessionState.asyncTasks.reconcile(child.threadId, child.sessionId);
24340
+ }
24285
24341
  const handledBySubagents = await this.subagents.handle(notification);
24286
24342
  for (const buffered of this.subagents.takeBufferedNotifications()) {
24287
24343
  await this.handleNotification(buffered);
24288
24344
  }
24289
- if (handledBySubagents) {
24290
- return;
24345
+ const ignoredBySubagents = !handledBySubagents && this.subagents.shouldIgnore(notification);
24346
+ let updateEvent;
24347
+ if (!handledBySubagents && !ignoredBySubagents && notification.method === "item/started" && notification.params.item.type === "commandExecution") {
24348
+ updateEvent = await this.createUpdateEvent(notification);
24291
24349
  }
24292
- if (this.subagents.shouldIgnore(notification)) {
24293
- return;
24350
+ if (!handledBySubagents) {
24351
+ await this.sessionState.asyncTasks.handleNotification(
24352
+ notification,
24353
+ this.subagents.notificationSessionId(notification),
24354
+ toolCallTitle(updateEvent)
24355
+ );
24294
24356
  }
24295
- const updateEvent = await this.createUpdateEvent(notification);
24357
+ if (handledBySubagents) return;
24358
+ if (ignoredBySubagents) return;
24359
+ if (updateEvent === void 0) updateEvent = await this.createUpdateEvent(notification);
24296
24360
  if (updateEvent) {
24297
24361
  await this.session.update(updateEvent, this.subagents.notificationSessionId(notification));
24298
24362
  }
@@ -24397,6 +24461,9 @@ var CodexEventHandler = class _CodexEventHandler {
24397
24461
  case "account/rateLimits/updated":
24398
24462
  this.handleRateLimitsUpdated(notification.params);
24399
24463
  return null;
24464
+ case "account/updated":
24465
+ this.onAccountUpdated?.(notification.params);
24466
+ return null;
24400
24467
  case "configWarning":
24401
24468
  return await this.createConfigWarningEvent(notification.params);
24402
24469
  case "warning":
@@ -24447,7 +24514,6 @@ var CodexEventHandler = class _CodexEventHandler {
24447
24514
  case "turn/moderationMetadata":
24448
24515
  case "item/fileChange/outputDelta":
24449
24516
  case "item/fileChange/patchUpdated":
24450
- case "account/updated":
24451
24517
  case "fs/changed":
24452
24518
  case "mcpServer/startupStatus/updated":
24453
24519
  case "mcpServer/event/stream/notification":
@@ -25093,11 +25159,13 @@ ${event.stdin}
25093
25159
  if (!this.sessionState.rateLimits) {
25094
25160
  this.sessionState.rateLimits = /* @__PURE__ */ new Map();
25095
25161
  }
25096
- const limitId = params.rateLimits.limitId ?? params.rateLimits.limitName ?? "unknown";
25162
+ const limitId = params.rateLimits.limitId ?? "codex";
25163
+ const existingEntry = this.sessionState.rateLimits.get(limitId);
25164
+ const snapshot = existingEntry ? mergeRateLimitSnapshot(existingEntry.snapshot, params.rateLimits) : { ...params.rateLimits, limitId };
25097
25165
  this.sessionState.rateLimits.set(limitId, {
25098
25166
  limitId,
25099
- limitName: params.rateLimits.limitName ?? limitId,
25100
- snapshot: params.rateLimits
25167
+ limitName: snapshot.limitName ?? existingEntry?.limitName ?? limitId,
25168
+ snapshot
25101
25169
  });
25102
25170
  }
25103
25171
  handleFuzzyFileSearchSessionUpdated(params) {
@@ -25125,6 +25193,10 @@ ${event.stdin}
25125
25193
  return createGuardianApprovalReviewToolCall(params);
25126
25194
  }
25127
25195
  };
25196
+ function toolCallTitle(update) {
25197
+ if (update?.sessionUpdate !== "tool_call") return void 0;
25198
+ return update.title;
25199
+ }
25128
25200
 
25129
25201
  // src/permissions/option-ids.ts
25130
25202
  var ApprovalOptionId = {
@@ -27257,7 +27329,7 @@ var package_default = {
27257
27329
  publishConfig: {
27258
27330
  access: "public"
27259
27331
  },
27260
- version: "1.8.0-agentconnect.1",
27332
+ version: "1.10.0-agentconnect.1",
27261
27333
  description: "",
27262
27334
  main: "dist/index.js",
27263
27335
  bin: {
@@ -27319,7 +27391,7 @@ var package_default = {
27319
27391
  },
27320
27392
  dependencies: {
27321
27393
  "@agentclientprotocol/sdk": "^1.4.0",
27322
- "@openai/codex": "^0.152.0",
27394
+ "@openai/codex": "^0.153.3",
27323
27395
  diff: "^9.0.0",
27324
27396
  open: "^11.0.1",
27325
27397
  "vscode-jsonrpc": "^9.0.1",
@@ -27945,6 +28017,12 @@ var CodexAcpClient = class {
27945
28017
  modelProvider;
27946
28018
  permissionProfileConfig;
27947
28019
  gatewayConfig;
28020
+ /**
28021
+ * Where the stored gateway routing came from: the `gateway` auth method
28022
+ * (agent-owned authentication) or the ACP `providers/*` API (client-driven
28023
+ * routing). `authStatus` reports only the agent-owned one.
28024
+ */
28025
+ gatewayConfigSource;
27948
28026
  pendingLoginCompleted = null;
27949
28027
  pendingAccountUpdated = null;
27950
28028
  sessionNotificationQueues = /* @__PURE__ */ new Map();
@@ -27957,6 +28035,7 @@ var CodexAcpClient = class {
27957
28035
  this.modelProvider = modelProvider ?? null;
27958
28036
  this.permissionProfileConfig = permissionProfileConfig;
27959
28037
  this.gatewayConfig = null;
28038
+ this.gatewayConfigSource = null;
27960
28039
  this.subagents = new CodexSubagentSubscriptions(codexClient);
27961
28040
  }
27962
28041
  get appServerClient() {
@@ -27989,6 +28068,7 @@ var CodexAcpClient = class {
27989
28068
  throw RequestError.invalidRequest();
27990
28069
  }
27991
28070
  this.gatewayConfig = null;
28071
+ this.gatewayConfigSource = null;
27992
28072
  switch (authRequest.methodId) {
27993
28073
  case "api-key":
27994
28074
  return await this.authenticateWithApiKey(authRequest);
@@ -28072,7 +28152,7 @@ var CodexAcpClient = class {
28072
28152
  apiType: GatewayAuthMethod._meta.gateway.protocol,
28073
28153
  headers: gatewaySettings.headers,
28074
28154
  providerName: gatewaySettings.providerName
28075
- });
28155
+ }, "authentication");
28076
28156
  return true;
28077
28157
  }
28078
28158
  readApiKeyFromEnv() {
@@ -28118,6 +28198,11 @@ var CodexAcpClient = class {
28118
28198
  };
28119
28199
  }
28120
28200
  }
28201
+ /**
28202
+ * The provider that actually serves requests, ACP-configured gateway
28203
+ * routing included. Use {@link getAgentConfiguredModelProvider} instead
28204
+ * when asking what the agent itself is configured with (`authStatus`).
28205
+ */
28121
28206
  async getCurrentModelProvider() {
28122
28207
  const sessionModelProvider = this.getModelProvider();
28123
28208
  if (sessionModelProvider !== null) {
@@ -28143,7 +28228,7 @@ var CodexAcpClient = class {
28143
28228
  * method and the ACP `providers/set` method. Throws `invalid_params` for an
28144
28229
  * unsupported protocol or a malformed base URL.
28145
28230
  */
28146
- applyGatewayConfig(params) {
28231
+ applyGatewayConfig(params, source) {
28147
28232
  const apiType = params.apiType;
28148
28233
  const wireApi = SUPPORTED_GATEWAY_PROTOCOLS[apiType];
28149
28234
  if (!wireApi) {
@@ -28160,6 +28245,7 @@ var CodexAcpClient = class {
28160
28245
  "X-Client-Feature-ID": "codex",
28161
28246
  ...params.headers
28162
28247
  };
28248
+ this.gatewayConfigSource = source;
28163
28249
  this.gatewayConfig = {
28164
28250
  modelProvider: CUSTOM_GATEWAY_PROVIDER_ID,
28165
28251
  config: {
@@ -28224,7 +28310,7 @@ var CodexAcpClient = class {
28224
28310
  apiType: request.apiType,
28225
28311
  baseUrl: request.baseUrl,
28226
28312
  headers: request.headers
28227
- });
28313
+ }, "acpProviders");
28228
28314
  logger.log("providers/set applied", {
28229
28315
  providerId: request.providerId,
28230
28316
  apiType: request.apiType,
@@ -28240,6 +28326,7 @@ var CodexAcpClient = class {
28240
28326
  const overrideWasActive = this.gatewayConfig !== null;
28241
28327
  if (request.providerId === OPENAI_PROVIDER_ID) {
28242
28328
  this.gatewayConfig = null;
28329
+ this.gatewayConfigSource = null;
28243
28330
  }
28244
28331
  const current = this.gatewayConfig ? {
28245
28332
  apiType: gatewayApiTypeFromConfig(this.gatewayConfig),
@@ -28257,6 +28344,36 @@ var CodexAcpClient = class {
28257
28344
  async getAccount() {
28258
28345
  return this.codexClient.accountRead({ refreshToken: false });
28259
28346
  }
28347
+ async getRateLimits() {
28348
+ return this.codexClient.accountRateLimitsRead();
28349
+ }
28350
+ /**
28351
+ * Presentable name of the gateway the agent itself authenticated against
28352
+ * (the `gateway` auth method), or `null`. Routing that the client
28353
+ * configured through `providers/set` is deliberately not reported here:
28354
+ * `authStatus` describes the agent-owned login only.
28355
+ */
28356
+ getAuthGatewayProviderName() {
28357
+ return this.gatewayConfigSource === "authentication" ? this.gatewayConfig?.config.name ?? null : null;
28358
+ }
28359
+ /** Whether this provider id is client-driven routing set through `providers/set`. */
28360
+ isClientConfiguredProvider(providerId) {
28361
+ return providerId === CUSTOM_GATEWAY_PROVIDER_ID && this.gatewayConfigSource === "acpProviders";
28362
+ }
28363
+ /**
28364
+ * The model provider the agent itself is configured with (launch option or
28365
+ * Codex config), ignoring any ACP-configured gateway routing. The
28366
+ * routing-aware counterpart is {@link getCurrentModelProvider}.
28367
+ */
28368
+ async getAgentConfiguredModelProvider() {
28369
+ const provider = this.getModelProvider();
28370
+ const agentProvider = this.isClientConfiguredProvider(provider) ? this.modelProvider : provider;
28371
+ if (agentProvider !== null) {
28372
+ return agentProvider;
28373
+ }
28374
+ const settingsModelProvider = await this.codexClient.configRead({ includeLayers: false });
28375
+ return settingsModelProvider?.config?.model_provider ?? null;
28376
+ }
28260
28377
  async resumeSession(request, onSubscribed) {
28261
28378
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
28262
28379
  const initialAgentMode = AgentMode.getInitialAgentMode();
@@ -29478,6 +29595,12 @@ var CodexAppServerClient = class {
29478
29595
  async threadCompactStart(params) {
29479
29596
  return await this.sendRequest({ method: "thread/compact/start", params });
29480
29597
  }
29598
+ async threadBackgroundTerminalsList(params) {
29599
+ return await this.sendRequest({ method: "thread/backgroundTerminals/list", params });
29600
+ }
29601
+ async threadBackgroundTerminalsTerminate(params) {
29602
+ return await this.sendRequest({ method: "thread/backgroundTerminals/terminate", params });
29603
+ }
29481
29604
  async threadGoalSet(params) {
29482
29605
  return await this.sendRequest({ method: "thread/goal/set", params });
29483
29606
  }
@@ -29543,6 +29666,9 @@ var CodexAppServerClient = class {
29543
29666
  async accountRead(params) {
29544
29667
  return await this.sendRequest({ method: "account/read", params });
29545
29668
  }
29669
+ async accountRateLimitsRead() {
29670
+ return await this.sendRequest({ method: "account/rateLimits/read", params: void 0 });
29671
+ }
29546
29672
  //TODO create type-safe helper
29547
29673
  async awaitTurnCompleted(threadId, turnId) {
29548
29674
  return await new Promise((resolve) => {
@@ -30149,6 +30275,7 @@ var CodexCommands = class {
30149
30275
  return { handled: true, turnCompleted };
30150
30276
  }
30151
30277
  case "status": {
30278
+ await this.refreshRateLimits(sessionState);
30152
30279
  const session = new ACPSessionConnection(this.connection, sessionId);
30153
30280
  const message = this.buildStatusMessage(sessionState);
30154
30281
  await session.update(createAgentTextMessageChunk(message));
@@ -30297,6 +30424,16 @@ var CodexCommands = class {
30297
30424
  ];
30298
30425
  return lines.join(" \n");
30299
30426
  }
30427
+ async refreshRateLimits(sessionState) {
30428
+ try {
30429
+ const response = await this.runWithProcessCheck(() => this.codexAcpClient.getRateLimits());
30430
+ if (response) {
30431
+ sessionState.rateLimits = createRateLimitsMap(response);
30432
+ }
30433
+ } catch (err) {
30434
+ logger.error(`Failed to refresh rate limits for session ${sessionState.sessionId}`, err);
30435
+ }
30436
+ }
30300
30437
  formatAccountInfo(account) {
30301
30438
  if (!account) {
30302
30439
  return "not logged in";
@@ -30327,10 +30464,10 @@ var CodexCommands = class {
30327
30464
  return "data not available yet";
30328
30465
  }
30329
30466
  const used = usage.totalTokens;
30330
- const percentLeft = Math.round((contextWindow - used) / contextWindow * 100);
30467
+ const percentUsed = Math.round(used / contextWindow * 100);
30331
30468
  const usedFormatted = this.formatTokenCount(used);
30332
30469
  const totalFormatted = this.formatTokenCount(contextWindow);
30333
- return `${percentLeft}% left (${usedFormatted} used / ${totalFormatted})`;
30470
+ return `${percentUsed}% used (${usedFormatted} used / ${totalFormatted})`;
30334
30471
  }
30335
30472
  formatRateLimitLines(rateLimits) {
30336
30473
  if (!rateLimits || rateLimits.size === 0) {
@@ -30364,8 +30501,31 @@ var CodexCommands = class {
30364
30501
  lines.push(`**${prefix}Credits:** ${rateLimits.credits.balance}`);
30365
30502
  }
30366
30503
  }
30504
+ if (rateLimits.individualLimit) {
30505
+ const limit = rateLimits.individualLimit;
30506
+ const used = this.formatCreditAmount(limit.used);
30507
+ const total = this.formatCreditAmount(limit.limit);
30508
+ if (used !== null && total !== null) {
30509
+ const percentLeft = Math.round(Math.min(100, Math.max(0, limit.remainingPercent)));
30510
+ const resetDate = new Date(limit.resetsAt * 1e3).toLocaleDateString("en-US", { month: "short", day: "numeric" });
30511
+ lines.push(
30512
+ `**${prefix}individual spend limit:** ${percentLeft}% left (${used} of ${total} credits used; resets ${resetDate})`
30513
+ );
30514
+ }
30515
+ }
30367
30516
  return lines;
30368
30517
  }
30518
+ formatCreditAmount(raw) {
30519
+ const trimmed = raw.trim();
30520
+ if (trimmed.length === 0) {
30521
+ return null;
30522
+ }
30523
+ const value = Number(trimmed);
30524
+ if (!Number.isFinite(value) || value < 0) {
30525
+ return null;
30526
+ }
30527
+ return Math.round(value).toLocaleString("en-US");
30528
+ }
30369
30529
  formatWindowLabel(windowDurationMins) {
30370
30530
  if (windowDurationMins === null) {
30371
30531
  return "Limit";
@@ -31412,11 +31572,147 @@ function numberValue(value) {
31412
31572
  return typeof value === "number" && Number.isFinite(value) ? value : null;
31413
31573
  }
31414
31574
 
31575
+ // src/async-tasks/AsyncTaskExtension.ts
31576
+ var ASYNC_TASK_STOP_METHOD = "_session/async_task/stop";
31577
+
31578
+ // src/AuthStatusMeta.ts
31579
+ var AUTH_STATUS_UPDATE_METHOD = "_auth/status_update";
31580
+ var AUTH_STATUS_META_KEY = "authStatus";
31581
+ function authStatusCapability() {
31582
+ return {};
31583
+ }
31584
+ var NOT_LOGGED_IN_LABEL = "Not logged in";
31585
+ var DEFAULT_GATEWAY_LABEL = "Custom model gateway";
31586
+ var PLAN_DISPLAY_NAMES = {
31587
+ free: "Free",
31588
+ go: "Go",
31589
+ plus: "Plus",
31590
+ pro: "Pro",
31591
+ team: "Team",
31592
+ business: "Business",
31593
+ enterprise: "Enterprise",
31594
+ edu: "Edu"
31595
+ };
31596
+ function planTypePresentable(planType) {
31597
+ if (!planType || planType === "unknown") {
31598
+ return null;
31599
+ }
31600
+ return PLAN_DISPLAY_NAMES[planType] ?? capitalize2(planType);
31601
+ }
31602
+ function capitalize2(value) {
31603
+ return value.charAt(0).toUpperCase() + value.slice(1);
31604
+ }
31605
+ function chatGptLabel(planType) {
31606
+ const plan = planTypePresentable(planType);
31607
+ return plan === null ? "ChatGPT" : `ChatGPT ${plan}`;
31608
+ }
31609
+ function gatewayStatus(providerName) {
31610
+ const detail = typeof providerName === "string" && providerName.trim().length > 0 ? providerName.trim() : void 0;
31611
+ return {
31612
+ kind: "gateway",
31613
+ label: DEFAULT_GATEWAY_LABEL,
31614
+ ...detail === void 0 ? {} : { detail }
31615
+ };
31616
+ }
31617
+ function unauthenticatedStatus() {
31618
+ return {
31619
+ kind: "none",
31620
+ label: NOT_LOGGED_IN_LABEL
31621
+ };
31622
+ }
31623
+ function fromAccount(account) {
31624
+ if (account === null) {
31625
+ return unauthenticatedStatus();
31626
+ }
31627
+ switch (account.type) {
31628
+ case "chatgpt": {
31629
+ const accountInfo = {};
31630
+ if (account.email) {
31631
+ accountInfo.email = account.email;
31632
+ }
31633
+ if (account.planType) {
31634
+ accountInfo.plan = account.planType;
31635
+ }
31636
+ return {
31637
+ kind: "account",
31638
+ label: chatGptLabel(account.planType),
31639
+ ...Object.keys(accountInfo).length > 0 ? { account: accountInfo } : {}
31640
+ };
31641
+ }
31642
+ case "apiKey":
31643
+ return {
31644
+ kind: "api_key",
31645
+ label: "OpenAI API key"
31646
+ };
31647
+ case "amazonBedrock":
31648
+ return {
31649
+ kind: "external",
31650
+ label: "AWS Bedrock"
31651
+ };
31652
+ }
31653
+ }
31654
+ function fromAccountUpdated(notification, previous) {
31655
+ const authMode = notification.authMode;
31656
+ if (authMode === null) {
31657
+ return unauthenticatedStatus();
31658
+ }
31659
+ switch (authMode) {
31660
+ case "chatgpt":
31661
+ case "chatgptAuthTokens": {
31662
+ const status = {
31663
+ kind: "account",
31664
+ label: chatGptLabel(notification.planType)
31665
+ };
31666
+ const accountInfo = {};
31667
+ const previousEmail = previous?.kind === "account" ? previous.account?.email : void 0;
31668
+ if (previousEmail) {
31669
+ accountInfo.email = previousEmail;
31670
+ }
31671
+ if (notification.planType) {
31672
+ accountInfo.plan = notification.planType;
31673
+ }
31674
+ if (Object.keys(accountInfo).length > 0) {
31675
+ status.account = accountInfo;
31676
+ }
31677
+ return status;
31678
+ }
31679
+ case "apikey":
31680
+ return {
31681
+ kind: "api_key",
31682
+ label: "OpenAI API key"
31683
+ };
31684
+ case "personalAccessToken":
31685
+ return {
31686
+ kind: "api_key",
31687
+ label: "OpenAI personal access token"
31688
+ };
31689
+ case "bedrockApiKey":
31690
+ case "bedrockAccessKeys":
31691
+ return {
31692
+ kind: "external",
31693
+ label: "AWS Bedrock"
31694
+ };
31695
+ case "agentIdentity":
31696
+ return {
31697
+ kind: "external",
31698
+ label: "Agent identity"
31699
+ };
31700
+ case "headers":
31701
+ return gatewayStatus();
31702
+ }
31703
+ }
31704
+ function sameAuthStatus(previous, next) {
31705
+ if (!previous) {
31706
+ return false;
31707
+ }
31708
+ return previous.kind === next.kind && previous.label === next.label && previous.detail === next.detail && previous.account?.email === next.account?.email && previous.account?.organization === next.account?.organization && previous.account?.plan === next.account?.plan && JSON.stringify(previous.vendor) === JSON.stringify(next.vendor);
31709
+ }
31710
+
31415
31711
  // src/AcpExtensions.ts
31416
31712
  var LEGACY_SET_SESSION_MODEL_METHOD = "session/set_model";
31417
31713
  var SESSION_STEERING_METHOD = "_session/steering";
31418
31714
  function isExtMethodRequest(request) {
31419
- return request.method === "authentication/status" || request.method === "authentication/logout" || request.method === LEGACY_SET_SESSION_MODEL_METHOD || request.method === GOAL_CONTROL_METHOD || request.method === LEGACY_GOAL_CONTROL_METHOD || request.method === SESSION_STEERING_METHOD;
31715
+ return request.method === "authentication/status" || request.method === "authentication/logout" || request.method === LEGACY_SET_SESSION_MODEL_METHOD || request.method === GOAL_CONTROL_METHOD || request.method === LEGACY_GOAL_CONTROL_METHOD || request.method === SESSION_STEERING_METHOD || request.method === ASYNC_TASK_STOP_METHOD;
31420
31716
  }
31421
31717
 
31422
31718
  // src/FastModeConfig.ts
@@ -31483,6 +31779,15 @@ function clientSupportsPlanUpdates(clientCapabilities) {
31483
31779
  return clientCapabilities?.plan != null;
31484
31780
  }
31485
31781
 
31782
+ // src/subagents/AcpSubagents.ts
31783
+ function clientSupportsSubagents(capabilities) {
31784
+ const subagents = capabilities?.subagents;
31785
+ if (typeof subagents === "object" && subagents !== null && !Array.isArray(subagents)) {
31786
+ return true;
31787
+ }
31788
+ return clientSupportsAirCapability(capabilities, AIR_NATIVE_SUBAGENT_SESSIONS_KEY);
31789
+ }
31790
+
31486
31791
  // src/CodexAcpServer.ts
31487
31792
  import { randomUUID as randomUUID2 } from "node:crypto";
31488
31793
 
@@ -31571,6 +31876,332 @@ function extractTitle(turn) {
31571
31876
 
31572
31877
  // src/CodexAcpServer.ts
31573
31878
  import { once } from "node:events";
31879
+
31880
+ // src/async-tasks/CodexBackgroundTerminalTasks.ts
31881
+ var CodexBackgroundTerminalTasks = class {
31882
+ constructor(enabled, rootSessionId, appServer, session) {
31883
+ this.enabled = enabled;
31884
+ this.rootSessionId = rootSessionId;
31885
+ this.appServer = appServer;
31886
+ this.session = session;
31887
+ }
31888
+ enabled;
31889
+ rootSessionId;
31890
+ appServer;
31891
+ session;
31892
+ tasks = /* @__PURE__ */ new Map();
31893
+ syncs = /* @__PURE__ */ new Map();
31894
+ appServerGeneration = 0;
31895
+ appServerQueriesEnabled = true;
31896
+ disposed = false;
31897
+ async handleNotification(notification, sessionId, commandTitle2) {
31898
+ if (!this.isActive()) return;
31899
+ const threadId = notificationThreadId(notification);
31900
+ if (threadId === null) return;
31901
+ if (notification.method === "item/started" && notification.params.item.type === "commandExecution") {
31902
+ this.observeCommandStarted(notification.params.item, threadId, sessionId, commandTitle2);
31903
+ return;
31904
+ }
31905
+ if (notification.method === "item/completed" && notification.params.item.type === "commandExecution") {
31906
+ await this.observeCommandCompleted(notification.params.item, threadId);
31907
+ return;
31908
+ }
31909
+ if (notification.method === "turn/completed") {
31910
+ await this.reconcile(threadId, sessionId);
31911
+ return;
31912
+ }
31913
+ if (notification.method === "item/started") {
31914
+ this.refresh(threadId, sessionId);
31915
+ }
31916
+ }
31917
+ observeCommandStarted(item, threadId, sessionId, commandTitle2) {
31918
+ if (!this.isActive() || item.processId === null) return;
31919
+ this.remember(threadId, sessionId, {
31920
+ itemId: item.id,
31921
+ processId: item.processId,
31922
+ command: item.command
31923
+ }, commandTitle2);
31924
+ }
31925
+ async observeCommandCompleted(item, threadId) {
31926
+ if (!this.isActive()) return;
31927
+ const task = this.tasks.get(wireTaskId(this.rootSessionId, threadId, item.id));
31928
+ if (task) await this.finish(task, item.status === "completed" ? "completed" : "failed");
31929
+ }
31930
+ refresh(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31931
+ void this.reconcile(threadId, sessionId);
31932
+ }
31933
+ async reconcile(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31934
+ try {
31935
+ await this.sync(threadId, sessionId);
31936
+ } catch (error51) {
31937
+ if (this.isActive()) logger.error(`Failed to list background terminals for ${threadId}`, error51);
31938
+ }
31939
+ }
31940
+ setAppServer(appServer) {
31941
+ this.appServer = appServer;
31942
+ this.appServerGeneration += 1;
31943
+ this.appServerQueriesEnabled = true;
31944
+ }
31945
+ prepareForAppServerReplacement() {
31946
+ this.appServerGeneration += 1;
31947
+ this.appServerQueriesEnabled = false;
31948
+ }
31949
+ async recover(threadId, sessionId, itemIds) {
31950
+ if (!this.canQueryAppServer() || itemIds.size === 0) return;
31951
+ const snapshot = await this.listAll(threadId);
31952
+ if (snapshot === null) return;
31953
+ for (const terminal of snapshot.terminals) {
31954
+ if (!this.queryIsCurrent(snapshot.generation)) return;
31955
+ if (!itemIds.has(terminal.itemId)) continue;
31956
+ const task = this.remember(threadId, sessionId, terminal);
31957
+ if (task.publication === "unpublished" && task.state === "running") await this.announce(task);
31958
+ }
31959
+ }
31960
+ async finishAll(state) {
31961
+ if (!this.isActive()) return;
31962
+ const errors = [];
31963
+ for (const task of this.tasks.values()) {
31964
+ try {
31965
+ await this.finish(task, state);
31966
+ } catch (error51) {
31967
+ errors.push(error51);
31968
+ }
31969
+ }
31970
+ if (errors.length > 0) {
31971
+ throw new AggregateError(errors, `Failed to finish ${errors.length} background terminal task(s)`);
31972
+ }
31973
+ }
31974
+ async sync(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31975
+ if (!this.canQueryAppServer()) return;
31976
+ const current = this.syncs.get(threadId);
31977
+ if (current) {
31978
+ current.requested = true;
31979
+ current.sessionId = sessionId;
31980
+ return await current.promise;
31981
+ }
31982
+ const pending = {
31983
+ requested: false,
31984
+ sessionId,
31985
+ promise: Promise.resolve()
31986
+ };
31987
+ pending.promise = this.syncUntilCurrent(threadId, pending).finally(() => {
31988
+ if (this.syncs.get(threadId) === pending) this.syncs.delete(threadId);
31989
+ });
31990
+ this.syncs.set(threadId, pending);
31991
+ await pending.promise;
31992
+ }
31993
+ async stop(taskId) {
31994
+ if (!this.isActive()) return false;
31995
+ const task = this.tasks.get(taskId);
31996
+ if (!task || task.publication !== "published") return false;
31997
+ if (task.state === "stopped" && !task.terminalPublished) {
31998
+ await this.publishTerminalState(task);
31999
+ return true;
32000
+ }
32001
+ if (task.state !== "running") return false;
32002
+ task.state = "stopping";
32003
+ try {
32004
+ const response = await this.appServer.threadBackgroundTerminalsTerminate({
32005
+ threadId: task.threadId,
32006
+ processId: task.processId
32007
+ });
32008
+ if (!response.terminated) {
32009
+ if (task.state === "stopping") task.state = "running";
32010
+ return false;
32011
+ }
32012
+ await this.finish(task, "stopped");
32013
+ return true;
32014
+ } catch (error51) {
32015
+ if (task.state === "stopping") task.state = "running";
32016
+ throw error51;
32017
+ }
32018
+ }
32019
+ clear() {
32020
+ this.disposed = true;
32021
+ this.appServerGeneration += 1;
32022
+ this.appServerQueriesEnabled = false;
32023
+ this.tasks.clear();
32024
+ this.syncs.clear();
32025
+ }
32026
+ async syncThread(threadId, sessionId) {
32027
+ const snapshot = await this.listAll(threadId);
32028
+ if (snapshot === null || !this.queryIsCurrent(snapshot.generation)) return;
32029
+ const liveTaskIds = /* @__PURE__ */ new Set();
32030
+ for (const terminal of snapshot.terminals) {
32031
+ if (!this.queryIsCurrent(snapshot.generation)) return;
32032
+ liveTaskIds.add(terminal.itemId);
32033
+ const task = this.remember(threadId, sessionId, terminal);
32034
+ if (task.publication === "unpublished" && task.state === "running") await this.announce(task);
32035
+ }
32036
+ for (const task of this.tasks.values()) {
32037
+ if (!this.queryIsCurrent(snapshot.generation)) return;
32038
+ if (task.threadId === threadId && task.publication === "published" && (task.state === "running" || task.state === "stopping") && !liveTaskIds.has(task.itemId)) {
32039
+ await this.finish(task, "stopped");
32040
+ }
32041
+ }
32042
+ }
32043
+ async syncUntilCurrent(threadId, pending) {
32044
+ let hasFailure = false;
32045
+ let failure;
32046
+ do {
32047
+ pending.requested = false;
32048
+ try {
32049
+ await this.syncThread(threadId, pending.sessionId);
32050
+ hasFailure = false;
32051
+ } catch (error51) {
32052
+ hasFailure = true;
32053
+ failure = error51;
32054
+ }
32055
+ } while (pending.requested && this.canQueryAppServer());
32056
+ if (hasFailure) throw failure;
32057
+ }
32058
+ async announce(task) {
32059
+ if (task.publication === "published") return;
32060
+ if (task.announcement !== null) {
32061
+ await task.announcement;
32062
+ return;
32063
+ }
32064
+ task.publication = "publishing";
32065
+ const announcement = this.publishAnnouncement(task);
32066
+ task.announcement = announcement;
32067
+ try {
32068
+ await announcement;
32069
+ } finally {
32070
+ if (task.announcement === announcement) task.announcement = null;
32071
+ }
32072
+ if (isTerminalState(task.state)) await this.publishTerminalState(task);
32073
+ }
32074
+ async publishAnnouncement(task) {
32075
+ try {
32076
+ await this.publishSpawn(task);
32077
+ task.publication = "published";
32078
+ } catch (error51) {
32079
+ task.publication = "unpublished";
32080
+ throw error51;
32081
+ }
32082
+ }
32083
+ async publishSpawn(task) {
32084
+ await this.session.update({
32085
+ sessionUpdate: "tool_call_update",
32086
+ toolCallId: task.itemId,
32087
+ _meta: {
32088
+ [JETBRAINS_META_KEY]: {
32089
+ [AIR_META_KEY]: {
32090
+ [AIR_ASYNC_TASKS_KEY]: {
32091
+ [AIR_ASYNC_TASKS_BACKGROUNDED_KEY]: true
32092
+ }
32093
+ }
32094
+ }
32095
+ }
32096
+ }, task.sessionId);
32097
+ await this.session.update({
32098
+ sessionUpdate: "async_task_spawned",
32099
+ asyncTaskId: task.asyncTaskId,
32100
+ name: task.name,
32101
+ taskType: "shell",
32102
+ showInTranscript: false,
32103
+ canStop: true,
32104
+ toolCallId: task.itemId
32105
+ }, task.sessionId);
32106
+ }
32107
+ remember(threadId, sessionId, terminal, commandTitle2) {
32108
+ const asyncTaskId = wireTaskId(this.rootSessionId, threadId, terminal.itemId);
32109
+ const existing = this.tasks.get(asyncTaskId);
32110
+ if (existing) {
32111
+ existing.processId = terminal.processId;
32112
+ if (commandTitle2 !== void 0) existing.name = commandTitle2;
32113
+ return existing;
32114
+ }
32115
+ const task = {
32116
+ threadId,
32117
+ sessionId,
32118
+ asyncTaskId,
32119
+ processId: terminal.processId,
32120
+ itemId: terminal.itemId,
32121
+ name: commandTitle2 ?? terminal.command,
32122
+ publication: "unpublished",
32123
+ announcement: null,
32124
+ terminalUpdate: null,
32125
+ terminalPublished: false,
32126
+ state: "running"
32127
+ };
32128
+ this.tasks.set(asyncTaskId, task);
32129
+ return task;
32130
+ }
32131
+ async finish(task, state) {
32132
+ if (task.state === "running" || task.state === "stopping") {
32133
+ task.state = state;
32134
+ } else if (task.state !== state) {
32135
+ return;
32136
+ }
32137
+ if (task.announcement !== null) await task.announcement;
32138
+ if (task.publication === "published") await this.publishTerminalState(task);
32139
+ }
32140
+ async publishTerminalState(task) {
32141
+ if (!isTerminalState(task.state) || task.terminalPublished) return;
32142
+ if (task.terminalUpdate !== null) {
32143
+ await task.terminalUpdate;
32144
+ return;
32145
+ }
32146
+ const terminalUpdate = this.session.update({
32147
+ sessionUpdate: "async_task_state_update",
32148
+ asyncTaskId: task.asyncTaskId,
32149
+ state: task.state,
32150
+ toolCallId: task.itemId
32151
+ }, task.sessionId);
32152
+ task.terminalUpdate = terminalUpdate;
32153
+ try {
32154
+ await terminalUpdate;
32155
+ task.terminalPublished = true;
32156
+ } finally {
32157
+ if (task.terminalUpdate === terminalUpdate) task.terminalUpdate = null;
32158
+ }
32159
+ }
32160
+ async listAll(threadId) {
32161
+ const appServer = this.appServer;
32162
+ const generation = this.appServerGeneration;
32163
+ const terminals = [];
32164
+ const seenCursors = /* @__PURE__ */ new Set();
32165
+ let cursor = null;
32166
+ do {
32167
+ const response = await appServer.threadBackgroundTerminalsList({
32168
+ threadId,
32169
+ cursor
32170
+ });
32171
+ if (!this.queryIsCurrent(generation)) return null;
32172
+ terminals.push(...response.data);
32173
+ cursor = response.nextCursor;
32174
+ if (cursor !== null) {
32175
+ if (seenCursors.has(cursor)) {
32176
+ throw new Error("Codex returned a repeated background terminal cursor");
32177
+ }
32178
+ seenCursors.add(cursor);
32179
+ }
32180
+ } while (cursor !== null);
32181
+ return { generation, terminals };
32182
+ }
32183
+ isActive() {
32184
+ return this.enabled && !this.disposed;
32185
+ }
32186
+ canQueryAppServer() {
32187
+ return this.isActive() && this.appServerQueriesEnabled;
32188
+ }
32189
+ queryIsCurrent(generation) {
32190
+ return this.canQueryAppServer() && generation === this.appServerGeneration;
32191
+ }
32192
+ };
32193
+ function isTerminalState(state) {
32194
+ return state === "completed" || state === "failed" || state === "stopped";
32195
+ }
32196
+ function wireTaskId(rootSessionId, threadId, itemId) {
32197
+ return threadId === rootSessionId ? itemId : `${threadId}:${itemId}`;
32198
+ }
32199
+ function notificationThreadId(notification) {
32200
+ const threadId = notification.params.threadId;
32201
+ return typeof threadId === "string" ? threadId : null;
32202
+ }
32203
+
32204
+ // src/CodexAcpServer.ts
31574
32205
  var CODEX_PROCESS_EXITED_ERROR_CODE = 1001;
31575
32206
  function clientSupportsTypedSessionFailures(capabilities) {
31576
32207
  return clientSupportsAirCapability(capabilities, AIR_SESSION_FAILURE_KEY);
@@ -31595,6 +32226,8 @@ var CodexAcpServer = class _CodexAcpServer {
31595
32226
  clientCapabilities;
31596
32227
  terminalOutputMode;
31597
32228
  booleanConfigOptionsSupported;
32229
+ /** Last `authStatus` pushed to the client; used to suppress duplicates. */
32230
+ currentAuthStatus;
31598
32231
  sessions;
31599
32232
  pendingMcpStartupSessions;
31600
32233
  pendingTurnStarts;
@@ -31606,6 +32239,7 @@ var CodexAcpServer = class _CodexAcpServer {
31606
32239
  goalControlGenerations;
31607
32240
  permissionLifecycleContexts;
31608
32241
  codexProcessState;
32242
+ codexProcessGeneration = 0;
31609
32243
  initializeRequest = null;
31610
32244
  providerUpdate = null;
31611
32245
  constructor(connection, codexAcpClient, defaultAuthRequest, getExitCode, getRecentStderr, codexProcessState) {
@@ -31631,14 +32265,16 @@ var CodexAcpServer = class _CodexAcpServer {
31631
32265
  this.clientCapabilities = null;
31632
32266
  this.terminalOutputMode = "terminal_output_delta";
31633
32267
  this.booleanConfigOptionsSupported = false;
32268
+ this.currentAuthStatus = null;
31634
32269
  this.availableCommands = this.createAvailableCommands(codexAcpClient);
32270
+ this.observeCodexProcess();
31635
32271
  }
31636
32272
  createAvailableCommands(client) {
31637
32273
  return new CodexCommands(
31638
32274
  this.connection,
31639
32275
  client,
31640
32276
  (operation) => this.runWithProcessCheck(operation),
31641
- () => this.refreshSessionsAuthState(null)
32277
+ () => this.refreshAuthState(null)
31642
32278
  );
31643
32279
  }
31644
32280
  async initialize(_params) {
@@ -31649,6 +32285,7 @@ var CodexAcpServer = class _CodexAcpServer {
31649
32285
  this.terminalOutputMode = resolveTerminalOutputMode(_params.clientCapabilities);
31650
32286
  this.booleanConfigOptionsSupported = clientSupportsBooleanConfigOptions(_params.clientCapabilities);
31651
32287
  await this.runWithProcessCheck(() => this.codexAcpClient.initialize(_params));
32288
+ this.publishFirstAuthStatusAfterResponse();
31652
32289
  const sessionCapabilities = {
31653
32290
  resume: {},
31654
32291
  list: {},
@@ -31680,6 +32317,11 @@ var CodexAcpServer = class _CodexAcpServer {
31680
32317
  acp: false,
31681
32318
  http: true,
31682
32319
  sse: false
32320
+ },
32321
+ _meta: {
32322
+ // Presence means "this agent pushes `_auth/status_update`". It
32323
+ // never carries a payload, and the client never asks for one.
32324
+ [AUTH_STATUS_META_KEY]: authStatusCapability()
31683
32325
  }
31684
32326
  },
31685
32327
  authMethods: getCodexAuthMethods(_params.clientCapabilities),
@@ -31698,7 +32340,8 @@ var CodexAcpServer = class _CodexAcpServer {
31698
32340
  [AIR_EXTENSION_CAPABILITIES_KEY]: [
31699
32341
  AIR_SESSION_FAILURE_KEY,
31700
32342
  AIR_AGENT_FILE_CHANGE_REPORT_KEY,
31701
- AIR_NATIVE_SUBAGENT_SESSIONS_KEY
32343
+ AIR_NATIVE_SUBAGENT_SESSIONS_KEY,
32344
+ AIR_ASYNC_TASKS_KEY
31702
32345
  ]
31703
32346
  }
31704
32347
  }
@@ -31721,6 +32364,18 @@ var CodexAcpServer = class _CodexAcpServer {
31721
32364
  return await this.unstable_setSessionModel(this.parseLegacySetSessionModelParams(methodRequest.params));
31722
32365
  case SESSION_STEERING_METHOD:
31723
32366
  return await this.executeOrQueueSteeringRequest(this.parseSessionSteerParams(methodRequest.params));
32367
+ case ASYNC_TASK_STOP_METHOD: {
32368
+ if (this.providerUpdate !== null) {
32369
+ await this.providerUpdate;
32370
+ }
32371
+ const sessionState = this.sessions.get(methodRequest.params.sessionId);
32372
+ if (!sessionState) return { stopped: false };
32373
+ return {
32374
+ stopped: await this.runWithProcessCheck(
32375
+ () => sessionState.asyncTasks.stop(methodRequest.params.asyncTaskId)
32376
+ )
32377
+ };
32378
+ }
31724
32379
  case GOAL_CONTROL_METHOD:
31725
32380
  case LEGACY_GOAL_CONTROL_METHOD: {
31726
32381
  const sessionState = this.sessions.get(methodRequest.params.sessionId);
@@ -31811,7 +32466,7 @@ var CodexAcpServer = class _CodexAcpServer {
31811
32466
  async handleError(e) {
31812
32467
  if (e.message.includes("log out") || e.message.includes("cloud requirements")) {
31813
32468
  await this.runWithProcessCheck(() => this.codexAcpClient.logout());
31814
- await this.refreshSessionsAuthState(null);
32469
+ await this.refreshAuthState(null);
31815
32470
  throw RequestError.internalError(`${e.message}
31816
32471
 
31817
32472
  You have been logged out. Please try again.`);
@@ -31962,7 +32617,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
31962
32617
  sessionId,
31963
32618
  clientSupportsSubagents(this.clientCapabilities),
31964
32619
  new ACPSessionConnection(this.connection, sessionId)
31965
- )
32620
+ ),
32621
+ asyncTasks: this.createAsyncTasks(sessionId)
31966
32622
  };
31967
32623
  sessionState.titleGen = new TitleGenerator(
31968
32624
  this.codexAcpClient.appServerClient,
@@ -31970,7 +32626,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31970
32626
  sessionState.cwd,
31971
32627
  () => sessionState.sessionTitleSource
31972
32628
  );
31973
- this.sessions.set(sessionId, sessionState);
32629
+ this.installSessionState(sessionState);
31974
32630
  resumeSubscribed = false;
31975
32631
  const canPublishSessionUpdates = operation !== "fork";
31976
32632
  if (canPublishSessionUpdates && requestedMcpServers.length > 0 && mcpServerStartupVersion !== null) {
@@ -31985,6 +32641,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31985
32641
  }
31986
32642
  if (operation === "resume") {
31987
32643
  this.publishCurrentGoalAsync(sessionState, sessionGeneration);
32644
+ this.publishAsyncTasksAsync(sessionState, sessionGeneration);
31988
32645
  }
31989
32646
  const sessionModelState = this.createModelState(models, currentModelId);
31990
32647
  const sessionModeState = sessionState.agentMode.toSessionModeState();
@@ -31992,12 +32649,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
31992
32649
  }
31993
32650
  async getAuthStateForProvider(authProvider) {
31994
32651
  if (!this.authProviderUsesOpenAiAccount(authProvider)) {
32652
+ await this.publishAuthStatus(authProvider, null);
31995
32653
  return {
31996
32654
  account: null,
31997
32655
  authConfigured: true
31998
32656
  };
31999
32657
  }
32000
32658
  const accountResponse = await this.runWithProcessCheck(() => this.codexAcpClient.getAccount());
32659
+ await this.publishAuthStatus(authProvider, accountResponse.account);
32001
32660
  return {
32002
32661
  account: accountResponse.account,
32003
32662
  authConfigured: accountResponse.account !== null || !accountResponse.requiresOpenaiAuth
@@ -32012,6 +32671,18 @@ Check ${configPath} and project .codex directories, especially their config.toml
32012
32671
  }
32013
32672
  return a === b;
32014
32673
  }
32674
+ createAsyncTasks(sessionId) {
32675
+ return new CodexBackgroundTerminalTasks(
32676
+ clientSupportsAirCapability(this.clientCapabilities, AIR_ASYNC_TASKS_KEY),
32677
+ sessionId,
32678
+ this.codexAcpClient.appServerClient,
32679
+ new ACPSessionConnection(this.connection, sessionId)
32680
+ );
32681
+ }
32682
+ installSessionState(sessionState) {
32683
+ this.sessions.get(sessionState.sessionId)?.asyncTasks.clear();
32684
+ this.sessions.set(sessionState.sessionId, sessionState);
32685
+ }
32015
32686
  getAuthProviderForAuthenticateRequest(request) {
32016
32687
  if (isCodexAuthRequest(request) && request.methodId === "gateway") {
32017
32688
  return "custom-gateway";
@@ -32030,6 +32701,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32030
32701
  thread
32031
32702
  } = await this.getOrCreateSessionWithHistory(params);
32032
32703
  await this.streamThreadHistory(sessionId, thread);
32704
+ await this.getSessionState(sessionId).asyncTasks.reconcile();
32033
32705
  logger.log("Session loaded", {
32034
32706
  sessionId,
32035
32707
  modelId: modelState.currentModelId,
@@ -32103,6 +32775,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32103
32775
  try {
32104
32776
  if (sessionState) {
32105
32777
  await this.interruptSessionTurn(sessionState, "Close", true);
32778
+ sessionState.asyncTasks.clear();
32106
32779
  } else {
32107
32780
  logger.log("Close request received for unknown local session", { sessionId: params.sessionId });
32108
32781
  }
@@ -32176,7 +32849,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32176
32849
  logger.log("Authenticate request failed");
32177
32850
  throw RequestError.invalidParams();
32178
32851
  }
32179
- await this.refreshSessionsAuthState(this.getAuthProviderForAuthenticateRequest(_params));
32852
+ await this.refreshAuthState(this.getAuthProviderForAuthenticateRequest(_params));
32180
32853
  logger.log("Authenticate request completed");
32181
32854
  return {};
32182
32855
  }
@@ -32207,7 +32880,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32207
32880
  async logout(_params) {
32208
32881
  logger.log("Logout request received");
32209
32882
  await this.runWithProcessCheck(() => this.codexAcpClient.logout());
32210
- await this.refreshSessionsAuthState(null);
32883
+ await this.refreshAuthState(null);
32211
32884
  logger.log("Logout request completed");
32212
32885
  }
32213
32886
  listProviders(_params) {
@@ -32238,6 +32911,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
32238
32911
  await Promise.all(activePrompts);
32239
32912
  }
32240
32913
  logger.log("Restarting Codex app-server for provider update", { sessionCount: this.sessions.size });
32914
+ for (const session of this.sessions.values()) {
32915
+ session.asyncTasks.prepareForAppServerReplacement();
32916
+ }
32917
+ await this.finishAllAsyncTasks("stopped", "before the provider restart");
32241
32918
  const replacement = await this.restartCodexClient();
32242
32919
  apply(replacement);
32243
32920
  if (this.initializeRequest === null) {
@@ -32248,6 +32925,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32248
32925
  this.availableCommands = this.createAvailableCommands(replacement);
32249
32926
  const resumeErrors = [];
32250
32927
  for (const session of this.sessions.values()) {
32928
+ session.asyncTasks.setAppServer(replacement.appServerClient);
32251
32929
  try {
32252
32930
  await replacement.resumeSession({
32253
32931
  sessionId: session.sessionId,
@@ -32256,6 +32934,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32256
32934
  mcpServers: session.mcpServers ?? []
32257
32935
  });
32258
32936
  session.authProvider = replacement.getModelProvider();
32937
+ session.asyncTasks.refresh();
32259
32938
  logger.log("Resumed session after provider restart", { sessionId: session.sessionId });
32260
32939
  } catch (error51) {
32261
32940
  resumeErrors.push(error51);
@@ -32285,12 +32964,22 @@ Check ${configPath} and project .codex directories, especially their config.toml
32285
32964
  state.stderr = (state.stderr + data.toString()).slice(-2 * 1024);
32286
32965
  });
32287
32966
  }
32967
+ observeCodexProcess() {
32968
+ const process12 = this.codexProcessState?.connection.process;
32969
+ if (!process12) return;
32970
+ const generation = ++this.codexProcessGeneration;
32971
+ process12.once("exit", () => {
32972
+ if (generation !== this.codexProcessGeneration) return;
32973
+ void this.finishAllAsyncTasks("failed", "after the Codex process exited");
32974
+ });
32975
+ }
32288
32976
  async restartCodexClient() {
32289
32977
  const state = this.codexProcessState;
32290
32978
  if (state === null) {
32291
32979
  throw new Error("Codex process state is unavailable");
32292
32980
  }
32293
32981
  const previous = state.connection;
32982
+ this.codexProcessGeneration += 1;
32294
32983
  const exited = previous.process.exitCode === null ? once(previous.process, "exit") : Promise.resolve();
32295
32984
  previous.process.stdin.end();
32296
32985
  const forceKill = setTimeout(() => {
@@ -32308,6 +32997,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32308
32997
  state.permissionProfileConfig?.configOverrides
32309
32998
  );
32310
32999
  this.captureStderr();
33000
+ this.observeCodexProcess();
32311
33001
  return new CodexAcpClient(
32312
33002
  new CodexAppServerClient(state.connection.connection),
32313
33003
  state.config,
@@ -32315,15 +33005,155 @@ Check ${configPath} and project .codex directories, especially their config.toml
32315
33005
  state.permissionProfileConfig
32316
33006
  );
32317
33007
  }
33008
+ /** Returns whether the auth state was read (and thus the auth status pushed). */
32318
33009
  async refreshSessionsAuthState(authProvider) {
32319
- if (this.sessions.size === 0) return;
33010
+ if (this.sessions.size === 0) return false;
32320
33011
  const sessionsToRefresh = [...this.sessions.values()].filter((sessionState) => this.authProvidersMatch(sessionState.authProvider, authProvider));
32321
- if (sessionsToRefresh.length === 0) return;
33012
+ if (sessionsToRefresh.length === 0) return false;
32322
33013
  const authState = await this.getAuthStateForProvider(authProvider);
32323
33014
  for (const sessionState of sessionsToRefresh) {
32324
33015
  sessionState.account = authState.account;
32325
33016
  sessionState.authConfigured = authState.authConfigured;
32326
33017
  }
33018
+ return true;
33019
+ }
33020
+ /**
33021
+ * Refreshes the sessions of a provider and makes sure the connection-level
33022
+ * `authStatus` is pushed even when no session matched (the empty-screen
33023
+ * login case). Reuses the session refresh read; never adds a second one.
33024
+ */
33025
+ async refreshAuthState(authProvider) {
33026
+ const refreshed = await this.refreshSessionsAuthState(authProvider);
33027
+ if (refreshed) return;
33028
+ try {
33029
+ await this.getAuthStateForProvider(authProvider ?? this.codexAcpClient.getModelProvider());
33030
+ } catch (error51) {
33031
+ logger.log("Failed to refresh auth status", { error: String(error51) });
33032
+ }
33033
+ }
33034
+ /**
33035
+ * Schedules the connection's first `_auth/status_update`: one account read,
33036
+ * pushed whatever it says, including `none`.
33037
+ *
33038
+ * The push must not overtake the `initialize` response. The JSON-RPC layer
33039
+ * writes that response in the microtask that resolves {@link initialize}, so
33040
+ * the read starts from a check-phase callback, which always runs after it.
33041
+ * `initialize` itself never waits for the read.
33042
+ *
33043
+ * "Unconditional" costs nothing extra here: nothing has been pushed yet on
33044
+ * this connection, so {@link setAuthStatus} cannot suppress this one.
33045
+ */
33046
+ publishFirstAuthStatusAfterResponse() {
33047
+ setImmediate(() => void this.publishAuthStatusRead());
33048
+ }
33049
+ /**
33050
+ * Reads the agent-owned identity and pushes it.
33051
+ *
33052
+ * Never rejects: an unreadable source means "nothing to report", not an
33053
+ * error. The client then keeps showing the last pushed value, or "not
33054
+ * reported" when there was none.
33055
+ */
33056
+ async publishAuthStatusRead() {
33057
+ let authStatus;
33058
+ try {
33059
+ authStatus = await this.readAgentAuthIdentity();
33060
+ } catch (error51) {
33061
+ logger.log("Cannot determine auth status", { error: String(error51) });
33062
+ return;
33063
+ }
33064
+ await this.setAuthStatus(authStatus);
33065
+ }
33066
+ /**
33067
+ * Builds the agent-owned auth identity. Routing the client configured
33068
+ * through the ACP `providers/*` API is invisible here: the reported state
33069
+ * is what the agent itself is logged in with. `gateway` stays reserved for
33070
+ * agent-owned gateway state — the `gateway` auth method, or a provider the
33071
+ * user configured in Codex's own config.
33072
+ */
33073
+ async readAgentAuthIdentity() {
33074
+ const authGatewayName = this.codexAcpClient.getAuthGatewayProviderName();
33075
+ if (authGatewayName !== null) {
33076
+ return gatewayStatus(authGatewayName);
33077
+ }
33078
+ const modelProvider = await this.runWithProcessCheck(() => this.codexAcpClient.getAgentConfiguredModelProvider());
33079
+ if (!this.authProviderUsesOpenAiAccount(modelProvider)) {
33080
+ return gatewayStatus(modelProvider);
33081
+ }
33082
+ const accountResponse = await this.runWithProcessCheck(() => this.codexAcpClient.getAccount());
33083
+ return fromAccount(accountResponse.account);
33084
+ }
33085
+ /**
33086
+ * Pushes `_auth/status_update` for the freshly read account of a provider.
33087
+ * Agent-owned gateway authentication wins; a client-driven provider
33088
+ * override is ignored and the agent-owned login is reported instead.
33089
+ */
33090
+ async publishAuthStatus(authProvider, account) {
33091
+ const authGatewayName = this.codexAcpClient.getAuthGatewayProviderName();
33092
+ if (authGatewayName !== null) {
33093
+ await this.setAuthStatus(gatewayStatus(authGatewayName));
33094
+ return;
33095
+ }
33096
+ if (this.authProviderUsesOpenAiAccount(authProvider)) {
33097
+ await this.setAuthStatus(fromAccount(account));
33098
+ return;
33099
+ }
33100
+ if (this.codexAcpClient.isClientConfiguredProvider(authProvider)) {
33101
+ await this.publishAuthStatusRead();
33102
+ return;
33103
+ }
33104
+ await this.setAuthStatus(gatewayStatus(authProvider));
33105
+ }
33106
+ /**
33107
+ * Handles the app-server `account/updated` push: the free freshness channel
33108
+ * for logins and logouts happening outside this connection.
33109
+ */
33110
+ handleAccountUpdated(notification) {
33111
+ void this.applyAccountUpdated(notification);
33112
+ }
33113
+ /**
33114
+ * `account/updated` describes the Codex account only. It must never
33115
+ * overwrite an agent-owned gateway status, which no account event can
33116
+ * invalidate; only a gateway logout or a provider change does.
33117
+ */
33118
+ async applyAccountUpdated(notification) {
33119
+ try {
33120
+ if (this.codexAcpClient.getAuthGatewayProviderName() !== null) {
33121
+ return;
33122
+ }
33123
+ if (this.currentAuthStatus === null) {
33124
+ await this.publishAuthStatusRead();
33125
+ return;
33126
+ }
33127
+ if (this.currentAuthStatus.kind === "gateway") {
33128
+ return;
33129
+ }
33130
+ await this.setAuthStatus(fromAccountUpdated(notification, this.currentAuthStatus));
33131
+ } catch (error51) {
33132
+ logger.log("Failed to apply account update to auth status", { error: String(error51) });
33133
+ }
33134
+ }
33135
+ /**
33136
+ * Stores `next` and pushes `_auth/status_update`.
33137
+ *
33138
+ * A push goes out only when the payload changed. The identity is read on
33139
+ * many occasions — `initialize`, each session create, each `account/updated`
33140
+ * — and almost all of them see the login already reported.
33141
+ * Clients replace their whole state on each update and tolerate duplicates,
33142
+ * so a repeat is harmless, but it is pure noise all the same.
33143
+ *
33144
+ * The first push of a connection always goes out: nothing was reported yet,
33145
+ * so no payload can equal it.
33146
+ */
33147
+ async setAuthStatus(next) {
33148
+ if (sameAuthStatus(this.currentAuthStatus, next)) {
33149
+ return;
33150
+ }
33151
+ this.currentAuthStatus = next;
33152
+ try {
33153
+ await this.connection.notify(AUTH_STATUS_UPDATE_METHOD, { authStatus: next });
33154
+ } catch (error51) {
33155
+ logger.log("Failed to send auth status update", { error: String(error51) });
33156
+ }
32327
33157
  }
32328
33158
  async setSessionMode(_params) {
32329
33159
  logger.log("Set session mode requested", {
@@ -32722,6 +33552,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
32722
33552
  publishCurrentGoalAsync(sessionState, sessionGeneration) {
32723
33553
  void this.publishCurrentGoalBestEffort(sessionState, sessionGeneration, true);
32724
33554
  }
33555
+ publishAsyncTasksAsync(sessionState, sessionGeneration) {
33556
+ if (!this.sessionPublishIsCurrent(sessionState, sessionGeneration)) return;
33557
+ sessionState.asyncTasks.refresh();
33558
+ }
32725
33559
  async publishCurrentGoalBestEffort(sessionState, sessionGeneration, force) {
32726
33560
  try {
32727
33561
  await this.publishCurrentGoal(sessionState, sessionGeneration, force);
@@ -32845,7 +33679,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
32845
33679
  sessionId,
32846
33680
  clientSupportsSubagents(this.clientCapabilities),
32847
33681
  new ACPSessionConnection(this.connection, sessionId)
32848
- )
33682
+ ),
33683
+ asyncTasks: this.createAsyncTasks(sessionId)
32849
33684
  };
32850
33685
  sessionState.titleGen = new TitleGenerator(
32851
33686
  this.codexAcpClient.appServerClient,
@@ -32853,7 +33688,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32853
33688
  sessionState.cwd,
32854
33689
  () => sessionState.sessionTitleSource
32855
33690
  );
32856
- this.sessions.set(sessionId, sessionState);
33691
+ this.installSessionState(sessionState);
32857
33692
  subscribed = false;
32858
33693
  if (requestedMcpServers.length > 0 && mcpServerStartupVersion !== null) {
32859
33694
  this.pendingMcpStartupSessions.set(sessionId, {
@@ -32945,6 +33780,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
32945
33780
  /* @__PURE__ */ new Set([...ancestry, item.agentThreadId]),
32946
33781
  threadCache
32947
33782
  );
33783
+ try {
33784
+ await sessionState.asyncTasks.recover(
33785
+ item.agentThreadId,
33786
+ childSessionId,
33787
+ commandItemIds(childTurn.items)
33788
+ );
33789
+ } catch (error51) {
33790
+ logger.error(`Failed to restore background terminals for ${item.agentThreadId}`, error51);
33791
+ }
32948
33792
  }
32949
33793
  }
32950
33794
  } else if (activityKind === "completed" || activityKind === "interrupted") {
@@ -33518,7 +34362,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
33518
34362
  let recoverableSessionFailure = sessionState.sessionFailure;
33519
34363
  const promptStartTotalTokenUsage = sessionState.totalTokenUsage;
33520
34364
  sessionState.currentTurnId = null;
33521
- sessionState.lastTokenUsage = null;
33522
34365
  const activePrompt = this.trackActivePrompt(params.sessionId);
33523
34366
  let pendingTurnStart = null;
33524
34367
  const ensurePendingTurnStart = () => {
@@ -33551,7 +34394,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
33551
34394
  clientSupportsPlanUpdates(this.clientCapabilities),
33552
34395
  clientSupportsTypedSessionFailures(this.clientCapabilities),
33553
34396
  this.sessionFailureEpoch,
33554
- sessionState.subagents
34397
+ sessionState.subagents,
34398
+ (accountUpdated) => this.handleAccountUpdated(accountUpdated)
33555
34399
  );
33556
34400
  eventHandler = promptEventHandler;
33557
34401
  const permissionLifecycle = this.permissionLifecycleContext(sessionState);
@@ -33596,6 +34440,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33596
34440
  }
33597
34441
  const commandPromise = this.availableCommands.tryHandleCommand(params.prompt, sessionState, {
33598
34442
  onTurnStartPending: () => {
34443
+ sessionState.lastTokenUsage = null;
33599
34444
  ensurePendingTurnStart();
33600
34445
  },
33601
34446
  onTurnStarted: (turnId, threadId) => {
@@ -33693,6 +34538,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33693
34538
  sessionState.fastModeEnabled,
33694
34539
  sessionState.currentModelSupportsFast
33695
34540
  );
34541
+ sessionState.lastTokenUsage = null;
33696
34542
  ensurePendingTurnStart();
33697
34543
  const sendPromptPromise = this.runWithProcessCheck(
33698
34544
  () => this.codexAcpClient.sendPrompt(
@@ -33999,6 +34845,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33999
34845
  throw new RequestError(requestErrorCode, `VC++ redistributable should be installed`);
34000
34846
  }
34001
34847
  if (exitCode !== null) {
34848
+ await this.finishAllAsyncTasks("failed", "after the Codex process exited");
34002
34849
  const stderr = this.getRecentStderr().trim();
34003
34850
  const detail = stderr ? `:
34004
34851
  ${stderr}` : "";
@@ -34007,6 +34854,15 @@ ${stderr}` : "";
34007
34854
  throw err;
34008
34855
  }
34009
34856
  }
34857
+ async finishAllAsyncTasks(state, reason) {
34858
+ for (const session of this.sessions.values()) {
34859
+ try {
34860
+ await session.asyncTasks.finishAll(state);
34861
+ } catch (error51) {
34862
+ logger.error(`Failed to finish background terminal tasks ${reason}`, error51);
34863
+ }
34864
+ }
34865
+ }
34010
34866
  async cancel(params) {
34011
34867
  const sessionState = this.sessions.get(params.sessionId);
34012
34868
  if (!sessionState) {
@@ -34056,6 +34912,9 @@ function mergeHistoryUpdates(responseItemFallbackUpdates, threadUpdates) {
34056
34912
  }
34057
34913
  return merged;
34058
34914
  }
34915
+ function commandItemIds(items) {
34916
+ return new Set(items.filter((item) => item.type === "commandExecution").map((item) => item.id));
34917
+ }
34059
34918
  function historyUpdateKey(update) {
34060
34919
  switch (update.sessionUpdate) {
34061
34920
  case "user_message_chunk":
@@ -34239,6 +35098,10 @@ var goalControlParamsParser = external_exports.discriminatedUnion("action", [
34239
35098
  action: external_exports.enum(["pause", "resume", "clear"])
34240
35099
  }).passthrough()
34241
35100
  ]);
35101
+ var asyncTaskStopParamsParser = external_exports.object({
35102
+ sessionId: external_exports.string().trim().min(1),
35103
+ asyncTaskId: external_exports.string().trim().min(1)
35104
+ }).passthrough();
34242
35105
  if (process.argv.includes("--version")) {
34243
35106
  console.log(`${package_default.name} ${package_default.version}`);
34244
35107
  process.exit(0);
@@ -34333,5 +35196,5 @@ function startAcpServer() {
34333
35196
  codexAcpServer = null;
34334
35197
  }
34335
35198
  });
34336
- }).onRequest(methods.agent.initialize, (ctx) => getAgent().initialize(ctx.params)).onRequest(methods.agent.session.new, (ctx) => getAgent().newSession(ctx.params)).onRequest(methods.agent.session.load, (ctx) => getAgent().loadSession(ctx.params)).onRequest(methods.agent.session.fork, (ctx) => getAgent().forkSession(ctx.params)).onRequest(methods.agent.session.list, (ctx) => getAgent().listSessions(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => getAgent().deleteSession(ctx.params)).onRequest(methods.agent.session.resume, (ctx) => getAgent().resumeSession(ctx.params)).onRequest(methods.agent.session.close, (ctx) => getAgent().closeSession(ctx.params)).onRequest(methods.agent.session.setMode, (ctx) => getAgent().setSessionMode(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => getAgent().setSessionConfigOption(ctx.params)).onRequest(methods.agent.authenticate, (ctx) => getAgent().authenticate(ctx.params, ctx.requestId)).onRequest(methods.agent.logout, (ctx) => getAgent().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => getAgent().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => getAgent().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => getAgent().disableProvider(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => getAgent().prompt(ctx.params, ctx.signal)).onNotification(methods.agent.session.cancel, (ctx) => getAgent().cancel(ctx.params)).onRequest("authentication/status", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/status", ctx.params)).onRequest("authentication/logout", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/logout", ctx.params)).onRequest(LEGACY_SET_SESSION_MODEL_METHOD, legacySetSessionModelParamsParser, (ctx) => getAgent().extMethod(LEGACY_SET_SESSION_MODEL_METHOD, ctx.params)).onRequest(SESSION_STEERING_METHOD, sessionSteerParamsParser, (ctx) => getAgent().extMethod(SESSION_STEERING_METHOD, ctx.params)).onRequest(GOAL_CONTROL_METHOD, goalControlParamsParser, (ctx) => getAgent().extMethod(GOAL_CONTROL_METHOD, ctx.params)).connect(acpJsonStream);
35199
+ }).onRequest(methods.agent.initialize, (ctx) => getAgent().initialize(ctx.params)).onRequest(methods.agent.session.new, (ctx) => getAgent().newSession(ctx.params)).onRequest(methods.agent.session.load, (ctx) => getAgent().loadSession(ctx.params)).onRequest(methods.agent.session.fork, (ctx) => getAgent().forkSession(ctx.params)).onRequest(methods.agent.session.list, (ctx) => getAgent().listSessions(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => getAgent().deleteSession(ctx.params)).onRequest(methods.agent.session.resume, (ctx) => getAgent().resumeSession(ctx.params)).onRequest(methods.agent.session.close, (ctx) => getAgent().closeSession(ctx.params)).onRequest(methods.agent.session.setMode, (ctx) => getAgent().setSessionMode(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => getAgent().setSessionConfigOption(ctx.params)).onRequest(methods.agent.authenticate, (ctx) => getAgent().authenticate(ctx.params, ctx.requestId)).onRequest(methods.agent.logout, (ctx) => getAgent().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => getAgent().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => getAgent().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => getAgent().disableProvider(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => getAgent().prompt(ctx.params, ctx.signal)).onNotification(methods.agent.session.cancel, (ctx) => getAgent().cancel(ctx.params)).onRequest("authentication/status", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/status", ctx.params)).onRequest("authentication/logout", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/logout", ctx.params)).onRequest(LEGACY_SET_SESSION_MODEL_METHOD, legacySetSessionModelParamsParser, (ctx) => getAgent().extMethod(LEGACY_SET_SESSION_MODEL_METHOD, ctx.params)).onRequest(SESSION_STEERING_METHOD, sessionSteerParamsParser, (ctx) => getAgent().extMethod(SESSION_STEERING_METHOD, ctx.params)).onRequest(ASYNC_TASK_STOP_METHOD, asyncTaskStopParamsParser, (ctx) => getAgent().extMethod(ASYNC_TASK_STOP_METHOD, ctx.params)).onRequest(GOAL_CONTROL_METHOD, goalControlParamsParser, (ctx) => getAgent().extMethod(GOAL_CONTROL_METHOD, ctx.params)).connect(acpJsonStream);
34337
35200
  }