# 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.json`、`desktop/scripts/build-macos-arm64.sh`、`desktop/src-tauri/resources/preview-agent.js`、`desktop/src/components/workspace/WorkspaceFileOpenWith.test.tsx`、`desktop/src/lib/previewEvents.ts` 和 `desktop/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 `。 - [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 URL`、`updates`、`notifications`、`dialogs`、`shell`、`window`、`terminal`、`preview`、`appMode`。 - [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.ts`、`browserHost.ts`、`index.ts`、`contract.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.tsx`、`Sidebar.tsx`、`Settings.tsx`、`TerminalSettings.tsx`、`ComputerUseSettings.tsx`、`WorkspaceFileOpenWith.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.ts`、`desktop/src/lib/previewBridge.ts`、`desktop/src/lib/previewEvents.ts`、`desktop/src/lib/appZoom.ts`、`desktop/src/lib/desktopNotifications.ts`、`desktop/src/stores/updateStore.ts`、`desktop/src/stores/adapterStore.ts`、`desktop/src/stores/settingsStore.ts` 的 runtime/terminal/preview/zoom/notification/update/adapter restart/app mode 调用到 `desktopHost`。 - `desktopHost` 现在覆盖 `app.getVersion`、`commands.invoke`、`events.listen`、`webview.onDragDropEvent`、`dialogs.open/save`、`shell.open`、`notifications.permission/request/send/onAction`、`window.minimize/toggleMaximize/close/startDragging/focus/requestAttention/isMaximized/onResized/onNativeMenuNavigate`、`updates.check/prepare/cancel/relaunch` 等 Tauri 能力。 - 已运行 focused tests:`desktopRuntime.test.ts`、`desktopHost/contract.test.ts`、`terminal.test.ts`、`previewBridge.test.ts`、`previewEvents.test.ts`、`appZoom.test.ts`、`desktopNotifications.test.ts`、`updateStore.test.ts`、`adapterStore.test.ts`、`settingsStore.test.ts`、`AppShell.test.tsx`、`WindowControls.test.tsx`、`ComputerUseSettings.test.tsx` 全部通过。 - 继续迁移了 dialog/shell/window/menu 组件层、file drop webview、official login/open-with 等打开路径:`DirectoryPicker`、`composerAttachments`、`TerminalSettings`、`ComputerUseSettings`、`Settings`、`Sidebar`、`TabBar`、`WindowControls`、`AppShell`、`WorkspaceFileOpenWith`、`AssistantMessage`、`AssistantOutputTargetCard`、`CurrentTurnChangeCard`。 - 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:dev`、`electron:build`、`check: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.ts`、`preload.ts`、`ipc/channels.ts`、`ipc/capabilities.ts`、`ipc/capabilities.test.ts`、`electron/tsconfig.json`、`scripts/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.cjs` 与 `electron-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_DIR`、`CC_HAHA_APP_PORTABLE_DIR`、`ADAPTER_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.ts` 和 `serverRuntime.ts`;Electron main 在 ready 阶段启动 server sidecar,`runtime.getServerUrl()` 通过 IPC 返回动态 loopback URL。 - `sidecarManager` 复用现有 `desktop/src-tauri/binaries/claude-sidecar-`,server 参数保持 `server --app-root --host 0.0.0.0 --port `,adapter 参数保持 `adapters --app-root --feishu/--telegram/--wechat/--dingtalk`。 - 环境变量保留 `CLAUDE_CONFIG_DIR`、`XDG_CACHE_HOME`、`CLAUDE_H5_AUTO_PUBLIC_URL`、`CLAUDE_H5_DIST_DIR`;adapter 注入 `ADAPTER_SERVER_URL=ws://`。 - 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] `openFile`、`openDirectory`、`saveFile` 映射到 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.ts` 与 `shell.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.ts`、`dialogs.test.ts`、`electronHost.test.ts`、`desktopHost/contract.test.ts`、`WorkspaceFileOpenWith.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-dialog` 和 `plugin-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.ts`、`tray.ts`、`menu.ts`、`singleInstance.ts`。 - Electron main 现在启动前申请 single-instance lock;重复启动会 show/focus 主窗口。 - 主窗口关闭默认隐藏到托盘,托盘菜单提供 `Show Claude Code Haha` 和 `Quit 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_state`、`macos_request_notification_permission`、`macos_send_notification`、`plugin:notification|is_permission_granted`、`plugin: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=` 后,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.app` 和 `SystemUIServer` 均返回 `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=`,仅在显式 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 记录 `scheduled` 与 `sent:true`,证明 Electron main 已向系统发出真实通知。但 Computer Use 仍无法读取 `SystemUIServer` 或 `NotificationCenter.app`(均 `timeoutReached`),日志也未出现 `action`,所以“点击通知回到目标 session”仍未完成。 - 2026-06-01 通知点击当前态再复测:用 canonical packaged app 启动隔离通知 smoke,设置 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_SESSION_ID=notification-click-smoke-session`、`CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_DELAY_MS=8000` 和 JSONL log;Computer Use 关闭主窗口后 app 留在后台,JSONL 记录 `scheduled` 与 `sent:true`。Computer Use 对 `/System/Library/CoreServices/UserNotificationCenter.app/` 返回安全策略拒绝,对 `NotificationCenter.app` 仍 `timeoutReached`,所以仍无法执行真实 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.ts`;`autoDownload=false`,`checkForUpdates()` 只返回 metadata,`downloadUpdate()` 将 electron-updater `download-progress` 转成现有 `DesktopUpdateDownloadEvent`,`relaunch()` 阶段调用 `quitAndInstall(false, true)`。 - 扩展 Electron IPC:`desktop:update:download`、`desktop:update:install` 和 `desktop: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.json` 的 `desktopTerminal` Windows shell 配置;保留 Windows `bash_path`、`COMSPEC`、POSIX `SHELL`、`/bin/zsh`/`/bin/bash` 默认 shell 选择。 - `terminal_spawn` 等价实现最小尺寸限制:`cols >= 20`、`rows >= 8`;默认 cwd 仍按 `cwd -> CLAUDE_CONFIG_DIR -> HOME/USERPROFILE -> process.cwd()`;环境变量合并 login shell env,并强制 UTF-8 locale,补充 `TERM=xterm-256color` 与 `COLORTERM=truecolor`。 - `desktop/electron/main.ts` 已注册 `desktop:terminal:*` IPC:`spawn/write/resize/kill/get-bash-path/set-bash-path`,并通过 `desktop:terminal:output`、`desktop: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---` 后加载,避免嵌套 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.json` 的 `build: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_mode`、`set_app_mode`、`detect_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=1` 与 `WEBVIEW2_USER_DATA_FOLDER=/EBWebView`。 - `desktop:app-mode:get` 返回现有 renderer 形状:`mode`、`portableDir`、`defaultPortableDir`、`activeConfigDir`、`configDirSource`;`configDirSource` 区分 `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.ts` 与 `zoom.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/`。 - [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:build`、`electron:package`、`electron:package:dir`,并将 `build:electron` 扩展为 main/preload/preview-preload 三个 bundle。 - Electron builder 配置包含 `dist/**`、`electron-dist/**`、`src-tauri/binaries/**`、`src-tauri/resources/preview-agent.js` 和 `node_modules/node-pty/**`;`electron-updater` 已打进 main bundle,`node-pty` 保持外部原生模块并通过 `asarUnpack` 解包。 - `check:native` 已从 Tauri `cargo check` 改为 `build:sidecars + check:electron`;`quality: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|arm64` 和 `LINUX_TARGETS` 覆盖,默认构建 AppImage/deb x64;脚本会复制 `.AppImage`、`.deb`、`latest-linux.yml`、blockmap 和 `linux-unpacked` 到 canonical `desktop/build-artifacts/linux-`,并默认运行 `bun run test:package-smoke --platform linux --package-kind release --artifacts-dir desktop/build-artifacts/linux-`。 - `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.app`;`codesign --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_TRIPLE`;`build-sidecars.ts` 暂时兼容旧 `TAURI_ENV_TARGET_TRIPLE`,但 CI 和平台脚本不再使用 Tauri 命名。 - `scripts/pr/change-policy.ts` 将 `desktop/electron/tsconfig.json`、Electron macOS/Windows build scripts 纳入 native/release paths;`scripts/quality-gate/modes.ts` 和 `scripts/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 --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.json`、`Cargo.toml` 或 `Cargo.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.json` 和 `release-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.json` 与 `src/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` 产出 `.app`、`Claude-Code-Haha-0.3.2-arm64.zip`、`.zip.blockmap` 和 `latest-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.asar`、`app-update.yml`、unpacked sidecar 和 `node-pty` 原生模块;脚本默认在 Windows runner 上执行 canonical package-smoke,但本机 macOS 未执行 NSIS 真实构建/安装。 - 2026-06-01 Linux build script hardening:新增 `desktop/scripts/build-linux.sh` 与 `build:linux-x64` / `build:linux-arm64` scripts,canonical 输出现在保留 AppImage/deb、`latest-linux.yml` 和 `linux-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.yml` 和 `app-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.yml` 和 `latest-linux-arm64.yml`,并检查 metadata 引用的 AppImage/deb artifact 是否真实存在。 - 2026-06-01 package-smoke 增加显式 `--package-kind dir|release`:`check:native` 使用 `dir + desktop/build-artifacts/electron`,发布/dev packaging workflow 和平台 release 脚本使用 `release + 精确 artifacts-dir`,避免旧 installer/metadata 污染本轮验包。 - 2026-06-01 Gatekeeper 诊断加固:`--require-macos-gatekeeper` 的 `spctl` 失败时,package-smoke 会同时记录 `codesign --verify --deep --strict --verbose=2`、`codesign -dv --verbose=4` 与 `xcrun 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:` 改为 `--package-kind release --artifacts-dir desktop/build-artifacts/`,避免验到旧的 `--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_CERTIFICATE`、`MACOS_CERTIFICATE_PASSWORD`、`APPLE_ID`、`APPLE_APP_SPECIFIC_PASSWORD`、`APPLE_TEAM_ID`、`WINDOWS_CERTIFICATE` 和 `WINDOWS_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-pty` 和 `spawn-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.blockmap`、`latest-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 `。 - [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.md`,`passed=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.md`,`passed=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.md`,`passed=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_apps` 和 `get_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)` 仍为 `cgWindowNotFound`,`screencapture` 仍失败为 `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.app` 报 `bundle 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.md`,`passed=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.app`,`get_app_state(Finder)` 仍为 `cgWindowNotFound`,`get_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.message`;`shell.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.yml` 或 `latest-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=true`、`dependencies.installed=true`、`permissions.accessibility=true`、`permissions.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.json` 与 `GET /api/computer-use/authorized-apps` 均返回 `authorizedApps[0].bundleId=com.apple.Terminal`,`grantFlags.clipboardRead=true`、`grantFlags.clipboardWrite=true`、`grantFlags.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` 预期失败,但输出现在同时包含 `spctl`、`codesign` 和 `notarization 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 unsuitable`,`stapler` 返回 `does not have a ticket stapled to it`。package-smoke 现已在 `spctl` 报 `Too 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 记录 `check`、`download-start`、`download-finish`、`quit-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-create`、`after-initial-show`、`did-finish-load` 和 `after-final-show` 写入窗口快照。早前同一 packaged dir 启动日志显示 Electron `BrowserWindow` 已 `visible: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=` 后,packaged app 的 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG` 正常写入 `scheduled` 和 `sent:true`。Computer Use 对 `/System/Library/CoreServices/NotificationCenter.app` 和 `SystemUIServer` 仍返回 `timeoutReached`,Finder 状态读取返回 ScreenCaptureKit stream error,JSONL 未出现 `action`;因此“点击通知回到目标 session”仍保持未完成。 - 2026-06-01 通知点击再次复测:用 `launchctl setenv` 启动 worktree packaged app 后,JSONL 正常写入 `scheduled` 和 `sent: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_LOG` 的 `renderer_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 unsuitable`,`codesign --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=` 启动当前 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 记录 `scheduled`、`sent:true`、`synthetic_action` 和 `renderer_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:`,用当前平台 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.ts` 的 `registerHandler()` 在进入 handler 前执行 `isElectronIpcChannel()` 和 `validateElectronIpcPayload()`;renderer `electronHost` 也在 preload bridge 调用前执行相同 payload validation。 - REST/WebSocket 主干复核通过:session/workspace/team 仍在 `desktop/src/api/sessions.ts`、`desktop/src/api/teams.ts`、`desktop/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 阻塞。