kaizenai 0.3.0 → 0.5.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.
Files changed (74) hide show
  1. package/README.md +0 -4
  2. package/bin/kaizen +16 -5
  3. package/dist/client/app-icon-180.png +0 -0
  4. package/dist/client/app-icon-192.png +0 -0
  5. package/dist/client/app-icon-512.png +0 -0
  6. package/dist/client/app-icon-96.png +0 -0
  7. package/dist/client/apple-touch-icon.png +0 -0
  8. package/dist/client/assets/index-C4Zm475L.js +638 -0
  9. package/dist/client/assets/index-CxKis6wK.css +32 -0
  10. package/dist/client/favicon.png +0 -0
  11. package/dist/client/index.html +17 -10
  12. package/dist/client/manifest-dark.webmanifest +3 -3
  13. package/dist/client/manifest.webmanifest +3 -3
  14. package/dist/client/pwa-192.png +0 -0
  15. package/dist/client/pwa-512.png +0 -0
  16. package/dist/server/cli-supervisor.js +306 -0
  17. package/dist/server/cli.js +12636 -0
  18. package/package.json +7 -10
  19. package/dist/client/assets/index-BBs80KD-.js +0 -623
  20. package/dist/client/assets/index-CkCgyLNq.css +0 -32
  21. package/src/server/acp-shared.ts +0 -315
  22. package/src/server/agent.ts +0 -1159
  23. package/src/server/attachments.ts +0 -133
  24. package/src/server/backgrounds.ts +0 -74
  25. package/src/server/cli-runtime.ts +0 -375
  26. package/src/server/cli-supervisor.ts +0 -97
  27. package/src/server/cli.ts +0 -68
  28. package/src/server/codex-app-server-protocol.ts +0 -453
  29. package/src/server/codex-app-server.ts +0 -1350
  30. package/src/server/cursor-acp.ts +0 -819
  31. package/src/server/discovery.ts +0 -322
  32. package/src/server/event-store.ts +0 -1470
  33. package/src/server/events.ts +0 -252
  34. package/src/server/external-open.ts +0 -272
  35. package/src/server/gemini-acp.ts +0 -844
  36. package/src/server/gemini-cli.ts +0 -525
  37. package/src/server/generate-title.ts +0 -36
  38. package/src/server/git-manager.ts +0 -79
  39. package/src/server/git-repository.ts +0 -101
  40. package/src/server/harness-types.ts +0 -20
  41. package/src/server/keybindings.ts +0 -177
  42. package/src/server/machine-name.ts +0 -22
  43. package/src/server/paths.ts +0 -112
  44. package/src/server/process-utils.ts +0 -22
  45. package/src/server/project-icon.ts +0 -352
  46. package/src/server/project-metadata.ts +0 -10
  47. package/src/server/provider-catalog.ts +0 -85
  48. package/src/server/provider-settings.ts +0 -155
  49. package/src/server/quick-response.ts +0 -153
  50. package/src/server/read-models.ts +0 -275
  51. package/src/server/recovery.ts +0 -507
  52. package/src/server/restart.ts +0 -56
  53. package/src/server/server.ts +0 -244
  54. package/src/server/terminal-manager.ts +0 -350
  55. package/src/server/theme-settings.ts +0 -179
  56. package/src/server/update-manager.ts +0 -230
  57. package/src/server/usage/base-provider-usage.ts +0 -57
  58. package/src/server/usage/claude-usage.ts +0 -558
  59. package/src/server/usage/codex-usage.ts +0 -144
  60. package/src/server/usage/cursor-browser.ts +0 -120
  61. package/src/server/usage/cursor-cookies.ts +0 -390
  62. package/src/server/usage/cursor-usage.ts +0 -490
  63. package/src/server/usage/gemini-usage.ts +0 -24
  64. package/src/server/usage/provider-usage.ts +0 -61
  65. package/src/server/usage/test-helpers.ts +0 -9
  66. package/src/server/usage/types.ts +0 -54
  67. package/src/server/usage/utils.ts +0 -325
  68. package/src/server/ws-router.ts +0 -742
  69. package/src/shared/branding.ts +0 -83
  70. package/src/shared/dev-ports.ts +0 -43
  71. package/src/shared/ports.ts +0 -2
  72. package/src/shared/protocol.ts +0 -156
  73. package/src/shared/tools.ts +0 -251
  74. package/src/shared/types.ts +0 -1040
@@ -1,742 +0,0 @@
1
- import type { ServerWebSocket } from "bun"
2
- import { DEFAULT_PROVIDER_SETTINGS, PROTOCOL_VERSION, getSelectableProviders, isProviderSelectable, type AgentProvider } from "../shared/types"
3
- import type { ClientEnvelope, ServerEnvelope, SubscriptionTopic } from "../shared/protocol"
4
- import { isClientEnvelope } from "../shared/protocol"
5
- import type { AgentCoordinator } from "./agent"
6
- import type { DiscoveredProject } from "./discovery"
7
- import { EventStore } from "./event-store"
8
- import { openExternal, openUrl } from "./external-open"
9
- import { GitManager } from "./git-manager"
10
- import { KeybindingsManager } from "./keybindings"
11
- import { ProviderSettingsManager } from "./provider-settings"
12
- import { ThemeSettingsManager } from "./theme-settings"
13
- import { listProjectDirectories, requireProjectDirectory, ensureProjectDirectory } from "./paths"
14
- import { importProjectHistory } from "./recovery"
15
- import { TerminalManager } from "./terminal-manager"
16
- import type { UpdateManager } from "./update-manager"
17
- import { deriveChatSnapshot, deriveLocalProjectsSnapshot, deriveSidebarData } from "./read-models"
18
- import { refreshClaudeRateLimitFromCli } from "./usage/claude-usage"
19
- import { importCursorUsageFromCurl, refreshCursorUsage, signInToCursorWithBrowser } from "./usage/cursor-usage"
20
-
21
- export interface ClientState {
22
- subscriptions: Map<string, SubscriptionTopic>
23
- }
24
-
25
- const PROVIDER_USAGE_POLL_INTERVAL_MS = 30 * 60 * 1000
26
- const PROVIDER_USAGE_POLL_MAX_INTERVAL_MS = 31 * 60 * 1000
27
-
28
- interface CreateWsRouterArgs {
29
- store: EventStore
30
- agent: AgentCoordinator
31
- terminals: TerminalManager
32
- git: GitManager
33
- keybindings: KeybindingsManager
34
- providerSettings?: ProviderSettingsManager
35
- themeSettings: ThemeSettingsManager
36
- refreshDiscovery: () => Promise<DiscoveredProject[]>
37
- getDiscoveredProjects: () => DiscoveredProject[]
38
- machineDisplayName: string
39
- updateManager: UpdateManager | null
40
- providerUsagePollIntervalMs?: number
41
- refreshProviderUsage?: (provider?: AgentProvider, force?: boolean) => Promise<void>
42
- openUrlCommand?: typeof openUrl
43
- }
44
-
45
- function send(ws: ServerWebSocket<ClientState>, message: ServerEnvelope) {
46
- ws.send(JSON.stringify(message))
47
- }
48
-
49
- export function createWsRouter({
50
- store,
51
- agent,
52
- terminals,
53
- git,
54
- keybindings,
55
- providerSettings,
56
- themeSettings,
57
- refreshDiscovery,
58
- getDiscoveredProjects,
59
- machineDisplayName,
60
- updateManager,
61
- providerUsagePollIntervalMs,
62
- refreshProviderUsage = async (provider, force) => {
63
- const selectableProviders = getSelectableProviders(providerSettings?.getSnapshot().settings ?? DEFAULT_PROVIDER_SETTINGS).map((entry) => entry.id)
64
- if (((!provider && selectableProviders.includes("claude")) || provider === "claude") && selectableProviders.includes("claude")) {
65
- await refreshClaudeRateLimitFromCli(store.dataDir, undefined, force).then(() => {})
66
- }
67
- if (((!provider && selectableProviders.includes("cursor")) || provider === "cursor") && selectableProviders.includes("cursor")) {
68
- await refreshCursorUsage(store.dataDir, undefined, force).then(() => {})
69
- }
70
- },
71
- openUrlCommand = openUrl,
72
- }: CreateWsRouterArgs) {
73
- const sockets = new Set<ServerWebSocket<ClientState>>()
74
- let providerUsageRefreshInFlight: Promise<void> | null = null
75
- let providerUsagePollTimer: Timer | null = null
76
-
77
- function createEnvelope(id: string, topic: SubscriptionTopic): ServerEnvelope {
78
- if (topic.type === "sidebar") {
79
- return {
80
- v: PROTOCOL_VERSION,
81
- type: "snapshot",
82
- id,
83
- snapshot: {
84
- type: "sidebar",
85
- data: deriveSidebarData(store.state, agent.getActiveStatuses(), agent.getProviderUsage()),
86
- },
87
- }
88
- }
89
-
90
- if (topic.type === "local-projects") {
91
- const discoveredProjects = getDiscoveredProjects()
92
- const data = deriveLocalProjectsSnapshot(store.state, discoveredProjects, machineDisplayName)
93
-
94
- return {
95
- v: PROTOCOL_VERSION,
96
- type: "snapshot",
97
- id,
98
- snapshot: {
99
- type: "local-projects",
100
- data,
101
- },
102
- }
103
- }
104
-
105
- if (topic.type === "keybindings") {
106
- return {
107
- v: PROTOCOL_VERSION,
108
- type: "snapshot",
109
- id,
110
- snapshot: {
111
- type: "keybindings",
112
- data: keybindings.getSnapshot(),
113
- },
114
- }
115
- }
116
-
117
- if (topic.type === "theme-settings") {
118
- return {
119
- v: PROTOCOL_VERSION,
120
- type: "snapshot",
121
- id,
122
- snapshot: {
123
- type: "theme-settings",
124
- data: themeSettings.getSnapshot(),
125
- },
126
- }
127
- }
128
-
129
- if (topic.type === "provider-settings") {
130
- return {
131
- v: PROTOCOL_VERSION,
132
- type: "snapshot",
133
- id,
134
- snapshot: {
135
- type: "provider-settings",
136
- data: providerSettings?.getSnapshot() ?? {
137
- settings: DEFAULT_PROVIDER_SETTINGS,
138
- warning: null,
139
- filePathDisplay: "",
140
- },
141
- },
142
- }
143
- }
144
-
145
- if (topic.type === "update") {
146
- return {
147
- v: PROTOCOL_VERSION,
148
- type: "snapshot",
149
- id,
150
- snapshot: {
151
- type: "update",
152
- data: updateManager?.getSnapshot() ?? {
153
- currentVersion: "unknown",
154
- latestVersion: null,
155
- status: "idle",
156
- updateAvailable: false,
157
- lastCheckedAt: null,
158
- error: null,
159
- installAction: "restart",
160
- },
161
- },
162
- }
163
- }
164
-
165
- if (topic.type === "terminal") {
166
- return {
167
- v: PROTOCOL_VERSION,
168
- type: "snapshot",
169
- id,
170
- snapshot: {
171
- type: "terminal",
172
- data: terminals.getSnapshot(topic.terminalId),
173
- },
174
- }
175
- }
176
-
177
- if (topic.type === "feature-overview") {
178
- return {
179
- v: PROTOCOL_VERSION,
180
- type: "snapshot",
181
- id,
182
- snapshot: {
183
- type: "feature-overview",
184
- data: store.getFeatureOverviewSnapshot(topic.featureId),
185
- },
186
- }
187
- }
188
-
189
- return {
190
- v: PROTOCOL_VERSION,
191
- type: "snapshot",
192
- id,
193
- snapshot: {
194
- type: "chat",
195
- data: (() => {
196
- return deriveChatSnapshot(
197
- store.state,
198
- agent.getActiveStatuses(),
199
- topic.chatId,
200
- (chatId) => store.getMessages(chatId),
201
- agent.getChatPendingTool(topic.chatId),
202
- agent.getLiveUsage(topic.chatId),
203
- providerSettings?.getSnapshot().settings ?? DEFAULT_PROVIDER_SETTINGS
204
- )
205
- })(),
206
- },
207
- }
208
- }
209
-
210
- function pushSnapshots(ws: ServerWebSocket<ClientState>) {
211
- for (const [id, topic] of ws.data.subscriptions.entries()) {
212
- send(ws, createEnvelope(id, topic))
213
- }
214
- }
215
-
216
- function broadcastSnapshots() {
217
- for (const ws of sockets) {
218
- pushSnapshots(ws)
219
- }
220
- }
221
-
222
- function broadcastSidebarSnapshots() {
223
- for (const ws of sockets) {
224
- for (const [id, topic] of ws.data.subscriptions.entries()) {
225
- if (topic.type !== "sidebar") continue
226
- send(ws, createEnvelope(id, topic))
227
- }
228
- }
229
- }
230
-
231
- function hasSidebarSubscribers() {
232
- for (const ws of sockets) {
233
- for (const topic of ws.data.subscriptions.values()) {
234
- if (topic.type === "sidebar") return true
235
- }
236
- }
237
- return false
238
- }
239
-
240
- function pushTerminalSnapshot(terminalId: string) {
241
- for (const ws of sockets) {
242
- for (const [id, topic] of ws.data.subscriptions.entries()) {
243
- if (topic.type !== "terminal" || topic.terminalId !== terminalId) continue
244
- send(ws, createEnvelope(id, topic))
245
- }
246
- }
247
- }
248
-
249
- function pushFeatureOverviewSnapshot(featureId: string) {
250
- for (const ws of sockets) {
251
- for (const [id, topic] of ws.data.subscriptions.entries()) {
252
- if (topic.type !== "feature-overview" || topic.featureId !== featureId) continue
253
- send(ws, createEnvelope(id, topic))
254
- }
255
- }
256
- }
257
-
258
- function pushTerminalEvent(terminalId: string, event: Extract<ServerEnvelope, { type: "event" }>["event"]) {
259
- for (const ws of sockets) {
260
- for (const [id, topic] of ws.data.subscriptions.entries()) {
261
- if (topic.type !== "terminal" || topic.terminalId !== terminalId) continue
262
- send(ws, {
263
- v: PROTOCOL_VERSION,
264
- type: "event",
265
- id,
266
- event,
267
- })
268
- }
269
- }
270
- }
271
-
272
- const disposeTerminalEvents = terminals.onEvent((event) => {
273
- pushTerminalEvent(event.terminalId, event)
274
- })
275
-
276
- const disposeKeybindingEvents = keybindings.onChange(() => {
277
- for (const ws of sockets) {
278
- for (const [id, topic] of ws.data.subscriptions.entries()) {
279
- if (topic.type !== "keybindings") continue
280
- send(ws, createEnvelope(id, topic))
281
- }
282
- }
283
- })
284
-
285
- const disposeThemeSettingsEvents = themeSettings.onChange(() => {
286
- for (const ws of sockets) {
287
- for (const [id, topic] of ws.data.subscriptions.entries()) {
288
- if (topic.type !== "theme-settings") continue
289
- send(ws, createEnvelope(id, topic))
290
- }
291
- }
292
- })
293
-
294
- const disposeProviderSettingsEvents = providerSettings?.onChange(() => {
295
- for (const ws of sockets) {
296
- for (const [id, topic] of ws.data.subscriptions.entries()) {
297
- if (topic.type !== "provider-settings") continue
298
- send(ws, createEnvelope(id, topic))
299
- }
300
- }
301
- broadcastSnapshots()
302
- }) ?? (() => {})
303
-
304
- const disposeUpdateEvents = updateManager?.onChange(() => {
305
- for (const ws of sockets) {
306
- for (const [id, topic] of ws.data.subscriptions.entries()) {
307
- if (topic.type !== "update") continue
308
- send(ws, createEnvelope(id, topic))
309
- }
310
- }
311
- }) ?? (() => {})
312
-
313
- async function runProviderUsagePoll() {
314
- if (!hasSidebarSubscribers()) return
315
- if (!providerUsageRefreshInFlight) {
316
- providerUsageRefreshInFlight = refreshProviderUsage().finally(() => {
317
- providerUsageRefreshInFlight = null
318
- })
319
- }
320
- await providerUsageRefreshInFlight
321
- broadcastSidebarSnapshots()
322
- }
323
-
324
- function nextProviderUsagePollDelayMs() {
325
- if (typeof providerUsagePollIntervalMs === "number") {
326
- return providerUsagePollIntervalMs
327
- }
328
-
329
- return Math.floor(Math.random() * (PROVIDER_USAGE_POLL_MAX_INTERVAL_MS - PROVIDER_USAGE_POLL_INTERVAL_MS + 1))
330
- + PROVIDER_USAGE_POLL_INTERVAL_MS
331
- }
332
-
333
- function scheduleProviderUsagePoll() {
334
- if (providerUsagePollTimer) {
335
- clearTimeout(providerUsagePollTimer)
336
- }
337
-
338
- providerUsagePollTimer = setTimeout(() => {
339
- void runProviderUsagePoll().finally(() => {
340
- scheduleProviderUsagePoll()
341
- })
342
- }, nextProviderUsagePollDelayMs())
343
- }
344
-
345
- scheduleProviderUsagePoll()
346
-
347
- async function handleCommand(ws: ServerWebSocket<ClientState>, message: Extract<ClientEnvelope, { type: "command" }>) {
348
- const { command, id } = message
349
- try {
350
- switch (command.type) {
351
- case "system.ping": {
352
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
353
- return
354
- }
355
- case "system.listDirectory": {
356
- const directory = await listProjectDirectories(command.localPath)
357
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: directory })
358
- return
359
- }
360
- case "update.check": {
361
- const snapshot = updateManager
362
- ? await updateManager.checkForUpdates({ force: command.force })
363
- : {
364
- currentVersion: "unknown",
365
- latestVersion: null,
366
- status: "error",
367
- updateAvailable: false,
368
- lastCheckedAt: Date.now(),
369
- error: "Update manager unavailable.",
370
- installAction: "restart",
371
- }
372
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
373
- return
374
- }
375
- case "update.install": {
376
- if (!updateManager) {
377
- throw new Error("Update manager unavailable.")
378
- }
379
- const result = await updateManager.installUpdate()
380
- send(ws, {
381
- v: PROTOCOL_VERSION,
382
- type: "ack",
383
- id,
384
- result,
385
- })
386
- return
387
- }
388
- case "settings.readKeybindings": {
389
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: keybindings.getSnapshot() })
390
- return
391
- }
392
- case "settings.writeKeybindings": {
393
- const snapshot = await keybindings.write(command.bindings)
394
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
395
- return
396
- }
397
- case "settings.writeThemeSettings": {
398
- const snapshot = await themeSettings.write(command.settings)
399
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
400
- return
401
- }
402
- case "settings.writeProviderSettings": {
403
- if (!providerSettings) throw new Error("Provider settings unavailable.")
404
- const snapshot = await providerSettings.write(command.settings)
405
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
406
- return
407
- }
408
- case "project.open": {
409
- await requireProjectDirectory(command.localPath)
410
- await store.unhideProject(command.localPath)
411
- const project = await store.openProject(command.localPath)
412
- const imported = await importProjectHistory({
413
- store,
414
- projectId: project.id,
415
- repoKey: project.repoKey,
416
- localPath: project.localPath,
417
- worktreePaths: project.worktreePaths,
418
- })
419
- await store.reconcileProjectFeatureState(project.id)
420
- await refreshDiscovery()
421
- send(ws, {
422
- v: PROTOCOL_VERSION,
423
- type: "ack",
424
- id,
425
- result: {
426
- projectId: project.id,
427
- chatId: imported.newestChatId,
428
- importedChats: imported.importedChats,
429
- },
430
- })
431
- break
432
- }
433
- case "project.create": {
434
- await ensureProjectDirectory(command.localPath)
435
- await store.unhideProject(command.localPath)
436
- const project = await store.openProject(command.localPath, command.title)
437
- const imported = await importProjectHistory({
438
- store,
439
- projectId: project.id,
440
- repoKey: project.repoKey,
441
- localPath: project.localPath,
442
- worktreePaths: project.worktreePaths,
443
- })
444
- await store.reconcileProjectFeatureState(project.id)
445
- await refreshDiscovery()
446
- send(ws, {
447
- v: PROTOCOL_VERSION,
448
- type: "ack",
449
- id,
450
- result: {
451
- projectId: project.id,
452
- chatId: imported.newestChatId,
453
- importedChats: imported.importedChats,
454
- },
455
- })
456
- break
457
- }
458
- case "project.remove": {
459
- const project = store.getProject(command.projectId)
460
- for (const chat of store.listChatsByProject(command.projectId)) {
461
- await agent.cancel(chat.id)
462
- }
463
- if (project) {
464
- for (const worktreePath of project.worktreePaths) {
465
- terminals.closeByCwd(worktreePath)
466
- }
467
- await store.hideProject(project.localPath)
468
- }
469
- await refreshDiscovery()
470
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
471
- break
472
- }
473
- case "project.setBrowserState": {
474
- await store.setProjectBrowserState(command.projectId, command.browserState)
475
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
476
- break
477
- }
478
- case "project.setGeneralChatsBrowserState": {
479
- await store.setProjectGeneralChatsBrowserState(command.projectId, command.browserState)
480
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
481
- break
482
- }
483
- case "project.hide": {
484
- const existingProject = store.listProjects().find((project) => project.localPath === command.localPath)
485
- if (existingProject) {
486
- for (const chat of store.listChatsByProject(existingProject.id)) {
487
- await agent.cancel(chat.id)
488
- }
489
- for (const worktreePath of existingProject.worktreePaths) {
490
- terminals.closeByCwd(worktreePath)
491
- }
492
- }
493
- await store.hideProject(command.localPath)
494
- await refreshDiscovery()
495
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
496
- break
497
- }
498
- case "project.setProjectMetadataDirectoryCommitMode": {
499
- const localPath = command.projectId
500
- ? store.getProject(command.projectId)?.localPath
501
- : command.localPath
502
- if (!localPath) {
503
- throw new Error("Project not found")
504
- }
505
- await git.setProjectMetadataDirectoryCommitMode(localPath, command.commitProjectMetadata)
506
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
507
- break
508
- }
509
- case "system.openExternal": {
510
- await openExternal(command)
511
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
512
- break
513
- }
514
- case "system.openUrl": {
515
- await openUrlCommand(command)
516
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
517
- break
518
- }
519
- case "provider.refreshUsage": {
520
- if (command.provider && !isProviderSelectable(command.provider, providerSettings?.getSnapshot().settings ?? DEFAULT_PROVIDER_SETTINGS)) {
521
- throw new Error(`${command.provider} is inactive.`)
522
- }
523
- await refreshProviderUsage(command.provider, true)
524
- broadcastSidebarSnapshots()
525
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
526
- break
527
- }
528
- case "provider.browserLogin": {
529
- if (command.provider !== "cursor") {
530
- throw new Error(`Browser login is not supported for provider: ${command.provider}`)
531
- }
532
- const result = await signInToCursorWithBrowser(store.dataDir)
533
- broadcastSidebarSnapshots()
534
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
535
- break
536
- }
537
- case "provider.importUsageCurl": {
538
- if (command.provider !== "cursor") {
539
- throw new Error(`cURL import is not supported for provider: ${command.provider}`)
540
- }
541
- const result = await importCursorUsageFromCurl(store.dataDir, command.curlCommand)
542
- broadcastSidebarSnapshots()
543
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
544
- break
545
- }
546
- case "feature.create": {
547
- const feature = await store.createFeature(
548
- command.projectId,
549
- command.title,
550
- command.description ?? "",
551
- command.generateOverview ?? true
552
- )
553
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: { featureId: feature.id } })
554
- break
555
- }
556
- case "feature.rename": {
557
- await store.renameFeature(command.featureId, command.title)
558
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
559
- break
560
- }
561
- case "feature.setBrowserState": {
562
- await store.setFeatureBrowserState(command.featureId, command.browserState)
563
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
564
- break
565
- }
566
- case "feature.setStage": {
567
- await store.setFeatureStage(command.featureId, command.stage)
568
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
569
- break
570
- }
571
- case "feature.reorder": {
572
- await store.reorderFeatures(command.projectId, command.orderedFeatureIds)
573
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
574
- break
575
- }
576
- case "feature.delete": {
577
- await store.deleteFeature(command.featureId)
578
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
579
- break
580
- }
581
- case "feature.updateOverview": {
582
- const snapshot = await store.updateFeatureOverview(command.featureId, command.content)
583
- pushFeatureOverviewSnapshot(command.featureId)
584
- broadcastSidebarSnapshots()
585
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
586
- break
587
- }
588
- case "chat.create": {
589
- const chat = await store.createChat(command.projectId, command.featureId)
590
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: { chatId: chat.id } })
591
- break
592
- }
593
- case "chat.setFeature": {
594
- await store.setChatFeature(command.chatId, command.featureId)
595
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
596
- break
597
- }
598
- case "chat.rename": {
599
- await store.renameChat(command.chatId, command.title)
600
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
601
- break
602
- }
603
- case "chat.delete": {
604
- await agent.cancel(command.chatId)
605
- await store.deleteChat(command.chatId)
606
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
607
- break
608
- }
609
- case "chat.send": {
610
- if (command.provider && !isProviderSelectable(command.provider, providerSettings?.getSnapshot().settings ?? DEFAULT_PROVIDER_SETTINGS)) {
611
- throw new Error(`${command.provider} is inactive.`)
612
- }
613
- const result = await agent.send(command)
614
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
615
- break
616
- }
617
- case "chat.cancel": {
618
- await agent.cancel(command.chatId)
619
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
620
- break
621
- }
622
- case "chat.respondTool": {
623
- await agent.respondTool(command)
624
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
625
- break
626
- }
627
- case "terminal.create": {
628
- const project = store.getProject(command.projectId)
629
- if (!project) {
630
- throw new Error("Project not found")
631
- }
632
- const snapshot = terminals.createTerminal({
633
- projectPath: project.localPath,
634
- terminalId: command.terminalId,
635
- cols: command.cols,
636
- rows: command.rows,
637
- scrollback: command.scrollback,
638
- })
639
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
640
- return
641
- }
642
- case "terminal.input": {
643
- terminals.write(command.terminalId, command.data)
644
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
645
- return
646
- }
647
- case "terminal.resize": {
648
- terminals.resize(command.terminalId, command.cols, command.rows)
649
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
650
- return
651
- }
652
- case "terminal.close": {
653
- terminals.close(command.terminalId)
654
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
655
- pushTerminalSnapshot(command.terminalId)
656
- return
657
- }
658
- case "git.getBranches": {
659
- const project = store.getProject(command.projectId)
660
- if (!project) throw new Error("Project not found")
661
- const result = await git.getBranches(project.localPath)
662
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
663
- return
664
- }
665
- case "git.switchBranch": {
666
- const project = store.getProject(command.projectId)
667
- if (!project) throw new Error("Project not found")
668
- const result = await git.switchBranch(project.localPath, command.branchName)
669
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
670
- return
671
- }
672
- case "git.createBranch": {
673
- const project = store.getProject(command.projectId)
674
- if (!project) throw new Error("Project not found")
675
- const result = await git.createBranch(project.localPath, command.branchName, command.checkout)
676
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
677
- return
678
- }
679
- }
680
-
681
- broadcastSnapshots()
682
- } catch (error) {
683
- const messageText = error instanceof Error ? error.message : String(error)
684
- send(ws, { v: PROTOCOL_VERSION, type: "error", id, message: messageText })
685
- }
686
- }
687
-
688
- return {
689
- handleOpen(ws: ServerWebSocket<ClientState>) {
690
- sockets.add(ws)
691
- },
692
- handleClose(ws: ServerWebSocket<ClientState>) {
693
- sockets.delete(ws)
694
- },
695
- broadcastSnapshots,
696
- handleMessage(ws: ServerWebSocket<ClientState>, raw: string | Buffer | ArrayBuffer | Uint8Array) {
697
- let parsed: unknown
698
- try {
699
- parsed = JSON.parse(String(raw))
700
- } catch {
701
- send(ws, { v: PROTOCOL_VERSION, type: "error", message: "Invalid JSON" })
702
- return
703
- }
704
-
705
- if (!isClientEnvelope(parsed)) {
706
- send(ws, { v: PROTOCOL_VERSION, type: "error", message: "Invalid envelope" })
707
- return
708
- }
709
-
710
- if (parsed.type === "subscribe") {
711
- ws.data.subscriptions.set(parsed.id, parsed.topic)
712
- if (parsed.topic.type === "local-projects") {
713
- void refreshDiscovery().then(() => {
714
- if (ws.data.subscriptions.has(parsed.id)) {
715
- send(ws, createEnvelope(parsed.id, parsed.topic))
716
- }
717
- })
718
- }
719
- send(ws, createEnvelope(parsed.id, parsed.topic))
720
- return
721
- }
722
-
723
- if (parsed.type === "unsubscribe") {
724
- ws.data.subscriptions.delete(parsed.id)
725
- send(ws, { v: PROTOCOL_VERSION, type: "ack", id: parsed.id })
726
- return
727
- }
728
-
729
- void handleCommand(ws, parsed)
730
- },
731
- dispose() {
732
- if (providerUsagePollTimer) {
733
- clearTimeout(providerUsagePollTimer)
734
- }
735
- disposeTerminalEvents()
736
- disposeKeybindingEvents()
737
- disposeProviderSettingsEvents()
738
- disposeThemeSettingsEvents()
739
- disposeUpdateEvents()
740
- },
741
- }
742
- }