kanna-code 0.2.0 → 0.4.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 +32 -10
- package/dist/client/assets/index-5ura1eo0.js +419 -0
- package/dist/client/assets/index-B0Cwdy1-.css +1 -0
- package/dist/client/index.html +2 -2
- package/package.json +3 -2
- package/src/server/agent.test.ts +297 -1
- package/src/server/agent.ts +56 -8
- package/src/server/cli-runtime.test.ts +180 -0
- package/src/server/cli-runtime.ts +274 -0
- package/src/server/cli.ts +20 -127
- package/src/server/codex-app-server.test.ts +236 -0
- package/src/server/codex-app-server.ts +68 -2
- package/src/server/discovery.test.ts +211 -0
- package/src/server/discovery.ts +253 -17
- package/src/server/generate-title.ts +32 -39
- package/src/server/quick-response.test.ts +86 -0
- package/src/server/quick-response.ts +124 -0
- package/src/server/read-models.test.ts +43 -1
- package/src/server/server.ts +5 -3
- package/src/server/ws-router.test.ts +47 -0
- package/src/server/ws-router.ts +4 -0
- package/src/shared/protocol.ts +1 -0
- package/src/shared/tools.test.ts +12 -1
- package/src/shared/tools.ts +19 -1
- package/src/shared/types.ts +5 -1
- package/dist/client/assets/index-C-sGbl7X.js +0 -409
- package/dist/client/assets/index-gld9RxCU.css +0 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { PROTOCOL_VERSION } from "../shared/types"
|
|
3
|
+
import { createEmptyState } from "./events"
|
|
4
|
+
import { createWsRouter } from "./ws-router"
|
|
5
|
+
|
|
6
|
+
class FakeWebSocket {
|
|
7
|
+
readonly sent: unknown[] = []
|
|
8
|
+
readonly data = {
|
|
9
|
+
subscriptions: new Map(),
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
send(message: string) {
|
|
13
|
+
this.sent.push(JSON.parse(message))
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("ws-router", () => {
|
|
18
|
+
test("acks system.ping without broadcasting snapshots", () => {
|
|
19
|
+
const router = createWsRouter({
|
|
20
|
+
store: { state: createEmptyState() } as never,
|
|
21
|
+
agent: { getActiveStatuses: () => new Map() } as never,
|
|
22
|
+
refreshDiscovery: async () => [],
|
|
23
|
+
getDiscoveredProjects: () => [],
|
|
24
|
+
machineDisplayName: "Local Machine",
|
|
25
|
+
})
|
|
26
|
+
const ws = new FakeWebSocket()
|
|
27
|
+
|
|
28
|
+
ws.data.subscriptions.set("sub-1", { type: "sidebar" })
|
|
29
|
+
router.handleMessage(
|
|
30
|
+
ws as never,
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
v: 1,
|
|
33
|
+
type: "command",
|
|
34
|
+
id: "ping-1",
|
|
35
|
+
command: { type: "system.ping" },
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
expect(ws.sent).toEqual([
|
|
40
|
+
{
|
|
41
|
+
v: PROTOCOL_VERSION,
|
|
42
|
+
type: "ack",
|
|
43
|
+
id: "ping-1",
|
|
44
|
+
},
|
|
45
|
+
])
|
|
46
|
+
})
|
|
47
|
+
})
|
package/src/server/ws-router.ts
CHANGED
|
@@ -89,6 +89,10 @@ export function createWsRouter({
|
|
|
89
89
|
const { command, id } = message
|
|
90
90
|
try {
|
|
91
91
|
switch (command.type) {
|
|
92
|
+
case "system.ping": {
|
|
93
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
94
|
+
return
|
|
95
|
+
}
|
|
92
96
|
case "project.open": {
|
|
93
97
|
await ensureProjectDirectory(command.localPath)
|
|
94
98
|
const project = await store.openProject(command.localPath)
|
package/src/shared/protocol.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type ClientCommand =
|
|
|
9
9
|
| { type: "project.open"; localPath: string }
|
|
10
10
|
| { type: "project.create"; localPath: string; title: string }
|
|
11
11
|
| { type: "project.remove"; projectId: string }
|
|
12
|
+
| { type: "system.ping" }
|
|
12
13
|
| { type: "system.openExternal"; localPath: string; action: "open_finder" | "open_terminal" | "open_editor" }
|
|
13
14
|
| { type: "chat.create"; projectId: string }
|
|
14
15
|
| { type: "chat.rename"; chatId: string; title: string }
|
package/src/shared/tools.test.ts
CHANGED
|
@@ -62,7 +62,18 @@ describe("hydrateToolResult", () => {
|
|
|
62
62
|
})
|
|
63
63
|
|
|
64
64
|
const result = hydrateToolResult(tool, JSON.stringify({ answers: { runtime: "codex" } }))
|
|
65
|
-
expect(result).toEqual({ answers: { runtime: "codex" } })
|
|
65
|
+
expect(result).toEqual({ answers: { runtime: ["codex"] } })
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test("hydrates AskUserQuestion multi-select answers", () => {
|
|
69
|
+
const tool = normalizeToolCall({
|
|
70
|
+
toolName: "AskUserQuestion",
|
|
71
|
+
toolId: "tool-1",
|
|
72
|
+
input: { questions: [] },
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const result = hydrateToolResult(tool, JSON.stringify({ answers: { runtime: ["bun", "node"] } }))
|
|
76
|
+
expect(result).toEqual({ answers: { runtime: ["bun", "node"] } })
|
|
66
77
|
})
|
|
67
78
|
|
|
68
79
|
test("hydrates ExitPlanMode decisions", () => {
|
package/src/shared/tools.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AskUserQuestionItem,
|
|
3
|
+
AskUserQuestionAnswerMap,
|
|
3
4
|
AskUserQuestionToolResult,
|
|
4
5
|
ExitPlanModeToolResult,
|
|
5
6
|
HydratedToolCall,
|
|
@@ -209,7 +210,23 @@ export function hydrateToolResult(tool: NormalizedToolCall, raw: unknown): Hydra
|
|
|
209
210
|
case "ask_user_question": {
|
|
210
211
|
const record = asRecord(parsed)
|
|
211
212
|
const answers = asRecord(record?.answers) ?? (record ? record : {})
|
|
212
|
-
return {
|
|
213
|
+
return {
|
|
214
|
+
answers: Object.fromEntries(
|
|
215
|
+
Object.entries(answers).map(([key, value]) => {
|
|
216
|
+
if (Array.isArray(value)) {
|
|
217
|
+
return [key, value.map((entry) => String(entry))]
|
|
218
|
+
}
|
|
219
|
+
if (value && typeof value === "object" && Array.isArray((value as { answers?: unknown }).answers)) {
|
|
220
|
+
return [key, (value as { answers: unknown[] }).answers.map((entry) => String(entry))]
|
|
221
|
+
}
|
|
222
|
+
if (value == null || value === "") {
|
|
223
|
+
return [key, []]
|
|
224
|
+
}
|
|
225
|
+
return [key, [String(value)]]
|
|
226
|
+
})
|
|
227
|
+
) as AskUserQuestionAnswerMap,
|
|
228
|
+
...(record?.discarded === true ? { discarded: true } : {}),
|
|
229
|
+
} satisfies AskUserQuestionToolResult
|
|
213
230
|
}
|
|
214
231
|
case "exit_plan_mode": {
|
|
215
232
|
const record = asRecord(parsed)
|
|
@@ -217,6 +234,7 @@ export function hydrateToolResult(tool: NormalizedToolCall, raw: unknown): Hydra
|
|
|
217
234
|
confirmed: typeof record?.confirmed === "boolean" ? record.confirmed : undefined,
|
|
218
235
|
clearContext: typeof record?.clearContext === "boolean" ? record.clearContext : undefined,
|
|
219
236
|
message: typeof record?.message === "string" ? record.message : undefined,
|
|
237
|
+
...(record?.discarded === true ? { discarded: true } : {}),
|
|
220
238
|
} satisfies ExitPlanModeToolResult
|
|
221
239
|
}
|
|
222
240
|
case "read_file":
|
package/src/shared/types.ts
CHANGED
|
@@ -194,6 +194,8 @@ export interface AskUserQuestionItem {
|
|
|
194
194
|
multiSelect?: boolean
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
export type AskUserQuestionAnswerMap = Record<string, string[]>
|
|
198
|
+
|
|
197
199
|
export interface TodoItem {
|
|
198
200
|
content: string
|
|
199
201
|
status: "pending" | "in_progress" | "completed"
|
|
@@ -373,13 +375,15 @@ export interface HydratedToolCallBase<TKind extends string, TInput, TResult> {
|
|
|
373
375
|
}
|
|
374
376
|
|
|
375
377
|
export interface AskUserQuestionToolResult {
|
|
376
|
-
answers:
|
|
378
|
+
answers: AskUserQuestionAnswerMap
|
|
379
|
+
discarded?: boolean
|
|
377
380
|
}
|
|
378
381
|
|
|
379
382
|
export interface ExitPlanModeToolResult {
|
|
380
383
|
confirmed?: boolean
|
|
381
384
|
clearContext?: boolean
|
|
382
385
|
message?: string
|
|
386
|
+
discarded?: boolean
|
|
383
387
|
}
|
|
384
388
|
|
|
385
389
|
export type HydratedAskUserQuestionToolCall =
|