@github/copilot-sdk 1.0.29 → 1.0.30-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.
@@ -31,6 +31,7 @@ __export(client_exports, {
31
31
  CopilotClient: () => CopilotClient
32
32
  });
33
33
  module.exports = __toCommonJS(client_exports);
34
+ var import_node_async_hooks = require("node:async_hooks");
34
35
  var import_node_child_process = require("node:child_process");
35
36
  var import_node_crypto = require("node:crypto");
36
37
  var import_node_fs = require("node:fs");
@@ -38,6 +39,7 @@ var import_node_net = require("node:net");
38
39
  var import_node_path = require("node:path");
39
40
  var import_node = require("vscode-jsonrpc/node");
40
41
  var import_rpc = require("./generated/rpc.js");
42
+ var import_decisions = require("./decisions.js");
41
43
  var import_sdkProtocolVersion = require("./sdkProtocolVersion.js");
42
44
  var import_session = require("./session.js");
43
45
  var import_host = require("./host.js");
@@ -54,6 +56,20 @@ var import_types = require("./types.js");
54
56
  const MIN_PROTOCOL_VERSION = 3;
55
57
  const RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
56
58
  const CLOUD_SESSION_CLEANUP_TIMEOUT_MS = 1e4;
59
+ const AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS = 1e4;
60
+ function sessionIdsOverlap(left, right) {
61
+ if (left === right) return true;
62
+ if (!isUuidSessionId(left) || !isUuidSessionId(right)) return false;
63
+ const normalizedLeft = left.toLowerCase();
64
+ const normalizedRight = right.toLowerCase();
65
+ return normalizedLeft.startsWith(normalizedRight) || normalizedRight.startsWith(normalizedLeft);
66
+ }
67
+ function isAbbreviatedUuidPrefix(sessionId) {
68
+ return /^[0-9a-f]{7}[0-9a-f-]{0,28}$/i.test(sessionId);
69
+ }
70
+ function isUuidSessionId(sessionId) {
71
+ return isAbbreviatedUuidPrefix(sessionId) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId);
72
+ }
57
73
  const messageEncodeErrors = /* @__PURE__ */ new WeakSet();
58
74
  const jsonMessageEncoder = {
59
75
  name: "application/json",
@@ -67,11 +83,12 @@ const jsonMessageEncoder = {
67
83
  }
68
84
  }
69
85
  };
70
- function createMessageConnection(reader, writer) {
86
+ function createMessageConnection(reader, writer, sessionInitializationContext) {
71
87
  let dispatch;
72
88
  let finishDrain;
73
89
  let drained;
74
90
  let disposed = false;
91
+ const callbacks = /* @__PURE__ */ new Map();
75
92
  const barrier = { jsonrpc: "2.0", method: "$/sdkDrain" };
76
93
  const connection = (0, import_node.createMessageConnection)(
77
94
  {
@@ -84,7 +101,22 @@ function createMessageConnection(reader, writer) {
84
101
  },
85
102
  dispose: () => reader.dispose()
86
103
  },
87
- writer,
104
+ {
105
+ onError: writer.onError,
106
+ onClose: writer.onClose,
107
+ write: (message) => {
108
+ if (import_node.Message.isResponse(message)) {
109
+ const context = callbacks.get(message.id);
110
+ if (context) {
111
+ context.active = false;
112
+ callbacks.delete(message.id);
113
+ }
114
+ }
115
+ return writer.write(message);
116
+ },
117
+ end: () => writer.end(),
118
+ dispose: () => writer.dispose()
119
+ },
88
120
  void 0,
89
121
  {
90
122
  messageStrategy: {
@@ -92,7 +124,14 @@ function createMessageConnection(reader, writer) {
92
124
  if (message === barrier) {
93
125
  finishDrain?.();
94
126
  } else {
95
- next(message);
127
+ const request = import_node.Message.isRequest(message) ? message : void 0;
128
+ const params = request?.params;
129
+ const sessionId = params && typeof params === "object" && "sessionId" in params && typeof params.sessionId === "string" ? params.sessionId : void 0;
130
+ const context = sessionId === void 0 ? void 0 : { sessionId, active: true };
131
+ if (request && context) {
132
+ callbacks.set(request.id, context);
133
+ }
134
+ sessionInitializationContext.run(context, next, message);
96
135
  }
97
136
  }
98
137
  }
@@ -100,6 +139,10 @@ function createMessageConnection(reader, writer) {
100
139
  );
101
140
  connection.onDispose(() => {
102
141
  disposed = true;
142
+ for (const context of callbacks.values()) {
143
+ context.active = false;
144
+ }
145
+ callbacks.clear();
103
146
  finishDrain?.();
104
147
  });
105
148
  return Object.assign(connection, {
@@ -162,8 +205,8 @@ async function waitForChildExit(child, timeoutMs) {
162
205
  }
163
206
  });
164
207
  }
165
- async function acquireKerberosToken(connection, callback, request, token) {
166
- const ended = () => token.isCancellationRequested ? new import_node.ResponseError(-32800, "Kerberos auth request cancelled") : new Error("Kerberos auth connection closed");
208
+ async function runCancellableCallback(connection, label, token, invoke) {
209
+ const ended = () => token.isCancellationRequested ? new import_node.ResponseError(-32800, `${label} request cancelled`) : new Error(`${label} connection closed`);
167
210
  if (token.isCancellationRequested) {
168
211
  throw ended();
169
212
  }
@@ -178,14 +221,47 @@ async function acquireKerberosToken(connection, callback, request, token) {
178
221
  connection.onDispose(abort)
179
222
  ];
180
223
  try {
181
- const lookup = Promise.resolve().then(() => callback(request, controller.signal));
182
- return { token: await Promise.race([lookup, abandoned]) };
224
+ const lookup = Promise.resolve().then(() => {
225
+ if (controller.signal.aborted) {
226
+ throw ended();
227
+ }
228
+ return invoke(controller.signal);
229
+ });
230
+ return await Promise.race([lookup, abandoned]);
183
231
  } finally {
184
232
  for (const subscription of subscriptions) {
185
233
  subscription.dispose();
186
234
  }
187
235
  }
188
236
  }
237
+ function snapshotNetworkConfig(network) {
238
+ const { scope, resolveProxy, authenticateNegotiate } = network;
239
+ return {
240
+ scope,
241
+ resolveProxy: resolveProxy && ((request, signal) => resolveProxy.call(network, request, signal)),
242
+ authenticateNegotiate: authenticateNegotiate && ((request, signal) => authenticateNegotiate.call(network, request, signal))
243
+ };
244
+ }
245
+ async function resolveClientProxy(connection, callback, request, token) {
246
+ return {
247
+ proxy: await runCancellableCallback(
248
+ connection,
249
+ "Proxy resolution",
250
+ token,
251
+ (signal) => callback(request, signal)
252
+ )
253
+ };
254
+ }
255
+ async function authenticateClientNegotiate(connection, callback, request, token) {
256
+ return {
257
+ token: await runCancellableCallback(
258
+ connection,
259
+ "Negotiate authentication",
260
+ token,
261
+ (signal) => callback(request, signal)
262
+ )
263
+ };
264
+ }
189
265
  const DEFAULT_PROVIDER_NAME = "default";
190
266
  function extractBearerTokenProviders(provider, providers) {
191
267
  const callbacks = /* @__PURE__ */ new Map();
@@ -224,6 +300,18 @@ function toWireMcpServers(mcpServers) {
224
300
  })
225
301
  );
226
302
  }
303
+ function requestedMemoryStore(memory) {
304
+ if (!memory?.storeMemory) return void 0;
305
+ if (memory.enabled !== true) {
306
+ throw new Error("memory.storeMemory requires memory.enabled to be true.");
307
+ }
308
+ return memory.storeMemory;
309
+ }
310
+ function toWireMemoryConfiguration(memory) {
311
+ if (!memory) return memory;
312
+ const { storeMemory: _storeMemory, ...wire } = memory;
313
+ return wire;
314
+ }
227
315
  function toWireCustomAgents(agents) {
228
316
  if (!agents) return void 0;
229
317
  return agents.map((agent) => {
@@ -326,6 +414,7 @@ class CopilotClient {
326
414
  ffiHost = null;
327
415
  connection = null;
328
416
  requestAdapter = null;
417
+ pendingMemoryStores = /* @__PURE__ */ new Set();
329
418
  messageWriter = null;
330
419
  connectionClosed = false;
331
420
  connectionEpoch = 0;
@@ -335,7 +424,16 @@ class CopilotClient {
335
424
  state = "disconnected";
336
425
  /** Shared in-flight start; concurrent callers await it instead of spawning another CLI. */
337
426
  startPromise = null;
427
+ shutdownCanceledStartup = null;
428
+ stopPromise = null;
429
+ forceStopPromise = null;
338
430
  sessions = /* @__PURE__ */ new Map();
431
+ pendingSessionResumes = /* @__PURE__ */ new Map();
432
+ sessionInitializations = /* @__PURE__ */ new Map();
433
+ failedAutoModeRecoveryCleanups = /* @__PURE__ */ new WeakMap();
434
+ sessionInitializationContext = new import_node_async_hooks.AsyncLocalStorage();
435
+ sessionInitializationLifetime = new AbortController();
436
+ sessionInitializationGeneration = 0;
339
437
  hostExitCallbacks = /* @__PURE__ */ new Map();
340
438
  hostSessionFactories = /* @__PURE__ */ new Map();
341
439
  hostAuthenticators = /* @__PURE__ */ new Map();
@@ -364,6 +462,7 @@ class CopilotClient {
364
462
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
365
463
  _rpc = null;
366
464
  _internalRpc = null;
465
+ _decisions;
367
466
  processExitPromise = null;
368
467
  // Rejects when CLI process exits
369
468
  processTransportError = null;
@@ -372,7 +471,7 @@ class CopilotClient {
372
471
  sessionFsConfig = null;
373
472
  requestHandler = null;
374
473
  extensionLaunchProvider;
375
- kerberosAuth;
474
+ network;
376
475
  installationConfirmationHandler;
377
476
  builtinPluginDirectories = [];
378
477
  onGitHubTelemetry;
@@ -391,6 +490,16 @@ class CopilotClient {
391
490
  }
392
491
  return this._rpc;
393
492
  }
493
+ /**
494
+ * One-off yes/no, pick-one and rating questions answered by Copilot decision
495
+ * models. Requires the runtime's `DECISIONS_API` feature flag.
496
+ *
497
+ * @experimental
498
+ */
499
+ get decisions() {
500
+ this._decisions ??= new import_decisions.CopilotDecisions(() => this.rpc);
501
+ return this._decisions;
502
+ }
394
503
  /**
395
504
  * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
396
505
  * @internal
@@ -535,7 +644,7 @@ class CopilotClient {
535
644
  this.sessionFsConfig = options.sessionFs ?? null;
536
645
  this.requestHandler = options.requestHandler ?? null;
537
646
  this.extensionLaunchProvider = options.extensionLaunchProvider;
538
- this.kerberosAuth = options.kerberosAuth;
647
+ this.network = options.network ? snapshotNetworkConfig(options.network) : void 0;
539
648
  this.installationConfirmationHandler = options.installationConfirmationHandler;
540
649
  this.onGitHubTelemetry = options.onGitHubTelemetry;
541
650
  this.setupClientGlobalHandlers();
@@ -578,10 +687,15 @@ class CopilotClient {
578
687
  connectionExtraArgs = [];
579
688
  /**
580
689
  * Parse CLI URL into host and port
581
- * Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port"
690
+ * Supports raw TCP addresses: "host:port", "[ipv6]:port", "http://host:port", or just "port"
582
691
  */
583
692
  parseCliUrl(url) {
584
- const cleanUrl = url.replace(/^https?:\/\//, "");
693
+ if (/^https:\/\//i.test(url)) {
694
+ throw new Error(
695
+ "HTTPS runtime connections are not supported: this transport uses plaintext TCP, not TLS"
696
+ );
697
+ }
698
+ const cleanUrl = url.replace(/^http:\/\//, "");
585
699
  if (/^\d+$/.test(cleanUrl)) {
586
700
  return { host: "localhost", port: parseInt(cleanUrl, 10) };
587
701
  }
@@ -753,20 +867,38 @@ class CopilotClient {
753
867
  * ```
754
868
  */
755
869
  async start() {
870
+ if (this.forceStopPromise || this.stopPromise && !this.forceStopping) {
871
+ throw new Error("Client disconnected during session initialization");
872
+ }
873
+ const startup = this.startPromise;
874
+ if (startup && startup === this.shutdownCanceledStartup) {
875
+ const generation = this.sessionInitializationGeneration;
876
+ await Promise.allSettled([startup, this.stopPromise]);
877
+ if (generation !== this.sessionInitializationGeneration) {
878
+ throw new Error("Client disconnected during session initialization");
879
+ }
880
+ return this.start();
881
+ }
882
+ if (startup && !this.stopPromise) {
883
+ return startup;
884
+ }
885
+ if (this.stopPromise || this.connection && this.sessionInitializationLifetime.signal.aborted) {
886
+ throw new Error("Client disconnected during session initialization");
887
+ }
756
888
  if (this.state === "connected") {
757
889
  return;
758
890
  }
759
- if (this.startPromise) {
760
- return this.startPromise;
761
- }
762
891
  this.startPromise = this.doStart();
763
892
  try {
764
893
  await this.startPromise;
765
894
  } finally {
766
895
  this.startPromise = null;
896
+ this.shutdownCanceledStartup = null;
767
897
  }
768
898
  }
769
899
  async doStart() {
900
+ this.sessionInitializationLifetime = new AbortController();
901
+ const signal = this.sessionInitializationLifetime.signal;
770
902
  this.forceStopping = false;
771
903
  this.connectionEpoch++;
772
904
  this.connectionClosed = false;
@@ -778,24 +910,25 @@ class CopilotClient {
778
910
  } else if (!this.isExternalServer) {
779
911
  await this.startCLIServer();
780
912
  }
913
+ signal.throwIfAborted();
781
914
  await this.connectToServer();
915
+ signal.throwIfAborted();
782
916
  await this.verifyProtocolVersion();
783
- if (this.kerberosAuth) {
784
- const { mode, scope } = this.kerberosAuth;
785
- await this.rpc.kerberosAuth.setProvider(scope ? { mode, scope } : { mode });
917
+ if (this.network?.resolveProxy || this.network?.authenticateNegotiate) {
918
+ const { scope, resolveProxy, authenticateNegotiate } = this.network;
919
+ await this.rpc.network.setProvider({
920
+ ...scope ? { scope } : {},
921
+ ...resolveProxy ? { resolveProxy: {} } : {},
922
+ ...authenticateNegotiate ? { authenticateNegotiate: {} } : {}
923
+ });
786
924
  }
787
925
  if (this.extensionLaunchProvider) {
788
926
  await this.rpc.registerExtensionLaunchProvider();
789
927
  }
790
928
  if (this.builtinPluginDirectories.length > 0) {
791
- try {
792
- await this.connection.sendRequest("plugins.builtin.set", {
793
- paths: this.builtinPluginDirectories
794
- });
795
- } catch (error) {
796
- await this.forceStop();
797
- throw error;
798
- }
929
+ await this.connection.sendRequest("plugins.builtin.set", {
930
+ paths: this.builtinPluginDirectories
931
+ });
799
932
  }
800
933
  if (this.sessionFsConfig) {
801
934
  await this.connection.sendRequest("sessionFs.setProvider", {
@@ -811,10 +944,14 @@ class CopilotClient {
811
944
  if (this.connectionClosed) {
812
945
  throw new Error("CLI server connection closed during startup");
813
946
  }
947
+ signal.throwIfAborted();
814
948
  this.state = "connected";
815
949
  } catch (error) {
816
950
  const startupError = this.processTransportError ?? error;
817
- await this.forceStop();
951
+ if (!signal.aborted) {
952
+ this.invalidateSessionInitializations();
953
+ }
954
+ await this.doForceStop();
818
955
  this.state = "error";
819
956
  throw startupError;
820
957
  }
@@ -850,6 +987,21 @@ class CopilotClient {
850
987
  * ```
851
988
  */
852
989
  async stop() {
990
+ if (this.stopPromise) {
991
+ return this.stopPromise;
992
+ }
993
+ if (!this.sessionInitializationLifetime.signal.aborted) {
994
+ this.shutdownCanceledStartup = this.startPromise;
995
+ }
996
+ this.invalidateSessionInitializations();
997
+ this.stopPromise = this.doStop(this.startPromise);
998
+ try {
999
+ return await this.stopPromise;
1000
+ } finally {
1001
+ this.stopPromise = null;
1002
+ }
1003
+ }
1004
+ async doStop(startup) {
853
1005
  const errors = [];
854
1006
  const activeSessions = [...this.sessions.values()];
855
1007
  if (this.connectionConfig.kind === "inprocess") {
@@ -903,12 +1055,13 @@ class CopilotClient {
903
1055
  }
904
1056
  }
905
1057
  clearTimeout(detachDeadlineTimer);
906
- for (const session of activeSessions) {
1058
+ for (const session of /* @__PURE__ */ new Set([...activeSessions, ...this.sessions.values()])) {
907
1059
  session._markDisconnected();
908
1060
  }
909
1061
  this.sessions.clear();
910
1062
  this.githubTokenProviders.clear();
911
1063
  this.requestAdapter?.cancelPending();
1064
+ this.abortPendingMemoryStores();
912
1065
  if (this.connection && !this.connectionClosed && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
913
1066
  const runtimeShutdownStart = Date.now();
914
1067
  const shutdownPromise = this.rpc.runtime.shutdown();
@@ -1044,6 +1197,9 @@ class CopilotClient {
1044
1197
  clearTimeout(this.cliStartTimeout);
1045
1198
  this.cliStartTimeout = null;
1046
1199
  }
1200
+ if (startup) {
1201
+ await Promise.allSettled([startup]);
1202
+ }
1047
1203
  this.state = "disconnected";
1048
1204
  this.runtimePort = null;
1049
1205
  this.stderrBuffer = "";
@@ -1075,7 +1231,8 @@ class CopilotClient {
1075
1231
  * - Sends SIGKILL to the CLI process (if spawned by this client)
1076
1232
  * - Waits for the spawned CLI process to exit
1077
1233
  *
1078
- * @returns A promise that resolves when the force stop is complete
1234
+ * @returns Resolves after disconnection and spawned-process exit; later starts
1235
+ * wait for pending startup cleanup.
1079
1236
  * @throws If the spawned CLI process cannot be terminated
1080
1237
  *
1081
1238
  * @example
@@ -1094,6 +1251,21 @@ class CopilotClient {
1094
1251
  * ```
1095
1252
  */
1096
1253
  async forceStop() {
1254
+ if (this.forceStopPromise) {
1255
+ return this.forceStopPromise;
1256
+ }
1257
+ if (!this.sessionInitializationLifetime.signal.aborted) {
1258
+ this.shutdownCanceledStartup = this.startPromise;
1259
+ }
1260
+ this.invalidateSessionInitializations();
1261
+ this.forceStopPromise = this.doForceStop();
1262
+ try {
1263
+ await this.forceStopPromise;
1264
+ } finally {
1265
+ this.forceStopPromise = null;
1266
+ }
1267
+ }
1268
+ async doForceStop() {
1097
1269
  this.forceStopping = true;
1098
1270
  this.disconnectHosts();
1099
1271
  for (const session of this.sessions.values()) {
@@ -1102,6 +1274,7 @@ class CopilotClient {
1102
1274
  this.sessions.clear();
1103
1275
  this.githubTokenProviders.clear();
1104
1276
  this.requestAdapter?.cancelPending();
1277
+ this.abortPendingMemoryStores();
1105
1278
  if (this.messageWriter) {
1106
1279
  this.messageWriter.suppressWriteErrors = true;
1107
1280
  }
@@ -1313,15 +1486,112 @@ class CopilotClient {
1313
1486
  }
1314
1487
  }
1315
1488
  async createSession(config) {
1489
+ if (config.autoModeRecovery && typeof config.onAutoModeSwitchRequest !== "function") {
1490
+ throw new Error("autoModeRecovery requires onAutoModeSwitchRequest");
1491
+ }
1316
1492
  if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
1317
1493
  throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
1318
1494
  }
1319
1495
  if (config.cloud != null && config.skillProvider) {
1320
1496
  throw new Error("Skill providers are not supported for cloud sessions.");
1321
1497
  }
1322
- if (!this.connection || this.startPromise) {
1323
- await this.start();
1498
+ const storeMemory = requestedMemoryStore(config.memory);
1499
+ if (config.cloud != null && storeMemory) {
1500
+ throw new Error("Memory stores are not supported for cloud sessions.");
1501
+ }
1502
+ const signal = await this.getSessionInitializationSignal();
1503
+ const sessionId = config.sessionId ?? (config.cloud != null ? void 0 : (0, import_node_crypto.randomUUID)());
1504
+ if (sessionId === void 0) {
1505
+ return this.createSessionInternal(config, signal);
1506
+ }
1507
+ const release = await this.acquireSessionInitialization(sessionId, signal);
1508
+ try {
1509
+ signal.throwIfAborted();
1510
+ return await this.createSessionInternal({ ...config, sessionId }, signal);
1511
+ } finally {
1512
+ release();
1513
+ }
1514
+ }
1515
+ invalidateSessionInitializations() {
1516
+ this.sessionInitializationGeneration++;
1517
+ this.sessionInitializationLifetime.abort(
1518
+ new Error("Client disconnected during session initialization")
1519
+ );
1520
+ }
1521
+ async getSessionInitializationSignal() {
1522
+ const generation = this.sessionInitializationGeneration;
1523
+ const starting = !this.connection || this.startPromise ? this.start() : void 0;
1524
+ await starting;
1525
+ if (generation !== this.sessionInitializationGeneration) {
1526
+ throw new Error("Client disconnected during session initialization");
1527
+ }
1528
+ const signal = this.sessionInitializationLifetime.signal;
1529
+ signal.throwIfAborted();
1530
+ return signal;
1531
+ }
1532
+ async runInitializationCallback(sessionId, callback) {
1533
+ const context = sessionId === void 0 ? void 0 : { sessionId, active: true };
1534
+ try {
1535
+ return await this.sessionInitializationContext.run(context, callback);
1536
+ } finally {
1537
+ if (context) context.active = false;
1538
+ }
1539
+ }
1540
+ async acquireSessionInitialization(sessionId, signal) {
1541
+ signal.throwIfAborted();
1542
+ const predecessors = [...this.sessionInitializations].filter(([id]) => sessionIdsOverlap(id, sessionId)).map(([, pending]) => pending);
1543
+ const previous = predecessors.length ? Promise.all(predecessors).then(() => {
1544
+ }) : void 0;
1545
+ const context = this.sessionInitializationContext.getStore();
1546
+ if (previous && context?.active && sessionIdsOverlap(context.sessionId, sessionId)) {
1547
+ throw new Error(
1548
+ `Cannot re-enter session initialization for ${sessionId} from its callback`
1549
+ );
1324
1550
  }
1551
+ let finish;
1552
+ const current = new Promise((resolve2) => {
1553
+ finish = resolve2;
1554
+ });
1555
+ const tail = previous ? previous.then(() => current) : current;
1556
+ this.sessionInitializations.set(sessionId, tail);
1557
+ void tail.then(() => {
1558
+ if (this.sessionInitializations.get(sessionId) === tail) {
1559
+ this.sessionInitializations.delete(sessionId);
1560
+ }
1561
+ });
1562
+ try {
1563
+ if (previous) {
1564
+ let cancel;
1565
+ try {
1566
+ await new Promise((resolve2, reject) => {
1567
+ cancel = () => reject(signal.reason);
1568
+ signal.addEventListener("abort", cancel, { once: true });
1569
+ void previous.then(resolve2);
1570
+ });
1571
+ } finally {
1572
+ signal.removeEventListener("abort", cancel);
1573
+ }
1574
+ }
1575
+ signal.throwIfAborted();
1576
+ const cleanupError = this.connection ? [...this.failedAutoModeRecoveryCleanups.get(this.connection) ?? []].find(
1577
+ ([id]) => sessionIdsOverlap(id, sessionId)
1578
+ )?.[1] : void 0;
1579
+ if (cleanupError) {
1580
+ throw new Error(
1581
+ `Cannot initialize session ${sessionId} after failed Auto recovery cleanup; stop and restart the client before retrying`,
1582
+ { cause: cleanupError }
1583
+ );
1584
+ }
1585
+ return finish;
1586
+ } catch (error) {
1587
+ finish();
1588
+ throw error;
1589
+ }
1590
+ }
1591
+ async createSessionInternal(config, signal) {
1592
+ signal.throwIfAborted();
1593
+ const connection = this.connection;
1594
+ const storeMemory = requestedMemoryStore(config.memory);
1325
1595
  const modeDefaults = this.configDefaultsForMode();
1326
1596
  config = { ...modeDefaults, ...config };
1327
1597
  config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
@@ -1342,20 +1612,13 @@ class CopilotClient {
1342
1612
  const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
1343
1613
  config.systemMessage
1344
1614
  );
1615
+ let replacedSession;
1345
1616
  const initializeSession = (sessionId) => {
1346
- const s = new import_session.CopilotSession(
1347
- sessionId,
1348
- this.connection,
1349
- void 0,
1350
- this.onGetTraceContext,
1351
- {
1352
- mcpAuthHandler: config.onMcpAuthRequest,
1353
- managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
1354
- onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(
1355
- gitHubTokenProviderRegistrationId
1356
- )
1357
- }
1358
- );
1617
+ const s = new import_session.CopilotSession(sessionId, connection, void 0, this.onGetTraceContext, {
1618
+ mcpAuthHandler: config.onMcpAuthRequest,
1619
+ managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
1620
+ onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId)
1621
+ });
1359
1622
  s.registerTools(config.tools);
1360
1623
  s.registerCanvases(config.canvases);
1361
1624
  s.registerCommands(config.commands);
@@ -1381,6 +1644,7 @@ class CopilotClient {
1381
1644
  if (config.skillProvider) {
1382
1645
  s.registerSkillProvider(config.skillProvider);
1383
1646
  }
1647
+ s._registerMemoryStore(storeMemory);
1384
1648
  if (transformCallbacks) {
1385
1649
  s.registerTransformCallbacks(transformCallbacks);
1386
1650
  }
@@ -1388,12 +1652,15 @@ class CopilotClient {
1388
1652
  s.on(config.onEvent);
1389
1653
  }
1390
1654
  this.setupSessionFs(s, config);
1655
+ signal.throwIfAborted();
1656
+ replacedSession = this.sessions.get(sessionId);
1391
1657
  this.sessions.set(sessionId, s);
1392
1658
  return s;
1393
1659
  };
1394
1660
  let session;
1395
1661
  let registeredId;
1396
1662
  let uninitializedCloudSessionId;
1663
+ let releaseCloudInitialization;
1397
1664
  if (localSessionId !== void 0) {
1398
1665
  try {
1399
1666
  session = initializeSession(localSessionId);
@@ -1406,14 +1673,20 @@ class CopilotClient {
1406
1673
  }
1407
1674
  }
1408
1675
  try {
1409
- const response = await this.connection.sendRequest("session.create", {
1410
- ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
1676
+ const traceContext = await this.runInitializationCallback(
1677
+ localSessionId,
1678
+ () => (0, import_telemetry.getTraceContext)(this.onGetTraceContext)
1679
+ );
1680
+ signal.throwIfAborted();
1681
+ const response = await connection.sendRequest("session.create", {
1682
+ ...traceContext,
1411
1683
  model: config.model,
1412
1684
  allowedModels: config.allowedModels,
1413
1685
  sessionId: localSessionId,
1414
1686
  name: config.name,
1415
1687
  clientName: config.clientName,
1416
1688
  reasoningEffort: config.reasoningEffort,
1689
+ allowMultipleToolCalls: config.allowMultipleToolCalls,
1417
1690
  reasoningSummary: config.reasoningSummary,
1418
1691
  isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
1419
1692
  contextTier: config.contextTier,
@@ -1464,6 +1737,7 @@ class CopilotClient {
1464
1737
  ...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
1465
1738
  requestExitPlanMode: !!config.onExitPlanModeRequest,
1466
1739
  requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
1740
+ ...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
1467
1741
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
1468
1742
  workingDirectory: config.workingDirectory,
1469
1743
  trustWorkingDirectory: config.trustWorkingDirectory,
@@ -1494,6 +1768,7 @@ class CopilotClient {
1494
1768
  enableSkills: config.enableSkills,
1495
1769
  skillDirectories: config.skillDirectories,
1496
1770
  ...config.skillProvider ? { hasSkillProvider: true } : {},
1771
+ ...storeMemory ? { hasMemoryStore: true } : {},
1497
1772
  agentDirectories: config.agentDirectories,
1498
1773
  hookDirectories: config.hookDirectories,
1499
1774
  pluginDirectories: config.pluginDirectories,
@@ -1502,7 +1777,7 @@ class CopilotClient {
1502
1777
  disabledMcpServers: config.disabledMcpServers,
1503
1778
  sandboxConfig: config.sandboxConfig,
1504
1779
  infiniteSessions: config.infiniteSessions,
1505
- memory: config.memory,
1780
+ memory: toWireMemoryConfiguration(config.memory),
1506
1781
  gitHubToken: config.gitHubToken,
1507
1782
  gitHubTokenProviderRegistrationId,
1508
1783
  cloud: config.cloud,
@@ -1512,12 +1787,14 @@ class CopilotClient {
1512
1787
  excludedManagedSettings: config.excludedManagedSettings,
1513
1788
  enforceManagedModelDefaults: config.enforceManagedModelDefaults,
1514
1789
  interactiveUser: config.interactiveUser,
1790
+ permissionMode: config.permissionMode,
1515
1791
  managedSettings: config.managedSettings
1516
1792
  });
1517
1793
  const {
1518
1794
  sessionId: returnedSessionId,
1519
1795
  workspacePath,
1520
- capabilities
1796
+ capabilities,
1797
+ permissionModeResult
1521
1798
  } = response;
1522
1799
  if (!returnedSessionId) {
1523
1800
  throw new Error("session.create response did not include a sessionId");
@@ -1529,25 +1806,47 @@ class CopilotClient {
1529
1806
  }
1530
1807
  if (session === void 0) {
1531
1808
  uninitializedCloudSessionId = returnedSessionId;
1809
+ }
1810
+ signal.throwIfAborted();
1811
+ if (session === void 0) {
1812
+ releaseCloudInitialization = await this.acquireSessionInitialization(
1813
+ returnedSessionId,
1814
+ signal
1815
+ );
1816
+ signal.throwIfAborted();
1532
1817
  session = initializeSession(returnedSessionId);
1533
- uninitializedCloudSessionId = void 0;
1534
1818
  registeredId = returnedSessionId;
1535
1819
  }
1536
1820
  this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId);
1821
+ session.setCapabilities(capabilities);
1822
+ if (config.autoModeRecovery) {
1823
+ await this.requireAutoModeRecovery(session, connection);
1824
+ }
1537
1825
  if (config.onMcpAuthRequest) {
1538
- await this.connection.sendRequest("session.eventLog.registerInterest", {
1826
+ await connection.sendRequest("session.eventLog.registerInterest", {
1539
1827
  sessionId: returnedSessionId,
1540
1828
  eventType: "mcp.oauth_required"
1541
1829
  });
1542
1830
  }
1543
1831
  session["_workspacePath"] = workspacePath;
1544
- session.setCapabilities(capabilities);
1832
+ session.setPermissionModeResult(permissionModeResult);
1545
1833
  await this.updateSessionOptionsForMode(session, config);
1834
+ signal.throwIfAborted();
1835
+ uninitializedCloudSessionId = void 0;
1546
1836
  this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
1547
1837
  } catch (e) {
1548
1838
  session?._markDisconnected();
1549
- if (registeredId !== void 0) {
1550
- this.sessions.delete(registeredId);
1839
+ if (registeredId !== void 0 && session !== void 0) {
1840
+ const current = this.sessions.get(registeredId);
1841
+ if (current === session) {
1842
+ if (replacedSession && !replacedSession._isDisconnected()) {
1843
+ this.sessions.set(registeredId, replacedSession);
1844
+ } else {
1845
+ this.sessions.delete(registeredId);
1846
+ }
1847
+ } else if (current === void 0) {
1848
+ replacedSession?._markDisconnected();
1849
+ }
1551
1850
  }
1552
1851
  if (gitHubTokenProviderRegistrationId !== void 0) {
1553
1852
  this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
@@ -1555,18 +1854,33 @@ class CopilotClient {
1555
1854
  if (uninitializedCloudSessionId !== void 0) {
1556
1855
  try {
1557
1856
  await withTimeout(
1558
- this.deleteSession(uninitializedCloudSessionId),
1857
+ this.requestSessionDeletion(connection, uninitializedCloudSessionId),
1559
1858
  CLOUD_SESSION_CLEANUP_TIMEOUT_MS,
1560
1859
  `session.delete timed out after ${CLOUD_SESSION_CLEANUP_TIMEOUT_MS}ms`
1561
1860
  );
1562
1861
  } catch (cleanupError) {
1563
- throw new AggregateError(
1862
+ const failure = new AggregateError(
1564
1863
  [e, cleanupError],
1565
- "Failed to initialize and delete cloud session"
1864
+ `Failed to initialize and delete cloud session ${uninitializedCloudSessionId}`
1566
1865
  );
1866
+ if (config.autoModeRecovery) {
1867
+ this.recordFailedAutoModeRecoveryCleanup(
1868
+ connection,
1869
+ uninitializedCloudSessionId,
1870
+ failure
1871
+ );
1872
+ }
1873
+ throw failure;
1567
1874
  }
1568
1875
  }
1569
1876
  throw e;
1877
+ } finally {
1878
+ releaseCloudInitialization?.();
1879
+ }
1880
+ if (session._isDisconnected()) {
1881
+ this.forgetSession(session);
1882
+ } else {
1883
+ session._setReleaseFromClient(this.forgetSession.bind(this, session));
1570
1884
  }
1571
1885
  for (const entry of this.hostHandoffs.values()) {
1572
1886
  if (entry.requestedSessionId === session.sessionId) {
@@ -1607,16 +1921,52 @@ class CopilotClient {
1607
1921
  return this.resumeSessionInternal(sessionId, config, contributions, extensionOptions);
1608
1922
  }
1609
1923
  async resumeSessionInternal(sessionId, config, contributions = {}, extensionOptions) {
1610
- const { workflows } = contributions;
1924
+ if (config.autoModeRecovery && typeof config.onAutoModeSwitchRequest !== "function") {
1925
+ throw new Error("autoModeRecovery requires onAutoModeSwitchRequest");
1926
+ }
1611
1927
  if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
1612
1928
  throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
1613
1929
  }
1614
- if (!this.connection || this.startPromise) {
1615
- await this.start();
1930
+ requestedMemoryStore(config.memory);
1931
+ const signal = await this.getSessionInitializationSignal();
1932
+ const release = await this.acquireSessionInitialization(sessionId, signal);
1933
+ try {
1934
+ signal.throwIfAborted();
1935
+ return await this.resumeSessionWithInitialization(
1936
+ sessionId,
1937
+ config,
1938
+ contributions,
1939
+ signal,
1940
+ extensionOptions
1941
+ );
1942
+ } finally {
1943
+ release();
1944
+ }
1945
+ }
1946
+ async resumeSessionWithInitialization(sessionId, config, contributions, signal, extensionOptions) {
1947
+ const { workflows } = contributions;
1948
+ const storeMemory = requestedMemoryStore(config.memory);
1949
+ signal.throwIfAborted();
1950
+ const connection = this.connection;
1951
+ const resolveAliases = config.autoModeRecovery === true && !this.sessionFsConfig;
1952
+ if (resolveAliases && isAbbreviatedUuidPrefix(sessionId)) {
1953
+ const resolved = await connection.sendRequest(
1954
+ "sessions.findByPrefix",
1955
+ { prefix: sessionId, configDir: config.configDirectory }
1956
+ );
1957
+ const canonicalId = resolved?.sessionId;
1958
+ if (typeof canonicalId !== "string" || canonicalId.length === 0) {
1959
+ throw new Error(`No unique session matches prefix ${sessionId}`);
1960
+ }
1961
+ if (!canonicalId.toLowerCase().startsWith(sessionId.toLowerCase())) {
1962
+ throw new Error(`Runtime returned an unrelated session ID for prefix ${sessionId}`);
1963
+ }
1964
+ sessionId = canonicalId;
1965
+ signal.throwIfAborted();
1616
1966
  }
1617
1967
  const session = new import_session.CopilotSession(
1618
1968
  sessionId,
1619
- this.connection,
1969
+ connection,
1620
1970
  void 0,
1621
1971
  this.onGetTraceContext,
1622
1972
  {
@@ -1655,6 +2005,7 @@ class CopilotClient {
1655
2005
  if (config.skillProvider) {
1656
2006
  session.registerSkillProvider(config.skillProvider);
1657
2007
  }
2008
+ session._registerMemoryStore(storeMemory);
1658
2009
  const modeDefaults = this.configDefaultsForMode();
1659
2010
  config = { ...modeDefaults, ...config };
1660
2011
  config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
@@ -1670,8 +2021,12 @@ class CopilotClient {
1670
2021
  }
1671
2022
  const toolFilterOptions = this.resolveToolFilterOptions(config);
1672
2023
  this.setupSessionFs(session, config);
1673
- const replacedSession = this.sessions.get(sessionId);
1674
- this.sessions.set(sessionId, session);
2024
+ signal.throwIfAborted();
2025
+ const requestedSessionId = sessionId;
2026
+ let replacedRequestedSession;
2027
+ let replacedSession;
2028
+ let releaseCanonicalInitialization;
2029
+ let keepResumedSessionOnRecoveryRejection = false;
1675
2030
  const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider(
1676
2031
  config.gitHubTokenProvider,
1677
2032
  sessionId
@@ -1682,14 +2037,29 @@ class CopilotClient {
1682
2037
  );
1683
2038
  }
1684
2039
  try {
1685
- const response = await this.connection.sendRequest("session.resume", {
2040
+ const traceContext = await this.runInitializationCallback(
2041
+ sessionId,
2042
+ () => (0, import_telemetry.getTraceContext)(this.onGetTraceContext)
2043
+ );
2044
+ signal.throwIfAborted();
2045
+ replacedRequestedSession = this.sessions.get(sessionId);
2046
+ replacedSession = replacedRequestedSession;
2047
+ this.sessions.set(sessionId, session);
2048
+ this.pendingSessionResumes.set(requestedSessionId, {
2049
+ session,
2050
+ connection,
2051
+ gitHubTokenProviderRegistrationId,
2052
+ resolveAliases
2053
+ });
2054
+ const response = await connection.sendRequest("session.resume", {
1686
2055
  allowedModels: config.allowedModels,
1687
- ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
2056
+ ...traceContext,
1688
2057
  sessionId,
1689
2058
  allowTranscriptRecovery: config.allowTranscriptRecovery,
1690
2059
  clientName: config.clientName,
1691
2060
  model: config.model,
1692
2061
  reasoningEffort: config.reasoningEffort,
2062
+ allowMultipleToolCalls: config.allowMultipleToolCalls,
1693
2063
  reasoningSummary: config.reasoningSummary,
1694
2064
  isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
1695
2065
  contextTier: config.contextTier,
@@ -1741,6 +2111,7 @@ class CopilotClient {
1741
2111
  ...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
1742
2112
  requestExitPlanMode: !!config.onExitPlanModeRequest,
1743
2113
  requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
2114
+ ...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
1744
2115
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
1745
2116
  workingDirectory: config.workingDirectory,
1746
2117
  additionalDirectories: config.additionalDirectories,
@@ -1769,6 +2140,7 @@ class CopilotClient {
1769
2140
  agent: config.agent,
1770
2141
  skillDirectories: config.skillDirectories,
1771
2142
  ...config.skillProvider ? { hasSkillProvider: true } : {},
2143
+ ...storeMemory ? { hasMemoryStore: true } : {},
1772
2144
  agentDirectories: config.agentDirectories,
1773
2145
  hookDirectories: config.hookDirectories,
1774
2146
  pluginDirectories: config.pluginDirectories,
@@ -1777,7 +2149,7 @@ class CopilotClient {
1777
2149
  disabledMcpServers: config.disabledMcpServers,
1778
2150
  sandboxConfig: config.sandboxConfig,
1779
2151
  infiniteSessions: config.infiniteSessions,
1780
- memory: config.memory,
2152
+ memory: toWireMemoryConfiguration(config.memory),
1781
2153
  disableResume: config.suppressResumeEvent,
1782
2154
  continuePendingWork: config.continuePendingWork,
1783
2155
  gitHubToken: config.gitHubToken,
@@ -1789,11 +2161,34 @@ class CopilotClient {
1789
2161
  excludedManagedSettings: config.excludedManagedSettings,
1790
2162
  enforceManagedModelDefaults: config.enforceManagedModelDefaults,
1791
2163
  interactiveUser: config.interactiveUser,
2164
+ permissionMode: config.permissionMode,
1792
2165
  managedSettings: config.managedSettings,
1793
2166
  ...extensionOptions?.requestedEnvironmentVariables ? {
1794
2167
  requestedEnvironmentVariables: extensionOptions.requestedEnvironmentVariables
1795
2168
  } : {}
1796
2169
  });
2170
+ const { sessionId: returnedSessionId } = response;
2171
+ if (returnedSessionId && returnedSessionId !== sessionId) {
2172
+ if (!sessionIdsOverlap(requestedSessionId, returnedSessionId)) {
2173
+ releaseCanonicalInitialization = await this.acquireSessionInitialization(
2174
+ returnedSessionId,
2175
+ signal
2176
+ );
2177
+ }
2178
+ replacedSession = this.sessions.get(returnedSessionId);
2179
+ if (this.sessions.get(requestedSessionId) === session) {
2180
+ if (replacedRequestedSession) {
2181
+ this.sessions.set(requestedSessionId, replacedRequestedSession);
2182
+ } else {
2183
+ this.sessions.delete(requestedSessionId);
2184
+ }
2185
+ }
2186
+ session._setSessionId(returnedSessionId);
2187
+ sessionId = returnedSessionId;
2188
+ this.sessions.set(sessionId, session);
2189
+ this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, sessionId);
2190
+ }
2191
+ signal.throwIfAborted();
1797
2192
  if (extensionOptions?.requestedEnvironmentVariables) {
1798
2193
  const requested = new Set(extensionOptions.requestedEnvironmentVariables);
1799
2194
  const { grantedEnvironmentVariables } = response;
@@ -1803,32 +2198,67 @@ class CopilotClient {
1803
2198
  }
1804
2199
  }
1805
2200
  }
1806
- const { workspacePath, capabilities, openCanvases, transcriptRecovery } = response;
2201
+ const {
2202
+ workspacePath,
2203
+ capabilities,
2204
+ openCanvases,
2205
+ transcriptRecovery,
2206
+ permissionModeResult
2207
+ } = response;
1807
2208
  session["_workspacePath"] = workspacePath;
1808
2209
  session.setCapabilities(capabilities);
2210
+ if (config.autoModeRecovery) {
2211
+ keepResumedSessionOnRecoveryRejection = replacedSession !== void 0 && session.capabilities.ui?.autoModeRecovery !== true;
2212
+ await this.requireAutoModeRecovery(
2213
+ session,
2214
+ connection,
2215
+ replacedSession === void 0
2216
+ );
2217
+ }
1809
2218
  session.setOpenCanvases(openCanvases ?? []);
1810
2219
  session.setTranscriptRecovery(transcriptRecovery);
2220
+ session.setPermissionModeResult(permissionModeResult);
1811
2221
  if (config.onMcpAuthRequest) {
1812
- await this.connection.sendRequest("session.eventLog.registerInterest", {
2222
+ await connection.sendRequest("session.eventLog.registerInterest", {
1813
2223
  sessionId,
1814
2224
  eventType: "mcp.oauth_required"
1815
2225
  });
1816
2226
  }
1817
2227
  await this.updateSessionOptionsForMode(session, config);
2228
+ signal.throwIfAborted();
1818
2229
  this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
1819
2230
  } catch (e) {
1820
- session._markDisconnected();
1821
- if (this.sessions.get(sessionId) === session) {
1822
- if (replacedSession) {
2231
+ if (!keepResumedSessionOnRecoveryRejection) {
2232
+ session._markDisconnected();
2233
+ }
2234
+ const current = this.sessions.get(sessionId);
2235
+ if (current === session) {
2236
+ if (replacedSession && !replacedSession._isDisconnected() && !keepResumedSessionOnRecoveryRejection) {
1823
2237
  this.sessions.set(sessionId, replacedSession);
1824
- } else {
2238
+ } else if (!keepResumedSessionOnRecoveryRejection) {
1825
2239
  this.sessions.delete(sessionId);
1826
2240
  }
2241
+ } else if (current === void 0) {
2242
+ replacedSession?._markDisconnected();
1827
2243
  }
1828
2244
  if (gitHubTokenProviderRegistrationId !== void 0) {
1829
- this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
2245
+ if (keepResumedSessionOnRecoveryRejection) {
2246
+ this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
2247
+ } else {
2248
+ this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
2249
+ }
1830
2250
  }
1831
2251
  throw e;
2252
+ } finally {
2253
+ if (this.pendingSessionResumes.get(requestedSessionId)?.session === session) {
2254
+ this.pendingSessionResumes.delete(requestedSessionId);
2255
+ }
2256
+ releaseCanonicalInitialization?.();
2257
+ }
2258
+ if (session._isDisconnected()) {
2259
+ this.forgetSession(session);
2260
+ } else {
2261
+ session._setReleaseFromClient(this.forgetSession.bind(this, session));
1832
2262
  }
1833
2263
  for (const entry of this.hostHandoffs.values()) {
1834
2264
  if (entry.requestedSessionId === session.sessionId) {
@@ -1837,6 +2267,47 @@ class CopilotClient {
1837
2267
  }
1838
2268
  return session;
1839
2269
  }
2270
+ async requireAutoModeRecovery(session, connection, detachOnFailure = true) {
2271
+ if (session.capabilities.ui?.autoModeRecovery === true) {
2272
+ return;
2273
+ }
2274
+ const error = new Error("The runtime does not support autoModeRecovery");
2275
+ if (!detachOnFailure) {
2276
+ throw error;
2277
+ }
2278
+ const cancellation = new import_node.CancellationTokenSource();
2279
+ try {
2280
+ const response = await withTimeout(
2281
+ connection.sendRequest(
2282
+ "session.detach",
2283
+ { sessionId: session.sessionId },
2284
+ cancellation.token
2285
+ ),
2286
+ AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS,
2287
+ `session.detach timed out after ${AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS}ms`
2288
+ );
2289
+ if (!response.success) {
2290
+ throw new Error(`Failed to detach session: ${response.error || "Unknown error"}`);
2291
+ }
2292
+ } catch (cleanupError) {
2293
+ const failure = new AggregateError([error, cleanupError], error.message);
2294
+ this.recordFailedAutoModeRecoveryCleanup(connection, session.sessionId, failure);
2295
+ throw failure;
2296
+ } finally {
2297
+ cancellation.cancel();
2298
+ cancellation.dispose();
2299
+ session._markDisconnected();
2300
+ }
2301
+ throw error;
2302
+ }
2303
+ recordFailedAutoModeRecoveryCleanup(connection, sessionId, failure) {
2304
+ let failed = this.failedAutoModeRecoveryCleanups.get(connection);
2305
+ if (!failed) {
2306
+ failed = /* @__PURE__ */ new Map();
2307
+ this.failedAutoModeRecoveryCleanups.set(connection, failed);
2308
+ }
2309
+ failed.set(sessionId, failure);
2310
+ }
1840
2311
  /**
1841
2312
  * Start a complete AHP listener hosted in-process by this runtime.
1842
2313
  *
@@ -1948,6 +2419,12 @@ class CopilotClient {
1948
2419
  }
1949
2420
  }
1950
2421
  }
2422
+ abortPendingMemoryStores() {
2423
+ for (const controller of this.pendingMemoryStores) {
2424
+ controller.abort();
2425
+ }
2426
+ this.pendingMemoryStores.clear();
2427
+ }
1951
2428
  disconnectHosts() {
1952
2429
  for (const hostId of /* @__PURE__ */ new Set([
1953
2430
  ...this.hostExitCallbacks.keys(),
@@ -2279,16 +2756,39 @@ class CopilotClient {
2279
2756
  if (!this.connection) {
2280
2757
  throw new Error("Client not connected");
2281
2758
  }
2282
- const response = await this.connection.sendRequest("session.delete", {
2283
- sessionId
2284
- });
2759
+ const signal = await this.getSessionInitializationSignal();
2760
+ const release = await this.acquireSessionInitialization(sessionId, signal);
2761
+ try {
2762
+ signal.throwIfAborted();
2763
+ const connection = this.connection;
2764
+ const session = this.sessions.get(sessionId);
2765
+ await this.requestSessionDeletion(connection, sessionId);
2766
+ signal.throwIfAborted();
2767
+ if (this.sessions.get(sessionId) === session) {
2768
+ this.sessions.delete(sessionId);
2769
+ session?._markDisconnected();
2770
+ }
2771
+ } finally {
2772
+ release();
2773
+ }
2774
+ }
2775
+ async requestSessionDeletion(connection, sessionId) {
2776
+ const response = await connection.sendRequest(
2777
+ "session.delete",
2778
+ {
2779
+ sessionId
2780
+ }
2781
+ );
2285
2782
  const { success, error } = response;
2286
2783
  if (!success) {
2287
2784
  throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
2288
2785
  }
2289
- const session = this.sessions.get(sessionId);
2290
- this.sessions.delete(sessionId);
2291
- session?._runOnDisconnected();
2786
+ }
2787
+ /** Stops tracking a disconnected session unless a later create or resume replaced it. */
2788
+ forgetSession(session) {
2789
+ if (this.sessions.get(session.sessionId) === session) {
2790
+ this.sessions.delete(session.sessionId);
2791
+ }
2292
2792
  }
2293
2793
  /**
2294
2794
  * List all available sessions.
@@ -2745,7 +3245,8 @@ stderr: ${this.stderrBuffer.trim().slice(-8192) || "(empty)"}`
2745
3245
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
2746
3246
  this.connection = createMessageConnection(
2747
3247
  new import_node.StreamMessageReader(this.ffiHost.receiveStream),
2748
- this.messageWriter
3248
+ this.messageWriter,
3249
+ this.sessionInitializationContext
2749
3250
  );
2750
3251
  this.attachConnectionHandlers();
2751
3252
  this.connection.listen();
@@ -2805,7 +3306,8 @@ stderr: ${stderrOutput}` : ""}`
2805
3306
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.cliProcess.stdin);
2806
3307
  this.connection = createMessageConnection(
2807
3308
  new import_node.StreamMessageReader(this.cliProcess.stdout),
2808
- this.messageWriter
3309
+ this.messageWriter,
3310
+ this.sessionInitializationContext
2809
3311
  );
2810
3312
  this.attachConnectionHandlers();
2811
3313
  this.connection.listen();
@@ -2820,7 +3322,8 @@ stderr: ${stderrOutput}` : ""}`
2820
3322
  this.messageWriter = new TeardownResilientStreamMessageWriter(process.stdout);
2821
3323
  this.connection = createMessageConnection(
2822
3324
  new import_node.StreamMessageReader(process.stdin),
2823
- this.messageWriter
3325
+ this.messageWriter,
3326
+ this.sessionInitializationContext
2824
3327
  );
2825
3328
  this.attachConnectionHandlers();
2826
3329
  this.connection.listen();
@@ -2843,7 +3346,8 @@ stderr: ${stderrOutput}` : ""}`
2843
3346
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.socket);
2844
3347
  this.connection = createMessageConnection(
2845
3348
  new import_node.StreamMessageReader(this.socket),
2846
- this.messageWriter
3349
+ this.messageWriter,
3350
+ this.sessionInitializationContext
2847
3351
  );
2848
3352
  this.attachConnectionHandlers();
2849
3353
  this.connection.listen();
@@ -2881,9 +3385,8 @@ stderr: ${stderrOutput}` : ""}`
2881
3385
  "systemMessage.transform",
2882
3386
  async (params) => await this.handleSystemMessageTransform(params)
2883
3387
  );
2884
- const sessions = this.sessions;
2885
3388
  (0, import_rpc.registerClientSessionApiHandlers)(this.connection, (sessionId) => {
2886
- const session = sessions.get(sessionId);
3389
+ const session = this.resolveSessionCallback(sessionId);
2887
3390
  if (!session) throw new Error(`No session found for sessionId: ${sessionId}`);
2888
3391
  return session.clientSessionApis;
2889
3392
  });
@@ -2901,12 +3404,20 @@ stderr: ${stderrOutput}` : ""}`
2901
3404
  (params, token) => confirmation.confirm(params, token)
2902
3405
  );
2903
3406
  }
2904
- if (this.kerberosAuth) {
2905
- const { callback } = this.kerberosAuth;
3407
+ const resolveProxy = this.network?.resolveProxy;
3408
+ if (resolveProxy) {
3409
+ const connection2 = this.connection;
3410
+ this.connection.onRequest(
3411
+ "network.resolveProxy",
3412
+ (params, token) => resolveClientProxy(connection2, resolveProxy, params, token)
3413
+ );
3414
+ }
3415
+ const authenticateNegotiate = this.network?.authenticateNegotiate;
3416
+ if (authenticateNegotiate) {
2906
3417
  const connection2 = this.connection;
2907
3418
  this.connection.onRequest(
2908
- "kerberosAuth.acquire",
2909
- (params, token) => acquireKerberosToken(connection2, callback, params, token)
3419
+ "network.authenticateNegotiate",
3420
+ (params, token) => authenticateClientNegotiate(connection2, authenticateNegotiate, params, token)
2910
3421
  );
2911
3422
  }
2912
3423
  this.connection.onRequest(
@@ -2977,11 +3488,29 @@ stderr: ${stderrOutput}` : ""}`
2977
3488
  this.connection.onRequest(
2978
3489
  "sessionManagement.create",
2979
3490
  async (params, token) => {
2980
- const session = this.sessions.get(params.sessionId);
3491
+ const session = this.resolveSessionCallback(params.sessionId);
2981
3492
  if (!session) throw new Error("Session creation owner is unavailable");
2982
3493
  return await session._handleSessionManagementCreate(params, token);
2983
3494
  }
2984
3495
  );
3496
+ this.connection.onRequest(
3497
+ "memoryStore.store",
3498
+ async (params, token) => {
3499
+ const session = this.sessions.get(params?.sessionId);
3500
+ if (!session) throw new Error("Memory store owner is unavailable");
3501
+ const controller = new AbortController();
3502
+ this.pendingMemoryStores.add(controller);
3503
+ const subscription = token.onCancellationRequested(() => controller.abort());
3504
+ if (token.isCancellationRequested) controller.abort();
3505
+ try {
3506
+ await session._handleMemoryStore(params, controller.signal);
3507
+ } finally {
3508
+ this.pendingMemoryStores.delete(controller);
3509
+ subscription.dispose();
3510
+ }
3511
+ return null;
3512
+ }
3513
+ );
2985
3514
  this.connection.onRequest(
2986
3515
  "skillProvider.read",
2987
3516
  async (params, token) => {
@@ -3013,6 +3542,7 @@ stderr: ${stderrOutput}` : ""}`
3013
3542
  this.sessions.clear();
3014
3543
  this.githubTokenProviders.clear();
3015
3544
  this.requestAdapter?.cancelPending();
3545
+ this.abortPendingMemoryStores();
3016
3546
  connection.dispose();
3017
3547
  this.socket?.destroy();
3018
3548
  this.connection = null;
@@ -3032,6 +3562,7 @@ stderr: ${stderrOutput}` : ""}`
3032
3562
  }
3033
3563
  if (disconnecting) return;
3034
3564
  disconnecting = true;
3565
+ this.invalidateSessionInitializations();
3035
3566
  this.connectionClosed = true;
3036
3567
  if (messageWriter) messageWriter.suppressWriteErrors = true;
3037
3568
  void connection.drain().then(markDisconnected);
@@ -3063,11 +3594,34 @@ stderr: ${stderrOutput}` : ""}`
3063
3594
  }
3064
3595
  });
3065
3596
  }
3597
+ resolveSessionCallback(sessionId) {
3598
+ let selected;
3599
+ let selectedPrefixLength = -1;
3600
+ for (const [prefix, pending] of this.pendingSessionResumes) {
3601
+ if (pending.connection !== this.connection || !pending.resolveAliases && prefix !== sessionId || prefix.length <= selectedPrefixLength || !(sessionIdsOverlap(prefix, sessionId) && sessionId.length >= prefix.length))
3602
+ continue;
3603
+ const knownOwners = new Set(
3604
+ [...this.sessions.values()].filter(
3605
+ (owner) => owner !== pending.session && sessionIdsOverlap(prefix, owner.sessionId) && owner.sessionId.length >= prefix.length
3606
+ )
3607
+ );
3608
+ if (knownOwners.size > 1) continue;
3609
+ if (pending.session.sessionId !== sessionId) {
3610
+ pending.session._setSessionId(sessionId);
3611
+ }
3612
+ this.assignGitHubTokenProvider(pending.gitHubTokenProviderRegistrationId, sessionId);
3613
+ selected = pending.session;
3614
+ selectedPrefixLength = prefix.length;
3615
+ }
3616
+ return selected ?? this.sessions.get(sessionId);
3617
+ }
3066
3618
  handleSessionEventNotification(notification) {
3067
3619
  if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) {
3068
3620
  return;
3069
3621
  }
3070
- const session = this.sessions.get(notification.sessionId);
3622
+ const session = this.resolveSessionCallback(
3623
+ notification.sessionId
3624
+ );
3071
3625
  const event = notification.event;
3072
3626
  if (session) {
3073
3627
  session._dispatchEvent(event);
@@ -3111,7 +3665,7 @@ stderr: ${stderrOutput}` : ""}`
3111
3665
  if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
3112
3666
  throw new Error("Invalid user input request payload");
3113
3667
  }
3114
- const session = this.sessions.get(params.sessionId);
3668
+ const session = this.resolveSessionCallback(params.sessionId);
3115
3669
  if (!session) {
3116
3670
  throw new Error(`Session not found: ${params.sessionId}`);
3117
3671
  }
@@ -3127,7 +3681,7 @@ stderr: ${stderrOutput}` : ""}`
3127
3681
  if (!params || typeof params.sessionId !== "string" || typeof params.summary !== "string" || !Array.isArray(params.actions) || typeof params.recommendedAction !== "string") {
3128
3682
  throw new Error("Invalid exit plan mode request payload");
3129
3683
  }
3130
- const session = this.sessions.get(params.sessionId);
3684
+ const session = this.resolveSessionCallback(params.sessionId);
3131
3685
  if (!session) {
3132
3686
  throw new Error(`Session not found: ${params.sessionId}`);
3133
3687
  }
@@ -3143,13 +3697,14 @@ stderr: ${stderrOutput}` : ""}`
3143
3697
  if (!params || typeof params.sessionId !== "string") {
3144
3698
  throw new Error("Invalid auto mode switch request payload");
3145
3699
  }
3146
- const session = this.sessions.get(params.sessionId);
3700
+ const session = this.resolveSessionCallback(params.sessionId);
3147
3701
  if (!session) {
3148
3702
  throw new Error(`Session not found: ${params.sessionId}`);
3149
3703
  }
3150
3704
  const response = await session._handleAutoModeSwitchRequest({
3151
3705
  errorCode: params.errorCode,
3152
- retryAfterSeconds: params.retryAfterSeconds
3706
+ retryAfterSeconds: params.retryAfterSeconds,
3707
+ ...params.autoModeRecovery === void 0 ? {} : { autoModeRecovery: params.autoModeRecovery }
3153
3708
  });
3154
3709
  return { response };
3155
3710
  }
@@ -3157,7 +3712,7 @@ stderr: ${stderrOutput}` : ""}`
3157
3712
  if (!params || typeof params.sessionId !== "string") {
3158
3713
  throw new Error("Invalid skillProvider payload");
3159
3714
  }
3160
- const session = this.sessions.get(params.sessionId);
3715
+ const session = this.resolveSessionCallback(params.sessionId);
3161
3716
  if (!session) {
3162
3717
  throw new Error(`Session not found: ${params.sessionId}`);
3163
3718
  }
@@ -3167,7 +3722,7 @@ stderr: ${stderrOutput}` : ""}`
3167
3722
  if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
3168
3723
  throw new Error("Invalid hooks invoke payload");
3169
3724
  }
3170
- const session = this.sessions.get(params.sessionId);
3725
+ const session = this.resolveSessionCallback(params.sessionId);
3171
3726
  if (!session) {
3172
3727
  throw new Error(`Session not found: ${params.sessionId}`);
3173
3728
  }
@@ -3178,7 +3733,7 @@ stderr: ${stderrOutput}` : ""}`
3178
3733
  if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
3179
3734
  throw new Error("Invalid systemMessage.transform payload");
3180
3735
  }
3181
- const session = this.sessions.get(params.sessionId);
3736
+ const session = this.resolveSessionCallback(params.sessionId);
3182
3737
  if (!session) {
3183
3738
  throw new Error(`Session not found: ${params.sessionId}`);
3184
3739
  }