@github/copilot-sdk 1.0.29-preview.9 → 1.0.30-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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") {
@@ -909,6 +1061,7 @@ class CopilotClient {
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
+ );
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;
1324
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;
@@ -1343,19 +1613,11 @@ class CopilotClient {
1343
1613
  config.systemMessage
1344
1614
  );
1345
1615
  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
- );
1616
+ const s = new import_session.CopilotSession(sessionId, connection, void 0, this.onGetTraceContext, {
1617
+ mcpAuthHandler: config.onMcpAuthRequest,
1618
+ managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
1619
+ onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId)
1620
+ });
1359
1621
  s.registerTools(config.tools);
1360
1622
  s.registerCanvases(config.canvases);
1361
1623
  s.registerCommands(config.commands);
@@ -1381,6 +1643,7 @@ class CopilotClient {
1381
1643
  if (config.skillProvider) {
1382
1644
  s.registerSkillProvider(config.skillProvider);
1383
1645
  }
1646
+ s._registerMemoryStore(storeMemory);
1384
1647
  if (transformCallbacks) {
1385
1648
  s.registerTransformCallbacks(transformCallbacks);
1386
1649
  }
@@ -1388,12 +1651,14 @@ class CopilotClient {
1388
1651
  s.on(config.onEvent);
1389
1652
  }
1390
1653
  this.setupSessionFs(s, config);
1654
+ signal.throwIfAborted();
1391
1655
  this.sessions.set(sessionId, s);
1392
1656
  return s;
1393
1657
  };
1394
1658
  let session;
1395
1659
  let registeredId;
1396
1660
  let uninitializedCloudSessionId;
1661
+ let releaseCloudInitialization;
1397
1662
  if (localSessionId !== void 0) {
1398
1663
  try {
1399
1664
  session = initializeSession(localSessionId);
@@ -1406,14 +1671,20 @@ class CopilotClient {
1406
1671
  }
1407
1672
  }
1408
1673
  try {
1409
- const response = await this.connection.sendRequest("session.create", {
1410
- ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
1674
+ const traceContext = await this.runInitializationCallback(
1675
+ localSessionId,
1676
+ () => (0, import_telemetry.getTraceContext)(this.onGetTraceContext)
1677
+ );
1678
+ signal.throwIfAborted();
1679
+ const response = await connection.sendRequest("session.create", {
1680
+ ...traceContext,
1411
1681
  model: config.model,
1412
1682
  allowedModels: config.allowedModels,
1413
1683
  sessionId: localSessionId,
1414
1684
  name: config.name,
1415
1685
  clientName: config.clientName,
1416
1686
  reasoningEffort: config.reasoningEffort,
1687
+ allowMultipleToolCalls: config.allowMultipleToolCalls,
1417
1688
  reasoningSummary: config.reasoningSummary,
1418
1689
  isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
1419
1690
  contextTier: config.contextTier,
@@ -1464,6 +1735,7 @@ class CopilotClient {
1464
1735
  ...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
1465
1736
  requestExitPlanMode: !!config.onExitPlanModeRequest,
1466
1737
  requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
1738
+ ...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
1467
1739
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
1468
1740
  workingDirectory: config.workingDirectory,
1469
1741
  trustWorkingDirectory: config.trustWorkingDirectory,
@@ -1494,6 +1766,7 @@ class CopilotClient {
1494
1766
  enableSkills: config.enableSkills,
1495
1767
  skillDirectories: config.skillDirectories,
1496
1768
  ...config.skillProvider ? { hasSkillProvider: true } : {},
1769
+ ...storeMemory ? { hasMemoryStore: true } : {},
1497
1770
  agentDirectories: config.agentDirectories,
1498
1771
  hookDirectories: config.hookDirectories,
1499
1772
  pluginDirectories: config.pluginDirectories,
@@ -1502,7 +1775,7 @@ class CopilotClient {
1502
1775
  disabledMcpServers: config.disabledMcpServers,
1503
1776
  sandboxConfig: config.sandboxConfig,
1504
1777
  infiniteSessions: config.infiniteSessions,
1505
- memory: config.memory,
1778
+ memory: toWireMemoryConfiguration(config.memory),
1506
1779
  gitHubToken: config.gitHubToken,
1507
1780
  gitHubTokenProviderRegistrationId,
1508
1781
  cloud: config.cloud,
@@ -1512,12 +1785,14 @@ class CopilotClient {
1512
1785
  excludedManagedSettings: config.excludedManagedSettings,
1513
1786
  enforceManagedModelDefaults: config.enforceManagedModelDefaults,
1514
1787
  interactiveUser: config.interactiveUser,
1788
+ permissionMode: config.permissionMode,
1515
1789
  managedSettings: config.managedSettings
1516
1790
  });
1517
1791
  const {
1518
1792
  sessionId: returnedSessionId,
1519
1793
  workspacePath,
1520
- capabilities
1794
+ capabilities,
1795
+ permissionModeResult
1521
1796
  } = response;
1522
1797
  if (!returnedSessionId) {
1523
1798
  throw new Error("session.create response did not include a sessionId");
@@ -1529,24 +1804,37 @@ class CopilotClient {
1529
1804
  }
1530
1805
  if (session === void 0) {
1531
1806
  uninitializedCloudSessionId = returnedSessionId;
1807
+ }
1808
+ signal.throwIfAborted();
1809
+ if (session === void 0) {
1810
+ releaseCloudInitialization = await this.acquireSessionInitialization(
1811
+ returnedSessionId,
1812
+ signal
1813
+ );
1814
+ signal.throwIfAborted();
1532
1815
  session = initializeSession(returnedSessionId);
1533
- uninitializedCloudSessionId = void 0;
1534
1816
  registeredId = returnedSessionId;
1535
1817
  }
1536
1818
  this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId);
1819
+ session.setCapabilities(capabilities);
1820
+ if (config.autoModeRecovery) {
1821
+ await this.requireAutoModeRecovery(session, connection);
1822
+ }
1537
1823
  if (config.onMcpAuthRequest) {
1538
- await this.connection.sendRequest("session.eventLog.registerInterest", {
1824
+ await connection.sendRequest("session.eventLog.registerInterest", {
1539
1825
  sessionId: returnedSessionId,
1540
1826
  eventType: "mcp.oauth_required"
1541
1827
  });
1542
1828
  }
1543
1829
  session["_workspacePath"] = workspacePath;
1544
- session.setCapabilities(capabilities);
1830
+ session.setPermissionModeResult(permissionModeResult);
1545
1831
  await this.updateSessionOptionsForMode(session, config);
1832
+ signal.throwIfAborted();
1833
+ uninitializedCloudSessionId = void 0;
1546
1834
  this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
1547
1835
  } catch (e) {
1548
1836
  session?._markDisconnected();
1549
- if (registeredId !== void 0) {
1837
+ if (registeredId !== void 0 && this.sessions.get(registeredId) === session) {
1550
1838
  this.sessions.delete(registeredId);
1551
1839
  }
1552
1840
  if (gitHubTokenProviderRegistrationId !== void 0) {
@@ -1555,18 +1843,28 @@ class CopilotClient {
1555
1843
  if (uninitializedCloudSessionId !== void 0) {
1556
1844
  try {
1557
1845
  await withTimeout(
1558
- this.deleteSession(uninitializedCloudSessionId),
1846
+ this.requestSessionDeletion(connection, uninitializedCloudSessionId),
1559
1847
  CLOUD_SESSION_CLEANUP_TIMEOUT_MS,
1560
1848
  `session.delete timed out after ${CLOUD_SESSION_CLEANUP_TIMEOUT_MS}ms`
1561
1849
  );
1562
1850
  } catch (cleanupError) {
1563
- throw new AggregateError(
1851
+ const failure = new AggregateError(
1564
1852
  [e, cleanupError],
1565
- "Failed to initialize and delete cloud session"
1853
+ `Failed to initialize and delete cloud session ${uninitializedCloudSessionId}`
1566
1854
  );
1855
+ if (config.autoModeRecovery) {
1856
+ this.recordFailedAutoModeRecoveryCleanup(
1857
+ connection,
1858
+ uninitializedCloudSessionId,
1859
+ failure
1860
+ );
1861
+ }
1862
+ throw failure;
1567
1863
  }
1568
1864
  }
1569
1865
  throw e;
1866
+ } finally {
1867
+ releaseCloudInitialization?.();
1570
1868
  }
1571
1869
  for (const entry of this.hostHandoffs.values()) {
1572
1870
  if (entry.requestedSessionId === session.sessionId) {
@@ -1607,16 +1905,52 @@ class CopilotClient {
1607
1905
  return this.resumeSessionInternal(sessionId, config, contributions, extensionOptions);
1608
1906
  }
1609
1907
  async resumeSessionInternal(sessionId, config, contributions = {}, extensionOptions) {
1610
- const { workflows } = contributions;
1908
+ if (config.autoModeRecovery && typeof config.onAutoModeSwitchRequest !== "function") {
1909
+ throw new Error("autoModeRecovery requires onAutoModeSwitchRequest");
1910
+ }
1611
1911
  if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
1612
1912
  throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
1613
1913
  }
1614
- if (!this.connection || this.startPromise) {
1615
- await this.start();
1914
+ requestedMemoryStore(config.memory);
1915
+ const signal = await this.getSessionInitializationSignal();
1916
+ const release = await this.acquireSessionInitialization(sessionId, signal);
1917
+ try {
1918
+ signal.throwIfAborted();
1919
+ return await this.resumeSessionWithInitialization(
1920
+ sessionId,
1921
+ config,
1922
+ contributions,
1923
+ signal,
1924
+ extensionOptions
1925
+ );
1926
+ } finally {
1927
+ release();
1928
+ }
1929
+ }
1930
+ async resumeSessionWithInitialization(sessionId, config, contributions, signal, extensionOptions) {
1931
+ const { workflows } = contributions;
1932
+ const storeMemory = requestedMemoryStore(config.memory);
1933
+ signal.throwIfAborted();
1934
+ const connection = this.connection;
1935
+ const resolveAliases = config.autoModeRecovery === true && !this.sessionFsConfig;
1936
+ if (resolveAliases && isAbbreviatedUuidPrefix(sessionId)) {
1937
+ const resolved = await connection.sendRequest(
1938
+ "sessions.findByPrefix",
1939
+ { prefix: sessionId, configDir: config.configDirectory }
1940
+ );
1941
+ const canonicalId = resolved?.sessionId;
1942
+ if (typeof canonicalId !== "string" || canonicalId.length === 0) {
1943
+ throw new Error(`No unique session matches prefix ${sessionId}`);
1944
+ }
1945
+ if (!canonicalId.toLowerCase().startsWith(sessionId.toLowerCase())) {
1946
+ throw new Error(`Runtime returned an unrelated session ID for prefix ${sessionId}`);
1947
+ }
1948
+ sessionId = canonicalId;
1949
+ signal.throwIfAborted();
1616
1950
  }
1617
1951
  const session = new import_session.CopilotSession(
1618
1952
  sessionId,
1619
- this.connection,
1953
+ connection,
1620
1954
  void 0,
1621
1955
  this.onGetTraceContext,
1622
1956
  {
@@ -1655,6 +1989,7 @@ class CopilotClient {
1655
1989
  if (config.skillProvider) {
1656
1990
  session.registerSkillProvider(config.skillProvider);
1657
1991
  }
1992
+ session._registerMemoryStore(storeMemory);
1658
1993
  const modeDefaults = this.configDefaultsForMode();
1659
1994
  config = { ...modeDefaults, ...config };
1660
1995
  config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
@@ -1670,8 +2005,12 @@ class CopilotClient {
1670
2005
  }
1671
2006
  const toolFilterOptions = this.resolveToolFilterOptions(config);
1672
2007
  this.setupSessionFs(session, config);
1673
- const replacedSession = this.sessions.get(sessionId);
1674
- this.sessions.set(sessionId, session);
2008
+ signal.throwIfAborted();
2009
+ const requestedSessionId = sessionId;
2010
+ let replacedRequestedSession;
2011
+ let replacedSession;
2012
+ let releaseCanonicalInitialization;
2013
+ let keepResumedSessionOnRecoveryRejection = false;
1675
2014
  const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider(
1676
2015
  config.gitHubTokenProvider,
1677
2016
  sessionId
@@ -1682,14 +2021,29 @@ class CopilotClient {
1682
2021
  );
1683
2022
  }
1684
2023
  try {
1685
- const response = await this.connection.sendRequest("session.resume", {
2024
+ const traceContext = await this.runInitializationCallback(
2025
+ sessionId,
2026
+ () => (0, import_telemetry.getTraceContext)(this.onGetTraceContext)
2027
+ );
2028
+ signal.throwIfAborted();
2029
+ replacedRequestedSession = this.sessions.get(sessionId);
2030
+ replacedSession = replacedRequestedSession;
2031
+ this.sessions.set(sessionId, session);
2032
+ this.pendingSessionResumes.set(requestedSessionId, {
2033
+ session,
2034
+ connection,
2035
+ gitHubTokenProviderRegistrationId,
2036
+ resolveAliases
2037
+ });
2038
+ const response = await connection.sendRequest("session.resume", {
1686
2039
  allowedModels: config.allowedModels,
1687
- ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
2040
+ ...traceContext,
1688
2041
  sessionId,
1689
2042
  allowTranscriptRecovery: config.allowTranscriptRecovery,
1690
2043
  clientName: config.clientName,
1691
2044
  model: config.model,
1692
2045
  reasoningEffort: config.reasoningEffort,
2046
+ allowMultipleToolCalls: config.allowMultipleToolCalls,
1693
2047
  reasoningSummary: config.reasoningSummary,
1694
2048
  isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
1695
2049
  contextTier: config.contextTier,
@@ -1741,6 +2095,7 @@ class CopilotClient {
1741
2095
  ...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
1742
2096
  requestExitPlanMode: !!config.onExitPlanModeRequest,
1743
2097
  requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
2098
+ ...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
1744
2099
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
1745
2100
  workingDirectory: config.workingDirectory,
1746
2101
  additionalDirectories: config.additionalDirectories,
@@ -1769,6 +2124,7 @@ class CopilotClient {
1769
2124
  agent: config.agent,
1770
2125
  skillDirectories: config.skillDirectories,
1771
2126
  ...config.skillProvider ? { hasSkillProvider: true } : {},
2127
+ ...storeMemory ? { hasMemoryStore: true } : {},
1772
2128
  agentDirectories: config.agentDirectories,
1773
2129
  hookDirectories: config.hookDirectories,
1774
2130
  pluginDirectories: config.pluginDirectories,
@@ -1777,7 +2133,7 @@ class CopilotClient {
1777
2133
  disabledMcpServers: config.disabledMcpServers,
1778
2134
  sandboxConfig: config.sandboxConfig,
1779
2135
  infiniteSessions: config.infiniteSessions,
1780
- memory: config.memory,
2136
+ memory: toWireMemoryConfiguration(config.memory),
1781
2137
  disableResume: config.suppressResumeEvent,
1782
2138
  continuePendingWork: config.continuePendingWork,
1783
2139
  gitHubToken: config.gitHubToken,
@@ -1789,11 +2145,34 @@ class CopilotClient {
1789
2145
  excludedManagedSettings: config.excludedManagedSettings,
1790
2146
  enforceManagedModelDefaults: config.enforceManagedModelDefaults,
1791
2147
  interactiveUser: config.interactiveUser,
2148
+ permissionMode: config.permissionMode,
1792
2149
  managedSettings: config.managedSettings,
1793
2150
  ...extensionOptions?.requestedEnvironmentVariables ? {
1794
2151
  requestedEnvironmentVariables: extensionOptions.requestedEnvironmentVariables
1795
2152
  } : {}
1796
2153
  });
2154
+ const { sessionId: returnedSessionId } = response;
2155
+ if (returnedSessionId && returnedSessionId !== sessionId) {
2156
+ if (!sessionIdsOverlap(requestedSessionId, returnedSessionId)) {
2157
+ releaseCanonicalInitialization = await this.acquireSessionInitialization(
2158
+ returnedSessionId,
2159
+ signal
2160
+ );
2161
+ }
2162
+ replacedSession = this.sessions.get(returnedSessionId);
2163
+ if (this.sessions.get(requestedSessionId) === session) {
2164
+ if (replacedRequestedSession) {
2165
+ this.sessions.set(requestedSessionId, replacedRequestedSession);
2166
+ } else {
2167
+ this.sessions.delete(requestedSessionId);
2168
+ }
2169
+ }
2170
+ session._setSessionId(returnedSessionId);
2171
+ sessionId = returnedSessionId;
2172
+ this.sessions.set(sessionId, session);
2173
+ this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, sessionId);
2174
+ }
2175
+ signal.throwIfAborted();
1797
2176
  if (extensionOptions?.requestedEnvironmentVariables) {
1798
2177
  const requested = new Set(extensionOptions.requestedEnvironmentVariables);
1799
2178
  const { grantedEnvironmentVariables } = response;
@@ -1803,32 +2182,59 @@ class CopilotClient {
1803
2182
  }
1804
2183
  }
1805
2184
  }
1806
- const { workspacePath, capabilities, openCanvases, transcriptRecovery } = response;
2185
+ const {
2186
+ workspacePath,
2187
+ capabilities,
2188
+ openCanvases,
2189
+ transcriptRecovery,
2190
+ permissionModeResult
2191
+ } = response;
1807
2192
  session["_workspacePath"] = workspacePath;
1808
2193
  session.setCapabilities(capabilities);
2194
+ if (config.autoModeRecovery) {
2195
+ keepResumedSessionOnRecoveryRejection = replacedSession !== void 0 && session.capabilities.ui?.autoModeRecovery !== true;
2196
+ await this.requireAutoModeRecovery(
2197
+ session,
2198
+ connection,
2199
+ replacedSession === void 0
2200
+ );
2201
+ }
1809
2202
  session.setOpenCanvases(openCanvases ?? []);
1810
2203
  session.setTranscriptRecovery(transcriptRecovery);
2204
+ session.setPermissionModeResult(permissionModeResult);
1811
2205
  if (config.onMcpAuthRequest) {
1812
- await this.connection.sendRequest("session.eventLog.registerInterest", {
2206
+ await connection.sendRequest("session.eventLog.registerInterest", {
1813
2207
  sessionId,
1814
2208
  eventType: "mcp.oauth_required"
1815
2209
  });
1816
2210
  }
1817
2211
  await this.updateSessionOptionsForMode(session, config);
2212
+ signal.throwIfAborted();
1818
2213
  this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
1819
2214
  } catch (e) {
1820
- session._markDisconnected();
2215
+ if (!keepResumedSessionOnRecoveryRejection) {
2216
+ session._markDisconnected();
2217
+ }
1821
2218
  if (this.sessions.get(sessionId) === session) {
1822
- if (replacedSession) {
2219
+ if (replacedSession && !keepResumedSessionOnRecoveryRejection) {
1823
2220
  this.sessions.set(sessionId, replacedSession);
1824
- } else {
2221
+ } else if (!keepResumedSessionOnRecoveryRejection) {
1825
2222
  this.sessions.delete(sessionId);
1826
2223
  }
1827
2224
  }
1828
2225
  if (gitHubTokenProviderRegistrationId !== void 0) {
1829
- this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
2226
+ if (keepResumedSessionOnRecoveryRejection) {
2227
+ this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
2228
+ } else {
2229
+ this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
2230
+ }
1830
2231
  }
1831
2232
  throw e;
2233
+ } finally {
2234
+ if (this.pendingSessionResumes.get(requestedSessionId)?.session === session) {
2235
+ this.pendingSessionResumes.delete(requestedSessionId);
2236
+ }
2237
+ releaseCanonicalInitialization?.();
1832
2238
  }
1833
2239
  for (const entry of this.hostHandoffs.values()) {
1834
2240
  if (entry.requestedSessionId === session.sessionId) {
@@ -1837,6 +2243,47 @@ class CopilotClient {
1837
2243
  }
1838
2244
  return session;
1839
2245
  }
2246
+ async requireAutoModeRecovery(session, connection, detachOnFailure = true) {
2247
+ if (session.capabilities.ui?.autoModeRecovery === true) {
2248
+ return;
2249
+ }
2250
+ const error = new Error("The runtime does not support autoModeRecovery");
2251
+ if (!detachOnFailure) {
2252
+ throw error;
2253
+ }
2254
+ const cancellation = new import_node.CancellationTokenSource();
2255
+ try {
2256
+ const response = await withTimeout(
2257
+ connection.sendRequest(
2258
+ "session.detach",
2259
+ { sessionId: session.sessionId },
2260
+ cancellation.token
2261
+ ),
2262
+ AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS,
2263
+ `session.detach timed out after ${AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS}ms`
2264
+ );
2265
+ if (!response.success) {
2266
+ throw new Error(`Failed to detach session: ${response.error || "Unknown error"}`);
2267
+ }
2268
+ } catch (cleanupError) {
2269
+ const failure = new AggregateError([error, cleanupError], error.message);
2270
+ this.recordFailedAutoModeRecoveryCleanup(connection, session.sessionId, failure);
2271
+ throw failure;
2272
+ } finally {
2273
+ cancellation.cancel();
2274
+ cancellation.dispose();
2275
+ session._markDisconnected();
2276
+ }
2277
+ throw error;
2278
+ }
2279
+ recordFailedAutoModeRecoveryCleanup(connection, sessionId, failure) {
2280
+ let failed = this.failedAutoModeRecoveryCleanups.get(connection);
2281
+ if (!failed) {
2282
+ failed = /* @__PURE__ */ new Map();
2283
+ this.failedAutoModeRecoveryCleanups.set(connection, failed);
2284
+ }
2285
+ failed.set(sessionId, failure);
2286
+ }
1840
2287
  /**
1841
2288
  * Start a complete AHP listener hosted in-process by this runtime.
1842
2289
  *
@@ -1948,6 +2395,12 @@ class CopilotClient {
1948
2395
  }
1949
2396
  }
1950
2397
  }
2398
+ abortPendingMemoryStores() {
2399
+ for (const controller of this.pendingMemoryStores) {
2400
+ controller.abort();
2401
+ }
2402
+ this.pendingMemoryStores.clear();
2403
+ }
1951
2404
  disconnectHosts() {
1952
2405
  for (const hostId of /* @__PURE__ */ new Set([
1953
2406
  ...this.hostExitCallbacks.keys(),
@@ -2279,16 +2732,34 @@ class CopilotClient {
2279
2732
  if (!this.connection) {
2280
2733
  throw new Error("Client not connected");
2281
2734
  }
2282
- const response = await this.connection.sendRequest("session.delete", {
2283
- sessionId
2284
- });
2735
+ const signal = await this.getSessionInitializationSignal();
2736
+ const release = await this.acquireSessionInitialization(sessionId, signal);
2737
+ try {
2738
+ signal.throwIfAborted();
2739
+ const connection = this.connection;
2740
+ const session = this.sessions.get(sessionId);
2741
+ await this.requestSessionDeletion(connection, sessionId);
2742
+ signal.throwIfAborted();
2743
+ if (this.sessions.get(sessionId) === session) {
2744
+ this.sessions.delete(sessionId);
2745
+ session?._clearMemoryStore();
2746
+ session?._runOnDisconnected();
2747
+ }
2748
+ } finally {
2749
+ release();
2750
+ }
2751
+ }
2752
+ async requestSessionDeletion(connection, sessionId) {
2753
+ const response = await connection.sendRequest(
2754
+ "session.delete",
2755
+ {
2756
+ sessionId
2757
+ }
2758
+ );
2285
2759
  const { success, error } = response;
2286
2760
  if (!success) {
2287
2761
  throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
2288
2762
  }
2289
- const session = this.sessions.get(sessionId);
2290
- this.sessions.delete(sessionId);
2291
- session?._runOnDisconnected();
2292
2763
  }
2293
2764
  /**
2294
2765
  * List all available sessions.
@@ -2745,7 +3216,8 @@ stderr: ${this.stderrBuffer.trim().slice(-8192) || "(empty)"}`
2745
3216
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
2746
3217
  this.connection = createMessageConnection(
2747
3218
  new import_node.StreamMessageReader(this.ffiHost.receiveStream),
2748
- this.messageWriter
3219
+ this.messageWriter,
3220
+ this.sessionInitializationContext
2749
3221
  );
2750
3222
  this.attachConnectionHandlers();
2751
3223
  this.connection.listen();
@@ -2805,7 +3277,8 @@ stderr: ${stderrOutput}` : ""}`
2805
3277
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.cliProcess.stdin);
2806
3278
  this.connection = createMessageConnection(
2807
3279
  new import_node.StreamMessageReader(this.cliProcess.stdout),
2808
- this.messageWriter
3280
+ this.messageWriter,
3281
+ this.sessionInitializationContext
2809
3282
  );
2810
3283
  this.attachConnectionHandlers();
2811
3284
  this.connection.listen();
@@ -2820,7 +3293,8 @@ stderr: ${stderrOutput}` : ""}`
2820
3293
  this.messageWriter = new TeardownResilientStreamMessageWriter(process.stdout);
2821
3294
  this.connection = createMessageConnection(
2822
3295
  new import_node.StreamMessageReader(process.stdin),
2823
- this.messageWriter
3296
+ this.messageWriter,
3297
+ this.sessionInitializationContext
2824
3298
  );
2825
3299
  this.attachConnectionHandlers();
2826
3300
  this.connection.listen();
@@ -2843,7 +3317,8 @@ stderr: ${stderrOutput}` : ""}`
2843
3317
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.socket);
2844
3318
  this.connection = createMessageConnection(
2845
3319
  new import_node.StreamMessageReader(this.socket),
2846
- this.messageWriter
3320
+ this.messageWriter,
3321
+ this.sessionInitializationContext
2847
3322
  );
2848
3323
  this.attachConnectionHandlers();
2849
3324
  this.connection.listen();
@@ -2881,9 +3356,8 @@ stderr: ${stderrOutput}` : ""}`
2881
3356
  "systemMessage.transform",
2882
3357
  async (params) => await this.handleSystemMessageTransform(params)
2883
3358
  );
2884
- const sessions = this.sessions;
2885
3359
  (0, import_rpc.registerClientSessionApiHandlers)(this.connection, (sessionId) => {
2886
- const session = sessions.get(sessionId);
3360
+ const session = this.resolveSessionCallback(sessionId);
2887
3361
  if (!session) throw new Error(`No session found for sessionId: ${sessionId}`);
2888
3362
  return session.clientSessionApis;
2889
3363
  });
@@ -2901,12 +3375,20 @@ stderr: ${stderrOutput}` : ""}`
2901
3375
  (params, token) => confirmation.confirm(params, token)
2902
3376
  );
2903
3377
  }
2904
- if (this.kerberosAuth) {
2905
- const { callback } = this.kerberosAuth;
3378
+ const resolveProxy = this.network?.resolveProxy;
3379
+ if (resolveProxy) {
3380
+ const connection2 = this.connection;
3381
+ this.connection.onRequest(
3382
+ "network.resolveProxy",
3383
+ (params, token) => resolveClientProxy(connection2, resolveProxy, params, token)
3384
+ );
3385
+ }
3386
+ const authenticateNegotiate = this.network?.authenticateNegotiate;
3387
+ if (authenticateNegotiate) {
2906
3388
  const connection2 = this.connection;
2907
3389
  this.connection.onRequest(
2908
- "kerberosAuth.acquire",
2909
- (params, token) => acquireKerberosToken(connection2, callback, params, token)
3390
+ "network.authenticateNegotiate",
3391
+ (params, token) => authenticateClientNegotiate(connection2, authenticateNegotiate, params, token)
2910
3392
  );
2911
3393
  }
2912
3394
  this.connection.onRequest(
@@ -2977,11 +3459,29 @@ stderr: ${stderrOutput}` : ""}`
2977
3459
  this.connection.onRequest(
2978
3460
  "sessionManagement.create",
2979
3461
  async (params, token) => {
2980
- const session = this.sessions.get(params.sessionId);
3462
+ const session = this.resolveSessionCallback(params.sessionId);
2981
3463
  if (!session) throw new Error("Session creation owner is unavailable");
2982
3464
  return await session._handleSessionManagementCreate(params, token);
2983
3465
  }
2984
3466
  );
3467
+ this.connection.onRequest(
3468
+ "memoryStore.store",
3469
+ async (params, token) => {
3470
+ const session = this.sessions.get(params?.sessionId);
3471
+ if (!session) throw new Error("Memory store owner is unavailable");
3472
+ const controller = new AbortController();
3473
+ this.pendingMemoryStores.add(controller);
3474
+ const subscription = token.onCancellationRequested(() => controller.abort());
3475
+ if (token.isCancellationRequested) controller.abort();
3476
+ try {
3477
+ await session._handleMemoryStore(params, controller.signal);
3478
+ } finally {
3479
+ this.pendingMemoryStores.delete(controller);
3480
+ subscription.dispose();
3481
+ }
3482
+ return null;
3483
+ }
3484
+ );
2985
3485
  this.connection.onRequest(
2986
3486
  "skillProvider.read",
2987
3487
  async (params, token) => {
@@ -3013,6 +3513,7 @@ stderr: ${stderrOutput}` : ""}`
3013
3513
  this.sessions.clear();
3014
3514
  this.githubTokenProviders.clear();
3015
3515
  this.requestAdapter?.cancelPending();
3516
+ this.abortPendingMemoryStores();
3016
3517
  connection.dispose();
3017
3518
  this.socket?.destroy();
3018
3519
  this.connection = null;
@@ -3032,6 +3533,7 @@ stderr: ${stderrOutput}` : ""}`
3032
3533
  }
3033
3534
  if (disconnecting) return;
3034
3535
  disconnecting = true;
3536
+ this.invalidateSessionInitializations();
3035
3537
  this.connectionClosed = true;
3036
3538
  if (messageWriter) messageWriter.suppressWriteErrors = true;
3037
3539
  void connection.drain().then(markDisconnected);
@@ -3063,11 +3565,34 @@ stderr: ${stderrOutput}` : ""}`
3063
3565
  }
3064
3566
  });
3065
3567
  }
3568
+ resolveSessionCallback(sessionId) {
3569
+ let selected;
3570
+ let selectedPrefixLength = -1;
3571
+ for (const [prefix, pending] of this.pendingSessionResumes) {
3572
+ if (pending.connection !== this.connection || !pending.resolveAliases && prefix !== sessionId || prefix.length <= selectedPrefixLength || !(sessionIdsOverlap(prefix, sessionId) && sessionId.length >= prefix.length))
3573
+ continue;
3574
+ const knownOwners = new Set(
3575
+ [...this.sessions.values()].filter(
3576
+ (owner) => owner !== pending.session && sessionIdsOverlap(prefix, owner.sessionId) && owner.sessionId.length >= prefix.length
3577
+ )
3578
+ );
3579
+ if (knownOwners.size > 1) continue;
3580
+ if (pending.session.sessionId !== sessionId) {
3581
+ pending.session._setSessionId(sessionId);
3582
+ }
3583
+ this.assignGitHubTokenProvider(pending.gitHubTokenProviderRegistrationId, sessionId);
3584
+ selected = pending.session;
3585
+ selectedPrefixLength = prefix.length;
3586
+ }
3587
+ return selected ?? this.sessions.get(sessionId);
3588
+ }
3066
3589
  handleSessionEventNotification(notification) {
3067
3590
  if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) {
3068
3591
  return;
3069
3592
  }
3070
- const session = this.sessions.get(notification.sessionId);
3593
+ const session = this.resolveSessionCallback(
3594
+ notification.sessionId
3595
+ );
3071
3596
  const event = notification.event;
3072
3597
  if (session) {
3073
3598
  session._dispatchEvent(event);
@@ -3111,7 +3636,7 @@ stderr: ${stderrOutput}` : ""}`
3111
3636
  if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
3112
3637
  throw new Error("Invalid user input request payload");
3113
3638
  }
3114
- const session = this.sessions.get(params.sessionId);
3639
+ const session = this.resolveSessionCallback(params.sessionId);
3115
3640
  if (!session) {
3116
3641
  throw new Error(`Session not found: ${params.sessionId}`);
3117
3642
  }
@@ -3127,7 +3652,7 @@ stderr: ${stderrOutput}` : ""}`
3127
3652
  if (!params || typeof params.sessionId !== "string" || typeof params.summary !== "string" || !Array.isArray(params.actions) || typeof params.recommendedAction !== "string") {
3128
3653
  throw new Error("Invalid exit plan mode request payload");
3129
3654
  }
3130
- const session = this.sessions.get(params.sessionId);
3655
+ const session = this.resolveSessionCallback(params.sessionId);
3131
3656
  if (!session) {
3132
3657
  throw new Error(`Session not found: ${params.sessionId}`);
3133
3658
  }
@@ -3143,13 +3668,14 @@ stderr: ${stderrOutput}` : ""}`
3143
3668
  if (!params || typeof params.sessionId !== "string") {
3144
3669
  throw new Error("Invalid auto mode switch request payload");
3145
3670
  }
3146
- const session = this.sessions.get(params.sessionId);
3671
+ const session = this.resolveSessionCallback(params.sessionId);
3147
3672
  if (!session) {
3148
3673
  throw new Error(`Session not found: ${params.sessionId}`);
3149
3674
  }
3150
3675
  const response = await session._handleAutoModeSwitchRequest({
3151
3676
  errorCode: params.errorCode,
3152
- retryAfterSeconds: params.retryAfterSeconds
3677
+ retryAfterSeconds: params.retryAfterSeconds,
3678
+ ...params.autoModeRecovery === void 0 ? {} : { autoModeRecovery: params.autoModeRecovery }
3153
3679
  });
3154
3680
  return { response };
3155
3681
  }
@@ -3157,7 +3683,7 @@ stderr: ${stderrOutput}` : ""}`
3157
3683
  if (!params || typeof params.sessionId !== "string") {
3158
3684
  throw new Error("Invalid skillProvider payload");
3159
3685
  }
3160
- const session = this.sessions.get(params.sessionId);
3686
+ const session = this.resolveSessionCallback(params.sessionId);
3161
3687
  if (!session) {
3162
3688
  throw new Error(`Session not found: ${params.sessionId}`);
3163
3689
  }
@@ -3167,7 +3693,7 @@ stderr: ${stderrOutput}` : ""}`
3167
3693
  if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
3168
3694
  throw new Error("Invalid hooks invoke payload");
3169
3695
  }
3170
- const session = this.sessions.get(params.sessionId);
3696
+ const session = this.resolveSessionCallback(params.sessionId);
3171
3697
  if (!session) {
3172
3698
  throw new Error(`Session not found: ${params.sessionId}`);
3173
3699
  }
@@ -3178,7 +3704,7 @@ stderr: ${stderrOutput}` : ""}`
3178
3704
  if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
3179
3705
  throw new Error("Invalid systemMessage.transform payload");
3180
3706
  }
3181
- const session = this.sessions.get(params.sessionId);
3707
+ const session = this.resolveSessionCallback(params.sessionId);
3182
3708
  if (!session) {
3183
3709
  throw new Error(`Session not found: ${params.sessionId}`);
3184
3710
  }