@kanadego/dsh-heartbeat 1.8.0 → 1.9.0
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 +20 -4
- package/config/policy.json +73 -72
- package/dist/{chunk-SQJLKP7L.js → chunk-2SYXKKZF.js} +145 -37
- package/dist/chunk-2SYXKKZF.js.map +1 -0
- package/dist/{chunk-UCJR6TC7.js → chunk-IPOZFZPY.js} +28 -14
- package/dist/chunk-IPOZFZPY.js.map +1 -0
- package/dist/{chunk-MXJPX3FM.js → chunk-PQTSXW4F.js} +37 -8
- package/dist/chunk-PQTSXW4F.js.map +1 -0
- package/dist/cli/index.js +14 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +387 -174
- package/dist/index.js.map +1 -1
- package/dist/{migrate-43PUK25G.js → migrate-N6NXMZ3G.js} +2 -2
- package/dist/{pool-JQC5JWIZ.js → pool-7I4VACG2.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-MXJPX3FM.js.map +0 -1
- package/dist/chunk-SQJLKP7L.js.map +0 -1
- package/dist/chunk-UCJR6TC7.js.map +0 -1
- /package/dist/{migrate-43PUK25G.js.map → migrate-N6NXMZ3G.js.map} +0 -0
- /package/dist/{pool-JQC5JWIZ.js.map → pool-7I4VACG2.js.map} +0 -0
package/README.md
CHANGED
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
- **状态栏与时间感知**:心跳把"此刻在干什么"(在闲逛 / 看到你在忙 / 在场待着…)注入日常会话——支持动态系统提示词的模型常驻系统提示词(KV-cache 安全追加),其余模型只在用户发起轮次时带一行。精确时间按 20–30 分钟节流注入,任务进行中绝不中途插话。
|
|
70
70
|
- **视觉感知(可选)**:每跳对全屏截图做一次视觉识别(经 [ModLens](https://github.com/liustack/modlens) 外接多模态模型),投递的素材包会带一句「(他此刻大概在:…)」;识别失败时画面与窗口标题一概不进任何提示词——隐私优先,宁可不说。
|
|
71
71
|
- **兴趣闲逛**:按你定义的兴趣范围轮换挑焦点(同一条 3 天冷却),用 `web_search` 看新东西(每次 3~9 轮搜索),结果由代码登记入素材池——模型只负责搜索筛选,不做任何写盘动作。话题库存见底(≤4 条)时自动触发补货闲逛(每天至多 2 次)。
|
|
72
|
-
- **素材池(话题 / 聊天双仓)**:话题库存 16 条 + 聊天素材 14 条,四条确定性淘汰(TTL / 消费退休 / 冷板凳 / 容量挤出);聊天种子来自画像合并时从对话里挑出的"值得聊的话题",有时效性——投递过 1
|
|
73
|
-
- **反刍投递(目标会话拍板)**:代码先凑好候选(话题随机 4 + 聊天最新 2,互补至多 6 条),引擎室挑 ≤3 条各压缩成一句,拼成素材包注入你读的会话——附一句「他此刻大概在:…」(视觉识别成功时)+声明「这是心跳插件素材投递」+素材清单(必要时附「也可以说一句真心话」)。说不说、说哪条,由目标会话里的 agent
|
|
72
|
+
- **素材池(话题 / 聊天双仓)**:话题库存 16 条 + 聊天素材 14 条,四条确定性淘汰(TTL / 消费退休 / 冷板凳 / 容量挤出);聊天种子来自画像合并时从对话里挑出的"值得聊的话题",有时效性——投递过 1 次即退休。归档区也有上限(默认 50 条),超出按**最老优先**裁掉;新增 / 合并 / 淘汰 / 裁剪 / 恢复 / 删除都会写进审计日志(只记编号与元数据,不记素材正文)。是缓存不是记忆,与画像单向隔离。
|
|
73
|
+
- **反刍投递(目标会话拍板)**:代码先凑好候选(话题随机 4 + 聊天最新 2,互补至多 6 条),引擎室挑 ≤3 条各压缩成一句,拼成素材包注入你读的会话——附一句「他此刻大概在:…」(视觉识别成功时)+声明「这是心跳插件素材投递」+素材清单(必要时附「也可以说一句真心话」)。说不说、说哪条,由目标会话里的 agent 按当下处境自己判断;话落到你眼前的会话后,agent 会**按本次投递编号报账**——哪几条被用上、是引用素材还是顺着自己发挥,都如实记下。
|
|
74
74
|
- **用户画像**:本地结构化档案(兴趣 / 项目 / 沟通偏好 / 心理基线),带来源与置信度,稳定特质不因时间遗忘;journal 单一权威,可校验、可重建、可解密导出。
|
|
75
75
|
- **会话绑定**:把心跳消息投递到你常用的会话(agent 会"到你房间里说话"),或观察会话内容喂养画像——投递/观察是两个独立开关,可双开。
|
|
76
76
|
- **周报**:每 7 天一份,只报道这周干了些什么——表达与静默、新认识、账本、素材池、活跃高峰。由后台「心跳」署名写成平实中性的一句话档案,DPAPI 加密落盘,卡片可翻。
|
|
@@ -146,7 +146,7 @@ node <插件目录>/dist/cli/index.js <命令>
|
|
|
146
146
|
## 安全
|
|
147
147
|
|
|
148
148
|
- **路径守卫**:全部落盘经过守卫规范化比对,`..` 穿越、符号链接、8.3 短名、UNC 一律拒绝;`data/` 是唯一可写目录。
|
|
149
|
-
- **模型工具硬限制**:心跳 agent 的工具面被钉死为**只有 `web_search`**——没有 shell、文件系统、子代理、技能目录。闲逛轮之外的模型调用(决策 /
|
|
149
|
+
- **模型工具硬限制**:心跳 agent 的工具面被钉死为**只有 `web_search`**——没有 shell、文件系统、子代理、技能目录。闲逛轮之外的模型调用(决策 / 合并)**零工具**。账本与素材报账这两个工具注册在**全局层**(你的日常会话里可用),心跳自己的轮次看不到它们。
|
|
150
150
|
- **建议 · 防注入**:网页内容应视为数据而非指令(闲逛提示词中明示);画像裁决 prompt 同理(观察内容是数据,不应改写人格设定)。这是推荐的做法,是否采纳由使用者自行决定,并非安装即强制生效。
|
|
151
151
|
- **模型出库边界**:画像合并调用走宿主模型通道(插件不持有任何云凭证),只送非 psy 分区的条目字段与 ≤1 句观察引语——不送 psy、不送对话原文、不送证据全量。
|
|
152
152
|
- **一键焚毁**:`burn`(预演)→ `burn --yes`(运行时数据覆写 x3 后删除,用户设置保留)→ `burn --all`(连设置归零)。
|
|
@@ -192,7 +192,7 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
|
|
|
192
192
|
|
|
193
193
|
| 插件版本 | 适配的 DSH |
|
|
194
194
|
|---|---|
|
|
195
|
-
| **v1.7.4 – v1.
|
|
195
|
+
| **v1.7.4 – v1.9.0**(当前) | `0.1.2-rc.1` – `0.2.0-rc.2` |
|
|
196
196
|
| v1.7.2 – v1.7.3 | `0.1.2-rc.1` – `0.2.0-rc.1` |
|
|
197
197
|
| v1.7.0 – v1.7.1 | `0.1.2-rc.1` – `0.1.7-rc.2` |
|
|
198
198
|
| v1.2.x – v1.6.4 | `0.1.2-rc.1` – `0.1.5-rc.2` |
|
|
@@ -206,6 +206,22 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
|
|
|
206
206
|
## 版本更新
|
|
207
207
|
|
|
208
208
|
<details open>
|
|
209
|
+
<summary><b>v1.9.x</b></summary>
|
|
210
|
+
|
|
211
|
+
### v1.9.0 · 2026-10-06(新功能:素材报账,归账不再靠猜)
|
|
212
|
+
|
|
213
|
+
- **素材报账**:投递素材后由目标会话的 agent **亲口报账**——这轮用了哪几条、用的是「素材本身」还是「顺着素材自己说的话」,都如实记下。此前归账靠事后拿文本做兜底匹配,漏记错记都躲不掉;现在报账带**本次投递编号**核对,不会串到别轮。工具注册在全局层,日常会话里也能用。
|
|
214
|
+
- **话题偏好加权**:每条话题按「被采纳率」算权重(30 天半衰期,冷启动中性),闲逛挑话题时自动偏向你真接得住的那些,接不住的慢慢退到后面;**14 天没被碰过的老话题有硬地板**优先轮换,不会被高权重话题永远压在下面。
|
|
215
|
+
- **报账缺失可见**:真没等到报账的投递,周报里单独报一行,不再靠「静默次数」糊过去;这类投递不计入偏好统计,免得把「没记账」算成「发了没人要」。
|
|
216
|
+
- **素材池审计**:新增 / 合并 / 淘汰 / 归档裁剪 / 恢复 / 删除,全部写进 `data/logs/heartbeat.jsonl`(`seed_added`、`seed_retired`、`seed_archive_trimmed`、`seed_restored`、`seed_deleted`)。只记 id 与元数据,**不记素材正文**——那本日志是明文,正文来自对话,不该落到明文里。
|
|
217
|
+
- **归档区上限**:素材归档区超过 `seeds.archiveCap`(默认 50)时按**最老优先**裁掉,不再无限增长;CLI `seeds gc` 会打印裁剪条数。
|
|
218
|
+
- **迁移容器 KDF 参数化**:加密容器现在把 KDF 强度记进容器、解密封装按其解密并做范围校验(此前写死,哪天调强就会把旧容器误报成「密码错」)。**旧容器不受影响,照常解**。
|
|
219
|
+
- **周报锚定**:装好当周不再立刻出一份空报,而是记下起点、满 7 天出第一期——行为与说明文档一致。
|
|
220
|
+
- 修复:报账里的素材编号带方括号 / 引号(如 `[s3]`)现在能正确识别;报账工具写盘超时从 5s 放宽到 15s(DPAPI 单次可能到 2.7s,原来偏紧);闲置时的兜底素材改用画像里的真实条目 id,不再是占位编号。
|
|
221
|
+
|
|
222
|
+
</details>
|
|
223
|
+
|
|
224
|
+
<details>
|
|
209
225
|
<summary><b>v1.8.x</b></summary>
|
|
210
226
|
|
|
211
227
|
### v1.8.0 · 2026-10-05(新功能:周报 + 账本工具)
|
package/config/policy.json
CHANGED
|
@@ -1,72 +1,73 @@
|
|
|
1
|
-
{
|
|
2
|
-
"heartbeat": {
|
|
3
|
-
"intervalMin": 20,
|
|
4
|
-
"idleMode": false,
|
|
5
|
-
"archiveRotatedHome": true
|
|
6
|
-
},
|
|
7
|
-
"gate": {
|
|
8
|
-
"maxDailySend": 3,
|
|
9
|
-
"cooldownMinutes": 30,
|
|
10
|
-
"quietHours": {
|
|
11
|
-
"start": "01:00",
|
|
12
|
-
"end": "08:00"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"browse": {
|
|
16
|
-
"windows": [
|
|
17
|
-
{
|
|
18
|
-
"start": "11:00",
|
|
19
|
-
"end": "15:00"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"start": "17:00",
|
|
23
|
-
"end": "21:00"
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"minIntervalHours": 4,
|
|
27
|
-
"maxSeedsPerVisit": 2
|
|
28
|
-
},
|
|
29
|
-
"seeds": {
|
|
30
|
-
"maxActive": 30,
|
|
31
|
-
"ttlDays": {
|
|
32
|
-
"news": 3,
|
|
33
|
-
"fandom": 14,
|
|
34
|
-
"scene": 60,
|
|
35
|
-
"promise": 90
|
|
36
|
-
},
|
|
37
|
-
"coldBenchDays": 21,
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"heartbeat": {
|
|
3
|
+
"intervalMin": 20,
|
|
4
|
+
"idleMode": false,
|
|
5
|
+
"archiveRotatedHome": true
|
|
6
|
+
},
|
|
7
|
+
"gate": {
|
|
8
|
+
"maxDailySend": 3,
|
|
9
|
+
"cooldownMinutes": 30,
|
|
10
|
+
"quietHours": {
|
|
11
|
+
"start": "01:00",
|
|
12
|
+
"end": "08:00"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"browse": {
|
|
16
|
+
"windows": [
|
|
17
|
+
{
|
|
18
|
+
"start": "11:00",
|
|
19
|
+
"end": "15:00"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"start": "17:00",
|
|
23
|
+
"end": "21:00"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"minIntervalHours": 4,
|
|
27
|
+
"maxSeedsPerVisit": 2
|
|
28
|
+
},
|
|
29
|
+
"seeds": {
|
|
30
|
+
"maxActive": 30,
|
|
31
|
+
"ttlDays": {
|
|
32
|
+
"news": 3,
|
|
33
|
+
"fandom": 14,
|
|
34
|
+
"scene": 60,
|
|
35
|
+
"promise": 90
|
|
36
|
+
},
|
|
37
|
+
"coldBenchDays": 21,
|
|
38
|
+
"archiveCap": 50,
|
|
39
|
+
"retireAfterUsed": 2,
|
|
40
|
+
"scoreWeights": {
|
|
41
|
+
"freshness": 0.4,
|
|
42
|
+
"unused": 0.3,
|
|
43
|
+
"confidence": 0.3
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"profile": {
|
|
47
|
+
"consolidation": {
|
|
48
|
+
"minIntervalHours": 12,
|
|
49
|
+
"inboxBacklog": 30
|
|
50
|
+
},
|
|
51
|
+
"partitionCap": 50,
|
|
52
|
+
"maxOpsPerRun": 10,
|
|
53
|
+
"confidenceCap": {
|
|
54
|
+
"chat": 0.6,
|
|
55
|
+
"screen": 0.4,
|
|
56
|
+
"browse": 0.4
|
|
57
|
+
},
|
|
58
|
+
"volatileDays": 14,
|
|
59
|
+
"stableLowActivityDays": 180,
|
|
60
|
+
"psyEnabled": false
|
|
61
|
+
},
|
|
62
|
+
"observe": {
|
|
63
|
+
"maxChars": 80,
|
|
64
|
+
"perBeat": 10
|
|
65
|
+
},
|
|
66
|
+
"weekly": {
|
|
67
|
+
"enabled": true
|
|
68
|
+
},
|
|
69
|
+
"retention": {
|
|
70
|
+
"envPulseHours": 48,
|
|
71
|
+
"decisionLogDays": 30
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
loadPool,
|
|
4
4
|
normalizeCategory,
|
|
5
5
|
seedsFilePath
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-PQTSXW4F.js";
|
|
7
7
|
import {
|
|
8
8
|
atomicWriteJsonSync
|
|
9
9
|
} from "./chunk-VIZNIQLK.js";
|
|
@@ -122,6 +122,7 @@ function assertPolicy(input) {
|
|
|
122
122
|
if (typeof s.ttlDays[k] !== "number") fail(`seeds.ttlDays.${k} missing`);
|
|
123
123
|
}
|
|
124
124
|
if (typeof s.coldBenchDays !== "number") fail("seeds.coldBenchDays missing");
|
|
125
|
+
if (typeof s.archiveCap !== "number" || s.archiveCap < 1) fail("seeds.archiveCap must be >= 1");
|
|
125
126
|
if (typeof s.retireAfterUsed !== "number" || s.retireAfterUsed < 1) fail("seeds.retireAfterUsed must be >= 1");
|
|
126
127
|
if (!isPlainObject(s.scoreWeights)) fail("seeds.scoreWeights missing");
|
|
127
128
|
const pr = p.profile;
|
|
@@ -270,14 +271,93 @@ function pendingOlderThan(guard, file, days, now = Date.now()) {
|
|
|
270
271
|
|
|
271
272
|
// src/browse/browse.ts
|
|
272
273
|
import fs3 from "fs";
|
|
274
|
+
import path5 from "path";
|
|
275
|
+
|
|
276
|
+
// src/browse/preference.ts
|
|
273
277
|
import path4 from "path";
|
|
278
|
+
var DAY_MS2 = 864e5;
|
|
279
|
+
var W_MIN = 0.5;
|
|
280
|
+
var W_MAX = 2;
|
|
281
|
+
var SMOOTH_ADOPTED = 1;
|
|
282
|
+
var SMOOTH_DELIVERED = 4;
|
|
283
|
+
var DECAY_HALF_LIFE_DAYS = 30;
|
|
284
|
+
function preferenceFilePath(dataDir) {
|
|
285
|
+
return path4.join(dataDir, "preference.json");
|
|
286
|
+
}
|
|
287
|
+
function num(v) {
|
|
288
|
+
const n = Number(v);
|
|
289
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
290
|
+
}
|
|
291
|
+
function sanitizeTopicPref(raw) {
|
|
292
|
+
if (!raw || typeof raw !== "object") return null;
|
|
293
|
+
const t = raw;
|
|
294
|
+
if (!Number.isFinite(Number(t.delivered)) || !Number.isFinite(Number(t.adopted))) return null;
|
|
295
|
+
const delivered = Math.floor(num(t.delivered));
|
|
296
|
+
return {
|
|
297
|
+
delivered,
|
|
298
|
+
adopted: Math.min(delivered, Math.floor(num(t.adopted))),
|
|
299
|
+
lastDeliveredAt: num(t.lastDeliveredAt),
|
|
300
|
+
lastAdoptedAt: num(t.lastAdoptedAt)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function loadPreference(guard, file) {
|
|
304
|
+
const doc = loadJson(guard, file);
|
|
305
|
+
if (!doc || typeof doc !== "object" || !doc.topics || typeof doc.topics !== "object") {
|
|
306
|
+
return { version: 1, topics: {}, updatedAt: 0 };
|
|
307
|
+
}
|
|
308
|
+
const topics = {};
|
|
309
|
+
for (const [key, raw] of Object.entries(doc.topics)) {
|
|
310
|
+
const t = sanitizeTopicPref(raw);
|
|
311
|
+
if (key && t) topics[key] = t;
|
|
312
|
+
}
|
|
313
|
+
const updatedAt = Number(doc.updatedAt);
|
|
314
|
+
return { version: 1, topics, updatedAt: Number.isFinite(updatedAt) ? updatedAt : 0 };
|
|
315
|
+
}
|
|
316
|
+
function savePreference(guard, file, state) {
|
|
317
|
+
saveJson(guard, file, state);
|
|
318
|
+
}
|
|
319
|
+
function recordDelivery(guard, file, opts) {
|
|
320
|
+
const state = loadPreference(guard, file);
|
|
321
|
+
const adopted = new Set(opts.adoptedTopics);
|
|
322
|
+
for (const topic of opts.offeredTopics) {
|
|
323
|
+
if (!topic) continue;
|
|
324
|
+
const t = state.topics[topic] ?? { delivered: 0, adopted: 0, lastDeliveredAt: 0, lastAdoptedAt: 0 };
|
|
325
|
+
t.delivered += 1;
|
|
326
|
+
t.lastDeliveredAt = opts.now;
|
|
327
|
+
if (adopted.has(topic)) {
|
|
328
|
+
t.adopted += 1;
|
|
329
|
+
t.lastAdoptedAt = opts.now;
|
|
330
|
+
}
|
|
331
|
+
state.topics[topic] = t;
|
|
332
|
+
}
|
|
333
|
+
state.updatedAt = opts.now;
|
|
334
|
+
savePreference(guard, file, state);
|
|
335
|
+
return state;
|
|
336
|
+
}
|
|
337
|
+
function decayed(t, now) {
|
|
338
|
+
const dAgeDays = Math.max(0, now - t.lastDeliveredAt) / DAY_MS2;
|
|
339
|
+
const aAgeDays = Math.max(0, now - t.lastAdoptedAt) / DAY_MS2;
|
|
340
|
+
const dHalf = Math.pow(0.5, dAgeDays / DECAY_HALF_LIFE_DAYS);
|
|
341
|
+
const aHalf = Math.pow(0.5, aAgeDays / DECAY_HALF_LIFE_DAYS);
|
|
342
|
+
return { delivered: t.delivered * dHalf, adopted: t.adopted * aHalf };
|
|
343
|
+
}
|
|
344
|
+
function preferenceWeight(state, topic, now) {
|
|
345
|
+
const t = state.topics[topic];
|
|
346
|
+
const rate = t ? (() => {
|
|
347
|
+
const d = decayed(t, now);
|
|
348
|
+
return (d.adopted + SMOOTH_ADOPTED) / (d.delivered + SMOOTH_DELIVERED);
|
|
349
|
+
})() : SMOOTH_ADOPTED / SMOOTH_DELIVERED;
|
|
350
|
+
return Math.min(W_MAX, Math.max(W_MIN, rate / (SMOOTH_ADOPTED / SMOOTH_DELIVERED)));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/browse/browse.ts
|
|
274
354
|
var WATCH_THROTTLE_MS = 6 * 36e5;
|
|
275
355
|
var UA = { "User-Agent": "dsh-heartbeat/2.0 (+local; personal companion)" };
|
|
276
356
|
function emptyBrowseState() {
|
|
277
357
|
return { targets: {}, last_check_at: 0, wander: { focusHistory: {}, focusCount: {}, last_wander_at: 0, refillCount: {} } };
|
|
278
358
|
}
|
|
279
359
|
function browseStatePath(paths) {
|
|
280
|
-
return
|
|
360
|
+
return path5.join(paths.dataDir, "browse.json");
|
|
281
361
|
}
|
|
282
362
|
function readJsonFile(file, fallback) {
|
|
283
363
|
try {
|
|
@@ -287,14 +367,14 @@ function readJsonFile(file, fallback) {
|
|
|
287
367
|
}
|
|
288
368
|
}
|
|
289
369
|
function loadInterests(paths) {
|
|
290
|
-
const userPath =
|
|
370
|
+
const userPath = path5.join(paths.settingsDir, "interests.json");
|
|
291
371
|
if (fs3.existsSync(userPath)) return readJsonFile(userPath, { interests: [], _schedule: {} });
|
|
292
|
-
return readJsonFile(
|
|
372
|
+
return readJsonFile(path5.join(paths.configDir, "interests.json"), { interests: [], _schedule: {} });
|
|
293
373
|
}
|
|
294
374
|
function loadWatchlist(paths) {
|
|
295
|
-
const userPath =
|
|
375
|
+
const userPath = path5.join(paths.settingsDir, "watchlist.json");
|
|
296
376
|
if (fs3.existsSync(userPath)) return readJsonFile(userPath, { targets: [] });
|
|
297
|
-
return readJsonFile(
|
|
377
|
+
return readJsonFile(path5.join(paths.configDir, "watchlist.json"), { targets: [] });
|
|
298
378
|
}
|
|
299
379
|
function loadState(guard, paths) {
|
|
300
380
|
return loadJson(guard, browseStatePath(paths)) ?? emptyBrowseState();
|
|
@@ -362,12 +442,24 @@ function onCooldown(state, focus, cooldownDays, now) {
|
|
|
362
442
|
const last = state.wander.focusHistory[focus] ?? 0;
|
|
363
443
|
return last > now - cooldownDays * 864e5;
|
|
364
444
|
}
|
|
365
|
-
|
|
445
|
+
var STARVATION_DAYS = 14;
|
|
446
|
+
function pickFocus(state, interests, now, pref) {
|
|
366
447
|
const sc = interests._schedule ?? {};
|
|
367
448
|
const cooldown = sc.focus_cooldown_days ?? 3;
|
|
368
449
|
const pool = (interests.interests ?? []).filter((t) => !onCooldown(state, t, cooldown, now));
|
|
369
450
|
if (pool.length === 0) return null;
|
|
370
|
-
|
|
451
|
+
if (!pref) {
|
|
452
|
+
pool.sort((a, b) => (state.wander.focusHistory[a] ?? 0) - (state.wander.focusHistory[b] ?? 0));
|
|
453
|
+
return pool[0];
|
|
454
|
+
}
|
|
455
|
+
const age = (t) => now - (state.wander.focusHistory[t] ?? 0);
|
|
456
|
+
const starved = pool.filter((t) => age(t) >= STARVATION_DAYS * 864e5);
|
|
457
|
+
if (starved.length > 0) {
|
|
458
|
+
starved.sort((a, b) => age(b) - age(a));
|
|
459
|
+
return starved[0];
|
|
460
|
+
}
|
|
461
|
+
const effAge = (t) => age(t) * preferenceWeight(pref, t, now);
|
|
462
|
+
pool.sort((a, b) => effAge(b) - effAge(a));
|
|
371
463
|
return pool[0];
|
|
372
464
|
}
|
|
373
465
|
function adviseWander(guard, paths, policy, now = /* @__PURE__ */ new Date()) {
|
|
@@ -383,7 +475,7 @@ function adviseWander(guard, paths, policy, now = /* @__PURE__ */ new Date()) {
|
|
|
383
475
|
if (now.getTime() - state.wander.last_wander_at < minGap) {
|
|
384
476
|
return { focus: null, query: null, skipped: "min-interval" };
|
|
385
477
|
}
|
|
386
|
-
const focus = pickFocus(state, interests, now.getTime());
|
|
478
|
+
const focus = pickFocus(state, interests, now.getTime(), loadPreference(guard, preferenceFilePath(paths.dataDir)));
|
|
387
479
|
if (!focus) return { focus: null, query: null, skipped: "no-focus" };
|
|
388
480
|
return { focus, query: `${focus} 2026 \u6700\u65B0`, skipped: null };
|
|
389
481
|
}
|
|
@@ -422,7 +514,7 @@ function adviseRefillWander(guard, paths, policy, now = /* @__PURE__ */ new Date
|
|
|
422
514
|
return { focus: null, query: null, skipped: `topic-stock-ok(${topicCount})`, topicCount, refillsToday };
|
|
423
515
|
}
|
|
424
516
|
const interests = loadInterests(paths);
|
|
425
|
-
const focus = pickFocus(state, interests, now.getTime());
|
|
517
|
+
const focus = pickFocus(state, interests, now.getTime(), loadPreference(guard, preferenceFilePath(paths.dataDir)));
|
|
426
518
|
if (!focus) {
|
|
427
519
|
return { focus: null, query: null, skipped: "no-focus", topicCount, refillsToday };
|
|
428
520
|
}
|
|
@@ -434,11 +526,11 @@ function browseStatus(guard, paths) {
|
|
|
434
526
|
|
|
435
527
|
// src/notify/notify.ts
|
|
436
528
|
import { spawnSync } from "child_process";
|
|
437
|
-
import
|
|
529
|
+
import path6 from "path";
|
|
438
530
|
function runNotify(paths, args) {
|
|
439
531
|
const r = spawnSync(
|
|
440
532
|
"powershell.exe",
|
|
441
|
-
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File",
|
|
533
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path6.join(paths.assetsDir, "notify.ps1"), ...args],
|
|
442
534
|
{ timeout: 2e4, encoding: "utf8" }
|
|
443
535
|
);
|
|
444
536
|
return { status: r.status ?? -1, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
|
|
@@ -460,13 +552,13 @@ function sendWeeklyReadyHint(paths) {
|
|
|
460
552
|
|
|
461
553
|
// src/weekly/report.ts
|
|
462
554
|
import fs4 from "fs";
|
|
463
|
-
import
|
|
555
|
+
import path7 from "path";
|
|
464
556
|
var WEEKLY_INTERVAL_MS = 7 * 864e5;
|
|
465
557
|
function weeklyDirPath(dataDir) {
|
|
466
|
-
return
|
|
558
|
+
return path7.join(dataDir, "weekly");
|
|
467
559
|
}
|
|
468
560
|
function stateFilePath(dataDir) {
|
|
469
|
-
return
|
|
561
|
+
return path7.join(weeklyDirPath(dataDir), "state.json");
|
|
470
562
|
}
|
|
471
563
|
function reportFileName(endIso) {
|
|
472
564
|
return `report-${endIso.slice(0, 10)}.json`;
|
|
@@ -483,10 +575,16 @@ function weeklyDue(guard, dataDir, now = Date.now()) {
|
|
|
483
575
|
const last = lastWeeklyGeneratedAt(guard, dataDir);
|
|
484
576
|
return now - last >= WEEKLY_INTERVAL_MS;
|
|
485
577
|
}
|
|
578
|
+
function ensureWeeklyAnchor(guard, dataDir, now = Date.now()) {
|
|
579
|
+
if (lastWeeklyGeneratedAt(guard, dataDir) > 0) return false;
|
|
580
|
+
fs4.mkdirSync(weeklyDirPath(dataDir), { recursive: true });
|
|
581
|
+
atomicWriteJsonSync(stateFilePath(dataDir), { lastGeneratedAt: now });
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
486
584
|
function saveWeeklyReport(guard, dataDir, report) {
|
|
487
585
|
const dir = weeklyDirPath(dataDir);
|
|
488
586
|
fs4.mkdirSync(dir, { recursive: true });
|
|
489
|
-
const file =
|
|
587
|
+
const file = path7.join(dir, reportFileName(report.end));
|
|
490
588
|
saveEncryptedText(guard, file, JSON.stringify(report, null, 1));
|
|
491
589
|
atomicWriteJsonSync(stateFilePath(dataDir), { lastGeneratedAt: Date.parse(report.generatedAt) });
|
|
492
590
|
return file;
|
|
@@ -507,7 +605,7 @@ function listWeeklyReports(guard, dataDir) {
|
|
|
507
605
|
function readWeeklyReport(guard, dataDir, file) {
|
|
508
606
|
if (!/^report-\d{4}-\d{2}-\d{2}\.json$/.test(file)) return null;
|
|
509
607
|
try {
|
|
510
|
-
const raw = loadEncryptedText(guard,
|
|
608
|
+
const raw = loadEncryptedText(guard, path7.join(weeklyDirPath(dataDir), file));
|
|
511
609
|
if (!raw) return null;
|
|
512
610
|
const rep = JSON.parse(raw);
|
|
513
611
|
if (typeof rep.text !== "string") return null;
|
|
@@ -555,6 +653,13 @@ function renderTemplateReport(facts) {
|
|
|
555
653
|
if (facts.seeds.waiting.length > 0) {
|
|
556
654
|
lines.push(`\u8FD8\u6CA1\u804A\u8FC7\u7684\u7D20\u6750\uFF1A${facts.seeds.waiting.join("\u3001")}\u3002`);
|
|
557
655
|
}
|
|
656
|
+
if (facts.reports.total > 0) {
|
|
657
|
+
const reasonPart = facts.reports.reasons.length > 0 ? `\uFF08${facts.reports.reasons.map((r) => `${r.reason} ${r.count} \u6B21`).join("\u3001")}\uFF09` : "";
|
|
658
|
+
lines.push(`\u6295\u9012\u62A5\u8D26 ${facts.reports.total} \u6B21\uFF1A\u7528\u7D20\u6750 ${facts.reports.material}\u3001\u8BF4\u771F\u5FC3\u8BDD ${facts.reports.heartfelt}\u3001\u6CA1\u8BF4\u8BDD ${facts.reports.silent}${reasonPart}\u3002`);
|
|
659
|
+
}
|
|
660
|
+
if (facts.reports.missing > 0) {
|
|
661
|
+
lines.push(`\u53E6\u6709 ${facts.reports.missing} \u6B21\u6295\u9012\u6CA1\u7B49\u5230\u62A5\u8D26\uFF08\u8FD9\u90E8\u5206\u6CA1\u7B97\u8FDB\u504F\u597D\u7EDF\u8BA1\uFF09\u3002`);
|
|
662
|
+
}
|
|
558
663
|
if (facts.peakHours.length > 0) {
|
|
559
664
|
lines.push(`\u6D3B\u8DC3\u9AD8\u5CF0\uFF1A${facts.peakHours.join("\u3001")}\u3002`);
|
|
560
665
|
}
|
|
@@ -564,7 +669,7 @@ function renderTemplateReport(facts) {
|
|
|
564
669
|
// src/core/preset-install.ts
|
|
565
670
|
import fs5 from "fs";
|
|
566
671
|
import os from "os";
|
|
567
|
-
import
|
|
672
|
+
import path8 from "path";
|
|
568
673
|
import { fileURLToPath } from "url";
|
|
569
674
|
var COMPOSITION_FILE = "agent.cordis.yml";
|
|
570
675
|
var METADATA_FILE = "preset.yml";
|
|
@@ -572,14 +677,14 @@ var BUNDLED_PRESET_ID = "heartbeat";
|
|
|
572
677
|
function bundledPresetDir(moduleUrl, id = BUNDLED_PRESET_ID) {
|
|
573
678
|
let dir;
|
|
574
679
|
try {
|
|
575
|
-
dir =
|
|
680
|
+
dir = path8.dirname(fileURLToPath(moduleUrl));
|
|
576
681
|
} catch {
|
|
577
682
|
return void 0;
|
|
578
683
|
}
|
|
579
684
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
580
|
-
const candidate =
|
|
581
|
-
if (fs5.existsSync(
|
|
582
|
-
const parent =
|
|
685
|
+
const candidate = path8.join(dir, "assets", "presets", id);
|
|
686
|
+
if (fs5.existsSync(path8.join(candidate, COMPOSITION_FILE))) return candidate;
|
|
687
|
+
const parent = path8.dirname(dir);
|
|
583
688
|
if (parent === dir) break;
|
|
584
689
|
dir = parent;
|
|
585
690
|
}
|
|
@@ -589,12 +694,12 @@ function userPresetRoot(roots) {
|
|
|
589
694
|
const found = roots?.find(
|
|
590
695
|
(root) => root?.trust === "user" && typeof root.path === "string" && root.path.length > 0
|
|
591
696
|
);
|
|
592
|
-
return found?.path === void 0 ? void 0 :
|
|
697
|
+
return found?.path === void 0 ? void 0 : path8.resolve(found.path);
|
|
593
698
|
}
|
|
594
699
|
function conventionalUserPresetRoot(env = process.env, home = os.homedir()) {
|
|
595
700
|
const override = env.DSH_HOME?.trim();
|
|
596
|
-
const root = override && override.length > 0 ? override :
|
|
597
|
-
return
|
|
701
|
+
const root = override && override.length > 0 ? override : path8.join(home, ".dsh");
|
|
702
|
+
return path8.join(root, ".agent-presets");
|
|
598
703
|
}
|
|
599
704
|
function installBundledPreset(options) {
|
|
600
705
|
const id = options.id && options.id.length > 0 ? options.id : BUNDLED_PRESET_ID;
|
|
@@ -626,12 +731,12 @@ function installBundledPreset(options) {
|
|
|
626
731
|
detail: "the roster mounts no user preset root (includeUserRoot=false)"
|
|
627
732
|
};
|
|
628
733
|
}
|
|
629
|
-
const dir =
|
|
630
|
-
const composition =
|
|
734
|
+
const dir = path8.join(root, id);
|
|
735
|
+
const composition = path8.join(dir, COMPOSITION_FILE);
|
|
631
736
|
try {
|
|
632
737
|
if (fs5.existsSync(composition)) {
|
|
633
738
|
if (options.force !== true) {
|
|
634
|
-
const drifted = !sameBytes(composition,
|
|
739
|
+
const drifted = !sameBytes(composition, path8.join(bundledDir, COMPOSITION_FILE));
|
|
635
740
|
return {
|
|
636
741
|
action: "exists",
|
|
637
742
|
id,
|
|
@@ -640,7 +745,7 @@ function installBundledPreset(options) {
|
|
|
640
745
|
detail: drifted ? "kept as-is (differs from the bundled template)" : "kept as-is"
|
|
641
746
|
};
|
|
642
747
|
}
|
|
643
|
-
fs5.copyFileSync(
|
|
748
|
+
fs5.copyFileSync(path8.join(bundledDir, COMPOSITION_FILE), composition);
|
|
644
749
|
return {
|
|
645
750
|
action: "restored",
|
|
646
751
|
id,
|
|
@@ -651,9 +756,9 @@ function installBundledPreset(options) {
|
|
|
651
756
|
}
|
|
652
757
|
const existed = fs5.existsSync(dir);
|
|
653
758
|
fs5.mkdirSync(dir, { recursive: true });
|
|
654
|
-
fs5.copyFileSync(
|
|
655
|
-
const metadata =
|
|
656
|
-
if (!fs5.existsSync(metadata)) fs5.copyFileSync(
|
|
759
|
+
fs5.copyFileSync(path8.join(bundledDir, COMPOSITION_FILE), composition);
|
|
760
|
+
const metadata = path8.join(dir, METADATA_FILE);
|
|
761
|
+
if (!fs5.existsSync(metadata)) fs5.copyFileSync(path8.join(bundledDir, METADATA_FILE), metadata);
|
|
657
762
|
return {
|
|
658
763
|
action: existed ? "repaired" : "created",
|
|
659
764
|
id,
|
|
@@ -671,16 +776,16 @@ function describeInstall(result) {
|
|
|
671
776
|
return `preset ${result.id} ${result.action}${where}${why}`;
|
|
672
777
|
}
|
|
673
778
|
function presetStatus(moduleUrl, id = BUNDLED_PRESET_ID, root = conventionalUserPresetRoot()) {
|
|
674
|
-
const dir =
|
|
779
|
+
const dir = path8.join(root, id);
|
|
675
780
|
const bundledDir = bundledPresetDir(moduleUrl, id);
|
|
676
|
-
const installed = fs5.existsSync(
|
|
781
|
+
const installed = fs5.existsSync(path8.join(dir, COMPOSITION_FILE));
|
|
677
782
|
return {
|
|
678
783
|
id,
|
|
679
784
|
dir,
|
|
680
785
|
...bundledDir === void 0 ? {} : { bundledDir },
|
|
681
786
|
installed,
|
|
682
|
-
compositionMatches: installed && bundledDir !== void 0 && sameBytes(
|
|
683
|
-
metadataMatches: bundledDir !== void 0 && fs5.existsSync(
|
|
787
|
+
compositionMatches: installed && bundledDir !== void 0 && sameBytes(path8.join(dir, COMPOSITION_FILE), path8.join(bundledDir, COMPOSITION_FILE)),
|
|
788
|
+
metadataMatches: bundledDir !== void 0 && fs5.existsSync(path8.join(dir, METADATA_FILE)) && sameBytes(path8.join(dir, METADATA_FILE), path8.join(bundledDir, METADATA_FILE))
|
|
684
789
|
};
|
|
685
790
|
}
|
|
686
791
|
function sameBytes(left, right) {
|
|
@@ -702,6 +807,8 @@ export {
|
|
|
702
807
|
markDone,
|
|
703
808
|
scanPending,
|
|
704
809
|
pendingOlderThan,
|
|
810
|
+
preferenceFilePath,
|
|
811
|
+
recordDelivery,
|
|
705
812
|
loadInterests,
|
|
706
813
|
loadWatchlist,
|
|
707
814
|
checkWatchlist,
|
|
@@ -714,6 +821,7 @@ export {
|
|
|
714
821
|
sendWeeklyReadyHint,
|
|
715
822
|
weeklyDirPath,
|
|
716
823
|
weeklyDue,
|
|
824
|
+
ensureWeeklyAnchor,
|
|
717
825
|
saveWeeklyReport,
|
|
718
826
|
listWeeklyReports,
|
|
719
827
|
readWeeklyReport,
|
|
@@ -726,4 +834,4 @@ export {
|
|
|
726
834
|
describeInstall,
|
|
727
835
|
presetStatus
|
|
728
836
|
};
|
|
729
|
-
//# sourceMappingURL=chunk-
|
|
837
|
+
//# sourceMappingURL=chunk-2SYXKKZF.js.map
|