dsh-galatea 0.3.11 → 0.4.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.
Files changed (2) hide show
  1. package/index.js +56 -2
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -16,7 +16,7 @@ import z from '@deepseek-ai/schemastery';
16
16
  import { renderIndex, dataRoot, listEntries } from './lib/store.js';
17
17
  import { remember, recall } from './lib/tools.js';
18
18
  import { makeBeatTool, snapshotAgents } from './lib/beat-tool.js';
19
- import { loadState, noteUserMessage, stateFile } from './lib/heartbeat-runner.js';
19
+ import { loadState, noteUserMessage, stateFile, tick } from './lib/heartbeat-runner.js';
20
20
  import { DEFAULTS } from './lib/heartbeat.js';
21
21
 
22
22
  /** Cordis 插件名。 */
@@ -66,6 +66,11 @@ export const Config = z.object({
66
66
  .default(DEFAULTS.quietRounds)
67
67
  .description('连续这么多轮没开口,就强制说一次。0 = 关掉这个保底。关掉的话,它可能安静到让你以为坏了。')
68
68
  .volatile(),
69
+ autoBeatMinutes: z
70
+ .number()
71
+ .default(0)
72
+ .description('每隔多少分钟自己跑一次心跳。0 = 关(默认)。开着的话,它会按自己判断的节奏记下决定 —— 但这一版还不会真的发消息出去。想验证它是不是在动,看心跳账本里有没有新记录。')
73
+ .volatile(),
69
74
  });
70
75
 
71
76
  /** 需要宿主注入的服务。 */
@@ -109,7 +114,28 @@ function liveConfig(raw) {
109
114
  */
110
115
  export function apply(ctx, config) {
111
116
  const raw = config ?? {};
112
- const cfg = raw;
117
+ // volatile 字段在 apply 里拿到的是引用(`{ get() }`),不是值。
118
+ //
119
+ // 直接读 `raw.x` 会拿到对象 —— `Number(对象)` 是 NaN,`if (NaN > 0)` 不成立,
120
+ // 于是那个开关永远不会生效,而且**没有任何地方会报错**。
121
+ //
122
+ // 0.4.0 的自动心跳就是这么死的:同一个文件里六个字段走了 pick(),
123
+ // 第七个(autoBeatMinutes)忘了,于是它从来没有被打开过。
124
+ //
125
+ // 与其要求每个调用点都记得 pick(),不如在这里一次性解引用:
126
+ // 之后所有地方只看普通值,「忘了 pick」这种错误就不可能再发生。
127
+ const cfg = {};
128
+ for (const [k, v] of Object.entries(raw)) {
129
+ cfg[k] = v && typeof v.get === 'function' ? v.get() : v;
130
+ }
131
+
132
+ // 解引用之后再自检一次:还有对象就说明这个字段的形状跟以为的不一样。
133
+ // 不能说"大概没配"就把它当默认值用掉 —— 那正是 0.4.0 的死法。
134
+ for (const [k, v] of Object.entries(cfg)) {
135
+ if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
136
+ console.warn(`[galatea] 设置项 ${k} 解引用后仍然是对象,这项配置可能没生效`);
137
+ }
138
+ }
113
139
 
114
140
  // ── 正控:这一段真的跑过吗 ────────────────────────────────
115
141
  // marker 不是日志。它只回答一个问题:apply() 到底有没有被执行。
@@ -182,4 +208,32 @@ export function apply(ctx, config) {
182
208
  } else {
183
209
  console.warn('[galatea] 宿主没有 ctx.on,cooldown 不会生效。');
184
210
  }
211
+
212
+ // ── 自动心跳 ────────────────────────────────────────────
213
+ // 手动档(galatea_beat)能证明判定链路通,但它证明不了「它自己会动」。
214
+ // 定时器补的就是这一段:到点了自己跑一次,把决定记进账本。
215
+ //
216
+ // **这一步只算不发。**
217
+ // 真的「开口」(构造一条消息、叫醒某个会话)还没接 —— 那一步一错,
218
+ // 就是真的发消息打扰人,不该和「证明定时器活着」混在同一版里。
219
+ // 想确认它在动,看心跳账本里有没有新记录就够了。
220
+ //
221
+ // 默认关(autoBeatMinutes = 0):一个会自己转的东西,不该在装上的那一刻就开始转。
222
+ const everyMin = Number(cfg.autoBeatMinutes ?? 0);
223
+ if (Number.isFinite(everyMin) && everyMin > 0) {
224
+ if (ctx && typeof ctx.setInterval === 'function') {
225
+ const ms = Math.max(60_000, everyMin * 60_000);
226
+ ctx.setInterval(() => {
227
+ try {
228
+ tick({});
229
+ } catch (err) {
230
+ // 定时器抛出去会打断整个宿主,所以在这里兜住,但要说出来
231
+ console.warn('[galatea] 自动心跳失败:', err && err.message);
232
+ }
233
+ }, ms);
234
+ console.log(`[galatea] 自动心跳已开:每 ${everyMin} 分钟跑一次(只算不发)`);
235
+ } else {
236
+ console.warn('[galatea] 宿主没有 setInterval,自动心跳没接上。');
237
+ }
238
+ }
185
239
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-galatea",
3
- "version": "0.3.11",
3
+ "version": "0.4.1",
4
4
  "description": "Galatea — memory, heartbeat and continuity for DeepSeek Harness agents.",
5
5
  "repository": {
6
6
  "type": "git",