@agentclientprotocol/codex-acp 1.8.0 → 1.10.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 (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
@@ -22131,32 +22131,7 @@ function attachLogs(proc) {
22131
22131
  });
22132
22132
  }
22133
22133
 
22134
- // src/AirExtension.ts
22135
- var JETBRAINS_META_KEY = "jetbrains";
22136
- var AIR_META_KEY = "air";
22137
- var AIR_EXTENSION_VERSION_KEY = "version";
22138
- var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
22139
- var AIR_SESSION_FAILURE_KEY = "sessionFailure";
22140
- var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
22141
- var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
22142
- var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
22143
- var AIR_EXTENSION_VERSION = 1;
22144
- function clientSupportsAirCapability(capabilities, capability) {
22145
- const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
22146
- const air = jetbrains?.[AIR_META_KEY];
22147
- const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
22148
- const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
22149
- return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
22150
- }
22151
-
22152
- // src/subagents/AcpSubagents.ts
22153
- function clientSupportsSubagents(capabilities) {
22154
- const subagents = capabilities?.subagents;
22155
- if (typeof subagents === "object" && subagents !== null && !Array.isArray(subagents)) {
22156
- return true;
22157
- }
22158
- return clientSupportsAirCapability(capabilities, AIR_NATIVE_SUBAGENT_SESSIONS_KEY);
22159
- }
22134
+ // src/AcpSessionExtensions.ts
22160
22135
  function asSdkSessionNotification(notification) {
22161
22136
  return notification;
22162
22137
  }
@@ -23676,6 +23651,26 @@ function sameThreadGoalSnapshot(left, right) {
23676
23651
  // src/CodexEventHandler.ts
23677
23652
  import { randomUUID } from "node:crypto";
23678
23653
 
23654
+ // src/AirExtension.ts
23655
+ var JETBRAINS_META_KEY = "jetbrains";
23656
+ var AIR_META_KEY = "air";
23657
+ var AIR_EXTENSION_VERSION_KEY = "version";
23658
+ var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
23659
+ var AIR_SESSION_FAILURE_KEY = "sessionFailure";
23660
+ var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
23661
+ var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
23662
+ var AIR_ASYNC_TASKS_KEY = "asyncTasks";
23663
+ var AIR_ASYNC_TASKS_BACKGROUNDED_KEY = "backgrounded";
23664
+ var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
23665
+ var AIR_EXTENSION_VERSION = 1;
23666
+ function clientSupportsAirCapability(capabilities, capability) {
23667
+ const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
23668
+ const air = jetbrains?.[AIR_META_KEY];
23669
+ const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
23670
+ const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
23671
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
23672
+ }
23673
+
23679
23674
  // src/subagents/CodexAgentPath.ts
23680
23675
  function normalizeAgentPath(path9) {
23681
23676
  const normalized = path9.trim().replace(/\/+$/, "");
@@ -23731,14 +23726,14 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23731
23726
  }
23732
23727
  return childTurn;
23733
23728
  }
23734
- const notificationThreadId = notification.params.threadId;
23735
- if (typeof notificationThreadId === "string" && this.pendingSpawns.has(notificationThreadId)) {
23736
- const pending = this.pendingSpawns.get(notificationThreadId);
23729
+ const notificationThreadId2 = notification.params.threadId;
23730
+ if (typeof notificationThreadId2 === "string" && this.pendingSpawns.has(notificationThreadId2)) {
23731
+ const pending = this.pendingSpawns.get(notificationThreadId2);
23737
23732
  if (pending.buffered.length === _CodexSubagentEventRouter.MAX_PENDING_NOTIFICATIONS) {
23738
23733
  pending.buffered.shift();
23739
23734
  pending.droppedBufferedNotifications += 1;
23740
23735
  if (pending.droppedBufferedNotifications === 1) {
23741
- logger.log(`Pending subagent ${notificationThreadId} exceeded the notification buffer; dropping oldest updates`);
23736
+ logger.log(`Pending subagent ${notificationThreadId2} exceeded the notification buffer; dropping oldest updates`);
23742
23737
  }
23743
23738
  }
23744
23739
  pending.buffered.push(notification);
@@ -23823,6 +23818,25 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23823
23818
  const threadId = notification.params.threadId;
23824
23819
  return typeof threadId === "string" && this.children.has(threadId) ? this.children.get(threadId).sessionId : this.rootSessionId;
23825
23820
  }
23821
+ closingChildSessions(notification) {
23822
+ if (!this.supported) return [];
23823
+ if (notification.method === "turn/completed") {
23824
+ if (terminalStateFromTurn(notification.params.turn.status) === void 0) return [];
23825
+ return this.closingChildSession(notification.params.threadId);
23826
+ }
23827
+ if (notification.method !== "item/started" && notification.method !== "item/completed") return [];
23828
+ const item = notification.params.item;
23829
+ if (item.type === "subAgentActivity") {
23830
+ return item.kind === "interrupted" ? this.closingChildSession(item.agentThreadId) : [];
23831
+ }
23832
+ if (item.type !== "collabAgentToolCall") return [];
23833
+ const closing = /* @__PURE__ */ new Map();
23834
+ for (const [threadId, state] of Object.entries(item.agentsStates)) {
23835
+ if (!state || terminalStateOf(state.status) === void 0) continue;
23836
+ for (const child of this.closingChildSession(threadId)) closing.set(threadId, child);
23837
+ }
23838
+ return [...closing.values()];
23839
+ }
23826
23840
  takeBufferedNotifications() {
23827
23841
  return this.replayQueue.splice(0);
23828
23842
  }
@@ -23898,6 +23912,10 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23898
23912
  isKnownChild(threadId) {
23899
23913
  return threadId !== this.rootSessionId && (this.children.has(threadId) || this.pendingSpawns.has(threadId) || this.terminalPendingSpawns.has(threadId));
23900
23914
  }
23915
+ closingChildSession(threadId) {
23916
+ const child = this.children.get(threadId);
23917
+ return child && child.terminalState === void 0 ? [{ threadId, sessionId: child.sessionId }] : [];
23918
+ }
23901
23919
  async materialize(childSessionId, path9) {
23902
23920
  if (this.children.has(childSessionId)) return;
23903
23921
  const pending = this.pendingSpawns.get(childSessionId);
@@ -24051,6 +24069,37 @@ function fallbackName(sessionId) {
24051
24069
  return `Agent ${suffix}`;
24052
24070
  }
24053
24071
 
24072
+ // src/RateLimitsMap.ts
24073
+ function rateLimitId(snapshot, fallback = "codex") {
24074
+ return snapshot.limitId ?? fallback;
24075
+ }
24076
+ function createRateLimitsMap(response) {
24077
+ const result = /* @__PURE__ */ new Map();
24078
+ const snapshots = Object.entries(response.rateLimitsByLimitId ?? {}).filter((entry) => entry[1] !== void 0);
24079
+ if (snapshots.length === 0) {
24080
+ snapshots.push([rateLimitId(response.rateLimits), response.rateLimits]);
24081
+ }
24082
+ for (const [fallbackId, snapshot] of snapshots) {
24083
+ const limitId = rateLimitId(snapshot, fallbackId);
24084
+ result.set(limitId, {
24085
+ limitId,
24086
+ limitName: snapshot.limitName ?? limitId,
24087
+ snapshot
24088
+ });
24089
+ }
24090
+ return result;
24091
+ }
24092
+ function mergeRateLimitSnapshot(previous, update) {
24093
+ return {
24094
+ ...update,
24095
+ limitId: update.limitId ?? "codex",
24096
+ credits: update.credits ?? previous.credits,
24097
+ individualLimit: update.individualLimit ?? previous.individualLimit,
24098
+ spendControlReached: update.spendControlReached ?? previous.spendControlReached,
24099
+ planType: update.planType ?? previous.planType
24100
+ };
24101
+ }
24102
+
24054
24103
  // src/CodexEventHandler.ts
24055
24104
  var MAX_SESSION_FAILURE_TITLE_LENGTH = 240;
24056
24105
  var SESSION_FAILURE_POLICY = {
@@ -24159,11 +24208,14 @@ var CodexEventHandler = class _CodexEventHandler {
24159
24208
  terminalCommandOutputIds = /* @__PURE__ */ new Set();
24160
24209
  agentMessagePhases = /* @__PURE__ */ new Map();
24161
24210
  subagents;
24211
+ /** Connection-level `authStatus` sink; the app-server account push feeds it. */
24212
+ onAccountUpdated;
24162
24213
  constructor(connection, sessionState, supportsPlanUpdates = false, supportsTypedSessionFailures = false, sessionFailureEpoch = randomUUID(), subagents = new CodexSubagentEventRouter(
24163
24214
  sessionState.sessionId,
24164
24215
  false,
24165
24216
  new ACPSessionConnection(connection, sessionState.sessionId)
24166
- )) {
24217
+ ), onAccountUpdated) {
24218
+ this.onAccountUpdated = onAccountUpdated;
24167
24219
  this.sessionState = sessionState;
24168
24220
  this.supportsPlanUpdates = supportsPlanUpdates;
24169
24221
  this.supportsTypedSessionFailures = supportsTypedSessionFailures;
@@ -24272,17 +24324,29 @@ var CodexEventHandler = class _CodexEventHandler {
24272
24324
  }
24273
24325
  async handleNotification(notification) {
24274
24326
  await this.flushPendingErrors();
24327
+ const closingChildren = this.subagents.closingChildSessions(notification);
24328
+ for (const child of closingChildren) {
24329
+ await this.sessionState.asyncTasks.reconcile(child.threadId, child.sessionId);
24330
+ }
24275
24331
  const handledBySubagents = await this.subagents.handle(notification);
24276
24332
  for (const buffered of this.subagents.takeBufferedNotifications()) {
24277
24333
  await this.handleNotification(buffered);
24278
24334
  }
24279
- if (handledBySubagents) {
24280
- return;
24335
+ const ignoredBySubagents = !handledBySubagents && this.subagents.shouldIgnore(notification);
24336
+ let updateEvent;
24337
+ if (!handledBySubagents && !ignoredBySubagents && notification.method === "item/started" && notification.params.item.type === "commandExecution") {
24338
+ updateEvent = await this.createUpdateEvent(notification);
24281
24339
  }
24282
- if (this.subagents.shouldIgnore(notification)) {
24283
- return;
24340
+ if (!handledBySubagents) {
24341
+ await this.sessionState.asyncTasks.handleNotification(
24342
+ notification,
24343
+ this.subagents.notificationSessionId(notification),
24344
+ toolCallTitle(updateEvent)
24345
+ );
24284
24346
  }
24285
- const updateEvent = await this.createUpdateEvent(notification);
24347
+ if (handledBySubagents) return;
24348
+ if (ignoredBySubagents) return;
24349
+ if (updateEvent === void 0) updateEvent = await this.createUpdateEvent(notification);
24286
24350
  if (updateEvent) {
24287
24351
  await this.session.update(updateEvent, this.subagents.notificationSessionId(notification));
24288
24352
  }
@@ -24387,6 +24451,9 @@ var CodexEventHandler = class _CodexEventHandler {
24387
24451
  case "account/rateLimits/updated":
24388
24452
  this.handleRateLimitsUpdated(notification.params);
24389
24453
  return null;
24454
+ case "account/updated":
24455
+ this.onAccountUpdated?.(notification.params);
24456
+ return null;
24390
24457
  case "configWarning":
24391
24458
  return await this.createConfigWarningEvent(notification.params);
24392
24459
  case "warning":
@@ -24437,7 +24504,6 @@ var CodexEventHandler = class _CodexEventHandler {
24437
24504
  case "turn/moderationMetadata":
24438
24505
  case "item/fileChange/outputDelta":
24439
24506
  case "item/fileChange/patchUpdated":
24440
- case "account/updated":
24441
24507
  case "fs/changed":
24442
24508
  case "mcpServer/startupStatus/updated":
24443
24509
  case "mcpServer/event/stream/notification":
@@ -25083,11 +25149,13 @@ ${event.stdin}
25083
25149
  if (!this.sessionState.rateLimits) {
25084
25150
  this.sessionState.rateLimits = /* @__PURE__ */ new Map();
25085
25151
  }
25086
- const limitId = params.rateLimits.limitId ?? params.rateLimits.limitName ?? "unknown";
25152
+ const limitId = params.rateLimits.limitId ?? "codex";
25153
+ const existingEntry = this.sessionState.rateLimits.get(limitId);
25154
+ const snapshot = existingEntry ? mergeRateLimitSnapshot(existingEntry.snapshot, params.rateLimits) : { ...params.rateLimits, limitId };
25087
25155
  this.sessionState.rateLimits.set(limitId, {
25088
25156
  limitId,
25089
- limitName: params.rateLimits.limitName ?? limitId,
25090
- snapshot: params.rateLimits
25157
+ limitName: snapshot.limitName ?? existingEntry?.limitName ?? limitId,
25158
+ snapshot
25091
25159
  });
25092
25160
  }
25093
25161
  handleFuzzyFileSearchSessionUpdated(params) {
@@ -25115,6 +25183,10 @@ ${event.stdin}
25115
25183
  return createGuardianApprovalReviewToolCall(params);
25116
25184
  }
25117
25185
  };
25186
+ function toolCallTitle(update) {
25187
+ if (update?.sessionUpdate !== "tool_call") return void 0;
25188
+ return update.title;
25189
+ }
25118
25190
 
25119
25191
  // src/permissions/option-ids.ts
25120
25192
  var ApprovalOptionId = {
@@ -27247,7 +27319,7 @@ var package_default = {
27247
27319
  publishConfig: {
27248
27320
  access: "public"
27249
27321
  },
27250
- version: "1.8.0",
27322
+ version: "1.10.0",
27251
27323
  description: "",
27252
27324
  main: "dist/index.js",
27253
27325
  bin: {
@@ -27310,7 +27382,7 @@ var package_default = {
27310
27382
  },
27311
27383
  dependencies: {
27312
27384
  "@agentclientprotocol/sdk": "^1.4.0",
27313
- "@openai/codex": "^0.152.0",
27385
+ "@openai/codex": "^0.153.3",
27314
27386
  diff: "^9.0.0",
27315
27387
  open: "^11.0.1",
27316
27388
  "vscode-jsonrpc": "^9.0.1",
@@ -27908,6 +27980,12 @@ var CodexAcpClient = class {
27908
27980
  config;
27909
27981
  modelProvider;
27910
27982
  gatewayConfig;
27983
+ /**
27984
+ * Where the stored gateway routing came from: the `gateway` auth method
27985
+ * (agent-owned authentication) or the ACP `providers/*` API (client-driven
27986
+ * routing). `authStatus` reports only the agent-owned one.
27987
+ */
27988
+ gatewayConfigSource;
27911
27989
  pendingLoginCompleted = null;
27912
27990
  pendingAccountUpdated = null;
27913
27991
  sessionNotificationQueues = /* @__PURE__ */ new Map();
@@ -27919,6 +27997,7 @@ var CodexAcpClient = class {
27919
27997
  this.config = codexConfig ?? {};
27920
27998
  this.modelProvider = modelProvider ?? null;
27921
27999
  this.gatewayConfig = null;
28000
+ this.gatewayConfigSource = null;
27922
28001
  this.subagents = new CodexSubagentSubscriptions(codexClient);
27923
28002
  }
27924
28003
  get appServerClient() {
@@ -27951,6 +28030,7 @@ var CodexAcpClient = class {
27951
28030
  throw RequestError.invalidRequest();
27952
28031
  }
27953
28032
  this.gatewayConfig = null;
28033
+ this.gatewayConfigSource = null;
27954
28034
  switch (authRequest.methodId) {
27955
28035
  case "api-key":
27956
28036
  return await this.authenticateWithApiKey(authRequest);
@@ -28034,7 +28114,7 @@ var CodexAcpClient = class {
28034
28114
  apiType: GatewayAuthMethod._meta.gateway.protocol,
28035
28115
  headers: gatewaySettings.headers,
28036
28116
  providerName: gatewaySettings.providerName
28037
- });
28117
+ }, "authentication");
28038
28118
  return true;
28039
28119
  }
28040
28120
  readApiKeyFromEnv() {
@@ -28080,6 +28160,11 @@ var CodexAcpClient = class {
28080
28160
  };
28081
28161
  }
28082
28162
  }
28163
+ /**
28164
+ * The provider that actually serves requests, ACP-configured gateway
28165
+ * routing included. Use {@link getAgentConfiguredModelProvider} instead
28166
+ * when asking what the agent itself is configured with (`authStatus`).
28167
+ */
28083
28168
  async getCurrentModelProvider() {
28084
28169
  const sessionModelProvider = this.getModelProvider();
28085
28170
  if (sessionModelProvider !== null) {
@@ -28105,7 +28190,7 @@ var CodexAcpClient = class {
28105
28190
  * method and the ACP `providers/set` method. Throws `invalid_params` for an
28106
28191
  * unsupported protocol or a malformed base URL.
28107
28192
  */
28108
- applyGatewayConfig(params) {
28193
+ applyGatewayConfig(params, source) {
28109
28194
  const apiType = params.apiType;
28110
28195
  const wireApi = SUPPORTED_GATEWAY_PROTOCOLS[apiType];
28111
28196
  if (!wireApi) {
@@ -28122,6 +28207,7 @@ var CodexAcpClient = class {
28122
28207
  "X-Client-Feature-ID": "codex",
28123
28208
  ...params.headers
28124
28209
  };
28210
+ this.gatewayConfigSource = source;
28125
28211
  this.gatewayConfig = {
28126
28212
  modelProvider: CUSTOM_GATEWAY_PROVIDER_ID,
28127
28213
  config: {
@@ -28186,7 +28272,7 @@ var CodexAcpClient = class {
28186
28272
  apiType: request.apiType,
28187
28273
  baseUrl: request.baseUrl,
28188
28274
  headers: request.headers
28189
- });
28275
+ }, "acpProviders");
28190
28276
  logger.log("providers/set applied", {
28191
28277
  providerId: request.providerId,
28192
28278
  apiType: request.apiType,
@@ -28202,6 +28288,7 @@ var CodexAcpClient = class {
28202
28288
  const overrideWasActive = this.gatewayConfig !== null;
28203
28289
  if (request.providerId === OPENAI_PROVIDER_ID) {
28204
28290
  this.gatewayConfig = null;
28291
+ this.gatewayConfigSource = null;
28205
28292
  }
28206
28293
  const current = this.gatewayConfig ? {
28207
28294
  apiType: gatewayApiTypeFromConfig(this.gatewayConfig),
@@ -28219,6 +28306,36 @@ var CodexAcpClient = class {
28219
28306
  async getAccount() {
28220
28307
  return this.codexClient.accountRead({ refreshToken: false });
28221
28308
  }
28309
+ async getRateLimits() {
28310
+ return this.codexClient.accountRateLimitsRead();
28311
+ }
28312
+ /**
28313
+ * Presentable name of the gateway the agent itself authenticated against
28314
+ * (the `gateway` auth method), or `null`. Routing that the client
28315
+ * configured through `providers/set` is deliberately not reported here:
28316
+ * `authStatus` describes the agent-owned login only.
28317
+ */
28318
+ getAuthGatewayProviderName() {
28319
+ return this.gatewayConfigSource === "authentication" ? this.gatewayConfig?.config.name ?? null : null;
28320
+ }
28321
+ /** Whether this provider id is client-driven routing set through `providers/set`. */
28322
+ isClientConfiguredProvider(providerId) {
28323
+ return providerId === CUSTOM_GATEWAY_PROVIDER_ID && this.gatewayConfigSource === "acpProviders";
28324
+ }
28325
+ /**
28326
+ * The model provider the agent itself is configured with (launch option or
28327
+ * Codex config), ignoring any ACP-configured gateway routing. The
28328
+ * routing-aware counterpart is {@link getCurrentModelProvider}.
28329
+ */
28330
+ async getAgentConfiguredModelProvider() {
28331
+ const provider = this.getModelProvider();
28332
+ const agentProvider = this.isClientConfiguredProvider(provider) ? this.modelProvider : provider;
28333
+ if (agentProvider !== null) {
28334
+ return agentProvider;
28335
+ }
28336
+ const settingsModelProvider = await this.codexClient.configRead({ includeLayers: false });
28337
+ return settingsModelProvider?.config?.model_provider ?? null;
28338
+ }
28222
28339
  async resumeSession(request, onSubscribed) {
28223
28340
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
28224
28341
  await this.refreshSkills(request.cwd, additionalDirectories);
@@ -29408,6 +29525,12 @@ var CodexAppServerClient = class {
29408
29525
  async threadCompactStart(params) {
29409
29526
  return await this.sendRequest({ method: "thread/compact/start", params });
29410
29527
  }
29528
+ async threadBackgroundTerminalsList(params) {
29529
+ return await this.sendRequest({ method: "thread/backgroundTerminals/list", params });
29530
+ }
29531
+ async threadBackgroundTerminalsTerminate(params) {
29532
+ return await this.sendRequest({ method: "thread/backgroundTerminals/terminate", params });
29533
+ }
29411
29534
  async threadGoalSet(params) {
29412
29535
  return await this.sendRequest({ method: "thread/goal/set", params });
29413
29536
  }
@@ -29473,6 +29596,9 @@ var CodexAppServerClient = class {
29473
29596
  async accountRead(params) {
29474
29597
  return await this.sendRequest({ method: "account/read", params });
29475
29598
  }
29599
+ async accountRateLimitsRead() {
29600
+ return await this.sendRequest({ method: "account/rateLimits/read", params: void 0 });
29601
+ }
29476
29602
  //TODO create type-safe helper
29477
29603
  async awaitTurnCompleted(threadId, turnId) {
29478
29604
  return await new Promise((resolve) => {
@@ -30079,6 +30205,7 @@ var CodexCommands = class {
30079
30205
  return { handled: true, turnCompleted };
30080
30206
  }
30081
30207
  case "status": {
30208
+ await this.refreshRateLimits(sessionState);
30082
30209
  const session = new ACPSessionConnection(this.connection, sessionId);
30083
30210
  const message = this.buildStatusMessage(sessionState);
30084
30211
  await session.update(createAgentTextMessageChunk(message));
@@ -30227,6 +30354,16 @@ var CodexCommands = class {
30227
30354
  ];
30228
30355
  return lines.join(" \n");
30229
30356
  }
30357
+ async refreshRateLimits(sessionState) {
30358
+ try {
30359
+ const response = await this.runWithProcessCheck(() => this.codexAcpClient.getRateLimits());
30360
+ if (response) {
30361
+ sessionState.rateLimits = createRateLimitsMap(response);
30362
+ }
30363
+ } catch (err) {
30364
+ logger.error(`Failed to refresh rate limits for session ${sessionState.sessionId}`, err);
30365
+ }
30366
+ }
30230
30367
  formatAccountInfo(account) {
30231
30368
  if (!account) {
30232
30369
  return "not logged in";
@@ -30257,10 +30394,10 @@ var CodexCommands = class {
30257
30394
  return "data not available yet";
30258
30395
  }
30259
30396
  const used = usage.totalTokens;
30260
- const percentLeft = Math.round((contextWindow - used) / contextWindow * 100);
30397
+ const percentUsed = Math.round(used / contextWindow * 100);
30261
30398
  const usedFormatted = this.formatTokenCount(used);
30262
30399
  const totalFormatted = this.formatTokenCount(contextWindow);
30263
- return `${percentLeft}% left (${usedFormatted} used / ${totalFormatted})`;
30400
+ return `${percentUsed}% used (${usedFormatted} used / ${totalFormatted})`;
30264
30401
  }
30265
30402
  formatRateLimitLines(rateLimits) {
30266
30403
  if (!rateLimits || rateLimits.size === 0) {
@@ -30294,8 +30431,31 @@ var CodexCommands = class {
30294
30431
  lines.push(`**${prefix}Credits:** ${rateLimits.credits.balance}`);
30295
30432
  }
30296
30433
  }
30434
+ if (rateLimits.individualLimit) {
30435
+ const limit = rateLimits.individualLimit;
30436
+ const used = this.formatCreditAmount(limit.used);
30437
+ const total = this.formatCreditAmount(limit.limit);
30438
+ if (used !== null && total !== null) {
30439
+ const percentLeft = Math.round(Math.min(100, Math.max(0, limit.remainingPercent)));
30440
+ const resetDate = new Date(limit.resetsAt * 1e3).toLocaleDateString("en-US", { month: "short", day: "numeric" });
30441
+ lines.push(
30442
+ `**${prefix}individual spend limit:** ${percentLeft}% left (${used} of ${total} credits used; resets ${resetDate})`
30443
+ );
30444
+ }
30445
+ }
30297
30446
  return lines;
30298
30447
  }
30448
+ formatCreditAmount(raw) {
30449
+ const trimmed = raw.trim();
30450
+ if (trimmed.length === 0) {
30451
+ return null;
30452
+ }
30453
+ const value = Number(trimmed);
30454
+ if (!Number.isFinite(value) || value < 0) {
30455
+ return null;
30456
+ }
30457
+ return Math.round(value).toLocaleString("en-US");
30458
+ }
30299
30459
  formatWindowLabel(windowDurationMins) {
30300
30460
  if (windowDurationMins === null) {
30301
30461
  return "Limit";
@@ -31342,11 +31502,147 @@ function numberValue(value) {
31342
31502
  return typeof value === "number" && Number.isFinite(value) ? value : null;
31343
31503
  }
31344
31504
 
31505
+ // src/async-tasks/AsyncTaskExtension.ts
31506
+ var ASYNC_TASK_STOP_METHOD = "_session/async_task/stop";
31507
+
31508
+ // src/AuthStatusMeta.ts
31509
+ var AUTH_STATUS_UPDATE_METHOD = "_auth/status_update";
31510
+ var AUTH_STATUS_META_KEY = "authStatus";
31511
+ function authStatusCapability() {
31512
+ return {};
31513
+ }
31514
+ var NOT_LOGGED_IN_LABEL = "Not logged in";
31515
+ var DEFAULT_GATEWAY_LABEL = "Custom model gateway";
31516
+ var PLAN_DISPLAY_NAMES = {
31517
+ free: "Free",
31518
+ go: "Go",
31519
+ plus: "Plus",
31520
+ pro: "Pro",
31521
+ team: "Team",
31522
+ business: "Business",
31523
+ enterprise: "Enterprise",
31524
+ edu: "Edu"
31525
+ };
31526
+ function planTypePresentable(planType) {
31527
+ if (!planType || planType === "unknown") {
31528
+ return null;
31529
+ }
31530
+ return PLAN_DISPLAY_NAMES[planType] ?? capitalize2(planType);
31531
+ }
31532
+ function capitalize2(value) {
31533
+ return value.charAt(0).toUpperCase() + value.slice(1);
31534
+ }
31535
+ function chatGptLabel(planType) {
31536
+ const plan = planTypePresentable(planType);
31537
+ return plan === null ? "ChatGPT" : `ChatGPT ${plan}`;
31538
+ }
31539
+ function gatewayStatus(providerName) {
31540
+ const detail = typeof providerName === "string" && providerName.trim().length > 0 ? providerName.trim() : void 0;
31541
+ return {
31542
+ kind: "gateway",
31543
+ label: DEFAULT_GATEWAY_LABEL,
31544
+ ...detail === void 0 ? {} : { detail }
31545
+ };
31546
+ }
31547
+ function unauthenticatedStatus() {
31548
+ return {
31549
+ kind: "none",
31550
+ label: NOT_LOGGED_IN_LABEL
31551
+ };
31552
+ }
31553
+ function fromAccount(account) {
31554
+ if (account === null) {
31555
+ return unauthenticatedStatus();
31556
+ }
31557
+ switch (account.type) {
31558
+ case "chatgpt": {
31559
+ const accountInfo = {};
31560
+ if (account.email) {
31561
+ accountInfo.email = account.email;
31562
+ }
31563
+ if (account.planType) {
31564
+ accountInfo.plan = account.planType;
31565
+ }
31566
+ return {
31567
+ kind: "account",
31568
+ label: chatGptLabel(account.planType),
31569
+ ...Object.keys(accountInfo).length > 0 ? { account: accountInfo } : {}
31570
+ };
31571
+ }
31572
+ case "apiKey":
31573
+ return {
31574
+ kind: "api_key",
31575
+ label: "OpenAI API key"
31576
+ };
31577
+ case "amazonBedrock":
31578
+ return {
31579
+ kind: "external",
31580
+ label: "AWS Bedrock"
31581
+ };
31582
+ }
31583
+ }
31584
+ function fromAccountUpdated(notification, previous) {
31585
+ const authMode = notification.authMode;
31586
+ if (authMode === null) {
31587
+ return unauthenticatedStatus();
31588
+ }
31589
+ switch (authMode) {
31590
+ case "chatgpt":
31591
+ case "chatgptAuthTokens": {
31592
+ const status = {
31593
+ kind: "account",
31594
+ label: chatGptLabel(notification.planType)
31595
+ };
31596
+ const accountInfo = {};
31597
+ const previousEmail = previous?.kind === "account" ? previous.account?.email : void 0;
31598
+ if (previousEmail) {
31599
+ accountInfo.email = previousEmail;
31600
+ }
31601
+ if (notification.planType) {
31602
+ accountInfo.plan = notification.planType;
31603
+ }
31604
+ if (Object.keys(accountInfo).length > 0) {
31605
+ status.account = accountInfo;
31606
+ }
31607
+ return status;
31608
+ }
31609
+ case "apikey":
31610
+ return {
31611
+ kind: "api_key",
31612
+ label: "OpenAI API key"
31613
+ };
31614
+ case "personalAccessToken":
31615
+ return {
31616
+ kind: "api_key",
31617
+ label: "OpenAI personal access token"
31618
+ };
31619
+ case "bedrockApiKey":
31620
+ case "bedrockAccessKeys":
31621
+ return {
31622
+ kind: "external",
31623
+ label: "AWS Bedrock"
31624
+ };
31625
+ case "agentIdentity":
31626
+ return {
31627
+ kind: "external",
31628
+ label: "Agent identity"
31629
+ };
31630
+ case "headers":
31631
+ return gatewayStatus();
31632
+ }
31633
+ }
31634
+ function sameAuthStatus(previous, next) {
31635
+ if (!previous) {
31636
+ return false;
31637
+ }
31638
+ 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);
31639
+ }
31640
+
31345
31641
  // src/AcpExtensions.ts
31346
31642
  var LEGACY_SET_SESSION_MODEL_METHOD = "session/set_model";
31347
31643
  var SESSION_STEERING_METHOD = "_session/steering";
31348
31644
  function isExtMethodRequest(request) {
31349
- 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;
31645
+ 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;
31350
31646
  }
31351
31647
 
31352
31648
  // src/FastModeConfig.ts
@@ -31413,6 +31709,15 @@ function clientSupportsPlanUpdates(clientCapabilities) {
31413
31709
  return clientCapabilities?.plan != null;
31414
31710
  }
31415
31711
 
31712
+ // src/subagents/AcpSubagents.ts
31713
+ function clientSupportsSubagents(capabilities) {
31714
+ const subagents = capabilities?.subagents;
31715
+ if (typeof subagents === "object" && subagents !== null && !Array.isArray(subagents)) {
31716
+ return true;
31717
+ }
31718
+ return clientSupportsAirCapability(capabilities, AIR_NATIVE_SUBAGENT_SESSIONS_KEY);
31719
+ }
31720
+
31416
31721
  // src/CodexAcpServer.ts
31417
31722
  import { randomUUID as randomUUID2 } from "node:crypto";
31418
31723
 
@@ -31501,6 +31806,332 @@ function extractTitle(turn) {
31501
31806
 
31502
31807
  // src/CodexAcpServer.ts
31503
31808
  import { once } from "node:events";
31809
+
31810
+ // src/async-tasks/CodexBackgroundTerminalTasks.ts
31811
+ var CodexBackgroundTerminalTasks = class {
31812
+ constructor(enabled, rootSessionId, appServer, session) {
31813
+ this.enabled = enabled;
31814
+ this.rootSessionId = rootSessionId;
31815
+ this.appServer = appServer;
31816
+ this.session = session;
31817
+ }
31818
+ enabled;
31819
+ rootSessionId;
31820
+ appServer;
31821
+ session;
31822
+ tasks = /* @__PURE__ */ new Map();
31823
+ syncs = /* @__PURE__ */ new Map();
31824
+ appServerGeneration = 0;
31825
+ appServerQueriesEnabled = true;
31826
+ disposed = false;
31827
+ async handleNotification(notification, sessionId, commandTitle2) {
31828
+ if (!this.isActive()) return;
31829
+ const threadId = notificationThreadId(notification);
31830
+ if (threadId === null) return;
31831
+ if (notification.method === "item/started" && notification.params.item.type === "commandExecution") {
31832
+ this.observeCommandStarted(notification.params.item, threadId, sessionId, commandTitle2);
31833
+ return;
31834
+ }
31835
+ if (notification.method === "item/completed" && notification.params.item.type === "commandExecution") {
31836
+ await this.observeCommandCompleted(notification.params.item, threadId);
31837
+ return;
31838
+ }
31839
+ if (notification.method === "turn/completed") {
31840
+ await this.reconcile(threadId, sessionId);
31841
+ return;
31842
+ }
31843
+ if (notification.method === "item/started") {
31844
+ this.refresh(threadId, sessionId);
31845
+ }
31846
+ }
31847
+ observeCommandStarted(item, threadId, sessionId, commandTitle2) {
31848
+ if (!this.isActive() || item.processId === null) return;
31849
+ this.remember(threadId, sessionId, {
31850
+ itemId: item.id,
31851
+ processId: item.processId,
31852
+ command: item.command
31853
+ }, commandTitle2);
31854
+ }
31855
+ async observeCommandCompleted(item, threadId) {
31856
+ if (!this.isActive()) return;
31857
+ const task = this.tasks.get(wireTaskId(this.rootSessionId, threadId, item.id));
31858
+ if (task) await this.finish(task, item.status === "completed" ? "completed" : "failed");
31859
+ }
31860
+ refresh(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31861
+ void this.reconcile(threadId, sessionId);
31862
+ }
31863
+ async reconcile(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31864
+ try {
31865
+ await this.sync(threadId, sessionId);
31866
+ } catch (error51) {
31867
+ if (this.isActive()) logger.error(`Failed to list background terminals for ${threadId}`, error51);
31868
+ }
31869
+ }
31870
+ setAppServer(appServer) {
31871
+ this.appServer = appServer;
31872
+ this.appServerGeneration += 1;
31873
+ this.appServerQueriesEnabled = true;
31874
+ }
31875
+ prepareForAppServerReplacement() {
31876
+ this.appServerGeneration += 1;
31877
+ this.appServerQueriesEnabled = false;
31878
+ }
31879
+ async recover(threadId, sessionId, itemIds) {
31880
+ if (!this.canQueryAppServer() || itemIds.size === 0) return;
31881
+ const snapshot = await this.listAll(threadId);
31882
+ if (snapshot === null) return;
31883
+ for (const terminal of snapshot.terminals) {
31884
+ if (!this.queryIsCurrent(snapshot.generation)) return;
31885
+ if (!itemIds.has(terminal.itemId)) continue;
31886
+ const task = this.remember(threadId, sessionId, terminal);
31887
+ if (task.publication === "unpublished" && task.state === "running") await this.announce(task);
31888
+ }
31889
+ }
31890
+ async finishAll(state) {
31891
+ if (!this.isActive()) return;
31892
+ const errors = [];
31893
+ for (const task of this.tasks.values()) {
31894
+ try {
31895
+ await this.finish(task, state);
31896
+ } catch (error51) {
31897
+ errors.push(error51);
31898
+ }
31899
+ }
31900
+ if (errors.length > 0) {
31901
+ throw new AggregateError(errors, `Failed to finish ${errors.length} background terminal task(s)`);
31902
+ }
31903
+ }
31904
+ async sync(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31905
+ if (!this.canQueryAppServer()) return;
31906
+ const current = this.syncs.get(threadId);
31907
+ if (current) {
31908
+ current.requested = true;
31909
+ current.sessionId = sessionId;
31910
+ return await current.promise;
31911
+ }
31912
+ const pending = {
31913
+ requested: false,
31914
+ sessionId,
31915
+ promise: Promise.resolve()
31916
+ };
31917
+ pending.promise = this.syncUntilCurrent(threadId, pending).finally(() => {
31918
+ if (this.syncs.get(threadId) === pending) this.syncs.delete(threadId);
31919
+ });
31920
+ this.syncs.set(threadId, pending);
31921
+ await pending.promise;
31922
+ }
31923
+ async stop(taskId) {
31924
+ if (!this.isActive()) return false;
31925
+ const task = this.tasks.get(taskId);
31926
+ if (!task || task.publication !== "published") return false;
31927
+ if (task.state === "stopped" && !task.terminalPublished) {
31928
+ await this.publishTerminalState(task);
31929
+ return true;
31930
+ }
31931
+ if (task.state !== "running") return false;
31932
+ task.state = "stopping";
31933
+ try {
31934
+ const response = await this.appServer.threadBackgroundTerminalsTerminate({
31935
+ threadId: task.threadId,
31936
+ processId: task.processId
31937
+ });
31938
+ if (!response.terminated) {
31939
+ if (task.state === "stopping") task.state = "running";
31940
+ return false;
31941
+ }
31942
+ await this.finish(task, "stopped");
31943
+ return true;
31944
+ } catch (error51) {
31945
+ if (task.state === "stopping") task.state = "running";
31946
+ throw error51;
31947
+ }
31948
+ }
31949
+ clear() {
31950
+ this.disposed = true;
31951
+ this.appServerGeneration += 1;
31952
+ this.appServerQueriesEnabled = false;
31953
+ this.tasks.clear();
31954
+ this.syncs.clear();
31955
+ }
31956
+ async syncThread(threadId, sessionId) {
31957
+ const snapshot = await this.listAll(threadId);
31958
+ if (snapshot === null || !this.queryIsCurrent(snapshot.generation)) return;
31959
+ const liveTaskIds = /* @__PURE__ */ new Set();
31960
+ for (const terminal of snapshot.terminals) {
31961
+ if (!this.queryIsCurrent(snapshot.generation)) return;
31962
+ liveTaskIds.add(terminal.itemId);
31963
+ const task = this.remember(threadId, sessionId, terminal);
31964
+ if (task.publication === "unpublished" && task.state === "running") await this.announce(task);
31965
+ }
31966
+ for (const task of this.tasks.values()) {
31967
+ if (!this.queryIsCurrent(snapshot.generation)) return;
31968
+ if (task.threadId === threadId && task.publication === "published" && (task.state === "running" || task.state === "stopping") && !liveTaskIds.has(task.itemId)) {
31969
+ await this.finish(task, "stopped");
31970
+ }
31971
+ }
31972
+ }
31973
+ async syncUntilCurrent(threadId, pending) {
31974
+ let hasFailure = false;
31975
+ let failure;
31976
+ do {
31977
+ pending.requested = false;
31978
+ try {
31979
+ await this.syncThread(threadId, pending.sessionId);
31980
+ hasFailure = false;
31981
+ } catch (error51) {
31982
+ hasFailure = true;
31983
+ failure = error51;
31984
+ }
31985
+ } while (pending.requested && this.canQueryAppServer());
31986
+ if (hasFailure) throw failure;
31987
+ }
31988
+ async announce(task) {
31989
+ if (task.publication === "published") return;
31990
+ if (task.announcement !== null) {
31991
+ await task.announcement;
31992
+ return;
31993
+ }
31994
+ task.publication = "publishing";
31995
+ const announcement = this.publishAnnouncement(task);
31996
+ task.announcement = announcement;
31997
+ try {
31998
+ await announcement;
31999
+ } finally {
32000
+ if (task.announcement === announcement) task.announcement = null;
32001
+ }
32002
+ if (isTerminalState(task.state)) await this.publishTerminalState(task);
32003
+ }
32004
+ async publishAnnouncement(task) {
32005
+ try {
32006
+ await this.publishSpawn(task);
32007
+ task.publication = "published";
32008
+ } catch (error51) {
32009
+ task.publication = "unpublished";
32010
+ throw error51;
32011
+ }
32012
+ }
32013
+ async publishSpawn(task) {
32014
+ await this.session.update({
32015
+ sessionUpdate: "tool_call_update",
32016
+ toolCallId: task.itemId,
32017
+ _meta: {
32018
+ [JETBRAINS_META_KEY]: {
32019
+ [AIR_META_KEY]: {
32020
+ [AIR_ASYNC_TASKS_KEY]: {
32021
+ [AIR_ASYNC_TASKS_BACKGROUNDED_KEY]: true
32022
+ }
32023
+ }
32024
+ }
32025
+ }
32026
+ }, task.sessionId);
32027
+ await this.session.update({
32028
+ sessionUpdate: "async_task_spawned",
32029
+ asyncTaskId: task.asyncTaskId,
32030
+ name: task.name,
32031
+ taskType: "shell",
32032
+ showInTranscript: false,
32033
+ canStop: true,
32034
+ toolCallId: task.itemId
32035
+ }, task.sessionId);
32036
+ }
32037
+ remember(threadId, sessionId, terminal, commandTitle2) {
32038
+ const asyncTaskId = wireTaskId(this.rootSessionId, threadId, terminal.itemId);
32039
+ const existing = this.tasks.get(asyncTaskId);
32040
+ if (existing) {
32041
+ existing.processId = terminal.processId;
32042
+ if (commandTitle2 !== void 0) existing.name = commandTitle2;
32043
+ return existing;
32044
+ }
32045
+ const task = {
32046
+ threadId,
32047
+ sessionId,
32048
+ asyncTaskId,
32049
+ processId: terminal.processId,
32050
+ itemId: terminal.itemId,
32051
+ name: commandTitle2 ?? terminal.command,
32052
+ publication: "unpublished",
32053
+ announcement: null,
32054
+ terminalUpdate: null,
32055
+ terminalPublished: false,
32056
+ state: "running"
32057
+ };
32058
+ this.tasks.set(asyncTaskId, task);
32059
+ return task;
32060
+ }
32061
+ async finish(task, state) {
32062
+ if (task.state === "running" || task.state === "stopping") {
32063
+ task.state = state;
32064
+ } else if (task.state !== state) {
32065
+ return;
32066
+ }
32067
+ if (task.announcement !== null) await task.announcement;
32068
+ if (task.publication === "published") await this.publishTerminalState(task);
32069
+ }
32070
+ async publishTerminalState(task) {
32071
+ if (!isTerminalState(task.state) || task.terminalPublished) return;
32072
+ if (task.terminalUpdate !== null) {
32073
+ await task.terminalUpdate;
32074
+ return;
32075
+ }
32076
+ const terminalUpdate = this.session.update({
32077
+ sessionUpdate: "async_task_state_update",
32078
+ asyncTaskId: task.asyncTaskId,
32079
+ state: task.state,
32080
+ toolCallId: task.itemId
32081
+ }, task.sessionId);
32082
+ task.terminalUpdate = terminalUpdate;
32083
+ try {
32084
+ await terminalUpdate;
32085
+ task.terminalPublished = true;
32086
+ } finally {
32087
+ if (task.terminalUpdate === terminalUpdate) task.terminalUpdate = null;
32088
+ }
32089
+ }
32090
+ async listAll(threadId) {
32091
+ const appServer = this.appServer;
32092
+ const generation = this.appServerGeneration;
32093
+ const terminals = [];
32094
+ const seenCursors = /* @__PURE__ */ new Set();
32095
+ let cursor = null;
32096
+ do {
32097
+ const response = await appServer.threadBackgroundTerminalsList({
32098
+ threadId,
32099
+ cursor
32100
+ });
32101
+ if (!this.queryIsCurrent(generation)) return null;
32102
+ terminals.push(...response.data);
32103
+ cursor = response.nextCursor;
32104
+ if (cursor !== null) {
32105
+ if (seenCursors.has(cursor)) {
32106
+ throw new Error("Codex returned a repeated background terminal cursor");
32107
+ }
32108
+ seenCursors.add(cursor);
32109
+ }
32110
+ } while (cursor !== null);
32111
+ return { generation, terminals };
32112
+ }
32113
+ isActive() {
32114
+ return this.enabled && !this.disposed;
32115
+ }
32116
+ canQueryAppServer() {
32117
+ return this.isActive() && this.appServerQueriesEnabled;
32118
+ }
32119
+ queryIsCurrent(generation) {
32120
+ return this.canQueryAppServer() && generation === this.appServerGeneration;
32121
+ }
32122
+ };
32123
+ function isTerminalState(state) {
32124
+ return state === "completed" || state === "failed" || state === "stopped";
32125
+ }
32126
+ function wireTaskId(rootSessionId, threadId, itemId) {
32127
+ return threadId === rootSessionId ? itemId : `${threadId}:${itemId}`;
32128
+ }
32129
+ function notificationThreadId(notification) {
32130
+ const threadId = notification.params.threadId;
32131
+ return typeof threadId === "string" ? threadId : null;
32132
+ }
32133
+
32134
+ // src/CodexAcpServer.ts
31504
32135
  var CODEX_PROCESS_EXITED_ERROR_CODE = 1001;
31505
32136
  function clientSupportsTypedSessionFailures(capabilities) {
31506
32137
  return clientSupportsAirCapability(capabilities, AIR_SESSION_FAILURE_KEY);
@@ -31525,6 +32156,8 @@ var CodexAcpServer = class _CodexAcpServer {
31525
32156
  clientCapabilities;
31526
32157
  terminalOutputMode;
31527
32158
  booleanConfigOptionsSupported;
32159
+ /** Last `authStatus` pushed to the client; used to suppress duplicates. */
32160
+ currentAuthStatus;
31528
32161
  sessions;
31529
32162
  pendingMcpStartupSessions;
31530
32163
  pendingTurnStarts;
@@ -31536,6 +32169,7 @@ var CodexAcpServer = class _CodexAcpServer {
31536
32169
  goalControlGenerations;
31537
32170
  permissionLifecycleContexts;
31538
32171
  codexProcessState;
32172
+ codexProcessGeneration = 0;
31539
32173
  initializeRequest = null;
31540
32174
  providerUpdate = null;
31541
32175
  constructor(connection, codexAcpClient, defaultAuthRequest, getExitCode, getRecentStderr, codexProcessState) {
@@ -31561,14 +32195,16 @@ var CodexAcpServer = class _CodexAcpServer {
31561
32195
  this.clientCapabilities = null;
31562
32196
  this.terminalOutputMode = "terminal_output_delta";
31563
32197
  this.booleanConfigOptionsSupported = false;
32198
+ this.currentAuthStatus = null;
31564
32199
  this.availableCommands = this.createAvailableCommands(codexAcpClient);
32200
+ this.observeCodexProcess();
31565
32201
  }
31566
32202
  createAvailableCommands(client) {
31567
32203
  return new CodexCommands(
31568
32204
  this.connection,
31569
32205
  client,
31570
32206
  (operation) => this.runWithProcessCheck(operation),
31571
- () => this.refreshSessionsAuthState(null)
32207
+ () => this.refreshAuthState(null)
31572
32208
  );
31573
32209
  }
31574
32210
  async initialize(_params) {
@@ -31579,6 +32215,7 @@ var CodexAcpServer = class _CodexAcpServer {
31579
32215
  this.terminalOutputMode = resolveTerminalOutputMode(_params.clientCapabilities);
31580
32216
  this.booleanConfigOptionsSupported = clientSupportsBooleanConfigOptions(_params.clientCapabilities);
31581
32217
  await this.runWithProcessCheck(() => this.codexAcpClient.initialize(_params));
32218
+ this.publishFirstAuthStatusAfterResponse();
31582
32219
  const sessionCapabilities = {
31583
32220
  resume: {},
31584
32221
  list: {},
@@ -31610,6 +32247,11 @@ var CodexAcpServer = class _CodexAcpServer {
31610
32247
  acp: false,
31611
32248
  http: true,
31612
32249
  sse: false
32250
+ },
32251
+ _meta: {
32252
+ // Presence means "this agent pushes `_auth/status_update`". It
32253
+ // never carries a payload, and the client never asks for one.
32254
+ [AUTH_STATUS_META_KEY]: authStatusCapability()
31613
32255
  }
31614
32256
  },
31615
32257
  authMethods: getCodexAuthMethods(_params.clientCapabilities),
@@ -31628,7 +32270,8 @@ var CodexAcpServer = class _CodexAcpServer {
31628
32270
  [AIR_EXTENSION_CAPABILITIES_KEY]: [
31629
32271
  AIR_SESSION_FAILURE_KEY,
31630
32272
  AIR_AGENT_FILE_CHANGE_REPORT_KEY,
31631
- AIR_NATIVE_SUBAGENT_SESSIONS_KEY
32273
+ AIR_NATIVE_SUBAGENT_SESSIONS_KEY,
32274
+ AIR_ASYNC_TASKS_KEY
31632
32275
  ]
31633
32276
  }
31634
32277
  }
@@ -31651,6 +32294,18 @@ var CodexAcpServer = class _CodexAcpServer {
31651
32294
  return await this.unstable_setSessionModel(this.parseLegacySetSessionModelParams(methodRequest.params));
31652
32295
  case SESSION_STEERING_METHOD:
31653
32296
  return await this.executeOrQueueSteeringRequest(this.parseSessionSteerParams(methodRequest.params));
32297
+ case ASYNC_TASK_STOP_METHOD: {
32298
+ if (this.providerUpdate !== null) {
32299
+ await this.providerUpdate;
32300
+ }
32301
+ const sessionState = this.sessions.get(methodRequest.params.sessionId);
32302
+ if (!sessionState) return { stopped: false };
32303
+ return {
32304
+ stopped: await this.runWithProcessCheck(
32305
+ () => sessionState.asyncTasks.stop(methodRequest.params.asyncTaskId)
32306
+ )
32307
+ };
32308
+ }
31654
32309
  case GOAL_CONTROL_METHOD:
31655
32310
  case LEGACY_GOAL_CONTROL_METHOD: {
31656
32311
  const sessionState = this.sessions.get(methodRequest.params.sessionId);
@@ -31741,7 +32396,7 @@ var CodexAcpServer = class _CodexAcpServer {
31741
32396
  async handleError(e) {
31742
32397
  if (e.message.includes("log out") || e.message.includes("cloud requirements")) {
31743
32398
  await this.runWithProcessCheck(() => this.codexAcpClient.logout());
31744
- await this.refreshSessionsAuthState(null);
32399
+ await this.refreshAuthState(null);
31745
32400
  throw RequestError.internalError(`${e.message}
31746
32401
 
31747
32402
  You have been logged out. Please try again.`);
@@ -31892,7 +32547,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
31892
32547
  sessionId,
31893
32548
  clientSupportsSubagents(this.clientCapabilities),
31894
32549
  new ACPSessionConnection(this.connection, sessionId)
31895
- )
32550
+ ),
32551
+ asyncTasks: this.createAsyncTasks(sessionId)
31896
32552
  };
31897
32553
  sessionState.titleGen = new TitleGenerator(
31898
32554
  this.codexAcpClient.appServerClient,
@@ -31900,7 +32556,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31900
32556
  sessionState.cwd,
31901
32557
  () => sessionState.sessionTitleSource
31902
32558
  );
31903
- this.sessions.set(sessionId, sessionState);
32559
+ this.installSessionState(sessionState);
31904
32560
  resumeSubscribed = false;
31905
32561
  const canPublishSessionUpdates = operation !== "fork";
31906
32562
  if (canPublishSessionUpdates && requestedMcpServers.length > 0 && mcpServerStartupVersion !== null) {
@@ -31915,6 +32571,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31915
32571
  }
31916
32572
  if (operation === "resume") {
31917
32573
  this.publishCurrentGoalAsync(sessionState, sessionGeneration);
32574
+ this.publishAsyncTasksAsync(sessionState, sessionGeneration);
31918
32575
  }
31919
32576
  const sessionModelState = this.createModelState(models, currentModelId);
31920
32577
  const sessionModeState = sessionState.agentMode.toSessionModeState();
@@ -31922,12 +32579,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
31922
32579
  }
31923
32580
  async getAuthStateForProvider(authProvider) {
31924
32581
  if (!this.authProviderUsesOpenAiAccount(authProvider)) {
32582
+ await this.publishAuthStatus(authProvider, null);
31925
32583
  return {
31926
32584
  account: null,
31927
32585
  authConfigured: true
31928
32586
  };
31929
32587
  }
31930
32588
  const accountResponse = await this.runWithProcessCheck(() => this.codexAcpClient.getAccount());
32589
+ await this.publishAuthStatus(authProvider, accountResponse.account);
31931
32590
  return {
31932
32591
  account: accountResponse.account,
31933
32592
  authConfigured: accountResponse.account !== null || !accountResponse.requiresOpenaiAuth
@@ -31942,6 +32601,18 @@ Check ${configPath} and project .codex directories, especially their config.toml
31942
32601
  }
31943
32602
  return a === b;
31944
32603
  }
32604
+ createAsyncTasks(sessionId) {
32605
+ return new CodexBackgroundTerminalTasks(
32606
+ clientSupportsAirCapability(this.clientCapabilities, AIR_ASYNC_TASKS_KEY),
32607
+ sessionId,
32608
+ this.codexAcpClient.appServerClient,
32609
+ new ACPSessionConnection(this.connection, sessionId)
32610
+ );
32611
+ }
32612
+ installSessionState(sessionState) {
32613
+ this.sessions.get(sessionState.sessionId)?.asyncTasks.clear();
32614
+ this.sessions.set(sessionState.sessionId, sessionState);
32615
+ }
31945
32616
  getAuthProviderForAuthenticateRequest(request) {
31946
32617
  if (isCodexAuthRequest(request) && request.methodId === "gateway") {
31947
32618
  return "custom-gateway";
@@ -31960,6 +32631,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31960
32631
  thread
31961
32632
  } = await this.getOrCreateSessionWithHistory(params);
31962
32633
  await this.streamThreadHistory(sessionId, thread);
32634
+ await this.getSessionState(sessionId).asyncTasks.reconcile();
31963
32635
  logger.log("Session loaded", {
31964
32636
  sessionId,
31965
32637
  modelId: modelState.currentModelId,
@@ -32033,6 +32705,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32033
32705
  try {
32034
32706
  if (sessionState) {
32035
32707
  await this.interruptSessionTurn(sessionState, "Close", true);
32708
+ sessionState.asyncTasks.clear();
32036
32709
  } else {
32037
32710
  logger.log("Close request received for unknown local session", { sessionId: params.sessionId });
32038
32711
  }
@@ -32106,7 +32779,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32106
32779
  logger.log("Authenticate request failed");
32107
32780
  throw RequestError.invalidParams();
32108
32781
  }
32109
- await this.refreshSessionsAuthState(this.getAuthProviderForAuthenticateRequest(_params));
32782
+ await this.refreshAuthState(this.getAuthProviderForAuthenticateRequest(_params));
32110
32783
  logger.log("Authenticate request completed");
32111
32784
  return {};
32112
32785
  }
@@ -32137,7 +32810,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32137
32810
  async logout(_params) {
32138
32811
  logger.log("Logout request received");
32139
32812
  await this.runWithProcessCheck(() => this.codexAcpClient.logout());
32140
- await this.refreshSessionsAuthState(null);
32813
+ await this.refreshAuthState(null);
32141
32814
  logger.log("Logout request completed");
32142
32815
  }
32143
32816
  listProviders(_params) {
@@ -32168,6 +32841,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
32168
32841
  await Promise.all(activePrompts);
32169
32842
  }
32170
32843
  logger.log("Restarting Codex app-server for provider update", { sessionCount: this.sessions.size });
32844
+ for (const session of this.sessions.values()) {
32845
+ session.asyncTasks.prepareForAppServerReplacement();
32846
+ }
32847
+ await this.finishAllAsyncTasks("stopped", "before the provider restart");
32171
32848
  const replacement = await this.restartCodexClient();
32172
32849
  apply(replacement);
32173
32850
  if (this.initializeRequest === null) {
@@ -32178,6 +32855,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32178
32855
  this.availableCommands = this.createAvailableCommands(replacement);
32179
32856
  const resumeErrors = [];
32180
32857
  for (const session of this.sessions.values()) {
32858
+ session.asyncTasks.setAppServer(replacement.appServerClient);
32181
32859
  try {
32182
32860
  await replacement.resumeSession({
32183
32861
  sessionId: session.sessionId,
@@ -32186,6 +32864,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32186
32864
  mcpServers: session.mcpServers ?? []
32187
32865
  });
32188
32866
  session.authProvider = replacement.getModelProvider();
32867
+ session.asyncTasks.refresh();
32189
32868
  logger.log("Resumed session after provider restart", { sessionId: session.sessionId });
32190
32869
  } catch (error51) {
32191
32870
  resumeErrors.push(error51);
@@ -32215,12 +32894,22 @@ Check ${configPath} and project .codex directories, especially their config.toml
32215
32894
  state.stderr = (state.stderr + data.toString()).slice(-2 * 1024);
32216
32895
  });
32217
32896
  }
32897
+ observeCodexProcess() {
32898
+ const process12 = this.codexProcessState?.connection.process;
32899
+ if (!process12) return;
32900
+ const generation = ++this.codexProcessGeneration;
32901
+ process12.once("exit", () => {
32902
+ if (generation !== this.codexProcessGeneration) return;
32903
+ void this.finishAllAsyncTasks("failed", "after the Codex process exited");
32904
+ });
32905
+ }
32218
32906
  async restartCodexClient() {
32219
32907
  const state = this.codexProcessState;
32220
32908
  if (state === null) {
32221
32909
  throw new Error("Codex process state is unavailable");
32222
32910
  }
32223
32911
  const previous = state.connection;
32912
+ this.codexProcessGeneration += 1;
32224
32913
  const exited = previous.process.exitCode === null ? once(previous.process, "exit") : Promise.resolve();
32225
32914
  previous.process.stdin.end();
32226
32915
  const forceKill = setTimeout(() => {
@@ -32234,21 +32923,162 @@ Check ${configPath} and project .codex directories, especially their config.toml
32234
32923
  state.stderr = "";
32235
32924
  state.connection = startCodexConnection(state.codexPath);
32236
32925
  this.captureStderr();
32926
+ this.observeCodexProcess();
32237
32927
  return new CodexAcpClient(
32238
32928
  new CodexAppServerClient(state.connection.connection),
32239
32929
  state.config,
32240
32930
  state.modelProvider
32241
32931
  );
32242
32932
  }
32933
+ /** Returns whether the auth state was read (and thus the auth status pushed). */
32243
32934
  async refreshSessionsAuthState(authProvider) {
32244
- if (this.sessions.size === 0) return;
32935
+ if (this.sessions.size === 0) return false;
32245
32936
  const sessionsToRefresh = [...this.sessions.values()].filter((sessionState) => this.authProvidersMatch(sessionState.authProvider, authProvider));
32246
- if (sessionsToRefresh.length === 0) return;
32937
+ if (sessionsToRefresh.length === 0) return false;
32247
32938
  const authState = await this.getAuthStateForProvider(authProvider);
32248
32939
  for (const sessionState of sessionsToRefresh) {
32249
32940
  sessionState.account = authState.account;
32250
32941
  sessionState.authConfigured = authState.authConfigured;
32251
32942
  }
32943
+ return true;
32944
+ }
32945
+ /**
32946
+ * Refreshes the sessions of a provider and makes sure the connection-level
32947
+ * `authStatus` is pushed even when no session matched (the empty-screen
32948
+ * login case). Reuses the session refresh read; never adds a second one.
32949
+ */
32950
+ async refreshAuthState(authProvider) {
32951
+ const refreshed = await this.refreshSessionsAuthState(authProvider);
32952
+ if (refreshed) return;
32953
+ try {
32954
+ await this.getAuthStateForProvider(authProvider ?? this.codexAcpClient.getModelProvider());
32955
+ } catch (error51) {
32956
+ logger.log("Failed to refresh auth status", { error: String(error51) });
32957
+ }
32958
+ }
32959
+ /**
32960
+ * Schedules the connection's first `_auth/status_update`: one account read,
32961
+ * pushed whatever it says, including `none`.
32962
+ *
32963
+ * The push must not overtake the `initialize` response. The JSON-RPC layer
32964
+ * writes that response in the microtask that resolves {@link initialize}, so
32965
+ * the read starts from a check-phase callback, which always runs after it.
32966
+ * `initialize` itself never waits for the read.
32967
+ *
32968
+ * "Unconditional" costs nothing extra here: nothing has been pushed yet on
32969
+ * this connection, so {@link setAuthStatus} cannot suppress this one.
32970
+ */
32971
+ publishFirstAuthStatusAfterResponse() {
32972
+ setImmediate(() => void this.publishAuthStatusRead());
32973
+ }
32974
+ /**
32975
+ * Reads the agent-owned identity and pushes it.
32976
+ *
32977
+ * Never rejects: an unreadable source means "nothing to report", not an
32978
+ * error. The client then keeps showing the last pushed value, or "not
32979
+ * reported" when there was none.
32980
+ */
32981
+ async publishAuthStatusRead() {
32982
+ let authStatus;
32983
+ try {
32984
+ authStatus = await this.readAgentAuthIdentity();
32985
+ } catch (error51) {
32986
+ logger.log("Cannot determine auth status", { error: String(error51) });
32987
+ return;
32988
+ }
32989
+ await this.setAuthStatus(authStatus);
32990
+ }
32991
+ /**
32992
+ * Builds the agent-owned auth identity. Routing the client configured
32993
+ * through the ACP `providers/*` API is invisible here: the reported state
32994
+ * is what the agent itself is logged in with. `gateway` stays reserved for
32995
+ * agent-owned gateway state — the `gateway` auth method, or a provider the
32996
+ * user configured in Codex's own config.
32997
+ */
32998
+ async readAgentAuthIdentity() {
32999
+ const authGatewayName = this.codexAcpClient.getAuthGatewayProviderName();
33000
+ if (authGatewayName !== null) {
33001
+ return gatewayStatus(authGatewayName);
33002
+ }
33003
+ const modelProvider = await this.runWithProcessCheck(() => this.codexAcpClient.getAgentConfiguredModelProvider());
33004
+ if (!this.authProviderUsesOpenAiAccount(modelProvider)) {
33005
+ return gatewayStatus(modelProvider);
33006
+ }
33007
+ const accountResponse = await this.runWithProcessCheck(() => this.codexAcpClient.getAccount());
33008
+ return fromAccount(accountResponse.account);
33009
+ }
33010
+ /**
33011
+ * Pushes `_auth/status_update` for the freshly read account of a provider.
33012
+ * Agent-owned gateway authentication wins; a client-driven provider
33013
+ * override is ignored and the agent-owned login is reported instead.
33014
+ */
33015
+ async publishAuthStatus(authProvider, account) {
33016
+ const authGatewayName = this.codexAcpClient.getAuthGatewayProviderName();
33017
+ if (authGatewayName !== null) {
33018
+ await this.setAuthStatus(gatewayStatus(authGatewayName));
33019
+ return;
33020
+ }
33021
+ if (this.authProviderUsesOpenAiAccount(authProvider)) {
33022
+ await this.setAuthStatus(fromAccount(account));
33023
+ return;
33024
+ }
33025
+ if (this.codexAcpClient.isClientConfiguredProvider(authProvider)) {
33026
+ await this.publishAuthStatusRead();
33027
+ return;
33028
+ }
33029
+ await this.setAuthStatus(gatewayStatus(authProvider));
33030
+ }
33031
+ /**
33032
+ * Handles the app-server `account/updated` push: the free freshness channel
33033
+ * for logins and logouts happening outside this connection.
33034
+ */
33035
+ handleAccountUpdated(notification) {
33036
+ void this.applyAccountUpdated(notification);
33037
+ }
33038
+ /**
33039
+ * `account/updated` describes the Codex account only. It must never
33040
+ * overwrite an agent-owned gateway status, which no account event can
33041
+ * invalidate; only a gateway logout or a provider change does.
33042
+ */
33043
+ async applyAccountUpdated(notification) {
33044
+ try {
33045
+ if (this.codexAcpClient.getAuthGatewayProviderName() !== null) {
33046
+ return;
33047
+ }
33048
+ if (this.currentAuthStatus === null) {
33049
+ await this.publishAuthStatusRead();
33050
+ return;
33051
+ }
33052
+ if (this.currentAuthStatus.kind === "gateway") {
33053
+ return;
33054
+ }
33055
+ await this.setAuthStatus(fromAccountUpdated(notification, this.currentAuthStatus));
33056
+ } catch (error51) {
33057
+ logger.log("Failed to apply account update to auth status", { error: String(error51) });
33058
+ }
33059
+ }
33060
+ /**
33061
+ * Stores `next` and pushes `_auth/status_update`.
33062
+ *
33063
+ * A push goes out only when the payload changed. The identity is read on
33064
+ * many occasions — `initialize`, each session create, each `account/updated`
33065
+ * — and almost all of them see the login already reported.
33066
+ * Clients replace their whole state on each update and tolerate duplicates,
33067
+ * so a repeat is harmless, but it is pure noise all the same.
33068
+ *
33069
+ * The first push of a connection always goes out: nothing was reported yet,
33070
+ * so no payload can equal it.
33071
+ */
33072
+ async setAuthStatus(next) {
33073
+ if (sameAuthStatus(this.currentAuthStatus, next)) {
33074
+ return;
33075
+ }
33076
+ this.currentAuthStatus = next;
33077
+ try {
33078
+ await this.connection.notify(AUTH_STATUS_UPDATE_METHOD, { authStatus: next });
33079
+ } catch (error51) {
33080
+ logger.log("Failed to send auth status update", { error: String(error51) });
33081
+ }
32252
33082
  }
32253
33083
  async setSessionMode(_params) {
32254
33084
  logger.log("Set session mode requested", {
@@ -32643,6 +33473,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
32643
33473
  publishCurrentGoalAsync(sessionState, sessionGeneration) {
32644
33474
  void this.publishCurrentGoalBestEffort(sessionState, sessionGeneration, true);
32645
33475
  }
33476
+ publishAsyncTasksAsync(sessionState, sessionGeneration) {
33477
+ if (!this.sessionPublishIsCurrent(sessionState, sessionGeneration)) return;
33478
+ sessionState.asyncTasks.refresh();
33479
+ }
32646
33480
  async publishCurrentGoalBestEffort(sessionState, sessionGeneration, force) {
32647
33481
  try {
32648
33482
  await this.publishCurrentGoal(sessionState, sessionGeneration, force);
@@ -32766,7 +33600,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
32766
33600
  sessionId,
32767
33601
  clientSupportsSubagents(this.clientCapabilities),
32768
33602
  new ACPSessionConnection(this.connection, sessionId)
32769
- )
33603
+ ),
33604
+ asyncTasks: this.createAsyncTasks(sessionId)
32770
33605
  };
32771
33606
  sessionState.titleGen = new TitleGenerator(
32772
33607
  this.codexAcpClient.appServerClient,
@@ -32774,7 +33609,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32774
33609
  sessionState.cwd,
32775
33610
  () => sessionState.sessionTitleSource
32776
33611
  );
32777
- this.sessions.set(sessionId, sessionState);
33612
+ this.installSessionState(sessionState);
32778
33613
  subscribed = false;
32779
33614
  if (requestedMcpServers.length > 0 && mcpServerStartupVersion !== null) {
32780
33615
  this.pendingMcpStartupSessions.set(sessionId, {
@@ -32866,6 +33701,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
32866
33701
  /* @__PURE__ */ new Set([...ancestry, item.agentThreadId]),
32867
33702
  threadCache
32868
33703
  );
33704
+ try {
33705
+ await sessionState.asyncTasks.recover(
33706
+ item.agentThreadId,
33707
+ childSessionId,
33708
+ commandItemIds(childTurn.items)
33709
+ );
33710
+ } catch (error51) {
33711
+ logger.error(`Failed to restore background terminals for ${item.agentThreadId}`, error51);
33712
+ }
32869
33713
  }
32870
33714
  }
32871
33715
  } else if (activityKind === "completed" || activityKind === "interrupted") {
@@ -33438,7 +34282,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
33438
34282
  let promptWasCancelled = false;
33439
34283
  let recoverableSessionFailure = sessionState.sessionFailure;
33440
34284
  sessionState.currentTurnId = null;
33441
- sessionState.lastTokenUsage = null;
33442
34285
  const activePrompt = this.trackActivePrompt(params.sessionId);
33443
34286
  let pendingTurnStart = null;
33444
34287
  const ensurePendingTurnStart = () => {
@@ -33471,7 +34314,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
33471
34314
  clientSupportsPlanUpdates(this.clientCapabilities),
33472
34315
  clientSupportsTypedSessionFailures(this.clientCapabilities),
33473
34316
  this.sessionFailureEpoch,
33474
- sessionState.subagents
34317
+ sessionState.subagents,
34318
+ (accountUpdated) => this.handleAccountUpdated(accountUpdated)
33475
34319
  );
33476
34320
  eventHandler = promptEventHandler;
33477
34321
  const permissionLifecycle = this.permissionLifecycleContext(sessionState);
@@ -33516,6 +34360,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33516
34360
  }
33517
34361
  const commandPromise = this.availableCommands.tryHandleCommand(params.prompt, sessionState, {
33518
34362
  onTurnStartPending: () => {
34363
+ sessionState.lastTokenUsage = null;
33519
34364
  ensurePendingTurnStart();
33520
34365
  },
33521
34366
  onTurnStarted: (turnId, threadId) => {
@@ -33612,6 +34457,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33612
34457
  sessionState.fastModeEnabled,
33613
34458
  sessionState.currentModelSupportsFast
33614
34459
  );
34460
+ sessionState.lastTokenUsage = null;
33615
34461
  ensurePendingTurnStart();
33616
34462
  const sendPromptPromise = this.runWithProcessCheck(
33617
34463
  () => this.codexAcpClient.sendPrompt(
@@ -33914,6 +34760,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33914
34760
  throw new RequestError(requestErrorCode, `VC++ redistributable should be installed`);
33915
34761
  }
33916
34762
  if (exitCode !== null) {
34763
+ await this.finishAllAsyncTasks("failed", "after the Codex process exited");
33917
34764
  const stderr = this.getRecentStderr().trim();
33918
34765
  const detail = stderr ? `:
33919
34766
  ${stderr}` : "";
@@ -33922,6 +34769,15 @@ ${stderr}` : "";
33922
34769
  throw err;
33923
34770
  }
33924
34771
  }
34772
+ async finishAllAsyncTasks(state, reason) {
34773
+ for (const session of this.sessions.values()) {
34774
+ try {
34775
+ await session.asyncTasks.finishAll(state);
34776
+ } catch (error51) {
34777
+ logger.error(`Failed to finish background terminal tasks ${reason}`, error51);
34778
+ }
34779
+ }
34780
+ }
33925
34781
  async cancel(params) {
33926
34782
  const sessionState = this.sessions.get(params.sessionId);
33927
34783
  if (!sessionState) {
@@ -33971,6 +34827,9 @@ function mergeHistoryUpdates(responseItemFallbackUpdates, threadUpdates) {
33971
34827
  }
33972
34828
  return merged;
33973
34829
  }
34830
+ function commandItemIds(items) {
34831
+ return new Set(items.filter((item) => item.type === "commandExecution").map((item) => item.id));
34832
+ }
33974
34833
  function historyUpdateKey(update) {
33975
34834
  switch (update.sessionUpdate) {
33976
34835
  case "user_message_chunk":
@@ -34154,6 +35013,10 @@ var goalControlParamsParser = external_exports.discriminatedUnion("action", [
34154
35013
  action: external_exports.enum(["pause", "resume", "clear"])
34155
35014
  }).passthrough()
34156
35015
  ]);
35016
+ var asyncTaskStopParamsParser = external_exports.object({
35017
+ sessionId: external_exports.string().trim().min(1),
35018
+ asyncTaskId: external_exports.string().trim().min(1)
35019
+ }).passthrough();
34157
35020
  if (process.argv.includes("--version")) {
34158
35021
  console.log(`${package_default.name} ${package_default.version}`);
34159
35022
  process.exit(0);
@@ -34234,5 +35097,5 @@ function startAcpServer() {
34234
35097
  codexAcpServer = null;
34235
35098
  }
34236
35099
  });
34237
- }).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);
35100
+ }).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);
34238
35101
  }