@kanadego/dsh-heartbeat 1.6.1 → 1.6.3

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
@@ -140,7 +140,9 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
140
140
 
141
141
  | 插件版本 | 适配的 DSH | 备注 |
142
142
  |---|---|---|
143
- | **v1.6.1**(当前) | ≥ `0.1.2-rc.1` | v1.6.0 + 决策轮超时降级(反刍轮超时不再整跳报错,素材留到下一跳) |
143
+ | **v1.6.3**(当前) | ≥ `0.1.2-rc.1` | v1.6.2 + 闲着模式(素材池为空时可用画像话题兜底主动搭话,闸门仍生效;默认关闭) |
144
+ | **v1.6.2** | ≥ `0.1.2-rc.1` | v1.6.1 + consolidation 白名单修复(画像合并遵守 profile-schema,不再自创分区/错误 ref 被拒;rebuild 改加密写) |
145
+ | v1.6.1 | ≥ `0.1.2-rc.1` | v1.6.0 + 决策轮超时降级(反刍轮超时不再整跳报错,素材留到下一跳) |
144
146
  | v1.6.0 | ≥ `0.1.2-rc.1` | 素材闭环 + 视觉识别;修复 `0.1.5` 下节律配置保存不生效(v1.5.x 在 0.1.5 上有此问题,务必升级) |
145
147
  | v1.5.x | ≥ `0.1.2-rc.1` | 反刍投递改版;v1.5.1 元数据补全 |
146
148
  | v1.4 | ≥ `0.1.2-rc.1` | M7 全部能力在 `0.1.5-rc.2` 验收;旧宿主上状态栏自动走 pre-step 轨道 |
@@ -152,6 +154,21 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
152
154
 
153
155
  ## 版本更新
154
156
 
157
+ ### v1.6.3 · 2026-09-20
158
+
159
+ **闲着模式(默认关闭)**:当素材池为空、而用户打开了「闲着模式」开关时,心跳改用本跳画像 digest 的话题切面兜底,仍走原有投递链路主动搭话(每日上限、静默时段等闸门照常生效)。未开启时维持原来的「空池即沉默」。
160
+
161
+ - 新增配置项 `heartbeat.idleMode`(默认 `false`),可在设置卡「节律配置」里切换,保存即时生效。
162
+
163
+ ### v1.6.2 · 2026-09-20
164
+
165
+ **补丁:画像合并(consolidation)白名单修复**——画像从出生就空的问题根因在 `buildConsolidationPrompt` 从没把 `profile-schema.json` 的分区白名单传给裁决模型:引擎室凭直觉自创分区(如 `background`/`relation`/`preference`)全被 `partition not in schema` 拒绝,导致增量几乎 100% 被拒、画像长期为空。本次:
166
+
167
+ - 提示词注入完整白名单(partition/topic/subTopic + 各格 temporal 允许集),并明确禁止自创分区、evidence ref 必须写成 `cursors.json#<时间戳>` 而非带 `chat#` 前缀。
168
+ - 扩充 schema 槽位(新增 `acg`/`hardware`/`audio`/`writing`/`life`、`heartbeat`/`dsh`/`zcode`、`interaction`/`boundary`、`background`/`social` 等 topic)。
169
+ - 修复 `profile rebuild` 写明文 profile.json 的问题(改回 DPAPI 加密写)。
170
+ - 把历史被拒但有效的高价值观察回填进画像。
171
+
155
172
  ### v1.6.1 · 2026-09-20
156
173
 
157
174
  **补丁:反刍轮超时降级**——决策轮(反刍)在 240s 预算内没等到模型回话时,不再把整跳记成异常:改为记 `decision_deferred` 审计、best-effort 取消孤儿轮次、本跳按沉默处理,素材自动留到下一跳(与表达轮 `spoke_deferred` 同一语义)。同时在反刍提示词里钉死一条纪律:画面与窗口只用于写「他此刻大概在:」,即使画面出现想查的内容也不发起搜索、不调用工具。
package/client.js CHANGED
@@ -341,12 +341,14 @@ window.__ModuleLoader__.load({
341
341
  const cap = Number(value.maxDailySend) > 0 ? Number(value.maxDailySend) : 3;
342
342
  const timeInject = value.timeInjectMin === 0 ? 0 : (Number(value.timeInjectMin) > 0 ? Number(value.timeInjectMin) : 25);
343
343
  const statusbar = value.statusbar !== false;
344
+ const idleMode = value.idleMode === true;
344
345
  const [draftInterval, setDraftInterval] = React.useState(interval);
345
346
  const [draftCap, setDraftCap] = React.useState(cap);
346
347
  const [draftTimeInject, setDraftTimeInject] = React.useState(timeInject);
347
348
  const [draftStatusbar, setDraftStatusbar] = React.useState(statusbar);
349
+ const [draftIdle, setDraftIdle] = React.useState(idleMode);
348
350
  const [status, setStatus] = React.useState("");
349
- React.useEffect(() => { setDraftInterval(interval); setDraftCap(cap); setDraftTimeInject(timeInject); setDraftStatusbar(statusbar); }, [interval, cap, timeInject, statusbar]);
351
+ React.useEffect(() => { setDraftInterval(interval); setDraftCap(cap); setDraftTimeInject(timeInject); setDraftStatusbar(statusbar); setDraftIdle(idleMode); }, [interval, cap, timeInject, statusbar, idleMode]);
350
352
  const save = async () => {
351
353
  try {
352
354
  const di = Math.max(1, Math.min(1440, Math.floor(Number(draftInterval) || 0)));
@@ -356,6 +358,7 @@ window.__ModuleLoader__.load({
356
358
  await scope.set("maxDailySend", dc);
357
359
  await scope.set("timeInjectMin", dt);
358
360
  await scope.set("statusbar", !!draftStatusbar);
361
+ await scope.set("idleMode", !!draftIdle);
359
362
  setStatus("已保存(全部即时生效,无需重启)");
360
363
  } catch (e) {
361
364
  setStatus("保存失败:" + String(e).slice(0, 80));
@@ -378,6 +381,10 @@ window.__ModuleLoader__.load({
378
381
  React.createElement("span", { style: labelStyle }, "状态栏"),
379
382
  React.createElement("button", { style: draftStatusbar ? buttonStyle : buttonGhost, onClick: () => setDraftStatusbar(!draftStatusbar) }, draftStatusbar ? "☑ 开启" : "☐ 关闭"),
380
383
  React.createElement("span", { style: hintStyle }, "日常会话中的心跳状态感知")),
384
+ React.createElement("div", { style: rowStyle },
385
+ React.createElement("span", { style: labelStyle }, "闲着模式"),
386
+ React.createElement("button", { style: draftIdle ? buttonStyle : buttonGhost, onClick: () => setDraftIdle(!draftIdle) }, draftIdle ? "☑ 开启" : "☐ 关闭"),
387
+ React.createElement("span", { style: hintStyle }, "素材池空时用画像话题兜底主动搭话(闸门仍生效)"))
381
388
  React.createElement("div", { style: rowStyle },
382
389
  React.createElement("button", { style: buttonStyle, onClick: () => { void save(); } }, "保存"),
383
390
  React.createElement("span", { style: hintStyle }, status || "全部参数保存后即时生效,无需重启")),
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "heartbeat": {
3
- "intervalMin": 20
3
+ "intervalMin": 20,
4
+ "idleMode": false
4
5
  },
5
6
  "gate": {
6
7
  "maxDailySend": 3,
@@ -12,8 +13,14 @@
12
13
  },
13
14
  "browse": {
14
15
  "windows": [
15
- { "start": "11:00", "end": "15:00" },
16
- { "start": "17:00", "end": "21:00" }
16
+ {
17
+ "start": "11:00",
18
+ "end": "15:00"
19
+ },
20
+ {
21
+ "start": "17:00",
22
+ "end": "21:00"
23
+ }
17
24
  ],
18
25
  "minIntervalHours": 4,
19
26
  "maxSeedsPerVisit": 2
@@ -1,31 +1,139 @@
1
1
  {
2
2
  "version": 1,
3
- "_comment": "profile-schema.json is the machine-executable collection boundary (privacy charter). Partitions/topics/subtopics NOT listed here are rejected by code, no matter what the consolidation LLM proposes. Each sub_topic declares its ALLOWED temporal tiers and the DEFAULT tier; the LLM nominates within the allowed set only. Unlisted defaults to 'stable' (asymmetric risk: mislabeling stable->volatile costs forgetting; the reverse is absorbed by the low-activity marker). psy is additionally gated by policy.profile.psyEnabled. Edit the copy in data/settings/ - this factory file is read-only.",
3
+ "_comment": "profile-schema.json is the machine-executable collection boundary (privacy charter). Partitions/topics/subtopics NOT listed here are rejected by code, no matter what the consolidation LLM proposes. Each sub_topic declares its ALLOWED temporal tiers and the DEFAULT tier; the LLM nominates within the allowed set only. Unlisted defaults to 'stable'. psy is additionally gated by policy.profile.psyEnabled. Edit the copy in data/settings/ - this factory file is read-only. NOTE (2026-09-20): partitions are hardcoded to interest/projects/comm/psy in code (Partition type + PARTITIONS const); DO NOT add new top-level partitions here. Expansion happens at topic/subTopic level only.",
4
4
  "partitions": {
5
5
  "interest": {
6
6
  "topics": {
7
7
  "games": {
8
8
  "subtopics": {
9
- "current": { "allowed": ["volatile"], "default": "volatile" },
10
- "preference": { "allowed": ["stable"], "default": "stable" }
9
+ "current": {
10
+ "allowed": [
11
+ "volatile"
12
+ ],
13
+ "default": "volatile"
14
+ },
15
+ "preference": {
16
+ "allowed": [
17
+ "stable"
18
+ ],
19
+ "default": "stable"
20
+ }
11
21
  }
12
22
  },
13
23
  "anime_manga": {
14
24
  "subtopics": {
15
- "current": { "allowed": ["volatile"], "default": "volatile" },
16
- "preference": { "allowed": ["stable"], "default": "stable" }
25
+ "current": {
26
+ "allowed": [
27
+ "volatile"
28
+ ],
29
+ "default": "volatile"
30
+ },
31
+ "preference": {
32
+ "allowed": [
33
+ "stable"
34
+ ],
35
+ "default": "stable"
36
+ }
17
37
  }
18
38
  },
19
39
  "tech": {
20
40
  "subtopics": {
21
- "current": { "allowed": ["volatile"], "default": "volatile" },
22
- "preference": { "allowed": ["stable"], "default": "stable" }
41
+ "current": {
42
+ "allowed": [
43
+ "volatile"
44
+ ],
45
+ "default": "volatile"
46
+ },
47
+ "preference": {
48
+ "allowed": [
49
+ "stable"
50
+ ],
51
+ "default": "stable"
52
+ }
23
53
  }
24
54
  },
25
55
  "creator_content": {
26
56
  "subtopics": {
27
- "current": { "allowed": ["volatile"], "default": "volatile" },
28
- "preference": { "allowed": ["stable"], "default": "stable" }
57
+ "current": {
58
+ "allowed": [
59
+ "volatile"
60
+ ],
61
+ "default": "volatile"
62
+ },
63
+ "preference": {
64
+ "allowed": [
65
+ "stable"
66
+ ],
67
+ "default": "stable"
68
+ }
69
+ }
70
+ },
71
+ "acg": {
72
+ "subtopics": {
73
+ "current": {
74
+ "allowed": [
75
+ "volatile"
76
+ ],
77
+ "default": "volatile"
78
+ },
79
+ "preference": {
80
+ "allowed": [
81
+ "stable"
82
+ ],
83
+ "default": "stable"
84
+ }
85
+ }
86
+ },
87
+ "hardware": {
88
+ "subtopics": {
89
+ "current": {
90
+ "allowed": [
91
+ "volatile"
92
+ ],
93
+ "default": "volatile"
94
+ },
95
+ "preference": {
96
+ "allowed": [
97
+ "stable"
98
+ ],
99
+ "default": "stable"
100
+ }
101
+ }
102
+ },
103
+ "audio": {
104
+ "subtopics": {
105
+ "current": {
106
+ "allowed": [
107
+ "volatile"
108
+ ],
109
+ "default": "volatile"
110
+ },
111
+ "preference": {
112
+ "allowed": [
113
+ "stable"
114
+ ],
115
+ "default": "stable"
116
+ }
117
+ }
118
+ },
119
+ "writing": {
120
+ "subtopics": {
121
+ "preference": {
122
+ "allowed": [
123
+ "stable"
124
+ ],
125
+ "default": "stable"
126
+ }
127
+ }
128
+ },
129
+ "life": {
130
+ "subtopics": {
131
+ "preference": {
132
+ "allowed": [
133
+ "stable"
134
+ ],
135
+ "default": "stable"
136
+ }
29
137
  }
30
138
  }
31
139
  }
@@ -34,12 +142,71 @@
34
142
  "topics": {
35
143
  "active_work": {
36
144
  "subtopics": {
37
- "ongoing": { "allowed": ["volatile"], "default": "volatile" }
145
+ "ongoing": {
146
+ "allowed": [
147
+ "volatile"
148
+ ],
149
+ "default": "volatile"
150
+ }
38
151
  }
39
152
  },
40
153
  "delegated": {
41
154
  "subtopics": {
42
- "promise": { "allowed": ["stable", "volatile"], "default": "stable" }
155
+ "promise": {
156
+ "allowed": [
157
+ "stable",
158
+ "volatile"
159
+ ],
160
+ "default": "stable"
161
+ }
162
+ }
163
+ },
164
+ "heartbeat": {
165
+ "subtopics": {
166
+ "ongoing": {
167
+ "allowed": [
168
+ "volatile"
169
+ ],
170
+ "default": "volatile"
171
+ },
172
+ "decided": {
173
+ "allowed": [
174
+ "stable"
175
+ ],
176
+ "default": "stable"
177
+ }
178
+ }
179
+ },
180
+ "dsh": {
181
+ "subtopics": {
182
+ "ongoing": {
183
+ "allowed": [
184
+ "volatile"
185
+ ],
186
+ "default": "volatile"
187
+ },
188
+ "decided": {
189
+ "allowed": [
190
+ "stable"
191
+ ],
192
+ "default": "stable"
193
+ }
194
+ }
195
+ },
196
+ "zcode": {
197
+ "subtopics": {
198
+ "ongoing": {
199
+ "allowed": [
200
+ "volatile"
201
+ ],
202
+ "default": "volatile"
203
+ },
204
+ "decided": {
205
+ "allowed": [
206
+ "stable"
207
+ ],
208
+ "default": "stable"
209
+ }
43
210
  }
44
211
  }
45
212
  }
@@ -48,8 +215,48 @@
48
215
  "topics": {
49
216
  "expression": {
50
217
  "subtopics": {
51
- "style": { "allowed": ["stable"], "default": "stable" },
52
- "boundaries": { "allowed": ["stable"], "default": "stable" }
218
+ "style": {
219
+ "allowed": [
220
+ "stable"
221
+ ],
222
+ "default": "stable"
223
+ },
224
+ "boundaries": {
225
+ "allowed": [
226
+ "stable"
227
+ ],
228
+ "default": "stable"
229
+ }
230
+ }
231
+ },
232
+ "interaction": {
233
+ "subtopics": {
234
+ "style": {
235
+ "allowed": [
236
+ "stable"
237
+ ],
238
+ "default": "stable"
239
+ }
240
+ }
241
+ },
242
+ "boundary": {
243
+ "subtopics": {
244
+ "rules": {
245
+ "allowed": [
246
+ "stable"
247
+ ],
248
+ "default": "stable"
249
+ }
250
+ }
251
+ },
252
+ "preference": {
253
+ "subtopics": {
254
+ "style": {
255
+ "allowed": [
256
+ "stable"
257
+ ],
258
+ "default": "stable"
259
+ }
53
260
  }
54
261
  }
55
262
  }
@@ -58,8 +265,39 @@
58
265
  "topics": {
59
266
  "baseline": {
60
267
  "subtopics": {
61
- "rhythm": { "allowed": ["stable", "volatile"], "default": "stable" },
62
- "stress": { "allowed": ["volatile"], "default": "volatile" }
268
+ "rhythm": {
269
+ "allowed": [
270
+ "stable",
271
+ "volatile"
272
+ ],
273
+ "default": "stable"
274
+ },
275
+ "stress": {
276
+ "allowed": [
277
+ "volatile"
278
+ ],
279
+ "default": "volatile"
280
+ }
281
+ }
282
+ },
283
+ "background": {
284
+ "subtopics": {
285
+ "traits": {
286
+ "allowed": [
287
+ "stable"
288
+ ],
289
+ "default": "stable"
290
+ }
291
+ }
292
+ },
293
+ "social": {
294
+ "subtopics": {
295
+ "habits": {
296
+ "allowed": [
297
+ "stable"
298
+ ],
299
+ "default": "stable"
300
+ }
63
301
  }
64
302
  }
65
303
  }
@@ -16,4 +16,4 @@ export {
16
16
  getRuntime,
17
17
  resetRuntimeForTest
18
18
  };
19
- //# sourceMappingURL=chunk-S7PTR42P.js.map
19
+ //# sourceMappingURL=chunk-SJUNS2BE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/runtime.ts"],"sourcesContent":["// Runtime singleton shared by all modules after the plugin entry initializes.\n\nimport type { PathGuard } from './path-guard.js';\nimport type { WorkspacePaths } from './paths.js';\nimport type { Policy } from '../config/schema.js';\n\nexport interface HeartbeatRuntime {\n paths: WorkspacePaths;\n guard: PathGuard;\n policy: Policy;\n /** Live UI flags (M7): re-read by the RPC status endpoint for the card. */\n flags: {\n statusbarEnabled(): boolean;\n timeInjectMin(): number;\n idleMode(): boolean;\n };\n}\n\nlet runtime: HeartbeatRuntime | null = null;\n\nexport function setRuntime(r: HeartbeatRuntime): void {\n runtime = r;\n}\n\nexport function getRuntime(): HeartbeatRuntime {\n if (!runtime) throw new Error('heartbeat runtime not initialized');\n return runtime;\n}\n\nexport function resetRuntimeForTest(): void {\n runtime = null;\n}\n"],"mappings":";AAkBA,IAAI,UAAmC;AAEhC,SAAS,WAAW,GAA2B;AACpD,YAAU;AACZ;AAEO,SAAS,aAA+B;AAC7C,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,mCAAmC;AACjE,SAAO;AACT;AAEO,SAAS,sBAA4B;AAC1C,YAAU;AACZ;","names":[]}
@@ -161,6 +161,7 @@ function assertPolicy(input) {
161
161
  if (typeof hb.intervalMin !== "number" || hb.intervalMin < 1 || hb.intervalMin > 1440) {
162
162
  fail("heartbeat.intervalMin must be a number in [1, 1440]");
163
163
  }
164
+ if (typeof hb.idleMode !== "boolean") fail("heartbeat.idleMode must be boolean");
164
165
  const g = p.gate;
165
166
  if (!isPlainObject(g)) fail("gate missing");
166
167
  if (typeof g.maxDailySend !== "number" || g.maxDailySend < 0) fail("gate.maxDailySend must be >= 0");
@@ -799,12 +800,9 @@ function verifyProfile(guard, dataDir) {
799
800
  function rebuildProfile(guard, dataDir, opts = {}) {
800
801
  const replayed = replayJournal(guard, dataDir);
801
802
  const target = profileFilePath(dataDir);
802
- const tmp = path8.join(dataDir, `.profile.rebuild.${Date.now()}.tmp`);
803
- atomicWriteFileSync(tmp, JSON.stringify(replayed.doc, null, 2));
804
803
  if (opts.check) {
805
804
  const onDisk = loadProfile(guard, target);
806
805
  const same = JSON.stringify(onDisk) === JSON.stringify(replayed.doc);
807
- fs7.rmSync(tmp, { force: true });
808
806
  return {
809
807
  ok: same,
810
808
  truncatedTail: replayed.truncatedTail,
@@ -813,7 +811,7 @@ function rebuildProfile(guard, dataDir, opts = {}) {
813
811
  diffSummary: same ? "no diff" : "materialized view differs from journal replay"
814
812
  };
815
813
  }
816
- fs7.renameSync(tmp, target);
814
+ saveJson(guard, target, replayed.doc);
817
815
  if (replayed.truncatedTail > 0) {
818
816
  writeText(
819
817
  guard,
@@ -1015,4 +1013,4 @@ export {
1015
1013
  describeInstall,
1016
1014
  presetStatus
1017
1015
  };
1018
- //# sourceMappingURL=chunk-5TNGUHIR.js.map
1016
+ //# sourceMappingURL=chunk-TFMQKETS.js.map