@github/copilot-sdk 1.0.13-preview.6 → 1.0.13

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.
package/README.md CHANGED
@@ -319,6 +319,32 @@ const unsubscribe = session.on((event) => {
319
319
  unsubscribe();
320
320
  ```
321
321
 
322
+ ##### `setModel(model: string, options?): Promise<void>`
323
+
324
+ Change the model for this session. The new model takes effect for the next message; conversation history is preserved.
325
+
326
+ **Options:**
327
+
328
+ - `reasoningEffort?: string` - Reasoning effort level
329
+ - `autoTier?: AutoTier | null` - Auto routing preference to stage together with selecting `auto`. Pass `null` to return to the provider's default Auto routing; omit it to leave the current preference unchanged.
330
+
331
+ ##### `setAutoTier(autoTier: AutoTier | null): Promise<ModelSwitchAutoTierResult>`
332
+
333
+ Change the Auto routing preference without changing the selected model. Pass `null` to return to the provider's default Auto routing.
334
+
335
+ The runtime does not apply the preference immediately. It records the request and commits it only when a later user turn using the `auto` model successfully obtains a usable model from the provider, so a `pending` status confirms acceptance rather than effect. Only the most recent request survives.
336
+
337
+ Watch for the outcome through the `session.model_change` event on success or the ephemeral `session.auto_tier_switch_failed` event on failure, and read the authoritative state at any time with `session.rpc.model.getCurrent()`.
338
+
339
+ ```typescript
340
+ const result = await session.setAutoTier("intelligence");
341
+ if (result.status === "pending") {
342
+ // Accepted, but not yet in effect.
343
+ }
344
+ ```
345
+
346
+ See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) for the full lifecycle rules.
347
+
322
348
  ##### `abort(): Promise<void>`
323
349
 
324
350
  Abort the currently processing message in this session.
@@ -22,7 +22,7 @@ __export(cliVersion_exports, {
22
22
  COPILOT_CLI_VERSION: () => COPILOT_CLI_VERSION
23
23
  });
24
24
  module.exports = __toCommonJS(cliVersion_exports);
25
- const COPILOT_CLI_VERSION = "1.0.83-3";
25
+ const COPILOT_CLI_VERSION = "1.0.83";
26
26
  const COPILOT_CLI_USE_NPM_PACKAGE = false;
27
27
  // Annotate the CommonJS export names for ESM import in node:
28
28
  0 && (module.exports = {
@@ -236,6 +236,7 @@ class CopilotClient {
236
236
  ffiHost = null;
237
237
  connection = null;
238
238
  messageWriter = null;
239
+ connectionClosed = false;
239
240
  socket = null;
240
241
  runtimePort = null;
241
242
  actualHost = "localhost";
@@ -618,6 +619,7 @@ class CopilotClient {
618
619
  return;
619
620
  }
620
621
  this.forceStopping = false;
622
+ this.connectionClosed = false;
621
623
  this.processTransportError = null;
622
624
  this.state = "connecting";
623
625
  try {
@@ -716,7 +718,7 @@ class CopilotClient {
716
718
  }
717
719
  this.sessions.clear();
718
720
  this.githubTokenProviders.clear();
719
- if (this.connection && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
721
+ if (this.connection && !this.connectionClosed && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
720
722
  const runtimeShutdownStart = Date.now();
721
723
  const shutdownPromise = this.rpc.runtime.shutdown();
722
724
  void shutdownPromise.catch(() => void 0);
@@ -1281,6 +1283,7 @@ class CopilotClient {
1281
1283
  await this.updateSessionOptionsForMode(session, config);
1282
1284
  this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
1283
1285
  } catch (e) {
1286
+ session?._markDisconnected();
1284
1287
  if (registeredId !== void 0) {
1285
1288
  this.sessions.delete(registeredId);
1286
1289
  }
@@ -1513,6 +1516,7 @@ class CopilotClient {
1513
1516
  await this.updateSessionOptionsForMode(session, config);
1514
1517
  this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
1515
1518
  } catch (e) {
1519
+ session._markDisconnected();
1516
1520
  this.sessions.delete(sessionId);
1517
1521
  if (gitHubTokenProviderRegistrationId !== void 0) {
1518
1522
  this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
@@ -1628,7 +1632,10 @@ class CopilotClient {
1628
1632
  const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
1629
1633
  let serverVersion;
1630
1634
  try {
1631
- const connectParams = { token: this.effectiveConnectionToken };
1635
+ const connectParams = {
1636
+ token: this.effectiveConnectionToken,
1637
+ supportedTaskKinds: ["agent", "client", "shell"]
1638
+ };
1632
1639
  if (this.onGitHubTelemetry != null) {
1633
1640
  connectParams.enableGitHubTelemetryForwarding = true;
1634
1641
  }
@@ -2259,12 +2266,24 @@ stderr: ${stderrOutput}` : ""}`
2259
2266
  return await this.handleHooksInvoke(params);
2260
2267
  }
2261
2268
  );
2262
- this.connection.onClose(() => {
2269
+ const connection = this.connection;
2270
+ const markDisconnected = () => {
2271
+ if (this.connection !== connection) {
2272
+ return;
2273
+ }
2274
+ this.connectionClosed = true;
2263
2275
  this.state = "disconnected";
2276
+ for (const session of this.sessions.values()) {
2277
+ session._markDisconnected();
2278
+ }
2279
+ this.sessions.clear();
2264
2280
  this.githubTokenProviders.clear();
2265
- });
2266
- this.connection.onError((_error) => {
2267
- this.state = "disconnected";
2281
+ };
2282
+ this.connection.onClose(markDisconnected);
2283
+ this.connection.onError(() => {
2284
+ if (this.connection === connection) {
2285
+ this.state = "disconnected";
2286
+ }
2268
2287
  });
2269
2288
  }
2270
2289
  handleSessionEventNotification(notification) {
@@ -438,7 +438,11 @@ function createServerRpc(connection) {
438
438
  *
439
439
  * @returns Validated device-managed settings discovered before a session exists.
440
440
  */
441
- read: async () => connection.sendRequest("managedSettings.read", {})
441
+ read: async () => connection.sendRequest("managedSettings.read", {}),
442
+ /**
443
+ * Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `<cacheHome>/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed.
444
+ */
445
+ clearCache: async () => connection.sendRequest("managedSettings.clearCache", {})
442
446
  },
443
447
  /** @experimental */
444
448
  runtime: {
@@ -1002,9 +1006,9 @@ function createSessionRpc(connection, sessionId) {
1002
1006
  /** @experimental */
1003
1007
  model: {
1004
1008
  /**
1005
- * Gets the currently selected model for the session.
1009
+ * Gets the session's authoritative model snapshot, including the committed Auto preference and any newer unclaimed Auto preference waiting for a future user turn.
1006
1010
  *
1007
- * @returns The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
1011
+ * @returns The session's authoritative model snapshot. Auto preference fields are configuration for the virtual `auto` model and do not change the selected model identifier. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
1008
1012
  */
1009
1013
  getCurrent: async () => connection.sendRequest("session.model.getCurrent", { sessionId }),
1010
1014
  /**
@@ -1015,6 +1019,14 @@ function createSessionRpc(connection, sessionId) {
1015
1019
  * @returns The model identifier active on the session after the switch.
1016
1020
  */
1017
1021
  switchTo: async (params) => connection.sendRequest("session.model.switchTo", { sessionId, ...params }),
1022
+ /**
1023
+ * Requests an Auto preference change without changing the session's selected model. The latest unclaimed request wins; the runtime commits it only after a later prompt using the `auto` model mints a usable model and token pair. A `pending` response confirms that the request was accepted, not that it committed. Observe eventual success through `session.model_change`, failure through the ephemeral `session.auto_tier_switch_failed` event, or current unclaimed state through `session.model.getCurrent`.
1024
+ *
1025
+ * @param params An Auto preference request for the session. This updates Auto configuration only; it does not change the selected model to `auto`.
1026
+ *
1027
+ * @returns Immediate acknowledgement and Auto preference snapshot after a switch request. This result never implies that a pending preference committed.
1028
+ */
1029
+ switchAutoTier: async (params) => connection.sendRequest("session.model.switchAutoTier", { sessionId, ...params }),
1018
1030
  /**
1019
1031
  * Updates the session's reasoning effort without changing the selected model.
1020
1032
  *
@@ -1221,6 +1233,15 @@ function createSessionRpc(connection, sessionId) {
1221
1233
  diff: async (params) => connection.sendRequest("session.workspaces.diff", { sessionId, ...params })
1222
1234
  },
1223
1235
  /** @experimental */
1236
+ autopilotObjective: {
1237
+ /**
1238
+ * Reads the current canonical autopilot objective state for this session.
1239
+ *
1240
+ * @returns Canonical runtime state for the session's current autopilot objective.
1241
+ */
1242
+ getState: async () => connection.sendRequest("session.autopilotObjective.getState", { sessionId })
1243
+ },
1244
+ /** @experimental */
1224
1245
  completions: {
1225
1246
  /**
1226
1247
  * Gets the characters that should trigger host-driven completions for the session. Empty disables host-driven completions (e.g. local sessions, or a relay host that does not advertise them).
@@ -1314,6 +1335,22 @@ function createSessionRpc(connection, sessionId) {
1314
1335
  * @returns Background tasks currently tracked by the session.
1315
1336
  */
1316
1337
  list: async () => connection.sendRequest("session.tasks.list", { sessionId }),
1338
+ /**
1339
+ * Registers a client-owned task, or reclaims an orphaned task belonging to the same extension principal.
1340
+ *
1341
+ * @param params Registers or reclaims a client-owned task.
1342
+ *
1343
+ * @returns Result of registering or reclaiming a client-owned task.
1344
+ */
1345
+ register: async (params) => connection.sendRequest("session.tasks.register", { sessionId, ...params }),
1346
+ /**
1347
+ * Publishes generic progress or a terminal outcome for a client-owned task.
1348
+ *
1349
+ * @param params Updates a client-owned task.
1350
+ *
1351
+ * @returns Result of publishing a client-owned task update.
1352
+ */
1353
+ update: async (params) => connection.sendRequest("session.tasks.update", { sessionId, ...params }),
1317
1354
  /**
1318
1355
  * Refreshes metadata for any detached background shells the runtime knows about.
1319
1356
  *
@@ -2782,6 +2819,11 @@ function registerClientSessionApiHandlers(connection, getHandlers) {
2782
2819
  if (!handler) throw new Error(`No factory handler registered for session: ${params.sessionId}`);
2783
2820
  return handler.abort(params);
2784
2821
  });
2822
+ connection.onRequest("tasks.cancel", async (params) => {
2823
+ const handler = getHandlers(params.sessionId).tasks;
2824
+ if (!handler) throw new Error(`No tasks handler registered for session: ${params.sessionId}`);
2825
+ return handler.cancel(params);
2826
+ });
2785
2827
  connection.onRequest("sessionFs.readFile", async (params) => {
2786
2828
  const handler = getHandlers(params.sessionId).sessionFs;
2787
2829
  if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`);
@@ -249,6 +249,7 @@ class CopilotSession {
249
249
  eventHandlers = /* @__PURE__ */ new Set();
250
250
  typedEventHandlers = /* @__PURE__ */ new Map();
251
251
  toolHandlers = /* @__PURE__ */ new Map();
252
+ pendingExternalTools = /* @__PURE__ */ new Map();
252
253
  canvases = /* @__PURE__ */ new Map();
253
254
  bearerTokenProviders = /* @__PURE__ */ new Map();
254
255
  commandHandlers = /* @__PURE__ */ new Map();
@@ -268,6 +269,7 @@ class CopilotSession {
268
269
  _capabilities = {};
269
270
  openCanvasInstances = [];
270
271
  disconnected = false;
272
+ disconnecting = false;
271
273
  onDisconnected;
272
274
  /** @internal Client session API handlers, populated by CopilotClient during create/resume. */
273
275
  clientSessionApis = {};
@@ -525,6 +527,10 @@ class CopilotSession {
525
527
  return;
526
528
  }
527
529
  this.disconnected = true;
530
+ for (const controller of this.pendingExternalTools.values()) {
531
+ controller.abort();
532
+ }
533
+ this.pendingExternalTools.clear();
528
534
  this._runOnDisconnected();
529
535
  this.eventHandlers.clear();
530
536
  this.typedEventHandlers.clear();
@@ -628,6 +634,15 @@ class CopilotSession {
628
634
  tracestate
629
635
  );
630
636
  }
637
+ } else if (event.type === "external_tool.completed") {
638
+ const { requestId } = event.data;
639
+ if (requestId) {
640
+ const controller = this.pendingExternalTools.get(requestId);
641
+ if (controller) {
642
+ this.pendingExternalTools.delete(requestId);
643
+ controller.abort();
644
+ }
645
+ }
631
646
  } else if (event.type === "permission.requested") {
632
647
  const { requestId, permissionRequest, resolvedByHook } = event.data;
633
648
  if (resolvedByHook) {
@@ -708,16 +723,35 @@ class CopilotSession {
708
723
  * @internal
709
724
  */
710
725
  async _executeToolAndRespond(requestId, toolName, toolCallId, args, handler, traceparent, tracestate) {
726
+ const controller = new AbortController();
727
+ if (this.disconnected || this.pendingExternalTools.has(requestId)) {
728
+ return;
729
+ }
730
+ this.pendingExternalTools.set(requestId, controller);
711
731
  try {
712
732
  let availableTools;
713
733
  if (toolName === TOOL_SEARCH_TOOL_NAME) {
734
+ if (controller.signal.aborted) {
735
+ return;
736
+ }
737
+ const aborted = new Promise((resolve) => {
738
+ controller.signal.addEventListener("abort", () => resolve(void 0), {
739
+ once: true
740
+ });
741
+ });
714
742
  try {
715
- const metadata = await this.rpc.tools.getCurrentMetadata();
716
- availableTools = metadata.tools ?? void 0;
743
+ const metadata = await Promise.race([
744
+ this.rpc.tools.getCurrentMetadata(),
745
+ aborted
746
+ ]);
747
+ availableTools = metadata?.tools ?? void 0;
717
748
  } catch {
718
749
  availableTools = void 0;
719
750
  }
720
751
  }
752
+ if (controller.signal.aborted) {
753
+ return;
754
+ }
721
755
  const rawResult = await handler(args, {
722
756
  sessionId: this.sessionId,
723
757
  toolCallId,
@@ -725,7 +759,8 @@ class CopilotSession {
725
759
  arguments: args,
726
760
  availableTools,
727
761
  traceparent,
728
- tracestate
762
+ tracestate,
763
+ signal: controller.signal
729
764
  });
730
765
  let result;
731
766
  if (rawResult == null) {
@@ -737,12 +772,12 @@ class CopilotSession {
737
772
  } else {
738
773
  result = JSON.stringify(rawResult);
739
774
  }
740
- if (this.disconnected) {
775
+ if (!this._claimExternalTool(requestId, controller)) {
741
776
  return;
742
777
  }
743
778
  await this.rpc.tools.handlePendingToolCall({ requestId, result });
744
779
  } catch (error) {
745
- if (this.disconnected) {
780
+ if (!this._claimExternalTool(requestId, controller)) {
746
781
  return;
747
782
  }
748
783
  const message = error instanceof Error ? error.message : String(error);
@@ -753,7 +788,19 @@ class CopilotSession {
753
788
  throw rpcError;
754
789
  }
755
790
  }
791
+ } finally {
792
+ if (this.pendingExternalTools.get(requestId) === controller) {
793
+ this.pendingExternalTools.delete(requestId);
794
+ }
795
+ controller.abort();
796
+ }
797
+ }
798
+ _claimExternalTool(requestId, controller) {
799
+ if (this.disconnected || this.pendingExternalTools.get(requestId) !== controller) {
800
+ return false;
756
801
  }
802
+ this.pendingExternalTools.delete(requestId);
803
+ return true;
757
804
  }
758
805
  /**
759
806
  * Executes a permission handler and sends the result back via RPC.
@@ -1507,13 +1554,27 @@ class CopilotSession {
1507
1554
  * ```
1508
1555
  */
1509
1556
  async disconnect() {
1510
- if (this.disconnected) {
1557
+ if (this.disconnected || this.disconnecting) {
1511
1558
  return;
1512
1559
  }
1513
- await this.connection.sendRequest("session.destroy", {
1514
- sessionId: this.sessionId
1515
- });
1516
- this._markDisconnected();
1560
+ this.disconnecting = true;
1561
+ try {
1562
+ let response = { success: false };
1563
+ for (let attempt = 0; attempt < 2 && !response.success; attempt++) {
1564
+ response = await this.connection.sendRequest("session.detach", {
1565
+ sessionId: this.sessionId
1566
+ });
1567
+ }
1568
+ if (!response.success) {
1569
+ throw new Error(
1570
+ `Failed to disconnect session ${this.sessionId}: ${response.error || "Unknown error"}`
1571
+ );
1572
+ }
1573
+ this._markDisconnected();
1574
+ } catch (error) {
1575
+ this.disconnecting = false;
1576
+ throw error;
1577
+ }
1517
1578
  }
1518
1579
  /** Enables `await using session = ...` syntax for automatic cleanup. */
1519
1580
  async [Symbol.asyncDispose]() {
@@ -1555,11 +1616,48 @@ class CopilotSession {
1555
1616
  * ```typescript
1556
1617
  * await session.setModel("gpt-5.4");
1557
1618
  * await session.setModel("claude-sonnet-4.6", { reasoningEffort: "high" });
1619
+ *
1620
+ * // Select the Auto model and its routing preference in one call.
1621
+ * await session.setModel("auto", { autoTier: "intelligence" });
1558
1622
  * ```
1559
1623
  */
1560
1624
  async setModel(model, options) {
1561
1625
  await this.rpc.model.switchTo({ modelId: model, ...options });
1562
1626
  }
1627
+ /**
1628
+ * Change the Auto routing preference without changing the selected model.
1629
+ *
1630
+ * The runtime does not apply the preference immediately. It records the
1631
+ * request and commits it only when a later user turn using the `auto` model
1632
+ * successfully obtains a usable model from the provider. A `pending` status
1633
+ * therefore confirms that the request was accepted, not that it took effect.
1634
+ *
1635
+ * Watch for the outcome through the `session.model_change` event on success,
1636
+ * or the ephemeral `session.auto_tier_switch_failed` event on failure. You
1637
+ * can also read the current committed and in-flight state at any time with
1638
+ * `session.rpc.model.getCurrent()`.
1639
+ *
1640
+ * Only the most recent request survives: issuing a new request replaces any
1641
+ * earlier one that has not yet been claimed by a turn.
1642
+ *
1643
+ * @param autoTier - Routing preference to activate, or `null` to return to
1644
+ * the provider's default Auto routing
1645
+ * @returns The runtime's immediate acknowledgement and Auto preference snapshot
1646
+ *
1647
+ * @experimental Part of an experimental Auto routing surface and may change
1648
+ * or be removed in a future release.
1649
+ *
1650
+ * @example
1651
+ * ```typescript
1652
+ * const result = await session.setAutoTier("intelligence");
1653
+ * if (result.status === "pending") {
1654
+ * // Takes effect on a later turn that uses the `auto` model.
1655
+ * }
1656
+ * ```
1657
+ */
1658
+ async setAutoTier(autoTier) {
1659
+ return await this.rpc.model.switchAutoTier({ autoTier });
1660
+ }
1563
1661
  /**
1564
1662
  * Log a message to the session timeline.
1565
1663
  * The message appears in the session event stream and is visible to SDK consumers
@@ -1,2 +1,2 @@
1
- export declare const COPILOT_CLI_VERSION = "1.0.83-3";
1
+ export declare const COPILOT_CLI_VERSION = "1.0.83";
2
2
  export declare const COPILOT_CLI_USE_NPM_PACKAGE = false;
@@ -1,4 +1,4 @@
1
- const COPILOT_CLI_VERSION = "1.0.83-3";
1
+ const COPILOT_CLI_VERSION = "1.0.83";
2
2
  const COPILOT_CLI_USE_NPM_PACKAGE = false;
3
3
  export {
4
4
  COPILOT_CLI_USE_NPM_PACKAGE,
package/dist/client.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare class CopilotClient {
7
7
  private ffiHost;
8
8
  private connection;
9
9
  private messageWriter;
10
+ private connectionClosed;
10
11
  private socket;
11
12
  private runtimePort;
12
13
  private actualHost;
package/dist/client.js CHANGED
@@ -214,6 +214,7 @@ class CopilotClient {
214
214
  ffiHost = null;
215
215
  connection = null;
216
216
  messageWriter = null;
217
+ connectionClosed = false;
217
218
  socket = null;
218
219
  runtimePort = null;
219
220
  actualHost = "localhost";
@@ -596,6 +597,7 @@ class CopilotClient {
596
597
  return;
597
598
  }
598
599
  this.forceStopping = false;
600
+ this.connectionClosed = false;
599
601
  this.processTransportError = null;
600
602
  this.state = "connecting";
601
603
  try {
@@ -694,7 +696,7 @@ class CopilotClient {
694
696
  }
695
697
  this.sessions.clear();
696
698
  this.githubTokenProviders.clear();
697
- if (this.connection && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
699
+ if (this.connection && !this.connectionClosed && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
698
700
  const runtimeShutdownStart = Date.now();
699
701
  const shutdownPromise = this.rpc.runtime.shutdown();
700
702
  void shutdownPromise.catch(() => void 0);
@@ -1259,6 +1261,7 @@ class CopilotClient {
1259
1261
  await this.updateSessionOptionsForMode(session, config);
1260
1262
  this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
1261
1263
  } catch (e) {
1264
+ session?._markDisconnected();
1262
1265
  if (registeredId !== void 0) {
1263
1266
  this.sessions.delete(registeredId);
1264
1267
  }
@@ -1491,6 +1494,7 @@ class CopilotClient {
1491
1494
  await this.updateSessionOptionsForMode(session, config);
1492
1495
  this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
1493
1496
  } catch (e) {
1497
+ session._markDisconnected();
1494
1498
  this.sessions.delete(sessionId);
1495
1499
  if (gitHubTokenProviderRegistrationId !== void 0) {
1496
1500
  this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
@@ -1606,7 +1610,10 @@ class CopilotClient {
1606
1610
  const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
1607
1611
  let serverVersion;
1608
1612
  try {
1609
- const connectParams = { token: this.effectiveConnectionToken };
1613
+ const connectParams = {
1614
+ token: this.effectiveConnectionToken,
1615
+ supportedTaskKinds: ["agent", "client", "shell"]
1616
+ };
1610
1617
  if (this.onGitHubTelemetry != null) {
1611
1618
  connectParams.enableGitHubTelemetryForwarding = true;
1612
1619
  }
@@ -2237,12 +2244,24 @@ stderr: ${stderrOutput}` : ""}`
2237
2244
  return await this.handleHooksInvoke(params);
2238
2245
  }
2239
2246
  );
2240
- this.connection.onClose(() => {
2247
+ const connection = this.connection;
2248
+ const markDisconnected = () => {
2249
+ if (this.connection !== connection) {
2250
+ return;
2251
+ }
2252
+ this.connectionClosed = true;
2241
2253
  this.state = "disconnected";
2254
+ for (const session of this.sessions.values()) {
2255
+ session._markDisconnected();
2256
+ }
2257
+ this.sessions.clear();
2242
2258
  this.githubTokenProviders.clear();
2243
- });
2244
- this.connection.onError((_error) => {
2245
- this.state = "disconnected";
2259
+ };
2260
+ this.connection.onClose(markDisconnected);
2261
+ this.connection.onError(() => {
2262
+ if (this.connection === connection) {
2263
+ this.state = "disconnected";
2264
+ }
2246
2265
  });
2247
2266
  }
2248
2267
  handleSessionEventNotification(notification) {