kanna-code 0.9.1 → 0.10.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.
@@ -0,0 +1,163 @@
1
+ import { watch, type FSWatcher } from "node:fs"
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises"
3
+ import { homedir } from "node:os"
4
+ import path from "node:path"
5
+ import { getDataRootDir, LOG_PREFIX } from "../shared/branding"
6
+ import { DEFAULT_KEYBINDINGS, type KeybindingAction, type KeybindingsSnapshot } from "../shared/types"
7
+
8
+ const KEYBINDING_ACTIONS = Object.keys(DEFAULT_KEYBINDINGS) as KeybindingAction[]
9
+
10
+ type KeybindingsFile = Partial<Record<KeybindingAction, unknown>>
11
+
12
+ export class KeybindingsManager {
13
+ readonly filePath: string
14
+ private watcher: FSWatcher | null = null
15
+ private snapshot: KeybindingsSnapshot = createDefaultSnapshot()
16
+ private readonly listeners = new Set<(snapshot: KeybindingsSnapshot) => void>()
17
+
18
+ constructor(filePath = path.join(getDataRootDir(homedir()), "keybindings.json")) {
19
+ this.filePath = filePath
20
+ }
21
+
22
+ async initialize() {
23
+ await mkdir(path.dirname(this.filePath), { recursive: true })
24
+ const file = Bun.file(this.filePath)
25
+ if (!(await file.exists())) {
26
+ await writeFile(this.filePath, `${JSON.stringify(DEFAULT_KEYBINDINGS, null, 2)}\n`, "utf8")
27
+ }
28
+ await this.reload()
29
+ this.startWatching()
30
+ }
31
+
32
+ dispose() {
33
+ this.watcher?.close()
34
+ this.watcher = null
35
+ this.listeners.clear()
36
+ }
37
+
38
+ getSnapshot() {
39
+ return this.snapshot
40
+ }
41
+
42
+ onChange(listener: (snapshot: KeybindingsSnapshot) => void) {
43
+ this.listeners.add(listener)
44
+ return () => {
45
+ this.listeners.delete(listener)
46
+ }
47
+ }
48
+
49
+ async reload() {
50
+ const nextSnapshot = await readKeybindingsSnapshot(this.filePath)
51
+ this.setSnapshot(nextSnapshot)
52
+ }
53
+
54
+ async write(bindings: Partial<Record<KeybindingAction, string[]>>) {
55
+ const nextSnapshot = normalizeKeybindings(bindings)
56
+ await mkdir(path.dirname(this.filePath), { recursive: true })
57
+ await writeFile(this.filePath, `${JSON.stringify(nextSnapshot.bindings, null, 2)}\n`, "utf8")
58
+ this.setSnapshot(nextSnapshot)
59
+ return nextSnapshot
60
+ }
61
+
62
+ private setSnapshot(snapshot: KeybindingsSnapshot) {
63
+ this.snapshot = snapshot
64
+ for (const listener of this.listeners) {
65
+ listener(snapshot)
66
+ }
67
+ }
68
+
69
+ private startWatching() {
70
+ this.watcher?.close()
71
+ try {
72
+ this.watcher = watch(path.dirname(this.filePath), { persistent: false }, (_eventType, filename) => {
73
+ if (filename && filename !== path.basename(this.filePath)) {
74
+ return
75
+ }
76
+ void this.reload().catch((error: unknown) => {
77
+ console.warn(`${LOG_PREFIX} Failed to reload keybindings:`, error)
78
+ })
79
+ })
80
+ } catch (error) {
81
+ console.warn(`${LOG_PREFIX} Failed to watch keybindings file:`, error)
82
+ this.watcher = null
83
+ }
84
+ }
85
+ }
86
+
87
+ export async function readKeybindingsSnapshot(filePath: string) {
88
+ try {
89
+ const text = await readFile(filePath, "utf8")
90
+ if (!text.trim()) {
91
+ return createDefaultSnapshot("Keybindings file was empty. Using defaults.")
92
+ }
93
+ const parsed = JSON.parse(text) as KeybindingsFile
94
+ return normalizeKeybindings(parsed)
95
+ } catch (error) {
96
+ if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
97
+ return createDefaultSnapshot()
98
+ }
99
+
100
+ if (error instanceof SyntaxError) {
101
+ return createDefaultSnapshot("Keybindings file is invalid JSON. Using defaults.")
102
+ }
103
+
104
+ throw error
105
+ }
106
+ }
107
+
108
+ export function normalizeKeybindings(value: KeybindingsFile | null | undefined): KeybindingsSnapshot {
109
+ const warnings: string[] = []
110
+ const source = value && typeof value === "object" && !Array.isArray(value)
111
+ ? value
112
+ : null
113
+
114
+ if (!source) {
115
+ return createDefaultSnapshot("Keybindings file must contain a JSON object. Using defaults.")
116
+ }
117
+
118
+ const bindings = {} as Record<KeybindingAction, string[]>
119
+ for (const action of KEYBINDING_ACTIONS) {
120
+ const rawValue = source[action]
121
+ if (!Array.isArray(rawValue)) {
122
+ bindings[action] = [...DEFAULT_KEYBINDINGS[action]]
123
+ if (rawValue !== undefined) {
124
+ warnings.push(`${action} must be an array of shortcut strings`)
125
+ }
126
+ continue
127
+ }
128
+
129
+ const normalized = rawValue
130
+ .filter((entry): entry is string => typeof entry === "string")
131
+ .map((entry) => entry.trim())
132
+ .map((entry) => entry.toLowerCase())
133
+ .filter(Boolean)
134
+
135
+ if (normalized.length === 0) {
136
+ bindings[action] = [...DEFAULT_KEYBINDINGS[action]]
137
+ if (rawValue.length > 0 || source[action] !== undefined) {
138
+ warnings.push(`${action} did not contain any valid shortcut strings`)
139
+ }
140
+ continue
141
+ }
142
+
143
+ bindings[action] = normalized
144
+ }
145
+
146
+ return {
147
+ bindings,
148
+ warning: warnings.length > 0 ? `Some keybindings were reset to defaults: ${warnings.join("; ")}` : null,
149
+ }
150
+ }
151
+
152
+ function createDefaultSnapshot(warning: string | null = null): KeybindingsSnapshot {
153
+ return {
154
+ bindings: {
155
+ toggleEmbeddedTerminal: [...DEFAULT_KEYBINDINGS.toggleEmbeddedTerminal],
156
+ toggleRightSidebar: [...DEFAULT_KEYBINDINGS.toggleRightSidebar],
157
+ openInFinder: [...DEFAULT_KEYBINDINGS.openInFinder],
158
+ openInEditor: [...DEFAULT_KEYBINDINGS.openInEditor],
159
+ addSplitTerminal: [...DEFAULT_KEYBINDINGS.addSplitTerminal],
160
+ },
161
+ warning,
162
+ }
163
+ }
@@ -4,6 +4,7 @@ import { EventStore } from "./event-store"
4
4
  import { AgentCoordinator } from "./agent"
5
5
  import { discoverProjects, type DiscoveredProject } from "./discovery"
6
6
  import { FileTreeManager } from "./file-tree-manager"
7
+ import { KeybindingsManager } from "./keybindings"
7
8
  import { getMachineDisplayName } from "./machine-name"
8
9
  import { TerminalManager } from "./terminal-manager"
9
10
  import { createWsRouter, type ClientState } from "./ws-router"
@@ -31,6 +32,8 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
31
32
  let server: ReturnType<typeof Bun.serve<ClientState>>
32
33
  let router: ReturnType<typeof createWsRouter>
33
34
  const terminals = new TerminalManager()
35
+ const keybindings = new KeybindingsManager()
36
+ await keybindings.initialize()
34
37
  const fileTree = new FileTreeManager({
35
38
  getProject: (projectId) => store.getProject(projectId),
36
39
  })
@@ -44,6 +47,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
44
47
  store,
45
48
  agent,
46
49
  terminals,
50
+ keybindings,
47
51
  fileTree,
48
52
  refreshDiscovery,
49
53
  getDiscoveredProjects: () => discoveredProjects,
@@ -107,6 +111,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
107
111
  }
108
112
  router.dispose()
109
113
  fileTree.dispose()
114
+ keybindings.dispose()
110
115
  terminals.closeAll()
111
116
  await store.compact()
112
117
  server.stop(true)
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test"
2
+ import type { KeybindingsSnapshot } from "../shared/types"
2
3
  import { PROTOCOL_VERSION } from "../shared/types"
3
4
  import { createEmptyState } from "./events"
4
5
  import { createWsRouter } from "./ws-router"
@@ -23,6 +24,10 @@ describe("ws-router", () => {
23
24
  getSnapshot: () => null,
24
25
  onEvent: () => () => {},
25
26
  } as never,
27
+ keybindings: {
28
+ getSnapshot: () => ({ bindings: { toggleEmbeddedTerminal: ["cmd+j", "ctrl+`"], toggleRightSidebar: ["ctrl+b"], openInFinder: ["cmd+alt+f"], openInEditor: ["cmd+shift+o"], addSplitTerminal: ["cmd+shift+j"] }, warning: null }),
29
+ onChange: () => () => {},
30
+ } as never,
26
31
  fileTree: {
27
32
  getSnapshot: () => ({ projectId: "project-1", rootPath: "/tmp/project-1", pageSize: 200, supportsRealtime: true }),
28
33
  onInvalidate: () => () => {},
@@ -62,6 +67,10 @@ describe("ws-router", () => {
62
67
  onEvent: () => () => {},
63
68
  write: () => {},
64
69
  } as never,
70
+ keybindings: {
71
+ getSnapshot: () => ({ bindings: { toggleEmbeddedTerminal: ["cmd+j", "ctrl+`"], toggleRightSidebar: ["ctrl+b"], openInFinder: ["cmd+alt+f"], openInEditor: ["cmd+shift+o"], addSplitTerminal: ["cmd+shift+j"] }, warning: null }),
72
+ onChange: () => () => {},
73
+ } as never,
65
74
  fileTree: {
66
75
  getSnapshot: () => ({ projectId: "project-1", rootPath: "/tmp/project-1", pageSize: 200, supportsRealtime: true }),
67
76
  onInvalidate: () => () => {},
@@ -128,6 +137,10 @@ describe("ws-router", () => {
128
137
  getSnapshot: () => null,
129
138
  onEvent: () => () => {},
130
139
  } as never,
140
+ keybindings: {
141
+ getSnapshot: () => ({ bindings: { toggleEmbeddedTerminal: ["cmd+j", "ctrl+`"], toggleRightSidebar: ["ctrl+b"], openInFinder: ["cmd+alt+f"], openInEditor: ["cmd+shift+o"], addSplitTerminal: ["cmd+shift+j"] }, warning: null }),
142
+ onChange: () => () => {},
143
+ } as never,
131
144
  fileTree: fileTree as never,
132
145
  refreshDiscovery: async () => [],
133
146
  getDiscoveredProjects: () => [],
@@ -204,4 +217,102 @@ describe("ws-router", () => {
204
217
  id: "tree-sub-1",
205
218
  })
206
219
  })
220
+
221
+ test("subscribes to keybindings snapshots and writes keybindings through the router", async () => {
222
+ const initialSnapshot: KeybindingsSnapshot = {
223
+ bindings: {
224
+ toggleEmbeddedTerminal: ["cmd+j", "ctrl+`"],
225
+ toggleRightSidebar: ["ctrl+b"],
226
+ openInFinder: ["cmd+alt+f"],
227
+ openInEditor: ["cmd+shift+o"],
228
+ addSplitTerminal: ["cmd+shift+j"],
229
+ },
230
+ warning: null,
231
+ }
232
+ const keybindings = {
233
+ snapshot: initialSnapshot,
234
+ getSnapshot() {
235
+ return this.snapshot
236
+ },
237
+ onChange: () => () => {},
238
+ async write(bindings: KeybindingsSnapshot["bindings"]) {
239
+ this.snapshot = { bindings, warning: null }
240
+ return this.snapshot
241
+ },
242
+ }
243
+
244
+ const router = createWsRouter({
245
+ store: { state: createEmptyState() } as never,
246
+ agent: { getActiveStatuses: () => new Map() } as never,
247
+ terminals: {
248
+ getSnapshot: () => null,
249
+ onEvent: () => () => {},
250
+ } as never,
251
+ keybindings: keybindings as never,
252
+ fileTree: {
253
+ getSnapshot: () => ({ projectId: "project-1", rootPath: "/tmp/project-1", pageSize: 200, supportsRealtime: true }),
254
+ onInvalidate: () => () => {},
255
+ } as never,
256
+ refreshDiscovery: async () => [],
257
+ getDiscoveredProjects: () => [],
258
+ machineDisplayName: "Local Machine",
259
+ })
260
+ const ws = new FakeWebSocket()
261
+
262
+ router.handleMessage(
263
+ ws as never,
264
+ JSON.stringify({
265
+ v: 1,
266
+ type: "subscribe",
267
+ id: "keybindings-sub-1",
268
+ topic: { type: "keybindings" },
269
+ })
270
+ )
271
+
272
+ expect(ws.sent[0]).toEqual({
273
+ v: PROTOCOL_VERSION,
274
+ type: "snapshot",
275
+ id: "keybindings-sub-1",
276
+ snapshot: {
277
+ type: "keybindings",
278
+ data: keybindings.snapshot,
279
+ },
280
+ })
281
+
282
+ router.handleMessage(
283
+ ws as never,
284
+ JSON.stringify({
285
+ v: 1,
286
+ type: "command",
287
+ id: "keybindings-write-1",
288
+ command: {
289
+ type: "settings.writeKeybindings",
290
+ bindings: {
291
+ toggleEmbeddedTerminal: ["cmd+k"],
292
+ toggleRightSidebar: ["ctrl+shift+b"],
293
+ openInFinder: ["cmd+shift+g"],
294
+ openInEditor: ["cmd+shift+p"],
295
+ addSplitTerminal: ["cmd+alt+j"],
296
+ },
297
+ },
298
+ })
299
+ )
300
+
301
+ await Promise.resolve()
302
+ expect(ws.sent[1]).toEqual({
303
+ v: PROTOCOL_VERSION,
304
+ type: "ack",
305
+ id: "keybindings-write-1",
306
+ result: {
307
+ bindings: {
308
+ toggleEmbeddedTerminal: ["cmd+k"],
309
+ toggleRightSidebar: ["ctrl+shift+b"],
310
+ openInFinder: ["cmd+shift+g"],
311
+ openInEditor: ["cmd+shift+p"],
312
+ addSplitTerminal: ["cmd+alt+j"],
313
+ },
314
+ warning: null,
315
+ },
316
+ })
317
+ })
207
318
  })
@@ -7,6 +7,7 @@ import type { DiscoveredProject } from "./discovery"
7
7
  import { EventStore } from "./event-store"
8
8
  import { openExternal } from "./external-open"
9
9
  import { FileTreeManager } from "./file-tree-manager"
10
+ import { KeybindingsManager } from "./keybindings"
10
11
  import { ensureProjectDirectory } from "./paths"
11
12
  import { TerminalManager } from "./terminal-manager"
12
13
  import { deriveChatSnapshot, deriveLocalProjectsSnapshot, deriveSidebarData } from "./read-models"
@@ -19,6 +20,7 @@ interface CreateWsRouterArgs {
19
20
  store: EventStore
20
21
  agent: AgentCoordinator
21
22
  terminals: TerminalManager
23
+ keybindings: KeybindingsManager
22
24
  fileTree: FileTreeManager
23
25
  refreshDiscovery: () => Promise<DiscoveredProject[]>
24
26
  getDiscoveredProjects: () => DiscoveredProject[]
@@ -33,6 +35,7 @@ export function createWsRouter({
33
35
  store,
34
36
  agent,
35
37
  terminals,
38
+ keybindings,
36
39
  fileTree,
37
40
  refreshDiscovery,
38
41
  getDiscoveredProjects,
@@ -68,6 +71,18 @@ export function createWsRouter({
68
71
  }
69
72
  }
70
73
 
74
+ if (topic.type === "keybindings") {
75
+ return {
76
+ v: PROTOCOL_VERSION,
77
+ type: "snapshot",
78
+ id,
79
+ snapshot: {
80
+ type: "keybindings",
81
+ data: keybindings.getSnapshot(),
82
+ },
83
+ }
84
+ }
85
+
71
86
  if (topic.type === "terminal") {
72
87
  return {
73
88
  v: PROTOCOL_VERSION,
@@ -156,6 +171,15 @@ export function createWsRouter({
156
171
  }
157
172
  })
158
173
 
174
+ const disposeKeybindingEvents = keybindings.onChange(() => {
175
+ for (const ws of sockets) {
176
+ for (const [id, topic] of ws.data.subscriptions.entries()) {
177
+ if (topic.type !== "keybindings") continue
178
+ send(ws, createEnvelope(id, topic))
179
+ }
180
+ }
181
+ })
182
+
159
183
  async function handleCommand(ws: ServerWebSocket<ClientState>, message: Extract<ClientEnvelope, { type: "command" }>) {
160
184
  const { command, id } = message
161
185
  try {
@@ -164,6 +188,15 @@ export function createWsRouter({
164
188
  send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
165
189
  return
166
190
  }
191
+ case "settings.readKeybindings": {
192
+ send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: keybindings.getSnapshot() })
193
+ return
194
+ }
195
+ case "settings.writeKeybindings": {
196
+ const snapshot = await keybindings.write(command.bindings)
197
+ send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
198
+ return
199
+ }
167
200
  case "project.open": {
168
201
  await ensureProjectDirectory(command.localPath)
169
202
  const project = await store.openProject(command.localPath)
@@ -329,6 +362,7 @@ export function createWsRouter({
329
362
  dispose() {
330
363
  disposeTerminalEvents()
331
364
  disposeFileTreeEvents()
365
+ disposeKeybindingEvents()
332
366
  },
333
367
  }
334
368
  }
@@ -12,8 +12,12 @@ export function getDataRootName() {
12
12
  return DATA_ROOT_NAME
13
13
  }
14
14
 
15
+ export function getDataRootDir(homeDir: string) {
16
+ return `${homeDir}/${DATA_ROOT_NAME}`
17
+ }
18
+
15
19
  export function getDataDir(homeDir: string) {
16
- return `${homeDir}/${DATA_ROOT_NAME}/data`
20
+ return `${getDataRootDir(homeDir)}/data`
17
21
  }
18
22
 
19
23
  export function getDataDirDisplay() {
@@ -3,6 +3,7 @@ import type {
3
3
  ChatSnapshot,
4
4
  FileTreeDirectoryPage,
5
5
  FileTreeSnapshot,
6
+ KeybindingsSnapshot,
6
7
  LocalProjectsSnapshot,
7
8
  ModelOptions,
8
9
  SidebarData,
@@ -18,6 +19,7 @@ export interface EditorOpenSettings {
18
19
  export type SubscriptionTopic =
19
20
  | { type: "sidebar" }
20
21
  | { type: "local-projects" }
22
+ | { type: "keybindings" }
21
23
  | { type: "file-tree"; projectId: string }
22
24
  | { type: "chat"; chatId: string }
23
25
  | { type: "terminal"; terminalId: string }
@@ -45,6 +47,8 @@ export type ClientCommand =
45
47
  | { type: "project.create"; localPath: string; title: string }
46
48
  | { type: "project.remove"; projectId: string }
47
49
  | { type: "system.ping" }
50
+ | { type: "settings.readKeybindings" }
51
+ | { type: "settings.writeKeybindings"; bindings: KeybindingsSnapshot["bindings"] }
48
52
  | {
49
53
  type: "system.openExternal"
50
54
  localPath: string
@@ -89,6 +93,7 @@ export type ClientEnvelope =
89
93
  export type ServerSnapshot =
90
94
  | { type: "sidebar"; data: SidebarData }
91
95
  | { type: "local-projects"; data: LocalProjectsSnapshot }
96
+ | { type: "keybindings"; data: KeybindingsSnapshot }
92
97
  | { type: "file-tree"; data: FileTreeSnapshot }
93
98
  | { type: "chat"; data: ChatSnapshot | null }
94
99
  | { type: "terminal"; data: TerminalSnapshot | null }
@@ -191,6 +191,26 @@ export interface FileTreeSnapshot {
191
191
  supportsRealtime: true
192
192
  }
193
193
 
194
+ export type KeybindingAction =
195
+ | "toggleEmbeddedTerminal"
196
+ | "toggleRightSidebar"
197
+ | "openInFinder"
198
+ | "openInEditor"
199
+ | "addSplitTerminal"
200
+
201
+ export const DEFAULT_KEYBINDINGS: Record<KeybindingAction, string[]> = {
202
+ toggleEmbeddedTerminal: ["cmd+j", "ctrl+`"],
203
+ toggleRightSidebar: ["cmd+b", "ctrl+b"],
204
+ openInFinder: ["cmd+alt+f", "ctrl+alt+f"],
205
+ openInEditor: ["cmd+shift+o", "ctrl+shift+o"],
206
+ addSplitTerminal: ["cmd+/", "ctrl+/"],
207
+ }
208
+
209
+ export interface KeybindingsSnapshot {
210
+ bindings: Record<KeybindingAction, string[]>
211
+ warning: string | null
212
+ }
213
+
194
214
  export interface McpServerInfo {
195
215
  name: string
196
216
  status: string
@@ -244,46 +264,46 @@ interface ToolCallBase<TKind extends string, TInput> {
244
264
  }
245
265
 
246
266
  export interface AskUserQuestionToolCall
247
- extends ToolCallBase<"ask_user_question", { questions: AskUserQuestionItem[] }> {}
267
+ extends ToolCallBase<"ask_user_question", { questions: AskUserQuestionItem[] }> { }
248
268
 
249
269
  export interface ExitPlanModeToolCall
250
- extends ToolCallBase<"exit_plan_mode", { plan?: string; summary?: string }> {}
270
+ extends ToolCallBase<"exit_plan_mode", { plan?: string; summary?: string }> { }
251
271
 
252
272
  export interface TodoWriteToolCall
253
- extends ToolCallBase<"todo_write", { todos: TodoItem[] }> {}
273
+ extends ToolCallBase<"todo_write", { todos: TodoItem[] }> { }
254
274
 
255
275
  export interface SkillToolCall
256
- extends ToolCallBase<"skill", { skill: string }> {}
276
+ extends ToolCallBase<"skill", { skill: string }> { }
257
277
 
258
278
  export interface GlobToolCall
259
- extends ToolCallBase<"glob", { pattern: string }> {}
279
+ extends ToolCallBase<"glob", { pattern: string }> { }
260
280
 
261
281
  export interface GrepToolCall
262
- extends ToolCallBase<"grep", { pattern: string; outputMode?: string }> {}
282
+ extends ToolCallBase<"grep", { pattern: string; outputMode?: string }> { }
263
283
 
264
284
  export interface BashToolCall
265
- extends ToolCallBase<"bash", { command: string; description?: string; timeoutMs?: number; runInBackground?: boolean }> {}
285
+ extends ToolCallBase<"bash", { command: string; description?: string; timeoutMs?: number; runInBackground?: boolean }> { }
266
286
 
267
287
  export interface WebSearchToolCall
268
- extends ToolCallBase<"web_search", { query: string }> {}
288
+ extends ToolCallBase<"web_search", { query: string }> { }
269
289
 
270
290
  export interface ReadFileToolCall
271
- extends ToolCallBase<"read_file", { filePath: string }> {}
291
+ extends ToolCallBase<"read_file", { filePath: string }> { }
272
292
 
273
293
  export interface WriteFileToolCall
274
- extends ToolCallBase<"write_file", { filePath: string; content: string }> {}
294
+ extends ToolCallBase<"write_file", { filePath: string; content: string }> { }
275
295
 
276
296
  export interface EditFileToolCall
277
- extends ToolCallBase<"edit_file", { filePath: string; oldString: string; newString: string }> {}
297
+ extends ToolCallBase<"edit_file", { filePath: string; oldString: string; newString: string }> { }
278
298
 
279
299
  export interface SubagentTaskToolCall
280
- extends ToolCallBase<"subagent_task", { subagentType?: string }> {}
300
+ extends ToolCallBase<"subagent_task", { subagentType?: string }> { }
281
301
 
282
302
  export interface McpGenericToolCall
283
- extends ToolCallBase<"mcp_generic", { server: string; tool: string; payload: Record<string, unknown> }> {}
303
+ extends ToolCallBase<"mcp_generic", { server: string; tool: string; payload: Record<string, unknown> }> { }
284
304
 
285
305
  export interface UnknownToolCall
286
- extends ToolCallBase<"unknown_tool", { payload: Record<string, unknown> }> {}
306
+ extends ToolCallBase<"unknown_tool", { payload: Record<string, unknown> }> { }
287
307
 
288
308
  export type NormalizedToolCall =
289
309
  | AskUserQuestionToolCall