HDSH
在 HarmonyOS 上自足运行 DeepSeek Harness 的应用
HDSH 把 DeepSeek Harness(dsh)的运行本体装进一个鸿蒙应用:HAP 内自带 Node 运行时与 dsh 核心树,核心在本机 127.0.0.1 上起 Host,应用内的原生 ArkUI 页面就是这个本地 Host 的客户端。
它不是 PC 上 dsh 的遥控器,也不需要在电脑上常驻任何服务:装上即用,数据只在本机应用沙箱内。支持 HarmonyOS 手机 / 折叠屏 / 平板 / 2in1。
能力
| 领域 | 说明 |
|---|---|
| 本地核心 | 内置 Node 运行时(自建、jitless)与 dsh 核心树;Host 仅监听 127.0.0.1,随应用生命周期起停 |
| 对话与轨迹 | 对话视图(问答,思考过程折叠在回答上方)与轨迹视图(工具调用、子代理、目标/任务、交付物、错误)分开呈现 |
| 工作区 | 工作区为组、会话挂在组下;可在设备上选择文件夹作为工作区(系统文件夹选择器),并在其中浏览文件 |
| 模型与密钥 | 按提供方管理:API 密钥(只写)、baseURL 与模型目录;默认模型与推理强度可选 |
| 插件 | 查看随包插件清单与运行阶段;按行启用/禁用,并可恢复部署默认 |
| 核心版本 | 同时安装多个核心版本,一键切换 / 回滚(停旧起新,逐版本校验后激活) |
| 多语言 | 界面文案跟随系统语言,默认中文 |
| 上架友好 | 不申请 JIT 等特殊权限,全部按 jitless 运行;权限仅网络相关三项 |
架构
┌─ HDSH(一个 HAP)──────────────────────────────────────────────┐
│ ArkUI 原生页面(客户端) │
│ │ HTTP /api/* WebSocket /api/remote.mux │
│ ▼ │
│ 端侧 dsh Host(Node 运行时运行在**本应用进程内**) │
│ │ DSH_HOME = <应用沙箱>/dsh/home(跨版本共享的唯一数据) │
│ ▼ │
│ 核心版本仓库(多个版本可并存,切换 = 停旧 + 起新 + 校验) │
└─────────────────────────────────────────────────────────────────┘
同进程带来的是安全语义的简化:客户端与 Host 走回环,不需要把服务暴露到局域网,也不需要跨设备转发。
端侧运行时的硬约束:jitless ⇒ 没有 WASM
不申请 JIT 权限意味着 Host 全程以 --jitless 运行,而 V8 的 --jitless 与 --expose_wasm 互斥 ⇒ 端侧 typeof WebAssembly === 'undefined'(恒定,不是配置问题)。由此推出一条对上游代码的判据:
凡是上游直接
import('undici')的功能,在端侧都会失败(undici 的 HTTP 解析器是 WASM)。
实测过的一个实例:dsh-web-fetch-http 不用全局 fetch 而自建 Agent,于是 web_fetch 打不开任何网页,而走纯 JS node:http 垫片的 web_search 照常工作 ⇒ 对付它需要两层,缺一层就会出现"Host 起来了、模型也能回话,但某个工具静默坏掉":
| 层 | 做什么 | 不做的后果 |
|---|---|---|
全局 fetch 垫片(fetch-shim.js) |
用 node:http/https 重写 fetch/Request/Response/Headers/FormData |
调模型就走不通 |
undici 模块名解析钩子(main.js 的 installUndiciNameHook()) |
让上游的 await import("undici") 拿到同一个垫片 |
web_fetch 打不开任何网页 |
不改上游源码、不改核心树;由 tools/check-web-fetch-jitless.mjs 守着(自带对照实验:不注册钩子必须失败、注册后必须全过,且跨源跳转仍须被拒)。细节见 docs/parity-matrix.md §3.2。
构建
前置:DevEco Command Line Tools(含 hvigor / ohpm / codelinter / SDK)、JDK 17、Node.js (仅用于仓库内的构建与检查脚本)。
SDK 版本口径:
compatibleSdkVersion/targetSdkVersion固定为6.1.1(24)(决策,2026-09-14)。 代价是不能用 API 26 的「沉浸光感」材质(官方要求targetAPIVersion ≥ 26)—— 界面层次当前由系统阴影表达;升级路径与要改的几处列在appstate/ui/HarmonyTheme.ets的HarmonyMaterial。
不入库的产物(新克隆必须先备齐,否则编不过):
| 产物 | 路径 | 说明 |
|---|---|---|
| Node 头文件 | entry/src/main/cpp/node-headers/ |
编 libdshhost 只需要它(libnode 不参与链接) |
| 原生库 | entry/libs/<abi>/ |
运行期需要(含 libnode.so.127);也是"编不编 koffi/flock"的门 |
| 核心包 | entry/src/main/resources/resfile/*.zip |
首启解包出端侧核心树 |
| 入口脚本 | entry/src/main/resources/resfile/resources/app/ |
由 node tools/place-host-app.mjs 从 hostcore/app/ 生成 |
# 1) 打完整体(HAP + 原生库 + 内置核心资源)
devecocli build
# 2) 需要重新打包核心树时(素材来自 dist/core/,产物落到应用资源目录)
node tools/pack-core.mjs --skip-install --place-in-app
node tools/place-host-app.mjs
# 3) 安装到已连接设备(注意:产物是 **unsigned**;签名材料不在库内)
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
构建、检查、发布与上架的完整口径见 docs/06-开发与发布指南.md 与 docs/70-上架合规自查.md。
设计体系(P1.5:Web 语义 + HarmonyOS 原生表达)
产品语义对齐官方 Web,视觉与交互用 HarmonyOS 原生表达——不是把 Web 的 CSS 机械翻译成 ArkUI:
| 层 | 落点 | 作用 |
|---|---|---|
| 尺度原语 | appstate/ui/Tokens.ets |
Sp / Radius / Border / Fs / Sz / Dur("有哪些档位") |
| 语义令牌 | appstate/ui/HarmonyTheme.ets |
角色 → 系统语义资源(sys.color.*)+ 排版成套角色 + 层级/动效/触控;WEB_TOKEN_MAP 逐条映射官方 --dsw-* |
| 原生原语 | entry/src/main/ets/view/NativePrimitives.ets |
NativeChip / NativeCard / NativeButton / NativeActionBar / NativeSectionTitle + Sheet 参数助手 |
四条已经定下来的规则(都是踩过或查证后写的,不是偏好):
- 浮层用系统形态:半模态一律
bindSheet(harmonySheetOptions()统一参数),全应用只在页面根挂一次(bindSheet是组件属性,同一节点只能绑一个):sheetKind()从既有状态派生显示哪个、closeSheet()一处复位 ⇒ 结构上不可能同时开出两个浮层,复位也只写一次。 - 遮罩交给系统:不手写
rgba(...)遮罩(35% 黑在深色主题下观感就是错的)。 - 图标用系统符号:
SymbolGlyph只支持系统预置资源,不引入 Web SVG(API 约束)。 - 裸值只许变少:
tools/check-design-tokens.mjs是棘轮门禁,管字号/圆角/描边/颜色字面量(含rgb()/rgba()/hsl()——首版漏检过,已补)。
仓库结构
| 目录 | 作用 |
|---|---|
entry/ |
鸿蒙应用入口:ArkUI 页面与视图(含原生原语 view/NativePrimitives.ets)、原生桥(libdshhost)、随包资源(核心包与原生库) |
hostcore/ |
端侧 Host 的入口脚本与 profile(cordis.patch.yml)、fetch 垫片、undici 模块名解析钩子(见上文 jitless 一节) |
hostruntime/ |
核心版本仓库、激活事务、运行时载体(RuntimePort → NodeRuntime) |
appstate/ |
客户端状态中枢(store/SessionHub)与纯逻辑:导航/布局/断点决策、回合与轨迹模型、工具呈现、失败文案、脱敏、目标、产出文件等——零依赖,可在本机直接测(断言见 docs/05) |
platform/ |
系统能力封装(文件选择、剪贴板、通知、窗口记忆等) |
dshcompat/ |
与上游协议有关的全部事实:端点、参数形状、事件类型与投影键 |
tools/ |
构建与检查脚本(核心打包、依赖闭包、上架红线、协议往返、死代码与接线门禁等) |
docs/ |
文档基线,索引见 docs/README.md |
当前状态
阶段:P8(稳定性、导航与安全收口)。信息架构骨架(AppFrame → Sidebar → Main → Rightbar → Settings 域)与四形态轨道已落地;近期十四轮:
- P8-1 返回层级收口:把"单栏下预览 = 工作区第 2 层"这条布局事实写进返回阶梯(预览层排在"下钻退一层"之前);关预览改走中枢(此前只清本地视图镜像 = 假关闭);新增
BACK_TO_LIST,修掉"手机上从会话页按返回第一次白按、第二次直接退出应用";四个"退回一层"入口收敛成一个执行点。 - P9-7 交付物分享成真:交付物卡的「分享」不再是假禁用——沙箱内的文件分享文件本体、沙箱外如实降级为分享路径(按钮文案随之变「分享路径」)、定位不到则禁用并写明原因;「保存」仍禁用(平台确实没有"另存为")。
- P9-6 会话滚动位置:去设置页/看文件预览再回到同一个会话时,回到你离开的位置(切到别的会话仍按既有决定贴到底部)。
- P9-5
@引用菜单钻取:选中目录后菜单不关、接着列下一层并显示面包屑(工作区 › src › …);顺带修好引号形态——含空格的路径(@"my dir/)此前会被当成"引用结束",查询词还带着引号发给 Host,等于永远补全不了。 - P9-4 命令参数分流:需要参数的命令不再"选中即执行"(此前必然被 Host 拒绝并弹英文 usage),而是把
命令 + 空格填进输入框并说明理由;判据取 Host 的input.hint,不写命令白名单。 - P9-3 正文文件提及可点:终答里行内代码写的路径,若命中本回合产出的文件就变可点(官方
MarkdownFileMentions的规则:只认行内代码、用真实文件词汇解析、认不出来保持普通代码)——此前它只是等宽文字。 - P9-2 原图预览(灯箱):点缩略图(单图与方图都行)打开全屏原图预览,几何与无障碍文案逐字对齐官方
ImageLightbox——此前多图只是"就地放大到 240",单图压根点不动。 - P8-6c 错误提示的生命周期:过期的错误提示会自己消失(动作成功即清、连接级只有真正连上才清)——此前一次"发送被拒"之后成功跑完一整轮,那条红字还挂着,用户只能怀疑已经成功的事。
- P8-7 技术债与守卫:
accessSync会抛异常而我们 24 处当它返回 false —— 核心树校验的"关键件哨兵"因此会把可读失败变成未处理异常;统一走FileProbe,并清掉 3 处 API 弃用告警(模块编译从 6 条告警到 0 条真问题)。 - P8-4b 操作区不被裁:输入区工具行 / 会话头视图切换行 / 时间总览统计行改成可换行(与官方 composer 的
flex-wrap同构)——此前它们装满固定宽度控件,手机上最右边的「模型」chip 会被静默挤出屏幕。 - P8-4 浮层关闭语义:改了一半的设置被拖拽关掉时,现在会明确说"已放弃未保存的修改、没有写入 Host"(此前静默丢弃 ⇒ 用户以为生效了);没改过则一个字都不说。
- P8-3 每会话草稿:草稿按会话保存与取回(官方契约:草稿是会话视图状态的一部分),并落盘——修掉"在会话 A 写一半、切到 B 顺手发送 ⇒ 那段话发给了 B",以及"应用一重启没发出去的字全没了"。
- P8-6 重试链收口:同一次故障的多次自动重试合并成一行(官方
retryId是链身份),状态行改用官方message.retry.status一行模板(等待 / 正在 / 已重试 / 已取消)——此前每一次重试各占一条红色错误卡,写着同一件事却像 N 次独立失败;按「停止」后那一行不再假装还会重试。 - P8-5 敏感内容隔离:内部事件(系统提示词、未识别事件载荷)的正文不再进入任何用户可达出口(轨迹详情 / 复制 / 引用),Host 错误原文进界面前统一脱敏。
能力对等与"还差什么":唯一清单是 docs/parity-matrix.md(逐功能台账 + §6 缺口登记,由 tools/check-parity.mjs 强制)。当前状态的权威结论见 docs/07-当前状态与缺口.md;逐轮"缺陷 → 官方依据 → 修法 → 门禁"的历史见 docs/13-缺陷编年.md。
验收口径:DONE 只表示"实现侧完成",设备验收是另一根轴——逐项判据与真机读数在 docs/device-validation.md。设备验收已于 2026-09-16/17 在 HUAWEI Mate 70 Pro+ 上跑过一轮,抓出 32 条缺陷(原始读数与 F1–F33 台账见 docs/device-validation-readings.md)。
当前状态(2026-09-17 盘点,可机器复核):已修已验 6 条 / 已修待验 16 条 / 部分已修 2 条 / 待定位 1 条 / 待修 1 条 / 核心版本不具备 4 条(定时任务、取消归档、撤销、待决跨重启——已逐条核实无端点,不造假入口)。
- 怎么修、为什么这么修:
docs/80-修复与优化方案.md(含 UI 对标官方的口径表与逐项结论); - 接上设备怎么验:
docs/81-待真机复验清单.md(甲/乙/丙/丁/戊五档,甲档约 15 分钟跑完)。
本仓库不宣称"已通过设备验收":上面那 16 条 已修待验 都还没在设备上复核过。
文档
从 docs/README.md(总索引与阅读顺序)进入。最常用的四份:
docs/01-产品与功能说明.md—— 产品是什么、做什么、不做什么。docs/02-开发计划.md—— 当前阶段与实施顺序(开发代理的执行入口)。docs/03-技术架构与模块.md+docs/04-HarmonyOS多端UI设计规范.md—— 代码放哪里、UI 怎么做。docs/05-验证与验收.md—— 无设备时做到哪一层、什么时候必须上设备。
设计原则
- 不 fork、不魔改 dsh:端侧差异只通过 dsh 自己的组合面(profile /
cordis.patch.yml/ bundle)表达。 - 不申请特殊权限:需要 JIT、ACL 之类前提的方案一律不进入选型,以保证可正常上架。
- 界面不撒谎:失败必须给出下一步;空态说明"可以做什么";不可用的动作把原因写在旁边,而不是给一个点了没反应的入口。
- 上游知识只出现在
dshcompat:字段名、端点形状、事件类型集中一处,升级上游时改一个地方。 - 产品语义跟 Web,视觉与交互用鸿蒙原生:信息架构与行为逐行对照官方 Web(矩阵登记),视觉不用 CSS 的像素级翻译——用系统语义色、系统符号、原生控件与多窗口语义表达,官方主题更新时无需重做界面。
许可
见 LICENSE。
No comments yet. Be the first to write one.