kanna-code 0.26.7 → 0.26.9
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/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-C6vlAZYX.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-C5RBxQW4.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -19,6 +19,22 @@ import { resolveLocalPath } from "./paths"
|
|
|
19
19
|
|
|
20
20
|
const COMPACTION_THRESHOLD_BYTES = 2 * 1024 * 1024
|
|
21
21
|
const STALE_EMPTY_CHAT_MAX_AGE_MS = 30 * 60 * 1000
|
|
22
|
+
|
|
23
|
+
function isSendToStartingProfilingEnabled() {
|
|
24
|
+
return process.env.KANNA_PROFILE_SEND_TO_STARTING === "1"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function logSendToStartingProfile(stage: string, details?: Record<string, unknown>) {
|
|
28
|
+
if (!isSendToStartingProfilingEnabled()) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log("[kanna/send->starting][server]", JSON.stringify({
|
|
33
|
+
stage,
|
|
34
|
+
...details,
|
|
35
|
+
}))
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
interface LegacyTranscriptStats {
|
|
23
39
|
hasLegacyData: boolean
|
|
24
40
|
sources: Array<"snapshot" | "messages_log">
|
|
@@ -589,13 +605,27 @@ export class EventStore {
|
|
|
589
605
|
this.requireChat(chatId)
|
|
590
606
|
const payload = `${JSON.stringify(entry)}\n`
|
|
591
607
|
const transcriptPath = this.transcriptPath(chatId)
|
|
608
|
+
const queuedAt = performance.now()
|
|
592
609
|
this.writeChain = this.writeChain.then(async () => {
|
|
610
|
+
const startedAt = performance.now()
|
|
611
|
+
const queueDelayMs = Number((startedAt - queuedAt).toFixed(1))
|
|
593
612
|
await mkdir(this.transcriptsDir, { recursive: true })
|
|
613
|
+
const beforeAppendAt = performance.now()
|
|
594
614
|
await appendFile(transcriptPath, payload, "utf8")
|
|
615
|
+
const afterAppendAt = performance.now()
|
|
595
616
|
this.applyMessageMetadata(chatId, entry)
|
|
596
617
|
if (this.cachedTranscript?.chatId === chatId) {
|
|
597
618
|
this.cachedTranscript.entries.push({ ...entry })
|
|
598
619
|
}
|
|
620
|
+
logSendToStartingProfile("event_store.append_message", {
|
|
621
|
+
chatId,
|
|
622
|
+
entryId: entry._id,
|
|
623
|
+
kind: entry.kind,
|
|
624
|
+
payloadBytes: payload.length,
|
|
625
|
+
queueDelayMs,
|
|
626
|
+
appendMs: Number((afterAppendAt - beforeAppendAt).toFixed(1)),
|
|
627
|
+
totalMs: Number((afterAppendAt - queuedAt).toFixed(1)),
|
|
628
|
+
})
|
|
599
629
|
})
|
|
600
630
|
return this.writeChain
|
|
601
631
|
}
|
package/src/server/ws-router.ts
CHANGED
|
@@ -37,6 +37,53 @@ function logSendToStartingProfile(
|
|
|
37
37
|
}))
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
function countSubscriptionsByTopic(ws: ServerWebSocket<ClientState>) {
|
|
41
|
+
let sidebar = 0
|
|
42
|
+
let chat = 0
|
|
43
|
+
let projectGit = 0
|
|
44
|
+
let localProjects = 0
|
|
45
|
+
let update = 0
|
|
46
|
+
let keybindings = 0
|
|
47
|
+
let terminal = 0
|
|
48
|
+
|
|
49
|
+
for (const topic of ws.data.subscriptions.values()) {
|
|
50
|
+
switch (topic.type) {
|
|
51
|
+
case "sidebar":
|
|
52
|
+
sidebar += 1
|
|
53
|
+
break
|
|
54
|
+
case "chat":
|
|
55
|
+
chat += 1
|
|
56
|
+
break
|
|
57
|
+
case "project-git":
|
|
58
|
+
projectGit += 1
|
|
59
|
+
break
|
|
60
|
+
case "local-projects":
|
|
61
|
+
localProjects += 1
|
|
62
|
+
break
|
|
63
|
+
case "update":
|
|
64
|
+
update += 1
|
|
65
|
+
break
|
|
66
|
+
case "keybindings":
|
|
67
|
+
keybindings += 1
|
|
68
|
+
break
|
|
69
|
+
case "terminal":
|
|
70
|
+
terminal += 1
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
total: ws.data.subscriptions.size,
|
|
77
|
+
sidebar,
|
|
78
|
+
chat,
|
|
79
|
+
projectGit,
|
|
80
|
+
localProjects,
|
|
81
|
+
update,
|
|
82
|
+
keybindings,
|
|
83
|
+
terminal,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
40
87
|
export interface ClientState {
|
|
41
88
|
subscriptions: Map<string, SubscriptionTopic>
|
|
42
89
|
snapshotSignatures: Map<string, string>
|
|
@@ -56,7 +103,9 @@ interface CreateWsRouterArgs {
|
|
|
56
103
|
}
|
|
57
104
|
|
|
58
105
|
function send(ws: ServerWebSocket<ClientState>, message: ServerEnvelope) {
|
|
59
|
-
|
|
106
|
+
const payload = JSON.stringify(message)
|
|
107
|
+
ws.send(payload)
|
|
108
|
+
return payload.length
|
|
60
109
|
}
|
|
61
110
|
|
|
62
111
|
function ensureSnapshotSignatures(ws: ServerWebSocket<ClientState>) {
|
|
@@ -100,9 +149,13 @@ export function createWsRouter({
|
|
|
100
149
|
}
|
|
101
150
|
|
|
102
151
|
function getProtectedChatIds() {
|
|
152
|
+
const activeStatuses = agent.getActiveStatuses()
|
|
153
|
+
const drainingChatIds = typeof agent.getDrainingChatIds === "function"
|
|
154
|
+
? agent.getDrainingChatIds()
|
|
155
|
+
: new Set<string>()
|
|
103
156
|
return new Set([
|
|
104
|
-
...
|
|
105
|
-
...
|
|
157
|
+
...activeStatuses.keys(),
|
|
158
|
+
...drainingChatIds.values(),
|
|
106
159
|
])
|
|
107
160
|
}
|
|
108
161
|
|
|
@@ -125,21 +178,48 @@ export function createWsRouter({
|
|
|
125
178
|
}
|
|
126
179
|
|
|
127
180
|
async function maybePruneStaleEmptyChats(extraSockets?: Iterable<ServerWebSocket<ClientState>>) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
181
|
+
const startedAt = performance.now()
|
|
182
|
+
const activeChatIds = getProtectedChatIds()
|
|
183
|
+
const protectedDraftChatIds = getProtectedDraftChatIds(extraSockets)
|
|
184
|
+
const prunedChatIds = await store.pruneStaleEmptyChats?.({
|
|
185
|
+
activeChatIds,
|
|
186
|
+
protectedChatIds: protectedDraftChatIds,
|
|
131
187
|
})
|
|
188
|
+
if (isSendToStartingProfilingEnabled()) {
|
|
189
|
+
console.log("[kanna/send->starting][server]", JSON.stringify({
|
|
190
|
+
stage: "ws.prune_stale_empty_chats",
|
|
191
|
+
elapsedMs: Number((performance.now() - startedAt).toFixed(1)),
|
|
192
|
+
activeChatCount: activeChatIds.size,
|
|
193
|
+
protectedDraftChatCount: protectedDraftChatIds.size,
|
|
194
|
+
prunedCount: prunedChatIds?.length ?? 0,
|
|
195
|
+
totalChatCount: store.state.chatsById.size,
|
|
196
|
+
totalProjectCount: store.state.projectsById.size,
|
|
197
|
+
}))
|
|
198
|
+
}
|
|
132
199
|
}
|
|
133
200
|
|
|
134
201
|
function createEnvelope(id: string, topic: SubscriptionTopic): ServerEnvelope {
|
|
135
202
|
if (topic.type === "sidebar") {
|
|
203
|
+
const startedAt = performance.now()
|
|
204
|
+
const data = deriveSidebarData(store.state, agent.getActiveStatuses())
|
|
205
|
+
if (isSendToStartingProfilingEnabled()) {
|
|
206
|
+
const totalChats = data.projectGroups.reduce((count, group) => count + group.chats.length, 0)
|
|
207
|
+
console.log("[kanna/send->starting][server]", JSON.stringify({
|
|
208
|
+
stage: "ws.sidebar_snapshot_built",
|
|
209
|
+
elapsedMs: Number((performance.now() - startedAt).toFixed(1)),
|
|
210
|
+
projectGroupCount: data.projectGroups.length,
|
|
211
|
+
chatCount: totalChats,
|
|
212
|
+
totalChatCount: store.state.chatsById.size,
|
|
213
|
+
totalProjectCount: store.state.projectsById.size,
|
|
214
|
+
}))
|
|
215
|
+
}
|
|
136
216
|
return {
|
|
137
217
|
v: PROTOCOL_VERSION,
|
|
138
218
|
type: "snapshot",
|
|
139
219
|
id,
|
|
140
220
|
snapshot: {
|
|
141
221
|
type: "sidebar",
|
|
142
|
-
data
|
|
222
|
+
data,
|
|
143
223
|
},
|
|
144
224
|
}
|
|
145
225
|
}
|
|
@@ -235,15 +315,22 @@ export function createWsRouter({
|
|
|
235
315
|
}
|
|
236
316
|
|
|
237
317
|
async function pushSnapshots(ws: ServerWebSocket<ClientState>, options?: { skipPrune?: boolean }) {
|
|
318
|
+
const pushStartedAt = performance.now()
|
|
238
319
|
if (!options?.skipPrune) {
|
|
239
320
|
await maybePruneStaleEmptyChats([ws])
|
|
240
321
|
}
|
|
241
322
|
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
323
|
+
let sentCount = 0
|
|
324
|
+
let skippedCount = 0
|
|
242
325
|
for (const [id, topic] of ws.data.subscriptions.entries()) {
|
|
326
|
+
const envelopeStartedAt = performance.now()
|
|
243
327
|
const envelope = createEnvelope(id, topic)
|
|
328
|
+
const createdAt = performance.now()
|
|
244
329
|
if (envelope.type !== "snapshot") continue
|
|
245
330
|
const signature = JSON.stringify(envelope.snapshot)
|
|
331
|
+
const signatureReadyAt = performance.now()
|
|
246
332
|
if (snapshotSignatures.get(id) === signature) {
|
|
333
|
+
skippedCount += 1
|
|
247
334
|
continue
|
|
248
335
|
}
|
|
249
336
|
snapshotSignatures.set(id, signature)
|
|
@@ -253,17 +340,52 @@ export function createWsRouter({
|
|
|
253
340
|
chatId: topic.chatId,
|
|
254
341
|
status: envelope.snapshot.data.runtime.status,
|
|
255
342
|
messageCount: envelope.snapshot.data.messages.length,
|
|
343
|
+
buildMs: Number((createdAt - envelopeStartedAt).toFixed(1)),
|
|
344
|
+
signatureMs: Number((signatureReadyAt - createdAt).toFixed(1)),
|
|
345
|
+
signatureBytes: signature.length,
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
const payloadBytes = send(ws, envelope)
|
|
349
|
+
sentCount += 1
|
|
350
|
+
if (topic.type === "chat" && envelope.snapshot.type === "chat" && envelope.snapshot.data?.runtime.status === "starting") {
|
|
351
|
+
const profile = agent.getActiveTurnProfile(topic.chatId)
|
|
352
|
+
logSendToStartingProfile(profile?.traceId, profile?.startedAt, "ws.snapshot_send_completed", {
|
|
353
|
+
chatId: topic.chatId,
|
|
354
|
+
payloadBytes,
|
|
256
355
|
})
|
|
257
356
|
}
|
|
258
|
-
|
|
357
|
+
}
|
|
358
|
+
if (isSendToStartingProfilingEnabled()) {
|
|
359
|
+
console.log("[kanna/send->starting][server]", JSON.stringify({
|
|
360
|
+
stage: "ws.push_snapshots_completed",
|
|
361
|
+
elapsedMs: Number((performance.now() - pushStartedAt).toFixed(1)),
|
|
362
|
+
skipPrune: Boolean(options?.skipPrune),
|
|
363
|
+
sentCount,
|
|
364
|
+
skippedCount,
|
|
365
|
+
...countSubscriptionsByTopic(ws),
|
|
366
|
+
}))
|
|
259
367
|
}
|
|
260
368
|
}
|
|
261
369
|
|
|
262
370
|
async function broadcastSnapshots() {
|
|
371
|
+
const startedAt = performance.now()
|
|
263
372
|
await maybePruneStaleEmptyChats()
|
|
373
|
+
const afterPruneAt = performance.now()
|
|
374
|
+
let socketCount = 0
|
|
264
375
|
for (const ws of sockets) {
|
|
376
|
+
socketCount += 1
|
|
265
377
|
await pushSnapshots(ws, { skipPrune: true })
|
|
266
378
|
}
|
|
379
|
+
if (isSendToStartingProfilingEnabled()) {
|
|
380
|
+
console.log("[kanna/send->starting][server]", JSON.stringify({
|
|
381
|
+
stage: "ws.broadcast_snapshots_completed",
|
|
382
|
+
elapsedMs: Number((performance.now() - startedAt).toFixed(1)),
|
|
383
|
+
pruneMs: Number((afterPruneAt - startedAt).toFixed(1)),
|
|
384
|
+
socketCount,
|
|
385
|
+
totalChatCount: store.state.chatsById.size,
|
|
386
|
+
totalProjectCount: store.state.projectsById.size,
|
|
387
|
+
}))
|
|
388
|
+
}
|
|
267
389
|
}
|
|
268
390
|
|
|
269
391
|
function broadcastError(message: string) {
|
|
@@ -473,7 +595,11 @@ export function createWsRouter({
|
|
|
473
595
|
logSendToStartingProfile(profile?.traceId ?? command.clientTraceId, profile?.startedAt, "ws.chat_send_ack", {
|
|
474
596
|
chatId: result.chatId ?? null,
|
|
475
597
|
})
|
|
476
|
-
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
598
|
+
const payloadBytes = send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
599
|
+
logSendToStartingProfile(profile?.traceId ?? command.clientTraceId, profile?.startedAt, "ws.chat_send_ack_completed", {
|
|
600
|
+
chatId: result.chatId ?? null,
|
|
601
|
+
payloadBytes,
|
|
602
|
+
})
|
|
477
603
|
break
|
|
478
604
|
}
|
|
479
605
|
case "chat.refreshDiffs": {
|