kanna-code 0.22.0 → 0.23.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-DADxOnBF.css +32 -0
- package/dist/client/assets/index-y8BVCflz.js +2506 -0
- package/dist/client/index.html +2 -2
- package/package.json +2 -1
- package/src/server/diff-store.test.ts +133 -2
- package/src/server/diff-store.ts +136 -1
- package/src/server/generate-commit-message.test.ts +2 -0
- package/src/server/generate-commit-message.ts +2 -2
- package/src/server/ws-router.test.ts +196 -14
- package/src/server/ws-router.ts +43 -1
- package/src/shared/protocol.ts +2 -0
- package/src/shared/types.ts +1 -0
- package/dist/client/assets/index-1BGyFTrZ.js +0 -2508
- package/dist/client/assets/index-DKD9EePK.css +0 -32
|
@@ -38,7 +38,7 @@ const DEFAULT_UPDATE_SNAPSHOT: UpdateSnapshot = {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
describe("ws-router", () => {
|
|
41
|
-
test("acks system.ping without broadcasting snapshots", () => {
|
|
41
|
+
test("acks system.ping without broadcasting snapshots", async () => {
|
|
42
42
|
const router = createWsRouter({
|
|
43
43
|
store: { state: createEmptyState() } as never,
|
|
44
44
|
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
@@ -58,7 +58,7 @@ describe("ws-router", () => {
|
|
|
58
58
|
const ws = new FakeWebSocket()
|
|
59
59
|
|
|
60
60
|
ws.data.subscriptions.set("sub-1", { type: "sidebar" })
|
|
61
|
-
router.handleMessage(
|
|
61
|
+
await router.handleMessage(
|
|
62
62
|
ws as never,
|
|
63
63
|
JSON.stringify({
|
|
64
64
|
v: 1,
|
|
@@ -77,7 +77,7 @@ describe("ws-router", () => {
|
|
|
77
77
|
])
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
-
test("acks terminal.input without rebroadcasting terminal snapshots", () => {
|
|
80
|
+
test("acks terminal.input without rebroadcasting terminal snapshots", async () => {
|
|
81
81
|
const router = createWsRouter({
|
|
82
82
|
store: { state: createEmptyState() } as never,
|
|
83
83
|
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
@@ -98,7 +98,7 @@ describe("ws-router", () => {
|
|
|
98
98
|
const ws = new FakeWebSocket()
|
|
99
99
|
|
|
100
100
|
ws.data.subscriptions.set("sub-terminal", { type: "terminal", terminalId: "terminal-1" })
|
|
101
|
-
router.handleMessage(
|
|
101
|
+
await router.handleMessage(
|
|
102
102
|
ws as never,
|
|
103
103
|
JSON.stringify({
|
|
104
104
|
v: 1,
|
|
@@ -121,7 +121,7 @@ describe("ws-router", () => {
|
|
|
121
121
|
])
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
-
test("subscribes and unsubscribes chat topics", () => {
|
|
124
|
+
test("subscribes and unsubscribes chat topics", async () => {
|
|
125
125
|
const router = createWsRouter({
|
|
126
126
|
store: { state: createEmptyState() } as never,
|
|
127
127
|
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
@@ -140,7 +140,7 @@ describe("ws-router", () => {
|
|
|
140
140
|
})
|
|
141
141
|
const ws = new FakeWebSocket()
|
|
142
142
|
|
|
143
|
-
router.handleMessage(
|
|
143
|
+
await router.handleMessage(
|
|
144
144
|
ws as never,
|
|
145
145
|
JSON.stringify({
|
|
146
146
|
v: 1,
|
|
@@ -160,7 +160,7 @@ describe("ws-router", () => {
|
|
|
160
160
|
},
|
|
161
161
|
})
|
|
162
162
|
|
|
163
|
-
router.handleMessage(
|
|
163
|
+
await router.handleMessage(
|
|
164
164
|
ws as never,
|
|
165
165
|
JSON.stringify({
|
|
166
166
|
v: 1,
|
|
@@ -176,7 +176,7 @@ describe("ws-router", () => {
|
|
|
176
176
|
})
|
|
177
177
|
})
|
|
178
178
|
|
|
179
|
-
test("loads older chat history pages", () => {
|
|
179
|
+
test("loads older chat history pages", async () => {
|
|
180
180
|
const state = createEmptyState()
|
|
181
181
|
state.projectsById.set("project-1", {
|
|
182
182
|
id: "project-1",
|
|
@@ -230,7 +230,7 @@ describe("ws-router", () => {
|
|
|
230
230
|
})
|
|
231
231
|
const ws = new FakeWebSocket()
|
|
232
232
|
|
|
233
|
-
router.handleMessage(
|
|
233
|
+
await router.handleMessage(
|
|
234
234
|
ws as never,
|
|
235
235
|
JSON.stringify({
|
|
236
236
|
v: 1,
|
|
@@ -472,7 +472,7 @@ describe("ws-router", () => {
|
|
|
472
472
|
})
|
|
473
473
|
const ws = new FakeWebSocket()
|
|
474
474
|
|
|
475
|
-
router.handleMessage(
|
|
475
|
+
await router.handleMessage(
|
|
476
476
|
ws as never,
|
|
477
477
|
JSON.stringify({
|
|
478
478
|
v: 1,
|
|
@@ -492,7 +492,7 @@ describe("ws-router", () => {
|
|
|
492
492
|
},
|
|
493
493
|
})
|
|
494
494
|
|
|
495
|
-
router.handleMessage(
|
|
495
|
+
await router.handleMessage(
|
|
496
496
|
ws as never,
|
|
497
497
|
JSON.stringify({
|
|
498
498
|
v: 1,
|
|
@@ -576,7 +576,7 @@ describe("ws-router", () => {
|
|
|
576
576
|
})
|
|
577
577
|
const ws = new FakeWebSocket()
|
|
578
578
|
|
|
579
|
-
router.handleMessage(
|
|
579
|
+
await router.handleMessage(
|
|
580
580
|
ws as never,
|
|
581
581
|
JSON.stringify({
|
|
582
582
|
v: 1,
|
|
@@ -596,7 +596,7 @@ describe("ws-router", () => {
|
|
|
596
596
|
},
|
|
597
597
|
})
|
|
598
598
|
|
|
599
|
-
router.handleMessage(
|
|
599
|
+
await router.handleMessage(
|
|
600
600
|
ws as never,
|
|
601
601
|
JSON.stringify({
|
|
602
602
|
v: 1,
|
|
@@ -625,7 +625,7 @@ describe("ws-router", () => {
|
|
|
625
625
|
},
|
|
626
626
|
})
|
|
627
627
|
|
|
628
|
-
router.handleMessage(
|
|
628
|
+
await router.handleMessage(
|
|
629
629
|
ws as never,
|
|
630
630
|
JSON.stringify({
|
|
631
631
|
v: 1,
|
|
@@ -651,4 +651,186 @@ describe("ws-router", () => {
|
|
|
651
651
|
},
|
|
652
652
|
})
|
|
653
653
|
})
|
|
654
|
+
|
|
655
|
+
test("routes discard diff file commands through the diff store and rebroadcasts chat snapshots", async () => {
|
|
656
|
+
const state = createEmptyState()
|
|
657
|
+
state.projectsById.set("project-1", {
|
|
658
|
+
id: "project-1",
|
|
659
|
+
localPath: "/tmp/project",
|
|
660
|
+
title: "Project",
|
|
661
|
+
createdAt: 1,
|
|
662
|
+
updatedAt: 1,
|
|
663
|
+
})
|
|
664
|
+
state.projectIdsByPath.set("/tmp/project", "project-1")
|
|
665
|
+
state.chatsById.set("chat-1", {
|
|
666
|
+
id: "chat-1",
|
|
667
|
+
projectId: "project-1",
|
|
668
|
+
title: "Chat",
|
|
669
|
+
createdAt: 1,
|
|
670
|
+
updatedAt: 1,
|
|
671
|
+
unread: false,
|
|
672
|
+
provider: null,
|
|
673
|
+
planMode: false,
|
|
674
|
+
sessionToken: null,
|
|
675
|
+
lastTurnOutcome: null,
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
const discardCalls: Array<{ chatId: string; projectPath: string; path: string }> = []
|
|
679
|
+
const diffStore = {
|
|
680
|
+
getSnapshot: () => ({ status: "ready" as const, files: [] }),
|
|
681
|
+
refreshSnapshot: async () => false,
|
|
682
|
+
generateCommitMessage: async () => ({ subject: "", body: "" }),
|
|
683
|
+
commitFiles: async () => ({ ok: true as const, mode: "commit_only" as const, pushed: false, snapshotChanged: false }),
|
|
684
|
+
discardFile: async (args: { chatId: string; projectPath: string; path: string }) => {
|
|
685
|
+
discardCalls.push(args)
|
|
686
|
+
return { snapshotChanged: true }
|
|
687
|
+
},
|
|
688
|
+
ignoreFile: async () => ({ snapshotChanged: false }),
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const router = createWsRouter({
|
|
692
|
+
store: {
|
|
693
|
+
state,
|
|
694
|
+
getChat: (chatId: string) => state.chatsById.get(chatId) ?? null,
|
|
695
|
+
getProject: (projectId: string) => state.projectsById.get(projectId) ?? null,
|
|
696
|
+
getRecentChatHistory: () => ({ entries: [], hasOlder: false, olderCursor: null }),
|
|
697
|
+
} as never,
|
|
698
|
+
diffStore: diffStore as never,
|
|
699
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
700
|
+
terminals: {
|
|
701
|
+
getSnapshot: () => null,
|
|
702
|
+
onEvent: () => () => {},
|
|
703
|
+
} as never,
|
|
704
|
+
keybindings: {
|
|
705
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
706
|
+
onChange: () => () => {},
|
|
707
|
+
} as never,
|
|
708
|
+
refreshDiscovery: async () => [],
|
|
709
|
+
getDiscoveredProjects: () => [],
|
|
710
|
+
machineDisplayName: "Local Machine",
|
|
711
|
+
updateManager: null,
|
|
712
|
+
})
|
|
713
|
+
const ws = new FakeWebSocket()
|
|
714
|
+
|
|
715
|
+
router.handleOpen(ws as never)
|
|
716
|
+
router.handleMessage(
|
|
717
|
+
ws as never,
|
|
718
|
+
JSON.stringify({
|
|
719
|
+
v: 1,
|
|
720
|
+
type: "subscribe",
|
|
721
|
+
id: "chat-sub",
|
|
722
|
+
topic: { type: "chat", chatId: "chat-1" },
|
|
723
|
+
})
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
await router.handleMessage(
|
|
727
|
+
ws as never,
|
|
728
|
+
JSON.stringify({
|
|
729
|
+
v: 1,
|
|
730
|
+
type: "command",
|
|
731
|
+
id: "discard-1",
|
|
732
|
+
command: {
|
|
733
|
+
type: "chat.discardDiffFile",
|
|
734
|
+
chatId: "chat-1",
|
|
735
|
+
path: "app.txt",
|
|
736
|
+
},
|
|
737
|
+
})
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
expect(discardCalls).toEqual([{
|
|
741
|
+
chatId: "chat-1",
|
|
742
|
+
projectPath: "/tmp/project",
|
|
743
|
+
path: "app.txt",
|
|
744
|
+
}])
|
|
745
|
+
expect(ws.sent).toContainEqual({
|
|
746
|
+
v: PROTOCOL_VERSION,
|
|
747
|
+
type: "ack",
|
|
748
|
+
id: "discard-1",
|
|
749
|
+
result: { snapshotChanged: true },
|
|
750
|
+
})
|
|
751
|
+
})
|
|
752
|
+
|
|
753
|
+
test("routes ignore diff file commands through the diff store", async () => {
|
|
754
|
+
const state = createEmptyState()
|
|
755
|
+
state.projectsById.set("project-1", {
|
|
756
|
+
id: "project-1",
|
|
757
|
+
localPath: "/tmp/project",
|
|
758
|
+
title: "Project",
|
|
759
|
+
createdAt: 1,
|
|
760
|
+
updatedAt: 1,
|
|
761
|
+
})
|
|
762
|
+
state.projectIdsByPath.set("/tmp/project", "project-1")
|
|
763
|
+
state.chatsById.set("chat-1", {
|
|
764
|
+
id: "chat-1",
|
|
765
|
+
projectId: "project-1",
|
|
766
|
+
title: "Chat",
|
|
767
|
+
createdAt: 1,
|
|
768
|
+
updatedAt: 1,
|
|
769
|
+
unread: false,
|
|
770
|
+
provider: null,
|
|
771
|
+
planMode: false,
|
|
772
|
+
sessionToken: null,
|
|
773
|
+
lastTurnOutcome: null,
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
const ignoreCalls: Array<{ chatId: string; projectPath: string; path: string }> = []
|
|
777
|
+
const router = createWsRouter({
|
|
778
|
+
store: {
|
|
779
|
+
state,
|
|
780
|
+
getChat: (chatId: string) => state.chatsById.get(chatId) ?? null,
|
|
781
|
+
getProject: (projectId: string) => state.projectsById.get(projectId) ?? null,
|
|
782
|
+
} as never,
|
|
783
|
+
diffStore: {
|
|
784
|
+
getSnapshot: () => ({ status: "ready" as const, files: [] }),
|
|
785
|
+
refreshSnapshot: async () => false,
|
|
786
|
+
generateCommitMessage: async () => ({ subject: "", body: "" }),
|
|
787
|
+
commitFiles: async () => ({ ok: true as const, mode: "commit_only" as const, pushed: false, snapshotChanged: false }),
|
|
788
|
+
discardFile: async () => ({ snapshotChanged: false }),
|
|
789
|
+
ignoreFile: async (args: { chatId: string; projectPath: string; path: string }) => {
|
|
790
|
+
ignoreCalls.push(args)
|
|
791
|
+
return { snapshotChanged: false }
|
|
792
|
+
},
|
|
793
|
+
} as never,
|
|
794
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
795
|
+
terminals: {
|
|
796
|
+
getSnapshot: () => null,
|
|
797
|
+
onEvent: () => () => {},
|
|
798
|
+
} as never,
|
|
799
|
+
keybindings: {
|
|
800
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
801
|
+
onChange: () => () => {},
|
|
802
|
+
} as never,
|
|
803
|
+
refreshDiscovery: async () => [],
|
|
804
|
+
getDiscoveredProjects: () => [],
|
|
805
|
+
machineDisplayName: "Local Machine",
|
|
806
|
+
updateManager: null,
|
|
807
|
+
})
|
|
808
|
+
const ws = new FakeWebSocket()
|
|
809
|
+
|
|
810
|
+
await router.handleMessage(
|
|
811
|
+
ws as never,
|
|
812
|
+
JSON.stringify({
|
|
813
|
+
v: 1,
|
|
814
|
+
type: "command",
|
|
815
|
+
id: "ignore-1",
|
|
816
|
+
command: {
|
|
817
|
+
type: "chat.ignoreDiffFile",
|
|
818
|
+
chatId: "chat-1",
|
|
819
|
+
path: "scratch.log",
|
|
820
|
+
},
|
|
821
|
+
})
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
expect(ignoreCalls).toEqual([{
|
|
825
|
+
chatId: "chat-1",
|
|
826
|
+
projectPath: "/tmp/project",
|
|
827
|
+
path: "scratch.log",
|
|
828
|
+
}])
|
|
829
|
+
expect(ws.sent).toContainEqual({
|
|
830
|
+
v: PROTOCOL_VERSION,
|
|
831
|
+
type: "ack",
|
|
832
|
+
id: "ignore-1",
|
|
833
|
+
result: { snapshotChanged: false },
|
|
834
|
+
})
|
|
835
|
+
})
|
|
654
836
|
})
|
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, "getSnapshot" | "refreshSnapshot" | "generateCommitMessage" | "commitFiles">
|
|
25
|
+
diffStore?: Pick<DiffStore, "getSnapshot" | "refreshSnapshot" | "generateCommitMessage" | "commitFiles" | "discardFile" | "ignoreFile">
|
|
26
26
|
agent: AgentCoordinator
|
|
27
27
|
terminals: TerminalManager
|
|
28
28
|
keybindings: KeybindingsManager
|
|
@@ -61,6 +61,8 @@ export function createWsRouter({
|
|
|
61
61
|
refreshSnapshot: async () => false,
|
|
62
62
|
generateCommitMessage: async () => ({ subject: "Update selected files", body: "", usedFallback: true, failureMessage: null }),
|
|
63
63
|
commitFiles: async () => ({ ok: true, mode: "commit_only" as const, branchName: undefined, pushed: false, snapshotChanged: false }),
|
|
64
|
+
discardFile: async () => ({ snapshotChanged: false }),
|
|
65
|
+
ignoreFile: async () => ({ snapshotChanged: false }),
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
function createEnvelope(id: string, topic: SubscriptionTopic): ServerEnvelope {
|
|
@@ -407,6 +409,46 @@ export function createWsRouter({
|
|
|
407
409
|
}
|
|
408
410
|
return
|
|
409
411
|
}
|
|
412
|
+
case "chat.discardDiffFile": {
|
|
413
|
+
const chat = store.getChat(command.chatId)
|
|
414
|
+
if (!chat) {
|
|
415
|
+
throw new Error("Chat not found")
|
|
416
|
+
}
|
|
417
|
+
const project = store.getProject(chat.projectId)
|
|
418
|
+
if (!project) {
|
|
419
|
+
throw new Error("Project not found")
|
|
420
|
+
}
|
|
421
|
+
const result = await resolvedDiffStore.discardFile({
|
|
422
|
+
chatId: command.chatId,
|
|
423
|
+
projectPath: project.localPath,
|
|
424
|
+
path: command.path,
|
|
425
|
+
})
|
|
426
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
427
|
+
if (result.snapshotChanged) {
|
|
428
|
+
broadcastSnapshots()
|
|
429
|
+
}
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
case "chat.ignoreDiffFile": {
|
|
433
|
+
const chat = store.getChat(command.chatId)
|
|
434
|
+
if (!chat) {
|
|
435
|
+
throw new Error("Chat not found")
|
|
436
|
+
}
|
|
437
|
+
const project = store.getProject(chat.projectId)
|
|
438
|
+
if (!project) {
|
|
439
|
+
throw new Error("Project not found")
|
|
440
|
+
}
|
|
441
|
+
const result = await resolvedDiffStore.ignoreFile({
|
|
442
|
+
chatId: command.chatId,
|
|
443
|
+
projectPath: project.localPath,
|
|
444
|
+
path: command.path,
|
|
445
|
+
})
|
|
446
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
447
|
+
if (result.snapshotChanged) {
|
|
448
|
+
broadcastSnapshots()
|
|
449
|
+
}
|
|
450
|
+
return
|
|
451
|
+
}
|
|
410
452
|
case "chat.cancel": {
|
|
411
453
|
await agent.cancel(command.chatId)
|
|
412
454
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
package/src/shared/protocol.ts
CHANGED
|
@@ -80,6 +80,8 @@ export type ClientCommand =
|
|
|
80
80
|
| { type: "chat.refreshDiffs"; chatId: string }
|
|
81
81
|
| { type: "chat.generateCommitMessage"; chatId: string; paths: string[] }
|
|
82
82
|
| { type: "chat.commitDiffs"; chatId: string; paths: string[]; summary: string; description?: string; mode: DiffCommitMode }
|
|
83
|
+
| { type: "chat.discardDiffFile"; chatId: string; path: string }
|
|
84
|
+
| { type: "chat.ignoreDiffFile"; chatId: string; path: string }
|
|
83
85
|
| { type: "chat.cancel"; chatId: string }
|
|
84
86
|
| { type: "chat.stopDraining"; chatId: string }
|
|
85
87
|
| { type: "chat.loadHistory"; chatId: string; beforeCursor: string; limit: number }
|
package/src/shared/types.ts
CHANGED