@kanadego/dsh-heartbeat 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,9 +29,10 @@
29
29
  - **心跳节律**:定时唤醒(默认 20 分钟一跳,卡片可调),按七相循环运行——维护 → 采集 → 闲逛 → 闸门 → Digest → 反刍备料 → 素材投递 → 留痕。每跳都有审计留痕,沉默必带原因。
30
30
  - **分寸闸门**:静默时段、忙时窗口、每日表达上限、开口冷却、在场联动,全部是代码层的独立判定,不靠模型自觉。默认倾向开口,但他正忙或已到深夜时绝不打扰。
31
31
  - **状态栏与时间感知**:心跳把"此刻在干什么"(在闲逛 / 看到你在忙 / 在场待着…)注入日常会话——支持动态系统提示词的模型常驻系统提示词(KV-cache 安全追加),其余模型只在用户发起轮次时带一行。精确时间按 20–30 分钟节流注入,任务进行中绝不中途插话。
32
- - **兴趣闲逛**:按你定义的兴趣范围轮换挑焦点(同一条 3 天冷却),用 `web_search` 看新东西,结果由代码登记入素材池——模型只负责搜索筛选,不做任何写盘动作。
33
- - **素材池**:话题种子缓存,四条确定性淘汰(TTL / 消费退休 / 冷板凳 / 容量挤出),是缓存不是记忆,与画像单向隔离。
34
- - **反刍投递(目标会话拍板)**:引擎室从素材池挑 ≤3 条各压缩成一句(不带理由不排序),拼成素材包注入你读的会话——声明「这是心跳插件素材投递」+素材清单(必要时附「也可以说一句真心话」)。说不说、说哪条,由目标会话里的 agent 按当下处境自己判断;话落到你眼前的会话,素材自动归账。
32
+ - **视觉感知(可选)**:每跳对全屏截图做一次视觉识别(经 [ModLens](https://github.com/liustack/modlens) 外接多模态模型),投递的素材包会带一句「(他此刻大概在:…)」;识别失败时画面与窗口标题一概不进任何提示词——隐私优先,宁可不说。
33
+ - **兴趣闲逛**:按你定义的兴趣范围轮换挑焦点(同一条 3 天冷却),用 `web_search` 看新东西(每次 3~9 轮搜索),结果由代码登记入素材池——模型只负责搜索筛选,不做任何写盘动作。话题库存见底(≤4 条)时自动触发补货闲逛(每天至多 2 次)。
34
+ - **素材池(话题 / 聊天双仓)**:话题库存 16 条 + 聊天素材 14 条,四条确定性淘汰(TTL / 消费退休 / 冷板凳 / 容量挤出);聊天种子来自画像合并时从对话里挑出的"值得聊的话题",有时效性——投递过 1 次即退休。是缓存不是记忆,与画像单向隔离。
35
+ - **反刍投递(目标会话拍板)**:代码先凑好候选(话题随机 4 + 聊天最新 2,互补至多 6 条),引擎室挑 ≤3 条各压缩成一句,拼成素材包注入你读的会话——附一句「他此刻大概在:…」(视觉识别成功时)+声明「这是心跳插件素材投递」+素材清单(必要时附「也可以说一句真心话」)。说不说、说哪条,由目标会话里的 agent 按当下处境自己判断;话落到你眼前的会话,素材自动归账。
35
36
  - **用户画像**:本地结构化档案(兴趣 / 项目 / 沟通偏好 / 心理基线),带来源与置信度,稳定特质不因时间遗忘;journal 单一权威,可校验、可重建、可解密导出。
36
37
  - **会话绑定**:把心跳消息投递到你常用的会话(agent 会"到你房间里说话"),或观察会话内容喂养画像——投递/观察是两个独立开关,可双开。
37
38
  - **加密与焚毁**:敏感文件 DPAPI(CurrentUser)全文件加密;一键焚毁运行时数据;模型出库仅限合并裁决的最小必要摘要。
@@ -114,6 +115,9 @@ node <插件目录>/dist/cli/index.js <命令>
114
115
  | 闲逛永远秒结束、`tool_policy` 报 `unknown global tool "web_search"` | 预设没挂上(裸 agent):`preset status` → `preset install`,装好后 `tool_policy` 行应是 `preset=mounted(heartbeat) restrict=ok` |
115
116
  | 设置页整块没有心跳卡片(数据照常写) | client 注册 id 与包名不一致被宿主**静默剔除**:三处必须严格等于包名,改完**重启** |
116
117
  | 卡片分区报 `加载失败: … HTTP 405` | RPC 路由没注册成功:审计里应出现 `rpc_registered route=/api/heartbeat`,没有 = dist 未同步,重装/同步后重启 |
118
+ | 节律配置保存不生效、重启后回默认(0.1.5 宿主) | v1.5.x 的已知问题(dsh-settings 包缺失 + 0.1.5 API 变更):升级到 v1.6.0 并重启 |
119
+ | 投递没带「他此刻大概在:…」、审计有 `screen_vision ok:false` | 视觉识别失败:error 字段带 ModLens 具体报错;常见为 provider 未配置或网络超时。识别失败时画面/标题按隐私设计不进提示词,心跳其余功能不受影响 |
120
+ | 结果显示「心跳异常(decision: whenIdle timeout)」 | 反刍轮超时(240s 内没等到模型回话)。v1.6.0 起不再整跳报错:记 `decision_deferred`、本跳按沉默处理、素材自动留到下一跳。若反复出现,看该行 reason 与 ModLens 画面描述是否诱发跑题 |
117
121
  | 每跳 `beat_error: … reading 'length'` | 宿主移除了 `Session.events`——插件版本与宿主不匹配,对照〈版本兼容〉换版本 |
118
122
  | 每轮报 `prompt variable "{{model}}" has no value` | agent 没有模型路由(审计 `model` 字段为 `(none)`)——同样是版本不匹配症状 |
119
123
  | 素材投递偶尔不成功、`spoke_failed: non-Chinese output discarded` | 目标会话 agent 收到素材包后回合以工具调用收尾,末行是 `</tool_calls>`(纯英文)被旧判定误拦。v1.5 起已修:跳过工具收尾标签、取最后一个含中文的行;整段无中文才拒。升级后重启即可 |
@@ -136,8 +140,9 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
136
140
 
137
141
  | 插件版本 | 适配的 DSH | 备注 |
138
142
  |---|---|---|
139
- | **v1.5.1**(当前) | ≥ `0.1.2-rc.1` | 元数据补全(repository / peer 依赖),无行为改动 |
140
- | v1.5 | ≥ `0.1.2-rc.1` | 反刍投递改版 |
143
+ | **v1.6.1**(当前) | ≥ `0.1.2-rc.1` | v1.6.0 + 决策轮超时降级(反刍轮超时不再整跳报错,素材留到下一跳) |
144
+ | v1.6.0 | ≥ `0.1.2-rc.1` | 素材闭环 + 视觉识别;修复 `0.1.5` 下节律配置保存不生效(v1.5.x 在 0.1.5 上有此问题,务必升级) |
145
+ | v1.5.x | ≥ `0.1.2-rc.1` | 反刍投递改版;v1.5.1 元数据补全 |
141
146
  | v1.4 | ≥ `0.1.2-rc.1` | M7 全部能力在 `0.1.5-rc.2` 验收;旧宿主上状态栏自动走 pre-step 轨道 |
142
147
  | v1.2.x – v1.3.x | ≥ `0.1.2-rc.1` | `0.1.5` 下须 ≥ v1.2.1,否则卡片 RPC 405 |
143
148
  | v1.1 | ≥ `0.1.2-rc.1` | `0.1.5` 下诊断行缺细节,功能不受影响 |
@@ -147,6 +152,23 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
147
152
 
148
153
  ## 版本更新
149
154
 
155
+ ### v1.6.1 · 2026-09-20
156
+
157
+ **补丁:反刍轮超时降级**——决策轮(反刍)在 240s 预算内没等到模型回话时,不再把整跳记成异常:改为记 `decision_deferred` 审计、best-effort 取消孤儿轮次、本跳按沉默处理,素材自动留到下一跳(与表达轮 `spoke_deferred` 同一语义)。同时在反刍提示词里钉死一条纪律:画面与窗口只用于写「他此刻大概在:」,即使画面出现想查的内容也不发起搜索、不调用工具。
158
+
159
+ ### v1.6.0 · 2026-09-19
160
+
161
+ **素材闭环**——截图→视觉识别→引擎室总结→投递→聊天→观察沉淀→consolidation 生成聊天种子→素材池→反刍投递,全链路打通:
162
+
163
+ - **素材池品类化**:种子分 `topic`(话题库存)/ `chat`(聊天长出)两类,分池上限 16 + 14;聊天种子有时效性——**投递过 1 次即退休**(话题类仍是 2 次)。
164
+ - **候选组包代码化**:反刍候选由代码凑齐——话题随机 4 条 + 聊天最新证据 2 条、不足互补、至多 6 条交给引擎室挑 ≤3 条;池子全空直接安静,不再空烧一次模型调用。
165
+ - **聊天种子**:画像合并(consolidation)时引擎室从观察对话里自主挑"值得聊的话题"存入素材池——只挑真兴趣和新事物,寒暄不记。合并与反刍彻底分工:合并只看观察对话,反刍只看素材池。
166
+ - **补货闲逛**:话题种子剩 ≤4 条时自动触发一次额外闲逛补货(不受浏览窗口与 4h 间隔限制,仍尊重焦点 3 天冷却,每天至多补 2 次);每次闲逛的搜索次数从"至多 3 次"放宽到 **3~9 次**。
167
+ - **视觉识别与"我在干嘛"**:截图改为全屏原图(修复降采样图被误判成全屏游戏);每跳经 [ModLens](https://github.com/liustack/modlens) 识别画面内容,投递的素材包会带一句「(他此刻大概在:…)」。**识别失败时画面与窗口标题一概不进入任何提示词**——隐私优先,宁可不说。provider 在插件侧钉死(默认 openai 兼容通道,`data/settings/vision.json` 可覆盖),不再受 ModLens 默认链探测干扰。
168
+ - **修复 `0.1.5` 下节律配置失效**:v1.5.x 起在 0.1.5 宿主上 UI 保存不生效、重启回默认(dsh-settings 包缺失 + 其 0.1.5 API 变更双重原因)。现已适配两代 API 并把依赖归位——0.1.5 用户务必升级。
169
+ - **UI**:心跳状态卡新增「插件版本」一行。
170
+ - 测试 163 条全绿。
171
+
150
172
  ### v1.5.1 · 2026-09-16
151
173
 
152
174
  **元数据补全,无行为改动**——只影响安装与生态对接:
@@ -1,13 +1,14 @@
1
- # screenpulse.ps1 - screen pulse collector (port of v1, logic unchanged).
1
+ # screenpulse.ps1 - screen pulse collector (port of v1; screenshot at native
2
+ # resolution since 2026-09-18 spec ①).
2
3
  # Collects: foreground title + process + rect, focus window, visible top-level
3
- # windows (taskbar-level, cap 20), full-screen screenshot downsampled to
4
- # width 1024.
4
+ # windows (taskbar-level, cap 20), full-screen screenshot at native size.
5
5
  #
6
6
  # Privacy discipline (privacy charter v0.9, user-authorized):
7
7
  # - visible-window enumeration only (EnumWindows + IsWindowVisible, cap 20);
8
8
  # no tray icons, no process enumeration
9
- # - screenshot force-downsampled to width 1024 (mood-level detail, small
10
- # text is unreadable)
9
+ # - screenshot saved at NATIVE resolution (spec ①: fixes the "partial
10
+ # screen misread as full-screen game" failure); the file is DPAPI-
11
+ # encrypted at rest by the caller and burned after use
11
12
  # - this script only produces PLAINTEXT intermediates in the caller-provided
12
13
  # outdir (which must be data/tmp); encryption at rest is the caller's job
13
14
  # - title/process text is sensitive -> DPAPI-encrypted by caller, or burned
@@ -120,7 +121,9 @@ foreach ($w in [WinForeground]::VisibleTopLevel()) {
120
121
  $visible += @{ title = $wi.title; process = $wi.process; pid = $wi.pid }
121
122
  }
122
123
 
123
- # -- 2. screenshot, downsampled to width 1024 (degrade to text-only on failure) --
124
+ # -- 2. screenshot, FULL screen at native resolution (spec ①, 2026-09-18:
125
+ # the old width-1024 downsample made a full-screen game look like a
126
+ # windowed shot; degrade to text-only on failure) --
124
127
  $shotPath = ''
125
128
  $scr = $null
126
129
  $sw = 0; $sh = 0
@@ -135,19 +138,11 @@ try {
135
138
  $g.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
136
139
  $g.Dispose()
137
140
 
138
- $sw = 1024
139
- $sh = [int]([math]::Round($bounds.Height * ($sw / [double]$bounds.Width)))
140
- if ($sh -le 0) { throw 'bad resize' }
141
- $small = New-Object System.Drawing.Bitmap $sw, $sh
142
- $sg = [System.Drawing.Graphics]::FromImage($small)
143
- $sg.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
144
- $sg.DrawImage($bmp, 0, 0, $sw, $sh)
145
- $sg.Dispose()
146
- $bmp.Dispose()
147
-
141
+ $sw = $bounds.Width
142
+ $sh = $bounds.Height
148
143
  $shotPath = Join-Path $outdir 'screen.raw.jpg'
149
- $small.Save($shotPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
150
- $small.Dispose()
144
+ $bmp.Save($shotPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
145
+ $bmp.Dispose()
151
146
  } catch {
152
147
  Write-Warning "screen shot skipped: $($_.Exception.Message)"
153
148
  }
package/client.js CHANGED
@@ -124,6 +124,7 @@ window.__ModuleLoader__.load({
124
124
  return React.createElement(
125
125
  "div",
126
126
  { style: listStyle },
127
+ React.createElement("div", null, "插件版本:v" + (view.version || "未知")),
127
128
  React.createElement("div", null, "上次心跳:", fmtTime(lb.at)),
128
129
  React.createElement("div", null, "结果:", verdictText),
129
130
  React.createElement("div", null, "今日表达:", view.cap.used, " / ", view.cap.max, " 条"),