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,315 @@
1
+ import { randomUUID } from "node:crypto"
2
+ import type { NormalizedToolCall, TranscriptEntry } from "../shared/types"
3
+ import { normalizeToolCall } from "../shared/tools"
4
+
5
+ export type JsonRpcId = string | number
6
+
7
+ export interface JsonRpcRequest {
8
+ jsonrpc: "2.0"
9
+ id: JsonRpcId
10
+ method: string
11
+ params?: unknown
12
+ }
13
+
14
+ export interface JsonRpcNotification {
15
+ jsonrpc: "2.0"
16
+ method: string
17
+ params?: unknown
18
+ }
19
+
20
+ export interface JsonRpcResponse {
21
+ jsonrpc: "2.0"
22
+ id: JsonRpcId
23
+ result?: unknown
24
+ error?: {
25
+ code: number
26
+ message: string
27
+ data?: unknown
28
+ }
29
+ }
30
+
31
+ export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse
32
+
33
+ export interface PendingRequest<TResult> {
34
+ method: string
35
+ resolve: (value: TResult) => void
36
+ reject: (error: Error) => void
37
+ }
38
+
39
+ type AcpToolCallUpdate = {
40
+ toolCallId: string
41
+ title?: string | null
42
+ kind?: string | null
43
+ locations?: Array<{ path?: string | null }> | null
44
+ content?: Array<Record<string, unknown>> | null
45
+ }
46
+
47
+ export function asRecord(value: unknown): Record<string, unknown> | null {
48
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null
49
+ return value as Record<string, unknown>
50
+ }
51
+
52
+ export function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
53
+ entry: T,
54
+ createdAt = Date.now()
55
+ ): TranscriptEntry {
56
+ return {
57
+ _id: randomUUID(),
58
+ createdAt,
59
+ ...entry,
60
+ } as TranscriptEntry
61
+ }
62
+
63
+ export function errorMessage(error: unknown) {
64
+ if (error instanceof Error) return error.message
65
+ return String(error)
66
+ }
67
+
68
+ export function stringifyJson(value: unknown) {
69
+ if (typeof value === "string") return value
70
+ if (value == null) return ""
71
+ try {
72
+ return JSON.stringify(value, null, 2)
73
+ } catch {
74
+ return String(value)
75
+ }
76
+ }
77
+
78
+ export function parseJsonLine(line: string): JsonRpcMessage | null {
79
+ try {
80
+ const parsed = JSON.parse(line) as JsonRpcMessage
81
+ if (parsed && typeof parsed === "object" && parsed.jsonrpc === "2.0") {
82
+ return parsed
83
+ }
84
+ return null
85
+ } catch {
86
+ return null
87
+ }
88
+ }
89
+
90
+ export function isJsonRpcResponse(message: JsonRpcMessage): message is JsonRpcResponse {
91
+ return "id" in message && ("result" in message || "error" in message)
92
+ }
93
+
94
+ export function inferToolNameFromUpdate(toolCall: {
95
+ title?: string | null
96
+ kind?: string | null
97
+ locations?: Array<{ path?: string | null }> | null
98
+ content?: Array<Record<string, unknown>> | null
99
+ }) {
100
+ const title = (toolCall.title ?? "").toLowerCase()
101
+ if (title.startsWith("asking user:")) return "AskUserQuestion"
102
+ if (title.startsWith("requesting plan approval for:")) return "ExitPlanMode"
103
+ if (title === "create plan" || title.startsWith("create plan:")) return "ExitPlanMode"
104
+ if (title === "plan" || title.startsWith("plan:")) return "ExitPlanMode"
105
+ if (title === "update todos" || title.startsWith("update todos:")) return "TodoWrite"
106
+
107
+ switch (toolCall.kind) {
108
+ case "read":
109
+ return "Read"
110
+ case "edit": {
111
+ const firstDiff = toolCall.content?.find((entry) => entry.type === "diff")
112
+ const oldText = typeof firstDiff?.oldText === "string" ? firstDiff.oldText : null
113
+ const newText = typeof firstDiff?.newText === "string" ? firstDiff.newText : null
114
+ if (!oldText && newText) return "Write"
115
+ return "Edit"
116
+ }
117
+ case "delete":
118
+ return "Edit"
119
+ case "move":
120
+ return "Edit"
121
+ case "search":
122
+ return title.includes("web") ? "WebSearch" : "Grep"
123
+ case "execute":
124
+ return "Bash"
125
+ case "fetch":
126
+ return title.includes("web") ? "WebFetch" : "Read"
127
+ case "switch_mode":
128
+ return "ExitPlanMode"
129
+ default:
130
+ if (toolCall.locations?.[0]?.path) return "Read"
131
+ return "Tool"
132
+ }
133
+ }
134
+
135
+ export function inferToolInput(toolName: string, toolCall: {
136
+ title?: string | null
137
+ locations?: Array<{ path?: string | null }> | null
138
+ content?: Array<Record<string, unknown>> | null
139
+ }) {
140
+ const firstLocationPath = typeof toolCall.locations?.[0]?.path === "string"
141
+ ? toolCall.locations[0].path
142
+ : undefined
143
+
144
+ if (toolName === "AskUserQuestion") {
145
+ const questionText = (toolCall.title ?? "Agent requested user input").replace(/^Asking user:\s*/i, "").trim()
146
+ return {
147
+ questions: [{ question: questionText || "Agent requested user input." }],
148
+ }
149
+ }
150
+
151
+ if (toolName === "ExitPlanMode") {
152
+ const planPath = (toolCall.title ?? "")
153
+ .replace(/^Requesting plan approval for:\s*/i, "")
154
+ .replace(/^Create plan:?\s*/i, "")
155
+ .replace(/^Plan:?\s*/i, "")
156
+ .trim()
157
+ return {
158
+ summary: planPath || undefined,
159
+ }
160
+ }
161
+
162
+ if (toolName === "TodoWrite") {
163
+ return {
164
+ todos: [],
165
+ }
166
+ }
167
+
168
+ if (toolName === "Read") {
169
+ return { file_path: firstLocationPath ?? "" }
170
+ }
171
+
172
+ if (toolName === "Write" || toolName === "Edit") {
173
+ const firstDiff = toolCall.content?.find((entry) => entry.type === "diff")
174
+ const diffPath = typeof firstDiff?.path === "string" ? firstDiff.path : firstLocationPath ?? ""
175
+ return {
176
+ file_path: diffPath,
177
+ old_string: typeof firstDiff?.oldText === "string" ? firstDiff.oldText : "",
178
+ new_string: typeof firstDiff?.newText === "string" ? firstDiff.newText : "",
179
+ content: typeof firstDiff?.newText === "string" ? firstDiff.newText : "",
180
+ }
181
+ }
182
+
183
+ if (toolName === "Bash") {
184
+ return {
185
+ command: typeof toolCall.title === "string" ? toolCall.title : "",
186
+ }
187
+ }
188
+
189
+ if (toolName === "WebSearch") {
190
+ return {
191
+ query: typeof toolCall.title === "string" ? toolCall.title : "",
192
+ }
193
+ }
194
+
195
+ if (toolName === "WebFetch") {
196
+ return {
197
+ file_path: firstLocationPath ?? "",
198
+ }
199
+ }
200
+
201
+ return {
202
+ payload: {
203
+ title: toolCall.title ?? undefined,
204
+ locations: toolCall.locations ?? [],
205
+ content: toolCall.content ?? [],
206
+ },
207
+ }
208
+ }
209
+
210
+ export function normalizeAcpToolCall(toolCall: AcpToolCallUpdate): NormalizedToolCall {
211
+ const toolName = inferToolNameFromUpdate(toolCall)
212
+ const input = inferToolInput(toolName, toolCall)
213
+ return normalizeToolCall({
214
+ toolName,
215
+ toolId: toolCall.toolCallId,
216
+ input,
217
+ })
218
+ }
219
+
220
+ export function populateExitPlanFromAssistantText(
221
+ tool: NormalizedToolCall,
222
+ assistantText: string | null | undefined
223
+ ): NormalizedToolCall {
224
+ if (tool.toolKind !== "exit_plan_mode" || tool.input.plan) {
225
+ return tool
226
+ }
227
+
228
+ const plan = assistantText?.trim()
229
+ if (!plan) return tool
230
+
231
+ return normalizeToolCall({
232
+ toolName: "ExitPlanMode",
233
+ toolId: tool.toolId,
234
+ input: {
235
+ plan,
236
+ summary: tool.input.summary,
237
+ },
238
+ })
239
+ }
240
+
241
+ export function stringifyToolCallContent(content: Array<Record<string, unknown>> | null | undefined) {
242
+ if (!content?.length) return ""
243
+ return content.map((entry) => {
244
+ if (entry.type === "content") {
245
+ const inner = asRecord(entry.content)
246
+ if (typeof inner?.text === "string") return inner.text
247
+ }
248
+ if (entry.type === "diff") {
249
+ const path = typeof entry.path === "string" ? entry.path : "unknown"
250
+ return `Updated ${path}`
251
+ }
252
+ return stringifyJson(entry)
253
+ }).filter(Boolean).join("\n\n")
254
+ }
255
+
256
+ export function createResultEntry(result: { stopReason?: unknown }): TranscriptEntry {
257
+ const stopReason = typeof result.stopReason === "string" ? result.stopReason : "end_turn"
258
+ if (stopReason === "cancelled") {
259
+ return timestamped({
260
+ kind: "result",
261
+ subtype: "cancelled",
262
+ isError: false,
263
+ durationMs: 0,
264
+ result: "",
265
+ })
266
+ }
267
+
268
+ return timestamped({
269
+ kind: "result",
270
+ subtype: "success",
271
+ isError: false,
272
+ durationMs: 0,
273
+ result: "",
274
+ })
275
+ }
276
+
277
+ export class AsyncQueue<T> implements AsyncIterable<T> {
278
+ private values: T[] = []
279
+ private resolvers: Array<(value: IteratorResult<T>) => void> = []
280
+ private done = false
281
+
282
+ push(value: T) {
283
+ if (this.done) return
284
+ const resolver = this.resolvers.shift()
285
+ if (resolver) {
286
+ resolver({ value, done: false })
287
+ return
288
+ }
289
+ this.values.push(value)
290
+ }
291
+
292
+ finish() {
293
+ if (this.done) return
294
+ this.done = true
295
+ while (this.resolvers.length > 0) {
296
+ this.resolvers.shift()?.({ value: undefined as T, done: true })
297
+ }
298
+ }
299
+
300
+ [Symbol.asyncIterator](): AsyncIterator<T> {
301
+ return {
302
+ next: () => {
303
+ if (this.values.length > 0) {
304
+ return Promise.resolve({ value: this.values.shift() as T, done: false })
305
+ }
306
+ if (this.done) {
307
+ return Promise.resolve({ value: undefined as T, done: true })
308
+ }
309
+ return new Promise<IteratorResult<T>>((resolve) => {
310
+ this.resolvers.push(resolve)
311
+ })
312
+ },
313
+ }
314
+ }
315
+ }