kanna-code 0.23.1 → 0.25.0
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/README.md +6 -2
- package/dist/client/assets/index-Hy4LEj8j.css +32 -0
- package/dist/client/assets/index-Tg96DNSK.js +2554 -0
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/diff-store.test.ts +382 -27
- package/src/server/diff-store.ts +1293 -30
- package/src/server/generate-commit-message.test.ts +0 -2
- package/src/server/generate-commit-message.ts +9 -4
- package/src/server/keybindings.test.ts +12 -0
- package/src/server/keybindings.ts +3 -0
- package/src/server/read-models.test.ts +1 -2
- package/src/server/read-models.ts +1 -4
- package/src/server/ws-router.test.ts +277 -8
- package/src/server/ws-router.ts +124 -50
- package/src/shared/protocol.ts +56 -0
- package/src/shared/types.ts +119 -13
- package/dist/client/assets/index-DADxOnBF.css +0 -32
- package/dist/client/assets/index-y8BVCflz.js +0 -2506
|
@@ -11,7 +11,6 @@ describe("generateCommitMessageDetailed", () => {
|
|
|
11
11
|
files: [{
|
|
12
12
|
path: "app.ts",
|
|
13
13
|
changeType: "modified",
|
|
14
|
-
isUntracked: false,
|
|
15
14
|
patch: "diff --git a/app.ts b/app.ts\n--- a/app.ts\n+++ b/app.ts\n@@\n-old\n+new\n",
|
|
16
15
|
}],
|
|
17
16
|
},
|
|
@@ -39,7 +38,6 @@ describe("generateCommitMessageDetailed", () => {
|
|
|
39
38
|
files: [{
|
|
40
39
|
path: "src/feature.ts",
|
|
41
40
|
changeType: "modified",
|
|
42
|
-
isUntracked: false,
|
|
43
41
|
patch: "diff --git a/src/feature.ts b/src/feature.ts\n",
|
|
44
42
|
}],
|
|
45
43
|
},
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
|
-
import type { ChatDiffFile } from "../shared/types"
|
|
3
2
|
import { QuickResponseAdapter } from "./quick-response"
|
|
4
3
|
|
|
4
|
+
interface CommitMessageFile {
|
|
5
|
+
path: string
|
|
6
|
+
changeType: "added" | "deleted" | "modified" | "renamed"
|
|
7
|
+
patch: string
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
const COMMIT_MESSAGE_SCHEMA = {
|
|
6
11
|
type: "object",
|
|
7
12
|
properties: {
|
|
@@ -44,7 +49,7 @@ function sanitizeBody(value: unknown): string {
|
|
|
44
49
|
return value.trim()
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
function fallbackSubject(files:
|
|
52
|
+
function fallbackSubject(files: CommitMessageFile[]) {
|
|
48
53
|
if (files.length === 1) {
|
|
49
54
|
const fileName = path.posix.basename(files[0]?.path ?? "file")
|
|
50
55
|
const normalized = `Update ${fileName}`.replace(/\s+/g, " ").trim()
|
|
@@ -56,7 +61,7 @@ function fallbackSubject(files: ChatDiffFile[]) {
|
|
|
56
61
|
|
|
57
62
|
function buildCommitMessagePrompt(args: {
|
|
58
63
|
branchName?: string
|
|
59
|
-
files:
|
|
64
|
+
files: CommitMessageFile[]
|
|
60
65
|
}) {
|
|
61
66
|
const fileList = args.files.map((file) => `${file.changeType}: ${file.path}`).join("\n")
|
|
62
67
|
const combinedPatch = args.files.map((file) => file.patch).join("\n\n")
|
|
@@ -83,7 +88,7 @@ export async function generateCommitMessageDetailed(
|
|
|
83
88
|
args: {
|
|
84
89
|
cwd: string
|
|
85
90
|
branchName?: string
|
|
86
|
-
files:
|
|
91
|
+
files: CommitMessageFile[]
|
|
87
92
|
},
|
|
88
93
|
adapter = new QuickResponseAdapter()
|
|
89
94
|
): Promise<GenerateCommitMessageResult> {
|
|
@@ -38,6 +38,9 @@ describe("normalizeKeybindings", () => {
|
|
|
38
38
|
openInFinder: ["Cmd+Alt+F"],
|
|
39
39
|
openInEditor: ["Cmd+Shift+O"],
|
|
40
40
|
addSplitTerminal: ["Cmd+Shift+J"],
|
|
41
|
+
jumpToSidebarChat: ["Cmd+Alt"],
|
|
42
|
+
createChatInCurrentProject: ["Cmd+Alt+N"],
|
|
43
|
+
openAddProject: ["Cmd+Alt+O"],
|
|
41
44
|
}, TEST_FILE_PATH)
|
|
42
45
|
|
|
43
46
|
expect(snapshot).toEqual({
|
|
@@ -47,6 +50,9 @@ describe("normalizeKeybindings", () => {
|
|
|
47
50
|
openInFinder: ["cmd+alt+f"],
|
|
48
51
|
openInEditor: ["cmd+shift+o"],
|
|
49
52
|
addSplitTerminal: ["cmd+shift+j"],
|
|
53
|
+
jumpToSidebarChat: ["cmd+alt"],
|
|
54
|
+
createChatInCurrentProject: ["cmd+alt+n"],
|
|
55
|
+
openAddProject: ["cmd+alt+o"],
|
|
50
56
|
},
|
|
51
57
|
warning: null,
|
|
52
58
|
filePathDisplay: TEST_FILE_PATH,
|
|
@@ -97,6 +103,9 @@ describe("KeybindingsManager", () => {
|
|
|
97
103
|
openInFinder: ["Cmd+Alt+F"],
|
|
98
104
|
openInEditor: ["Cmd+Shift+O"],
|
|
99
105
|
addSplitTerminal: ["Cmd+Shift+J"],
|
|
106
|
+
jumpToSidebarChat: ["Cmd+Alt"],
|
|
107
|
+
createChatInCurrentProject: ["Cmd+Alt+N"],
|
|
108
|
+
openAddProject: ["Cmd+Alt+O"],
|
|
100
109
|
})
|
|
101
110
|
|
|
102
111
|
expect(snapshot).toEqual({
|
|
@@ -106,6 +115,9 @@ describe("KeybindingsManager", () => {
|
|
|
106
115
|
openInFinder: ["cmd+alt+f"],
|
|
107
116
|
openInEditor: ["cmd+shift+o"],
|
|
108
117
|
addSplitTerminal: ["cmd+shift+j"],
|
|
118
|
+
jumpToSidebarChat: ["cmd+alt"],
|
|
119
|
+
createChatInCurrentProject: ["cmd+alt+n"],
|
|
120
|
+
openAddProject: ["cmd+alt+o"],
|
|
109
121
|
},
|
|
110
122
|
warning: null,
|
|
111
123
|
filePathDisplay: filePath,
|
|
@@ -159,6 +159,9 @@ function createDefaultSnapshot(filePath: string, warning: string | null = null):
|
|
|
159
159
|
openInFinder: [...DEFAULT_KEYBINDINGS.openInFinder],
|
|
160
160
|
openInEditor: [...DEFAULT_KEYBINDINGS.openInEditor],
|
|
161
161
|
addSplitTerminal: [...DEFAULT_KEYBINDINGS.addSplitTerminal],
|
|
162
|
+
jumpToSidebarChat: [...DEFAULT_KEYBINDINGS.jumpToSidebarChat],
|
|
163
|
+
createChatInCurrentProject: [...DEFAULT_KEYBINDINGS.createChatInCurrentProject],
|
|
164
|
+
openAddProject: [...DEFAULT_KEYBINDINGS.openAddProject],
|
|
162
165
|
},
|
|
163
166
|
warning,
|
|
164
167
|
filePathDisplay: formatDisplayPath(filePath),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
ChatDiffSnapshot,
|
|
3
2
|
ChatRuntime,
|
|
4
3
|
ChatSnapshot,
|
|
5
4
|
KannaStatus,
|
|
@@ -105,8 +104,7 @@ export function deriveChatSnapshot(
|
|
|
105
104
|
activeStatuses: Map<string, KannaStatus>,
|
|
106
105
|
drainingChatIds: Set<string>,
|
|
107
106
|
chatId: string,
|
|
108
|
-
getMessages: (chatId: string) => Pick<ChatSnapshot, "messages" | "history"
|
|
109
|
-
getDiffs: (chatId: string) => ChatDiffSnapshot
|
|
107
|
+
getMessages: (chatId: string) => Pick<ChatSnapshot, "messages" | "history">
|
|
110
108
|
): ChatSnapshot | null {
|
|
111
109
|
const chat = state.chatsById.get(chatId)
|
|
112
110
|
if (!chat || chat.deletedAt) return null
|
|
@@ -131,7 +129,6 @@ export function deriveChatSnapshot(
|
|
|
131
129
|
runtime,
|
|
132
130
|
messages: transcript.messages,
|
|
133
131
|
history: transcript.history,
|
|
134
|
-
diffs: getDiffs(chat.id),
|
|
135
132
|
availableProviders: [...SERVER_PROVIDERS],
|
|
136
133
|
}
|
|
137
134
|
}
|
|
@@ -22,6 +22,9 @@ const DEFAULT_KEYBINDINGS_SNAPSHOT: KeybindingsSnapshot = {
|
|
|
22
22
|
openInFinder: ["cmd+alt+f"],
|
|
23
23
|
openInEditor: ["cmd+shift+o"],
|
|
24
24
|
addSplitTerminal: ["cmd+shift+j"],
|
|
25
|
+
jumpToSidebarChat: ["cmd+alt"],
|
|
26
|
+
createChatInCurrentProject: ["cmd+alt+n"],
|
|
27
|
+
openAddProject: ["cmd+alt+o"],
|
|
25
28
|
},
|
|
26
29
|
warning: null,
|
|
27
30
|
filePathDisplay: "~/.kanna/keybindings.json",
|
|
@@ -176,6 +179,264 @@ describe("ws-router", () => {
|
|
|
176
179
|
})
|
|
177
180
|
})
|
|
178
181
|
|
|
182
|
+
test("subscribes to project git snapshots independently from chat snapshots", async () => {
|
|
183
|
+
const state = createEmptyState()
|
|
184
|
+
state.projectsById.set("project-1", {
|
|
185
|
+
id: "project-1",
|
|
186
|
+
localPath: "/tmp/project",
|
|
187
|
+
title: "Project",
|
|
188
|
+
createdAt: 1,
|
|
189
|
+
updatedAt: 1,
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const router = createWsRouter({
|
|
193
|
+
store: {
|
|
194
|
+
state,
|
|
195
|
+
getProject: () => state.projectsById.get("project-1") ?? null,
|
|
196
|
+
} as never,
|
|
197
|
+
diffStore: {
|
|
198
|
+
getProjectSnapshot: () => ({
|
|
199
|
+
status: "ready",
|
|
200
|
+
branchName: "main",
|
|
201
|
+
files: [],
|
|
202
|
+
branchHistory: { entries: [] },
|
|
203
|
+
}),
|
|
204
|
+
refreshSnapshot: async () => false,
|
|
205
|
+
listBranches: async () => ({ recent: [], local: [], remote: [], pullRequests: [], pullRequestsStatus: "unavailable" }),
|
|
206
|
+
previewMergeBranch: async () => ({ currentBranchName: "main", targetBranchName: "feature/test", targetDisplayName: "feature/test", status: "mergeable", commitCount: 1, hasConflicts: false, message: "ready" }),
|
|
207
|
+
mergeBranch: async () => ({ ok: true, branchName: "main", snapshotChanged: false }),
|
|
208
|
+
syncBranch: async () => ({ ok: true, action: "fetch", snapshotChanged: false }),
|
|
209
|
+
checkoutBranch: async () => ({ ok: true, snapshotChanged: false }),
|
|
210
|
+
createBranch: async () => ({ ok: true, branchName: "main", snapshotChanged: false }),
|
|
211
|
+
generateCommitMessage: async () => ({ subject: "", body: "", usedFallback: true, failureMessage: null }),
|
|
212
|
+
commitFiles: async () => ({ ok: true, mode: "commit_only", pushed: false, snapshotChanged: false }),
|
|
213
|
+
discardFile: async () => ({ snapshotChanged: false }),
|
|
214
|
+
ignoreFile: async () => ({ snapshotChanged: false }),
|
|
215
|
+
readPatch: async () => ({ patch: "" }),
|
|
216
|
+
} as never,
|
|
217
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
218
|
+
terminals: {
|
|
219
|
+
getSnapshot: () => null,
|
|
220
|
+
onEvent: () => () => {},
|
|
221
|
+
} as never,
|
|
222
|
+
keybindings: {
|
|
223
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
224
|
+
onChange: () => () => {},
|
|
225
|
+
} as never,
|
|
226
|
+
refreshDiscovery: async () => [],
|
|
227
|
+
getDiscoveredProjects: () => [],
|
|
228
|
+
machineDisplayName: "Local Machine",
|
|
229
|
+
updateManager: null,
|
|
230
|
+
})
|
|
231
|
+
const ws = new FakeWebSocket()
|
|
232
|
+
|
|
233
|
+
await router.handleMessage(
|
|
234
|
+
ws as never,
|
|
235
|
+
JSON.stringify({
|
|
236
|
+
v: 1,
|
|
237
|
+
type: "subscribe",
|
|
238
|
+
id: "project-git-sub-1",
|
|
239
|
+
topic: { type: "project-git", projectId: "project-1" },
|
|
240
|
+
})
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
expect(ws.sent[0]).toEqual({
|
|
244
|
+
v: PROTOCOL_VERSION,
|
|
245
|
+
type: "snapshot",
|
|
246
|
+
id: "project-git-sub-1",
|
|
247
|
+
snapshot: {
|
|
248
|
+
type: "project-git",
|
|
249
|
+
data: {
|
|
250
|
+
status: "ready",
|
|
251
|
+
branchName: "main",
|
|
252
|
+
files: [],
|
|
253
|
+
branchHistory: { entries: [] },
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test("reads diff patches through the project-scoped command", async () => {
|
|
260
|
+
const state = createEmptyState()
|
|
261
|
+
state.projectsById.set("project-1", {
|
|
262
|
+
id: "project-1",
|
|
263
|
+
localPath: "/tmp/project",
|
|
264
|
+
title: "Project",
|
|
265
|
+
createdAt: 1,
|
|
266
|
+
updatedAt: 1,
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const router = createWsRouter({
|
|
270
|
+
store: {
|
|
271
|
+
state,
|
|
272
|
+
getProject: (projectId: string) => state.projectsById.get(projectId) ?? null,
|
|
273
|
+
} as never,
|
|
274
|
+
diffStore: {
|
|
275
|
+
getProjectSnapshot: () => null,
|
|
276
|
+
refreshSnapshot: async () => false,
|
|
277
|
+
listBranches: async () => ({ recent: [], local: [], remote: [], pullRequests: [], pullRequestsStatus: "unavailable" }),
|
|
278
|
+
previewMergeBranch: async () => ({ currentBranchName: "main", targetBranchName: "feature/test", targetDisplayName: "feature/test", status: "mergeable", commitCount: 1, hasConflicts: false, message: "ready" }),
|
|
279
|
+
mergeBranch: async () => ({ ok: true, branchName: "main", snapshotChanged: false }),
|
|
280
|
+
syncBranch: async () => ({ ok: true, action: "fetch", snapshotChanged: false }),
|
|
281
|
+
checkoutBranch: async () => ({ ok: true, snapshotChanged: false }),
|
|
282
|
+
createBranch: async () => ({ ok: true, branchName: "main", snapshotChanged: false }),
|
|
283
|
+
generateCommitMessage: async () => ({ subject: "", body: "", usedFallback: true, failureMessage: null }),
|
|
284
|
+
commitFiles: async () => ({ ok: true, mode: "commit_only", pushed: false, snapshotChanged: false }),
|
|
285
|
+
discardFile: async () => ({ snapshotChanged: false }),
|
|
286
|
+
ignoreFile: async () => ({ snapshotChanged: false }),
|
|
287
|
+
readPatch: async () => ({ patch: "diff --git a/app.txt b/app.txt" }),
|
|
288
|
+
} as never,
|
|
289
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
290
|
+
terminals: {
|
|
291
|
+
getSnapshot: () => null,
|
|
292
|
+
onEvent: () => () => {},
|
|
293
|
+
} as never,
|
|
294
|
+
keybindings: {
|
|
295
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
296
|
+
onChange: () => () => {},
|
|
297
|
+
} as never,
|
|
298
|
+
refreshDiscovery: async () => [],
|
|
299
|
+
getDiscoveredProjects: () => [],
|
|
300
|
+
machineDisplayName: "Local Machine",
|
|
301
|
+
updateManager: null,
|
|
302
|
+
})
|
|
303
|
+
const ws = new FakeWebSocket()
|
|
304
|
+
|
|
305
|
+
await router.handleMessage(
|
|
306
|
+
ws as never,
|
|
307
|
+
JSON.stringify({
|
|
308
|
+
v: 1,
|
|
309
|
+
type: "command",
|
|
310
|
+
id: "read-patch-1",
|
|
311
|
+
command: {
|
|
312
|
+
type: "project.readDiffPatch",
|
|
313
|
+
projectId: "project-1",
|
|
314
|
+
path: "app.txt",
|
|
315
|
+
},
|
|
316
|
+
})
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
expect(ws.sent[0]).toEqual({
|
|
320
|
+
v: PROTOCOL_VERSION,
|
|
321
|
+
type: "ack",
|
|
322
|
+
id: "read-patch-1",
|
|
323
|
+
result: { patch: "diff --git a/app.txt b/app.txt" },
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
test("routes merge preview and merge commands through the diff store", async () => {
|
|
328
|
+
const state = createEmptyState()
|
|
329
|
+
state.projectsById.set("project-1", {
|
|
330
|
+
id: "project-1",
|
|
331
|
+
localPath: "/tmp/project",
|
|
332
|
+
title: "Project",
|
|
333
|
+
createdAt: 1,
|
|
334
|
+
updatedAt: 1,
|
|
335
|
+
})
|
|
336
|
+
state.chatsById.set("chat-1", {
|
|
337
|
+
id: "chat-1",
|
|
338
|
+
projectId: "project-1",
|
|
339
|
+
title: "Chat",
|
|
340
|
+
createdAt: 1,
|
|
341
|
+
updatedAt: 1,
|
|
342
|
+
unread: false,
|
|
343
|
+
provider: null,
|
|
344
|
+
planMode: false,
|
|
345
|
+
sessionToken: null,
|
|
346
|
+
lastTurnOutcome: null,
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
const router = createWsRouter({
|
|
350
|
+
store: {
|
|
351
|
+
state,
|
|
352
|
+
getProject: (projectId: string) => state.projectsById.get(projectId) ?? null,
|
|
353
|
+
getChat: (chatId: string) => state.chatsById.get(chatId) ?? null,
|
|
354
|
+
} as never,
|
|
355
|
+
diffStore: {
|
|
356
|
+
getProjectSnapshot: () => ({ status: "ready", branchName: "main", files: [], branchHistory: { entries: [] } }),
|
|
357
|
+
refreshSnapshot: async () => false,
|
|
358
|
+
listBranches: async () => ({ recent: [], local: [], remote: [], pullRequests: [], pullRequestsStatus: "unavailable" }),
|
|
359
|
+
previewMergeBranch: async () => ({ currentBranchName: "main", targetBranchName: "feature/test", targetDisplayName: "feature/test", status: "mergeable", commitCount: 2, hasConflicts: false, message: "2 commits from feature/test will merge into main." }),
|
|
360
|
+
mergeBranch: async () => ({ ok: true, branchName: "main", snapshotChanged: true }),
|
|
361
|
+
syncBranch: async () => ({ ok: true, action: "fetch", snapshotChanged: false }),
|
|
362
|
+
checkoutBranch: async () => ({ ok: true, snapshotChanged: false }),
|
|
363
|
+
createBranch: async () => ({ ok: true, branchName: "main", snapshotChanged: false }),
|
|
364
|
+
generateCommitMessage: async () => ({ subject: "", body: "", usedFallback: true, failureMessage: null }),
|
|
365
|
+
commitFiles: async () => ({ ok: true, mode: "commit_only", pushed: false, snapshotChanged: false }),
|
|
366
|
+
discardFile: async () => ({ snapshotChanged: false }),
|
|
367
|
+
ignoreFile: async () => ({ snapshotChanged: false }),
|
|
368
|
+
readPatch: async () => ({ patch: "" }),
|
|
369
|
+
} as never,
|
|
370
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
371
|
+
terminals: {
|
|
372
|
+
getSnapshot: () => null,
|
|
373
|
+
onEvent: () => () => {},
|
|
374
|
+
} as never,
|
|
375
|
+
keybindings: {
|
|
376
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
377
|
+
onChange: () => () => {},
|
|
378
|
+
} as never,
|
|
379
|
+
refreshDiscovery: async () => [],
|
|
380
|
+
getDiscoveredProjects: () => [],
|
|
381
|
+
machineDisplayName: "Local Machine",
|
|
382
|
+
updateManager: null,
|
|
383
|
+
})
|
|
384
|
+
const ws = new FakeWebSocket()
|
|
385
|
+
|
|
386
|
+
await router.handleMessage(
|
|
387
|
+
ws as never,
|
|
388
|
+
JSON.stringify({
|
|
389
|
+
v: 1,
|
|
390
|
+
type: "command",
|
|
391
|
+
id: "preview-merge-1",
|
|
392
|
+
command: {
|
|
393
|
+
type: "chat.previewMergeBranch",
|
|
394
|
+
chatId: "chat-1",
|
|
395
|
+
branch: { kind: "local", name: "feature/test" },
|
|
396
|
+
},
|
|
397
|
+
})
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
await router.handleMessage(
|
|
401
|
+
ws as never,
|
|
402
|
+
JSON.stringify({
|
|
403
|
+
v: 1,
|
|
404
|
+
type: "command",
|
|
405
|
+
id: "merge-1",
|
|
406
|
+
command: {
|
|
407
|
+
type: "chat.mergeBranch",
|
|
408
|
+
chatId: "chat-1",
|
|
409
|
+
branch: { kind: "local", name: "feature/test" },
|
|
410
|
+
},
|
|
411
|
+
})
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
expect(ws.sent[0]).toEqual({
|
|
415
|
+
v: PROTOCOL_VERSION,
|
|
416
|
+
type: "ack",
|
|
417
|
+
id: "preview-merge-1",
|
|
418
|
+
result: {
|
|
419
|
+
currentBranchName: "main",
|
|
420
|
+
targetBranchName: "feature/test",
|
|
421
|
+
targetDisplayName: "feature/test",
|
|
422
|
+
status: "mergeable",
|
|
423
|
+
commitCount: 2,
|
|
424
|
+
hasConflicts: false,
|
|
425
|
+
message: "2 commits from feature/test will merge into main.",
|
|
426
|
+
},
|
|
427
|
+
})
|
|
428
|
+
expect(ws.sent[1]).toEqual({
|
|
429
|
+
v: PROTOCOL_VERSION,
|
|
430
|
+
type: "ack",
|
|
431
|
+
id: "merge-1",
|
|
432
|
+
result: {
|
|
433
|
+
ok: true,
|
|
434
|
+
branchName: "main",
|
|
435
|
+
snapshotChanged: true,
|
|
436
|
+
},
|
|
437
|
+
})
|
|
438
|
+
})
|
|
439
|
+
|
|
179
440
|
test("loads older chat history pages", async () => {
|
|
180
441
|
const state = createEmptyState()
|
|
181
442
|
state.projectsById.set("project-1", {
|
|
@@ -506,6 +767,9 @@ describe("ws-router", () => {
|
|
|
506
767
|
openInFinder: ["cmd+shift+g"],
|
|
507
768
|
openInEditor: ["cmd+shift+p"],
|
|
508
769
|
addSplitTerminal: ["cmd+alt+j"],
|
|
770
|
+
jumpToSidebarChat: ["cmd+alt"],
|
|
771
|
+
createChatInCurrentProject: ["cmd+alt+n"],
|
|
772
|
+
openAddProject: ["cmd+alt+o"],
|
|
509
773
|
},
|
|
510
774
|
},
|
|
511
775
|
})
|
|
@@ -523,6 +787,9 @@ describe("ws-router", () => {
|
|
|
523
787
|
openInFinder: ["cmd+shift+g"],
|
|
524
788
|
openInEditor: ["cmd+shift+p"],
|
|
525
789
|
addSplitTerminal: ["cmd+alt+j"],
|
|
790
|
+
jumpToSidebarChat: ["cmd+alt"],
|
|
791
|
+
createChatInCurrentProject: ["cmd+alt+n"],
|
|
792
|
+
openAddProject: ["cmd+alt+o"],
|
|
526
793
|
},
|
|
527
794
|
warning: null,
|
|
528
795
|
filePathDisplay: "~/.kanna/keybindings.json",
|
|
@@ -675,13 +942,14 @@ describe("ws-router", () => {
|
|
|
675
942
|
lastTurnOutcome: null,
|
|
676
943
|
})
|
|
677
944
|
|
|
678
|
-
const discardCalls: Array<{
|
|
945
|
+
const discardCalls: Array<{ projectId: string; projectPath: string; path: string }> = []
|
|
679
946
|
const diffStore = {
|
|
680
|
-
|
|
947
|
+
getProjectSnapshot: () => ({ status: "ready" as const, files: [], defaultBranchName: "main", originRepoSlug: "acme/repo", aheadCount: 0, behindCount: 0, lastFetchedAt: undefined }),
|
|
681
948
|
refreshSnapshot: async () => false,
|
|
949
|
+
syncBranch: async () => ({ ok: true as const, action: "fetch" as const, snapshotChanged: false }),
|
|
682
950
|
generateCommitMessage: async () => ({ subject: "", body: "" }),
|
|
683
951
|
commitFiles: async () => ({ ok: true as const, mode: "commit_only" as const, pushed: false, snapshotChanged: false }),
|
|
684
|
-
discardFile: async (args: {
|
|
952
|
+
discardFile: async (args: { projectId: string; projectPath: string; path: string }) => {
|
|
685
953
|
discardCalls.push(args)
|
|
686
954
|
return { snapshotChanged: true }
|
|
687
955
|
},
|
|
@@ -738,7 +1006,7 @@ describe("ws-router", () => {
|
|
|
738
1006
|
)
|
|
739
1007
|
|
|
740
1008
|
expect(discardCalls).toEqual([{
|
|
741
|
-
|
|
1009
|
+
projectId: "project-1",
|
|
742
1010
|
projectPath: "/tmp/project",
|
|
743
1011
|
path: "app.txt",
|
|
744
1012
|
}])
|
|
@@ -773,7 +1041,7 @@ describe("ws-router", () => {
|
|
|
773
1041
|
lastTurnOutcome: null,
|
|
774
1042
|
})
|
|
775
1043
|
|
|
776
|
-
const ignoreCalls: Array<{
|
|
1044
|
+
const ignoreCalls: Array<{ projectId: string; projectPath: string; path: string }> = []
|
|
777
1045
|
const router = createWsRouter({
|
|
778
1046
|
store: {
|
|
779
1047
|
state,
|
|
@@ -781,12 +1049,13 @@ describe("ws-router", () => {
|
|
|
781
1049
|
getProject: (projectId: string) => state.projectsById.get(projectId) ?? null,
|
|
782
1050
|
} as never,
|
|
783
1051
|
diffStore: {
|
|
784
|
-
|
|
1052
|
+
getProjectSnapshot: () => ({ status: "ready" as const, files: [], defaultBranchName: "main", originRepoSlug: "acme/repo", aheadCount: 0, behindCount: 0, lastFetchedAt: undefined }),
|
|
785
1053
|
refreshSnapshot: async () => false,
|
|
1054
|
+
syncBranch: async () => ({ ok: true as const, action: "fetch" as const, snapshotChanged: false }),
|
|
786
1055
|
generateCommitMessage: async () => ({ subject: "", body: "" }),
|
|
787
1056
|
commitFiles: async () => ({ ok: true as const, mode: "commit_only" as const, pushed: false, snapshotChanged: false }),
|
|
788
1057
|
discardFile: async () => ({ snapshotChanged: false }),
|
|
789
|
-
ignoreFile: async (args: {
|
|
1058
|
+
ignoreFile: async (args: { projectId: string; projectPath: string; path: string }) => {
|
|
790
1059
|
ignoreCalls.push(args)
|
|
791
1060
|
return { snapshotChanged: false }
|
|
792
1061
|
},
|
|
@@ -822,7 +1091,7 @@ describe("ws-router", () => {
|
|
|
822
1091
|
)
|
|
823
1092
|
|
|
824
1093
|
expect(ignoreCalls).toEqual([{
|
|
825
|
-
|
|
1094
|
+
projectId: "project-1",
|
|
826
1095
|
projectPath: "/tmp/project",
|
|
827
1096
|
path: "scratch.log",
|
|
828
1097
|
}])
|