## v1.19.6(正式版 + Lite 共存版 + 兼容版 · 2026-10-05) > ⚠️ **这是 v1.19.1 之后的第一次发布**,内容覆盖 **v1.19.2 + v1.19.3 + v1.19.4 + v1.19.5 + v1.19.6** 五级 > (中间四级从未单独发布过)。 > **一句话**:**AI 浏览器有了自己的独立进程**(页面崩了不带走控制台和引擎)、**能同时开好几个页签**, > 控制台多了一个**「AI 浏览器」页**(看它正在哪一页、把画面同屏显示出来),**浏览器插件可以随时开关**; > 控制台「全定制」四层 + 导入导出往返**全部真机测过**,主控台与八个细页面统一了版式。 > versionCode 仍 **54**,内核仍 **DSH 0.2.0-rc.2**;同签名覆盖安装,**配置 / 会话 / 插件都不丢**。 ### 一、AI 浏览器搬进独立进程 `:browser`(v1.19.6) 以前浏览器跑在主进程里 —— 页面一崩,控制台和引擎跟着一起遭殃。现在它有自己的房子: | 项 | 说明 | |---|---| | **独立进程** | 浏览器跑在 `:browser` 进程(`BrowserService` + Messenger IPC,主进程只在 `/browser` 路由处转发);真机 `ps` 里能看到 `com.deepseek.harness:browser` 与主进程**不同 pid** | | **承载方式** | 进程内一扇 **1080×1920 不可见悬浮窗**(`TYPE_APPLICATION_OVERLAY`,不可聚焦 + 触摸穿透);容器口径与搬进程前**逐像素一致**(快照视口仍是 540×960) | | **隔离与代价** | ⚠ 依赖「悬浮窗」权限(没有就回一条明确错误并提示去控制台「权限」页授权,**不静默降级**);子进程用 `setDataDirectorySuffix("browser")` 与主界面**分开 WebView 数据目录**(Android 硬约束:同 App 两个进程不能共用同一个数据目录),顺带白拿一层 cookie / 存储隔离 | | **一键整套丢弃** | 新 op `process.kill`:先回信、再自杀 —— 子进程消失、**主进程与引擎不受影响**,下一次操作自动拉起全新进程 | | **对外契约零改动** | 插件侧 10 个浏览器工具、AI 的用法**一行没改** | ### 二、多页签(v1.19.6) - 新增第 10 个工具 **`browser_tabs`**:`list / new / switch / close`;页签列表带标题、`tabId`、地址、当前标记。 - **跨页签点错会被拦下**:每个 ref 记住它属于哪个页签,动作自动带上 `tabId`;拿 A 页签的 ref 去点 B 页签 → 回 **`stale-tab`** + "先 switch 过去"的提示(不再静默点错)。 ### 三、控制台「AI 浏览器」页 + 插件可开可关(v1.19.6) 主控台「系统」组多了一行入口,点进去是浏览器页: - **状态卡**(点一下 = 刷新):`运行中 · N 个页签 · 同屏中`;**页签列表**;**刷新 / 关闭浏览器**(走确认框)。 - **「同屏查看」开关**:把 AI 浏览器的画面显示在屏幕上 —— **只读,能看不能点**(触摸穿透到下面); **只切可见性、不动窗口尺寸**(AI 的点击坐标口径依赖它);还没有浏览器时点同屏**只回 `no-browser`**,**不会建一扇全屏空窗盖住屏幕**。 - **插件可开可关**(路线图③):`dsh-tool-browser` 进控制台「插件」页(默认启用)——关掉则 AI 不能再上网; 改完**立刻重绘**,页头计数与行状态跟着变(原来是只改控件、计数停在旧数字);关着时浏览器页多一行提示,整行可点直达「插件」页。 - **控制台八个细页面统一版式**(v1.19.6):主控台(状态块 + 会话/系统/诊断三分组)、权限 / 插件 / 日志 / 主题 / 内核自检 / 会话管理 / 回收站 / 会话修复 —— 圆角卡片行 + 右列短状态 + 「›」、说明走统一段落、不可逆动作单独红框、长标题自动截断; 自愈账本从"一段灰字"收进卡片行(右列「看账本 ›」)、AI 修复提案区改成一行一条。 ### 四、修掉的真缺陷(v1.19.6) | # | 症状 | 根因 / 修法 | |---|---|---| | 1 | **插件开关改完,页头「已启用 N / M」停在旧数字** | 开关后只改控件自己、没重绘 → 补一次重绘;关着时浏览器页补一行提示 | | 2 | **长正文弹窗把动作按钮挤出屏幕**(会话修复详情里「取消 / 确认」两个按钮直接不存在,弹窗卡死只能按返回键逃) | 弹窗卡片被硬截到屏幕 80%,竖直布局**裁掉排在最后的子 View**(正是按钮行)→ 正文一律包进 `ScrollView`,80% 兜底改成**只压缩正文区** | | 3 | **按钮被挤成"扁色块、没有文字"** | `cButton()` 只钉了最小宽度没钉最小高度 → 补最小高度 | | 4 | **深色主题下日志弹窗"浅底浅字"看不见** | 控制台里两套"深色"定义混用(配色方案 vs 系统深浅色)→ 三处漏用的底色统一到主题配色;像素判据对比差 **6 → 217** | | 5 | **只写文案 / 布局 / 按钮、不写 `appearance` 的 `console.json`,后三层被静默丢掉** | 解析器"没有 `appearance` 就直接 return",而 `layout` / `text` / `actions` 全在那个 return 之后 —— 界面还显示"主题:… · 状态:已生效",实际一个字都没生效(**离线校验器也判 0 错**,只有真机比界面才看得见)→ 改成"当成没有外观覆盖,继续解析后面的层" | | 6 | **跨页签点击静默落到错的那一个页签** | 见 §二(`stale-tab` 闸门从工具面够不到) | | 7 | **控制台换页继承上一页的滚动位置** | 主控台滚到底再进主题页,落点在页面中段、第一行根本不在可视区 → 记住上次渲染的页号,换页就归零滚动 | - **顺带加固**:控制台每秒刷新链加 try/catch + 照常续链 —— 原来 `conThemeTick()` / `refreshConsole()` 一旦抛异常, **整条每秒刷新链永久死掉**,之后改 `console.json` 再也不热重载,只能重启 App(防御性改动,不是已证实的缺陷)。 ### 五、已知限制(如实列出) - **自定义动作没接执行**:`http` / `intent` 只弹 toast「还没接」,`prompt` 退化成"复制到剪贴板";`behavior`(自动解压 / 自动起引擎 / 双击退出)**只解析、不执行**; 导入目前是"点击时确认",**还没有**逐个确认 + 内容哈希;动作执行不写日志页。界面与 SPEC 都**如实说明**,不是谎报。 - **AI 浏览器独立进程依赖「悬浮窗」权限**(没有时不静默降级,会明确报错并提示去授权);**同屏是只读的**(能看不能点)。 - **`layout.order` / `hidden` / `detailOpen` 与 `actions[]` 自定义按钮,在默认(精简)界面下不生效** —— 精简主控台是硬编码骨架, 不走 `conCardsOrder()`;它们在 `layout.style:"classic"` 或 `layout.pages` 整页自拼下照旧生效(**已知语义,不是本版回归**。 `appearance.logo` 同理:默认界面下看不到)。新增的 `browser` 卡片**只有走 `layout.pages` 积木时才会出现**。 - **`theme_pack_check.py` 遇到 `layout.style` 会 `TypeError` 崩掉**(v1.19.6 已修 + 补自测用例⑥)—— 装旧版工具链的人会遇到;自测 6/6。 - **时间竞态**:`console.json` 先落地、图片后到,若 App 恰好在两者之间 reload 一次,背景图会被判"引用不存在"并记一条「已回退」,图片后到**不会**再触发 reload。 - **Lite / compat 的包**:本轮源码与补丁是四份同步的,但 **Lite / compat 产物需按老流程重编后装机核对**(versionCode 没变 → 装机后设备**不会自动重解压**, 控制台「环境」→「重新解压」一次即可)。 ### 六、判据(怎么验的) | 层 | 脚本 | 期望 | |---|---|---| | 包内 | `verify-v1193.sh build` | **93 PASS / 0 FAIL**(含控制台浏览器页 / `panel.state` / `no-browser` / 插件表 / SPEC 同步 10 条新闸门) | | 控制台浏览器页 | `device-console-browser.ps1` | **38 PASS / 0 FAIL** | | 独立进程 | `verify-browser-process.ps1` | **16 PASS / 0 FAIL**(含"服务已销毁 + 承载窗已摘 + 主进程不受影响") | | 只读同屏 | `verify-browser-panel.ps1` | **7 PASS / 0 FAIL**(含"视口 540×960 前后一致"防呆) | | 多页签直连 | `tabs-direct.mjs` | **20 PASS / 0 FAIL**;AI 工具面七步全过(核心第 4 步实测回 `stale-tab`) | | 插件开关端到端 | `verify-plugin-toggle-e2e.ps1` | **11 PASS / 0 FAIL**:引擎工具表 **73 个(10 个 `browser_*`)→ 63 个(0 个)→ 72 个(10 个)** | | 细页面整页 | `device-console-v6.ps1` | **31 PASS / 0 FAIL** | | 全定制四层 | `verify-console-4layer.py` | **16 PASS / 0 FAIL**(差分:`compact` 行距 145px vs 159px;删掉主题后背景色采样点 87268 → 101) | | 导入导出往返 | `verify-console-roundtrip.py` | **37 PASS / 0 FAIL**(导出 → 删掉 → 再导入:`console.json` 与 4 张图片**逐字节还原**) | | 会话 / 自愈(离线) | `test-admin.mjs` / `test-heal.mjs` | **PASS=32** / **PASS=24** | > **本轮判据总表**:`tmp-diag/v1180/本轮判据总表.md`(第五轮)+ `tmp-diag/v1180/全量回归结果.md`(第六轮全量回归)。 > 全部落在**同一个产物**上:official APK md5 **`f021d20dd1a5a4c7cf4ac31ad5fe0610`**(158,086,925 B · javac 397 类 · 包内 93 PASS / 0 FAIL · 设备端逐字节一致)。 > 全量回归总入口:`powershell -File run-regression.ps1 -Serial <串号>`(8 条离线 + 11 条真机 → `全量回归结果.md`)。 > > **第六轮又修掉两个真缺陷**(`patch86`,四份同步): > ① 🔴 一份**没有 `appearance`** 的 `console.json`,`layout` / `text` / `actions` **会被整层丢掉** > (主题页显示"已生效",界面却一字不变 —— `ConsoleTheme.parse()` 里一句 `if (av == null) return;` 干的)。 > 现在没有 `appearance` 就按"没有外观覆盖"处理,继续解析后面的层。 > ② 控制台**换页会继承上一页的滚动位置**:从主控台底部点进任何一页,落点都在页面中段,第一行看不见。现在换页回到页首。 > ③ 加固:控制台每秒刷新链一旦抛异常会**永久死掉**(之后改 `console.json` 再也不热重载)→ 现在异常吞掉并续链。 --- ## v1.19.5(正式版 + Lite 共存版 + 兼容版 · 2026-10-05) > **一句话**:**主控台默认界面按「方案 B」重做** —— 原来"东西多了很杂很乱"的卡片平铺,变成 > **一块状态 + 三个分组**;会话管理 / 会话修复**在首页就能点到**,页脚收成一行三个等宽按钮。 > versionCode 仍 54,内核仍 **DSH 0.2.0-rc.2**。 ### 一、主控台默认 UI 重做(方案 B) - **状态块**:把「运行环境 + 引擎」**合成一张卡** —— 大字状态 + 两行说明 + 一个随状态变的主按钮 + 重启 / 停止 + 进度条 + 详情; 没解压时讲"运行环境",解压好了讲"引擎"(不再同一屏出现两套引擎状态)。 - **三个分组**:**会话**(会话管理 / 会话修复 —— 原来藏在「内核自检」页里,两层深)、**系统**(运行环境 / 权限 / 插件 / 日志 / 主题)、**诊断**(内核自检)。 - **页脚**:`救援 / 检查更新 / 界面主题` 三个**等宽按钮**一行(原来是"救援标签 + 长按钮 + 独立主题行",挤成三行)。 - **状态栏避让**:顶部内边距不再固定 24dp —— 真机上品牌字曾被状态栏压住。 - 用户拍板的取舍:**界面不露"回到完整版"的按钮,但主题配置里还能选** —— `layout.style:"classic"` 仍然有效。 ### 二、规范补齐两件(v1.19.5) - **`selfcheck` 卡片补登记**(v1.19.x 漏的规范缺口,四处一起改):主题里写 `layout.order: ["selfcheck", …]` 曾经会被判"未知卡片 id"并**整份回退**、还不告诉你为什么;现已登记,并放进"**可排序、不可隐藏**"(它是自修复的唯一入口)。 - **8 个新文案键**登记四份 SPEC(分组标题 / 会话管理 / 会话修复 / 救援按钮等)。 ### 三、备份完整性:导出已验证(v1.19.5) - 实测导出 `DSH备份-20261004-225657.zip` = **167 MB** = **附件实体 95.2 MB / 651 项** + **私有工作区 51.5 MB / 2428 项** + 会话 28.1 MB / 25 条。 - **意义**:附件那 95 MB 以前**完全不在备份里** —— "导出 → 清数据 / 换机 → 导入"之后,每一条带图的会话都会变成"附件实体不存在"、每次请求都失败。 ### 四、已知限制(如实列出) - ⚠ **这一版的 Lite / compat 没重编**(源码已同步,产物还是旧的);**新主控台当时还没真机验过**(在 v1.19.6 里补验)。 - **重装 APK 后首次启动有一段忙期(实测约 10 分钟)**:这期间的点击会被**延迟甚至丢弃**("点了没反应"多半是它); 冷启动后立刻跑扫描 / 起引擎也极慢(首次 160 秒、有一次 >5 分钟;等 App 稳定后同一操作只要 2~3 秒)。 - 本版**尚未修**、在 v1.19.6 才修掉的:不写 `appearance` 的 `console.json` 后三层被丢掉;换页继承滚动位置;「恢复默认」没有确认框(一次误触就把主题收走,真机上真发生过)。 - 备份**导入**在当时还是"用户实测报导入失败、根因未查明"的状态(logcat 里那条 `Read-only file system` 是系统媒体扫描的,与导入无关;数据没被破坏)。 ### 五、判据(怎么验的) | 层 | 脚本 | 期望 | |---|---|---| | 编译快检 | `check-java.ps1` | javac OK · **371 类**(official / Lite) | | 包内 | `verify-v1193.sh build` | **75 PASS / 0 FAIL** | | 主题工具链 | `test-checker.py` | **5 用例全过**;`v195-abilities.zip` / `v195-pages.zip` 均 **0 错 0 警** | | 会话(离线) | `test-admin.mjs` / `test-heal.mjs` | **PASS=32** / **PASS=24** | | 真机 | `device-console-v2…v5.ps1` + 人工复核 | 新主控台:**无「完整版」按钮**、有状态块、三分组在、系统组第一行「运行环境」、页脚三按钮一行;「运行环境」弹窗三个按钮都在 | --- ## v1.19.4(正式版 + Lite 共存版 + 兼容版 · 2026-10-04) > **一句话**:**App 里第一次能删会话了**(删除 = 先进回收站,随时捞得回来;"彻底删除"只在回收站里、单独红框确认), > 同时**备份终于把图片一起带上了** —— 附件实体 95 MB + 私有工作区 51 MB 实测进包(导出 167 MB)。 > versionCode 仍 54,内核仍 **DSH 0.2.0-rc.2**。 ### 一、会话管理:终于能删会话了 > 用户原话:「这个软件不能删会话,删不了会话,这是个问题,**加一个页面,专门删会话**。」 - 控制台新增**会话管理**页:列表(分批 30 条)· 每行标题 + 元信息 + 「删除」;删除前弹确认框,**明确写出删的是哪一条** (标题 / 大小 / 行数 / 最后活动 + "会做什么 / 不会做什么")。 - 新增**回收站**页:`N 条 · X MB`,每条带**原位置**;「恢复」一键放回原位;「彻底删除」只在这里提供且每次单独红框确认(文案明说不可恢复);也可清空回收站。 - **安全边界(写死在代码里)**:删除 = **移到** `/sessions-deleted/`(回收站,在 `sessions/` **之外**,内核扫不到),**不是 `rm`**; 越界请求在 Java 与脚本两侧**各拦一次**;每个条目写 `.origin` 记住原 slug(恢复才能放回原位);删除 / 恢复 / 彻底删 / 清空**各写一条自愈账本**。 - **另一条硬纪律**(这一轮再次确认):**任何"修复 / 清理"都不许靠删掉整条会话解决** —— 会话只能攒着;修复只降级那一条消息里的那个坏块。 ### 二、备份完整性:附件实体 + 私有工作区(两处缺口,都补上) | 缺口 | 以前的后果 | |---|---| | **附件实体**(`dshhome/attachments/**`)不在备份里 | 会话历史存的是附件**引用**(`sha256:…` → `attachments/v1/objects/…`)→「导出 → 清数据 / 换机 → 导入」之后,**每一条带图的会话都变成"附件实体不存在"、每次请求都失败** —— 备份功能在**批量制造**坏会话 | | **私有工作区**(`` 下除 `payload` 与 `tools` 之外的内容)不在备份里 | 真实会话的 `cwd` 就是这里(12 处);AI 写的 `ai-chat.html` / `gh.mjs` / `tmp/` / `.certs` … 一个字都没进备份 | - 导入侧顺带**如实报数**:「(含 N 个附件实体;含 M 个工作区文件)」;遇到旧备份会明确提示"这份备份里没有附件实体 —— 若会话里的图片打不开,去「会话修复」跑一次扫描"。 - ⚠ **旧备份仍然缺这两块**(格式如此,改不了)→ 所以那句提示是必须的,不装作没事。 ### 三、已知限制(如实列出) - **备份"导入"仍然失败**(用户实测报过):根因未查明,零风险探针包 `probe.zip` 已备好但没跑完(看 App 侧 `Log.e(TAG, "backup import", t)`)。 - 备份**混进了可重建的缓存**(`.cache` 3.3MB、`.npm` 3.8MB、`.npm-global` 1MB、`.local` 2MB、`1/.dsh-tmp` 6.5MB,以及套娃的旧备份 `dshhome.tgz` 25MB)→ 排除后可从 167MB 降到 ~120MB(**未做**)。 - **公共存储目录 `/sdcard/DeepSeekHarness/` 仍不在备份里**(含 `console/` 主题、`heal-ledger/` 账本、`work/`、截图):清数据不会删它,但**换机 / 手动删会丢**。 - **会话修复列表在修复后不会自动刷新**(要点一次「重新扫描」);`selfcheck` 卡片当时还没登记进规范(v1.19.5 补的)。 ### 四、判据(怎么验的) | 层 | 脚本 | 期望 | |---|---|---| | 会话管理(离线) | `node tmp-diag/v1180/test-admin.mjs` | **PASS=32 / FAIL=0**(含 **6 项越界拒绝**;删除后内容逐字节不变、恢复回原 slug、彻底删只删回收站里的) | | 备份条目体检 | `check-backup-entries.py` | 0 逃逸 / 0 超长 / 0 非法 / 0 重复 | | 包内 | `verify-v1193.sh build` | **75 PASS / 0 FAIL** | | 真机 | `device-session-admin.ps1` | 读列表 `25 条 · 4.9 MB` → 删一条(账本 `…-session-trash.json`,被删的是 `打开抖音 然后给Y发消息 发鸡 / 1.0 MB · 5309 行`)→ 条数自动变 **24** → 回收站 `1 条 · 1.0 MB` 带原位置 slug → 恢复(账本 `…-session-restore.json`)→ **回收站空、列表回到 25 条,数据一条没丢** | | 导出实测 | 真机 22:56 | `DSH备份-20261004-225657.zip` = **167 MB** = 附件实体 **95.2 MB / 651 项** + 私有工作区 **51.5 MB / 2428 项** + 会话 28.1 MB / 25 条 | --- ## v1.19.3(正式版 + Lite 共存版 + 兼容版 · 2026-10-04) > **一句话**:**坏了能自己修**(自修复③)—— 被坏附件毒死的会话,现在能**只降级那一条消息里的附件、保留正文与上下文** > (先备份 → 写盘 → 立刻复检 → 写账本),真机上**修好了用户一条真实坏会话**;「我保存过的主题」也从三层弹窗收成**一个弹窗 + 可清空记录**。 > versionCode 仍 54,内核仍 **DSH 0.2.0-rc.2**。 ### 一、会话级自愈(自修复③的写路径) > **为什么需要**:坏附件(例如文件头合法、IDAT 已损坏的 PNG)一旦作为 `tool/result` 进历史,**每次请求都会重新读它、每次都以同样方式失败** —— > 以前用户只能放弃整条会话。 - 控制台新增**会话修复**页:**扫描**(只读,2~3 秒出结果)→ 列出问题会话(每条多少处坏附件)→ 详情(坏在**哪一条**、哪个文件、为什么)→ **红框确认** → 执行 → 结果 + 账本。 - 修法**最小且可证伪**:会话是**多帧 zstd**(实测每帧都落在完整 JSONL 行边界上)→ 只重写含坏消息的那一帧,**未命中帧字节原样**; 只把那一处 image / file 块**换成一行文字**,正文与上下文一字不动。 - **写死的边界(代码里,不得绕过)**:只处理**用户点选的那一条**(没有"扫全目录批量修");**引擎在跑时不允许改写**(先停引擎);**写盘前必须备份**(同目录 `.corrupt-`,备份失败即中止); 写完**立刻复检**(坏引用必须归零、行数与 JSON 合法性必须不变,否则如实报"部分完成");**默认 dry-run**,只有显式 `--apply` 才落盘。 - **真机证据**:修好 `session-32bb8195`(4 处)→「已降级 4 处坏附件 · **复检坏引用 0 · 行数 67 → 67** · JSON 损坏 0」, 备份 `session.jsonl.zstd.corrupt-20261004133830`,账本留证(`framesRewritten:4`、`bytesBefore:30214 → bytesAfter:30750`); 重新扫描:**3 条 38 处 → 2 条 34 处**,引擎启动后**无任何附件相关错误**。 ### 二、主题记录:一个弹窗解决(用户明确要求) > 用户原话:「我保存过的主题那个功能 不能清空记录,而且弹窗还要弹好几个,**一个弹窗解决**」。 - **一个自绘弹窗**:每行 = `名称 · 类型 · 时间 · 大小` + 行尾「恢复」,**点一下直接恢复**(恢复本身可逆 —— 覆盖前会把当前这份也留底,所以不再套第二层确认)。 - 弹窗内**「清空记录」**:红框确认 + **二次确认**,只删 `console.json.disabled-*` / `.replaced-*` / `.bak*`,**绝不动正在用的 `console.json`**。 - 用户本人在真机上点了「确认清空」→ 账本 `20261004-214226-theme-backup-clear.json`:`deleted:5 / failed:0 / bytes:7516`,5 个文件名逐个留证。 ### 三、护栏演示:同一故障最多 2 次、第 3 次只报告(真机铁证) - 同一份源指纹上第 3 次点「执行」→ 弹窗直接拦下:**「已经修过两次了 / 同一份源指纹(a03f6e203c96)上的修复动作已经执行过 2 次… 同一故障最多自动执行 2 次,第 3 次只报告不执行」** —— 防止"越修越坏"。 ### 四、已知限制(如实列出) - **设备上还有 2 条坏会话没修**(`session-33198717` 6 处、`session-f937bcba` 28 处 / 1 MB):功能已验证可用,**修不修要问用户**(那是他的会话数据)。 - **公共存储目录 `/sdcard/DeepSeekHarness/` 不在备份里**(含 `console/` 主题、`heal-ledger/` 账本、`work/`、117 张截图)——清数据不会删它,换机 / 手动删会丢;要不要加,下轮再议。 - **会话修复列表在修完后不会自动刷新**(要点「重新扫描」;"上次修复"那一行能看到结果,所以不算误导)。 - ⚠ **Lite / compat 未重编、未归档、未发布**;`README.md` 的插件表当时还只有 4 个(缺 `dsh-tool-browser`)。 ### 五、判据(怎么验的) | 层 | 脚本 | 期望 | |---|---|---| | 离线 | `node tmp-diag/v1180/test-heal.mjs` | **PASS=24 / FAIL=0**:在真实毒会话(`session-f937bcba…`,1 MB / 3172 帧 / 28 处附件引用)上合成三种坏法(实体缺失 / CRC 字段坏 / IDAT 数据坏 / 大小不符),验分类准确、dry-run 不落盘、备份逐字节一致、复检归零、**3145 个未命中帧字节原样**、幂等 | | 包内 | `bash tmp-diag/v1180/verify-v1193.sh build` | **50 PASS / 0 FAIL**(在 v119x 的 27 条上补:`SessionHeal` / `session-heal.mjs` / `conSessionScan` / `conSessionHealRun` / `conDialogDanger` / 三处中文串 / `conThemeBackupRow` / `conThemeClearBackupsNow` / 脚本 11 项内容判据 / 旧弹窗已删) | | 产物 | official APK | `158,049,991 B` / md5 `7ec494f071770823ebbb7a91249f6f33` / class **341**;装机后设备端 md5 **逐字节一致** | | 真机 | `device-session-heal-run.ps1` + `device-heal-third-time2.ps1` + `device-theme-backups-ui.ps1` | 扫描 `25 条 · 其中 3 条有坏附件(共 38 处)`;修复一条(**行数 67 → 67**);单弹窗列出 5 份 + 清空二次确认;③ 第 3 次被拦下 | --- ## v1.19.2(正式版 + Lite 共存版 + 兼容版 · 2026-10-04) > **一句话**:控制台默认界面**收成"精简版"**(不再卡片平铺、"东西多了很杂很乱"),并给它一个**能切回完整版的开关**; > 顺带解决了三个用户反馈的小痛点:**恢复默认主题后找不回自己的定制**、**自检没有进度条**、**`**读**` 的星号直接显示在界面上**。 > versionCode 仍 54,内核仍 **DSH 0.2.0-rc.2**。 ### 一、控制台默认 UI 精简(`layout.style`,用户说"太粗糙、太杂") - 新的精简版主控台(缺省):品牌 + 版本一行 → **运行环境**状态块 → **引擎**状态块 → 一个随状态变的主按钮 → 重启 / 停止 → 权限 / 插件 / 日志 / 主题 / 内核自检 几行入口 → 页脚(版本 + 救援 + 检查更新 + 界面主题)。 - **风格只是一个开关**:`layout.style` = `simple`(缺省)/ `classic`(原样);**classic 分支代码一行未改** —— `pages` / `order` / `hidden` / `text` / `actions` / `appearance` / 积木照旧;`layout.pages`(整页自拼)**优先级最高,不受影响**。 - 顶部内边距**避让状态栏**(原来固定 24dp,真机上品牌字被状态栏压住)。 ### 二、主题可恢复:恢复默认不是"一去不回" - 主题页新增 **「我保存过的主题(N)」** 入口 + 一键恢复(只在真有备份时出现); `conThemeReset()` 额外再存一份 `console.json.replaced-`(**动手前先把当前这份留底**),恢复后写账本。 ### 三、内核自检:进度 + 取消(用户说"加个进度条吧") - **分段进度**:预扫清单拿总数 → 遍历时每 128 个文件回调一次 → 界面上自绘进度条 + `已处理 N/M · 阶段 · 已用 Xs` + **可取消**。 - **取消是协作式的**:取消后**如实**返回 `verdict=cancelled`,**不假装"检查过全部"**。 ### 四、修掉的缺陷 - 🔴 **自检页占页号 5,而自定义页也从 5 起编号 → 会顶掉用户第一张自定义页**:改用**负页号哨兵** `PAGE_SELFCHECK = -100`(与所有 `>= 0` 的页号结构上不可能冲突)。 - **界面文案里 `**读**` 直接显示成星号**(markdown 记号原样露出):去掉用户可见文案里的 markdown 记号,并加显示层兜底 `uiPlain()`。 - **"切过去回不来"**:精简版右上角点一次「完整版」会把偏好固定成 classic,而完整版里**没有切回来的入口**(单向开关缺陷)→ 判定顺序改为"**用户点过的偏好 > 主题 `layout.style` > 缺省 simple**",并在「界面主题」弹窗里给两行 + ✓,双向可切。 - 顺带修一个真缺陷:`conRenderedSimple` 只置 true 从不复位 → 主题热切 classic 会把卡片版的状态行**整行藏掉**。 ### 五、已知限制(如实列出) - ⚠ **这一版的 UI 部分当时只做完一半**:细页面(权限 / 插件 / 日志 / 主题 / 自检 / 会话三页)**都还是旧样式** (在 v1.19.5 / v1.19.6 才补齐);Lite / compat 未重编,**未归档、未发布**。 - `layout.order` / `hidden` / `detailOpen` 与 `actions[]` 在**默认(精简)界面下不生效**(精简骨架硬编码、不走 `conCardsOrder()`)—— **已知语义,不是回归**;SPEC 里"适用范围"当时还没写清(v1.19.6 仍未改)。 - 自修复③(会话级自愈)的**写路径尚未做**(只有只读体检工具 `session-audit.mjs`);④"第 3 次只报告"只验证了计数、没做完整演示(v1.19.3 补的)。 ### 六、判据(怎么验的) | 层 | 脚本 | 期望 | |---|---|---| | 编译快检 | `check-java.ps1` | javac OK · **326 类**(official;本轮 +9) | | 包内 | `bash tmp-diag/v1180/verify-v119x.sh build` | **FAIL=0**(27 条) | | 主题工具链 | `test-checker.py` | **5 用例全过**(`style:"fancy"` 会如实报错) | | 真机(风格双向) | `device-style-toggle-test.ps1` | 「界面主题」弹窗里出现「控制台风格 / 精简版 / 完整版 ✓」;点精简版 → 出现 `运行环境` + `引擎` + 右上角 `完整版`;点完整版 → 切回 `已解压` + `救援` | | 真机(观感) | 截图 `ui-simple-final.png` | 顶部不再被状态栏压;`[运行环境] 已解压 / 28,402 个文件 · 208 MB`、`[引擎] 在睡觉 / 点「启动引擎」开始 · 端口 3080`;页脚 `救援 / 检查更新 / 界面主题` | | 真机(主题可恢复) | 主题页 | 显示 `我保存过的主题(5)` + `可恢复:有 5 份你用过的配置`;用户主题「软软小白团」md5 `0e580d50549d4754f9c4361834aa8867` 完好 | --- ## v1.19.1(正式版 + Lite 共存版 + 兼容版 · 2026-10-04) > ⚠️ **这是 v1.17.3 之后的第一次发布**,内容覆盖 **v1.18.0 + v1.19.0 + v1.19.1** 三级。 > **一句话**:**AI 有了一个真能操作网页的浏览器**(结构化 DOM + 稳定 ref,不是截图猜)、 > **本地服务全部上了令牌闸门**(安全加固)、**内核出问题能自己说清哪里坏了**(内核自检 + 自愈账本)。 > versionCode **54**,内核仍 **DSH 0.2.0-rc.2**;同签名覆盖安装,**配置 / 会话 / 插件都不丢**。 ### 一、AI 浏览器:让 AI 真的能操作网页(v1.19.0 + v1.19.1) 以前 AI 只能"看截图猜"。现在它有一双手: | 能力 | 说明 | |---|---| | **结构化 DOM 快照** | `browser_snapshot` 返回页面里**语义元素**的表格(ref / role / name / 位置 / 是否在视口内 / 是否禁用),不是 OCR、不是像素猜测 | | **稳定 ref** | 每个元素的身份指纹(`r` + 7 位十六进制,写进 `data-dsh-ref`)**跨快照保持不变** —— 上一轮拿到的 ref 这一轮还能用(同名的自动加 `-2`/`-3` 并标注) | | **真差分** | `browser_snapshot{since:上次代次}` 只回变化(新增/消失/改动/未变计数);页面没变时只花几十 token | | **检索式定位** | `browser_find "登录"` 按文本或角色打分匹配,**命中的元素自动打 ref** → 找到就能点(大页面首选,不必拉全量) | | **动作后置校验** | 点击/输入后回 `changed` + 最小差异 —— AI 当场知道"这一下点没点动",而不是再拍一次猜 | | **读正文** | `browser_read` 直接读 DOM 文本(不是 OCR),超长自动截断 | 工具共 9 个:`browser_caps / open / snapshot / find / click / type / read / nav / scroll`。 **安全边界(写死在代码里)**:只允许 `http/https`;**禁止访问本机回环地址**(`127.0.0.1`、`localhost`、 `127.1`、`2130706433`、`0x7f000001` 等变体一律拒)—— 免得 AI 浏览器打到 App 自己的本地服务。 ### 二、本地服务安全加固(v1.18.0) - App 里 4 个绑 `127.0.0.1` 的本地服务(通知 / 无障碍 / 虚拟屏桥 / 虚拟屏核心)**统一要求令牌** (`X-DSH-Token`),**拿不到令牌一律拒绝**(fail-closed);未匹配的路径不再"落到默认处理器"。 - 虚拟屏 jar 增加**随包 SHA-256 校验**:被改过一个字节就拒绝启动,并自动从包内重新提取。 - 说明:修之前,本机任意应用都能通过这几个端口读屏/截屏/注入手势/伪造通知。 ### 三、内核自检 + 自愈账本(v1.19.1,控制台「内核自检」) - **清单式一致性证明**:随包带一份内核清单(每个文件的路径/大小/sha256),点一次自检就能告诉你 **具体哪个文件缺失或内容不符** —— 而不再是"感觉坏了、清数据吧"。 两种模式:**快速自检**(只比大小,约 1 秒)/**全量校验**(逐个 sha256)。 - **自愈账本**:每次自检、每次解压/清理、每次配置快照都会写一条 JSON 记录到 `/sdcard/<包名目录>/heal-ledger/` —— 修过什么、依据什么、结果如何,全都可查(也可以直接让 AI 读给你听)。 - **安全边界(写死)**:自检**只读**;清理只发生在内核自己的 `@deepseek-ai` 目录里、且只删 `dsh` 开头的陈旧条目; **用户数据与你自己装的第三方插件永远只报不改**。 ### 四、修掉的四个浏览器故障(v1.19.1) 这四个都属于"装得上、起得来、日志几乎无感、一用就废"的类型,现已全部定位并修复: | # | 症状 | 根因 | |---|---|---| | 1 | 9 个浏览器工具**一个都不出现**在 AI 的工具列表里 | 工具入参 schema 写成了 JSON Schema(DSH 要的是"属性名 → 值 schema"的映射表) | | 2 | `browser_snapshot` / `browser_find` 立刻失败("快照脚本没有返回") | 注入页面的脚本里选择器**引号嵌套**导致 JS 语法错(`[contenteditable="true"]` 被双引号包住) | | 3 | 工具能调用,但每次都报 `userRender is not a function` | 结果渲染函数挂错了层级(应在 `output` 里) | | 4 | **点击永远失败**("ref 已失效"),而快照明明能拿到 ref | 注入脚本缺少公共函数定义(WebView 每次求值互相独立、不共享作用域)→ 脚本报错被误报成"元素不在了" | > 真机验收:9 个工具**走通 8 个**(`browser_type` 需要一个带输入框的页面,留待下一轮补测); > 差分、`find` 定位、点击跳转(`example.com` → `iana.org`,`changed=true`)、正文读取、回环地址拦截**逐条通过**。 ### 五、其它 - 内核树「混装树自愈」(v1.17.9 起):升级后引擎起不来,控制台点一次「重新解压」即可修好,**不用再清数据**。 - 控制台「一切皆自定义」(v1.17.3 起):外观 / 布局 / 文案 / 行为四层都能改,主题包可导出导入。 - 兼容版自带 GeckoView(v1.17.3 起),不依赖系统 WebView。 ### 从旧版本升级 直接装 **v1.19.1** 即可(覆盖 v1.18.0 + v1.19.0 + v1.19.1): 同签名覆盖安装,**配置 / 会话 / 已装插件都不丢**;装完如果控制台提示需要解压,点一次「解压文件」再「启动引擎」。 ⚠️ **装了新版本后,系统可能会自动关闭无障碍服务**(Android 的行为)—— 需要用到无障碍(读屏/点击/截图)时, 请到系统设置里重新打开一次,引擎不用重启。 --- ## v1.17.3(正式版 + Lite 共存版 + 兼容版 · 2026-10-03) > ⚠️ **这是 v1.17.1 之后的第一次发布**,内容覆盖 **v1.17.2 + v1.17.3** 两级(中间那两级从未单独发布过)。 > **一句话**:**兼容版自带浏览器引擎**、**控制台"一切皆自定义"(主题包)**、**升级后引擎起不来可以一键修好**。 > versionCode **52**(从 v1.17.1 的 50 跨上来),内核仍 **DSH 0.2.0-rc.2**;同签名覆盖安装,**配置 / 会话 / 插件都不丢**。 ### 一、兼容版内嵌 GeckoView —— 不再依赖系统 WebView **为什么做**:老设备(Chromium 91~93)打开界面纯白([#38](https://github.com/woaiys3/deepseek-harness-android-app/issues/38)), 而"兼容版"此前同样是靠系统 WebView + 语法降级,救不了这一类设备。现在**兼容版自带 GeckoView 引擎**,与系统 WebView 版本彻底解耦。 | 项 | 值 | |---|---| | 引擎 | GeckoView `157.0.20260924084938`(arm64) | | 体积 | compat 由 ~157MB → **250MB**(+86MB,13 个 `.so` + `omni.ja`) | | minSdk | compat 由 24 → **26**(正式版 / Lite 仍 24) | | 打包 | 无 Gradle:手工并入 12 个 androidx AAR + kotlin-stdlib/guava 等,`aapt --extra-packages` 逐个生成 R 类,`d8 --min-api 26`,**javac 后硬闸门断言 4 个 R 类存在** | | 接入 | `MainActivity` 里 `new WebView(...)` → 内层类 `GvWebView`(`GeckoView` + `GeckoSession`):`loadUrl/goBack/onResume/onPause/destroy/setBackgroundColor` 接口不变,**上层 60+ 处调用一行没改** | - **有意退化两处(如实说明)**:① 该版 GeckoView **没有 JS 求值接口**(`evaluateJavascript` 无对应物),所以"页面底色回填"(`refreshPageBackground`) 改为**跟随 App 主题**;② `addJavascriptInterface("dshshell")` 无对应物,整段移除(该桥在本项目没有被使用)。 - **真机实测**:不再闪退,先 `HARNESS / Loading plugins…` 约 40 秒后进入完整主界面,侧边栏抽屉正常,引擎 PID/端口正常。 - ⚠ **未完成的部分**:发消息实跑、退出确认弹窗 / 鲸鱼悬浮窗 / 虚拟屏、**附件选择**(`handleFilePrompt` 写了但**尚未实测**)、 Chromium 91 那台设备的复测 —— 都还挂在 [#38](https://github.com/woaiys3/deepseek-harness-android-app/issues/38) 上。 ### 二、控制台"一切皆自定义"(主题包体系,四层全通) 一份 `/sdcard/<包名目录>/console/console.json` 就能改控制台的**外观 / 布局 / 文案 / 行为**,改完存盘几秒生效(热重载)。 配套**主题包**(zip)可导出/导入,主题页内置 5 条"让 AI 生成主题包"的提示词。 | 层 | 能改什么 | |---|---| | **外观** | 深/浅两套配色(bg/card/text/sub/line/accent/green/red/track/statusBar)、字号(×0.8~1.4)、圆角(0~32)、日志等宽、logo、卡片不透明度;**背景图**:cover/contain/stretch/tile/center + 九宫格锚点 + 透明度 + 压暗 + 模糊(Android 12+)+ 视差 | | **布局** | 卡片显隐 / 顺序 / 默认页 / 详情默认展开 / 紧凑(间距减半);**整页声明式控件树** `layout.pages`:`row/column/card/text/button/image/spacer/divider/builtin`,17 个内置积木(引擎启停、解压块、权限/插件摘要、日志预览、救援按钮、主题卡…),可自己拼页、加第二页 | | **文案** | 52 个键(主控台 33 + 主题页 19):卡片标题、按钮、状态行、底部说明…(不写配置时**一字不变**) | | **行为** | `actions[]` 自定义按钮(主控台"我的按钮"卡片):内置动作(引擎启停 / 解压 / 校验 / 打开权限·插件·日志 / 主题操作)、`url`、`clipboard`、`toast`、`settings`、**`shell`**(App 身份或特权);可写成数组(组合动作) | - **主题页(控制台第 5 页)**:当前主题状态 + 五个操作(重新加载 / **导出主题包** / **导入主题包** / 恢复默认 / 打开主题目录)、 5 条可复制的提示词、规范与自检入口、可执行动作清单、**诊断**(看解析结果与回退项)。 - **兜底与救援(永不因为配置打不开 App)**:语法错 → 整体回退 + 顶部黄条(`last-error.txt` 记原因,`console.json.bak` 留底); 单项非法 → 只回退那一项并列出;schema 版本更高 → 整体回退;「恢复默认」= 改名 `console.json.disabled-<时间戳>`(不删); **救援面(救援卡片 / 主题页)不可从布局里移除**;逃生口:**长按左上角品牌字 →「以默认样式打开控制台」**。 - **安全红线**:`shell`/`http`/`intent`/`prompt` 这类**可执行动作**,界面上是**红框按钮 + 点击时弹确认框 + 显示命令原文** (并注明"它来自主题配置,可能是 AI 生成的")—— **AI 生成的包不可能静默拿到执行能力**。 - **随包分发**:`assets/console-theme/`(规范 `THEME-PACK-SPEC.md`、`console.schema.json`、示例、**零依赖离线校验器 `theme_pack_check.py`**) 首次运行解到 `/sdcard/<包名目录>/console/`,设备上的 AI 不联网也能读到规范、自己生成合规主题包。 - **未做(如实列出)**:`behavior`(自动解压 / 自动起引擎 / 双击退出)、`http`/`intent` 真正执行、`prompt` 自动发给 AI(需内核 RPC)、 导入时"逐个确认 + 内容哈希"(目前是**点击时确认**,安全效果等价但少一道)、动作执行写进日志页、权限/插件/日志页的页内文案。 ### 三、混装树自愈:升级后引擎起不来,不用再清数据 **现象**(真实故障):内核升级后引擎报 `cosmokit does not provide an export named 'updateVolatile'` 直接拒启, **连"重新解压"都救不回**,用户唯一出路是「导出数据 → 清除数据 → 重建 → 还原」。 **根因(两条)**:① 外部内核树在**全量模式**下仍然"文件已存在就跳过" → 上游改过的文件永远是旧的(引擎 import 到旧导出 → 拒启); ② 两种模式都**不清孤儿** → 上游删掉的文件留在树里,尤其 `…/dsh/node_modules/@deepseek-ai/` 下的**旧嵌套副本会被 Node 优先解析**。 **修法(只在整个树全量同步时跑:内核版本变化 / 上次解压被打断 / 用户点「重新解压」)**: 1. 全量模式**按 payload 覆盖写**(只覆盖 payload 里有的文件;用户文件与 profile 合并等既有规则**一律不变**); 2. 解压后**清掉 payload 已不存在的陈旧内核包**; 3. **如实上报**:控制台 toast「解压完成(顺带清理了 N 个内核树里多余的文件)」,不静默。 > **安全边界写死在代码里**(用户明确要求:不能删用户自己的东西、装的插件): > 只在 `…/node_modules/@deepseek-ai`(顶层)与 `…/@deepseek-ai/dsh/node_modules/@deepseek-ai`(嵌套)**两个目录**里跑, > 且**只删名字以 `dsh` 开头的条目**;其他名字只记一条"跳过"日志,**一个都不动**。 > 用户数据(`dshhome/**`)、第三方插件(`dshhome/profiles/**`)、AI 运行时产物**永不被删**。 **真机验收(三条,全部通过)**: - ① **健康树零误删**:修好后再点「重新解压」→ `extracted 25787 entries`,**零 `prune stale`**、toast 不带"清理了 N 个";对照文件 md5 全部不变; - ② **陈旧内核包被清**:真机上一次性清掉 **91** 个(含旧内核残留 `dsh-agent-presets` 与 `dsh-tool-vscreen` 的嵌套副本), 控制台文件计数 28,491 → **28,400**(正好 −91); - ③ **用户的东西一个不少**:用 App 自带「导出全部数据」做前后对账(328 vs 329 条,逐条 SHA256)—— **消失 0 条**、内容变化只有时间戳文件、会话/插件/凭证原样;清理后引擎正常、`session/create` 返回 `{"ok":true,…,"agentPreset":"standard"}`。 ### 四、其他 - **老 WebView 提示阈值 80 → 94**:0.2.0 前端真正需要的是 **Chromium 94**(`class static {}`),此前 80~93 的设备**静默白屏且不提示**;现在会明确告诉你"内核太旧 + 装兼容版"。 - **三套彻底对齐**:official / Lite / compat 都由同一份最新源码重编并装机,设备端 `md5sum` 与发布包**逐字节一致** (Lite 从 vc51/1.17.2 直接跨到 vc52/1.17.3)。 ### 五、已知限制(如实列出,未变) - **语音输入在 Android 上不可用**:上游把 `android-arm64` 硬排除在本地语音白名单外,且底层是原生模块,与本项目"payload 不带原生模块"冲突 → 需要上游支持。 - **含 C 扩展的 Python 包**(numpy/pandas 等)装不了(无编译工具链);纯 Python 包正常。 - **侧栏终端 / Office 预览**依赖被剥离的原生模块,不可用。 - 虚拟屏 **8998/8999 端口三变体共用**(正式版与 Lite 同时开虚拟屏只能二选一);识图大图不缩放。 - **compat 的 GeckoView 接入尚有待测项**(见 §一 的"未完成的部分")。 ### 六、升级建议 - 从任何旧版本**直接装 v1.17.3**(同签名可覆盖,不丢配置);Lite 共存版与 compat 各自独立安装。 - 升级后控制台若显示"未解压",点一次「解压文件 / 重新解压」即可(versionCode 变了会走一次解压)。 ## v1.17.2(正式版 + Lite 共存版 + 兼容版 · 2026-10-03) > **修两个真机问题**:① 发截图 / `read_image` / `android_see` 等一切要走内容寻址附件存储的操作, > 全量报 `EINVAL: invalid argument, fsync`,整条链路中断;② 展开侧边栏后**正文整片消失(纯白)**。 > versionCode **51**,内核仍 **DSH 0.2.0-rc.2**。 > 报告来源:用户设备反馈(fsync 那条附带了逐级复现证据与根因定位;侧边栏那条为现场截图)。 ### 🐛 根因(三层,前两层不是 bug,第三层才是) 真机逐级测试的实际结果: ``` /data/user/0 open->EACCES ← 已被既有补丁挡下 /data/user open->EACCES /data open->EACCES / open OK sync->EINVAL ← 真凶:裸奔的 sync 调用 ``` 1. **内核行为(非 bug)**:`fsync("/")` 返回 `EINVAL` 是 POSIX 允许的正常行为 —— 根目录是只读的, 没有任何需要落盘的目录项。 2. **算法设计(可商榷,未改)**:`ensureDurableHome` 拿 `parse(home).root`(即 `/`)当目录持久化同步的边界; 在 Android 沙盒下 App 永远无法越过 `/data/user/0` 向上 `fsync`。也就是说「保证到 `/` 都持久」 在 Android 上原理上就不可达 —— 但目录同步本就是**尽力而为**,所以保留边界、只做容错。 3. **★ 移植补丁缺陷(真 bug)**:`dsh-attachment-local` 的 `syncDirectory()` 里, `open` 失败的 `catch` 已经处理了 `EACCES`/`EPERM`,**但紧随其后的 `handle.sync()` 完全没有 catch** —— 于是 `sync("/")` 的 `EINVAL` 直接抛穿整条附件写入链。 ### ✅ 修法 把 `EINVAL` / `ENOTSUP` / `EOPNOTSUPP` 与 `EACCES` / `EPERM` 同等对待(「该目录不支持持久化同步」→ 跳过); **但保留 `ENOSPC`(磁盘满)与 `EIO`(硬件 I/O 错误)的抛出**,不拿容错掩盖真实故障。 | 包 | 位置 | 性质 | |---|---|---| | `dsh-attachment-local` | `syncDirectory()` | **真 bug**(真机上正在炸的就是它) | | `dsh-storage-json` | `fsyncDirectory()` | 同类加固(裸奔的目录 fsync) | | `dsh-session-persistence-jsonl` | `syncDirectory()` + `syncDirPosix()` | 同类加固(同上) | > **没有改的一处**:`dsh-fs-local` 的 `writeFileAtomic()` 里也有一个裸 `handle.sync()`, > 但那是对**临时文件句柄**(`open(tempPath, "wx")`)的 fsync —— 文件 fsync 裸奔是**正确**的, > 必须让 `ENOSPC`/`EIO` 照常抛出。**不要**把那处也加上容错。 ### 🔧 配套改动(缺一不可) - **`overlay-020` 固化**:`dsh-storage-json/lib/index.js` 成为 overlay-020 的**第 13 个补丁文件**; 另两个包的文件同步更新。`deploy-patches-020.sh` 的文件清单、关键补丁断言、语法自检一并扩容。 - **App 白名单(四份 `MainActivity.java`)**:`FORCE_OVERWRITE_PREFIXES` 增加 `dsh-storage-json/`。 内核版本号没变 → 覆盖安装走 `dshroot-fast` 同步,**只覆盖白名单**;不加这一行, 这个补丁进包了也不会落到设备上(v1.11 / v1.15.1 / v1.15.2 都栽过这个坑)。 ### 🖼️ 第二个修复:展开侧边栏后正文整片消失(纯白) **现象**:手机上点左上角三条杠展开侧边栏后,侧边栏右边那一片是**纯白**,聊天内容完全不见。 **根因(我方移动端补丁的缺陷,非上游)**:`dsh-client-ui-layout/lib/client.js` 的浮层抽屉补丁 把 `sidebarCol` 这个 **grid item 自己**设成了 `position: fixed`。 ``` 网格:0px | 720px | 0px (窄屏:侧栏与右栏都取 0,侧栏改浮层) 子元素:sidebarCol(fixed→脱离流) · centerCol · rightbarCol ``` grid item 一旦脱离流,后面的两个子元素就**自动前移一列**: | | sidebarCol | centerCol(聊天) | rightbarCol | |---|---|---|---| | 修前(展开) | fixed 浮层 | 落到第 1 列 → **0px** ❌ | 落到第 2 列 → **720px** | | 修后(展开) | 在流内占 0px | 第 2 列 → **720px** ✅ | 第 3 列 → **0px** ✅ | 于是聊天列宽度为 0 → 整片白,而那个空白右栏占满全屏。**折叠状态看起来正常**,所以只在「打开侧边栏」时才暴露。 **修法**:`sidebarCol` 保持在流内(占 0px 的第 1 列),改用**内层 div** 承担 fixed + 阴影 —— 网格自动放置就不会错位(真机实测:`center 0→720`,`right 720→0`)。 **真机复现与验证**(PLT120 / Android 15,用 Playwright 直连设备上正在跑的引擎,视口 720×1570): | 步骤 | 结果 | |---|---| | 折叠态基线 | `grid = 0px 720px 0px`,center=720 ✅ 正常 | | 点三条杠展开 | center=**0**、right=**720** → **纯白复现** ❌ | | DOM 里把三列显式钉住 | center=**720**、right=0 → 证实根因 ✅ | | DOM 里换成内层 fixed 包装 | center=**720**、right=0,抽屉 280×1570 正常显示 → 采纳本方案 ✅ | > `dsh-client-ui-layout/lib/client.js` **本来就在 `FORCE_OVERWRITE_PREFIXES` 白名单里**,无需额外改 App。 ### 🧪 验证(本轮实际跑过的) | 项 | 方法 | 结果 | |---|---|---| | **errno 矩阵 + 整链走位** | 从 **v1.17.1 出货 APK 的 payload**(修复前)与**修复后的内核树**各**逐字抽出**真实函数体跑,注入假 `open`/句柄 | **PASS=141 FAIL=0** | | └ 关键 A/B | 同一段「逐级向上 fsync 到 `/`」走位:修复前在 `sync("/")` 抛 EINVAL(复现报告现象);修复后通过 | ✅ | | └ 不吞真实故障 | `ENOSPC` / `EIO` / `EPERM` 仍然抛出;句柄始终关闭;`win32` 早退未变 | ✅ | | 图像回归(`sharp-shim` 单测) | `tmp-diag/v115/shim-test.mjs` | **PASS=24 FAIL=0** | | 图像回归(真实 PNG 往返) | `tmp-diag/v115/regress-test.mjs` | 40 个 PNG 全部往返成功、0 失败;JPEG 无回归 | | 引擎启动 | 用修复后的树 `--profile web`(`--expose-internals`) | 只打印一行 token URL,**零警告零 pending** | | 会话可建 | `tmp-diag/v1200/rpc-check.mjs` | `{"ok":true,"sessionId":"session-…","agentPreset":"standard"}` | | 配置树 | `--profile web --dump-config` | `not found` 命中 **0**;三个补丁包均在挂载列表 | | 落补丁脚本 | 复跑 `deploy-patches-020.sh`(对现役树) | **幂等**(`dsh/package.json` md5 不变);14 个文件✅ / 10 条关键断言✅ / 语法自检 12 项 0 FAIL | | payload 红线 | `find dshroot -name '*.node' -o -name '*.so' -o -name '*.dll'` | **0**;树体积 **253M**(未变) | ### 已知边界 - **只覆盖 PNG 像素级校验**(非 PNG 仍原样透传)、`JPEG/WebP/GIF` 不缩小 —— 均为既有取舍,本轮未动。 - 两条**已被坏图写入历史**的旧会话仍会复现旧错误(本轮修的是「不再产生新的失败」,不改已落盘数据)。 - **语音输入在本项目上不可用**(用户再次反馈,本轮复核结论不变):`dsh-experimental-speech-to-text-sensevoice/lib/index.js:200-207` 是**上游硬编码的平台白名单**,只有 `darwin-arm64/x64`、`linux-arm64/x64`、`win32-x64`, **`android-arm64` 被排除**(报错原文即 `Local speech is unavailable for android-arm64`); 且底层 `sherpa-onnx-node` 是**原生模块**,与本项目「payload 不带原生模块」的红线直接冲突。 该能力**不是我们能修的**,需上游支持 —— 归为上游需求,不在本版本内。 ## v1.17.1(正式版 + Lite 共存版 + 兼容版 · 2026-10-01) > 在 v1.17.0(内核 0.2.0-rc.2)基础上**删除自研定时任务功能**:0.2.0 内核自带了定时任务, > 两套并存没有意义。versionCode **50**,内核仍 **DSH 0.2.0-rc.2**。 ### 🗑️ 移除自研定时任务(让位给内核自带的) - **为什么删**:0.2.0 内核新增了 `@deepseek-ai/dsh-schedule`(「主机级持久提醒」, 支持 at / after / daily / weekly / cron / every,到点作为 follow-up 投递回**原会话**), 配套还有 `ui-schedule` 任务页与 `time-context`。我们此前那套(`android_schedule` 工具 + AlarmManager + 到点自动拉起引擎执行)与它功能重叠,且能力更弱(没有 cron/每周、结果不回原会话)。 - **删了什么**(四份源码全部同步): - 工具 `android_schedule`(`dsh-tool-shizuku`) - `AlarmReceiver.java` + `ScheduleExecutor.java`(整文件删除) - `MainActivity`:`/schedule` 路由、定时任务持久化、到点自动执行、以及**只服务于它的那套自研 DSH RPC** (`createSession` / `sendPrompt` / `rpcCall` / `escapeJson`) - `EngineService`:`scheduledTask` extra 处理 - `AndroidManifest.xml`:`SCHEDULE_EXACT_ALARM` 权限 + `.AlarmReceiver` 声明 - `build.sh`:javac 清单里的两个文件 - UI 文案里提到「定时任务」的地方(权限页通知说明、插件页描述) - **内核那套怎么用**:它是**可选 bundle**(`dsh-experimental-schedule-bundle`,含 schedule + ui-schedule + time-context),**默认不启用**(`profiles/web` 的 bundles 仍只有 `dsh-base` + `dsh-web-app`)。 需要时在**控制台 → 插件**里启用该 bundle 即可,或让 AI 自己开。 ### 📝 顺带订正 - 插件页描述与实际能力对齐:`tool-android` 原写「用量统计 / 悬浮窗 / 剪贴板 / 定时任务」, 改为「用量统计 / 悬浮窗 / 装包 / 应用与设置 / 截图 / 输入」;`tool-shizuku` 补上「通知 / 剪贴板」。 ### 已知限制(本轮实测确认,未变) - **语音输入**:0.2.0 的本地语音(`dsh-experimental-voice-input-bundle` → SenseVoice)**在 Android 上不可用** —— 上游的平台白名单只有 `darwin-arm64/x64`、`linux-arm64/x64`、`win32-x64`,`android-arm64` 被**硬排除**, 报错原文即 `Local speech is unavailable for android-arm64`;且底层 `sherpa-onnx-node` 是**原生模块**, 与本项目「payload 不带原生模块」的红线冲突。该能力**不是我们能修的**,需上游支持。 同 bundle 里的**云端 STT**(`api-speech-to-text`)是纯 JS、理论可用,但需要外部语音服务凭据,本轮未启用。 - **老 WebView(Chromium ≤93)纯白**(issue #38):本轮仍未修,已单独排期(要动构建链路的 esbuild target + polyfill)。 ## v1.17.0(正式版 + Lite 共存版 + 兼容版 · 2026-10-01) > **内核升级:DSH 0.1.7-rc.1 → 0.2.0-rc.2**(上游当前 latest),并修两个社区报告的缺陷 + > 适配 0.2.0 变更过的 RPC 协议。versionCode **49**。 ### ⬆️ 内核升级到 0.2.0-rc.2 - **做法**:沿用项目既有「旧法」——另起一棵树解析依赖闭包(530 包)后组装,不就地换包。 新树 253 MB、`@deepseek-ai` 287 包 + 第三方 145 包、**原生模块 0**(红线守住)。 - **补丁面(12 个文件)**:8 个文件上游逐字节未变、直接沿用;4 个上游有改动,用三方合并重打 (合并后与上游差异**恰好等于我们原本的改动量**,说明上游改动全保留)。 - **0.2.0 新增的兼容性闸门**:内核开始校验插件的 `peerDependencies`。我们 4 个自研插件原先写的是 `@deepseek-ai/dsh-tools: ^0.1.0-rc.6`(semver 里 `^0.x` 只覆盖 `0.1.x`)→ 0.2.0 判定不兼容, **把 4 个插件静默禁用**(引擎照常启动,但 31 个安卓工具全部消失)。已改为 `>=0.1.0-rc.6`。 - **验证**(本地真实内核树):`--profile web` **零警告、零 pending**;`session/create` 返回 `{"ok":true,"value":{"sessionId":…,"agentPreset":"standard"}}` —— 即 `sandbox → ptc-runtime → workflow` 这条链在 0.2.0 上仍然通(正是 v1.15.1 那个 P0 的复检点)。 ### 🐛 定时任务「发送提示词」在 0.2.0 上必然失败(RPC 协议变更) - **根因**:0.2.0 的 RPC 线上形状与 0.1.x **完全不同**,四处都变了: ① endpoint 必须是**两段式** `a/b`(`/api/session/create`,旧的是 `/api/session.create`); ② body 里的 `method` 必须与 endpoint 逐字一致;③ payload 必须**包一层 `args`**; ④ `args` 内字段要匹配内核的 descriptor(`session/create` 要 `request`,`session/prompt` 还要 `requestId` + `mode` + `content`)。 写错的报错分别是 `404 not found` / `method … does not match endpoint …` / `Remote payload must contain exactly one plain-object args field` / `args fields do not match the descriptor`。 - **影响面**:`ScheduleExecutor.rpc()` 用的是 0.1.x 形状 → 升级后**定时任务的「发送提示词」100% 404**。 - **修法**:按实测形状重写 `rpc()`(自动把 `session.create` 转成 `session/create`、包 `args`), 并按 0.2.0 要求补 **`session/prompt` 的 `requestId` / `mode` / `content`** 字段。 - **顺带修掉一个历史欠账**:`/api/*` **一直需要浏览器鉴权 cookie**,而定时任务从来没带过 (交接文档里登记过「大概率一直失败」)。现在会从引擎日志里取本端口的 token → `GET /?token=` 换 `dsh-auth-*` cookie(30 天)→ 缓存复用;401 时自动清缓存重取。 ### 🐛 虚拟屏「闪一下就没」(issue #36) - **现象**:点小鲸鱼面板的「虚拟屏」,屏幕闪出一块黑色窗口约 0.4 秒后自动关闭,反复多次都一样; 社区在 Redmi(Android 14/16,HyperOS)、OPPO(Android 14)上均复现。 - **根因(社区取证 + 代码定位一致)**:该入口**只做「把已收起的预览窗叫回来」,从不发 `/vscreen/create`**。而预览轮询每轮都会查 `/vscreen/status`,看到 `displayId < 0` 就调 `hidePreviewWindow()` **自己把窗收掉** —— 于是「建窗 → 下一轮轮询发现没屏 → 自己 removeView」, 正好是 0.4 秒闪退;没有虚拟屏的用户永远进不去。 - **修法**:入口语义改成幂等的「**确保虚拟屏可用**」:① 核心没跑就拉起(带上构建指纹校验); ② 没有屏就自动发一次 `/vscreen/create`(默认竖屏 1008×1792);③ 成功才显示预览窗; ④ **任何一步失败都如实提示原因**(以前是静默闪退,什么都看不到)。 同时加守卫:**建屏进行中轮询不收窗**(否则新逻辑同样会闪)。 全程在后台线程 —— 报告人日志里那条 `Slow Binder: IRemoteProcess.waitFor()` 1~8 秒就是 同步等 Shizuku 拉进程,放主线程会 ANR。 - **顺带澄清**:这与 Android 15/16 的 MediaProjection 新限制**无关** —— 本 App 是 targetSdk 28, 且从 v1.10 起虚拟屏走的就是「特权进程 + `DisplayManager.createVirtualDisplay` + ImageReader」, 根本没有用 MediaProjection。 ### 🐛 平板分屏拖动分隔条导致界面白闪、重放启动页(issue #37) - **现象**:平板左右分屏时,每次拖动中间分隔条调整比例,DSH 这边白闪一次并重放启动页; **会话进度没有中断**(引擎没被杀),纯粹是界面重新加载了一遍。 - **根因**:`MainActivity` 的 `android:configChanges` 只有 `orientation|screenSize|keyboardHidden`, **缺 `screenLayout` 与 `smallestScreenSize`**。分屏/自由窗口拖分隔条时系统下发的正是 `CONFIG_SCREEN_LAYOUT + CONFIG_SMALLEST_SCREEN_SIZE`;没声明就不归 App 处理 → 系统**销毁重建** Activity → 新建 WebView 重新 `loadUrl` + 启动页盖上来。真机 `dumpsys` 实证 `configChanges=0x4a3`, 确实不含 `0x100`/`0x800`。 - **修法**:① `configChanges` 补 `smallestScreenSize|screenLayout`;② 新增 `onConfigurationChanged()`,只重贴「不会自己更新」的状态栏/导航栏底色(WebView 是 `MATCH_PARENT`,框架会按新尺寸自动重排,无需手动布局,更不需要重建)。 - **⚠️ 故意不加** `uiMode` / `density` / `fontScale` —— 跟随系统深浅色、显示大小、字体大小正是靠 「重建 → `onCreate` 的 `setTheme()` 重新生效」,加进去反而会让主题不再跟随。 ### 已知限制(未变) - 无编译工具链,**含 C 扩展的 Python 包(numpy/pandas…)仍装不了**,纯 Python 包正常。 - PNG 有完整的纯 JS 编解码与损坏检测;**JPEG/WebP/GIF 仍是原样透传**(不缩小、不做像素级校验)。 - headless / sdk profile 仍未在真机上跑通。 - 虚拟屏 `8998/8999` 三变体共用:正式版与 Lite 同时开虚拟屏只能二选一。 - 老 WebView(Chromium ≤93)纯白问题(issue #38)**本轮未修**,已单独排期。 ## v1.16.1(正式版 + Lite 共存版 + 兼容版 · 2026-09-26) > 修 v1.16.0 上线后真机实测暴露的三个问题:**pip 不可用、npm 装出的 CLI 无法执行、npm 全局前缀不可写**。 > versionCode **48**,内核仍 **DSH 0.1.7-rc.1**。 ### 🐛 pip 完全不可用(`No module named pip`) - **现象**:`python3 -m pip --version` → `No module named pip`; `python3 -m ensurepip --upgrade` → `FileNotFoundError: .../ensurepip/_bundled/pip-26.1.2-py3-none-any.whl`; `python3 -m venv` 跟着失败(venv 内部也是靠 ensurepip 装 pip)。 - **根因**:Termux 把 pip 拆成独立包,`python` 这个包自带的 `ensurepip` **没有 `_bundled/` 目录** (只有 `__init__.py` / `__main__.py` / `_uninstall.py`)。`ensurepip --version` 仍会打印 `pip 26.1.2`(那只是读硬编码版本号),极易误判。 - **修法**:① 补 `ensurepip/_bundled/pip-26.1.2-py3-none-any.whl`(让 ensurepip / venv 可用); ② 把**同一版本** pip 预装进 `site-packages`(让 `python3 -m pip` 开箱可用)。两处同版本,不会冲突。 - **验证**:真机 `pip --version` → `26.1.2`;`pip install requests` → 装上并 `import` 成功; `python3 -m venv v` → 建成功且 venv 内 pip 可用。 ### 🐛 npm 装出来的 CLI 直接执行报 ENOENT - **现象**:`npx cowsay@1.6.0 hi` → `sh: .../.bin/cowsay: No such file or directory`; `node .../cowsay/cli.js hi` 却正常。 - **根因**:`.bin/*` 首行是 `#!/usr/bin/env node`,而 **Android 没有 `/usr/bin/env`**(只有 `/system/bin/env`), 内核按**绝对路径**找解释器 → 直接 exec 必 ENOENT。与包本身、与安装过程**都无关**。 npm 官方的 `bin-links/fix-bin.js` 在 Unix 上**只 chmod、不重写 shebang**(只处理 Windows CRLF) → 配置解决不了,必须改代码。 - **修法**:给内置 npm 的 `fix-bin.js` 打 **Android shebang 适配**:安装时自动把 `#!/usr/bin/env` 改写为 `#!/system/bin/env`(node 由 PATH 解析,引擎已把 payload 目录放进 PATH)。 - **验证**:真机 `npm install cowsay` 后,`cowsay/cli.js` 首行变为 `#!/system/bin/env node`; `./node_modules/.bin/cowsay hi` 与经 PATH 调用**都正常输出**;`npx` 同样通过。 ### 🐛 npm 默认全局前缀不可写 - **现象**:`npm install -g xxx` 失败(`npm config get prefix` = `/runtime`,不可写)。 - **根因**:npm 按 `dirname(dirname(process.execPath))` 推导前缀,而 execPath 在 payload 内; 即使可写也不该放那里 —— **payload 会被升级覆盖**。 - **修法**:内置 npm 的 wrapper 把 `NPM_CONFIG_PREFIX` 指到 **`$HOME/.npm-global`** (App 私有目录,可写且跨版本升级保留);同时把 `/.npm-global/bin` 加进引擎 `PATH`, 全局装的命令可直接调用。 - **验证**:真机 `npm config get prefix` → `/data/user/0/.../files/.npm-global`; `npm i -g cowsay` 后直接 `cowsay` 可执行。 ## v1.16.0(正式版 + Lite 共存版 + 兼容版 · 2026-09-26) > **payload 现在自带 Python 与 npm** —— 以前 AI 在手机上既没有 Python、也装不了 npm 包。 > versionCode **47**,内核仍 **DSH 0.1.7-rc.1**。 ### ✨ 内置 Python 3.14.6 - **为何要做**:AI 经常需要跑脚本做数据处理、解析、统计,此前完全没有 python(只有 bash + node)。 - **做法**:沿用项目既有老路(rg / curl / git / pnpm 都这么来的)—— 从 Termux 仓库取 aarch64 deb 解包;依赖闭包 17 个包,其中 4 个(openssl / zlib / libffi / libsqlite3 等)我们**已有**,只新增 13 个 `.so` 及其 soname 实体名。 - **产出**:`bin/python3` + `bin/python`(wrapper)→ `payload/python/`(本体 + 标准库 + lib-dynload)。 `bin/` 在引擎 PATH 上,AI 直接 `python3 xxx.py` 即可。 - **取舍**:不带 `include/` 与 `pkgconfig`(只有现场编译 C 扩展才需要); **PYTHONHOME 不用设** —— python 按 argv0 自推 prefix(真机实测)。 ### ✨ 内置 npm 11.20.0 / npx - **为何要做**:payload **本来就有 node v26.4.0**(引擎就跑在它上面,`runtime/bin` 在 PATH 上, AI 本就能 `node xxx.js`),但 Termux 的 nodejs 包**不含 npm**(npm 是单列的另一个 deb), 所以「装 node 包」这条链是断的。 - **产出**:`bin/npm` + `bin/npx`(wrapper)→ `payload/npm/`(npm 本体)。 - **取舍**:裁掉 `docs/`(2.4M)与 `man/`(466K)—— 纯文档,运行时用不到,16M → 13M。 ### 📦 体积 APK **135.6MB → 151.1MB**(+15.5MB;Python 26M + npm 13M,APK 内为压缩存储)。 ## v1.15.9(正式版 + Lite 共存版 + 兼容版 · 2026-09-26) > 两件事:**插件挂载失败终于看得见** + **修 issue #33(每次开机配置被重置)**。 > versionCode **46**,内核仍 **DSH 0.1.7-rc.1**。 ### 🐛 issue #33:每次开机配置被重置(报告人 @C4RP3N0CT3M) - **现象**:引擎启动时改写配置,用户在 `dshhome/cordis.patch.yml` 上的改动**每次开机被擦掉**。 - **根因**:**App 期望的标记与配置文件里的标记不一致**: 代码里是 `dsh-android-patch: v2`,而 `config/cordis.patch.yml` 首行是 `v3`。 判定用 `content.contains("…: v2")` → **恒为 false** → 每次启动都判「配置不完整」→ 从 payload 重刷官方配置。 成因:v1.15.1 为强制升级用户迁移(修 sandbox P0)把 yml 标记提到 `v3`, **但 App 侧那个常量没跟着改**,把「一次性迁移」变成了**永久循环**。 - **修法**:常量跟到 `v3`(四份源码同步),并在注释写明「今后改 yml 标记必须同步改这里」。 - **核验**:装机后每次启动都**不再**出现 `cordis.patch.yml missing or incomplete, restoring from payload.zip`。 ### ✨ 插件挂载失败可见:`$DSH_HOME/logs/plugins.log` - **问题**:插件装上了、引擎启动「零警告」,但插件功能就是不出现,日志一片干净。 - **根因**:cordis 的 logger 默认只 push 进**内存 buffer**(上限 1000 条), 而 `dsh-app-boot` 注册的 exporter 把 warn/error 收进**局部变量**,**只在启动失败时**才随错误抛出 —— 启动成功就直接丢弃。 - **修法**:在该 exporter 内顺手把 warn/error 追加写入 `$DSH_HOME/logs/plugins.log` (1MB 自动重开;写盘失败全部 try/catch,**诊断功能不得拖垮启动**;不新增任何依赖)。 - **收益**:插件缺依赖 / 挂载方式不对 / 插件间接口不兼容,从「隐形」变成「一眼可见」。 ## v1.15.8(· 2026-09-26) > 修两个「做好了却被上游机制吃掉」的问题。versionCode **45**。 ### 🐛 `git: 'remote-https' is not a git command` - **根因**:git 的远程助手**缺执行位**。payload.zip 里两类条目属性不同 (`bin/pnpm` 带 Unix 属性位,而 `git/libexec/...` 是 MS-DOS 属性), 解压后**权位完全由 App 的 `setExecutables()` 决定**,而清单里只列了 `bin/git`。 - **修法**:`setExecutables()` 补上 `git-remote-{https,http}`。 - **真机 A/B**:去掉执行位 → 逐字复现报错;`chmod +x` 后 → `git ls-remote` 返回 HEAD 哈希。 ### 🐛 装完插件 → 重启引擎 → 插件消失 - **根因**:`dsh-plugin-manager` 把已装插件写进 profile 的 `package.json` (`dependencies` + `dsh.profile.bundles` 就是**插件注册表**), 而该文件在 `DSHHOME_CONFIG_PATHS`(**整文件覆盖**)清单里 → 每次解压/刷新被打回 payload 模板。 - **修法**:两处覆盖路径(`extractPayload` / `refreshInternalConfig`)改成**合并** —— 内置 bundle 跟着 APK 走,用户装的插件保留。 ## v1.15.7 / v1.15.6(· 2026-09-25/26) > 让 **ssh 形式的 git URL 自动改写成 https**(payload 没有 ssh 客户端,用户填 > `git+ssh://git@github.com/...` 必报 `cannot run ssh`)。 > v1.15.6 用「环境变量传 git 配置」的方案,**被内核的安全清洗干掉**: > 变量名 `GIT_CONFIG_KEY_` 含 `KEY` → 命中 `SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i` > → 被当凭据洗掉,报 `missing config key GIT_CONFIG_KEY_0`。 > v1.15.7 换成**写配置文件 + `GIT_CONFIG_GLOBAL`**(名字不含敏感词,能活过清洗), > 并把用户自己的 `~/.gitconfig` include 进来。同时保留 `GIT_TERMINAL_PROMPT=0`(需要凭据时立即失败,不挂住等 tty)。 ## v1.15.5(· 2026-09-25) > 内置 **git 2.55.0**(约 3.9MB)+ **修证书坑**。versionCode **42**。 ### ✨ 内置 git - 从 Termux aarch64 deb 取;只带主程序 + 两个远程助手 + templates, **不带** `libexec/git-core/` 里那 146 个与主程序同尺寸的硬链接副本(照搬会撑到 536MB)。 依赖只新增 `libiconv.so` + `libcharset.so`(其余 libcurl/openssl/pcre2/zlib 早已随 curl 带入)。 ### 🐛 curl / git 全线证书报错 - **根因**:内置 `libcrypto` 的 `OPENSSLDIR` 编译时**写死指向 Termux** (`/data/data/com.termux/files/usr/etc/tls/cert.pem`),本机没装 Termux → OpenSSL 打不开 **直接报错、不回退**(`curl: (77) error adding trust anchors from file`)。 - **修法**:启动时把系统 CA(`/system/etc/security/cacerts`,145 张)合成为 bundle, 并设置 `SSL_CERT_FILE` / `CURL_CA_BUNDLE` / `GIT_SSL_CAINFO` —— **三个都要设**(实测只设 `SSL_CERT_FILE` 对 git 无效)。 - **附带**:支持用户自己装的证书(每次启动重拼 bundle)。 ## v1.15.4(· 2026-09-25) > 修「工作区里 .md 文件只有图标没有名字」。versionCode **41**。 - **现象**:文件列表里 `.md` 行只显示类型徽标,文件名不见(其它扩展名正常)。 - **根因**:**我们自己的 CSS bug**。`mobile.css` 一条竖屏规则用 `[class*="_markdown_"]` 想管正文宽度,却**误命中文件类型图标的 SVG**(类名恰好是 `_markdown_1wejo_28`), 把 16px 图标撑成整行宽(实测 324px),同行文件名被挤成 **0 宽**。 (文件名本身完好,磁盘上是正常中文 —— 不是编码问题。) - **修法**:加一条**只命中 `svg` 本身**的还原规则(把尺寸还给 SVG 自身的 width/height 属性)。 ## v1.15.3(· 2026-09-25) > 修「添加插件」装不了任何插件。versionCode **40**。 - **现象**:三种输入(本地目录 / 包名 / GitHub 地址)**全部**失败,只有一行 `退出码 127 / 无输出`。 - **根因**:payload 里**从来没有 pnpm**(`bin/` 只有 bash,`runtime/bin/` 只有 node/rg/curl), 而 `dsh-plugin-manager` 全程 `pnpm add` → 127。 - **修法**:内置 pnpm 10.18.3 的纯 JS bundle(**7.8MB / 5 文件**;砍掉 `dist/node_modules` 9.2MB、 win 的 `fastlist.exe`、darwin/win32 的 `reflink.*.node`)+ `bin/pnpm` wrapper + 加进 `setExecutables`(解压不保留执行位,不加就 127)。 ## v1.15.2(正式版 + Lite 共存版 + 兼容版 · 2026-09-24) > 修三处**只在真机上发作**的缺陷(PC / 静态验证都看不出来),来源是一份外部 AI 的 > 全工具真机测试报告,逐条核实后确认并修复。versionCode **39**,内核仍 **DSH 0.1.7-rc.1**。 ### 🐛 `write` 工具无法新建文件(写已有文件正常) - **现象**:新建文件报 `EACCES: permission denied, link .../xxx.tmpdir/xxx.tmp -> xxx`; 工作区目录与 `/sdcard` 都失败,**覆盖**已有文件却成功。 - **根因**:`dsh-fs-local` 的新建路径用硬链接发布(`link()`)保证“不覆盖”,而 Android 在 App 私有目录(f2fs + SELinux)与 FUSE 外部存储上**直接拒绝 `link()`**,且失败后没有回退。 (本项目 v1.6.1 曾修过同一个包的另一处 `link()`,升级 0.1.5 时这处漏移植了。) - **修法**:`link()` 因权限/不支持类错误码失败时,**先复查目标不存在**再退化为同目录 `rename()`; 目标已存在仍抛 `FS_NOT_OBSERVED`(保持“未读取不得覆盖”语义),`EEXIST` 等其它码仍走原路径。 - **验证**:用真实出货代码 A/B(注入“拒绝 link”的文件系统):修复前逐字复现报错, 修复后新建成功、覆盖仍被拒、`EEXIST`/`ENOSPC` 语义不变,10/10 断言通过。 ### 🐛 `workflow` 工具不可用(后台 worker 起不来) - **现象**:`workflow` 启动即失败: `worker-exit: Node process exited before completing (1): CANNOT LINK EXECUTABLE ".../runtime/bin/node": library "libz.so.1" not found`。 bash 里 `node -e` 正常 —— 只有 worker 挂了。 - **根因**:`dsh-ptc-runtime-node` 给 worker **故意的清空环境变量**(内核契约为“worker 的 `process.env` 从空开始”),但 node 会**丢弃值为 `undefined` 的键**,而 Android 内置 node 的 动态库(libz/libcrypto/libicu…)全靠 `LD_LIBRARY_PATH` 提供 → worker 直接链接失败。 - **修法**:仅在 Android 上把 `LD_LIBRARY_PATH` 与 `OPENSSL_CONF`(Termux 共存所需)按需补回, **其余环境变量仍保持清空**,内核契约不被放开。 - **验证**:从真实源码逐字抽取这两段逻辑 + 真·spawn 子进程确认继承,8/8 断言通过。 ### 🐛 图片层失败被误报成「网络传输失败」并白等 5 次重试 - **现象**:附件解码/校验失败时报 `DeepSeek Messages transport failed`(TRANSPORT), 看起来像网络问题;且 `TRANSPORT` 在默认可重试码里 → 一条消息白白退避重试 5 次 (实测约 15 秒),而失败发生在 `fetch()` 之前,重试注定无效。 - **根因**:`dsh-llm-deepseek` 把所有非 `LlmError` 的异常统一包成 `TRANSPORT`,附件错误 (`AttachmentError`)因此被贴上网络标签,`cause` 也不落盘,事后查不出真因。 - **修法**:附件层错误如实归类为非可重试的 `INVALID_REQUEST`,并把真实原因写进 message; 其它错误仍是 `TRANSPORT`(真网络抖动照旧重试),措辞也补上原因。 - **验证**:从真实源码逐字抽取分类分支 + 注入真实 `AttachmentError`/网络错误对象,10/10 断言通过。 ### 🐛 `android_touch_status` 只回「操作成功。」(`held[]` 被丢掉) - 该工具返回了手指编号/坐标/按住时长,schema 也声明了,但通用渲染只输出“操作成功。”, 而“问一下现在按着什么”正是它的全部用途。现改为专用渲染,逐项回显手指、像素/分数坐标、 已按住毫秒数与超时上限。 ### 📝 `android_type` 的描述按真机实测改正 - 原文写“WebView/网页输入框请用 `paste:true`”,而真机实测 `paste:true` 也常只落到 输入法候选栏、不提交。现改为如实说明:WebView/contenteditable 无障碍输入不可靠 (setText 不改前端;paste 只进候选栏),应改用特权版 `android_input`(先 tap 聚焦、 确认键盘弹出,再输入)—— 这条路已验证可用。 --- ## v1.15.1(正式版 + Lite 共存版 + 兼容版 · 2026-09-24) > v1.15.0 上了真机之后暴露的回归修复。versionCode **38**,内核 **DSH 0.1.7-rc.1**。 > ⚠️ **强烈建议从 v1.15.0 升级到本版**:v1.15.0 上所有 agent preset 会话都建不起来。 ### 🐛 【P0】所有会话都建不起来(v1.15.0 引入) - **现象**:装 v1.15.0 后发消息、恢复会话全部失败,报 `resume failed for session "…": workflow-ptc (@deepseek-ai/dsh-workflow-ptc): waiting for ptcRuntime / tool-workflow (@deepseek-ai/dsh-tool-workflow): waiting for workflowEngine`。 - **根因**:`cordis.patch.yml` 沿用了旧版本的 `- id: sandbox / disabled: true`。而 0.1.7 起 `dsh-ptc-runtime-node` 把 `sandbox` 列为注入依赖 → 一禁用,`ptc-runtime` 永久 pending → `workflow-ptc` 拿不到 `ptcRuntime` → `workflowEngine` 缺失 → **standard / ptc / cordis 三套 preset 的会话 create/resume 全部报错**(引擎本身能起、网页能开,所以静态验证看不出来)。 - **为什么旧版本禁它**:担心 `dsh-sandbox-local` 需要 koffi 原生模块。**实测该判断不成立**: 它只通过 `@deepseek-ai/node-addon-system` 的 landlock-run 解析平台包(缺失时 probe 判为 unusable),加载路径上没有任何原生 import,在“树内 0 个 `.node`”的环境里 import 成功。 真正的隔离只在 `confine()` 被调用时 fail-closed,而默认 preset 是 `danger-full-access`。 - **修法**:恢复 `sandbox` 启用(只保留 `bash-sandbox` / `pwsh-sandbox` 禁用)。 bash 不受影响 —— `dsh-bash-local` 只注入 `subprocess`,从不调用 `ctx.sandbox`。 - **验证**:本地 A/B 对照(禁用 → 复现同样报错;启用 → `session/create` 返回 `{ok:true, agentPreset:"standard"}`,启动日志 0 pending);真机装上后引擎启动日志干净、 发消息正常。 ### 🐛 三条杠侧边栏消失(0.1.7 升级漏打的补丁) - 窄屏下左上角的三条杠按钮、浮层抽屉、遮罩全部不见了 —— 因为 0.1.7 升级时 `dsh-client-ui-layout` 的移动端补丁**没有重打**(上一轮误判它是“空补丁”)。 - 现按 0.1.7 上游结构重打 4 处:窄屏侧栏不占列宽(`gridTemplateColumns` 置 0)、 展开时以浮层抽屉覆盖、左上角三条杠按钮、点击即收的透明遮罩;同时给 DragHandle 加 `!narrow` (窄屏是抽屉,没有可拖的分栏)。 ### 🐛 控制台找不到「清空日志」 - **根因**:日志页(`consolePage == 3`)原本**没有任何入口能进** —— `cNavRow` 只接了权限(1)/插件(2), 于是渲染日志页的 `renderConsoleLog()`(含上一版新加的「清空日志」)成了不可达的死代码。 - **修法**:① 主页面「日志」行整行可点进入日志页(并补 `›` 提示);② 该行直接加一个「清空」按钮, 主页面一键直达。清空逻辑仍是截断(`setLength(0)`)而非删除,不会打断运行中的引擎。 ### 🐛 菜单被压成「一两个字」(属性选择器误伤) - **现象**:会话行的「归档/置顶」菜单、聊天区的工具气泡菜单显示成 `置 / 重 / 亻 / 収`,面板也歪了。 - **根因**:`mobile.css` 上一版把选择器改成 `[class*="_名字_"]`(为避免哈希变化失效), 但只核对了“名字还在不在”,**没核对这个名字在 0.1.7 里属于哪个组件**。 实测 0.1.7 里名字恰好叫 `item` 的组件**只有菜单项**(模块 1t7on,同模块 root/list/submenu/label/separator…), 而我们的规则把它强制成 `width:48px; height:48px` → 文字被裁掉。 `_label_` / `_separator_` / `_itemLabel_` 同理;`_rail_` / `_thumbnail_` / `_gallery_` / `_illustration_` 在 0.1.7 已无同名组件(死规则)。 - **修法**:删掉这 6 组会误伤的规则;保留的(`_wrap_`/`_input_`/`_answer_`/`_markdown_`/`_content_`/`_block_`/ `_output_`/`_command_`/`_card_`/`_button_`/`_line_`)都是**方向安全**的(只限制宽度/字号,不强制尺寸)。 ### 🐛 图片:损坏图能穿到上传(会话被永久毒死)+ 合法 PNG 发不出去(报 TRANSPORT) - **病灶**:Android 用的纯 JS `sharp-shim.js` 过去只解析文件头,`resize`/`png()`/`jpeg()` 全是空操作, **返回原始字节却按请求格式标注**。同一处谎言分两个分支发作: · 目标尺寸 ≥ 源尺寸时走直通,跳过 `verifyRequestImage()` → 坏图原样上传 → provider 400, 且图已进会话历史,**之后每次请求都复现**; · 目标尺寸 < 源尺寸时进重编码,断言发现“声明 jpeg / 实为 png” → 抛 `AttachmentError`, 又被适配器包成 `TRANSPORT`(原始原因丢失)——**只对“需要缩放的非 JPEG 图”发作**(PNG/WebP/GIF)。 - **修法(四处)**: 1. `sharp-shim.js` **重写**:新增**纯 JS 的 PNG 编解码**(CRC 校验 + inflate + 反滤波; 位深 1/2/4/8/16、颜色类型 0/2/3/4/6、隔行 0/1;输出 8 位 sRGB)→ `raw().toBuffer()` **真的解码** (这是 `detectImage()` 唯一那道完整性防线)、`resize()` 对 PNG **真的生效**(顺带修了“识图大图不缩放”)。 2. `metadataOf()` 返回**真实 `hasAlpha`**(PNG 看 IHDR 颜色类型 + tRNS)。 3. `encode()` 不再硬编码 mediaType,改以**输出字节的真实格式**声明 → “声明 == 字节”永真。 4. `readRequestImageFile()` 的**快路径也校验**(删掉 `data === attachment.data` 就跳过断言的优化)。 - **边界(已知取舍)**:JPEG/WebP/GIF 仍**原样透传、不缩小、不做像素级完整性校验** (纯 JS 无法重编码,且 `raw().toBuffer()` 对它们**必须返回字节而不能抛错** —— 上游对所有格式都走这道校验,抛错会把全部 JPEG 一起拒掉)。它们会**如实声明真实格式**,不再被 provider 拒收。 - **验证**:替身单测 24 项全过;坏图穿管实验(修复前能穿 / 修复后被拒); 用真机拉回的真实截图(1008×1792)端到端跑通;35 个真实 PNG 解码→缩放→再编码往返全过;JPEG 无回归。 ### 🐛 控制台弹窗风格不统一(安全模式 / 清空日志) - 「安全模式启动」「退出安全模式」「清空日志」「从备份导入还原」四处仍是系统 `AlertDialog`, 会多出一层主题面板外框,与页面其它弹窗不是一套视觉。 - 现全部改用 v1.14.0 已做好的自绘浮层 `conDialog(...)`:圆角卡片、跟随深浅色、同套字体/按钮、点空白取消。 --- ## v1.15.0(正式版 + Lite 共存版 + 兼容版 · 2026-09-24) > 内核升级 **DSH 0.1.7-rc.1**(原 0.1.5-rc.1)。versionCode **37**。 > 详见 `交接文档-内核017升级与v1.15.md`。**⚠️ 本版有一处 P0(见上),请直接用 v1.15.1。** - 内核 0.1.7-rc.1;payload 补丁面重做(7 个文件:flock 空锁回退、 `node-addon-require-builtin` 的 JS 回退、bash-local `sandboxMode`、fs-search 的 Android rg 回退、 attachment-local、session-persistence-jsonl);移动端 `mobile.css` 改写为属性选择器 (Vite 哈希类名随前端构建变化,原先 20 个选择器在 0.1.7 上 0 命中)。 - 新功能:控制台「安全模式启动 / 退出」与「导出全部数据 / 从备份导入还原」。 --- ## v1.14.0(正式版 + Lite 共存版 + 兼容版 · 2026-09-18) > versionCode **36**,内核仍为 DSH 0.1.5-rc.1。 > 详见 `交接文档-v1.14.0.md`。 - 修复覆盖安装丢模型/供应商配置(`settings.yaml` / `.credentials.yaml` 移出强制覆盖清单); 小鲸鱼跑出屏幕(布局落定后再贴边);**升级后「每次都要重新解压」的真因** (`dshKernelChanged` 字符串比对恒为真 → 快速同步从未生效);`dshroot-add` 清理过期插件包空转。 --- ## v1.13.6(正式版 + Lite 共存版 + 兼容版 · 2026-09-15) > 接 v1.13.5:把「**升级用户**」那条路径也覆盖到 —— dex 收权在“文件已存在直接返回”分支也要做。 > versionCode **33**,内核仍为 DSH 0.1.5-rc.1。 ### 🐛 `extractRishDex()` 的“已存在”分支没收权(升级用户中招) - v1.13.5 只在**写出后**给 `files/rish/rish_shizuku.dex` 收权,而升级用户走的是 `if (dex.exists() && dex.length() > 0) return dex;` —— 旧版留下的 0666 副本不会被修。 - 现该分支也调用 `secureDexPermissions(dex)`;加上解压收尾的 `secureDexFiles(payload)`(每次启动无条件跑), 「新装 / 覆盖升级 / App 自提」**三条路径全部覆盖**(本次只改 App Java,未动插件/payload/内核)。 - 这是用户追问“不覆盖旧文件真的可以吗”逼出来的一处漏洞,已修。 --- ## v1.13.5(正式版 + Lite 共存版 + 兼容版 · 2026-09-15) > `payload/rish/rish_shizuku.dex` 权限是 0666 → Android 14+ 的 ART 拒绝加载可写 dex > (进程直接 SIGABRT / exit 134,终端上只有一句 “Aborted”)—— 修。 > versionCode **32**,内核仍为 DSH 0.1.5-rc.1。 ### 🐛 payload 里的 rish dex 是 0666,导致 rish 调用静默崩溃 - **现象**(用户/AI 提交的报告,已逐条核实):任何指向 payload 副本的 rish 调用直接崩溃, `exit=134`(SIGABRT),终端只看到 `Aborted`;真因只在 logcat: `java.lang.SecurityException: Writable dex file '' is not allowed`。 同一文件两份权限不一致:`files/payload/rish/rish_shizuku.dex` 是 `-rw-rw-rw-` ❌, `files/rish/rish_shizuku.dex`(`$SHIZUKU_DEX` 指向的那份)是 `-r--r--r--` ✅ → 主流程不受影响。 - **核实结果**: - `assets/payload.zip` 的 **28604 个条目 unix 权限全部为 0**(create_system=MS-DOS,external_attr=0), 即 **权限不是打包带进去的,而是解压时决定的**(这一点报告说得对); - 解压写入用 `FileOutputStream` 创建文件(本机 umask 下即 rw-rw-rw-);且 `prepareTarget()` 里的 `setWritable(true, false)` 会把 **group/other 的写位也加上**(0444 → 0666)—— 这就是“`chmod 444` 后一更新就复发”的机制。 - **修法**(`MainActivity.java` 四处,四份同步,只改 App Java): 1. 解压写入循环:`name.endsWith(".dex")` → `secureDexPermissions(target)`(`chmod 0444`); 2. 解压收尾:`secureDexFiles(payload)` 兜底把 `rish/rish_shizuku.dex`、`vscreen/vscreen_shizuku.dex` 收成 0444(覆盖升级时旧文件不会被重写,扫不到就白搭); 3. `extractRishDex()`:**App 自己那份** dex 写出后立即收权(插件 begin 前也存在窗口期); 4. `prepareTarget()`:`setWritable(true, false)` → `setWritable(true, true)`(不再把写位扩散到 group/other)。 - ⚠️ **本次未在真机复验**(做这轮时用户已关无线调试)。验收方式: ```bash adb shell ls -l /data/user/0/com.deepseek.harness/files/payload/rish/rish_shizuku.dex # 期望 -r--r--r--(旧版是 -rw-rw-rw-) ``` 再用报告里的复现命令跑一次,应当不再 `Aborted`、正常输出。 --- ## v1.13.4(正式版 + Lite 共存版 + 兼容版 · 2026-09-15) > App 侧 JSON 取字段改成**认识转义** —— 修 `shizuku_shell` 传“带引号/换行命令”被截断的隐蔽 bug。 > versionCode **31**,内核仍为 DSH 0.1.5-rc.1。 ### 🐛 `shizuku_shell` 传带引号/换行的命令会被截断(静默执行错的东西) - **根因**:`MainActivity.jsonField()` 是朴素实现(“找 `"key"` → 找 `:` → 取下一个 `"` 到再下一个 `"`”), **不认 `\"` `\\` `\n` 等转义**;而插件是用 `JSON.stringify()` 发的正常 JSON,命令里的 `"` 会被写成 `\"` → 在那个引号处提前结束。 - **真机可复现的例子**(用同一段算法跑出来的): ``` 插件发: {"command":"pm install -r \"/sdcard/Download/my app.apk\"", ...} 旧实现解析出: "pm install -r \" ← 断在转义引号处,路径全丢,命令照跑 新实现解析出: "pm install -r \"/sdcard/Download/my app.apk\"" ``` 多行命令同理:`{"command":"id\necho hi"}` 旧实现得到字面 `id\necho hi`(`\n` 没还原成换行)。 - **修法**:`jsonField()` 改为逐字符扫描、处理 `\" \\ \/ \n \r \t \b \f \uXXXX` 的标准转义(约 25 行,无新依赖)。 同一个方法也被 `/schedule`、`/setting`、`/clipboard`、`/overlay`、`/usage` 使用 → 那几条一并变正确。 - **验证(本地单测)**:`tmp-diag/json-test/`(`JsonFieldTest.java` 的方法体由 awk **从源码逐字抽取**,避免手抄走样) 11 个用例全过 `PASS=11 FAIL=0`:普通值回归、转义引号、换行、反斜杠、`\uXXXX`、值含键名、数字字段(与旧行为一致)、 缺字段、空串、`sh -c "…"`、转义斜杠路径。 - 说明:本轮**只改 App Java**,未动插件/payload/内核 → 引擎内特权通道行为不变(v1.13.2 已端到端验证)。 --- ## v1.13.3(正式版 + Lite 共存版 + 兼容版 · 2026-09-15) > 控制台「管理 Shizuku」弹不出授权框 —— 修。versionCode **30**,内核仍为 DSH 0.1.5-rc.1。 ### 🐛 控制台点「管理」(Shizuku 行)不弹授权框,而引擎里 AI 调 shizuku 却会弹 - **真机现象(用户回报)**:没有给本应用授权 Shizuku 时,引擎里让 AI 调 shizuku → **能弹出** Shizuku 授权框; 但在控制台权限页点 Shizuku 行的「管理」→ **什么都不弹**。 - **根因**:`conPermAction("shizuku")` 里原来只调了 `probeShizuku()`(纯探测),**从未发出过真实请求**, 自然不会触发 Shizuku 的授权框;而引擎里那条路会 spawn `rish`(发 `REQUEST_BINDER` 广播), Shizuku 应用收到**真实请求**才弹框。 - **修法**: 1. Shizuku 行改走统一入口 `showShizukuDialog()`(实时探测 → 未授权则请求 → 仍未授权再给手动引导); 2. 新增 `triggerShizukuPrompt()`:在 `Shizuku.requestPermission()` 之外,**补两条与引擎同款的真实触发** —— ① `Shizuku.getBinder()`(client provider 路径)② App 自己 spawn 一个 `rish`(同 dex、同 env 清理、 同广播);真机实测 `requestPermission()` 在本机(Shizuku 13.6.0 + ColorOS 15)不弹框; 3. 3 秒后回探刷新(授权后界面立刻变),12 秒仍未授权 → 弹「需手动授权」引导框(带「打开 Shizuku」按钮)。 --- ## v1.13.2(正式版 + Lite 共存版 + 兼容版 · 2026-09-15) > 接 v1.13.1:真机端到端实测又抓出并修掉 1 个**我自己引入**的 bug(工具输出 schema 非法)。 > versionCode **29**。内核仍为 DSH 0.1.5-rc.1。 > ⚠️ **v1.13.1(vc28)只用于调试、不要发布**——它的 `shizuku_shell` 结果会被判非法输出。 ### 🐛 `shizuku_shell` 返回“结果 schema 不匹配”(v1.13.1 引入) - **现象(真机、App 内 AI 原话)**:`id output is not obtainable through shizuku_shell right now because of this app-side schema mismatch (a bug in the tool's result schema versus what it returns)`。 - **根因**:`appShellCmd()` 的返回值里带了内部字段 `transport`,而 `shizuku_shell` 的输出 schema 是 `additionalProperties:false`,DSH 校验不通过 → 整个工具结果作废(App 通道其实**已经执行并返回了**)。 - **修法**:`privCmd()` 返回前 `delete r.transport`(该字段只在插件内部作信号用)。 - 教训(与本项目已有的两条老坑同源):**输出 schema 与实现必须同步**;`additionalProperties:false` 下 多一个字段 = 结果作废(v1.6、v1.12 都踩过同类)。 --- ## v1.13.1(正式版 + Lite 共存版 + 兼容版 · 2026-09-15) > **Shizuku 特权通道改走 App 进程(根治“Request timeout”)**。versionCode **28**,内核仍为 DSH 0.1.5-rc.1。 ### 🐛 Shizuku 工具一直“Request timeout” —— 真因与根治 - **真因(真机日志实锤)**:引擎内的 `rish` 子进程(`app_process … ShizukuShellLoader`,以正式版 uid 运行) 调 Shizuku 时,Shizuku **服务端需要回头找 Shizuku 应用本体校验“这个包有没有被授权”**; 而 ColorOS 会冻结/查杀 Shizuku 应用: ``` OPM: handleAppExit … package=moe.shizuku.privileged.api reason=9 (EXCESSIVE RESOURCE USAGE) subreason=7 (EXCESSIVE CPU USAGE) … description=excessive binder traffic during cached state OplusHansManager: uid=10361 … F exit(), F stay=144 … unfreeze … reason: UidGone NativeFreezeManager: freezeForAppSwitchScene … mFgAppPkgname moe.shizuku.privileged.api ``` 应用一被冻结/查杀,那一跳就阻塞 → Shizuku 客户端库报 `Request timeout. The connection between the current app (…) and Shizuku app may be blocked by your system…` - **为什么以前查不出来**:`adb`(uid 2000 = Shizuku server 自己的 uid)**天然免检**, 连把 `RISH_APPLICATION_ID` 填成不存在的包名都能跑通 —— 所以“从 adb 测 rish”永远看不到这个故障; 而 App 进程内的 Shizuku API 通道(`Shizuku.getBinder()` + `IShizukuService.newProcess`,虚拟屏核心就是它拉起的)**实测一直正常**。 - **根治**:新增 App 本地路由 **`POST 127.0.0.1:/shell`**(`MainActivity`), 在 **App 进程内**用 Shizuku API 以 shell 身份执行命令并回收 stdout/stderr/退出码(带超时终止); 插件 `dsh-tool-shizuku` 的 `shizuku_shell` 改为**优先走这条通道**,`rish` 仅作旧 APK 兜底。 - **安全**:`/shell` 需要 **`APP_LOCAL_TOKEN`**(App 开机生成 32 位随机串、存 `dsh_prefs`、经 env 只交给本应用引擎), 避免设备上任意应用通过 127.0.0.1 调用拿到 shell 权限。 - **`shizuku_status` 说真话**:两条通道都实跑探针,分别回报 `shizuku(App 进程 / Shizuku API)` / `shizuku(rish 兜底)`,失败时把两边原因都带出来。 - **环境侧(用户侧)**:Shizuku 应用需在 ColorOS 里允许后台活动(否则还会被厂商冻结); 本次已用 `cmd deviceidle whitelist +moe.shizuku.privileged.api` 把它加入电池优化白名单。 --- ## v1.13(正式版 + Lite 共存版 + 兼容版 · 2026-09-14) > **控制台/引擎判定大修**(12 项)+ **Shizuku 门卫修复** + **老 WebView `Iterator` 崩溃修复** + 界面/分享打磨。 > versionCode **27**。内核仍为 DSH 0.1.5-rc.1(随 v1.11 升级,本版未动)。 ### 🔧 控制台与引擎状态(真机逐项实测) - **关插件把 `cordis.patch.yml` 写坏 → 引擎启动即崩(致命)**:旧实现删掉 `- id:` 整行再把条目搬到文件顶部 → 悬空 `name:` 造成 `duplicated mapping key`;且搬到顶部也不生效(patch 按顺序生效,须在 `- insert:` 之后)。 现改为**原地**增删 `disabled:` + **结构自愈**(起引擎前/启动自检时自动修复)。 - **「停止」停不掉**:新增 `engineStartAborted` / `engineStoppedByUser`(打断在飞的等待 + 看门狗不再自动拉起)。 - **引擎在跑却显示“未启动”**:探测原本在主线程做网络 IO → `NetworkOnMainThreadException` 被吞; 现探测全部移到后台线程并缓存(`conProbeEngineNow`),点击启动也先后台真探一次。 - **启动中界面“一闪一闪”+ 按钮提前点亮、点进去没反应**:真机 node 就绪需 17~43 秒, 旧判定把“进程活着”当“已就绪”;现**只认真实端口探测**,未就绪期间显示「启动中…」并**禁用**入口。 - **重复拉起两个 node(抢 3080,通知端口 EADDRINUSE)**:`launchEngine` 增加“已有 node 存活”“端口已 listen” 两道防线,只等待不再 spawn;`startNotifyServer` 改为幂等。 - **黑鲸鱼悬浮窗 / 常驻通知一直显示“引擎未运行”**:探测只认首页 HTML 的 ``; 现与 `isDshEngine` 对齐(`303/302 = token 有效`、`401 + dsh web authentication required = 引擎在跑`)。 - **定时任务**:`engineReady()` 同上修复;`enginePort()` 三版原**都硬编码 3080**(Lite 的定时任务会打到正式版引擎)→ 改为按包名派生(3080/3082/3084)。 ### 🐛 Shizuku 门卫不认(正式版引擎自称 beta) - `MainActivity.spawnNode()` 曾**硬编码** `SHIZUKU_APP_ID="com.deepseek.harness.beta"`,而三版共用同一份源码 → 正式版/兼容版也在自称 beta,`rish` 拿它去 Shizuku 要授权时包名/uid 对不上 → 授权被拒。 - 现改为 `getPackageName()`(与 `ScheduleExecutor` 一致)并写回 `dsh_prefs:shizuku_app_id`。 ### 🌐 老 WebView:`Failed to load plugins`(`Iterator is not defined`) - 官方插件 `@deepseek-ai/dsh-client-ui-sidebar-documentpreview` 顶层直接读 `Iterator.prototype.join` (ES2025 Iterator Helpers,WebView 122+ 才有)→ 老设备 `ReferenceError` → 插件 import 失败 → 前端白页。 - **实测发现连现代 Chromium 都还没实现 `Iterator.prototype.join`**,所以补丁对所有设备都必要。 - 修法:Iterator polyfill 注入 `mobile-patch/mobile.js`(body 末尾普通脚本,早于所有 module,三版通吃); 兼容版另并入 `legacy-patch/polyfill.js`。 ### 🎨 界面与分享 - **退出弹窗“外面还套了一层小白边/小黑边”**:系统对话框面板的圆角/描边露在直角色块外; 现将窗口背景置为**全透明**、卡片自绘圆角。 - **「分享」日志改为文件形式**:新增最小只读 `LogShareProvider`(本项目无 androidx,且 targetSdk 28 不允许直传 `file://`), 把 `dsh-web.log`/`startup-diag.txt` 拷到 `cache/share` 后以 `content://` + 读权限授权分享(多文件走 `ACTION_SEND_MULTIPLE`)。 - **无障碍按文字/坐标点击点不到按钮**(AI 操作手机的基础能力):新增 `closestClickable()`(向上找可点击祖先, 带面积护栏);`findNodeByText` 改为择优匹配;`findNodeByPoint` 改为最深可点击节点。 ### 🔧 验证 - 用**从 MainActivity.java 原文抽取**的同一份方法做单测(`tmp-diag/patch-test/`): 自愈→幂等、关/开插件、连点 5 轮不增生;产出文件用**内核自带的 yaml 解析器**逐个验证可解析。 - Iterator polyfill:在真实浏览器里模拟“无 Iterator 的老 WebView”,验证插件原句不再抛错、 各类迭代器 `join` 行为与规范一致;并确认不会覆盖原生实现。 - 真机(PLT120 / Android 15):逐秒采样验证 —— 启动过程不再闪烁、按钮不再提前点亮、 node 始终只有 1 个;无障碍 `tap?text=启动引擎` 返回 `method="node-text"`(修复前 found:false / gesture)。 --- ## v1.12(正式版 + Lite 共存版 + 兼容版 · 2026-09-13/14) > **冷启动先进原生控制台**(不进 DSH 网页):解压/启动分两步、权限八项检测、插件开关、日志查看导出。 > versionCode **26**。**只改 App 外壳,未动 DSH 内核与网页**。 - **控制台四页**:① 解压/启动(拆成两步,无“第 1/2 步”标签)② 权限(八项真实检测,点击跳系统页) ③ 插件开关(读写 `cordis.patch.yml` 的 `disabled`)④ 日志(查看/导出/分享);主题跟随系统深浅色。 - **修:引擎在跑但控制台显示“未启动”**: ① 探测器用带 token 的 URL 探测,**把一次性 token 吃掉了**,WebView 随后拿同一个 token → 401(白屏); 现改为**只用不带 token 的 `/`** 探测(`401 + dsh web authentication required` 也算“引擎在跑”)。 ② 旧实现只看内存里的进程句柄,App 重启后 `nodeProcess == null` → 恒显“未启动”; 现改为**先探端口、再回退看句柄**。③ App 重启后丢 token → 新增 `conTokenFromLog()` 从引擎日志尾部捞回。 - **兼容版白屏的真因(历轮误判)**:`build-legacy.sh` 把 Vite 的 `<script type="module">` 换成 head 里**不带 defer** 的普通 script → 在 `<div id="root">` 解析前执行 → `missing #root` 纯白页; 现输出 `<script defer src="/assets/index.legacy.js">`。 - **「增加供应商」按钮置灰的真因**:`dsh-llm-pi-ai` 被 `cordis.patch.yml` 禁用(旧理由已过期); 现取消禁用(0.1.5 树里 `@earendil-works/pi-ai` 为纯 JS,无原生模块)。 --- ## v1.11(正式版 + Lite 共存版 + 兼容版 · 2026-09-12) > 内核升级 **DSH 0.1.5-rc.1**(原 0.1.1-rc.2)+ **6 个 android 插件缺陷修复** + **虚拟屏比例归一化**。 > versionCode 25。**这是 v1.7.5 之后最大的一次更新**(跨 v1.8 → v1.11,共 4 个小版本)。 ### ⬆️ 内核升级到 DSH 0.1.5-rc.1(用「旧法」重建依赖树) - 上一轮曾用 `npm install --install-strategy=nested` 重装整个依赖闭包 → 内核树 **1214 MB**、486 包 **1691 个副本**、APK **343~526 MB**,并且丢了 `dsh` 包内的 `config/agent-presets/`(npm tarball 只含 lib)。 - 本轮回到项目既有的「旧法」:以既有可工作内核树为基线 → 用 npm 解析出 0.1.5 的 **hoisted** 闭包 → 按旧树布局组装 → 重放补丁面 → 剥离原生模块 → 重建 APK。 - 结果:内核树 **214 MB**、树内 **0 个原生 `.node/.so/.dll`**、APK **123 MB**,结构完整。 ### 🐛 修复 6 个 android_* 插件缺陷(均已真机逐条验证) | 工具 | 问题 | 修复 | |---|---|---| | `android_see` | 截图落在 **App 私有目录**,跨包读必 EACCES(“看图”能力彻底失效) | 无障碍截图改写到**共享目录** `<sdcard>/<pkgRoot>/screenshots/`;并修掉真因——**无障碍端口串台**(见下) | | `android_touch_status` | `held[]` 实际返回 `elapsedMs`,schema 未声明 → `additionalProperties:false` 判 invalid output | schema 补 `elapsedMs` | | `android_schedule` | App 返回体带 `repeat`,schema 未声明 → 整个调用报 error,**而闹钟其实已注册**(“报错 ≠ 没执行”) | schema 补 `repeat`;返回值改为**只带已声明字段**;另暴露 `repeat`/`intervalMin` 参数 | | `android_input text` | **中文全部丢失、exit_code 仍是 0**(插件 `safe()` 会删掉非 ASCII,`input text` 也只认 ASCII) | 文本不再过 `safe()`;ASCII 走 `input text`(空格转 `%s`);**非 ASCII 自动改走「剪贴板 + 粘贴」** | | `android_type` | 空字符串(=清空输入框)被真值判断误判为“没传参” | 改为只判 `undefined/null`,放行 `""` | | `android_package install` | **静默假成功**:单发 `pm install` 不解析输出,失败也返回 `exit_code:0`;且 `apk_path` 过 `safe()` 会把中文目录名删掉 | 先拷到 `/data/local/tmp`,再走 `install-create/install-write/install-commit`,**校验输出含 `Success`**,失败如实报错;给了 `package` 再用 `pm path` 二次校验 | ### 🖥️ 虚拟屏比例:真正做到“一律 9:16 / 16:9” - **核心侧归一化**:`createDisplay()` 新增 `toPhoneSize()/normalizeShortEdge()`,短边取 144 的倍数 → 比例精确 9:16/16:9 且 16 像素对齐(如 `1520×720` → **1280×720**)。 - **插件侧默认竖屏**:不传 `orientation` 时插件显式发 `1008×1792`(此前**什么都不发**,尺寸完全由服务端默认值决定——旧核心默认 `720×1520` 并非 9:16)。 - **核心指纹升版**:`BUILD` / `EXPECTED_CORE_BUILD` → `vs112-20260912`。 **教训**:虚拟屏核心是独立特权进程、**比 App 活得久**(实测跨 4 次装包存活),只改核心代码不升指纹 → App 判不出“跑的是旧 core” → 改动**静默失效**。 ### 🔧 其他修复 - **附件按钮点了没反应**:官方前端用 `<input type="file">`,而 App 从未实现 `WebChromeClient.onShowFileChooser` → 已补(含单选/多选)。 - **读图三件套全挂**(`android_see` / `android_vscreen_see` / `read_image` 报 `EACCES: open '/data/user/0'`):attachment-local 的 Android 补丁在 0.1.5 上漏移植两处 → 补 `syncDirectory` 的 EACCES/EPERM 容错 + `link()` 失败退化 `copyFile`。 - **“修了但手机上没生效”**:`FORCE_OVERWRITE_PREFIXES`(fast 同步白名单)补齐补丁面;并新增**内核树布局标记**(`assets/dshroot_layout.txt` = `hoisted-1`),布局变化时整棵重推。 - **三版本端口撞车**:引擎端口三套全写死 3080(注释却写“各用独立端口”)→ 同时装会 `EADDRINUSE`,插件的 3081/3181 会打到**另一个版本**的 App 上。现统一按包名派生:**3080 / 3082 / 3084**,通知端口 +1,无障碍端口 +101。 - **无障碍端口串台(`android_see` 报 EACCES 的真因)**:无障碍服务读的是**跨版本持久化**的 `dsh_prefs:a11y_port`,旧包写下的 3181 在升级后仍被读到 → 正式版和 Lite 都往 3181 绑,**后连的那个静默失败、先连的那个应答**,于是正式版拿到的是 **Lite** 的截图路径。现改为**按包名推导**并纠正脏值。 - **新增 `appPost()`**:App 本地服务会把请求行的 query 丢掉、只把 body 交给 `/clipboard`、`/schedule` → 这两个端点**必须 POST + JSON**;用 `GET?action=write` 会被当成 `read`(写入静默无效,随后粘贴的是剪贴板里的旧内容)。 - **移除右上角浮动「退出」按钮**(系统返回键的确认退出保留)。 - **🔐 移除硬编码的签名密码**:`android-app-fix/build-fix.sh` 里曾**明文写有签名密钥密码**(该文件在此前版本中已经公开,**该密码应视为已泄露**),现改为从 `KEYSTORE_PASS` 环境变量读取、缺失即报错——与其两个兄弟脚本(`android-app/build.sh`、`android-app-fix/build.sh`)保持一致。密钥文件 `release.jks` 始终未入库(`.gitignore` 已排除)。 ### 🙏 开源致谢 - 虚拟屏(vscreen)实现**移植/对齐 [Operit](https://github.com/AAswordman/Operit)**(LGPL-3.0)→ 本仓库虚拟屏相关文件同样按 LGPL-3.0 分发,全文与说明见 [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)。 --- ## v1.10(正式版 + Lite 共存版 + 兼容版 · 2026-09-11) > versionCode 24。**虚拟屏修复版**:解决「`android_vscreen_create` 每次都失败(`displayId=-1`)」+「物理屏看不到虚拟屏」两个问题。 > 预览方案对齐 [Operit](https://github.com/AAswordman/Operit):**H.264 视频流实时解码渲染到悬浮窗**(不是轮询截图)。 ### ✨ 虚拟屏架构重做 - **服务改为 App 进程内嵌启动**:不再依赖 Shizuku/root 拉起 `app_process` shell 进程——shell 域在 Android 15 enforcing 下 `createVirtualDisplay` 会被 Binder 拒(`Bad file descriptor`)。 - **建屏即自动弹出独立预览悬浮窗**:实时显示虚拟屏画面(画中画),**不依赖 App 前后台**,一直显示到虚拟屏关闭。 - **预览窗可拖动、可双指缩放**(对齐 Operit 交互),默认右上角留边距。 - **预览 = H.264 视频流**:服务端 MediaCodec 编码器 → `setVideoSink` 推给 App 内本地 sink → `H264PreviewRenderer` 解码 → 渲染到 SurfaceView。含 **SPS/PPS(csd-0/csd-1)缓存重放**(编码器在建屏瞬间就发配置帧,后挂的 sink 拿不到)。 - **截图**改走 PixelCopy(公开 API,从虚拟屏 Surface 读帧);**输入**走 Shizuku `input -d` / `am start --display`。 - **不再请求 MediaProjection 授权**(用户反感弹窗,且主 App 也用不到)。 ### 🐛 修复 - 预览窗 `updateViewLayout` 崩溃(SurfaceView → 容器)+ 触摸穿透(去 `FLAG_NOT_TOUCH_MODAL`,改用 FrameLayout 容器接收触摸)。 - `dsh-tool-vscreen` 的 `readFile` 从 `node:fs` 误用为 Promise → AI 的 see 报「读取截图失败」;改从 `node:fs/promises` 导入。 - `AbortSignal.any` polyfill(内置 node 版本 < 20.3 无此 API)。 ### ⚠️ 已知边界 - 需要 **Android 11+**;无悬浮窗权限时预览窗不显示;无 Shizuku 时可“看”不可“点”。 - 正式版与 Lite 共存版**同时启动**时 **8999 端口互斥** → 虚拟屏实际二选一。 --- ## v1.9(正式版 + Lite 共存版 + 兼容版 · 2026-08-29) > 内核 DSH 0.1.1-rc.2,versionCode 23,targetSdk 28。虚拟屏(vscreen)能力 + 存储权限自动请求修复(“未知错误”根因)。 ### 🔧 v1.9 二次修复(对齐 Operit 成熟方案,Android 15 实测问题解决) - **存活方案(关键)**:真机(Android 15 + Shizuku)实测 server 启动后被杀(日志停在 HTTP listening,进程消失)——根因是 rish `-c` 命令会话结束会清理子进程(setsid/nohup/double-fork 均无效)。**对齐 Operit(Android 最成熟同类)**:改为 App 在 Shizuku 授权后通过 `IShizukuService.newProcess()` 启动 server(独立进程,App 持有 IRemoteProcess,不受命令会话清理)——项目 libs 已内置 shizuku-aidl.aar(13.1.5,IShizukuService.newProcess 直接可用)。MainActivity `ensureVscreenServer()` 实现:newProcess cp jar → chmod → newProcess 启动 app_process server;启动时自动探测 8999(已活则跳过)。 - **Android 10+ 安全模型(实测)**:App 进程内建虚拟屏被拒(Requires CAPTURE_VIDEO_OUTPUT or MediaProjection)——确认虚拟屏必须由特权进程创建(shell uid 有 CAPTURE_VIDEO_OUTPUT);曾尝试 App 内公开 API 建屏(Operit VirtualDisplayManager 思路)实测被权限拒绝,已回退特权 server 方案。 - **注入改为特权短命令**:tap/swipe/key 由插件执行 `input -d <displayId> tap/swipe/keyevent`(一次性命令,无需长驻特权进程;`-d` 语法已实测正确)。 - **插件 ensureServer 重构**:Shizuku 场景只探测 8999(App 已自动启动 server)不再自行 rish 启动;root 场景保留 su 路径。 ### ✨ 新增(虚拟屏 vscreen) - **真·虚拟屏服务(VirtualScreenServer)**:app_process 特权进程(root 或 Shizuku shell 通道),反射 `DisplayManager.createVirtualDisplay()` 创建独立虚拟显示器;MediaCodec H.264 编码器 surface(无编码器环境回退 ImageReader);`ActivityOptions.setLaunchDisplayId` 启动 App 到虚拟屏;`InputManager` 反射 + `setDisplayId` 定向注入触摸/按键;FakeContext(60+ 抽象方法的最小 Context 实现)供 DisplayManager 构造时传入,解决 app_process 无 Context 的 NPE。 - **插件 `dsh-tool-vscreen`(8 工具)**:android_vscreen_create / status / launch / see / tap / swipe / key / close。see 通过 attachments 注入注册(截图作为图片发送给视觉模型),返回 screenW/H、imageW/H、scaleX/Y 供坐标换算。 - **部署链路**:server jar 随 APK assets 打包(vscreen_shizuku.jar),App 启动提取到外部共享目录(/sdcard/DeepSeekHarness*/vscreen/,App 可写、shell 可读),插件以特权通道拷贝到 /data/local/tmp 后 app_process 加载;每次启动强制覆盖(防旧 jar 残留)。 - **可观测性**:server 日志写 /data/local/tmp/vscreen.log(shell 可读);插件启动失败自动带出日志末尾;cp 失败报真实 stderr(不再“未知错误”)。 ### 🐛 修复(“未知错误”根因) - **首次启动存储权限**:Android 10+ 上 targetSdk28 的 App 写 /sdcard 必须先运行时授权;之前不弹窗,提取 jar 到外部目录 EACCES 后静默 fallback 私有目录 → 特权通道(shell)读不到 → vscreen 永远起不来,AI 只看到“未知错误”。现 onCreate 自动请求存储权限,授权回调里重新提取 jar 到外部目录(日志:vscreen jar re-extracted after permission grant),模拟器全流程验证通过。 - **manifest 加 requestLegacyExternalStorage="true"**(Android 10/11 legacy 访问保险,三版本)。 - **vscreen 工具无屏时明确报错**:launch/tap/swipe/key/see 在未 create 时返回“虚拟屏未创建,请先调用 /vscreen/create”,不再假成功(之前 launch 会启动到主屏 displayId 0、tap 静默失败)。 - **android_vscreen_see 返回尺寸字段**:server 截图响应补齐 screenW/H、imageW/H、scaleX/Y(之前插件拿默认 0/1,坐标换算失效)。 - **正式版/Lite/兼容版 MainActivity 统一**:FORCE_OVERWRITE_PREFIXES 增加 dsh-tool-vscreen(随 APK 覆盖旧插件,避免旧版插件挡住更新)。 ### ⚠️ 说明 - 虚拟屏需要 **Android 11+** 且 root 或 Shizuku 授权;模拟器无 H.264 编码器/软渲染限制,请以真机验证为准。 - 已验证(模拟器):jar 部署链路、app_process 启动(shell uid)、FakeContext 修复 NPE、插件 8 工具注册、无屏操作明确报错、see 尺寸字段、存储权限自动请求全流程。 - 未验证(需真机):createVirtualDisplay 成功建屏(模拟器无 H.264 编码器 + ImageReader Binder fd 限制;真机有硬件编码器,为 Operit/scrcpy 同款标准路径)。 # DeepSeek Harness Android · 移动端优化改动清单 ## v1.8(2026-08-29 凌晨 · **未单独发布**) > versionCode 22。**中间构建**:虚拟屏插件(`dsh-tool-vscreen`)首次随包进入 payload,但**服务端尚未落地** > (包内没有 `assets/vscreen_shizuku.dex`,App 内也没有任何 vscreen 代码)——该版本的虚拟屏必然不可用。 > 约 1 小时后被 **v1.9** 完整取代,未单独发布;此条仅作版本沿革记录。 --- ## v1.7.5(正式版 + Lite 共存版 + 兼容版 · 2026-08-28) > 内核 DSH 0.1.1-rc.2,versionCode 21,targetSdk 28。Termux 共存修复 + 无障碍手势引擎 + 工具输出校验修复。 ### 🐛 修复 - **Termux 共存**:内置 node 在 Termux 环境编译,OPENSSLDIR 被编译死为 /data/data/com.termux/files/usr——装了 Termux 的设备启动即崩(EACCES),没装时靠 ENOENT 静默才碰巧正常。payload 内置最小 openssl.cnf,启动时注入 OPENSSL_CONF 指向它,有无 Termux 均稳定。 - **android_usage 报错/空结果**:appRequest 不解析 JSON(execute 返回字符串,被 DSH 工具运行时 schema 校验拒绝:"value" must be an object);已改为返回对象,并为 android_usage / android_overlay 补全输出 schema(days/apps/running/engineUp/granted/msg);days 参数(GET query 丢失)与 android_overlay 的 action 参数一并修复。 - **无障碍手势引擎**:新增通用触摸原语(多指同时、按住保持、拖动、分数坐标 fx/fy、网格截图);修复等待时序下手指数误抬起、手势中途出错的状态污染、同请求内 down 后 move 误判;网格截图 Immutable bitmap 崩溃修复。 ### ✨ 新增(无障碍) - android_swipe / android_hold / android_touch(状态式虚拟触摸屏,多指核心)/ android_gesture(多笔组合手势)/ android_touch_status(查询按住的手指) - android_tap 支持 fx/fy 分数坐标(免疫截图缩放误差);android_see 返回屏幕/截图尺寸与换算系数 + grid 网格叠加;android_screen 无节点界面(Unity/游戏)自动提示改用截图 + 分数坐标 # DeepSeek Harness Android · 移动端优化改动清单 ## v1.7.0(正式版 + Lite + 兼容版 · 2026-08-27) > 内核 DSH 0.1.1-rc.2,versionCode 17,targetSdk 28。新增**无障碍自动化(读屏 + 模拟操作)+ 屏幕理解(无障碍截图 + 视觉模型)**。 ### ✨ 新功能 - **无障碍屏幕助手**:系统设置 → 无障碍开启「DeepSeek Harness 屏幕助手」后,AI 可读屏(android_screen)、点击(android_tap)、输入(android_type)、返回/主页(android_back/android_home)、滚动(android_scroll)、截图理解(android_see,无障碍截图 + attachments 发给视觉模型) - 无障碍服务本地 HTTP 端口 = 通知端口 + 100(正式版 3181 / Lite 3183 / 兼容版 3185),三版本共存不冲突 - **启动失败诊断**:失败时多位置写 startup-diag.txt(外部目录/App 专属/Download),引擎日志镜像到外部 dsh-web.log(无需 root/adb 可读排查) ### 🐛 修复 - **工具名冲突导致引擎启动失败**:无障碍插件 android_input 与特权版(dsh-tool-android)android_input 重名,真机有 Shizuku/root 授权时引擎加载插件树报 duplicate → 无障碍输入改名 **android_type** - **pwsh-sandbox 禁用不生效**:cordis.patch.yml 里条目 id 写的是 dsh-pwsh-sandbox,插件树实际 id 是 pwsh-sandbox → 禁用匹配不上、引擎启动 pending 崩溃 → 修正 id - **表格窄屏被裁切无法横滑**:mobile.css 表格滚动类名哈希随前端构建变化失效 + 前端 md-table-wide 依赖 hover 显示滚动条(触屏无 hover)→ 改用稳定选择器 `[class*="tableScroll"]` 且移动端始终 `overflow-x: auto` - **无障碍输入不进 WebView/网页输入框**:setText 只改无障碍节点、不触发前端 input 事件 → android_type 支持 `paste:true`(剪贴板粘贴,触发前端更新) ### 发布 - GitHub Release v1.7.0:正式版 / Lite / 兼容版 三 APK ## v1.6.5(正式版 + Lite + 兼容版 · 2026-08-25) > 内核 DSH 0.1.1-rc.2,versionCode 16,targetSdk 28。三个版本可共存:正式版(com.deepseek.harness,3080)/ Lite(.beta,3082)/ **兼容版(.compat,3084,新)**。 ### ✨ 新功能 - **AI 工作区(可选)**:权限页 SAF 选择外部共享存储文件夹(如 /sdcard/Documents)作为 AI 文件操作工作根目录,启动时经 `DSH_WORKSPACE` 环境变量传给引擎,bash 工具 cwd 自动切换;不限制工作区外访问权限 - **悬浮窗改 DSH 官方黑鲸鱼图标**(无背景)+ 展开面板显示引擎状态与 **AI 回复状态**(空闲/回复中,每 ~6 秒经 session.list 的 running 字段刷新) - **内置 curl**:Android 系统无 curl,打包 termux NDK 原生构建的 curl 8.21.0 + libcurl/libnghttp2/3/libngtcp2/libssh2 依赖到 runtime,AI 可直接使用 ### 🐛 修复 - **老安卓 WebView 兼容**:DSH 前端(Vite 6)需 Chromium 80+(module script + 可选链/nullish),Android 7/8 出厂 WebView(Chromium 51/59)白屏被误认“引擎启动失败”。正式版/Lite 启动检测 WebView 版本并提示引导;新增**兼容版 APK**(esbuild 打包 + polyfill 转译,老 WebView 可用) - **版本比较 bug**:versionName 带后缀(1.6.5-test/lite/compat)时版本段解析失败变 0,导致误弹“发现新版本”,已改为提取数字前缀 - 悬浮窗会话状态解析(session.list 响应结构为 result.value.items,此前多解析一层) ### 发布 - GitHub Release v1.6.5:正式版 / Lite / 兼容版 三 APK ## v1.6.1(2026-08-24) - Write 工具修复(dsh-tool-fs 写文件链路) ## v1.6.0(2026-08-24) - 修复:history unavailable(attachment-local 缺 maxImageDimension → 默认 2000)、识图必挂(补 readImageRequest)、read_image EACCES(syncDirectory 容错)、grep/glob(内置 rg 15.2.0 + libpcre2)、android_usage/overlay(GET 读超时 + JSON.parse + schema)、Lite SHIZUKU_APP_ID - 新功能:定时任务 Kun 式增强(repeat daily/interval + 结果通知)、应用使用时长(UsageStats /usage)、小鲸鱼悬浮窗(OverlayService)、android_overlay 工具 ## v1.5.5(✅ 正式版:慢启动根因修复 · 2026-08-23) > 纯修复版:功能基线同 v1.5.4(无端口冲突自动换端口,端口被占会启动失败属预期)。 > 内核 DSH 0.1.1-rc.2。真机验证(MT6835 / Android 15):第二次冷启动 ~4s(正式版)/ ~4.6s(Lite,删除外部旧目录后完全内部存储),无超时。 ### 已修复 - **慢启动根因(90s 超时)**:v1.5.1 引入的 `isDshEngine()` 健康检查只读首页**前 4096 字节**查找 `<title>DeepSeek Harness`,但 DSH 首页实际约 14KB,`` 位于**第 ~13.4KB 处**(13KB 内联引导脚本在前)→ 永远匹配不到 → `waitForServer` 干等 90s 超时(引擎其实 5s 就绪)。改为**读完整页面**(上限 256KB,本地读取 <50ms);`ScheduleExecutor.engineReady()` 同步修复 - **排查排除项(有实测证据)**:payload 与 v1.4.0 逐字节一致;真机 cpuset 0-7 / top-app / ~2GHz 无资源限制;外部 FUSE 存储非主因(内部模式同样生效) ### 发布 - GitHub Release v1.5.5:`DeepSeekHarness-v1.5.5.apk`(正式版 com.deepseek.harness,versionCode 12)+ `DeepSeekHarness-Lite-v1.5.5.apk`(Lite 共存版 com.deepseek.harness.beta,端口 3082) ### 📌 说明 - 规划中的新功能(定时任务 Kun 式增强 / 应用使用时长 UsageStats / 小鲸鱼悬浮窗 / 插件适配加强)**代码已完成**(工作区 build/ 与 build-lite/),归入 **v1.6** ## v1.5.0(⚠️ 测试版本:功能增强 + 稳定性修复 · 2026-08-21) > ⚠️ **本版本为测试版本(非正式版)**:新功能已实现且主要链路验证通过, > 但**端口冲突处理存在已知 bug**(3080 被占用时引擎可能起不来,下版本修), > 且定时任务等新功能仍需更多真机验证。发布目的是让用户提前体验,**不保证完全稳定**。 ### 新功能 - **① 端口冲突处理**(⚠️ 有 bug,见下):默认端口被占时自动换空闲端口(3081~3099) - **② ABI 检测**:非 arm64 设备启动时提示(引擎仅支持 64 位) - **③ 补丁启动自检**:`cordis.patch.yml` 缺失/被改坏时自动从 APK 恢复(防"没带禁用配置启动失败") - **④ 电池优化引导**:未设"不限制"时弹窗引导(防后台被杀) - **⑤ 本地设置通道**:`android_setting_app` 工具——给「修改系统设置」权限即可改亮度/音量/超时等(免 Shizuku);**音量走 AudioManager 真实生效**(修复 Settings.System 记录不生效的坑) - **⑥ 定时任务**:`android_schedule` 工具——AlarmManager 系统闹钟 + 前台服务执行,到点**自动拉起引擎执行任务**(无需用户操作),结果可发通知 - **⑦ 剪贴板工具**:`android_clipboard`——AI 读写剪贴板(免权限) - **⑧ 更新提示**:启动时查 GitHub 最新版,有新版弹窗引导下载 ### 已修复 - **音量调节不生效**:`Settings.System.putInt("volume_music")` 只改记录不调音量 → 改走 `AudioManager.setStreamVolume` 真实生效;工具输出 schema 补 `stream/level/max` 字段(修复"写入成功但报输出无效") - **定时任务只发通知不执行**:BroadcastReceiver 里跑线程会被系统回收 → 改走**前台服务**执行;闹钟改 `setAlarmClock`(无需权限、Doze 也触发);**DSH API 调用格式修正**(缺 `type/rpcId/method/payload` 包装 → 补全);**sessionId 解析修正**(indexOf 偏移错误 → 精确匹配 `"sessionId":"`) - **定时任务执行日志**:写到外部目录(`/sdcard/DeepSeekHarnessLite/scheduled-log.txt` / `DeepSeekHarness/scheduled-log.txt`),便于排查 ### ⚠️ 已知问题(下版本修) - **① 端口冲突处理有 bug**:3080 被占时换端口后引擎可能未在目标端口启动(WebView 显示占位服务内容);连带 `ScheduleExecutor` 固定端口与主引擎换端口后不一致。**v1.5.1 修** ### 验证 - [x] 音量(AudioManager + schema):真机通过 - [x] 定时任务(闹钟→前台服务→自动执行→结果通知):真机通过(scheduled-log 确认"任务已发送给 AI") - [x] 剪贴板:真机通过 - [x] 编译:63 class + 2 插件无错误 - [ ] 端口冲突(已知 bug,待修) - [ ] 补丁自检 / 电池优化引导 / ABI / 更新提示:逻辑简单,未逐一真机验证 --- ## v1.4.0(可选特权降级 + 前台保活 + AI 通知 · 2026-08-20) > **背景**:此前系统操作(装应用/改设置/模拟输入)全部依赖 Shizuku, > 未授权时工具仍注册,AI 反复调用失败;且 root 设备无法利用 root 权限。 > 用户诉求:不授予 root/Shizuku 也能正常使用(文件读写/预览/编辑只需 > 「所有文件访问」权限),未授权时 AI 不要一直尝试调用特权工具; > AI 干活时 App 挂后台不被杀;AI 能发通知(只需通知权限)。 ### 功能 - **特权通道二选一**:MainActivity 启动时探测 `su -c id`(root)与 Shizuku 授权状态,通过环境变量 `ROOT_AVAILABLE` / `SHIZUKU_AVAILABLE` 传给内核; 插件执行时 **root(su) 优先,否则 Shizuku**(新增 `suCmd`,与 rish 同构)。 - **未授权不注册特权工具**(关键):`dsh-tool-shizuku` / `dsh-tool-android` 在两者都未授予时**不注册** `shizuku_shell` / `android_*` —— AI 工具列表里 没有它们,自然不会反复尝试;只保留只读的 `shizuku_status` 供 AI 自查, 其描述明确提示"文件读写请用 fs/bash 工具(只需所有文件访问权限)"。 需要系统操作时 AI 会**引导用户授权**(弹 Shizuku 授权页),而非反复失败。 - **文件操作不依赖特权**:DSH 内核自带 `dsh-tool-fs`(read/write/edit)与 bash 工具本就可用,授予「所有文件访问」即可编辑 /sdcard 文件,无需 Shizuku。 - **前台保活服务(EngineService.java)**:引擎启动时 `startForegroundService` 拉起常驻通知服务(`foregroundServiceType="dataSync"`、START_STICKY),挂后台/ 锁屏引擎持续运行、AI 后台任务不被杀;用户主动「退出」时停止服务。 - **AI 发通知(android_notify 工具,仅需通知权限)**:MainActivity 起本地 ServerSocket `127.0.0.1:3081`,收到 `{"title","text"}` JSON 即发系统通知; 插件 `android_notify` **始终注册**(不依赖特权),端口由 `APP_NOTIFY_PORT` 环境变量指定。真机验证:标题/正文正常显示。 - **权限引导页**:Shizuku 行改为「Shizuku / Root 特权(可选)」,文案说明 不授权也能正常使用;root 探测在后台线程执行并缓存(避免主线程跑 su、 避免 Magisk 弹窗反复触发),onResume 时重置重测。 - 版本号:versionCode 5 → **6**,versionName 1.3.3 → **1.4.0** ### 修复的 bug - **通知空消息**:`handleNotifyConnection` 用 `readLine()` 读到空行即停,但 HTTP 正文在空行之后 → title/text 永远为空。改为解析 Content-Length 后精确读 body。 - **聊天记录互通(端口冲突)**:共存版与正式版抢 3080 端口,共存版引擎没起来时 WebView 直连正式版引擎 → 显示正式版聊天记录。共存版改独立端口 3082/3083。 ### DeepSeek Harness Lite(共存版,v1.4.0-lite) - 给"不敢直接升级正式版"的用户试用:包名 `com.deepseek.harness.beta`(与正式版 完全独立共存)、外部目录 `/sdcard/DeepSeekHarnessLite/`、独立端口 3082/3083、 独立 dshhome(API Key 需单独填)。 - 真机验证通过:无 Shizuku 时 fs 工具建文件成功、聊天记录与正式版隔离、通知 标题正文正常、需特权时引导授权。 ### 验证 - [x] 构建通过(versionCode 6,dex 含 MainActivity/EngineService 102336 bytes) - [x] 未授权场景:工具列表仅 shizuku_status(node 实测四种场景) - [x] 引擎自测 HTTP 200(payload 实测) - [x] 真机(Lite 版):无 Shizuku 时 AI 成功创建 /sdcard/Download/test.txt - [x] 真机(Lite 版):通知标题/正文正常显示 - [x] 真机(Lite 版):需特权操作时引导授权(弹 Shizuku 授权页) --- ## v1.3.3(修复:相册出现大量"零分零秒视频" · 2026-08-18) > **背景**:外部运行目录 `/sdcard/DeepSeekHarness/dshroot` 含 2 万+ 文件 > (node_modules 的 .js/.ts/.d.ts 等),Android MediaStore 对未知类型文件做 > **内容嗅探**,把大量文本文件**误判为视频** → 相册出现"零分零秒"的假视频, > 所有使用外部 dshroot 的用户都会遇到。 ### 修复 - **MainActivity 启动时自动创建 `/sdcard/DeepSeekHarness/.nomedia`**: MediaStore 忽略整个外部目录(含 dshroot),相册不再出现误判文件。 幂等(已存在则跳过),外部目录可写时生效。 - 版本号:versionCode 4 → **5**,versionName 1.3.2 → **1.3.3** ### 用户侧修复(已装旧版的用户) - 手动创建:文件管理器在 `/sdcard/DeepSeekHarness/` 下新建空文件 `.nomedia`; 或直接升级 v1.3.3(自动创建) - 相册里已出现的假视频可直接删除(都是 0 字节/损坏文本文件,无内容); 删除后若相册仍显示,重启相册或清除相册缓存 ### 验证 - [ ] 构建通过(versionCode 5,dex 含 MainActivity) - [ ] 引擎自测 HTTP 200 - [ ] .nomedia 创建逻辑进 dex(字符串验证) --- ## v1.3.2(修复:升级用户 UI 不更新 · 2026-08-18) > **背景**:v1.3.0/v1.3.1 的侧栏改造与竖屏适配改的是**核心源码** > (dsh-client-ui-layout / dsh-client-ui-cordis 的 client.js)。外部运行目录 > `/sdcard/DeepSeekHarness/dshroot` 采用"已有文件不覆盖"策略,而这两个 > client.js **不在强制覆盖白名单** → 从旧版升级的用户,外部目录保留旧文件, > 页面仍是旧 UI(无三条杠侧栏、竖屏不适配);只有干净安装/清数据重装的用户 > 才是新版 UI。真机反馈"下载 v1.3.x 页面还是旧版本"即此根因。 ### 修复 - **MainActivity.java `FORCE_OVERWRITE_PREFIXES` 增加 2 项**(升级时强制覆盖): - `dsh-client-ui-layout/lib/client.js`(侧栏改造:三条杠/浮层侧栏/gridColumn) - `dsh-client-ui-cordis/lib/client.js`(插件按钮 header 单实例) - 版本号:versionCode 3 → **4**,versionName 1.3.1 → **1.3.2** - buildenv 重建(清数据被删):从 /sdcard/github 归档恢复 + 工具 wrapper 重写 + devhome 软链 + v1.3.x UI 文件同步 ### 用户侧修复(已装旧版的用户) - 方式一:直接升级 v1.3.2 → 启动时自动强制覆盖这两个文件(REVISION 变化触发补齐) - 方式二:删除 `/sdcard/DeepSeekHarness/dshroot` 重开 App(全量重新解压) ### 验证 - [x] dex 含 MainActivity ✅(94140 bytes,构建校验通过) - [x] 引擎自测 HTTP 200 ✅(v1.3.2 payload 完整实测) - [x] 白名单含 layout/cordis client.js ✅(dex 字符串验证) - [x] payload 含 v1.3.x 新版 UI(gridColumn / toggleSidebar / header.utilities)✅ --- ## v1.3.1(移动端 UI 打磨 + 插件按钮核心化 · 2026-08-17 深夜 ~ 08-18) ### 移动端布局打磨(mobile.css + mobile.js,运行目录同步生效) - **消息操作条多行**:`.p-xYUq_actions`(复制/赞踩/分支/时间/耗时/token)`flex-wrap:wrap !important` (压过插件运行时注入的同名规则),窄屏不再一行溢出。 - **标题栏让位三条杠**:`.wSkVaW_header` 窄屏 `padding-left:52px`。 - **正文/输入框扩宽**:消息区 `.Md3f7G_scroll` padding 32→8px;composer `--dsh-composer-side-clearance/inset` 16/8→4px。 - **输入栏按钮重叠修复**:滚动容器 `scrollbar-gutter:auto`(有会话时滚动条不再占位压窄输入栏); 输入栏右侧按钮保持一行(nowrap)+ 模型名限宽 26vw + gap 6px。 - **Bash 工具卡片防横向溢出**:命令/输出 `white-space:pre-wrap` + `word-break:break-all`。 - **后台任务菜单防溢出**:`.QsffPG_menu` 窄屏 `right:0` 左展开 + 限宽。 - **设置页单栏适配**:`.VOzbGW_panel` 窄屏上下排列(导航横排可横向滚动 + 内容区 `overflow-y:auto` 可滚动)。 - **键盘防自动聚焦**:mobile.js 用 pointerdown 位置判断 focus 来源——切换话题/新会话 自动聚焦输入框时立即 blur(不弹键盘),只有用户点击输入框才弹。 ### 插件按钮(真·改核心源码) - **`dsh-client-ui-cordis/lib/client.js`**:CordisPanel 注册从 `sidebar.footer.action` 改为 `conversation.session.header.utilities`(**单一实例**): - 修掉双实例 bug(sidebar+header 各注册一份 → 两个独立 open 状态 → 面板开在一侧、 点另一侧按钮关不掉); - 面板本身 fixed 全屏(bottom:128px 左下),不依赖侧边栏展开。 - **位置**:mobile.css 把 header 里的 `[data-cordis-badge]` `position:fixed` 到三条杠 下方(52px/8px,32×32,隐藏文字只留图标);**不动 DOM**(按钮留在 #root 内, React 事件委托才有效)。 - 侧边栏里不再有插件按钮(注册已移走)。 ### 其他 - **Session log 按钮禁用**:补丁加 `session-log-download: disabled`(右上角导出 ZIP 入口移除)。 - **补丁自动加载澄清**:`$DSH_HOME/cordis.patch.yml` 由 profile-boot homePatches 自动加载, App 启动**不需要 --patch**;加了反而 duplicate 崩溃(曾误改 MainActivity 又撤回)。 - **备份**:`/sdcard/github/backup-20260817-可运行版/`(mobile-patch + dshroot 关键文件 + 可用 APK)。 ### 验证 - 最终 APK:dex 含 MainActivity ✅、payload 含核心注册 ✅、引擎自测 HTTP 200 ✅ - 真机验证:插件按钮开关面板正常、AI 生成插件审批后可关闭 ✅ --- ## v1.3.0(闪退修复 + 侧栏改造真正落地 · 2026-08-17 深夜) > ⚠️ **v1.2.0 的 APK 是坏的(安装即闪退)**:build.sh 里 javac 路径硬编码指向 > 已不存在的旧 Termux 目录(/data/data/com.coomi.android/...),javac 失败但被 > `|| true` 吞掉,dex 里没有 MainActivity,安装后启动报 ClassNotFoundException。 ### 修复内容 - **build.sh**:javac 路径改为从 DSH_DEV_HOME 推导 + 失败立即中止 + class 数非空校验 + **dex 必须含 MainActivity 才放行**(防止再产出坏包);构建环境 env.sh / d8 / apksigner 硬编码旧路径全部重写为新路径。 - **重新构建**:javac 49 个 class,dex 含 MainActivity(93KB),payload 引擎自测 HTTP 200。 ### 侧栏改造(真·改核心文件,替代 v1.2.0 外部注入) - **直接修改 `dsh-client-ui-layout/lib/client.js`(AppFrame 组件源码)**: - 窄屏(viewport<1024,竖屏+手机横屏)时 sidebar 列恒 0 宽 → 聊天区全宽; - 折叠时左上角渲染**三条杠按钮**(内联 SVG + 内联样式,点击 `actions.toggleSidebar()`); - 展开时侧栏转 **fixed 浮层**(280px,z-30,阴影),不挤压聊天区; - 展开时渲染**全屏遮罩**(z-25),点击遮罩任意位置即收起侧栏; - 窄屏隐藏侧栏/详情拖拽把手; - **CenterColumn/DetailsColumn 显式指定 `gridColumn: 2/3`**——否则 sidebarCol 展开时 转 fixed 脱离 grid 后,grid 自动放置会把聊天区排到第 1 列(0px)→ 聊天区消失、 右边变成空白详情列(真机踩坑:展开侧栏后右侧纯色/内容靠左,靠这个修复)。 - **mobile-patch 清理**:删除 v1.2.0 的旧三条杠注入(mobile.js 第二个 IIFE + mobile.css 相关段落),避免与 AppFrame 原生实现重复(双按钮/双实现冲突);保留 v1.1.1 的 软键盘适配与触摸优化。 - 类名事实纠正:`.pI_x6G_*`(layout)、`.hHd-Xa_*`(sidebar)、`data-sidebar-collapsed` 都是**真实存在**的运行时插件 CSS module 类名(不在预构建 bundle 里,由 ModuleLoader 动态注入),v1.2.0 的旧实现类名其实没写错,但改为原生实现更干净。 - 快速打包:payload.zip 仅 3 个小文件变化 → 用 `jar uf` 就地更新 APK 条目 + zipalign + 重签(约 2 分钟,跳过全量构建)。 ### 验证 - 最终 APK:dex 含 MainActivity ✅、payload 含新 client.js ✅、引擎自测 HTTP 200 ✅ - 运行目录同步:外部 /sdcard/DeepSeekHarness/dshroot(App 立即生效,重启即可见)✅ --- ## v1.2.0(竖屏 UI 改造 · 2026-08-17) > 竖屏适配升级:**左侧竖栏(rail)改为左上角三条杠按钮**,聊天区全宽; > 点三条杠等价于原 rail 顶部小鲸鱼按钮(展开/收起侧栏),功能不缺。 ### 改动内容 - **mobile.css**: - 竖屏(portrait)下 AppFrame 侧栏列恒为 0 宽(`grid-template-columns: 0 minmax(0,1fr) 0 !important`), 左侧 56px rail 整列隐藏,聊天区全宽; - 点三条杠展开侧栏时,侧栏以 **fixed 覆盖层**浮在聊天区上方(`position: fixed; z-index: 30;` + 阴影), **不挤压**聊天区;横屏不受影响; - 竖屏隐藏侧栏拖拽把手; - 新增 `.dsh-mobile-menu-btn`:左上角三条杠按钮,透明底、主题色线条(`--dsw-alias-label-secondary`), 尺寸 36px(图标 22px,与小鲸鱼 24px 相当),按压时才出现柔和背景,风格与 App 主题一致。 - **mobile.js**:新增注入逻辑—— - 竖屏 + rail 折叠(`data-sidebar-collapsed`)时显示三条杠按钮; - 点击 = 等价于点击原 rail 顶部小鲸鱼按钮(`.hHd-Xa_toggle.click()` → `toggleSidebar`); - 展开侧栏后按钮自动隐藏(侧栏自带收起按钮),收起后恢复显示; - MutationObserver 监听 `data-sidebar-collapsed` 变化 + resize/orientationchange 刷新。 ### 验证 - 新 APK(`android-app/DeepSeekHarness.apk`,109MB)构建成功并签名,包内 payload 已含 v2 mobile.css/js; - 运行目录同步:`/sdcard/DeepSeekHarness/dshroot`、内部 `payload/dshroot`(立即生效,无需重装)。 --- ## v1.1.1 稳定基线(原始记录) > ⚠️ **说明**:本 PR 为**稳定基线**(对应实测可用的 v1.1.1), > **UI 移动端适配属于半成品(WIP)**:侧边栏自动收起、设置页"点击跳转"等实验性 > UI 变换在部分设备上可能引入启动/渲染风险,故**不包含在本基线**(将以独立分支/后续版本提供)。 > 本基线优先保证:**启动稳定 + 基础移动端可用**。 > 本 PR 基于原作者 v0.1.0 源码,聚焦两类问题: > **① 真机启动稳定性(node 运行时 + 服务器保活)② 基础移动端可用性(竖屏/触摸/退出)** > 改动文件:`AndroidManifest.xml` / `build.sh` / `MainActivity.java` / `mobile-patch/*` / `README.md` --- ## 一、启动稳定性(修复真机 ERR_CONNECTION_REFUSED) ### 1.1 Node 运行时 soname 符号链接丢失(致命,已修复) - **根因**:`build.sh` 用 `jar cMf` 打包 payload,**把符号链接全部压平成普通内容**;解压后 `runtime/lib/` 只剩带版本号的文件(`libz.so.1.3.2` 等),`libz.so.1`、`libcrypto.so`、 `libssl.so`、`libsqlite3.so.0` 全部缺失 → node 启动即报 `CANNOT LINK EXECUTABLE: library "libz.so.1" not found`。 - **修复**:`build.sh` 在组装 payload 时**按 LINKS.txt 把 soname 目标复制成同名实体文件** (不依赖设备是否支持软链接,动态加载器按名字找文件即可)。经真机验证 node 正常启动。 - 代价:payload 增大 ~34MB(版本化 .so 的实体副本)。 ### 1.2 服务器保活:看门狗 + WebView 自动重试(新增) - **根因**:原版 `webView.loadUrl()` 只执行一次;若 node 未就绪或进程被系统回收,页面永久停在 `net::ERR_CONNECTION_REFUSED`,且没有任何恢复手段。 - **修复**(`MainActivity.java`): - **WebView 失败重试**:主框架加载失败时每 2.5s 自动 `loadUrl(URL_HOME)`,直到服务器就绪(上限 120 次)。 - **node 看门狗**:后台线程每 5s 检查 `healthOk()` + `nodeProcess.isAlive()`;node 死亡且服务不可用 时自动重启引擎并刷新页面(20s 防抖避免风车重启)。 ### 1.3 外部 dshroot 前端资源强制覆盖(保证 UI 资源随 APK 更新) - **根因**:外部 `/sdcard/DeepSeekHarness/dshroot` 采用"已有文件永不覆盖"策略, 旧版本的 `dist/mobile.css`/`mobile.js`/`index.html` 不会被新 APK 覆盖 → 移动端样式不生效。 - **修复**:将 `dsh-web-frontend/dist/mobile.css`、`mobile.js`、`index.html` 加入 `FORCE_OVERWRITE_PREFIXES` 强制覆盖白名单,随 APK 更新。 --- ## 二、基础移动端可用性 ### 2.1 解锁竖屏(AndroidManifest.xml) - `android:screenOrientation="sensorLandscape"` → `"unspecified"`(自由旋转)。 - 版本号升至 `versionCode=2 / versionName=1.1.0`。 ### 2.2 mobile.css 重写(修复"死代码") - **根因**:原 mobile.css 使用的类名(`gdEzaW_`、`hHd-Xa_`、`pbvGtq_`、`qSYn7G_` 等)在 真实前端构建(0.1.0-rc.6 dist)中**不存在**,全部规则无效。 - **修复**:改用从真实构建提取的类名(`_rail_1hk8w`、`_wrap_1ao1y`、`_answer_d4nqi`、 `_markdown_1nba0`、`_block_10eou`、`_item_19372` 等):触摸优化(点击目标 ≥44px)、 竖屏内容全宽、鲸鱼蓝皮肤(`--dsw-alias-brand-primary: #4D6BFE`)。 ### 2.3 mobile.js(稳定基线):软键盘适配 - VisualViewport + translateY 方案,竖屏横屏通用,rAF 节流。 - 刻意**不包含**实验性 UI 变换(侧边栏自动收起、设置页点击跳转等),以保证各设备启动/渲染稳定。 ### 2.4 退出交互(MainActivity.java) - 右上角常驻「退出」浮动按钮(确认对话框后退出)。 - 系统返回键:有历史先 `goBack()`(可关侧边栏),无历史弹确认退出。 --- ## 三、验证情况 | 项目 | 结果 | |---|---| | node 启动(soname 修复) | ✅ 真机验证 node 正常运行 | | ERR_CONNECTION_REFUSED 恢复 | ✅ 看门狗 + 自动重试生效 | | 竖屏自由旋转 | ✅ 已解锁 | | 移动端样式注入生效 | ✅ 强制覆盖白名单保证更新 | | 退出按钮 / 返回键 | ✅ | | 依赖完整性 | ✅ payload 内 239 个依赖齐全(云端实测 dsh --version 可跑) | ## 四、构建与注意事项 - 构建:`bash android-app/build.sh`(需 android.jar、java-17、aapt/d8/zipalign/apksigner、release.jks)。 - 签名:本 PR 未包含签名密钥;安装包需自行签名。 - targetSdk 保持 28(≥29 会导致 node 二进制 EACCES)。 - 首次启动需解压 payload(2 万+ 文件,约 1-3 分钟),期间勿切后台。 - 外部存储权限未授予时回退内部 dshroot;授予后外部优先(已有文件不覆盖,白名单除外)。