kanna-code 0.25.1 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{index-Cr8ogroL.js → index-BsfPN3Jg.js} +221 -192
- package/dist/client/assets/index-CzFdfKR3.css +32 -0
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/diff-store.ts +276 -0
- 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 +82 -17
- package/src/shared/protocol.ts +11 -0
- package/src/shared/types.ts +13 -0
- package/dist/client/assets/index-Hy4LEj8j.css +0 -32
|
@@ -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
|
@@ -22,7 +22,7 @@ export interface ClientState {
|
|
|
22
22
|
|
|
23
23
|
interface CreateWsRouterArgs {
|
|
24
24
|
store: EventStore
|
|
25
|
-
diffStore?: Pick<DiffStore, "getProjectSnapshot" | "refreshSnapshot" | "listBranches" | "previewMergeBranch" | "mergeBranch" | "syncBranch" | "checkoutBranch" | "createBranch" | "generateCommitMessage" | "commitFiles" | "discardFile" | "ignoreFile" | "readPatch">
|
|
25
|
+
diffStore?: Pick<DiffStore, "getProjectSnapshot" | "refreshSnapshot" | "initializeGit" | "getGitHubPublishInfo" | "checkGitHubRepoAvailability" | "publishToGitHub" | "listBranches" | "previewMergeBranch" | "mergeBranch" | "syncBranch" | "checkoutBranch" | "createBranch" | "generateCommitMessage" | "commitFiles" | "discardFile" | "ignoreFile" | "readPatch">
|
|
26
26
|
agent: AgentCoordinator
|
|
27
27
|
terminals: TerminalManager
|
|
28
28
|
keybindings: KeybindingsManager
|
|
@@ -59,6 +59,10 @@ export function createWsRouter({
|
|
|
59
59
|
const resolvedDiffStore = diffStore ?? {
|
|
60
60
|
getProjectSnapshot: () => ({ status: "unknown", branchName: undefined, defaultBranchName: undefined, originRepoSlug: undefined, hasUpstream: undefined, aheadCount: undefined, behindCount: undefined, lastFetchedAt: undefined, files: [] as const, branchHistory: { entries: [] as const } }),
|
|
61
61
|
refreshSnapshot: async () => false,
|
|
62
|
+
initializeGit: async () => ({ ok: true, branchName: undefined, snapshotChanged: false }),
|
|
63
|
+
getGitHubPublishInfo: async () => ({ ghInstalled: false, authenticated: false, activeAccountLogin: undefined, owners: [], suggestedRepoName: "my-repo" }),
|
|
64
|
+
checkGitHubRepoAvailability: async () => ({ available: false, message: "Unavailable" }),
|
|
65
|
+
publishToGitHub: async () => ({ ok: false, title: "Publish failed", message: "Unavailable", snapshotChanged: false }),
|
|
62
66
|
listBranches: async () => ({ recent: [], local: [], remote: [], pullRequests: [], pullRequestsStatus: "unavailable" as const }),
|
|
63
67
|
previewMergeBranch: async () => ({ currentBranchName: undefined, targetBranchName: "", targetDisplayName: "", status: "error" as const, commitCount: 0, hasConflicts: false, message: "Merge preview unavailable." }),
|
|
64
68
|
mergeBranch: async () => ({ ok: false as const, title: "Merge failed", message: "Merge unavailable.", snapshotChanged: false }),
|
|
@@ -72,6 +76,19 @@ export function createWsRouter({
|
|
|
72
76
|
readPatch: async () => ({ patch: "" }),
|
|
73
77
|
}
|
|
74
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
|
+
|
|
75
92
|
function createEnvelope(id: string, topic: SubscriptionTopic): ServerEnvelope {
|
|
76
93
|
if (topic.type === "sidebar") {
|
|
77
94
|
return {
|
|
@@ -175,7 +192,10 @@ export function createWsRouter({
|
|
|
175
192
|
}
|
|
176
193
|
}
|
|
177
194
|
|
|
178
|
-
function pushSnapshots(ws: ServerWebSocket<ClientState
|
|
195
|
+
async function pushSnapshots(ws: ServerWebSocket<ClientState>, options?: { skipPrune?: boolean }) {
|
|
196
|
+
if (!options?.skipPrune) {
|
|
197
|
+
await maybePruneStaleEmptyChats()
|
|
198
|
+
}
|
|
179
199
|
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
180
200
|
for (const [id, topic] of ws.data.subscriptions.entries()) {
|
|
181
201
|
const envelope = createEnvelope(id, topic)
|
|
@@ -189,9 +209,10 @@ export function createWsRouter({
|
|
|
189
209
|
}
|
|
190
210
|
}
|
|
191
211
|
|
|
192
|
-
function broadcastSnapshots() {
|
|
212
|
+
async function broadcastSnapshots() {
|
|
213
|
+
await maybePruneStaleEmptyChats()
|
|
193
214
|
for (const ws of sockets) {
|
|
194
|
-
pushSnapshots(ws)
|
|
215
|
+
await pushSnapshots(ws, { skipPrune: true })
|
|
195
216
|
}
|
|
196
217
|
}
|
|
197
218
|
|
|
@@ -399,7 +420,51 @@ export function createWsRouter({
|
|
|
399
420
|
const changed = await resolvedDiffStore.refreshSnapshot(project.id, project.localPath)
|
|
400
421
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
401
422
|
if (changed) {
|
|
402
|
-
broadcastSnapshots()
|
|
423
|
+
void broadcastSnapshots()
|
|
424
|
+
}
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
case "chat.initGit": {
|
|
428
|
+
const { project } = resolveChatProject(command.chatId)
|
|
429
|
+
const result = await resolvedDiffStore.initializeGit({
|
|
430
|
+
projectId: project.id,
|
|
431
|
+
projectPath: project.localPath,
|
|
432
|
+
})
|
|
433
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
434
|
+
if (result.snapshotChanged) {
|
|
435
|
+
void broadcastSnapshots()
|
|
436
|
+
}
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
case "chat.getGitHubPublishInfo": {
|
|
440
|
+
const { project } = resolveChatProject(command.chatId)
|
|
441
|
+
const result = await resolvedDiffStore.getGitHubPublishInfo({
|
|
442
|
+
projectPath: project.localPath,
|
|
443
|
+
})
|
|
444
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
case "chat.checkGitHubRepoAvailability": {
|
|
448
|
+
const result = await resolvedDiffStore.checkGitHubRepoAvailability({
|
|
449
|
+
owner: command.owner,
|
|
450
|
+
name: command.name,
|
|
451
|
+
})
|
|
452
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
case "chat.publishToGitHub": {
|
|
456
|
+
const { project } = resolveChatProject(command.chatId)
|
|
457
|
+
const result = await resolvedDiffStore.publishToGitHub({
|
|
458
|
+
projectId: project.id,
|
|
459
|
+
projectPath: project.localPath,
|
|
460
|
+
owner: command.owner,
|
|
461
|
+
name: command.name,
|
|
462
|
+
visibility: command.visibility,
|
|
463
|
+
description: command.description,
|
|
464
|
+
})
|
|
465
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
466
|
+
if (result.snapshotChanged) {
|
|
467
|
+
void broadcastSnapshots()
|
|
403
468
|
}
|
|
404
469
|
return
|
|
405
470
|
}
|
|
@@ -429,7 +494,7 @@ export function createWsRouter({
|
|
|
429
494
|
})
|
|
430
495
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
431
496
|
if (result.snapshotChanged) {
|
|
432
|
-
broadcastSnapshots()
|
|
497
|
+
void broadcastSnapshots()
|
|
433
498
|
}
|
|
434
499
|
return
|
|
435
500
|
}
|
|
@@ -443,7 +508,7 @@ export function createWsRouter({
|
|
|
443
508
|
})
|
|
444
509
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
445
510
|
if (result.snapshotChanged) {
|
|
446
|
-
broadcastSnapshots()
|
|
511
|
+
void broadcastSnapshots()
|
|
447
512
|
}
|
|
448
513
|
return
|
|
449
514
|
}
|
|
@@ -456,7 +521,7 @@ export function createWsRouter({
|
|
|
456
521
|
})
|
|
457
522
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
458
523
|
if (result.snapshotChanged) {
|
|
459
|
-
broadcastSnapshots()
|
|
524
|
+
void broadcastSnapshots()
|
|
460
525
|
}
|
|
461
526
|
return
|
|
462
527
|
}
|
|
@@ -470,7 +535,7 @@ export function createWsRouter({
|
|
|
470
535
|
})
|
|
471
536
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
472
537
|
if (result.snapshotChanged) {
|
|
473
|
-
broadcastSnapshots()
|
|
538
|
+
void broadcastSnapshots()
|
|
474
539
|
}
|
|
475
540
|
return
|
|
476
541
|
}
|
|
@@ -495,7 +560,7 @@ export function createWsRouter({
|
|
|
495
560
|
})
|
|
496
561
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
497
562
|
if (result.snapshotChanged) {
|
|
498
|
-
broadcastSnapshots()
|
|
563
|
+
void broadcastSnapshots()
|
|
499
564
|
}
|
|
500
565
|
return
|
|
501
566
|
}
|
|
@@ -508,7 +573,7 @@ export function createWsRouter({
|
|
|
508
573
|
})
|
|
509
574
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
510
575
|
if (result.snapshotChanged) {
|
|
511
|
-
broadcastSnapshots()
|
|
576
|
+
void broadcastSnapshots()
|
|
512
577
|
}
|
|
513
578
|
return
|
|
514
579
|
}
|
|
@@ -521,7 +586,7 @@ export function createWsRouter({
|
|
|
521
586
|
})
|
|
522
587
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
523
588
|
if (result.snapshotChanged) {
|
|
524
|
-
broadcastSnapshots()
|
|
589
|
+
void broadcastSnapshots()
|
|
525
590
|
}
|
|
526
591
|
return
|
|
527
592
|
}
|
|
@@ -580,7 +645,7 @@ export function createWsRouter({
|
|
|
580
645
|
}
|
|
581
646
|
}
|
|
582
647
|
|
|
583
|
-
broadcastSnapshots()
|
|
648
|
+
await broadcastSnapshots()
|
|
584
649
|
} catch (error) {
|
|
585
650
|
const messageText = error instanceof Error ? error.message : String(error)
|
|
586
651
|
console.error("[ws-router] command failed", {
|
|
@@ -600,7 +665,7 @@ export function createWsRouter({
|
|
|
600
665
|
sockets.delete(ws)
|
|
601
666
|
},
|
|
602
667
|
broadcastSnapshots,
|
|
603
|
-
handleMessage(ws: ServerWebSocket<ClientState>, raw: string | Buffer | ArrayBuffer | Uint8Array) {
|
|
668
|
+
async handleMessage(ws: ServerWebSocket<ClientState>, raw: string | Buffer | ArrayBuffer | Uint8Array) {
|
|
604
669
|
let parsed: unknown
|
|
605
670
|
try {
|
|
606
671
|
parsed = JSON.parse(String(raw))
|
|
@@ -621,12 +686,12 @@ export function createWsRouter({
|
|
|
621
686
|
if (parsed.topic.type === "local-projects") {
|
|
622
687
|
void refreshDiscovery().then(() => {
|
|
623
688
|
if (ws.data.subscriptions.has(parsed.id)) {
|
|
624
|
-
pushSnapshots(ws)
|
|
689
|
+
void pushSnapshots(ws)
|
|
625
690
|
}
|
|
626
691
|
})
|
|
627
692
|
return
|
|
628
693
|
}
|
|
629
|
-
pushSnapshots(ws)
|
|
694
|
+
await pushSnapshots(ws)
|
|
630
695
|
return
|
|
631
696
|
}
|
|
632
697
|
|
|
@@ -638,7 +703,7 @@ export function createWsRouter({
|
|
|
638
703
|
return
|
|
639
704
|
}
|
|
640
705
|
|
|
641
|
-
|
|
706
|
+
await handleCommand(ws, parsed)
|
|
642
707
|
},
|
|
643
708
|
dispose() {
|
|
644
709
|
agent.setBackgroundErrorReporter?.(null)
|
package/src/shared/protocol.ts
CHANGED
|
@@ -81,6 +81,17 @@ export type ClientCommand =
|
|
|
81
81
|
planMode?: boolean
|
|
82
82
|
}
|
|
83
83
|
| { type: "chat.refreshDiffs"; chatId: string }
|
|
84
|
+
| { type: "chat.initGit"; chatId: string }
|
|
85
|
+
| { type: "chat.getGitHubPublishInfo"; chatId: string }
|
|
86
|
+
| { type: "chat.checkGitHubRepoAvailability"; chatId: string; owner: string; name: string }
|
|
87
|
+
| {
|
|
88
|
+
type: "chat.publishToGitHub"
|
|
89
|
+
chatId: string
|
|
90
|
+
owner: string
|
|
91
|
+
name: string
|
|
92
|
+
visibility: "public" | "private"
|
|
93
|
+
description?: string
|
|
94
|
+
}
|
|
84
95
|
| { type: "chat.listBranches"; chatId: string }
|
|
85
96
|
| {
|
|
86
97
|
type: "chat.previewMergeBranch"
|
package/src/shared/types.ts
CHANGED
|
@@ -528,6 +528,19 @@ export interface ChatBranchListResult {
|
|
|
528
528
|
pullRequestsError?: string
|
|
529
529
|
}
|
|
530
530
|
|
|
531
|
+
export interface GitHubPublishInfo {
|
|
532
|
+
ghInstalled: boolean
|
|
533
|
+
authenticated: boolean
|
|
534
|
+
activeAccountLogin?: string
|
|
535
|
+
owners: string[]
|
|
536
|
+
suggestedRepoName: string
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export interface GitHubRepoAvailabilityResult {
|
|
540
|
+
available: boolean
|
|
541
|
+
message: string
|
|
542
|
+
}
|
|
543
|
+
|
|
531
544
|
export interface BranchMetadata {
|
|
532
545
|
branchName?: string
|
|
533
546
|
defaultBranchName?: string
|