@agentclientprotocol/codex-acp 1.9.0 → 1.10.1-preview.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 +17 -0
  2. package/dist/index.js +574 -64
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -13,6 +13,7 @@ Use [OpenAI Codex](https://github.com/openai/codex) from [Agent Client Protocol]
13
13
  - Text prompts, embedded context, images, resource links, and additional workspace directories.
14
14
  - Shell command, file change, [permission request](docs/permission-extension.md), MCP tool call, terminal output, reasoning, plan, web search, image generation, image view, token usage, and review events.
15
15
  - [Native ACP subagent sessions](docs/subagent-sessions.md) (after capability negotiation) with separate child histories and root-routed permissions; a legacy tool-call fallback otherwise.
16
+ - [Background terminal tasks](docs/async-tasks.md) in AIR, with task status and targeted stop support after capability negotiation.
16
17
  - Session-scoped long-running goals through the provider-neutral [goal extension](docs/goal-extension.md).
17
18
  - A per-turn [agent file-change report](docs/agent-file-change-report.md) after capability negotiation.
18
19
  - Client-provided MCP servers over command-based stdio config and HTTP transport.
@@ -39,6 +40,16 @@ The npm package includes a compatible `@openai/codex` dependency. Set `CODEX_PAT
39
40
  CODEX_PATH=/path/to/codex npx -y @agentclientprotocol/codex-acp
40
41
  ```
41
42
 
43
+ To try changes that have landed on `main` but are not released yet, install from the
44
+ `preview` channel. Pushes to `main` trigger preview publishing without waiting
45
+ for CI or release-please; release commits are excluded, and newer pushes can
46
+ replace queued previews. See
47
+ [docs/RELEASES.md](docs/RELEASES.md#preview-releases).
48
+
49
+ ```bash
50
+ npx -y @agentclientprotocol/codex-acp@preview
51
+ ```
52
+
42
53
  ## Authentication
43
54
 
44
55
  The adapter advertises ACP auth methods during initialization. Clients can authenticate with:
@@ -82,6 +93,12 @@ Subagent sessions follow the draft [ACP subagent RFD](https://github.com/agentcl
82
93
 
83
94
  See [docs/subagent-sessions.md](docs/subagent-sessions.md) for the negotiation, lifecycle events, `session/load` reconstruction, and legacy fallback details.
84
95
 
96
+ ### Background terminal tasks
97
+
98
+ Codex can keep a shell command running after a turn continues. AIR clients can show this work in the Async Tasks panel and stop one command.
99
+
100
+ See [docs/async-tasks.md](docs/async-tasks.md) for the capability, lifecycle events, and stop request.
101
+
85
102
  ## License
86
103
 
87
104
  By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.
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);
@@ -24306,17 +24324,29 @@ var CodexEventHandler = class _CodexEventHandler {
24306
24324
  }
24307
24325
  async handleNotification(notification) {
24308
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
+ }
24309
24331
  const handledBySubagents = await this.subagents.handle(notification);
24310
24332
  for (const buffered of this.subagents.takeBufferedNotifications()) {
24311
24333
  await this.handleNotification(buffered);
24312
24334
  }
24313
- if (handledBySubagents) {
24314
- 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);
24315
24339
  }
24316
- if (this.subagents.shouldIgnore(notification)) {
24317
- return;
24340
+ if (!handledBySubagents) {
24341
+ await this.sessionState.asyncTasks.handleNotification(
24342
+ notification,
24343
+ this.subagents.notificationSessionId(notification),
24344
+ toolCallTitle(updateEvent)
24345
+ );
24318
24346
  }
24319
- const updateEvent = await this.createUpdateEvent(notification);
24347
+ if (handledBySubagents) return;
24348
+ if (ignoredBySubagents) return;
24349
+ if (updateEvent === void 0) updateEvent = await this.createUpdateEvent(notification);
24320
24350
  if (updateEvent) {
24321
24351
  await this.session.update(updateEvent, this.subagents.notificationSessionId(notification));
24322
24352
  }
@@ -25153,6 +25183,10 @@ ${event.stdin}
25153
25183
  return createGuardianApprovalReviewToolCall(params);
25154
25184
  }
25155
25185
  };
25186
+ function toolCallTitle(update) {
25187
+ if (update?.sessionUpdate !== "tool_call") return void 0;
25188
+ return update.title;
25189
+ }
25156
25190
 
25157
25191
  // src/permissions/option-ids.ts
25158
25192
  var ApprovalOptionId = {
@@ -25922,8 +25956,9 @@ function buildMcpPermissionRequest(sessionId, params, context, nextStandaloneToo
25922
25956
  toolCallId: nextStandaloneToolCallId(),
25923
25957
  kind: context.isToolApproval ? "execute" : "other",
25924
25958
  status: "pending",
25959
+ title: context.isToolApproval ? "MCP tool call approval" : "Question from MCP server",
25925
25960
  content: [messageContent],
25926
- rawInput: { serverName: params.serverName, schema: params.requestedSchema }
25961
+ rawInput: { serverName: params.serverName, description: params.message, schema: params.requestedSchema }
25927
25962
  },
25928
25963
  ...context.isToolApproval ? { _meta: { is_mcp_tool_approval: true } } : {},
25929
25964
  options
@@ -25941,8 +25976,9 @@ function buildMcpPermissionRequest(sessionId, params, context, nextStandaloneToo
25941
25976
  toolCallId: `elicitation-${params.elicitationId}`,
25942
25977
  kind: "fetch",
25943
25978
  status: "pending",
25979
+ title: "MCP server requests to open a URL",
25944
25980
  content: [messageContent],
25945
- rawInput: { serverName: params.serverName, url: params.url }
25981
+ rawInput: { serverName: params.serverName, description: params.message, url: params.url }
25946
25982
  },
25947
25983
  options
25948
25984
  },
@@ -26124,11 +26160,29 @@ var CodexElicitationHandler = class {
26124
26160
  context.isToolApproval,
26125
26161
  context.persistOptions
26126
26162
  );
26127
- if (correlatedCallId !== void 0 && result.action === "accept") {
26128
- await this.connection.notify(methods.client.session.update, {
26129
- sessionId: params.threadId,
26130
- update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }
26131
- });
26163
+ if (correlatedCallId !== void 0) {
26164
+ if (result.action === "accept") {
26165
+ await this.connection.notify(methods.client.session.update, {
26166
+ sessionId: params.threadId,
26167
+ update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }
26168
+ });
26169
+ }
26170
+ } else {
26171
+ try {
26172
+ await this.connection.notify(methods.client.session.update, {
26173
+ sessionId: params.threadId,
26174
+ update: {
26175
+ sessionUpdate: "tool_call_update",
26176
+ toolCallId: request.toolCall.toolCallId,
26177
+ status: "completed",
26178
+ title: request.toolCall.title,
26179
+ content: request.toolCall.content,
26180
+ rawOutput: { action: result.action }
26181
+ }
26182
+ });
26183
+ } catch (error51) {
26184
+ logger.error("Failed to finalize standalone MCP elicitation tool call", error51);
26185
+ }
26132
26186
  }
26133
26187
  return result;
26134
26188
  } catch (error51) {
@@ -27285,7 +27339,7 @@ var package_default = {
27285
27339
  publishConfig: {
27286
27340
  access: "public"
27287
27341
  },
27288
- version: "1.9.0",
27342
+ version: "1.10.1-preview.1",
27289
27343
  description: "",
27290
27344
  main: "dist/index.js",
27291
27345
  bin: {
@@ -27348,7 +27402,7 @@ var package_default = {
27348
27402
  },
27349
27403
  dependencies: {
27350
27404
  "@agentclientprotocol/sdk": "^1.4.0",
27351
- "@openai/codex": "^0.153.2",
27405
+ "@openai/codex": "^0.153.3",
27352
27406
  diff: "^9.0.0",
27353
27407
  open: "^11.0.1",
27354
27408
  "vscode-jsonrpc": "^9.0.1",
@@ -27855,6 +27909,7 @@ async function forkSession(request, additionalDirectories, dependencies) {
27855
27909
  await dependencies.refreshSkills(request.cwd, additionalDirectories);
27856
27910
  const lastTurnId = await resolveForkTurnId(request, dependencies.codexClient);
27857
27911
  const response = await dependencies.codexClient.threadFork({
27912
+ excludeTurns: true,
27858
27913
  config: await dependencies.createSessionConfig(
27859
27914
  request.cwd,
27860
27915
  additionalDirectories,
@@ -27880,10 +27935,7 @@ async function forkSession(request, additionalDirectories, dependencies) {
27880
27935
  async function resolveForkTurnId(request, codexClient) {
27881
27936
  const forkPoint = readAirForkPoint(request._meta);
27882
27937
  if (!forkPoint) return void 0;
27883
- const history = await codexClient.threadRead({
27884
- threadId: request.sessionId,
27885
- includeTurns: true
27886
- });
27938
+ const history = await codexClient.threadReadWithHistory(request.sessionId);
27887
27939
  const candidateIds = airForkMessageIdCandidates(forkPoint.messageId);
27888
27940
  const itemTurnId = candidateIds.map((candidateId) => history.thread.turns.find((turn) => turn.items.some((item) => item.id === candidateId))?.id).find((turnId) => turnId !== void 0);
27889
27941
  if (itemTurnId) return itemTurnId;
@@ -28306,6 +28358,7 @@ var CodexAcpClient = class {
28306
28358
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
28307
28359
  await this.refreshSkills(request.cwd, additionalDirectories);
28308
28360
  const response = await this.codexClient.threadResume({
28361
+ excludeTurns: true,
28309
28362
  config: await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []),
28310
28363
  cwd: request.cwd,
28311
28364
  modelProvider: await this.getResumeModelProvider(),
@@ -28340,16 +28393,17 @@ var CodexAcpClient = class {
28340
28393
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
28341
28394
  await this.refreshSkills(request.cwd, additionalDirectories);
28342
28395
  const response = await this.codexClient.threadResume({
28396
+ excludeTurns: true,
28343
28397
  config: await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []),
28344
28398
  cwd: request.cwd,
28345
28399
  modelProvider: await this.getResumeModelProvider(),
28346
28400
  threadId: request.sessionId
28347
28401
  });
28348
28402
  onSubscribed?.();
28349
- const historyResponse = await this.codexClient.threadRead({
28350
- threadId: response.thread.id,
28351
- includeTurns: true
28352
- });
28403
+ const thread = response.thread.historyMode === "paginated" ? {
28404
+ ...response.thread,
28405
+ turns: response.turnsBackwardsCursor === null ? [] : await this.codexClient.threadReadHistory(response.thread.id, response.turnsBackwardsCursor)
28406
+ } : (await this.codexClient.threadReadWithHistory(response.thread.id)).thread;
28353
28407
  const codexModels = await this.fetchAvailableModels();
28354
28408
  const currentModelId = this.createModelId(codexModels, response.model, response.reasoningEffort).toString();
28355
28409
  return {
@@ -28359,15 +28413,12 @@ var CodexAcpClient = class {
28359
28413
  collaborationMode: this.getCollaborationMode(response.thread.id),
28360
28414
  modelProvider: response.modelProvider,
28361
28415
  currentServiceTier: response.serviceTier ?? null,
28362
- thread: historyResponse.thread,
28416
+ thread,
28363
28417
  additionalDirectories
28364
28418
  };
28365
28419
  }
28366
28420
  async readSessionThread(sessionId) {
28367
- return (await this.codexClient.threadRead({
28368
- threadId: sessionId,
28369
- includeTurns: true
28370
- })).thread;
28421
+ return (await this.codexClient.threadReadWithHistory(sessionId)).thread;
28371
28422
  }
28372
28423
  async newSession(request) {
28373
28424
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
@@ -28648,6 +28699,7 @@ var CodexAcpClient = class {
28648
28699
  let lateStopReason = null;
28649
28700
  try {
28650
28701
  const forkPromise = this.codexClient.threadFork({
28702
+ excludeTurns: true,
28651
28703
  threadId: params.sessionId,
28652
28704
  lastTurnId: params.turnId,
28653
28705
  cwd: params.workspace.cwd,
@@ -29482,6 +29534,41 @@ var CodexAppServerClient = class {
29482
29534
  async threadRead(params) {
29483
29535
  return await this.sendRequest({ method: "thread/read", params });
29484
29536
  }
29537
+ async threadTurnsList(params) {
29538
+ return await this.sendRequest({ method: "thread/turns/list", params });
29539
+ }
29540
+ async threadReadWithHistory(threadId) {
29541
+ const response = await this.threadRead({ threadId });
29542
+ if (response.thread.historyMode === "legacy") {
29543
+ return await this.threadRead({ threadId, includeTurns: true });
29544
+ }
29545
+ const turns = await this.threadReadHistory(threadId);
29546
+ return { ...response, thread: { ...response.thread, turns } };
29547
+ }
29548
+ async threadReadHistory(threadId, initialCursor = null) {
29549
+ const turns = [];
29550
+ const seenCursors = /* @__PURE__ */ new Set();
29551
+ if (initialCursor !== null) seenCursors.add(initialCursor);
29552
+ let cursor = initialCursor;
29553
+ do {
29554
+ const page = await this.threadTurnsList({
29555
+ threadId,
29556
+ cursor,
29557
+ limit: 50,
29558
+ sortDirection: "desc",
29559
+ itemsView: "full"
29560
+ });
29561
+ turns.push(...page.data);
29562
+ cursor = page.nextCursor;
29563
+ if (cursor !== null) {
29564
+ if (seenCursors.has(cursor)) {
29565
+ throw new Error("Codex returned a repeated thread history cursor");
29566
+ }
29567
+ seenCursors.add(cursor);
29568
+ }
29569
+ } while (cursor !== null);
29570
+ return turns.reverse();
29571
+ }
29485
29572
  async threadArchive(params) {
29486
29573
  return await this.sendRequest({ method: "thread/archive", params });
29487
29574
  }
@@ -29491,6 +29578,12 @@ var CodexAppServerClient = class {
29491
29578
  async threadCompactStart(params) {
29492
29579
  return await this.sendRequest({ method: "thread/compact/start", params });
29493
29580
  }
29581
+ async threadBackgroundTerminalsList(params) {
29582
+ return await this.sendRequest({ method: "thread/backgroundTerminals/list", params });
29583
+ }
29584
+ async threadBackgroundTerminalsTerminate(params) {
29585
+ return await this.sendRequest({ method: "thread/backgroundTerminals/terminate", params });
29586
+ }
29494
29587
  async threadGoalSet(params) {
29495
29588
  return await this.sendRequest({ method: "thread/goal/set", params });
29496
29589
  }
@@ -31462,6 +31555,9 @@ function numberValue(value) {
31462
31555
  return typeof value === "number" && Number.isFinite(value) ? value : null;
31463
31556
  }
31464
31557
 
31558
+ // src/async-tasks/AsyncTaskExtension.ts
31559
+ var ASYNC_TASK_STOP_METHOD = "_session/async_task/stop";
31560
+
31465
31561
  // src/AuthStatusMeta.ts
31466
31562
  var AUTH_STATUS_UPDATE_METHOD = "_auth/status_update";
31467
31563
  var AUTH_STATUS_META_KEY = "authStatus";
@@ -31599,7 +31695,7 @@ function sameAuthStatus(previous, next) {
31599
31695
  var LEGACY_SET_SESSION_MODEL_METHOD = "session/set_model";
31600
31696
  var SESSION_STEERING_METHOD = "_session/steering";
31601
31697
  function isExtMethodRequest(request) {
31602
- 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;
31698
+ 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;
31603
31699
  }
31604
31700
 
31605
31701
  // src/FastModeConfig.ts
@@ -31666,6 +31762,15 @@ function clientSupportsPlanUpdates(clientCapabilities) {
31666
31762
  return clientCapabilities?.plan != null;
31667
31763
  }
31668
31764
 
31765
+ // src/subagents/AcpSubagents.ts
31766
+ function clientSupportsSubagents(capabilities) {
31767
+ const subagents = capabilities?.subagents;
31768
+ if (typeof subagents === "object" && subagents !== null && !Array.isArray(subagents)) {
31769
+ return true;
31770
+ }
31771
+ return clientSupportsAirCapability(capabilities, AIR_NATIVE_SUBAGENT_SESSIONS_KEY);
31772
+ }
31773
+
31669
31774
  // src/CodexAcpServer.ts
31670
31775
  import { randomUUID as randomUUID2 } from "node:crypto";
31671
31776
 
@@ -31754,6 +31859,332 @@ function extractTitle(turn) {
31754
31859
 
31755
31860
  // src/CodexAcpServer.ts
31756
31861
  import { once } from "node:events";
31862
+
31863
+ // src/async-tasks/CodexBackgroundTerminalTasks.ts
31864
+ var CodexBackgroundTerminalTasks = class {
31865
+ constructor(enabled, rootSessionId, appServer, session) {
31866
+ this.enabled = enabled;
31867
+ this.rootSessionId = rootSessionId;
31868
+ this.appServer = appServer;
31869
+ this.session = session;
31870
+ }
31871
+ enabled;
31872
+ rootSessionId;
31873
+ appServer;
31874
+ session;
31875
+ tasks = /* @__PURE__ */ new Map();
31876
+ syncs = /* @__PURE__ */ new Map();
31877
+ appServerGeneration = 0;
31878
+ appServerQueriesEnabled = true;
31879
+ disposed = false;
31880
+ async handleNotification(notification, sessionId, commandTitle2) {
31881
+ if (!this.isActive()) return;
31882
+ const threadId = notificationThreadId(notification);
31883
+ if (threadId === null) return;
31884
+ if (notification.method === "item/started" && notification.params.item.type === "commandExecution") {
31885
+ this.observeCommandStarted(notification.params.item, threadId, sessionId, commandTitle2);
31886
+ return;
31887
+ }
31888
+ if (notification.method === "item/completed" && notification.params.item.type === "commandExecution") {
31889
+ await this.observeCommandCompleted(notification.params.item, threadId);
31890
+ return;
31891
+ }
31892
+ if (notification.method === "turn/completed") {
31893
+ await this.reconcile(threadId, sessionId);
31894
+ return;
31895
+ }
31896
+ if (notification.method === "item/started") {
31897
+ this.refresh(threadId, sessionId);
31898
+ }
31899
+ }
31900
+ observeCommandStarted(item, threadId, sessionId, commandTitle2) {
31901
+ if (!this.isActive() || item.processId === null) return;
31902
+ this.remember(threadId, sessionId, {
31903
+ itemId: item.id,
31904
+ processId: item.processId,
31905
+ command: item.command
31906
+ }, commandTitle2);
31907
+ }
31908
+ async observeCommandCompleted(item, threadId) {
31909
+ if (!this.isActive()) return;
31910
+ const task = this.tasks.get(wireTaskId(this.rootSessionId, threadId, item.id));
31911
+ if (task) await this.finish(task, item.status === "completed" ? "completed" : "failed");
31912
+ }
31913
+ refresh(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31914
+ void this.reconcile(threadId, sessionId);
31915
+ }
31916
+ async reconcile(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31917
+ try {
31918
+ await this.sync(threadId, sessionId);
31919
+ } catch (error51) {
31920
+ if (this.isActive()) logger.error(`Failed to list background terminals for ${threadId}`, error51);
31921
+ }
31922
+ }
31923
+ setAppServer(appServer) {
31924
+ this.appServer = appServer;
31925
+ this.appServerGeneration += 1;
31926
+ this.appServerQueriesEnabled = true;
31927
+ }
31928
+ prepareForAppServerReplacement() {
31929
+ this.appServerGeneration += 1;
31930
+ this.appServerQueriesEnabled = false;
31931
+ }
31932
+ async recover(threadId, sessionId, itemIds) {
31933
+ if (!this.canQueryAppServer() || itemIds.size === 0) return;
31934
+ const snapshot = await this.listAll(threadId);
31935
+ if (snapshot === null) return;
31936
+ for (const terminal of snapshot.terminals) {
31937
+ if (!this.queryIsCurrent(snapshot.generation)) return;
31938
+ if (!itemIds.has(terminal.itemId)) continue;
31939
+ const task = this.remember(threadId, sessionId, terminal);
31940
+ if (task.publication === "unpublished" && task.state === "running") await this.announce(task);
31941
+ }
31942
+ }
31943
+ async finishAll(state) {
31944
+ if (!this.isActive()) return;
31945
+ const errors = [];
31946
+ for (const task of this.tasks.values()) {
31947
+ try {
31948
+ await this.finish(task, state);
31949
+ } catch (error51) {
31950
+ errors.push(error51);
31951
+ }
31952
+ }
31953
+ if (errors.length > 0) {
31954
+ throw new AggregateError(errors, `Failed to finish ${errors.length} background terminal task(s)`);
31955
+ }
31956
+ }
31957
+ async sync(threadId = this.rootSessionId, sessionId = this.rootSessionId) {
31958
+ if (!this.canQueryAppServer()) return;
31959
+ const current = this.syncs.get(threadId);
31960
+ if (current) {
31961
+ current.requested = true;
31962
+ current.sessionId = sessionId;
31963
+ return await current.promise;
31964
+ }
31965
+ const pending = {
31966
+ requested: false,
31967
+ sessionId,
31968
+ promise: Promise.resolve()
31969
+ };
31970
+ pending.promise = this.syncUntilCurrent(threadId, pending).finally(() => {
31971
+ if (this.syncs.get(threadId) === pending) this.syncs.delete(threadId);
31972
+ });
31973
+ this.syncs.set(threadId, pending);
31974
+ await pending.promise;
31975
+ }
31976
+ async stop(taskId) {
31977
+ if (!this.isActive()) return false;
31978
+ const task = this.tasks.get(taskId);
31979
+ if (!task || task.publication !== "published") return false;
31980
+ if (task.state === "stopped" && !task.terminalPublished) {
31981
+ await this.publishTerminalState(task);
31982
+ return true;
31983
+ }
31984
+ if (task.state !== "running") return false;
31985
+ task.state = "stopping";
31986
+ try {
31987
+ const response = await this.appServer.threadBackgroundTerminalsTerminate({
31988
+ threadId: task.threadId,
31989
+ processId: task.processId
31990
+ });
31991
+ if (!response.terminated) {
31992
+ if (task.state === "stopping") task.state = "running";
31993
+ return false;
31994
+ }
31995
+ await this.finish(task, "stopped");
31996
+ return true;
31997
+ } catch (error51) {
31998
+ if (task.state === "stopping") task.state = "running";
31999
+ throw error51;
32000
+ }
32001
+ }
32002
+ clear() {
32003
+ this.disposed = true;
32004
+ this.appServerGeneration += 1;
32005
+ this.appServerQueriesEnabled = false;
32006
+ this.tasks.clear();
32007
+ this.syncs.clear();
32008
+ }
32009
+ async syncThread(threadId, sessionId) {
32010
+ const snapshot = await this.listAll(threadId);
32011
+ if (snapshot === null || !this.queryIsCurrent(snapshot.generation)) return;
32012
+ const liveTaskIds = /* @__PURE__ */ new Set();
32013
+ for (const terminal of snapshot.terminals) {
32014
+ if (!this.queryIsCurrent(snapshot.generation)) return;
32015
+ liveTaskIds.add(terminal.itemId);
32016
+ const task = this.remember(threadId, sessionId, terminal);
32017
+ if (task.publication === "unpublished" && task.state === "running") await this.announce(task);
32018
+ }
32019
+ for (const task of this.tasks.values()) {
32020
+ if (!this.queryIsCurrent(snapshot.generation)) return;
32021
+ if (task.threadId === threadId && task.publication === "published" && (task.state === "running" || task.state === "stopping") && !liveTaskIds.has(task.itemId)) {
32022
+ await this.finish(task, "stopped");
32023
+ }
32024
+ }
32025
+ }
32026
+ async syncUntilCurrent(threadId, pending) {
32027
+ let hasFailure = false;
32028
+ let failure;
32029
+ do {
32030
+ pending.requested = false;
32031
+ try {
32032
+ await this.syncThread(threadId, pending.sessionId);
32033
+ hasFailure = false;
32034
+ } catch (error51) {
32035
+ hasFailure = true;
32036
+ failure = error51;
32037
+ }
32038
+ } while (pending.requested && this.canQueryAppServer());
32039
+ if (hasFailure) throw failure;
32040
+ }
32041
+ async announce(task) {
32042
+ if (task.publication === "published") return;
32043
+ if (task.announcement !== null) {
32044
+ await task.announcement;
32045
+ return;
32046
+ }
32047
+ task.publication = "publishing";
32048
+ const announcement = this.publishAnnouncement(task);
32049
+ task.announcement = announcement;
32050
+ try {
32051
+ await announcement;
32052
+ } finally {
32053
+ if (task.announcement === announcement) task.announcement = null;
32054
+ }
32055
+ if (isTerminalState(task.state)) await this.publishTerminalState(task);
32056
+ }
32057
+ async publishAnnouncement(task) {
32058
+ try {
32059
+ await this.publishSpawn(task);
32060
+ task.publication = "published";
32061
+ } catch (error51) {
32062
+ task.publication = "unpublished";
32063
+ throw error51;
32064
+ }
32065
+ }
32066
+ async publishSpawn(task) {
32067
+ await this.session.update({
32068
+ sessionUpdate: "tool_call_update",
32069
+ toolCallId: task.itemId,
32070
+ _meta: {
32071
+ [JETBRAINS_META_KEY]: {
32072
+ [AIR_META_KEY]: {
32073
+ [AIR_ASYNC_TASKS_KEY]: {
32074
+ [AIR_ASYNC_TASKS_BACKGROUNDED_KEY]: true
32075
+ }
32076
+ }
32077
+ }
32078
+ }
32079
+ }, task.sessionId);
32080
+ await this.session.update({
32081
+ sessionUpdate: "async_task_spawned",
32082
+ asyncTaskId: task.asyncTaskId,
32083
+ name: task.name,
32084
+ taskType: "shell",
32085
+ showInTranscript: false,
32086
+ canStop: true,
32087
+ toolCallId: task.itemId
32088
+ }, task.sessionId);
32089
+ }
32090
+ remember(threadId, sessionId, terminal, commandTitle2) {
32091
+ const asyncTaskId = wireTaskId(this.rootSessionId, threadId, terminal.itemId);
32092
+ const existing = this.tasks.get(asyncTaskId);
32093
+ if (existing) {
32094
+ existing.processId = terminal.processId;
32095
+ if (commandTitle2 !== void 0) existing.name = commandTitle2;
32096
+ return existing;
32097
+ }
32098
+ const task = {
32099
+ threadId,
32100
+ sessionId,
32101
+ asyncTaskId,
32102
+ processId: terminal.processId,
32103
+ itemId: terminal.itemId,
32104
+ name: commandTitle2 ?? terminal.command,
32105
+ publication: "unpublished",
32106
+ announcement: null,
32107
+ terminalUpdate: null,
32108
+ terminalPublished: false,
32109
+ state: "running"
32110
+ };
32111
+ this.tasks.set(asyncTaskId, task);
32112
+ return task;
32113
+ }
32114
+ async finish(task, state) {
32115
+ if (task.state === "running" || task.state === "stopping") {
32116
+ task.state = state;
32117
+ } else if (task.state !== state) {
32118
+ return;
32119
+ }
32120
+ if (task.announcement !== null) await task.announcement;
32121
+ if (task.publication === "published") await this.publishTerminalState(task);
32122
+ }
32123
+ async publishTerminalState(task) {
32124
+ if (!isTerminalState(task.state) || task.terminalPublished) return;
32125
+ if (task.terminalUpdate !== null) {
32126
+ await task.terminalUpdate;
32127
+ return;
32128
+ }
32129
+ const terminalUpdate = this.session.update({
32130
+ sessionUpdate: "async_task_state_update",
32131
+ asyncTaskId: task.asyncTaskId,
32132
+ state: task.state,
32133
+ toolCallId: task.itemId
32134
+ }, task.sessionId);
32135
+ task.terminalUpdate = terminalUpdate;
32136
+ try {
32137
+ await terminalUpdate;
32138
+ task.terminalPublished = true;
32139
+ } finally {
32140
+ if (task.terminalUpdate === terminalUpdate) task.terminalUpdate = null;
32141
+ }
32142
+ }
32143
+ async listAll(threadId) {
32144
+ const appServer = this.appServer;
32145
+ const generation = this.appServerGeneration;
32146
+ const terminals = [];
32147
+ const seenCursors = /* @__PURE__ */ new Set();
32148
+ let cursor = null;
32149
+ do {
32150
+ const response = await appServer.threadBackgroundTerminalsList({
32151
+ threadId,
32152
+ cursor
32153
+ });
32154
+ if (!this.queryIsCurrent(generation)) return null;
32155
+ terminals.push(...response.data);
32156
+ cursor = response.nextCursor;
32157
+ if (cursor !== null) {
32158
+ if (seenCursors.has(cursor)) {
32159
+ throw new Error("Codex returned a repeated background terminal cursor");
32160
+ }
32161
+ seenCursors.add(cursor);
32162
+ }
32163
+ } while (cursor !== null);
32164
+ return { generation, terminals };
32165
+ }
32166
+ isActive() {
32167
+ return this.enabled && !this.disposed;
32168
+ }
32169
+ canQueryAppServer() {
32170
+ return this.isActive() && this.appServerQueriesEnabled;
32171
+ }
32172
+ queryIsCurrent(generation) {
32173
+ return this.canQueryAppServer() && generation === this.appServerGeneration;
32174
+ }
32175
+ };
32176
+ function isTerminalState(state) {
32177
+ return state === "completed" || state === "failed" || state === "stopped";
32178
+ }
32179
+ function wireTaskId(rootSessionId, threadId, itemId) {
32180
+ return threadId === rootSessionId ? itemId : `${threadId}:${itemId}`;
32181
+ }
32182
+ function notificationThreadId(notification) {
32183
+ const threadId = notification.params.threadId;
32184
+ return typeof threadId === "string" ? threadId : null;
32185
+ }
32186
+
32187
+ // src/CodexAcpServer.ts
31757
32188
  var CODEX_PROCESS_EXITED_ERROR_CODE = 1001;
31758
32189
  function clientSupportsTypedSessionFailures(capabilities) {
31759
32190
  return clientSupportsAirCapability(capabilities, AIR_SESSION_FAILURE_KEY);
@@ -31791,6 +32222,7 @@ var CodexAcpServer = class _CodexAcpServer {
31791
32222
  goalControlGenerations;
31792
32223
  permissionLifecycleContexts;
31793
32224
  codexProcessState;
32225
+ codexProcessGeneration = 0;
31794
32226
  initializeRequest = null;
31795
32227
  providerUpdate = null;
31796
32228
  constructor(connection, codexAcpClient, defaultAuthRequest, getExitCode, getRecentStderr, codexProcessState) {
@@ -31818,6 +32250,7 @@ var CodexAcpServer = class _CodexAcpServer {
31818
32250
  this.booleanConfigOptionsSupported = false;
31819
32251
  this.currentAuthStatus = null;
31820
32252
  this.availableCommands = this.createAvailableCommands(codexAcpClient);
32253
+ this.observeCodexProcess();
31821
32254
  }
31822
32255
  createAvailableCommands(client) {
31823
32256
  return new CodexCommands(
@@ -31890,7 +32323,8 @@ var CodexAcpServer = class _CodexAcpServer {
31890
32323
  [AIR_EXTENSION_CAPABILITIES_KEY]: [
31891
32324
  AIR_SESSION_FAILURE_KEY,
31892
32325
  AIR_AGENT_FILE_CHANGE_REPORT_KEY,
31893
- AIR_NATIVE_SUBAGENT_SESSIONS_KEY
32326
+ AIR_NATIVE_SUBAGENT_SESSIONS_KEY,
32327
+ AIR_ASYNC_TASKS_KEY
31894
32328
  ]
31895
32329
  }
31896
32330
  }
@@ -31913,6 +32347,18 @@ var CodexAcpServer = class _CodexAcpServer {
31913
32347
  return await this.unstable_setSessionModel(this.parseLegacySetSessionModelParams(methodRequest.params));
31914
32348
  case SESSION_STEERING_METHOD:
31915
32349
  return await this.executeOrQueueSteeringRequest(this.parseSessionSteerParams(methodRequest.params));
32350
+ case ASYNC_TASK_STOP_METHOD: {
32351
+ if (this.providerUpdate !== null) {
32352
+ await this.providerUpdate;
32353
+ }
32354
+ const sessionState = this.sessions.get(methodRequest.params.sessionId);
32355
+ if (!sessionState) return { stopped: false };
32356
+ return {
32357
+ stopped: await this.runWithProcessCheck(
32358
+ () => sessionState.asyncTasks.stop(methodRequest.params.asyncTaskId)
32359
+ )
32360
+ };
32361
+ }
31916
32362
  case GOAL_CONTROL_METHOD:
31917
32363
  case LEGACY_GOAL_CONTROL_METHOD: {
31918
32364
  const sessionState = this.sessions.get(methodRequest.params.sessionId);
@@ -32154,7 +32600,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
32154
32600
  sessionId,
32155
32601
  clientSupportsSubagents(this.clientCapabilities),
32156
32602
  new ACPSessionConnection(this.connection, sessionId)
32157
- )
32603
+ ),
32604
+ asyncTasks: this.createAsyncTasks(sessionId)
32158
32605
  };
32159
32606
  sessionState.titleGen = new TitleGenerator(
32160
32607
  this.codexAcpClient.appServerClient,
@@ -32162,7 +32609,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32162
32609
  sessionState.cwd,
32163
32610
  () => sessionState.sessionTitleSource
32164
32611
  );
32165
- this.sessions.set(sessionId, sessionState);
32612
+ this.installSessionState(sessionState);
32166
32613
  resumeSubscribed = false;
32167
32614
  const canPublishSessionUpdates = operation !== "fork";
32168
32615
  if (canPublishSessionUpdates && requestedMcpServers.length > 0 && mcpServerStartupVersion !== null) {
@@ -32177,6 +32624,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32177
32624
  }
32178
32625
  if (operation === "resume") {
32179
32626
  this.publishCurrentGoalAsync(sessionState, sessionGeneration);
32627
+ this.publishAsyncTasksAsync(sessionState, sessionGeneration);
32180
32628
  }
32181
32629
  const sessionModelState = this.createModelState(models, currentModelId);
32182
32630
  const sessionModeState = sessionState.agentMode.toSessionModeState();
@@ -32206,6 +32654,18 @@ Check ${configPath} and project .codex directories, especially their config.toml
32206
32654
  }
32207
32655
  return a === b;
32208
32656
  }
32657
+ createAsyncTasks(sessionId) {
32658
+ return new CodexBackgroundTerminalTasks(
32659
+ clientSupportsAirCapability(this.clientCapabilities, AIR_ASYNC_TASKS_KEY),
32660
+ sessionId,
32661
+ this.codexAcpClient.appServerClient,
32662
+ new ACPSessionConnection(this.connection, sessionId)
32663
+ );
32664
+ }
32665
+ installSessionState(sessionState) {
32666
+ this.sessions.get(sessionState.sessionId)?.asyncTasks.clear();
32667
+ this.sessions.set(sessionState.sessionId, sessionState);
32668
+ }
32209
32669
  getAuthProviderForAuthenticateRequest(request) {
32210
32670
  if (isCodexAuthRequest(request) && request.methodId === "gateway") {
32211
32671
  return "custom-gateway";
@@ -32224,6 +32684,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32224
32684
  thread
32225
32685
  } = await this.getOrCreateSessionWithHistory(params);
32226
32686
  await this.streamThreadHistory(sessionId, thread);
32687
+ await this.getSessionState(sessionId).asyncTasks.reconcile();
32227
32688
  logger.log("Session loaded", {
32228
32689
  sessionId,
32229
32690
  modelId: modelState.currentModelId,
@@ -32297,6 +32758,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32297
32758
  try {
32298
32759
  if (sessionState) {
32299
32760
  await this.interruptSessionTurn(sessionState, "Close", true);
32761
+ sessionState.asyncTasks.clear();
32300
32762
  } else {
32301
32763
  logger.log("Close request received for unknown local session", { sessionId: params.sessionId });
32302
32764
  }
@@ -32432,6 +32894,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
32432
32894
  await Promise.all(activePrompts);
32433
32895
  }
32434
32896
  logger.log("Restarting Codex app-server for provider update", { sessionCount: this.sessions.size });
32897
+ for (const session of this.sessions.values()) {
32898
+ session.asyncTasks.prepareForAppServerReplacement();
32899
+ }
32900
+ await this.finishAllAsyncTasks("stopped", "before the provider restart");
32435
32901
  const replacement = await this.restartCodexClient();
32436
32902
  apply(replacement);
32437
32903
  if (this.initializeRequest === null) {
@@ -32442,6 +32908,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32442
32908
  this.availableCommands = this.createAvailableCommands(replacement);
32443
32909
  const resumeErrors = [];
32444
32910
  for (const session of this.sessions.values()) {
32911
+ session.asyncTasks.setAppServer(replacement.appServerClient);
32445
32912
  try {
32446
32913
  await replacement.resumeSession({
32447
32914
  sessionId: session.sessionId,
@@ -32450,6 +32917,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32450
32917
  mcpServers: session.mcpServers ?? []
32451
32918
  });
32452
32919
  session.authProvider = replacement.getModelProvider();
32920
+ session.asyncTasks.refresh();
32453
32921
  logger.log("Resumed session after provider restart", { sessionId: session.sessionId });
32454
32922
  } catch (error51) {
32455
32923
  resumeErrors.push(error51);
@@ -32479,12 +32947,22 @@ Check ${configPath} and project .codex directories, especially their config.toml
32479
32947
  state.stderr = (state.stderr + data.toString()).slice(-2 * 1024);
32480
32948
  });
32481
32949
  }
32950
+ observeCodexProcess() {
32951
+ const process12 = this.codexProcessState?.connection.process;
32952
+ if (!process12) return;
32953
+ const generation = ++this.codexProcessGeneration;
32954
+ process12.once("exit", () => {
32955
+ if (generation !== this.codexProcessGeneration) return;
32956
+ void this.finishAllAsyncTasks("failed", "after the Codex process exited");
32957
+ });
32958
+ }
32482
32959
  async restartCodexClient() {
32483
32960
  const state = this.codexProcessState;
32484
32961
  if (state === null) {
32485
32962
  throw new Error("Codex process state is unavailable");
32486
32963
  }
32487
32964
  const previous = state.connection;
32965
+ this.codexProcessGeneration += 1;
32488
32966
  const exited = previous.process.exitCode === null ? once(previous.process, "exit") : Promise.resolve();
32489
32967
  previous.process.stdin.end();
32490
32968
  const forceKill = setTimeout(() => {
@@ -32498,6 +32976,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32498
32976
  state.stderr = "";
32499
32977
  state.connection = startCodexConnection(state.codexPath);
32500
32978
  this.captureStderr();
32979
+ this.observeCodexProcess();
32501
32980
  return new CodexAcpClient(
32502
32981
  new CodexAppServerClient(state.connection.connection),
32503
32982
  state.config,
@@ -33047,6 +33526,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
33047
33526
  publishCurrentGoalAsync(sessionState, sessionGeneration) {
33048
33527
  void this.publishCurrentGoalBestEffort(sessionState, sessionGeneration, true);
33049
33528
  }
33529
+ publishAsyncTasksAsync(sessionState, sessionGeneration) {
33530
+ if (!this.sessionPublishIsCurrent(sessionState, sessionGeneration)) return;
33531
+ sessionState.asyncTasks.refresh();
33532
+ }
33050
33533
  async publishCurrentGoalBestEffort(sessionState, sessionGeneration, force) {
33051
33534
  try {
33052
33535
  await this.publishCurrentGoal(sessionState, sessionGeneration, force);
@@ -33170,7 +33653,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
33170
33653
  sessionId,
33171
33654
  clientSupportsSubagents(this.clientCapabilities),
33172
33655
  new ACPSessionConnection(this.connection, sessionId)
33173
- )
33656
+ ),
33657
+ asyncTasks: this.createAsyncTasks(sessionId)
33174
33658
  };
33175
33659
  sessionState.titleGen = new TitleGenerator(
33176
33660
  this.codexAcpClient.appServerClient,
@@ -33178,7 +33662,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33178
33662
  sessionState.cwd,
33179
33663
  () => sessionState.sessionTitleSource
33180
33664
  );
33181
- this.sessions.set(sessionId, sessionState);
33665
+ this.installSessionState(sessionState);
33182
33666
  subscribed = false;
33183
33667
  if (requestedMcpServers.length > 0 && mcpServerStartupVersion !== null) {
33184
33668
  this.pendingMcpStartupSessions.set(sessionId, {
@@ -33270,6 +33754,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
33270
33754
  /* @__PURE__ */ new Set([...ancestry, item.agentThreadId]),
33271
33755
  threadCache
33272
33756
  );
33757
+ try {
33758
+ await sessionState.asyncTasks.recover(
33759
+ item.agentThreadId,
33760
+ childSessionId,
33761
+ commandItemIds(childTurn.items)
33762
+ );
33763
+ } catch (error51) {
33764
+ logger.error(`Failed to restore background terminals for ${item.agentThreadId}`, error51);
33765
+ }
33273
33766
  }
33274
33767
  }
33275
33768
  } else if (activityKind === "completed" || activityKind === "interrupted") {
@@ -34320,6 +34813,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
34320
34813
  throw new RequestError(requestErrorCode, `VC++ redistributable should be installed`);
34321
34814
  }
34322
34815
  if (exitCode !== null) {
34816
+ await this.finishAllAsyncTasks("failed", "after the Codex process exited");
34323
34817
  const stderr = this.getRecentStderr().trim();
34324
34818
  const detail = stderr ? `:
34325
34819
  ${stderr}` : "";
@@ -34328,6 +34822,15 @@ ${stderr}` : "";
34328
34822
  throw err;
34329
34823
  }
34330
34824
  }
34825
+ async finishAllAsyncTasks(state, reason) {
34826
+ for (const session of this.sessions.values()) {
34827
+ try {
34828
+ await session.asyncTasks.finishAll(state);
34829
+ } catch (error51) {
34830
+ logger.error(`Failed to finish background terminal tasks ${reason}`, error51);
34831
+ }
34832
+ }
34833
+ }
34331
34834
  async cancel(params) {
34332
34835
  const sessionState = this.sessions.get(params.sessionId);
34333
34836
  if (!sessionState) {
@@ -34377,6 +34880,9 @@ function mergeHistoryUpdates(responseItemFallbackUpdates, threadUpdates) {
34377
34880
  }
34378
34881
  return merged;
34379
34882
  }
34883
+ function commandItemIds(items) {
34884
+ return new Set(items.filter((item) => item.type === "commandExecution").map((item) => item.id));
34885
+ }
34380
34886
  function historyUpdateKey(update) {
34381
34887
  switch (update.sessionUpdate) {
34382
34888
  case "user_message_chunk":
@@ -34560,6 +35066,10 @@ var goalControlParamsParser = external_exports.discriminatedUnion("action", [
34560
35066
  action: external_exports.enum(["pause", "resume", "clear"])
34561
35067
  }).passthrough()
34562
35068
  ]);
35069
+ var asyncTaskStopParamsParser = external_exports.object({
35070
+ sessionId: external_exports.string().trim().min(1),
35071
+ asyncTaskId: external_exports.string().trim().min(1)
35072
+ }).passthrough();
34563
35073
  if (process.argv.includes("--version")) {
34564
35074
  console.log(`${package_default.name} ${package_default.version}`);
34565
35075
  process.exit(0);
@@ -34640,5 +35150,5 @@ function startAcpServer() {
34640
35150
  codexAcpServer = null;
34641
35151
  }
34642
35152
  });
34643
- }).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);
35153
+ }).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);
34644
35154
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.9.0",
6
+ "version": "1.10.1-preview.1",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@agentclientprotocol/sdk": "^1.4.0",
69
- "@openai/codex": "^0.153.2",
69
+ "@openai/codex": "^0.153.3",
70
70
  "diff": "^9.0.0",
71
71
  "open": "^11.0.1",
72
72
  "vscode-jsonrpc": "^9.0.1",