dsh-galatea 0.4.8 → 0.5.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 -3
- package/lib/beat.js +6 -1
- package/lib/heartbeat-runner.js +5 -0
- package/lib/journal.js +9 -6
- package/lib/material.js +32 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
| 块 | 状态 |
|
|
14
14
|
|---|---|
|
|
15
15
|
| 能被 DSH 装起来 | ✅ 实测通过(dsh 0.2.2-rc.3) |
|
|
16
|
-
| 发布到 npm | ✅ 0.
|
|
16
|
+
| 发布到 npm | ✅ 0.5.0(GitHub Actions 自动发,不需要 token) |
|
|
17
17
|
| 记忆:读写 Markdown | ✅ 15 条测试 |
|
|
18
18
|
| 记忆:注入索引 | ✅ **已核实内容真的进了上下文**(不是在会话日志里看到「接上了没告警」) |
|
|
19
19
|
| 记忆:`remember` / `recall` | ✅ 23 条测试 |
|
|
@@ -203,6 +203,23 @@
|
|
|
203
203
|
(同理,日志里那句「有素材,这次没掷中」和「记忆里一条都没有」是两件事。
|
|
204
204
|
前者什么都别做,后者去记一条。)
|
|
205
205
|
|
|
206
|
+
### 它想说的话,劈成两半
|
|
207
|
+
|
|
208
|
+
每次「该说了」,账本里存两样东西:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
15:32 说了 — 太久没开口,这次强制说一句
|
|
212
|
+
本来想说:11 月 14 日 ·「土豆硬是咖喱放早了」
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- **say(A)**:能直接说给用户听的那一句。只有日期和标题,**原样引用**。
|
|
216
|
+
- **prompt(B)**:交给下一轮模型的一段指令 ——「这是你自己记下的一件事……要说的话,就围绕它」。它**不是给用户看的文本**。
|
|
217
|
+
|
|
218
|
+
为什么劈成两半:语气那一半才可能编。事实那一半是结构生成的,
|
|
219
|
+
抄错了随时能对回原条目;而编出来的句子,你没法指出它错在哪 —— 它每一句都像真的。
|
|
220
|
+
|
|
221
|
+
所以 A 由结构保证,B 只负责怎么说。两样都进账本,能并排看。
|
|
222
|
+
|
|
206
223
|
## 四条教训:粒度、上限、旧数据、清理的对象
|
|
207
224
|
|
|
208
225
|
### 一 · 天粒度和秒粒度不能比较
|
|
@@ -305,7 +322,7 @@ $DSH_HOME/galatea/heartbeat-state.json # 心跳状态账本
|
|
|
305
322
|
## 本地开发
|
|
306
323
|
|
|
307
324
|
```bash
|
|
308
|
-
npm test #
|
|
325
|
+
npm test # 116 条,不依赖 DSH,纯 node
|
|
309
326
|
```
|
|
310
327
|
|
|
311
328
|
装了之后验证它真的加载了,只看一个东西:
|
|
@@ -326,7 +343,7 @@ dsh plugin add dsh-galatea
|
|
|
326
343
|
或者在 profile 的 `package.json` 里手写:
|
|
327
344
|
|
|
328
345
|
```json
|
|
329
|
-
"dependencies": { "dsh-galatea": "0.
|
|
346
|
+
"dependencies": { "dsh-galatea": "0.5.0" }
|
|
330
347
|
```
|
|
331
348
|
|
|
332
349
|
**版本号钉死,不要写 `^`。** 同一个版本号指向两个不同的包,以后就说不清你装的是哪一份。
|
package/lib/beat.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 空话比不说更糟,它消耗的是「它真的在」那点信用。
|
|
15
15
|
*/
|
|
16
16
|
import { pickTarget } from './target.js';
|
|
17
|
-
import { pickMaterial, materialPrompt } from './material.js';
|
|
17
|
+
import { pickMaterial, materialPrompt, entryLine } from './material.js';
|
|
18
18
|
import { decide, DEFAULTS } from './heartbeat.js';
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -83,6 +83,10 @@ export function planBeat(input) {
|
|
|
83
83
|
speak: decision.speak,
|
|
84
84
|
reason: decision.reason,
|
|
85
85
|
sessionId: target.sessionId,
|
|
86
|
+
// A:能直接说给用户听的那一句。只有事实(日期 + 标题),可核对。
|
|
87
|
+
say: decision.speak ? entryLine(material.entry) : null,
|
|
88
|
+
// B:交给**下一轮的模型** —— 它读这段,决定怎么说。
|
|
89
|
+
// 注意 prompt 不是给用户看的文本,两者别混。
|
|
86
90
|
prompt: decision.speak ? materialPrompt(material.entry) : null,
|
|
87
91
|
switched: target.switched,
|
|
88
92
|
previous: target.previous,
|
|
@@ -96,6 +100,7 @@ export function planBeat(input) {
|
|
|
96
100
|
speak: false,
|
|
97
101
|
reason,
|
|
98
102
|
sessionId: extra.sessionId ?? null,
|
|
103
|
+
say: null,
|
|
99
104
|
prompt: null,
|
|
100
105
|
switched: extra.switched,
|
|
101
106
|
previous: extra.previous,
|
package/lib/heartbeat-runner.js
CHANGED
|
@@ -84,6 +84,11 @@ export function commitDecision(state, decision, now = new Date(), opts = {}) {
|
|
|
84
84
|
speak: decision.speak,
|
|
85
85
|
reason: decision.reason,
|
|
86
86
|
detail: decision.detail,
|
|
87
|
+
// A:本来要说给用户听的那一句。只有事实(日期 + 标题),可核对。
|
|
88
|
+
say: decision.say ?? null,
|
|
89
|
+
// B:交给**下一轮模型**的那段指令 —— 不是给用户看的文本。
|
|
90
|
+
// 两样都存,才能并排看「它想说什么」和「它打算怎么说」。
|
|
91
|
+
prompt: decision.prompt ?? null,
|
|
87
92
|
};
|
|
88
93
|
const next = {
|
|
89
94
|
...state,
|
package/lib/journal.js
CHANGED
|
@@ -70,12 +70,15 @@ export function renderJournal(state, opts = {}) {
|
|
|
70
70
|
// 早退会把 streak 吞掉,而那正是沉默和坏了唯一的分界。
|
|
71
71
|
lines.push('心跳还没有记录过。它至少跑过一次之后,这里才会有东西。');
|
|
72
72
|
} else {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
73
|
+
// 每条决定下面多一行「本来想说什么」。
|
|
74
|
+
//
|
|
75
|
+
// say(A)是**事实**:哪天记的、标题是什么,原样引用。
|
|
76
|
+
// 不印出来,「说了 / 没说」就只是一句判决 ——
|
|
77
|
+
// 用户看不到它到底想说什么,那和空账本没区别。
|
|
78
|
+
for (const d of ds.slice(-limit).reverse()) {
|
|
79
|
+
lines.push(`${clockOf(d.at)} ${d.speak ? '说了' : '没说'} — ${explain(d.reason)}`);
|
|
80
|
+
if (d.say) lines.push(` 本来想说:${d.say}`);
|
|
81
|
+
}
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
// 账本满了要说一声。
|
package/lib/material.js
CHANGED
|
@@ -80,3 +80,35 @@ export function materialPrompt(entry) {
|
|
|
80
80
|
'要说的话,就围绕它。不要复述标题,也不要念稿。',
|
|
81
81
|
].join('\n');
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 日期写成「11 月 14 日」。
|
|
86
|
+
*
|
|
87
|
+
* 同时吃 `2026-11-14`(文件名里那种)和完整的 ISO 时刻(mtime 那种)——
|
|
88
|
+
* 两种在系统里都真实存在,只认一种,另一种就会渲染成半句话。认不出就原样返回,不编。
|
|
89
|
+
*/
|
|
90
|
+
export function shortDate(value) {
|
|
91
|
+
const s = String(value ?? '');
|
|
92
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(s);
|
|
93
|
+
if (!m) return s;
|
|
94
|
+
return `${Number(m[2])} 月 ${Number(m[3])} 日`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* **A:可以原样说给用户听的那一句。**
|
|
99
|
+
*
|
|
100
|
+
* 只有事实,没有语气。日期和标题都原样引用,一个字不改。
|
|
101
|
+
*
|
|
102
|
+
* 为什么把「说什么」劈成 A 和 B 两半:
|
|
103
|
+
*
|
|
104
|
+
* 语气那一半才可能编。事实这一半是结构生成的 —— 抄错了随时能对回原条目,
|
|
105
|
+
* 而编出来的句子,你没法指出它错在哪(它每一句都像真的)。
|
|
106
|
+
*
|
|
107
|
+
* 所以:A 由结构保证,B(materialPrompt,交给下一轮的模型)只负责怎么说。
|
|
108
|
+
* 两者都进账本,能并排看。
|
|
109
|
+
*/
|
|
110
|
+
export function entryLine(entry) {
|
|
111
|
+
if (!entry) return null;
|
|
112
|
+
// 优先用文件名里的天(人读着舒服);没有就退回 mtime 的 ISO 时刻。
|
|
113
|
+
return `${shortDate(entry.date ?? entry.at)} ·「${entry.title}」`;
|
|
114
|
+
}
|