@akasha-book/sleep 0.0.0-stage → 1.5.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.
- package/README.md +26 -2
- package/cordis.patch.yml +8 -0
- package/lib/index.js +190 -0
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @akasha-book/sleep
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 阿卡夏之书(Akasha Book)· 睡眠器插件
|
|
4
|
+
|
|
5
|
+
阿卡夏之书睡眠器 v1.5.1 —— **调度与注入**(蒸馏逻辑归核心库 `../../core/sleep.mjs`;手动触发 `node akasha.mjs sleep [--dry]` 与之共用同一水位线)。
|
|
6
|
+
|
|
7
|
+
- **触发**:`agent/status → idle`,去抖 `minIntervalHours`(默认 6);另一路定时兜底 `ctx.interval`(默认 1h——timer 属混入服务,需 `inject:['timer']`)。跳过也留痕(`sleep-skip`);首次激活即跑一次(`activate`)。
|
|
8
|
+
- **蒸馏**:`hooks.jsonl` 增量(水位线 `processedLines`)→ 统计 + 失败备注 + 待办(审计警告 → review;工具失败 / 拦截 / agent 错误 → 孤案候选);报告 `sleep-<date>.json`(**同日重复跑加时间戳后缀,不覆盖**);有待办时追加 `logs/inbox.jsonl`。
|
|
9
|
+
- **条子一(每回合)**:`akasha:sleep`(order 130)——最近一次睡眠摘要(含待办数)。
|
|
10
|
+
- **条子二(新会话·唤醒条)**:`agent/created` → 经 `agent.inject()` 递「起床包」摘要(不唤醒、不打断;仅当 无睡眠记录 / 有待办 / 审计警告 才发)。
|
|
11
|
+
- **条子三(每回合·库脉搏 + 回查)**:`akasha:pulse`(order 132)——六库计数 + 最近写入(**写→可见通道**);窗口内(默认 30 分钟)有失败时追一行**回查提示**(注入级,不拦截)。
|
|
12
|
+
- **配置**:`akashaDir` / `log` / `stateFile` / `inboxFile` / `appModulesDir`(唤醒条用的宿主模块目录;**留空则跳过并留痕 `wake-note-llm-skip`**)/ `minIntervalHours` / `timerCheckMs` / `contextOrder` / `pulseOrder`。
|
|
13
|
+
- 观测线:`sleep-armed` / `sleep-done` / `sleep-skip` / `sleep-error` / `wake-note` / `wake-note-error` / `wake-note-llm-error` / `wake-note-llm-skip` / `sleep-report-error` / `sleep-state-error` / `sleep-inbox-error`。
|
|
14
|
+
- 自测:`node selftest.mjs`(纯函数 + 桩 ctx 端到端 + 写失败线 + 无核心降级;不碰真实水位线)。
|
|
15
|
+
|
|
16
|
+
## ⚠️ 版本纪律
|
|
17
|
+
|
|
18
|
+
替换已安装包的 JS **无法进程内热更**(模块代缓存)——升级后需重启宿主载入。
|
|
19
|
+
|
|
20
|
+
## 打包 / 安装(DSH 宿主)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm pack
|
|
24
|
+
# 然后:plugin_manager install_bundle <tgz 绝对路径>(新 bundle 热生效;替换已装包需重启宿主)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
依赖:DSH 宿主(`systemPrompt` / `timer` 服务与事件面);核心库 `../../core/sleep.mjs` + `lib.mjs`(经 `akashaDir` 动态加载)。
|
package/cordis.patch.yml
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// 阿卡夏之书睡眠器 v1.5.1 —— 调度与注入(蒸馏逻辑归核心库 sleep.mjs)。
|
|
2
|
+
// · 触发:agent/status → idle(去抖 minIntervalHours)+ 定时兜底 ctx.interval(默认 1h)
|
|
3
|
+
// · 蒸馏/复核/待办/报告/水位线:核心库 sleepRun —— 手动 `node akasha.mjs sleep` 与之共用同一水位线
|
|
4
|
+
// · 条子一:systemPrompt.context(akasha:sleep)——每回合带最近一次睡眠摘要
|
|
5
|
+
// · 条子二(唤醒条):agent/created → agent.inject 起床包摘要(不唤醒、不打断)
|
|
6
|
+
// · 条子三(库脉搏 + 回查,v1.5):systemPrompt.context(akasha:pulse)——六库计数 + 最近写入;
|
|
7
|
+
// 窗口内(默认 30 分钟)有失败时追一行「回查提示」(错误钩子的注入级动作,不拦截)
|
|
8
|
+
// · 降级:核心库不可用 → sleep-error 观测线 + 条子提示文案,不炸、不失活。
|
|
9
|
+
import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import { pathToFileURL } from 'node:url';
|
|
14
|
+
|
|
15
|
+
export const inject = ['systemPrompt', 'timer'];
|
|
16
|
+
|
|
17
|
+
// 默认值:家目录下的 .akasha(可用 config 逐项覆盖)。
|
|
18
|
+
const DEFAULT_AKASHA = join(homedir(), '.akasha');
|
|
19
|
+
const DEFAULTS = {
|
|
20
|
+
log: join(DEFAULT_AKASHA, 'logs', 'hooks.jsonl'),
|
|
21
|
+
akashaDir: DEFAULT_AKASHA,
|
|
22
|
+
stateFile: join(DEFAULT_AKASHA, 'logs', 'sleep-state.json'),
|
|
23
|
+
inboxFile: join(DEFAULT_AKASHA, 'logs', 'inbox.jsonl'),
|
|
24
|
+
// 宿主应用模块目录(唤醒条用的 createUserMessage 从这里加载);留空 = 唤醒条注入跳过(其余功能不受影响)。
|
|
25
|
+
appModulesDir: '',
|
|
26
|
+
minIntervalHours: 6,
|
|
27
|
+
timerCheckMs: 3600000,
|
|
28
|
+
contextOrder: 130,
|
|
29
|
+
pulseOrder: 132
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** 唤醒条是否值得推(有内容才说话):无睡眠记录 / 有待办 / 审计有警告。 */
|
|
33
|
+
export function isWakeNoteworthy(k) {
|
|
34
|
+
return !k.sleep || (k.inbox?.totalItems ?? 0) > 0 || (k.review?.findings ?? []).some((f) => f.level === 'warn');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 组装「唤醒条」文本(纯函数)。 */
|
|
38
|
+
export function buildWakeNote(k) {
|
|
39
|
+
const lines = ['【阿卡夏·起床包】'];
|
|
40
|
+
lines.push(`库况:canon ${k.library.canon} / mirror ${k.library.mirror} / orphan ${k.library.orphan} / pricing ${k.library.pricing} / lexicon ${k.library.lexicon} / frontier ${k.library.frontier}`);
|
|
41
|
+
lines.push(k.sleep ? `睡眠:${k.sleep.lastRunAt}(${k.sleep.trigger})待办 ${k.sleep.todo}` : '睡眠:无记录(等待首个空闲)');
|
|
42
|
+
if ((k.inbox?.totalItems ?? 0) > 0) lines.push(`待办 inbox:${k.inbox.totalItems} 条(akasha_promote 可转正)`);
|
|
43
|
+
const warns = (k.review?.findings ?? []).filter((f) => f.level === 'warn');
|
|
44
|
+
if (warns.length) lines.push(`审计:${warns.map((f) => f.code + '×' + f.count).join(',')}`);
|
|
45
|
+
lines.push('详情:akasha_kit');
|
|
46
|
+
return lines.join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function apply(ctx, config = {}) {
|
|
50
|
+
const cfg = { ...DEFAULTS, ...config };
|
|
51
|
+
const require_ = createRequire(import.meta.url);
|
|
52
|
+
const reportDir = join(cfg.akashaDir, 'logs');
|
|
53
|
+
const stateFile = cfg.stateFile || join(reportDir, 'sleep-state.json');
|
|
54
|
+
const inboxFile = cfg.inboxFile || join(reportDir, 'inbox.jsonl');
|
|
55
|
+
|
|
56
|
+
const log = (record) => {
|
|
57
|
+
try {
|
|
58
|
+
mkdirSync(dirname(cfg.log), { recursive: true });
|
|
59
|
+
appendFileSync(cfg.log, JSON.stringify({ ts: new Date().toISOString(), ...record }) + '\n', 'utf8');
|
|
60
|
+
} catch { /* 静默:观测线写不了不拖垮宿主 */ }
|
|
61
|
+
};
|
|
62
|
+
const readState = () => { try { return JSON.parse(readFileSync(stateFile, 'utf8')); } catch { return {}; } };
|
|
63
|
+
|
|
64
|
+
// 核心库(蒸馏逻辑)动态加载:插件只做调度与注入;缺文件时降级留痕(不失活)。
|
|
65
|
+
const core = () => require_(join(cfg.akashaDir, 'sleep.mjs'));
|
|
66
|
+
|
|
67
|
+
const runSleep = (trigger) => {
|
|
68
|
+
try {
|
|
69
|
+
const r = core().sleepRun({ trigger, log: cfg.log, stateFile, inboxFile, reportDir });
|
|
70
|
+
if (!r.ok) { log({ kind: 'sleep-error', trigger, message: String(r.error).slice(0, 300) }); return false; }
|
|
71
|
+
log({ kind: 'sleep-done', trigger, report: r.reportFile, todo: r.todo, counters: r.state?.lastCounters, audit: r.state?.lastAudit });
|
|
72
|
+
return true;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
log({ kind: 'sleep-error', trigger, message: String(error?.message ?? error).slice(0, 300) });
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const maybeSleep = (trigger) => {
|
|
80
|
+
try {
|
|
81
|
+
const verdict = core().shouldSleep(readState(), Date.now(), cfg.minIntervalHours);
|
|
82
|
+
if (!verdict.due) { log({ kind: 'sleep-skip', trigger, sinceHours: verdict.hoursSince }); return; }
|
|
83
|
+
runSleep(trigger);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
log({ kind: 'sleep-error', trigger, message: String(error?.message ?? error).slice(0, 300) });
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// 唤醒条(M6b):新会话(agent/created)自动把「起床包」摘要递进 next-step inbox(不唤醒、不打断)。
|
|
90
|
+
// 仅当有内容(无睡眠记录 / 有待办 / 审计警告)才说话;子代理与 compact 不打扰;一切异常只留痕。
|
|
91
|
+
let createUserMessage = null;
|
|
92
|
+
let llmReady = Promise.resolve();
|
|
93
|
+
let llmSkipNote = null;
|
|
94
|
+
if (cfg.appModulesDir) {
|
|
95
|
+
llmReady = import(pathToFileURL(join(cfg.appModulesDir, '@deepseek-ai', 'dsh-llm', 'lib', 'index.js')).href)
|
|
96
|
+
.then((mod) => { if (typeof mod.createUserMessage === 'function') createUserMessage = mod.createUserMessage; })
|
|
97
|
+
.catch((error) => log({ kind: 'wake-note-llm-error', message: String(error?.message ?? error).slice(0, 200) }));
|
|
98
|
+
} else {
|
|
99
|
+
// 延迟到 armed 之后留痕:不让观测线提前污染 hooks 水位线(boot 首跑的 processedLines)
|
|
100
|
+
llmSkipNote = 'appModulesDir 未配置:唤醒条注入跳过(其余功能不受影响)';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const injectWakeNote = (agent) => {
|
|
104
|
+
try {
|
|
105
|
+
if (!agent || typeof agent.inject !== 'function' || !createUserMessage) return;
|
|
106
|
+
const k = require_(join(cfg.akashaDir, 'lib.mjs')).kit();
|
|
107
|
+
if (!isWakeNoteworthy(k)) return;
|
|
108
|
+
const message = createUserMessage({
|
|
109
|
+
content: [{ type: 'text', text: buildWakeNote(k) }],
|
|
110
|
+
source: { kind: 'akasha-wake' }
|
|
111
|
+
});
|
|
112
|
+
agent.inject(message);
|
|
113
|
+
log({ kind: 'wake-note', session: String(agent.id ?? ''), todo: k.inbox?.totalItems ?? 0, auditOk: k.review?.ok ?? null });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
log({ kind: 'wake-note-error', message: String(error?.message ?? error).slice(0, 200) });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
ctx.on('agent/created', (payload) => {
|
|
120
|
+
try {
|
|
121
|
+
// 2026-10-07 复查:官方 agent/created 的 source 只有 startup/resume;子代理守卫沿用 header.origin / delegationDepth。
|
|
122
|
+
const agent = payload?.agent;
|
|
123
|
+
const header = agent?.session?.header;
|
|
124
|
+
if (header?.origin === 'subagent' || (header?.delegationDepth ?? 0) > 0) return;
|
|
125
|
+
if (createUserMessage) { injectWakeNote(agent); return; }
|
|
126
|
+
void llmReady.then(() => injectWakeNote(agent));
|
|
127
|
+
} catch (error) {
|
|
128
|
+
log({ kind: 'wake-note-error', message: String(error?.message ?? error).slice(0, 200) });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// 条子:每个新回合的 runtime context 自动带上最近一次睡眠摘要(渲染自核心库,失败安全)。
|
|
133
|
+
ctx.systemPrompt.context({
|
|
134
|
+
name: 'akasha:sleep',
|
|
135
|
+
order: cfg.contextOrder,
|
|
136
|
+
text: () => {
|
|
137
|
+
try { return core().renderContextLine(readState()); }
|
|
138
|
+
catch { return '阿卡夏·睡眠:核心库不可用(akasha\\sleep.mjs 缺失或损坏)。'; }
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 回查提示(v1.5,错误钩子的注入级动作):进程内记录近期失败(工具 / agent),窗口内幂等渲染。
|
|
143
|
+
const recentFailures = [];
|
|
144
|
+
const noteFailure = (tool) => {
|
|
145
|
+
try {
|
|
146
|
+
recentFailures.push({ ts: Date.now(), tool: String(tool ?? '?') });
|
|
147
|
+
if (recentFailures.length > 20) recentFailures.splice(0, recentFailures.length - 20);
|
|
148
|
+
} catch { /* 静默 */ }
|
|
149
|
+
};
|
|
150
|
+
ctx.on('tools/result', (exec, result) => { if (result?.isError === true) noteFailure(exec?.name); });
|
|
151
|
+
ctx.on('agent/error', () => noteFailure('agent'));
|
|
152
|
+
|
|
153
|
+
// 库脉搏(v1.3;v1.5 起附加回查行):写→可见通道——并行会话 / 脚本写入库后,下一回合即可见并命名新内容。
|
|
154
|
+
ctx.systemPrompt.context({
|
|
155
|
+
name: 'akasha:pulse',
|
|
156
|
+
order: cfg.pulseOrder,
|
|
157
|
+
text: () => {
|
|
158
|
+
try {
|
|
159
|
+
const lib = require_(join(cfg.akashaDir, 'lib.mjs'));
|
|
160
|
+
const lines = [core().renderPulseLine(lib.pulse())];
|
|
161
|
+
const recall = core().renderRecallLine(recentFailures, Date.now());
|
|
162
|
+
if (recall) lines.push(recall);
|
|
163
|
+
return lines.filter(Boolean).join('\n') || null;
|
|
164
|
+
} catch { return null; }
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 空闲触发(事件失败开放)。
|
|
169
|
+
ctx.on('agent/status', (payload) => {
|
|
170
|
+
try { if (payload?.status === 'idle') maybeSleep('idle'); } catch { /* 静默 */ }
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// 定时兜底:宿主长期不空闲也能按节奏睡。
|
|
174
|
+
// ⚠️ timer 是「混入服务」:访问 ctx.interval 需要 inject:['timer'],否则属性访问本身抛出
|
|
175
|
+
// "cannot get property \"timer\" without inject"——v1.1.0 事故:整个 apply 失败、插件静默失活。
|
|
176
|
+
// ctx.interval 返回的停止函数再挂到本插件自己的 effect 上,卸载即停。
|
|
177
|
+
if (typeof ctx.interval === 'function') {
|
|
178
|
+
try {
|
|
179
|
+
const stop = ctx.interval(() => { try { maybeSleep('timer'); } catch { /* 静默 */ } }, cfg.timerCheckMs);
|
|
180
|
+
ctx.effect(() => () => { try { stop(); } catch { /* 静默 */ } });
|
|
181
|
+
} catch { /* 静默 */ }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 首次激活即跑一次,此后按去抖节奏。
|
|
185
|
+
const boot = readState();
|
|
186
|
+
if (!boot.lastRunAt) runSleep('activate');
|
|
187
|
+
|
|
188
|
+
log({ kind: 'sleep-armed', pid: process.pid, minIntervalHours: cfg.minIntervalHours, timerCheckMs: cfg.timerCheckMs });
|
|
189
|
+
if (llmSkipNote) log({ kind: 'wake-note-llm-skip', message: llmSkipNote });
|
|
190
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akasha-book/sleep",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.5.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "阿卡夏之书睡眠器 v1.5:调度与注入(蒸馏逻辑归核心库 sleep.mjs,可手动 node akasha.mjs sleep)——空闲/定时触发 + 每回合条子 + 新会话唤醒条 + 库脉搏(写→可见通道)+ 回查提示(窗口内失败追一行)",
|
|
7
|
+
"exports": { ".": "./lib/index.js" },
|
|
8
|
+
"files": ["lib/index.js", "cordis.patch.yml", "README.md"],
|
|
9
|
+
"dsh": {
|
|
10
|
+
"bundle": {
|
|
11
|
+
"patch": "./cordis.patch.yml"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|