From 0aa8eea38b5102298984e35205e045d44af67e5e Mon Sep 17 00:00:00 2001 From: 7Sageer <7sageer@djwcb.cn> Date: Wed, 29 Jul 2026 13:00:48 +0800 Subject: [PATCH] refactor(desktop): drop server_mode and fold runtime locale into track --- apps/desktop/docs/native-todos.md | 2 +- apps/desktop/src/main/connect.ts | 2 -- apps/desktop/src/main/runtime-context.ts | 25 ---------------- apps/desktop/src/main/telemetry.ts | 5 +--- apps/desktop/src/main/track.ts | 12 ++++++++ apps/desktop/src/main/tray.ts | 3 +- apps/desktop/tests/main/connect.test.ts | 13 --------- apps/desktop/tests/main/telemetry.test.ts | 35 ++++------------------- apps/desktop/tests/main/track.test.ts | 17 ++++++++++- 9 files changed, 37 insertions(+), 77 deletions(-) delete mode 100644 apps/desktop/src/main/runtime-context.ts diff --git a/apps/desktop/docs/native-todos.md b/apps/desktop/docs/native-todos.md index c7bd4088b..52461d7b0 100644 --- a/apps/desktop/docs/native-todos.md +++ b/apps/desktop/docs/native-todos.md @@ -138,7 +138,7 @@ - [x] **埋点扩充(观测指标文档批次)**(已完成,desktop 专属,2026-07) - 依据飞书《桌面端观测指标 & 埋点补充》:新增事件——主进程 `app_launched`(launch_intent,jump-list argv 判定)、`startup_timing`(main_ready / window_shown / renderer_loaded / renderer_ready 四阶段,`process.uptime()` 累计)、`renderer_crashed`(render-process-gone 的 reason + exit_code,Electron 新枚举 memory-eviction 折叠为 oom)、`startup_connect_result`(ok / failure_phase / retry_count / error_class,connect.ts 失败分类纯函数)、`startup_failure_screen_shown`;renderer `session_created`(kind new/resumed + source,见下)、`notification_shown/clicked`(kind 由通知 tag 前缀映射)、`onboarding_completed/abandoned`(wizard 记时,step1 skip=abandoned、step2 skip=completed)、`search_opened/executed`(TranscriptSearch;仅查询驱动上报,MutationObserver 流式刷新不报,结果数分桶)、`logout`、`plan_usage_card_viewed`(PlanUsageCard 加 active prop——Account 面板是 v-show,onMounted 不等于曝光;usage_bucket 取各行最严重级别)、`telemetry_consent_changed`、`renderer_error`(trace.ts 的 window error/unhandledrejection,只报 Error.name)、`connection_lost/restored`(useKimiWebClient 连接状态迁移,首次连接前的断开不报)、`workspace_added/removed`(workspace_count 为操作后总数)。 - 参数修订:`app_crashed` + process(main/gpu,gpu 来自 child-process-gone)+app_uptime_ms;`update_status_changed` version→to_version + from_version/prev_state/error_class;`window_lifecycle` + reason(quit/close_to_tray/deactivate)/visible_duration_ms(close→hide 路径=close_to_tray、minimize=deactivate、before-quit=quit);`approval_decision` + request_id(ApprovalCard 加可选 prop,父级透传 approvalId);`onboarding_step` 改为**离开步骤时**上报 + step_index/total_steps/duration_ms;`oauth_login_step` + method/duration_ms/error_class(start_failed/poll_failed/expired/cancelled);`settings_changed` + source_panel(LanguageSwitcher 与 useUpdateStatus.setAutoDownload 需调用方传参区分面板);`attachment_added` + size_bucket/count;`ui_element_toggled` + sample_rate(literal 1,未真实采样);`system_metrics` + window_count 与 session_count(后者经内嵌 core 的 `ISessionIndex.countActive` 进程内查询,external 模式省略)。 - - 公共参数注入:`telemetry.ts` 的 track impl 包装给每个事件合并 server_mode(connect.ts 解析后写 `runtime-context.ts` 新模块,避免循环依赖)/ app_uptime_ms / days_since_install(device_id 文件 birthtime)/ locale(`kimi:locale` 推送经 tray.ts 落入 runtime-context)/ theme(nativeTheme),事件自带字段优先。 + - 公共参数注入:`telemetry.ts` 的 track impl 包装给每个事件合并 app_uptime_ms / days_since_install(device_id 文件 birthtime)/ locale(`kimi:locale` 推送经 tray.ts 落入 `track.ts`)/ theme(nativeTheme),事件自带字段优先。server_mode 与 `runtime-context.ts` 已于 2026-07 移除:外部 server 仅 dev 联调、生产恒 embedded,该维度无分析价值,locale 状态并入 `track.ts`。 - **启动事件缓冲**:`track.ts` 在 impl 安装前缓冲事件(上限 200 丢最旧),`setDesktopTrackImpl` 时回放——telemetry 要等内嵌 server 就绪才接线,此前 app_launched/startup_timing 全丢;window_lifecycle 的原 replayWindowLifecycle 随之删除(缓冲覆盖)。外部 server 模式(KIMI_SERVER_URL,dev 联调用)仍无 telemetry——appender 挂在内嵌 core 上,生产无影响。注意口径:回放事件的管道时间戳与 super property `app_uptime_ms` 取**接线时刻**(非事件发生时刻,偏移为 shell-env 探测 + server 启动耗时;`startup_timing` 自带的 `duration_ms` 不受影响)。 - `session_created` 的 source 归因:`lib/session-intent.ts`(**不同步 web**,同 track.ts 先例)模块级 pending intent,入口点(runShortcutAction 按 source 映射 / jump-list 回调 / tray / 通知 onclick / 搜索框选择 / 斜杠命令)先 set,`createDraftSession`(kind:new)与 `selectSession`(kind:resumed)consume;createDraftSession 内部 selectSession 传 skipTrack 去重;**自动恢复路径全部 skipTrack**(首载 deep-link/自动选择、popstate 死链兜底、归档/会话消失兜底)——漏斗只计用户主动打开。 - 未做(需 kimi-code core / 服务端 / 产品 UI 先行):upgrade_clicked 与 limit_reached_shown(无升级/限额 UI,ManagedUsageResult 无 plan_type)、user_id / install_id / ingest_ts / is_backfilled(管道侧)、consent 热切换(维持重启生效——由此 `telemetry_consent_changed{enabled:true}` 永不送达:consent 关闭时管线不接线,事件只进内存缓冲并随退出丢弃;`enabled:false` 方向正常送达)。 diff --git a/apps/desktop/src/main/connect.ts b/apps/desktop/src/main/connect.ts index e54f3385f..988a72cdf 100644 --- a/apps/desktop/src/main/connect.ts +++ b/apps/desktop/src/main/connect.ts @@ -13,7 +13,6 @@ import { isOnboarded, isVibrancyEnabled } from './ui-state'; import { resolveConnectTarget } from './connect-target'; import { dataUrl, errorHtml } from './screens'; import { log, redactUrlForLog } from './log'; -import { setServerMode } from './runtime-context'; import { trackDesktopEvent } from './track'; import type { StartupConnectResultEvent } from './telemetry-events'; import { DESKTOP_PRODUCT_NAME } from '../shared/identity'; @@ -136,7 +135,6 @@ async function connectOnce(win: BrowserWindow): Promise { // builds always use `app://renderer`. const devBase = app.isPackaged ? undefined : rendererDevBase(process.env['KIMI_RENDERER_DEV_URL']); const target = resolveConnectTarget(process.env['KIMI_SERVER_URL'], readServerToken); - setServerMode(target.external ? 'external' : 'embedded'); if (target.external) { ({ origin, token } = target); // Redact: KIMI_SERVER_URL may carry basic-auth userinfo, which must not diff --git a/apps/desktop/src/main/runtime-context.ts b/apps/desktop/src/main/runtime-context.ts deleted file mode 100644 index 6da5f7eb4..000000000 --- a/apps/desktop/src/main/runtime-context.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Module-level runtime context, merged into every telemetry event as super -// properties (telemetry.ts). Kept dependency-free so connect.ts / tray.ts can -// write it without pulling the telemetry wiring module (agent-core-v2) into -// their load-time graph. - -export type ServerMode = 'embedded' | 'external'; - -let serverMode: ServerMode | undefined; -let locale: string | undefined; - -export function setServerMode(mode: ServerMode | undefined): void { - serverMode = mode; -} - -export function getServerMode(): ServerMode | undefined { - return serverMode; -} - -export function setRuntimeLocale(next: string | undefined): void { - locale = next; -} - -export function getRuntimeLocale(): string | undefined { - return locale; -} diff --git a/apps/desktop/src/main/telemetry.ts b/apps/desktop/src/main/telemetry.ts index 96980e5d2..125397d98 100644 --- a/apps/desktop/src/main/telemetry.ts +++ b/apps/desktop/src/main/telemetry.ts @@ -16,9 +16,8 @@ import { resolveKimiHome } from '@moonshot-ai/kimi-code-sdk'; import { DESKTOP_PRODUCT_NAME, DESKTOP_UI_MODE } from '../shared/identity'; import { log } from './log'; -import { getRuntimeLocale, getServerMode } from './runtime-context'; import { startDesktopSystemMetrics, stopDesktopSystemMetrics } from './system-metrics'; -import { setDesktopTrackImpl } from './track'; +import { getRuntimeLocale, setDesktopTrackImpl } from './track'; export interface DesktopTelemetryHandle { /** Emits `exit`, flushes the buffer, stops periodic flush. Idempotent. */ @@ -82,8 +81,6 @@ function withSuperProperties(properties: TelemetryProperties | undefined): Telem app_uptime_ms: Math.round(process.uptime() * 1000), theme: nativeTheme.shouldUseDarkColors ? 'dark' : 'light', }; - const serverMode = getServerMode(); - if (serverMode !== undefined) injected['server_mode'] = serverMode; const daysSinceInstall = readDaysSinceInstall(); if (daysSinceInstall !== undefined) injected['days_since_install'] = daysSinceInstall; const locale = getRuntimeLocale(); diff --git a/apps/desktop/src/main/track.ts b/apps/desktop/src/main/track.ts index eb3ddca0b..c86ada562 100644 --- a/apps/desktop/src/main/track.ts +++ b/apps/desktop/src/main/track.ts @@ -1,6 +1,8 @@ // Host telemetry facade. Events fired before embedded telemetry is wired (app // launch, startup phases) are buffered and replayed once wiring completes; // without an impl the buffer is the only place they exist, so it stays small. +// Also holds the runtime locale (renderer-pushed via tray.ts) that +// telemetry.ts merges into every event as the `locale` super property. import type { TelemetryProperties } from '@moonshot-ai/agent-core-v2'; @@ -44,3 +46,13 @@ export function trackDesktopEvent( } impl(event, props); } + +let locale: string | undefined; + +export function setRuntimeLocale(next: string | undefined): void { + locale = next; +} + +export function getRuntimeLocale(): string | undefined { + return locale; +} diff --git a/apps/desktop/src/main/tray.ts b/apps/desktop/src/main/tray.ts index 7a8d6c5b7..d52a7f0e5 100644 --- a/apps/desktop/src/main/tray.ts +++ b/apps/desktop/src/main/tray.ts @@ -3,8 +3,7 @@ import { join } from 'node:path'; import { app, Menu, nativeImage, Tray } from 'electron'; import type { MenuItemConstructorOptions } from 'electron'; -import { trackDesktopEvent } from './track'; -import { setRuntimeLocale } from './runtime-context'; +import { setRuntimeLocale, trackDesktopEvent } from './track'; import { setTaskbarAttention } from './taskbar'; // System tray (macOS menu-bar / Windows notification area). Desktop-only — the diff --git a/apps/desktop/tests/main/connect.test.ts b/apps/desktop/tests/main/connect.test.ts index 55f10d595..a18d78101 100644 --- a/apps/desktop/tests/main/connect.test.ts +++ b/apps/desktop/tests/main/connect.test.ts @@ -413,19 +413,6 @@ describe('startup_connect_result', () => { expect.anything(), ); }); - - it('records the server mode in the runtime context', async () => { - const { connect } = await importConnect(); - const { getServerMode } = await import('../../src/main/runtime-context'); - mocks.startDesktopServer.mockResolvedValue(fakeHandle()); - - await connect(fakeWindow() as unknown as BrowserWindow); - expect(getServerMode()).toBe('embedded'); - - process.env['KIMI_SERVER_URL'] = 'http://127.0.0.1:58627'; - await connect(fakeWindow() as unknown as BrowserWindow); - expect(getServerMode()).toBe('external'); - }); }); describe('classifyConnectFailure', () => { diff --git a/apps/desktop/tests/main/telemetry.test.ts b/apps/desktop/tests/main/telemetry.test.ts index edfce79d0..9c49c24b2 100644 --- a/apps/desktop/tests/main/telemetry.test.ts +++ b/apps/desktop/tests/main/telemetry.test.ts @@ -38,13 +38,11 @@ import { resetDaysSinceInstallCacheForTests, wireDesktopTelemetry, } from '../../src/main/telemetry'; -import { setDesktopTrackImpl, trackDesktopEvent } from '../../src/main/track'; import { - getRuntimeLocale, - getServerMode, + setDesktopTrackImpl, setRuntimeLocale, - setServerMode, -} from '../../src/main/runtime-context'; + trackDesktopEvent, +} from '../../src/main/track'; const EXISTING_DEVICE = { deviceId: 'device-1', firstLaunch: false } as const; @@ -272,7 +270,6 @@ describe('super properties injection', () => { beforeEach(async () => { createCloudAppenderMock.mockReset(); nativeThemeMock.shouldUseDarkColors = false; - setServerMode(undefined); setRuntimeLocale(undefined); resetDaysSinceInstallCacheForTests(); homeDir = await mkdtemp(join(tmpdir(), 'kimi-telemetry-')); @@ -281,7 +278,6 @@ describe('super properties injection', () => { afterEach(() => { setDesktopTrackImpl(null); - setServerMode(undefined); setRuntimeLocale(undefined); vi.unstubAllEnvs(); }); @@ -295,7 +291,6 @@ describe('super properties injection', () => { await writeFile(join(homeDir, 'device_id'), 'device-1'); wiredTrack(); const { core, telemetryService } = makeCore(undefined); - setServerMode('embedded'); setRuntimeLocale('zh'); const handle = await wireDesktopTelemetry(core as never, EXISTING_DEVICE); @@ -304,7 +299,6 @@ describe('super properties injection', () => { trackDesktopEvent('window_lifecycle', { action: 'shown' }); expect(telemetryService.track).toHaveBeenCalledWith('window_lifecycle', { action: 'shown', - server_mode: 'embedded', locale: 'zh', theme: 'light', days_since_install: 0, @@ -322,8 +316,8 @@ describe('super properties injection', () => { }); it('omits context values that are unset or unreadable', async () => { - // No device_id file in homeDir → days_since_install is dropped; server_mode - // and locale stay unset. + // No device_id file in homeDir → days_since_install is dropped; locale + // stays unset. wiredTrack(); const { core, telemetryService } = makeCore(undefined); @@ -332,7 +326,6 @@ describe('super properties injection', () => { trackDesktopEvent('global_shortcut_invoked', {}); const properties = telemetryService.track.mock.calls.at(-1)?.[1] as Record; - expect(properties['server_mode']).toBeUndefined(); expect(properties['locale']).toBeUndefined(); expect(properties['days_since_install']).toBeUndefined(); expect(properties['app_uptime_ms']).toEqual(expect.any(Number)); @@ -347,30 +340,14 @@ describe('super properties injection', () => { nativeThemeMock.shouldUseDarkColors = true; const handle = await wireDesktopTelemetry(core as never, EXISTING_DEVICE); - setServerMode('external'); setRuntimeLocale('en'); trackDesktopEvent('global_shortcut_invoked', {}); expect(telemetryService.track).toHaveBeenCalledWith( 'global_shortcut_invoked', - expect.objectContaining({ server_mode: 'external', locale: 'en', theme: 'dark' }), + expect.objectContaining({ locale: 'en', theme: 'dark' }), ); await handle!.shutdown(); }); }); - -describe('runtime-context', () => { - it('stores server_mode and locale, defaulting to undefined', () => { - expect(getServerMode()).toBeUndefined(); - expect(getRuntimeLocale()).toBeUndefined(); - setServerMode('embedded'); - setRuntimeLocale('zh'); - expect(getServerMode()).toBe('embedded'); - expect(getRuntimeLocale()).toBe('zh'); - setServerMode(undefined); - setRuntimeLocale(undefined); - expect(getServerMode()).toBeUndefined(); - expect(getRuntimeLocale()).toBeUndefined(); - }); -}); diff --git a/apps/desktop/tests/main/track.test.ts b/apps/desktop/tests/main/track.test.ts index 68c100d7b..2c8eedaf7 100644 --- a/apps/desktop/tests/main/track.test.ts +++ b/apps/desktop/tests/main/track.test.ts @@ -1,7 +1,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { asRendererTrackEvent } from '../../src/main/renderer-track-validation'; -import { setDesktopTrackImpl, trackDesktopEvent } from '../../src/main/track'; +import { + getRuntimeLocale, + setDesktopTrackImpl, + setRuntimeLocale, + trackDesktopEvent, +} from '../../src/main/track'; beforeEach(() => { setDesktopTrackImpl(null); @@ -78,6 +83,16 @@ describe('trackDesktopEvent', () => { }); }); +describe('runtime locale', () => { + it('stores the pushed locale, defaulting to undefined', () => { + expect(getRuntimeLocale()).toBeUndefined(); + setRuntimeLocale('zh'); + expect(getRuntimeLocale()).toBe('zh'); + setRuntimeLocale(undefined); + expect(getRuntimeLocale()).toBeUndefined(); + }); +}); + describe('asRendererTrackEvent', () => { it('accepts action_invoked with a valid source', () => { expect(