@github/copilot-sdk 1.0.29 → 1.0.30-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -7
- package/dist/cjs/cliVersion.js +1 -1
- package/dist/cjs/client.js +651 -96
- package/dist/cjs/decisions.js +139 -0
- package/dist/cjs/generated/rpc.js +164 -14
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/session.js +113 -9
- package/dist/cliVersion.d.ts +1 -1
- package/dist/cliVersion.js +1 -1
- package/dist/client.d.ts +38 -3
- package/dist/client.js +653 -96
- package/dist/decisions.d.ts +65 -0
- package/dist/decisions.js +113 -0
- package/dist/generated/rpc.d.ts +1432 -130
- package/dist/generated/rpc.js +164 -14
- package/dist/generated/session-events.d.ts +213 -19
- package/dist/index.d.ts +4 -2
- package/dist/index.js +8 -0
- package/dist/session.d.ts +12 -2
- package/dist/session.js +113 -9
- package/dist/types.d.ts +143 -28
- package/docs/agent-author.md +1 -1
- package/docs/examples.md +77 -17
- package/package.json +14 -13
package/dist/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") {
|
|
@@ -885,12 +1039,13 @@ class CopilotClient {
|
|
|
885
1039
|
}
|
|
886
1040
|
}
|
|
887
1041
|
clearTimeout(detachDeadlineTimer);
|
|
888
|
-
for (const session of activeSessions) {
|
|
1042
|
+
for (const session of /* @__PURE__ */ new Set([...activeSessions, ...this.sessions.values()])) {
|
|
889
1043
|
session._markDisconnected();
|
|
890
1044
|
}
|
|
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
|
+
);
|
|
1306
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;
|
|
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;
|
|
@@ -1324,20 +1596,13 @@ class CopilotClient {
|
|
|
1324
1596
|
const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
|
|
1325
1597
|
config.systemMessage
|
|
1326
1598
|
);
|
|
1599
|
+
let replacedSession;
|
|
1327
1600
|
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
|
-
);
|
|
1601
|
+
const s = new CopilotSession(sessionId, connection, void 0, this.onGetTraceContext, {
|
|
1602
|
+
mcpAuthHandler: config.onMcpAuthRequest,
|
|
1603
|
+
managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
|
|
1604
|
+
onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId)
|
|
1605
|
+
});
|
|
1341
1606
|
s.registerTools(config.tools);
|
|
1342
1607
|
s.registerCanvases(config.canvases);
|
|
1343
1608
|
s.registerCommands(config.commands);
|
|
@@ -1363,6 +1628,7 @@ class CopilotClient {
|
|
|
1363
1628
|
if (config.skillProvider) {
|
|
1364
1629
|
s.registerSkillProvider(config.skillProvider);
|
|
1365
1630
|
}
|
|
1631
|
+
s._registerMemoryStore(storeMemory);
|
|
1366
1632
|
if (transformCallbacks) {
|
|
1367
1633
|
s.registerTransformCallbacks(transformCallbacks);
|
|
1368
1634
|
}
|
|
@@ -1370,12 +1636,15 @@ class CopilotClient {
|
|
|
1370
1636
|
s.on(config.onEvent);
|
|
1371
1637
|
}
|
|
1372
1638
|
this.setupSessionFs(s, config);
|
|
1639
|
+
signal.throwIfAborted();
|
|
1640
|
+
replacedSession = this.sessions.get(sessionId);
|
|
1373
1641
|
this.sessions.set(sessionId, s);
|
|
1374
1642
|
return s;
|
|
1375
1643
|
};
|
|
1376
1644
|
let session;
|
|
1377
1645
|
let registeredId;
|
|
1378
1646
|
let uninitializedCloudSessionId;
|
|
1647
|
+
let releaseCloudInitialization;
|
|
1379
1648
|
if (localSessionId !== void 0) {
|
|
1380
1649
|
try {
|
|
1381
1650
|
session = initializeSession(localSessionId);
|
|
@@ -1388,14 +1657,20 @@ class CopilotClient {
|
|
|
1388
1657
|
}
|
|
1389
1658
|
}
|
|
1390
1659
|
try {
|
|
1391
|
-
const
|
|
1392
|
-
|
|
1660
|
+
const traceContext = await this.runInitializationCallback(
|
|
1661
|
+
localSessionId,
|
|
1662
|
+
() => getTraceContext(this.onGetTraceContext)
|
|
1663
|
+
);
|
|
1664
|
+
signal.throwIfAborted();
|
|
1665
|
+
const response = await connection.sendRequest("session.create", {
|
|
1666
|
+
...traceContext,
|
|
1393
1667
|
model: config.model,
|
|
1394
1668
|
allowedModels: config.allowedModels,
|
|
1395
1669
|
sessionId: localSessionId,
|
|
1396
1670
|
name: config.name,
|
|
1397
1671
|
clientName: config.clientName,
|
|
1398
1672
|
reasoningEffort: config.reasoningEffort,
|
|
1673
|
+
allowMultipleToolCalls: config.allowMultipleToolCalls,
|
|
1399
1674
|
reasoningSummary: config.reasoningSummary,
|
|
1400
1675
|
isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
|
|
1401
1676
|
contextTier: config.contextTier,
|
|
@@ -1446,6 +1721,7 @@ class CopilotClient {
|
|
|
1446
1721
|
...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
|
|
1447
1722
|
requestExitPlanMode: !!config.onExitPlanModeRequest,
|
|
1448
1723
|
requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
|
|
1724
|
+
...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
|
|
1449
1725
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
1450
1726
|
workingDirectory: config.workingDirectory,
|
|
1451
1727
|
trustWorkingDirectory: config.trustWorkingDirectory,
|
|
@@ -1476,6 +1752,7 @@ class CopilotClient {
|
|
|
1476
1752
|
enableSkills: config.enableSkills,
|
|
1477
1753
|
skillDirectories: config.skillDirectories,
|
|
1478
1754
|
...config.skillProvider ? { hasSkillProvider: true } : {},
|
|
1755
|
+
...storeMemory ? { hasMemoryStore: true } : {},
|
|
1479
1756
|
agentDirectories: config.agentDirectories,
|
|
1480
1757
|
hookDirectories: config.hookDirectories,
|
|
1481
1758
|
pluginDirectories: config.pluginDirectories,
|
|
@@ -1484,7 +1761,7 @@ class CopilotClient {
|
|
|
1484
1761
|
disabledMcpServers: config.disabledMcpServers,
|
|
1485
1762
|
sandboxConfig: config.sandboxConfig,
|
|
1486
1763
|
infiniteSessions: config.infiniteSessions,
|
|
1487
|
-
memory: config.memory,
|
|
1764
|
+
memory: toWireMemoryConfiguration(config.memory),
|
|
1488
1765
|
gitHubToken: config.gitHubToken,
|
|
1489
1766
|
gitHubTokenProviderRegistrationId,
|
|
1490
1767
|
cloud: config.cloud,
|
|
@@ -1494,12 +1771,14 @@ class CopilotClient {
|
|
|
1494
1771
|
excludedManagedSettings: config.excludedManagedSettings,
|
|
1495
1772
|
enforceManagedModelDefaults: config.enforceManagedModelDefaults,
|
|
1496
1773
|
interactiveUser: config.interactiveUser,
|
|
1774
|
+
permissionMode: config.permissionMode,
|
|
1497
1775
|
managedSettings: config.managedSettings
|
|
1498
1776
|
});
|
|
1499
1777
|
const {
|
|
1500
1778
|
sessionId: returnedSessionId,
|
|
1501
1779
|
workspacePath,
|
|
1502
|
-
capabilities
|
|
1780
|
+
capabilities,
|
|
1781
|
+
permissionModeResult
|
|
1503
1782
|
} = response;
|
|
1504
1783
|
if (!returnedSessionId) {
|
|
1505
1784
|
throw new Error("session.create response did not include a sessionId");
|
|
@@ -1511,25 +1790,47 @@ class CopilotClient {
|
|
|
1511
1790
|
}
|
|
1512
1791
|
if (session === void 0) {
|
|
1513
1792
|
uninitializedCloudSessionId = returnedSessionId;
|
|
1793
|
+
}
|
|
1794
|
+
signal.throwIfAborted();
|
|
1795
|
+
if (session === void 0) {
|
|
1796
|
+
releaseCloudInitialization = await this.acquireSessionInitialization(
|
|
1797
|
+
returnedSessionId,
|
|
1798
|
+
signal
|
|
1799
|
+
);
|
|
1800
|
+
signal.throwIfAborted();
|
|
1514
1801
|
session = initializeSession(returnedSessionId);
|
|
1515
|
-
uninitializedCloudSessionId = void 0;
|
|
1516
1802
|
registeredId = returnedSessionId;
|
|
1517
1803
|
}
|
|
1518
1804
|
this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId);
|
|
1805
|
+
session.setCapabilities(capabilities);
|
|
1806
|
+
if (config.autoModeRecovery) {
|
|
1807
|
+
await this.requireAutoModeRecovery(session, connection);
|
|
1808
|
+
}
|
|
1519
1809
|
if (config.onMcpAuthRequest) {
|
|
1520
|
-
await
|
|
1810
|
+
await connection.sendRequest("session.eventLog.registerInterest", {
|
|
1521
1811
|
sessionId: returnedSessionId,
|
|
1522
1812
|
eventType: "mcp.oauth_required"
|
|
1523
1813
|
});
|
|
1524
1814
|
}
|
|
1525
1815
|
session["_workspacePath"] = workspacePath;
|
|
1526
|
-
session.
|
|
1816
|
+
session.setPermissionModeResult(permissionModeResult);
|
|
1527
1817
|
await this.updateSessionOptionsForMode(session, config);
|
|
1818
|
+
signal.throwIfAborted();
|
|
1819
|
+
uninitializedCloudSessionId = void 0;
|
|
1528
1820
|
this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
|
|
1529
1821
|
} catch (e) {
|
|
1530
1822
|
session?._markDisconnected();
|
|
1531
|
-
if (registeredId !== void 0) {
|
|
1532
|
-
this.sessions.
|
|
1823
|
+
if (registeredId !== void 0 && session !== void 0) {
|
|
1824
|
+
const current = this.sessions.get(registeredId);
|
|
1825
|
+
if (current === session) {
|
|
1826
|
+
if (replacedSession && !replacedSession._isDisconnected()) {
|
|
1827
|
+
this.sessions.set(registeredId, replacedSession);
|
|
1828
|
+
} else {
|
|
1829
|
+
this.sessions.delete(registeredId);
|
|
1830
|
+
}
|
|
1831
|
+
} else if (current === void 0) {
|
|
1832
|
+
replacedSession?._markDisconnected();
|
|
1833
|
+
}
|
|
1533
1834
|
}
|
|
1534
1835
|
if (gitHubTokenProviderRegistrationId !== void 0) {
|
|
1535
1836
|
this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
|
|
@@ -1537,18 +1838,33 @@ class CopilotClient {
|
|
|
1537
1838
|
if (uninitializedCloudSessionId !== void 0) {
|
|
1538
1839
|
try {
|
|
1539
1840
|
await withTimeout(
|
|
1540
|
-
this.
|
|
1841
|
+
this.requestSessionDeletion(connection, uninitializedCloudSessionId),
|
|
1541
1842
|
CLOUD_SESSION_CLEANUP_TIMEOUT_MS,
|
|
1542
1843
|
`session.delete timed out after ${CLOUD_SESSION_CLEANUP_TIMEOUT_MS}ms`
|
|
1543
1844
|
);
|
|
1544
1845
|
} catch (cleanupError) {
|
|
1545
|
-
|
|
1846
|
+
const failure = new AggregateError(
|
|
1546
1847
|
[e, cleanupError],
|
|
1547
|
-
|
|
1848
|
+
`Failed to initialize and delete cloud session ${uninitializedCloudSessionId}`
|
|
1548
1849
|
);
|
|
1850
|
+
if (config.autoModeRecovery) {
|
|
1851
|
+
this.recordFailedAutoModeRecoveryCleanup(
|
|
1852
|
+
connection,
|
|
1853
|
+
uninitializedCloudSessionId,
|
|
1854
|
+
failure
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
throw failure;
|
|
1549
1858
|
}
|
|
1550
1859
|
}
|
|
1551
1860
|
throw e;
|
|
1861
|
+
} finally {
|
|
1862
|
+
releaseCloudInitialization?.();
|
|
1863
|
+
}
|
|
1864
|
+
if (session._isDisconnected()) {
|
|
1865
|
+
this.forgetSession(session);
|
|
1866
|
+
} else {
|
|
1867
|
+
session._setReleaseFromClient(this.forgetSession.bind(this, session));
|
|
1552
1868
|
}
|
|
1553
1869
|
for (const entry of this.hostHandoffs.values()) {
|
|
1554
1870
|
if (entry.requestedSessionId === session.sessionId) {
|
|
@@ -1589,16 +1905,52 @@ class CopilotClient {
|
|
|
1589
1905
|
return this.resumeSessionInternal(sessionId, config, contributions, extensionOptions);
|
|
1590
1906
|
}
|
|
1591
1907
|
async resumeSessionInternal(sessionId, config, contributions = {}, extensionOptions) {
|
|
1592
|
-
|
|
1908
|
+
if (config.autoModeRecovery && typeof config.onAutoModeSwitchRequest !== "function") {
|
|
1909
|
+
throw new Error("autoModeRecovery requires onAutoModeSwitchRequest");
|
|
1910
|
+
}
|
|
1593
1911
|
if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
|
|
1594
1912
|
throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
|
|
1595
1913
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
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();
|
|
1598
1950
|
}
|
|
1599
1951
|
const session = new CopilotSession(
|
|
1600
1952
|
sessionId,
|
|
1601
|
-
|
|
1953
|
+
connection,
|
|
1602
1954
|
void 0,
|
|
1603
1955
|
this.onGetTraceContext,
|
|
1604
1956
|
{
|
|
@@ -1637,6 +1989,7 @@ class CopilotClient {
|
|
|
1637
1989
|
if (config.skillProvider) {
|
|
1638
1990
|
session.registerSkillProvider(config.skillProvider);
|
|
1639
1991
|
}
|
|
1992
|
+
session._registerMemoryStore(storeMemory);
|
|
1640
1993
|
const modeDefaults = this.configDefaultsForMode();
|
|
1641
1994
|
config = { ...modeDefaults, ...config };
|
|
1642
1995
|
config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly;
|
|
@@ -1652,8 +2005,12 @@ class CopilotClient {
|
|
|
1652
2005
|
}
|
|
1653
2006
|
const toolFilterOptions = this.resolveToolFilterOptions(config);
|
|
1654
2007
|
this.setupSessionFs(session, config);
|
|
1655
|
-
|
|
1656
|
-
|
|
2008
|
+
signal.throwIfAborted();
|
|
2009
|
+
const requestedSessionId = sessionId;
|
|
2010
|
+
let replacedRequestedSession;
|
|
2011
|
+
let replacedSession;
|
|
2012
|
+
let releaseCanonicalInitialization;
|
|
2013
|
+
let keepResumedSessionOnRecoveryRejection = false;
|
|
1657
2014
|
const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider(
|
|
1658
2015
|
config.gitHubTokenProvider,
|
|
1659
2016
|
sessionId
|
|
@@ -1664,14 +2021,29 @@ class CopilotClient {
|
|
|
1664
2021
|
);
|
|
1665
2022
|
}
|
|
1666
2023
|
try {
|
|
1667
|
-
const
|
|
2024
|
+
const traceContext = await this.runInitializationCallback(
|
|
2025
|
+
sessionId,
|
|
2026
|
+
() => 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", {
|
|
1668
2039
|
allowedModels: config.allowedModels,
|
|
1669
|
-
...
|
|
2040
|
+
...traceContext,
|
|
1670
2041
|
sessionId,
|
|
1671
2042
|
allowTranscriptRecovery: config.allowTranscriptRecovery,
|
|
1672
2043
|
clientName: config.clientName,
|
|
1673
2044
|
model: config.model,
|
|
1674
2045
|
reasoningEffort: config.reasoningEffort,
|
|
2046
|
+
allowMultipleToolCalls: config.allowMultipleToolCalls,
|
|
1675
2047
|
reasoningSummary: config.reasoningSummary,
|
|
1676
2048
|
isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode),
|
|
1677
2049
|
contextTier: config.contextTier,
|
|
@@ -1723,6 +2095,7 @@ class CopilotClient {
|
|
|
1723
2095
|
...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
|
|
1724
2096
|
requestExitPlanMode: !!config.onExitPlanModeRequest,
|
|
1725
2097
|
requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
|
|
2098
|
+
...config.autoModeRecovery ? { requestAutoModeRecovery: true } : {},
|
|
1726
2099
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
1727
2100
|
workingDirectory: config.workingDirectory,
|
|
1728
2101
|
additionalDirectories: config.additionalDirectories,
|
|
@@ -1751,6 +2124,7 @@ class CopilotClient {
|
|
|
1751
2124
|
agent: config.agent,
|
|
1752
2125
|
skillDirectories: config.skillDirectories,
|
|
1753
2126
|
...config.skillProvider ? { hasSkillProvider: true } : {},
|
|
2127
|
+
...storeMemory ? { hasMemoryStore: true } : {},
|
|
1754
2128
|
agentDirectories: config.agentDirectories,
|
|
1755
2129
|
hookDirectories: config.hookDirectories,
|
|
1756
2130
|
pluginDirectories: config.pluginDirectories,
|
|
@@ -1759,7 +2133,7 @@ class CopilotClient {
|
|
|
1759
2133
|
disabledMcpServers: config.disabledMcpServers,
|
|
1760
2134
|
sandboxConfig: config.sandboxConfig,
|
|
1761
2135
|
infiniteSessions: config.infiniteSessions,
|
|
1762
|
-
memory: config.memory,
|
|
2136
|
+
memory: toWireMemoryConfiguration(config.memory),
|
|
1763
2137
|
disableResume: config.suppressResumeEvent,
|
|
1764
2138
|
continuePendingWork: config.continuePendingWork,
|
|
1765
2139
|
gitHubToken: config.gitHubToken,
|
|
@@ -1771,11 +2145,34 @@ class CopilotClient {
|
|
|
1771
2145
|
excludedManagedSettings: config.excludedManagedSettings,
|
|
1772
2146
|
enforceManagedModelDefaults: config.enforceManagedModelDefaults,
|
|
1773
2147
|
interactiveUser: config.interactiveUser,
|
|
2148
|
+
permissionMode: config.permissionMode,
|
|
1774
2149
|
managedSettings: config.managedSettings,
|
|
1775
2150
|
...extensionOptions?.requestedEnvironmentVariables ? {
|
|
1776
2151
|
requestedEnvironmentVariables: extensionOptions.requestedEnvironmentVariables
|
|
1777
2152
|
} : {}
|
|
1778
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();
|
|
1779
2176
|
if (extensionOptions?.requestedEnvironmentVariables) {
|
|
1780
2177
|
const requested = new Set(extensionOptions.requestedEnvironmentVariables);
|
|
1781
2178
|
const { grantedEnvironmentVariables } = response;
|
|
@@ -1785,32 +2182,67 @@ class CopilotClient {
|
|
|
1785
2182
|
}
|
|
1786
2183
|
}
|
|
1787
2184
|
}
|
|
1788
|
-
const {
|
|
2185
|
+
const {
|
|
2186
|
+
workspacePath,
|
|
2187
|
+
capabilities,
|
|
2188
|
+
openCanvases,
|
|
2189
|
+
transcriptRecovery,
|
|
2190
|
+
permissionModeResult
|
|
2191
|
+
} = response;
|
|
1789
2192
|
session["_workspacePath"] = workspacePath;
|
|
1790
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
|
+
}
|
|
1791
2202
|
session.setOpenCanvases(openCanvases ?? []);
|
|
1792
2203
|
session.setTranscriptRecovery(transcriptRecovery);
|
|
2204
|
+
session.setPermissionModeResult(permissionModeResult);
|
|
1793
2205
|
if (config.onMcpAuthRequest) {
|
|
1794
|
-
await
|
|
2206
|
+
await connection.sendRequest("session.eventLog.registerInterest", {
|
|
1795
2207
|
sessionId,
|
|
1796
2208
|
eventType: "mcp.oauth_required"
|
|
1797
2209
|
});
|
|
1798
2210
|
}
|
|
1799
2211
|
await this.updateSessionOptionsForMode(session, config);
|
|
2212
|
+
signal.throwIfAborted();
|
|
1800
2213
|
this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
|
|
1801
2214
|
} catch (e) {
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2215
|
+
if (!keepResumedSessionOnRecoveryRejection) {
|
|
2216
|
+
session._markDisconnected();
|
|
2217
|
+
}
|
|
2218
|
+
const current = this.sessions.get(sessionId);
|
|
2219
|
+
if (current === session) {
|
|
2220
|
+
if (replacedSession && !replacedSession._isDisconnected() && !keepResumedSessionOnRecoveryRejection) {
|
|
1805
2221
|
this.sessions.set(sessionId, replacedSession);
|
|
1806
|
-
} else {
|
|
2222
|
+
} else if (!keepResumedSessionOnRecoveryRejection) {
|
|
1807
2223
|
this.sessions.delete(sessionId);
|
|
1808
2224
|
}
|
|
2225
|
+
} else if (current === void 0) {
|
|
2226
|
+
replacedSession?._markDisconnected();
|
|
1809
2227
|
}
|
|
1810
2228
|
if (gitHubTokenProviderRegistrationId !== void 0) {
|
|
1811
|
-
|
|
2229
|
+
if (keepResumedSessionOnRecoveryRejection) {
|
|
2230
|
+
this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
|
|
2231
|
+
} else {
|
|
2232
|
+
this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
|
|
2233
|
+
}
|
|
1812
2234
|
}
|
|
1813
2235
|
throw e;
|
|
2236
|
+
} finally {
|
|
2237
|
+
if (this.pendingSessionResumes.get(requestedSessionId)?.session === session) {
|
|
2238
|
+
this.pendingSessionResumes.delete(requestedSessionId);
|
|
2239
|
+
}
|
|
2240
|
+
releaseCanonicalInitialization?.();
|
|
2241
|
+
}
|
|
2242
|
+
if (session._isDisconnected()) {
|
|
2243
|
+
this.forgetSession(session);
|
|
2244
|
+
} else {
|
|
2245
|
+
session._setReleaseFromClient(this.forgetSession.bind(this, session));
|
|
1814
2246
|
}
|
|
1815
2247
|
for (const entry of this.hostHandoffs.values()) {
|
|
1816
2248
|
if (entry.requestedSessionId === session.sessionId) {
|
|
@@ -1819,6 +2251,47 @@ class CopilotClient {
|
|
|
1819
2251
|
}
|
|
1820
2252
|
return session;
|
|
1821
2253
|
}
|
|
2254
|
+
async requireAutoModeRecovery(session, connection, detachOnFailure = true) {
|
|
2255
|
+
if (session.capabilities.ui?.autoModeRecovery === true) {
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
const error = new Error("The runtime does not support autoModeRecovery");
|
|
2259
|
+
if (!detachOnFailure) {
|
|
2260
|
+
throw error;
|
|
2261
|
+
}
|
|
2262
|
+
const cancellation = new CancellationTokenSource();
|
|
2263
|
+
try {
|
|
2264
|
+
const response = await withTimeout(
|
|
2265
|
+
connection.sendRequest(
|
|
2266
|
+
"session.detach",
|
|
2267
|
+
{ sessionId: session.sessionId },
|
|
2268
|
+
cancellation.token
|
|
2269
|
+
),
|
|
2270
|
+
AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS,
|
|
2271
|
+
`session.detach timed out after ${AUTO_MODE_RECOVERY_CLEANUP_TIMEOUT_MS}ms`
|
|
2272
|
+
);
|
|
2273
|
+
if (!response.success) {
|
|
2274
|
+
throw new Error(`Failed to detach session: ${response.error || "Unknown error"}`);
|
|
2275
|
+
}
|
|
2276
|
+
} catch (cleanupError) {
|
|
2277
|
+
const failure = new AggregateError([error, cleanupError], error.message);
|
|
2278
|
+
this.recordFailedAutoModeRecoveryCleanup(connection, session.sessionId, failure);
|
|
2279
|
+
throw failure;
|
|
2280
|
+
} finally {
|
|
2281
|
+
cancellation.cancel();
|
|
2282
|
+
cancellation.dispose();
|
|
2283
|
+
session._markDisconnected();
|
|
2284
|
+
}
|
|
2285
|
+
throw error;
|
|
2286
|
+
}
|
|
2287
|
+
recordFailedAutoModeRecoveryCleanup(connection, sessionId, failure) {
|
|
2288
|
+
let failed = this.failedAutoModeRecoveryCleanups.get(connection);
|
|
2289
|
+
if (!failed) {
|
|
2290
|
+
failed = /* @__PURE__ */ new Map();
|
|
2291
|
+
this.failedAutoModeRecoveryCleanups.set(connection, failed);
|
|
2292
|
+
}
|
|
2293
|
+
failed.set(sessionId, failure);
|
|
2294
|
+
}
|
|
1822
2295
|
/**
|
|
1823
2296
|
* Start a complete AHP listener hosted in-process by this runtime.
|
|
1824
2297
|
*
|
|
@@ -1930,6 +2403,12 @@ class CopilotClient {
|
|
|
1930
2403
|
}
|
|
1931
2404
|
}
|
|
1932
2405
|
}
|
|
2406
|
+
abortPendingMemoryStores() {
|
|
2407
|
+
for (const controller of this.pendingMemoryStores) {
|
|
2408
|
+
controller.abort();
|
|
2409
|
+
}
|
|
2410
|
+
this.pendingMemoryStores.clear();
|
|
2411
|
+
}
|
|
1933
2412
|
disconnectHosts() {
|
|
1934
2413
|
for (const hostId of /* @__PURE__ */ new Set([
|
|
1935
2414
|
...this.hostExitCallbacks.keys(),
|
|
@@ -2261,16 +2740,39 @@ class CopilotClient {
|
|
|
2261
2740
|
if (!this.connection) {
|
|
2262
2741
|
throw new Error("Client not connected");
|
|
2263
2742
|
}
|
|
2264
|
-
const
|
|
2265
|
-
|
|
2266
|
-
|
|
2743
|
+
const signal = await this.getSessionInitializationSignal();
|
|
2744
|
+
const release = await this.acquireSessionInitialization(sessionId, signal);
|
|
2745
|
+
try {
|
|
2746
|
+
signal.throwIfAborted();
|
|
2747
|
+
const connection = this.connection;
|
|
2748
|
+
const session = this.sessions.get(sessionId);
|
|
2749
|
+
await this.requestSessionDeletion(connection, sessionId);
|
|
2750
|
+
signal.throwIfAborted();
|
|
2751
|
+
if (this.sessions.get(sessionId) === session) {
|
|
2752
|
+
this.sessions.delete(sessionId);
|
|
2753
|
+
session?._markDisconnected();
|
|
2754
|
+
}
|
|
2755
|
+
} finally {
|
|
2756
|
+
release();
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
async requestSessionDeletion(connection, sessionId) {
|
|
2760
|
+
const response = await connection.sendRequest(
|
|
2761
|
+
"session.delete",
|
|
2762
|
+
{
|
|
2763
|
+
sessionId
|
|
2764
|
+
}
|
|
2765
|
+
);
|
|
2267
2766
|
const { success, error } = response;
|
|
2268
2767
|
if (!success) {
|
|
2269
2768
|
throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
|
|
2270
2769
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2770
|
+
}
|
|
2771
|
+
/** Stops tracking a disconnected session unless a later create or resume replaced it. */
|
|
2772
|
+
forgetSession(session) {
|
|
2773
|
+
if (this.sessions.get(session.sessionId) === session) {
|
|
2774
|
+
this.sessions.delete(session.sessionId);
|
|
2775
|
+
}
|
|
2274
2776
|
}
|
|
2275
2777
|
/**
|
|
2276
2778
|
* List all available sessions.
|
|
@@ -2727,7 +3229,8 @@ stderr: ${this.stderrBuffer.trim().slice(-8192) || "(empty)"}`
|
|
|
2727
3229
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
|
|
2728
3230
|
this.connection = createMessageConnection(
|
|
2729
3231
|
new StreamMessageReader(this.ffiHost.receiveStream),
|
|
2730
|
-
this.messageWriter
|
|
3232
|
+
this.messageWriter,
|
|
3233
|
+
this.sessionInitializationContext
|
|
2731
3234
|
);
|
|
2732
3235
|
this.attachConnectionHandlers();
|
|
2733
3236
|
this.connection.listen();
|
|
@@ -2787,7 +3290,8 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2787
3290
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.cliProcess.stdin);
|
|
2788
3291
|
this.connection = createMessageConnection(
|
|
2789
3292
|
new StreamMessageReader(this.cliProcess.stdout),
|
|
2790
|
-
this.messageWriter
|
|
3293
|
+
this.messageWriter,
|
|
3294
|
+
this.sessionInitializationContext
|
|
2791
3295
|
);
|
|
2792
3296
|
this.attachConnectionHandlers();
|
|
2793
3297
|
this.connection.listen();
|
|
@@ -2802,7 +3306,8 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2802
3306
|
this.messageWriter = new TeardownResilientStreamMessageWriter(process.stdout);
|
|
2803
3307
|
this.connection = createMessageConnection(
|
|
2804
3308
|
new StreamMessageReader(process.stdin),
|
|
2805
|
-
this.messageWriter
|
|
3309
|
+
this.messageWriter,
|
|
3310
|
+
this.sessionInitializationContext
|
|
2806
3311
|
);
|
|
2807
3312
|
this.attachConnectionHandlers();
|
|
2808
3313
|
this.connection.listen();
|
|
@@ -2825,7 +3330,8 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2825
3330
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.socket);
|
|
2826
3331
|
this.connection = createMessageConnection(
|
|
2827
3332
|
new StreamMessageReader(this.socket),
|
|
2828
|
-
this.messageWriter
|
|
3333
|
+
this.messageWriter,
|
|
3334
|
+
this.sessionInitializationContext
|
|
2829
3335
|
);
|
|
2830
3336
|
this.attachConnectionHandlers();
|
|
2831
3337
|
this.connection.listen();
|
|
@@ -2863,9 +3369,8 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2863
3369
|
"systemMessage.transform",
|
|
2864
3370
|
async (params) => await this.handleSystemMessageTransform(params)
|
|
2865
3371
|
);
|
|
2866
|
-
const sessions = this.sessions;
|
|
2867
3372
|
registerClientSessionApiHandlers(this.connection, (sessionId) => {
|
|
2868
|
-
const session =
|
|
3373
|
+
const session = this.resolveSessionCallback(sessionId);
|
|
2869
3374
|
if (!session) throw new Error(`No session found for sessionId: ${sessionId}`);
|
|
2870
3375
|
return session.clientSessionApis;
|
|
2871
3376
|
});
|
|
@@ -2883,12 +3388,20 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2883
3388
|
(params, token) => confirmation.confirm(params, token)
|
|
2884
3389
|
);
|
|
2885
3390
|
}
|
|
2886
|
-
|
|
2887
|
-
|
|
3391
|
+
const resolveProxy = this.network?.resolveProxy;
|
|
3392
|
+
if (resolveProxy) {
|
|
3393
|
+
const connection2 = this.connection;
|
|
3394
|
+
this.connection.onRequest(
|
|
3395
|
+
"network.resolveProxy",
|
|
3396
|
+
(params, token) => resolveClientProxy(connection2, resolveProxy, params, token)
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
const authenticateNegotiate = this.network?.authenticateNegotiate;
|
|
3400
|
+
if (authenticateNegotiate) {
|
|
2888
3401
|
const connection2 = this.connection;
|
|
2889
3402
|
this.connection.onRequest(
|
|
2890
|
-
"
|
|
2891
|
-
(params, token) =>
|
|
3403
|
+
"network.authenticateNegotiate",
|
|
3404
|
+
(params, token) => authenticateClientNegotiate(connection2, authenticateNegotiate, params, token)
|
|
2892
3405
|
);
|
|
2893
3406
|
}
|
|
2894
3407
|
this.connection.onRequest(
|
|
@@ -2959,11 +3472,29 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2959
3472
|
this.connection.onRequest(
|
|
2960
3473
|
"sessionManagement.create",
|
|
2961
3474
|
async (params, token) => {
|
|
2962
|
-
const session = this.
|
|
3475
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
2963
3476
|
if (!session) throw new Error("Session creation owner is unavailable");
|
|
2964
3477
|
return await session._handleSessionManagementCreate(params, token);
|
|
2965
3478
|
}
|
|
2966
3479
|
);
|
|
3480
|
+
this.connection.onRequest(
|
|
3481
|
+
"memoryStore.store",
|
|
3482
|
+
async (params, token) => {
|
|
3483
|
+
const session = this.sessions.get(params?.sessionId);
|
|
3484
|
+
if (!session) throw new Error("Memory store owner is unavailable");
|
|
3485
|
+
const controller = new AbortController();
|
|
3486
|
+
this.pendingMemoryStores.add(controller);
|
|
3487
|
+
const subscription = token.onCancellationRequested(() => controller.abort());
|
|
3488
|
+
if (token.isCancellationRequested) controller.abort();
|
|
3489
|
+
try {
|
|
3490
|
+
await session._handleMemoryStore(params, controller.signal);
|
|
3491
|
+
} finally {
|
|
3492
|
+
this.pendingMemoryStores.delete(controller);
|
|
3493
|
+
subscription.dispose();
|
|
3494
|
+
}
|
|
3495
|
+
return null;
|
|
3496
|
+
}
|
|
3497
|
+
);
|
|
2967
3498
|
this.connection.onRequest(
|
|
2968
3499
|
"skillProvider.read",
|
|
2969
3500
|
async (params, token) => {
|
|
@@ -2995,6 +3526,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2995
3526
|
this.sessions.clear();
|
|
2996
3527
|
this.githubTokenProviders.clear();
|
|
2997
3528
|
this.requestAdapter?.cancelPending();
|
|
3529
|
+
this.abortPendingMemoryStores();
|
|
2998
3530
|
connection.dispose();
|
|
2999
3531
|
this.socket?.destroy();
|
|
3000
3532
|
this.connection = null;
|
|
@@ -3014,6 +3546,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3014
3546
|
}
|
|
3015
3547
|
if (disconnecting) return;
|
|
3016
3548
|
disconnecting = true;
|
|
3549
|
+
this.invalidateSessionInitializations();
|
|
3017
3550
|
this.connectionClosed = true;
|
|
3018
3551
|
if (messageWriter) messageWriter.suppressWriteErrors = true;
|
|
3019
3552
|
void connection.drain().then(markDisconnected);
|
|
@@ -3045,11 +3578,34 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3045
3578
|
}
|
|
3046
3579
|
});
|
|
3047
3580
|
}
|
|
3581
|
+
resolveSessionCallback(sessionId) {
|
|
3582
|
+
let selected;
|
|
3583
|
+
let selectedPrefixLength = -1;
|
|
3584
|
+
for (const [prefix, pending] of this.pendingSessionResumes) {
|
|
3585
|
+
if (pending.connection !== this.connection || !pending.resolveAliases && prefix !== sessionId || prefix.length <= selectedPrefixLength || !(sessionIdsOverlap(prefix, sessionId) && sessionId.length >= prefix.length))
|
|
3586
|
+
continue;
|
|
3587
|
+
const knownOwners = new Set(
|
|
3588
|
+
[...this.sessions.values()].filter(
|
|
3589
|
+
(owner) => owner !== pending.session && sessionIdsOverlap(prefix, owner.sessionId) && owner.sessionId.length >= prefix.length
|
|
3590
|
+
)
|
|
3591
|
+
);
|
|
3592
|
+
if (knownOwners.size > 1) continue;
|
|
3593
|
+
if (pending.session.sessionId !== sessionId) {
|
|
3594
|
+
pending.session._setSessionId(sessionId);
|
|
3595
|
+
}
|
|
3596
|
+
this.assignGitHubTokenProvider(pending.gitHubTokenProviderRegistrationId, sessionId);
|
|
3597
|
+
selected = pending.session;
|
|
3598
|
+
selectedPrefixLength = prefix.length;
|
|
3599
|
+
}
|
|
3600
|
+
return selected ?? this.sessions.get(sessionId);
|
|
3601
|
+
}
|
|
3048
3602
|
handleSessionEventNotification(notification) {
|
|
3049
3603
|
if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) {
|
|
3050
3604
|
return;
|
|
3051
3605
|
}
|
|
3052
|
-
const session = this.
|
|
3606
|
+
const session = this.resolveSessionCallback(
|
|
3607
|
+
notification.sessionId
|
|
3608
|
+
);
|
|
3053
3609
|
const event = notification.event;
|
|
3054
3610
|
if (session) {
|
|
3055
3611
|
session._dispatchEvent(event);
|
|
@@ -3093,7 +3649,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3093
3649
|
if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
|
|
3094
3650
|
throw new Error("Invalid user input request payload");
|
|
3095
3651
|
}
|
|
3096
|
-
const session = this.
|
|
3652
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3097
3653
|
if (!session) {
|
|
3098
3654
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3099
3655
|
}
|
|
@@ -3109,7 +3665,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3109
3665
|
if (!params || typeof params.sessionId !== "string" || typeof params.summary !== "string" || !Array.isArray(params.actions) || typeof params.recommendedAction !== "string") {
|
|
3110
3666
|
throw new Error("Invalid exit plan mode request payload");
|
|
3111
3667
|
}
|
|
3112
|
-
const session = this.
|
|
3668
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3113
3669
|
if (!session) {
|
|
3114
3670
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3115
3671
|
}
|
|
@@ -3125,13 +3681,14 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3125
3681
|
if (!params || typeof params.sessionId !== "string") {
|
|
3126
3682
|
throw new Error("Invalid auto mode switch request payload");
|
|
3127
3683
|
}
|
|
3128
|
-
const session = this.
|
|
3684
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3129
3685
|
if (!session) {
|
|
3130
3686
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3131
3687
|
}
|
|
3132
3688
|
const response = await session._handleAutoModeSwitchRequest({
|
|
3133
3689
|
errorCode: params.errorCode,
|
|
3134
|
-
retryAfterSeconds: params.retryAfterSeconds
|
|
3690
|
+
retryAfterSeconds: params.retryAfterSeconds,
|
|
3691
|
+
...params.autoModeRecovery === void 0 ? {} : { autoModeRecovery: params.autoModeRecovery }
|
|
3135
3692
|
});
|
|
3136
3693
|
return { response };
|
|
3137
3694
|
}
|
|
@@ -3139,7 +3696,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3139
3696
|
if (!params || typeof params.sessionId !== "string") {
|
|
3140
3697
|
throw new Error("Invalid skillProvider payload");
|
|
3141
3698
|
}
|
|
3142
|
-
const session = this.
|
|
3699
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3143
3700
|
if (!session) {
|
|
3144
3701
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3145
3702
|
}
|
|
@@ -3149,7 +3706,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3149
3706
|
if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
|
|
3150
3707
|
throw new Error("Invalid hooks invoke payload");
|
|
3151
3708
|
}
|
|
3152
|
-
const session = this.
|
|
3709
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3153
3710
|
if (!session) {
|
|
3154
3711
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3155
3712
|
}
|
|
@@ -3160,7 +3717,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
3160
3717
|
if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
|
|
3161
3718
|
throw new Error("Invalid systemMessage.transform payload");
|
|
3162
3719
|
}
|
|
3163
|
-
const session = this.
|
|
3720
|
+
const session = this.resolveSessionCallback(params.sessionId);
|
|
3164
3721
|
if (!session) {
|
|
3165
3722
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
3166
3723
|
}
|