# modules.md — 目录与源文件作用(关键函数锚点) > grep 用法:`grep -n "<文件名>.kt" docs/AGENTS/modules.md`。行号随版本漂移,以函数名为准。 > 与 docs/AGENTS/ARCHITECTURE.md 冲突时以 ARCHITECTURE.md 为准。 ## 4. 目录与源文件作用(关键函数带代码位置;文件行数随版本变化,以函数名为准) > **维护者文档(权威登记处,0.13.5 起迁入 docs/AGENTS/)**:`docs/AGENTS/ARCHITECTURE.md`(.kt 模块地图 + 依赖方向 + assets 结构 + manifest 组件)/ `docs/AGENTS/BRIDGE-API.md`(桥协议 + 通道 + 增量节)/ `docs/AGENTS/ANDROID-API-USAGE.md`(android.* 按域分组 + API 等级守卫点)/ `docs/AGENTS/DEPENDENCIES.md`(gradle 依赖 + 升级策略)/ `docs/AGENTS/RUNTIME-PATCHES.md`(assets/patched 文件登记 + 构建期 patches 分工)。本节保留速查职能,与五文档冲突时以文档(源码 grep 实证)为准。 `app/src/main/java/com/dsharnessmobile/shell/`: | 文件 | 作用 | 关键点(0.13.0 定稿) | |---|---|---| | **SplashPolicy.kt / SplashStore.kt / SplashOverlay.kt / SplashToggle.kt / SplashSettingsPage.kt / SplashImportController.kt** | 启动页管理(0.14.9):开屏视频与「打开应用后自动启动」 | `SplashPolicy` 是纯规则(50 MiB 上限、`.ts` 按扩展名与 `video/mp2t` 双拒、固定落点名、字节/时长格式化),JVM 可测;`SplashStore` 写 `files/splash/splash_video.`(临时文件 + 原子 rename,边拷边计数,元数据读不出即拒绝,不覆盖已可用的旧视频);`SplashOverlay` 用 `TextureView + MediaPlayer`(静音、循环、CENTER_CROP,退出淡出后才释放播放器,任何解码失败都移除视图);`SplashToggle` 是自绘药丸开关(48dp 触区、`stateDescription`、动画开关受 `DsUi.animationsEnabled` 控制);`SplashSettingsPage` 挂在启动页齿轮 → 管理中心的三级页;`SplashImportController` 用 `OpenDocument(video/*)`,拷贝在 daemon 线程。播放结束信号是页面自报 `[dsh-boot-ready]`(`MainActivity.onConsoleMessage`),错误/恢复/关闭相位与 90s 上限都会撤下视频。 | | **FileIncoming.kt** | F5 外部文件草稿:校验/净化/拷贝/元数据/清理 | `copyIn` **200MB 有界拷贝**;`sanitizeName/uniqueName/validate`;`tmpWorkspace=files/home/.dsh/workspaces/incoming`;`.sessions` 未 claim 来件也受 task-removed 清理豁免,源文件交 TTL;不生成 `@路径` user message。 | | **UndoGate.kt** | 崩溃自动恢复(F3):看门狗/启动失败→按 fingerprint-bound Hard `{id,name}` manifest 隔离失败 Soft entry(保留原块并追加 disabled override),或使用匹配当前 snapshot fingerprint 的 known-good ID | `runCli` **必须注入 `OPENSSL_CONF=/etc/tls/openssl.cnf`**;Soft Candidate 需同 digest 在两次实际 spawn 上通过 Cordis inventory、enabled preset fibers、`session/list`、`session/modelCatalog`;`.undo-auto-armed/.undo-auto-done` 管观察窗与重试抑制 | | **EngineManager.kt** | 引擎总管:解压/指纹/环境/进程/补丁 | `snapshotFresh()` 接受有效 `.online-snapshot` 的 base/archive SHA 绑定;`recoverInterruptedRefresh()` 冷启恢复共享事务,未收敛失败与残留 marker 会阻断Activity探活/运行时spawn及服务看门狗启动。Factory刷新仍走暂存、工厂条目交换、指纹提交;`onlineUpdateActive` 与 `snapshotRefreshing` 互斥。**0.14.7 诊断补强**:`refreshSnapshotInternal`/`extractSnapshotTo` 的**每条** `return false` 都挂 `lastRefreshFailure` + 稳定 code(`snapshot-stage-residue`/`snapshot-extract-failed`/`snapshot-stage-incomplete`),真因可落 `boot-fail.log`(旧实现只留布尔值 ⇒ `error=none(boolean-failure-path)`,真因只在易失 logcat);**解码之前**先做流式 SHA-256 预检(`SnapshotBundledAsset`,64KB 固定缓冲、不整读入内存),不一致即 `snapshot-bundled-asset-corrupt` 并带 expected/actual/bytes。**0.14.8 P-AC-04 修尺子**:`watchEngineListen()` 的固定 500ms 轮询改为自适应步进——顶层纯函数 `nextListenProbeDelayMs(attempt)`(20ms 起步、1.5 几何退避、`LISTEN_PROBE_MAX_STEP_MS`=100 封顶),单次 connect 预算 `LISTEN_PROBE_CONNECT_TIMEOUT_MS`=150ms 并对剩余窗口取 min;观察窗口仍是 `LISTEN_WATCH_WINDOW_MS`=90s,daemon 线程名与 `markListen` 幂等一字未动。旧口径的量化误差恒为 [0,500)(均值 250ms;loopback 上无人监听是立即 refused,固定节拍不为任何真实开销买单),改后栅格降到均值 49ms / 最大 97ms。**读数不可跨版本直接对比**:同一事件时刻下新探针系统性早约 250ms,`check-boot-budget` 的 C1(HTTP 观测 − LISTEN 观测 ≤ 4000ms)会随之变大(阈值未动)。**PATH 组装(0.14.9 补丁)**:`shellEnv()` 的 PATH 不再就地拼字面量,统一走顶层纯函数 `enginePath(usrDir, profileBinDir)` = `/bin:/system/bin`,仅当 `/node_modules/.bin` 存在时把它**追加在末尾**(工厂 `usr/bin` 优先,用户包不得遮盖工具链)。目录由 `activeProfileBinDir()` 从 `PluginMounts.PROFILE` 推导(不新写 profile 名字面量)。起因:装了带 CLI 的插件(实测 `@deepseek-ai/dsh-subagent-codex@0.2.1-rc.3` → `@openai/codex`)后 `.bin/codex` 就在设备上,但旧 PATH 里没有它,`which codex` 退出 1。注意:这只解决「找得到」,该包的平台二进制在 Android 上仍缺(见 `docs/contrib/kelai141/2026-10-31-codex-bin-on-path.md`)。其余 shellEnv、P-AC-04、#309 取证契约见下方既有说明。`engineBootMayContinue()` 仅供 Activity 启动轮询:观察共享 STARTING 与存活进程,覆盖另一 manager 尚未 spawn 的准备窗;不改变 watchdog/归属使用的 `engineProcessAlive()`。 **0.14.8 堆下限**:`shellEnv()` 注入 `NODE_OPTIONS=--max-old-space-size=`(顶层纯函数,与 `uvThreadPoolSize` 同款可 JVM 断言),取值 = `clamp(设备总内存/2, ENGINE_HEAP_FLOOR_MB=512, ENGINE_HEAP_CAP_MB=1024)`,读不到内存退回下限。此前**无人管理**该值(argv 无标志、env 无 NODE_OPTIONS),`heap_size_limit` 纯由物理内存推导;用 env 而非 argv 是实测 argv 会覆盖 NODE_OPTIONS。另一半在快照内启动器 `usr/libexec/dsh-node`(覆盖 PTC 那类过滤 env 的调用方),双源一致性由 `check-engine-heap-floor.mjs` 判红。`markBootStart(context, engineHeapMb(...))` 另把本次生效堆值落进 boot-segments 的 `note=boot-start engineHeapMb=` 行。 | | **ProfileNormalizeBackup.kt** | Durable normalization backups with exclusive attempt ownership | `write` allocates a strictly named 13-digit sequence beyond existing attempts, uses `CREATE_NEW` without following links and forces its channel to disk. `isValid` requires a non-link regular file with identical bytes. `prune` manages only that exact naming grammar and keeps successful/failed helper quotas separately; UUID and foreign names are excluded. | | **ProfileNormalizationPublication.kt** | Outcome-aware normalization backup settlement | `publish` exclusively creates and forces a pending original before invoking the live update. Only the returned outcome selects a retention group. Failed settlement retains the pending original; bounded orphan cleanup excludes the current attempt, foreign names, directories and links. | | **SafeModeMigrationDeviceTest.kt** | Android qualification of Safe Mode migration | Exercises the actual status/enter/exit methods with isolated production-layout profiles, duplicate state, real links/directories, column-zero comments and verified backup restoration. | | **CapturePipeDrain.kt** | tier-2 截图管道的**有界并发排空**(issue #607):读线程、取消与收尾预算 | `drain(read, closeReader, startCapture, joinTimeoutMs, cancelJoinTimeoutMs)`:读线程**先于** `capture` 启动(管道缓冲 64KiB,先等回执再读会双端互等);正常收尾 `DEFAULT_JOIN_TIMEOUT_MS`=15s、取消收尾 `CANCEL_JOIN_TIMEOUT_MS`=500ms;**每个出口**都置取消标记、关读端、有界 join,回执 false / binder 异常 / join 超时 / 线程被中断一律不留孤儿读线程;binder 异常原样上抛(外层 `capturePng` 的 `runCatching` 把它折成 null)。读实现必须用 `POLL_INTERVAL_MS`=100ms 的 `poll` 轮询让取消有界(关文件描述打不断另一个线程里已阻塞的 `read`)。 | | **RuntimeTree.kt** | 运行时树完整性判据(0.14.2-fx-2 缺陷 A):把「usr/lib 动态库在不在」并入引擎可启动的前置判据 | `missingEntries(root, profileDir, probe)`:基础 3 项 + `REQUIRED_LIBS`(8 条实测 DT_NEEDED:libz.so.1/libcares.so/libsqlite3.so/libcrypto.so.3/libssl.so.3/libicui18n.so.78/libicuuc.so.78/libc++_shared.so)+ 链接目标在树内(兼容 /data/user/0 与 /data/data 双拼写);`snapshotLinkFailure(logText)` 识别 CANNOT LINK 与 library not found;`maySelfHeal` + `DAMAGE_MARKER` 构成「每次 app 运行最多自愈一次」的预算。**issue #309 增补**:`START_RECOVERY_CONFIRMED_ENTRIES`(只含快照自身条目 bin/node · dsh/lib/bin.js · profiles/web)与 `confirmedDamage(missing)` 做**证据分级**——`REQUIRED_LIBS` 成员不进确诊集合(该表不含传递依赖,命中可能是假阴性;issue 原文告诫「不要贸然补全该表」);`allowStartRecovery(confirmedMissing, alreadyRecoveredThisRun, userForced)` 是**唯一**的恢复闸门(预算 + 分级;用户显式动作只放行分级);`describeEntry(file)` 出「absent / present size= mtime=」供取证。旧判据只查 3 项 ⇒ usr/lib 全缺也判完整 ⇒ 引擎反复拉起、每次 CANNOT LINK 而死。 | | **SnapshotRecoveryNotice.kt** | 快照恢复期拒绝回滚的用户可见上报与启动闭锁判据 | `forRejection` 文案说明数据未被替换;`blocksRuntimeStart` 仅在恢复失败明细与事务marker同时存在时阻止probe/spawn;一次性标记 `.recovery-rejected-notice` 仍在成功注入后消费 | | **EngineService.kt** | 前台服务 + 看门狗 | onCreate先前台提升再初始化;启动请求延后到Activity生命周期回调之后;watchdog 5s 探活 + UndoGate 触发 + 唤醒锁续期/释放 + onTaskRemoved 清理(F5 生命礼仪) | | **MainActivity.kt** | 主 WebView、隔离 BrowserHost、桥接线与意图处理 | `FileIncoming.processIncomingIntent`(VIEW/SEND→POST `/api/android/file-incoming`);BrowserHost 按生命周期暂停/销毁;AndroidBridge 接线含 ScreenScope 与 BrowserHost callbacks。 | | **AndroidBridge.kt** | `window.androidBridge` 协议 v1(方法计数由 `check-bridge-symmetry.mjs` 从源码守) | 设置/路径/授权族 + `get/setScreenScope`(用户设置唯一写面)+ `browserHostStatus/show/hide/reload/bounds/viewport` + `vdisplayStatus/Create/Destroy/LaunchSettingsProbe/BackProbe/Bounds` + `a11yStatus/openA11ySettings/unlockRestrictedSettings` + `apkUpdate` + `openFileExternal/shareFile`(文件外部打开与转发;返回 `{ok}` / `{ok:false,error}`)。 | | **AndroidBridge.kt** | `window.androidBridge` 协议 v1(方法计数由 `check-bridge-symmetry.mjs` 从源码守) | 设置/路径/授权族 + `get/setScreenScope`(用户设置唯一写面)+ `browserHostStatus/show/hide/reload/bounds/viewport` + `vdisplayStatus/Create/Destroy/LaunchSettingsProbe/BackProbe/Bounds` + `a11yStatus/openA11ySettings/unlockRestrictedSettings` + `apkUpdate` + `capturePhoto`(原生拍照;受理后经 `window.__dshBridge.onPhotoCaptured` 回推)。 | | **MotionCoalescer.kt**(issue #540 新增) | tier-2 触摸事件流的**合帧整形器**(纯 Kotlin,只用基本类型、不依赖任何 Android 类,可 JVM 单测) | `MotionCoalescer.offer/drain`:DOWN/UP/CANCEL 保序保留、连续 MOVE 只留最后一条(新 MOVE 覆盖尚未被取走的 MOVE)、上限 64 且溢出优先丢最老的 MOVE、队列里没有 MOVE 时拒绝新事件而不丢结构;`TabMotionQueues` 是 tabId → 合帧器的按 tab 注册表(自带锁:binder 线程入队、主线程 `drainAll`,tab 消失 `forget`)。反证用例 `MotionCoalescerTest`(8 例)。 | | **BrowserProcessService.kt** / **RemoteRendererHost.kt**(tier-2 浏览器兼容档) | `:browser` 渲染进程的 AIDL 服务实现(WebView + SCVH 投影)与主进程宿主面(绑定/投影/输入转发) | `forwardMotion` 是 **oneway**:binder 线程只入队(`TabMotionQueues`),主线程 `drainMotion` 逐条 `MotionEvent.obtain`/`dispatchTouchEvent`/`recycle`,取不到 live tab 整批丢弃;destroyTab/destroyAll/onRenderProcessGone 即 `forget`。主进程侧触摸回调恒按「已消费」返回(一次 MOVE 回 false 会被系统当成手势结束);`dispatchTap`/`dispatchKey`/`evaluateJs`/`capture` 仍同步回执。 | | **BrowserHost.kt** / **BrowserHostNavigationPolicy.kt** | 每 Session/tab 的无桥浏览 WebView、捕获目标与请求策略 | `controlOp` 捕获 Session/tab/modelTabId,UI stage focus 分开;`command` 为可信页面的窄 session/uiTabId/tabId 导航面;每 tab identity/viewport/error/refs。`status`/`caps`/UI 回执统一走 [BrowserStatusSemantics](),把 `browserAvailable` 与 `isolationAvailable` 分开:隔离缺失只进 `profileReason` 提示,不把浏览器报成不可用。仅已验证 nonDefault profile 允许 HTTP/loopback,默认拒3080控制 origin、未指定/link-local/metadata 与危险 scheme;TLS 不绕过。 | | [ShotCachePolicy]()(issue #672) | `browser_screenshot` 的临时 PNG 保留策略(纯 JVM,可单测):`prune(dir, nowMs, ttlMs, maxFiles, maxBytes)` 先删超过 TTL(默认 1 小时)的旧图,再按数量(默认 32)与总量(默认 128 MiB)从**最旧**开始删到阈值内;只认 `browser-shot-*.png`,别的前缀/同名目录一律不碰;目录不存在或删除失败只跳过、不抛。调用点 = `BrowserHost.shotDir()`,tier-1/tier-2 共用。`DeviceControlService.pruneShots` 只管 `shot-` 前缀(screencap),两者互不覆盖。 | | **ScreenScope.kt** | 用户拥有的 Android 屏幕访问范围真源 | `virtual-only`(默认)/`real-only`/`all`;stable `real`/`virtual-1` aliases,只有 real 固定 display 0。 | | **SnapshotExtractor.kt** | tar 解压(xz→dest、symlink、exec 属性戳印)+ **zip-slip 防护**(resolveEntry 拒绝 .. / 绝对路径 / 越界 symlink)+ **解码前资产摘要预检**(`SnapshotBundledAsset`) | `extract(input,totalBytes,dest,onProgress,runtimeRoot)`:`runtimeRoot`(默认=dest)是绝对符号链接目标的允许根——快照内 554 个绝对链接中 ~530 个指向 `files/usr/...`(busybox/vim applets),暂存解压必须放行,交换后即正确路径;`isLinkTargetAllowed()` 为纯策略函数(单测覆盖,无需符号链接特权)。**0.14.7**:`extract` 拆出主体 `extractEntries`,tar/xz 走 `try/finally` 关闭且 close 异常进 `addSuppressed`(**不得顶掉正在传播的真因**——真机现场是 `CorruptedInputException`) | | **SnapshotTransaction.kt** | 工厂刷新与在线更新共用的持久事务journal/stage/previous | FACTORY仍按既有STAGED丢弃、SWAPPING回滚、SWAPPED前滚;ONLINE_UPDATE只允许usr stage,健康前SWAPPED冷启回滚、ONLINE_COMMITTED前滚;缺旧usr证据不覆盖live,未知/非法marker fail closed。用户preservedNames与工厂条目合并契约不变。 | | **SnapshotFs.kt** | 符号链接安全的文件原语(0.13.3 新增;0.14.1 修 API 越级崩溃) | `exists/isSymbolicLink/deletePath/move/createDirectories`——全部 NOFOLLOW:递归删除绝不穿透链接进用户数据;**目录枚举用 `Files.newDirectoryStream`(`DirectoryStream` 是 Iterable)而非 `Files.list(...).toList()`**——后者是 Java `Stream.toList()`、**API 34 才提供**,minSdk 26 下真机抛 `NoSuchMethodError`(Error,`catch(Exception)` 抓不住),曾让快照事务恢复永久卡死;防线见 `ApiLevelGuardTest`(坑 153) | | **SnapshotUserData.kt** | 用户配置保留、legacy backup 与活动配置传输 | `prepareStagedSnapshot` 保留 `settings.yaml.imported` 并抑制工厂 reseed;`configurationDocument` 选择真实 web profile patch;`exportConfiguration/importConfiguration` 原子替换/导入备份,旧共享 YAML 不覆盖已迁移 patch;`retireReseededFactorySettings` 仅隔离已知空工厂模板。legacy backup 不复活已迁移 YAML。 | | **ProfilePackageManifest.kt** | profile 根 `package.json` 的纯并集合并器 | Snapshot 刷新和用户数据恢复共用;user JSON 为基保留 pin/未知字段,补入工厂新增 dependency/bundle,解析失败返回拒绝信号。 | | **PluginMounts.kt / EngineManager.kt** 的启动前 Hard bundle 迁移 | 每次 `startEngine()` 决策前调用 `ensureHardBundlesSelected` | 仅自动追加 name/version/patch digest 与可信清单完全匹配的缺失 nested bundle,保留 Soft、依赖 pin 与未知字段。身份漂移逐项记录期望/实际摘要,不阻断普通启动或覆盖手工修复;坏 JSON、缺少权威清单和歧义结构仍拒绝。Safe Mode 与可信恢复继续严格验身份。 | | **MainActivity.kt / RepairState.kt / RepairActivity.kt** | Explicit retry and late cold-start observation | `retryEngineExplicitly` / `ACTION_RETRY_ENGINE` clear the durable gate and retry count only for explicit retry. `mayObserveColdStart` permits passive health observation of the already authorized in-flight cold attempt; resume does not spawn or reset its budget. The assistant keeps model loading and diagnostic export failures separate. | | **CoordBasisPolicy.kt**(0.14.2 新增,141 行) | issue #258 修:无障碍 `nx`/`ny` 归一化的**基准尺寸**解析(单一真源) | `resolve(Reading)` 是**纯函数**(输入可注入,纯 JVM 单测免 Robolectric);优先级 = `maximumWindowMetrics.bounds`(API 30+,多窗口下仍给整屏)> `Display.getRealMetrics()`(26-29 唯一整屏途径,已废弃但必须留)> 当前窗口 bounds(**最后兜底**,显式标 `WIRE_WINDOW_CURRENT` 回显,便于同类缺陷自证);三 wire 值 = `screen-max-window` / `screen-real-metrics` / `window-current`,`Basis.isScreenScope` 即「基准是不是整屏」。真因:分屏/自由窗口/悬浮窗下 Android 返回**窗口**尺寸而非整屏(实报 HUAWEI BTK-W00 2200x1440、窗口 bounds=(1438,106) 733x1389,`nx=0.712` 算出 522 落在屏幕左侧背景应用 ⇒ DSH 自身浮窗整块点不到)。四路统一:`DeviceControlService.coordBasis()`(:579)为唯一入口,`screenSize()`(:581)委托它,click(:1105)/longClick(:1167)/快照 `screen` 字段(:556)/滚动兜底手势(:1300)共用;`withBasis()`(:593)把实际基准并进返回值(`basis`/`basisWidth`/`basisHeight`)。反证用例 `CoordBasisPolicyTest`(227 行 / 9 例,含真实缺陷数值与「不得等于窗口尺寸」双向断言 + 静态断言四条消费路径都走真源) | | **UpdateManager.kt** | 在线快照更新(与 SnapshotTransaction 共用 journal/stage/previous) | ONLINE_UPDATE 只替换 usr;健康探活提交,冷启动恢复未提交事务;legacy `usr-old` 保留兼容且不作新事务归属证明 | | **UpdateChecker.kt** | APK 自更新的**版本号确认链**与下载/校验/安装器拉起 | 六档确认链(gh-proxy → ghfast → github → Pages JSON → ghproxy.net → api.github.com,各档独立超时 8/15/15/8/30/15s),任一档解析出版本号即停;**全失败绝不返回"已是最新"**(按网络类/服务类聚合分类)。`parseManifest` 解析 `MANIFEST.txt` 三列格式并带出每件资产的 sha256(这正是装包校验真正生效的依据,历史缺陷见坑 333);`parsePagesReleaseJson` 解析下载站清单;两条都归一成 `LatestFacts` 再走 `classifyFacts`(选包顺序不变:先确认有本机可用包,再比版本)。`MIRROR_PREFIXES` 与下载站 `MIRROR_TEMPLATES` 逐条同序(门禁 `check-mirror-parity.mjs`)。 Per-attempt download staging and owner-gated publication are covered by `UpdateDownloadIsolationTest`. | | **UpdateCheckDeadline.kt** | Shared monotonic metadata deadline | One total budget for the watchdog, sequential sources, redirect hops and body reads; source budgets include connection and reading, cancellation rejects expired or superseded checks. | | **ApkUpdateAction.kt** | APK 自更新的**唯一状态机与推进者**(Activity 作用域) | 八态 `idle/checking/up-to-date/available/downloading/installing/need-permission/failed`;观察者**多播**(`observe` 返回取消句柄,修掉单槽夺槽导致"点了没反馈、再点静默下载 169MB",见坑 332);检查与下载是**两个入口**(`onCheckPressed`/`onDownloadPressed`),available 态检查按钮由渲染层禁用(`beginCheck` 会清待下载载荷);`start()` 先看续继链(`readyToInstall`/等授权)再看载荷;快照带 `canStart`/`sha256`/`hasReadyArtifact`;watchdog 保证任何终态必收敛。 Epoch admission, result publication and invalidation share one critical section. | | **RepairActivity.kt / RepairModelClient.kt / RepairConversationStore.kt / RepairSessionWriter.kt** | 引擎启动失败后的原生修复助手;不依赖 Node、WebView、插件或快照 | 对话与事件分开持久化,最多 50 个会话;工具卡默认折叠,支持模型与推理强度选择、停止、旋转和浅深色。模型历史保留原生 `assistant(tool_calls) -> tool...`,只对显式 `toolHistoryMode=user-observation` 的兼容路由合并为单条观察,绝不写入时间戳或随机字段。传输只对一次可证明未完成的连接错误使用同一请求体重试;协议响应不重试。UI 同步等待中断安全的闭锁,停止和 Activity 销毁必须先完成会话保存和凭据释放。**主线程只做提交**:模型配置的唯一解码点是后台 `loadRepairModels() -> RepairModelConfig.loadDetailed`(Context 窄入口 `load(context)` 已删除,它就是「一次打开解析两遍」的来源);会话落盘经 `RepairSessionWriter` 单线程串行写(同一时刻一笔、最新覆盖、onDestroy flush 到落盘),`RepairSessionJson.copy` 做结构深拷贝代替整份字符串往返。 | | **WatchdogV2.kt** | 引擎看门狗(v2) | 连续失败熔断;boot 恢复用户同意状态;**FX-212.3 标记消费面**:`.task-done.ndjson` 按字节偏移推进(`markerConsumeWindow` 纯函数 + 有界 CAP + 永不截断),偏移 `@Volatile markerOffset` 并落 prefs `notify.markerOffset` | | **ShizukuSupport.kt** | Shizuku 反射探活(历史:引导页状态行只读展示,不参与授权链) | — | | **ShizukuTransport.kt** / **ShizukuUserService.kt** | 0.14 新增:特权 transport(应用侧 UserService 生命周期 + shell/root 侧 AIDL 实现) | 原生控制器使用固定 argv;获授权的 `android_shell_exec` 经引擎与壳侧范围门、`ShellOps` 到 `sh -c`;文件分块传输;原始 binder 不向页面/引擎暴露,Shizuku 授权由用户在 Shizuku App 授予。**2026-10-02 判据收紧**:`probeRootChannel`(经可注入 `RootProbeEnv`/`SystemRootProbeEnv` 取原始信号)+ 纯函数 `decideNoRootChannel`(`BindOutcome` + `RootChannel` 三态)是唯一 root 通道判据,只认三类证明、其余 UNKNOWN;`autoClearAllowed`/`clearLeaseWhenNoRootChannel` 做入口清算,`forceClearMaintenanceLease` 是用户确认的人工出口(已派发租约照清并回 `terminationUnproven=true`) | | **ShizukuProbe.kt** | 0.14 新增:Shizuku 五态探针(absent/not-running/denied/prev11/…,fail-closed + guidance) | 与 ShizukuSupport 同走反射;调试/诊断面 | | **ShizukuBindState.kt** | 绑定态结构化 code、连接代次与启动失败退避的单一真源(issue #471 扩展) | `binding/attempts/lastError/currentGeneration/failures/startFailed`;发起、重置、超时与断连作废旧代次,回调须携带原代次,迟到的连接、断连或异常不能覆盖新尝试;`reapIfStale` 为非阻塞、幂等看门狗。连续失败(超时/发起即抛/无效 binder)计数,`canAutoRetry`/`autoRetryAfterMs` 给出指数退避(15s→300s,只约束 `kickBind` 自动重试);成功或用户重置清零。纯函数 `classifyShizukuStartFailure`/`shizukuDiagnosticsExcerpt`/`shizukuStartFailureGuidance` 从 logcat 认出 `LoadedApk.makeApplicationInner` NPE 并给出降级 13.5.4 / ADB 通道引导。 | | **RootGrant.kt** | AI root 策略与**免责声明内容版本**知情同意 | `decision` 将 root Shizuku 与显式授权 su 视为替代通道;`isGranted` = raw switch 且 `consentRevision` 覆盖 `DISCLAIMER_REVISION`(`consentValidFor(revision, required)` = `revision > 0 && revision >= required`)——**与 APK versionCode 解绑**(旧实现绑 versionCode ⇒ 每次升级必掉同意,用户实测反馈);老键 `consentVersionCode > 0` 经纯函数 `migratedConsentRevision(stored, legacy, current)` 一次性迁移为新键(新键 `> 0` 时权威、不被旧值覆盖;双键非正 ⇒ 0,撤销后不复活);关闭永远允许;`state()` 保留旧字段名并新增 `consentRevision`/`currentRevision`;`state().ownership` 回读共享维护状态。 | | **RootAccess.kt** | 应用 su 授权检测、最后执行点授权门与固定属主维护 | `requestGrant` 是显式、有界 uid 检测,不承诺管理器弹窗;`execRoot` 经有效 AI 同意与 `RootExecutionFence.command`;`repairOwnership` 仅从已安装签名 APK 运行 `RootRepairMain`,五个固定参数、`ProcIo.readBoundedMillis`、完整 JSON 与退出码联合校验,不执行 find/chown/restorecon 文本链;**2026-10-01 结算硬化**:`execPrivileged` 拉不起 su(spawn 异常)⇒ granted 缓存立即降级 denied(缓存不得比 su 本体活得久),`su-exec-failed` 按「未派发」结算租约(不进 UNKNOWN 隔离);**2026-10-02 派发证据**:`execPrivileged` 在真正派发前,若此刻有在飞租约就 `markDispatched(context, "su")`——`synchronized` 证明不了外部 su 子进程已退出,故此后只允许用户显式清除,不再自动清算。 | | **OwnershipRepairCore.kt** / **OwnershipRepair.kt** | 共享有界 DFS 与 Android held-FD 适配 | core 可注入 fake backend;cap/depth/deadline 在访问/变更前检查;Android O_PATH/O_NOFOLLOW pin、fstat/fchown、protected_hardlinks 前置;失败/截断/未验证变更不报成功,SELinux 不重标。 | | **RootRepairMain.kt** | su 固定 signed-APK helper | `parse` 验证完整 app UID、可信 anchor、相对路径、cap/deadline;`main` 仅输出一个有界 JSON,成功 exit0,否则 exit2。 | | **RootExecutionFence.kt** / **RootOwnershipJobs.kt** | Context 感知公平读写栅栏与 honest pending single flight | 命令零毫秒 timed read tryLock、维护 write;锁前/锁内查耐久 lease;UI立即回、caller最多30s;每租约 pending/UNKNOWN 不被旧结果或 worker返回清掉,关闭caller不取消共享worker。详见 [Root维护]()。 | | **LocalDocs.kt** | 0.14.2-fx-2-root.1 新增:APK 内本地文档唯一出口(免责声明通道) | 与 `ExternalLinks` 同形但**不走 https 判据**(assets 不是外链):页面只传 key(`root-disclaimer`),登记表 `TARGETS` 在壳侧;`read` 从 assets 读全文,`open` 用 AlertDialog+WebView 应用内渲染(`loadDataWithBaseURL` 固定 asset 路径);文档缺失 → `missing-asset`(打包漏文档 fail-closed 报出来,不白屏) | | **ShizukuTransport.resetConnection(context)** | 设置页「重置链接」与超时恢复 | 立即作废 `activeAttempt`、清空 `service/connectedAt/configuredForAge`,放行旧代次 latch,失效 caps 缓存;`discardOnMain` 在 SDK 回调所在主线程依次 `remove=false` 注销缓存、`remove=true` 移除远端。每次绑定有独立回调与 latch;SDK 注册、注销和重建按主线程队列排序。重置响应 `ok=true/reset=true` 表示动作已提交,通道事实由 status 回读;主线程调用者不等待建链。**issue #471**:重置同时清零连续失败计数,点完即可重试;`status()` 额外暴露 `connectFailures/startFailed/retryAfterMs/diagnostics`,连续失败达阈值后由后台线程有界截取 logcat(`ProcIo.readBounded`,不阻塞 UI 轮询),连接成功后清空诊断。 | | **ControlCarrier.invalidateShizukuCache()** | 0.14.2 P1 新增函数:令 `shizukuReady` 缓存立即失效 | **只把时间戳置 0、不顺带改 ready 值**——下一次读取会重新真问一次 Shizuku(唯一权威面);猜一个值就把「缓存失效」变成了「伪造状态」 | | **VdisplayController.kt** / **VdisplayHost.kt** / **VirtualDisplayProbe.kt** | 0.14 新增:虚拟屏 controller(建屏/销毁/`input -d` 探针)+ viewer SurfaceView 宿主 + 建屏 flags 探针 | viewer 几何来自可信 Files stage;`virtual-1` 建成前一律 `screen-not-ready`,绝不映射 display 0 | | **BackGate.kt** | 0.14 新增:返回网关(页内层栈信号 → Activity 决策) | `dshBackBridge`(setAvailable/getBackAvailable)供注入层回传层栈可用性 | | **ConsoleActivity/ConsoleSession** | Embedded terminal | Measures remaining IME overlap; serializes PTY readiness, queued replay and protocol writes; readOutput settles relay EOF and deduplicates exit events | | **ConsoleOutputBatcher** | Terminal output size/deadline batching | Flushes idle PTY output independently of the reader; close drains pending output and releases the scheduler | | **LogCollector.kt** | 调试日志收集 + **P-AC-04/C1 启动分段插桩** + **块L boot 卡住诊断落盘** | **0.14.7**:`bootConfigFingerprint` 增补 `snapshotEmbedded=`(**内嵌** `assets/snapshot.sha256` 声明值,与 live/已提交的 `snapshot=` 并列,既有字段名一字未改)——此前只记 live 指纹,导致「内嵌资产坏了」与「提交值不一致」在 `boot-fail.log` 里无法区分;`writeBootFail` 增加同名可选参数与读取入口 `embeddedAssetFingerprint(context)`(fail-soft)。日文件轮转;判据字段 `t_boot_start`/`t_listen`/`t_listen_ms`/`t_first_http`/`t_first_http_ms`/`t_compose_total`/`t_compose_source` 落**壳侧自有判据文件 `files/boot-segments.log`**(O_APPEND、单写者、超 64 KiB 轮转 .1;字段恒在场,未知写 -1,绝不省字段);**绝不写 engine.log**——它是引擎 stdout 重定向、fd 非 append,壳侧追加会被引擎从自己的偏移整行覆盖(设备实测 r10:三字段全丢、无残尾);采集器在跑时同一行另落按天日志(出口脱敏);**`t_compose_total` 解析 `[perf] TOTAL calls=… totalMs=…`,其解析走 `startProbeTail` 的有界独立 tail(与调试日志采集器解耦,90s 上限、独立读偏移)——旧实现只挂 `tick()`,而采集器默认关闭,这正是恒 -1 的第二半成因;TOTAL 可能早于 LISTEN,`composeTotalUpdate` 先缓存真实毫秒值(不提前发判据),`markListen` 写出的 `note=listen` 行携带该值,后续 TOTAL 更新再单独落行;探针缺席时落 `note=probe-absent`,`t_compose_source` 区分 none/preload-total/a5-singles/a3-cache-only,**A3/A5 口径不含耗时不产出 totalMs(禁冒充)**;`t_first_http` 由 EngineStartFlow 三条路径(启动轮询/早退探活/3s 监控 tick)标 `markFirstHttp`,**不依赖引擎侧插桩故今天即出真值**;块L:`writeBootDiag()` 落 `files/boot-diag.log`(`dsh-boot-diag` 标记;页面侧 fiber 状态从壳侧不可得时显式写 `pageSideRuntime=unavailable`,**绝不留空数组**);审计另见 AdbAudit(files/audit/audit.ndjson) | | **NotifySuppressQueue.kt** | 前台抑制耐久待投队列(抑制=延后) | `dsh-notify/notify.deferred.v1` 同步提交、启动恢复、TTL 5min、容量 8、单次补投 3、同 key 覆盖去重、自续 30s tick;入队失败不得推进源 offset。投递前保留 journal,结算成功后清除;稳定内容 hash 与系统 active notification 身份减少已发未清结窗口重复。复用 `NotifyCenter.deliverDeferred`,详见持久化台账。 | | **LogShare.kt** | 0.14.6 功能 B 新增:启动页「分享诊断包」——白名单收集必要日志与诊断(engine.log 及轮转世代 / boot-fail / boot-diag / boot-segments / 当日 dsh-*.log / 有界 logcat / device.txt),打成 ZIP 并交系统分享面板 | **引擎无关**(不碰 EngineManager 与引擎 HTTP,故引擎起不来时仍可用,B3);候选清单是**逐条点名的白名单**而非目录遍历,私有配置/凭据/工作区/会话内容结构上进不来(B4);条目先写 `.tmp` 再 rename 原子落盘,压缩失败删半截临时文件并如实回执(B6);manifest.txt 逐条如实标 present/absent/truncated/read-failed/sanitize-failed,缺失不得静默消失;ZIP 落 `cacheDir/log-share/`(file_paths.xml 最窄 cache-path 映射);**唯一触发点是用户点击**(B5),入口可见性由 `shouldOffer`(boot-fail/boot-diag 非空 或 Error/Closed 相位)驱动(B1/B3) | | **LogSanitizer.kt** | 0.14.6 功能 B 新增:诊断包出口的**唯一脱敏咽喉**(纯函数,无 IO/Context/可变状态) | 先复用 `EngineAuth.redact`(启动令牌参数),再补七类:PEM 私钥整块 / Authorization 头 / Cookie 头 / 厂商密钥形态(sk-、xai-、ghp_、AIza、AKIA…)/ 敏感键名键值(apiKey、token、secret、password、credential…)/ 环境变量导出行 / URL 查询串敏感参数;**fail-closed**:`sanitizeOrNull` 失败返回 null,调用方必须丢弃该条目并记 `sanitize-failed`,绝不允许原样写入;长度下限刻意极短——**脱敏口径不等于提取口径**(EngineAuth 曾把提取正则的长度下限误用到脱敏出口而漏打短令牌,同一坑不重犯);方向是宁可多打码不可漏打码 | | **EngineAuth.kt** | 引擎 /api 浏览器鉴权载体(0.13.3 W2 新增):P0=engine.log(三代取最新)解析 `dsh web: .../?token=` 行 → GET / 捕 303 Set-Cookie 存 SharedPreferences;P1=读 files/home/.dsh/.credentials.yaml records[client-connection/browser-session] 自 mint cookie(HMAC-SHA256/sha256/base64url 标准件,cookie 名 dsh-auth-+b64url(sha256(authority)),authority=127.0.0.1:3080);**token/cookie/secret 禁落日志**;handleUnauthorized=**强制刷新**(force:丢内存+prefs 后重取,不被缓存短路);ST-13 起 mux 握手 401/403 走 `handleUnauthorizedBound()`(无 Context 形参);attachMux 供 WS 握手;cookie 跨引擎重启有效(签名密钥持久) | | **EngineProbe.kt** | 本地引擎探活(0.13.2-fix 重构;0.13.3 W2 起 401/303 视作 running=alive 防 watchdog 误杀,auth 字段区分 ok/missing);另提供 Soft promotion 的 authenticated read-only health evidence | `check()` **全链 Proxy.NO_PROXY 直连**;`completeSoftHealth()` 检查 plugin inventory 中 enabled fibers、exact Hard identities、preset enabled fibers,以及 `session/list` / `session/modelCatalog` 响应结构;它不做付费 provider 请求。`portReachable()` TCP 端口级判定;error 区分 timeout 与 refused | | **OverlayService.kt** / **OverlayController.kt** | 悬浮球 v2.1(0.13.2-fix 二批;v2 交互在其上重构为**三窗口架构**;0.14.1 块I 起**取消贴边吸附**、光环加**状态描边 ring**,块H 起加**完成态卡片与报告栏窗口**):**球窗 34dp**(NOT_FOCUSABLE+NOT_TOUCH_MODAL+ADJUST_NOTHING,尺寸/标志全程不变——互吞与错位根治);**光环窗 50dp**(NOT_FOCUSABLE+NOT_TOUCHABLE 纯视觉,**background = glow+ring 两层 LayerDrawable**(0.14.1 块I;ring 硬边描边 2dp、内缩 4.5dp,颜色与 glow **同源**取自 `Halo.color`、脉冲同相位因同 View),radial 辉光半径 24dp≤半窗 25dp;**50dp=2×(贴边 margin 8dp+球半径 17dp)——贴边时窗口恰内切屏幕不被 WMS clamp**;旧 64dp 贴边越界 7dp 被 WMS 整窗平移回屏(dumpsys 实锤请求 x=-14→frame x=0)=「吸边后球/光环中心错位 14px」,2026-09-05 三批修);四态 IDLE 白/WORKING 蓝/PENDING 琥珀/ERROR 红;**面板窗**(独立 focusable TYPE_APPLICATION_OVERLAY+NOT_TOUCH_MODAL,宽 min(屏宽-球-64dp,400dp),**SOFT_INPUT_ADJUST_PAN**——键盘弹出系统原生上推面板、球不动;非相交 overlay 窗收不到 IME insets 已实测,勿再做自管 insets);收起=纯黑白球+光环、展开=圆角矩形面板(会话选择器/状态行/输入行同 v2) | 双维状态解耦保留(EngineProbe 探活 + live 流 turn_start/tool_call/turn_end busy 会话感知);**光环状态派生唯一权威 deriveHalo()**(探活 tick 与事件渲染共用——探活自带判定不带 PENDING 项会每 10s 把待答琥珀盖回白=「必须展开才见黄」,2026-09-05 三批修);**乐观置忙 markBusyOptimistic**(发送成功/应答提交即亮工作态补 live 空窗——live 流原本无起轮事件,发送到首 tool_call 间壳侧失聪显「空闲」;45s 无 live 事件由探活兜底回退);**PENDING 审批/提问走 MuxClient WS 下行**(见下行),卡片官方风格:问题卡=题头+加粗题干+编号选项行+✎ 自定义输入+‹n/n› 翻页+跳过/下一题/提交,审批卡=工具+理由+批准一次/拒绝;应答 POST /api/respond 全信封(approval value={sessionId,approvalId,outcome};question value={sessionId,answer:{answers:[{id,selected[,custom]}]}}——**selected 用选项 label 非 id**、顺序匹配 questions;question 取消发 ok:false error cancelled,approval 无取消通道);状态文案模板 prefs 化(overlay_display:template_thinking "Deep diving..." / template_tool "{tool} · {summary}",摘要取 args 命令/路径等键值折叠空白前 24 字符);拖动仅四向钳制(0.14.1 块I:删 springSnapToEdge/cancelSpring/springAnim,`BALL_EDGE_MARGIN_DP=8` 为唯一具名边距常量、同时推导 haloSizeDp;「展开拖动只有光环动」根因=按球径 clamp 致 WMS 重新贴边);positionPanel 随球同步(右溢出翻左侧);debug 待答注入:`echo question\|approval\|clear > files/home/.dsh/.overlay-test-pending`(FileObserver 消费,debuggable 门控);关闭钮独立 ✕ 图标(dsh_ic_close.xml);引擎页避让帧保留;**文件拆分(2026-09-05 Phase 3c,纯搬移零行为变更,1564→611 行)**:光环维=**OverlayHalo.kt**(Halo 枚举顶层 + drawable/setHalo/syncHalo/deriveHalo)、面板维=**OverlayPanel.kt**(buildUnit 构建/updateBallOnly 渲染/状态模板/待答卡/POST /api/respond 应答;PendingApproval/PendingQuestion 顶层类)、live 流=**OverlayLiveFeed.kt**(FileObserver drainLive 事件分发 + F7 android_* 避让 + .overlay-test-pending debug 注入 + toolSummary)、主题=**OverlayTheme.kt**(色板/明暗);协作类同包顶层、构造注入服务引用(internal 成员共享状态,无静态单例),**F7/F8/F9 三修行为与关键注释(F8 z 序约定)原样保留** | | **OverlayRecall.kt** | 0.14.9-preview 块J 新增:**隐藏态召回条**——球/光环离屏后贴在最近水平边缘的 8×44dp 半透明药丸条(懒建的第 4 个 overlay 窗口,NOT_FOCUSABLE+NOT_TOUCH_MODAL,窗口尺寸=可视尺寸不留隐形触区);纯函数 `recallStripPlacement`(贴边二选一 + 纵向夹取)/ `recallBallPlacement`(召回落点,与 `clampBallPos` 同边界) | 悬停判据见 `docs/0.14.9-preview-OVERLAY-BALL-HIDE.md`;条底色的唯一来源是 `OverlayTheme.ThemeColors.recallStrip`(深浅两档中性灰/白);`show/hide/reposition/refreshTheme/isShowing` 五个入口全部由 OverlayService 调(`hideBall`/`recallBall`/`onConfigurationChanged`/`onDestroy`) | | **MuxClient.kt** | 引擎事件 mux WS 常驻客户端(0.13.2-fix 二批新增) | 手写 WS(Socket 握手 + **SHA-1 Sec-WebSocket-Accept 校验(首版 substring 前缀比较永不匹配致静默失败,实锤)** + 服务器帧解析 + 掩码控制帧 pong/close + 分片续帧缓冲);0.13.3 W3 重做:连 `ws://127.0.0.1:3080/api/remote.mux`(**握手带 Cookie**=EngineAuth.attachMux;不带 Origin/sec-fetch-site——雷区 5)+ 连上即发 `{type:"open",streamId,endpoint:"$events",payload:{args:{}}}`;服务端 item value = ready(clientId)/ waterfall(approval/request、user-questions/request,eventId+agentId)/ emit(api-session/status args=[agentId,running]);应答 POST /api/$events/result {clientId,eventId,outcome}(审批=词汇原字符串、提问 selected 数组、跳过=rejected);指数退避 1s→10s 重连、逐次 Log.w(tag dsh-overlay-mux);帧回调 OverlayPanel.handleMuxFrame(0.13.2-fix Phase 3c 拆分后落 OverlayPanel.kt,原 OverlayService.handleMuxFrame)增删 pendingApprovals/pendingQuestions 映射(approval/requested|resolved、question/requested|resolved) | | **ShimmerTextView.kt** | 官方「Deep diving...」品牌蓝渐变扫光文字(0.13.2-fix 新增) | 复刻官方 ChatView.module.css .turnStatus:LinearGradient shader(D500 #4176E6 / D200 #D3E2FF,宽 2.5W,translate −1.5W→0,1.8s linear infinite)+ ValueAnimator;reduced-motion 三 scale==0 时静态渐变 | | **AdbKeyboardService.kt** / **AdbKeyboardReceiver.kt** | 内嵌 ADBKeyboard 协议 IME(0.13.2 W6;0.14.8 #413 修正投递与来源校验) | 两个接收器实例按投递方式分工(`shouldHandle`):**点名组件/点名包**归 manifest 实例,**纯隐式**由 IME 在 onCreate 里**运行期注册**的实例承担(onDestroy 注销)——targetSdk 26+ 的 manifest 接收器收不到隐式广播,而两者互补保证一次注入只提交一遍;来源校验 = `BroadcastReceiver.getSentFromUid()`(root/adb shell)+ nonce;处理结果只在有序广播上写回结果码(1 已提交 / 2 IME 未就绪 / 3 来源被拒 / 4 请求不合法 / 5 无输入连接) | | **WebViewShim.kt** | WebView「版本敏感面」唯一入口:provider 回读 / androidx.webkit 特性门 / baseline+isolation 两档 settings | `providerPackageName/providerVersionName/providerAvailable`(provider 包版本与 Chromium 引擎版本分开;`chromiumVersionFromUserAgent` 从实际 UA 取 Chromium 版本,`syntaxFloorStatus` 返回 true/false/unknown,避免将厂商版本冒充 Chromium 版本);`supports(feature)` 按 **provider 能力**判而非 SDK_INT 猜;`applyBaseline`(JS/domStorage/关 fileAccess/MIXED_NEVER/NO_CACHE/钉死 mediaPlaybackRequiresUserGesture;引擎页传 `appOwnsTheme=true` 禁算法改色,其他调用保留系统算法深色)供引擎页与控制台,`applyIsolation` 在其上叠加(关 content/fileURL-JS/multiwindow/geo + overview/wideViewport + safeBrowsing)供 BrowserHost;**LocalDocs 刻意不走 baseline**(静态文档面关 JS)。原三处 `getCurrentWebViewPackage` 副本与三处 forceDark 分支全部收口于此 | | **FileExternalBridge.kt** | 文件「外部打开 / 转发」到别的应用(0.15.0):chooser 聚合 ACTION_VIEW + ACTION_SEND | `openFileExternal(path, mime)` / `shareFile(path, mime)` 两枚桥方法共用本对象。**必须 flag + ClipData 成对**:`FLAG_GRANT_READ_URI_PERMISSION` 只授权 intent 的 data,`EXTRA_STREAM` 与 `EXTRA_INITIAL_INTENTS` 里的 Uri 要另靠 ClipData 传播(只加 flag 就是「目标应用报无法打开/空白」的根因)。`IntentSpec` 把「发几条、每条 action/type/Uri/授权哪些」做成纯数据(全是 String)供 JVM 单测钉死,`toIntent` 只做无判定转换——因为 `isReturnDefaultValues=true` 下 `Uri.parse`/`Intent.createChooser` 返回 null,直接断言 Intent 会全绿假象。探活走 `queryIntentActivities`(0/非 0 两分支),空则回 `no-receiver`;目录项只在父目录**也能授权**时才加,否则不提供(不硬塞打不开的 URI)。错误码 `no-receiver`/`uri-denied`/`io`/`bad-request`,全部包 try 不抛。反证用例 `FileExternalBridgeTest`(17 例,含两条受控负控:去掉 grant → 2 红;目录项 type 改错 → 1 红)。 | | **PhotoCapture.kt / PhotoCaptureController.kt** | 原生拍照(WebView 加号面板的拍照瓦片):`ACTION_IMAGE_CAPTURE` → 压缩 → 既有来件通路 | `PhotoCapture` 是**纯逻辑面**(JVM 可测):错误码 `cancelled/permission-denied/camera-unavailable/camera-busy/camera-failed/compress-failed/busy/not-wired`、`namesFor` 时间戳命名、`sampleSizeFor/targetSize` 压缩几何(长边 ≤ `MAX_LONG_EDGE`=2048、`JPEG_QUALITY`=85)、`sweepOld` 清理(成品留 10 张、普通原图全清、**压缩失败保留位**按件数 `KEEP_RAW_MAX`=3 + 时间窗 `KEEP_RAW_WINDOW_MS`=24h 双上限有界回收,保留位 = `raw-*.jpg.keep`,见 `keptRawNameOf`/`isKeptRawName`/`keepRaw`/`stampOfRawName`,issue #995)、`messageFor` 中英文案单点。`PhotoCaptureController` 是 Activity 作用域(`registerForActivityResult` 必须在 STARTED 前注册):CAMERA 权限只申请一次、拒绝即回 `permission-denied`(不弹二次窗),压缩在 daemon 线程,成品经 `FileIncoming.copyIn` + `processIncomingPath` 走**同一段**来件投递链。落点 `cacheDir/attachments/`(原图在 `raw/` 子目录,压缩成功即删;**压缩失败则改名进保留位**并把新路径放进回执 `detail`,下一次拍照的 sweep 不会删它);FileProvider 最窄映射见 `res/xml/file_paths.xml` 的 `photo_attachments`。 | `app/src/main/assets/`:`snapshot.tar.xz`、`snapshot.sha256`、`undo-emergency.mjs`(急救 CLI,UndoGate 用)、`licenses/`(LICENSES 标准文本 + THIRD_PARTY_NOTICES.md,GPL 合规 A2)、`console.html`、`docs/root-disclaimer.html`(0.14.2-fx-2-root.1:issue #262 免责声明,`LocalDocs` 登记消费)。 ### 4.1 引擎侧插件文件(协调仓 `plugins/`,镜像到本仓同名目录) > 本表登记引擎侧插件中本轮新增或职责变化的文件;其余插件文件见协调仓对应 `plugins/*/src/`。 | 文件 | 职责 | 关键点 | |---|---|---| | `app/src/main/java/com/dsharnessmobile/shell/LinuxAssetIO.kt` | Linux 环境资产 IO:宿主侧「下载 + 校验 + 安全解包」判据(0.15.0) | 只认 https + 主机白名单 + 本仓 Releases 的 `/` 形态;重定向**逐跳**复核(关掉 `instanceFollowRedirects`,自动跟随不校验中间跳);摘要与**字节数**都验;解包复用 `SnapshotExtractor.extract`(绝对路径/`..`/符号链接逃逸/炸弹上限),落点由壳侧强制在 `DSH_FILES_DIR/linux-environment` 内。刻意**不复用** `UpdateChecker.download`(它走 gh-proxy 镜像链,与本面的「只认本仓直连」相反)。门禁 `environmentVerdict`:根目录 + `linux-bootstrap-state.json` 都在场才允许动作。绝不 shell out。 | | `app/src/main/java/com/dsharnessmobile/shell/LinuxAssetPrepare.kt` | Linux 环境资产准备入口(客户端触发,(iii) 形态) | 入参只有 `{url,sha256,size}`,落点按摘要推导,页面指定不了路径;先过环境/来源门再起后台线程(下载不得阻塞 WebView 线程);下载→校验→解包全过才**原子发布**成 `staging//` 并**保留归档字节**供引擎侧复验;任一步失败只清工作目录、绝不发布半成品。状态经 `linuxAssetPrepareStatus()` 轮询。 | | `plugins/dsh-android-linux-env/src/linux-environment.ts` | 实验性内部 Linux 插件(bootstrap/runtime)的惰性宿主适配与鉴权路由 | `linuxPluginHost` 仅激活内部 bootstrap 已校验/落位的 Cordis runtime;`install` 必须返回 `{manager, rollback?}`——bootstrap 按 `hostResult.manager ?? hostResult.service.manager ?? hostResult.service` 解析,取不到就中止安装(`The installed runtime plugin did not provide a runtime manager`),故宿主从激活作用域的 `linuxRuntimeManager` 服务读出 manager(`cordisScope` 同时供 `src/index.ts` 与测试复用,避免两侧适配漂移;`ready` 保留 Cordis fiber 的可等待性,否则 ACTIVE 判定会与异步 `apply()` 竞争)。并以 `isInjected({name,runtimeId,directory})` 作为在场(presence)的只读权威;`linuxBootstrap` 为状态和更新偏好的唯一真源。两个包随应用放入内置(hard)插件目录但**默认不启用**(存在 ≠ 启用),未启用返回 503。路径根来自 `DSH_FILES_DIR`;状态归一同时接受 Bionilux 定稿与提案字段;版本变化检查无轮询,手动检查不安装;手工移除的插件不会被新 Release/新版本复活。 | | `plugins/dsh-android-linux-env/src/linux-host-asset-io.ts` | 引擎侧 `linuxAssetIO` 的**宿主透传面**(0.15.0,(iii) 形态的引擎侧一半) | 造出 bootstrap 必需的 `downloadAsset`/`extractArchive`,语义是**透传 + 失败关闭**而非下载器:`downloadAsset` 只交回宿主已暂存的 `/linux-environment/staging//asset.tar.xz`(落点由 **manifest 摘要**推导,调用方不能指定;摘要格式 `^[a-f0-9]{64}$` 与包含关系两道判据与 Kotlin `LinuxAssetPrepare.stagedDir` 同口径——摘要是路径段的来源,理由同 #396 修的穿越缺陷),`extractArchive` 按**归档自身摘要**定位 `/unpacked/` 并**复制**进 bootstrap 目标目录(复制而非移动:宿主那份要留着供复验),并拒符号链接条目。宿主没准备/摘要不合规/树不安全一律抛带稳定码(`linux-asset-not-prepared`/`linux-asset-spec-invalid`/`linux-host-tree-unsafe`)的错,**绝不回退成自行下载**。`createLinuxBootstrapConfig` 的 `stagingDirectory` 故意**不等于**宿主暂存目录:bootstrap 把 `//` 当自己的包槽位(先 `rename` 到 `.backup-*` 再递归删除),指向宿主暂存会删掉宿主保留的归档与解包树。复验权威在 bootstrap(自算摘要与体量、重算 `unpackedSize`、校验包名与入口);上游 `dsh-bionilux@3f86a85` 起,命中自己写下的标记也**不再免检**——被篡改的槽位不被复用而是从已复验的宿主暂存**重建**,需要重建而重建源不可信时**失败关闭**(有内容级用例 + 撤掉上游修复的变异验证)。 | | `plugins/dsh-linux-bootstrap/` | vendored 的实验性内部 bootstrap 源码(`@dsh-android/linux-bootstrap` 0.5.0,源 `kelai141/dsh-bionilux@3f86a85` `packages/linux-bootstrap`) | **只 vendored 源码**(无二进制/构建产物/LFS),`lib/` 由 `build.mjs` 在链上重建(`plugins/*/lib/` 属 .gitignore)。它**不在注入集**:`plugin-dirs.json`/`contract.json`/`profile patch` 都不登记——bootstrap 的 `apply()` 需要函数型 config(`linuxAssetIO`),profile 装配表达不了,故只由宿主在用户显式启用时动态加载;不入注入集也让 `check-patch-mounts`/`check-contract` 保持真绿。漂移由 `scripts/check-vendored-linux-bootstrap.mjs` 守(9 个上游一致文件按 EOL 归一化摘要 + 断言偏差点 + 断言未 vendored 运行时/rootfs/Node)。**运行时插件、rootfs、共享 glibc Node 一律不走本仓,只来自 Release。** | | `dsh-client-ui-responsive/src/client/index.ts`(`settings.section` 归属) | 「Linux环境」设置入口的**唯一注册方**是 vendored bootstrap 的客户端条目(`plugins/dsh-linux-bootstrap/client/index.js`,id `linux-environment`、label「Linux环境」、`inject=['slots','linuxBootstrap']`、经 `ctx.effect` 托管 disposer) | **0.15.0 撤回 0.14.x 壳侧同名分区**:`dsh-client-ui-slots` 对 list 槽的重复 id 是**抛错**而非叠加,两处都注册会在插件被启用时当场抛错。同理 `mobile/settings-nav.ts` 的「Linux环境」短说明键一并移除(`settings-nav.spec.ts` 断言表键 == `index.ts` 注册的 label)。分区只在 bootstrap 被显式启用、拿到 `linuxBootstrap` 服务后出现;未启用无入口。签名的 `/api/android/linux-environment/*` 路由与 `linuxPluginHost` 作为惰性宿主基础设施保留。 | | `plugins/dsh-model-capability/src/models-dev.ts`(**0.14.2 P2 新增**) | **来源梯 S3**:models.dev 跨厂商百科,补引擎随包目录(S4)查不到的**长尾模型** | 字段路径**本仓实测**(2026-09-26 真实抓取,非抄自他人实现):provider map `{ : { id, name, models: { : } } }`;`entry.limit.context`→contextWindow(8181/8181 在场)、`entry.limit.output`→maxTokens、`entry.modalities.input[]`→input、`entry.reasoning_options[]`→thinkingLevels(4796/8181 在场)。**`values` 数组只存在于 `effort` 变体**(实测四种条目形态:仅 type / type+values / type+min+max / type+min),裸 `type:'toggle'` 或 `budget_tokens` 不点名任何档位。**只保留上列字段、其余全丢**;落盘前先量尺寸,超 `MODELS_DEV_MAX_BYTES`(8 MiB)拒写。`MODELS_DEV_URL = https://models.dev/api.json`(单请求、无 body、无凭据)、`MODELS_DEV_TTL_MS = 7 天`。**S3 与 S4 逐字段仲裁:分歧字段记为冲突且不写入**(`mergeModelsDev`) | | `plugins/dsh-android-vdisplay/src/index.ts`(协调仓同名插件镜像) | 虚拟屏工具面,包含 `android_vdisplay_input` 的严格回执 schema | `callVdOp` 成功回执包含 `state`,输入工具 schema 必须显式声明;壳侧 `vdInput` 正常回执还含 `verb` / `screenId` / `displayId`。web 与 headless profile 共用注入插件目录中的同一 `lib/index.js`,镜像按 §5.1 同步 `src/lib/package.json/test`。 | **来源梯全貌**(`capability-probe.ts` 文件头明载,0.14.2 P2 起为六级): | 级 | source | 说明 | |---|---|---| | S1 | `endpoint-descriptor` | 被动 GET 端点自己返回的元数据(无 body、无花费、无副作用) | | S2 | `vendor-descriptor` | 按**响应形状**解析厂商 schema | | **S3** | `models-dev` | **本批新增**:models.dev 百科(见上行) | | S4 | `engine-catalog` | pi-ai 随包目录的精确 model-id 命中 | | **S5** | `user-fallback` | **本批新增**:只在用户显式配了 `fallbacks` 时存在,**出厂无默认值** | | S6 | `active-probe` | 仅在调用方显式批准时(会发真实最小补全请求) | **不变量**:没有任何一级报出的能力**保持缺失(绝不猜测)**;每个报出的能力**都带 `source`**;`reasoningEfforts` 是**逐模型**值,本模块**从不**产出供应商级设置。 **合流不是替换**:S3/S5 是插进既有四级的两级,不是用一个实现替换另一个(判据见 `docs/0.14.2-preview-SHIZUKU-RESET-AND-MODEL-FILL.md` §1)。 ### 4.2 快照面板的客户端所有权 `dsh-client-ui-responsive/src/client/snapshot-panels-observer.ts` 的 `snapshotOwners` 按真实三层 `data-slot` 认领 vendored 快照面板的 header/titleRow;`SnapshotPanelsObserver` 分别租赁抬升和解除包含的临时 class,并在关闭、重分类、移除或 reload 时恢复。配套 `snapshot-panels.css.ts` 只改变这两个已认领元素的绘制层和包含约束。真因、测试与设备边界见 `gotchas.md` 坑 239。 ## 0.14.3 新模块与职责(源码登记,未执行验证) | 新模块 | 职责 / 关键入口 | |---|---| | [RootMaintenanceLease]() | begin/finish/markUnknown/outstanding/newBoot:真正UID0派发前耐久commit;同boot应用重启保留UNKNOWN,仅同epoch方案的真boot变化清除;`markDispatched(context, transport)`/`dispatched(context)` 记「特权工作已真正派发」的派发证据(`dispatched`/`dispatchedAt` 落盘,写不进即置结果不明),是自动清算的否决项之一(其余为:通道非 ABSENT、租约 `unknown`、进程内有在飞维护);clearWhenNoRootChannel 带锁内 guard + commit 有界重试。 | | [LeaseClearProbe]() | 残留租约「能不能清」的审计留痕(files/lease-clear-probe.log,有界 8 KiB):每次判定一行,记 `state/serverUid/decisive/forced/dispatched/leaseUnknown/allowed/detail`——`dispatched` 是租约的派发证据、`leaseUnknown` 是租约结算状态是否不可信(对应 `RootMaintenanceLease.unknown`)、`allowed` 是 `autoClearAllowed` 四条件准入结果(通道 ABSENT、未派发、`unknown=false`、无在飞维护),`detail` 是**原始信号明细**(`installed/suBinary/ping=true|false|throw/bind=…/uid`,含 `bind=NOT_ATTEMPTED`);只在存在残留租约时写、异常全吞。 | | [BrowserHostProfile]() | attach 首操作设置并回读验证 per-session nonDefault profile,再装 cookie/worker策略;失败不回 Default;dispose/limited clear 只触自己profile。 | | [ForegroundPageRecoveryPolicy]() | 前台generation、一次load-error retry、后台合并恢复、一次renderer recreate;重复崩溃落native error,user reload可显式重试。 | | [SnapshotFingerprintPolicy]() | 严格64hex SHA及fresh判据;缺失/非法包 metadata 不触发抽取/事务/启动,也不走legacy/degraded旁路。 | | [RuntimeTree]() 的 309 增补 | 证据分级 `START_RECOVERY_CONFIRMED_ENTRIES`/`confirmedDamage`、唯一恢复闸门 `allowStartRecovery`、现场描述 `describeEntry`。 | | [EngineStartFlow]() 的 309 增补 | `maybeRecoverFromIncompleteLiveRuntime(activity, confirmedMissing, userForced, current)`:拒启分支(自动)与错误页主按钮(显式)共用一个入口;动作顺序 标记→删指纹→清账本→诊断镜像→如实落盘。 | | [LogCollector]() 的客户端插件失败增补 | `PAGE_PLUGIN_FAIL_PREFIX = "[dsh-boot-failed]"`(与页面侧逐字一致,`LogCollector.kt:755`),是页面契约行前缀常量的壳侧唯一定义;`isClientPluginTreeFailureMessage(message)`(`:805`)纯前缀判定(trimStart 后 startsWith),只认前缀不做字段解析;`clientFailedIdsOf(message)`(`:822`)纯函数把 `failedIds=` 字段 split(',') 后按与页面侧同源的形状/长度/条数口径再滤一遍,字段缺失/被 fold 截断/写成 "-" 时返回更少或空列表,**绝不臆造**;`routePageConsole`(`:780`)新增第三分支(source 仍 page-console,排在 ready/stall 之后——顺序即契约)。 | | [EngineStartFlow]() 的客户端插件失败增补 | `onClientPluginTreeFailed(rawLine)`(`:317`,MainActivity 控制台路由调用,JavaBridge/WebView 线程):① 取 `clientFailedIdsOf` 得原因短句 → ② 失败终态落盘(`writeBootFail(stage="client-plugin-tree-failed")` + `Log.e`,detail 含 failedIds 与页面原文前 400 字符)→ ③ 置 latch `clientPluginTreeFailed`(`clientPluginTreeFailedLatch` 只读访问器在 `:301`,`clearClientPluginTreeFailureLatch()` 在 `:307`,用户显式动作/新启动世代/回滚放行时调用)→ ④ 前台切错误相位、非前台挂起待 `onResume` 消费 → ⑤ 起守护线程 `maybeRecoverFromClientPluginTreeFailure(generation)`(`:350`):一次性预算 `clientPluginTreeRecoverySpent` → `UndoGate.onClientPluginTreeFailure` 放行判定 → `PluginMounts.clientPullCandidate` 唯一点名则外科、点名不出但 `mountUnchangedSinceHealthy` 则整份、否则**不动作**并如实说明;唯一动作统一走 `UndoGate.execute(activity, engine, clientFailure)`(分支判定在它内部,EngineStartFlow 不重复决策)。 | | [UndoGate]() 的客户端插件失败增补 | `onClientPluginTreeFailure(context, detail)`(`:165`):浏览器侧失败的一次性入口,复用 `decide(TRIGGER_CONSEC_FAILURES, now, lastUndoAt, armedAt)` 的语义并把结果经纯函数 `clientPluginFailureDecision(decision)`(`:182`)映射成两态——SUPPRESS ⇒ false(防循环)、其余(IDLE/ARM/WAIT/EXECUTE)⇒ true。**刻意不用 ARM→WAIT 观察窗**:引擎健康时看门狗每 5s 一拍走 IDLE 并调用 `disarm()`,arm 文件被逐拍清掉 ⇒ `EXECUTE` 永远到不了,观察窗在这条路上等于静默掐死唯一自动恢复路径(反证见 `ClientRollbackGateTest`)。 | | [PluginMounts]() 的客户端失败与软健康 | `compositionSnapshot` 按 app-boot rc2 顺序绑定 install-first/profile-second 解析的 bundle `{name,version,patchSha256}`、profile cordis 原文、可选 HOME cordis 原文;`DSHCOMP2` digest 区分 HOME 缺席与空文件。`shouldProbeHealth` 以组合 digest 决定是否需要完整探活;组合不明时以现有 lastProbe 字段限速探活,`noteHealthy` 拒绝将未知组合晋级。生产 `clientPullCandidate(context, patch, ids, HardManifest)` 仅接受当前 bundle/profile/HOME insert 的唯一来源并按 Hard tuple/entry 拒绝;自动恢复重验来源后把带原因的 `disabled: true` override 原子追加到 HOME cordis(rc2 持久配置层中晚于 bundle 与 profile),保留原始 insert/config。Hard schema 2 将 APK profile identities、factory bundle `{name,version,patchSha256}` 与 final snapshot SHA 绑定;bundle 摘要基于 `DSHBNDL1` 和有序真实 patch bytes。旧资产缺字段保持 bundle ownership inconclusive,显式空数组表示无工厂 bundle。online sidecar 合并验证过的 stage `usr` entries、嵌入 profile 子集与从当前 APK可信名称在本次 verified stage 重算出的 bundle identity;完整 archive 优先信 archive web-profile `package.json` 选择,缺文件才回退 APK名单,malformed/symlink拒绝;不碰live HOME/旧sidecar。事务 marker/.online-snapshot 对齐双 fingerprint 后选择sidecar。Soft state 记录 Candidate/Stable digest 与 engine-spawn identity。 | | [SafeMode]() | 壳内安全模式事务:只过滤 insert 组中的第三方子条目,ownership 必须来自 exact Hard `{id,name}` manifest;保留顶层覆盖/禁用位及所有未匹配配置。生产 ON 入口在 Hard manifest 缺席时明确拒绝并说明“未生效”,不猜测 publisher 归属。备份逐字节校验并先落恢复状态,再写过滤后的 patch;OFF 校验备份后整份恢复。两阶段状态机(`phase=writing`/`active`)让中途失败可重入;重入时若首轮备份仍与 `backupSha256` 匹配则复用首轮原文与 `snapshotId`,不把已过滤的 patch 当原文备份。 | | [EngineStartFlow]() 的 0.14.8 P1 增补 | 刷新窗口内**不得静默丢弃**启动请求:`deferEngineStartWhileSnapshotTransactionBusy()`(`begin()` 返回 null 时按 destroyed/running 分开处置——destroyed 不启动,running 走 boot-fail 留痕 + 有界等待后自动续跑)、`deferredStartRunnable`(必须**同时**等「事务收口」与「世代所有权空出」,只等事务等于把丢弃换成忙等)。安全模式重启在**停旧引擎之前**先判跨进程真源 journal(`SnapshotTransaction.readMarker(filesDir)`):事务在场则 `awaitSnapshotTransactionThenRestartSafeMode()` 如实相位 + 等收口再先停后启(先停会让用户白等 8-12 分钟没有引擎)。成功面新增 `stage=safe-mode-restart-started`,与既有四个失败 stage 组成两面对照。 | | [EngineStartFlow]() 的 0.14.8 P1 纯判据 | `awaitSnapshotTransactionSettled(budgetMs, pollStepMs, now, sleep, transactionActive, current)`:与 `awaitEngineHttpReady` 同源的注入式 clock + 硬预算,JVM 可逐条反证「收口即停 / 超预算必返 / 换代立刻收手 / 无事务不等」;预算 `SNAPSHOT_TRANSACTION_WAIT_BUDGET_MS = 16 分钟`(本轮**新增**常量,依据 = 定位报告实测一次全量重解压 8-12 分钟上限 + 4 分钟换树收尾余量,步进 1s)。`snapshotTransactionWaitText(phase, accountedAgeMs)` 是事务等待的如实相位文案,**刻意不在 `safeModeRestartHint` 取值域内**(后者是 `SafeModeRestartDecision` 四值枚举的穷尽 when,扩枚举等于把修并发缺陷与改既有判定契约混进一个改动)。 | | [EngineManager]() 的 0.14.8 P1 口径纠正 | `refreshSnapshotInternal` 不再把「另一条事务在飞」记成**自己的失败**:`lastRefreshFailure=null` + `lastRefreshDeferred=true`(新字段,与失败语义分开),且**不进**跨进程失败账本——否则纯并发争用会喂出「同一份快照连续失败达阈」,让降级闸门此后永久跳过刷新。返回值仍是 false(本次**确实没有**刷新)。与同一堵墙的 `startEngine`(issue #684 起返回 `EngineStartOutcome`:`DEFERRED_REFRESHING` 让行、`REFUSED` 失败,不再用 `true` 表示「刷新中让行」)/`invalidateSnapshotFreshness`(返回 false 让行)同口径。 | 共享 core/Android held-FD/helper 已在上表逐项登记。新增同文件类型:ShizukuCaptureIo(private .part、immutable snapshot、只完整publish);StartupFlowOwnership(CAS generation/token);StartupOwnershipRetryBudget(六次2/4/8/16/30/30秒);WatchdogV2.WakeLockOwner 与 EpochResourceOwner(按Service epoch持有/迟到释放)。MainActivity配置桥现接 EngineManager,legacy ConfigTransfer 未挂载;同文件 DirectoryPickerController 仍服务 SAF,不误删其用途。 新增JVM覆盖账本见 [执行地图]() 与 [外部测试需求]();“文件在场”不等于用例已运行。0.14.3尚未构建,不写APK/hash/测试通过。 ## 5. 桥与通道说明 | 层 | 通道 | 语义 | |---|---|---| | 页面 → 壳 | `window.androidBridge` | 设置/路径/授权/ScreenScope/BrowserHost/虚拟屏等桥的**唯一**入口;ADB 授权变更只能走壳侧原生 AdbState(被提权方不得自改授权);目录/文件 pick(token);全文件访问;重启/控制台 | | 页面 → 壳 | `window.dshBackBridge`(BackGateBridge) | 注入层回传「页内层栈是否可用」(setAvailable)+ 只读回读(getBackAvailable) | | 壳 → 引擎 | HTTP 127.0.0.1:3080 | 文件来件 POST;pick 端点;**只读**状态端点(/api/android/privilege/status);通知/控制队列 exact 路由自带鉴权 | | 引擎 → 插件 | cordis 服务面 | androidPrivilege(状态机/execAdbShell/execAdbLine/gateFor 会话级 danger/screenScope/screenAccess);dsh-shell-termux 执行器 | | 插件 → 页面 | dsh.client 模块 + slots | ui-responsive(移动形态、DevSection/屏幕与 Shizuku 控制、F5 消费端轮询);bridge client(授权 UI);undo/marketplace 注册 | **授权模型(定稿)**:引擎级 = 门1 All Files Access(**live prefs 键 `fullAccess`,壳 syncFullAccess 写入;env DSH_ADB_FULLACCESS 仅兜底**——0.13.0 Q8 不再重启生效)+ 门2 允许开关(live prefs)+ 门3 真实配对(adb pair 握手);会话级 = `gateFor(exec.agent.session)` 实时 resolve,**ADB 能力(含观察类)仅 danger-full-access**,自动审批不参与;写面唯一在壳侧原生 AdbState(桥/引擎只读 live `dsh-adb.xml`)。0.13.8 #172:部署默认写面档位(tier)**只是视图字段,不参与任何能力门**——ADB 能力门 = 引擎级三道门 + 会话档位实时 resolve(出厂 workspace-write 默认不再钉死 ADB 通道,坑 29 定例)。**0.14.0(U-4 方向)**:正式特权 transport 转 Shizuku,ADB 三道人门保留为迁移/诊断面。 ## 6. 关键实现细节与坑(每次踩坑必须登记) 1. **realpath 前缀混用(B7 运行时表现,已修)**:Android 上 `/data/user/0` 可能是 `/data/data` 的软链——只把「文件侧」realpath 后再与未 realpath 的 ws 比较必拒。修复:`safeResolveInside` **两侧都 realpath**(ws 侧失败按原样参与),symlink 目标按 `dirname(rel)` 解析(`../../LICENSES` 从 `doc//` 出发 = `share/LICENSES`)。**新路径校验代码一律双侧规范化。** 2. **会话 header meta 白名单**:`Session.create(meta)` 的 `origin` 只允许 `"subagent"`——自定义值报 `session header origin must be "subagent"`;只写白名单键(如 `cwd`)。 3. **surface 事件必须带 surfaceOp**:`user/message` 等 surface-eligible 事件 append 需第 3 参 `{surfaceOp:'append'}`,否则 `requires a surfaceOp marker`。 4. **pnpm 是市场安装的硬依赖(已固化进快照构建)**:`dsh plugin add` spawns `pnpm`(apps/cli plugin.ts);快照缺 pnpm → `pnpm not found on PATH`;且**陈旧 pnpm 状态记录**(base-dsh 里的 `.modules.yaml`/`.pnpm-workspace-state`/`pnpm-lock`)指向旧 store → `ERR_PNPM_UNEXPECTED_STORE`——build-snapshot-013.mjs 已装配 pnpm 10.12.1 standalone(npm tgz + `usr/bin/pnpm` shim)并清理这三种记录。**市场目录里的部分插件(如 humanizer-ru)不在 npm,安装 404 属上游目录数据,不是本链路缺陷。** 5. **快照 node 需要 OPENSSL_CONF**:运行快照内 node 时务必与 UndoGate 同样注入(否则 OpenSSL config error 静默吞 CLI 输出)。 6. **Windows 侧读 WSL 9p 文件 = EACCES**:stage 文件不可直 stat/read;校验类代码走 `--tar`(wsl tar -tvf 带大小)视图;wsl.exe 输出前有 localhost 代理噪音行,解析时过滤。 7. **npm registry 元数据可能缺 dist.sha512**:pnpm tgz 完整性校验「在场则严格,缺席降级警告」。 8. **run-as 引号地狱 / adb 二进制传输**:PowerShell 双引号内 `$var` 本地展开;二进制经 `adb exec-out`/push 传输。 9. **template 字符串反斜杠**(页面注入):`\n` 双写。 10. **签名一致性**:debug.keystore 固定,否则覆盖安装失败。 11. **CDP 断言注意**:input placeholder 不在 innerText(查 `[placeholder]`);「live 会话禁止 API prompt」;`session.list` 的 stats 字段(turns/llmMs)判断代理是否真跑。 12. **引擎级 OPENSSL_CONF 曾有缺口(2026-08-24 真机实锤,已修)**:快照 node 编译期硬编码 OpenSSL 配置路径 `/data/data/com.termux/files/usr/etc/tls/openssl.cnf`(app 域不可读)——`shellEnv()` 未注入 OPENSSL_CONF 时,**任何 node/npm 子进程(agent 工具调用)启动即 OpenSSL configuration error 退出**;引擎本体侥幸存活(不触发该初始化的路径)。修复 = `shellEnv()` 加 `OPENSSL_CONF=/etc/tls/openssl.cnf`(与 UndoGate/AdbState 统一;坑 #5 是本坑在 CLI 面的显式版)。 13. **apt/dpkg 编译期路径(issue #80,2026-08-24 重写)**:apt/apt-get/dpkg 二进制内置 `/data/data/com.termux/files/usr` 编译期路径。`-o Dir::Etc=...` 参数覆盖不了 apt.conf.d 早期扫描(仍报 Permission denied);**有效方案 = APT_CONFIG 主文件**(build-snapshot-013.mjs 7d 段生成 `usr/etc/apt/apt.conf`,wrapper 统一 `export APT_CONFIG`)——注意 `K='...$B...'` 单引号不展开曾令 wrapper 失效。**dpkg 的 SYSCONFDIR(dpkg.cfg.d 配置目录)无 env 可覆盖**(strings 证实无 DPKG_CONFIG_DIR 变量;`--admindir/--instdir` 不覆盖)→ apt 在线安装 dpkg 阶段受限;`scripts/check-prefix-residue.sh` 设备端自检验证。 14. **配对伪成功防御(2026-08-24 真机实锤)**:`nativeBridge()?.setAdbPair?.()` 可选链在桥缺失/方法缺失时返回 undefined → `ok === false` 恒 false → 前端误报「配对完成」——**显式检查 `typeof b.setAdbPair === 'function'` 且无函数即 throw**。设置页两端口输入框是必经项,用户嫌手动抄录——0.13.0 起端口发现用 **NSD/mDNS**(AdbState.discoverPorts:系统属性直读优先 → `_adb-tls-pairing._tcp`/`_adb-tls-connect._tcp` NSD 发现(5s 超时)→ 手动输入硬回退;盲扫 37000-45999 已剔除,Q17)。 15. **临时工作区面板不可见(issue #60,2026-08-24 已修)**:workspace registry 只从**既有会话 cwd** bootstrap——无会话时「临时工作区」不出现在工作区面板。修复:dsh-android-file-open apply 时 `workspaceRegistry.create(tmpWorkspace(), '临时工作区')`(幂等复用)。TTL 清理(7 天)壳侧 FileIncoming.sweepExpired(启动 + 每次入队前)。 16. **老内核 ES2022 polyfill(issue apk#81/#79)**:华为/荣耀/小米定制 WebView(Chromium<92)缺 `Object.hasOwn`/`Array.at`/`String.at` → 前端加载插件报 "Failed to load plugins"。polyfill 现由注入层 `dsh-host-web-compat` 承担(0.1.12 起:装配补分号 + 装载期逐段 `new Function` 断言 + 常驻 `smoke-injections.mjs` 门禁,坑 61);旧的 `assets/patched/web-frontend-index.html` 注入点已于 0.13.7fx-1 退役(坑 64)。 17. **错位目录(issue #80 P5)**:relocate-snapshot 曾把包内绝对路径 `/data/data/com.termux` 当相对路径搬进 usr 树(`usr/data/data/...`)——纯冗余;构建链 7e 无条件删除 `usr/data`。 18. **debug APK 默认 x86_64 快照(2026-08-24 本日重大事故)**:`app/build.gradle.kts` mergeDebugAssets 注释写死「从 GitHub Releases 下载 snapshot-x86_64.tar.xz 放 assets」→ `app-debug.apk` 内快照是 x86_64;**装到 arm64 真机覆盖终版后引擎崩**:`error: "/data/data/.../usr/bin/node" is for EM_X86_64 (62) instead of EM_AARCH64 (183)`。核对方法:解快照 tar 读 `usr/bin/node` 的 ELF e_machine(62=x86_64,183=arm64),或 `aapt dump badging ` 看 native-code。修复/预防:构建/安装前核对设备 ABI 与快照 ABI;换 arm64 快照须**同时替换** `assets/snapshot.tar.xz` + `snapshot.sha256`(指纹变→refreshSnapshot 全量重解压 2-4 分钟,勿中断)。 19. **cordis.patch.yml 装配缺陷**:基座 cordis.patch.yml 只含 shell-termux/host-web-compat/ui-responsive——0.13.0 新增的 android-bridge/android-manage/android-linux-env/android-file-open/undo-savepoint/marketplace **从不进入快照装配** → 引擎不加载这些插件(`/api/android/file-incoming` 404、ADB 设置项缺失、通知事件桥无宿主)。修复:build-snapshot-013.mjs 7b2 用 `scripts/profile-web.cordis.patch.yml` **权威覆盖**快照内同名文件(缺失即 `process.exit` 拒发)。**注意**:真机热改 cordis.patch.yml 后必须**冷启动 app**(`am force-stop` + start)才重装配——watchdog 热重启引擎不重读 profile(只重跑 node)。 20. **EngineService 挂载缺失**:startEngineService 只在 startEngineFlow 首次轮询成功时调用——**引擎先跑、app 后启动(热启动/恢复)时服务从未启动 → watchdog 缺失 → 通知消费(task-done 标记)/自动回退/唤醒锁全链路失效**。修复:MainActivity `onResume` 幂等 `startEngineService()`。 21. **通知链路三缺(2026-08-24 用户实测「任务完成没通知」根因)**:① 引擎事件桥缺失(前端 showNotification 桥无调用方,F0.3 部分实现=以前未做)——补:dsh-android-bridge 监听 `ctx.on('session/event')` 捕获 `assistant/message` → 写 `files/home/.dsh/.task-done.ndjson` 标记;② WatchdogV2.consumeTaskDoneMarkers 消费标记 → `NotifyCenter.notify`(deepProbe 成功路径顺带;JSON 解析失败会丢通知——务必确保标记是 `JSON.stringify` 合法 JSON);③ EngineService 必须挂载(见坑 20)。验证:`files/notify-debug.log` 落盘每步(诊断时开);通知 id=`("dsh-"+category).hashCode() and 0x7fffffff` 稳定正数。 22. **免 hooks 环境 ELF 不可执行**:`adb shell run-as ... /usr/bin/ | head` 会报 `not executable: 64-bit ELF file` / `CANNOT LINK ... library libandroid-support.so not found`——因为 run-as 裸环境无 termux-exec LD_PRELOAD 钩子与 LD_LIBRARY_PATH。**验证快照内二进制必须带全套引擎 env**(`LD_PRELOAD=libtermux-exec-ld-preload.so` + `TERMUX_EXEC__*` + `LD_LIBRARY_PATH` + `OPENSSL_CONF`)。这是「run-as 测出假错误」的常见来源(如 node/npm/adb)。 23. **通知 debug 落盘**:WatchdogV2.consumeTaskDoneMarkers 写了 `files/notify-debug.log`(诊断用,**保留**——是排查通知链路的关键工具)。 24. **adb client 冷启动 server 必败(2026-08-27 真机实锤,已修)**:termux-exec/Linker64 重路由环境下,`adb pair` 首次调用自动起 server(fork-server)时握手坏——client 打印 "* daemon started successfully" 后读不到应答,报 `error: protocol fault (couldn't read status message)`,**配对必失败(用户侧观感"光速报错")**。对照实验证明:client 直连**已存在**的 server(`adb server nodaemon` 常驻)时 pair/connect 全部正常(同码同端口复刻 2/2 成功)。修复 = AdbState 壳内自管常驻 server(runAdb 前 `ensureAdbServer`:spawn `server nodaemon` + 5037 探测 + 日志落 `files/home/adb-server.log`)+ `retryRunAdb`(protocol fault 时毁 server 重建重试一次);pair 失败首行错误文本入审计(不含码)。另:discoverPorts 同步 NSD 等待 5s 会卡配对页 UI(bridge 同步调用链)——超时压到 2s + 启动后台预取 + 15s TTL 缓存(`cachedPorts`),bridge 缓存优先。**补锤**:app 域直接 exec app-data ELF 恒 EACCES(error=13,server/client 双双中招,审计 error 字段实锤),spawn 一律走 `spawnAdb`(捕获 Permission denied 降级 `/system/bin/linker64` 加载——EngineManager.startWithArgs 同机制)。 25. **配对码窗口被自家冷启动链耗光(2026-08-27 复盘实锤,三探针 logcat/audit/adb-server 时间线定案,已修 F1+F2+F3+F4)**:系统「使用配对码配对」弹窗的端口仅在弹窗存活期监听;用户点「配对」后壳侧才冷启动(linker64 加载 ~3s + 回收配对后密钥删除触发全新 RSA keygen + 首轮 client 握手竞态必吃 protocol fault 再自愈重建),合计 7 秒以上,拨号时窗口已关 → 恒 `Connection refused`(pairing_client.cpp),用户在系统弹窗上点什么都不救得回来。修复四件套 = **F1 常驻预热**(`AdbState.prewarm`:getAdbState 轮询钩子/onResume 后台线程拉起,60s 节流;**密钥生成移出关键路径**——回收配对会删 `$HOME/.android/adbkey*`,下次任何连接都会重新生成)+ **F2 真实就绪判定**(5037 bind ≠ 可服务:`ensureAdbServer` 放行条件收紧为一次真实 `devices` 客户端往返通过,`serverReady` 标志贯穿 spawn/复用孤儿/self-heal 三路径)+ **F3 结构化配对结果**(`setAdbPair` 返回 JSON `{ok, reason, message}` 替代 Boolean,壳侧 `classifyFailure` 归因 window-closed/protocol-fault/server-not-ready/handshake-timeout 等)+ **F4 前端分流**(AdbAuthSection 轮询不再抹操作报错——双通道 pollError/actionError 且后者留存 15s;失败不清空输入框;文案按 reason 分流,refused 明示「窗口已关闭请重开弹窗」而非误导性「核对 6 位码」)。 26. **vivo SELinux 拒读无线调试属性(2026-08-27 logcat 实锤)**:untrusted_app 读 `service.adb.tls.pairing_port` / `service.adb.tls.port` 触发 `avc denied { read } adbd_prop`——AdbState.discoverPorts 的「系统属性直读」优先路径在 vivo OriginOS 上恒失效(异常路径静默吞掉无感知),实际全靠 NSD/mDNS 兜底。勿据此属性在 vivo 上做正确性假设;后续若在此设备上看到直读成功属 ROM 变更,需回归。 27. **引擎侧页面误调 openNativePath(2026-08-27 记录在案)**:logcat 出现 `dsh-image: openNativePath: not exists: 自动扫描系统无线调试的配对/连接端口…`——引擎 UI 包把按钮 title 文案当路径传给了桥调用。壳侧安全拒绝 no-op 无实害;根因在上游引擎页面包(非本仓库管辖),升级引擎时留意。 28. **dsh-shell run() 返回 CollectedOutput 结构体(2026-08-27 活体插桩实锤,已修)**:`shellFace.run()` 契约返回 `{stdout:{text,truncated,spillPath?}, stderr:{…}, exitCode,…}` 而非字符串(引擎内置 bash 工具经 `streamText(output).text` 同款读取)。插件历史代码 `String(r.stdout)` 直取 → 恒 `"​[object Object]"`:**进程真实执行(审计恒 ok)、模型转录全毁**,并连带 device_info 满屏 `?` 占位(拿乱码 grep MODEL= 零匹配)与 lossless 拒收(可选字段 undefined 成员被引擎整值拒绝)。修复 = `collectText()` 解包 + `pickText()` 类型闸 render(非 string 一律 JSON 转写,杜绝 [object Object] 再入转录)+ 可选成员空串兜底。**伴生雷**:NSD 抓的连接端口随无线调试重启轮换(37575 失联实锤)→ `resolveLivePort()` 配置端口失效即回退 5555。**排障方法论沉淀**:①疑似「改码不改行为」先杀引擎进程再验——`force-stop` 后可能有孤儿 `node -`(linker64 链)幸存占 5037/3080 继续用旧模块(/proc 扫 cmdline 对照注入 mtime);②设备端插件注入用 base64 分块 `printf %s >> ` 通道(MSYS /tmp 不跨执行块存活,stdin 管道会截断);③插桩指纹(状态消息缀 ⟪标记⟫)一次往返即可判定加载版本。 29. **引擎会话档位为事件溯源、按会话隔离(2026-08-27 澄清,非缺陷)**:UI 档位选择器写入会话日志 `sandbox/mode` 事件(`effectiveSandboxMode` fold,最后一条生效),重启经重放恢复、两会话互不可见;`session.list` projections.permissions.currentValue 为真值。状态端点显示的 `writeMode=workspace-write` 仅部署默认(装配 yml shell-termux config),工具实际放行以会话档位为准(gateFor→sandboxPolicy.resolve)。勿把部署默认当死锁。 30. **ps1 双 ABI 循环后 assets 停留 x86_64(2026-08-29 实锤,坑 18 现代版)**:build-apk-013.ps1 循环内按 ABI 覆盖 `assets/snapshot.tar.xz`,循环结束留在 x86_64——此后直接 `gradlew assembleDebug` 的 debug 包即 x86 树,装 arm64 真机报 EM_X86_64(本轮已踩)。铁律:真机安装只用 ps1 对应 ABI 命名产物;存疑时 `od -A d -j 18 -N 2 -t u1` 读 node ELF 机器码(183=arm64/62=x86_64)。 31. **force-stop 杀不死 linker64 回退子进程(2026-08-29 vivo 实锤,0.14 修复)**:升级后旧引擎孤儿存活 → 双引擎抢 3080(探活打到旧引擎、新引擎 bind 失败循环;两代 engine.log 交错误导排障)。真机找引擎 `ps -A | grep linker64`(进程名非 node,pidof node 必空);处置:run-as kill 全部 linker64 → 看门狗 ~7s 自愈。`pkill -f bin.js` 在 vivo 疑似不生效(坑 28 排障方法论①的 /proc cmdline 扫描为可靠手段)。 32. **adb forward 静默失效(2026-08-29 实锤)**:APK 重装/USB 重枚举后宿主 forward 清空 → 宿主探活 000,但设备内正常(用户 WebView 秒起)——「引擎挂了」的判断必须先 `adb forward --list` 再重 forward,否则误诊。 33. **系统 HTTP 代理劫持壳侧本地探针(#118 根因1,2026-09-02 实锤)**:WiFi 配置系统代理时,`HttpURLConnection.openConnection()` 默认走 `ProxySelector` 下发的系统代理 → 把 `127.0.0.1:3080` 的本地请求发给代理网关(回不来本机)→ 探针恒 timeout;而 WebView(Chromium 对 loopback 豁免代理)与 curl(不读系统代理)直连正常 → 「网页能开、app 却判引擎没起来」的矛盾现场。修复 = **壳侧所有本地引擎端口调用一律 `openConnection(Proxy.NO_PROXY)`**(EngineProbe / file-incoming / session.export / session.cancel;UpdateManager 的远程下载**不走** NO_PROXY),且诊断包 probe 字段区分 timeout/refused。 34. **UndoGate.runCli 直接 exec app-data ELF 无 linker64 fallback(#118 根因2,2026-09-02 修)**:`runCli` 直接 `ProcessBuilder` exec `usr/bin/node`(对比 `EngineManager.startWithArgs` 有 `/system/bin/linker64` fallback)——Android 15+ 拒绝直接 exec app-data ELF → error=13 → auto-undo 从未真正执行。修复 = 与 startWithArgs 同款:捕获「Permission denied」降级 `linker64` 加载(`build` 复用 shellEnv/OPENSSL_CONF/redirectErrorStream)。 35. **[已更正 2026-09-27,权威条目见 `gotchas.md` 坑 35] requestLegacyExternalStorage 在 Android 10 上对 targetSdk 34 依然生效**:原结论(「仅对 targetSdk≤29 生效」)把 Android 11(R) 的行为外推到了 Android 10(Q)。AOSP 平台源码实锤:Q 的 `PackageParser.java` 是 `sa.getBoolean(requestLegacyExternalStorage, targetSdkVersion < Q)`——显式 `true` 覆盖缺省值、与 targetSdk 无关;Q 的 `StorageManagerService.getMountModeInternal()` 全程无 targetSdk 判定,且 `remountUidExternalStorage` 是 per-uid 挂载(node/bash 子进程一并继承);targetSdk 门禁只在 R。故 Android 10 修法 = 加该 flag(0.14.2-fx-2 已落地,另加 `preserveLegacyExternalStorage`),并**同时**持有运行时 `WRITE_EXTERNAL_STORAGE`。详见 `gotchas.md` 坑 35。 36. **自包含内置子仓副本必须与协调仓同版(2026-09-02 实锤)**:本仓库是**云端自包含构建宿主**(`.github/workflows/build-apk.yml` 依赖 `$GITHUB_WORKSPACE`=本仓库,不签协调私库),仓库内自带整套子仓副本(`dsh-shell-termux`、`dsh-client-ui-responsive`、`dsh-host-web-compat`、`plugins/dsh-android-*`)。**协调仓改动这些子仓的源码或 bump 版本后,必须把产物同步进本仓库对应子目录(src + package.json + lib/),否则自包含链(云端构建 + `gradlew assembleDebug` 直打)注入的是旧副本**——设备上表现「悬浮球开关消失 / ADB 面板缺失 / #120 拒绝信号缺席 / 配置导入导出按钮消失」这类**功能性缺失但编译通过**的幽灵缺陷。0.13.2 实测:协调仓 ui-responsive 0.1.11 含悬浮球开关、apk 仓副本 0.1.9 无它(DevSection 少了 W7 悬浮球开关行 + 配置导入导出块);host-web-compat 0.1.8 vs 0.1.6(#120 拒绝信号缺席)。**教训:凡协调仓动了这三个独立子仓/bridge/manage,发布前必须比对两个仓库的 package.json version + 抽验 apk 仓副本 lib/client.js 关键字符串(如「悬浮球」),不一致即用 robocopy 从协调仓同步**(`robocopy "<协调仓子仓>" "<本仓\<同名子目录>" /E /XD .git node_modules /XF *.tgz`,lib/ 必须一并拷入——自包含链不对这些子仓执行 npm build)。 37. **快照刷新中途杀进程 → 看门狗拿半解压运行时拉引擎 → 用户 settings 被剪(2026-09-05 三重实锤)**:① refreshSnapshot 全量解压在模拟器实测 **~8 分钟**(44805 文件;流式逐文件覆盖——「bridge mtime 已新」≠ 完成,**唯一完成标志 = `.snapshot-fingerprint` 翻转新值 + `.snapshot-transaction` 消失**(0.13.3 事务化后不再用 `.dsh-backup`;中途抽验必误判);② 解压中 force-stop → 无闸门的看门狗 5s 一拍用「半新半旧运行时」spawn 引擎(12:45:26 补丁日志实证)→ 混合态引擎的 settings 归一化把 `llm-pi-ai.providers`(**用户自定义供应商 Hy3/opencode-go 挂此**)剪成出厂空模板,且后续刷新备份忠实保留损坏结果;③ 恢复通道 = `undo-snapshots/auto/<最后好版本>/home-settings.yaml` 拷回 `.dsh/settings.yaml` + 重启(.credentials.yaml 的 apiKeyEnv 引用未受损)。**修复**:`EngineManager.snapshotRefreshing` companion 级 @Volatile 闸门(MainActivity/EngineService 各持实例字段互不可见,同 STARTING CAS 道理),刷新期 startEngine 直接跳过、finally 清标志。**升级/排障铁律:装新包触发重解压期间,禁 force-stop、禁拔 USB、禁 adb reboot(协调仓雷点 1 同源)。** ## 7. GPL 合规(2026-08-23 定稿) - 快照包:`usr/share/doc//copyright`(多数为软链 → `usr/share/LICENSES/.txt`)或 COPYING* 实体文件;`licenses` 包在 TARGETS 显式锁定(x86_64 曾漏带)。 - 仓库:`LICENSES/`(GPL-2.0/3.0、LGPL-2.1/3.0 全文)+ `THIRD_PARTY_NOTICES.md`(80 组件矩阵,含源码要约与再加工工具清单)+ `scripts/third-party-licenses.json` + `scripts/check-third-party.mjs`(矩阵覆盖 + copyleft 全文在场,三形态判定);门禁接入 build-apk-013.ps1,缺失即拒打包。 - APK:`assets/licenses/`(LICENSES + notices,随包分发)。 - 声明文:`docs/RELEASE.md §7`(D 章合规声明 + 源码要约 + 修改工具)。 ## 8. 待办与已知缺口(非本轮范围,记录防止再探) > **现行清单以 `docs/AGENTS/known-gaps.md` 为准**(0.13.8 / 0.14.0 收尾登记在彼处维护);本节保留历史条目。 - F2「T1 授权豁免自动升级」未落地(电池白名单仅引导 Intent;指数退避仅日志不改调度)——涉及系统策略写面,不自动执行。 - ~~F0.3 引擎事件桥未实现~~(0.13.x 已落地:dsh-android-bridge 监听 `session/event` → `.task-done.ndjson` / `.notify.ndjson` → 壳侧消费);F1.10 引擎更新通道未实现。 - 子代理 PRD 评审完整清单见协调仓库 `docs/review-0.13.0-20260823.md §九` 与 `.deploy-tmp/prd-gap-review.md`(U4/U5、A4/A6/A8、B4/B5/B7、F4、P4 未修项)。 - **~~扫描/图片版 PDF → 页图渲染受限~~(0.13.1 已修,0.13.0 记录作废)**:原记录「`@napi-rs/canvas` 仅 glibc 预编译装不上」系**误判**——npm 有 `@napi-rs/canvas-android-arm64`(N-API/Bionic 预编译,os=android cpu=arm64,真机 createCanvas 实测可用)。0.13.1 起随出厂快照装配(profiles/web package.json 登记 + tarball 解入,仅 arm64;npm 无 android-x86_64 triple,x86_64 模拟器维持守卫降级)。构建脚本 7c2 段。 - **marketplace 惰性加载决策(0.13.0 D4)**:cordis 装配层无惰性概念;拆装配违反 F4「内置市场」。启动速度优化由 D2(快照瘦身)+ D3(NODE_COMPILE_CACHE)承担,marketplace 保持启动装配。 - **provider 命名混淆(0.13.0 C3 实锤)**:默认 pin 曾为 `opencode-go`(OpenCode Zen Go 网关,`opencode.ai/zen/go/v1`,实测 404)——用户误以为配了 OpenRouter。0.13.0 默认 pin 改 `deepseek-official`(壳注 DEEPSEEK_API_KEY),opencode-go/OpenRouter 需在「添加自定义供应商」显式配置;设置页文案与文档需持续提醒区分。 --- ## 更新记录表 | 时间 | 版本 | 更新内容 | 更新者 | |---|---|---|---| | 2026-09-14 | **0.14.0-preview 已发布(vc38)+ 工作区续做** | 发布:B0/B1 缺陷收口(#204/#205/#206/#210/#211/#214)+ B2 门禁闭环(17 项聚合、五处接线、SKIP=0)+ 返回手势/通知分级与通知内应答/外部文件草稿;浏览器与虚拟屏为实验特性不接产品路径。工作区(未提交/未验收):Shizuku 特权 transport(ShizukuTransport/ShizukuUserService,api/provider 13.1.5)、虚拟屏(VdisplayController/VdisplayHost/VirtualDisplayProbe)、隔离 BrowserHost + 视口、ScreenScope 开放屏幕范围、#222 路由保护(check-api-route-auth + policy)。本文件同步新模块行与新坑(91-98)。 | AI 开发助手 | | 2026-09-06 | 0.13.3 | **0.13.3 开发批落地(W1-W8 代码面全绿;回归与 push/PR 待用户口令;vc30/0.13.3 版本号已入 build.gradle.kts)**:**壳侧**(本仓):EngineAuth.kt 新增(P0 engine.log token 交换 + P1 .credentials.yaml browser-session grant 自 mint cookie;token/cookie/secret 禁落日志;handleUnauthorized 401 自愈;attachMux 供 WS 握手)+ EngineProbe 401/303 视作 alive + postRpc/postRespond/downloadToDownloads 全调用面 Cookie + WebView CookieManager 注入或 token URL 自愈 + MuxClient 传输面重做(/api/remote.mux + Cookie 握手 + open $events 流 + waterfall/emit 帧迁移 eventId/agentId + 应答 POST /api/$events/result)+ OverlayService.applyAgentStatus 官方忙态锚点(api-session/status,turn_start 退役)+ OverlayPanel/OverlayLiveFeed 帧迁移(PendingApproval/Question 改 eventId/agentId,.overlay-test-pending 新帧形)+ AndroidBridge/MainActivity/WebUiChrome setTextZoom 桥与持久化退役(D6)+ compileDebugKotlin BUILD SUCCESSFUL。**构建链**(scripts 双写雷点 10):build-snapshot-013.mjs 0e 引擎 overlay(engine-overlay.json 264 包 + pi-ai pin 0.85.1 + 闭包缺口补齐 vendorTop 17/nested 24 + keepUnpublished 断言)+ 0f 引擎树补丁(pi-drift-F1 --scope engine,4 锚点降级告警+跳过)+ snapshot-config/engine-overlay{,-licenses}.json 新增 + check-engine-overlay.mjs 门禁(269 断言+marker+license,ps1 每ABI)+ pi-catalog-diff.mjs(P2 例行,ps1 接入)+ build-apk-013.ps1(overlay 抽验 + model-sync external + 挂载集)+ build-apk.mjs model-sync + patches(apply-patches --scope + registry pi-drift-F1)+ profile-web.cordis.patch.yml model-sync 挂载 + vendor/dsh-model-sync。**子仓副本**(坑 36 robocopy):ui-responsive 0.1.13(字体滑杆退役)/ host-web-compat 0.1.9(withResolvers polyfill)/ bridge 0.1.4(turn_start 行退役)+ lib 产物 | AI 开发助手 | | 2026-09-06 | 0.13.2-fix | **Phase 5 终包回归收官(本地提交;push 待批准)**:双模拟器(16416/16384)终包 x86_64 探活双 200、指纹翻转重解压闸门正常(2.5/10.5 分钟)、零 FATAL;**F8 dumpsys 实证:球窗 z 序高于光环窗且中心重合 (842,567)**;**F9 实证:任务移除 → 悬浮窗清零 + 引擎停机**(完整关闭语义按用户拍板);快照静态验证补丁 D 双侧在场;P1 修复(AdbState.adbShellExecute requireFullAccess 参数——API 29 SAF 方案 B 专用通道级门);报告见协调仓 docs/PHASE5-REGRESSION-2026-09-05.md | AI 开发助手 | | 2026-09-05 | 0.13.2-fix | **Phase 4 维护者五文档建立(docs/ 新增,源码 grep 实证)**:ARCHITECTURE.md(35 个 .kt 共 8516 行模块地图/构造注入方向/assets 六资产/manifest 8 组件 12 权限)、BRIDGE-API.md(androidBridge 31 方法逐条行号锚点+consoleBridge 6+原生→页面回调通道 8+MuxClient /api/events.mux 四帧型与 /api/respond 信封+__dsh_pick_refused__ 哨兵)、ANDROID-API-USAGE.md(android.* 导入 205 行去重 85 类/33 文件+11 个 API 等级守卫点+targetSdk 34 与 minSdk 26 理由)、DEPENDENCIES.md(gradle 5 依赖+用途与升级策略 4 条)、RUNTIME-PATCHES.md(assets/patched 六文件逐项:5 生效 1 在场未启用(llm-deepseek-index.js 无 applyAssetPatch 调用),内容指纹机制非 marker,与协调仓 scripts/patches/ 分工);§4 头部加五文档指针(冲突以文档为准);修正口径:桥方法 31+6=37(旧调研记 32 系估算) | AI 开发助手 | | 2026-09-05 | 0.13.2-fix | **Phase 3c OverlayService 职责拆分(纯搬移零行为变更,未提交)**:OverlayService.kt 1564→611 行(保留生命周期/三窗口 WindowManager 参数/拖动 clamp/吸边弹簧/探活与发送编排),新增四个同包顶层协作类(构造注入服务引用,无静态单例):OverlayHalo.kt 79 行(Halo 顶层枚举 + 辉光 drawable/setHalo/syncHalo/deriveHalo 唯一权威)、OverlayPanel.kt 749 行(buildUnit 面板构建/updateBallOnly 渲染/状态模板/待答卡官方风格/POST /api/respond 应答;PendingApproval/PendingQuestion 顶层)、OverlayLiveFeed.kt 167 行(FileObserver .live.ndjson drainLive 四事件分发 + F7 android_* 自动化避让 + .overlay-test-pending debug 注入 + toolSummary)、OverlayTheme.kt 38 行(isDarkTheme/ThemeColors 色板);服务以 internal 成员/委托入口(setHalo/deriveHalo/updateBallOnly 等)共享状态;**F7/F8/F9 行为与关键注释原样保留(F8 z 序约定注释留在 buildRoot 并在 OverlayHalo 抬头重申;MuxClient.kt 未动,其注释「OverlayService.handleMuxFrame」归属更新只落本文档)**;门禁 `:app:compileDebugKotlin --no-daemon` BUILD SUCCESSFUL;§4 OverlayService/MuxClient 行同步 | AI 开发助手 | | 2026-09-05 | 0.13.2-fix | **清理/统合/提速批 + ADB 可靠性批次(本地提交,GitHub 冻结中,push 待全量回归+用户批准)**:① scripts 双仓漂移收口(9 文件定权威双向同步:elf-check 双模式回灌修 build-release.ps1 单参断链、fix-shebang B 类 shebang 双应用防御、make-snapshot C3 修正、contract/licenses 同版)+ **修复幽灵缺陷:本仓 patch-marketplace.mjs 副本缺补丁 C/D(云端自包含构建产出缺补丁 APK)**,vendor 已应用并幂等验证;② Phase 2a 统一补丁框架 scripts/patches/(apply-patches.mjs + registry.json 12 补丁交叉校验 + data/compat-map.json,旧 patch-marketplace/patch-undo-mobile 删除,build-apk.mjs/ps1 门禁接入,反向去补丁重施加验证字节级恢复);③ Phase 2b snapshot-config/ 数据模块(preinstall/seed/strip/slim/apt.conf.template/install-clang.sh,编排器数据外置 648→587 行);④ Phase 2c 提速:inject-all.py 单 pass(与旧三步链等价性 EQUIVALENT 57749 成员零差异,preset1 下 4m52s→1m33s)+ build-apk.mjs 切单 pass + 归档 xz -T0 多线程(380s 级→47.5s)+ gradle.properties parallel/caching/configuration-cache 堆 4g;⑤ **ADB 可靠性批次**(真机网易云操控实测 docs/BUGS-open-2026-09-05-ADB-field-report.md):manage 0.1.2(F1 语义树动画止血/F2 screenshot 物理分辨率+ui_click nx/ny 归一化/F4 devices 清单/F5 ADBKeyboard 优先+IME 自动引导还原+clear 原子清空/F10 dsh-tmp LRU+远端即用即删)+ bridge 0.1.3(F3 execAdbShell 远端前置 export PATH=/system/bin:/system/xbin 整体单引号转义/F4 resolveLivePort 型号校验+status deviceModel/F6 guidance 透出)+ 壳侧 F7 悬浮面板自动化避让(drainLive 识别 android_* tool_call 自动收起)/F8 光环 z 序修复(halo 窗先于 ball 窗 addView——同 overlay type 按 add 顺序定 z 序,旧顺序辉光盖住球面=整球变红,z 序约定固化)/F9 划掉后台=完整关闭(用户拍板:onTaskRemoved 撤悬浮球+requestShutdown+stopSelf,START_STICKY 重投递 userShutdown 门拦截)/F3 AdbState 同款 PATH 前置;⑥ 双仓脚本同版铁律履行(build-apk-013.ps1 根自检测版)。待办:Phase 3 MainActivity/OverlayService 拆分、apk 仓 docs 五文档、双模拟器全量回归 | AI 开发助手 | | 2026-09-05 | 0.13.2 | **v0.13.2 正式发布收官(用户批准,横屏回归通过后)**:横屏 16384 终包回归全过(闸门二次验证解压 7 分钟零抢跑 / A pending 持久 / B 发送即忙+自动建会话+45s 兜底回退 / D 吸边同心 d1.5 密度双缘验证 / 面板左翻;C turn_start 无 key 不可独立复现、竖屏已实考);主分支 Gate 绿(e251257 success,无开放 PR 直落 main);发布构建 -ExportSnapshots 双快照一致性门禁双 PASS;15 资产上传(bridge 0.1.2 / ui-responsive 0.1.12 / host-web-compat 0.1.8 重新 pack,其余复用 preview 资产);draft 转正式 tag v0.13.2 落 main prerelease=false;npm pack 坑:--pack-destination 相对路径按 npm cwd 解析需绝对路径;§1 版本状态更新 | AI 开发助手 | | 2026-09-05 | 0.13.2-fix | **悬浮球三连修 + 快照刷新闸门(用户实测回归三问题全修 + 升级排障实锤坑 37;坑 36 编号不变)**:#1 收起态球不变黄(必须展开才见琥珀)→ probeEngine 每 10s 自带判定直接 setHalo 把 PENDING 琥珀盖回白——抽 deriveHalo() 唯一权威(ERROR>PENDING>WORKING>IDLE)探活与 updateBallOnly 共用(截帧实证:t+2s 琥珀 t+16s 被打白→修后 t+26s 持久);#2 发送后模型起轮前面板显「空闲」→ live 流无起轮事件壳侧失聪——markBusyOptimistic 乐观置忙(发送成功/应答提交即亮工作态,45s 无 live 事件探活兜底回退)+ **bridge 插件 0.1.2**(npm version bump + build + robocopy 已同步 apk 仓副本)新增 turn_start 行(上游 turn/start 事件在产),drainLive 消费——WebView 侧发送/提问续跑/无工具轮全覆盖(实测:面板收起纯 WebView 发送 t+1s 球即蓝、纯文本轮排除 tool_call 驱动);#3 吸边后球/光环中心错位 14px → 光环窗 64dp 贴边越界 7dp 被 WMS 整窗平移(dumpsys 实锤请求 x=-14→frame x=0)→ 光环窗缩 50dp(=2×(margin 8dp+球半径 17dp))贴边恰内切屏(修后 dumpsys 两窗中心同为 50,567);#4 快照刷新期看门狗拉引擎(坑 37:模拟器解压实测 ~8 分钟,中途 force-stop → 半解压运行时引擎剪掉用户 llm-pi-ai.providers/Hy3 配置,已从 undo savepoint 124343 恢复)→ EngineManager.snapshotRefreshing companion 级 @Volatile 闸门刷新期 startEngine 跳过;回归四项全 PASS(pending 持久/发送即忙/turn_start/吸边同心,详见协调仓 docs/HANDOVER-overlay-v21-20260904.md §九);§4 OverlayService/EngineManager 行与坑 37 同步 | AI 开发助手 | | 2026-09-04 | 0.13.2-fix | **悬浮球 v2.1 交互批 + 设置页全屏修复(未发版;竖屏 16416 + 横屏 16384 模拟器实测全过)**:**三窗口架构**(球窗 34dp 全程不变尺寸/标志 + 光环窗 64dp 纯视觉四态辉光(半径 24dp≤球心距边 25dp 贴边不截断)+ 面板独立 focusable 窗 SOFT_INPUT_ADJUST_PAN——键盘系统原生上推面板,非相交 overlay 窗收不到 IME insets 实锤,自管 insets 方案作废)——互吞复发与「展开拖动只有光环动」(clamp 按球径致 WMS 重新贴边)双根治;**MuxClient.kt 新增**:手写 WS 连 `/api/events.mux` 下行收 PENDING 审批/提问(accept 校验 substring 前缀比较永不匹配致静默失败已修 + 逐次重试日志;HTTP 无轮询通道、GET 426),应答 POST /api/respond 全信封(question selected 用选项 label、顺序匹配,可 cancelled 取消);**官方风格待答卡片**(问题卡:题头+加粗题干+编号选项行+✎ 自定义输入+‹n/n› 翻页+跳过/下一题/提交;审批卡:工具+理由+批准一次/拒绝);状态文案模板 prefs 化(overlay_display:template_thinking/template_tool,{tool}/{summary} 替换);关闭钮独立 ✕ 图标(dsh_ic_close.xml);**设置页全屏(ui-responsive 0.1.12 同步副本)**:抽屉 transform→margin-left(transform 生成的 containing block 劫持 fixed 子树 = 0.13.1 设置窄条 300px 根因)+ 设置弹层选择器去 :has(老内核 Chromium 83 整条丢弃)+ 面板 100vw×100vh,实测竖屏全屏横向 tab、横屏桌面分支正常大弹窗、抽屉滑入不受影响;调试注入 `.overlay-test-pending` 文件钩子(FileObserver,debuggable 门控);待真机项:IME 顶起实机观感、Deep diving/工具模板真实 agent 轮、MuxClient 长连稳定性 | AI 开发助手 | | 2026-09-02 | 0.13.2-fix | **子仓副本同步协调仓(坑 36 登记,幽灵缺陷根因)**:协调仓 ui-responsive 0.1.11(含 W7 悬浮球开关 + 配置导入导出块)/ host-web-compat 0.1.8(含 #120 拒绝信号)→ robocopy 同步到 apk 仓副本(`dsh-client-ui-responsive` 0.1.9→0.1.11、`dsh-host-web-compat` 0.1.6→0.1.8、`dsh-shell-termux` 补 lib/ 产物);实测设备快照此前为 0.1.9/0.1.6(自包含链注入旧副本 → DevSection 无悬浮球开关、#120 缺席) | AI 开发助手 | | 2026-09-02 | 0.13.2-fix | **悬浮球 v2 发布前回归修复(模拟器实测 4 问题)**:#1 展开弹输入法系统 pan 抬高整个 overlay 窗口(球+面板上跳)→ 窗口与 showPanel 设 `SOFT_INPUT_ADJUST_NOTHING`;#2 Deep diving 卡死→ busy 会话感知(仅当前目标会话 tool_call/turn_end 驱动,其它会话/陈旧 live 行不置忙)+ 修复 live 文件污染(残留 `session-light-test` 无 turn_end 工具行)清理;#3 展开面板无法选发送对话→ 新增目标会话下拉选择器(session.list 投影,第一项「新会话」,标题取 `projections.values.title`——顶层无 title 字段且 optString 对 NULL 返 "null" 字面量须判空);#4 手动输入发送不成功→ send 目标稳定(来自选择器)、session.create 回包去 `.take(200)` 截断、extractSessionId 正规解析两形态。模拟器复验:send 经 overlay 实测送达会话 history(`hello_final` 落 `session-9078…` user/message);§4 OverlayService 行语义同步 | AI 开发助手 | | 2026-09-02 | 0.13.2-fix | **修复批(未发版,实施中)**:**坑 33-35 登记**(系统代理劫持本地探针 → 全链 Proxy.NO_PROXY / UndoGate 无 linker64 fallback → 补 linkers / requestLegacyExternalStorage 对 targetSdk≥30 无效——MT 管理器调研实锤);#118 五项修复(EngineProbe 直连+portReachable+refused/timeout 区分、壳侧全部本地 HTTP 直连、UndoGate linker64、冷却窗/存活兜底端口级、启动失败自动重试 2×5s/10s);**悬浮球 v2 从零重写**(OverlayService v2:纯黑白球 bbox 裁剪居中 + 低饱和光环 + 上下合体圆角矩形 + ShimmerTextView deep diving + 工具×N + 插话/自动建会话 + spring 动效;飞行 v1 面板/12 条流/收起按钮——P5 未绑 onClick);**#120 壳侧落地**(SDK 26-28 运行时权限放行 + SDK 29 显式拒绝 reason 哨兵 `__dsh_pick_refused__:`,manifest 加 READ maxSdk32/WRITE maxSdk28);双形态设备验证全 PASS;§4 文件表补 ShimmerTextView/EngineProbe 语义 | AI 开发助手 | | 2026-08-21 | 0.13.0 | 首版创建:AGENT.md 规范落地(PRD F6);逐文件职责与代码位置截至 dsh-mobile-apk main@5679e59(0.12.5-fx-1) | AI 开发助手 | | 2026-08-23 | 0.13.0 | **重构为便利开发维护版**:真实 ADB 通道(AdbState 配对/端口/密钥/审计 + OPENSSL_CONF 坑)、F5 消费端(FileIncoming 200MB 上限)、构建链全景(快照 TARGETS/licenses/pnpm + 全门禁清单 + APK 产物路径)、合规 D 章、11 条坑记录(realpath/pnpm/header 白名单/surfaceOp/9p 权限/信封式 RPC 等) | AI 开发助手 | | 2026-08-24 | 0.13.0 | **真机回归发现全量固化**:引擎 OPENSSL_CONF 缺口(node 子进程全挂→shellEnv 统一注入)、apt/dpkg 编译期路径(APT_CONFIG 主文件方案重写 7d;dpkg SYSCONFDIR 已知限制)、git 预装 TARGETS、临时工作区(registry 强制登记 + TTL 7 天清扫)、通知首启权限注册、配对伪成功防御 + 端口自动扫描(discoverPorts)、老内核 ES2022 polyfill、错位目录剔除 + check-prefix-residue.sh 自检(坑 12-17) | AI 开发助手 | | 2026-08-24 | 0.13.0 | **真机回归雷点补全(坑 18-23)**:debug 快照 ABI 事故、cordis.patch.yml 装配缺陷、EngineService 挂载缺失、通知链路三缺(事件桥/task-done 标记消费/服务挂载)、run-as 假错误、通知 debug 落盘 + 通用化增强(环境无关声明、构建前 ABI 核对提醒) | AI 开发助手 | | 2026-08-24 | 0.13.0 | 新增第 3 节「环境无关的开发/维护流程」(环境矩阵 5 组合 / 起步流程 6 步 / 改动流程规范与三必做 / 环境差异点速查 5 项),原第 3-7 节顺延为 4-8 | AI 开发助手 | | 2026-08-25 | 0.13.0 | 声明主分支为 `main`(修正「分支 docs/0.13.0-prd / feat/0.13.0」旧引用)+ 新增「禁用 emoji」约定(提交/PR/文档)并清除本文件存量 emoji | AI 开发助手 | | 2026-08-25 | 0.13.0 | **0.13.0 正式版收口(PLAN-0.13.0-FINAL)**:端口发现改造—discoverPorts 系统属性直读+NSD 替代盲扫(坑 14 同步);门1 live 判定一致化(syncFullAccess prefs 键,壳唯一真值,引擎 live 读;授权模型 §5 同步);启动超时进程存活判定(engineProcessAlive + 90s 预算,D1);NODE_COMPILE_CACHE 注入(D3);已知缺口补 canvas/marketplace 惰性决策/provider 命名混淆(§8) | AI 开发助手 | | 2026-08-26 | 0.13.0 | **新增云端构建与门禁平台化**:`.github/workflows/build-apk.yml`(宿主=本仓库,workflow_dispatch 双 ABI,仅 upload-artifact 供本地下载 debug,不出 Release;快照/插件/vendor/底座来自协调库经 coord/ 签出,build-apk.mjs 以 DSH_APK_DIR=GITHUB_WORKSPACE 指向本仓库);构建链门禁改跨平台——check-snapshot-secrets.mjs 替代仅 Windows 的 .ps1、elf-check 校验快照 node ELF 架构(防坑 18 ABI 错配);协调库 base/(Git LFS)入库;§2 门禁清单与云端构建说明同步 | AI 开发助手 | | 2026-08-26 | 0.13.0 | **云端构建改自包含(从源重建,去协调库依赖)**:#93 起整链迁入本仓库——快照从源重建(base/ 底座 LFS 为输入)、6 个缺 lib/ 插件云端 npm 构建、注入/门禁/gradle 全链在云,仅 upload-artifact;不再签出协调库(私库 GITHUB_TOKEN 不可达);§2 云端构建说明与更新记录同步 | AI 开发助手 | | 2026-08-27 | 0.13.0 | **ADB 配对失效真机实锤修复(坑 24 登记)**:termux-exec 下 adb client fork-server 冷启动握手必败(protocol fault)→ 配对必失败;修复 = AdbState 常驻 `server nodaemon`(ensureAdbServer/5037 探测/日志)+ retryRunAdb 自愈重试 + pair 失败首行入审计;discoverPorts 卡 UI 修复(NSD 超时 5s→2s + 后台预取 + 15s TTL 缓存,bridge 缓存优先);配对页双端口输入提示。附:诊断方法论(run-as 同 env 复刻 vs 壳内调用的对照实验定案) | AI 开发助手 | | 2026-08-27 | 0.13.0 | **配对窗口竞态四连修(坑 25-27 登记;三探针 logcat/audit/adb-server.log 时间线定案根因)**:F1 AdbState.prewarm 常驻预热(getAdbState 轮询钩子 + onResume 后台线程,60s 节流,密钥生成移出关键路径);F2 ensureAdbServer 真实 devices 往返就绪判定(5037 bind 不等于可服务,首轮 protocol fault 必败消灭);F3 setAdbPair 桥返回结构化 JSON {ok,reason,message}(壳侧 classifyFailure 归因);F4 AdbAuthSection 双错误通道(pollError/actionError 后者留存 15s)+ 失败不清空输入 + 文案按 reason 分流。新坑:vivo SELinux 拒读 service.adb.tls.* 属性(坑 26)、引擎页面误调 openNativePath 记录在案(坑 27) | AI 开发助手 | | 2026-08-27 | 0.13.0 | **序列化层根因修复 + ADB 全链真机贯通(坑 28-29 登记)**:活体插桩定案 `[object Object]` 根因 = dsh-shell run() 返回 CollectedOutput 结构体被当字符串 String()(坑 28)→ collectText 解包 + pickText 类型闸 render + device_info undefined 成员归零(lossless 拒收修复);resolveLivePort() 配置端口失效回退 5555(NSD 端口轮换);fork 会话受控探针验证 `getprop` 真实输出直达模型(V2425A)。坑 29 澄清会话档位事件溯源按会话隔离(部署默认非死锁);排障方法论:孤儿 node 进程排查 / MSYS /tmp 不跨块 / 设备端 base64 分块注入通道 | AI 开发助手 | | 2026-08-28 | 0.13.0 | **0.13.0 正式版发布**:versionCode 25(覆盖安装 preview 24);workflow suffix 双修(PR #97 步骤 fallback + PR #98 输入默认值,GitHub 空串输入被默认值顶替行为实锤);纯净版双 ABI 构建(versionName 0.13.0,aapt 验证);Release v0.13.0 全套 13 资产(双 APK/双快照/7 插件包/MANIFEST/notes,旧版发布规则沿用);main 分支保护定案(直推被拦,改动走 PR 合并通道) | AI 开发助手 | | 2026-08-28 | 0.13.0-fx-1 | **fx-1 补丁版发布**:出厂 seed `llm-deepseek:` 裸键 null 实锤(settings-file section() TypeError → llm-deepseek apply 中途死亡 → 模型页全灭,模拟器首启实验逐环验证);PR #101 seed 改空对象 + #104 注释符修补(# 误入 JS 数组字面量致 SyntaxError——教训:改构建脚本必须本地 node --check)+ #102 versionCode 26;fx-1 全新安装复验通过(seed 含空对象、12 命名空间、模型目录含 vision-exp);Release v0.13.0-fx-1 全套 13 资产 | AI 开发助手 | | 2026-08-28 | 0.13.1 | **0.13.1 修复批(已发布:PR #113 合并 e47c400,Release v0.13.1 全套 15 资产,versionCode 27)**:W1 市场安装 execPath 安全化——真机 linker64 回退启动污染 process.execPath,市场装插件 execFile(linker64,[bin.js]) 报 bad ELF magic:23212f75(#83/#89/#96 根因实锤);patch-marketplace 补丁 B(TERMUX__PREFIX/bin/node,双仓同步+三形态测试)。W2 canvas 出厂依赖——@napi-rs/canvas-android-arm64 实为 Bionic 预编译(本文件旧记录误判已改);构建脚本 7c2 手工装配(仅 arm64)。W5 模型页提供方空白/添加按钮死根因=0.13.0 存量坏 seed 持久化(fx-1 只修出厂模板,升级设备不生效;模拟器双向复现)→ EngineManager 启动前坏键迁移(前瞻缩进防 DUPLICATE_KEY——首版无前瞻在 fx-1 正常文件上翻车,坑已录)。W3 engine.log 世代轮转(3 代,治看门狗截断毁现场)+ 失败诊断镜像(diagnostics/-<原因>/ 全触发点覆盖,undo-gate 实测)+ dshdata README.txt 每次启动生成。W4 配置导入导出桥(AndroidBridge.exportConfig/importConfig + DevSection 按钮 + settings.yaml.import-backup 防误导)。W6 apt 链三修复(trusted.gpg.d 悬空链接落实体/Dir::Log/install-clang.sh 解包式安装器端到端实测 clang 21.1.8;dpkg cfg.d app 域致命=apt install 不可用,M3 openjdk 系 root adbd 假象)+ LD_LIBRARY_PATH 先于外部命令坑。构建链脚本改动与协调仓双写同步 | AI 开发助手 | | 2026-08-30 | 0.13.1 | **文档结构化增补**:坑 30-32 登记(assets ABI 残留/linker64 孤儿 force-stop 杀不死/forward 静默失效——与协调仓雷点 14-16 同源);标题 AGENT.md→AGENTS.md 对齐文件名;产物路径硬编码 v0.13.0 → v<版本> 占位(产物命名已由 ps1 从 gradle 单一来源读取);§1 补版本状态与开放跟踪行 | AI 开发助手 | | 2026-08-31 | 0.13.2 | **插件构建产物收口(chore, 348011c)**:.gitignore 补 `plugins/*/lib/`、`plugins/*/node_modules/`、`plugins/*/cbin_*`(本地/云端 npm 构建产物不入库)并清理存量 untracked 产物;ci/pr61-fix 孤儿分支(无 PR)与已合并 PR 的旧分支保留未动 | AI 开发助手 | | 2026-08-31 | 0.13.2-preview | **0.13.2-preview 功能批(versionCode 28 + versionName 0.13.2-preview;未发布,待用户指示)**:W6 内嵌 ADBKeyboard 协议 IME(AdbKeyboardService/Receiver,dcfd573)+ manage 语义工具双写(1bb0cc7)+ 门禁 settings.yaml 内容级修正(5d9988b)+ W7 悬浮球全套(OverlayService/OverlayController + live 流 + 开关,d76dc41/56c96b7)+ **preview 修正批(ae8a78d)**——面板跟随球(repositionPanel)、引擎页避让帧(emitFrame/replayFrame + frameConsumer 先于 ensureStarted 注册 + onPageFinished 补放 + instance/onDestroy 注销)、贴边容差 20dp;设备实测:避让 124px 注入/拖动清零/面板跟随;§4 文件表补 OverlayService/AdbKeyboardService 两行;preview 发布/PR 模板三要点见协调仓 AGENTS.md §4 | AI 开发助手 | ## 0.13.5 新增模块(无障碍控制通道) | 文件 | 职责 | 关键函数 | |---|---|---| | `DeviceControlService.kt` | 无障碍服务(语义控制面):能力声明、树快照(路径 id + 与 uiautomator XML 同构的 attrs)、动作执行 | `buildSnapshot()` / `nodeAtPath()` / `handleClick/setText/scroll/global/screenshot()` / `token()` / `statusJson()` / `heartbeat()`;每个 real-screen op 在执行点重查 ScreenScope,范围切换会丢弃旧 snapshot/ref。 | | `ControlPoller.kt` | 引擎队列客户端:长轮询取活(空闲 5s / 有活即时)、执行、回填、退避 | `loop()` / `execute()` / `post()` | 相关壳侧改动:`AndroidBridge.a11yStatus()/openA11ySettings()/unlockRestrictedSettings()`;`AdbState.unlockRestrictedSettings()`(appops 一键解锁);`MainActivity.openAccessibilitySettings()`;`EngineManager.snapshotSettingsBackup()`(换树前 settings 备份,issue #126 P1 兜底)。 能力面/权限面全量参考:`docs/AGENTS/ACCESSIBILITY-API.md`。 ## 0.14.0-preview 新增模块(通知分级与通知内应答,计划 §6) > 0.14.0 后段的 Shizuku 特权 transport、虚拟屏、BrowserHost、ScreenScope、BackGate 已在 §4 表内登记(0.14 行)。 | 文件 | 职责 | 关键函数 | |---|---|---| | `NotifyCenter.kt`(重写) | 五类渠道一次性定案 + 候选 ID 迁移 + 六类 kind → 渠道/形态分流 + 自检面 + 失败可见 | `selectChannel()`(纯函数三态判定)/ `channelFor()` / `ensureChannels()` / `selfCheck()` / `notifyEvent()` / `postDeliveryFailure()` / `silent()` / `notify()`(旧调用点兼容) | | `NotifyStore.kt`(新) | `.notify.ndjson` 消费:FileObserver + **按字节偏移**推进(只消费到最后一个换行符)+ 偏移持久化 + 轮转残段补读 + 双读不双发门 | `start()` / `drain()` / `drainBytes()`(纯函数,单测)/ `parseEntry()` / `dispatch()` / `legacyFallback()` | | `NotifyBridge.kt`(新) | 专用 `$events` 流(`streamId = dsh-notify-responder`,独立于悬浮球):waterfall 投放通知、cancel 帧撤通知、`$events/result` 投递 | `start()` / `isReady()` / `handleValue()` / `markSettled()` / `postResult()` | | `NotifyDecisionQueue.kt`(新) | 决策耐久队列:先落盘 `files/notify-decisions.ndjson` 再投递、指数退避 ≤60s、requestId LRU、失败态可见;**NOT_READY(引擎/流未就绪)独立计数 + 5 分钟墙钟预算**(到期转 FAILED + 「提交失败,点击重试」;预算 > 实测冷启动上限 110s);**触发点自愈**:进程死亡会带走重试定时器,启动/装载路径必须调 `ensureScheduled()` 重评滞留决策的预算(否则永久滞留 pending) | `enqueue()` / `load()` / `mark()` / `compact()` / `flush()` / `handleNotReady()` / `notReadyAction()` / `resumePlan()` / `ensureScheduled()` / `retryDelayMs()` / `requestIdFor()` | | `NotifyActionReceiver.kt`(新) | 通知动作接收器(回复/选项/批准/拒绝/重试):outcome 构造 + 入队 + 后台快速投递,全程不 startActivity | `onReceive()` / `outcomeFor()`(纯函数)/ `replyText()` | | `NotifyProbe.kt`(新) | 通知面耐久探针:应答流关键状态写 `files/notify-responder.log`(追加 + 128KB 轮转),不依赖调试日志采集器——设备复验用 run-as cat 判定断点 | `log(context, tag, msg)` | **设备实测缺陷修复(0.14.0 L2,2026-09-13)**: - **DEF-NOTIFY-01**(NT-05 aborted 子项):引擎可对单条事件否决弹窗(`.notify.ndjson` 的 `popup=false`),但旧实现只认类别静态 `Face.popup`——`entry.popup` 解析后无消费点,用户按停后仍收到 `dsh-report` 高优 heads-up。修法:新增纯函数 `NotifyCenter.formDecision(face, entryPopup)`,`popup=false` 的工作汇报**降级为静默条目**(渠道换 `dsh-silent` + `setSilent(true)`,**保留会话级通知 ID** 以免覆盖看门狗单条);提问/审批是交互入口,**不接受**静默降级(保留弹窗 + 探针日志)。 - **FX-212.3**(0.14.0,2026-09-13):`.task-done.ndjson` 消费从 `readLines()` + `writeText("")` 改为**按字节偏移推进**(对齐 `OverlayLiveFeed.drainLive`):`@Volatile markerOffset` + 有界读 CAP 256KB + `len < offset` 归零 + 只消费整行前缀(半行不推进,等下一拍补齐)+ **永不截断文件**;纯函数 `WatchdogV2.markerConsumeWindow(payload, length)` 配并发单测(每行恰好一次、零丢失零重复、半行不推进、CAP 只吃整行、多字节截断安全);`dbg()` 增 `consumedBytes/newOffset` 供设备对账。 **消费面口径增补(Lead 2026-09-13 批准):偏移不只在内存,还落 prefs `notify.markerOffset`(`markerOffsetOf/markerOffsetTo` 为唯一读写入口)。** 理由:只放内存时进程每次重启 `offset` 归零,而「新壳 + 老引擎」组合没有 `.notify.ndjson`、`NotifyStore.notifyChannelActive` 永不置位 → 双读不双发门失效,历史标记会被**重复通知**一遍;落 prefs 后:离线期间写入的标记仍会被消费(保留原「关机期间的完成事件在启动后补通知」语义),重启不重放。判据不变(`@Volatile`/归零/CAP/整行推进/禁 `writeText("")` 逐条保留)。 - **DEF-NOTIFY-03**(NT-16 运行时,2026-09-13 设备实测):通知栏回复成功后通知**不撤**、停在「正在发送」并留着可再点的回复框。根因:网关只给**其它**持有者发 `cancel` 帧,提交者自己收不到,壳侧投递成功后只改状态不结算。修法:`NotifyDecisionQueue.flush` 的 `OK` 分支立即 `NotifyBridge.markSettled(context, eventId, kind)`;`markSettled` 改为**无条件撤通知**(不依赖 pending 表是否还有该条目——进程重启后 map 会丢)。 - **DEF-NOTIFY-02**(NT-11 阻塞):提问/审批走 WS 应答流(`NotifyBridge`),设备侧 20×4s 复查无 `dsh-question` 活跃记录。修法三件:① **帧异常不再冒到 MuxClient 读线程**(`onFrame` 包 Throwable 边界 + `NotifyCenter.notifyEvent` 自身包边界返回 `Result.ERROR`——旧行为下一帧异常会让整条应答流断开重连,提问/审批通知一起消失);② 前台抑制**只作用于 report**(提问/审批永不因 ActivityManager 粒度判定被丢弃);③ **耐久探针 + 连接心跳**(`files/notify-responder.log` 记 start/ready/waterfall/notify result/异常,未 ready 时前 2 分钟每 5s 一条),下次设备复验可判定断在「未启动 / 未 ready / ready 无帧 / 投递被拒」哪一段;另给 `end/error` 重连加 1s 退避防热循环。 相关壳侧改动:`MuxClient` 新增可选构造参数 `streamId`(默认 `dsh-overlay-events`;注意**尾随 lambda 绑定最后一个形参**,调用点须把 onFrame 写在括号内——`OverlayPanel.startMux()` 已同步);`EngineService.onCreate` 启动 `NotifyStore` + `NotifyBridge`(通知**独立于悬浮球开关**);`WatchdogV2.consumeTaskDoneMarkers` 改走 `NotifyStore.legacyFallback`(NT-09 双读不双发,标题回落会话短哈希,不再回落字面量「任务完成」);`AndroidManifest.xml` 新增 `NotifyActionReceiver`(exported=false + 显式 Intent)。 引擎侧(协调仓 `plugins/dsh-android-bridge`,镜像到本仓同名目录):`notify-projection.ts`(D13 `reason.kind` 成败判定 / D14 `session/title` 标题来源 / 待办 n-N / 汇报载荷)、`lossless-json.ts`(聚合返回体出口去 undefined——修 `android_privilege_status` 的 not lossless JSON)、`index.ts` 新增 `.notify.ndjson` 写入(`kind: report|todo`)与 `buildPrivilegeStatusPayload`/`buildPrivilegeStatusToolPayload`。 Android API 面登记见 `docs/AGENTS/ANDROID-API-USAGE.md` §1/§3/§4/§8;验收条目与设备判据见协调仓 `docs/NEXT-ITERATION-PLAN-2026-09-12.md` §6。 ## 原生管理入口(2026-10-16) - `GuideChrome.kt`:启动页与底部主操作、六齿管理按钮。 - `GuideManagementController.kt`:启动页 / 管理中心 / 插件与存储三级导航,0°/240°可逆过渡和预测返回进度。管理页打开期间,`GuidePageRenderer.showWeb()` 保留导航上下文。更新块(`updateBlock()` / `updateCard()`)是一张设置页卡片,内含标题行、状态行与检查 / 下载两枚各自独立的按钮(不合并——合并就得按状态推断「这次点击该检查还是该下载」,那是历史上误清已确认载荷的来源)。卡片外壳规格对齐 WebView 设置页的 `.dsh-screen-control-card`(`#860`):背景 `ds_surface`、圆角 `ds_radius_settings_card`(12dp,对应上游 `dsw-radius-md` 12px)、描边 `ds_hairline` 1 物理像素、四边 `ds_space_12` 内边距;文字层级同 `.dsh-gs-title` / `.dsh-gs-desc`(标题 14sp 主色、状态 12sp/1.5 倍行距次要色)。取值只从 `DsUi.settingsCardBackground` / `DsUi.settingsCardPaddingPx` 取,调用方不重复写 token。 - `PluginManagementRepository.kt`:鉴权 RPC、Hard 清单过滤和变更校验,复用运行时插件管理与配置事务。 - `PluginManagementPage.kt`:真实插件列表、搜索筛选、原生 Switch 与配置详情;网络读取不占用主线程。 - `PluginManagementPage.kt` 的 `pluginEngineAvailability`:区分未启动、启动中、在线和启动失败。未启动与启动失败不再只给「启动引擎」空态,而是路由到离线管理页(启动中仍显示启动中的等待态);启动入口委托现有主按钮,在线后读取真实列表,在线路径不变。 - `PluginPatchOffline.kt`:引擎未启动时的插件管理数据源,直接读写 live 装配清单(`home/.dsh/profiles/web/cordis.patch.yml`)。只把 `insert` 组里的子条目列为可管理行(顶层 `- id:` 是覆盖,不是插件来源);Hard 清单的 `{id,name}` 一律排除;生效值按 Cordis 覆盖语义折叠(同 id 顶层 `disabled` 最后一条生效,`name` 断言不匹配不生效);无改动或无法唯一定位返回 null(拒绝写入)。写入只改写目标 disabled 位或追加一条覆盖,其它字节逐字保留。 - `PluginOfflinePanel.kt`:离线管理页(列表 + 启停按钮 + 错误条),经 `PluginManagementPage` 在引擎不可用时路由进入。`PluginOfflineRepository` 负责读、写、原子替换与写后回读校验:回读状态与请求不一致即报失败并保留原清单。 - `EngineStartFlow.start()`:入口(主线程)同步把相位切到 `GuidePhase.Starting` 并 `showGuide()`,主按钮立刻显示「启动中」;这一步不探测、不解压、不重建状态,已解压且指纹匹配的运行时仍走既有早退路径。worker 线程内原先那次重复切相位已删。 - `ConsoleActivity.kt` / `assets/console.html`:控制台黑白灰日夜主题;会话与桥行为保持原有实现。