kanna-code 0.35.1 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/index-DEXZB-Si.js +2613 -0
- package/dist/client/assets/{index-Bvx0OmHx.css → index-DMZaICf7.css} +1 -1
- package/dist/client/index.html +3 -8
- package/dist/export-viewer/assets/index-C-FkZjXM.js +400 -0
- package/dist/export-viewer/assets/{index-CxxbFj9m.css → index-CGKmJfo4.css} +1 -1
- package/dist/export-viewer/index.html +2 -2
- package/package.json +1 -1
- package/src/server/analytics.ts +9 -4
- package/src/server/app-settings.test.ts +87 -15
- package/src/server/app-settings.ts +304 -73
- package/src/server/standalone-export.test.ts +51 -0
- package/src/server/standalone-export.ts +29 -12
- package/src/server/ws-router.test.ts +151 -0
- package/src/server/ws-router.ts +133 -13
- package/src/shared/protocol.ts +5 -1
- package/src/shared/types.ts +61 -0
- package/dist/client/assets/index-CaLDAY81.js +0 -2613
- package/dist/export-viewer/assets/index-DYoBS8BT.js +0 -400
|
@@ -5,7 +5,7 @@ import { cp as copyPath, copyFile, mkdir, readFile, readdir, stat, writeFile } f
|
|
|
5
5
|
import type {
|
|
6
6
|
StandaloneTranscriptAttachmentMode,
|
|
7
7
|
StandaloneTranscriptBundle,
|
|
8
|
-
|
|
8
|
+
StandaloneTranscriptExportCommandResult,
|
|
9
9
|
StandaloneTranscriptTheme,
|
|
10
10
|
TranscriptEntry,
|
|
11
11
|
} from "../shared/types"
|
|
@@ -76,7 +76,7 @@ export function getStandaloneViewerDistDir() {
|
|
|
76
76
|
export async function writeStandaloneTranscriptExport(
|
|
77
77
|
args: WriteStandaloneTranscriptExportArgs,
|
|
78
78
|
deps: StandaloneExportDeps = {},
|
|
79
|
-
): Promise<
|
|
79
|
+
): Promise<StandaloneTranscriptExportCommandResult> {
|
|
80
80
|
const viewerDistDir = deps.viewerDistDir ?? getStandaloneViewerDistDir()
|
|
81
81
|
const ensureDir = deps.mkdir ?? mkdir
|
|
82
82
|
const writeFileImpl = deps.writeFile ?? writeFile
|
|
@@ -124,21 +124,38 @@ export async function writeStandaloneTranscriptExport(
|
|
|
124
124
|
messages: prepared.messages,
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
const transcriptJson = `${JSON.stringify(bundle, null, 2)}\n`
|
|
127
128
|
const transcriptJsonPath = path.join(outputDir, "transcript.json")
|
|
128
|
-
await writeFileImpl(transcriptJsonPath,
|
|
129
|
+
await writeFileImpl(transcriptJsonPath, transcriptJson, "utf8")
|
|
129
130
|
const shareSlug = buildStandaloneShareSlug(args.title || args.chatId, deps.shareSlugSuffix)
|
|
130
131
|
const shareUrl = buildStandaloneShareUrl(sharePublicBaseUrl, shareSlug)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
let uploadedFileCount = 0
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
uploadedFileCount = await uploadStandaloneExportDirectory({
|
|
136
|
+
outputDir,
|
|
137
|
+
shareSlug,
|
|
138
|
+
uploadBaseUrl: shareUploadBaseUrl,
|
|
139
|
+
fetch: fetchImpl,
|
|
140
|
+
pathExists,
|
|
141
|
+
readDir,
|
|
142
|
+
readFile: readFileImpl,
|
|
143
|
+
})
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
error: error instanceof Error ? error.message : String(error),
|
|
148
|
+
outputDir,
|
|
149
|
+
transcriptJsonPath,
|
|
150
|
+
transcriptFileName: `${path.basename(outputDir)}-transcript.json`,
|
|
151
|
+
transcriptJson,
|
|
152
|
+
shareSlug,
|
|
153
|
+
shareUrl,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
140
156
|
|
|
141
157
|
return {
|
|
158
|
+
ok: true,
|
|
142
159
|
outputDir,
|
|
143
160
|
indexHtmlPath: path.join(outputDir, "index.html"),
|
|
144
161
|
transcriptJsonPath,
|
|
@@ -61,6 +61,37 @@ const DEFAULT_KEYBINDINGS_SNAPSHOT: KeybindingsSnapshot = {
|
|
|
61
61
|
|
|
62
62
|
const DEFAULT_APP_SETTINGS_SNAPSHOT: AppSettingsSnapshot = {
|
|
63
63
|
analyticsEnabled: true,
|
|
64
|
+
browserSettingsMigrated: false,
|
|
65
|
+
theme: "system",
|
|
66
|
+
chatSoundPreference: "always",
|
|
67
|
+
chatSoundId: "funk",
|
|
68
|
+
terminal: {
|
|
69
|
+
scrollbackLines: 1_000,
|
|
70
|
+
minColumnWidth: 450,
|
|
71
|
+
},
|
|
72
|
+
editor: {
|
|
73
|
+
preset: "cursor",
|
|
74
|
+
commandTemplate: "cursor {path}",
|
|
75
|
+
},
|
|
76
|
+
defaultProvider: "last_used",
|
|
77
|
+
providerDefaults: {
|
|
78
|
+
claude: {
|
|
79
|
+
model: "claude-opus-4-7",
|
|
80
|
+
modelOptions: {
|
|
81
|
+
reasoningEffort: "high",
|
|
82
|
+
contextWindow: "200k",
|
|
83
|
+
},
|
|
84
|
+
planMode: false,
|
|
85
|
+
},
|
|
86
|
+
codex: {
|
|
87
|
+
model: "gpt-5.5",
|
|
88
|
+
modelOptions: {
|
|
89
|
+
reasoningEffort: "high",
|
|
90
|
+
fastMode: false,
|
|
91
|
+
},
|
|
92
|
+
planMode: false,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
64
95
|
warning: null,
|
|
65
96
|
filePathDisplay: "~/.kanna/data/settings.json",
|
|
66
97
|
}
|
|
@@ -163,6 +194,7 @@ describe("ws-router", () => {
|
|
|
163
194
|
updateManager: null,
|
|
164
195
|
})
|
|
165
196
|
const ws = new FakeWebSocket()
|
|
197
|
+
router.handleOpen(ws as never)
|
|
166
198
|
|
|
167
199
|
await router.handleMessage(
|
|
168
200
|
ws as never,
|
|
@@ -254,6 +286,7 @@ describe("ws-router", () => {
|
|
|
254
286
|
updateManager: null,
|
|
255
287
|
})
|
|
256
288
|
const ws = new FakeWebSocket()
|
|
289
|
+
router.handleOpen(ws as never)
|
|
257
290
|
|
|
258
291
|
await router.handleMessage(
|
|
259
292
|
ws as never,
|
|
@@ -298,6 +331,124 @@ describe("ws-router", () => {
|
|
|
298
331
|
expect(writes).toEqual([{ analyticsEnabled: false }])
|
|
299
332
|
})
|
|
300
333
|
|
|
334
|
+
test("subscribes to app settings and writes patches through the router", async () => {
|
|
335
|
+
let snapshot: AppSettingsSnapshot = DEFAULT_APP_SETTINGS_SNAPSHOT
|
|
336
|
+
let listener: ((nextSnapshot: AppSettingsSnapshot) => void) | null = null
|
|
337
|
+
const router = createWsRouter({
|
|
338
|
+
store: { state: createEmptyState() } as never,
|
|
339
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
340
|
+
terminals: {
|
|
341
|
+
getSnapshot: () => null,
|
|
342
|
+
onEvent: () => () => {},
|
|
343
|
+
} as never,
|
|
344
|
+
keybindings: {
|
|
345
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
346
|
+
onChange: () => () => {},
|
|
347
|
+
} as never,
|
|
348
|
+
appSettings: {
|
|
349
|
+
getSnapshot: () => snapshot,
|
|
350
|
+
write: async (value) => {
|
|
351
|
+
snapshot = { ...snapshot, analyticsEnabled: value.analyticsEnabled }
|
|
352
|
+
return snapshot
|
|
353
|
+
},
|
|
354
|
+
writePatch: async (patch) => {
|
|
355
|
+
snapshot = {
|
|
356
|
+
...snapshot,
|
|
357
|
+
analyticsEnabled: patch.analyticsEnabled ?? snapshot.analyticsEnabled,
|
|
358
|
+
browserSettingsMigrated: patch.browserSettingsMigrated ?? snapshot.browserSettingsMigrated,
|
|
359
|
+
theme: patch.theme ?? snapshot.theme,
|
|
360
|
+
chatSoundPreference: patch.chatSoundPreference ?? snapshot.chatSoundPreference,
|
|
361
|
+
chatSoundId: patch.chatSoundId ?? snapshot.chatSoundId,
|
|
362
|
+
defaultProvider: patch.defaultProvider ?? snapshot.defaultProvider,
|
|
363
|
+
terminal: { ...snapshot.terminal, ...patch.terminal },
|
|
364
|
+
editor: { ...snapshot.editor, ...patch.editor },
|
|
365
|
+
}
|
|
366
|
+
listener?.(snapshot)
|
|
367
|
+
return snapshot
|
|
368
|
+
},
|
|
369
|
+
onChange: (nextListener) => {
|
|
370
|
+
listener = nextListener
|
|
371
|
+
return () => {
|
|
372
|
+
listener = null
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
refreshDiscovery: async () => [],
|
|
377
|
+
getDiscoveredProjects: () => [],
|
|
378
|
+
machineDisplayName: "Local Machine",
|
|
379
|
+
updateManager: null,
|
|
380
|
+
})
|
|
381
|
+
const ws = new FakeWebSocket()
|
|
382
|
+
router.handleOpen(ws as never)
|
|
383
|
+
|
|
384
|
+
await router.handleMessage(
|
|
385
|
+
ws as never,
|
|
386
|
+
JSON.stringify({
|
|
387
|
+
v: 1,
|
|
388
|
+
type: "subscribe",
|
|
389
|
+
id: "app-settings-sub-1",
|
|
390
|
+
topic: { type: "app-settings" },
|
|
391
|
+
})
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
await router.handleMessage(
|
|
395
|
+
ws as never,
|
|
396
|
+
JSON.stringify({
|
|
397
|
+
v: 1,
|
|
398
|
+
type: "command",
|
|
399
|
+
id: "settings-patch-1",
|
|
400
|
+
command: {
|
|
401
|
+
type: "settings.writeAppSettingsPatch",
|
|
402
|
+
patch: {
|
|
403
|
+
theme: "dark",
|
|
404
|
+
terminal: { scrollbackLines: 2_000 },
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
expect(ws.sent).toEqual([
|
|
411
|
+
{
|
|
412
|
+
v: PROTOCOL_VERSION,
|
|
413
|
+
type: "snapshot",
|
|
414
|
+
id: "app-settings-sub-1",
|
|
415
|
+
snapshot: {
|
|
416
|
+
type: "app-settings",
|
|
417
|
+
data: DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
v: PROTOCOL_VERSION,
|
|
422
|
+
type: "snapshot",
|
|
423
|
+
id: "app-settings-sub-1",
|
|
424
|
+
snapshot: {
|
|
425
|
+
type: "app-settings",
|
|
426
|
+
data: {
|
|
427
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
428
|
+
theme: "dark",
|
|
429
|
+
terminal: {
|
|
430
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT.terminal,
|
|
431
|
+
scrollbackLines: 2_000,
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
v: PROTOCOL_VERSION,
|
|
438
|
+
type: "ack",
|
|
439
|
+
id: "settings-patch-1",
|
|
440
|
+
result: {
|
|
441
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
442
|
+
theme: "dark",
|
|
443
|
+
terminal: {
|
|
444
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT.terminal,
|
|
445
|
+
scrollbackLines: 2_000,
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
])
|
|
450
|
+
})
|
|
451
|
+
|
|
301
452
|
test("tracks analytics preference transitions in the correct order", async () => {
|
|
302
453
|
const analyticsEvents: string[] = []
|
|
303
454
|
let analyticsEnabled = true
|
package/src/server/ws-router.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { writeStandaloneTranscriptExport } from "./standalone-export"
|
|
|
16
16
|
import { TerminalManager } from "./terminal-manager"
|
|
17
17
|
import type { UpdateManager } from "./update-manager"
|
|
18
18
|
import { deriveChatSnapshot, deriveLocalProjectsSnapshot, deriveSidebarData } from "./read-models"
|
|
19
|
-
import type { AppSettingsSnapshot, LlmProviderSnapshot, LlmProviderValidationResult } from "../shared/types"
|
|
19
|
+
import type { AppSettingsPatch, AppSettingsSnapshot, LlmProviderSnapshot, LlmProviderValidationResult } from "../shared/types"
|
|
20
20
|
|
|
21
21
|
const DEFAULT_CHAT_RECENT_LIMIT = 200
|
|
22
22
|
|
|
@@ -49,6 +49,7 @@ function countSubscriptionsByTopic(ws: ServerWebSocket<ClientState>) {
|
|
|
49
49
|
let localProjects = 0
|
|
50
50
|
let update = 0
|
|
51
51
|
let keybindings = 0
|
|
52
|
+
let appSettings = 0
|
|
52
53
|
let terminal = 0
|
|
53
54
|
|
|
54
55
|
for (const topic of ws.data.subscriptions.values()) {
|
|
@@ -71,6 +72,9 @@ function countSubscriptionsByTopic(ws: ServerWebSocket<ClientState>) {
|
|
|
71
72
|
case "keybindings":
|
|
72
73
|
keybindings += 1
|
|
73
74
|
break
|
|
75
|
+
case "app-settings":
|
|
76
|
+
appSettings += 1
|
|
77
|
+
break
|
|
74
78
|
case "terminal":
|
|
75
79
|
terminal += 1
|
|
76
80
|
break
|
|
@@ -85,6 +89,7 @@ function countSubscriptionsByTopic(ws: ServerWebSocket<ClientState>) {
|
|
|
85
89
|
localProjects,
|
|
86
90
|
update,
|
|
87
91
|
keybindings,
|
|
92
|
+
appSettings,
|
|
88
93
|
terminal,
|
|
89
94
|
}
|
|
90
95
|
}
|
|
@@ -101,7 +106,7 @@ interface CreateWsRouterArgs {
|
|
|
101
106
|
agent: AgentCoordinator
|
|
102
107
|
terminals: TerminalManager
|
|
103
108
|
keybindings: KeybindingsManager
|
|
104
|
-
appSettings?: Pick<AppSettingsManager, "getSnapshot" | "write">
|
|
109
|
+
appSettings?: Pick<AppSettingsManager, "getSnapshot" | "write"> & Partial<Pick<AppSettingsManager, "writePatch" | "onChange">>
|
|
105
110
|
analytics?: AnalyticsReporter
|
|
106
111
|
llmProvider?: {
|
|
107
112
|
read: () => Promise<LlmProviderSnapshot>
|
|
@@ -119,6 +124,7 @@ interface SnapshotBroadcastFilter {
|
|
|
119
124
|
includeLocalProjects?: boolean
|
|
120
125
|
includeUpdate?: boolean
|
|
121
126
|
includeKeybindings?: boolean
|
|
127
|
+
includeAppSettings?: boolean
|
|
122
128
|
chatIds?: Set<string>
|
|
123
129
|
projectIds?: Set<string>
|
|
124
130
|
terminalIds?: Set<string>
|
|
@@ -226,17 +232,88 @@ export function createWsRouter({
|
|
|
226
232
|
},
|
|
227
233
|
}),
|
|
228
234
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
let fallbackAppSettingsSnapshot: AppSettingsSnapshot = {
|
|
236
|
+
analyticsEnabled: true,
|
|
237
|
+
browserSettingsMigrated: false,
|
|
238
|
+
theme: "system",
|
|
239
|
+
chatSoundPreference: "always",
|
|
240
|
+
chatSoundId: "funk",
|
|
241
|
+
terminal: {
|
|
242
|
+
scrollbackLines: 1_000,
|
|
243
|
+
minColumnWidth: 450,
|
|
244
|
+
},
|
|
245
|
+
editor: {
|
|
246
|
+
preset: "cursor",
|
|
247
|
+
commandTemplate: "cursor {path}",
|
|
248
|
+
},
|
|
249
|
+
defaultProvider: "last_used",
|
|
250
|
+
providerDefaults: {
|
|
251
|
+
claude: {
|
|
252
|
+
model: "claude-opus-4-7",
|
|
253
|
+
modelOptions: {
|
|
254
|
+
reasoningEffort: "high",
|
|
255
|
+
contextWindow: "200k",
|
|
256
|
+
},
|
|
257
|
+
planMode: false,
|
|
258
|
+
},
|
|
259
|
+
codex: {
|
|
260
|
+
model: "gpt-5.5",
|
|
261
|
+
modelOptions: {
|
|
262
|
+
reasoningEffort: "high",
|
|
263
|
+
fastMode: false,
|
|
264
|
+
},
|
|
265
|
+
planMode: false,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
warning: null,
|
|
269
|
+
filePathDisplay: "~/.kanna/data/settings.json",
|
|
270
|
+
}
|
|
271
|
+
const mergeAppSettingsPatch = (snapshot: AppSettingsSnapshot, patch: AppSettingsPatch): AppSettingsSnapshot => ({
|
|
272
|
+
...snapshot,
|
|
273
|
+
...patch,
|
|
274
|
+
terminal: {
|
|
275
|
+
...snapshot.terminal,
|
|
276
|
+
...patch.terminal,
|
|
277
|
+
},
|
|
278
|
+
editor: {
|
|
279
|
+
...snapshot.editor,
|
|
280
|
+
...patch.editor,
|
|
281
|
+
},
|
|
282
|
+
providerDefaults: {
|
|
283
|
+
claude: {
|
|
284
|
+
...snapshot.providerDefaults.claude,
|
|
285
|
+
...patch.providerDefaults?.claude,
|
|
286
|
+
modelOptions: {
|
|
287
|
+
...snapshot.providerDefaults.claude.modelOptions,
|
|
288
|
+
...patch.providerDefaults?.claude?.modelOptions,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
codex: {
|
|
292
|
+
...snapshot.providerDefaults.codex,
|
|
293
|
+
...patch.providerDefaults?.codex,
|
|
294
|
+
modelOptions: {
|
|
295
|
+
...snapshot.providerDefaults.codex.modelOptions,
|
|
296
|
+
...patch.providerDefaults?.codex?.modelOptions,
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
})
|
|
301
|
+
const resolvedAppSettings = {
|
|
302
|
+
getSnapshot: () => appSettings?.getSnapshot() ?? fallbackAppSettingsSnapshot,
|
|
303
|
+
write: async (value: { analyticsEnabled: boolean }) => {
|
|
304
|
+
if (appSettings) return await appSettings.write(value)
|
|
305
|
+
fallbackAppSettingsSnapshot = { ...fallbackAppSettingsSnapshot, analyticsEnabled: value.analyticsEnabled }
|
|
306
|
+
return fallbackAppSettingsSnapshot
|
|
307
|
+
},
|
|
308
|
+
writePatch: async (patch: AppSettingsPatch) => {
|
|
309
|
+
if (appSettings?.writePatch) return await appSettings.writePatch(patch)
|
|
310
|
+
if (appSettings && patch.analyticsEnabled !== undefined && Object.keys(patch).length === 1) {
|
|
311
|
+
return await appSettings.write({ analyticsEnabled: patch.analyticsEnabled })
|
|
312
|
+
}
|
|
313
|
+
fallbackAppSettingsSnapshot = mergeAppSettingsPatch(appSettings?.getSnapshot() ?? fallbackAppSettingsSnapshot, patch)
|
|
314
|
+
return fallbackAppSettingsSnapshot
|
|
315
|
+
},
|
|
316
|
+
onChange: (listener: (snapshot: AppSettingsSnapshot) => void) => appSettings?.onChange?.(listener) ?? (() => {}),
|
|
240
317
|
}
|
|
241
318
|
const resolvedAnalytics = analytics ?? NoopAnalyticsReporter
|
|
242
319
|
|
|
@@ -307,6 +384,9 @@ export function createWsRouter({
|
|
|
307
384
|
if (topic.type === "keybindings") {
|
|
308
385
|
return Boolean(filter.includeKeybindings)
|
|
309
386
|
}
|
|
387
|
+
if (topic.type === "app-settings") {
|
|
388
|
+
return Boolean(filter.includeAppSettings)
|
|
389
|
+
}
|
|
310
390
|
if (topic.type === "chat") {
|
|
311
391
|
return filter.chatIds?.has(topic.chatId) ?? false
|
|
312
392
|
}
|
|
@@ -398,6 +478,18 @@ export function createWsRouter({
|
|
|
398
478
|
}
|
|
399
479
|
}
|
|
400
480
|
|
|
481
|
+
if (topic.type === "app-settings") {
|
|
482
|
+
return {
|
|
483
|
+
v: PROTOCOL_VERSION,
|
|
484
|
+
type: "snapshot",
|
|
485
|
+
id,
|
|
486
|
+
snapshot: {
|
|
487
|
+
type: "app-settings",
|
|
488
|
+
data: resolvedAppSettings.getSnapshot(),
|
|
489
|
+
},
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
401
493
|
if (topic.type === "update") {
|
|
402
494
|
return {
|
|
403
495
|
v: PROTOCOL_VERSION,
|
|
@@ -683,6 +775,21 @@ export function createWsRouter({
|
|
|
683
775
|
}
|
|
684
776
|
})
|
|
685
777
|
|
|
778
|
+
const disposeAppSettingsEvents = resolvedAppSettings.onChange(() => {
|
|
779
|
+
for (const ws of sockets) {
|
|
780
|
+
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
781
|
+
for (const [id, topic] of ws.data.subscriptions.entries()) {
|
|
782
|
+
if (topic.type !== "app-settings") continue
|
|
783
|
+
const envelope = createEnvelope(id, topic)
|
|
784
|
+
if (envelope.type !== "snapshot") continue
|
|
785
|
+
const signature = JSON.stringify(envelope.snapshot)
|
|
786
|
+
if (snapshotSignatures.get(id) === signature) continue
|
|
787
|
+
snapshotSignatures.set(id, signature)
|
|
788
|
+
send(ws, envelope)
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
})
|
|
792
|
+
|
|
686
793
|
const disposeUpdateEvents = updateManager?.onChange(() => {
|
|
687
794
|
for (const ws of sockets) {
|
|
688
795
|
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
@@ -770,6 +877,18 @@ export function createWsRouter({
|
|
|
770
877
|
}
|
|
771
878
|
return
|
|
772
879
|
}
|
|
880
|
+
case "settings.writeAppSettingsPatch": {
|
|
881
|
+
const previousAnalyticsEnabled = resolvedAppSettings.getSnapshot().analyticsEnabled
|
|
882
|
+
const snapshot = await resolvedAppSettings.writePatch(command.patch)
|
|
883
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
|
|
884
|
+
if (command.patch.analyticsEnabled !== undefined && previousAnalyticsEnabled && !snapshot.analyticsEnabled) {
|
|
885
|
+
resolvedAnalytics.track("analytics_disabled")
|
|
886
|
+
}
|
|
887
|
+
if (command.patch.analyticsEnabled !== undefined && !previousAnalyticsEnabled && snapshot.analyticsEnabled) {
|
|
888
|
+
resolvedAnalytics.track("analytics_enabled")
|
|
889
|
+
}
|
|
890
|
+
return
|
|
891
|
+
}
|
|
773
892
|
case "settings.readLlmProvider": {
|
|
774
893
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: await resolvedLlmProvider.read() })
|
|
775
894
|
return
|
|
@@ -1246,6 +1365,7 @@ export function createWsRouter({
|
|
|
1246
1365
|
agent.setBackgroundErrorReporter?.(null)
|
|
1247
1366
|
disposeTerminalEvents()
|
|
1248
1367
|
disposeKeybindingEvents()
|
|
1368
|
+
disposeAppSettingsEvents()
|
|
1249
1369
|
disposeUpdateEvents()
|
|
1250
1370
|
},
|
|
1251
1371
|
}
|
package/src/shared/protocol.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AppSettingsSnapshot,
|
|
3
|
+
AppSettingsPatch,
|
|
3
4
|
AgentProvider,
|
|
4
5
|
ChatAttachment,
|
|
5
6
|
ChatDiffSnapshot,
|
|
@@ -14,9 +15,10 @@ import type {
|
|
|
14
15
|
StandaloneTranscriptAttachmentMode,
|
|
15
16
|
StandaloneTranscriptExportResult,
|
|
16
17
|
UpdateSnapshot,
|
|
18
|
+
EditorPreset,
|
|
17
19
|
} from "./types"
|
|
18
20
|
|
|
19
|
-
export type EditorPreset
|
|
21
|
+
export type { EditorPreset }
|
|
20
22
|
|
|
21
23
|
export interface EditorOpenSettings {
|
|
22
24
|
preset: EditorPreset
|
|
@@ -28,6 +30,7 @@ export type SubscriptionTopic =
|
|
|
28
30
|
| { type: "local-projects" }
|
|
29
31
|
| { type: "update" }
|
|
30
32
|
| { type: "keybindings" }
|
|
33
|
+
| { type: "app-settings" }
|
|
31
34
|
| { type: "chat"; chatId: string; recentLimit?: number }
|
|
32
35
|
| { type: "project-git"; projectId: string }
|
|
33
36
|
| { type: "terminal"; terminalId: string }
|
|
@@ -63,6 +66,7 @@ export type ClientCommand =
|
|
|
63
66
|
| { type: "settings.writeKeybindings"; bindings: KeybindingsSnapshot["bindings"] }
|
|
64
67
|
| { type: "settings.readAppSettings" }
|
|
65
68
|
| { type: "settings.writeAppSettings"; analyticsEnabled: boolean }
|
|
69
|
+
| { type: "settings.writeAppSettingsPatch"; patch: AppSettingsPatch }
|
|
66
70
|
| { type: "settings.readLlmProvider" }
|
|
67
71
|
| {
|
|
68
72
|
type: "settings.writeLlmProvider"
|
package/src/shared/types.ts
CHANGED
|
@@ -3,6 +3,11 @@ export const PROTOCOL_VERSION = 1 as const
|
|
|
3
3
|
|
|
4
4
|
export type AgentProvider = "claude" | "codex"
|
|
5
5
|
export type LlmProviderKind = "openai" | "openrouter" | "custom"
|
|
6
|
+
export type AppThemePreference = "light" | "dark" | "system"
|
|
7
|
+
export type ChatSoundPreference = "never" | "unfocused" | "always"
|
|
8
|
+
export type ChatSoundId = "blow" | "bottle" | "frog" | "funk" | "glass" | "ping" | "pop" | "purr" | "tink"
|
|
9
|
+
export type DefaultProviderPreference = "last_used" | AgentProvider
|
|
10
|
+
export type EditorPreset = "cursor" | "vscode" | "xcode" | "windsurf" | "custom"
|
|
6
11
|
export const DEFAULT_OPENAI_SDK_MODEL = "gpt-5.4-mini"
|
|
7
12
|
export const DEFAULT_OPENROUTER_SDK_MODEL = "moonshotai/kimi-k2.5:nitro"
|
|
8
13
|
|
|
@@ -34,6 +39,7 @@ export interface StandaloneTranscriptBundle {
|
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
export interface StandaloneTranscriptExportResult {
|
|
42
|
+
ok: true
|
|
37
43
|
outputDir: string
|
|
38
44
|
indexHtmlPath: string
|
|
39
45
|
transcriptJsonPath: string
|
|
@@ -45,6 +51,21 @@ export interface StandaloneTranscriptExportResult {
|
|
|
45
51
|
uploadedFileCount: number
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
export interface StandaloneTranscriptExportFailureResult {
|
|
55
|
+
ok: false
|
|
56
|
+
error: string
|
|
57
|
+
outputDir: string
|
|
58
|
+
transcriptJsonPath: string
|
|
59
|
+
transcriptFileName: string
|
|
60
|
+
transcriptJson: string
|
|
61
|
+
shareSlug: string
|
|
62
|
+
shareUrl: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type StandaloneTranscriptExportCommandResult =
|
|
66
|
+
| StandaloneTranscriptExportResult
|
|
67
|
+
| StandaloneTranscriptExportFailureResult
|
|
68
|
+
|
|
48
69
|
export interface QueuedChatMessage {
|
|
49
70
|
id: string
|
|
50
71
|
content: string
|
|
@@ -116,6 +137,17 @@ export interface ProviderModelOptionsByProvider {
|
|
|
116
137
|
codex: CodexModelOptions
|
|
117
138
|
}
|
|
118
139
|
|
|
140
|
+
export interface ProviderPreference<TModelOptions> {
|
|
141
|
+
model: string
|
|
142
|
+
modelOptions: TModelOptions
|
|
143
|
+
planMode: boolean
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type ChatProviderPreferences = {
|
|
147
|
+
claude: ProviderPreference<ClaudeModelOptions>
|
|
148
|
+
codex: ProviderPreference<CodexModelOptions>
|
|
149
|
+
}
|
|
150
|
+
|
|
119
151
|
export type ModelOptions = Partial<{
|
|
120
152
|
[K in AgentProvider]: Partial<ProviderModelOptionsByProvider[K]>
|
|
121
153
|
}>
|
|
@@ -338,10 +370,39 @@ export interface LocalProjectsSnapshot {
|
|
|
338
370
|
|
|
339
371
|
export interface AppSettingsSnapshot {
|
|
340
372
|
analyticsEnabled: boolean
|
|
373
|
+
browserSettingsMigrated: boolean
|
|
374
|
+
theme: AppThemePreference
|
|
375
|
+
chatSoundPreference: ChatSoundPreference
|
|
376
|
+
chatSoundId: ChatSoundId
|
|
377
|
+
terminal: {
|
|
378
|
+
scrollbackLines: number
|
|
379
|
+
minColumnWidth: number
|
|
380
|
+
}
|
|
381
|
+
editor: {
|
|
382
|
+
preset: EditorPreset
|
|
383
|
+
commandTemplate: string
|
|
384
|
+
}
|
|
385
|
+
defaultProvider: DefaultProviderPreference
|
|
386
|
+
providerDefaults: ChatProviderPreferences
|
|
341
387
|
warning: string | null
|
|
342
388
|
filePathDisplay: string
|
|
343
389
|
}
|
|
344
390
|
|
|
391
|
+
export interface AppSettingsPatch {
|
|
392
|
+
analyticsEnabled?: boolean
|
|
393
|
+
browserSettingsMigrated?: boolean
|
|
394
|
+
theme?: AppThemePreference
|
|
395
|
+
chatSoundPreference?: ChatSoundPreference
|
|
396
|
+
chatSoundId?: ChatSoundId
|
|
397
|
+
terminal?: Partial<AppSettingsSnapshot["terminal"]>
|
|
398
|
+
editor?: Partial<AppSettingsSnapshot["editor"]>
|
|
399
|
+
defaultProvider?: DefaultProviderPreference
|
|
400
|
+
providerDefaults?: {
|
|
401
|
+
claude?: Partial<ProviderPreference<ClaudeModelOptions>>
|
|
402
|
+
codex?: Partial<ProviderPreference<CodexModelOptions>>
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
345
406
|
export interface LlmProviderFile {
|
|
346
407
|
provider?: LlmProviderKind
|
|
347
408
|
apiKey?: string
|