# RECIPE:在「应用」菜单里加「刷新 / 重启」(给 AI 的照抄步骤) > 目标:让 DSH 桌面端左上角「应用」下拉菜单里出现 > `刷新页面`(role: reload)与 `重启应用与 Host`(app.relaunch + quit)。 > > 这两个功能**本来就在代码里**,只是被开发构建开关挡住。所以这不是"新增功能", > 而是"把已有的两项从 dev-only 变成始终可见"。全程只改 **11 个字节 + 2 个哈希字符串**。 ## 0. 先确认事实(别猜) | 问题 | 答案 | | --- | --- | | 「应用」按钮是谁画的? | Electron 桌面壳:`app.asar:/lib/preload-app.cjs` → `installWindowsMenu()`,注入 `[data-windows-menu]` 影子 DOM,点击时 `ipcRenderer.invoke('dsh-desktop:windows-menu','application',x,y)` | | 菜单条目是谁拼的? | 主进程:`app.asar:/lib/main.js` → `applicationItems()`,`Menu.buildFromTemplate(...).popup(...)` | | 那两项在哪? | 同一个函数里,被 `...development ? [ ... ] : []` 包着;`development = !app.isPackaged`,安装版恒 `false` | | 为什么不能做成 Web 插件? | 菜单在主进程;页面没有往原生菜单加条目的 IPC 通道。插件只能在自己的 DOM 里画按钮,画不进这个菜单 | ## 1. 找到 app.asar Windows 安装版:`<安装目录>\resources\app.asar`(本机示例 `E:\Deepseek Harness\resources\app.asar`)。 从运行中的主进程可执行文件向上找 `resources/app.asar` 即可;`dsh web`(纯浏览器版)没有这个文件。 asar 结构:前 16 字节 = pickle 头(`[4..8]` 头长度,`[12..16]` JSON 长度), 随后是 JSON 目录,`base = 8 + 头长度`,每个条目的 `offset` 相对 `base`。 目标条目:`header.files.lib.files["main.js"]`(约 500 KB)。 ## 2. 定位开关 在 main.js 的字节内容里搜索(区分大小写、只应命中 **1 次**): ``` ...development ? [ ``` 上下文长这样: ```js ...development ? [ { type: "separator" }, { label: ...reloadPageMenu, role: "reload" }, { label: ...restartAppHostMenu, click: () => { app.relaunch(); quitWithoutConfirmation() } }, ] : [], ``` ## 3. 等长替换 ``` development (11 字节) !(0 === 1) (11 字节) ← 注意 0 和 === 之间有 2 个空格 ``` 替换后:`...!(0 === 1) ? [ ... ] : []` —— 恒真,数组照常展开,语义不变。 **两段必须长度相同**:长度一变,asar 头的 `json_len`、后面所有文件的 `offset`、 归档总大小都会错位,等于要重打包。 ## 4. 同步 integrity 哈希(必须做) asar 头里 `/lib/main.js` 的 `integrity.hash`(以及同文件的 `blocks[0]`,共 **2 处**) 存的是该文件的 SHA256。改完内容后: 1. 对新 `main.js` 内容算 SHA256 → 64 位十六进制; 2. 在**头部 JSON 的原始字节**里,把旧的 64 位哈希等长替换成新的(别动长度); 3. 主文件内容按原 `offset` 写回。 Electron 的 asar 完整性校验(`EnableEmbeddedAsarIntegrityValidation`)只在 macOS 强制, Windows 上不同步也能跑;但同步掉更稳,也让归档自洽。 ## 5. 生效 `main.js` 只在主进程启动时加载 → **必须重启桌面端**: - 托盘菜单 → 退出,再打开 `DeepSeek Harness.exe`;或 - 重启后点「应用」→ 应看到:关于 / 检查更新… / 管理 dsh Command… / ─── / **刷新页面** / **重启应用与 Host** / ─── / 退出。 ## 6. 回滚 把 11 字节换回 `development`,哈希再算一次换回即可(本插件会留 `patch-record.json`)。 ## 常见坑 1. **不能用 Node 的 `fs` 直接打开 app.asar**:宿主是 `ELECTRON_RUN_AS_NODE` 的 Electron,`fs` 被 asar 感知层接管,会报 `ENOENT, not found in `。用 `original-fs`(`createRequire(import.meta.url)` 同步取)。 2. **别用顶层 `await`**:插件模块求值必须同步完成,否则入口会卡在 `loading`,web boot 失败。 3. **别重打包**:等长替换就够,重打包容易破坏 asar 头/偏移。 4. **别用外部进程自杀式重启**:PowerShell 助手在这台机器上被火绒「系统加固」的 `隐藏执行PowerShell` 规则拦截;`taskkill /T` 还会把助手自己(Electron 主进程 → 宿主 → 助手)一起杀掉。 原生菜单的 `app.relaunch()` 才是可靠的重启路径 —— 这也是为什么只要打补丁,不要做插件重启按钮。 5. **桌面端更新会覆盖 app.asar**:更新后重新跑一次 apply。 ## 直接调用本插件 ```text dsh_desktop_menu({ action: "status" }) # 只读:asar 路径、补丁是否在位、哈希是否自洽 dsh_desktop_menu({ action: "apply" }) # 写入(幂等) dsh_desktop_menu({ action: "revert" }) # 回滚 ``` 不想依赖插件时,`docs/standalone/` 里有等价的 Python 脚本 (`apply_patch.py --dry-run/--apply/--revert`、`verify_patch.py`、`inspect_target.py`), 逻辑与插件里的 `lib/desktop-menu.js` 完全一致。