MaybeMeibeMaybi/dsh-harmonyos-hiboard ↗★ 1

dsh-harmonyos-hiboard

Push DeepSeek Harness (dsh) task results to Huawei HarmonyOS assistant-today service cards. Fixed for dsh 0.1.5 / cordis 4, with documentation of the card rendering contract. 适合华为鸿蒙系统用户,需在手机负一屏快速查看Markdown格式的对话结果。

Package
dsh-harmonyos-hiboard
Compatibility
Unverified
Harness peer range
>=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0
Cordis peer range
>=4.0.0-rc.1 <5.0.0-0
Version
0.2.0
License
MIT
Last updated
Sep 27, 2026

Install

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:MaybeMeibeMaybi/dsh-harmonyos-hiboard

华为鸿蒙系统 DeepSeek Harness 对话结果负一屏卡片推送

HarmonyOS DeepSeek Harness Result Push to Assistant-Today Cards

License: MIT HarmonyOS NEXT dsh Node

把 DeepSeek Harness(dsh) 的对话与任务结果,以 服务卡片 形式推送到华为鸿蒙 负一屏(智慧助手·今天),手机上点开即可看到完整的 Markdown 正文。

本仓库基于 Entity-Him/dsh-hiboard-push(MIT)修改, 修复了它在 dsh 0.1.5 / cordis 4 上的启动崩溃,并补齐了部署文档与卡片契约分析。 来源与改动逐条列于 NOTICE。


效果(真机截图)

负一屏卡片列表

① 负一屏卡片

同一 schedule_id 的卡片会归到一组

历史记录列表

② 历史记录

每条任务都有「进任务」入口

卡片详情页完整 Markdown

③ 点开详情页

完整 Markdown 正文,含渲染正常的表格 —— 这正是必须传 schedule_id 的原因

测试环境:HarmonyOS 7.0.0.107,仅测试了手机端(未验证平板 / 折叠屏 / 手表等形态)。 ③ 是项目介绍卡;①② 里带「示例」字样的卡片为测试数据,非真实任务记录。 Markdown 支持范围与表格写法要求见 兼容性 一节。


效果

DSH 每完成一件事,agent 调用一次工具:

hiboard_push(
  name        = "磁盘健康检查",
  content     = "# 检查结果\n\n- C 盘:正常\n- D 盘:正常",
  result      = "已完成",
  schedule_id = "dsh_worklog"        # 关键,见下
)

手机负一屏出现卡片 → 点进去 → 完整 Markdown 正文。

DSH agent ──调用 hiboard_push──► 华为 HIBoard 云侧 ──► 手机负一屏服务卡片
  (本插件注册的两个工具)            POST .../msg/upload

⚠️ 三个必须知道的坑

1. 卡片必须带 scheduleTaskId,否则正文完全不显示

这是本项目最有价值的实测结论(2026-09-23 对着线上服务验证):

负载形态手机上看到什么
scheduleTaskId 为空只有一行标题 + 时间 + 来源;正文一行都不显示,「进任务」深链点了没反应
scheduleTaskId 非空打开完整详情页,渲染整段 Markdown 正文 ✅

content 字段本身没问题(实测负载里带换行、序列化正确,接口也返回成功), 平台只是不给"没有 scheduleTaskId"的卡片开放详情页。

结论:推送一律传 schedule_id。 同一会话用同一个 ID,还能在负一屏里分组。

2. 授权码不能放 ~/.dsh/.env

DSH_HIBOARD_AUTH_CODE 是 dsh 的 bootstrap-only 变量。写进 .env 会让 dsh 直接拒绝启动:

Error: dsh: ...\.env sets "DSH_HIBOARD_AUTH_CODE", which only the launching
       environment may set ...

正确做法:写进 profile 补丁层的插件 config(见下方部署)。

3. 上游版本会让新版 dsh 启动崩溃(本仓库已修)

上游使用 @deepseek-ai/dsh-settings 的 installSettingsSection,该 API 在 cordis 4 已移除:

SyntaxError: The requested module '@deepseek-ai/dsh-settings'
             does not provide an export named 'installSettingsSection'
TypeError: Cannot read properties of undefined (reading 'validate')

src/lib/index.js 是已修复版:摘掉失效的设置面板注册,推送逻辑完整保留; 上游原版留作 src/lib/index.js.upstream-backup 便于比对。


快速开始

前置:Windows / macOS / Linux + Node.js 18+ + dsh 已安装 + pnpm 已安装。

# 1) 克隆到一个「路径不含空格」的位置(pnpm 的 file: 依赖会被空格截断)
git clone https://github.com/MaybeMeibeMaybi/dsh-harmonyos-hiboard.git E:/dsh-vendor/dsh-harmonyos-hiboard

# 2) 安装为 dsh 插件
dsh plugin --profile web add "file:E:/dsh-vendor/dsh-harmonyos-hiboard"

3) 写入授权码 —— 编辑 ~/.dsh/profiles/web/cordis.patch.yml:

- id: hiboard-push
  config:
    authCode: 

授权码获取:手机 负一屏 → 我的 → 动态管理 → 关联账号 → Claw 智能体。

4) 重启 dsh,然后让 agent 调用 hiboard_push(记得传 schedule_id)。

5) 装推送兜底(强烈建议) —— 不装的话,一旦 agent 把推送写成正文(见下节), 卡片会静默消失:

# 两个独立插件:push-guard(兜底)+ token-broadcast(启动推 token)
git clone https://github.com/MaybeMeibeMaybi/dsh-harmonyos-hiboard.git E:/dsh-vendor/dsh-push-guard
dsh plugin --profile web add "file:E:/dsh-vendor/dsh-push-guard/src/push-guard"

~/.dsh/profiles/web/cordis.patch.yml:

- insert:
    - id: push-guard
      name: dsh-push-guard
      config:
        enabled: true
        rescueEnabled: true          # L1:抢救"写成纯文本的调用"
        promiseRescueEnabled: true   # L2:抢救"承诺了却没调用"
        retryEnabled: true           # L3:失败按类别重试
        authCode:         # 与 hiboard-push 保持一致

⚠️ 同名 insert 条目只认第一个。曾经 v1/v2 各写了一份, 后写的配置被静默忽略(排查了十几分钟)。升级时记得替换而不是追加。

完整步骤(含预检、排错、错误码)见 docs/DEPLOY.md。


提供的工具

工具作用
hiboard_push推送一张任务卡片:name(标题)、content(Markdown,≤5000 字符)、result(状态标签)、schedule_id(必传)、dry_run(只校验不发送)
hiboard_verify发送一张「连接测试」卡片,端到端验证授权码与网络
hiboard_push_selfcheck由 dsh-push-guard 提供:报告兜底插件是否生效、hiboard_push 是否已被接管重试、授权码是否配置、接口是否可达、最近一次推送状态

⚠️ 第四个坑(最坑的一个):推送会静默丢失

2026-09-27 实测事故:一晚连着 3 张卡片没到手机,而 dsh 界面没有任何报错。 完整复盘见 docs/INCIDENT-2026-09-27-silent-push-loss.md。

工具不会自己调用 —— 而 agent 可能"以为"自己调用了。已实测到三种形态,全部是静默失败:

形态长什么样为什么没报错
A. 把调用写成正文正文里出现 `
…` 这段 XML只是普通文本,turn/end 依然 completed
B. 承诺了却没调用正文写"我把结论推给你:",然后什么调用都没有同上,从会话记录看一切正常
C. 标签带额外属性`
`朴素正则匹配不到 content → 被当成"参数不全",安静放弃

第五个坑:卡片能到手机、却点不开

第五种失败更气人:推送成功(接口回 0000000000),手机上却只有一行标题、 点开没有任何内容 —— 因为漏传了 schedule_id(第 3 节的规则:scheduleTaskId 为空时平台不开放详情页,正文一律不渲染)。2026-09-27 真实翻车。 所以现在不靠"记得传",而是双保险:

层做法效果
调用契约schedule_id 声明为 required: true漏传被参数校验直接拒绝(missing required property "schedule_id")
宿主兜底push-guard 检测到漏传/空串/纯空格 → 自动补 dsh_worklog(明确传值不覆盖)到达 HIBoard 的每张卡片都带非空 scheduleTaskId

还有一个易被忽略的隐患:重试分类若把"2xx 但响应无 code 字段"当成失败, 会在卡片已经推成功的情况下再推一遍 → 重复卡片。 原则是:宁可少重试,也不要制造重复卡片。

修复:三级兜底插件 src/push-guard/

级别触发条件行为
L1 rescue正文出现字面量调用标记,且该轮从未真正调用 hiboard_push用同一解析器还原参数,按原样代为推送
L2 promise该轮未推送,但正文明确承诺了推送且正文够长用该轮正文补推,result 标注"模型未调用推送工具"
L3 retry工具真调用了但返回失败按错误类别重试:瞬时可重试,永久错误(如 0000900034)不重试

关键实现细节(都是踩出来的):

  • 判定"本轮推过没有"用轮次号而非消息序号,否则"上一轮推过"会吃掉本轮的兜底;
  • 解析 时正则必须允许额外属性([^>]*>),否则形态 C 会漏;
  • L2 的意图识别保守优先:要不要我把结果推给你?/我不会推送这张卡片。 都不触发;
  • 插件的任何异常都内部吞掉并记日志 —— 绝不允许兜底逻辑把宿主 dsh 拖崩。

可观测性("彻底修好"的关键)

/lan/push-guard/
├── startup.json      启动自检:authCode 是否配置、三级开关、pid
├── last-state.json   最近一次兜底推送(哪一轮、哪一级、成功与否)
└── audit.jsonl       流水:push-ok / push-failed / retry-ok / rescue-ok / no-push

装好之后,先让 agent 调一次 hiboard_push_selfcheck 确认三件事: 兜底插件生效了、hiboard_push 被接管了、授权码配对了。 在这之前,"保险丝到底有没有生效"只能靠猜 —— 本次事故里就真的猜错过一次。


网络契约

项值
端点POST https://hiboard-claw-drcn.ai.dbankcloud.cn/distribution/message/cloud/claw/msg/upload
鉴权请求体中的 authCode(个人凭据)
必填字段msgId scheduleTaskId scheduleTaskName summary result content source taskFinishTime
source固定 "OpenClaw"(平台按此值品牌化渲染,不要改)
内容上限5000 字符
成功码0000000000(也接受 "0")
授权码无效0000900034
方法限制仅 POST(GET 返回 405)

字段细节、卡片形态对照、全部错误码(含 0200100004 的 CP 子码含义)见 docs/CARD-CONTRACT.md。


让"每一轮回答都推送"对所有会话生效

工具不会自己调用。把规则写进 全局指令文件 ~/.dsh/AGENTS.md, dsh 会在每个会话里自动加载它。现成模板见 docs/AGENTS-rule.md。


目录结构

.
├── README.md
├── LICENSE                                    MIT(含上游 Entity-Him 版权声明)
├── NOTICE                                     来源与改动声明
├── package.json
├── docs/
│   ├── images/                                真机截图(卡片 / 历史 / 详情页)
│   ├── DEPLOY.md                              从零部署(含排错表)
│   ├── CARD-CONTRACT.md                       负载契约 / 卡片形态 / 错误码
│   ├── AGENTS-rule.md                         全局推送规则模板
│   ├── INCIDENT-2026-09-27-silent-push-loss.md  ★ 推送静默丢失复盘(三级兜底的由来)
│   ├── UPDATES-2026-09-27.md                   ★ 兜底插件 / 可观测性 / token 卡片修复
│   └── UPDATES-2026-09-23.md                  演进汇总(含 token 推送插件)
└── src/
    ├── lib/index.js                           插件源码(兼容性修复版)
    ├── lib/index.js.upstream-backup           上游原始版本,便于比对
    ├── package.json
    ├── cordis.patch.yml                       bundle 补丁(挂载插件用)
    ├── push-guard/                            ★ 推送兜底插件(L1/L2/L3 + 自检 + 审计)
    │   ├── lib/index.js
    │   └── package.json
    └── token-broadcast/                       ★ 启动即推送 token 的插件
        ├── lib/index.js
        └── package.json

进阶用法:dsh 每次启动自动推一张 token 卡片

src/token-broadcast/ 是一个独立插件,解决一个很实际的问题: dsh 的会话 token 只在启动时打印一次到 stdout、不落盘, 而手机上经常要用到它(例如给 HTTPS 网关授权会话),手工翻日志很麻烦。

它做三件事:

  1. 等 dsh 启动器把本次运行的 token 写进 /.dsh/lan/web-state.json
  2. 推一张 token 负一屏卡片(必须带非空 scheduleTaskId,否则不渲染正文), 卡片里的网关地址与局域网地址都写真实 IP(见下)
  3. 可选:把 token 用注册密钥上报给你自己的网关,让浏览器只输密码即可进入

三个 2026-09-27 修掉的实际问题

  1. 地址曾经是占位符:卡片正文里原本写的是 https://:18443/?token=… 和 http://:3081/?token=… —— 手机上根本没法用。现在:
    • 网关主机从 registerUrl 推导(也可用 gatewayHost 显式指定);
    • 局域网 IP 每次启动动态探测(os.networkInterfaces() 取第一个非内部 IPv4)。 实测:同一天内换网,局域网 IP 就从 192.168.0.x 段变成了 192.168.3.x 段 —— 写死必然过期。
  2. 会推送上次的失效 token:web-state.json 在 dsh 退出时不会被删除, 上一轮的 token 一直躺在里面。原实现一读到就推,可能把已失效的 token 推给用户 (比不推更糟:用户拿着旧 token 反复试)。现在会校验文件里的 pid / startedAt 是否属于本次运行,只有确认新鲜才推。
  3. 网关注册只试一次、失败静默放弃:网关把 token 存在进程内存里, 每次 dsh 重启都必须重新注册。dsh 刚重启时网关/隧道恰好在重连, 那唯一一次注册很容易撞在窗口期;一旦漏掉,远程端会一直提示 "网关尚未收到本次启动的 token",用户手工粘贴 token 也无效 (因为手机上那张卡片可能是上一轮的)。这是真实发生过的故障。 现在:失败自动重试 3 次(间隔 5s,可配 registerAttempts / registerRetryMs), 结果写入 /lan/token-broadcast/register-status.json, 最终失败时日志明确写出"远程端会显示网关尚未收到 token"并给出复查入口 —— 不再静默。

安装与配置:

dsh plugin --profile web add "file:E:/dsh-vendor/dsh-token-broadcast"

~/.dsh/profiles/web/cordis.patch.yml:

- insert:
    - id: token-broadcast
      name: dsh-token-broadcast
      config:
        enabled: true
        authCode: 
        scheduleId: dsh_token_notice
        # 可选:网关注册(见 dsh-aliyun-relay-access 项目)
        registerUrl: 'https://:18443/__gw_register'
        registerKeyPath: ''
        registerInsecure: true    # 网关用自签证书时;对负一屏仍保持严格校验
        # 可选:留空则自动探测/推导,一般不需要填
        # gatewayHost: ':18443'
        # lanIp: ''
        # lanPort: 3081

两个实测教训(写在了源码注释里)

  1. schemastery 的 .default() 不会随 {...Config} 展开生效 —— Config 是 schema 对象,展开只能拿到 schema 自身属性,拿不到默认值。 所以默认值写成独立常量 DEFAULTS 并在 apply() 里显式兜底,否则请求 URL 会是空。
  2. HIBoard 接口要求 x-trace-id 请求头,缺了返回 {"code":"0000500001","desc":"Parameter x-trace-id is empty"}。 格式与官方客户端一致:task-push-。

兼容性

组件版本
DeepSeek Harness (dsh)0.1.5(验证版本)
cordis4.x
Node.js18+
HarmonyOS7.0.0.107(实测;仅手机端)

平台侧:华为负一屏「智慧助手·今天」的 HIBoard 云侧接口。 网络契约与官方 today-task skill、以及独立 Rust 实现 lichtcui/hwpush 交叉核对一致。

卡片正文的 Markdown 渲染实测

详情页(效果图 ③)实际渲染范围:

Markdown 语法是否渲染说明
# ## 标题✅各级标题正常,长标题会自动换行
- 无序列表✅缩进与符号正常
1. 有序列表✅编号正常
**加粗**✅正常
> 引用块✅左侧竖线样式正常
表格 | a | b |✅ 支持见下方写法要求
`行内代码`未单独验证建议少量使用
代码块 ```未单独验证长代码块在窄屏可读性差,建议只放结论

表格写法要求(实测:格式正确即可正常渲染,效果图 ③ 中即有一张渲染正常的表格):

  1. 表格前必须留一个空行,否则可能被当作普通文本
  2. 必须有表头行 + 分隔行 |---|---|,两者都不能省
  3. 每行的竖线数量要一致(列数与表头匹配)
  4. 单元格内容不要跨行;内容过长会让列被挤窄,建议精简措辞

content 会被原样上传、平台按 Markdown 渲染,因此正文里的换行要写成 \n (本插件的 normalizeContent 也会把误转义的 \n 还原成真实换行)。


安全

  • authCode 是个人凭据:持有它的人可以往你负一屏推卡片。 不要提交到仓库、不要发群聊。泄露后去负一屏重新取码即可让旧码失效。
  • 本仓库所有示例与模板中的授权码一律为 `` 占位。

许可证与致谢

MIT。上游版权归 Entity-Him 所有,见 LICENSE 与 NOTICE。 感谢上游作者以及 lichtcui/hwpush 提供的协议参考。