@bolloon/bolloon-agent 0.3.46 → 0.3.48
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/dist/agents/knowledge-organizer.js +470 -0
- package/dist/agents/pi-sdk-tools.js +5 -3
- package/dist/agents/skill-organizer.js +322 -0
- package/dist/bootstrap/context-os.js +19 -10
- package/dist/cli/ink-app.js +15 -1
- package/dist/cli/loading-tui.js +18 -10
- package/dist/index.js +163 -30
- package/dist/social/agent-heartbeat.js +62 -1
- package/dist/web/server.js +43 -0
- package/package.json +1 -1
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge-organizer.ts — 自动整理心跳 · 知识层整理 (2026-08-10)
|
|
3
|
+
*
|
|
4
|
+
* 目的: 自动整理不止 skills — 每次整理 tick 还维护 Bolloon 的"自我认知"知识层:
|
|
5
|
+
*
|
|
6
|
+
* 1. archiveContextOs 归档 Context OS 资产层 (快照 + tmp 清理)
|
|
7
|
+
* 2. tidySocialRelations 整理外部社交关系 (known_peers + dunbar tier 统计/失联)
|
|
8
|
+
* 3. tidyExternalAgents 整理外部智能体描述 (peers/<pk>/agents/*)
|
|
9
|
+
* 4. tidyInternalAgents 整理内部智能体描述 (channels persona + persona/<agentId>/)
|
|
10
|
+
* 5. maintainJudgeness judgeness 内容维护 (descriptions 归档)
|
|
11
|
+
* 6. understandProjects 其他项目目录的理解 (扫描项目 → 04-Projects 索引)
|
|
12
|
+
* 7. understandUserProfile 用户画像的理解 (persona user.md + 01-Me → 画像快照)
|
|
13
|
+
* 8. archiveRecentLogs 最近记录的日志归档 (goals/engine 等 jsonl)
|
|
14
|
+
* 9. maintainGoals 用户长期/短期目标维护 (goals → 03-Current 摘要)
|
|
15
|
+
*
|
|
16
|
+
* 设计原则:
|
|
17
|
+
* - 每个整理器是纯函数: 输入 home/cwd (可注入), 输出 OrganizeSection, 全部 try/catch —
|
|
18
|
+
* 单个失败不阻塞其他 (整理是尽力而为, 不抛错).
|
|
19
|
+
* - 默认无 LLM (文件统计/归档/快照); 传 llm 时增强"理解类"整理器 (项目/画像/目标摘要).
|
|
20
|
+
* - runKnowledgeOrganize 串行跑全部 (顺序稳定, 日志可读), 汇总 sections.
|
|
21
|
+
*/
|
|
22
|
+
import * as fs from 'fs/promises';
|
|
23
|
+
import * as os from 'os';
|
|
24
|
+
import * as path from 'path';
|
|
25
|
+
/** 读取 JSON 文件, 失败返回 null (安静) */
|
|
26
|
+
async function readJson(p) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(await fs.readFile(p, 'utf-8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** 读取目录下直接子项 (文件+目录), 失败返回 [] */
|
|
35
|
+
async function readDir(p) {
|
|
36
|
+
try {
|
|
37
|
+
return await fs.readdir(p);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** 目录下 *.md 文件 (一层), 带 mtime */
|
|
44
|
+
async function listMd(p) {
|
|
45
|
+
const entries = await readDir(p);
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const e of entries) {
|
|
48
|
+
if (!e.endsWith('.md'))
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
const st = await fs.stat(path.join(p, e));
|
|
52
|
+
out.push({ file: e, mtime: st.mtimeMs });
|
|
53
|
+
}
|
|
54
|
+
catch { /* 跳过 */ }
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
// ───────────────────────── 1. Context OS 归档 ─────────────────────────
|
|
59
|
+
const CONTEXT_OS_LAYERS = [
|
|
60
|
+
'01-Me', '02-Network', '03-Current', '04-Projects', '05-Prompts',
|
|
61
|
+
'06-Protocols', '07-Knowledge', '08-Insights', '09-Tools', '10-Skills',
|
|
62
|
+
'11-Write', '12-Analysis',
|
|
63
|
+
];
|
|
64
|
+
/** 归档 Context OS: 统计各层资产 + 打快照清单 + 清理过期 tmp 草稿 */
|
|
65
|
+
export async function archiveContextOs(home) {
|
|
66
|
+
const root = path.join(home, '.bolloon', 'context-os');
|
|
67
|
+
const snapshotsDir = path.join(root, 'snapshots');
|
|
68
|
+
let handled = 0;
|
|
69
|
+
let totalAssets = 0;
|
|
70
|
+
let archivedTmp = 0;
|
|
71
|
+
try {
|
|
72
|
+
await fs.mkdir(snapshotsDir, { recursive: true });
|
|
73
|
+
// 统计各层资产 (直接子文件, 排除 README.md)
|
|
74
|
+
const layerStats = [];
|
|
75
|
+
for (const layer of CONTEXT_OS_LAYERS) {
|
|
76
|
+
const dir = path.join(root, layer);
|
|
77
|
+
const files = (await readDir(dir)).filter((f) => !f.startsWith('.') && f !== 'README.md');
|
|
78
|
+
if (files.length > 0)
|
|
79
|
+
layerStats.push({ layer, count: files.length });
|
|
80
|
+
totalAssets += files.length;
|
|
81
|
+
}
|
|
82
|
+
// tmp/ 过期草稿 (>1 天未改) → 归档到 snapshots/trash-<ts>/
|
|
83
|
+
const tmpDir = path.join(root, 'tmp');
|
|
84
|
+
try {
|
|
85
|
+
const tmpEntries = await fs.readdir(tmpDir);
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const trashDir = path.join(snapshotsDir, `trash-${Date.now()}`);
|
|
88
|
+
for (const f of tmpEntries) {
|
|
89
|
+
const fp = path.join(tmpDir, f);
|
|
90
|
+
try {
|
|
91
|
+
const st = await fs.stat(fp);
|
|
92
|
+
if (now - st.mtimeMs > 24 * 3600 * 1000) {
|
|
93
|
+
await fs.mkdir(trashDir, { recursive: true });
|
|
94
|
+
await fs.rename(fp, path.join(trashDir, f));
|
|
95
|
+
archivedTmp++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch { /* 单个失败跳过 */ }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch { /* tmp 不存在跳过 */ }
|
|
102
|
+
// 打快照清单 (轻量归档, 幂等)
|
|
103
|
+
const manifest = {
|
|
104
|
+
ts: new Date().toISOString(),
|
|
105
|
+
layers: layerStats,
|
|
106
|
+
totalAssets,
|
|
107
|
+
archivedTmp,
|
|
108
|
+
};
|
|
109
|
+
await fs.writeFile(path.join(snapshotsDir, `manifest-${Date.now()}.json`), JSON.stringify(manifest, null, 2), 'utf-8');
|
|
110
|
+
handled = totalAssets;
|
|
111
|
+
return {
|
|
112
|
+
key: 'context-os', label: 'Context OS 归档',
|
|
113
|
+
handled,
|
|
114
|
+
summary: `${layerStats.length} 层 · ${totalAssets} 个资产${archivedTmp > 0 ? ` · 归档 tmp ${archivedTmp} 个` : ''}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
return { key: 'context-os', label: 'Context OS 归档', handled, error: e?.message || String(e) };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// ───────────────────────── 2. 外部社交关系 ─────────────────────────
|
|
122
|
+
/** 整理外部社交关系: known_peers 统计 (活跃/失联) + dunbar tier 分布 */
|
|
123
|
+
export async function tidySocialRelations(home) {
|
|
124
|
+
try {
|
|
125
|
+
const peers = (await readJson(path.join(home, '.bolloon', 'known_peers.json')))?.peers || {};
|
|
126
|
+
const peerEntries = Object.entries(peers);
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
let live = 0;
|
|
129
|
+
let stale = 0;
|
|
130
|
+
for (const [, p] of peerEntries) {
|
|
131
|
+
const last = p?.lastConnectedAt ? new Date(p.lastConnectedAt).getTime() : 0;
|
|
132
|
+
if (now - last < 30 * 24 * 3600 * 1000)
|
|
133
|
+
live++;
|
|
134
|
+
else
|
|
135
|
+
stale++;
|
|
136
|
+
}
|
|
137
|
+
// dunbar tier 分布
|
|
138
|
+
const tierCount = {};
|
|
139
|
+
const peersDir = path.join(home, '.bolloon', 'peers');
|
|
140
|
+
const peerDirs = await readDir(peersDir);
|
|
141
|
+
for (const pk of peerDirs) {
|
|
142
|
+
const tier = await readJson(path.join(peersDir, pk, 'dunbar-tier.json'));
|
|
143
|
+
const t = tier?.tier || 'unknown';
|
|
144
|
+
tierCount[t] = (tierCount[t] || 0) + 1;
|
|
145
|
+
}
|
|
146
|
+
const tierDesc = Object.entries(tierCount)
|
|
147
|
+
.map(([t, n]) => `${t}=${n}`)
|
|
148
|
+
.join(' ');
|
|
149
|
+
return {
|
|
150
|
+
key: 'social', label: '外部社交关系',
|
|
151
|
+
handled: peerEntries.length,
|
|
152
|
+
summary: `${peerEntries.length} peers · 活跃 ${live} · 失联 ${stale} · tier[${tierDesc}]`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
return { key: 'social', label: '外部社交关系', handled: 0, error: e?.message || String(e) };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// ───────────────────────── 3. 外部智能体描述 ─────────────────────────
|
|
160
|
+
/** 整理外部智能体描述: peers/<pk>/agents/ 下的远端 manifest */
|
|
161
|
+
export async function tidyExternalAgents(home) {
|
|
162
|
+
try {
|
|
163
|
+
const peersDir = path.join(home, '.bolloon', 'peers');
|
|
164
|
+
const peerDirs = await readDir(peersDir);
|
|
165
|
+
let manifests = 0;
|
|
166
|
+
let peersWithAgents = 0;
|
|
167
|
+
for (const pk of peerDirs) {
|
|
168
|
+
const agentDir = path.join(peersDir, pk, 'agents');
|
|
169
|
+
const files = (await readDir(agentDir)).filter((f) => f.endsWith('.json') || f.endsWith('.md'));
|
|
170
|
+
if (files.length > 0) {
|
|
171
|
+
manifests += files.length;
|
|
172
|
+
peersWithAgents++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
key: 'agents-ext', label: '外部智能体描述',
|
|
177
|
+
handled: manifests,
|
|
178
|
+
summary: `${manifests} 个远端 agent (${peersWithAgents} 个 peer)`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
return { key: 'agents-ext', label: '外部智能体描述', handled: 0, error: e?.message || String(e) };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// ───────────────────────── 4. 内部智能体描述 ─────────────────────────
|
|
186
|
+
/** 整理内部智能体描述: channels.json persona + persona/<agentId>/ 6 文件 */
|
|
187
|
+
export async function tidyInternalAgents(home) {
|
|
188
|
+
try {
|
|
189
|
+
// channels.json 真实位置在 ~/.bolloon/sessions/channels.json (server-storage 主路径), 兼容旧 ~/.bolloon/channels.json
|
|
190
|
+
const sessionsFile = path.join(home, '.bolloon', 'sessions', 'channels.json');
|
|
191
|
+
const legacyFile = path.join(home, '.bolloon', 'channels.json');
|
|
192
|
+
let channelsRaw = await readJson(sessionsFile);
|
|
193
|
+
if (!channelsRaw)
|
|
194
|
+
channelsRaw = await readJson(legacyFile);
|
|
195
|
+
// channels.json 是纯数组 (server-storage 主格式), 兼容旧 {channels:[...]} 对象形态
|
|
196
|
+
const channels = Array.isArray(channelsRaw) ? channelsRaw : channelsRaw?.channels || [];
|
|
197
|
+
let withPersona = 0;
|
|
198
|
+
for (const c of channels) {
|
|
199
|
+
if (c?.persona?.name || c?.persona?.description)
|
|
200
|
+
withPersona++;
|
|
201
|
+
}
|
|
202
|
+
const personaDir = path.join(home, '.bolloon', 'persona');
|
|
203
|
+
const agentDirs = (await readDir(personaDir)).filter((d) => !d.startsWith('.'));
|
|
204
|
+
let personaFiles = 0;
|
|
205
|
+
for (const d of agentDirs) {
|
|
206
|
+
personaFiles += (await readDir(path.join(personaDir, d))).filter((f) => f.endsWith('.md')).length;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
key: 'agents-int', label: '内部智能体描述',
|
|
210
|
+
handled: channels.length,
|
|
211
|
+
summary: `${channels.length} channels (${withPersona} 有 persona) · ${agentDirs.length} 个 persona 目录 · ${personaFiles} 个文档`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
return { key: 'agents-int', label: '内部智能体描述', handled: 0, error: e?.message || String(e) };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// ───────────────────────── 5. judgeness 维护 ─────────────────────────
|
|
219
|
+
/** judgeness 内容维护: descriptions 统计 + 归档 >30 天未改的旧描述 */
|
|
220
|
+
export async function maintainJudgeness(home) {
|
|
221
|
+
try {
|
|
222
|
+
const descDir = path.join(home, '.bolloon', 'judgeness', 'descriptions');
|
|
223
|
+
const files = await listMd(descDir);
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
let archived = 0;
|
|
226
|
+
const archiveDir = path.join(descDir, '..', 'archive');
|
|
227
|
+
for (const f of files) {
|
|
228
|
+
if (now - f.mtime > 30 * 24 * 3600 * 1000) {
|
|
229
|
+
try {
|
|
230
|
+
await fs.mkdir(archiveDir, { recursive: true });
|
|
231
|
+
await fs.rename(path.join(descDir, f.file), path.join(archiveDir, f.file));
|
|
232
|
+
archived++;
|
|
233
|
+
}
|
|
234
|
+
catch { /* 单个失败跳过 */ }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
key: 'judgeness', label: 'judgeness 维护',
|
|
239
|
+
handled: files.length,
|
|
240
|
+
summary: `${files.length} 条描述${archived > 0 ? ` · 归档 ${archived} 条旧描述` : ''}`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
return { key: 'judgeness', label: 'judgeness 维护', handled: 0, error: e?.message || String(e) };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// ───────────────────────── 6. 其他项目目录的理解 ─────────────────────────
|
|
248
|
+
const PROJECT_MANIFESTS = ['package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'pom.xml', 'Makefile'];
|
|
249
|
+
/** 扫描的候选项目根目录 (相对 home) — 覆盖用户常见项目位置 */
|
|
250
|
+
const PROJECT_SCAN_ROOTS = ['Downloads', 'lean', 'DIAP-TS-SDK', 'alou', 'projects', 'workspace', 'dev', 'code', 'src'];
|
|
251
|
+
/** 扫描 home 下的项目目录 (深度 1, 找 manifest), 跳过 node_modules/.git/dist */
|
|
252
|
+
export async function scanProjects(home) {
|
|
253
|
+
const out = [];
|
|
254
|
+
for (const rootName of PROJECT_SCAN_ROOTS) {
|
|
255
|
+
const root = path.join(home, rootName);
|
|
256
|
+
const entries = await readDir(root);
|
|
257
|
+
for (const e of entries) {
|
|
258
|
+
if (e.startsWith('.') || e === 'node_modules' || e === 'Library')
|
|
259
|
+
continue;
|
|
260
|
+
const dir = path.join(root, e);
|
|
261
|
+
let type = '';
|
|
262
|
+
try {
|
|
263
|
+
const st = await fs.stat(dir);
|
|
264
|
+
if (!st.isDirectory())
|
|
265
|
+
continue;
|
|
266
|
+
for (const m of PROJECT_MANIFESTS) {
|
|
267
|
+
try {
|
|
268
|
+
await fs.access(path.join(dir, m));
|
|
269
|
+
type = m;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
catch { /* 下一个 */ }
|
|
273
|
+
}
|
|
274
|
+
if (!type)
|
|
275
|
+
continue;
|
|
276
|
+
out.push({ name: e, path: dir, type, mtime: st.mtimeMs });
|
|
277
|
+
}
|
|
278
|
+
catch { /* 跳过 */ }
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// 去重 (同一目录可能被多个 root 覆盖)
|
|
282
|
+
const seen = new Set();
|
|
283
|
+
return out.filter((p) => {
|
|
284
|
+
if (seen.has(p.path))
|
|
285
|
+
return false;
|
|
286
|
+
seen.add(p.path);
|
|
287
|
+
return true;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/** 其他项目目录的理解: 扫描项目 → 更新 04-Projects/项目理解.md (llm 可选增强) */
|
|
291
|
+
export async function understandProjects(home, llm) {
|
|
292
|
+
try {
|
|
293
|
+
const projects = await scanProjects(home);
|
|
294
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
295
|
+
let extra = '';
|
|
296
|
+
if (llm && projects.length > 0) {
|
|
297
|
+
try {
|
|
298
|
+
const lines = projects.map((p) => `- ${p.name} (${p.type})`).join('\n');
|
|
299
|
+
const resp = await llm(`以下是用户 ${os.homedir()} 下的项目清单, 请用一句话概括每个项目可能是什么 (技术栈/用途), 按原样输出每行:\n${lines}\n\n输出格式: - 项目名: 一句话理解`);
|
|
300
|
+
extra = `\n\n## LLM 理解 (${now})\n${resp.slice(0, 2000)}`;
|
|
301
|
+
}
|
|
302
|
+
catch { /* LLM 增强失败用模板 */ }
|
|
303
|
+
}
|
|
304
|
+
const sorted = [...projects].sort((a, b) => b.mtime - a.mtime);
|
|
305
|
+
const body = `# 项目理解索引 (自动整理心跳, ${now})\n\n` +
|
|
306
|
+
`> 由自动整理心跳生成 — 扫描 home 下常见项目目录的 manifest (package.json / pyproject.toml / go.mod / Cargo.toml).\n\n` +
|
|
307
|
+
`## 项目清单 (${projects.length})\n\n` +
|
|
308
|
+
sorted.map((p) => `- **${p.name}** \`${p.type}\` — ${p.path}`).join('\n') +
|
|
309
|
+
extra + '\n';
|
|
310
|
+
const outFile = path.join(home, '.bolloon', 'context-os', '04-Projects', '项目理解.md');
|
|
311
|
+
await fs.mkdir(path.dirname(outFile), { recursive: true });
|
|
312
|
+
await fs.writeFile(outFile, body, 'utf-8');
|
|
313
|
+
return {
|
|
314
|
+
key: 'projects', label: '项目目录理解',
|
|
315
|
+
handled: projects.length,
|
|
316
|
+
summary: `${projects.length} 个项目已索引 → 04-Projects/项目理解.md`,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
return { key: 'projects', label: '项目目录理解', handled: 0, error: e?.message || String(e) };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// ───────────────────────── 7. 用户画像的理解 ─────────────────────────
|
|
324
|
+
/** 用户画像的理解: 汇总 persona/<agentId>/user.md + context-os/01-Me/ → 画像快照 */
|
|
325
|
+
export async function understandUserProfile(home, llm) {
|
|
326
|
+
try {
|
|
327
|
+
const personaDir = path.join(home, '.bolloon', 'persona');
|
|
328
|
+
const agentDirs = (await readDir(personaDir)).filter((d) => !d.startsWith('.'));
|
|
329
|
+
const meDir = path.join(home, '.bolloon', 'context-os', '01-Me');
|
|
330
|
+
const meFiles = (await listMd(meDir)).filter((f) => f.file !== 'README.md');
|
|
331
|
+
let handled = agentDirs.length + meFiles.length;
|
|
332
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
333
|
+
// 读 user.md 内容片段 (每 agent 目录)
|
|
334
|
+
let profileSnippet = '';
|
|
335
|
+
for (const d of agentDirs) {
|
|
336
|
+
const userMd = path.join(personaDir, d, 'user.md');
|
|
337
|
+
try {
|
|
338
|
+
const raw = (await fs.readFile(userMd, 'utf-8')).slice(0, 800);
|
|
339
|
+
if (raw.trim())
|
|
340
|
+
profileSnippet += `\n### ${d}/user.md\n${raw}\n`;
|
|
341
|
+
}
|
|
342
|
+
catch { /* 无 user.md */ }
|
|
343
|
+
}
|
|
344
|
+
let extra = '';
|
|
345
|
+
if (llm && (profileSnippet || meFiles.length > 0)) {
|
|
346
|
+
try {
|
|
347
|
+
const resp = await llm(`以下是用户的画像素材 (persona user.md + 01-Me 资产). 请提炼 3-5 条"稳定的用户画像要点" (身份/偏好/目标/工作方式):\n${profileSnippet.slice(0, 3000)}\n\n输出要点列表 (每条一行, 以 - 开头)`);
|
|
348
|
+
extra = `\n\n## LLM 画像要点 (${now})\n${resp.slice(0, 2000)}`;
|
|
349
|
+
}
|
|
350
|
+
catch { /* LLM 增强失败 */ }
|
|
351
|
+
}
|
|
352
|
+
const meNames = meFiles.map((f) => f.file).join('; ');
|
|
353
|
+
const body = `# 用户画像快照 (自动整理心跳, ${now})\n\n` +
|
|
354
|
+
`> 由自动整理心跳汇总 persona user.md + Context OS 01-Me. 详细档案见 01-Me/个人档案.md.\n\n` +
|
|
355
|
+
`## 画像素材来源\n- persona 目录: ${agentDirs.length} 个 (${agentDirs.join(', ') || '无'})\n- 01-Me 资产: ${meFiles.length} 个 (${meNames || '无'})` +
|
|
356
|
+
(profileSnippet ? `\n\n## user.md 内容片段\n${profileSnippet.slice(0, 3000)}` : '') +
|
|
357
|
+
extra + '\n';
|
|
358
|
+
const outFile = path.join(meDir, '用户画像快照.md');
|
|
359
|
+
await fs.mkdir(meDir, { recursive: true });
|
|
360
|
+
await fs.writeFile(outFile, body, 'utf-8');
|
|
361
|
+
return {
|
|
362
|
+
key: 'user', label: '用户画像理解',
|
|
363
|
+
handled,
|
|
364
|
+
summary: `${agentDirs.length} 个 persona + ${meFiles.length} 个 01-Me 资产 → 用户画像快照`,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
catch (e) {
|
|
368
|
+
return { key: 'user', label: '用户画像理解', handled: 0, error: e?.message || String(e) };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// ───────────────────────── 8. 最近记录的日志归档 ─────────────────────────
|
|
372
|
+
/** 最近记录的日志: 统计 + 归档 >30 天未改的 jsonl 旧文件 (排除活跃依赖: goals/event.jsonl) */
|
|
373
|
+
export async function archiveRecentLogs(home) {
|
|
374
|
+
try {
|
|
375
|
+
const root = path.join(home, '.bolloon');
|
|
376
|
+
// 候选日志目录: goals/ engine/ trajectories/ sessions/jsonl/ sidechains/ (存在的才扫)
|
|
377
|
+
const logDirs = ['goals', 'engine', 'trajectories', path.join('sessions', 'jsonl'), 'sidechains'];
|
|
378
|
+
let total = 0;
|
|
379
|
+
let archived = 0;
|
|
380
|
+
const now = Date.now();
|
|
381
|
+
// goal-resume.ts 依赖 goals/event.jsonl 恢复目标事件 — 永不归档
|
|
382
|
+
const protectedFiles = new Set(['event.jsonl']);
|
|
383
|
+
for (const rel of logDirs) {
|
|
384
|
+
const dir = path.join(root, rel);
|
|
385
|
+
const entries = await readDir(dir);
|
|
386
|
+
for (const e of entries) {
|
|
387
|
+
if (!e.endsWith('.jsonl') && !e.endsWith('.log'))
|
|
388
|
+
continue;
|
|
389
|
+
if (protectedFiles.has(e))
|
|
390
|
+
continue;
|
|
391
|
+
total++;
|
|
392
|
+
try {
|
|
393
|
+
const st = await fs.stat(path.join(dir, e));
|
|
394
|
+
if (now - st.mtimeMs > 30 * 24 * 3600 * 1000) {
|
|
395
|
+
const archiveDir = path.join(dir, 'archive');
|
|
396
|
+
await fs.mkdir(archiveDir, { recursive: true });
|
|
397
|
+
await fs.rename(path.join(dir, e), path.join(archiveDir, e));
|
|
398
|
+
archived++;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch { /* 单个失败跳过 */ }
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
key: 'logs', label: '最近日志归档',
|
|
406
|
+
handled: total,
|
|
407
|
+
summary: `${total} 个日志文件${archived > 0 ? ` · 归档 ${archived} 个旧文件` : ''}`,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
return { key: 'logs', label: '最近日志归档', handled: 0, error: e?.message || String(e) };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// ───────────────────────── 9. 用户目标维护 ─────────────────────────
|
|
415
|
+
/** 用户长期/短期目标维护: goals/queue.json + event.jsonl + 03-Current → 目标摘要 */
|
|
416
|
+
export async function maintainGoals(home, llm) {
|
|
417
|
+
try {
|
|
418
|
+
const goalsDir = path.join(home, '.bolloon', 'goals');
|
|
419
|
+
const queue = (await readJson(path.join(goalsDir, 'queue.json'))) || {};
|
|
420
|
+
// queue.json 结构未知, 兼容 object/array
|
|
421
|
+
const queueItems = Array.isArray(queue) ? queue : Object.values(queue).filter((v) => v && typeof v === 'object');
|
|
422
|
+
const events = await readDir(goalsDir);
|
|
423
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
424
|
+
const currentDir = path.join(home, '.bolloon', 'context-os', '03-Current');
|
|
425
|
+
const currentFiles = (await listMd(currentDir)).filter((f) => f.file !== 'README.md');
|
|
426
|
+
let extra = '';
|
|
427
|
+
if (llm && (queueItems.length > 0 || currentFiles.length > 0)) {
|
|
428
|
+
try {
|
|
429
|
+
const resp = await llm(`以下是用户当前目标素材 (goals queue + 03-Current 进行中的任务). 请区分长期目标与短期目标, 每条一行:\n- [长期/短期] 目标描述\n素材:\n${JSON.stringify(queueItems).slice(0, 1500)}\n${currentFiles.map((f) => f.file).join('\n')}`);
|
|
430
|
+
extra = `\n\n## LLM 目标分层 (${now})\n${resp.slice(0, 1500)}`;
|
|
431
|
+
}
|
|
432
|
+
catch { /* LLM 增强失败 */ }
|
|
433
|
+
}
|
|
434
|
+
const body = `# 用户目标摘要 (自动整理心跳, ${now})\n\n` +
|
|
435
|
+
`> 来源: goals/queue.json + goals/event.jsonl + 03-Current 进行中任务.\n\n` +
|
|
436
|
+
`## 目标队列\n${queueItems.length > 0 ? queueItems.map((g, i) => `- ${g?.description || g?.target || g?.id || `目标 ${i + 1}`}`).join('\n') : '(空)'}\n` +
|
|
437
|
+
`## 进行中任务 (03-Current)\n${currentFiles.length > 0 ? currentFiles.map((f) => `- ${f.file.replace(/\.md$/, '')}`).join('\n') : '(空)'}` +
|
|
438
|
+
extra + '\n';
|
|
439
|
+
const outFile = path.join(currentDir, '目标摘要.md');
|
|
440
|
+
await fs.mkdir(currentDir, { recursive: true });
|
|
441
|
+
await fs.writeFile(outFile, body, 'utf-8');
|
|
442
|
+
return {
|
|
443
|
+
key: 'goals', label: '用户目标维护',
|
|
444
|
+
handled: queueItems.length + currentFiles.length,
|
|
445
|
+
summary: `${queueItems.length} 条队列 + ${currentFiles.length} 个进行中 → 目标摘要`,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
catch (e) {
|
|
449
|
+
return { key: 'goals', label: '用户目标维护', handled: 0, error: e?.message || String(e) };
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// ───────────────────────── 总入口 ─────────────────────────
|
|
453
|
+
/** 自动整理心跳 · 知识层: 串行跑全部 9 个整理器, 汇总 sections */
|
|
454
|
+
export async function runKnowledgeOrganize(opts = {}) {
|
|
455
|
+
const home = opts.home || os.homedir();
|
|
456
|
+
const cwd = opts.cwd || process.cwd();
|
|
457
|
+
const llm = opts.llm;
|
|
458
|
+
const sections = [];
|
|
459
|
+
sections.push(await archiveContextOs(home));
|
|
460
|
+
sections.push(await tidySocialRelations(home));
|
|
461
|
+
sections.push(await tidyExternalAgents(home));
|
|
462
|
+
sections.push(await tidyInternalAgents(home));
|
|
463
|
+
sections.push(await maintainJudgeness(home));
|
|
464
|
+
sections.push(await understandProjects(home, llm));
|
|
465
|
+
sections.push(await understandUserProfile(home, llm));
|
|
466
|
+
sections.push(await archiveRecentLogs(home));
|
|
467
|
+
sections.push(await maintainGoals(home, llm));
|
|
468
|
+
const totalHandled = sections.reduce((acc, s) => acc + (s.error ? 0 : s.handled), 0);
|
|
469
|
+
return { sections, totalHandled };
|
|
470
|
+
}
|
|
@@ -1776,7 +1776,8 @@ export function registerBuiltinTools(ctx) {
|
|
|
1776
1776
|
execute: async () => {
|
|
1777
1777
|
try {
|
|
1778
1778
|
const { readContextAssets, formatLayerListing } = await import('../bootstrap/context-os.js');
|
|
1779
|
-
|
|
1779
|
+
// 2026-08-09: 按 agentId 分区读取 (每个智能体独立 Context OS)
|
|
1780
|
+
const listings = await readContextAssets(undefined, undefined, undefined, ctx.agentId || '');
|
|
1780
1781
|
const total = listings.reduce((s, l) => s + l.fileCount, 0);
|
|
1781
1782
|
if (total === 0)
|
|
1782
1783
|
return { success: true, output: '📂 Context OS 资产层已就绪 (12+3 层), 当前暂无资产. 有价值的内容用 write_context_asset 写入对应层.' };
|
|
@@ -1816,7 +1817,7 @@ export function registerBuiltinTools(ctx) {
|
|
|
1816
1817
|
tags = t.map(String);
|
|
1817
1818
|
}
|
|
1818
1819
|
catch { /* tags 解析失败 */ }
|
|
1819
|
-
const r = await writeContextAsset({ layer, title, content, tags, domain: args.domain ? String(args.domain) : undefined });
|
|
1820
|
+
const r = await writeContextAsset({ layer, title, content, tags, domain: args.domain ? String(args.domain) : undefined }, undefined, ctx.agentId || '');
|
|
1820
1821
|
if (!r.ok)
|
|
1821
1822
|
return { success: false, error: r.error };
|
|
1822
1823
|
if (r.skipped)
|
|
@@ -1840,7 +1841,8 @@ export function registerBuiltinTools(ctx) {
|
|
|
1840
1841
|
const { readContextAssets, formatLayerListing } = await import('../bootstrap/context-os.js');
|
|
1841
1842
|
const layer = args.layer ? String(args.layer) : undefined;
|
|
1842
1843
|
const kw = args.keyword ? String(args.keyword) : undefined;
|
|
1843
|
-
|
|
1844
|
+
// 2026-08-09: 按 agentId 分区读取 (每个智能体独立 Context OS)
|
|
1845
|
+
const listings = await readContextAssets(layer, kw, undefined, ctx.agentId || '');
|
|
1844
1846
|
if (listings.every((l) => l.fileCount === 0)) {
|
|
1845
1847
|
return { success: true, output: layer ? `📂 资产层 ${layer} 暂无资产` : '📂 资产层暂无资产' };
|
|
1846
1848
|
}
|