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