@github/copilot-sdk 1.0.29 → 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.
package/dist/client.js CHANGED
@@ -1,11 +1,14 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import { spawn } from "node:child_process";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { accessSync, constants, existsSync, statSync } from "node:fs";
4
5
  import { isIPv6, Socket } from "node:net";
5
6
  import { dirname, isAbsolute, join, resolve, win32 } from "node:path";
6
7
  import {
8
+ CancellationTokenSource,
7
9
  createMessageConnection as createRpcMessageConnection,
8
10
  ErrorCodes,
11
+ Message,
9
12
  ResponseError,
10
13
  StreamMessageReader,
11
14
  StreamMessageWriter
@@ -16,6 +19,7 @@ import {
16
19
  registerClientGlobalApiHandlers,
17
20
  registerClientSessionApiHandlers
18
21
  } from "./generated/rpc.js";
22
+ import { CopilotDecisions } from "./decisions.js";
19
23
  import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
20
24
  import { CopilotSession } from "./session.js";
21
25
  import {
@@ -36,6 +40,20 @@ import { defaultJoinSessionPermissionHandler } from "./types.js";
36
40
  const MIN_PROTOCOL_VERSION = 3;
37
41
  const RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
38
42
  const CLOUD_SESSION_CLEANUP_TIMEOUT_MS = 1e4;
43
+ const AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS = 1e4;
44
+ function sessionIdsOverlap(left, right) {
45
+ if (left === right) return true;
46
+ if (!isUuidSessionId(left) || !isUuidSessionId(right)) return false;
47
+ const normalizedLeft = left.toLowerCase();
48
+ const normalizedRight = right.toLowerCase();
49
+ return normalizedLeft.startsWith(normalizedRight) || normalizedRight.startsWith(normalizedLeft);
50
+ }
51
+ function isAbbreviatedUuidPrefix(sessionId) {
52
+ return /^[0-9a-f]{7}[0-9a-f-]{0,28}$/i.test(sessionId);
53
+ }
54
+ function isUuidSessionId(sessionId) {
55
+ 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);
56
+ }
39
57
  const messageEncodeErrors = /* @__PURE__ */ new WeakSet();
40
58
  const jsonMessageEncoder = {
41
59
  name: "application/json",
@@ -49,11 +67,12 @@ const jsonMessageEncoder = {
49
67
  }
50
68
  }
51
69
  };
52
- function createMessageConnection(reader, writer) {
70
+ function createMessageConnection(reader, writer, sessionInitializationContext) {
53
71
  let dispatch;
54
72
  let finishDrain;
55
73
  let drained;
56
74
  let disposed = false;
75
+ const callbacks = /* @__PURE__ */ new Map();
57
76
  const barrier = { jsonrpc: "2.0", method: "$/sdkDrain" };
58
77
  const connection = createRpcMessageConnection(
59
78
  {
@@ -66,7 +85,22 @@ function createMessageConnection(reader, writer) {
66
85
  },
67
86
  dispose: () => reader.dispose()
68
87
  },
69
- writer,
88
+ {
89
+ onError: writer.onError,
90
+ onClose: writer.onClose,
91
+ write: (message) => {
92
+ if (Message.isResponse(message)) {
93
+ const context = callbacks.get(message.id);
94
+ if (context) {
95
+ context.active = false;
96
+ callbacks.delete(message.id);
97
+ }
98
+ }
99
+ return writer.write(message);
100
+ },
101
+ end: () => writer.end(),
102
+ dispose: () => writer.dispose()
103
+ },
70
104
  void 0,
71
105
  {
72
106
  messageStrategy: {
@@ -74,7 +108,14 @@ function createMessageConnection(reader, writer) {
74
108
  if (message === barrier) {
75
109
  finishDrain?.();
76
110
  } else {
77
- next(message);
111
+ const request = Message.isRequest(message) ? message : void 0;
112
+ const params = request?.params;
113
+ const sessionId = params && typeof params === "object" && "sessionId" in params && typeof params.sessionId === "string" ? params.sessionId : void 0;
114
+ const context = sessionId === void 0 ? void 0 : { sessionId, active: true };
115
+ if (request && context) {
116
+ callbacks.set(request.id, context);
117
+ }
118
+ sessionInitializationContext.run(context, next, message);
78
119
  }
79
120
  }
80
121
  }
@@ -82,6 +123,10 @@ function createMessageConnection(reader, writer) {
82
123
  );
83
124
  connection.onDispose(() => {
84
125
  disposed = true;
126
+ for (const context of callbacks.values()) {
127
+ context.active = false;
128
+ }
129
+ callbacks.clear();
85
130
  finishDrain?.();
86
131
  });
87
132
  return Object.assign(connection, {
@@ -144,8 +189,8 @@ async function waitForChildExit(child, timeoutMs) {
144
189
  }
145
190
  });
146
191
  }
147
- async function acquireKerberosToken(connection, callback, request, token) {
148
- const ended = () => token.isCancellationRequested ? new ResponseError(-32800, "Kerberos auth request cancelled") : new Error("Kerberos auth connection closed");
192
+ async function runCancellableCallback(connection, label, token, invoke) {
193
+ const ended = () => token.isCancellationRequested ? new ResponseError(-32800, `${label} request cancelled`) : new Error(`${label} connection closed`);
149
194
  if (token.isCancellationRequested) {
150
195
  throw ended();
151
196
  }
@@ -160,14 +205,47 @@ async function acquireKerberosToken(connection, callback, request, token) {
160
205
  connection.onDispose(abort)
161
206
  ];
162
207
  try {
163
- const lookup = Promise.resolve().then(() => callback(request, controller.signal));
164
- return { token: await Promise.race([lookup, abandoned]) };
208
+ const lookup = Promise.resolve().then(() => {
209
+ if (controller.signal.aborted) {
210
+ throw ended();
211
+ }
212
+ return invoke(controller.signal);
213
+ });
214
+ return await Promise.race([lookup, abandoned]);
165
215
  } finally {
166
216
  for (const subscription of subscriptions) {
167
217
  subscription.dispose();
168
218
  }
169
219
  }
170
220
  }
221
+ function snapshotNetworkConfig(network) {
222
+ const { scope, resolveProxy, authenticateNegotiate } = network;
223
+ return {
224
+ scope,
225
+ resolveProxy: resolveProxy && ((request, signal) => resolveProxy.call(network, request, signal)),
226
+ authenticateNegotiate: authenticateNegotiate && ((request, signal) => authenticateNegotiate.call(network, request, signal))
227
+ };
228
+ }
229
+ async function resolveClientProxy(connection, callback, request, token) {
230
+ return {
231
+ proxy: await runCancellableCallback(
232
+ connection,
233
+ "Proxy resolution",
234
+ token,
235
+ (signal) => callback(request, signal)
236
+ )
237
+ };
238
+ }
239
+ async function authenticateClientNegotiate(connection, callback, request, token) {
240
+ return {
241
+ token: await runCancellableCallback(
242
+ connection,
243
+ "Negotiate authentication",
244
+ token,
245
+ (signal) => callback(request, signal)
246
+ )
247
+ };
248
+ }
171
249
  const DEFAULT_PROVIDER_NAME = "default";
172
250
  function extractBearerTokenProviders(provider, providers) {
173
251
  const callbacks = /* @__PURE__ */ new Map();
@@ -206,6 +284,18 @@ function toWireMcpServers(mcpServers) {
206
284
  })
207
285
  );
208
286
  }
287
+ function requestedMemoryStore(memory) {
288
+ if (!memory?.storeMemory) return void 0;
289
+ if (memory.enabled !== true) {
290
+ throw new Error("memory.storeMemory requires memory.enabled to be true.");
291
+ }
292
+ return memory.storeMemory;
293
+ }
294
+ function toWireMemoryConfiguration(memory) {
295
+ if (!memory) return memory;
296
+ const { storeMemory: _storeMemory, ...wire } = memory;
297
+ return wire;
298
+ }
209
299
  function toWireCustomAgents(agents) {
210
300
  if (!agents) return void 0;
211
301
  return agents.map((agent) => {
@@ -308,6 +398,7 @@ class CopilotClient {
308
398
  ffiHost = null;
309
399
  connection = null;
310
400
  requestAdapter = null;
401
+ pendingMemoryStores = /* @__PURE__ */ new Set();
311
402
  messageWriter = null;
312
403
  connectionClosed = false;
313
404
  connectionEpoch = 0;
@@ -317,7 +408,16 @@ class CopilotClient {
317
408
  state = "disconnected";
318
409
  /** Shared in-flight start; concurrent callers await it instead of spawning another CLI. */
319
410
  startPromise = null;
411
+ shutdownCanceledStartup = null;
412
+ stopPromise = null;
413
+ forceStopPromise = null;
320
414
  sessions = /* @__PURE__ */ new Map();
415
+ pendingSessionResumes = /* @__PURE__ */ new Map();
416
+ sessionInitializations = /* @__PURE__ */ new Map();
417
+ failedAutoModeRecoveryCleanups = /* @__PURE__ */ new WeakMap();
418
+ sessionInitializationContext = new AsyncLocalStorage();
419
+ sessionInitializationLifetime = new AbortController();
420
+ sessionInitializationGeneration = 0;
321
421
  hostExitCallbacks = /* @__PURE__ */ new Map();
322
422
  hostSessionFactories = /* @__PURE__ */ new Map();
323
423
  hostAuthenticators = /* @__PURE__ */ new Map();
@@ -346,6 +446,7 @@ class CopilotClient {
346
446
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
347
447
  _rpc = null;
348
448
  _internalRpc = null;
449
+ _decisions;
349
450
  processExitPromise = null;
350
451
  // Rejects when CLI process exits
351
452
  processTransportError = null;
@@ -354,7 +455,7 @@ class CopilotClient {
354
455
  sessionFsConfig = null;
355
456
  requestHandler = null;
356
457
  extensionLaunchProvider;
357
- kerberosAuth;
458
+ network;
358
459
  installationConfirmationHandler;
359
460
  builtinPluginDirectories = [];
360
461
  onGitHubTelemetry;
@@ -373,6 +474,16 @@ class CopilotClient {
373
474
  }
374
475
  return this._rpc;
375
476
  }
477
+ /**
478
+ * One-off yes/no, pick-one and rating questions answered by Copilot decision
479
+ * models. Requires the runtime's `DECISIONS_API` feature flag.
480
+ *
481
+ * @experimental
482
+ */
483
+ get decisions() {
484
+ this._decisions ??= new CopilotDecisions(() => this.rpc);
485
+ return this._decisions;
486
+ }
376
487
  /**
377
488
  * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
378
489
  * @internal
@@ -517,7 +628,7 @@ class CopilotClient {
517
628
  this.sessionFsConfig = options.sessionFs ?? null;
518
629
  this.requestHandler = options.requestHandler ?? null;
519
630
  this.extensionLaunchProvider = options.extensionLaunchProvider;
520
- this.kerberosAuth = options.kerberosAuth;
631
+ this.network = options.network ? snapshotNetworkConfig(options.network) : void 0;
521
632
  this.installationConfirmationHandler = options.installationConfirmationHandler;
522
633
  this.onGitHubTelemetry = options.onGitHubTelemetry;
523
634
  this.setupClientGlobalHandlers();
@@ -560,10 +671,15 @@ class CopilotClient {
560
671
  connectionExtraArgs = [];
561
672
  /**
562
673
  * Parse CLI URL into host and port
563
- * Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port"
674
+ * Supports raw TCP addresses: "host:port", "[ipv6]:port", "http://host:port", or just "port"
564
675
  */
565
676
  parseCliUrl(url) {
566
- const cleanUrl = url.replace(/^https?:\/\//, "");
677
+ if (/^https:\/\//i.test(url)) {
678
+ throw new Error(
679
+ "HTTPS runtime connections are not supported: this transport uses plaintext TCP, not TLS"
680
+ );
681
+ }
682
+ const cleanUrl = url.replace(/^http:\/\//, "");
567
683
  if (/^\d+$/.test(cleanUrl)) {
568
684
  return { host: "localhost", port: parseInt(cleanUrl, 10) };
569
685
  }
@@ -735,20 +851,38 @@ class CopilotClient {
735
851
  * ```
736
852
  */
737
853
  async start() {
854
+ if (this.forceStopPromise || this.stopPromise && !this.forceStopping) {
855
+ throw new Error("Client disconnected during session initialization");
856
+ }
857
+ const startup = this.startPromise;
858
+ if (startup && startup === this.shutdownCanceledStartup) {
859
+ const generation = this.sessionInitializationGeneration;
860
+ await Promise.allSettled([startup, this.stopPromise]);
861
+ if (generation !== this.sessionInitializationGeneration) {
862
+ throw new Error("Client disconnected during session initialization");
863
+ }
864
+ return this.start();
865
+ }
866
+ if (startup && !this.stopPromise) {
867
+ return startup;
868
+ }
869
+ if (this.stopPromise || this.connection && this.sessionInitializationLifetime.signal.aborted) {
870
+ throw new Error("Client disconnected during session initialization");
871
+ }
738
872
  if (this.state === "connected") {
739
873
  return;
740
874
  }
741
- if (this.startPromise) {
742
- return this.startPromise;
743
- }
744
875
  this.startPromise = this.doStart();
745
876
  try {
746
877
  await this.startPromise;
747
878
  } finally {
748
879
  this.startPromise = null;
880
+ this.shutdownCanceledStartup = null;
749
881
  }
750
882
  }
751
883
  async doStart() {
884
+ this.sessionInitializationLifetime = new AbortController();
885
+ const signal = this.sessionInitializationLifetime.signal;
752
886
  this.forceStopping = false;
753
887
  this.connectionEpoch++;
754
888
  this.connectionClosed = false;
@@ -760,24 +894,25 @@ class CopilotClient {
760
894
  } else if (!this.isExternalServer) {
761
895
  await this.startCLIServer();
762
896
  }
897
+ signal.throwIfAborted();
763
898
  await this.connectToServer();
899
+ signal.throwIfAborted();
764
900
  await this.verifyProtocolVersion();
765
- if (this.kerberosAuth) {
766
- const { mode, scope } = this.kerberosAuth;
767
- await this.rpc.kerberosAuth.setProvider(scope ? { mode, scope } : { mode });
901
+ if (this.network?.resolveProxy || this.network?.authenticateNegotiate) {
902
+ const { scope, resolveProxy, authenticateNegotiate } = this.network;
903
+ await this.rpc.network.setProvider({
904
+ ...scope ? { scope } : {},
905
+ ...resolveProxy ? { resolveProxy: {} } : {},
906
+ ...authenticateNegotiate ? { authenticateNegotiate: {} } : {}
907
+ });
768
908
  }
769
909
  if (this.extensionLaunchProvider) {
770
910
  await this.rpc.registerExtensionLaunchProvider();
771
911
  }
772
912
  if (this.builtinPluginDirectories.length > 0) {
773
- try {
774
- await this.connection.sendRequest("plugins.builtin.set", {
775
- paths: this.builtinPluginDirectories
776
- });
777
- } catch (error) {
778
- await this.forceStop();
779
- throw error;
780
- }
913
+ await this.connection.sendRequest("plugins.builtin.set", {
914
+ paths: this.builtinPluginDirectories
915
+ });
781
916
  }
782
917
  if (this.sessionFsConfig) {
783
918
  await this.connection.sendRequest("sessionFs.setProvider", {
@@ -793,10 +928,14 @@ class CopilotClient {
793
928
  if (this.connectionClosed) {
794
929
  throw new Error("CLI server connection closed during startup");
795
930
  }
931
+ signal.throwIfAborted();
796
932
  this.state = "connected";
797
933
  } catch (error) {
798
934
  const startupError = this.processTransportError ?? error;
799
- await this.forceStop();
935
+ if (!signal.aborted) {
936
+ this.invalidateSessionInitializations();
937
+ }
938
+ await this.doForceStop();
800
939
  this.state = "error";
801
940
  throw startupError;
802
941
  }
@@ -832,6 +971,21 @@ class CopilotClient {
832
971
  * ```
833
972
  */
834
973
  async stop() {
974
+ if (this.stopPromise) {
975
+ return this.stopPromise;
976
+ }
977
+ if (!this.sessionInitializationLifetime.signal.aborted) {
978
+ this.shutdownCanceledStartup = this.startPromise;
979
+ }
980
+ this.invalidateSessionInitializations();
981
+ this.stopPromise = this.doStop(this.startPromise);
982
+ try {
983
+ return await this.stopPromise;
984
+ } finally {
985
+ this.stopPromise = null;
986
+ }
987
+ }
988
+ async doStop(startup) {
835
989
  const errors = [];
836
990
  const activeSessions = [...this.sessions.values()];
837
991
  if (this.connectionConfig.kind === "inprocess") {
@@ -891,6 +1045,7 @@ class CopilotClient {
891
1045
  this.sessions.clear();
892
1046
  this.githubTokenProviders.clear();
893
1047
  this.requestAdapter?.cancelPending();
1048
+ this.abortPendingMemoryStores();
894
1049
  if (this.connection && !this.connectionClosed && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
895
1050
  const runtimeShutdownStart = Date.now();
896
1051
  const shutdownPromise = this.rpc.runtime.shutdown();
@@ -1026,6 +1181,9 @@ class CopilotClient {
1026
1181
  clearTimeout(this.cliStartTimeout);
1027
1182
  this.cliStartTimeout = null;
1028
1183
  }
1184
+ if (startup) {
1185
+ await Promise.allSettled([startup]);
1186
+ }
1029
1187
  this.state = "disconnected";
1030
1188
  this.runtimePort = null;
1031
1189
  this.stderrBuffer = "";
@@ -1057,7 +1215,8 @@ class CopilotClient {
1057
1215
  * - Sends SIGKILL to the CLI process (if spawned by this client)
1058
1216
  * - Waits for the spawned CLI process to exit
1059
1217
  *
1060
- * @returns A promise that resolves when the force stop is complete
1218
+ * @returns Resolves after disconnection and spawned-process exit; later starts
1219
+ * wait for pending startup cleanup.
1061
1220
  * @throws If the spawned CLI process cannot be terminated
1062
1221
  *
1063
1222
  * @example
@@ -1076,6 +1235,21 @@ class CopilotClient {
1076
1235
  * ```
1077
1236
  */
1078
1237
  async forceStop() {
1238
+ if (this.forceStopPromise) {
1239
+ return this.forceStopPromise;
1240
+ }
1241
+ if (!this.sessionInitializationLifetime.signal.aborted) {
1242
+ this.shutdownCanceledStartup = this.startPromise;
1243
+ }
1244
+ this.invalidateSessionInitializations();
1245
+ this.forceStopPromise = this.doForceStop();
1246
+ try {
1247
+ await this.forceStopPromise;
1248
+ } finally {
1249
+ this.forceStopPromise = null;
1250
+ }
1251
+ }
1252
+ async doForceStop() {
1079
1253
  this.forceStopping = true;
1080
1254
  this.disconnectHosts();
1081
1255
  for (const session of this.sessions.values()) {
@@ -1084,6 +1258,7 @@ class CopilotClient {
1084
1258
  this.sessions.clear();
1085
1259
  this.githubTokenProviders.clear();
1086
1260
  this.requestAdapter?.cancelPending();
1261
+ this.abortPendingMemoryStores();
1087
1262
  if (this.messageWriter) {
1088
1263
  this.messageWriter.suppressWriteErrors = true;
1089
1264
  }
@@ -1295,15 +1470,112 @@ class CopilotClient {
1295
1470
  }
1296
1471
  }
1297
1472
  async createSession(config) {
1473
+ if (config.autoModeRecovery && typeof config.onAutoModeSwitchRequest !== "function") {
1474
+ throw new Error("autoModeRecovery requires onAutoModeSwitchRequest");
1475
+ }
1298
1476
  if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
1299
1477
  throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
1300
1478
  }
1301
1479
  if (config.cloud != null && config.skillProvider) {
1302
1480
  throw new Error("Skill providers are not supported for cloud sessions.");
1303
1481
  }
1304
- if (!this.connection || this.startPromise) {
1305
- await this.start();
1482
+ const storeMemory = requestedMemoryStore(config.memory);
1483
+ if (config.cloud != null && storeMemory) {
1484
+ throw new Error("Memory stores are not supported for cloud sessions.");
1485
+ }
1486
+ const signal = await this.getSessionInitializationSignal();
1487
+ const sessionId = config.sessionId ?? (config.cloud != null ? void 0 : randomUUID());
1488
+ if (sessionId === void 0) {
1489
+ return this.createSessionInternal(config, signal);
1490
+ }
1491
+ const release = await this.acquireSessionInitialization(sessionId, signal);
1492
+ try {
1493
+ signal.throwIfAborted();
1494
+ return await this.createSessionInternal({ ...config, sessionId }, signal);
1495
+ } finally {
1496
+ release();
1497
+ }
1498
+ }
1499
+ invalidateSessionInitializations() {
1500
+ this.sessionInitializationGeneration++;
1501
+ this.sessionInitializationLifetime.abort(
1502
+ new Error("Client disconnected during session initialization")
1503
+ );
1504
+ }
1505
+ async getSessionInitializationSignal() {
1506
+ const generation = this.sessionInitializationGeneration;
1507
+ const starting = !this.connection || this.startPromise ? this.start() : void 0;
1508
+ await starting;
1509
+ if (generation !== this.sessionInitializationGeneration) {
1510
+ throw new Error("Client disconnected during session initialization");
1511
+ }
1512
+ const signal = this.sessionInitializationLifetime.signal;
1513
+ signal.throwIfAborted();
1514
+ return signal;
1515
+ }
1516
+ async runInitializationCallback(sessionId, callback) {
1517
+ const context = sessionId === void 0 ? void 0 : { sessionId, active: true };
1518
+ try {
1519
+ return await this.sessionInitializationContext.run(context, callback);
1520
+ } finally {
1521
+ if (context) context.active = false;
1522
+ }
1523
+ }
1524
+ async acquireSessionInitialization(sessionId, signal) {
1525
+ signal.throwIfAborted();
1526
+ const predecessors = [...this.sessionInitializations].filter(([id]) => sessionIdsOverlap(id, sessionId)).map(([, pending]) => pending);
1527
+ const previous = predecessors.length ? Promise.all(predecessors).then(() => {
1528
+ }) : void 0;
1529
+ const context = this.sessionInitializationContext.getStore();
1530
+ if (previous && context?.active && sessionIdsOverlap(context.sessionId, sessionId)) {
1531
+ throw new Error(
1532
+ `Cannot re-enter session initialization for ${sessionId} from its callback`
1533
+ );
1534
+ }
1535
+ let finish;
1536
+ const current = new Promise((resolve2) => {
1537
+ finish = resolve2;
1538
+ });
1539
+ const tail = previous ? previous.then(() => current) : current;
1540
+ this.sessionInitializations.set(sessionId, tail);
1541
+ void tail.then(() => {
1542
+ if (this.sessionInitializations.get(sessionId) === tail) {
1543
+ this.sessionInitializations.delete(sessionId);
1544
+ }
1545
+ });
1546
+ try {
1547
+ if (previous) {
1548
+ let cancel;
1549
+ try {
1550
+ await new Promise((resolve2, reject) => {
1551
+ cancel = () => reject(signal.reason);
1552
+ signal.addEventListener("abort", cancel, { once: true });
1553
+ void previous.then(resolve2);
1554
+ });
1555
+ } finally {
1556
+ signal.removeEventListener("abort", cancel);
1557
+ }
1558
+ }
1559
+ signal.throwIfAborted();
1560
+ const cleanupError = this.connection ? [...this.failedAutoModeRecoveryCleanups.get(this.connection) ?? []].find(
1561
+ ([id]) => sessionIdsOverlap(id, sessionId)
1562
+ )?.[1] : void 0;
1563
+ if (cleanupError) {
1564
+ throw new Error(
1565
+ `Cannot initialize session ${sessionId} after failed Auto recovery cleanup; stop and restart the client before retrying`,
1566
+ { cause: cleanupError }
1567
+ );
1568
+ }
1569
+ return finish;
1570
+ } catch (error) {
1571
+ finish();
1572
+ throw error;
1306
1573
  }
1574
+ }
1575
+ async createSessionInternal(config, signal) {
1576
+ signal.throwIfAborted();
1577
+ const connection = this.connection;
1578
+ const storeMemory = requestedMemoryStore(config.memory);
1307
1579
  const modeDefaults = this.configDefaultsForMode();
1308
1580
  config = { ...modeDefaults, ...config };
1309
1581
  config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
@@ -1325,19 +1597,11 @@ class CopilotClient {
1325
1597
  config.systemMessage
1326
1598
  );
1327
1599
  const initializeSession = (sessionId) => {
1328
- const s = new CopilotSession(
1329
- sessionId,
1330
- this.connection,
1331
- void 0,
1332
- this.onGetTraceContext,
1333
- {
1334
- mcpAuthHandler: config.onMcpAuthRequest,
1335
- managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
1336
- onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(
1337
- gitHubTokenProviderRegistrationId
1338
- )
1339
- }
1340
- );
1600
+ const s = new CopilotSession(sessionId, connection, void 0, this.onGetTraceContext, {
1601
+ mcpAuthHandler: config.onMcpAuthRequest,
1602
+ managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
1603
+ onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId)
1604
+ });
1341
1605
  s.registerTools(config.tools);
1342
1606
  s.registerCanvases(config.canvases);
1343
1607
  s.registerCommands(config.commands);
@@ -1363,6 +1627,7 @@ class CopilotClient {
1363
1627
  if (config.skillProvider) {
1364
1628
  s.registerSkillProvider(config.skillProvider);
1365
1629
  }
1630
+ s._registerMemoryStore(storeMemory);
1366
1631
  if (transformCallbacks) {
1367
1632
  s.registerTransformCallbacks(transformCallbacks);
1368
1633
  }
@@ -1370,12 +1635,14 @@ class CopilotClient {
1370
1635
  s.on(config.onEvent);
1371
1636
  }
1372
1637
  this.setupSessionFs(s, config);
1638
+ signal.throwIfAborted();
1373
1639
  this.sessions.set(sessionId, s);
1374
1640
  return s;
1375
1641
  };
1376
1642
  let session;
1377
1643
  let registeredId;
1378
1644
  let uninitializedCloudSessionId;
1645
+ let releaseCloudInitialization;
1379
1646
  if (localSessionId !== void 0) {
1380
1647
  try {
1381
1648
  session = initializeSession(localSessionId);
@@ -1388,14 +1655,20 @@ class CopilotClient {
1388
1655
  }
1389
1656
  }
1390
1657
  try {
1391
- const response = await this.connection.sendRequest("session.create", {
1392
- ...await getTraceContext(this.onGetTraceContext),
1658
+ const traceContext = await this.runInitializationCallback(
1659
+ localSessionId,
1660
+ () => getTraceContext(this.onGetTraceContext)
1661
+ );
1662
+ signal.throwIfAborted();
1663
+ const response = await connection.sendRequest("session.create", {
1664
+ ...traceContext,
1393
1665
  model: config.model,
1394
1666
  allowedModels: config.allowedModels,
1395
1667
  sessionId: localSessionId,
1396
1668
  name: config.name,
1397
1669
  clientName: config.clientName,
1398
1670
  reasoningEffort: config.reasoningEffort,
1671
+ allowMultipleToolCalls: config.allowMultipleToolCalls,
1399
1672
  reasoningSummary: config.reasoningSummary,
1400
1673
  isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
1401
1674
  contextTier: config.contextTier,
@@ -1446,6 +1719,7 @@ class CopilotClient {
1446
1719
  ...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
1447
1720
  requestExitPlanMode: !!config.onExitPlanModeRequest,
1448
1721
  requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
1722
+ ...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
1449
1723
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
1450
1724
  workingDirectory: config.workingDirectory,
1451
1725
  trustWorkingDirectory: config.trustWorkingDirectory,
@@ -1476,6 +1750,7 @@ class CopilotClient {
1476
1750
  enableSkills: config.enableSkills,
1477
1751
  skillDirectories: config.skillDirectories,
1478
1752
  ...config.skillProvider ? { hasSkillProvider: true } : {},
1753
+ ...storeMemory ? { hasMemoryStore: true } : {},
1479
1754
  agentDirectories: config.agentDirectories,
1480
1755
  hookDirectories: config.hookDirectories,
1481
1756
  pluginDirectories: config.pluginDirectories,
@@ -1484,7 +1759,7 @@ class CopilotClient {
1484
1759
  disabledMcpServers: config.disabledMcpServers,
1485
1760
  sandboxConfig: config.sandboxConfig,
1486
1761
  infiniteSessions: config.infiniteSessions,
1487
- memory: config.memory,
1762
+ memory: toWireMemoryConfiguration(config.memory),
1488
1763
  gitHubToken: config.gitHubToken,
1489
1764
  gitHubTokenProviderRegistrationId,
1490
1765
  cloud: config.cloud,
@@ -1494,12 +1769,14 @@ class CopilotClient {
1494
1769
  excludedManagedSettings: config.excludedManagedSettings,
1495
1770
  enforceManagedModelDefaults: config.enforceManagedModelDefaults,
1496
1771
  interactiveUser: config.interactiveUser,
1772
+ permissionMode: config.permissionMode,
1497
1773
  managedSettings: config.managedSettings
1498
1774
  });
1499
1775
  const {
1500
1776
  sessionId: returnedSessionId,
1501
1777
  workspacePath,
1502
- capabilities
1778
+ capabilities,
1779
+ permissionModeResult
1503
1780
  } = response;
1504
1781
  if (!returnedSessionId) {
1505
1782
  throw new Error("session.create response did not include a sessionId");
@@ -1511,24 +1788,37 @@ class CopilotClient {
1511
1788
  }
1512
1789
  if (session === void 0) {
1513
1790
  uninitializedCloudSessionId = returnedSessionId;
1791
+ }
1792
+ signal.throwIfAborted();
1793
+ if (session === void 0) {
1794
+ releaseCloudInitialization = await this.acquireSessionInitialization(
1795
+ returnedSessionId,
1796
+ signal
1797
+ );
1798
+ signal.throwIfAborted();
1514
1799
  session = initializeSession(returnedSessionId);
1515
- uninitializedCloudSessionId = void 0;
1516
1800
  registeredId = returnedSessionId;
1517
1801
  }
1518
1802
  this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId);
1803
+ session.setCapabilities(capabilities);
1804
+ if (config.autoModeRecovery) {
1805
+ await this.requireAutoModeRecovery(session, connection);
1806
+ }
1519
1807
  if (config.onMcpAuthRequest) {
1520
- await this.connection.sendRequest("session.eventLog.registerInterest", {
1808
+ await connection.sendRequest("session.eventLog.registerInterest", {
1521
1809
  sessionId: returnedSessionId,
1522
1810
  eventType: "mcp.oauth_required"
1523
1811
  });
1524
1812
  }
1525
1813
  session["_workspacePath"] = workspacePath;
1526
- session.setCapabilities(capabilities);
1814
+ session.setPermissionModeResult(permissionModeResult);
1527
1815
  await this.updateSessionOptionsForMode(session, config);
1816
+ signal.throwIfAborted();
1817
+ uninitializedCloudSessionId = void 0;
1528
1818
  this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
1529
1819
  } catch (e) {
1530
1820
  session?._markDisconnected();
1531
- if (registeredId !== void 0) {
1821
+ if (registeredId !== void 0 && this.sessions.get(registeredId) === session) {
1532
1822
  this.sessions.delete(registeredId);
1533
1823
  }
1534
1824
  if (gitHubTokenProviderRegistrationId !== void 0) {
@@ -1537,18 +1827,28 @@ class CopilotClient {
1537
1827
  if (uninitializedCloudSessionId !== void 0) {
1538
1828
  try {
1539
1829
  await withTimeout(
1540
- this.deleteSession(uninitializedCloudSessionId),
1830
+ this.requestSessionDeletion(connection, uninitializedCloudSessionId),
1541
1831
  CLOUD_SESSION_CLEANUP_TIMEOUT_MS,
1542
1832
  `session.delete timed out after ${CLOUD_SESSION_CLEANUP_TIMEOUT_MS}ms`
1543
1833
  );
1544
1834
  } catch (cleanupError) {
1545
- throw new AggregateError(
1835
+ const failure = new AggregateError(
1546
1836
  [e, cleanupError],
1547
- "Failed to initialize and delete cloud session"
1837
+ `Failed to initialize and delete cloud session ${uninitializedCloudSessionId}`
1548
1838
  );
1839
+ if (config.autoModeRecovery) {
1840
+ this.recordFailedAutoModeRecoveryCleanup(
1841
+ connection,
1842
+ uninitializedCloudSessionId,
1843
+ failure
1844
+ );
1845
+ }
1846
+ throw failure;
1549
1847
  }
1550
1848
  }
1551
1849
  throw e;
1850
+ } finally {
1851
+ releaseCloudInitialization?.();
1552
1852
  }
1553
1853
  for (const entry of this.hostHandoffs.values()) {
1554
1854
  if (entry.requestedSessionId === session.sessionId) {
@@ -1589,16 +1889,52 @@ class CopilotClient {
1589
1889
  return this.resumeSessionInternal(sessionId, config, contributions, extensionOptions);
1590
1890
  }
1591
1891
  async resumeSessionInternal(sessionId, config, contributions = {}, extensionOptions) {
1592
- const { workflows } = contributions;
1892
+ if (config.autoModeRecovery && typeof config.onAutoModeSwitchRequest !== "function") {
1893
+ throw new Error("autoModeRecovery requires onAutoModeSwitchRequest");
1894
+ }
1593
1895
  if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
1594
1896
  throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
1595
1897
  }
1596
- if (!this.connection || this.startPromise) {
1597
- await this.start();
1898
+ requestedMemoryStore(config.memory);
1899
+ const signal = await this.getSessionInitializationSignal();
1900
+ const release = await this.acquireSessionInitialization(sessionId, signal);
1901
+ try {
1902
+ signal.throwIfAborted();
1903
+ return await this.resumeSessionWithInitialization(
1904
+ sessionId,
1905
+ config,
1906
+ contributions,
1907
+ signal,
1908
+ extensionOptions
1909
+ );
1910
+ } finally {
1911
+ release();
1912
+ }
1913
+ }
1914
+ async resumeSessionWithInitialization(sessionId, config, contributions, signal, extensionOptions) {
1915
+ const { workflows } = contributions;
1916
+ const storeMemory = requestedMemoryStore(config.memory);
1917
+ signal.throwIfAborted();
1918
+ const connection = this.connection;
1919
+ const resolveAliases = config.autoModeRecovery === true && !this.sessionFsConfig;
1920
+ if (resolveAliases && isAbbreviatedUuidPrefix(sessionId)) {
1921
+ const resolved = await connection.sendRequest(
1922
+ "sessions.findByPrefix",
1923
+ { prefix: sessionId, configDir: config.configDirectory }
1924
+ );
1925
+ const canonicalId = resolved?.sessionId;
1926
+ if (typeof canonicalId !== "string" || canonicalId.length === 0) {
1927
+ throw new Error(`No unique session matches prefix ${sessionId}`);
1928
+ }
1929
+ if (!canonicalId.toLowerCase().startsWith(sessionId.toLowerCase())) {
1930
+ throw new Error(`Runtime returned an unrelated session ID for prefix ${sessionId}`);
1931
+ }
1932
+ sessionId = canonicalId;
1933
+ signal.throwIfAborted();
1598
1934
  }
1599
1935
  const session = new CopilotSession(
1600
1936
  sessionId,
1601
- this.connection,
1937
+ connection,
1602
1938
  void 0,
1603
1939
  this.onGetTraceContext,
1604
1940
  {
@@ -1637,6 +1973,7 @@ class CopilotClient {
1637
1973
  if (config.skillProvider) {
1638
1974
  session.registerSkillProvider(config.skillProvider);
1639
1975
  }
1976
+ session._registerMemoryStore(storeMemory);
1640
1977
  const modeDefaults = this.configDefaultsForMode();
1641
1978
  config = { ...modeDefaults, ...config };
1642
1979
  config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
@@ -1652,8 +1989,12 @@ class CopilotClient {
1652
1989
  }
1653
1990
  const toolFilterOptions = this.resolveToolFilterOptions(config);
1654
1991
  this.setupSessionFs(session, config);
1655
- const replacedSession = this.sessions.get(sessionId);
1656
- this.sessions.set(sessionId, session);
1992
+ signal.throwIfAborted();
1993
+ const requestedSessionId = sessionId;
1994
+ let replacedRequestedSession;
1995
+ let replacedSession;
1996
+ let releaseCanonicalInitialization;
1997
+ let keepResumedSessionOnRecoveryRejection = false;
1657
1998
  const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider(
1658
1999
  config.gitHubTokenProvider,
1659
2000
  sessionId
@@ -1664,14 +2005,29 @@ class CopilotClient {
1664
2005
  );
1665
2006
  }
1666
2007
  try {
1667
- const response = await this.connection.sendRequest("session.resume", {
2008
+ const traceContext = await this.runInitializationCallback(
2009
+ sessionId,
2010
+ () => getTraceContext(this.onGetTraceContext)
2011
+ );
2012
+ signal.throwIfAborted();
2013
+ replacedRequestedSession = this.sessions.get(sessionId);
2014
+ replacedSession = replacedRequestedSession;
2015
+ this.sessions.set(sessionId, session);
2016
+ this.pendingSessionResumes.set(requestedSessionId, {
2017
+ session,
2018
+ connection,
2019
+ gitHubTokenProviderRegistrationId,
2020
+ resolveAliases
2021
+ });
2022
+ const response = await connection.sendRequest("session.resume", {
1668
2023
  allowedModels: config.allowedModels,
1669
- ...await getTraceContext(this.onGetTraceContext),
2024
+ ...traceContext,
1670
2025
  sessionId,
1671
2026
  allowTranscriptRecovery: config.allowTranscriptRecovery,
1672
2027
  clientName: config.clientName,
1673
2028
  model: config.model,
1674
2029
  reasoningEffort: config.reasoningEffort,
2030
+ allowMultipleToolCalls: config.allowMultipleToolCalls,
1675
2031
  reasoningSummary: config.reasoningSummary,
1676
2032
  isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
1677
2033
  contextTier: config.contextTier,
@@ -1723,6 +2079,7 @@ class CopilotClient {
1723
2079
  ...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
1724
2080
  requestExitPlanMode: !!config.onExitPlanModeRequest,
1725
2081
  requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
2082
+ ...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
1726
2083
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
1727
2084
  workingDirectory: config.workingDirectory,
1728
2085
  additionalDirectories: config.additionalDirectories,
@@ -1751,6 +2108,7 @@ class CopilotClient {
1751
2108
  agent: config.agent,
1752
2109
  skillDirectories: config.skillDirectories,
1753
2110
  ...config.skillProvider ? { hasSkillProvider: true } : {},
2111
+ ...storeMemory ? { hasMemoryStore: true } : {},
1754
2112
  agentDirectories: config.agentDirectories,
1755
2113
  hookDirectories: config.hookDirectories,
1756
2114
  pluginDirectories: config.pluginDirectories,
@@ -1759,7 +2117,7 @@ class CopilotClient {
1759
2117
  disabledMcpServers: config.disabledMcpServers,
1760
2118
  sandboxConfig: config.sandboxConfig,
1761
2119
  infiniteSessions: config.infiniteSessions,
1762
- memory: config.memory,
2120
+ memory: toWireMemoryConfiguration(config.memory),
1763
2121
  disableResume: config.suppressResumeEvent,
1764
2122
  continuePendingWork: config.continuePendingWork,
1765
2123
  gitHubToken: config.gitHubToken,
@@ -1771,11 +2129,34 @@ class CopilotClient {
1771
2129
  excludedManagedSettings: config.excludedManagedSettings,
1772
2130
  enforceManagedModelDefaults: config.enforceManagedModelDefaults,
1773
2131
  interactiveUser: config.interactiveUser,
2132
+ permissionMode: config.permissionMode,
1774
2133
  managedSettings: config.managedSettings,
1775
2134
  ...extensionOptions?.requestedEnvironmentVariables ? {
1776
2135
  requestedEnvironmentVariables: extensionOptions.requestedEnvironmentVariables
1777
2136
  } : {}
1778
2137
  });
2138
+ const { sessionId: returnedSessionId } = response;
2139
+ if (returnedSessionId && returnedSessionId !== sessionId) {
2140
+ if (!sessionIdsOverlap(requestedSessionId, returnedSessionId)) {
2141
+ releaseCanonicalInitialization = await this.acquireSessionInitialization(
2142
+ returnedSessionId,
2143
+ signal
2144
+ );
2145
+ }
2146
+ replacedSession = this.sessions.get(returnedSessionId);
2147
+ if (this.sessions.get(requestedSessionId) === session) {
2148
+ if (replacedRequestedSession) {
2149
+ this.sessions.set(requestedSessionId, replacedRequestedSession);
2150
+ } else {
2151
+ this.sessions.delete(requestedSessionId);
2152
+ }
2153
+ }
2154
+ session._setSessionId(returnedSessionId);
2155
+ sessionId = returnedSessionId;
2156
+ this.sessions.set(sessionId, session);
2157
+ this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, sessionId);
2158
+ }
2159
+ signal.throwIfAborted();
1779
2160
  if (extensionOptions?.requestedEnvironmentVariables) {
1780
2161
  const requested = new Set(extensionOptions.requestedEnvironmentVariables);
1781
2162
  const { grantedEnvironmentVariables } = response;
@@ -1785,32 +2166,59 @@ class CopilotClient {
1785
2166
  }
1786
2167
  }
1787
2168
  }
1788
- const { workspacePath, capabilities, openCanvases, transcriptRecovery } = response;
2169
+ const {
2170
+ workspacePath,
2171
+ capabilities,
2172
+ openCanvases,
2173
+ transcriptRecovery,
2174
+ permissionModeResult
2175
+ } = response;
1789
2176
  session["_workspacePath"] = workspacePath;
1790
2177
  session.setCapabilities(capabilities);
2178
+ if (config.autoModeRecovery) {
2179
+ keepResumedSessionOnRecoveryRejection = replacedSession !== void 0 && session.capabilities.ui?.autoModeRecovery !== true;
2180
+ await this.requireAutoModeRecovery(
2181
+ session,
2182
+ connection,
2183
+ replacedSession === void 0
2184
+ );
2185
+ }
1791
2186
  session.setOpenCanvases(openCanvases ?? []);
1792
2187
  session.setTranscriptRecovery(transcriptRecovery);
2188
+ session.setPermissionModeResult(permissionModeResult);
1793
2189
  if (config.onMcpAuthRequest) {
1794
- await this.connection.sendRequest("session.eventLog.registerInterest", {
2190
+ await connection.sendRequest("session.eventLog.registerInterest", {
1795
2191
  sessionId,
1796
2192
  eventType: "mcp.oauth_required"
1797
2193
  });
1798
2194
  }
1799
2195
  await this.updateSessionOptionsForMode(session, config);
2196
+ signal.throwIfAborted();
1800
2197
  this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
1801
2198
  } catch (e) {
1802
- session._markDisconnected();
2199
+ if (!keepResumedSessionOnRecoveryRejection) {
2200
+ session._markDisconnected();
2201
+ }
1803
2202
  if (this.sessions.get(sessionId) === session) {
1804
- if (replacedSession) {
2203
+ if (replacedSession && !keepResumedSessionOnRecoveryRejection) {
1805
2204
  this.sessions.set(sessionId, replacedSession);
1806
- } else {
2205
+ } else if (!keepResumedSessionOnRecoveryRejection) {
1807
2206
  this.sessions.delete(sessionId);
1808
2207
  }
1809
2208
  }
1810
2209
  if (gitHubTokenProviderRegistrationId !== void 0) {
1811
- this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
2210
+ if (keepResumedSessionOnRecoveryRejection) {
2211
+ this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
2212
+ } else {
2213
+ this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
2214
+ }
1812
2215
  }
1813
2216
  throw e;
2217
+ } finally {
2218
+ if (this.pendingSessionResumes.get(requestedSessionId)?.session === session) {
2219
+ this.pendingSessionResumes.delete(requestedSessionId);
2220
+ }
2221
+ releaseCanonicalInitialization?.();
1814
2222
  }
1815
2223
  for (const entry of this.hostHandoffs.values()) {
1816
2224
  if (entry.requestedSessionId === session.sessionId) {
@@ -1819,6 +2227,47 @@ class CopilotClient {
1819
2227
  }
1820
2228
  return session;
1821
2229
  }
2230
+ async requireAutoModeRecovery(session, connection, detachOnFailure = true) {
2231
+ if (session.capabilities.ui?.autoModeRecovery === true) {
2232
+ return;
2233
+ }
2234
+ const error = new Error("The runtime does not support autoModeRecovery");
2235
+ if (!detachOnFailure) {
2236
+ throw error;
2237
+ }
2238
+ const cancellation = new CancellationTokenSource();
2239
+ try {
2240
+ const response = await withTimeout(
2241
+ connection.sendRequest(
2242
+ "session.detach",
2243
+ { sessionId: session.sessionId },
2244
+ cancellation.token
2245
+ ),
2246
+ AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS,
2247
+ `session.detach timed out after ${AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS}ms`
2248
+ );
2249
+ if (!response.success) {
2250
+ throw new Error(`Failed to detach session: ${response.error || "Unknown error"}`);
2251
+ }
2252
+ } catch (cleanupError) {
2253
+ const failure = new AggregateError([error, cleanupError], error.message);
2254
+ this.recordFailedAutoModeRecoveryCleanup(connection, session.sessionId, failure);
2255
+ throw failure;
2256
+ } finally {
2257
+ cancellation.cancel();
2258
+ cancellation.dispose();
2259
+ session._markDisconnected();
2260
+ }
2261
+ throw error;
2262
+ }
2263
+ recordFailedAutoModeRecoveryCleanup(connection, sessionId, failure) {
2264
+ let failed = this.failedAutoModeRecoveryCleanups.get(connection);
2265
+ if (!failed) {
2266
+ failed = /* @__PURE__ */ new Map();
2267
+ this.failedAutoModeRecoveryCleanups.set(connection, failed);
2268
+ }
2269
+ failed.set(sessionId, failure);
2270
+ }
1822
2271
  /**
1823
2272
  * Start a complete AHP listener hosted in-process by this runtime.
1824
2273
  *
@@ -1930,6 +2379,12 @@ class CopilotClient {
1930
2379
  }
1931
2380
  }
1932
2381
  }
2382
+ abortPendingMemoryStores() {
2383
+ for (const controller of this.pendingMemoryStores) {
2384
+ controller.abort();
2385
+ }
2386
+ this.pendingMemoryStores.clear();
2387
+ }
1933
2388
  disconnectHosts() {
1934
2389
  for (const hostId of /* @__PURE__ */ new Set([
1935
2390
  ...this.hostExitCallbacks.keys(),
@@ -2261,16 +2716,34 @@ class CopilotClient {
2261
2716
  if (!this.connection) {
2262
2717
  throw new Error("Client not connected");
2263
2718
  }
2264
- const response = await this.connection.sendRequest("session.delete", {
2265
- sessionId
2266
- });
2719
+ const signal = await this.getSessionInitializationSignal();
2720
+ const release = await this.acquireSessionInitialization(sessionId, signal);
2721
+ try {
2722
+ signal.throwIfAborted();
2723
+ const connection = this.connection;
2724
+ const session = this.sessions.get(sessionId);
2725
+ await this.requestSessionDeletion(connection, sessionId);
2726
+ signal.throwIfAborted();
2727
+ if (this.sessions.get(sessionId) === session) {
2728
+ this.sessions.delete(sessionId);
2729
+ session?._clearMemoryStore();
2730
+ session?._runOnDisconnected();
2731
+ }
2732
+ } finally {
2733
+ release();
2734
+ }
2735
+ }
2736
+ async requestSessionDeletion(connection, sessionId) {
2737
+ const response = await connection.sendRequest(
2738
+ "session.delete",
2739
+ {
2740
+ sessionId
2741
+ }
2742
+ );
2267
2743
  const { success, error } = response;
2268
2744
  if (!success) {
2269
2745
  throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
2270
2746
  }
2271
- const session = this.sessions.get(sessionId);
2272
- this.sessions.delete(sessionId);
2273
- session?._runOnDisconnected();
2274
2747
  }
2275
2748
  /**
2276
2749
  * List all available sessions.
@@ -2727,7 +3200,8 @@ stderr: ${this.stderrBuffer.trim().slice(-8192) || "(empty)"}`
2727
3200
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
2728
3201
  this.connection = createMessageConnection(
2729
3202
  new StreamMessageReader(this.ffiHost.receiveStream),
2730
- this.messageWriter
3203
+ this.messageWriter,
3204
+ this.sessionInitializationContext
2731
3205
  );
2732
3206
  this.attachConnectionHandlers();
2733
3207
  this.connection.listen();
@@ -2787,7 +3261,8 @@ stderr: ${stderrOutput}` : ""}`
2787
3261
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.cliProcess.stdin);
2788
3262
  this.connection = createMessageConnection(
2789
3263
  new StreamMessageReader(this.cliProcess.stdout),
2790
- this.messageWriter
3264
+ this.messageWriter,
3265
+ this.sessionInitializationContext
2791
3266
  );
2792
3267
  this.attachConnectionHandlers();
2793
3268
  this.connection.listen();
@@ -2802,7 +3277,8 @@ stderr: ${stderrOutput}` : ""}`
2802
3277
  this.messageWriter = new TeardownResilientStreamMessageWriter(process.stdout);
2803
3278
  this.connection = createMessageConnection(
2804
3279
  new StreamMessageReader(process.stdin),
2805
- this.messageWriter
3280
+ this.messageWriter,
3281
+ this.sessionInitializationContext
2806
3282
  );
2807
3283
  this.attachConnectionHandlers();
2808
3284
  this.connection.listen();
@@ -2825,7 +3301,8 @@ stderr: ${stderrOutput}` : ""}`
2825
3301
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.socket);
2826
3302
  this.connection = createMessageConnection(
2827
3303
  new StreamMessageReader(this.socket),
2828
- this.messageWriter
3304
+ this.messageWriter,
3305
+ this.sessionInitializationContext
2829
3306
  );
2830
3307
  this.attachConnectionHandlers();
2831
3308
  this.connection.listen();
@@ -2863,9 +3340,8 @@ stderr: ${stderrOutput}` : ""}`
2863
3340
  "systemMessage.transform",
2864
3341
  async (params) => await this.handleSystemMessageTransform(params)
2865
3342
  );
2866
- const sessions = this.sessions;
2867
3343
  registerClientSessionApiHandlers(this.connection, (sessionId) => {
2868
- const session = sessions.get(sessionId);
3344
+ const session = this.resolveSessionCallback(sessionId);
2869
3345
  if (!session) throw new Error(`No session found for sessionId: ${sessionId}`);
2870
3346
  return session.clientSessionApis;
2871
3347
  });
@@ -2883,12 +3359,20 @@ stderr: ${stderrOutput}` : ""}`
2883
3359
  (params, token) => confirmation.confirm(params, token)
2884
3360
  );
2885
3361
  }
2886
- if (this.kerberosAuth) {
2887
- const { callback } = this.kerberosAuth;
3362
+ const resolveProxy = this.network?.resolveProxy;
3363
+ if (resolveProxy) {
3364
+ const connection2 = this.connection;
3365
+ this.connection.onRequest(
3366
+ "network.resolveProxy",
3367
+ (params, token) => resolveClientProxy(connection2, resolveProxy, params, token)
3368
+ );
3369
+ }
3370
+ const authenticateNegotiate = this.network?.authenticateNegotiate;
3371
+ if (authenticateNegotiate) {
2888
3372
  const connection2 = this.connection;
2889
3373
  this.connection.onRequest(
2890
- "kerberosAuth.acquire",
2891
- (params, token) => acquireKerberosToken(connection2, callback, params, token)
3374
+ "network.authenticateNegotiate",
3375
+ (params, token) => authenticateClientNegotiate(connection2, authenticateNegotiate, params, token)
2892
3376
  );
2893
3377
  }
2894
3378
  this.connection.onRequest(
@@ -2959,11 +3443,29 @@ stderr: ${stderrOutput}` : ""}`
2959
3443
  this.connection.onRequest(
2960
3444
  "sessionManagement.create",
2961
3445
  async (params, token) => {
2962
- const session = this.sessions.get(params.sessionId);
3446
+ const session = this.resolveSessionCallback(params.sessionId);
2963
3447
  if (!session) throw new Error("Session creation owner is unavailable");
2964
3448
  return await session._handleSessionManagementCreate(params, token);
2965
3449
  }
2966
3450
  );
3451
+ this.connection.onRequest(
3452
+ "memoryStore.store",
3453
+ async (params, token) => {
3454
+ const session = this.sessions.get(params?.sessionId);
3455
+ if (!session) throw new Error("Memory store owner is unavailable");
3456
+ const controller = new AbortController();
3457
+ this.pendingMemoryStores.add(controller);
3458
+ const subscription = token.onCancellationRequested(() => controller.abort());
3459
+ if (token.isCancellationRequested) controller.abort();
3460
+ try {
3461
+ await session._handleMemoryStore(params, controller.signal);
3462
+ } finally {
3463
+ this.pendingMemoryStores.delete(controller);
3464
+ subscription.dispose();
3465
+ }
3466
+ return null;
3467
+ }
3468
+ );
2967
3469
  this.connection.onRequest(
2968
3470
  "skillProvider.read",
2969
3471
  async (params, token) => {
@@ -2995,6 +3497,7 @@ stderr: ${stderrOutput}` : ""}`
2995
3497
  this.sessions.clear();
2996
3498
  this.githubTokenProviders.clear();
2997
3499
  this.requestAdapter?.cancelPending();
3500
+ this.abortPendingMemoryStores();
2998
3501
  connection.dispose();
2999
3502
  this.socket?.destroy();
3000
3503
  this.connection = null;
@@ -3014,6 +3517,7 @@ stderr: ${stderrOutput}` : ""}`
3014
3517
  }
3015
3518
  if (disconnecting) return;
3016
3519
  disconnecting = true;
3520
+ this.invalidateSessionInitializations();
3017
3521
  this.connectionClosed = true;
3018
3522
  if (messageWriter) messageWriter.suppressWriteErrors = true;
3019
3523
  void connection.drain().then(markDisconnected);
@@ -3045,11 +3549,34 @@ stderr: ${stderrOutput}` : ""}`
3045
3549
  }
3046
3550
  });
3047
3551
  }
3552
+ resolveSessionCallback(sessionId) {
3553
+ let selected;
3554
+ let selectedPrefixLength = -1;
3555
+ for (const [prefix, pending] of this.pendingSessionResumes) {
3556
+ if (pending.connection !== this.connection || !pending.resolveAliases && prefix !== sessionId || prefix.length <= selectedPrefixLength || !(sessionIdsOverlap(prefix, sessionId) && sessionId.length >= prefix.length))
3557
+ continue;
3558
+ const knownOwners = new Set(
3559
+ [...this.sessions.values()].filter(
3560
+ (owner) => owner !== pending.session && sessionIdsOverlap(prefix, owner.sessionId) && owner.sessionId.length >= prefix.length
3561
+ )
3562
+ );
3563
+ if (knownOwners.size > 1) continue;
3564
+ if (pending.session.sessionId !== sessionId) {
3565
+ pending.session._setSessionId(sessionId);
3566
+ }
3567
+ this.assignGitHubTokenProvider(pending.gitHubTokenProviderRegistrationId, sessionId);
3568
+ selected = pending.session;
3569
+ selectedPrefixLength = prefix.length;
3570
+ }
3571
+ return selected ?? this.sessions.get(sessionId);
3572
+ }
3048
3573
  handleSessionEventNotification(notification) {
3049
3574
  if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) {
3050
3575
  return;
3051
3576
  }
3052
- const session = this.sessions.get(notification.sessionId);
3577
+ const session = this.resolveSessionCallback(
3578
+ notification.sessionId
3579
+ );
3053
3580
  const event = notification.event;
3054
3581
  if (session) {
3055
3582
  session._dispatchEvent(event);
@@ -3093,7 +3620,7 @@ stderr: ${stderrOutput}` : ""}`
3093
3620
  if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
3094
3621
  throw new Error("Invalid user input request payload");
3095
3622
  }
3096
- const session = this.sessions.get(params.sessionId);
3623
+ const session = this.resolveSessionCallback(params.sessionId);
3097
3624
  if (!session) {
3098
3625
  throw new Error(`Session not found: ${params.sessionId}`);
3099
3626
  }
@@ -3109,7 +3636,7 @@ stderr: ${stderrOutput}` : ""}`
3109
3636
  if (!params || typeof params.sessionId !== "string" || typeof params.summary !== "string" || !Array.isArray(params.actions) || typeof params.recommendedAction !== "string") {
3110
3637
  throw new Error("Invalid exit plan mode request payload");
3111
3638
  }
3112
- const session = this.sessions.get(params.sessionId);
3639
+ const session = this.resolveSessionCallback(params.sessionId);
3113
3640
  if (!session) {
3114
3641
  throw new Error(`Session not found: ${params.sessionId}`);
3115
3642
  }
@@ -3125,13 +3652,14 @@ stderr: ${stderrOutput}` : ""}`
3125
3652
  if (!params || typeof params.sessionId !== "string") {
3126
3653
  throw new Error("Invalid auto mode switch request payload");
3127
3654
  }
3128
- const session = this.sessions.get(params.sessionId);
3655
+ const session = this.resolveSessionCallback(params.sessionId);
3129
3656
  if (!session) {
3130
3657
  throw new Error(`Session not found: ${params.sessionId}`);
3131
3658
  }
3132
3659
  const response = await session._handleAutoModeSwitchRequest({
3133
3660
  errorCode: params.errorCode,
3134
- retryAfterSeconds: params.retryAfterSeconds
3661
+ retryAfterSeconds: params.retryAfterSeconds,
3662
+ ...params.autoModeRecovery === void 0 ? {} : { autoModeRecovery: params.autoModeRecovery }
3135
3663
  });
3136
3664
  return { response };
3137
3665
  }
@@ -3139,7 +3667,7 @@ stderr: ${stderrOutput}` : ""}`
3139
3667
  if (!params || typeof params.sessionId !== "string") {
3140
3668
  throw new Error("Invalid skillProvider payload");
3141
3669
  }
3142
- const session = this.sessions.get(params.sessionId);
3670
+ const session = this.resolveSessionCallback(params.sessionId);
3143
3671
  if (!session) {
3144
3672
  throw new Error(`Session not found: ${params.sessionId}`);
3145
3673
  }
@@ -3149,7 +3677,7 @@ stderr: ${stderrOutput}` : ""}`
3149
3677
  if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
3150
3678
  throw new Error("Invalid hooks invoke payload");
3151
3679
  }
3152
- const session = this.sessions.get(params.sessionId);
3680
+ const session = this.resolveSessionCallback(params.sessionId);
3153
3681
  if (!session) {
3154
3682
  throw new Error(`Session not found: ${params.sessionId}`);
3155
3683
  }
@@ -3160,7 +3688,7 @@ stderr: ${stderrOutput}` : ""}`
3160
3688
  if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
3161
3689
  throw new Error("Invalid systemMessage.transform payload");
3162
3690
  }
3163
- const session = this.sessions.get(params.sessionId);
3691
+ const session = this.resolveSessionCallback(params.sessionId);
3164
3692
  if (!session) {
3165
3693
  throw new Error(`Session not found: ${params.sessionId}`);
3166
3694
  }