Skip to content

Electron 迁移任务清单

For agentic workers: 按任务顺序执行。每个任务完成后先跑该任务列出的核对命令,再进入下一任务。不要跳过打包壳 smoke,也不要把浏览器/Vite smoke 当作 Electron 壳验收。

目标: 将桌面端 host runtime 从 Tauri 2 迁移到 Electron,同时保持 React 层、server/sidecar、自动更新、系统交互、通知、构建发布和 Computer Use 能力可用。 架构: 先抽象 desktopHost contract,再并行实现 Tauri 与 Electron,最后切换构建发布链路。保留本地 Bun server + REST/WebSocket 边界,不把会话、聊天、workspace、team 业务协议改成 Electron IPC。 技术栈: React 18、Vite、Zustand、Bun、Electron、electron-builder/electron-updater、现有 sidecar、现有质量门禁。


当前同步状态

执行记录(2026-06-01):

  • 已按本地 main 重新同步当前迁移工作区:当前分支 feat/electron-migration-main-sync-2392 已快进到本地 main 的最新提交后再恢复 Electron 迁移改动。
  • 已在当前工作区内解决同步冲突,重点文件包括 desktop/package.jsondesktop/scripts/build-macos-arm64.shdesktop/src-tauri/resources/preview-agent.jsdesktop/src/components/workspace/WorkspaceFileOpenWith.test.tsxdesktop/src/lib/previewEvents.tsdesktop/src/lib/previewEvents.test.ts
  • 冲突解决后已重建 preview agent,并运行 targeted tests 与 Electron package dir 构建;后续迭代均基于同步后的本地 main
  • packaged Electron renderer 使用 file://.../app.asar/dist/index.html,真实模型 smoke 暴露出 H5 token mode 下 WebSocket 会因 Origin: file:// 被拒绝;已在 server CORS/H5 access policy 中把 file:// 纳入本地桌面可信来源,并补回归测试。

Phase 0:迁移前基线

1. 冻结当前 Tauri 行为基线

涉及文件:

  • 只读:desktop/src/**
  • 只读:desktop/src-tauri/**
  • 只读:scripts/quality-gate/**

步骤:

  • [x] 运行 git status --short,确认当前工作区是否有无关改动。
  • [x] 运行 bun run check:policy
  • [x] 运行 bun run check:desktop
  • [x] 运行 bun run check:server
  • [x] 运行 bun run check:coverage
  • [ ] 有 live provider 时运行 bun run quality:gate --mode baseline --allow-live --provider-model <provider:model[:label]>
  • [x] 记录 quality report 路径和失败项;失败时先修现有基线,不进入迁移。

执行记录(2026-05-31):

  • bun run check:policy 通过。
  • bun run check:desktop 通过;首次缺少 desktop 依赖,已在 desktop/ 执行 bun install 后复跑通过。
  • bun run check:server 通过。
  • bun run check:coverage 初次运行未通过;归因后在 adapters/ 执行 bun install 修复 grammy 缺包环境问题,并补足 changed-line 覆盖率。
  • 后续 bun run check:coverage 复跑曾因本机 WeChat 占用随机端口 24023 导致 src/server/__tests__/h5-access-auth.test.ts 超时;已把该测试的随机端口选择改为先探测可绑定端口。
  • 最新 bun run check:coverage 通过;report 为 artifacts/coverage/2026-05-31T13-15-40-522Z/coverage-report.md,summary 为 passed=5 failed=0,changed-lines coverage 为 90.67% (204/225)

通过标准: 现有 Tauri 主干的 desktop/server/coverage/baseline 状态可复现。 阻断条件: 当前主干已有红灯且未归因。


Phase 1:Host Adapter Contract

2. 新增 host adapter 类型与 browser fallback

创建:

  • desktop/src/lib/desktopHost/types.ts
  • desktop/src/lib/desktopHost/browserHost.ts
  • desktop/src/lib/desktopHost/index.ts
  • desktop/src/lib/desktopHost/contract.test.ts

步骤:

  • [x] 定义 DesktopHost 接口,至少覆盖 runtime/server URLupdatesnotificationsdialogsshellwindowterminalpreviewappMode
  • [x] 在 browserHost 中保留 H5/browser fallback:没有桌面能力时返回明确错误或 no-op。
  • [x] 在 contract test 中固定每个方法的 payload 和错误语义。
  • [x] 运行 cd desktop && bun run test -- src/lib/desktopHost/contract.test.ts --run

执行记录(2026-05-31):

  • 新增 desktop/src/lib/desktopHost/types.tsbrowserHost.tsindex.tscontract.test.ts
  • 先运行 contract test 得到预期红灯(缺少 browserHost 模块),补实现后 desktopHost/contract.test.ts 6 tests 通过。

通过标准: host adapter 类型能表达当前所有 Tauri command/plugin 能力。 阻断条件: 仍需要 renderer 直接 import Tauri API 才能表达某个能力。

3. 实现 Tauri host adapter

创建:

  • desktop/src/lib/desktopHost/tauriHost.ts

修改:

  • desktop/src/lib/desktopRuntime.ts
  • desktop/src/api/terminal.ts
  • desktop/src/stores/updateStore.ts
  • desktop/src/lib/desktopNotifications.ts
  • desktop/src/lib/previewBridge.ts
  • desktop/src/lib/previewEvents.ts
  • desktop/src/lib/appZoom.ts
  • desktop/src/stores/adapterStore.ts
  • desktop/src/stores/settingsStore.ts
  • dialog/shell 调用组件:DirectoryPicker.tsxSidebar.tsxSettings.tsxTerminalSettings.tsxComputerUseSettings.tsxWorkspaceFileOpenWith.tsx、聊天链接打开组件

步骤:

  • [x] 把 @tauri-apps/api/core.invoke 包进 tauriHost.commands.invoke 或具体方法。
  • [x] 把 Tauri listen 包进 desktopHost.events
  • [x] 把 dialog/shell/window/update/notification 调用全部迁到 tauriHost
  • [x] 运行 rg "@tauri-apps|__TAURI_INTERNALS__|window\\.__TAURI__|from '@tauri" desktop/src
  • [x] 确认生产代码只在 desktop/src/lib/desktopHost/tauriHost.ts 命中。
  • [x] 运行 bun run check:desktop

通过标准: renderer 业务文件不再知道 Tauri API。 阻断条件: 任一 store/component 仍直接动态 import @tauri-apps/*

当前进度(2026-05-31):

  • 新增 desktop/src/lib/desktopHost/tauriHost.ts,先迁入 get_server_url 运行时调用。
  • desktop/src/lib/desktopRuntime.ts 已改为通过 desktopHost 选择 Electron preload host、Tauri host 或 browser fallback;新增回归测试确保 window.desktopHost 优先于 H5/browser fallback。
  • 已迁移 desktop/src/api/terminal.tsdesktop/src/lib/previewBridge.tsdesktop/src/lib/previewEvents.tsdesktop/src/lib/appZoom.tsdesktop/src/lib/desktopNotifications.tsdesktop/src/stores/updateStore.tsdesktop/src/stores/adapterStore.tsdesktop/src/stores/settingsStore.ts 的 runtime/terminal/preview/zoom/notification/update/adapter restart/app mode 调用到 desktopHost
  • desktopHost 现在覆盖 app.getVersioncommands.invokeevents.listenwebview.onDragDropEventdialogs.open/saveshell.opennotifications.permission/request/send/onActionwindow.minimize/toggleMaximize/close/startDragging/focus/requestAttention/isMaximized/onResized/onNativeMenuNavigateupdates.check/prepare/cancel/relaunch 等 Tauri 能力。
  • 已运行 focused tests:desktopRuntime.test.tsdesktopHost/contract.test.tsterminal.test.tspreviewBridge.test.tspreviewEvents.test.tsappZoom.test.tsdesktopNotifications.test.tsupdateStore.test.tsadapterStore.test.tssettingsStore.test.tsAppShell.test.tsxWindowControls.test.tsxComputerUseSettings.test.tsx 全部通过。
  • 继续迁移了 dialog/shell/window/menu 组件层、file drop webview、official login/open-with 等打开路径:DirectoryPickercomposerAttachmentsTerminalSettingsComputerUseSettingsSettingsSidebarTabBarWindowControlsAppShellWorkspaceFileOpenWithAssistantMessageAssistantOutputTargetCardCurrentTurnChangeCard
  • 2026-05-31 扫描结果:业务 renderer 生产代码不再直接 import @tauri-apps/*;剩余 Tauri 命中为 desktopHost 自身、runtime detection 类型声明和测试 mock。生产路径排除 desktopHost 后扫描为空:rg -n "@tauri-apps|__TAURI_INTERNALS__|window\\.__TAURI__|from '@tauri" desktop/src --glob '!**/*.test.ts' --glob '!**/*.test.tsx' --glob '!**/lib/desktopHost/**' --glob '!src-tauri/**'
  • bun run check:desktop 通过:desktop tsc --noEmit、Vitest 全量 146 test files / 1127 tests passed,production build 成功;现有 React act(...) warnings 仍为非阻断输出。

Phase 2:Electron 最小壳

4. 新增 Electron main/preload 骨架

创建:

  • desktop/electron/main.ts
  • desktop/electron/preload.ts
  • desktop/electron/ipc/channels.ts
  • desktop/electron/ipc/capabilities.ts
  • desktop/electron/tsconfig.json

修改:

  • desktop/package.json
  • package.json

步骤:

  • [x] 新增 electron:develectron:buildcheck:electron 脚本。
  • [x] BrowserWindow 使用 preload,保持 contextIsolation,不启用 renderer Node。
  • [x] preload 暴露 window.desktopHost,不暴露通用 ipcRenderer
  • [x] capabilities.ts 枚举允许的 IPC channel 和 payload validator。
  • [x] 运行 bun run check:electron

执行记录(2026-05-31):

  • 新增 desktop/electron/main.tspreload.tsipc/channels.tsipc/capabilities.tsipc/capabilities.test.tselectron/tsconfig.jsonscripts/electron-dev.ts
  • BrowserWindow 配置为 preload + contextIsolation: true + nodeIntegration: false + sandbox: true;preload 只通过 contextBridge.exposeInMainWorld('desktopHost', ...) 暴露 typed host contract。
  • check:electron 通过:Electron IPC validator 测试 3/3 通过,electron-dist/main.cjselectron-dist/preload.cjs 构建成功。
  • electron:build 通过:renderer desktop/dist 和 Electron CJS entrypoints 均构建成功。
  • 短时 Electron 启动 smoke 已尝试;当前机器首次运行 electron 会触发 Electron binary 下载,下载未在本轮完成,因此真实窗口启动验证保留到下一轮 runtime/server bridge smoke。

通过标准: Electron 壳能启动并加载 Vite/dev 或 desktop/dist阻断条件: renderer 需要 Node API 或 Electron API 才能工作。

5. 实现 Electron runtime/server bridge

创建:

  • desktop/electron/services/sidecarManager.ts
  • desktop/electron/services/serverRuntime.ts

步骤:

  • [x] 复刻 start_server_sidecar:保留动态端口、host、config、startup log、超时错误。
  • [x] 复刻 adapter sidecar 启动和 restart_adapters_sidecar
  • [x] 保留 CLAUDE_CONFIG_DIRCC_HAHA_APP_PORTABLE_DIRADAPTER_SERVER_URL 注入。
  • [x] 提供 get_server_url 等价 IPC。
  • [x] 确认 renderer 仍通过 desktop/src/api/client.ts 和 WebSocket manager 访问会话/聊天/workspace/team,不新增这些业务的 IPC handler。
  • [x] 运行 bun run check:electron
  • [x] 打开 Electron dev app,确认首页能连上 local server。

执行记录(2026-05-31):

  • 新增 desktop/electron/services/sidecarManager.tsserverRuntime.ts;Electron main 在 ready 阶段启动 server sidecar,runtime.getServerUrl() 通过 IPC 返回动态 loopback URL。
  • sidecarManager 复用现有 desktop/src-tauri/binaries/claude-sidecar-<triple>,server 参数保持 server --app-root <path> --host 0.0.0.0 --port <dynamic>,adapter 参数保持 adapters --app-root <path> --feishu/--telegram/--wechat/--dingtalk
  • 环境变量保留 CLAUDE_CONFIG_DIRXDG_CACHE_HOMECLAUDE_H5_AUTO_PUBLIC_URLCLAUDE_H5_DIST_DIR;adapter 注入 ADAPTER_SERVER_URL=ws://<dynamic>
  • headless smoke 通过:bun run build:sidecars 后用 ElectronServerRuntime 启动 server,GET /health 返回 200,随后 stopAll() 停止 server/adapters 子进程。
  • 2026-06-01 Electron dev 壳已由 Computer Use 多轮复测:renderer 加载 localhost:1420/,main process 自动启动 local server/sidecar,首页、设置页和会话页均可通过 server API/WebSocket 工作。

通过标准: Electron app 不依赖外部手动启动 server。 阻断条件: 需要用户先运行 SERVER_PORT=... bun run src/server/index.ts 才能使用桌面端,或迁移方案开始绕过 local server 直接重写 session/chat IPC。


Phase 3:系统能力迁移

6. 迁移文件选择、保存、外链打开

创建/修改:

  • desktop/electron/services/dialogs.ts
  • desktop/electron/services/shell.ts
  • desktop/src/lib/desktopHost/electronHost.ts

步骤:

  • [x] openFileopenDirectorysaveFile 映射到 Electron dialog。
  • [x] openExternal 限制 URL scheme,openPath 限制为明确来自文件选择或 workspace 的路径。
  • [x] 更新 adapter contract tests。
  • [x] 运行 bun run check:desktop && bun run check:electron
  • [x] 手动 smoke:选择工作目录、选择附件、选择 Python 路径、打开外链。

执行记录(2026-05-31):

  • 新增 desktop/electron/services/dialogs.tsshell.ts;dialog options 映射到 Electron showOpenDialog/showSaveDialog,shell URL 只允许 http: / https: / mailto:
  • DesktopHost.shell 拆分为 open(url)openPath(path);workspace/open-with 本地文件路径改走 openPath,普通网页/OAuth/通知设置等外链继续走 open
  • 新增 desktop/src/lib/desktopHost/electronHost.ts,preload 改为复用该 typed host factory;IPC payload 在 preload 进入 ipcRenderer.invoke 前先校验。
  • Focused tests 通过:electron/services/shell.test.tsdialogs.test.tselectronHost.test.tsdesktopHost/contract.test.tsWorkspaceFileOpenWith.test.tsx
  • bun run check:electron 通过:16 tests passed,Electron main/preload 构建成功。
  • bun run check:desktop 通过,现有 React/Tauri 主干未被 openPath contract 破坏。
  • 2026-06-01 Electron dev 壳 Computer Use 复测附件选择:点击 composer 添加文件或图片 打开 macOS 原生 打开 面板,选择 /Users/nanmi/cc-haha-cua-attachment-smoke.txt 后回到应用,composer 显示 cc-haha-cua-attachment-smoke.txt 附件 chip。
  • 2026-06-01 Electron dev 壳 Computer Use 复测 Python 路径选择:在 Computer Use 设置中点击 选择 打开 macOS 原生 选择 Python 解释器 面板,选择 /Users/nanmi/cc-haha-cua-python3-smoke 后应用解析并保存为 /opt/homebrew/Cellar/python@3.14/3.14.5/Frameworks/Python.framework/Versions/3.14/bin/python3.14;随后恢复自动检测,/api/computer-use/authorized-apps 返回 pythonPath: null
  • 2026-06-01 Electron dev 壳 Computer Use 复测外链:点击关于页 GitHub 项目卡片后,系统浏览器 Google Chrome 被拉到前台,地址栏为 github.com/NanmiCoder/cc-haha;Electron renderer 仍停留在 localhost:1420/,未在应用内导航外链。

通过标准: 所有原 plugin-dialogplugin-shell 用户路径可用。 阻断条件: 任一路径绕过 preload 或接受未校验目标。

7. 迁移窗口、托盘、菜单和单实例

创建/修改:

  • desktop/electron/services/windows.ts
  • desktop/electron/services/tray.ts
  • desktop/electron/services/menu.ts
  • desktop/electron/services/singleInstance.ts

步骤:

  • [x] 实现 minimize/toggleMaximize/close/startDragging。
  • [x] 实现关闭隐藏到托盘、托盘 show/quit。
  • [x] 实现 macOS About/Settings 菜单并发送 native-menu-navigate 等价事件。
  • [x] 实现窗口位置/尺寸持久化和 monitor 可见性保护。
  • [x] 实现 app.requestSingleInstanceLock(),第二实例显示主窗口。
  • [x] 运行 bun run check:electron
  • [ ] 打包 app smoke:关闭窗口、托盘恢复、菜单打开设置页、重启后窗口仍可见。

执行记录(2026-05-31):

  • 新增 desktop/electron/services/windows.tstray.tsmenu.tssingleInstance.ts
  • Electron main 现在启动前申请 single-instance lock;重复启动会 show/focus 主窗口。
  • 主窗口关闭默认隐藏到托盘,托盘菜单提供 Show Claude Code HahaQuit Claude Code Haha;显式 quit 才停止 sidecar 并退出。
  • 窗口位置/尺寸/maximized 状态写入 window-state.json,优先使用 CLAUDE_CONFIG_DIR,恢复前会验证尺寸和 display/workArea 可见性。
  • 原生菜单发送 desktop:window:native-menu-navigate,保持 renderer 现有 settings/about 路由入口。
  • Electron 先不声明自定义 Windows chrome 能力:windowControls=false,renderer 不渲染自定义窗口按钮,也不会把空白 tab/sidebar 区当成可拖拽窗口区域;后续若启用 frameless window,需要同时实现真实拖拽。
  • bun run check:electron 通过:26 tests passed,Electron main/preload 构建成功。
  • 2026-06-01 Electron dev 壳 Computer Use 复测窗口关闭/恢复:点击 macOS 关闭按钮后 Electron 进程仍在 list_apps 中保持 running,主窗口消失;通过 macOS app activation 路径重新激活同一 Electron app 后,Computer Use 再次看到 Claude Code Companion 主窗口和 localhost:1420/ renderer。Computer Use 当前不能稳定枚举 Dock,因此本轮验证的是与 Dock 点击等价的 app activation/show 主窗口路径。
  • 2026-06-01 packaged app Computer Use 复测菜单与窗口生命周期:在 desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app 隔离实例中,通过 macOS app menu 点击 Settings... 后 renderer 打开设置页;点击关闭按钮后 Computer Use 返回 noWindowsAvailable,System Events 显示同一 PID count=0 且进程仍存活;随后用 app activation 恢复,Computer Use 再次读取到 Claude Code Companion 设置页窗口。继续通过 app menu Quit 退出后,隔离配置写入 window-state.json;用同一 CLAUDE_CONFIG_DIR--user-data-dir 重启,Computer Use 成功读取新 PID 的 packaged file://.../app.asar/dist/index.html 主窗口,System Events 显示 count=1 names=Claude Code Companion。该轮覆盖 packaged 菜单打开设置、关闭隐藏、activation 恢复和重启后窗口仍可见;真实 tray 图标点击仍需后续实机/可枚举 tray 环境复验。

通过标准: macOS/Windows/Linux 主窗口生命周期等价。 阻断条件: 关闭行为会误退出或窗口状态可能恢复到屏幕外。

8. 迁移通知

创建/修改:

  • desktop/electron/services/notifications.ts
  • desktop/src/lib/desktopNotifications.ts
  • desktop/src/lib/desktopHost/contract.test.ts

步骤:

  • [x] 实现权限状态、请求权限、发送通知。
  • [x] 实现通知点击回跳并传递 target。
  • [x] Windows 保留打开系统通知设置入口。
  • [x] macOS 若 Electron Notification 无法满足权限/点击语义,保留 native bridge 或补原生模块。
  • [x] 运行 bun run check:electron
  • [x] 运行 bun run check:desktop
  • [ ] 打包 app smoke:发送通知、点击通知、跳回目标 session。

执行记录(2026-05-31):

  • 新增 desktop/electron/services/notifications.ts,使用 Electron main-process Notification 发送系统通知并监听 click
  • 通知点击通过 desktop:notification:action 发送 { id, extra, target, action: 'click' },renderer 继续复用现有 installDesktopNotificationClickListener target 解析和导航。
  • Electron commandInvoke 兼容现有 renderer 的 legacy 通知入口:macos_notification_permission_statemacos_request_notification_permissionmacos_send_notificationplugin:notification|is_permission_grantedplugin:notification|request_permission
  • Windows 通知设置入口通过 allowlisted ms-settings:notifications 打开;macOS 系统通知设置 URL 仍限制在 allowlist。
  • 参考 Electron 官方 Notification 文档:main process 使用 new Notification(...).show(),点击通过 notification.on('click', ...) 处理;Electron 没有独立的桌面通知 request-permission API,因此 Electron host 将 Notification.isSupported() 映射为 host permission state。
  • bun run check:electron 通过:32 tests passed,Electron main/preload 构建成功。
  • bun run check:desktop 通过:141 test files / 1103 tests passed,production build 成功;现有 React act(...) warnings 仍为非阻断输出。

执行记录(2026-06-01):

  • 新增 desktop/electron/services/notificationSmoke.ts,提供显式环境变量触发的 Electron 通知 smoke hook:设置 CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_SESSION_ID=<session-id> 后,main process 会延迟发送带 { type: 'session', sessionId } target 的真实 Electron Notification。默认不启用,不影响正常启动和生产行为。
  • 新增 desktop/electron/services/notificationSmoke.test.ts,锁定默认不发送、target payload 透传和 delay clamp;cd desktop && bun test electron/services/notificationSmoke.test.ts electron/services/notifications.test.ts electron/services/windows.test.ts 通过:16 tests passed。
  • Computer Use 真实 OS 点击复测:带 smoke env 启动 Electron dev 壳并关闭主窗口后,Electron 仍 running;但本机 Computer Use 对 /System/Library/CoreServices/NotificationCenter.appSystemUIServer 均返回 timeoutReached,无法稳定枚举或点击 macOS 通知横幅。重新 get_app_state(Electron) 会触发 app activation 并恢复窗口,因此不能作为“点击通知回跳”的证据。该项保持未完成,需在可操作 Notification Center/横幅的 macOS 会话或签名 packaged app 上复验。
  • 2026-06-01 通知点击二次复测:用 CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_DELAY_MS=30000 启动 Electron dev 壳,Computer Use 确认主窗口可见后点击 macOS close button,Electron 无主窗口且进程保持 running;等待通知触发后,Computer Use 对 SystemUIServer 与 Notification Center 仍 timeoutReached,前台 Chrome 截图/AX 树也没有出现可点击通知横幅。真实 OS 通知点击回跳仍未验收通过。
  • 2026-06-01 通知 smoke 可审计化:新增 CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG=<path>,仅在显式 smoke env 下把 scheduled/sent/action/send_failed 写入 JSONL,方便真实 macOS/Windows runner 点击通知后确认 Electron main 是否收到 OS click。cd desktop && bun test electron/services/notificationSmoke.test.ts electron/services/notifications.test.ts electron/services/windows.test.ts 通过 17 tests,cd desktop && bun run check:electron 通过 85 tests。
  • 2026-06-01 packaged 通知 smoke 复测:重新运行 CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 后,用隔离配置启动 packaged app,并设置 CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG=/tmp/.../notification-smoke.jsonl。Computer Use 确认 packaged file://.../app.asar/dist/index.html 主窗口可见;JSONL 记录 scheduledsent:true,证明 Electron main 已向系统发出真实通知。但 Computer Use 仍无法读取 SystemUIServerNotificationCenter.app(均 timeoutReached),日志也未出现 action,所以“点击通知回到目标 session”仍未完成。
  • 2026-06-01 通知点击当前态再复测:用 canonical packaged app 启动隔离通知 smoke,设置 CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_SESSION_ID=notification-click-smoke-sessionCC_HAHA_ELECTRON_NOTIFICATION_SMOKE_DELAY_MS=8000 和 JSONL log;Computer Use 关闭主窗口后 app 留在后台,JSONL 记录 scheduledsent:true。Computer Use 对 /System/Library/CoreServices/UserNotificationCenter.app/ 返回安全策略拒绝,对 NotificationCenter.apptimeoutReached,所以仍无法执行真实 OS 通知点击。为后续实机复验,通知 smoke 日志已扩展记录 lifecycle: close|failed,可区分通知发出后被系统关闭、发送失败和用户点击;cd desktop && bun test electron/services/notificationSmoke.test.ts electron/services/notifications.test.ts 通过 10 tests,cd desktop && bun run check:electron 通过 90 tests。

通过标准: 通知提示、权限、点击回跳都能在真实打包 app 里证明。 阻断条件: 只在 unit test 中 mock 成功,未走系统通知。

9. 迁移自动更新

创建/修改:

  • desktop/electron/services/updater.ts
  • desktop/src/stores/updateStore.ts
  • desktop/package.json
  • .github/workflows/release-desktop.yml

步骤:

  • [x] 选定 electron-builder + electron-updater,不要用 Electron 内置 updater 覆盖 Linux。
  • [x] 保留 check -> download -> progress -> install -> relaunch 状态机。
  • [x] install 前调用 sidecar manager 停止 server/adapters,失败后恢复可重试状态。
  • [x] 建立 mock update feed 测试。
  • [x] macOS 签名/zip metadata、Windows NSIS metadata、Linux AppImage/deb update 策略写入 Electron build metadata。
  • [x] 运行 bun run check:electron
  • [x] 运行 bun run check:desktop
  • [x] release workflow 切换到 Electron builder,并在打包前验证签名/notarization/update metadata secrets 是否配置齐全。

通过标准: 自动更新全链路有 unit、mock feed、打包产物证据。 阻断条件: 只能检查到新版本,不能安全安装并 relaunch。

执行记录(2026-05-31):

  • 新增运行时依赖 electron-updater@6.8.3 和打包依赖 electron-builder@26.8.1
  • 新增 desktop/electron/services/updater.tsautoDownload=falsecheckForUpdates() 只返回 metadata,downloadUpdate() 将 electron-updater download-progress 转成现有 DesktopUpdateDownloadEventrelaunch() 阶段调用 quitAndInstall(false, true)
  • 扩展 Electron IPC:desktop:update:downloaddesktop:update:installdesktop:update:download-event
  • desktop/src/lib/desktopHost/electronHost.ts 将 Electron update metadata 包装成现有 DesktopUpdate 对象,renderer updateStore 不需要改状态机。
  • desktop/package.json 已加入 Electron builder metadata:appId=com.claude-code-haha.desktop、GitHub publish 到 NanmiCoder/cc-haha、macOS dmg/zip、Windows nsis、Linux AppImage/deb;packaged runtime 使用 asar=true,并通过 asarUnpack 保留 node-pty 与 sidecar binaries。
  • bun run check:electron 通过:36 tests passed,Electron main/preload 构建成功。
  • bun run check:desktop 通过:desktop tsc --noEmit、Vitest 全量 146 test files / 1127 tests passed,production build 成功。

执行记录(2026-06-01):

  • 完整 macOS Electron Builder release packaging 暴露出两个自动更新发布问题:平台级 publish: ["github"] 会在 worktree/CI 中触发 git remote 自动探测并导致 update info 生成崩溃;默认含空格 artifact name 会让 latest-mac.yml 指向 Claude-Code-Haha-...,但实际上传文件是 Claude Code Haha-...
  • 已修复 desktop/package.json:保留顶层显式 GitHub publish 元数据,并移除 mac/win/linux 平台级 publish 覆盖;新增统一无空格 artifactName,避免 update metadata 与上传 asset 文件名漂移。
  • 已增强 scripts/quality-gate/package-smoke/index.ts:当存在 latest-mac.yml / latest.yml / latest-linux.yml 时,会解析 url / path 并确认引用的 artifact 文件实际存在;当发布 archive 存在但 update metadata 缺失时会失败。
  • 已新增回归测试:scripts/pr/release-workflow.test.ts 防止 Electron Builder 再依赖 git remote autodetection;scripts/quality-gate/package-smoke/index.test.ts 会捕获 latest-mac.yml 指向缺失 asset 的情况。
  • 2026-06-01 packaged app updater 缺失 channel metadata 修复:本地打包 app 启动后 electron-updater 可访问 GitHub release,但当前 release 缺少 latest-mac.yml 时会抛 ERR_UPDATER_CHANNEL_FILE_NOT_FOUND。该错误现在仅在 message 指向 latest*.yml 时降级为“无更新”,继续保留其它 updater 错误抛出;cd desktop && bun test electron/services/updater.test.ts 通过 7 tests,cd desktop && bun run check:electron 通过 84 tests。
  • 2026-06-01 packaged app 当前态复验:重新运行 cd desktop && CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 后,从 desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app 以隔离 CLAUDE_CONFIG_DIR / --user-data-dir 启动;Computer Use 确认主窗口加载 file://.../app.asar/dist/index.html,sidecar 自动启动,未出现 Safe Storage 钥匙串弹窗,日志未再出现 ERR_UPDATER_CHANNEL_FILE_NOT_FOUND IPC handler 崩错。纯 --dir 包缺少 app-update.yml 的提示仍按无更新处理,不代表 signed release update feed 已验收。
  • 当前本机 .dmg 构建仍受 macOS DiskImages/hdiutil 状态阻塞:hdiutil: create failed - 操作超时,且当前 worktree 下残留的临时读写 disk image 无法正常 detach;这属于本机磁盘镜像服务阻塞,不应被 package-smoke 或浏览器 smoke 替代为发布通过。

10. 迁移 PTY 终端

创建/修改:

  • desktop/electron/services/terminal.ts
  • desktop/src/api/terminal.ts

步骤:

  • [x] 明确 PTY 实现:保留 Rust sidecar command 或引入 Node PTY 方案。
  • [x] 实现 spawn/write/resize/kill 和 output/exit event。
  • [x] 保留 terminal shell 配置读写语义。
  • [x] 运行 bun run check:desktop && bun run check:electron
  • [x] 打包 app smoke:打开终端、输入命令;resize/kill 需随完整 packaged smoke 继续复验。

通过标准: 终端交互和事件流等价。 阻断条件: Windows shell 或自定义 shell path 行为不明确。

执行记录(2026-05-31):

  • 已选择 Node PTY 方案:新增运行时依赖 node-pty@1.1.0,Electron main process 负责 pty.spawn(),renderer 只通过 preload IPC 发送 spawn/write/resize/kill。Context7 /microsoft/node-pty 文档确认主进程集成方式为 spawn + onData/onExit + write/resize/kill
  • 新增 desktop/electron/services/terminal.ts,保留 Tauri 终端语义:CLAUDE_CONFIG_DIR/terminal-config.json 优先,其次 Electron userData/terminal-config.json;继续读取 ~/.claude/settings.jsondesktopTerminal Windows shell 配置;保留 Windows bash_pathCOMSPEC、POSIX SHELL/bin/zsh//bin/bash 默认 shell 选择。
  • terminal_spawn 等价实现最小尺寸限制:cols >= 20rows >= 8;默认 cwd 仍按 cwd -> CLAUDE_CONFIG_DIR -> HOME/USERPROFILE -> process.cwd();环境变量合并 login shell env,并强制 UTF-8 locale,补充 TERM=xterm-256colorCOLORTERM=truecolor
  • desktop/electron/main.ts 已注册 desktop:terminal:* IPC:spawn/write/resize/kill/get-bash-path/set-bash-path,并通过 desktop:terminal:outputdesktop:terminal:exit 事件回传现有 renderer payload。
  • desktop/electron/ipc/capabilities.ts 收紧 terminalSpawn payload 校验,避免 renderer 传入非数字尺寸或非字符串 cwd/shell。
  • 新增 desktop/electron/services/terminal.test.ts,覆盖配置路径、bash path 持久化、Windows shell 解析、UTF-8 env、spawn event 转发、write/resize/kill、exit 后 session 清理。
  • packaged macOS node-pty 直接从 .app/Contents/Resources/app.asar.unpacked 执行会在 spawn-helper 上触发 posix_spawnp failed;已改为打包时先执行 prepare:node-pty 修正 helper executable bit,并在 packaged macOS runtime 将 node-pty 复制到 Electron userData/native/node-pty-<platform>-<arch>-<version> 后加载,避免嵌套 app bundle 资源执行限制。
  • bun run check:electron 通过:58 tests passed,Electron main/preload/preview-preload 构建成功。
  • bun run check:desktop 通过:desktop tsc --noEmit、Vitest 全量 146 test files / 1127 tests passed,production build 成功。
  • CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 通过并产出 unpacked macOS .app;Computer Use packaged smoke 通过:启动 Claude Code Haha.app,点击“打开终端”后显示 运行中 /bin/zsh,在终端输入 printf electron-terminal-ok 并看到输出 electron-terminal-ok。终端 resize/close kill 仍需在完整 packaged smoke 中补齐。

11. 迁移预览面板

创建/修改:

  • desktop/electron/services/preview.ts
  • desktop/src/lib/previewBridge.ts
  • desktop/src/lib/previewEvents.ts

步骤:

  • [x] 使用 WebContentsView,不要用已弃用的 BrowserView
  • [x] 保留 http/https URL 白名单。
  • [x] 保留 preview agent 注入和 JSON 消息校验。
  • [x] 实现 open/navigate/setBounds/setVisible/close/eval/message。
  • [x] 运行 bun run check:desktop && bun run check:electron
  • [x] 打包 app smoke:打开 workbench/browser panel,导航 URL,完成 preview/screenshot 回填 smoke。

通过标准: 预览面板功能等价且安全边界保留。 阻断条件: 允许 file:javascript: URL 进入 preview。

执行记录(2026-05-31):

  • Context7 /electron/electron 文档确认当前 API 使用 WebContentsView + contentView.addChildView() + setBounds() + webContents.loadURL(),不再新增 BrowserView
  • 新增 desktop/electron/services/preview.ts,使用 WebContentsView 实现 open/navigate/setBounds/setVisible/close/eval,继续拒绝空 URL、file:javascript: 等非 http/https URL。
  • 新增 desktop/electron/preview-preload.ts,只暴露 window.__DESKTOP_PREVIEW_POST__(raw);外部页面无法拿到 Node/Electron API,只能把 JSON 字符串发回 main process。
  • desktop/src/preview-agent/index.ts 改为调用通用 __DESKTOP_PREVIEW_POST__;Tauri 端在 desktop/src-tauri/src/webview_panel.rs 创建 child webview 时先注入该函数,再注入 preview agent;Electron 端由 preview preload 注入同名函数。
  • Electron main process 增加内部 channel desktop:preview:message-from-view,只接受当前 preview webContents 发出的 raw JSON;校验 JSON 后通过 desktop:preview:event 转发给 React。
  • desktop/package.jsonbuild:electron 增加 preview-preload.cjs 构建,Electron builder files 增加 src-tauri/resources/preview-agent.js,保证 packaged app 能读取注入脚本。
  • 新增 desktop/electron/services/preview.test.ts,覆盖 URL 白名单、bounds 规范化、child view 复用、load 后注入 preview agent、JSON event 转发、close 清理。
  • bun run build:preview-agent 已重建 desktop/src-tauri/resources/preview-agent.js
  • bun run check:electron 通过:49 tests passed,Electron main/preload/preview-preload 构建成功。
  • bun run check:desktop 通过:desktop tsc --noEmit、Vitest 全量 146 test files / 1127 tests passed,production build 成功。
  • 2026-06-01 Computer Use packaged smoke 覆盖 preview/workbench:临时 packaged app 中打开 browser panel,导航 https://example.com/ 成功,并完成 screenshot 回填 composer;完整 preview event 回归继续由 desktop/electron/services/preview.test.ts 覆盖。

12. 迁移 app mode、portable config 和 zoom

创建/修改:

  • desktop/electron/services/appMode.ts
  • desktop/electron/services/zoom.ts
  • desktop/src/stores/settingsStore.ts
  • desktop/src/lib/appZoom.ts

步骤:

  • [x] 保留 get_app_modeset_app_modedetect_portable_dir 返回形状。
  • [x] 保留同时写系统默认配置目录和 portable 目录的语义。
  • [x] zoom 继续 clamp 到 0.5..2.0
  • [x] 运行 bun run check:persistence-upgrade
  • [x] 运行 bun run check:desktop && bun run check:electron

通过标准: 老配置可升级,unknown fields 保留。 阻断条件: 写入 ~/.claude/settings.json 或受保护配置路径。

执行记录(2026-05-31):

  • 新增 desktop/electron/services/appMode.ts,复刻 Tauri app-mode.json 语义:启动时按 external CLAUDE_CONFIG_DIR -> default portable app-mode.json -> system app-mode.json -> default portable data auto-detect 判定是否设置 CLAUDE_CONFIG_DIR
  • Electron app.whenReady() 后、server sidecar 启动前调用 applyStartupPortableMode(app),保证 sidecar、window state、terminal config 都能看到 portable env;同时设置 CC_HAHA_APP_PORTABLE_DIR=1WEBVIEW2_USER_DATA_FOLDER=<portable>/EBWebView
  • desktop:app-mode:get 返回现有 renderer 形状:modeportableDirdefaultPortableDiractiveConfigDirconfigDirSourceconfigDirSource 区分 system、外部环境变量 environment、应用切换产生的 portable
  • desktop:app-mode:set 在当前 active config、目标 portable dir、系统默认 userData 三处写入 app-mode.json,不写 ~/.claude/settings.json,也不移动/删除已有数据。
  • desktop:app-mode:restart 现在 relaunch() 后立即 quit(),与设置页“切换后重启”的语义一致;prepareRestart 继续先停止 server/adapters。
  • 新增 desktop/electron/services/zoom.ts,native zoom 统一 clamp 到 0.5..2.0,避免 Electron IPC 绕过 renderer 侧限制。
  • 新增 appMode.test.tszoom.test.ts,覆盖 portable 数据探测、启动模式解析、env 设置、返回形状、三处 app-mode 写入、zoom clamp。
  • bun run check:electron 通过:56 tests passed,Electron main/preload/preview-preload 构建成功。
  • bun run check:persistence-upgrade 通过:server persistence 6 tests passed,desktop localStorage migration 8 tests passed。
  • bun run check:desktop 通过:desktop tsc --noEmit、Vitest 全量 146 test files / 1127 tests passed,production build 成功。

Phase 4:Build、CI、Release

13. 替换本地构建脚本

修改:

  • desktop/package.json
  • package.json
  • desktop/scripts/build-macos-arm64.sh
  • desktop/scripts/build-windows-x64.ps1

步骤:

  • [x] 保留 desktop build 作为 renderer build。
  • [x] 保留 build:sidecars
  • [x] 新增 Electron package 脚本,输出到 desktop/build-artifacts/<platform>
  • [x] check:native 替换为 Electron main/preload/package config check,或新增 check:electron 并让 quality:gate 使用它。
  • [x] 运行 bun run check:desktop && bun run check:electron

通过标准: 本地 macOS/Windows 构建命令产出可启动 app。 阻断条件: sidecar 没有被打进产物或产物启动后仍读开发路径。

执行记录(2026-05-31):

  • desktop/package.json 保留 build 为 renderer build,新增/调整 electron:buildelectron:packageelectron:package:dir,并将 build:electron 扩展为 main/preload/preview-preload 三个 bundle。
  • Electron builder 配置包含 dist/**electron-dist/**src-tauri/binaries/**src-tauri/resources/preview-agent.jsnode_modules/node-pty/**electron-updater 已打进 main bundle,node-pty 保持外部原生模块并通过 asarUnpack 解包。
  • check:native 已从 Tauri cargo check 改为 build:sidecars + check:electronquality:gate native lane 描述同步为 Electron host/package checks。
  • desktop/scripts/build-macos-arm64.sh 改为 Electron Builder:构建 sidecar、renderer、Electron bundles,执行 electron-builder --mac dmg zip --arm64 --publish never,输出复制到 desktop/build-artifacts/macos-arm64
  • desktop/scripts/build-windows-x64.ps1 改为 Electron Builder:导入 MSVC 环境,构建 sidecar、renderer、Electron bundles,执行 electron-builder --win nsis --x64 --publish never,输出复制到 desktop/build-artifacts/windows-x64
  • desktop/scripts/build-macos-arm64.sh 支持 MAC_TARGETS 覆盖 Electron Builder macOS target,默认仍为 dmg zip;本机 DiskImages 异常时可用 MAC_TARGETS=zip 单独验证 zip/update metadata 链路。脚本会对当前 worktree 的 stale Electron Builder 临时 DMG 挂载 fail fast,成功复制 canonical artifacts 后默认运行 package-smoke。
  • desktop/scripts/build-windows-x64.ps1 会把 installer、update metadata、blockmap 和 win-unpacked 复制到 canonical desktop/build-artifacts/windows-x64;成功复制后默认运行 bun run test:package-smoke --platform windows --package-kind release --artifacts-dir desktop/build-artifacts/windows-x64,可用 SKIP_PACKAGE_SMOKE=1 跳过静态验包。
  • desktop/scripts/build-linux.sh 支持 LINUX_ARCH=x64|arm64LINUX_TARGETS 覆盖,默认构建 AppImage/deb x64;脚本会复制 .AppImage.deblatest-linux.yml、blockmap 和 linux-unpacked 到 canonical desktop/build-artifacts/linux-<arch>,并默认运行 bun run test:package-smoke --platform linux --package-kind release --artifacts-dir desktop/build-artifacts/linux-<arch>
  • node-pty 原生模块验证:bunx electron ./tmp-electron-node-pty-smoke.cjs 在 Electron 42.3.0 runtime 下成功输出 node-pty spawn type: function
  • electron-builder install-app-deps 在本机 @electron/rebuild node-gyp worker 上空转无输出;由于 node-pty Electron runtime smoke 已通过,默认配置设为 npmRebuild=false,脚本保留 REBUILD_NATIVE=1 作为 runner/调试开关。
  • packaged .app 首轮 smoke 暴露两个打包差异:Vite 默认绝对 /assets/...file:// 下加载失败,已通过 vite.config.ts base: './'publicAssetPath() 和字体 URL 改造修复;asar=false 的全量 unpacked resources 会触发 macOS assessment Too many open files,已切换为 asar=true + asarUnpack
  • CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 通过并产出 desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.appcodesign --verify --deep --strict --verbose=2 通过;packaged app 启动后 sidecar 从 app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-apple-darwin 运行,renderer 从 app.asar/dist/index.html 加载。
  • bun run check:native 通过:sidecar build 成功,Electron host 56 tests passed,main/preload/preview-preload 构建成功。
  • 2026-06-01 已把 check:native 扩展为 build:sidecars + check:electron + electron:package:dir + test:package-smoke:current;最新复跑通过:Electron 77 tests passed,Electron --dir 产出 desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app,当前平台 macOS package-smoke PASS。
  • bun run check:desktop 通过:desktop tsc --noEmit、Vitest 全量 146 test files / 1127 tests passed,production build 成功。
  • 生产 React 直接 Tauri 扫描无输出:rg -n "@tauri-apps|__TAURI_INTERNALS__|window\\.__TAURI__|from '@tauri" desktop/src --glob '!**/*.test.ts' --glob '!**/*.test.tsx' --glob '!**/lib/desktopHost/**' --glob '!src-tauri/**'

14. 替换 CI workflow

修改:

  • .github/workflows/build-desktop-dev.yml
  • .github/workflows/release-desktop.yml
  • .github/workflows/pr-quality.yml
  • scripts/quality-gate/modes.ts
  • scripts/pr/change-policy.ts
  • scripts/pr/check-pr.ts

步骤:

  • [x] 移除 tauri-apps/tauri-action@v0
  • [x] 移除 Linux WebKitGTK/Rust/Tauri 专用依赖,保留 Electron/Linux 打包需要的依赖。
  • [x] 配置 macOS arm64/x64、Windows x64、Linux x64/arm64 matrix。
  • [x] 配置 signing/notarization/update metadata secrets。
  • [x] 让 PR/change policy 识别 desktop/electron/** 和 Electron build config。
  • [x] 运行 bun run check:policy

通过标准: CI lane 名称、触发条件和产物路径都更新为 Electron。 阻断条件: PR 改 Electron host 文件但不会触发桌面/原生检查。

执行记录(2026-05-31):

  • .github/workflows/build-desktop-dev.yml 改为 Electron Builder matrix:macOS arm64/x64、Windows x64、Linux x64/arm64;每个 job 安装 Bun/Node、构建 sidecars、构建 renderer/Electron bundles、运行 electron-builder 并上传 desktop/build-artifacts/electron 产物。
  • .github/workflows/release-desktop.yml 移除 tauri-apps/tauri-action@v0,改为 electron-builder --publish never 生成产物,再用 softprops/action-gh-release@v2 上传 release assets。
  • Linux CI 依赖删除 WebKitGTK/Rust/Tauri 专用包,仅保留 Electron packaging 与 AppImage 需要的基础构建工具和 libfuse2
  • PR desktop-native-checks 不再安装 Rust toolchain 或 Rust cache;desktop/scripts/build-sidecars.ts 改为用 process.platform / process.arch 推导 host triple,避免 Electron check:native 继续依赖 rustc -vV
  • sidecar build target env 已改为 SIDECAR_TARGET_TRIPLEbuild-sidecars.ts 暂时兼容旧 TAURI_ENV_TARGET_TRIPLE,但 CI 和平台脚本不再使用 Tauri 命名。
  • scripts/pr/change-policy.tsdesktop/electron/tsconfig.json、Electron macOS/Windows build scripts 纳入 native/release paths;scripts/quality-gate/modes.tsscripts/pr/impact-report.ts 文案从 Tauri native 改为 Electron host/package。
  • bun run check:policy 通过:64 tests passed,quarantine review 0 expired。

15. 替换 release 脚本

修改:

  • scripts/release.ts
  • scripts/pr/release-workflow.test.ts

步骤:

  • [x] 版本来源从 desktop/src-tauri/tauri.conf.json 改为 Electron package/build config。
  • [x] 删除 Cargo.toml/Cargo.lock release 更新。
  • [x] 保留 release note 校验、commit、tag 流程。
  • [x] 运行 bun run scripts/release.ts <next-version> --dry
  • [x] 运行 bun test scripts/pr/release-workflow.test.ts

通过标准: dry run 显示的版本文件全部属于 Electron 发布链。 阻断条件: release 仍依赖 Tauri 版本文件。

执行记录(2026-05-31):

  • scripts/release.ts 当前版本来源改为 desktop/package.json,版本更新文件只保留 desktop/package.json
  • release 脚本不再更新 desktop/src-tauri/tauri.conf.jsonCargo.tomlCargo.lock,也不再运行 cargo generate-lockfile
  • Git commit/tag 流程仍保留 release notes 校验、git add desktop/package.json release-notes/vX.Y.Z.md、commit、annotated tag。
  • bun run scripts/release.ts patch --dry 通过,显示 0.3.1 -> 0.3.2,只会更新 desktop/package.jsonrelease-notes/v0.3.2.md
  • bun test scripts/pr/release-workflow.test.ts scripts/pr/change-policy.test.ts 通过:11 tests passed。

Phase 5:最终验收

16. Electron packaged smoke

步骤:

  • [ ] macOS:构建 .app/.dmg,启动 app,验证聊天、附件、通知、托盘、更新 mock、终端、预览面板。
  • [ ] Windows:构建 NSIS 安装器,安装后启动 app,验证 sidecar 文件锁和更新前停进程。
  • [ ] Linux:构建 deb/AppImage,验证启动、tray、通知、更新策略。
  • [x] 运行 bun run test:package-smoke --platform macos 作为包结构预检;Windows/Linux 待对应平台产物。

通过标准: 每个平台至少一个真实发布格式完成 smoke。 阻断条件: 只有 dev build 或 Vite 页面通过。

执行记录(2026-05-31):

  • macOS unpacked .app smoke 已完成一部分:CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 成功,Computer Use 启动 packaged app 后主界面正常渲染,server sidecar 正常启动,终端面板可打开并执行 printf electron-terminal-ok
  • Review 后重新运行 cd desktop && CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 成功;bun run test:package-smoke --platform macos 通过,确认当前 .app 结构、app.asar、unpacked sidecar、unpacked node-pty native module 和 spawn-helper 存在。
  • 因本机已有正式 /Applications/Claude Code Haha.app 实例持有单实例锁,Computer Use 最终 smoke 使用同一构建输出重新打包临时 Claude Code Haha Smoke.app(仅临时 appId/productName 不同)执行:packaged renderer 从 app.asar/dist/index.html 加载,sidecar 自动启动,终端执行 electron-terminal-ok,系统目录选择器可打开,preview WebContentsView 可导航 https://example.com/,截图可回填 composer。
  • 注意:test:package-smoke 是 bundle-structure/static-artifact preflight,不启动 GUI,也不替代 Computer Use 或实机 smoke。
  • 2026-06-01 同步本地 main 后重新运行 cd desktop && CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 成功,产出 desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app
  • 2026-06-01 使用 Computer Use 操作真实 packaged app,并把本机 cc-haha provider/model 配置复制到隔离临时 CLAUDE_CONFIG_DIR:模型选择器显示 gpt-5.5 Sub2API-ChatGPT,系统目录选择器选中 /private/tmp/cc-haha-electron-real-fixed-sRFUeO/project
  • 真实模型 smoke 会话 e0d7abc2-27db-4ff1-9954-7923f6c3385e 通过:packaged renderer 从 file://.../app.asar/dist/index.html 加载,server sidecar 建立 WebSocket,CLI subprocess 使用 --model gpt-5.5 启动,模型读取 package.jsonsrc/greeting.ts 后经权限审批执行 bun test,结果为 1 pass / 0 fail,最终回复“测试通过。”。
  • 2026-06-01 重跑 SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh:修复平台级 publish 后曾产出 .app/.zip/.dmg/latest-mac.yml,但 latest-mac.yml 与实际含空格 asset 文件名不一致;加入稳定 artifactName 后,清理旧 disk image 挂载前本机 DiskImages/hdiutil 在 .dmg 创建阶段超时,zip-only Electron Builder 也曾在本机 7za 子进程处失败。
  • 2026-06-01 清理旧 Tauri/Electron disk image 挂载后,MAC_TARGETS=zip SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh 通过,canonical desktop/build-artifacts/macos-arm64 产出 .appClaude-Code-Haha-0.3.2-arm64.zip.zip.blockmaplatest-mac.yml;脚本自动运行 bun run test:package-smoke --platform macos --artifacts-dir desktop/build-artifacts/macos-arm64 并 PASS,确认 latest-mac.yml 引用真实 zip 且 packaged resources 含 app-update.yml
  • 2026-06-01 .dmg target 最新复验通过:后续默认 SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh 已完成 dmg zip target,canonical desktop/build-artifacts/macos-arm64 包含 .app.dmg.zip、blockmap 和 latest-mac.yml,脚本自动 release package-smoke PASS。Gatekeeper/signed/notarized launch 仍保持 release blocker。
  • 2026-06-01 macOS build script hardening:默认 dmg zip target 在当前 worktree 检测到 stale Electron Builder .temp...dmg 挂载时会提前失败并给出 MAC_TARGETS=zip 替代路径,避免继续进入会卡死的 DiskImages detach/create 分支。
  • 2026-06-01 Windows build script hardening:canonical 输出现在保留 win-unpacked 供 package-smoke 检查 app.asarapp-update.yml、unpacked sidecar 和 node-pty 原生模块;脚本默认在 Windows runner 上执行 canonical package-smoke,但本机 macOS 未执行 NSIS 真实构建/安装。
  • 2026-06-01 Linux build script hardening:新增 desktop/scripts/build-linux.shbuild:linux-x64 / build:linux-arm64 scripts,canonical 输出现在保留 AppImage/deb、latest-linux.ymllinux-unpacked 供 package-smoke 检查;本机 macOS 不能执行 Linux 真实构建/安装。
  • 2026-06-01 Linux package-smoke 区分开发目录包和发布包:electron-builder --dir 只产出 linux-unpacked 时仍检查 app.asar、sidecar 和 node-pty 并通过 check:native;AppImage/deb 发布产物存在时继续要求 latest-linux.ymlapp-update.yml
  • 2026-06-01 Linux arm64 updater metadata 修复:build-linux.sh 现在复制 latest-linux*.yml,避免 arm64 Electron Builder 产出 latest-linux-arm64.yml 时 canonical linux-arm64 目录丢失 update metadata;package-smoke 同步识别 latest-linux.ymllatest-linux-arm64.yml,并检查 metadata 引用的 AppImage/deb artifact 是否真实存在。
  • 2026-06-01 package-smoke 增加显式 --package-kind dir|releasecheck:native 使用 dir + desktop/build-artifacts/electron,发布/dev packaging workflow 和平台 release 脚本使用 release + 精确 artifacts-dir,避免旧 installer/metadata 污染本轮验包。
  • 2026-06-01 Gatekeeper 诊断加固:--require-macos-gatekeeperspctl 失败时,package-smoke 会同时记录 codesign --verify --deep --strict --verbose=2codesign -dv --verbose=4xcrun stapler validate 摘要,方便区分未签名、签名链、bundle 格式和 notarization ticket 问题。
  • 2026-06-01 SubAgent Review 修复:release workflow 在打包矩阵前新增 bun run verify 非 live 预检;quality:gate --mode release 会包含 PR checks,并把 desktop-package-smoke:<platform> 改为 --package-kind release --artifacts-dir desktop/build-artifacts/<platform-arch>,避免验到旧的 --dir 包。
  • 2026-06-01 多架构 metadata 风险收敛:release workflow 上传前会把 latest*.yml 改名为带 matrix label 后缀的唯一 asset,避免 GitHub Release 中同名 metadata 互相覆盖或上传失败;matrix 全部通过后由 scripts/release-update-metadata.ts 重新发布标准 updater channel metadata。macOS 会把 x64/arm64 zip entries 合并回 latest-mac.yml,Linux 会恢复 latest-linux.yml / latest-linux-arm64.yml,Windows 会恢复 latest.yml。完整自动更新 release 仍需在 signed/notarized artifact 上复验。
  • 2026-06-01 signing/notarization secrets preflight:release workflow 新增非 matrix signing-preflight job,在任何平台 artifact 上传前检查 MACOS_CERTIFICATEMACOS_CERTIFICATE_PASSWORDAPPLE_IDAPPLE_APP_SPECIFIC_PASSWORDAPPLE_TEAM_IDWINDOWS_CERTIFICATEWINDOWS_CERTIFICATE_PASSWORD。缺失时用 GitHub Actions error 早停,避免部分 matrix leg 先发布 partial release。真实 Developer ID signing/notarization 仍需在 CI secrets 存在时复验。
  • 2026-06-01 native/package 当前态复验:bun run check:native 通过,完成 sidecar 构建和 ad-hoc signing、check:electron 83 tests、desktop production build、electron-builder --dir,并运行 bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron PASS。该证据只覆盖 directory-only unpacked app bundle 结构、app.asar、sidecar、node-ptyspawn-helper,不替代 .dmg、Gatekeeper 或 signed release smoke。
  • 2026-06-01 DMG 当前态复验:默认 SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh 已通过,canonical desktop/build-artifacts/macos-arm64 恢复 .app.dmg.dmg.blockmap.zip.zip.blockmaplatest-mac.yml,并由 release package-smoke PASS。
  • 2026-06-01 跨平台 package-smoke 当前态复验:bun test scripts/quality-gate/package-smoke/index.test.ts scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts 通过 26 tests,覆盖 Windows canonical output、Linux x64/arm64 update metadata、release workflow Gatekeeper/signing preflight 和 metadata republish;bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64 PASS,确认当前 macOS zip/update canonical artifact set 仍完整。
  • 本轮尚未完成 .dmg 安装包、通知点击、signed/notarized Gatekeeper launch、Windows NSIS 和 Linux AppImage/deb 的真实发布格式 smoke,因此本阶段保持未完成。附件选择、目录选择、外链、终端和 preview 已有 dev 或 unpacked packaged Computer Use 证据,但还不能替代最终 signed release artifact 的全量验收。

17. Release gate

步骤:

  • [x] 运行 bun run verify
  • [x] 有 live provider 时运行 bun run quality:gate --mode release --allow-live --provider-model <provider:model[:label]>
  • [x] 检查 quality report 的 pass/fail/skip。
  • [x] 记录 coverage report 路径。

通过标准: PR/release 门禁通过,coverage 未退化。 阻断条件: 任一 release 必需 lane 失败。

执行记录(2026-06-01):

  • bun run verify 通过:最新为 artifacts/quality-runs/2026-05-31T21-42-57-279Z/report.mdpassed=9 failed=0 skipped=1
  • 首次重跑完整 release --allow-live 后,旧 agent-browser/Vite blocker 已消失;唯一失败为 coverage 中 h5-access-auth.test.ts 随机端口撞到本机 WeChat 的 127.0.0.1:24023
  • 已把 h5-access-auth.test.ts 端口选择改成 OS 分配 ephemeral port;focused bun test src/server/__tests__/h5-access-auth.test.ts 通过,bun run check:coverage 最新通过:artifacts/coverage/2026-05-31T21-46-15-873Z/coverage-report.mdpassed=5 failed=0
  • 历史 bun run quality:gate --mode release --allow-live --provider-model sub2api-chatgpt:main:sub2api-chatgpt-main 曾通过:artifacts/quality-runs/2026-05-31T20-29-45-758Z/report.mdpassed=15 failed=0 skipped=0。SubAgent Review 后 release gate 已收窄为必须检查 canonical release artifact,当前仍需在 signed/notarized artifact 与可用 Computer Use 桌面会话上重新跑完整 release gate。

18. Computer Use 全面测试

步骤:

  • [x] 用真实打包 app 运行 Computer Use 设置流程。
  • [x] 验证 Python 路径选择、权限授权、屏幕录制/辅助功能授权提示、授权应用列表。
  • [x] 验证点击、输入、滚动、系统快捷键、剪贴板。
  • [ ] 验证通知点击回到目标会话。
  • [ ] macOS 和 Windows 至少各完成一次;Linux 如能力降级,写入 release note 和已知限制。

通过标准: Computer Use 在真实 Electron app 中完成端到端操作。 阻断条件: 只用浏览器/Vite 或 mock 路径证明。

验证清单: 迁移收口使用 docs/desktop/09-electron-migration-validation-checklist.md,其中区分自动化证据、macOS Computer Use smoke、Windows 待实机和 Linux 待实机。

执行记录(2026-06-01):

  • 尝试继续 Computer Use GUI 验收时,Computer Use 工具层对 list_appsget_app_state(Finder) 均返回 NSOSStatusErrorDomain Code=-600 procNotFound。该结果说明当前 macOS 自动化桥未拿到可用进程,不是 Electron app 单点问题;剩余附件、Python 路径、外链、Dock/tray、通知点击和完整 update feed 验收保持未完成。
  • 2026-06-01 后续复测 Computer Use 仍返回同一 procNotFound,这是连续第二个 goal turn 复现;目标仍可在代码和发布门禁层继续推进,因此暂不标记 goal blocked。
  • 2026-06-01 再次复测后 Computer Use list_apps 已恢复,但 get_app_state(Electron) 对 Electron dev 壳超时,get_app_state(Finder)cgWindowNotFound;系统 screencapture 同时返回 could not create image from display。这说明当前桌面会话没有可用截图/窗口捕获能力,Computer Use 交互验收仍不能推进。
  • 2026-06-01 continuation 复测:Computer Use list_apps 仍可列应用,get_app_state(Finder) 仍为 cgWindowNotFoundscreencapture 仍失败为 could not create image from display;当前仍不能完成附件、Python 路径、外链、Dock/tray、通知点击等 GUI 验收。
  • 2026-06-01 packaged app 启动被 macOS AppleSystemPolicy 拦截:spctl -a -t execute desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.appbundle format unrecognized, invalid, or unsuitable,系统日志显示 ad-hoc/unknown-chain app 被终止。本轮已新增 bun run test:package-smoke --platform macos --require-macos-gatekeeper 作为可选 release-readiness 检查;当前该检查失败,release-ready 需要 Developer ID signing/notarization 或真实签名产物复验。
  • 2026-06-01 release workflow 已补强:macOS release artifact 上传前强制运行 bun run test:package-smoke --platform macos --require-macos-gatekeeper,防止未通过 macOS 启动策略的 .app/.dmg/.zip 被发布;dev build 仍保留结构型 package-smoke。
  • 2026-06-01 修复 Electron dev launcher 代理问题:electron:dev 默认 renderer URL 改为 http://localhost:1420,并对当前进程、Vite 与 Electron 子进程补齐 NO_PROXY/no_proxy=localhost,127.0.0.1,::1,避免本机代理把 localhost renderer 等待请求打成 502。
  • 2026-06-01 主窗口显示逻辑加固:创建 BrowserWindow 后立即 show/focus,renderer load 完成后再次 show/focus,避免隐藏窗口在 load 等待期间被托盘/单实例/Computer Use 路径视为无窗口。
  • 2026-06-01 SubAgent gap follow-up:renderer 生产代码不再用 isTauriRuntime() 判断通用桌面能力,新增 isDesktopRuntime(),更新弹窗、附件 native picker、设置 app mode、移动布局分支均识别 Electron/Tauri desktop host。
  • 2026-06-01 Electron updater proxy contract 已补齐:renderer 传入的 { proxy } 会进入 Electron main,检查更新前应用到 Electron app/session proxy;切回系统代理时会清理手动代理,避免更新检查继续沿用旧 proxy。
  • 2026-06-01 package-smoke 增加发布型包的 installed updater metadata 检查:当 artifact set 含 .zip/.dmg/latest-mac.yml、Windows installer/latest.yml 或 Linux package/latest-linux.yml 时,安装后 resources 目录必须包含 app-update.yml;纯 --dir 开发包会记录 note 但不失败。
  • 2026-06-01 真实 provider smoke 使用本机 ~/.claude/cc-haha/providers.json selector sub2api-chatgpt:main:sub2api-chatgpt-main 通过:artifacts/quality-runs/2026-05-31T21-28-07-154Z/report.mdpassed=1 failed=0 skipped=0。该路径复用 cc-haha provider 配置,不走 QUALITY_GATE_PROVIDER_* 明文 env-only 分支。
  • 2026-06-01 check:native 已补上打包后验包:根脚本现在会在 Electron --dir 打包后运行 test:package-smoke:current。最新 bun run check:native 通过,package-smoke notes 明确当前纯 --dir macOS artifact set 不强制 app-update.yml,也不代表 GUI 启动或 Gatekeeper approval。
  • 2026-06-01 final-definition 复测:Computer Use list_apps 正常,但 get_app_state(Finder) 仍返回 cgWindowNotFound;系统 screencapture -x /tmp/cc-haha-cua-retake-final.png 仍失败为 could not create image from display。当前机器继续不能承载真实 GUI 操作验收。
  • 2026-06-01 cross-platform-script 复测:Computer Use list_apps 正常且可见 /Applications/Claude Code Haha.appget_app_state(Finder) 仍为 cgWindowNotFoundget_app_state(Claude Code Haha) 返回 remoteConnection,系统 screencapture -x /tmp/cc-haha-computer-use-check.png 仍失败为 could not create image from display。当前仍不能完成点击、输入、附件选择、托盘恢复、通知点击等 GUI 验收。
  • 2026-06-01 package-kind 复测:Computer Use list_apps 正常且显示 /Applications/Claude Code Haha.app 正在运行,get_app_state(Claude Code Haha)get_app_state(Finder) 均返回 cgWindowNotFound,系统 screencapture -x /tmp/cc-haha-computer-use-check-2.png 仍失败为 could not create image from display。当前机器仍不能承载真实 GUI 操作验收。
  • 2026-06-01 Gatekeeper 诊断加固:release-readiness package-smoke 在 spctl 失败时会补充 codesign verify/details 摘要,后续 signed/notarized artifact 的失败可以直接从 quality log 判断是签名链、bundle 格式还是 notarization policy 问题。
  • 2026-06-01 Gatekeeper 诊断复验:当前 ad-hoc .app 仍被 spctl 拒绝为 bundle format unrecognized, invalid, or unsuitable,但 package-smoke 已输出 codesign verification exited with status 0 与 bundle identifier/signature detail 摘要,证明诊断链路可用;release blocker 仍是 Developer ID signing/notarization 或真实签名产物复验。
  • 2026-06-01 Gatekeeper 诊断后 Computer Use 复测:list_apps 可见运行中的 /Applications/Claude Code Haha.app,但 get_app_state(Claude Code Haha)get_app_state(Finder) 均仍为 cgWindowNotFound,系统 screencapture -x /tmp/cc-haha-computer-use-check-3.png 仍失败为 could not create image from display。当前机器继续无法执行点击、输入、文件选择器、托盘/通知等 GUI 验收。
  • 2026-06-01 Security/Runtime Review 修复:Electron 移除了 renderer 可调用的 previewEval 任意 JS 注入通道,预览截图/元素选择改走结构化 preview.messageshell.openPath 改为只允许已存在的普通文件/目录,并拒绝 .app、脚本/安装器/Windows 可执行扩展和 POSIX executable 文件;Electron 暂不声明自定义 window controls,避免 Windows 原生标题栏与自定义标题栏双渲染。
  • 2026-06-01 release metadata 合并后 Computer Use 复测:list_apps 可见运行中的 /Applications/Claude Code Haha.app,但 get_app_state(Claude Code Haha)get_app_state(Finder) 均仍为 cgWindowNotFound,系统 screencapture -x /tmp/cc-haha-computer-use-check-4.png 仍失败为 could not create image from display。当前机器继续无法执行点击、输入、文件选择器、托盘/通知等 GUI 验收。
  • 2026-06-01 mock update feed UI 流程补强:UpdateChecker 集成测试通过 Electron desktopHost mock update feed 驱动真实 updateStore,覆盖 check/download/install/relaunch 状态流、下载完成弹窗、点击安装后 prompt 退出。signed/release feed 仍需真实 signed/notarized artifact 复验。
  • 2026-06-01 mock update feed UI 补强后 Computer Use 复测:list_apps 可见运行中的 /Applications/Claude Code Haha.app,但 get_app_state(Claude Code Haha)get_app_state(Finder) 均仍为 cgWindowNotFound,系统 screencapture -x /tmp/cc-haha-computer-use-check-5.png 仍失败为 could not create image from display。当前机器继续无法执行点击、输入、文件选择器、托盘/通知等 GUI 验收。
  • 2026-06-01 macOS Keychain 弹窗修复:用户截图显示 Electron/Chromium 反复请求 claude-code-desktop Safe Storage。原因是 Chromium profile safe storage 会访问 macOS Keychain;本项目 OAuth token 已走 sidecar file-backed storage,不依赖 Chromium cookie/password store。Electron main 现在在启动早期对 macOS 设置 use-mock-keychain,并把 ModelSelector 的官方 OAuth status 从挂载即请求改为 runtime 下拉打开时按需请求一次,避免启动/重渲染时反复触发敏感存储路径。验证:cd desktop && bun run test -- --run src/components/controls/ModelSelector.test.tsx electron/services/keychain.test.ts 12 tests passed;cd desktop && bun run check:electron 80 tests passed 并完成 main/preload bundle。
  • 2026-06-01 Keychain 修复后 Computer Use 复测:bun run electron:dev 启动当前 Electron dev 壳,get_app_state(Electron) 成功返回 localhost:1420/ 主界面和模型选择器 gpt-5.5 Sub2API-ChatGPT;截图/AX 树中未再出现 claude-code-desktop Safe Storage 钥匙串授权弹窗。随后已终止 dev Electron/Vite 进程。
  • 2026-06-01 Keychain 当前态复验:cd desktop && bun run test -- --run electron/services/keychain.test.ts src/components/controls/ModelSelector.test.tsx 通过 12 tests;cd desktop && bun run check:electron 通过 83 tests 并完成 Electron main/preload/preview-preload bundle。Computer Use get_app_state(Electron) 返回 Claude Code Companion 主窗口和 localhost:1420/ renderer,未出现 Safe Storage 钥匙串授权弹窗。
  • 2026-06-01 原生附件文件选择补强:Electron dialog IPC 改为使用当前 BrowserWindow 作为 parent window,composer 打开原生选择器前先关闭 plus 菜单,避免无 owner sheet 返回后菜单状态残留。验证:bun test electron/services/dialogs.test.ts 3 tests passed;bun run test -- --run src/lib/composerAttachments.test.ts src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx 33 tests passed;bun run check:electron 80 tests passed;Computer Use 在 Electron dev 壳中成功选择 /Users/nanmi/cc-haha-cua-attachment-smoke.txt 并显示附件 chip,未出现 Safe Storage 弹窗。
  • 2026-06-01 packaged updater 噪音修复:Electron updater service 在 packaged app-update.yml 缺失时直接返回“无更新”,并将 electron-updater 内置 logger 设为 null,避免目录包或 GitHub release 暂缺 latest-mac.yml 时在启动日志里打印 404 stack;非 metadata 错误仍会抛出。验证:cd desktop && bun test electron/services/updater.test.ts electron/services/singleInstance.test.ts 13 tests passed;cd desktop && bun run check:electron 89 tests passed。
  • 2026-06-01 single-instance 验证绕过:新增显式 CC_HAHA_ELECTRON_DISABLE_SINGLE_INSTANCE_LOCK=1 供本地验证启动同 bundle 的 worktree/canonical app,不影响默认生产单实例行为,避免需要杀掉用户正在使用的 /Applications/Claude Code Haha.app
  • 2026-06-01 canonical macOS release Computer Use 复测:SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh 通过默认 dmg zip,release package-smoke PASS;用隔离配置和 CC_HAHA_ELECTRON_DISABLE_SINGLE_INSTANCE_LOCK=1 启动 desktop/build-artifacts/macos-arm64/Claude Code Haha.app,server sidecar 启动,Computer Use get_app_state 成功读取 Claude Code Companion 主窗口和 packaged renderer。启动日志没有 app-update.ymllatest-mac.yml updater stack。
  • 2026-06-01 canonical packaged 系统交互复测:在同一个 desktop/build-artifacts/macos-arm64/Claude Code Haha.app 隔离实例中,Computer Use 确认项目选择器已选中系统原生目录选择结果 /private/tmp/cc-haha-electron-real-fixed-sRFUeO/project;点击 打开终端 后终端面板显示 运行中 /bin/zsh / /tmp/cc-haha-canonical-cua-flow.xTxSmt/claude-config,输入 printf 'canonical-terminal-ok\n'; pwd 后画面可见 canonical-terminal-ok 和实际工作目录输出。该轮未出现 claude-code-desktop Safe Storage 钥匙串弹窗。
  • 2026-06-01 canonical packaged Computer Use 设置流程复测:用隔离 CLAUDE_CONFIG_DIR 启动 desktop/build-artifacts/macos-arm64/Claude Code Haha.app,Computer Use 打开设置页后进入 Computer Use tab。首次状态请求曾因 30s timeout 显示 Failed to check status,点击 重试 后页面正常显示 Python 3.14.5、venv 未创建、依赖未安装。通过页面 安装环境 按钮完成真实 server/setup 链路,随后 UI 显示 venv 已就绪、依赖包已安装、辅助功能权限已授权、屏幕录制权限未授权;curl /api/computer-use/status 返回 venv.created=truedependencies.installed=truepermissions.accessibility=truepermissions.screenRecording=false。点击 打开屏幕录制设置 后 Computer Use 成功读取 macOS 系统设置窗口 录屏与系统录音,并看到 Claude Code Haha 权限项。
  • 2026-06-01 canonical packaged Computer Use 授权应用复测:复用隔离 packaged app,Computer Use 在设置页搜索 Terminal,应用列表返回 Terminal / com.apple.Terminal;点击该项后 UI 显示 check 状态,隔离配置 cc-haha/computer-use-config.jsonGET /api/computer-use/authorized-apps 均返回 authorizedApps[0].bundleId=com.apple.TerminalgrantFlags.clipboardRead=truegrantFlags.clipboardWrite=truegrantFlags.systemKeyCombos=true。该轮覆盖了真实 UI 点击、输入、滚动、应用枚举和授权配置持久化。
  • 2026-06-01 notarization 诊断复验:bun test scripts/quality-gate/package-smoke/index.test.ts 15 tests passed;bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64 --require-macos-gatekeeper 预期失败,但输出现在同时包含 spctlcodesignnotarization ticket validation 诊断,当前 ad-hoc app 被分类为没有 stapled notarization ticket。
  • 2026-06-01 Gatekeeper 当前态复跑:bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64 通过,确认 release artifact 结构、latest-mac.yml 引用 zip、app-update.yml、sidecar 和 node-pty 完整;追加 --require-macos-gatekeeper 后仍失败,codesign verify/details 通过但 spctl 返回 bundle format unrecognized, invalid, or unsuitablestapler 返回 does not have a ticket stapled to it。package-smoke 现已在 spctlToo many open files 时用 raised file descriptor limit 自动重试,避免该临时诊断掩盖真实 Gatekeeper 结果。release-ready 仍需要 Developer ID signing/notarization 或真实签名产物复验。
  • 2026-06-01 Gatekeeper 诊断补强后 Computer Use 复测:用隔离 CLAUDE_CONFIG_DIR--user-data-dir 启动 desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app,Computer Use 成功读取 Claude Code Companion 主窗口和 packaged file://.../app.asar/dist/index.html renderer,证明 package-smoke 诊断补强未破坏 packaged app 运行路径。
  • 2026-06-01 release gate 口径收紧:quality:gate --mode release 的当前平台 desktop-package-smoke:macos lane 现在会追加 --require-macos-gatekeeper,与 GitHub release workflow 上传前 Gatekeeper 检查保持一致。验证:bun test scripts/quality-gate/runner.test.ts scripts/quality-gate/package-smoke/index.test.ts scripts/pr/release-workflow.test.ts 通过 39 tests;bun run quality:gate --mode release --dry-run --only 'desktop-package-smoke:*' 报告命令包含 --require-macos-gatekeeper;实际运行 bun run quality:gate --mode release --only 'desktop-package-smoke:*' 失败于 Gatekeeper,报告为 artifacts/quality-runs/2026-06-01T12-16-06-442Z/report.md
  • 2026-06-01 当前 release provider smoke 复验:bun run quality:gate --mode release --allow-live --only 'provider-smoke:*' --provider-model sub2api-chatgpt:main:sub2api-chatgpt-main 通过,报告为 artifacts/quality-runs/2026-06-01T12-18-47-144Z/report.md;随后用 Computer Use 启动隔离 packaged app,成功读取 Claude Code Companion 主窗口和 packaged file://.../app.asar/dist/index.html renderer。
  • 2026-06-01 governance gate 复验:bun run check:policy 通过 77 tests,并执行 check:quarantine,覆盖 release workflow、quality gate runner、provider/desktop smoke、change policy、quality contract 和 quarantine governance。随后用 Computer Use 再次启动隔离 packaged app;首次读取遇到 ScreenCaptureKit stream error,但 System Events 显示同一 PID 有 Claude Code Companion 窗口,重试后 Computer Use 成功读取 packaged file://.../app.asar/dist/index.html renderer。
  • 2026-06-01 本地 main 同步后复验:已把本地 main 最近 10 个提交的非重叠改动应用到当前迁移工作区,并手动合并 ChatInput.tsx / previewEvents.ts / previewEvents.test.ts 中的 append prefill 与 Electron desktopHost.preview 改造。验证:cd desktop && bun run test -- --run src/components/chat/ChatInput.test.tsx src/lib/previewEvents.test.ts 通过 24 tests;bun test src/cli/__tests__/structuredIO.test.ts src/services/api/withRetry.test.ts src/utils/__tests__/imageResizer.test.ts src/utils/shell/powershellDetection.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/conversation-attachments.test.ts src/server/__tests__/websocket-handler.test.ts 通过 124 tests;cd adapters && bun test feishu/__tests__/streaming-card.test.ts 通过 36 tests;cd desktop && bun run check:electron 通过 94 tests;CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 重新生成 packaged dir;bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron 通过;Computer Use 成功读取新打包的 desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app 主窗口和 packaged renderer。
  • 2026-06-01 release update blockmap 验证补强:package-smoke 的 release 模式现在强制检查 macOS .zip/.dmg.blockmap、Windows .exe.blockmap 和 Linux .AppImage.blockmap,避免只有 channel metadata 和安装包但缺少 electron-updater 差分更新文件。验证:bun test scripts/quality-gate/package-smoke/index.test.ts 通过 17 tests;bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64 通过,并确认当前 macOS release artifact set 含 .dmg.blockmap.zip.blockmap
  • 2026-06-01 packaged update smoke 补强:新增 CC_HAHA_ELECTRON_UPDATE_SMOKE_VERSION 驱动的 Electron main updater stub,仅在显式 smoke env 下启用;app-update.yml 缺失不会阻断该 smoke stub,生产 electron-updater 仍保持缺失 metadata 时 no-op。验证:cd desktop && bun run check:electron 通过 94 tests;CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 生成新的 packaged dir;复制为唯一 bundle id 的 smoke app 后,packaged renderer file://.../app.asar/dist/index.html 通过 preload 调用 desktopHost.updates.check/download/prepareInstall/install/relaunch,JSONL 记录 checkdownload-startdownload-finishquit-and-install。同轮 Computer Use list_apps 可见唯一 smoke app,但 get_app_state 对该 app 仍超时,故真实“点击安装”仍不能声明通过。
  • 2026-06-01 packaged window smoke 诊断:新增 CC_HAHA_ELECTRON_WINDOW_SMOKE_LOG,main process 在 after-createafter-initial-showdid-finish-loadafter-final-show 写入窗口快照。早前同一 packaged dir 启动日志显示 Electron BrowserWindowvisible:true,bounds 为 1280x820,URL 为 packaged file://.../app.asar/dist/index.html,但 System Events 曾读取到 windows count=0 且 Computer Use get_app_state 超时;随后用完整 .app 路径复测,Computer Use 成功读取 Claude Code Companion 主窗口和 packaged renderer,System Events 返回 front=true count=1 names=Claude Code Companion。当前结论是之前的卡点在 macOS AX/窗口捕获状态波动,不是 renderer、server、更新 preload smoke 或关闭智能体流程。
  • 2026-06-01 packaged notification click 复测:直接执行 app binary 时 smoke env 未进入最终 Electron app 进程,改用 launchctl setenv + open -n ... --args --user-data-dir=<tmp> 后,packaged app 的 CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG 正常写入 scheduledsent:true。Computer Use 对 /System/Library/CoreServices/NotificationCenter.appSystemUIServer 仍返回 timeoutReached,Finder 状态读取返回 ScreenCaptureKit stream error,JSONL 未出现 action;因此“点击通知回到目标 session”仍保持未完成。
  • 2026-06-01 通知点击再次复测:用 launchctl setenv 启动 worktree packaged app 后,JSONL 正常写入 scheduledsent:true,但 Computer Use 对该 worktree app 连续返回 ScreenCaptureKit stream error;list_apps 能看到 worktree app running。随后复制当前 packaged app 为唯一 bundle id smoke app 并 ad-hoc 重签名,LaunchServices 能注册该 smoke app,但应用立即退出,系统日志显示 appDeath;因此本轮仍不能把真实 OS 通知点击回跳勾选为完成。
  • 2026-06-01 当前全量桌面/服务端门禁复验:cd desktop && bun run check:desktop 通过 desktop lint、151 个 Vitest 文件、1199 个测试和 production build;bun run check:server 通过 88 files / 969 tests。通知真实点击、signed/notarized Gatekeeper、Windows/Linux 实机 release smoke 仍保持未完成项。
  • 2026-06-01 通知点击 renderer ack 补强:新增 Electron desktop:notification:action-ack IPC,renderer 处理通知 action 并打开目标 session 后,会把 { target, payload } 写入 CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOGrenderer_ack 事件。该补强不模拟 OS 点击,只让后续可点击通知的 macOS/Windows runner 能证明 main action 已进入 UI 导航链路。验证:focused notification/desktopHost tests 42 passed;cd desktop && bun run check:electron 通过 97 tests;CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir 重新生成 packaged dir;bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron PASS。
  • 2026-06-01 renderer ack 补强后 Computer Use 复测:用隔离 CLAUDE_CONFIG_DIR、隔离 --user-data-dir 和 notification smoke env 启动当前 worktree packaged app 时,Computer Use 首次 get_app_state 返回 timeoutReached,直接执行也曾退出 137。随后用隔离 CLAUDE_CONFIG_DIR / --user-data-dir 重新直接运行同一 .app 后保持运行,Computer Use 成功读取 Claude Code Companion 主窗口,renderer URL 为当前 worktree 的 packaged file://.../app.asar/dist/index.html;点击设置入口后进入 packaged 设置页。该轮确认当前 Electron build 可被 Computer Use 读取和交互;spctl -a -t execute desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app 仍返回 bundle format unrecognized, invalid, or unsuitablecodesign --verify --deep --strict 通过,因此真实 OS 通知点击和 signed/notarized launch 仍未完成。
  • 2026-06-01 packaged synthetic notification action 复测:新增显式 smoke-only 开关 CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_TRIGGER_ACTION=1,只在测试环境中由 main process 对刚发送的通知 action 触发同一条 renderer ack 链路。用 launchctl setenv + open -n desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app --args --user-data-dir=<tmp> 启动当前 worktree packaged app 后,Computer Use 成功读取 Claude Code Companion,renderer URL 为 file:///Users/nanmi/.codex/worktrees/2392/claude-code-haha/desktop/build-artifacts/electron/mac-arm64/Claude%20Code%20Haha.app/Contents/Resources/app.asar/dist/index.html;JSONL 记录 scheduledsent:truesynthetic_actionrenderer_ack。该 synthetic action 仅证明 packaged main -> renderer action -> ack 链路,不替代真实 OS 通知点击;真实“点击通知后回到目标 session”仍未完成。

Review 收口记录

2026-05-31 SubAgent Review

  • [x] Security Review:收紧 packaged renderer entry,packaged app 不再接受 ELECTRON_RENDERER_URL;dev 模式只允许 local HTTP renderer。
  • [x] Security Review:preview bridge 增加大小上限、事件 allowlist、字段 schema、data:image/* 校验,外部页面不能向 host 注入无限大或未知事件。
  • [x] Security Review:packaged macOS node-pty runtime cache 增加 manifest 校验、tamper 后重建、cache/helper 权限收紧。
  • [x] Code Review:macOS “打开通知设置”改走 native command 和 allowlisted system settings URL。
  • [x] Code Review:Electron preview.message() 改为转发到 injected preview bridge,不再 silent no-op。
  • [x] Code Review:dev/release desktop workflows 在 Electron builder 后运行 test:package-smoke,避免上传缺少 sidecar 或 node-pty 关键资源的产物。
  • [x] Verification Review:新增 docs/desktop/09-electron-migration-validation-checklist.md,明确 package-smoke 只是结构预检,最终完成仍需要真实 packaged app + Computer Use/实机 smoke。
  • [x] Release gate follow-up:desktop-smoke:agent-browser-chat:* 保留为 baseline/browser confidence lane,不再作为 release 必需 lane;release mode 新增 desktop-package-smoke:<platform>,用当前平台 Electron packaged artifact 结构检查配合 Computer Use 真实 app 验收,避免用浏览器/Vite open 结果代表桌面构建可用性。

最终完成定义

  • [x] desktop/src 生产代码不再直接依赖 @tauri-apps/*
  • [x] Electron main/preload IPC 有 capability registry 和 payload validation。
  • [x] 本地 Bun server + REST/WebSocket contract 保留,renderer 没有把 session/chat/workspace/team 主链改成 IPC。
  • [x] 自动更新、通知、文件选择、外链打开、窗口/托盘/菜单、sidecar、terminal、preview、app mode、zoom 全部有测试或 smoke 证据。
  • [ ] macOS、Windows、Linux 发布产物可构建并启动。
  • [x] bun run verify 通过。
  • [x] release gate 通过,或 live provider blocker 被明确记录。
  • [ ] Computer Use 对真实打包 app 完成全面测试。

最终定义复核(2026-06-01):

  • 生产 Tauri 边界扫描通过:rg -n "@tauri-apps|__TAURI_INTERNALS__|window\\.__TAURI__|from ['\\\"]@tauri" desktop/src --glob '!**/*.test.ts' --glob '!**/*.test.tsx' --glob '!**/lib/desktopHost/**' --glob '!src-tauri/**' 无输出。
  • Electron IPC 复核通过:desktop/electron/ipc/capabilities.ts 为每个 ELECTRON_IPC_CHANNELS channel 定义 validator,desktop/electron/main.tsregisterHandler() 在进入 handler 前执行 isElectronIpcChannel()validateElectronIpcPayload();renderer electronHost 也在 preload bridge 调用前执行相同 payload validation。
  • REST/WebSocket 主干复核通过:session/workspace/team 仍在 desktop/src/api/sessions.tsdesktop/src/api/teams.tsdesktop/src/api/websocket.ts 调用 /api/**/ws/:sessionId;Electron IPC 命中仅覆盖 host/system 能力、terminal 和 preview,不承载 chat/session/workspace/team 主业务。
  • 系统能力证据复核通过:Electron service/API tests 覆盖 updater、notifications、dialogs、shell、windows/tray/menu/single-instance、sidecar、terminal、preview、app mode、zoom;macOS .dmg/.zip/latest-mac.yml canonical release artifact set 已通过 package-smoke,canonical .app 已通过 Computer Use 窗口可见性 smoke;完整完成仍受 signed/notarized Gatekeeper、通知真实点击、Windows/Linux 实机发布格式 smoke 阻塞。

Released under the MIT License.