按规则筛选生命周期事件并推送到多个通知渠道
29.7/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/9/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Archaofan/dsh-notify-relay该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · platform
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 40 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 7 处,展示前 7 条)
- 写入本地文件index.js:888
await mkdir(dshHomePath('storages', PLUGIN_ID), { recursive: true }) - 写入本地文件index.js:890
await writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, 'utf8') - 写入本地文件index.js:891
await rename(temp, path) - 读取本地文件index.js:896
const parsed = JSON.parse(await readFile(configFile(), 'utf8')) - 读取本地文件index.js:906
const parsed = JSON.parse(await readFile(logFile(), 'utf8')) - 读取本地文件index.js:915
const parsed = JSON.parse(await readFile(outboxFile(), 'utf8')) - 发起网络请求index.js:1115
const response = await fetch(built.url, { ...built.init, signal: AbortSignal.timeout(DELIVERY_TIMEOUT_MS) })
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-home-paths@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 生命周期事件筛选
- 多渠道通知路由
- 去重免打扰摘要合批
DSH 外联通知中枢:生命周期事件经去重/免打扰/摘要合批后路由到 Bark/Server酱/Telegram/企业微信/飞书/ntfy/webhook。零运行时依赖。
README
由 DeepSeek 最新模型翻译生成dsh-notify-relay(通知中继)
DSH 的外联规则中枢:任务完成、任务失败、任务中止、任务受阻、请求失败、待审批、审批已决、工具失败这几类生命周期事件,先过去重、免打扰、摘要合批三道关,再发到 Bark、Server酱、Telegram、企业微信、飞书、钉钉、ntfy 或任意 webhook。投递失败自动重试,重启不丢。
零运行时依赖、无构建步骤,两个源文件(host 面 + 浏览器面)。中英双语,界面跟随 DSH 自己的语言设置;推送到你手机上的文案语言单独可设。
为什么还要有一个通知插件
市面上已经有不少"把消息转发到某处"的 DSH 插件。缺的是这条消息该不该发的判断层——而这恰恰是唯一有价值的部分:
| 规则 | 它拦住了什么 |
| 去重 | 同一个错误每秒重报一次。按 (类型, 会话, 标题, 正文) 取指纹,在 1–1440 分钟窗口内抑制重复 |
| 免打扰 | 凌晨两点半构建挂了。支持跨零点(22:00 → 08:00);窗口内的重复要么丢弃,要么留给下次摘要 |
| 摘要合批 | 二十个失败推二十条。被挂起的通知攒成一条,每 N 分钟发一次,也可手动立即发送 |
| 静音 | 手头正忙时 /notify mute 60。压掉一切,连新指纹也压 |
| 待审批穿透 | 免打扰和摘要不拦待审批。
展开完整文档(README 剩余 1 节)
dsh-notify-relay(通知中继)
DSH 的外联规则中枢:任务完成、任务失败、任务中止、任务受阻、请求失败、待审批、审批已决、工具失败这几类生命周期事件,先过去重、免打扰、摘要合批三道关,再发到 Bark、Server酱、Telegram、企业微信、飞书、钉钉、ntfy 或任意 webhook。投递失败自动重试,重启不丢。
零运行时依赖、无构建步骤,两个源文件(host 面 + 浏览器面)。中英双语,界面跟随 DSH 自己的语言设置;推送到你手机上的文案语言单独可设。
为什么还要有一个通知插件
市面上已经有不少"把消息转发到某处"的 DSH 插件。缺的是这条消息该不该发的判断层——而这恰恰是唯一有价值的部分:
| 规则 | 它拦住了什么 |
| 去重 | 同一个错误每秒重报一次。按 (类型, 会话, 标题, 正文) 取指纹,在 1–1440 分钟窗口内抑制重复 |
| 免打扰 | 凌晨两点半构建挂了。支持跨零点(22:00 → 08:00);窗口内的重复要么丢弃,要么留给下次摘要 |
| 摘要合批 | 二十个失败推二十条。被挂起的通知攒成一条,每 N 分钟发一次,也可手动立即发送 |
| 静音 | 手头正忙时 /notify mute 60。压掉一切,连新指纹也压 |
| 待审批穿透 | 免打扰和摘要不拦待审批。压住它到早上八点,等于让任务停摆到早上八点,而用户只会怪插件 |
| 失败重试 | 通道临时挂了。失败的投递进持久化 outbox,按 30s→1m→2m→…→30min 指数退避重试,重启后接着试 |
| 分流 | 每个通道独立的事件过滤:失败才发 Telegram,全部发 Bark |
| 脱敏 | token 落在日志文件里。密钥在所有 API 响应里都以 •••••••• 占位,投递日志中绝不出现 |
通道适配器刻意做得很薄——七个,每个都是一个纯函数,负责拼一条 payload。价值在它们上面那一层。
你会看到什么
| 位置 | 内容 |
| 官方设置页 | 设置导航 → 外联中枢:总开关、八个事件开关、去重窗口、免打扰(起 / 止 / 模式)、摘要间隔、外发语言、待重试队列、通道编辑器 |
| 通道编辑器 | 增删改通道;每个通道可设名称、类型、地址、密钥、事件过滤,并有测试按钮,立刻真实投递一次 |
| 侧边栏底部 | 状态胶囊——开关态、最近一次投递结果,侧边栏收起时自动切紧凑形态;点开是最近的投递记录 |
| 斜杠命令 | /notify status、/notify test [通道]、/notify mute 、/notify unmute、/notify flush、/notify retry,不经过模型 |
投递日志里未投递的行也会出现,并写明原因(去重 / 静音 / 免打扰挂起 / 摘要挂起 / 无可用通道)。"到底发没发"是这个生态里被抱怨得最多的问题,而一份只记录成功发送的日志恰恰答不了它——因为有意思的那一行正是缺失的那一行。
通道编辑器里的密钥显示为掩码,只有你真的改动了它才会回传给 host——所以打开编辑器直接按保存,不会把没动过的 token 清空。
通道
| 类型 | 地址 | 说明 |
| bark | 你的 Bark 服务地址 | iOS 推送,group = dsh-notify-relay |
| serverchan | 你的 SendKey | Server酱 |
| telegram | bot token + chat id | sendMessage,任务完成时静音 |
| wecom | webhook key | 企业微信机器人 markdown |
| feishu | webhook token | 飞书交互卡片 |
| dingtalk | access token + 加签密钥(可空) | 钉钉自定义机器人 markdown,critical 时 @所有人 |
| ntfy | topic(可选服务地址) | ntfy JSON 发布 |
| webhook | 任意 URL | 通用 JSON POST;可配一个自定义请求头携带密钥 |
每个通道有独立的事件过滤( 表示全部)。未知类型在校验阶段直接丢弃而不是让启动失败——新版写进来的配置不会把旧版插件拖垮。而已知类型下未知的密钥字段会保留——丢弃它们等于静默毁掉凭证。
安装
已在 DSH 0.1.6-alpha.2 与 0.1.7-rc.2 两条线上实测通过。两条线上的插件代码完全相同:运行时不做任何版本门禁,0.1.7 唯一发现的差异是首次运行引导(一个会拦掉所有点击的整页遮罩),那影响的是测试脚本而不是插件本身。
生产 profile
dsh plugin --profile web add github:Archaofan/dsh-notify-relay --ignore-scripts
或本地目录 / tarball
dsh plugin --profile web add file:./dsh-notify-relay-0.3.3.tgz --ignore-scripts
--ignore-scripts 是必要的:本插件没有安装脚本,也没有运行时依赖,拒绝执行它们既更快也更安全。
装完重启 DSH(或重新加载 web 应用),进入 设置 → 外联中枢。
如果第二次安装报 ERR_PNPM_MISSING_TARBALL_INTEGRITY
这是 pnpm 的 bug,不是插件的问题,在完全不涉及 DSH 的纯 pnpm 里就能复现:
$ pnpm add https://github.com/Archaofan/dsh-notify-relay/releases/download/v0.3.3/dsh-notify-relay-0.3.3.tgz
成功,但 lockfile 里写的是 resolution: {tarball: ...},没有 integrity 字段
$ pnpm add
ERR_PNPM_MISSING_TARBALL_INTEGRITY Cannot install package "dsh-notify-relay@...":
its lockfile entry has no "integrity" field, so pnpm cannot verify the tarball.
pnpm 从内容寻址的 store 里直接取 tarball(而不是重新下载)时,写进 lockfile 的
条目会缺 integrity 字段。触发它的那次安装是成功的;同一 profile 里的下一次安装才会被拒绝,因为 pnpm 不肯安装一个无法校验的 tarball。
第一次安装总是成功的。 所以这只在"往已经装了一个插件的 profile 里再装一个" 时才咬人。
清掉它要同时删两个文件——只删 lockfile 没用,pnpm 会从 package.json 重新生成,而 store 还是热的:
/profiles//
rm -rf node_modules pnpm-lock.yaml
dsh plugin --profile web add file:./dsh-notify-relay-0.3.3.tgz --ignore-scripts
pnpm store prune、pnpm add --force、以及换一个冷的 --store-dir 都测过,
只要 node_modules 里还留着那个包,没有一个能清掉它。
配置
最快的路径是设置页。想直接写文件的话,配置在 /storages/notify-relay/config.json,每次加载都会校验——非法值回落到默认值而不是让插件崩掉:
{
"enabled": true,
"language": "zh",
"events": {
"task.done": false,
"task.failed": true,
"task.aborted": true,
"task.blocked": true,
"request.failed": true,
"approval.asked": true,
"approval.decided": false,
"tool.failed": true
},
"dedup": { "windowMinutes": 10 },
"quiet": { "enabled": false, "start": "22:00", "end": "08:00", "mode": "digest" },
"digest": { "enabled": false, "intervalMinutes": 30 },
"deepLink": "",
"channels": [
{
"id": "phone",
"name": "我的手机", "kind": "bark", "url": "https://api.day.app/YOUR_KEY",
"secrets": {},
"events": [""]
}
]
}
mode 为 digest(挂起重复,合成一条发送)或 drop(直接丢弃)。
language 是外发语言(zh / en),决定推送到你手机上的文案、/notify 的回复和摘要标题。界面语言仍跟随 DSH 自己的设置——两者是独立的,因为读界面的你和在凌晨两点读到推送的可能不是同一个人。取值不合法时回落到 zh。
deepLink 是可选项。只有 Bark(url)和 ntfy(Click)有点击跳转字段,其他通道没有对应能力,拿到的就是空——而不是把一串不可点的 URL 塞进正文。它必须是含 {session} 的 http(s) 地址,{session} 按通知逐条替换;没有会话可替换的链接等于说谎,所以不会发送:
"deepLink": "https://dsh.example.com/session/{session}"
严重度
一句"发生了"远远不够。待审批和任务完成都是事件,但前者漏掉要花钱,后者只是噪音。成熟的通知系统都把严重度和内容分开处理;做不到这一点的通知器,逼你在"漏掉重要的"和"被不重要的刷屏"之间二选一。
| 事件 | 严重度 |
| approval.asked | 紧急(critical) |
| task.failed、task.aborted、task.blocked、request.failed、tool.failed、relay.degraded | 重要(high) |
| task.done、approval.decided、测试 | 普通(normal) |
| (没有事件映射到 low;它存在是为了让通道能降级) | 低(low) |
摘要不在这张表里,因为它自己没有严重度。 它继承它所含事件里最高的那一级:五个 task.failed 攒成的一条摘要,就是五个 high,也按 high 发出去。开摘要是为了少收几条消息,不是为了少几分紧迫——而在 0.4.1 之前,摘要的严重度被硬编码成 normal,于是一条失败摘要到 Bark 是 active 而不是 timeSensitive,到 ntfy 是 Priority 3 而不是 4,日志里一个字都不提。
上限是 high,永远不是 critical:approval.asked 是唯一的 critical,而它穿透摘要,从来不会被挂起。所以一条失败摘要该穿透静音,但不该 @ 整个钉钉群。
严重度不是装饰,它落在各通道真实的 API 字段上,且已对着官方文档逐一核对:
| 通道 | 字段 | 紧急 | 普通 |
| Bark | level | critical + call: '1' + volume: '10' | active |
| Bark | — | low → passive,high → timeSensitive | |
| ntfy | Priority | 5 | 3(low → 2,high → 4) |
| Telegram | disable_notification | false | 仅 low 为 true |
| webhook | JSON severity | "critical" | "normal" |
Bark 的 critical + call 是这套通道里唯一能穿透 iOS 静音和勿扰的原语,因此只留给待审批:拿它发"任务完成",是这个插件被卸载的最快路径。Server酱、企业微信、飞书根本没有严重度字段,就保持原样——编一个 API 不认识的字段,比不给字段更糟。
单通道熔断
没有熔断,一个彻底挂掉的通道(token 被吊销、主机下线、DNS 不再解析)会在每一条通知上被重试六次,永远如此。每次重试都要等满一个超时,于是发件箱膨胀、退避上限被拉到半小时,而你好的通道被这个死通道堵在后面。
同一通道连续失败三次即打开熔断,之后完全不发请求,直到冷却结束(1 分 → 5 分 → 15 分 → 30 分,从 1 分钟起翻倍),届时只放一个探测请求出去。成功则全部重置,冷却阶梯也一起归零,所以恢复的通道不会背上比它应得更长的惩罚。
两个刻意的选择:
- 熔断中的通道不进重试队列。 逐条重试它正是熔断要阻止的行为。跳过仍然会写进日志, error: "circuit open"——静默跳过正是这个插件要消灭的失败模式。
- 熔断状态不落盘。 熔断描述的是"网络现在坏了",而重启恰恰是最可能已经修好的时刻。
重启后重新触发只需三次尝试,很便宜;反过来,跨重启信任一个过期的"不健康"标记,可能让一个正常通道被无故静音半小时。
设置页的通道卡片会显示熔断状态和距下次探测的时长,让被熔断的通道自己解释,而不是看起来像一个配置错误。
中继会报告自己的状态
这是一个通知器能做的最有价值的事,也是这个插件此前唯一缺失的能力。悄悄不再通知的通知器,比没有通知器更糟,因为你以为自己被覆盖着。Healthchecks.io 就是建立在这个想法上——一个周期加一个宽限期,让"沉默"本身成为告警。
心跳计时器(和发件箱排空共用同一个)每次跳动都会检查三件事,并且最多每小时一次,只通过健康的通道告警:
- 有通道熔断中,附上它的 id
- 有通知在重试队列里卡了超过 30 分钟,附上等了多久
- 自启动以来什么都没投递出去,而明明有启用的通道
它的保守是刻意的:每小时一次,因为每次跳动都告警只会训练你忽略它;只走健康通道,因为通过死通道告警等于给被报告的对象再添一次失败,甚至可能递归;绝不入重试队列,因为一条失败的降级告警不该加入它正在描述的发件箱;也绝不经过 classify(),因为 relay.degraded 不是用户可配置的事件,事件开关不该能把它静音。
如果所有通道都熔断了,那就没人可告诉。此时降级告警会写成一条日志——这至少是诚实的:打开面板的那个人还能看见它。
/notify status 也随时报告同样的状态:熔断的通道 id、最早就绪项的等待时长,以及距上次成功投递多久。
事件从哪来
八类事件,两种到达方式,区别不是装饰:
| 事件 | 来源 | 默认 |
| task.done | turn/end,reason.kind = completed | 关 |
| task.failed | agent/error | 开 |
| task.aborted | turn/end,reason.kind = aborted | 开 |
| task.blocked | turn/end,reason.kind = blocked | 开 |
| request.failed | agent/request-error | 开 |
| approval.asked | approval/asked | 开,穿透免打扰与摘要 |
| approval.decided | approval/decided | 关 |
| tool.failed | tool/result,且 data.error 是对象 | 开 |
turn/end 会带一个 reason(completed / aborted / blocked / error / max-tokens / interrupted),所以"任务完成"和"任务被中止"是两条不同的通知,而不是把所有结束都报成完成。
会话类事件(turn/、approval/、tool/result)在整个 DSH 里只以 session/event
一个名字分发一次,载荷是 (session, event),真实名字在 event.type 里。监听
ctx.on('turn/end') 等于监听一个 DSH 从不分发的事件——这正是第一版静默死掉的半边事件
覆盖,也是 harness 里那个"监听器注册在会话子事件名上"坏变体的由来。
规则引擎怎么判
每个事件都过一遍 classify(),只会得到其中一个结论:
off 总开关关着,什么都不做 muted 在静音窗口内,什么都不做 dedup 同一指纹在窗口内已经投过 quiet-drop 在免打扰时段内,且模式为 drop quiet-hold 在免打扰时段内,且模式为 digest;挂起等下次摘要 digest 摘要合批开着;挂起等下次摘要 send 立即投递
投递本身不抛异常、也不阻塞事件:某个通道超时(10 秒)或连不上,只在日志里记一条 failed,其他通道照常收到。并发上限 3,一个配错的地址不会把连接池打满。
改摘要间隔,当场生效
intervalMinutes 是在定时器上弦时读的,而改它那次保存会重新上弦——所以把 30 改成 5,设置页说生效就生效。0.4.3 之前不是这样:armDigest 在定时器已经跑着的时候直接返回,所以对一个已经挂着事件的 relay,那次保存是空转的,待发的摘要仍按第一条事件到达时的间隔发。值其实存对了,这才最难发现。
共用同一个 tick 的 outbox 心跳也一样,而且在那边更糟:它用首次上弦时捕获的那个 delay 重新上弦,于是心跳永远停在旧间隔上,每次刷新都是。
如果要改这块,有一个坑值得知道:disarmDigest() 会把挂起队列一起清空。合批被关掉时
这是对的,仅仅改间隔时则是灾难——每一条攒着的通知都会在"本该重新排定它们投递时间"的那次保存上被丢掉。改期走的是 rearmDigest(),只丢定时器不动队列;另有一个坏构建变体把 naive 写法换回来,证明门禁还抓得住。
退避是带有界抖动的指数退避:30 秒、1 分、2 分、4 分……上限 30 分钟,再乘以 [0.75, 1.25] 区间内的一个均匀抽样。没有抖动的话,所有待重试项会算出同一个下次尝试时刻——于是共享通道一恢复,或者进程重启、整个发件箱一次性重载的那一刻,全部排队的通知会同时砸过去。一个自身的失败模式是"很多东西同时失败"的通知器,正是产生惊群的那个场景。取 ±25% 而不是完整的 [0, cap] 抖动,是因为完整抖动可能把重试排到几乎立刻执行,对一个 10 秒超时的通道那就是热循环。
摘要继承它所含事件种类的受众。这个区别在 0.2.0 里是个 bug:摘要是以 kind: 'digest' 投递的,于是 channelsFor 拿它去匹配每个通道的 events 列表,把它过滤掉了——一个配了
events: ['task.failed'] 的通道(也就是本 README 自己的示例)根本收不到任何摘要。
验证
这个插件不是靠"看起来对"就发布的。四道门禁,全部必须通过,其中三道是专门设计成会失败的:
| 门禁 | 证明了什么 |
| .sandbox/host-harness.cjs | fail-loud 的 inject 契约、规则引擎(去重 / 免打扰 / 严重度映射 / payload 构造 / 脱敏)、一次打到回环服务器的真实 HTTP 投递、深链在真实请求里落地、熔断状态机、持久化 outbox 的入队 / 退避 / 重试 / 放弃 / 重启恢复、摘要间隔改期,18 个坏构建变体 |
| .sandbox/client-harness.cjs | 在严格假 ctx 下物化、inject 契约、设置页导航标签随语言切换、字典键对齐、编辑器完整往返、事件词表与 host 逐项对齐、通道词表与 host 逐项对齐,5 个坏变体 |
| .sandbox/live-check.cjs | 插件在真实 DSH 里启动:写配置 → 读回 → 真实 socket 投递 → 日志 → 复位 → /retry → 外发语言 |
| .sandbox/e2e-notify.mjs | 浏览器面在真实 GUI 里:状态胶囊、投递面板、官方设置分区、以及一次从界面穿透到 host 的修改 |
node .sandbox/gate.cjs # 两个 harness,两种语言,外加全部变体
两个 harness 都按语言各跑一遍——只在中文下能物化的构建,证明不了英文界面任何事。
发布版也真跑了 e2e,不是推断出来的
v0.3.3 与 v0.2.5 的 index.js / client.js 和上一版逐字节相同,所以"再跑一遍浏览器测的也是同样的字节"听起来是个正当理由。但那是推断,而门禁存在的意义正是把推断换掉。.sandbox/verify-e2e-releases.cjs 把两个插件的浏览器面都打到真实 GUI 上,覆盖两条运行时:
| 运行时 | notify-relay v0.3.3 | session-suspend v0.2.5 |
| DSH 0.1.6-alpha.2 | 28 项检查,0 失败 | 19 项检查,0 失败 |
| DSH 0.1.7-rc.2 | 28 项检查,0 失败 | 7 项检查,0 失败 |
两个插件都激活成功(没有 "did not activate")、不抛 page error、不抛 console error、状态胶囊 / 侧边栏行正常渲染、官方设置分区正常渲染,且界面上一次修改能穿透回 host。session-suspend 在 0.1.7 上检查项更少,是因为那个 profile 是新建的、只有一个空草稿——hover 链路需要一个已停放的会话来比对,它选择跳过而不是空跑通过。
只有"会失败的门禁"才抓到的 bug
一个从未执行过的 POST 路由。 第一版把 GET /config 和 POST /config 写成两条
路由。webServer.match() 按路径查表、完全忽略 method,于是第二次注册抛错,重复
注册被当成双重挂载吞掉,每次保存都落在读处理器上——返回 200,什么都没存。单元测不到,因为 harness 里的假 webServer 是像真路由器那样按 method 匹配的。是靠一次真实启动、读回一个没变的配置才发现的。修法是每路径一条路由、method 在 handler 内部分发; harness 的假 webServer 现在与真实实现逐字一致(按路径建 Map、重复即抛错),并加了一个故意重复注册路径的坏变体。
被当成投递日志传进去的 logger。 registerRoutes(server, log) 的第二个参数名叫 log,传进去的却是 ctx.logger(PLUGIN_ID),把数组遮蔽掉了,于是 /log 每次都抛异常、返回 500。直到 live-check.cjs 真的调了这个路由才发现。参数已经去掉,harness 现在也会真的调用路由,而不只是断言它存在。
半个事件覆盖是死的。 第一版同时注册了 ctx.on('turn/end') 和
ctx.on('approval/asked')。这两个事件从不以这些名字分发——会话事件只在
session/event 下出现一次——所以两个监听器从未执行,"任务完成"和"待审批"两类通知一条都没发过。改成 session/event + 内层 event.type 分支。harness 现在维护一份 DISPATCHED_EVENT_NAMES 注册表,断言每个监听器名字都在表内、HOST_EVENT_NAMES 与真实注册一致、没有监听器落在会话子事件名上;两个坏变体分别复现"死监听器"和"包装器吞掉第二个参数"两种形态。
mixiin 服务的方法在 ctx 上,不在服务上。 @cordisjs/plugin-timer 用
ctx.mixin('timer', [...]) 把方法直接挂到 context 上,所以 hostCtx.timeout(...) 是
对的。第一版 harness 只提供了 ctx.timer.timeout,fail-loud 代理理所当然地拒绝了 ctx.timeout——但 digest 路径从未带着排队项被走到过,于是这道门在一片从未执行的代码上亮着绿灯。现在假 ctx 同时提供两个面,并且在 mixin 服务本身被声明时允许它的方法名。
事件词表两边各一份,然后漂移了。 浏览器面拿不到 host 的 EVENT_KINDS,只能自己维护一份。turn-end 的 reason 被拆开之后 host 那边从四类长到八类,浏览器这份没动——设置页出现四个开关管八个事件,四类事件静默不可配置,而两边各自都是自洽的,所以两道门全是绿的。
只有真实浏览器里数了一遍开关才发现。现在 client harness 直接 import host 的 EVENT_KINDS,逐项、按顺序比对两份列表,并切换语言两个方向确认每类事件在两个字典里都有标签。
outbox 探针读错了 home。 变体运行器在自己的 finally 里恢复 DSH_HOME,而行为探针跑在它之后——被改坏的插件每一次写入都落在真实用户目录,探针读的却是一个空的临时目录,于是对一个 outbox 完全正常的构建报告"没有 outbox",给一个从未被真正观察到的回归放了绿灯。现在探针自己把 DSH_HOME 指回去,并且在得出结论前先断言配置写入和 outbox 文件都确实落地。
全部十处都记在 DEV-NOTES.md 里,附确切症状,下一个人不必重新踩。
卸载
dsh plugin --profile web remove dsh-notify-relay
许可
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Archaofan/dsh-notify-relay
按规则筛选生命周期事件并推送到多个通知渠道
相关插件 · 平台集成
智能体工程课walkinglabs/learn-harness-engineering
一门项目制课程,教你搭建环境、管理状态、做验证、加控制机制,让 AI 编程智能体稳定干活。
★ 21,155已汉化DSH
71.7
视频翻译配音liuzhao1225/YouDub-webui
开源视频本地化工作台:导入视频,识别、翻译并逐句克隆原声,生成字幕和配音成片。
★ 5,792已汉化Cordis
71.4
失败经验库Ikalus1988/MisakaNet
📚 零依赖、由 git 支撑的微课程库,让 AI 智能体异步分享并检索经过验证的调试经验。| https://misakanet.org
★ 550已汉化DSH
71.2
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,170已汉化DeepSeek 客户端
71
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 2,041已汉化DSH
70.7
这是你的仓库?认领条目