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