@github/copilot-sdk 1.0.29 → 1.0.30-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -7
- package/dist/cjs/cliVersion.js +1 -1
- package/dist/cjs/client.js +651 -96
- package/dist/cjs/decisions.js +139 -0
- package/dist/cjs/generated/rpc.js +164 -14
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/session.js +113 -9
- package/dist/cliVersion.d.ts +1 -1
- package/dist/cliVersion.js +1 -1
- package/dist/client.d.ts +38 -3
- package/dist/client.js +653 -96
- package/dist/decisions.d.ts +65 -0
- package/dist/decisions.js +113 -0
- package/dist/generated/rpc.d.ts +1432 -130
- package/dist/generated/rpc.js +164 -14
- package/dist/generated/session-events.d.ts +213 -19
- package/dist/index.d.ts +4 -2
- package/dist/index.js +8 -0
- package/dist/session.d.ts +12 -2
- package/dist/session.js +113 -9
- package/dist/types.d.ts +143 -28
- package/docs/agent-author.md +1 -1
- package/docs/examples.md +77 -17
- package/package.json +14 -13
package/dist/cjs/client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
166
|
-
const ended = () => token.isCancellationRequested ? new import_node.ResponseError(-32800,
|
|
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(() =>
|
|
182
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
690
|
+
* Supports raw TCP addresses: "host:port", "[ipv6]:port", "http://host:port", or just "port"
|
|
582
691
|
*/
|
|
583
692
|
parseCliUrl(url) {
|
|
584
|
-
|
|
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.
|
|
784
|
-
const {
|
|
785
|
-
await this.rpc.
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
-
|
|
951
|
+
if (!signal.aborted) {
|
|
952
|
+
this.invalidateSessionInitializations();
|
|
953
|
+
}
|
|
954
|
+
await this.doForceStop();
|
|
818
955
|
this.state = "error";
|
|
819
956
|
throw startupError;
|
|
820
957
|
}
|
|
@@ -850,6 +987,21 @@ class CopilotClient {
|
|
|
850
987
|
* ```
|
|
851
988
|
*/
|
|
852
989
|
async stop() {
|
|
990
|
+
if (this.stopPromise) {
|
|
991
|
+
return this.stopPromise;
|
|
992
|
+
}
|
|
993
|
+
if (!this.sessionInitializationLifetime.signal.aborted) {
|
|
994
|
+
this.shutdownCanceledStartup = this.startPromise;
|
|
995
|
+
}
|
|
996
|
+
this.invalidateSessionInitializations();
|
|
997
|
+
this.stopPromise = this.doStop(this.startPromise);
|
|
998
|
+
try {
|
|
999
|
+
return await this.stopPromise;
|
|
1000
|
+
} finally {
|
|
1001
|
+
this.stopPromise = null;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
async doStop(startup) {
|
|
853
1005
|
const errors = [];
|
|
854
1006
|
const activeSessions = [...this.sessions.values()];
|
|
855
1007
|
if (this.connectionConfig.kind === "inprocess") {
|
|
@@ -903,12 +1055,13 @@ class CopilotClient {
|
|
|
903
1055
|
}
|
|
904
1056
|
}
|
|
905
1057
|
clearTimeout(detachDeadlineTimer);
|
|
906
|
-
for (const session of activeSessions) {
|
|
1058
|
+
for (const session of /* @__PURE__ */ new Set([...activeSessions, ...this.sessions.values()])) {
|
|
907
1059
|
session._markDisconnected();
|
|
908
1060
|
}
|
|
909
1061
|
this.sessions.clear();
|
|
910
1062
|
this.githubTokenProviders.clear();
|
|
911
1063
|
this.requestAdapter?.cancelPending();
|
|
1064
|
+
this.abortPendingMemoryStores();
|
|
912
1065
|
if (this.connection && !this.connectionClosed && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
|
|
913
1066
|
const runtimeShutdownStart = Date.now();
|
|
914
1067
|
const shutdownPromise = this.rpc.runtime.shutdown();
|
|
@@ -1044,6 +1197,9 @@ class CopilotClient {
|
|
|
1044
1197
|
clearTimeout(this.cliStartTimeout);
|
|
1045
1198
|
this.cliStartTimeout = null;
|
|
1046
1199
|
}
|
|
1200
|
+
if (startup) {
|
|
1201
|
+
await Promise.allSettled([startup]);
|
|
1202
|
+
}
|
|
1047
1203
|
this.state = "disconnected";
|
|
1048
1204
|
this.runtimePort = null;
|
|
1049
1205
|
this.stderrBuffer = "";
|
|
@@ -1075,7 +1231,8 @@ class CopilotClient {
|
|
|
1075
1231
|
* - Sends SIGKILL to the CLI process (if spawned by this client)
|
|
1076
1232
|
* - Waits for the spawned CLI process to exit
|
|
1077
1233
|
*
|
|
1078
|
-
* @returns
|
|
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
|
-
|
|
1323
|
-
|
|
1498
|
+
const storeMemory = requestedMemoryStore(config.memory);
|
|
1499
|
+
if (config.cloud != null && storeMemory) {
|
|
1500
|
+
throw new Error("Memory stores are not supported for cloud sessions.");
|
|
1501
|
+
}
|
|
1502
|
+
const signal = await this.getSessionInitializationSignal();
|
|
1503
|
+
const sessionId = config.sessionId ?? (config.cloud != null ? void 0 : (0, import_node_crypto.randomUUID)());
|
|
1504
|
+
if (sessionId === void 0) {
|
|
1505
|
+
return this.createSessionInternal(config, signal);
|
|
1506
|
+
}
|
|
1507
|
+
const release = await this.acquireSessionInitialization(sessionId, signal);
|
|
1508
|
+
try {
|
|
1509
|
+
signal.throwIfAborted();
|
|
1510
|
+
return await this.createSessionInternal({ ...config, sessionId }, signal);
|
|
1511
|
+
} finally {
|
|
1512
|
+
release();
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
invalidateSessionInitializations() {
|
|
1516
|
+
this.sessionInitializationGeneration++;
|
|
1517
|
+
this.sessionInitializationLifetime.abort(
|
|
1518
|
+
new Error("Client disconnected during session initialization")
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
async getSessionInitializationSignal() {
|
|
1522
|
+
const generation = this.sessionInitializationGeneration;
|
|
1523
|
+
const starting = !this.connection || this.startPromise ? this.start() : void 0;
|
|
1524
|
+
await starting;
|
|
1525
|
+
if (generation !== this.sessionInitializationGeneration) {
|
|
1526
|
+
throw new Error("Client disconnected during session initialization");
|
|
1527
|
+
}
|
|
1528
|
+
const signal = this.sessionInitializationLifetime.signal;
|
|
1529
|
+
signal.throwIfAborted();
|
|
1530
|
+
return signal;
|
|
1531
|
+
}
|
|
1532
|
+
async runInitializationCallback(sessionId, callback) {
|
|
1533
|
+
const context = sessionId === void 0 ? void 0 : { sessionId, active: true };
|
|
1534
|
+
try {
|
|
1535
|
+
return await this.sessionInitializationContext.run(context, callback);
|
|
1536
|
+
} finally {
|
|
1537
|
+
if (context) context.active = false;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
async acquireSessionInitialization(sessionId, signal) {
|
|
1541
|
+
signal.throwIfAborted();
|
|
1542
|
+
const predecessors = [...this.sessionInitializations].filter(([id]) => sessionIdsOverlap(id, sessionId)).map(([, pending]) => pending);
|
|
1543
|
+
const previous = predecessors.length ? Promise.all(predecessors).then(() => {
|
|
1544
|
+
}) : void 0;
|
|
1545
|
+
const context = this.sessionInitializationContext.getStore();
|
|
1546
|
+
if (previous && context?.active && sessionIdsOverlap(context.sessionId, sessionId)) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
`Cannot re-enter session initialization for ${sessionId} from its callback`
|
|
1549
|
+
);
|
|
1324
1550
|
}
|
|
1551
|
+
let finish;
|
|
1552
|
+
const current = new Promise((resolve2) => {
|
|
1553
|
+
finish = resolve2;
|
|
1554
|
+
});
|
|
1555
|
+
const tail = previous ? previous.then(() => current) : current;
|
|
1556
|
+
this.sessionInitializations.set(sessionId, tail);
|
|
1557
|
+
void tail.then(() => {
|
|
1558
|
+
if (this.sessionInitializations.get(sessionId) === tail) {
|
|
1559
|
+
this.sessionInitializations.delete(sessionId);
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
try {
|
|
1563
|
+
if (previous) {
|
|
1564
|
+
let cancel;
|
|
1565
|
+
try {
|
|
1566
|
+
await new Promise((resolve2, reject) => {
|
|
1567
|
+
cancel = () => reject(signal.reason);
|
|
1568
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
1569
|
+
void previous.then(resolve2);
|
|
1570
|
+
});
|
|
1571
|
+
} finally {
|
|
1572
|
+
signal.removeEventListener("abort", cancel);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
signal.throwIfAborted();
|
|
1576
|
+
const cleanupError = this.connection ? [...this.failedAutoModeRecoveryCleanups.get(this.connection) ?? []].find(
|
|
1577
|
+
([id]) => sessionIdsOverlap(id, sessionId)
|
|
1578
|
+
)?.[1] : void 0;
|
|
1579
|
+
if (cleanupError) {
|
|
1580
|
+
throw new Error(
|
|
1581
|
+
`Cannot initialize session ${sessionId} after failed Auto recovery cleanup; stop and restart the client before retrying`,
|
|
1582
|
+
{ cause: cleanupError }
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
return finish;
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
finish();
|
|
1588
|
+
throw error;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
async createSessionInternal(config, signal) {
|
|
1592
|
+
signal.throwIfAborted();
|
|
1593
|
+
const connection = this.connection;
|
|
1594
|
+
const storeMemory = requestedMemoryStore(config.memory);
|
|
1325
1595
|
const modeDefaults = this.configDefaultsForMode();
|
|
1326
1596
|
config = { ...modeDefaults, ...config };
|
|
1327
1597
|
config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
|
|
@@ -1342,20 +1612,13 @@ class CopilotClient {
|
|
|
1342
1612
|
const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
|
|
1343
1613
|
config.systemMessage
|
|
1344
1614
|
);
|
|
1615
|
+
let replacedSession;
|
|
1345
1616
|
const initializeSession = (sessionId) => {
|
|
1346
|
-
const s = new import_session.CopilotSession(
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
void 0
|
|
1350
|
-
|
|
1351
|
-
{
|
|
1352
|
-
mcpAuthHandler: config.onMcpAuthRequest,
|
|
1353
|
-
managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
|
|
1354
|
-
onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(
|
|
1355
|
-
gitHubTokenProviderRegistrationId
|
|
1356
|
-
)
|
|
1357
|
-
}
|
|
1358
|
-
);
|
|
1617
|
+
const s = new import_session.CopilotSession(sessionId, connection, void 0, this.onGetTraceContext, {
|
|
1618
|
+
mcpAuthHandler: config.onMcpAuthRequest,
|
|
1619
|
+
managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
|
|
1620
|
+
onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId)
|
|
1621
|
+
});
|
|
1359
1622
|
s.registerTools(config.tools);
|
|
1360
1623
|
s.registerCanvases(config.canvases);
|
|
1361
1624
|
s.registerCommands(config.commands);
|
|
@@ -1381,6 +1644,7 @@ class CopilotClient {
|
|
|
1381
1644
|
if (config.skillProvider) {
|
|
1382
1645
|
s.registerSkillProvider(config.skillProvider);
|
|
1383
1646
|
}
|
|
1647
|
+
s._registerMemoryStore(storeMemory);
|
|
1384
1648
|
if (transformCallbacks) {
|
|
1385
1649
|
s.registerTransformCallbacks(transformCallbacks);
|
|
1386
1650
|
}
|
|
@@ -1388,12 +1652,15 @@ class CopilotClient {
|
|
|
1388
1652
|
s.on(config.onEvent);
|
|
1389
1653
|
}
|
|
1390
1654
|
this.setupSessionFs(s, config);
|
|
1655
|
+
signal.throwIfAborted();
|
|
1656
|
+
replacedSession = this.sessions.get(sessionId);
|
|
1391
1657
|
this.sessions.set(sessionId, s);
|
|
1392
1658
|
return s;
|
|
1393
1659
|
};
|
|
1394
1660
|
let session;
|
|
1395
1661
|
let registeredId;
|
|
1396
1662
|
let uninitializedCloudSessionId;
|
|
1663
|
+
let releaseCloudInitialization;
|
|
1397
1664
|
if (localSessionId !== void 0) {
|
|
1398
1665
|
try {
|
|
1399
1666
|
session = initializeSession(localSessionId);
|
|
@@ -1406,14 +1673,20 @@ class CopilotClient {
|
|
|
1406
1673
|
}
|
|
1407
1674
|
}
|
|
1408
1675
|
try {
|
|
1409
|
-
const
|
|
1410
|
-
|
|
1676
|
+
const traceContext = await this.runInitializationCallback(
|
|
1677
|
+
localSessionId,
|
|
1678
|
+
() => (0, import_telemetry.getTraceContext)(this.onGetTraceContext)
|
|
1679
|
+
);
|
|
1680
|
+
signal.throwIfAborted();
|
|
1681
|
+
const response = await connection.sendRequest("session.create", {
|
|
1682
|
+
...traceContext,
|
|
1411
1683
|
model: config.model,
|
|
1412
1684
|
allowedModels: config.allowedModels,
|
|
1413
1685
|
sessionId: localSessionId,
|
|
1414
1686
|
name: config.name,
|
|
1415
1687
|
clientName: config.clientName,
|
|
1416
1688
|
reasoningEffort: config.reasoningEffort,
|
|
1689
|
+
allowMultipleToolCalls: config.allowMultipleToolCalls,
|
|
1417
1690
|
reasoningSummary: config.reasoningSummary,
|
|
1418
1691
|
isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
|
|
1419
1692
|
contextTier: config.contextTier,
|
|
@@ -1464,6 +1737,7 @@ class CopilotClient {
|
|
|
1464
1737
|
...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
|
|
1465
1738
|
requestExitPlanMode: !!config.onExitPlanModeRequest,
|
|
1466
1739
|
requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
|
|
1740
|
+
...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
|
|
1467
1741
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
1468
1742
|
workingDirectory: config.workingDirectory,
|
|
1469
1743
|
trustWorkingDirectory: config.trustWorkingDirectory,
|
|
@@ -1494,6 +1768,7 @@ class CopilotClient {
|
|
|
1494
1768
|
enableSkills: config.enableSkills,
|
|
1495
1769
|
skillDirectories: config.skillDirectories,
|
|
1496
1770
|
...config.skillProvider ? { hasSkillProvider: true } : {},
|
|
1771
|
+
...storeMemory ? { hasMemoryStore: true } : {},
|
|
1497
1772
|
agentDirectories: config.agentDirectories,
|
|
1498
1773
|
hookDirectories: config.hookDirectories,
|
|
1499
1774
|
pluginDirectories: config.pluginDirectories,
|
|
@@ -1502,7 +1777,7 @@ class CopilotClient {
|
|
|
1502
1777
|
disabledMcpServers: config.disabledMcpServers,
|
|
1503
1778
|
sandboxConfig: config.sandboxConfig,
|
|
1504
1779
|
infiniteSessions: config.infiniteSessions,
|
|
1505
|
-
memory: config.memory,
|
|
1780
|
+
memory: toWireMemoryConfiguration(config.memory),
|
|
1506
1781
|
gitHubToken: config.gitHubToken,
|
|
1507
1782
|
gitHubTokenProviderRegistrationId,
|
|
1508
1783
|
cloud: config.cloud,
|
|
@@ -1512,12 +1787,14 @@ class CopilotClient {
|
|
|
1512
1787
|
excludedManagedSettings: config.excludedManagedSettings,
|
|
1513
1788
|
enforceManagedModelDefaults: config.enforceManagedModelDefaults,
|
|
1514
1789
|
interactiveUser: config.interactiveUser,
|
|
1790
|
+
permissionMode: config.permissionMode,
|
|
1515
1791
|
managedSettings: config.managedSettings
|
|
1516
1792
|
});
|
|
1517
1793
|
const {
|
|
1518
1794
|
sessionId: returnedSessionId,
|
|
1519
1795
|
workspacePath,
|
|
1520
|
-
capabilities
|
|
1796
|
+
capabilities,
|
|
1797
|
+
permissionModeResult
|
|
1521
1798
|
} = response;
|
|
1522
1799
|
if (!returnedSessionId) {
|
|
1523
1800
|
throw new Error("session.create response did not include a sessionId");
|
|
@@ -1529,25 +1806,47 @@ class CopilotClient {
|
|
|
1529
1806
|
}
|
|
1530
1807
|
if (session === void 0) {
|
|
1531
1808
|
uninitializedCloudSessionId = returnedSessionId;
|
|
1809
|
+
}
|
|
1810
|
+
signal.throwIfAborted();
|
|
1811
|
+
if (session === void 0) {
|
|
1812
|
+
releaseCloudInitialization = await this.acquireSessionInitialization(
|
|
1813
|
+
returnedSessionId,
|
|
1814
|
+
signal
|
|
1815
|
+
);
|
|
1816
|
+
signal.throwIfAborted();
|
|
1532
1817
|
session = initializeSession(returnedSessionId);
|
|
1533
|
-
uninitializedCloudSessionId = void 0;
|
|
1534
1818
|
registeredId = returnedSessionId;
|
|
1535
1819
|
}
|
|
1536
1820
|
this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId);
|
|
1821
|
+
session.setCapabilities(capabilities);
|
|
1822
|
+
if (config.autoModeRecovery) {
|
|
1823
|
+
await this.requireAutoModeRecovery(session, connection);
|
|
1824
|
+
}
|
|
1537
1825
|
if (config.onMcpAuthRequest) {
|
|
1538
|
-
await
|
|
1826
|
+
await connection.sendRequest("session.eventLog.registerInterest", {
|
|
1539
1827
|
sessionId: returnedSessionId,
|
|
1540
1828
|
eventType: "mcp.oauth_required"
|
|
1541
1829
|
});
|
|
1542
1830
|
}
|
|
1543
1831
|
session["_workspacePath"] = workspacePath;
|
|
1544
|
-
session.
|
|
1832
|
+
session.setPermissionModeResult(permissionModeResult);
|
|
1545
1833
|
await this.updateSessionOptionsForMode(session, config);
|
|
1834
|
+
signal.throwIfAborted();
|
|
1835
|
+
uninitializedCloudSessionId = void 0;
|
|
1546
1836
|
this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
|
|
1547
1837
|
} catch (e) {
|
|
1548
1838
|
session?._markDisconnected();
|
|
1549
|
-
if (registeredId !== void 0) {
|
|
1550
|
-
this.sessions.
|
|
1839
|
+
if (registeredId !== void 0 && session !== void 0) {
|
|
1840
|
+
const current = this.sessions.get(registeredId);
|
|
1841
|
+
if (current === session) {
|
|
1842
|
+
if (replacedSession && !replacedSession._isDisconnected()) {
|
|
1843
|
+
this.sessions.set(registeredId, replacedSession);
|
|
1844
|
+
} else {
|
|
1845
|
+
this.sessions.delete(registeredId);
|
|
1846
|
+
}
|
|
1847
|
+
} else if (current === void 0) {
|
|
1848
|
+
replacedSession?._markDisconnected();
|
|
1849
|
+
}
|
|
1551
1850
|
}
|
|
1552
1851
|
if (gitHubTokenProviderRegistrationId !== void 0) {
|
|
1553
1852
|
this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
|
|
@@ -1555,18 +1854,33 @@ class CopilotClient {
|
|
|
1555
1854
|
if (uninitializedCloudSessionId !== void 0) {
|
|
1556
1855
|
try {
|
|
1557
1856
|
await withTimeout(
|
|
1558
|
-
this.
|
|
1857
|
+
this.requestSessionDeletion(connection, uninitializedCloudSessionId),
|
|
1559
1858
|
CLOUD_SESSION_CLEANUP_TIMEOUT_MS,
|
|
1560
1859
|
`session.delete timed out after ${CLOUD_SESSION_CLEANUP_TIMEOUT_MS}ms`
|
|
1561
1860
|
);
|
|
1562
1861
|
} catch (cleanupError) {
|
|
1563
|
-
|
|
1862
|
+
const failure = new AggregateError(
|
|
1564
1863
|
[e, cleanupError],
|
|
1565
|
-
|
|
1864
|
+
`Failed to initialize and delete cloud session ${uninitializedCloudSessionId}`
|
|
1566
1865
|
);
|
|
1866
|
+
if (config.autoModeRecovery) {
|
|
1867
|
+
this.recordFailedAutoModeRecoveryCleanup(
|
|
1868
|
+
connection,
|
|
1869
|
+
uninitializedCloudSessionId,
|
|
1870
|
+
failure
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
throw failure;
|
|
1567
1874
|
}
|
|
1568
1875
|
}
|
|
1569
1876
|
throw e;
|
|
1877
|
+
} finally {
|
|
1878
|
+
releaseCloudInitialization?.();
|
|
1879
|
+
}
|
|
1880
|
+
if (session._isDisconnected()) {
|
|
1881
|
+
this.forgetSession(session);
|
|
1882
|
+
} else {
|
|
1883
|
+
session._setReleaseFromClient(this.forgetSession.bind(this, session));
|
|
1570
1884
|
}
|
|
1571
1885
|
for (const entry of this.hostHandoffs.values()) {
|
|
1572
1886
|
if (entry.requestedSessionId === session.sessionId) {
|
|
@@ -1607,16 +1921,52 @@ class CopilotClient {
|
|
|
1607
1921
|
return this.resumeSessionInternal(sessionId, config, contributions, extensionOptions);
|
|
1608
1922
|
}
|
|
1609
1923
|
async resumeSessionInternal(sessionId, config, contributions = {}, extensionOptions) {
|
|
1610
|
-
|
|
1924
|
+
if (config.autoModeRecovery && typeof config.onAutoModeSwitchRequest !== "function") {
|
|
1925
|
+
throw new Error("autoModeRecovery requires onAutoModeSwitchRequest");
|
|
1926
|
+
}
|
|
1611
1927
|
if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
|
|
1612
1928
|
throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
|
|
1613
1929
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1930
|
+
requestedMemoryStore(config.memory);
|
|
1931
|
+
const signal = await this.getSessionInitializationSignal();
|
|
1932
|
+
const release = await this.acquireSessionInitialization(sessionId, signal);
|
|
1933
|
+
try {
|
|
1934
|
+
signal.throwIfAborted();
|
|
1935
|
+
return await this.resumeSessionWithInitialization(
|
|
1936
|
+
sessionId,
|
|
1937
|
+
config,
|
|
1938
|
+
contributions,
|
|
1939
|
+
signal,
|
|
1940
|
+
extensionOptions
|
|
1941
|
+
);
|
|
1942
|
+
} finally {
|
|
1943
|
+
release();
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
async resumeSessionWithInitialization(sessionId, config, contributions, signal, extensionOptions) {
|
|
1947
|
+
const { workflows } = contributions;
|
|
1948
|
+
const storeMemory = requestedMemoryStore(config.memory);
|
|
1949
|
+
signal.throwIfAborted();
|
|
1950
|
+
const connection = this.connection;
|
|
1951
|
+
const resolveAliases = config.autoModeRecovery === true && !this.sessionFsConfig;
|
|
1952
|
+
if (resolveAliases && isAbbreviatedUuidPrefix(sessionId)) {
|
|
1953
|
+
const resolved = await connection.sendRequest(
|
|
1954
|
+
"sessions.findByPrefix",
|
|
1955
|
+
{ prefix: sessionId, configDir: config.configDirectory }
|
|
1956
|
+
);
|
|
1957
|
+
const canonicalId = resolved?.sessionId;
|
|
1958
|
+
if (typeof canonicalId !== "string" || canonicalId.length === 0) {
|
|
1959
|
+
throw new Error(`No unique session matches prefix ${sessionId}`);
|
|
1960
|
+
}
|
|
1961
|
+
if (!canonicalId.toLowerCase().startsWith(sessionId.toLowerCase())) {
|
|
1962
|
+
throw new Error(`Runtime returned an unrelated session ID for prefix ${sessionId}`);
|
|
1963
|
+
}
|
|
1964
|
+
sessionId = canonicalId;
|
|
1965
|
+
signal.throwIfAborted();
|
|
1616
1966
|
}
|
|
1617
1967
|
const session = new import_session.CopilotSession(
|
|
1618
1968
|
sessionId,
|
|
1619
|
-
|
|
1969
|
+
connection,
|
|
1620
1970
|
void 0,
|
|
1621
1971
|
this.onGetTraceContext,
|
|
1622
1972
|
{
|
|
@@ -1655,6 +2005,7 @@ class CopilotClient {
|
|
|
1655
2005
|
if (config.skillProvider) {
|
|
1656
2006
|
session.registerSkillProvider(config.skillProvider);
|
|
1657
2007
|
}
|
|
2008
|
+
session._registerMemoryStore(storeMemory);
|
|
1658
2009
|
const modeDefaults = this.configDefaultsForMode();
|
|
1659
2010
|
config = { ...modeDefaults, ...config };
|
|
1660
2011
|
config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
|
|
@@ -1670,8 +2021,12 @@ class CopilotClient {
|
|
|
1670
2021
|
}
|
|
1671
2022
|
const toolFilterOptions = this.resolveToolFilterOptions(config);
|
|
1672
2023
|
this.setupSessionFs(session, config);
|
|
1673
|
-
|
|
1674
|
-
|
|
2024
|
+
signal.throwIfAborted();
|
|
2025
|
+
const requestedSessionId = sessionId;
|
|
2026
|
+
let replacedRequestedSession;
|
|
2027
|
+
let replacedSession;
|
|
2028
|
+
let releaseCanonicalInitialization;
|
|
2029
|
+
let keepResumedSessionOnRecoveryRejection = false;
|
|
1675
2030
|
const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider(
|
|
1676
2031
|
config.gitHubTokenProvider,
|
|
1677
2032
|
sessionId
|
|
@@ -1682,14 +2037,29 @@ class CopilotClient {
|
|
|
1682
2037
|
);
|
|
1683
2038
|
}
|
|
1684
2039
|
try {
|
|
1685
|
-
const
|
|
2040
|
+
const traceContext = await this.runInitializationCallback(
|
|
2041
|
+
sessionId,
|
|
2042
|
+
() => (0, import_telemetry.getTraceContext)(this.onGetTraceContext)
|
|
2043
|
+
);
|
|
2044
|
+
signal.throwIfAborted();
|
|
2045
|
+
replacedRequestedSession = this.sessions.get(sessionId);
|
|
2046
|
+
replacedSession = replacedRequestedSession;
|
|
2047
|
+
this.sessions.set(sessionId, session);
|
|
2048
|
+
this.pendingSessionResumes.set(requestedSessionId, {
|
|
2049
|
+
session,
|
|
2050
|
+
connection,
|
|
2051
|
+
gitHubTokenProviderRegistrationId,
|
|
2052
|
+
resolveAliases
|
|
2053
|
+
});
|
|
2054
|
+
const response = await connection.sendRequest("session.resume", {
|
|
1686
2055
|
allowedModels: config.allowedModels,
|
|
1687
|
-
...
|
|
2056
|
+
...traceContext,
|
|
1688
2057
|
sessionId,
|
|
1689
2058
|
allowTranscriptRecovery: config.allowTranscriptRecovery,
|
|
1690
2059
|
clientName: config.clientName,
|
|
1691
2060
|
model: config.model,
|
|
1692
2061
|
reasoningEffort: config.reasoningEffort,
|
|
2062
|
+
allowMultipleToolCalls: config.allowMultipleToolCalls,
|
|
1693
2063
|
reasoningSummary: config.reasoningSummary,
|
|
1694
2064
|
isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
|
|
1695
2065
|
contextTier: config.contextTier,
|
|
@@ -1741,6 +2111,7 @@ class CopilotClient {
|
|
|
1741
2111
|
...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
|
|
1742
2112
|
requestExitPlanMode: !!config.onExitPlanModeRequest,
|
|
1743
2113
|
requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
|
|
2114
|
+
...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
|
|
1744
2115
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
1745
2116
|
workingDirectory: config.workingDirectory,
|
|
1746
2117
|
additionalDirectories: config.additionalDirectories,
|
|
@@ -1769,6 +2140,7 @@ class CopilotClient {
|
|
|
1769
2140
|
agent: config.agent,
|
|
1770
2141
|
skillDirectories: config.skillDirectories,
|
|
1771
2142
|
...config.skillProvider ? { hasSkillProvider: true } : {},
|
|
2143
|
+
...storeMemory ? { hasMemoryStore: true } : {},
|
|
1772
2144
|
agentDirectories: config.agentDirectories,
|
|
1773
2145
|
hookDirectories: config.hookDirectories,
|
|
1774
2146
|
pluginDirectories: config.pluginDirectories,
|
|
@@ -1777,7 +2149,7 @@ class CopilotClient {
|
|
|
1777
2149
|
disabledMcpServers: config.disabledMcpServers,
|
|
1778
2150
|
sandboxConfig: config.sandboxConfig,
|
|
1779
2151
|
infiniteSessions: config.infiniteSessions,
|
|
1780
|
-
memory: config.memory,
|
|
2152
|
+
memory: toWireMemoryConfiguration(config.memory),
|
|
1781
2153
|
disableResume: config.suppressResumeEvent,
|
|
1782
2154
|
continuePendingWork: config.continuePendingWork,
|
|
1783
2155
|
gitHubToken: config.gitHubToken,
|
|
@@ -1789,11 +2161,34 @@ class CopilotClient {
|
|
|
1789
2161
|
excludedManagedSettings: config.excludedManagedSettings,
|
|
1790
2162
|
enforceManagedModelDefaults: config.enforceManagedModelDefaults,
|
|
1791
2163
|
interactiveUser: config.interactiveUser,
|
|
2164
|
+
permissionMode: config.permissionMode,
|
|
1792
2165
|
managedSettings: config.managedSettings,
|
|
1793
2166
|
...extensionOptions?.requestedEnvironmentVariables ? {
|
|
1794
2167
|
requestedEnvironmentVariables: extensionOptions.requestedEnvironmentVariables
|
|
1795
2168
|
} : {}
|
|
1796
2169
|
});
|
|
2170
|
+
const { sessionId: returnedSessionId } = response;
|
|
2171
|
+
if (returnedSessionId && returnedSessionId !== sessionId) {
|
|
2172
|
+
if (!sessionIdsOverlap(requestedSessionId, returnedSessionId)) {
|
|
2173
|
+
releaseCanonicalInitialization = await this.acquireSessionInitialization(
|
|
2174
|
+
returnedSessionId,
|
|
2175
|
+
signal
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
replacedSession = this.sessions.get(returnedSessionId);
|
|
2179
|
+
if (this.sessions.get(requestedSessionId) === session) {
|
|
2180
|
+
if (replacedRequestedSession) {
|
|
2181
|
+
this.sessions.set(requestedSessionId, replacedRequestedSession);
|
|
2182
|
+
} else {
|
|
2183
|
+
this.sessions.delete(requestedSessionId);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
session._setSessionId(returnedSessionId);
|
|
2187
|
+
sessionId = returnedSessionId;
|
|
2188
|
+
this.sessions.set(sessionId, session);
|
|
2189
|
+
this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, sessionId);
|
|
2190
|
+
}
|
|
2191
|
+
signal.throwIfAborted();
|
|
1797
2192
|
if (extensionOptions?.requestedEnvironmentVariables) {
|
|
1798
2193
|
const requested = new Set(extensionOptions.requestedEnvironmentVariables);
|
|
1799
2194
|
const { grantedEnvironmentVariables } = response;
|
|
@@ -1803,32 +2198,67 @@ class CopilotClient {
|
|
|
1803
2198
|
}
|
|
1804
2199
|
}
|
|
1805
2200
|
}
|
|
1806
|
-
const {
|
|
2201
|
+
const {
|
|
2202
|
+
workspacePath,
|
|
2203
|
+
capabilities,
|
|
2204
|
+
openCanvases,
|
|
2205
|
+
transcriptRecovery,
|
|
2206
|
+
permissionModeResult
|
|
2207
|
+
} = response;
|
|
1807
2208
|
session["_workspacePath"] = workspacePath;
|
|
1808
2209
|
session.setCapabilities(capabilities);
|
|
2210
|
+
if (config.autoModeRecovery) {
|
|
2211
|
+
keepResumedSessionOnRecoveryRejection = replacedSession !== void 0 && session.capabilities.ui?.autoModeRecovery !== true;
|
|
2212
|
+
await this.requireAutoModeRecovery(
|
|
2213
|
+
session,
|
|
2214
|
+
connection,
|
|
2215
|
+
replacedSession === void 0
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
1809
2218
|
session.setOpenCanvases(openCanvases ?? []);
|
|
1810
2219
|
session.setTranscriptRecovery(transcriptRecovery);
|
|
2220
|
+
session.setPermissionModeResult(permissionModeResult);
|
|
1811
2221
|
if (config.onMcpAuthRequest) {
|
|
1812
|
-
await
|
|
2222
|
+
await connection.sendRequest("session.eventLog.registerInterest", {
|
|
1813
2223
|
sessionId,
|
|
1814
2224
|
eventType: "mcp.oauth_required"
|
|
1815
2225
|
});
|
|
1816
2226
|
}
|
|
1817
2227
|
await this.updateSessionOptionsForMode(session, config);
|
|
2228
|
+
signal.throwIfAborted();
|
|
1818
2229
|
this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
|
|
1819
2230
|
} catch (e) {
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
2231
|
+
if (!keepResumedSessionOnRecoveryRejection) {
|
|
2232
|
+
session._markDisconnected();
|
|
2233
|
+
}
|
|
2234
|
+
const current = this.sessions.get(sessionId);
|
|
2235
|
+
if (current === session) {
|
|
2236
|
+
if (replacedSession && !replacedSession._isDisconnected() && !keepResumedSessionOnRecoveryRejection) {
|
|
1823
2237
|
this.sessions.set(sessionId, replacedSession);
|
|
1824
|
-
} else {
|
|
2238
|
+
} else if (!keepResumedSessionOnRecoveryRejection) {
|
|
1825
2239
|
this.sessions.delete(sessionId);
|
|
1826
2240
|
}
|
|
2241
|
+
} else if (current === void 0) {
|
|
2242
|
+
replacedSession?._markDisconnected();
|
|
1827
2243
|
}
|
|
1828
2244
|
if (gitHubTokenProviderRegistrationId !== void 0) {
|
|
1829
|
-
|
|
2245
|
+
if (keepResumedSessionOnRecoveryRejection) {
|
|
2246
|
+
this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
|
|
2247
|
+
} else {
|
|
2248
|
+
this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
|
|
2249
|
+
}
|
|
1830
2250
|
}
|
|
1831
2251
|
throw e;
|
|
2252
|
+
} finally {
|
|
2253
|
+
if (this.pendingSessionResumes.get(requestedSessionId)?.session === session) {
|
|
2254
|
+
this.pendingSessionResumes.delete(requestedSessionId);
|
|
2255
|
+
}
|
|
2256
|
+
releaseCanonicalInitialization?.();
|
|
2257
|
+
}
|
|
2258
|
+
if (session._isDisconnected()) {
|
|
2259
|
+
this.forgetSession(session);
|
|
2260
|
+
} else {
|
|
2261
|
+
session._setReleaseFromClient(this.forgetSession.bind(this, session));
|
|
1832
2262
|
}
|
|
1833
2263
|
for (const entry of this.hostHandoffs.values()) {
|
|
1834
2264
|
if (entry.requestedSessionId === session.sessionId) {
|
|
@@ -1837,6 +2267,47 @@ class CopilotClient {
|
|
|
1837
2267
|
}
|
|
1838
2268
|
return session;
|
|
1839
2269
|
}
|
|
2270
|
+
async requireAutoModeRecovery(session, connection, detachOnFailure = true) {
|
|
2271
|
+
if (session.capabilities.ui?.autoModeRecovery === true) {
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const error = new Error("The runtime does not support autoModeRecovery");
|
|
2275
|
+
if (!detachOnFailure) {
|
|
2276
|
+
throw error;
|
|
2277
|
+
}
|
|
2278
|
+
const cancellation = new import_node.CancellationTokenSource();
|
|
2279
|
+
try {
|
|
2280
|
+
const response = await withTimeout(
|
|
2281
|
+
connection.sendRequest(
|
|
2282
|
+
"session.detach",
|
|
2283
|
+
{ sessionId: session.sessionId },
|
|
2284
|
+
cancellation.token
|
|
2285
|
+
),
|
|
2286
|
+
AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS,
|
|
2287
|
+
`session.detach timed out after ${AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS}ms`
|
|
2288
|
+
);
|
|
2289
|
+
if (!response.success) {
|
|
2290
|
+
throw new Error(`Failed to detach session: ${response.error || "Unknown error"}`);
|
|
2291
|
+
}
|
|
2292
|
+
} catch (cleanupError) {
|
|
2293
|
+
const failure = new AggregateError([error, cleanupError], error.message);
|
|
2294
|
+
this.recordFailedAutoModeRecoveryCleanup(connection, session.sessionId, failure);
|
|
2295
|
+
throw failure;
|
|
2296
|
+
} finally {
|
|
2297
|
+
cancellation.cancel();
|
|
2298
|
+
cancellation.dispose();
|
|
2299
|
+
session._markDisconnected();
|
|
2300
|
+
}
|
|
2301
|
+
throw error;
|
|
2302
|
+
}
|
|
2303
|
+
recordFailedAutoModeRecoveryCleanup(connection, sessionId, failure) {
|
|
2304
|
+
let failed = this.failedAutoModeRecoveryCleanups.get(connection);
|
|
2305
|
+
if (!failed) {
|
|
2306
|
+
failed = /* @__PURE__ */ new Map();
|
|
2307
|
+
this.failedAutoModeRecoveryCleanups.set(connection, failed);
|
|
2308
|
+
}
|
|
2309
|
+
failed.set(sessionId, failure);
|
|
2310
|
+
}
|
|
1840
2311
|
/**
|
|
1841
2312
|
* Start a complete AHP listener hosted in-process by this runtime.
|
|
1842
2313
|
*
|
|
@@ -1948,6 +2419,12 @@ class CopilotClient {
|
|
|
1948
2419
|
}
|
|
1949
2420
|
}
|
|
1950
2421
|
}
|
|
2422
|
+
abortPendingMemoryStores() {
|
|
2423
|
+
for (const controller of this.pendingMemoryStores) {
|
|
2424
|
+
controller.abort();
|
|
2425
|
+
}
|
|
2426
|
+
this.pendingMemoryStores.clear();
|
|
2427
|
+
}
|
|
1951
2428
|
disconnectHosts() {
|
|
1952
2429
|
for (const hostId of /* @__PURE__ */ new Set([
|
|
1953
2430
|
...this.hostExitCallbacks.keys(),
|
|
@@ -2279,16 +2756,39 @@ class CopilotClient {
|
|
|
2279
2756
|
if (!this.connection) {
|
|
2280
2757
|
throw new Error("Client not connected");
|
|
2281
2758
|
}
|
|
2282
|
-
const
|
|
2283
|
-
|
|
2284
|
-
|
|
2759
|
+
const signal = await this.getSessionInitializationSignal();
|
|
2760
|
+
const release = await this.acquireSessionInitialization(sessionId, signal);
|
|
2761
|
+
try {
|
|
2762
|
+
signal.throwIfAborted();
|
|
2763
|
+
const connection = this.connection;
|
|
2764
|
+
const session = this.sessions.get(sessionId);
|
|
2765
|
+
await this.requestSessionDeletion(connection, sessionId);
|
|
2766
|
+
signal.throwIfAborted();
|
|
2767
|
+
if (this.sessions.get(sessionId) === session) {
|
|
2768
|
+
this.sessions.delete(sessionId);
|
|
2769
|
+
session?._markDisconnected();
|
|
2770
|
+
}
|
|
2771
|
+
} finally {
|
|
2772
|
+
release();
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
async requestSessionDeletion(connection, sessionId) {
|
|
2776
|
+
const response = await connection.sendRequest(
|
|
2777
|
+
"session.delete",
|
|
2778
|
+
{
|
|
2779
|
+
sessionId
|
|
2780
|
+
}
|
|
2781
|
+
);
|
|
2285
2782
|
const { success, error } = response;
|
|
2286
2783
|
if (!success) {
|
|
2287
2784
|
throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
|
|
2288
2785
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2786
|
+
}
|
|
2787
|
+
/** Stops tracking a disconnected session unless a later create or resume replaced it. */
|
|
2788
|
+
forgetSession(session) {
|
|
2789
|
+
if (this.sessions.get(session.sessionId) === session) {
|
|
2790
|
+
this.sessions.delete(session.sessionId);
|
|
2791
|
+
}
|
|
2292
2792
|
}
|
|
2293
2793
|
/**
|
|
2294
2794
|
* List all available sessions.
|
|
@@ -2745,7 +3245,8 @@ stderr: ${this.stderrBuffer.trim().slice(-8192) || "(empty)"}`
|
|
|
2745
3245
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
|
|
2746
3246
|
this.connection = createMessageConnection(
|
|
2747
3247
|
new import_node.StreamMessageReader(this.ffiHost.receiveStream),
|
|
2748
|
-
this.messageWriter
|
|
3248
|
+
this.messageWriter,
|
|
3249
|
+
this.sessionInitializationContext
|
|
2749
3250
|
);
|
|
2750
3251
|
this.attachConnectionHandlers();
|
|
2751
3252
|
this.connection.listen();
|
|
@@ -2805,7 +3306,8 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2805
3306
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.cliProcess.stdin);
|
|
2806
3307
|
this.connection = createMessageConnection(
|
|
2807
3308
|
new import_node.StreamMessageReader(this.cliProcess.stdout),
|
|
2808
|
-
this.messageWriter
|
|
3309
|
+
this.messageWriter,
|
|
3310
|
+
this.sessionInitializationContext
|
|
2809
3311
|
);
|
|
2810
3312
|
this.attachConnectionHandlers();
|
|
2811
3313
|
this.connection.listen();
|
|
@@ -2820,7 +3322,8 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2820
3322
|
this.messageWriter = new TeardownResilientStreamMessageWriter(process.stdout);
|
|
2821
3323
|
this.connection = createMessageConnection(
|
|
2822
3324
|
new import_node.StreamMessageReader(process.stdin),
|
|
2823
|
-
this.messageWriter
|
|
3325
|
+
this.messageWriter,
|
|
3326
|
+
this.sessionInitializationContext
|
|
2824
3327
|
);
|
|
2825
3328
|
this.attachConnectionHandlers();
|
|
2826
3329
|
this.connection.listen();
|
|
@@ -2843,7 +3346,8 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2843
3346
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.socket);
|
|
2844
3347
|
this.connection = createMessageConnection(
|
|
2845
3348
|
new import_node.StreamMessageReader(this.socket),
|
|
2846
|
-
this.messageWriter
|
|
3349
|
+
this.messageWriter,
|
|
3350
|
+
this.sessionInitializationContext
|
|
2847
3351
|
);
|
|
2848
3352
|
this.attachConnectionHandlers();
|
|
2849
3353
|
this.connection.listen();
|
|
@@ -2881,9 +3385,8 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2881
3385
|
"systemMessage.transform",
|
|
2882
3386
|
async (params) => await this.handleSystemMessageTransform(params)
|
|
2883
3387
|
);
|
|
2884
|
-
const sessions = this.sessions;
|
|
2885
3388
|
(0, import_rpc.registerClientSessionApiHandlers)(this.connection, (sessionId) => {
|
|
2886
|
-
const session =
|
|
3389
|
+
const session = this.resolveSessionCallback(sessionId);
|
|
2887
3390
|
if (!session) throw new Error(`No session found for sessionId: ${sessionId}`);
|
|
2888
3391
|
return session.clientSessionApis;
|
|
2889
3392
|
});
|
|
@@ -2901,12 +3404,20 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2901
3404
|
(params, token) => confirmation.confirm(params, token)
|
|
2902
3405
|
);
|
|
2903
3406
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
3407
|
+
const resolveProxy = this.network?.resolveProxy;
|
|
3408
|
+
if (resolveProxy) {
|
|
3409
|
+
const connection2 = this.connection;
|
|
3410
|
+
this.connection.onRequest(
|
|
3411
|
+
"network.resolveProxy",
|
|
3412
|
+
(params, token) => resolveClientProxy(connection2, resolveProxy, params, token)
|
|
3413
|
+
);
|
|
3414
|
+
}
|
|
3415
|
+
const authenticateNegotiate = this.network?.authenticateNegotiate;
|
|
3416
|
+
if (authenticateNegotiate) {
|
|
2906
3417
|
const connection2 = this.connection;
|
|
2907
3418
|
this.connection.onRequest(
|
|
2908
|
-
"
|
|
2909
|
-
(params, token) =>
|
|
3419
|
+
"network.authenticateNegotiate",
|
|
3420
|
+
(params, token) => authenticateClientNegotiate(connection2, authenticateNegotiate, params, token)
|
|
2910
3421
|
);
|
|
2911
3422
|
}
|
|
2912
3423
|
this.connection.onRequest(
|
|
@@ -2977,11 +3488,29 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2977
3488
|
this.connection.onRequest(
|
|
2978
3489
|
"sessionManagement.create",
|
|
2979
3490
|
async (params, token) => {
|
|
2980
|
-
const session = this.
|
|
3491
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
2981
3492
|
if (!session) throw new Error("Session creation owner is unavailable");
|
|
2982
3493
|
return await session._handleSessionManagementCreate(params, token);
|
|
2983
3494
|
}
|
|
2984
3495
|
);
|
|
3496
|
+
this.connection.onRequest(
|
|
3497
|
+
"memoryStore.store",
|
|
3498
|
+
async (params, token) => {
|
|
3499
|
+
const session = this.sessions.get(params?.sessionId);
|
|
3500
|
+
if (!session) throw new Error("Memory store owner is unavailable");
|
|
3501
|
+
const controller = new AbortController();
|
|
3502
|
+
this.pendingMemoryStores.add(controller);
|
|
3503
|
+
const subscription = token.onCancellationRequested(() => controller.abort());
|
|
3504
|
+
if (token.isCancellationRequested) controller.abort();
|
|
3505
|
+
try {
|
|
3506
|
+
await session._handleMemoryStore(params, controller.signal);
|
|
3507
|
+
} finally {
|
|
3508
|
+
this.pendingMemoryStores.delete(controller);
|
|
3509
|
+
subscription.dispose();
|
|
3510
|
+
}
|
|
3511
|
+
return null;
|
|
3512
|
+
}
|
|
3513
|
+
);
|
|
2985
3514
|
this.connection.onRequest(
|
|
2986
3515
|
"skillProvider.read",
|
|
2987
3516
|
async (params, token) => {
|
|
@@ -3013,6 +3542,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3013
3542
|
this.sessions.clear();
|
|
3014
3543
|
this.githubTokenProviders.clear();
|
|
3015
3544
|
this.requestAdapter?.cancelPending();
|
|
3545
|
+
this.abortPendingMemoryStores();
|
|
3016
3546
|
connection.dispose();
|
|
3017
3547
|
this.socket?.destroy();
|
|
3018
3548
|
this.connection = null;
|
|
@@ -3032,6 +3562,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3032
3562
|
}
|
|
3033
3563
|
if (disconnecting) return;
|
|
3034
3564
|
disconnecting = true;
|
|
3565
|
+
this.invalidateSessionInitializations();
|
|
3035
3566
|
this.connectionClosed = true;
|
|
3036
3567
|
if (messageWriter) messageWriter.suppressWriteErrors = true;
|
|
3037
3568
|
void connection.drain().then(markDisconnected);
|
|
@@ -3063,11 +3594,34 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3063
3594
|
}
|
|
3064
3595
|
});
|
|
3065
3596
|
}
|
|
3597
|
+
resolveSessionCallback(sessionId) {
|
|
3598
|
+
let selected;
|
|
3599
|
+
let selectedPrefixLength = -1;
|
|
3600
|
+
for (const [prefix, pending] of this.pendingSessionResumes) {
|
|
3601
|
+
if (pending.connection !== this.connection || !pending.resolveAliases && prefix !== sessionId || prefix.length <= selectedPrefixLength || !(sessionIdsOverlap(prefix, sessionId) && sessionId.length >= prefix.length))
|
|
3602
|
+
continue;
|
|
3603
|
+
const knownOwners = new Set(
|
|
3604
|
+
[...this.sessions.values()].filter(
|
|
3605
|
+
(owner) => owner !== pending.session && sessionIdsOverlap(prefix, owner.sessionId) && owner.sessionId.length >= prefix.length
|
|
3606
|
+
)
|
|
3607
|
+
);
|
|
3608
|
+
if (knownOwners.size > 1) continue;
|
|
3609
|
+
if (pending.session.sessionId !== sessionId) {
|
|
3610
|
+
pending.session._setSessionId(sessionId);
|
|
3611
|
+
}
|
|
3612
|
+
this.assignGitHubTokenProvider(pending.gitHubTokenProviderRegistrationId, sessionId);
|
|
3613
|
+
selected = pending.session;
|
|
3614
|
+
selectedPrefixLength = prefix.length;
|
|
3615
|
+
}
|
|
3616
|
+
return selected ?? this.sessions.get(sessionId);
|
|
3617
|
+
}
|
|
3066
3618
|
handleSessionEventNotification(notification) {
|
|
3067
3619
|
if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) {
|
|
3068
3620
|
return;
|
|
3069
3621
|
}
|
|
3070
|
-
const session = this.
|
|
3622
|
+
const session = this.resolveSessionCallback(
|
|
3623
|
+
notification.sessionId
|
|
3624
|
+
);
|
|
3071
3625
|
const event = notification.event;
|
|
3072
3626
|
if (session) {
|
|
3073
3627
|
session._dispatchEvent(event);
|
|
@@ -3111,7 +3665,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3111
3665
|
if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
|
|
3112
3666
|
throw new Error("Invalid user input request payload");
|
|
3113
3667
|
}
|
|
3114
|
-
const session = this.
|
|
3668
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3115
3669
|
if (!session) {
|
|
3116
3670
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3117
3671
|
}
|
|
@@ -3127,7 +3681,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3127
3681
|
if (!params || typeof params.sessionId !== "string" || typeof params.summary !== "string" || !Array.isArray(params.actions) || typeof params.recommendedAction !== "string") {
|
|
3128
3682
|
throw new Error("Invalid exit plan mode request payload");
|
|
3129
3683
|
}
|
|
3130
|
-
const session = this.
|
|
3684
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3131
3685
|
if (!session) {
|
|
3132
3686
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3133
3687
|
}
|
|
@@ -3143,13 +3697,14 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3143
3697
|
if (!params || typeof params.sessionId !== "string") {
|
|
3144
3698
|
throw new Error("Invalid auto mode switch request payload");
|
|
3145
3699
|
}
|
|
3146
|
-
const session = this.
|
|
3700
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3147
3701
|
if (!session) {
|
|
3148
3702
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3149
3703
|
}
|
|
3150
3704
|
const response = await session._handleAutoModeSwitchRequest({
|
|
3151
3705
|
errorCode: params.errorCode,
|
|
3152
|
-
retryAfterSeconds: params.retryAfterSeconds
|
|
3706
|
+
retryAfterSeconds: params.retryAfterSeconds,
|
|
3707
|
+
...params.autoModeRecovery === void 0 ? {} : { autoModeRecovery: params.autoModeRecovery }
|
|
3153
3708
|
});
|
|
3154
3709
|
return { response };
|
|
3155
3710
|
}
|
|
@@ -3157,7 +3712,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3157
3712
|
if (!params || typeof params.sessionId !== "string") {
|
|
3158
3713
|
throw new Error("Invalid skillProvider payload");
|
|
3159
3714
|
}
|
|
3160
|
-
const session = this.
|
|
3715
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3161
3716
|
if (!session) {
|
|
3162
3717
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3163
3718
|
}
|
|
@@ -3167,7 +3722,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3167
3722
|
if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
|
|
3168
3723
|
throw new Error("Invalid hooks invoke payload");
|
|
3169
3724
|
}
|
|
3170
|
-
const session = this.
|
|
3725
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3171
3726
|
if (!session) {
|
|
3172
3727
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3173
3728
|
}
|
|
@@ -3178,7 +3733,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3178
3733
|
if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
|
|
3179
3734
|
throw new Error("Invalid systemMessage.transform payload");
|
|
3180
3735
|
}
|
|
3181
|
-
const session = this.
|
|
3736
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3182
3737
|
if (!session) {
|
|
3183
3738
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3184
3739
|
}
|