kanna-code 0.26.0 → 0.26.2
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/dist/client/assets/index-CzFdfKR3.css +32 -0
- package/dist/client/assets/{index-D3z0bCam.js → index-CzKoT6ON.js} +189 -189
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/event-store.test.ts +60 -0
- package/src/server/event-store.ts +37 -1
- package/src/server/server.ts +6 -1
- package/src/server/ws-router.test.ts +80 -5
- package/src/server/ws-router.ts +35 -18
- package/dist/client/assets/index-DFoGPygA.css +0 -32
package/dist/client/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>Kanna</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CzKoT6ON.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CzFdfKR3.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -225,4 +225,64 @@ describe("EventStore", () => {
|
|
|
225
225
|
|
|
226
226
|
expect(store.getChat("chat-1")?.unread).toBe(false)
|
|
227
227
|
})
|
|
228
|
+
|
|
229
|
+
test("prunes stale empty chats after five minutes", async () => {
|
|
230
|
+
const dataDir = await createTempDataDir()
|
|
231
|
+
const store = new EventStore(dataDir)
|
|
232
|
+
await store.initialize()
|
|
233
|
+
|
|
234
|
+
const project = await store.openProject("/tmp/project")
|
|
235
|
+
const chat = await store.createChat(project.id)
|
|
236
|
+
const staleNow = chat.createdAt + 5 * 60 * 1000
|
|
237
|
+
|
|
238
|
+
const pruned = await store.pruneStaleEmptyChats({ now: staleNow })
|
|
239
|
+
|
|
240
|
+
expect(pruned).toEqual([chat.id])
|
|
241
|
+
expect(store.getChat(chat.id)).toBeNull()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test("does not prune recent empty chats", async () => {
|
|
245
|
+
const dataDir = await createTempDataDir()
|
|
246
|
+
const store = new EventStore(dataDir)
|
|
247
|
+
await store.initialize()
|
|
248
|
+
|
|
249
|
+
const project = await store.openProject("/tmp/project")
|
|
250
|
+
const chat = await store.createChat(project.id)
|
|
251
|
+
const pruned = await store.pruneStaleEmptyChats({ now: chat.createdAt + 5 * 60 * 1000 - 1 })
|
|
252
|
+
|
|
253
|
+
expect(pruned).toEqual([])
|
|
254
|
+
expect(store.getChat(chat.id)?.id).toBe(chat.id)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test("does not prune chats once they have transcript messages", async () => {
|
|
258
|
+
const dataDir = await createTempDataDir()
|
|
259
|
+
const store = new EventStore(dataDir)
|
|
260
|
+
await store.initialize()
|
|
261
|
+
|
|
262
|
+
const project = await store.openProject("/tmp/project")
|
|
263
|
+
const chat = await store.createChat(project.id)
|
|
264
|
+
await store.appendMessage(chat.id, entry("user_prompt", chat.createdAt + 1, { content: "hello" }))
|
|
265
|
+
|
|
266
|
+
const pruned = await store.pruneStaleEmptyChats({ now: chat.createdAt + 5 * 60 * 1000 })
|
|
267
|
+
|
|
268
|
+
expect(pruned).toEqual([])
|
|
269
|
+
expect(store.getChat(chat.id)?.id).toBe(chat.id)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test("does not prune stale chats that are currently active", async () => {
|
|
273
|
+
const dataDir = await createTempDataDir()
|
|
274
|
+
const store = new EventStore(dataDir)
|
|
275
|
+
await store.initialize()
|
|
276
|
+
|
|
277
|
+
const project = await store.openProject("/tmp/project")
|
|
278
|
+
const chat = await store.createChat(project.id)
|
|
279
|
+
|
|
280
|
+
const pruned = await store.pruneStaleEmptyChats({
|
|
281
|
+
now: chat.createdAt + 5 * 60 * 1000,
|
|
282
|
+
activeChatIds: [chat.id],
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
expect(pruned).toEqual([])
|
|
286
|
+
expect(store.getChat(chat.id)?.id).toBe(chat.id)
|
|
287
|
+
})
|
|
228
288
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFile, mkdir, rename, writeFile } from "node:fs/promises"
|
|
1
|
+
import { appendFile, mkdir, rename, rm, writeFile } from "node:fs/promises"
|
|
2
2
|
import { existsSync, readFileSync as readFileSyncImmediate } from "node:fs"
|
|
3
3
|
import { homedir } from "node:os"
|
|
4
4
|
import path from "node:path"
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { resolveLocalPath } from "./paths"
|
|
20
20
|
|
|
21
21
|
const COMPACTION_THRESHOLD_BYTES = 2 * 1024 * 1024
|
|
22
|
+
const STALE_EMPTY_CHAT_MAX_AGE_MS = 5 * 60 * 1000
|
|
22
23
|
interface LegacyTranscriptStats {
|
|
23
24
|
hasLegacyData: boolean
|
|
24
25
|
sources: Array<"snapshot" | "messages_log">
|
|
@@ -454,6 +455,41 @@ export class EventStore {
|
|
|
454
455
|
await this.append(this.chatsLogPath, event)
|
|
455
456
|
}
|
|
456
457
|
|
|
458
|
+
async pruneStaleEmptyChats(args?: {
|
|
459
|
+
now?: number
|
|
460
|
+
maxAgeMs?: number
|
|
461
|
+
activeChatIds?: Iterable<string>
|
|
462
|
+
}) {
|
|
463
|
+
const now = args?.now ?? Date.now()
|
|
464
|
+
const maxAgeMs = args?.maxAgeMs ?? STALE_EMPTY_CHAT_MAX_AGE_MS
|
|
465
|
+
const activeChatIds = new Set(args?.activeChatIds ?? [])
|
|
466
|
+
const prunedChatIds: string[] = []
|
|
467
|
+
|
|
468
|
+
for (const chat of this.state.chatsById.values()) {
|
|
469
|
+
if (chat.deletedAt || activeChatIds.has(chat.id)) continue
|
|
470
|
+
if (now - chat.createdAt < maxAgeMs) continue
|
|
471
|
+
if (this.getMessages(chat.id).length > 0) continue
|
|
472
|
+
|
|
473
|
+
const event: ChatEvent = {
|
|
474
|
+
v: STORE_VERSION,
|
|
475
|
+
type: "chat_deleted",
|
|
476
|
+
timestamp: now,
|
|
477
|
+
chatId: chat.id,
|
|
478
|
+
}
|
|
479
|
+
await this.append(this.chatsLogPath, event)
|
|
480
|
+
|
|
481
|
+
const transcriptPath = this.transcriptPath(chat.id)
|
|
482
|
+
await rm(transcriptPath, { force: true })
|
|
483
|
+
if (this.cachedTranscript?.chatId === chat.id) {
|
|
484
|
+
this.cachedTranscript = null
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
prunedChatIds.push(chat.id)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return prunedChatIds
|
|
491
|
+
}
|
|
492
|
+
|
|
457
493
|
async setChatProvider(chatId: string, provider: AgentProvider) {
|
|
458
494
|
const chat = this.requireChat(chatId)
|
|
459
495
|
if (chat.provider === provider) return
|
package/src/server/server.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { getProjectUploadDir } from "./paths"
|
|
|
17
17
|
|
|
18
18
|
const MAX_UPLOAD_FILES = 10
|
|
19
19
|
const MAX_UPLOAD_SIZE_BYTES = 25 * 1024 * 1024
|
|
20
|
+
const STALE_EMPTY_CHAT_PRUNE_INTERVAL_MS = 60 * 1000
|
|
20
21
|
|
|
21
22
|
export async function persistUploadedFiles(args: {
|
|
22
23
|
projectId: string
|
|
@@ -99,7 +100,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
|
|
|
99
100
|
const agent = new AgentCoordinator({
|
|
100
101
|
store,
|
|
101
102
|
onStateChange: () => {
|
|
102
|
-
router.broadcastSnapshots()
|
|
103
|
+
void router.broadcastSnapshots()
|
|
103
104
|
},
|
|
104
105
|
})
|
|
105
106
|
router = createWsRouter({
|
|
@@ -113,6 +114,9 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
|
|
|
113
114
|
machineDisplayName,
|
|
114
115
|
updateManager,
|
|
115
116
|
})
|
|
117
|
+
const staleEmptyChatPruneInterval = setInterval(() => {
|
|
118
|
+
void router.broadcastSnapshots()
|
|
119
|
+
}, STALE_EMPTY_CHAT_PRUNE_INTERVAL_MS)
|
|
116
120
|
|
|
117
121
|
const distDir = path.join(import.meta.dir, "..", "..", "dist", "client")
|
|
118
122
|
|
|
@@ -188,6 +192,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
|
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
const shutdown = async () => {
|
|
195
|
+
clearInterval(staleEmptyChatPruneInterval)
|
|
191
196
|
for (const chatId of [...agent.activeTurns.keys()]) {
|
|
192
197
|
await agent.cancel(chatId)
|
|
193
198
|
}
|
|
@@ -577,7 +577,7 @@ describe("ws-router", () => {
|
|
|
577
577
|
router.handleOpen(wsA as never)
|
|
578
578
|
router.handleOpen(wsB as never)
|
|
579
579
|
|
|
580
|
-
router.handleMessage(
|
|
580
|
+
await router.handleMessage(
|
|
581
581
|
wsA as never,
|
|
582
582
|
JSON.stringify({
|
|
583
583
|
v: 1,
|
|
@@ -586,7 +586,7 @@ describe("ws-router", () => {
|
|
|
586
586
|
topic: { type: "sidebar" },
|
|
587
587
|
})
|
|
588
588
|
)
|
|
589
|
-
router.handleMessage(
|
|
589
|
+
await router.handleMessage(
|
|
590
590
|
wsB as never,
|
|
591
591
|
JSON.stringify({
|
|
592
592
|
v: 1,
|
|
@@ -596,7 +596,7 @@ describe("ws-router", () => {
|
|
|
596
596
|
})
|
|
597
597
|
)
|
|
598
598
|
|
|
599
|
-
router.handleMessage(
|
|
599
|
+
await router.handleMessage(
|
|
600
600
|
wsA as never,
|
|
601
601
|
JSON.stringify({
|
|
602
602
|
v: 1,
|
|
@@ -606,8 +606,6 @@ describe("ws-router", () => {
|
|
|
606
606
|
})
|
|
607
607
|
)
|
|
608
608
|
|
|
609
|
-
await Promise.resolve()
|
|
610
|
-
|
|
611
609
|
expect(wsA.sent.at(-2)).toEqual({
|
|
612
610
|
v: PROTOCOL_VERSION,
|
|
613
611
|
type: "ack",
|
|
@@ -667,6 +665,83 @@ describe("ws-router", () => {
|
|
|
667
665
|
})
|
|
668
666
|
})
|
|
669
667
|
|
|
668
|
+
test("prunes stale empty chats before sending sidebar snapshots", async () => {
|
|
669
|
+
const state = createEmptyState()
|
|
670
|
+
state.projectsById.set("project-1", {
|
|
671
|
+
id: "project-1",
|
|
672
|
+
localPath: "/tmp/project",
|
|
673
|
+
title: "Project",
|
|
674
|
+
createdAt: 1,
|
|
675
|
+
updatedAt: 1,
|
|
676
|
+
})
|
|
677
|
+
state.projectIdsByPath.set("/tmp/project", "project-1")
|
|
678
|
+
state.chatsById.set("chat-stale", {
|
|
679
|
+
id: "chat-stale",
|
|
680
|
+
projectId: "project-1",
|
|
681
|
+
title: "New Chat",
|
|
682
|
+
createdAt: 1,
|
|
683
|
+
updatedAt: 1,
|
|
684
|
+
unread: false,
|
|
685
|
+
provider: null,
|
|
686
|
+
planMode: false,
|
|
687
|
+
sessionToken: null,
|
|
688
|
+
lastTurnOutcome: null,
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
let pruneCalls = 0
|
|
692
|
+
const router = createWsRouter({
|
|
693
|
+
store: {
|
|
694
|
+
state,
|
|
695
|
+
async pruneStaleEmptyChats() {
|
|
696
|
+
pruneCalls += 1
|
|
697
|
+
state.chatsById.delete("chat-stale")
|
|
698
|
+
return ["chat-stale"]
|
|
699
|
+
},
|
|
700
|
+
} as never,
|
|
701
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
702
|
+
terminals: {
|
|
703
|
+
getSnapshot: () => null,
|
|
704
|
+
onEvent: () => () => {},
|
|
705
|
+
} as never,
|
|
706
|
+
keybindings: {
|
|
707
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
708
|
+
onChange: () => () => {},
|
|
709
|
+
} as never,
|
|
710
|
+
refreshDiscovery: async () => [],
|
|
711
|
+
getDiscoveredProjects: () => [],
|
|
712
|
+
machineDisplayName: "Local Machine",
|
|
713
|
+
updateManager: null,
|
|
714
|
+
})
|
|
715
|
+
const ws = new FakeWebSocket()
|
|
716
|
+
|
|
717
|
+
await router.handleMessage(
|
|
718
|
+
ws as never,
|
|
719
|
+
JSON.stringify({
|
|
720
|
+
v: 1,
|
|
721
|
+
type: "subscribe",
|
|
722
|
+
id: "sidebar-sub-1",
|
|
723
|
+
topic: { type: "sidebar" },
|
|
724
|
+
})
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
expect(pruneCalls).toBe(1)
|
|
728
|
+
expect(ws.sent[0]).toEqual({
|
|
729
|
+
v: PROTOCOL_VERSION,
|
|
730
|
+
type: "snapshot",
|
|
731
|
+
id: "sidebar-sub-1",
|
|
732
|
+
snapshot: {
|
|
733
|
+
type: "sidebar",
|
|
734
|
+
data: {
|
|
735
|
+
projectGroups: [{
|
|
736
|
+
groupKey: "project-1",
|
|
737
|
+
localPath: "/tmp/project",
|
|
738
|
+
chats: [],
|
|
739
|
+
}],
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
})
|
|
743
|
+
})
|
|
744
|
+
|
|
670
745
|
test("broadcasts background title-generation errors to connected clients", () => {
|
|
671
746
|
let reportBackgroundError: ((message: string) => void) | null | undefined
|
|
672
747
|
const router = createWsRouter({
|
package/src/server/ws-router.ts
CHANGED
|
@@ -76,6 +76,19 @@ export function createWsRouter({
|
|
|
76
76
|
readPatch: async () => ({ patch: "" }),
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function getProtectedChatIds() {
|
|
80
|
+
return new Set([
|
|
81
|
+
...agent.getActiveStatuses().keys(),
|
|
82
|
+
...agent.getDrainingChatIds().values(),
|
|
83
|
+
])
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function maybePruneStaleEmptyChats() {
|
|
87
|
+
await store.pruneStaleEmptyChats?.({
|
|
88
|
+
activeChatIds: getProtectedChatIds(),
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
79
92
|
function createEnvelope(id: string, topic: SubscriptionTopic): ServerEnvelope {
|
|
80
93
|
if (topic.type === "sidebar") {
|
|
81
94
|
return {
|
|
@@ -179,7 +192,10 @@ export function createWsRouter({
|
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
194
|
|
|
182
|
-
function pushSnapshots(ws: ServerWebSocket<ClientState
|
|
195
|
+
async function pushSnapshots(ws: ServerWebSocket<ClientState>, options?: { skipPrune?: boolean }) {
|
|
196
|
+
if (!options?.skipPrune) {
|
|
197
|
+
await maybePruneStaleEmptyChats()
|
|
198
|
+
}
|
|
183
199
|
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
184
200
|
for (const [id, topic] of ws.data.subscriptions.entries()) {
|
|
185
201
|
const envelope = createEnvelope(id, topic)
|
|
@@ -193,9 +209,10 @@ export function createWsRouter({
|
|
|
193
209
|
}
|
|
194
210
|
}
|
|
195
211
|
|
|
196
|
-
function broadcastSnapshots() {
|
|
212
|
+
async function broadcastSnapshots() {
|
|
213
|
+
await maybePruneStaleEmptyChats()
|
|
197
214
|
for (const ws of sockets) {
|
|
198
|
-
pushSnapshots(ws)
|
|
215
|
+
await pushSnapshots(ws, { skipPrune: true })
|
|
199
216
|
}
|
|
200
217
|
}
|
|
201
218
|
|
|
@@ -403,7 +420,7 @@ export function createWsRouter({
|
|
|
403
420
|
const changed = await resolvedDiffStore.refreshSnapshot(project.id, project.localPath)
|
|
404
421
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
405
422
|
if (changed) {
|
|
406
|
-
broadcastSnapshots()
|
|
423
|
+
void broadcastSnapshots()
|
|
407
424
|
}
|
|
408
425
|
return
|
|
409
426
|
}
|
|
@@ -415,7 +432,7 @@ export function createWsRouter({
|
|
|
415
432
|
})
|
|
416
433
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
417
434
|
if (result.snapshotChanged) {
|
|
418
|
-
broadcastSnapshots()
|
|
435
|
+
void broadcastSnapshots()
|
|
419
436
|
}
|
|
420
437
|
return
|
|
421
438
|
}
|
|
@@ -447,7 +464,7 @@ export function createWsRouter({
|
|
|
447
464
|
})
|
|
448
465
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
449
466
|
if (result.snapshotChanged) {
|
|
450
|
-
broadcastSnapshots()
|
|
467
|
+
void broadcastSnapshots()
|
|
451
468
|
}
|
|
452
469
|
return
|
|
453
470
|
}
|
|
@@ -477,7 +494,7 @@ export function createWsRouter({
|
|
|
477
494
|
})
|
|
478
495
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
479
496
|
if (result.snapshotChanged) {
|
|
480
|
-
broadcastSnapshots()
|
|
497
|
+
void broadcastSnapshots()
|
|
481
498
|
}
|
|
482
499
|
return
|
|
483
500
|
}
|
|
@@ -491,7 +508,7 @@ export function createWsRouter({
|
|
|
491
508
|
})
|
|
492
509
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
493
510
|
if (result.snapshotChanged) {
|
|
494
|
-
broadcastSnapshots()
|
|
511
|
+
void broadcastSnapshots()
|
|
495
512
|
}
|
|
496
513
|
return
|
|
497
514
|
}
|
|
@@ -504,7 +521,7 @@ export function createWsRouter({
|
|
|
504
521
|
})
|
|
505
522
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
506
523
|
if (result.snapshotChanged) {
|
|
507
|
-
broadcastSnapshots()
|
|
524
|
+
void broadcastSnapshots()
|
|
508
525
|
}
|
|
509
526
|
return
|
|
510
527
|
}
|
|
@@ -518,7 +535,7 @@ export function createWsRouter({
|
|
|
518
535
|
})
|
|
519
536
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
520
537
|
if (result.snapshotChanged) {
|
|
521
|
-
broadcastSnapshots()
|
|
538
|
+
void broadcastSnapshots()
|
|
522
539
|
}
|
|
523
540
|
return
|
|
524
541
|
}
|
|
@@ -543,7 +560,7 @@ export function createWsRouter({
|
|
|
543
560
|
})
|
|
544
561
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
545
562
|
if (result.snapshotChanged) {
|
|
546
|
-
broadcastSnapshots()
|
|
563
|
+
void broadcastSnapshots()
|
|
547
564
|
}
|
|
548
565
|
return
|
|
549
566
|
}
|
|
@@ -556,7 +573,7 @@ export function createWsRouter({
|
|
|
556
573
|
})
|
|
557
574
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
558
575
|
if (result.snapshotChanged) {
|
|
559
|
-
broadcastSnapshots()
|
|
576
|
+
void broadcastSnapshots()
|
|
560
577
|
}
|
|
561
578
|
return
|
|
562
579
|
}
|
|
@@ -569,7 +586,7 @@ export function createWsRouter({
|
|
|
569
586
|
})
|
|
570
587
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
571
588
|
if (result.snapshotChanged) {
|
|
572
|
-
broadcastSnapshots()
|
|
589
|
+
void broadcastSnapshots()
|
|
573
590
|
}
|
|
574
591
|
return
|
|
575
592
|
}
|
|
@@ -628,7 +645,7 @@ export function createWsRouter({
|
|
|
628
645
|
}
|
|
629
646
|
}
|
|
630
647
|
|
|
631
|
-
broadcastSnapshots()
|
|
648
|
+
await broadcastSnapshots()
|
|
632
649
|
} catch (error) {
|
|
633
650
|
const messageText = error instanceof Error ? error.message : String(error)
|
|
634
651
|
console.error("[ws-router] command failed", {
|
|
@@ -648,7 +665,7 @@ export function createWsRouter({
|
|
|
648
665
|
sockets.delete(ws)
|
|
649
666
|
},
|
|
650
667
|
broadcastSnapshots,
|
|
651
|
-
handleMessage(ws: ServerWebSocket<ClientState>, raw: string | Buffer | ArrayBuffer | Uint8Array) {
|
|
668
|
+
async handleMessage(ws: ServerWebSocket<ClientState>, raw: string | Buffer | ArrayBuffer | Uint8Array) {
|
|
652
669
|
let parsed: unknown
|
|
653
670
|
try {
|
|
654
671
|
parsed = JSON.parse(String(raw))
|
|
@@ -669,12 +686,12 @@ export function createWsRouter({
|
|
|
669
686
|
if (parsed.topic.type === "local-projects") {
|
|
670
687
|
void refreshDiscovery().then(() => {
|
|
671
688
|
if (ws.data.subscriptions.has(parsed.id)) {
|
|
672
|
-
pushSnapshots(ws)
|
|
689
|
+
void pushSnapshots(ws)
|
|
673
690
|
}
|
|
674
691
|
})
|
|
675
692
|
return
|
|
676
693
|
}
|
|
677
|
-
pushSnapshots(ws)
|
|
694
|
+
await pushSnapshots(ws)
|
|
678
695
|
return
|
|
679
696
|
}
|
|
680
697
|
|
|
@@ -686,7 +703,7 @@ export function createWsRouter({
|
|
|
686
703
|
return
|
|
687
704
|
}
|
|
688
705
|
|
|
689
|
-
|
|
706
|
+
await handleCommand(ws, parsed)
|
|
690
707
|
},
|
|
691
708
|
dispose() {
|
|
692
709
|
agent.setBackgroundErrorReporter?.(null)
|