中文EN

dsh-plugin-heartbeat

workflow已测通过npmMIT

给每个根 agent 装一套自主神经心跳:静默拍会 diff 会话的待办 / 团队任务板 / 目标,只有出现信号才开口(L1 汇报 / L2 提问 / L4 升级),只问不做。每条消息带 why-now 行、可选的一行生理签名(距你上次说话多久、今天第几次找你)与紧凑任务快照(800 字符上限);去重窗口、静默时段、每日预算、不应期 / 重启冷却与持久化生命体征共同限流,忙碌时 followup 注入排队不打断。它与 dsh-plugin-memory 共享一个建议性发言权(~/.dsh/proactive-cooldown.json,默认 180 秒)互不抢话,子 agent / teamm

下载量 / Downloads
2,957
依赖体积 / node_modules
0.69 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

安装

dsh plugin --profile web add dsh-plugin-heartbeat

沙箱测试结果

阶段结果
1 安装✓
2 列装✓
3 挂载✓
4 启动—

四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。

README

dsh-plugin-heartbeat

中文 | [English](README.md)

<p align="center">

<a href="https://github.com/LittleBlackTong/dsh-plugin-heartbeat"><img alt="GitHub" src="https://img.shields.io/badge/GitHub-LittleBlackTong%2Fdsh--plugin--heartbeat-blue?logo=github"></a>

<a href="https://github.com/LittleBlackTong/dsh-plugin-heartbeat/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/LittleBlackTong/dsh-plugin-heartbeat/actions/workflows/ci.yml/badge.svg"></a>

<a href="https://www.npmjs.com/package/dsh-plugin-heartbeat"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plugin-heartbeat?logo=npm"></a>

<img alt="license" src="https://img.shields.io/badge/license-MIT-blue">

<img alt="node" src="https://img.shields.io/badge/node-%3E%3D18-green">

</p>

DeepSeek Harness 的「自主神经」心跳:平时静默拍脉搏、读会话投影,只在真有变化时开口——L1 汇报、L2 问你要不要做、L4 升级提醒。没信号就不发消息。

English TL;DR — An autonomic heartbeat for DeepSeek Harness: it pulses silently, diffs the session's projections, and only opens a turn on a real change (L1 report / L2 ask / L4 escalate); no signal, no message.

v0.5 把心跳变成「懂任务」;v0.6.0 把它从节拍器升级成自主神经:每拍静默读一次会话、算出 arousal(唤醒度),有理由才开口;没东西可说时只记一条 vitals 脉搏、不注入任何消息。

注意:v0.6.0 默认行为变了

  • 0.5.x:每个 intervalSeconds 都注入一条消息,不管有没有事(任务快照被反复复述)。
  • 0.6.0:默认是静默拍——没信号就不开轮、不注入。只有检测到「值得说」的变化时才按 L1/L2/L4 开口(见下面的等级表)。
  • 配置文件升级为 v2:intervalSeconds → baseIntervalSeconds,另加节律 / 静默时段 / 去重 / 预算 / L3 / 工作区字段。旧的 v1 heartbeat.json 在加载时自动迁移,不需要你动手。
  • 运行态单独存 <dshHome>/heartbeat-vitals.json(与配置分离,手改配置不会污染运行态)。它跨重启保留;重启后的 restartCooldownSeconds(默认 5 分钟)内即使有信号也保持安静。
  • 设置侧栏的 label 跟随 App 语言——中文显示「心跳」、英文显示 Heartbeat,描边爱心图标不变。

怎么工作

Host 平面的 Cordis 插件,给每个 root agent(子 agent 跳过)挂一个 runtime —— 包括插件热挂载时已经存在的会话。

每一拍都走同样四步,插件内部不调用任何 LLM 做判断:

sensors(传感器)→ rhythm(节律)→ gate(门控)→ action(动作)
  • 传感器(lib/sensors.js):同步读会话的公开投影 —— todos、agentTeam、goal、userQuestions,外加 contextPressure —— 并与上一拍看到的做 diff。只有变化才会变成信号,每个信号带权重,权重加总成 arousal(0–8)。每拍重复同一份待办清单,正是 v0.6 要消除的噪音。
  • 节律(lib/rhythm.js):下一拍间隔 = baseInterval × (1 + missed) × mood(arousal),钳制在 30 秒 – 24 小时;arousal 越高间隔越短(arousal 4 → 间隔缩到三分之一)。静默时段直接不排拍,投递后有 refractory 静默窗,另加一个确定性的小 jitter,避免多个会话在同一刻一起跳。
  • 门控(lib/gate.js):判定 L0(静默)/ L1(汇报)/ L2(询问)/ L4(升级),套用去重窗口与每日预算;你刚说过话、上下文压力过高、处于静默时段、被打盹/暂停时,都拒绝开口。
  • 动作(lib/runtime.js):L1/L2/L4 时先按需压缩长历史,再注入一条消息,内含 why-now(为什么现在叫你)、任务快照(taskNudge 开着时)与该等级的指令。L0 拍不注入任何东西,只在 vitals 历史里记一行。

投递机制(沿用 v0.5):

  • agent 空闲 → 立刻开新一轮;
  • agent 忙 → 消息停在 next-turn inbox,当前轮结束立刻被取走 —— 最多一条待处理,原地替换(inbox.replace),绝不堆积;
  • 连续没人回 → 达到 pauseAfterMissed(默认 3)后硬停;只有你的真实消息能复活它并重置相位;
  • 可选的滑动每小时上限(maxBeatsPerHour)防「断续回复不断重置退避」的极端情况。

事件驱动(v0.6 新增):宿主提供 sessionProjections.onChanged 时,todos / agentTeam / goal / userQuestions 的变化会给会话打标记,2 秒防抖后额外跑一拍 —— 仍要过全部门控(去重、预算、静默时段)。没有该服务就退化为纯定时器模式。

自主等级(L0–L4)

等级 · 行为 · v0.6 默认

L0 · 静默拍:读、diff、记录 —— 不注入 · ✅ 所有无信号 tick 的默认

L1 · 开一轮,只汇报进展 / 风险 / 卡点 · ✅ 仅有进展类信号时

L2 · 开一轮并提问(ask_user_question):要不要现在做 / 怎么选 · ✅ 出现需要决策的信号时

L3 · 在已授权类别内自行推进,不必先问 · ⛔ 默认关闭(l3Allow: []),只做指令级授权

L4 · 与 L2 相同但要升级措辞:同一决策信号已经没人回应(missed ≥ 2)—— 只升级措辞,不加频 · ✅

需要决策(因而走 L2/L4)的信号:团队任务变可开始、团队任务变被阻塞、目标进入 blocked / paused、有待回答的用户提问。只是进展类的信号(团队任务完成、待办变化)走 L1。

一段话讲清 L3

L3 是一份按类别授权的清单 —— read(检索 / 读文件)、test(跑测试 / lint / 构建)、notes(写工作区文档 / 笔记)、continue(续写已约定草稿)。默认为空,所以 v0.6 默认行为就是「只问不做」。授权后,L2/L4 消息会多一行,说明这些类别内可以自行推进,其余一切仍然先问。L3 不是沙箱:发布、部署、删除、rm、git push、任何外部写永远不进白名单,不可逆操作仍由宿主 approval 拦截。授权只改指令,不改宿主的安全检查。

信号、去重与预算

信号 · 来源(投影 diff) · 去重键 · 权重

团队任务变可开始 · agentTeam · team:<id>:ready · +5

团队任务变被阻塞 · agentTeam · team:<id>:blocked · +4

目标受阻 / 暂停 · goal · goal:<id>:<phase> · +4

待回答的用户提问 · userQuestions · question:<callId> · +3

待办发生变化(有未完成项) · todos · todos:<signature> · +2

团队任务完成 · agentTeam · —(只汇报,不去重) · +1

arousal = clamp(Σ 权重, 0, 8);arousal 越高,下一拍间隔越短。

开口前会被这些条件抑制(都在 lib/gate.js):

  • 你刚说过话(2 分钟内)—— 让位给人;
  • 其他主动插件持有发言权 —— 对方 claim 后的 proactiveCooldownSeconds(默认 180 秒)内,本拍记 L0 · proactive-cooldown 让位;「立即一拍」是例外;
  • 子 agent / teammate 还在跑 —— root 只是在等委派出去的工作,本拍记 L0 · child-active、不注入、不压缩(v0.6.1;设 quietWhileChildrenActive: false 可恢复旧行为);
  • 上下文压力 > 0.9 —— 接近上下文上限时不额外开轮;
  • 静默时段(默认 23:00–08:00,可配置,null 关闭);
  • 禁用 / 打盹 / 暂停,或 mode: "silent";
  • 去重:同一信号键在 dedupeWindowMinutes(默认 90)内已经问过 → 丢弃;
  • 每日预算:每个自然日最多 maxBeatsPerDay(默认 12,0 = 不限)条主动消息,进程内跨会话共享计数。

唯一例外是面板上的 立即一拍(Beat now):手动拍跳过「礼貌闸门」(静默时段、预算、去重、你刚说过话、子会话正在跑),但仍会做等级判定,也仍要求 agent 真有可用的驱动面。

任务巡检(消息里的快照)

v0.5 起每拍可以携带一段任务快照;传感器复用同一批投影,所以「消息里说的」和「信号判的」不会互相矛盾。

投影 · 读到的内容 · 进快照的规则

todos · 会话待办清单(content / status) · 只留 in_progress / pending;completed 丢弃

agentTeam · Agent Team 团队任务板(subject / status / blockedBy) · 只留未完成条目,按「可开始 → 进行中 → 被阻塞」排序,阻塞会标明依赖

goal · 持久目标(objective / phase) · 只留 active / paused / blocked 三种相位

读之前要知道的两件事:

  • agentTeam 投影只有启用 Agent Team 可选包时才注册;未启用时读不到 → 自动降级,不报错、快照里也没有团队任务段。
  • todos 投影每个 turn 开始会重置:心跳看到的是「最近一个 turn 内的待办」,不是会话级长期 TODO。

健壮性:

  • 宿主没注册投影服务 / 会话未绑定 / 某个投影读取抛错 → 该信号或该快照降级;任何读取失败都不会让一拍丢失;
  • 任务块有 800 字符硬上限;taskNudgeMaxItems 再限每类条目数(默认 5,1–20);
  • 插件对投影只读不写,不改你的待办、任务板或目标;
  • 自定义 prompt 不含 {{tasks}} 时,任务块自动追加。

生命体征与手动控制

设置 → Heartbeat 面板展示心跳自己的生命体征,数据来自 GET /api/heartbeat/status(每 10 秒刷新):

  • 状态:运行中 / 打盹中 / 已暂停(含原因);
  • 下次检查:按当前节律算出的 ETA;
  • 今日预算:已投递条数 / maxBeatsPerDay;
  • 最近脉冲:最近 5 行(时间 + 等级 L0–L4 + 原因)——「它刚才为什么没说话」就查这里。

按钮(POST /api/heartbeat/control):

操作 · 效果

立即一拍(Beat now) · 立刻强制一拍(手动:跳过礼貌闸门,仍判等级)

打盹 30 分钟(Snooze 30m) · 30 分钟内不拍(API 的 minutes 可配,1–1440)

暂停(Pause)

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:12,最后更新 2026-10-30

链接

GitHub · Homepage · npm

同类插件