@duckmind/dm-darwin-arm64 0.13.5 → 0.13.7
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/dm +0 -0
- package/extensions/.dm-extensions.json +39 -15
- package/extensions/dm-multicodex/package-lock.json +302 -1814
- package/extensions/dm-phone/README.md +23 -0
- package/extensions/dm-phone/index.ts +12 -0
- package/extensions/dm-phone/node_modules/.package-lock.json +29 -0
- package/extensions/dm-phone/node_modules/ws/LICENSE +20 -0
- package/extensions/dm-phone/node_modules/ws/README.md +548 -0
- package/extensions/dm-phone/node_modules/ws/browser.js +8 -0
- package/extensions/dm-phone/node_modules/ws/index.js +22 -0
- package/extensions/dm-phone/node_modules/ws/lib/buffer-util.js +131 -0
- package/extensions/dm-phone/node_modules/ws/lib/constants.js +19 -0
- package/extensions/dm-phone/node_modules/ws/lib/event-target.js +292 -0
- package/extensions/dm-phone/node_modules/ws/lib/extension.js +203 -0
- package/extensions/dm-phone/node_modules/ws/lib/limiter.js +55 -0
- package/extensions/dm-phone/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/extensions/dm-phone/node_modules/ws/lib/receiver.js +706 -0
- package/extensions/dm-phone/node_modules/ws/lib/sender.js +602 -0
- package/extensions/dm-phone/node_modules/ws/lib/stream.js +161 -0
- package/extensions/dm-phone/node_modules/ws/lib/subprotocol.js +62 -0
- package/extensions/dm-phone/node_modules/ws/lib/validation.js +152 -0
- package/extensions/dm-phone/node_modules/ws/lib/websocket-server.js +554 -0
- package/extensions/dm-phone/node_modules/ws/lib/websocket.js +1393 -0
- package/extensions/dm-phone/node_modules/ws/package.json +70 -0
- package/extensions/dm-phone/node_modules/ws/wrapper.mjs +21 -0
- package/extensions/dm-phone/package-lock.json +66 -0
- package/extensions/dm-phone/package.json +35 -0
- package/extensions/dm-phone/phone-session-pool.ts +8 -0
- package/extensions/dm-phone/public/app/attachments.js +233 -0
- package/extensions/dm-phone/public/app/autocomplete-controller.js +81 -0
- package/extensions/dm-phone/public/app/autocomplete.js +135 -0
- package/extensions/dm-phone/public/app/bindings.js +178 -0
- package/extensions/dm-phone/public/app/command-catalog.js +76 -0
- package/extensions/dm-phone/public/app/commands.js +370 -0
- package/extensions/dm-phone/public/app/constants.js +60 -0
- package/extensions/dm-phone/public/app/formatters.js +131 -0
- package/extensions/dm-phone/public/app/handlers.js +442 -0
- package/extensions/dm-phone/public/app/main.js +6 -0
- package/extensions/dm-phone/public/app/markdown.js +105 -0
- package/extensions/dm-phone/public/app/messages.js +418 -0
- package/extensions/dm-phone/public/app/sheet-actions.js +113 -0
- package/extensions/dm-phone/public/app/sheet-navigation.js +19 -0
- package/extensions/dm-phone/public/app/sheets-view.js +272 -0
- package/extensions/dm-phone/public/app/state.js +95 -0
- package/extensions/dm-phone/public/app/tool-rendering.js +562 -0
- package/extensions/dm-phone/public/app/transport.js +176 -0
- package/extensions/dm-phone/public/app/ui.js +409 -0
- package/extensions/dm-phone/public/app.js +1 -0
- package/extensions/dm-phone/public/icon.svg +15 -0
- package/extensions/dm-phone/public/index.html +147 -0
- package/extensions/dm-phone/public/manifest.webmanifest +17 -0
- package/extensions/dm-phone/public/styles.css +1139 -0
- package/extensions/dm-phone/public/sw.js +78 -0
- package/extensions/dm-phone/src/extension/phone-args.ts +121 -0
- package/extensions/dm-phone/src/extension/phone-paths.ts +250 -0
- package/extensions/dm-phone/src/extension/phone-quota.ts +188 -0
- package/extensions/dm-phone/src/extension/phone-runtime.ts +154 -0
- package/extensions/dm-phone/src/extension/phone-server-runtime.ts +1217 -0
- package/extensions/dm-phone/src/extension/phone-sessions.ts +139 -0
- package/extensions/dm-phone/src/extension/phone-static.ts +30 -0
- package/extensions/dm-phone/src/extension/phone-tailscale.ts +148 -0
- package/extensions/dm-phone/src/extension/phone-theme.ts +85 -0
- package/extensions/dm-phone/src/extension/register-phone-child-extension.ts +112 -0
- package/extensions/dm-phone/src/extension/register-phone-extension.ts +106 -0
- package/extensions/dm-phone/src/extension/types.ts +73 -0
- package/extensions/dm-phone/src/session-pool/parent-session-worker.ts +881 -0
- package/extensions/dm-phone/src/session-pool/session-pool.ts +470 -0
- package/extensions/dm-phone/src/session-pool/session-worker.ts +734 -0
- package/extensions/dm-phone/src/session-pool/types.ts +105 -0
- package/extensions/dm-phone/src/session-pool/utils.ts +23 -0
- package/extensions/dm-subagents/artifacts.ts +11 -5
- package/extensions/dm-subagents/async-execution.ts +4 -1
- package/extensions/dm-subagents/index.ts +1 -1
- package/extensions/dm-subagents/schemas.ts +1 -1
- package/extensions/dm-subagents/settings.ts +6 -4
- package/extensions/dm-subagents/subagent-runner.ts +167 -50
- package/extensions/dm-subagents/types.ts +62 -2
- package/package.json +1 -1
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import type { WebSocket } from "ws";
|
|
2
|
+
import type {
|
|
3
|
+
ClientState,
|
|
4
|
+
PhoneSessionPoolOptions,
|
|
5
|
+
SessionController,
|
|
6
|
+
SessionSnapshot,
|
|
7
|
+
SessionSummary,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
export class PhoneSessionPool {
|
|
11
|
+
private readonly options: PhoneSessionPoolOptions;
|
|
12
|
+
private readonly workers = new Map<string, SessionController>();
|
|
13
|
+
private readonly clients = new Map<WebSocket, ClientState>();
|
|
14
|
+
private readonly statusSignatures = new Map<WebSocket, string>();
|
|
15
|
+
private readonly catalogSignatures = new Map<WebSocket, string>();
|
|
16
|
+
private defaultWorkerId: string | null = null;
|
|
17
|
+
private defaultWorkerPromise: Promise<SessionController> | null = null;
|
|
18
|
+
|
|
19
|
+
constructor(options: PhoneSessionPoolOptions) {
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setCwd(cwd: string) {
|
|
24
|
+
this.options.cwd = cwd;
|
|
25
|
+
this.broadcastStatus();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get clientCount() {
|
|
29
|
+
return this.clients.size;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getClients() {
|
|
33
|
+
return [...this.clients.keys()];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getSelectedSessionId() {
|
|
37
|
+
return this.defaultWorkerId;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getSession(sessionId: string | null | undefined) {
|
|
41
|
+
if (!sessionId) return null;
|
|
42
|
+
return this.workers.get(sessionId) || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getSessions() {
|
|
46
|
+
return [...this.workers.values()];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private attachSession(session: SessionController) {
|
|
50
|
+
this.workers.set(session.id, session);
|
|
51
|
+
return session;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private createDefaultSession() {
|
|
55
|
+
const session = this.options.createDefaultSession();
|
|
56
|
+
return this.attachSession(session);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private createParallelSession(sessionFile: string | null = null) {
|
|
60
|
+
const session = this.options.createParallelSession(sessionFile);
|
|
61
|
+
return this.attachSession(session);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private sortedWorkers() {
|
|
65
|
+
return [...this.workers.values()].sort((left, right) => right.lastActivityAt - left.lastActivityAt);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private serializeSessions() {
|
|
69
|
+
return this.sortedWorkers().map((worker) => worker.getSummary());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async ensureDefaultWorker() {
|
|
73
|
+
const existing = this.defaultWorkerId ? this.workers.get(this.defaultWorkerId) : this.sortedWorkers()[0];
|
|
74
|
+
if (existing) {
|
|
75
|
+
this.defaultWorkerId = existing.id;
|
|
76
|
+
return existing;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this.defaultWorkerPromise) {
|
|
80
|
+
return this.defaultWorkerPromise;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.defaultWorkerPromise = (async () => {
|
|
84
|
+
const worker = this.createDefaultSession();
|
|
85
|
+
this.defaultWorkerId = worker.id;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await worker.ensureStarted();
|
|
89
|
+
await worker.refreshCachedSnapshot(5000).catch(() => {});
|
|
90
|
+
this.broadcastCatalog();
|
|
91
|
+
this.broadcastStatus();
|
|
92
|
+
return worker;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.workers.delete(worker.id);
|
|
95
|
+
if (this.defaultWorkerId === worker.id) {
|
|
96
|
+
this.defaultWorkerId = null;
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
})().finally(() => {
|
|
101
|
+
this.defaultWorkerPromise = null;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return this.defaultWorkerPromise;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private async getWorkerForClient(ws: WebSocket) {
|
|
108
|
+
const client = this.clients.get(ws);
|
|
109
|
+
if (!client) {
|
|
110
|
+
const worker = await this.ensureDefaultWorker();
|
|
111
|
+
this.clients.set(ws, { activeSessionId: worker.id });
|
|
112
|
+
return worker;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const activeWorker = client.activeSessionId ? this.workers.get(client.activeSessionId) : null;
|
|
116
|
+
if (activeWorker) {
|
|
117
|
+
return activeWorker;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const fallback = await this.ensureDefaultWorker();
|
|
121
|
+
client.activeSessionId = fallback.id;
|
|
122
|
+
return fallback;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getActiveWorker(ws: WebSocket) {
|
|
126
|
+
return this.getWorkerForClient(ws);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private buildBaseStatus() {
|
|
130
|
+
const meta = this.options.buildStatusMeta();
|
|
131
|
+
return {
|
|
132
|
+
...meta,
|
|
133
|
+
connectedClients: this.clients.size,
|
|
134
|
+
sessionCount: this.workers.size,
|
|
135
|
+
isRunning: Boolean((meta as any).serverRunning),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private normalizeStatusSignature(status: Record<string, unknown>) {
|
|
140
|
+
const { lastActivityAt: _ignored, ...rest } = status;
|
|
141
|
+
return JSON.stringify(rest);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private normalizeCatalogSignature(data: { activeSessionId: string | null; sessions: SessionSummary[] }) {
|
|
145
|
+
return JSON.stringify({
|
|
146
|
+
activeSessionId: data.activeSessionId,
|
|
147
|
+
sessions: data.sessions.map(({ lastActivityAt: _ignored, ...session }) => session),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private handleWorkerStateChange(_worker: SessionController) {
|
|
152
|
+
this.broadcastCatalog();
|
|
153
|
+
this.broadcastStatus();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
buildOverallStatus() {
|
|
157
|
+
const worker = this.defaultWorkerId ? this.workers.get(this.defaultWorkerId) : this.sortedWorkers()[0] || null;
|
|
158
|
+
return {
|
|
159
|
+
...this.buildBaseStatus(),
|
|
160
|
+
...(worker ? worker.getStatus() : {
|
|
161
|
+
childRunning: false,
|
|
162
|
+
isStreaming: false,
|
|
163
|
+
isCompacting: false,
|
|
164
|
+
lastError: "",
|
|
165
|
+
childPid: null,
|
|
166
|
+
sessionWorkerId: null,
|
|
167
|
+
sessionKind: "parallel",
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private buildClientStatus(ws: WebSocket) {
|
|
173
|
+
const client = this.clients.get(ws);
|
|
174
|
+
const worker = client?.activeSessionId ? this.workers.get(client.activeSessionId) : null;
|
|
175
|
+
return {
|
|
176
|
+
...this.buildBaseStatus(),
|
|
177
|
+
...(worker ? worker.getStatus() : {
|
|
178
|
+
childRunning: false,
|
|
179
|
+
isStreaming: false,
|
|
180
|
+
isCompacting: false,
|
|
181
|
+
lastError: "",
|
|
182
|
+
childPid: null,
|
|
183
|
+
sessionWorkerId: null,
|
|
184
|
+
sessionKind: "parallel",
|
|
185
|
+
}),
|
|
186
|
+
activeSessionId: client?.activeSessionId || null,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private sendStatus(ws: WebSocket, options: { force?: boolean } = {}) {
|
|
191
|
+
const data = this.buildClientStatus(ws);
|
|
192
|
+
const signature = this.normalizeStatusSignature(data);
|
|
193
|
+
if (!options.force && this.statusSignatures.get(ws) === signature) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.statusSignatures.set(ws, signature);
|
|
198
|
+
this.options.send(ws, { channel: "server", event: "status", data });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private sendSnapshot(ws: WebSocket, worker: SessionController, snapshot: SessionSnapshot) {
|
|
202
|
+
this.options.send(ws, {
|
|
203
|
+
channel: "snapshot",
|
|
204
|
+
sessionWorkerId: worker.id,
|
|
205
|
+
state: snapshot.state,
|
|
206
|
+
messages: snapshot.messages || [],
|
|
207
|
+
commands: snapshot.commands || [],
|
|
208
|
+
liveAssistantMessage: snapshot.liveAssistantMessage || null,
|
|
209
|
+
liveTools: snapshot.liveTools || [],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
broadcastStatus() {
|
|
214
|
+
for (const ws of this.clients.keys()) {
|
|
215
|
+
this.sendStatus(ws);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
sendCatalog(ws: WebSocket, options: { force?: boolean } = {}) {
|
|
220
|
+
const client = this.clients.get(ws);
|
|
221
|
+
const data = {
|
|
222
|
+
activeSessionId: client?.activeSessionId || null,
|
|
223
|
+
sessions: this.serializeSessions(),
|
|
224
|
+
};
|
|
225
|
+
const signature = this.normalizeCatalogSignature(data);
|
|
226
|
+
if (!options.force && this.catalogSignatures.get(ws) === signature) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.catalogSignatures.set(ws, signature);
|
|
231
|
+
this.options.send(ws, {
|
|
232
|
+
channel: "sessions",
|
|
233
|
+
event: "catalog",
|
|
234
|
+
data,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
broadcastCatalog() {
|
|
239
|
+
for (const ws of this.clients.keys()) {
|
|
240
|
+
this.sendCatalog(ws);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private forwardEnvelope(worker: SessionController, envelope: any) {
|
|
245
|
+
for (const [ws, client] of this.clients.entries()) {
|
|
246
|
+
if (client.activeSessionId === worker.id) {
|
|
247
|
+
this.options.send(ws, envelope);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async addClient(ws: WebSocket) {
|
|
253
|
+
const worker = await this.ensureDefaultWorker();
|
|
254
|
+
this.clients.set(ws, { activeSessionId: this.defaultWorkerId || worker.id });
|
|
255
|
+
this.sendStatus(ws, { force: true });
|
|
256
|
+
this.sendCatalog(ws, { force: true });
|
|
257
|
+
await this.refreshActiveSnapshot(ws);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
removeClient(ws: WebSocket) {
|
|
261
|
+
this.clients.delete(ws);
|
|
262
|
+
this.statusSignatures.delete(ws);
|
|
263
|
+
this.catalogSignatures.delete(ws);
|
|
264
|
+
this.broadcastStatus();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async refreshActiveSnapshot(ws: WebSocket) {
|
|
268
|
+
const worker = await this.getWorkerForClient(ws);
|
|
269
|
+
const requestedWorkerId = worker.id;
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const snapshot = await worker.getSnapshot();
|
|
273
|
+
const client = this.clients.get(ws);
|
|
274
|
+
if (client?.activeSessionId !== requestedWorkerId) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.sendSnapshot(ws, worker, snapshot);
|
|
279
|
+
this.sendStatus(ws);
|
|
280
|
+
if (worker.pendingUiRequest) {
|
|
281
|
+
this.options.send(ws, { channel: "rpc", payload: worker.pendingUiRequest });
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
const client = this.clients.get(ws);
|
|
285
|
+
if (client?.activeSessionId !== requestedWorkerId) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.options.send(ws, {
|
|
290
|
+
channel: "server",
|
|
291
|
+
event: "snapshot-error",
|
|
292
|
+
data: { message: error instanceof Error ? error.message : String(error) },
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async broadcastSnapshots() {
|
|
298
|
+
await Promise.all(this.getClients().map(async (ws) => this.refreshActiveSnapshot(ws)));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async selectSession(ws: WebSocket, sessionId: string) {
|
|
302
|
+
const worker = this.workers.get(sessionId);
|
|
303
|
+
if (!worker) {
|
|
304
|
+
this.options.send(ws, { channel: "server", event: "client-error", data: { message: "That session no longer exists." } });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const client = this.clients.get(ws);
|
|
309
|
+
if (!client) {
|
|
310
|
+
this.clients.set(ws, { activeSessionId: sessionId });
|
|
311
|
+
} else {
|
|
312
|
+
client.activeSessionId = sessionId;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.defaultWorkerId = worker.id;
|
|
316
|
+
|
|
317
|
+
this.sendCatalog(ws, { force: true });
|
|
318
|
+
this.sendStatus(ws, { force: true });
|
|
319
|
+
this.sendSnapshot(ws, worker, worker.getCachedSnapshot());
|
|
320
|
+
await this.refreshActiveSnapshot(ws);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async spawnSession(ws: WebSocket, sessionFile: string | null = null) {
|
|
324
|
+
const worker = this.createParallelSession(sessionFile);
|
|
325
|
+
let added = false;
|
|
326
|
+
const existingClient = this.clients.get(ws);
|
|
327
|
+
const previousActiveSessionId = existingClient?.activeSessionId || null;
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
added = true;
|
|
331
|
+
this.defaultWorkerId = worker.id;
|
|
332
|
+
|
|
333
|
+
if (existingClient) {
|
|
334
|
+
existingClient.activeSessionId = worker.id;
|
|
335
|
+
} else {
|
|
336
|
+
this.clients.set(ws, { activeSessionId: worker.id });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
this.sendCatalog(ws, { force: true });
|
|
340
|
+
this.sendStatus(ws, { force: true });
|
|
341
|
+
this.sendSnapshot(ws, worker, worker.getCachedSnapshot());
|
|
342
|
+
|
|
343
|
+
await worker.ensureStarted();
|
|
344
|
+
await worker.refreshCachedSnapshot(5000).catch(() => {});
|
|
345
|
+
this.broadcastCatalog();
|
|
346
|
+
this.broadcastStatus();
|
|
347
|
+
await this.refreshActiveSnapshot(ws);
|
|
348
|
+
return worker;
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (added) {
|
|
351
|
+
this.workers.delete(worker.id);
|
|
352
|
+
}
|
|
353
|
+
const fallbackWorker = previousActiveSessionId ? this.workers.get(previousActiveSessionId) : this.sortedWorkers()[0] || null;
|
|
354
|
+
if (this.defaultWorkerId === worker.id) {
|
|
355
|
+
this.defaultWorkerId = fallbackWorker?.id || null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const client = this.clients.get(ws);
|
|
359
|
+
if (client) {
|
|
360
|
+
client.activeSessionId = fallbackWorker?.id || null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
await worker.dispose().catch(() => {});
|
|
364
|
+
this.sendCatalog(ws, { force: true });
|
|
365
|
+
this.sendStatus(ws, { force: true });
|
|
366
|
+
if (fallbackWorker) {
|
|
367
|
+
this.sendSnapshot(ws, fallbackWorker, fallbackWorker.getCachedSnapshot());
|
|
368
|
+
await this.refreshActiveSnapshot(ws).catch(() => {});
|
|
369
|
+
}
|
|
370
|
+
this.broadcastCatalog();
|
|
371
|
+
this.broadcastStatus();
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async removeSession(sessionId: string, options: { dispose?: boolean; fallbackSessionId?: string | null } = {}) {
|
|
377
|
+
const worker = this.workers.get(sessionId);
|
|
378
|
+
if (!worker) return false;
|
|
379
|
+
|
|
380
|
+
const { dispose = true } = options;
|
|
381
|
+
this.workers.delete(sessionId);
|
|
382
|
+
|
|
383
|
+
let fallbackWorker = options.fallbackSessionId ? this.workers.get(options.fallbackSessionId) || null : null;
|
|
384
|
+
if (!fallbackWorker) {
|
|
385
|
+
fallbackWorker = this.defaultWorkerId && this.defaultWorkerId !== sessionId
|
|
386
|
+
? this.workers.get(this.defaultWorkerId) || null
|
|
387
|
+
: null;
|
|
388
|
+
}
|
|
389
|
+
if (!fallbackWorker) {
|
|
390
|
+
fallbackWorker = this.sortedWorkers()[0] || null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (this.defaultWorkerId === sessionId) {
|
|
394
|
+
this.defaultWorkerId = fallbackWorker?.id || null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (const client of this.clients.values()) {
|
|
398
|
+
if (client.activeSessionId === sessionId) {
|
|
399
|
+
client.activeSessionId = fallbackWorker?.id || null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (dispose) {
|
|
404
|
+
await worker.dispose().catch(() => {});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
this.broadcastCatalog();
|
|
408
|
+
this.broadcastStatus();
|
|
409
|
+
await Promise.all(this.getClients().map(async (ws) => {
|
|
410
|
+
const client = this.clients.get(ws);
|
|
411
|
+
if (!client?.activeSessionId) return;
|
|
412
|
+
await this.refreshActiveSnapshot(ws).catch(() => {});
|
|
413
|
+
}));
|
|
414
|
+
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
setDefaultWorker(sessionId: string | null) {
|
|
419
|
+
this.defaultWorkerId = sessionId && this.workers.has(sessionId) ? sessionId : this.sortedWorkers()[0]?.id || null;
|
|
420
|
+
for (const client of this.clients.values()) {
|
|
421
|
+
if (!client.activeSessionId || !this.workers.has(client.activeSessionId)) {
|
|
422
|
+
client.activeSessionId = this.defaultWorkerId;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
this.broadcastCatalog();
|
|
426
|
+
this.broadcastStatus();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
bindExternalSession(session: SessionController) {
|
|
430
|
+
this.attachSession(session);
|
|
431
|
+
if (!this.defaultWorkerId) {
|
|
432
|
+
this.defaultWorkerId = session.id;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
notifySessionStateChanged(session: SessionController) {
|
|
437
|
+
this.handleWorkerStateChange(session);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
forwardSessionEnvelope(session: SessionController, envelope: any) {
|
|
441
|
+
this.forwardEnvelope(session, envelope);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async closeAllClients(options: { payload?: unknown; code?: number; reason?: string } = {}) {
|
|
445
|
+
const { payload, code = 1000, reason = "closing" } = options;
|
|
446
|
+
const sockets = this.getClients();
|
|
447
|
+
this.clients.clear();
|
|
448
|
+
this.statusSignatures.clear();
|
|
449
|
+
this.catalogSignatures.clear();
|
|
450
|
+
|
|
451
|
+
for (const ws of sockets) {
|
|
452
|
+
if (payload) {
|
|
453
|
+
this.options.send(ws, payload);
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
ws.close(code, reason);
|
|
457
|
+
} catch {
|
|
458
|
+
// ignore
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async dispose() {
|
|
464
|
+
await this.closeAllClients();
|
|
465
|
+
await Promise.all([...this.workers.values()].map(async (worker) => worker.dispose().catch(() => {})));
|
|
466
|
+
this.workers.clear();
|
|
467
|
+
this.defaultWorkerId = null;
|
|
468
|
+
this.defaultWorkerPromise = null;
|
|
469
|
+
}
|
|
470
|
+
}
|