🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

心跳唤醒 LittleBlackTong/dsh-plugin-heartbeat

DeepSeek 客户端v0.8.0
LittleBlackTong4 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

DeepSeek Harness 的「自主神经」心跳:平时静默拍脉搏、读会话投影,只在真有变化时开口——L1…

37.6/100
综合分
3
GitHub 星标
692
npm 周下载
—/5
用户评分

数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/LittleBlackTong/dsh-plugin-heartbeat.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
生态插件
可安装,未声明 dsh 能力
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 4 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-plugin-heartbeat @ 0.8.0
✓Node 引擎要求 >=18 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 00:32:33

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 21 处,展示前 21 条)
  • 发起网络请求
    lib/client.js:341
    fetch(STATUS_URL)
  • 发起网络请求
    lib/client.js:487
    fetch(STATUS_URL)
  • 发起网络请求
    lib/client.js:498
    fetch(CONFIG_URL).then(async (response) => {
  • 发起网络请求
    lib/client.js:502
    fetch(STATUS_URL).then(async (response) => (response.ok ? response.json() : undefined)),
  • 发起网络请求
    lib/client.js:523
    fetch(CONFIG_URL, {
  • 发起网络请求
    lib/client.js:540
    fetch(CONTROL_URL, {
  • 读取本地文件
    lib/store.js:74
    if (!existsSync(this.path)) return
  • 读取本地文件
    lib/store.js:75
    raw = JSON.parse(readFileSync(this.path, 'utf8'))
  • 写入本地文件
    lib/store.js:128
    mkdirSync(dirname(this.path), { recursive: true })
  • 写入本地文件
    lib/store.js:130
    writeFileSync(tmp, `${JSON.stringify(this.config, null, 2)}\n`)
  • 写入本地文件
    lib/store.js:131
    renameSync(tmp, this.path)
  • 读取本地文件
    lib/vitals.js:105
    if (!existsSync(this.path)) return
  • 读取本地文件
    lib/vitals.js:106
    const raw = JSON.parse(readFileSync(this.path, 'utf8'))
  • 写入本地文件
    lib/vitals.js:293
    mkdirSync(dirname(this.path), { recursive: true })
  • 写入本地文件
    lib/vitals.js:295
    writeFileSync(tmp, `${JSON.stringify(this.state, null, 2)}\n`)
  • 写入本地文件
    lib/vitals.js:296
    renameSync(tmp, this.path)
  • 读取本地文件
    lib/proactive-lock.js:52
    if (!existsSync(path)) return undefined
  • 读取本地文件
    lib/proactive-lock.js:53
    const raw = JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    lib/proactive-lock.js:67
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    lib/proactive-lock.js:69
    writeFileSync(tmp, `${JSON.stringify({ version: PROACTIVE_LOCK_VERSION, speaker, at })}\n`)
  • 写入本地文件
    lib/proactive-lock.js:70
    renameSync(tmp, path)
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 定时唤醒agent
  • 主动汇报进展
  • 主动汇报卡点

README

由 DeepSeek 最新模型翻译生成
dsh-plugin-heartbeat 中文 | English 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 开口(见下面的等级表)。
展开完整文档(README 剩余 1 节)
dsh-plugin-heartbeat

中文 | English

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) | 这个会话的心跳停到恢复为止 |
| 恢复(Resume) | 解除暂停 / 打盹,按当前节律重新排拍 |
| 重置(Reset) | 清掉打盹与暂停并恢复 |

心跳面板配置与控制

路由(回环、插件自建):

| 方法 | 路径 | 用途 |
| GET / POST | /api/heartbeat/config | 读 v2 配置 / patch 合并(校验后热应用) |
| GET | /api/heartbeat/status | 配置摘要、今日预算、每会话生命体征 + nextBeatAt + 最近 5 拍 |
| POST | /api/heartbeat/control | {action: "beat-now" \| "snooze" \| "pause" \| "resume" \| "reset", sessionId?, minutes?} |

指定 sessionId 时只作用于该会话,省略则作用于全部存活会话。

面板文案走 DSH 自己的语言层:client 半注入 locale、注册 heartbeat 命名空间的中英语言包,并在 settings section 上声明 locale: 'heartbeat'——所以面板跟随「设置 → 通用设置 → 语言」,切换 App 语言即时重渲染(不靠浏览器语言猜)。侧栏 label 走同一套语言包(中文「心跳」/ 英文 Heartbeat),语言切换后爱心图标的认领会自动重跑,标记始终落在当前那一行。

诊断(v0.8.0)

面板能直接回答「它刚才为什么没开口」,不用翻日志:诊断区列出最近 5 拍的时间 / 等级 / 原因(如 没有新信号、静默时段、proactive-cooldown(让位给 memory)、child-active),并显示当前发言权持有者(来自 status 路由新增的 lock 字段)——一眼就能解释一段安静。L3 区也从只显示勾选框改为同时给出「已授权类别」摘要。

心跳诊断区

与其他插件的发言权仲裁(v0.7.0)

同一段对话里可能有多个主动插件都想说话。v0.7 加了一套极小的共享协议,避免两个插件抢话:

- 投递前先 claim ${DSH_HOME:-~/.dsh}/proactive-cooldown.json —— { version: 1, speaker: "heartbeat" | "memory", at: <epoch ms> },tmp+rename 原子写;
- 若记录在 proactiveCooldownSeconds(默认 180,0 = 关闭)内且属于其他 speaker,本拍让位并记 L0 · proactive-cooldown(让位给 …),之后某一拍再试;
- 立即一拍(Beat now) 绕过仲裁 —— 手动拍是你的显式请求;
- 模块零 import 自 memory 插件:双方各自独立实现同一协议,任一方可单独安装 / 升级 / 卸载;读不到文件按「无锁」处理,写失败降级为「放行」(是礼貌,不是硬依赖)。

生理签名(v0.7.0)

心跳不是 cron:它是有身体的。L1/L2/L4 消息可以在 why-now 之后带一行很短的「状态」,例如 〔状态:距你上次说话 2h · 今天第 3 次找你〕:

- 由 lib/physiology.js 渲染,硬上限 60 字;physiologyLine(默认 true)是总开关,自定义 prompt 可以用 {{physiology}} 占位符指定位置;
- vitals 新记 lastUserMessageAt / lastTurnEndAt / busyMsToday,所以这行跨重启仍成立;
- 用户 20 分钟内出现过 → 不显示(是观察,不是打扰);措辞随时段变化(早上好:/ 傍晚了:/ 状态:),深夜更短更轻、不带计数。

userActiveSuppressSeconds(默认 120,0 = 不抑制)把「你刚说过话就静默」的窗口做成可配置;它用持久化的 lastUserMessageAt,跨重启也生效。

子智能体执行中胶囊(v0.7.0)

后台子智能体还在跑时,会话头部可能看起来已经结束。

这里规避的已知宿主行为:DSH 会话列表的 running 只在 agent 创建 / 销毁时重算,子会话 api-session/status 按 session 资源转发——父会话空闲时,它的视图可能拿不到子会话运行状态的变化,于是头部显示「N 个子智能体」而不是「正在运行」,主会话看起来已完成。

现在插件在 GET /api/heartbeat/status 的每个会话上报告 children: { total, running, runningIds }(来自 subagentCatalog / agentTeam 的 wire view 加活 agent registry——和 runtime 保持静默用的是同一份宿主真值),客户端在 conversation.session.header.actions 注册头部胶囊 「子智能体执行中 · N」,每 5 秒轮询;子任务结束自动隐藏,并跟随 App 语言。浏览器标签页隐藏时暂停轮询,回到前台立即恢复。纯插件层,零宿主改动。

会话头部的子智能体执行中胶囊

安装

dsh plugin --profile <profile> add dsh-plugin-heartbeat

(包内置 dsh.bundle manifest,dsh plugin add 会把它自动挂进 profile 的 bundles 层;dsh-market 里的一键安装同此通道。)

重启 DSH 后生效。之后在 设置 → Heartbeat 面板里随时改模式、间隔、静默时段、去重窗口、预算、L3 授权与任务巡检,保存即热应用(无需重启)。

⚠️ 不要再往 profile 的 cordis.patch.yml 里手写 - insert: {id: dsh-heartbeat, ...}:那会与 bundle manifest 的自动挂载产生两条同名 entry,整个 profile 会以 duplicate loader entry id "dsh-heartbeat" 启动失败(2026-08-18 实机事故,当时撞的是 memory 插件的同名条目)。运行期配置走 <dshHome>/heartbeat.json;覆盖 composition 键(如 prompt)用不带 insert 的 id 覆盖条目,见下。

设置侧栏图标

设置侧栏里的这一项带一个描边爱心标记:16×16 网格、stroke-width: 1.3(与宿主 ICON_MEDIUM_STROKE 一致)、纯黑描边只提供 alpha,可见颜色由 mask-image + currentColor 决定,深浅主题都跟随。规格与 dsh-plugin-memory 的图标完全统一。官方 settings.section 槽只投影 id / order / label,没有 icon 字段,所以插件按当前语言的 label 文本(中文「心跳」/ 英文 Heartbeat)精确认领自己那一行,并用 MutationObserver 跟随语言切换(认领键随 label 变化)与面板重挂载重新认领,卸载时清理干净。

版本要求

- DSH:官方 0.2.1-rc.5 系列。package.json 已声明 engines.dsh: "^0.2.1-rc.5"——宿主用 semver(includePrerelease: true)做引擎闸门,注意 prerelease 语义:^0.2.1 / >=0.2.0 都匹配不上 0.2.1-rc.5,必须写 ^0.2.1-rc.5 才认;它同时覆盖将来 0.2.1 正式版,而 0.3.0-rc.1 会被判不兼容。
- session format v4(官方 0.1.7+)。心跳消息的 source 使用 producer-owned kind plugin:heartbeat;v4 的原生准入拒绝 v3 的 {kind:'plugin', plugin:'heartbeat'} wrapper。v4 与 v3 白名单互斥、无法两全。
- 0.4.2 是最后一个 v3 兼容版本;0.5.0 起只支持官方 DSH 0.2.1-rc.5 系列。0.3.x 暂不支持。
- 请用 0.8.0:已发布的 0.6.1 误读会话投影——stateOf() 返回的是原始 state,普通会话的 subagent 身份是 {},被判成「这是子会话」→ 心跳完全挂不上,child-active 闸门也永不生效;0.6.2 修了这个问题,0.7.0 加了发言权仲裁、生理签名与子智能体胶囊,0.8.0 再加诊断面板、面板内可调仲裁/生理签名配置,以及更省电的胶囊轮询。
- 实机验证(DSH Desktop 0.2.1-rc.5,重启后加载 v0.6.0 构建):
- GET /api/heartbeat/status → 200,返回 2 个 live session(含 sessionId / cwd / nextBeatAt / 预算 / history);/api/heartbeat/config 返回 v2 配置;
- 已有的 v1 heartbeat.json 自动迁移到 v2(intervalSeconds → baseIntervalSeconds,新增 mode / 静默时段 / 预算等字段),并自动创建 heartbeat-vitals.json;
- 静默拍生效:临时把间隔调到 60 秒等一拍,两个会话的 vitals 各记录一条 L0 · 没有新信号 · delivered: false,每日预算仍为 0(零注入);随后已恢复 600 秒 / jitter 60;
- 热插拔:运行中 dsh plugin remove 使 /status 从 200 变 401(路由消失、无崩溃),add 回来恢复 200 且会话恢复;
- 控制路由:未知 action 返回 400,snooze 让 snoozeUntil 生效,reset 清零恢复;
- 侧栏爱心图标与 Heartbeat 区块正常渲染。
未单独实机验证(仅单测覆盖):事件驱动时延(≤10 秒)、pre-beat 压缩、真实的 L2 提问投递。v0.6 自主层由 186 个单测覆盖。

配置(v2)

用户配置在 <dshHome>/heartbeat.json(面板可写,也能手改;用 schemastery 校验)。composition 配置是基线层,用户文件覆盖它,匹配到的 workspaces 条目再覆盖两者。

| 键 | 默认 | 含义 |
| enabled | true | 总开关 |
| mode | "assist" | "assist" = 有信号就开口;"silent" = 永不开口(照常拍脉搏并记录) |
| baseIntervalSeconds | 600 | 基础拍间隔(30–86400 秒);退避档位以它为基准 |
| pauseAfterMissed | 3 | 连续 N 拍没人回 → 硬停(0 = 关闭);你的下一条消息复活它 |
| backoffSeconds | null | 显式退避档位数组(null = 自动推导 [base, 2×base, 3×base]) |
| compactBeforeBeat | true | 开口前先把长历史压成 checkpoint(尽力而为,失败不影响投递) |
| maxBeatsPerHour | 0 | 滑动每小时投递上限(0 = 关闭) |
| maxBeatsPerDay | 12 | 每日投递预算(0 = 不限),进程内跨会话共享 |
| dedupeWindowMinutes | 90 | 同一信号键在该窗口内不重复问(0 = 关闭) |
| refractorySeconds | 300 | 投递后至少安静这么久才允许下一拍 |
| jitterSeconds | 60 | 确定性的每会话抖动(0–600 秒),让多个会话错开 |
| restartCooldownSeconds | 300 | 重启后即使有信号也先安静这么久 |
| quietHours | {"start":"23:00","end":"08:00"} | 该时段内不开口(支持跨午夜;null = 关闭) |
| quietWhileChildrenActive | true | 子 agent / teammate 还在跑时保持静默(本拍记 L0 · child-active、不注入);手动「立即一拍」是例外(v0.6.1) |
| proactiveCooldownSeconds | 180 | 与其他主动插件的共享发言冷却(0 = 关闭;手动「立即一拍」绕过) |
| userActiveSuppressSeconds | 120 | 用户最后一条消息后这么久内不开口(0 = 不抑制) |
| physiologyLine | true | 心跳里带一行生理签名(自定义 prompt 可用 {{physiology}} 指定位置) |
| taskNudge | true | 是否在心跳里带任务快照 + 提问指令 |
| taskNudgeMaxItems | 5 | 任务快照每类条目上限(1–20) |
| l3Allow | [] | L3 授权类别:read / test / notes / continue |
| prompt | null | 自定义提示词模板({{time}} / {{tasks}} / {{reason}} / {{physiology}});null = 内置分级提示词 |
| workspaces | {} | 按会话绝对 cwd 的工作区覆盖 |

workspaces 条目可写:enabled、mode、baseIntervalSeconds、quietHours(对象或 null)、maxBeatsPerDay、l3Allow。优先级:composition 配置 < 用户配置 < 命中的工作区覆盖。

v1 → v2 迁移:v1 文件(intervalSeconds、无 version)在加载时迁移——intervalSeconds 变成 baseIntervalSeconds,其余字段语义不变,缺失字段取 v2 默认值——并回写成 v2。坏配置文件会被忽略(内存里保留上一份好配置,下次启动用默认值);无法识别的键不会写回。运行态是另一个文件:<dshHome>/heartbeat-vitals.json。

自定义心跳提示词(composition 配置,不带 insert,避免与 bundle 层重复):

- id: dsh-heartbeat
config:
prompt: |
【心跳】{{time}} —— 为什么现在叫你:{{reason}}
{{tasks}}
有实质进展/风险就简短汇报;要动手先问。

{{time}}、{{tasks}}、{{reason}} 会被替换。模板里没有 {{tasks}} 时任务块追加到末尾;没有 {{reason}} 时 why-now 行追加到末尾。

为什么不用 settings 面板的通用 namespace?DSH 的 settings wire 只服务一张硬编码白名单(WEB_SETTINGS_NAMESPACES),插件无法把自有 namespace 暴露给浏览器写入。本插件因此在 host 自建了 /api/heartbeat/ 路由,设置面板里的 Heartbeat 区块直连这些路由,写入即热应用。

开发

node --test

无参 node --test 会自动发现 test/.test.mjs;测试覆盖 runtime、tasks、sensors、rhythm、gate、vitals、config-store-v2、prompt-levels、wiring-v06、settings-nav-icon、client-half、proactive-lock、physiology、prompt-physiology、activity,当前共 186 个用例;CI 用 .github/workflows/ci.yml 跑同一套。

设计与实施记录在仓库里(不进 npm 包):docs/specs/2026-10-30-heartbeat-v0.6-design.md、docs/plans/2026-10-30-heartbeat-v0.6.md。跨插件共享发言协议见 docs/PROACTIVE-PROTOCOL.md;贡献与安全策略见 CONTRIBUTING.md、SECURITY.md。

已知边界

- 心跳只在 DSH 进程活着时存在(app 关了就停)。
- 每个 root agent 各自一条 runtime;子 agent 会话永不挂心跳(按身份守卫,见下)。
- 默认安静:没有变化就不说话。存在感现在靠面板的生命体征与「立即一拍」,而不是按点叨叨——想让它更活跃就调 mode / 去重 / 静默时段相关配置。
- 不打断委派工作(v0.6.1):子 agent / teammate 在跑时,root 只是因为等它才 idle;此时心跳若开新一轮,会在工作中间插入一条消息——客户端把「正在运行子会话」展示为当前活跃 turn 的一部分,新 turn 会把它顶掉,你就看不出后台还在跑了(pre-beat 压缩还可能把活跃 turn 折进摘要)。所以这类拍只记 L0 · child-active、不注入、不压缩,直到子任务全部结束;手动「立即一拍」仍可强制触发。
- 头部胶囊才是「仍在跑」的可靠指示(v0.7.0):子任务运行期间,宿主会话头部可能把父会话本轮显示为已结束(见上方已知宿主行为);插件胶囊会在同一个头部里把运行状态一直显示到所有子任务结束。
- 永不挂到子 agent 会话(v0.6.1):teammate / subagent 是 mode: "continuable" 的子会话,用户可能从侧栏把它当顶层会话重新打开 / resume;按宿主语义这种 resumed child 可能被算作 root(无 owner),所以 attach() 会先读该会话的 subagent 投影身份,非空就跳过不挂(日志 heartbeat: skip subagent session);运行中的 runtime 也会在每次 tick 复查身份,一旦发现自己是 subagent 会话就 dispose() 摘除。真机排查:当前实例 3 个运行时全是 root 用户会话,逐个扫 teammate 子会话日志心跳注入 0 条(那些日志里的 plugin:heartbeat 字样是工具输出 / 文档内容,不是注入消息)。另有一个不属于插件职责的边界:fork 型子 agent 会继承父会话历史,fork 前父会话里已有的心跳消息会随 seed 带过去——这是 fork 语义本身;spawn 型子 agent 不受影响。
- L3 不是沙箱:只改指令;不可逆操作仍由宿主 approval 拦截。
- 运行态(heartbeat-vitals.json)是进程内的,原子写 + 短防抖;两个 DSH 进程共用一个 home 时是「尽力而为」,不是强一致。
- 事件驱动需要 sessionProjections.onChanged;没有该服务时退化为纯定时器模式(门控相同,只是反应没那么快)。
- 插件在会话已打开后才热挂载时,会对现有 root agent 做一次幂等补挂;之后新开的会话仍走 agent/created。
- 插件依赖 agent.followup / agent.inbox 等 agent-loop 驱动面——不属于 DSH 公开 API catalog,已锁定 DSH 0.2.x;宿主若换实现,最多丢一拍并打一条 warn。
- taskNudge: false 会同时去掉任务快照与巡检 / 提问指令。
- 与 dsh-plugin-memory 互相独立、零依赖;两者同时安装时消息在同一个 inbox 队列里排队,不冲突。
- 版本要求:官方 DSH 0.2.1-rc.5 系列、session format v4;engines.dsh: "^0.2.1-rc.5",细节见上方「版本要求」。

License

MIT
预览分享卡片长什么样 ↓LittleBlackTong/dsh-plugin-heartbeat
分享 · DeepSeek Harness Hub
LittleBlackTong/dsh-plugin-heartbeat

DeepSeek Harness 的「自主神经」心跳:平时静默拍脉搏、读会话投影,只在真有变化时开口——L1…

回到顶部