@agentclientprotocol/codex-acp 2.5.1-preview.4 → 2.5.1-preview.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +142 -43
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -40475,7 +40475,7 @@ var package_default = {
40475
40475
  publishConfig: {
40476
40476
  access: "public"
40477
40477
  },
40478
- version: "2.5.1-preview.4",
40478
+ version: "2.5.1-preview.5",
40479
40479
  description: "",
40480
40480
  main: "dist/index.js",
40481
40481
  bin: {
@@ -40648,7 +40648,7 @@ var CodexSubagentSubscriptions = class {
40648
40648
  existing.current = subscription;
40649
40649
  return;
40650
40650
  }
40651
- const session = { current: subscription, children: /* @__PURE__ */ new Set() };
40651
+ const session = { current: subscription, children: /* @__PURE__ */ new Set(), childParents: /* @__PURE__ */ new Map() };
40652
40652
  this.sessions.set(subscription.rootSessionId, session);
40653
40653
  this.client.onServerNotification(subscription.rootSessionId, (event) => {
40654
40654
  this.discover(session, event);
@@ -40670,10 +40670,35 @@ var CodexSubagentSubscriptions = class {
40670
40670
  isChildThread(threadId) {
40671
40671
  return [...this.sessions.values()].some((session) => session.children.has(threadId));
40672
40672
  }
40673
+ async readChildThread(threadId) {
40674
+ const session = [...this.sessions.values()].find((session2) => session2.children.has(threadId));
40675
+ const { thread } = await this.client.threadRead({ threadId });
40676
+ if (!thread || thread.id !== threadId) return null;
40677
+ if (session && this.sessions.get(session.current.rootSessionId) === session) this.rememberParent(session, thread);
40678
+ return thread;
40679
+ }
40680
+ /** Use observed ownership, without waiting on unrelated metadata reads during close. */
40681
+ verifiedChildThreadIds(rootSessionId) {
40682
+ const parents = this.sessions.get(rootSessionId)?.childParents ?? /* @__PURE__ */ new Map();
40683
+ const owned = /* @__PURE__ */ new Set([rootSessionId]);
40684
+ let changed;
40685
+ do {
40686
+ changed = false;
40687
+ for (const [threadId, parentId] of parents) {
40688
+ if (!owned.has(threadId) && owned.has(parentId)) {
40689
+ owned.add(threadId);
40690
+ changed = true;
40691
+ }
40692
+ }
40693
+ } while (changed);
40694
+ owned.delete(rootSessionId);
40695
+ return [...owned];
40696
+ }
40673
40697
  async restoreChild(rootSessionId, parentThreadId, thread) {
40674
40698
  const session = this.sessions.get(rootSessionId);
40675
40699
  if (!session || session.children.has(thread.id) || thread.parentThreadId !== parentThreadId || parentThreadId !== rootSessionId && !session.children.has(parentThreadId)) return null;
40676
40700
  if (!this.attachChild(session, thread.id)) return null;
40701
+ this.rememberParent(session, thread);
40677
40702
  if (thread.status.type === "notLoaded") return null;
40678
40703
  try {
40679
40704
  const response = await this.client.threadResume({ threadId: thread.id, excludeTurns: true });
@@ -40686,6 +40711,10 @@ var CodexSubagentSubscriptions = class {
40686
40711
  return null;
40687
40712
  }
40688
40713
  discover(session, event) {
40714
+ if (event.method === "thread/started") {
40715
+ if (session.children.has(event.params.thread.id)) this.rememberParent(session, event.params.thread);
40716
+ return;
40717
+ }
40689
40718
  if (event.method !== "item/started" && event.method !== "item/completed") {
40690
40719
  return;
40691
40720
  }
@@ -40714,8 +40743,13 @@ var CodexSubagentSubscriptions = class {
40714
40743
  }
40715
40744
  detachChild(session, childSessionId) {
40716
40745
  if (this.sessions.get(session.current.rootSessionId) !== session || !session.children.delete(childSessionId)) return;
40746
+ session.childParents.delete(childSessionId);
40717
40747
  if (!this.sessions.has(childSessionId)) this.client.clearThreadHandlers(childSessionId);
40718
40748
  }
40749
+ rememberParent(session, thread) {
40750
+ if (thread.status.type === "notLoaded" || thread.parentThreadId === null) session.childParents.delete(thread.id);
40751
+ else session.childParents.set(thread.id, thread.parentThreadId);
40752
+ }
40719
40753
  registerInteractiveHandlers(session, targetSessionId) {
40720
40754
  this.client.onApprovalRequest(targetSessionId, {
40721
40755
  handleCommandExecution: async (params) => {
@@ -41635,6 +41669,9 @@ var CodexAcpClient = class _CodexAcpClient {
41635
41669
  isSubagentThread(threadId) {
41636
41670
  return this.subagents.isChildThread(threadId);
41637
41671
  }
41672
+ readSubagentThread(threadId) {
41673
+ return this.subagents.readChildThread(threadId);
41674
+ }
41638
41675
  async newSession(request) {
41639
41676
  const instructions = await this.resolveSessionInstructions(request, void 0, true);
41640
41677
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
@@ -41735,11 +41772,30 @@ var CodexAcpClient = class _CodexAcpClient {
41735
41772
  }
41736
41773
  }
41737
41774
  async closeSession(sessionId) {
41775
+ const children = this.subagents.verifiedChildThreadIds(sessionId);
41776
+ for (const threadId of [...children, sessionId]) {
41777
+ await this.stopBackgroundTerminals(threadId);
41778
+ }
41779
+ await this.codexClient.threadUnsubscribe({ threadId: sessionId });
41780
+ this.codexClient.clearThreadHandlers(sessionId);
41781
+ this.subagents.clear(sessionId);
41782
+ }
41783
+ async stopBackgroundTerminals(threadId) {
41738
41784
  try {
41739
- await this.codexClient.threadUnsubscribe({ threadId: sessionId });
41740
- } finally {
41741
- this.codexClient.clearThreadHandlers(sessionId);
41742
- this.subagents.clear(sessionId);
41785
+ const terminals = [];
41786
+ for await (const page of this.codexClient.backgroundTerminalPages(threadId)) terminals.push(...page);
41787
+ for (const terminal of terminals) {
41788
+ const result = await this.codexClient.threadBackgroundTerminalsTerminate({ threadId, processId: terminal.processId });
41789
+ if (!result.terminated) {
41790
+ for await (const page of this.codexClient.backgroundTerminalPages(threadId)) {
41791
+ if (page.some((current) => current.processId === terminal.processId)) {
41792
+ throw new Error(`Codex could not stop background terminal ${terminal.processId}`);
41793
+ }
41794
+ }
41795
+ }
41796
+ }
41797
+ } catch (error62) {
41798
+ if (!isThreadNotFoundError(error62) && !isThreadNotLoadedError(error62)) throw error62;
41743
41799
  }
41744
41800
  }
41745
41801
  /**
@@ -43277,6 +43333,19 @@ var CodexAppServerClient = class {
43277
43333
  async threadBackgroundTerminalsTerminate(params) {
43278
43334
  return await this.sendRequest({ method: "thread/backgroundTerminals/terminate", params });
43279
43335
  }
43336
+ async *backgroundTerminalPages(threadId) {
43337
+ const seen = /* @__PURE__ */ new Set();
43338
+ let cursor = null;
43339
+ do {
43340
+ const response = await this.threadBackgroundTerminalsList({ threadId, cursor });
43341
+ yield response.data;
43342
+ cursor = response.nextCursor;
43343
+ if (cursor !== null) {
43344
+ if (seen.has(cursor)) throw new Error("Codex returned a repeated background terminal cursor");
43345
+ seen.add(cursor);
43346
+ }
43347
+ } while (cursor !== null);
43348
+ }
43280
43349
  async threadGoalSet(params) {
43281
43350
  return await this.sendRequest({ method: "thread/goal/set", params });
43282
43351
  }
@@ -46164,13 +46233,16 @@ function hasPayload(update) {
46164
46233
  return Object.keys(update).some((name) => name !== "sessionUpdate" && name !== "toolCallId");
46165
46234
  }
46166
46235
 
46167
- // src/ToolCallReportingConnection.ts
46168
- var ToolCallReportingConnection = class {
46169
- constructor(client) {
46236
+ // src/AcpClientDelivery.ts
46237
+ var AcpClientDelivery = class {
46238
+ constructor(client, sessionIsClosing = () => false) {
46170
46239
  this.client = client;
46240
+ this.sessionIsClosing = sessionIsClosing;
46171
46241
  }
46172
46242
  client;
46243
+ sessionIsClosing;
46173
46244
  reports = new ToolCallReports();
46245
+ sessionRequests = /* @__PURE__ */ new Map();
46174
46246
  /** Returns this connection with the signature of the SDK connection. */
46175
46247
  asClientConnection() {
46176
46248
  return this;
@@ -46186,12 +46258,12 @@ var ToolCallReportingConnection = class {
46186
46258
  }
46187
46259
  async request(method, params, options) {
46188
46260
  if (method !== methods.client.session.requestPermission || !isPermissionRequestParams(params)) {
46189
- return await this.client.request(method, params, options);
46261
+ return await this.sendRequest(method, params, options);
46190
46262
  }
46191
46263
  this.reports.prepare(params.sessionId, { sessionUpdate: "tool_call_update", ...params.toolCall });
46192
46264
  let response;
46193
46265
  try {
46194
- response = await this.client.request(method, params, options);
46266
+ response = await this.sendRequest(method, params, options);
46195
46267
  } catch (error62) {
46196
46268
  this.reports.forgetOpen(params.sessionId, params.toolCall.toolCallId);
46197
46269
  throw error62;
@@ -46199,7 +46271,37 @@ var ToolCallReportingConnection = class {
46199
46271
  if (isCancelled(response)) this.reports.forgetOpen(params.sessionId, params.toolCall.toolCallId);
46200
46272
  return response;
46201
46273
  }
46274
+ /** Withdraw pending UI without waiting for the client to answer the cancelled requests. */
46275
+ cancelSessionRequests(sessionId) {
46276
+ for (const controller of this.sessionRequests.get(sessionId) ?? []) {
46277
+ controller.abort(RequestError.requestCancelled(void 0, `Session ${sessionId} is closing`));
46278
+ }
46279
+ }
46280
+ async sendRequest(method, params, options) {
46281
+ const sessionId = interactionSessionId(method, params);
46282
+ if (sessionId === null) return await this.client.request(method, params, options);
46283
+ if (this.sessionIsClosing(sessionId)) {
46284
+ throw RequestError.requestCancelled(void 0, `Session ${sessionId} is closing`);
46285
+ }
46286
+ const controller = new AbortController();
46287
+ const requests = this.sessionRequests.get(sessionId) ?? /* @__PURE__ */ new Set();
46288
+ this.sessionRequests.set(sessionId, requests);
46289
+ requests.add(controller);
46290
+ const cancellationSignal = options?.cancellationSignal ? AbortSignal.any([options.cancellationSignal, controller.signal]) : controller.signal;
46291
+ try {
46292
+ return await this.client.request(method, params, { ...options, cancellationSignal });
46293
+ } finally {
46294
+ requests.delete(controller);
46295
+ if (requests.size === 0) this.sessionRequests.delete(sessionId);
46296
+ }
46297
+ }
46202
46298
  };
46299
+ function interactionSessionId(method, params) {
46300
+ if (method !== methods.client.elicitation.create && method !== methods.client.session.requestPermission) return null;
46301
+ if (params === null || typeof params !== "object") return null;
46302
+ const sessionId = params.sessionId;
46303
+ return typeof sessionId === "string" ? sessionId : null;
46304
+ }
46203
46305
  function isCancelled(response) {
46204
46306
  return response?.outcome?.outcome === "cancelled";
46205
46307
  }
@@ -49406,23 +49508,10 @@ var CodexBackgroundTerminalTasks = class {
49406
49508
  const appServer = this.appServer;
49407
49509
  const generation = this.appServerGeneration;
49408
49510
  const terminals = [];
49409
- const seenCursors = /* @__PURE__ */ new Set();
49410
- let cursor = null;
49411
- do {
49412
- const response = await appServer.threadBackgroundTerminalsList({
49413
- threadId,
49414
- cursor
49415
- });
49511
+ for await (const page of appServer.backgroundTerminalPages(threadId)) {
49416
49512
  if (!this.queryIsCurrent(generation)) return null;
49417
- terminals.push(...response.data);
49418
- cursor = response.nextCursor;
49419
- if (cursor !== null) {
49420
- if (seenCursors.has(cursor)) {
49421
- throw new Error("Codex returned a repeated background terminal cursor");
49422
- }
49423
- seenCursors.add(cursor);
49424
- }
49425
- } while (cursor !== null);
49513
+ terminals.push(...page);
49514
+ }
49426
49515
  return { generation, terminals };
49427
49516
  }
49428
49517
  isActive() {
@@ -49559,7 +49648,7 @@ var CodexAcpServer = class {
49559
49648
  codexAcpClient;
49560
49649
  connection;
49561
49650
  mcpServerSignIn;
49562
- reportingConnection;
49651
+ clientDelivery;
49563
49652
  /** ACP protocol version of the connection this agent serves, fixed by the protocol router. */
49564
49653
  protocolVersion;
49565
49654
  /** The v2 client handle; `null` on a v1 connection. */
@@ -49656,18 +49745,19 @@ var CodexAcpServer = class {
49656
49745
  this.sessionGenerations = /* @__PURE__ */ new Map();
49657
49746
  this.sessionOpenGenerations = /* @__PURE__ */ new Map();
49658
49747
  this.permissionLifecycleContexts = /* @__PURE__ */ new WeakMap();
49748
+ const sessionIsClosing = (id) => this.sessionIsClosing(this.subagentAssociations.get(id)?.rootSessionId ?? id);
49659
49749
  if (connection instanceof AcpV2Connection) {
49660
49750
  this.protocolVersion = 2;
49661
49751
  this.v2Connection = connection.client;
49662
- this.reportingConnection = new ToolCallReportingConnection(connection.extensionOnlyV1View());
49663
- this.connection = this.reportingConnection.asClientConnection();
49752
+ this.clientDelivery = new AcpClientDelivery(connection.extensionOnlyV1View(), sessionIsClosing);
49753
+ this.connection = this.clientDelivery.asClientConnection();
49664
49754
  connection.registerView(this.connection);
49665
49755
  connection.setTurnRunningCheck((sessionId) => this.isSessionBusy(sessionId));
49666
49756
  } else {
49667
49757
  this.protocolVersion = 1;
49668
49758
  this.v2Connection = null;
49669
- this.reportingConnection = new ToolCallReportingConnection(connection);
49670
- this.connection = this.reportingConnection.asClientConnection();
49759
+ this.clientDelivery = new AcpClientDelivery(connection, sessionIsClosing);
49760
+ this.connection = this.clientDelivery.asClientConnection();
49671
49761
  }
49672
49762
  this.codexAcpClient = codexAcpClient;
49673
49763
  this.mcpServerSignIn = createMcpServerSignIn(
@@ -49716,7 +49806,7 @@ var CodexAcpServer = class {
49716
49806
  const existing = this.sessions.get(sessionId)?.subagents;
49717
49807
  if (this.usesStandardSubagents() && existing instanceof StandardSubagentEventRouter && !existing.isRetired()) return existing;
49718
49808
  const session = new ACPSessionConnection(this.connection, sessionId);
49719
- const release = (id) => this.reportingConnection.reports.releaseOpen(id);
49809
+ const release = (id) => this.clientDelivery.reports.releaseOpen(id);
49720
49810
  const models = () => {
49721
49811
  const state = this.sessions.get(sessionId);
49722
49812
  return state ? this.subagentModels(state) : null;
@@ -49740,7 +49830,7 @@ var CodexAcpServer = class {
49740
49830
  return router;
49741
49831
  }
49742
49832
  async readSubagentConfiguration(threadId, parentThreadId) {
49743
- const { thread } = await this.codexAcpClient.appServerClient.threadRead({ threadId });
49833
+ const thread = await this.codexAcpClient.readSubagentThread(threadId);
49744
49834
  if (!thread || thread.id !== threadId || thread.parentThreadId !== parentThreadId || thread.status.type === "notLoaded") return null;
49745
49835
  return { model: thread.model, effort: thread.reasoningEffort };
49746
49836
  }
@@ -49834,7 +49924,7 @@ var CodexAcpServer = class {
49834
49924
  this.clientCapabilities = _params.clientCapabilities ?? null;
49835
49925
  this.initializeRequest = _params;
49836
49926
  this.capabilities = ClientCapabilities.from(_params.clientCapabilities);
49837
- this.reportingConnection.reports.compareMeta = this.capabilities.airToolCallContract;
49927
+ this.clientDelivery.reports.compareMeta = this.capabilities.airToolCallContract;
49838
49928
  this.booleanConfigOptionsSupported = clientSupportsBooleanConfigOptions(_params.clientCapabilities);
49839
49929
  this.sessionIndex.configure(_params.clientCapabilities);
49840
49930
  this.sessionIndex.observe(this.codexAcpClient);
@@ -49892,7 +49982,7 @@ var CodexAcpServer = class {
49892
49982
  this.clientCapabilities = clientCapabilities;
49893
49983
  this.initializeRequest = { clientInfo: params.info };
49894
49984
  this.capabilities = ClientCapabilities.fromV2(clientCapabilities);
49895
- this.reportingConnection.reports.compareMeta = this.capabilities.airToolCallContract;
49985
+ this.clientDelivery.reports.compareMeta = this.capabilities.airToolCallContract;
49896
49986
  this.booleanConfigOptionsSupported = true;
49897
49987
  this.sessionIndex.configure(clientCapabilities);
49898
49988
  this.sessionIndex.observe(this.codexAcpClient);
@@ -50359,7 +50449,7 @@ You have been logged out. Please try again.`);
50359
50449
  subagents: this.createSubagentRouter(sessionId),
50360
50450
  asyncTasks: this.createAsyncTasks(sessionId),
50361
50451
  compactions: new CodexSessionCompactions(),
50362
- toolCallReports: this.reportingConnection.reports,
50452
+ toolCallReports: this.clientDelivery.reports,
50363
50453
  openToolCalls: new CodexSessionToolCalls()
50364
50454
  };
50365
50455
  sessionState.titleGen = new TitleGenerator(
@@ -50904,15 +50994,18 @@ You have been logged out. Please try again.`);
50904
50994
  }
50905
50995
  async closeSession(params) {
50906
50996
  logger.log("Closing session...", { sessionId: params.sessionId });
50907
- const closeGeneration = this.bumpSessionGeneration(params.sessionId);
50997
+ if (!this.hasLocalSession(params.sessionId)) return {};
50998
+ let closeGeneration = null;
50908
50999
  const sessionState = this.sessions.get(params.sessionId);
50909
51000
  this.beginSessionCloseFence(params.sessionId);
50910
51001
  try {
51002
+ this.clientDelivery.cancelSessionRequests(params.sessionId);
51003
+ for (const child of this.subagentAssociations.forRoot(params.sessionId)) {
51004
+ this.clientDelivery.cancelSessionRequests(child.sessionId);
51005
+ }
50911
51006
  this.cancelQueuedV2Prompts(params.sessionId);
50912
51007
  if (sessionState) {
50913
51008
  await this.interruptSessionTurn(sessionState, "Close", true);
50914
- if (sessionState.subagents instanceof StandardSubagentEventRouter) await sessionState.subagents.finishOutstanding("disconnected");
50915
- sessionState.asyncTasks.clear();
50916
51009
  sessionState.promptSuggestions?.invalidate();
50917
51010
  } else {
50918
51011
  logger.log("Close request received for unknown local session", { sessionId: params.sessionId });
@@ -50927,10 +51020,15 @@ You have been logged out. Please try again.`);
50927
51020
  } else {
50928
51021
  logger.log("Session closed without an app-server call: its app-server is gone", { sessionId: params.sessionId });
50929
51022
  }
51023
+ closeGeneration = this.bumpSessionGeneration(params.sessionId);
51024
+ if (sessionState) {
51025
+ if (sessionState.subagents instanceof StandardSubagentEventRouter) await sessionState.subagents.finishOutstanding("disconnected");
51026
+ await sessionState.asyncTasks.finishAll("stopped");
51027
+ }
50930
51028
  await this.reportCodexTurnState(params.sessionId, { state: "idle", stopReason: "cancelled" });
50931
51029
  logger.log("Session closed", { sessionId: params.sessionId });
50932
51030
  } finally {
50933
- if (this.getSessionGeneration(params.sessionId) === closeGeneration) {
51031
+ if (closeGeneration !== null && this.getSessionGeneration(params.sessionId) === closeGeneration) {
50934
51032
  this.sessions.delete(params.sessionId);
50935
51033
  this.asyncQuestions.get(params.sessionId)?.dispose();
50936
51034
  this.asyncQuestions.delete(params.sessionId);
@@ -50945,6 +51043,7 @@ You have been logged out. Please try again.`);
50945
51043
  this.queuedV2PromptCancellers.delete(params.sessionId);
50946
51044
  this.steeringQueues.delete(params.sessionId);
50947
51045
  if (sessionState) {
51046
+ sessionState.asyncTasks.clear();
50948
51047
  this.availableCommands.cancelPendingSkillsPublish(sessionState);
50949
51048
  }
50950
51049
  }
@@ -52120,7 +52219,7 @@ You have been logged out. Please try again.`);
52120
52219
  subagents: this.createSubagentRouter(sessionId, true),
52121
52220
  asyncTasks: this.createAsyncTasks(sessionId),
52122
52221
  compactions: new CodexSessionCompactions(),
52123
- toolCallReports: this.reportingConnection.reports,
52222
+ toolCallReports: this.clientDelivery.reports,
52124
52223
  openToolCalls: new CodexSessionToolCalls()
52125
52224
  };
52126
52225
  sessionState.titleGen = new TitleGenerator(
@@ -53831,7 +53930,7 @@ You have been logged out. Please try again.`);
53831
53930
  }
53832
53931
  async failUnfinishedToolCalls(sessionIds) {
53833
53932
  for (const sessionId of sessionIds) {
53834
- for (const toolCallId of this.reportingConnection.reports.unfinished(sessionId)) {
53933
+ for (const toolCallId of this.clientDelivery.reports.unfinished(sessionId)) {
53835
53934
  await this.connection.notify(methods.client.session.update, {
53836
53935
  sessionId,
53837
53936
  update: { sessionUpdate: "tool_call_update", toolCallId, status: "failed" }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.5.1-preview.4",
6
+ "version": "2.5.1-preview.5",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {