kaizenai 0.1.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/LICENSE +22 -0
  2. package/README.md +246 -0
  3. package/bin/kaizen +15 -0
  4. package/dist/client/apple-touch-icon.png +0 -0
  5. package/dist/client/assets/index-D-ORCGrq.js +603 -0
  6. package/dist/client/assets/index-r28mcHqz.css +32 -0
  7. package/dist/client/favicon.png +0 -0
  8. package/dist/client/fonts/body-medium.woff2 +0 -0
  9. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  10. package/dist/client/fonts/body-regular.woff2 +0 -0
  11. package/dist/client/fonts/body-semibold.woff2 +0 -0
  12. package/dist/client/index.html +22 -0
  13. package/dist/client/manifest-dark.webmanifest +24 -0
  14. package/dist/client/manifest.webmanifest +24 -0
  15. package/dist/client/pwa-192.png +0 -0
  16. package/dist/client/pwa-512.png +0 -0
  17. package/dist/client/pwa-icon.svg +15 -0
  18. package/dist/client/pwa-splash.png +0 -0
  19. package/dist/client/pwa-splash.svg +15 -0
  20. package/package.json +103 -0
  21. package/src/server/acp-shared.ts +315 -0
  22. package/src/server/agent.ts +1120 -0
  23. package/src/server/attachments.ts +133 -0
  24. package/src/server/backgrounds.ts +74 -0
  25. package/src/server/cli-runtime.ts +333 -0
  26. package/src/server/cli-supervisor.ts +81 -0
  27. package/src/server/cli.ts +68 -0
  28. package/src/server/codex-app-server-protocol.ts +453 -0
  29. package/src/server/codex-app-server.ts +1350 -0
  30. package/src/server/cursor-acp.ts +819 -0
  31. package/src/server/discovery.ts +322 -0
  32. package/src/server/event-store.ts +1369 -0
  33. package/src/server/events.ts +244 -0
  34. package/src/server/external-open.ts +272 -0
  35. package/src/server/gemini-acp.ts +844 -0
  36. package/src/server/gemini-cli.ts +525 -0
  37. package/src/server/generate-title.ts +36 -0
  38. package/src/server/git-manager.ts +79 -0
  39. package/src/server/git-repository.ts +101 -0
  40. package/src/server/harness-types.ts +20 -0
  41. package/src/server/keybindings.ts +177 -0
  42. package/src/server/machine-name.ts +22 -0
  43. package/src/server/paths.ts +112 -0
  44. package/src/server/process-utils.ts +22 -0
  45. package/src/server/project-icon.ts +344 -0
  46. package/src/server/project-metadata.ts +10 -0
  47. package/src/server/provider-catalog.ts +85 -0
  48. package/src/server/provider-settings.ts +155 -0
  49. package/src/server/quick-response.ts +153 -0
  50. package/src/server/read-models.ts +275 -0
  51. package/src/server/recovery.ts +507 -0
  52. package/src/server/restart.ts +30 -0
  53. package/src/server/server.ts +244 -0
  54. package/src/server/terminal-manager.ts +350 -0
  55. package/src/server/theme-settings.ts +179 -0
  56. package/src/server/update-manager.ts +230 -0
  57. package/src/server/usage/base-provider-usage.ts +57 -0
  58. package/src/server/usage/claude-usage.ts +558 -0
  59. package/src/server/usage/codex-usage.ts +144 -0
  60. package/src/server/usage/cursor-browser.ts +120 -0
  61. package/src/server/usage/cursor-cookies.ts +390 -0
  62. package/src/server/usage/cursor-usage.ts +490 -0
  63. package/src/server/usage/gemini-usage.ts +24 -0
  64. package/src/server/usage/provider-usage.ts +61 -0
  65. package/src/server/usage/test-helpers.ts +9 -0
  66. package/src/server/usage/types.ts +54 -0
  67. package/src/server/usage/utils.ts +325 -0
  68. package/src/server/ws-router.ts +717 -0
  69. package/src/shared/branding.ts +83 -0
  70. package/src/shared/dev-ports.ts +43 -0
  71. package/src/shared/ports.ts +2 -0
  72. package/src/shared/protocol.ts +152 -0
  73. package/src/shared/tools.ts +251 -0
  74. package/src/shared/types.ts +1028 -0
@@ -0,0 +1,1028 @@
1
+ export const STORE_VERSION = 3 as const
2
+ export const PROTOCOL_VERSION = 1 as const
3
+
4
+ export type AgentProvider = "claude" | "codex" | "gemini" | "cursor"
5
+
6
+ export interface ProviderModelOption {
7
+ id: string
8
+ label: string
9
+ supportsEffort: boolean
10
+ contextWindowOptions?: readonly ProviderContextWindowOption[]
11
+ }
12
+
13
+ export interface ProviderEffortOption {
14
+ id: string
15
+ label: string
16
+ }
17
+
18
+ export interface ProviderContextWindowOption {
19
+ id: ClaudeContextWindow
20
+ label: string
21
+ }
22
+
23
+ export interface GeminiThinkingOption {
24
+ id: "off" | "standard" | "high"
25
+ label: string
26
+ }
27
+
28
+ export const CLAUDE_REASONING_OPTIONS = [
29
+ { id: "low", label: "Low" },
30
+ { id: "medium", label: "Medium" },
31
+ { id: "high", label: "High" },
32
+ { id: "max", label: "Max" },
33
+ ] as const satisfies readonly ProviderEffortOption[]
34
+
35
+ export const CODEX_REASONING_OPTIONS = [
36
+ { id: "minimal", label: "Minimal" },
37
+ { id: "low", label: "Low" },
38
+ { id: "medium", label: "Medium" },
39
+ { id: "high", label: "High" },
40
+ { id: "xhigh", label: "XHigh" },
41
+ ] as const satisfies readonly ProviderEffortOption[]
42
+
43
+ export type ClaudeReasoningEffort = (typeof CLAUDE_REASONING_OPTIONS)[number]["id"]
44
+ export type CodexReasoningEffort = (typeof CODEX_REASONING_OPTIONS)[number]["id"]
45
+ export type ClaudeContextWindow = "200k" | "1m"
46
+ export type ServiceTier = "fast"
47
+
48
+ export const CLAUDE_CONTEXT_WINDOW_FALLBACKS: Record<string, number> = {
49
+ sonnet: 1_000_000,
50
+ opus: 1_000_000,
51
+ haiku: 200_000,
52
+ }
53
+
54
+ export const GEMINI_THINKING_OPTIONS = [
55
+ { id: "off", label: "Off" },
56
+ { id: "standard", label: "Standard" },
57
+ { id: "high", label: "High" },
58
+ ] as const satisfies readonly GeminiThinkingOption[]
59
+
60
+ export type GeminiThinkingMode = (typeof GEMINI_THINKING_OPTIONS)[number]["id"]
61
+
62
+ export interface ClaudeModelOptions {
63
+ reasoningEffort: ClaudeReasoningEffort
64
+ contextWindow?: ClaudeContextWindow
65
+ }
66
+
67
+ export interface CodexModelOptions {
68
+ reasoningEffort: CodexReasoningEffort
69
+ fastMode: boolean
70
+ }
71
+
72
+ export interface GeminiModelOptions {
73
+ thinkingMode: GeminiThinkingMode
74
+ }
75
+
76
+ export interface CursorModelOptions {}
77
+
78
+ export type CursorModelSpeed = "fast" | "standard"
79
+
80
+ export const DEFAULT_CURSOR_MODEL = "claude-opus-4-6[thinking=true,context=200k,effort=high,fast=false]" as const
81
+
82
+ export const CURSOR_MODELS = [
83
+ { id: "composer-2[fast=true]", label: "Composer 2", supportsEffort: false },
84
+ { id: "composer-1.5[]", label: "Composer 1.5", supportsEffort: false },
85
+ { id: "gpt-5.3-codex[reasoning=medium,fast=false]", label: "Codex 5.3", supportsEffort: false },
86
+ { id: "gpt-5.4[reasoning=medium,context=272k,fast=false]", label: "GPT-5.4", supportsEffort: false },
87
+ { id: "claude-sonnet-4-6[thinking=true,context=200k,effort=medium]", label: "Sonnet 4.6", supportsEffort: false },
88
+ { id: "claude-opus-4-6[thinking=true,context=200k,effort=high,fast=false]", label: "Opus 4.6", supportsEffort: false },
89
+ { id: "claude-opus-4-5[thinking=true]", label: "Opus 4.5", supportsEffort: false },
90
+ { id: "gpt-5.2[reasoning=medium,fast=false]", label: "GPT-5.2", supportsEffort: false },
91
+ { id: "gemini-3.1-pro[]", label: "Gemini 3.1 Pro", supportsEffort: false },
92
+ { id: "gpt-5.4-mini[reasoning=medium]", label: "GPT-5.4 Mini", supportsEffort: false },
93
+ { id: "gpt-5.4-nano[reasoning=medium]", label: "GPT-5.4 Nano", supportsEffort: false },
94
+ { id: "claude-haiku-4-5[thinking=true]", label: "Haiku 4.5", supportsEffort: false },
95
+ { id: "gpt-5.3-codex-spark[reasoning=medium]", label: "Codex 5.3 Spark", supportsEffort: false },
96
+ { id: "grok-4-20[thinking=true]", label: "Grok 4.20", supportsEffort: false },
97
+ { id: "claude-sonnet-4-5[thinking=true,context=200k]", label: "Sonnet 4.5", supportsEffort: false },
98
+ { id: "gpt-5.2-codex[reasoning=medium,fast=false]", label: "Codex 5.2", supportsEffort: false },
99
+ { id: "gemini-3-flash[]", label: "Gemini 3 Flash", supportsEffort: false },
100
+ { id: "claude-sonnet-4[thinking=false,context=200k]", label: "Sonnet 4", supportsEffort: false },
101
+ { id: "kimi-k2.5[]", label: "Kimi K2.5", supportsEffort: false },
102
+ ] as const satisfies readonly ProviderModelOption[]
103
+
104
+ export const CURSOR_MODEL_ALIASES: Record<string, string> = {
105
+ "composer-2-fast": "composer-2[fast=true]",
106
+ "composer-1.5": "composer-1.5[]",
107
+ "gpt-5.3-codex": "gpt-5.3-codex[reasoning=medium,fast=false]",
108
+ "gpt-5.4": "gpt-5.4[reasoning=medium,context=272k,fast=false]",
109
+ "gpt-5.4-medium": "gpt-5.4[reasoning=medium,context=272k,fast=false]",
110
+ "claude-4.6-sonnet-medium-thinking": "claude-sonnet-4-6[thinking=true,context=200k,effort=medium]",
111
+ "claude-4.6-opus-high-thinking": "claude-opus-4-6[thinking=true,context=200k,effort=high,fast=false]",
112
+ "claude-4.5-opus-high-thinking": "claude-opus-4-5[thinking=true]",
113
+ "gpt-5.2": "gpt-5.2[reasoning=medium,fast=false]",
114
+ "gemini-3.1-pro": "gemini-3.1-pro[]",
115
+ "gpt-5.4-mini": "gpt-5.4-mini[reasoning=medium]",
116
+ "gpt-5.4-mini-medium": "gpt-5.4-mini[reasoning=medium]",
117
+ "gpt-5.4-nano": "gpt-5.4-nano[reasoning=medium]",
118
+ "gpt-5.4-nano-medium": "gpt-5.4-nano[reasoning=medium]",
119
+ "claude-haiku-4.5": "claude-haiku-4-5[thinking=true]",
120
+ "gpt-5.3-codex-spark-preview": "gpt-5.3-codex-spark[reasoning=medium]",
121
+ "grok-4-20": "grok-4-20[thinking=true]",
122
+ "grok-4-20-thinking": "grok-4-20[thinking=true]",
123
+ "claude-4.5-sonnet-thinking": "claude-sonnet-4-5[thinking=true,context=200k]",
124
+ "gpt-5.2-codex": "gpt-5.2-codex[reasoning=medium,fast=false]",
125
+ "gemini-3-flash": "gemini-3-flash[]",
126
+ "claude-4-sonnet": "claude-sonnet-4[thinking=false,context=200k]",
127
+ "kimi-k2.5": "kimi-k2.5[]",
128
+ }
129
+
130
+ export function normalizeCursorModelId(model?: string): string {
131
+ if (!model) return DEFAULT_CURSOR_MODEL
132
+ if (CURSOR_MODELS.some((candidate) => candidate.id === model)) return model
133
+ return CURSOR_MODEL_ALIASES[model] ?? DEFAULT_CURSOR_MODEL
134
+ }
135
+
136
+ export function getCursorModelBaseId(modelId: string): string {
137
+ const bracketIndex = modelId.indexOf("[")
138
+ return bracketIndex >= 0 ? modelId.slice(0, bracketIndex) : modelId
139
+ }
140
+
141
+ export function getCursorModelSpeed(modelId: string): CursorModelSpeed | null {
142
+ const fastMatch = modelId.match(/\bfast=(true|false)\b/)
143
+ if (!fastMatch) return null
144
+ return fastMatch[1] === "true" ? "fast" : "standard"
145
+ }
146
+
147
+ export interface ProviderModelOptionsByProvider {
148
+ claude: ClaudeModelOptions
149
+ codex: CodexModelOptions
150
+ gemini: GeminiModelOptions
151
+ cursor: CursorModelOptions
152
+ }
153
+
154
+ export type ModelOptions = Partial<{
155
+ [K in AgentProvider]: Partial<ProviderModelOptionsByProvider[K]>
156
+ }>
157
+
158
+ export function isClaudeReasoningEffort(value: unknown): value is ClaudeReasoningEffort {
159
+ return CLAUDE_REASONING_OPTIONS.some((option) => option.id === value)
160
+ }
161
+
162
+ export function isCodexReasoningEffort(value: unknown): value is CodexReasoningEffort {
163
+ return CODEX_REASONING_OPTIONS.some((option) => option.id === value)
164
+ }
165
+
166
+ export const CLAUDE_CONTEXT_WINDOW_OPTIONS = [
167
+ { id: "200k", label: "200k" },
168
+ { id: "1m", label: "1M" },
169
+ ] as const satisfies readonly ProviderContextWindowOption[]
170
+
171
+ export function isClaudeContextWindow(value: unknown): value is ClaudeContextWindow {
172
+ return CLAUDE_CONTEXT_WINDOW_OPTIONS.some((option) => option.id === value)
173
+ }
174
+
175
+ export function isGeminiThinkingMode(value: unknown): value is GeminiThinkingMode {
176
+ return GEMINI_THINKING_OPTIONS.some((option) => option.id === value)
177
+ }
178
+
179
+ export interface ProviderCatalogEntry {
180
+ id: AgentProvider
181
+ label: string
182
+ systemActive: boolean
183
+ defaultModel: string
184
+ defaultEffort?: string
185
+ defaultModelOptions: Record<string, unknown>
186
+ supportsPlanMode: boolean
187
+ models: ProviderModelOption[]
188
+ efforts: ProviderEffortOption[]
189
+ }
190
+
191
+ export interface ProviderSettingsEntry {
192
+ active: boolean
193
+ }
194
+
195
+ export type ProviderSettingsMap = Record<AgentProvider, ProviderSettingsEntry>
196
+
197
+ export interface ProviderSettingsSnapshot {
198
+ settings: ProviderSettingsMap
199
+ warning: string | null
200
+ filePathDisplay: string
201
+ }
202
+
203
+ export interface EffectiveProviderEntry extends ProviderCatalogEntry {
204
+ active: boolean
205
+ isSelectable: boolean
206
+ isUsageEnabled: boolean
207
+ isInactive: boolean
208
+ inactiveMessage: string | null
209
+ }
210
+
211
+ export const PROVIDERS: ProviderCatalogEntry[] = [
212
+ {
213
+ id: "claude",
214
+ label: "Claude",
215
+ systemActive: true,
216
+ defaultModel: "sonnet",
217
+ defaultEffort: "high",
218
+ defaultModelOptions: {
219
+ reasoningEffort: "high",
220
+ contextWindow: "200k",
221
+ } as const satisfies ClaudeModelOptions,
222
+ supportsPlanMode: true,
223
+ models: [
224
+ { id: "opus", label: "Opus", supportsEffort: true, contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS] },
225
+ { id: "sonnet", label: "Sonnet", supportsEffort: true, contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS] },
226
+ { id: "haiku", label: "Haiku", supportsEffort: true },
227
+ ],
228
+ efforts: [...CLAUDE_REASONING_OPTIONS],
229
+ },
230
+ {
231
+ id: "codex",
232
+ label: "Codex",
233
+ systemActive: true,
234
+ defaultModel: "gpt-5.4",
235
+ defaultModelOptions: {
236
+ reasoningEffort: "high",
237
+ fastMode: false,
238
+ } as const satisfies CodexModelOptions,
239
+ supportsPlanMode: true,
240
+ models: [
241
+ { id: "gpt-5.4", label: "GPT-5.4", supportsEffort: false },
242
+ { id: "gpt-5.3-codex", label: "GPT-5.3 Codex", supportsEffort: false },
243
+ { id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark", supportsEffort: false },
244
+ ],
245
+ efforts: [],
246
+ },
247
+ {
248
+ id: "gemini",
249
+ label: "Gemini",
250
+ systemActive: false,
251
+ defaultModel: "auto-gemini-2.5",
252
+ defaultModelOptions: {
253
+ thinkingMode: "standard",
254
+ } as const satisfies GeminiModelOptions,
255
+ supportsPlanMode: true,
256
+ models: [
257
+ { id: "auto-gemini-3", label: "Auto (Gemini 3)", supportsEffort: false },
258
+ { id: "auto-gemini-2.5", label: "Auto (Gemini 2.5)", supportsEffort: false },
259
+ { id: "gemini-3.1-pro-preview", label: "3.1 Pro Preview", supportsEffort: false },
260
+ { id: "gemini-3-pro-preview", label: "3 Pro Preview", supportsEffort: false },
261
+ { id: "gemini-3-flash-preview", label: "3 Flash Preview", supportsEffort: false },
262
+ { id: "gemini-2.5-pro", label: "2.5 Pro", supportsEffort: false },
263
+ { id: "gemini-2.5-flash", label: "2.5 Flash", supportsEffort: false },
264
+ { id: "gemini-2.5-flash-lite", label: "2.5 Flash Lite", supportsEffort: false },
265
+ ],
266
+ efforts: [],
267
+ },
268
+ {
269
+ id: "cursor",
270
+ label: "Cursor",
271
+ systemActive: true,
272
+ defaultModel: DEFAULT_CURSOR_MODEL,
273
+ defaultModelOptions: {
274
+ } as const satisfies CursorModelOptions,
275
+ supportsPlanMode: true,
276
+ models: [...CURSOR_MODELS],
277
+ efforts: [],
278
+ },
279
+ ]
280
+
281
+ export const DEFAULT_PROVIDER_SETTINGS: ProviderSettingsMap = {
282
+ claude: {
283
+ active: true,
284
+ },
285
+ codex: {
286
+ active: true,
287
+ },
288
+ gemini: {
289
+ active: true,
290
+ },
291
+ cursor: {
292
+ active: true,
293
+ },
294
+ }
295
+
296
+ export function getProviderCatalog(provider: AgentProvider): ProviderCatalogEntry {
297
+ const entry = PROVIDERS.find((candidate) => candidate.id === provider)
298
+ if (!entry) {
299
+ throw new Error(`Unknown provider: ${provider}`)
300
+ }
301
+ return entry
302
+ }
303
+
304
+ export function getProviderSettingsEntry(
305
+ settings: Partial<Record<AgentProvider, Partial<ProviderSettingsEntry>>> | ProviderSettingsMap | null | undefined,
306
+ provider: AgentProvider
307
+ ): ProviderSettingsEntry {
308
+ const defaults = DEFAULT_PROVIDER_SETTINGS[provider]
309
+ const value = settings?.[provider]
310
+
311
+ return {
312
+ active: value?.active ?? defaults.active,
313
+ }
314
+ }
315
+
316
+ export function getProviderInactiveMessage(
317
+ provider: AgentProvider,
318
+ settings: Partial<Record<AgentProvider, Partial<ProviderSettingsEntry>>> | ProviderSettingsMap | null | undefined
319
+ ): string | null {
320
+ const catalog = getProviderCatalog(provider)
321
+ const entry = getProviderSettingsEntry(settings, provider)
322
+ if (catalog.systemActive && entry.active) return null
323
+ return `Provider ${catalog.label} is currently not active.`
324
+ }
325
+
326
+ export function isProviderSelectable(
327
+ provider: AgentProvider,
328
+ settings: Partial<Record<AgentProvider, Partial<ProviderSettingsEntry>>> | ProviderSettingsMap | null | undefined
329
+ ): boolean {
330
+ const catalog = getProviderCatalog(provider)
331
+ const entry = getProviderSettingsEntry(settings, provider)
332
+ return catalog.systemActive && entry.active
333
+ }
334
+
335
+ export function getEffectiveProviders(
336
+ settings: Partial<Record<AgentProvider, Partial<ProviderSettingsEntry>>> | ProviderSettingsMap | null | undefined
337
+ ): EffectiveProviderEntry[] {
338
+ return PROVIDERS.map((provider) => {
339
+ const providerSettings = getProviderSettingsEntry(settings, provider.id)
340
+ const isSelectable = provider.systemActive && providerSettings.active
341
+
342
+ return {
343
+ ...provider,
344
+ active: providerSettings.active,
345
+ isSelectable,
346
+ isUsageEnabled: isSelectable,
347
+ isInactive: !isSelectable,
348
+ inactiveMessage: getProviderInactiveMessage(provider.id, settings),
349
+ }
350
+ })
351
+ }
352
+
353
+ export function getSelectableProviders(
354
+ settings: Partial<Record<AgentProvider, Partial<ProviderSettingsEntry>>> | ProviderSettingsMap | null | undefined
355
+ ): ProviderCatalogEntry[] {
356
+ return getEffectiveProviders(settings)
357
+ .filter((provider) => provider.isSelectable)
358
+ .map(({ active: _active, isSelectable: _isSelectable, isUsageEnabled: _isUsageEnabled, isInactive: _isInactive, inactiveMessage: _inactiveMessage, ...provider }) => provider)
359
+ }
360
+
361
+ export function getProviderDefaultModelOptions<T extends AgentProvider>(
362
+ provider: T
363
+ ): ProviderModelOptionsByProvider[T] {
364
+ return getProviderCatalog(provider).defaultModelOptions as unknown as ProviderModelOptionsByProvider[T]
365
+ }
366
+
367
+ export const DEFAULT_CLAUDE_MODEL_OPTIONS =
368
+ getProviderDefaultModelOptions("claude") as ClaudeModelOptions
369
+
370
+ export const DEFAULT_CODEX_MODEL_OPTIONS =
371
+ getProviderDefaultModelOptions("codex") as CodexModelOptions
372
+
373
+ export const DEFAULT_GEMINI_MODEL_OPTIONS =
374
+ getProviderDefaultModelOptions("gemini") as GeminiModelOptions
375
+
376
+ export const DEFAULT_CURSOR_MODEL_OPTIONS =
377
+ getProviderDefaultModelOptions("cursor") as CursorModelOptions
378
+
379
+ export function getClaudeModelOption(modelId: string): ProviderModelOption | undefined {
380
+ return getProviderCatalog("claude").models.find((candidate) => candidate.id === modelId)
381
+ }
382
+
383
+ export function getClaudeContextWindowOptions(modelId: string): readonly ProviderContextWindowOption[] {
384
+ return getClaudeModelOption(modelId)?.contextWindowOptions ?? []
385
+ }
386
+
387
+ export function normalizeClaudeContextWindow(modelId: string, contextWindow?: unknown): ClaudeContextWindow | undefined {
388
+ const options = getClaudeContextWindowOptions(modelId)
389
+ if (options.length === 0) return undefined
390
+ return options.some((option) => option.id === contextWindow)
391
+ ? contextWindow as ClaudeContextWindow
392
+ : DEFAULT_CLAUDE_MODEL_OPTIONS.contextWindow
393
+ }
394
+
395
+ export function resolveClaudeApiModelId(modelId: string, contextWindow?: ClaudeContextWindow): string {
396
+ return contextWindow === "1m" ? `${modelId}[1m]` : modelId
397
+ }
398
+ export type KaizenStatus =
399
+ | "idle"
400
+ | "starting"
401
+ | "running"
402
+ | "waiting_for_user"
403
+ | "failed"
404
+
405
+ export const FEATURE_STAGES = ["idea", "todo", "progress", "testing", "done"] as const
406
+ export type FeatureStage = (typeof FEATURE_STAGES)[number]
407
+ export const FEATURE_BROWSER_STATES = ["OPEN", "CLOSED"] as const
408
+ export type FeatureBrowserState = (typeof FEATURE_BROWSER_STATES)[number]
409
+
410
+ export const FEATURE_STAGE_LABELS: Record<FeatureStage, string> = {
411
+ idea: "IDEA",
412
+ todo: "TODO",
413
+ progress: "PROGRESS",
414
+ testing: "TESTING",
415
+ done: "DONE",
416
+ }
417
+
418
+ export interface ProjectSummary {
419
+ id: string
420
+ repoKey: string
421
+ localPath: string
422
+ worktreePaths: string[]
423
+ title: string
424
+ browserState: FeatureBrowserState
425
+ generalChatsBrowserState: FeatureBrowserState
426
+ createdAt: number
427
+ updatedAt: number
428
+ }
429
+
430
+ export interface FeatureSummary {
431
+ id: string
432
+ projectId: string
433
+ title: string
434
+ description: string
435
+ browserState: FeatureBrowserState
436
+ stage: FeatureStage
437
+ sortOrder: number
438
+ directoryRelativePath: string
439
+ overviewRelativePath: string
440
+ createdAt: number
441
+ updatedAt: number
442
+ }
443
+
444
+ export interface SidebarChatRow {
445
+ _id: string
446
+ _creationTime: number
447
+ chatId: string
448
+ title: string
449
+ status: KaizenStatus
450
+ localPath: string
451
+ provider: AgentProvider | null
452
+ lastMessageAt?: number
453
+ hasAutomation: boolean
454
+ featureId?: string | null
455
+ }
456
+
457
+ export interface SidebarFeatureRow {
458
+ featureId: string
459
+ title: string
460
+ description: string
461
+ browserState: FeatureBrowserState
462
+ stage: FeatureStage
463
+ sortOrder: number
464
+ directoryRelativePath: string
465
+ overviewRelativePath: string
466
+ updatedAt: number
467
+ chats: SidebarChatRow[]
468
+ }
469
+
470
+ export interface SidebarProjectGroup {
471
+ groupKey: string
472
+ title: string
473
+ localPath: string
474
+ iconDataUrl?: string | null
475
+ browserState: FeatureBrowserState
476
+ generalChatsBrowserState: FeatureBrowserState
477
+ features: SidebarFeatureRow[]
478
+ generalChats: SidebarChatRow[]
479
+ }
480
+
481
+ export type ProviderUsageAvailability = "available" | "unavailable" | "stale" | "login_required"
482
+
483
+ export interface ProviderUsageEntry {
484
+ provider: AgentProvider
485
+ sessionLimitUsedPercent: number | null
486
+ apiLimitUsedPercent?: number | null
487
+ rateLimitResetAt: number | null
488
+ rateLimitResetLabel?: string | null
489
+ weeklyLimitUsedPercent?: number | null
490
+ weeklyRateLimitResetAt?: number | null
491
+ weeklyRateLimitResetLabel?: string | null
492
+ statusDetail?: string | null
493
+ availability: ProviderUsageAvailability
494
+ lastRequestedAt?: number | null
495
+ updatedAt: number | null
496
+ warnings: ChatUsageWarning[]
497
+ }
498
+
499
+ export type ProviderUsageMap = Partial<Record<AgentProvider, ProviderUsageEntry>>
500
+
501
+ export interface SidebarData {
502
+ projectGroups: SidebarProjectGroup[]
503
+ providerUsage?: ProviderUsageMap
504
+ }
505
+
506
+ export interface LocalProjectSummary {
507
+ localPath: string
508
+ title: string
509
+ iconDataUrl?: string | null
510
+ source: "saved" | "discovered"
511
+ lastOpenedAt?: number
512
+ chatCount: number
513
+ }
514
+
515
+ export interface DirectoryBrowserEntry {
516
+ name: string
517
+ localPath: string
518
+ }
519
+
520
+ export interface DirectoryBrowserSnapshot {
521
+ currentPath: string
522
+ parentPath: string | null
523
+ roots: DirectoryBrowserEntry[]
524
+ entries: DirectoryBrowserEntry[]
525
+ }
526
+
527
+ export interface SuggestedProjectFolder {
528
+ label: string
529
+ localPath: string
530
+ }
531
+
532
+ export interface LocalProjectsSnapshot {
533
+ machine: {
534
+ id: "local"
535
+ displayName: string
536
+ }
537
+ projects: LocalProjectSummary[]
538
+ suggestedFolders: SuggestedProjectFolder[]
539
+ rootDirectory: DirectoryBrowserSnapshot | null
540
+ }
541
+
542
+ export type UpdateStatus =
543
+ | "idle"
544
+ | "checking"
545
+ | "available"
546
+ | "up_to_date"
547
+ | "updating"
548
+ | "restart_pending"
549
+ | "error"
550
+
551
+ export interface UpdateSnapshot {
552
+ currentVersion: string
553
+ latestVersion: string | null
554
+ status: UpdateStatus
555
+ updateAvailable: boolean
556
+ lastCheckedAt: number | null
557
+ error: string | null
558
+ installAction: "restart" | "reload"
559
+ }
560
+
561
+ export type UpdateInstallErrorCode =
562
+ | "version_not_live_yet"
563
+ | "install_failed"
564
+ | "command_missing"
565
+
566
+ export interface UpdateInstallResult {
567
+ ok: boolean
568
+ action: "restart" | "reload"
569
+ errorCode: UpdateInstallErrorCode | null
570
+ userTitle: string | null
571
+ userMessage: string | null
572
+ }
573
+
574
+ export type KeybindingAction =
575
+ | "submitChatMessage"
576
+ | "toggleProjectsSidebar"
577
+ | "toggleEmbeddedTerminal"
578
+ | "toggleRightSidebar"
579
+ | "openInFinder"
580
+ | "openInEditor"
581
+ | "addSplitTerminal"
582
+
583
+ export const DEFAULT_KEYBINDINGS: Record<KeybindingAction, string[]> = {
584
+ submitChatMessage: ["enter"],
585
+ toggleProjectsSidebar: ["ctrl+a"],
586
+ toggleEmbeddedTerminal: ["cmd+j", "ctrl+`"],
587
+ toggleRightSidebar: ["cmd+b", "ctrl+b"],
588
+ openInFinder: ["cmd+alt+f", "ctrl+alt+f"],
589
+ openInEditor: ["cmd+shift+o", "ctrl+shift+o"],
590
+ addSplitTerminal: ["cmd+/", "ctrl+/"],
591
+ }
592
+
593
+ export interface KeybindingsSnapshot {
594
+ bindings: Record<KeybindingAction, string[]>
595
+ warning: string | null
596
+ filePathDisplay: string
597
+ }
598
+
599
+ export type ThemePreference = "light" | "dark" | "system" | "custom"
600
+ export type ColorTheme = "default" | "tokyo-night" | "catppuccin" | "dracula" | "nord" | "everforest" | "rose-pine"
601
+ export type CustomAppearance = "light" | "dark" | "system"
602
+
603
+ export interface ThemeSettingsSnapshot {
604
+ settings: {
605
+ themePreference: ThemePreference
606
+ colorTheme: ColorTheme
607
+ customAppearance: CustomAppearance
608
+ backgroundImage: string | null
609
+ backgroundOpacity: number
610
+ backgroundBlur: number
611
+ showProjectIconsInSidebar: boolean
612
+ }
613
+ warning: string | null
614
+ filePathDisplay: string
615
+ }
616
+
617
+ export const DEFAULT_THEME_SETTINGS: ThemeSettingsSnapshot["settings"] = {
618
+ themePreference: "system",
619
+ colorTheme: "default",
620
+ customAppearance: "system",
621
+ backgroundImage: null,
622
+ backgroundOpacity: 1,
623
+ backgroundBlur: 0,
624
+ showProjectIconsInSidebar: true,
625
+ }
626
+
627
+ export interface McpServerInfo {
628
+ name: string
629
+ status: string
630
+ error?: string
631
+ }
632
+
633
+ export interface AccountInfo {
634
+ email?: string
635
+ organization?: string
636
+ subscriptionType?: string
637
+ tokenSource?: string
638
+ apiKeySource?: string
639
+ }
640
+
641
+ export interface AskUserQuestionOption {
642
+ label: string
643
+ description?: string
644
+ }
645
+
646
+ export interface AskUserQuestionItem {
647
+ id?: string
648
+ question: string
649
+ header?: string
650
+ options?: AskUserQuestionOption[]
651
+ multiSelect?: boolean
652
+ }
653
+
654
+ export type AskUserQuestionAnswerMap = Record<string, string[]>
655
+
656
+ export interface TodoItem {
657
+ content: string
658
+ status: "pending" | "in_progress" | "completed"
659
+ activeForm: string
660
+ }
661
+
662
+ export const MAX_CHAT_ATTACHMENTS = 8
663
+ export const MAX_CHAT_IMAGE_BYTES = 10 * 1024 * 1024
664
+ export const SUPPORTED_CHAT_IMAGE_MIME_TYPES = [
665
+ "image/png",
666
+ "image/jpeg",
667
+ "image/webp",
668
+ "image/gif",
669
+ ] as const
670
+
671
+ export interface ChatImageAttachment {
672
+ type: "image"
673
+ id: string
674
+ name: string
675
+ mimeType: string
676
+ sizeBytes: number
677
+ relativePath: string
678
+ }
679
+
680
+ export interface ChatImageAttachmentUpload {
681
+ type: "image"
682
+ name: string
683
+ mimeType: string
684
+ sizeBytes: number
685
+ dataUrl: string
686
+ }
687
+
688
+ export type ChatAttachment = ChatImageAttachment
689
+ export type ChatAttachmentUpload = ChatImageAttachmentUpload
690
+
691
+ export interface ChatUserMessage {
692
+ text: string
693
+ attachments?: ChatAttachmentUpload[]
694
+ }
695
+
696
+ export interface HydratedChatImageAttachment extends ChatImageAttachment {
697
+ previewUrl: string
698
+ }
699
+
700
+ export type HydratedChatAttachment = HydratedChatImageAttachment
701
+
702
+ interface TranscriptEntryBase {
703
+ _id: string
704
+ messageId?: string
705
+ createdAt: number
706
+ hidden?: boolean
707
+ debugRaw?: string
708
+ }
709
+
710
+ interface ToolCallBase<TKind extends string, TInput> {
711
+ kind: "tool"
712
+ toolKind: TKind
713
+ toolName: string
714
+ toolId: string
715
+ input: TInput
716
+ rawInput?: Record<string, unknown>
717
+ }
718
+
719
+ export interface AskUserQuestionToolCall
720
+ extends ToolCallBase<"ask_user_question", { questions: AskUserQuestionItem[] }> { }
721
+
722
+ export interface ExitPlanModeToolCall
723
+ extends ToolCallBase<"exit_plan_mode", { plan?: string; summary?: string }> { }
724
+
725
+ export interface TodoWriteToolCall
726
+ extends ToolCallBase<"todo_write", { todos: TodoItem[] }> { }
727
+
728
+ export interface SkillToolCall
729
+ extends ToolCallBase<"skill", { skill: string }> { }
730
+
731
+ export interface GlobToolCall
732
+ extends ToolCallBase<"glob", { pattern: string }> { }
733
+
734
+ export interface GrepToolCall
735
+ extends ToolCallBase<"grep", { pattern: string; outputMode?: string }> { }
736
+
737
+ export interface BashToolCall
738
+ extends ToolCallBase<"bash", { command: string; description?: string; timeoutMs?: number; runInBackground?: boolean }> { }
739
+
740
+ export interface WebSearchToolCall
741
+ extends ToolCallBase<"web_search", { query: string }> { }
742
+
743
+ export interface ReadFileToolCall
744
+ extends ToolCallBase<"read_file", { filePath: string }> { }
745
+
746
+ export interface WriteFileToolCall
747
+ extends ToolCallBase<"write_file", { filePath: string; content: string }> { }
748
+
749
+ export interface EditFileToolCall
750
+ extends ToolCallBase<"edit_file", { filePath: string; oldString: string; newString: string }> { }
751
+
752
+ export interface SubagentTaskToolCall
753
+ extends ToolCallBase<"subagent_task", { subagentType?: string }> { }
754
+
755
+ export interface McpGenericToolCall
756
+ extends ToolCallBase<"mcp_generic", { server: string; tool: string; payload: Record<string, unknown> }> { }
757
+
758
+ export interface UnknownToolCall
759
+ extends ToolCallBase<"unknown_tool", { payload: Record<string, unknown> }> { }
760
+
761
+ export type NormalizedToolCall =
762
+ | AskUserQuestionToolCall
763
+ | ExitPlanModeToolCall
764
+ | TodoWriteToolCall
765
+ | SkillToolCall
766
+ | GlobToolCall
767
+ | GrepToolCall
768
+ | BashToolCall
769
+ | WebSearchToolCall
770
+ | ReadFileToolCall
771
+ | WriteFileToolCall
772
+ | EditFileToolCall
773
+ | SubagentTaskToolCall
774
+ | McpGenericToolCall
775
+ | UnknownToolCall
776
+
777
+ export interface ToolResultEntry extends TranscriptEntryBase {
778
+ kind: "tool_result"
779
+ toolId: string
780
+ content: unknown
781
+ isError?: boolean
782
+ }
783
+
784
+ export interface UserPromptEntry extends TranscriptEntryBase {
785
+ kind: "user_prompt"
786
+ content: string
787
+ attachments?: ChatAttachment[]
788
+ }
789
+
790
+ export interface SystemInitEntry extends TranscriptEntryBase {
791
+ kind: "system_init"
792
+ provider: AgentProvider
793
+ model: string
794
+ tools: string[]
795
+ agents: string[]
796
+ slashCommands: string[]
797
+ mcpServers: McpServerInfo[]
798
+ }
799
+
800
+ export interface AccountInfoEntry extends TranscriptEntryBase {
801
+ kind: "account_info"
802
+ accountInfo: AccountInfo
803
+ }
804
+
805
+ export interface AssistantTextEntry extends TranscriptEntryBase {
806
+ kind: "assistant_text"
807
+ text: string
808
+ }
809
+
810
+ export interface AssistantThoughtEntry extends TranscriptEntryBase {
811
+ kind: "assistant_thought"
812
+ text: string
813
+ }
814
+
815
+ export interface ToolCallEntry extends TranscriptEntryBase {
816
+ kind: "tool_call"
817
+ tool: NormalizedToolCall
818
+ }
819
+
820
+ export interface ResultEntry extends TranscriptEntryBase {
821
+ kind: "result"
822
+ subtype: "success" | "error" | "cancelled"
823
+ isError: boolean
824
+ durationMs: number
825
+ result: string
826
+ costUsd?: number
827
+ }
828
+
829
+ export interface StatusEntry extends TranscriptEntryBase {
830
+ kind: "status"
831
+ status: string
832
+ }
833
+
834
+ export interface CompactBoundaryEntry extends TranscriptEntryBase {
835
+ kind: "compact_boundary"
836
+ }
837
+
838
+ export interface CompactSummaryEntry extends TranscriptEntryBase {
839
+ kind: "compact_summary"
840
+ summary: string
841
+ }
842
+
843
+ export interface ContextClearedEntry extends TranscriptEntryBase {
844
+ kind: "context_cleared"
845
+ }
846
+
847
+ export interface InterruptedEntry extends TranscriptEntryBase {
848
+ kind: "interrupted"
849
+ }
850
+
851
+ export type TranscriptEntry =
852
+ | UserPromptEntry
853
+ | SystemInitEntry
854
+ | AccountInfoEntry
855
+ | AssistantTextEntry
856
+ | AssistantThoughtEntry
857
+ | ToolCallEntry
858
+ | ToolResultEntry
859
+ | ResultEntry
860
+ | StatusEntry
861
+ | CompactBoundaryEntry
862
+ | CompactSummaryEntry
863
+ | ContextClearedEntry
864
+ | InterruptedEntry
865
+
866
+ export interface HydratedToolCallBase<TKind extends string, TInput, TResult> {
867
+ id: string
868
+ messageId?: string
869
+ hidden?: boolean
870
+ kind: "tool"
871
+ toolKind: TKind
872
+ toolName: string
873
+ toolId: string
874
+ input: TInput
875
+ result?: TResult
876
+ rawResult?: unknown
877
+ isError?: boolean
878
+ timestamp: string
879
+ }
880
+
881
+ export interface AskUserQuestionToolResult {
882
+ answers: AskUserQuestionAnswerMap
883
+ discarded?: boolean
884
+ }
885
+
886
+ export interface ExitPlanModeToolResult {
887
+ confirmed?: boolean
888
+ clearContext?: boolean
889
+ message?: string
890
+ discarded?: boolean
891
+ }
892
+
893
+ export type HydratedAskUserQuestionToolCall =
894
+ HydratedToolCallBase<"ask_user_question", AskUserQuestionToolCall["input"], AskUserQuestionToolResult>
895
+
896
+ export type HydratedExitPlanModeToolCall =
897
+ HydratedToolCallBase<"exit_plan_mode", ExitPlanModeToolCall["input"], ExitPlanModeToolResult>
898
+
899
+ export type HydratedTodoWriteToolCall =
900
+ HydratedToolCallBase<"todo_write", TodoWriteToolCall["input"], unknown>
901
+
902
+ export type HydratedSkillToolCall =
903
+ HydratedToolCallBase<"skill", SkillToolCall["input"], unknown>
904
+
905
+ export type HydratedGlobToolCall =
906
+ HydratedToolCallBase<"glob", GlobToolCall["input"], unknown>
907
+
908
+ export type HydratedGrepToolCall =
909
+ HydratedToolCallBase<"grep", GrepToolCall["input"], unknown>
910
+
911
+ export type HydratedBashToolCall =
912
+ HydratedToolCallBase<"bash", BashToolCall["input"], unknown>
913
+
914
+ export type HydratedWebSearchToolCall =
915
+ HydratedToolCallBase<"web_search", WebSearchToolCall["input"], unknown>
916
+
917
+ export interface ReadFileToolResult {
918
+ content: string
919
+ }
920
+
921
+ export type HydratedReadFileToolCall =
922
+ HydratedToolCallBase<"read_file", ReadFileToolCall["input"], ReadFileToolResult | string>
923
+
924
+ export type HydratedWriteFileToolCall =
925
+ HydratedToolCallBase<"write_file", WriteFileToolCall["input"], unknown>
926
+
927
+ export type HydratedEditFileToolCall =
928
+ HydratedToolCallBase<"edit_file", EditFileToolCall["input"], unknown>
929
+
930
+ export type HydratedSubagentTaskToolCall =
931
+ HydratedToolCallBase<"subagent_task", SubagentTaskToolCall["input"], unknown>
932
+
933
+ export type HydratedMcpGenericToolCall =
934
+ HydratedToolCallBase<"mcp_generic", McpGenericToolCall["input"], unknown>
935
+
936
+ export type HydratedUnknownToolCall =
937
+ HydratedToolCallBase<"unknown_tool", UnknownToolCall["input"], unknown>
938
+
939
+ export type HydratedToolCall =
940
+ | HydratedAskUserQuestionToolCall
941
+ | HydratedExitPlanModeToolCall
942
+ | HydratedTodoWriteToolCall
943
+ | HydratedSkillToolCall
944
+ | HydratedGlobToolCall
945
+ | HydratedGrepToolCall
946
+ | HydratedBashToolCall
947
+ | HydratedWebSearchToolCall
948
+ | HydratedReadFileToolCall
949
+ | HydratedWriteFileToolCall
950
+ | HydratedEditFileToolCall
951
+ | HydratedSubagentTaskToolCall
952
+ | HydratedMcpGenericToolCall
953
+ | HydratedUnknownToolCall
954
+
955
+ export type HydratedTranscriptMessage =
956
+ | ({ kind: "user_prompt"; content: string; attachments?: HydratedChatAttachment[]; id: string; messageId?: string; timestamp: string; hidden?: boolean })
957
+ | ({ kind: "system_init"; model: string; tools: string[]; agents: string[]; slashCommands: string[]; mcpServers: McpServerInfo[]; provider: AgentProvider; id: string; messageId?: string; timestamp: string; hidden?: boolean; debugRaw?: string })
958
+ | ({ kind: "account_info"; accountInfo: AccountInfo; id: string; messageId?: string; timestamp: string; hidden?: boolean })
959
+ | ({ kind: "assistant_text"; text: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
960
+ | ({ kind: "assistant_thought"; text: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
961
+ | ({ kind: "result"; success: boolean; cancelled?: boolean; result: string; durationMs: number; costUsd?: number; id: string; messageId?: string; timestamp: string; hidden?: boolean })
962
+ | ({ kind: "status"; status: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
963
+ | ({ kind: "compact_boundary"; id: string; messageId?: string; timestamp: string; hidden?: boolean })
964
+ | ({ kind: "compact_summary"; summary: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
965
+ | ({ kind: "context_cleared"; id: string; messageId?: string; timestamp: string; hidden?: boolean })
966
+ | ({ kind: "interrupted"; id: string; messageId?: string; timestamp: string; hidden?: boolean })
967
+ | ({ kind: "unknown"; json: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
968
+ | ({ id: string; messageId?: string; hidden?: boolean } & HydratedToolCall)
969
+
970
+ export interface ChatRuntime {
971
+ chatId: string
972
+ projectId: string
973
+ localPath: string
974
+ title: string
975
+ status: KaizenStatus
976
+ provider: AgentProvider | null
977
+ model: string | null
978
+ planMode: boolean
979
+ sessionToken: string | null
980
+ pendingTool?: ChatPendingToolSnapshot | null
981
+ inactiveProviderMessage?: string | null
982
+ }
983
+
984
+ export type ChatUsageWarning =
985
+ | "context_warning"
986
+ | "context_critical"
987
+ | "rate_warning"
988
+ | "rate_critical"
989
+ | "stale"
990
+
991
+ export interface ChatUsageSnapshot {
992
+ provider: AgentProvider
993
+ threadTokens: number | null
994
+ contextWindowTokens: number | null
995
+ contextUsedPercent: number | null
996
+ lastTurnTokens: number | null
997
+ inputTokens: number | null
998
+ outputTokens: number | null
999
+ cachedInputTokens: number | null
1000
+ reasoningOutputTokens?: number | null
1001
+ sessionLimitUsedPercent: number | null
1002
+ rateLimitResetAt: number | null
1003
+ source: "live" | "reconstructed" | "unavailable"
1004
+ updatedAt: number | null
1005
+ warnings: ChatUsageWarning[]
1006
+ }
1007
+
1008
+ export interface ChatSnapshot {
1009
+ runtime: ChatRuntime
1010
+ messages: TranscriptEntry[]
1011
+ usage?: ChatUsageSnapshot | null
1012
+ availableProviders: ProviderCatalogEntry[]
1013
+ providerSettings?: ProviderSettingsSnapshot["settings"]
1014
+ }
1015
+
1016
+ export interface KaizenSnapshot {
1017
+ sidebar: SidebarData
1018
+ chat?: ChatSnapshot | null
1019
+ }
1020
+
1021
+ export interface PendingToolSnapshot {
1022
+ toolUseId: string
1023
+ toolKind: "ask_user_question" | "exit_plan_mode"
1024
+ }
1025
+
1026
+ export interface ChatPendingToolSnapshot extends PendingToolSnapshot {
1027
+ source?: "active" | "recovered"
1028
+ }