@akasha-book/sleep 1.6.5 → 1.7.2
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 +11 -4
- package/cordis.patch.yml +6 -6
- package/lib/index.js +202 -23
- package/package.json +15 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# @akasha-
|
|
1
|
+
# @local/akasha-sleep
|
|
2
2
|
|
|
3
|
-
阿卡夏之书睡眠器 v1.5.5 —— **调度与注入**(蒸馏逻辑归核心库
|
|
3
|
+
阿卡夏之书睡眠器 v1.5.5 —— **调度与注入**(蒸馏逻辑归核心库 `H:\Harness\akasha\sleep.mjs`;手动触发 `node akasha.mjs sleep [--dry]` 与之共用同一水位线)。
|
|
4
4
|
|
|
5
5
|
- **v1.5.5(2026-10-07 复查修复)**:移除死代码 `source==='compact'` 早退(官方 `agent/created` 的 source 只有 startup/resume;压缩信号是 session 事件 `compaction/*`);子代理守卫沿用官方 `header.origin/delegationDepth`(已核真实字段)。
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- **条子三(每回合·库脉搏 + 回查)**:`akasha:pulse`(order 132)——六库当前计数 + 最近写入(**写→可见通道**:任何会话 / 脚本写入库后,活跃会话下一回合即见);**v1.5 起**,窗口内(默认 30 分钟)有工具 / agent 失败时,追一行「回查提示」(错误钩子的注入级动作——只提示、不拦截;渲染幂等,同窗口重复渲染结果一致)。
|
|
12
12
|
- **降级(v1.4 切分)**:核心库不可用 → `sleep-error` 观测线 + 条子提示文案;不炸、不失活(插件不再内置蒸馏逻辑,拒绝双份实现)。
|
|
13
13
|
- 观测线:`sleep-armed` / `sleep-done` / `sleep-skip` / `sleep-error` / `wake-note` / `wake-note-error` / `wake-note-llm-error` / `sleep-report-error` / `sleep-state-error` / `sleep-inbox-error` 全部写进 `hooks.jsonl`(报告 / 状态 / inbox 三条由核心库发出)。
|
|
14
|
-
- 自测:`node selftest.mjs`(纯函数直测
|
|
14
|
+
- 自测:`node selftest.mjs`(纯函数直测 `../akasha/sleep.mjs`——含 v1.5 `renderRecallLine`;桩 ctx 端到端(含失败→回查行)+ 写失败线 + 无核心降级;临时目录,不碰真库水位线)。
|
|
15
15
|
|
|
16
16
|
## 版本与重启
|
|
17
17
|
|
|
@@ -23,7 +23,14 @@
|
|
|
23
23
|
## 打包 / 安装
|
|
24
24
|
|
|
25
25
|
```powershell
|
|
26
|
-
cd
|
|
26
|
+
cd H:\Harness\dsh-akasha-sleep
|
|
27
27
|
npm pack
|
|
28
28
|
# 然后:plugin_manager install_bundle <tgz 绝对路径>(首次安装会 HMR 热生效;替换需重启)
|
|
29
29
|
```
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## 源与许可(Source & License)
|
|
33
|
+
|
|
34
|
+
- 源仓库:https://github.com/Ersith/akasha-book
|
|
35
|
+
- 本版本固定提交(pinned commit):`5a55099586cfae4e7511ba433eaf202455d81c30`
|
|
36
|
+
- 许可证:MIT(见仓库根 LICENSE)
|
package/cordis.patch.yml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
- insert:
|
|
2
2
|
- id: akasha-sleep
|
|
3
|
-
name: '@akasha-
|
|
3
|
+
name: '@local/akasha-sleep'
|
|
4
4
|
config:
|
|
5
|
-
akashaDir: '
|
|
6
|
-
log: '
|
|
7
|
-
stateFile: '
|
|
8
|
-
inboxFile: '
|
|
9
|
-
reportDir: '
|
|
5
|
+
akashaDir: 'H:\Harness\akasha'
|
|
6
|
+
log: 'H:\Harness\akasha\logs\hooks.jsonl'
|
|
7
|
+
stateFile: 'H:\Harness\akasha\logs\sleep-state.json'
|
|
8
|
+
inboxFile: 'H:\Harness\akasha\logs\inbox.jsonl'
|
|
9
|
+
reportDir: 'H:\Harness\akasha\reports'
|
|
10
10
|
minIntervalHours: 6
|
|
11
11
|
timerCheckMs: 3600000
|
|
12
12
|
contextOrder: 130
|
package/lib/index.js
CHANGED
|
@@ -158,14 +158,45 @@ export function apply(ctx, config = {}) {
|
|
|
158
158
|
|
|
159
159
|
// 回查提示(v1.5,错误钩子的注入级动作):进程内记录近期失败(工具 / agent),窗口内幂等渲染。
|
|
160
160
|
const recentFailures = [];
|
|
161
|
+
const usedTools = new Set(); // 本会话用过哪些工具(首用预告的判据)
|
|
162
|
+
const announcedTraps = new Set(); // 已预告过的陷阱工具(每工具一次)
|
|
161
163
|
const noteFailure = (tool) => {
|
|
162
164
|
try {
|
|
163
165
|
recentFailures.push({ ts: Date.now(), tool: String(tool ?? '?') });
|
|
164
166
|
if (recentFailures.length > 20) recentFailures.splice(0, recentFailures.length - 20);
|
|
165
167
|
} catch { /* 静默 */ }
|
|
166
168
|
};
|
|
167
|
-
|
|
169
|
+
// 极限观测(2026-10-11 用户令):把「该查库了」变成**可见信号**,不靠记性——针对"200 轮后忘记查库"的观察。
|
|
170
|
+
const obs = { turn: 0, steps: 0, compactions: 0, usedToolsN: 0, lastRecallTurn: null, lastCompactionTurn: null };
|
|
171
|
+
const clip = (s) => (s.length <= 80 ? s : s.slice(0, 79) + '…'); // 行宽硬约束的统一裁剪(模块级,供多行共用)
|
|
172
|
+
const isRecallTool = (name) => /^mcp__akasha__/.test(String(name || ''));
|
|
173
|
+
ctx.on('tools/result', (exec, result) => {
|
|
174
|
+
const tool = String(exec?.name ?? '?');
|
|
175
|
+
usedTools.add(tool); // 首用预告用(2026-10-11)
|
|
176
|
+
obs.usedToolsN = usedTools.size;
|
|
177
|
+
if (isRecallTool(tool)) obs.lastRecallTurn = obs.turn; // MCP 召回 ⇒ 刷新"距上次查库"
|
|
178
|
+
else {
|
|
179
|
+
// CLI 查库也算召回(2026-10-11 实测:本会话大量查库走 `node akasha.mjs …`,只认 MCP 会显示"尚未查库"=自欺)
|
|
180
|
+
try {
|
|
181
|
+
const blob = JSON.stringify(exec?.args ?? exec?.input ?? exec?.params ?? '');
|
|
182
|
+
if (/akasha\.mjs|session\.mjs|mcp\.mjs/.test(blob)) obs.lastRecallTurn = obs.turn;
|
|
183
|
+
} catch { /* 忽略 */ }
|
|
184
|
+
}
|
|
185
|
+
if (result?.isError === true) noteFailure(exec?.name);
|
|
186
|
+
});
|
|
168
187
|
ctx.on('agent/error', () => noteFailure('agent'));
|
|
188
|
+
// 轮次与压缩真值:能拿到就用真值,拿不到就保持 0(宁缺勿假)
|
|
189
|
+
try { ctx.on('agent/turn-stopping', (p) => { if (p && typeof p.turn === 'number') obs.turn = p.turn; }); } catch { /* 事件不存在 */ }
|
|
190
|
+
try { ctx.on('agent/turn-end', (p) => { if (p && typeof p.turn === 'number') obs.turn = Math.max(obs.turn, p.turn); }); } catch { /* 事件不存在 */ }
|
|
191
|
+
try {
|
|
192
|
+
ctx.on('session/event', (p) => {
|
|
193
|
+
const t = String(p?.type ?? p?.name ?? p?.eventType ?? '');
|
|
194
|
+
if (t.startsWith('compaction/end') || (t === 'compaction' && p?.phase === 'end')) {
|
|
195
|
+
obs.compactions++;
|
|
196
|
+
obs.lastCompactionTurn = obs.turn; // 脆弱窗判据(压缩补偿行用)
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
} catch { /* 事件不存在 */ }
|
|
169
200
|
|
|
170
201
|
// 库脉搏(v1.3;v1.5 起附加回查行):写→可见通道——并行会话 / 脚本写入库后,下一回合即可见并命名新内容。
|
|
171
202
|
ctx.systemPrompt.context({
|
|
@@ -266,13 +297,7 @@ export function apply(ctx, config = {}) {
|
|
|
266
297
|
const now = Date.now();
|
|
267
298
|
const WINDOW = 30 * 60 * 1000;
|
|
268
299
|
const recent = (recentFailures || []).filter((f) => now - Number(f.ts ?? f.at) <= WINDOW); // 字段是 ts(见 noteFailure)
|
|
269
|
-
if (!recent.length) return null;
|
|
270
|
-
const last = recent[recent.length - 1];
|
|
271
|
-
const tool = String(last.tool || '');
|
|
272
300
|
const map = lib.RECALL_TRAP_MAP || {};
|
|
273
|
-
const ids = map[tool];
|
|
274
|
-
if (!ids || !ids.length) return null; // 不在映射表 ⇒ 不提示
|
|
275
|
-
if (preflightState.tool === tool && now - preflightState.at < WINDOW) return null; // 同工具 30 分钟去重
|
|
276
301
|
// 2026-10-11 修复(用户裁定:反射要能随工程知识纠错与优化):
|
|
277
302
|
// **反射文本随条目走** —— 不再只认写死的表。
|
|
278
303
|
// ① 链感知解析(id 或 id-rN 取链尾);② 条目缺失/已退役 ⇒ **不打**(fail closed,不执行过期反射);
|
|
@@ -291,30 +316,184 @@ export function apply(ctx, config = {}) {
|
|
|
291
316
|
const resolveEntry = (cid) => canonRecs.filter((r) => String(r.id) === cid || String(r.id).startsWith(cid + '-r')).pop() || null;
|
|
292
317
|
const isRetired = (rec) => !!rec && (/已退役|retired/i.test(String(rec.claim ?? '')) || String(rec.id).endsWith('-retired'));
|
|
293
318
|
const today = new Date().toISOString().slice(0, 10);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
319
|
+
/** 某工具的首选反射:条目 `hint` 优先 → 兜底表;退役/过期/缺失一律跳过;取不到返回 null(fail closed)。
|
|
320
|
+
* ⚠ 口径:**库可读时,条目是唯一来源**——该条目没有 hint 也**不**回落兜底(否则"退役/过期"会被兜底表救活,
|
|
321
|
+
* 等于过期反射照打)。兜底表只在**库读不到**时生效。 */
|
|
322
|
+
const hintForTool = (tool) => {
|
|
323
|
+
const ids = map[tool];
|
|
324
|
+
if (!ids || !ids.length) return null;
|
|
325
|
+
for (const cand of ids) {
|
|
326
|
+
if (canonRecs.length) {
|
|
327
|
+
const rec = resolveEntry(cand);
|
|
328
|
+
if (!rec) continue; // 链上已无此条 ⇒ 跳过
|
|
329
|
+
if (isRetired(rec)) continue; // 已退役 ⇒ 不执行过期反射
|
|
330
|
+
if (rec.hint_until && String(rec.hint_until) < today) continue; // 反射有效期已过 ⇒ 跳过
|
|
331
|
+
if (typeof rec.hint === 'string' && rec.hint.trim()) return { id: cand, act: rec.hint.trim(), hintSource: 'entry' };
|
|
332
|
+
continue; // 有条目但无 hint ⇒ 该候选作罢,不回落
|
|
333
|
+
}
|
|
334
|
+
if (FALLBACK_ACTIONS[cand]) return { id: cand, act: FALLBACK_ACTIONS[cand], hintSource: 'fallback' };
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
};
|
|
338
|
+
const emitPreflight = (payload) => {
|
|
339
|
+
try { // 事件:关闭时走不到这里(双停)
|
|
340
|
+
appendFileSync(join(cfg.akashaDir, 'logs', 'hooks.jsonl'),
|
|
341
|
+
JSON.stringify({ ts: new Date().toISOString(), kind: 'preflight', ...payload }) + '\n', 'utf8');
|
|
342
|
+
} catch { /* 事件失败不影响提示 */ }
|
|
343
|
+
};
|
|
344
|
+
const trim80 = (s) => (s.length <= 80 ? s : s.slice(0, 79) + '…');
|
|
345
|
+
|
|
346
|
+
// ① 失败驱动(原逻辑):近 30 分钟内有过失败 ⇒ 给"那件工具"的反射
|
|
347
|
+
if (recent.length) {
|
|
348
|
+
const tool = String(recent[recent.length - 1].tool || '');
|
|
349
|
+
const fresh = !(preflightState.tool === tool && now - preflightState.at < WINDOW); // 同工具 30 分钟去重
|
|
350
|
+
if (fresh && map[tool]) {
|
|
351
|
+
const h = hintForTool(tool);
|
|
352
|
+
if (h) {
|
|
353
|
+
preflightState.tool = tool; preflightState.at = now;
|
|
354
|
+
emitPreflight({ tool, entry: h.id, hintSource: h.hintSource, mode: 'failure' });
|
|
355
|
+
return trim80('[坑前提示] ' + tool + ' 前:' + h.act + ' —— 条目 ' + h.id);
|
|
356
|
+
}
|
|
302
357
|
}
|
|
303
|
-
if (FALLBACK_ACTIONS[cand]) { id = cand; act = FALLBACK_ACTIONS[cand]; hintSource = 'fallback'; break; }
|
|
304
358
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
359
|
+
|
|
360
|
+
// ② 首用预告(2026-10-11 新增;补"漏召"的结构缺口):
|
|
361
|
+
// 渲染时**不知道本回合会用什么工具**,但知道**本会话还没用过哪些** ⇒ 逐个把"未用过的陷阱工具"预告一遍,
|
|
362
|
+
// 每个工具只预告一次(用过即止)= 把"事后回查"提前成"动手前预检",不依赖历史失败。
|
|
363
|
+
if (cfg.preflightFirstUse !== false) {
|
|
364
|
+
const nextTool = Object.keys(map).find((t) => !usedTools.has(t) && !announcedTraps.has(t));
|
|
365
|
+
if (nextTool) {
|
|
366
|
+
const h = hintForTool(nextTool);
|
|
367
|
+
if (h) {
|
|
368
|
+
announcedTraps.add(nextTool);
|
|
369
|
+
emitPreflight({ tool: nextTool, entry: h.id, hintSource: h.hintSource, mode: 'first-use' });
|
|
370
|
+
return trim80('[坑前提示·首用] ' + nextTool + ' 还没用过:' + h.act + ' —— 条目 ' + h.id);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
} catch { return null; }
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// 极限观测行(2026-10-11,用户令):轮次维度的机械钩子——把「该查库了」变成可见信号,不靠记性。
|
|
381
|
+
// 只在知道轮次时渲染(拿不到真值就保持静默,不伪造)。硬约束同 C1:**一行、≤80 字**。
|
|
382
|
+
if (cfg.extremeObs !== false) {
|
|
383
|
+
ctx.systemPrompt.context({
|
|
384
|
+
name: 'akasha:extreme',
|
|
385
|
+
order: (cfg.pulseOrder ?? 132) + 2,
|
|
386
|
+
text: () => {
|
|
387
|
+
try {
|
|
388
|
+
if (!obs.turn) return null;
|
|
389
|
+
const gap = obs.lastRecallTurn === null ? '(本进程尚未查库)' : '距今 ' + Math.max(0, obs.turn - obs.lastRecallTurn) + ' 轮';
|
|
390
|
+
// 口径标注(2026-10-11):轮次/压缩/查库均为**本进程**统计——重启即归零;不标会被读成"整场会话"
|
|
391
|
+
const line = '[极限观测·本进程] 轮=' + obs.turn + ' 压缩=' + obs.compactions + ' 上次查库=' + gap;
|
|
312
392
|
return line.length <= 80 ? line : line.slice(0, 79) + '…';
|
|
313
393
|
} catch { return null; }
|
|
314
394
|
}
|
|
315
395
|
});
|
|
316
396
|
}
|
|
317
397
|
|
|
398
|
+
// 压缩补偿行(2026-10-11 用户方向:"针对压缩轮次增加,逐步强行增加使用库查记忆进行补全")。
|
|
399
|
+
// 依据(当晚实测):① 压缩摘要只保话题、**丢约定**——四条核心约定存活率 0–21%,而**每回合注入的称呼层 100% 存活**;
|
|
400
|
+
// ② 「丢记忆」实例(turn 220)落在**压缩后 15 轮内**(压缩在 205)。
|
|
401
|
+
// ⇒ 分级补偿:**脆弱窗**(距上次压缩 ≤15 轮)要求"重要结论先落库";**压缩 ≥3** 且本回合尚未查库 ⇒ 要求先查;
|
|
402
|
+
// **压缩 ≥5** ⇒ 升级为强制;**查过即不再催**(闭环,避免噪音)。
|
|
403
|
+
const RECALL_WINDOW = 15;
|
|
404
|
+
if (cfg.compensate !== false) {
|
|
405
|
+
ctx.systemPrompt.context({
|
|
406
|
+
name: 'akasha:compensate',
|
|
407
|
+
order: (cfg.pulseOrder ?? 132) + 3,
|
|
408
|
+
text: () => {
|
|
409
|
+
try {
|
|
410
|
+
if (!obs.turn || !obs.compactions) return null; // 没压缩过 ⇒ 不打扰
|
|
411
|
+
const sinceComp = obs.lastCompactionTurn === null ? null : Math.max(0, obs.turn - obs.lastCompactionTurn);
|
|
412
|
+
const recalledRecently = obs.lastRecallTurn !== null && (obs.turn - obs.lastRecallTurn) <= 2;
|
|
413
|
+
const inWindow = sinceComp !== null && sinceComp <= RECALL_WINDOW;
|
|
414
|
+
if (obs.compactions >= 5 && !recalledRecently) {
|
|
415
|
+
return clip('[压缩补偿·强制] 本会话已压缩 ' + obs.compactions + ' 次:关键动作前先 akasha_lookup/brief 补全,再动手');
|
|
416
|
+
}
|
|
417
|
+
if (obs.compactions >= 3 && !recalledRecently) {
|
|
418
|
+
return clip('[压缩补偿] 已压缩 ' + obs.compactions + ' 次' + (inWindow ? '(脆弱窗 距上次 ' + sinceComp + ' 轮)' : '') + ':本回合先查库再答');
|
|
419
|
+
}
|
|
420
|
+
if (inWindow) {
|
|
421
|
+
return clip('[压缩补偿·脆弱窗] 距上次压缩 ' + sinceComp + ' 轮:重要结论先落库,动手前先查');
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
} catch { return null; }
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// 核心约定常驻行(2026-10-11 用户裁定):**防目标漂移**——压缩丢的是"我们怎么做事"(实测五条约定存活 0–21%),
|
|
430
|
+
// 而每回合注入的称呼层 100% 存活 ⇒ 把核心约定也做成**常驻**。**库驱动**:渲染时读 canon 的
|
|
431
|
+
// `canon-core-conventions` 当前版本的 `line` 字段 ⇒ **改约定=改条目**(不碰代码);读不到 ⇒ 内置兜底(永不为空)。
|
|
432
|
+
// 行宽**破例放宽到 160 字**(其余行均为 80):它是漂移锚,值得多占一行。
|
|
433
|
+
if (cfg.conventions !== false) {
|
|
434
|
+
const CONV_ID = 'canon-core-conventions';
|
|
435
|
+
const FALLBACK_CONVENTIONS = '库无界·装载有界|来源态四标签|修订链:追加不改原文|零编造(红线)|称呼:Master/大肥鱼';
|
|
436
|
+
const clipLong = (s) => (s.length <= 160 ? s : s.slice(0, 159) + '…');
|
|
437
|
+
ctx.systemPrompt.context({
|
|
438
|
+
name: 'akasha:conventions',
|
|
439
|
+
order: (cfg.pulseOrder ?? 132) + 4,
|
|
440
|
+
text: () => {
|
|
441
|
+
try {
|
|
442
|
+
const lib = require_(join(cfg.akashaDir, 'lib.mjs'));
|
|
443
|
+
const recs = lib.currentRecords(lib.loadStore('canon').records);
|
|
444
|
+
const rec = recs.filter((r) => String(r.id) === CONV_ID || String(r.id).startsWith(CONV_ID + '-r')).pop();
|
|
445
|
+
const text = (rec && typeof rec.line === 'string' && rec.line.trim()) ? rec.line.trim() : FALLBACK_CONVENTIONS;
|
|
446
|
+
return clipLong('[约定] ' + text);
|
|
447
|
+
} catch { return clipLong('[约定] ' + FALLBACK_CONVENTIONS); }
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ① 语义钩子 + 显式钩子(2026-10-11 用户令「开搞」;补设计稿 §2.4 五类钩子中缺的两类)
|
|
453
|
+
// · **显式钩子**("上次那个"):读**本会话最近 intent 段**(会话层=用户消息的摘要)⇒ 检测指代词 ⇒ 检索六库 ⇒ **一行**指路。
|
|
454
|
+
// ⚠ 已知限制(如实):数据源是**摘要**而非原话,摘要可能丢掉指代词 ⇒ **宁可漏触发不误触发**;
|
|
455
|
+
// 若要原话级,需让 hooks 侧记录 user-message 行(待办)。
|
|
456
|
+
// · **语义钩子**("当前处境作为查询"):本会话近 30 分钟内有工具失败 ⇒ 用工具名检索六库 ⇒ **一行**指路。
|
|
457
|
+
// 两钩子**每回合最多出一行**,优先级 显式 > 语义;都无触发 ⇒ **静默**(不制造噪音);可关 `recallHook:false`。
|
|
458
|
+
if (cfg.recallHook !== false) {
|
|
459
|
+
const PRONOUN = /(上次|之前|上回|那天|那个|说过|记得|当时|前面说|前面提)/;
|
|
460
|
+
const recentIntentGist = () => {
|
|
461
|
+
try {
|
|
462
|
+
const lib = require_(join(cfg.akashaDir, 'lib.mjs'));
|
|
463
|
+
const rows = lib.loadStore('session').records.filter((r) => r.kind === 'intent');
|
|
464
|
+
const last = rows[rows.length - 1];
|
|
465
|
+
return last ? String(last.gist || '') : '';
|
|
466
|
+
} catch { return ''; }
|
|
467
|
+
};
|
|
468
|
+
const clip120 = (s) => (s.length <= 120 ? s : s.slice(0, 119) + '…');
|
|
469
|
+
ctx.systemPrompt.context({
|
|
470
|
+
name: 'akasha:recall',
|
|
471
|
+
order: (cfg.pulseOrder ?? 132) + 5,
|
|
472
|
+
text: () => {
|
|
473
|
+
try {
|
|
474
|
+
const lib = require_(join(cfg.akashaDir, 'lib.mjs'));
|
|
475
|
+
// 显式钩子(指代)
|
|
476
|
+
const gist = recentIntentGist();
|
|
477
|
+
if (gist && PRONOUN.test(gist)) {
|
|
478
|
+
// 去掉**所有**指代词(2026-10-11 修:replace 无 g 标志时只替一次,残留「那个」会污染查询)
|
|
479
|
+
const q = gist.replace(new RegExp(PRONOUN.source, 'g'), ' ').replace(/\s+/g, ' ').trim().slice(0, 60) || gist.slice(0, 60);
|
|
480
|
+
const r = lib.lookupDetailed(q, { limit: 2, contextBudget: false });
|
|
481
|
+
const ids = (r.hits || []).map((h) => String(h.id)).slice(0, 2).join(' · ');
|
|
482
|
+
if (ids) return clip120('[指代检索] 像是问旧事 → 先看:' + ids);
|
|
483
|
+
}
|
|
484
|
+
// 语义钩子(近失败)
|
|
485
|
+
const f = recentFailures[recentFailures.length - 1];
|
|
486
|
+
if (f && obs.turn && Date.now() - f.ts < 30 * 60 * 1000) {
|
|
487
|
+
const r = lib.lookupDetailed(String(f.tool || ''), { limit: 2, contextBudget: false });
|
|
488
|
+
const top = (r.hits || [])[0];
|
|
489
|
+
if (top) return clip120('[处境检索] ' + f.tool + ' 近 30 分钟内失败过 → 相关条目 ' + top.id + ':' + String(top.snippet || '').slice(0, 42));
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
} catch { return null; }
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
318
497
|
// 空闲触发(事件失败开放)。
|
|
319
498
|
ctx.on('agent/status', (payload) => {
|
|
320
499
|
try { if (payload?.status === 'idle') maybeSleep('idle'); } catch { /* 静默 */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akasha-book/sleep",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=18"
|
|
6
6
|
},
|
|
@@ -19,7 +19,18 @@
|
|
|
19
19
|
"patch": "./cordis.patch.yml"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
|
-
"
|
|
23
|
-
"
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@deepseek-ai/dsh": "^0.2.0-rc.2",
|
|
24
|
+
"@deepseek-ai/cordis": "^4.0.4"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/Ersith/akasha-book.git",
|
|
30
|
+
"directory": "dsh-akasha-sleep"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "node selftest.test.mjs",
|
|
34
|
+
"check": "node --check lib/index.js"
|
|
24
35
|
}
|
|
25
|
-
}
|
|
36
|
+
}
|