@agentknight/sdk 0.9.35

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.
@@ -0,0 +1,1252 @@
1
+ import {
2
+ AgentSelectionSchema,
3
+ assertSelectionSnapshotFits
4
+ } from "./chunk-C7XDS2KG.js";
5
+ import {
6
+ isResearchSessionId
7
+ } from "./chunk-AHJSR7OY.js";
8
+ import {
9
+ GroupChatError
10
+ } from "./chunk-ATMWDEPF.js";
11
+ import {
12
+ AgentModelListingError,
13
+ AgentSessionRenameError,
14
+ SessionOperationError,
15
+ UnsupportedAgentOperationError,
16
+ captureWorkbenchAccess,
17
+ validateSessionName
18
+ } from "./chunk-SJHJER3B.js";
19
+ import {
20
+ InteractionResponseSchema,
21
+ SessionSummarySchema
22
+ } from "./chunk-ZKAN2FO7.js";
23
+
24
+ // src/managed-agent-adapter.ts
25
+ import { createHash, randomUUID } from "node:crypto";
26
+ import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
27
+ import { join } from "node:path";
28
+ import { z } from "zod";
29
+ var ManagedSessionIdSchema = z.string().regex(/^managed:(copilot|pi|acp):session:[a-f0-9]{64}$/);
30
+ var StoredSelectionSchema = AgentSelectionSchema.extend({ version: z.literal(1) });
31
+ var LegacySelectionSchema = AgentSelectionSchema.extend({
32
+ sessionIds: z.array(ManagedSessionIdSchema).max(2e3).refine((ids) => new Set(ids).size === ids.length, "Duplicate managed session selection")
33
+ });
34
+ var DeletedSessionsSchema = z.object({
35
+ version: z.literal(1),
36
+ deletedSessionIds: z.array(ManagedSessionIdSchema).refine((ids) => new Set(ids).size === ids.length, "Duplicate deleted managed session"),
37
+ confirmedDeletedSessionIds: z.array(ManagedSessionIdSchema).optional()
38
+ }).strict().refine((value) => (value.confirmedDeletedSessionIds ?? []).every((id) => value.deletedSessionIds.includes(id)), "Confirmed deletion requires durable deletion intent");
39
+ function digest(value) {
40
+ return createHash("sha256").update(value).digest("hex");
41
+ }
42
+ function sessionId(provider, nativeId) {
43
+ return `managed:${provider.candidate.id}:session:${digest(nativeId)}`;
44
+ }
45
+ function modelId(provider, nativeId) {
46
+ return `managed:${provider.candidate.id}:model:${digest(nativeId)}`;
47
+ }
48
+ function message(error) {
49
+ return error instanceof Error ? error.message : String(error);
50
+ }
51
+ function isMissing(error) {
52
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
53
+ }
54
+ function cancellation(request) {
55
+ switch (request.kind) {
56
+ case "permission":
57
+ return { kind: "permission", decision: "reject" };
58
+ case "exit_plan":
59
+ return { kind: "exit_plan", approved: false };
60
+ case "elicitation":
61
+ return { kind: "elicitation", action: "cancel", values: {} };
62
+ case "mcp_oauth":
63
+ return { kind: "mcp_oauth", action: "cancel" };
64
+ case "mcp_headers":
65
+ return { kind: "mcp_headers", action: "none" };
66
+ case "auto_mode_switch":
67
+ return { kind: "auto_mode_switch", action: "no" };
68
+ case "session_limits":
69
+ return { kind: "session_limits", action: "cancel" };
70
+ // There is no cancel response in the native user-input contract.
71
+ case "user_input":
72
+ return void 0;
73
+ }
74
+ }
75
+ var ManagedCodingAgentAdapter = class {
76
+ agentId = "managed";
77
+ workbench;
78
+ sdkVersion = "managed-local-agents";
79
+ #directory;
80
+ #localOwner;
81
+ #providers;
82
+ #initialSelection;
83
+ #epochs = /* @__PURE__ */ new Map();
84
+ #pending = /* @__PURE__ */ new Map();
85
+ #owned = /* @__PURE__ */ new Map();
86
+ #opening = /* @__PURE__ */ new Map();
87
+ #peerTurns = /* @__PURE__ */ new Map();
88
+ #watched = /* @__PURE__ */ new Set();
89
+ #modelListings = /* @__PURE__ */ new Map();
90
+ #deleted = /* @__PURE__ */ new Set();
91
+ #confirmedDeleted = /* @__PURE__ */ new Set();
92
+ #selection = { agents: [] };
93
+ #callbacks;
94
+ #queue = Promise.resolve();
95
+ #pendingExclusive = 0;
96
+ #pendingRenames = 0;
97
+ #upgradeRevision = 0;
98
+ #started = false;
99
+ #stopping = false;
100
+ #groupChatPreference;
101
+ #peerChatPreference;
102
+ #groupChatRevision = 0;
103
+ #pendingGroupOperations = 0;
104
+ constructor(options) {
105
+ this.#groupChatPreference = options.groupChatEnabled !== false;
106
+ this.#peerChatPreference = options.peerChatEnabled !== false;
107
+ this.#directory = options.dataDirectory;
108
+ this.#localOwner = SessionSummarySchema.pick({ userId: true, machineId: true }).parse(options.owner);
109
+ const ids = /* @__PURE__ */ new Set();
110
+ this.#providers = options.candidates.map((candidate) => {
111
+ if (!["copilot", "pi", "acp"].includes(candidate.id) || ids.has(candidate.id)) {
112
+ throw new Error(`Invalid or duplicate managed agent: ${candidate.id}`);
113
+ }
114
+ ids.add(candidate.id);
115
+ return {
116
+ candidate,
117
+ available: false,
118
+ runtime: void 0,
119
+ shutdown: void 0,
120
+ error: void 0,
121
+ nonce: "",
122
+ sessions: /* @__PURE__ */ new Map(),
123
+ models: /* @__PURE__ */ new Map(),
124
+ creating: false
125
+ };
126
+ });
127
+ this.#initialSelection = options.initialSelection === void 0 ? void 0 : this.#validateSelection(options.initialSelection);
128
+ if (options.candidates.some((candidate) => candidate.id === "copilot")) {
129
+ this.workbench = {
130
+ schedules: {
131
+ isAvailable: () => this.#selectedProviders().some((provider) => provider.candidate.id === "copilot" && provider.runtime?.workbench?.schedules !== void 0),
132
+ snapshot: (id) => this.#withWorkbench(id, (runtime, native) => {
133
+ if (runtime.schedules === void 0) throw new SessionOperationError("SCHEDULE_UNSUPPORTED", "Native schedules are unavailable");
134
+ return runtime.schedules.snapshot(native);
135
+ }, false),
136
+ resume: (id, assertAccess) => {
137
+ const check = this.captureWorkbenchAccess(id);
138
+ return this.#withWorkbench(id, (runtime, native) => {
139
+ if (runtime.schedules === void 0) throw new SessionOperationError("SCHEDULE_UNSUPPORTED", "Native schedules are unavailable");
140
+ return runtime.schedules.resume(native, () => {
141
+ check();
142
+ assertAccess?.();
143
+ });
144
+ });
145
+ },
146
+ create: (id, request, check) => this.#withWorkbench(id, (runtime, native) => {
147
+ if (runtime.schedules === void 0) throw new SessionOperationError("SCHEDULE_UNSUPPORTED", "Native schedules are unavailable");
148
+ return runtime.schedules.create(native, request, check);
149
+ }, false),
150
+ delete: (id, scheduleId, check) => this.#withWorkbench(id, (runtime, native) => {
151
+ if (runtime.schedules === void 0) throw new SessionOperationError("SCHEDULE_UNSUPPORTED", "Native schedules are unavailable");
152
+ return runtime.schedules.delete(native, scheduleId, check);
153
+ }, false),
154
+ dismissDraft: (id, draftId, check) => this.#withWorkbench(id, (runtime, native) => {
155
+ if (runtime.schedules === void 0) throw new SessionOperationError("SCHEDULE_UNSUPPORTED", "Native schedules are unavailable");
156
+ return runtime.schedules.dismissDraft(native, draftId, check);
157
+ }, false)
158
+ },
159
+ ask: (id, question) => this.#withWorkbench(id, (runtime, native) => runtime.ask(native, question)),
160
+ commands: (id) => this.#withWorkbench(id, (runtime, native) => runtime.commands(native)),
161
+ invokeCommand: (id, name, input, operationId) => this.#withWorkbench(id, (runtime, native) => runtime.invokeCommand(native, name, input, operationId)),
162
+ queue: (id) => this.#withWorkbench(id, (runtime, native) => runtime.queue(native)),
163
+ mutateQueue: (id, request) => this.#withWorkbench(id, (runtime, native) => runtime.mutateQueue(native, request)),
164
+ insights: (id) => this.#withWorkbench(id, (runtime, native) => runtime.insights(native)),
165
+ compact: (id) => this.#withWorkbench(id, (runtime, native) => runtime.compact(native))
166
+ };
167
+ }
168
+ }
169
+ captureWorkbenchAccess(id) {
170
+ const route = this.#route(id);
171
+ const check = captureWorkbenchAccess(route.runtime, route.nativeId);
172
+ return () => {
173
+ this.#assertRoute(route);
174
+ check();
175
+ };
176
+ }
177
+ async renameSession(id, name, assertAccess) {
178
+ const requested = validateSessionName(name);
179
+ assertAccess?.();
180
+ if (!this.isSessionVisible(id)) {
181
+ throw new AgentSessionRenameError("SESSION_NOT_SHARED", "This session is no longer shared.");
182
+ }
183
+ const route = this.#route(id);
184
+ const check = () => {
185
+ assertAccess?.();
186
+ if (!this.#validRoute(route) || route.runtime.isSessionVisible?.(route.nativeId) === false) {
187
+ throw new AgentSessionRenameError("SESSION_NOT_SHARED", "Session access changed during rename.");
188
+ }
189
+ if (route.provider.sessions.get(id)?.renameSupported !== true || route.runtime.renameSession === void 0) {
190
+ throw new AgentSessionRenameError("AGENT_OPERATION_UNSUPPORTED", "This session does not support native rename.");
191
+ }
192
+ };
193
+ check();
194
+ const rename2 = route.runtime.renameSession;
195
+ if (rename2 === void 0) throw new UnsupportedAgentOperationError(route.provider.candidate.id, "session rename");
196
+ this.#upgradeRevision += 1;
197
+ this.#pendingRenames += 1;
198
+ try {
199
+ const confirmed = await rename2.call(route.runtime, route.nativeId, requested, check);
200
+ check();
201
+ if (confirmed !== requested) {
202
+ throw new AgentSessionRenameError("SESSION_RENAME_FAILED", "The provider did not confirm the requested session name.");
203
+ }
204
+ this.#callbacks?.onSessionInventoryChanged(id);
205
+ return confirmed;
206
+ } finally {
207
+ this.#pendingRenames -= 1;
208
+ }
209
+ }
210
+ captureResearchAccess(id) {
211
+ if (!this.#deleted.has(id)) return { detached: false, assertCurrent: this.captureWorkbenchAccess(id) };
212
+ const provider = this.#owner(id);
213
+ const runtime = provider?.runtime;
214
+ const selection = this.#selection;
215
+ const assertCurrent = () => {
216
+ if (!this.#started || this.#stopping || provider?.candidate.id !== "copilot" || runtime === void 0 || provider.runtime !== runtime || !provider.available || this.#selection !== selection || !this.#selection.agents.includes("copilot") || !this.#confirmedDeleted.has(id)) {
217
+ throw new SessionOperationError("SESSION_NOT_SHARED", "The research source provider is not shared or deletion is unconfirmed");
218
+ }
219
+ };
220
+ assertCurrent();
221
+ return { detached: true, assertCurrent };
222
+ }
223
+ async #withWorkbench(id, operation, owns = true) {
224
+ const check = this.captureWorkbenchAccess(id);
225
+ return this.#withSession(id, (route) => {
226
+ check();
227
+ const runtime = route.runtime.workbench;
228
+ if (runtime === void 0) throw new UnsupportedAgentOperationError(route.provider.candidate.id, "workbench");
229
+ return operation(runtime, route.nativeId);
230
+ }, owns);
231
+ }
232
+ get agentModes() {
233
+ return this.getSessionCapabilities().agentModes;
234
+ }
235
+ get supportsReasoningEffort() {
236
+ return this.getSessionCapabilities().supportsReasoningEffort;
237
+ }
238
+ get supportsSessionListUpdates() {
239
+ return this.#everySelected((runtime) => runtime.supportsSessionListUpdates === true);
240
+ }
241
+ get supportsAssistantPreview() {
242
+ return this.#everySelected((runtime) => runtime.supportsAssistantPreview === true);
243
+ }
244
+ get supportsSessionDelete() {
245
+ return this.#selectedProviders().some(({ runtime }) => runtime?.supportsSessionDelete === true && runtime.deleteSession !== void 0);
246
+ }
247
+ get supportsFileAttachments() {
248
+ return this.#everySelected((runtime) => runtime.supportsFileAttachments === true);
249
+ }
250
+ get groupChatEnabled() {
251
+ return this.#groupChatPreference && this.#selectedProviders().some((provider) => provider.candidate.id === "copilot" && provider.runtime?.groupChatEnabled === true && provider.runtime.createGroupChat !== void 0);
252
+ }
253
+ async setGroupChatEnabled(enabled) {
254
+ this.#groupChatPreference = enabled;
255
+ this.#groupChatRevision += 1;
256
+ this.#upgradeRevision += 1;
257
+ this.#pendingGroupOperations += 1;
258
+ try {
259
+ const updates = this.#providers.filter((provider) => provider.candidate.id === "copilot").map((provider) => provider.runtime?.setGroupChatEnabled?.(enabled));
260
+ this.#inventoryChanged();
261
+ await Promise.all(updates);
262
+ } finally {
263
+ this.#pendingGroupOperations -= 1;
264
+ this.#inventoryChanged();
265
+ }
266
+ }
267
+ async createGroupChat(sourceId, peerId, requestId, assertAccess) {
268
+ if (!this.groupChatEnabled) throw new GroupChatError("GROUP_CHAT_UNAVAILABLE", "Group chat requires an enabled, selected Copilot provider");
269
+ const source = this.#route(sourceId);
270
+ const peer = this.#route(peerId);
271
+ const revision = this.#groupChatRevision;
272
+ const check = () => {
273
+ assertAccess?.();
274
+ if (!this.groupChatEnabled || revision !== this.#groupChatRevision || !this.#validRoute(source) || !this.#validRoute(peer) || source.runtime.isSessionVisible?.(source.nativeId) === false || peer.runtime.isSessionVisible?.(peer.nativeId) === false) {
275
+ throw new GroupChatError("GROUP_CHAT_UNAVAILABLE", "Group participant access changed; create a new group request");
276
+ }
277
+ if (sourceId === peerId || source.provider !== peer.provider || source.runtime !== peer.runtime || source.provider.candidate.id !== "copilot" || [source, peer].some((route) => isResearchSessionId(route.nativeId) || route.provider.sessions.get(route.sessionId)?.groupChat !== void 0)) {
278
+ throw new GroupChatError("GROUP_CHAT_PARTICIPANT_UNSUPPORTED", "Choose two different ordinary sessions from the same Copilot provider");
279
+ }
280
+ };
281
+ check();
282
+ const create = source.runtime.createGroupChat;
283
+ if (create === void 0) throw new GroupChatError("GROUP_CHAT_UNAVAILABLE", "This provider does not support group chat");
284
+ this.#upgradeRevision += 1;
285
+ this.#pendingGroupOperations += 1;
286
+ try {
287
+ const native = await create.call(source.runtime, source.nativeId, peer.nativeId, requestId, check);
288
+ check();
289
+ this.#nativeId(native.sessionId);
290
+ const id = sessionId(source.provider, native.sessionId);
291
+ if (native.groupChat === void 0 || this.#deleted.has(id)) {
292
+ throw new GroupChatError("GROUP_CHAT_UNAVAILABLE", "The group is no longer available");
293
+ }
294
+ const summary = this.#summary(id, native, this.#localOwner.userId, this.#localOwner.machineId);
295
+ assertSelectionSnapshotFits([
296
+ ...this.#selectedSummaries(this.#selection).filter((session) => session.sessionId !== id),
297
+ summary
298
+ ]);
299
+ source.provider.sessions.set(id, native);
300
+ this.#owned.set(id, { ...source, sessionId: id, nativeId: native.sessionId, epoch: this.#epoch(id) });
301
+ this.#inventoryChanged(id);
302
+ return summary;
303
+ } finally {
304
+ this.#pendingGroupOperations -= 1;
305
+ }
306
+ }
307
+ async setPeerChatEnabled(enabled) {
308
+ this.#peerChatPreference = enabled;
309
+ this.#upgradeRevision += 1;
310
+ if (!enabled) {
311
+ for (const { controller } of this.#peerTurns.values()) {
312
+ controller.abort(new Error("Peer chat is disabled"));
313
+ }
314
+ }
315
+ this.#pendingGroupOperations += 1;
316
+ try {
317
+ const updates = this.#providers.filter((provider) => provider.candidate.id === "copilot").map((provider) => provider.runtime?.setPeerChatEnabled?.(enabled));
318
+ this.#inventoryChanged();
319
+ await Promise.all(updates);
320
+ } finally {
321
+ this.#pendingGroupOperations -= 1;
322
+ this.#inventoryChanged();
323
+ }
324
+ }
325
+ async runPeerTurn(id, prompt, options) {
326
+ if (!this.#peerChatPreference) throw new Error("Peer chat is disabled");
327
+ options.signal.throwIfAborted();
328
+ const route = this.#route(id);
329
+ const run = route.runtime.runPeerTurn;
330
+ if (route.provider.candidate.id !== "copilot" || run === void 0 || isResearchSessionId(route.nativeId) || route.provider.sessions.get(id)?.groupChat !== void 0) {
331
+ throw new UnsupportedAgentOperationError(route.provider.candidate.id, "peer session turns");
332
+ }
333
+ if (this.#peerTurns.has(id)) throw new Error("A peer turn is already running in this session");
334
+ const controller = new AbortController();
335
+ const cancel = () => controller.abort(options.signal.reason);
336
+ const check = () => {
337
+ controller.signal.throwIfAborted();
338
+ if (!this.#peerChatPreference) throw new Error("Peer chat is disabled");
339
+ this.#assertRoute(route);
340
+ if (route.runtime.isSessionVisible?.(route.nativeId) === false) throw new Error("Peer session is no longer shared");
341
+ };
342
+ check();
343
+ options.signal.addEventListener("abort", cancel, { once: true });
344
+ this.#peerTurns.set(id, { route, controller });
345
+ this.#upgradeRevision += 1;
346
+ const requestPeer = options.requestPeer;
347
+ try {
348
+ const reply = await run.call(route.runtime, route.nativeId, prompt, {
349
+ signal: controller.signal,
350
+ ...options.awaitNativeSettlement === true ? { awaitNativeSettlement: true } : {},
351
+ ...requestPeer === void 0 ? {} : {
352
+ requestPeer: async (content) => {
353
+ check();
354
+ const result = await requestPeer(content);
355
+ check();
356
+ return result;
357
+ }
358
+ }
359
+ });
360
+ check();
361
+ return reply;
362
+ } finally {
363
+ options.signal.removeEventListener("abort", cancel);
364
+ this.#peerTurns.delete(id);
365
+ }
366
+ }
367
+ getSessionCapabilities(id) {
368
+ if (id !== void 0) {
369
+ const route = this.#route(id);
370
+ return {
371
+ supportsFileAttachments: route.runtime.supportsFileAttachments === true,
372
+ ...route.runtime.getSessionCapabilities?.(route.nativeId) ?? {
373
+ agentModes: route.runtime.agentModes,
374
+ supportsReasoningEffort: route.runtime.supportsReasoningEffort
375
+ },
376
+ agentName: route.provider.candidate.name
377
+ };
378
+ }
379
+ return {
380
+ agentModes: ["interactive"],
381
+ supportsReasoningEffort: this.#everySelected((runtime) => runtime.supportsReasoningEffort),
382
+ supportsFileAttachments: this.supportsFileAttachments
383
+ };
384
+ }
385
+ isSessionVisible(id) {
386
+ if (!this.#started || this.#stopping || this.#deleted.has(id)) return false;
387
+ return this.#providers.some((provider) => provider.available && provider.runtime !== void 0 && this.#selection.agents.includes(provider.candidate.id) && provider.sessions.has(id));
388
+ }
389
+ setSessionCallbacks(callbacks) {
390
+ this.#callbacks = callbacks;
391
+ }
392
+ start() {
393
+ if (!this.#started) this.#stopping = false;
394
+ return this.#exclusive(() => this.#start());
395
+ }
396
+ async #start() {
397
+ if (this.#started) return;
398
+ if (this.#stopping) throw new Error("Managed agents are stopping");
399
+ let selection = {
400
+ agents: this.#providers.filter((provider) => provider.candidate.id === "copilot").map((provider) => provider.candidate.id)
401
+ };
402
+ let migrate = false;
403
+ try {
404
+ const stored = z.union([StoredSelectionSchema, LegacySelectionSchema]).parse(JSON.parse(await readFile(this.#selectionPath(), "utf8")));
405
+ selection = this.#validateSelection({ agents: stored.agents });
406
+ if ("sessionIds" in stored) {
407
+ for (const id of stored.sessionIds) {
408
+ const provider = this.#owner(id);
409
+ if (!provider || !selection.agents.includes(provider.candidate.id)) {
410
+ throw new Error(`Managed session requires its selected owner: ${id}`);
411
+ }
412
+ }
413
+ migrate = true;
414
+ }
415
+ } catch (error) {
416
+ if (!isMissing(error)) {
417
+ throw new Error(`Unable to load managed agent selection at ${this.#selectionPath()}: ${message(error)}`, { cause: error });
418
+ }
419
+ }
420
+ let deleted = /* @__PURE__ */ new Set();
421
+ let confirmedDeleted = /* @__PURE__ */ new Set();
422
+ try {
423
+ const stored = DeletedSessionsSchema.parse(JSON.parse(await readFile(this.#deletedPath(), "utf8")));
424
+ deleted = new Set(stored.deletedSessionIds);
425
+ confirmedDeleted = new Set(stored.confirmedDeletedSessionIds ?? []);
426
+ } catch (error) {
427
+ if (!isMissing(error)) {
428
+ throw new Error(`Unable to load managed session deletions at ${this.#deletedPath()}: ${message(error)}`, { cause: error });
429
+ }
430
+ }
431
+ if (migrate) await this.#persist(selection);
432
+ this.#selection = this.#initialSelection ?? selection;
433
+ this.#deleted = deleted;
434
+ this.#confirmedDeleted = confirmedDeleted;
435
+ this.#started = true;
436
+ await this.#scan();
437
+ }
438
+ stop() {
439
+ this.#stopping = true;
440
+ for (const peer of this.#peerTurns.values()) peer.controller.abort(new Error("Managed adapter stopped"));
441
+ return this.#exclusive(async () => {
442
+ this.#started = false;
443
+ const results = await Promise.allSettled(this.#providers.map((provider) => this.#retire(provider)));
444
+ this.#pending.clear();
445
+ this.#owned.clear();
446
+ this.#watched.clear();
447
+ const errors = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
448
+ if (errors.length) throw new AggregateError(errors, "Managed agent shutdown failed");
449
+ });
450
+ }
451
+ async canUpgrade() {
452
+ const revision = this.#upgradeRevision;
453
+ if (this.#hasUpgradeWork()) return false;
454
+ const runtimes = this.#providers.flatMap((provider) => provider.runtime === void 0 ? [] : [provider.runtime]);
455
+ const ready = await Promise.all(runtimes.map((runtime) => runtime.canUpgrade?.()));
456
+ return ready.every((value) => value === true) && revision === this.#upgradeRevision && !this.#hasUpgradeWork();
457
+ }
458
+ #hasUpgradeWork() {
459
+ return !this.#started || this.#stopping || this.#pendingExclusive > 0 || this.#pendingRenames > 0 || this.#pendingGroupOperations > 0 || this.#peerTurns.size > 0 || this.#pending.size > 0 || this.#opening.size > 0 || this.#providers.some((provider) => provider.creating || provider.shutdown !== void 0);
460
+ }
461
+ listAgents() {
462
+ return this.#exclusive(async () => {
463
+ if (!this.#started) await this.#start();
464
+ else await this.#scan();
465
+ return this.#providers.map((provider) => {
466
+ const sessions = [...provider.sessions].map(([id, summary]) => this.#selectableSession(id, summary));
467
+ return {
468
+ id: provider.candidate.id,
469
+ name: provider.candidate.name,
470
+ available: provider.available && provider.runtime !== void 0,
471
+ selected: this.#selection.agents.includes(provider.candidate.id),
472
+ ...provider.error ? { error: provider.error } : {},
473
+ sessions
474
+ };
475
+ });
476
+ });
477
+ }
478
+ async saveSelection(input) {
479
+ const selection = AgentSelectionSchema.parse(input);
480
+ return this.#exclusive(async () => {
481
+ await this.#start();
482
+ this.#assertStarted();
483
+ await this.#scan();
484
+ this.#validateSelection(selection);
485
+ assertSelectionSnapshotFits(this.#selectedSummaries(selection));
486
+ await this.#persist(selection);
487
+ await this.#apply(selection);
488
+ });
489
+ }
490
+ listSessions(userId, machineId) {
491
+ return this.#exclusive(async () => {
492
+ this.#assertStarted();
493
+ await this.#scan();
494
+ const sessions = this.#selectedSummaries(this.#selection, { userId, machineId });
495
+ assertSelectionSnapshotFits(sessions);
496
+ return sessions;
497
+ });
498
+ }
499
+ async listModels(id) {
500
+ const listing = await this.#exclusive(async () => {
501
+ this.#assertStarted();
502
+ const route = id === void 0 ? void 0 : this.#route(id);
503
+ const providers = route ? [route.provider] : this.#selectedProviders();
504
+ const snapshot = {
505
+ route,
506
+ selection: this.#selection,
507
+ sources: providers.map((provider) => {
508
+ const runtime = provider.runtime;
509
+ if (!runtime) throw new Error("Agent disconnected while listing models");
510
+ return { provider, runtime, nonce: provider.nonce };
511
+ })
512
+ };
513
+ const key = id ?? "";
514
+ const pending = this.#modelListings.get(key);
515
+ if (pending && pending.snapshot.selection === snapshot.selection && pending.snapshot.route?.epoch === route?.epoch && pending.snapshot.sources.length === snapshot.sources.length && pending.snapshot.sources.every((source, index) => source.provider === snapshot.sources[index]?.provider && source.runtime === snapshot.sources[index]?.runtime && source.nonce === snapshot.sources[index]?.nonce)) {
516
+ return { work: pending.work };
517
+ }
518
+ const work = this.#listModels(snapshot).finally(() => {
519
+ if (this.#modelListings.get(key)?.work === work) this.#modelListings.delete(key);
520
+ });
521
+ this.#modelListings.set(key, { snapshot, work });
522
+ return { work };
523
+ });
524
+ return listing.work;
525
+ }
526
+ async #listModels(snapshot) {
527
+ const results = await Promise.all(snapshot.sources.map(async (source) => {
528
+ try {
529
+ const nativeModels = await source.runtime.listModels(snapshot.route?.nativeId);
530
+ const entries = nativeModels.map((model) => {
531
+ this.#nativeId(model.id);
532
+ const id = modelId(source.provider, model.id);
533
+ const { creation: _creation, ...nativeModel } = model;
534
+ return {
535
+ nativeId: model.id,
536
+ model: {
537
+ ...nativeModel,
538
+ id,
539
+ name: (snapshot.route ? model.name : `${source.provider.candidate.name} \xB7 ${model.name}`).slice(0, 260),
540
+ ...snapshot.route ? {} : {
541
+ creation: {
542
+ agentId: source.provider.candidate.id,
543
+ agentName: source.provider.candidate.name,
544
+ modelName: model.name,
545
+ agentModes: [...source.runtime.agentModes],
546
+ reasoningEffortConfigurationSupported: source.runtime.supportsReasoningEffort,
547
+ isDefault: model.isDefault
548
+ }
549
+ }
550
+ }
551
+ };
552
+ });
553
+ return { ok: true, source, entries };
554
+ } catch (error) {
555
+ return { ok: false, source, error: new Error(
556
+ `${source.provider.candidate.name} model refresh failed: ${message(error)}`,
557
+ { cause: error }
558
+ ) };
559
+ }
560
+ }));
561
+ return this.#exclusive(async () => {
562
+ this.#assertStarted();
563
+ if (this.#selection !== snapshot.selection) throw new Error("Agent selection changed while listing models");
564
+ if (snapshot.route) this.#assertRoute(snapshot.route);
565
+ for (const { source } of results) {
566
+ if (source.provider.runtime !== source.runtime || source.provider.nonce !== source.nonce) {
567
+ throw new Error("Agent disconnected while listing models");
568
+ }
569
+ }
570
+ const models = [];
571
+ const errors = [];
572
+ for (const result of results) {
573
+ if (result.ok) {
574
+ for (const { nativeId, model } of result.entries) result.source.provider.models.set(model.id, nativeId);
575
+ models.push(...result.entries.map(({ model }) => model));
576
+ } else {
577
+ errors.push(result.error);
578
+ }
579
+ }
580
+ let hasDefault = false;
581
+ const available = models.map((model) => {
582
+ const isDefault = model.isDefault && !hasDefault;
583
+ hasDefault ||= isDefault;
584
+ return { ...model, isDefault };
585
+ });
586
+ if (errors.length) throw new AgentModelListingError(errors, available);
587
+ return available;
588
+ });
589
+ }
590
+ getSessionConfiguration(id) {
591
+ return this.#withSession(id, async (route) => this.#configuration(route.provider, await route.runtime.getSessionConfiguration(route.nativeId)), true);
592
+ }
593
+ getSessionEnvironment(id) {
594
+ return this.#withSession(id, (route) => route.runtime.getSessionEnvironment(route.nativeId), true);
595
+ }
596
+ getSessionDirectoryMetadata(id) {
597
+ return this.#withSession(id, (route) => {
598
+ if (!route.runtime.getSessionDirectoryMetadata) {
599
+ throw new UnsupportedAgentOperationError(route.provider.candidate.id, "session directory metadata");
600
+ }
601
+ return route.runtime.getSessionDirectoryMetadata(route.nativeId);
602
+ });
603
+ }
604
+ getSessionProjectIdentity(id) {
605
+ return this.#withSession(id, (route) => {
606
+ if (!route.runtime.getSessionProjectIdentity) {
607
+ throw new UnsupportedAgentOperationError(route.provider.candidate.id, "session project identity");
608
+ }
609
+ return route.runtime.getSessionProjectIdentity(route.nativeId);
610
+ });
611
+ }
612
+ createSession(options) {
613
+ return this.#exclusive(async () => {
614
+ this.#assertStarted();
615
+ await this.#scan();
616
+ const provider = this.#selectedProviders().find((entry) => entry.models.has(options.modelId));
617
+ const nativeModelId = provider?.models.get(options.modelId);
618
+ const runtime = provider?.runtime;
619
+ if (!provider || !runtime || nativeModelId === void 0) throw new Error("Select an available managed agent model before creating a session");
620
+ if (!runtime.agentModes.includes(options.agentMode)) throw new UnsupportedAgentOperationError(provider.candidate.id, `creation agent mode ${options.agentMode}`);
621
+ assertSelectionSnapshotFits(this.#selectedSummaries(this.#selection));
622
+ provider.creating = true;
623
+ let native;
624
+ try {
625
+ native = await runtime.createSession({ ...options, ...this.#localOwner, modelId: nativeModelId });
626
+ } finally {
627
+ provider.creating = false;
628
+ }
629
+ this.#nativeId(native.sessionId);
630
+ const id = sessionId(provider, native.sessionId);
631
+ if (provider.sessions.has(id) || this.#deleted.has(id)) throw new Error("Agent returned an existing session ID while creating a session");
632
+ const route = { provider, runtime, sessionId: id, nativeId: native.sessionId, epoch: this.#epoch(id) };
633
+ this.#owned.set(id, route);
634
+ try {
635
+ this.#assertStarted();
636
+ if (provider.runtime !== runtime) throw new Error("Agent disconnected while creating a session");
637
+ const summary = this.#summary(id, native, options.userId, options.machineId);
638
+ assertSelectionSnapshotFits([
639
+ ...this.#selectedSummaries(this.#selection),
640
+ this.#summary(id, native, this.#localOwner.userId, this.#localOwner.machineId)
641
+ ]);
642
+ provider.sessions.set(id, native);
643
+ this.#assertRoute(route);
644
+ this.#inventoryChanged();
645
+ return summary;
646
+ } catch (error) {
647
+ await this.#release(route).catch(async (cleanup) => {
648
+ await this.#failed(provider, cleanup);
649
+ throw new AggregateError([error, cleanup], "Unable to publish or cancel the newly created managed session");
650
+ });
651
+ throw error;
652
+ }
653
+ });
654
+ }
655
+ configureSession(id, options) {
656
+ return this.#withSession(id, async (route) => {
657
+ const nativeModelId = options.modelId === void 0 ? void 0 : route.provider.models.get(options.modelId);
658
+ if (options.modelId !== void 0 && nativeModelId === void 0) {
659
+ throw new Error("Model is unavailable or belongs to another managed agent");
660
+ }
661
+ const nativeOptions = { ...options, ...nativeModelId === void 0 ? {} : { modelId: nativeModelId } };
662
+ return this.#configuration(route.provider, await route.runtime.configureSession(route.nativeId, nativeOptions));
663
+ }, true);
664
+ }
665
+ deleteSession(id) {
666
+ return this.#exclusive(async () => {
667
+ const route = this.#route(id);
668
+ if (!route.runtime.supportsSessionDelete || !route.runtime.deleteSession) {
669
+ throw new UnsupportedAgentOperationError(route.provider.candidate.id, "session deletion");
670
+ }
671
+ const deleted = /* @__PURE__ */ new Set([...this.#deleted, id]);
672
+ await this.#persistFile(this.#deletedPath(), {
673
+ version: 1,
674
+ deletedSessionIds: [...deleted],
675
+ ...this.#confirmedDeleted.size === 0 ? {} : { confirmedDeletedSessionIds: [...this.#confirmedDeleted] }
676
+ });
677
+ this.#deleted = deleted;
678
+ this.#epochs.set(id, this.#epoch(id) + 1);
679
+ this.#inventoryChanged();
680
+ try {
681
+ this.#assertStarted();
682
+ if (route.provider.runtime !== route.runtime) throw new Error("Agent disconnected while deleting a session");
683
+ await route.runtime.deleteSession(route.nativeId);
684
+ const confirmed = /* @__PURE__ */ new Set([...this.#confirmedDeleted, id]);
685
+ await this.#persistFile(this.#deletedPath(), {
686
+ version: 1,
687
+ deletedSessionIds: [...this.#deleted],
688
+ confirmedDeletedSessionIds: [...confirmed]
689
+ });
690
+ this.#confirmedDeleted = confirmed;
691
+ } catch (error) {
692
+ await this.#revoke(id);
693
+ throw error;
694
+ }
695
+ for (const [requestId, pending] of this.#pending) {
696
+ if (pending.route.sessionId === id) this.#pending.delete(requestId);
697
+ }
698
+ this.#owned.delete(id);
699
+ this.#watched.delete(id);
700
+ route.provider.sessions.delete(id);
701
+ this.#inventoryChanged();
702
+ });
703
+ }
704
+ getSessionRemoteStatus(id) {
705
+ return this.#withSession(id, async (route) => this.#remoteStatus(route, await route.runtime.getSessionRemoteStatus(route.nativeId)));
706
+ }
707
+ setSessionRemote(id, enabled) {
708
+ return this.#withSession(id, async (route) => {
709
+ const current = await route.runtime.getSessionRemoteStatus(route.nativeId);
710
+ this.#assertRoute(route);
711
+ if (current.attachedSessionId !== void 0 && !this.isSessionVisible(sessionId(route.provider, current.attachedSessionId))) {
712
+ throw new Error("The agent's attached remote session is not selected");
713
+ }
714
+ return this.#remoteStatus(route, await route.runtime.setSessionRemote(route.nativeId, enabled));
715
+ }, enabled);
716
+ }
717
+ readSessionTimelinePage(id, options) {
718
+ return this.#withSession(id, async (route) => {
719
+ const page = await route.runtime.readSessionTimelinePage(route.nativeId, options);
720
+ return { ...page, items: page.items.map((item) => this.#item(route, item)) };
721
+ });
722
+ }
723
+ readSessionPlan(id) {
724
+ return this.#withSession(id, async (route) => {
725
+ if (route.runtime.readSessionPlan) {
726
+ try {
727
+ const item = await route.runtime.readSessionPlan(route.nativeId);
728
+ return this.#item(route, item);
729
+ } catch (error) {
730
+ if (!(error instanceof UnsupportedAgentOperationError)) throw error;
731
+ }
732
+ }
733
+ return {
734
+ id: `plan:${id}`,
735
+ kind: "plan-update",
736
+ revision: 1,
737
+ turnId: id,
738
+ timestamp: (/* @__PURE__ */ new Date(0)).toISOString(),
739
+ sourceEventIds: [`plan:${id}`],
740
+ actor: "assistant",
741
+ operation: "deleted",
742
+ content: "",
743
+ todos: []
744
+ };
745
+ });
746
+ }
747
+ watchSession(id, watched) {
748
+ return this.#withSession(id, async (route) => {
749
+ await route.runtime.watchSession(route.nativeId, watched);
750
+ if (watched) this.#watched.add(id);
751
+ else this.#watched.delete(id);
752
+ }, watched);
753
+ }
754
+ sendPrompt(id, prompt, mode, attachments, persistInteractions, fileAttachments, operationId) {
755
+ return this.#withSession(id, (route) => {
756
+ if (fileAttachments?.length && !this.getSessionCapabilities(id).supportsFileAttachments) {
757
+ throw new UnsupportedAgentOperationError(route.provider.candidate.id, "file attachments");
758
+ }
759
+ return route.runtime.sendPrompt(
760
+ route.nativeId,
761
+ prompt,
762
+ mode,
763
+ attachments,
764
+ persistInteractions,
765
+ fileAttachments,
766
+ ...operationId === void 0 ? [] : [operationId]
767
+ );
768
+ }, true);
769
+ }
770
+ abortSession(id) {
771
+ return this.#withSession(id, async (route) => {
772
+ if (this.#owned.has(id)) await route.runtime.abortSession(route.nativeId);
773
+ });
774
+ }
775
+ respondPermission(id, decision, feedback) {
776
+ if (decision !== "approve-once" && decision !== "reject") return false;
777
+ const pending = this.#pending.get(id);
778
+ if (!pending || pending.kind !== "permission" || !this.#validRoute(pending.route)) return false;
779
+ const accepted = pending.route.runtime.respondPermission(pending.nativeId, decision, feedback);
780
+ if (accepted) this.#pending.delete(id);
781
+ return accepted;
782
+ }
783
+ async respondInteraction(id, input) {
784
+ const response = InteractionResponseSchema.parse(input);
785
+ const pending = this.#pending.get(id);
786
+ if (!pending || pending.kind !== "interaction" || !this.#validRoute(pending.route) || pending.request?.kind !== response.kind) return false;
787
+ if (response.kind === "permission" && pending.request.kind === "permission" && (response.decision === "approve-for-session" && !pending.request.canApproveSession || response.decision === "approve-always" && pending.request.canApproveAlways !== true)) {
788
+ throw new Error("The agent did not offer this permission approval scope");
789
+ }
790
+ const accepted = await pending.route.runtime.respondInteraction(pending.nativeId, response);
791
+ if (accepted) this.#pending.delete(id);
792
+ return accepted && this.#validRoute(pending.route);
793
+ }
794
+ replayPendingInteractions() {
795
+ if (!this.#started || this.#stopping) return;
796
+ for (const provider of this.#selectedProviders()) provider.runtime?.replayPendingInteractions();
797
+ }
798
+ #exclusive(operation) {
799
+ this.#pendingExclusive += 1;
800
+ this.#upgradeRevision += 1;
801
+ const result = this.#queue.then(operation);
802
+ const settled = result.finally(() => {
803
+ this.#pendingExclusive -= 1;
804
+ });
805
+ this.#queue = settled.then(() => void 0, () => void 0);
806
+ return settled;
807
+ }
808
+ #assertStarted() {
809
+ if (!this.#started || this.#stopping) throw new Error("Managed agents are not running");
810
+ }
811
+ #inventoryChanged(id = "selection") {
812
+ if (this.#started && !this.#stopping) this.#callbacks?.onSessionInventoryChanged(id);
813
+ }
814
+ #selectionPath() {
815
+ return join(this.#directory, "agent-selection.json");
816
+ }
817
+ #deletedPath() {
818
+ return join(this.#directory, "managed-session-deletions.json");
819
+ }
820
+ #owner(id) {
821
+ const match = /^managed:(copilot|pi|acp):session:[a-f0-9]{64}$/.exec(id);
822
+ return match ? this.#providers.find((provider) => provider.candidate.id === match[1]) : void 0;
823
+ }
824
+ #validateSelection(input) {
825
+ const selection = AgentSelectionSchema.parse(input);
826
+ for (const id of selection.agents) {
827
+ if (!this.#providers.some((provider) => provider.candidate.id === id)) throw new Error(`Unknown managed agent: ${id}`);
828
+ }
829
+ return selection;
830
+ }
831
+ async #persist(selection) {
832
+ this.#validateSelection(selection);
833
+ await this.#persistFile(this.#selectionPath(), { version: 1, ...selection });
834
+ }
835
+ async #persistFile(path, value) {
836
+ await mkdir(this.#directory, { recursive: true, mode: 448 });
837
+ const staging = `${path}.${randomUUID()}.pending`;
838
+ try {
839
+ const file = await open(staging, "wx", 384);
840
+ try {
841
+ await file.writeFile(`${JSON.stringify(value)}
842
+ `, "utf8");
843
+ await file.sync();
844
+ } finally {
845
+ await file.close();
846
+ }
847
+ await rename(staging, path);
848
+ } catch (error) {
849
+ throw new Error(`Unable to persist managed agent state at ${path}: ${message(error)}`, { cause: error });
850
+ } finally {
851
+ await rm(staging, { force: true });
852
+ }
853
+ }
854
+ async #apply(selection) {
855
+ const removed = this.#providers.filter((provider) => this.#selection.agents.includes(provider.candidate.id) && !selection.agents.includes(provider.candidate.id)).flatMap((provider) => [...provider.sessions.keys()]);
856
+ this.#selection = selection;
857
+ for (const id of removed) this.#epochs.set(id, this.#epoch(id) + 1);
858
+ this.#inventoryChanged();
859
+ await Promise.all(removed.map((id) => this.#revoke(id)));
860
+ }
861
+ #selectedProviders() {
862
+ if (!this.#started || this.#stopping) return [];
863
+ return this.#providers.filter((provider) => provider.available && provider.runtime && this.#selection.agents.includes(provider.candidate.id));
864
+ }
865
+ #selectedSummaries(selection, owner = this.#localOwner) {
866
+ if (!this.#started || this.#stopping) return [];
867
+ return this.#providers.filter((provider) => provider.available && provider.runtime && selection.agents.includes(provider.candidate.id)).flatMap((provider) => [...provider.sessions].filter(([id]) => !this.#deleted.has(id)).map(([id, summary]) => this.#summary(id, summary, owner.userId, owner.machineId))).sort((a, b) => Date.parse(b.modifiedAt) - Date.parse(a.modifiedAt) || (a.sessionId < b.sessionId ? -1 : a.sessionId > b.sessionId ? 1 : 0));
868
+ }
869
+ #everySelected(predicate) {
870
+ const providers = this.#selectedProviders();
871
+ return providers.length > 0 && providers.length === this.#selection.agents.length && providers.every((provider) => provider.runtime && predicate(provider.runtime));
872
+ }
873
+ async #scan() {
874
+ this.#assertStarted();
875
+ const confirmed = new Set(this.#confirmedDeleted);
876
+ await Promise.all(this.#providers.map(async (provider) => {
877
+ try {
878
+ await provider.shutdown;
879
+ const available = await provider.candidate.detect();
880
+ if (!available) {
881
+ provider.available = false;
882
+ provider.error = "Agent is not installed or configured";
883
+ await this.#retire(provider);
884
+ return;
885
+ }
886
+ provider.available = true;
887
+ let runtime = provider.runtime;
888
+ if (!runtime) {
889
+ runtime = await provider.candidate.create();
890
+ provider.runtime = runtime;
891
+ provider.nonce = randomUUID();
892
+ if (runtime.agentId !== provider.candidate.id) throw new Error("Managed agent factory returned the wrong provider");
893
+ runtime.setSessionCallbacks(this.#providerCallbacks(provider, runtime));
894
+ if (provider.candidate.id === "copilot") await runtime.setGroupChatEnabled?.(this.#groupChatPreference);
895
+ if (provider.candidate.id === "copilot") await runtime.setPeerChatEnabled?.(this.#peerChatPreference);
896
+ await runtime.start();
897
+ if (provider.candidate.id === "copilot") await runtime.setGroupChatEnabled?.(this.#groupChatPreference);
898
+ if (provider.candidate.id === "copilot") await runtime.setPeerChatEnabled?.(this.#peerChatPreference);
899
+ }
900
+ this.#assertStarted();
901
+ if (provider.runtime !== runtime) throw new Error("Agent disconnected during discovery");
902
+ const inventory = await runtime.listSessions(this.#localOwner.userId, this.#localOwner.machineId);
903
+ this.#assertStarted();
904
+ if (provider.runtime !== runtime) throw new Error("Agent disconnected while listing sessions");
905
+ const next = /* @__PURE__ */ new Map();
906
+ const nativeIds = /* @__PURE__ */ new Set();
907
+ for (const summary of inventory) {
908
+ this.#nativeId(summary.sessionId);
909
+ const id = sessionId(provider, summary.sessionId);
910
+ nativeIds.add(id);
911
+ if (this.#deleted.has(id)) continue;
912
+ if (next.has(id)) throw new Error(`Duplicate ${provider.candidate.name} native session identity`);
913
+ this.#summary(id, summary, this.#localOwner.userId, this.#localOwner.machineId);
914
+ next.set(id, summary);
915
+ }
916
+ for (const id of this.#deleted) {
917
+ if (this.#owner(id) === provider && !nativeIds.has(id)) confirmed.add(id);
918
+ }
919
+ const removed = [...provider.sessions.keys()].filter((id) => !next.has(id));
920
+ const changed = removed.length > 0 || [...next.keys()].some((id) => !provider.sessions.has(id));
921
+ provider.sessions = next;
922
+ provider.error = void 0;
923
+ for (const id of removed) {
924
+ this.#epochs.set(id, this.#epoch(id) + 1);
925
+ await this.#revoke(id);
926
+ }
927
+ if (changed && this.#selection.agents.includes(provider.candidate.id)) this.#inventoryChanged();
928
+ } catch (error) {
929
+ await this.#failed(provider, error);
930
+ }
931
+ }));
932
+ this.#assertStarted();
933
+ if (confirmed.size !== this.#confirmedDeleted.size) {
934
+ await this.#persistFile(this.#deletedPath(), {
935
+ version: 1,
936
+ deletedSessionIds: [...this.#deleted],
937
+ confirmedDeletedSessionIds: [...confirmed]
938
+ });
939
+ this.#confirmedDeleted = confirmed;
940
+ this.#inventoryChanged();
941
+ }
942
+ }
943
+ async #failed(provider, error) {
944
+ provider.error = `${provider.candidate.name}: ${message(error)}`;
945
+ provider.available = false;
946
+ try {
947
+ await this.#retire(provider);
948
+ } catch (cleanup) {
949
+ provider.error += `; shutdown: ${message(cleanup)}`;
950
+ }
951
+ }
952
+ async #retire(provider) {
953
+ for (const peer of this.#peerTurns.values()) {
954
+ if (peer.route.provider === provider) peer.controller.abort(new Error("Managed Copilot provider stopped"));
955
+ }
956
+ const runtime = provider.runtime;
957
+ if (!runtime) {
958
+ await provider.shutdown;
959
+ return;
960
+ }
961
+ provider.runtime = void 0;
962
+ const visible = provider.sessions.size > 0 && this.#selection.agents.includes(provider.candidate.id);
963
+ for (const id of provider.sessions.keys()) this.#epochs.set(id, this.#epoch(id) + 1);
964
+ provider.sessions.clear();
965
+ provider.models.clear();
966
+ for (const [id, pending] of this.#pending) {
967
+ if (pending.route.provider === provider) this.#pending.delete(id);
968
+ }
969
+ for (const [id, route] of this.#owned) {
970
+ if (route.provider === provider) {
971
+ this.#owned.delete(id);
972
+ this.#watched.delete(id);
973
+ }
974
+ }
975
+ if (visible) this.#inventoryChanged();
976
+ const shutdown = runtime.stop();
977
+ provider.shutdown = shutdown;
978
+ try {
979
+ await shutdown;
980
+ } finally {
981
+ if (provider.shutdown === shutdown) provider.shutdown = void 0;
982
+ }
983
+ }
984
+ #nativeId(id) {
985
+ if (typeof id !== "string" || id.length === 0 || id.includes("\0")) throw new Error("Invalid native agent identity");
986
+ }
987
+ #selectableSession(id, summary) {
988
+ return {
989
+ id,
990
+ name: summary.summary || summary.sessionId,
991
+ ...summary.workingDirectory === void 0 ? {} : { workingDirectory: summary.workingDirectory }
992
+ };
993
+ }
994
+ #summary(id, summary, userId, machineId) {
995
+ const provider = this.#owner(id);
996
+ if (summary.groupChat !== void 0 && provider?.candidate.id !== "copilot") {
997
+ throw new GroupChatError("GROUP_CHAT_PARTICIPANT_UNSUPPORTED", "Only Copilot can provide group sessions");
998
+ }
999
+ return SessionSummarySchema.parse({
1000
+ ...summary,
1001
+ sessionId: id,
1002
+ userId,
1003
+ machineId,
1004
+ ...summary.groupChat === void 0 || provider === void 0 ? {} : {
1005
+ groupChat: {
1006
+ ...summary.groupChat,
1007
+ participants: summary.groupChat.participants.map((participant) => ({
1008
+ ...participant,
1009
+ sessionId: sessionId(provider, participant.sessionId)
1010
+ }))
1011
+ }
1012
+ },
1013
+ ...summary.summary === void 0 ? {} : { summary: summary.summary.slice(0, 4096) }
1014
+ });
1015
+ }
1016
+ #epoch(id) {
1017
+ return this.#epochs.get(id) ?? 0;
1018
+ }
1019
+ #route(id) {
1020
+ if (!this.isSessionVisible(id)) throw new Error("Managed session is not selected or available");
1021
+ if (this.#hasRevokedOpening(id)) throw new Error("Previous managed session work is still being cancelled");
1022
+ const provider = this.#owner(id);
1023
+ const native = provider?.sessions.get(id);
1024
+ const runtime = provider?.runtime;
1025
+ if (!provider || !native || !runtime) throw new Error("Unknown managed session");
1026
+ return { provider, runtime, sessionId: id, nativeId: native.sessionId, epoch: this.#epoch(id) };
1027
+ }
1028
+ #validRoute(route) {
1029
+ return route.provider.runtime === route.runtime && this.#epoch(route.sessionId) === route.epoch && this.isSessionVisible(route.sessionId);
1030
+ }
1031
+ #assertRoute(route) {
1032
+ if (!this.#validRoute(route)) throw new Error("Managed session selection changed during the operation");
1033
+ }
1034
+ #hasRevokedOpening(id) {
1035
+ return [...this.#opening.get(id) ?? []].some((route) => route.provider.runtime === route.runtime && route.epoch !== this.#epoch(id));
1036
+ }
1037
+ async #withSession(id, operation, owns = false) {
1038
+ const route = this.#route(id);
1039
+ const opening = this.#opening.get(id) ?? /* @__PURE__ */ new Set();
1040
+ if (owns) {
1041
+ this.#upgradeRevision += 1;
1042
+ opening.add(route);
1043
+ this.#opening.set(id, opening);
1044
+ }
1045
+ try {
1046
+ const result = await operation(route);
1047
+ this.#assertRoute(route);
1048
+ if (owns) this.#owned.set(id, route);
1049
+ return result;
1050
+ } finally {
1051
+ if (owns) {
1052
+ try {
1053
+ if (!this.#validRoute(route) && route.provider.runtime === route.runtime) await this.#release(route);
1054
+ } catch (error) {
1055
+ if (route.provider.runtime === route.runtime) {
1056
+ await this.#failed(route.provider, new Error(
1057
+ `Unable to cancel revoked session work: ${message(error)}`,
1058
+ { cause: error }
1059
+ ));
1060
+ }
1061
+ throw error;
1062
+ } finally {
1063
+ opening.delete(route);
1064
+ if (opening.size === 0) this.#opening.delete(id);
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ #configuration(provider, configuration) {
1070
+ if (configuration.modelId === void 0) return configuration;
1071
+ this.#nativeId(configuration.modelId);
1072
+ const id = modelId(provider, configuration.modelId);
1073
+ provider.models.set(id, configuration.modelId);
1074
+ return { ...configuration, modelId: id };
1075
+ }
1076
+ #remoteStatus(route, status) {
1077
+ if (status.attachedSessionId === void 0) return status;
1078
+ const { attachedSessionId, ...rest } = status;
1079
+ const id = sessionId(route.provider, attachedSessionId);
1080
+ return this.isSessionVisible(id) ? { ...rest, attachedSessionId: id } : { state: "off", steerable: false };
1081
+ }
1082
+ #requestId(route, nativeId, kind) {
1083
+ return `managed:${route.provider.candidate.id}:${kind}:${digest(JSON.stringify([
1084
+ route.nativeId,
1085
+ nativeId,
1086
+ route.provider.nonce,
1087
+ route.epoch
1088
+ ]))}`;
1089
+ }
1090
+ #item(route, item) {
1091
+ const participant = route.provider.sessions.get(route.sessionId)?.groupChat?.participants.find((entry) => "agentId" in item && entry.sessionId === item.agentId);
1092
+ return {
1093
+ ...item,
1094
+ ...participant === void 0 ? {} : { agentId: sessionId(route.provider, participant.sessionId) },
1095
+ ...item.sourceInteractionIds === void 0 ? {} : {
1096
+ sourceInteractionIds: item.sourceInteractionIds.map((id) => this.#requestId(route, id, "interaction"))
1097
+ }
1098
+ };
1099
+ }
1100
+ async #release(route) {
1101
+ this.#owned.delete(route.sessionId);
1102
+ const watched = this.#watched.delete(route.sessionId);
1103
+ if (watched) await route.runtime.watchSession(route.nativeId, false);
1104
+ await route.runtime.abortSession(route.nativeId);
1105
+ }
1106
+ async #revoke(id) {
1107
+ const peer = this.#peerTurns.get(id);
1108
+ if (peer !== void 0) {
1109
+ peer.controller.abort(new Error("Managed session selection changed during the peer turn"));
1110
+ for (const [requestId, pending] of this.#pending) {
1111
+ if (pending.route.sessionId === id) this.#pending.delete(requestId);
1112
+ }
1113
+ this.#owned.delete(id);
1114
+ this.#watched.delete(id);
1115
+ return;
1116
+ }
1117
+ const owned = this.#owned.get(id);
1118
+ const requests = [...this.#pending].filter(([, pending]) => pending.route.sessionId === id);
1119
+ for (const [requestId] of requests) this.#pending.delete(requestId);
1120
+ let restart;
1121
+ try {
1122
+ for (const [, pending] of requests) {
1123
+ if (pending.kind === "permission") {
1124
+ pending.route.runtime.respondPermission(pending.nativeId, "reject");
1125
+ } else if (pending.request) {
1126
+ const response = cancellation(pending.request);
1127
+ if (response) await pending.route.runtime.respondInteraction(pending.nativeId, response);
1128
+ else restart = pending.route.provider;
1129
+ }
1130
+ }
1131
+ if (owned) await this.#release(owned);
1132
+ if (restart) {
1133
+ restart.error = "Agent stopped to cancel a pending native input request; rescan to reconnect";
1134
+ await this.#retire(restart);
1135
+ }
1136
+ } catch (error) {
1137
+ const provider = owned?.provider ?? requests[0]?.[1].route.provider;
1138
+ if (provider) await this.#failed(provider, new Error(`Unable to cancel deselected session: ${message(error)}`, { cause: error }));
1139
+ else throw error;
1140
+ }
1141
+ }
1142
+ #providerCallbacks(provider, runtime) {
1143
+ const nonce = provider.nonce;
1144
+ const failed = async (error) => {
1145
+ if (provider.runtime === runtime && provider.nonce === nonce) await this.#failed(provider, error);
1146
+ };
1147
+ const visible = (nativeId) => {
1148
+ const id = sessionId(provider, nativeId);
1149
+ return provider.runtime === runtime && provider.nonce === nonce && this.isSessionVisible(id) && !this.#hasRevokedOpening(id) ? this.#route(id) : void 0;
1150
+ };
1151
+ const request = (nativeId, requestId, kind, value) => {
1152
+ const route = visible(nativeId);
1153
+ if (!route) return void 0;
1154
+ const id = this.#requestId(route, requestId, kind);
1155
+ this.#pending.set(id, { route, nativeId: requestId, kind, request: value });
1156
+ this.#owned.set(route.sessionId, route);
1157
+ return route;
1158
+ };
1159
+ return {
1160
+ onTimelineReadDiagnostic: (diagnostic) => {
1161
+ if (provider.runtime === runtime && provider.nonce === nonce && this.#selection.agents.includes(provider.candidate.id)) {
1162
+ this.#callbacks?.onTimelineReadDiagnostic?.(diagnostic);
1163
+ }
1164
+ },
1165
+ onConversationItem: (id, item) => {
1166
+ const route = visible(id);
1167
+ if (route) this.#callbacks?.onConversationItem(route.sessionId, this.#item(route, item));
1168
+ },
1169
+ onAssistantPreview: (id, item) => {
1170
+ const route = visible(id);
1171
+ if (route) this.#callbacks?.onAssistantPreview?.(route.sessionId, this.#item(route, item));
1172
+ },
1173
+ onTransientConversationItem: (id, item) => {
1174
+ const route = visible(id);
1175
+ if (route) {
1176
+ const publish = this.#callbacks?.onTransientConversationItem ?? this.#callbacks?.onConversationItem;
1177
+ publish?.(route.sessionId, this.#item(route, item));
1178
+ }
1179
+ },
1180
+ onConversationActivityStep: (id, delta) => {
1181
+ const route = visible(id);
1182
+ if (route) this.#callbacks?.onConversationActivityStep?.(route.sessionId, delta);
1183
+ },
1184
+ onTimelineInvalidated: (id, invalidation) => {
1185
+ const route = visible(id);
1186
+ if (route) this.#callbacks?.onTimelineInvalidated?.(route.sessionId, invalidation);
1187
+ },
1188
+ onPermissionRequest: (id, requestId, value) => {
1189
+ const route = request(id, requestId, "permission");
1190
+ if (route) this.#callbacks?.onPermissionRequest(route.sessionId, this.#requestId(route, requestId, "permission"), value);
1191
+ else if (provider.runtime === runtime && provider.nonce === nonce) {
1192
+ try {
1193
+ runtime.respondPermission(requestId, "reject");
1194
+ } catch (error) {
1195
+ void failed(error);
1196
+ }
1197
+ }
1198
+ },
1199
+ onInteractionRequest: (id, requestId, value) => {
1200
+ const route = request(id, requestId, "interaction", value);
1201
+ if (route) this.#callbacks?.onInteractionRequest?.(route.sessionId, this.#requestId(route, requestId, "interaction"), value);
1202
+ else if (provider.runtime === runtime && provider.nonce === nonce) {
1203
+ const response = cancellation(value);
1204
+ if (response) {
1205
+ void runtime.respondInteraction(requestId, response).catch(failed);
1206
+ } else {
1207
+ void failed(new Error("Agent stopped to cancel an unselected native input request"));
1208
+ }
1209
+ }
1210
+ },
1211
+ onInteractionDismissed: (id, requestId) => {
1212
+ const route = visible(id);
1213
+ if (!route) return;
1214
+ const wireId = this.#requestId(route, requestId, "interaction");
1215
+ this.#pending.delete(wireId);
1216
+ this.#callbacks?.onInteractionDismissed?.(route.sessionId, wireId);
1217
+ },
1218
+ onSessionInventoryChanged: (id) => {
1219
+ if (provider.runtime === runtime && provider.nonce === nonce && !provider.creating && this.#started && !this.#stopping && this.#selection.agents.includes(provider.candidate.id) && !this.#deleted.has(sessionId(provider, id))) {
1220
+ this.#inventoryChanged(visible(id)?.sessionId ?? "selection");
1221
+ }
1222
+ },
1223
+ onSessionActivity: (id, state, occurredAt) => {
1224
+ const route = visible(id);
1225
+ if (route) this.#callbacks?.onSessionActivity?.(route.sessionId, state, occurredAt);
1226
+ },
1227
+ onSyncError: (id, error) => {
1228
+ const route = visible(id);
1229
+ if (route) this.#callbacks?.onSyncError(route.sessionId, error);
1230
+ },
1231
+ onSyncRecovered: (id) => {
1232
+ const route = visible(id);
1233
+ if (route) this.#callbacks?.onSyncRecovered?.(route.sessionId);
1234
+ },
1235
+ onProjectMetadataError: (id, error) => {
1236
+ const route = visible(id);
1237
+ if (route) this.#callbacks?.onProjectMetadataError?.(route.sessionId, error);
1238
+ },
1239
+ onProjectMetadataRecovered: (id) => {
1240
+ const route = visible(id);
1241
+ if (route) this.#callbacks?.onProjectMetadataRecovered?.(route.sessionId);
1242
+ },
1243
+ onImageProjectionFailure: (id, failure) => {
1244
+ const route = visible(id);
1245
+ if (route) this.#callbacks?.onImageProjectionFailure?.(route.sessionId, failure);
1246
+ }
1247
+ };
1248
+ }
1249
+ };
1250
+ export {
1251
+ ManagedCodingAgentAdapter
1252
+ };