dsh-galatea 0.3.7 → 0.3.9

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
@@ -13,22 +13,29 @@
13
13
  | 块 | 状态 |
14
14
  |---|---|
15
15
  | 能被 DSH 装起来 | ✅ 实测通过(dsh 0.2.2-rc.3) |
16
+ | 发布到 npm | ✅ 0.3.8(GitHub Actions 自动发,不需要 token) |
16
17
  | 记忆:读写 Markdown | ✅ 15 条测试 |
17
- | 记忆:注入索引 | ✅ 接上 systemPrompt,实测无告警 |
18
+ | 记忆:注入索引 | ✅ **已核实内容真的进了上下文**(不是在会话日志里看到「接上了没告警」) |
18
19
  | 记忆:`remember` / `recall` | ✅ 23 条测试 |
20
+ | 记忆:清单看得见 | ✅ `galatea_beat` 里会列出「它记得哪些事」和「还有几条没跟人说过」 |
19
21
  | 心跳:门控(要不要开口) | ✅ 15 条测试 |
20
- | 心跳:状态账本(跨进程) | ✅ 10 条测试 |
22
+ | 心跳:状态账本(跨进程) | ✅ 10 条测试 · 真机验证过重启后仍在 |
21
23
  | 心跳:对谁说(排除子代理) | ✅ 10 条测试 |
22
24
  | 心跳:说什么(没出处就不说) | ✅ 10 条测试 |
23
25
  | 心跳:三段合成一个决定 | ✅ 10 条测试 |
24
- | 心跳:`galatea_beat` 手动档 | ✅ 实测加载,无告警 |
26
+ | 心跳:手动档真的写账本 | ✅ 实测:连跑两次结果不同 |
27
+ | 心跳:沉默可解释 | ✅ `galatea_beat` 会把最近几次的决定翻成人话 |
28
+ | 心跳:听得见你说话 | ⚠️ 接了 `agent/pre-step`,**但那条事件有没有真的触发,没验过** |
25
29
  | 设置页可改的字段 | ⚠️ 声明被宿主接受,**表单没在 GUI 里看过** |
26
30
  | 心跳:定时器 + 自动开口 | ⬜ 未做 |
27
- | 发布到 npm | ⬜ 未做 |
28
31
 
29
32
  > ⚠️ **它现在还不会自己开口。** 定时器和 `ctx.agents.resume` 都还没接,
30
33
  > 判定链路完整,但没有出口。别把「测试全绿」读成「它已经在主动找你了」。
31
34
 
35
+ 关于上面那条 ⚠️:`noteUserMessage` 接在 `agent/pre-step` 上,用来记「你刚说过话」
36
+ (cooldown 要用它)。**本机验证只到「加载不崩」**——那个测试 profile 里没有会话,
37
+ 事件根本没机会发生。这一条要等真机跑过才算数。
38
+
32
39
  ## 怎么用
33
40
 
34
41
  三个工具,Agent 自己会调:
@@ -37,10 +44,19 @@
37
44
  |---|---|
38
45
  | `galatea_remember` | 记一条。标题进索引,正文不进 |
39
46
  | `galatea_recall` | 给 id 读正文,不给 id 列标题 |
40
- | `galatea_beat` | 手动跑一次心跳:**现在该不该说话、对谁说、拿哪条当由头**。只算不发 |
47
+ | `galatea_beat` | 手动跑一次心跳,并回答三件事 |
48
+
49
+ `galatea_beat` 返回的那段文字同时回答三个问题:
50
+
51
+ - **现在该不该说话**(理由、对谁说、拿哪条当由头)
52
+ - **它记得什么**(`—— 它记得的这些 ——`,含「还有几条没跟人说过」)
53
+ - **最近几次是怎么决定的**(`—— 最近几次 ——`,把 reason 翻成人话)
41
54
 
42
- `galatea_beat` 是给「你现在想说什么」这个问题准备的。它不依赖定时器,
43
- 所以在任何有会话的地方都能立刻看到整条链路通不通。
55
+ 它不依赖定时器,所以在任何有会话的地方都能立刻看到整条链路通不通。
56
+ **跑一次就真的算一次心跳**——账本会往前走,所以连跑两次的结果不会一样。
57
+
58
+ 第三个问题那个数字不是装饰:用户问「它背着我记了什么」,
59
+ 它自己问「有没有东西值得去说」,是同一件事的两面。
44
60
 
45
61
  ## 设置(不用改代码)
46
62
 
@@ -57,7 +73,7 @@
57
73
 
58
74
  两处容易踩的:
59
75
 
60
- - **「连续几轮没说就强制一次」和「最少间隔」是耦合的。** 心跳间隔设得比保底所需的时间还短,保底永远不会触发。
76
+ - **「连续几轮没强制一次」和「最少间隔」是耦合的。** 心跳间隔设得比保底所需的时间还短,保底永远不会触发。
61
77
  - **把 `quietRounds` 关掉,它可能安静到让你以为坏了。** 默认闭嘴需要一条下限,不然沉默和故障长得一样。
62
78
 
63
79
  ## 唯一的设计判断
@@ -88,6 +104,26 @@
88
104
 
89
105
  门控可以自动决定,**内容不能自动生成**。空话比不说更糟,它消耗的是「它真的在」那点信用。
90
106
 
107
+ ## 一次教训:模块边界
108
+
109
+ 0.3.0 / 0.3.5 / 0.3.6 连着三次,坏的地方都在**两个文件交接处**:
110
+
111
+ | 版本 | 坏在哪 |
112
+ |---|---|
113
+ | 0.3.0 | `inject` 少声明了 `agents` |
114
+ | 0.3.5 | 一边写 `date`、一边读 `at` |
115
+ | 0.3.6 | 用了三个符号,**一个都没 import** |
116
+
117
+ 三次单元测试全绿,因为测试**直接调 lib 里的函数**,从来不经过
118
+ `index.js` / `beat-tool.js` 那一层。而问题恰好长在那一层。
119
+
120
+ 第三次最坏:`ReferenceError` 被同一个 `try/catch` 吞掉,工具照常返回,
121
+ 用户看不出任何异常,账本却一条都没写进去。
122
+
123
+ 现在有 `test/tool-execute.test.mjs`:**真的执行一次工具**,不是调 lib 函数。
124
+ 判据是「那个工具跑得起来吗、它说的话算不算数」——
125
+ 包括「账本文件必须真的出现」和「失败必须出现在给人看的文字里」。
126
+
91
127
  ## 数据放在哪
92
128
 
93
129
  ```
@@ -103,7 +139,7 @@ $DSH_HOME/galatea/heartbeat-state.json # 心跳状态账本
103
139
  ## 本地开发
104
140
 
105
141
  ```bash
106
- npm test # 78 条,不依赖 DSH,纯 node
142
+ npm test # 92 条,不依赖 DSH,纯 node
107
143
  ```
108
144
 
109
145
  装了之后验证它真的加载了,只看一个东西:
@@ -115,13 +151,26 @@ npm test # 78 条,不依赖 DSH,纯 node
115
151
  那行 marker 不是日志,是**正控**:只有它出现,才说明这段代码真的跑过。
116
152
  「进程起来了」「配置里写了」都不算证据。
117
153
 
118
- ## 安装(还没发布)
154
+ ## 安装
119
155
 
120
- profile 的 `package.json`:
156
+ ```bash
157
+ dsh plugin add dsh-galatea
158
+ ```
159
+
160
+ 或者在 profile 的 `package.json` 里手写:
121
161
 
122
162
  ```json
123
- "dependencies": { "dsh-galatea": "file:E:/Galatea" },
124
- "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "dsh-galatea"] } }
163
+ "dependencies": { "dsh-galatea": "0.3.8" }
164
+ ```
165
+
166
+ **版本号钉死,不要写 `^`。** 同一个版本号指向两个不同的包,以后就说不清你装的是哪一份。
167
+
168
+ ## 发布
169
+
170
+ 推 tag 就发,不需要 npm token:
171
+
172
+ ```bash
173
+ git tag v0.3.9 && git push origin v0.3.9
125
174
  ```
126
175
 
127
176
  ## 授权
package/lib/beat-tool.js CHANGED
@@ -38,6 +38,44 @@ export function snapshotAgents(agentsService) {
38
38
  * @param {() => object} deps.loadState
39
39
  * @param {object} [deps.config]
40
40
  */
41
+ /**
42
+ * 它记得什么 —— 给「人」看的一份。
43
+ *
44
+ * 正文不进这里(那是这个插件唯一的设计判断:正文只在被问到的时候才读)。
45
+ * 只列 id 和标题,按新到旧。
46
+ *
47
+ * 末尾那行同时回答两个问题:
48
+ * 用户问「它背着我记了什么」⟹ 给总条数
49
+ * 它自己问「有没有东西值得去说」⟹ 给还没说过的条数
50
+ * 这两个问题共用一行,是因为它们本来就是同一件事的两面。
51
+ */
52
+ function journalMemory(entries, pending) {
53
+ const list = Array.isArray(entries) ? entries : [];
54
+ if (list.length === 0) {
55
+ return '(一条都没有。要它记事,让它调 galatea_remember。)';
56
+ }
57
+
58
+ const MAX = 10;
59
+ const shown = list
60
+ .slice()
61
+ .sort((a, b) => String(b.id).localeCompare(String(a.id)))
62
+ .slice(0, MAX);
63
+
64
+ const lines = shown.map((e) => {
65
+ const t = String(e.title ?? '').trim();
66
+ return `${e.id} ${t.length > 40 ? `${t.slice(0, 40)}…` : t}`;
67
+ });
68
+ if (list.length > MAX) lines.push(`…… 还有 ${list.length - MAX} 条`);
69
+
70
+ lines.push('');
71
+ lines.push(
72
+ typeof pending === 'number' && pending > 0
73
+ ? `共 ${list.length} 条,其中 ${pending} 条还没跟人说过`
74
+ : `共 ${list.length} 条,都说过了`,
75
+ );
76
+ return lines.join('\n');
77
+ }
78
+
41
79
  export function makeBeatTool(deps) {
42
80
  return defineTool({
43
81
  name: 'galatea_beat',
@@ -84,7 +122,15 @@ export function makeBeatTool(deps) {
84
122
  });
85
123
 
86
124
  const material = entries.find((e) => e.id === r.materialId) ?? null;
87
- const picked = material ?? pickMaterial(entries, state.lastSpokeAt ?? null).entry;
125
+ const mat = pickMaterial(entries, state.lastSpokeAt ?? null);
126
+ const picked = material ?? mat.entry;
127
+
128
+ // 它现在记得什么 —— 给「人」看的那一份。
129
+ // 之前只有 galatea_recall 能列标题,而那是给 Agent 用的工具;
130
+ // 用户想确认「它背着我记了什么」,只能自己去翻 Markdown 目录。
131
+ // 末尾那个「还没说过的」不只是统计:它同时回答两个问题 ——
132
+ // 用户问「它记了什么」,我问「有没有东西值得去说」。
133
+ const memoryLines = journalMemory(entries, mat.pending);
88
134
 
89
135
  // 给人看的那几行:把它自己最近的决定翻成人话。
90
136
  const journal = renderJournal(state, { quietRounds: liveCfg.quietRounds });
@@ -121,6 +167,9 @@ export function makeBeatTool(deps) {
121
167
  lines.push(' (所以上面的「最近几次」不会长,节奏类规则也不会生效)');
122
168
  }
123
169
  lines.push('');
170
+ lines.push('—— 它记得的这些 ——');
171
+ lines.push(memoryLines);
172
+ lines.push('');
124
173
  lines.push('—— 最近几次 ——');
125
174
  lines.push(journal);
126
175
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-galatea",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Galatea — memory, heartbeat and continuity for DeepSeek Harness agents.",
5
5
  "repository": {
6
6
  "type": "git",