@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/index.js CHANGED
@@ -16,7 +16,7 @@ import { getGlobalSharedContext } from './social/global-shared-context.js';
16
16
  import { createBollharnessIntegration } from './bollharness-integration/index.js';
17
17
  import * as readline from 'readline';
18
18
  import { printBanner, renderDialog, renderUserMessage, renderAgentMessage, renderMessageBox, renderToolCallListItem, termWidth } from './cli/loading-tui.js';
19
- import { startInk, stopInk, inkAppendLine as appendLine, inkSetStatus, inkSetThinking } from './cli/ink-app.js';
19
+ import { startInk, stopInk, inkAppendLine as appendLine, inkSetStatus, inkSetThinking, inkSetTransient } from './cli/ink-app.js';
20
20
  // 启动自动检查更新:后台、节流、检测到新版本自动安装(可被 --no-update / BOLLOON_SKIP_UPDATE 关闭)
21
21
  import { createRequire } from 'module';
22
22
  const _require = createRequire(import.meta.url);
@@ -271,12 +271,44 @@ async function bootstrapIroh(keypair, name) {
271
271
  // Agent 懒加载
272
272
  // ---------------------------------------------------------------------------
273
273
  let agent = null;
274
+ /** 2026-08-09: agent 当前绑定的 channel id (null = 默认 harness 身份) — 切换时据此重建 */
275
+ let agentBoundChannelId = null;
274
276
  let harness = null;
275
277
  let hybridMessenger = null;
276
278
  let agentIdentity = null;
277
279
  async function getAgent() {
278
- if (!agent) {
279
- const identityDoc = agentIdentity ? {
280
+ // 2026-08-09: agent 身份绑定当前 active channel — 切换 / 新建 channel 后重建.
281
+ // 旧实现: agent 全局单例 + peerId:'harness' 固定, 切 channel 身份不变 (bug).
282
+ // 新实现: channel 有 agentId/did/publicKey/persona 时按 channel 建 session,
283
+ // agentIdentity 同步更新, loadSessionKey 回灌该 channel 的历史.
284
+ const targetChannelId = cliActiveChannelId || null;
285
+ if (agent && agentBoundChannelId === targetChannelId)
286
+ return agent;
287
+ // 读取当前 active channel 的持久身份
288
+ let chIdentity = null;
289
+ if (targetChannelId) {
290
+ try {
291
+ const { getIdentityStore } = await import('./agents/agent-identity-store.js');
292
+ const store = getIdentityStore();
293
+ await store.load();
294
+ const ch = store.rawChannels.find((c) => c.id === targetChannelId);
295
+ if (ch)
296
+ chIdentity = ch;
297
+ }
298
+ catch { /* 读不到就退默认 */ }
299
+ }
300
+ let identityDoc;
301
+ if (chIdentity?.did && chIdentity.publicKey) {
302
+ // channel 已有持久 DID → 用 channel 身份
303
+ identityDoc = {
304
+ did: chIdentity.did,
305
+ name: chIdentity.persona?.name || chIdentity.name || 'agent',
306
+ publicKey: chIdentity.publicKey,
307
+ createdAt: Date.now(),
308
+ };
309
+ }
310
+ else if (agentIdentity) {
311
+ identityDoc = {
280
312
  did: agentIdentity.did,
281
313
  name: agentIdentity.name,
282
314
  publicKey: agentIdentity.publicKey,
@@ -285,15 +317,39 @@ async function getAgent() {
285
317
  p2pChannel: agentIdentity.p2pChannel,
286
318
  cid: agentIdentity.cid,
287
319
  ipnsName: agentIdentity.ipnsName
288
- } : undefined;
289
- agent = await createAgentSession({
290
- cwd: process.cwd(),
291
- peerId: 'harness',
292
- identityDoc
293
- });
320
+ };
321
+ }
322
+ else {
323
+ identityDoc = undefined;
324
+ }
325
+ const loadSessionKey = targetChannelId
326
+ ? `${targetChannelId}:${chIdentity?.currentSessionId || 'default'}`
327
+ : undefined;
328
+ agent = await createAgentSession({
329
+ cwd: process.cwd(),
330
+ peerId: targetChannelId ?? 'harness',
331
+ identityDoc,
332
+ // 2026-08-09: 透传 channel.agentId → persona docs 按 agent 加载 (身份真正变化)
333
+ agentId: chIdentity?.agentId || (targetChannelId ? undefined : agentIdentity?.name),
334
+ loadSessionKey,
335
+ });
336
+ agentBoundChannelId = targetChannelId;
337
+ // 同步 agentIdentity (状态栏 / 身份引用)
338
+ if (chIdentity) {
339
+ agentIdentity = {
340
+ did: chIdentity.did || agentIdentity?.did || '',
341
+ name: chIdentity.persona?.name || chIdentity.name || 'agent',
342
+ publicKey: chIdentity.publicKey || agentIdentity?.publicKey || '',
343
+ peerId: targetChannelId ?? undefined,
344
+ };
294
345
  }
295
346
  return agent;
296
347
  }
348
+ /** 强制重建 agent (切 channel / 新建 agent 后调用) */
349
+ function invalidateAgent() {
350
+ agent = null;
351
+ agentBoundChannelId = null;
352
+ }
297
353
  // ---------------------------------------------------------------------------
298
354
  // Dispatch
299
355
  // ---------------------------------------------------------------------------
@@ -367,6 +423,8 @@ let cliStartTime = 0;
367
423
  let cliModelName = '…';
368
424
  let cliAgentName = '…';
369
425
  let cliActiveChannelId = null;
426
+ // 2026-08-10: CLI 自动整理心跳 (与社交心跳并列, 独立于 server) — 退出时 stop
427
+ let cliOrganizeHeartbeat = null;
370
428
  function fmtDuration(ms) {
371
429
  const s = Math.floor(ms / 1000);
372
430
  if (s < 60)
@@ -503,6 +561,59 @@ async function startCLI(comm) {
503
561
  catch { /* 降级: getCliCtxUsage 返回 0/1M */ }
504
562
  const initialStatus = `${C_ACCENT}${cliModelName}${RESET}${C_DIM} │${RESET} ${cliAgentName} ${C_DIM}│${RESET} ⏱ 0s${C_DIM} │${RESET} ${buildContextBar(getCliCtxUsage())}`;
505
563
  startInk((text) => { processInput(text, comm); }, initialStatus, getStatus);
564
+ // 2026-08-10: 自动整理心跳 (CLI 侧, 与社交心跳并列) — 启动后立即"固定看一下 skills view"
565
+ // (扫描遗留 skills), 之后按周期 (默认 30min, env BOLLOON_ORGANIZE_HEARTBEAT_MS) 完整进化经验.
566
+ // 显示走 transient 颜文字行: 触发时显示, 结束后清空 (显示为空).
567
+ try {
568
+ const { startOrganizeHeartbeat } = await import('./agents/skill-organizer.js');
569
+ let firstOrganizeScan = true; // 启动第一轮只做快速遗留扫描 (无 LLM), 不阻塞启动
570
+ cliOrganizeHeartbeat = startOrganizeHeartbeat({
571
+ intervalMs: Number(process.env.BOLLOON_ORGANIZE_HEARTBEAT_MS) || 30 * 60_000,
572
+ onStart: () => inkSetTransient(`${C_DIM}(`・ω・´) 自动整理经验中...${RESET}`),
573
+ onEnd: (r) => {
574
+ inkSetTransient(null); // 结束后去除显示效果 (显示为空)
575
+ if (r && r.leftovers.length > 0) {
576
+ appendLine(`${C_DIM}🧹 发现 ${r.leftovers.length} 个遗留 skills: ${r.leftovers.slice(0, 5).map(l => l.name).join(', ')}${RESET}`);
577
+ }
578
+ if (r && r.evolved.length > 0) {
579
+ appendLine(`${C_OK}✨ 经验进化: ${r.evolved.join(', ')}${RESET}`);
580
+ }
581
+ // 2026-08-10: 知识层整理汇总 (Context OS/社交/智能体/judgeness/项目/画像/日志/目标)
582
+ const kSections = (r?.knowledge?.sections || []).filter(s => s.handled > 0 || s.error);
583
+ if (kSections.length > 0) {
584
+ appendLine(`${C_DIM}🧠 知识整理: ${kSections.map(s => s.error ? `${s.label}✗` : `${s.label}✓`).join(' ')}${RESET}`);
585
+ }
586
+ },
587
+ onError: () => inkSetTransient(null),
588
+ run: async () => {
589
+ // 启动第一轮 (firstOrganizeScan=true) 只做快速扫描 — 不拿 LLM, 立即执行.
590
+ // 后续周期轮才取 agent LLM 做完整经验进化 (2026-08-10: getAgent 在无 LLM 环境可能
591
+ // 长时间挂起 → 8s 超时降级为仅扫描)
592
+ let llm;
593
+ const needEvolve = !firstOrganizeScan;
594
+ if (needEvolve) {
595
+ try {
596
+ const a = await Promise.race([
597
+ getAgent().catch(() => null),
598
+ new Promise((res) => setTimeout(() => res(null), 8000)),
599
+ ]);
600
+ if (a && typeof a.promptStream === 'function') {
601
+ llm = (p) => a.promptStream(p, () => { }, undefined, cliActiveChannelId || undefined);
602
+ }
603
+ }
604
+ catch { /* 无 agent → 仅扫描 */ }
605
+ }
606
+ const { runAutoOrganize } = await import('./agents/skill-organizer.js');
607
+ const evolve = needEvolve && !!llm;
608
+ firstOrganizeScan = false;
609
+ return runAutoOrganize({ llm, source: 'cli:organize-heartbeat', evolve });
610
+ },
611
+ });
612
+ // 启动即跑一轮: 每次打开后固定看一下 skills view (遗留扫描, 快, 不阻塞)
613
+ // 延迟 3s 等 Ink 挂载完成 (global __inkAppend/__inkSetTransient 注册) — 否则首轮显示丢失
614
+ setTimeout(() => { cliOrganizeHeartbeat?.runOnce().catch(() => { }); }, 3000);
615
+ }
616
+ catch { /* 自动整理启动失败不阻塞 CLI */ }
506
617
  // Wait on a promise that resolves on Ctrl+C / 双击 Esc
507
618
  // (ink-app 的 requestExit 调 __inkRequestExit → resolve, 清理后 process.exit)
508
619
  let cliExitResolve = () => { };
@@ -512,6 +623,10 @@ async function startCLI(comm) {
512
623
  delete globalThis.__inkRequestExit;
513
624
  stopInk();
514
625
  appendLine(`\n${CYAN}👋 再见!${RESET}`);
626
+ try {
627
+ cliOrganizeHeartbeat?.stop();
628
+ }
629
+ catch { /* 非致命 */ }
515
630
  comm.stop();
516
631
  process.exit(0);
517
632
  }
@@ -575,6 +690,13 @@ async function processInput(input, comm) {
575
690
  await store.setActive(r.channel.id);
576
691
  cliAgentName = r.identity.name;
577
692
  cliActiveChannelId = r.channel.id;
693
+ // 2026-08-09: 切 channel 必须重建 agent session — 否则身份/记忆停留在旧 channel (bug 修复)
694
+ invalidateAgent();
695
+ // 立即重建 (提前建好, 避免下次输入才卡顿; 失败不阻塞切换)
696
+ try {
697
+ await getAgent();
698
+ }
699
+ catch { /* 非致命, 下次输入时再试 */ }
578
700
  inkSetStatus(getStatus()); // 触发状态栏立即重绘 (无需等 1s 定时器)
579
701
  const extra = prev && prev.name !== r.identity.name ? ` (从 ${prev.name} 切换)` : '';
580
702
  appendLine(`${C_ACCENT}→ 当前智能体: ${r.identity.name}${RESET}${extra}`);
@@ -599,38 +721,47 @@ async function processInput(input, comm) {
599
721
  const { getIdentityStore } = await import('./agents/agent-identity-store.js');
600
722
  const store = getIdentityStore();
601
723
  await store.load();
602
- const { readFile, writeFile, mkdir } = await import('fs/promises');
603
- const { join } = await import('path');
604
- const home = process.env.HOME || '/tmp';
605
- const channelsPath = join(home, '.bolloon', 'sessions', 'channels.json');
606
- let channels = [];
607
- try {
608
- const parsed = JSON.parse(await readFile(channelsPath, 'utf-8'));
609
- channels = Array.isArray(parsed) ? parsed : parsed?.channels || [];
610
- }
611
- catch { /* 首次无文件 */ }
612
- const dupName = channels.find((c) => c.name === name.trim());
724
+ // 2026-08-09: 复用 server-storage updateChannels 原子写 (互斥锁) 旧实现裸 readFile→push→writeFile
725
+ // 与 Web server 并发写 channels.json 互相覆盖 → 创建的 agent 重启后丢失 (bug 修复)
726
+ const { updateChannels } = await import('./web/server-storage.js');
727
+ const dupName = store.rawChannels.find((c) => c.name === name.trim());
613
728
  if (dupName) {
614
729
  appendLine(`${C_ERROR}同名智能体已存在: '${dupName.name}' (id=${dupName.id})${RESET}`);
615
730
  return;
616
731
  }
617
732
  const id = `ch_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
733
+ const agentId = `agent-${name.trim().toLowerCase().replace(/\s+/g, '-')}`;
618
734
  const ch = {
619
735
  id,
620
736
  name: name.trim(),
621
- agentId: `agent-${name.trim().toLowerCase().replace(/\s+/g, '-')}`,
737
+ agentId,
622
738
  createdAt: new Date().toISOString(),
623
739
  updatedAt: new Date().toISOString(),
624
740
  currentSessionId: 'default',
625
741
  };
626
742
  if (personaHint)
627
743
  ch.persona = { name: name.trim(), description: personaHint };
628
- channels.push(ch);
629
- await mkdir(join(home, '.bolloon', 'sessions'), { recursive: true });
630
- await writeFile(channelsPath, JSON.stringify(channels, null, 2), 'utf-8');
744
+ // 2026-08-09: 立即生成该 agent 的持久 DID 身份 (agent-keys/<agentId>.json)
745
+ // 与 server fixOneChannelDID 对齐, 保证 CLI 新建的 agent 身份稳定且归属用户 DID
746
+ try {
747
+ const { loadOrCreateAgentIdentity } = await import('./agents/agent-identity.js');
748
+ const idt = loadOrCreateAgentIdentity(agentId);
749
+ ch.did = idt.did;
750
+ ch.publicKey = idt.publicKey;
751
+ }
752
+ catch { /* DID 生成失败不阻塞创建 */ }
753
+ const channels = await updateChannels((chs) => [...chs, ch]);
754
+ // 刷新 store 缓存 (updateChannels 走了 server-storage, store 内存还是旧的)
755
+ await store.load();
631
756
  await store.setActive(id);
632
757
  cliAgentName = name.trim();
633
758
  cliActiveChannelId = id;
759
+ // 2026-08-09: 新建 agent 后立即重建 session — 否则新 agent 身份不加载 (bug 修复)
760
+ invalidateAgent();
761
+ try {
762
+ await getAgent();
763
+ }
764
+ catch { /* 非致命 */ }
634
765
  inkSetStatus(getStatus());
635
766
  appendLine(`${C_OK}✓ 已创建智能体 channel: ${name.trim()}${RESET} (${C_DIM}${id}${RESET})${personaHint ? `\n ${C_DIM}persona: ${personaHint}${RESET}` : ''}`);
636
767
  }
@@ -1544,18 +1675,20 @@ async function processInput(input, comm) {
1544
1675
  appendLine(renderAgentMessage(response));
1545
1676
  // 停止思考动画
1546
1677
  inkSetThinking(false);
1547
- // 2026-08-04: run-end 经验整理 — 连续成功工具 ≥2 自动写 skill 候选 (颜文字加载)
1678
+ // 2026-08-04: run-end 经验整理 — 连续成功工具 ≥2 自动写 skill 候选
1679
+ // 2026-08-10: 显示改走 transient 行 (颜文字位置): 开始时显示, 结束后清空 (显示为空),
1680
+ // 不再追加 ✨ 消息行 → 不残留显示效果
1548
1681
  if (runEndOkSteps.length >= 2) {
1549
- appendLine(`${C_DIM}(`・ω・´) 整理本轮经验中... ${runEndOkSteps.length} 个工具调用${RESET}`);
1682
+ inkSetTransient(`${C_DIM}(`・ω・´) 整理本轮经验中... ${runEndOkSteps.length} 个工具调用${RESET}`);
1550
1683
  setImmediate(async () => {
1551
1684
  try {
1552
1685
  const { writeRunEndSkillCandidates } = await import('./agents/skill-writer.js');
1553
- const r = await writeRunEndSkillCandidates(runEndOkSteps, 'cli:interactive');
1554
- if (r.wrote) {
1555
- appendLine(`${C_OK}✨ (◕‿◕) 经验候选已写入: ${r.names}${RESET}`);
1556
- }
1686
+ await writeRunEndSkillCandidates(runEndOkSteps, 'cli:interactive');
1557
1687
  }
1558
1688
  catch { /* 非致命, 静默 */ }
1689
+ finally {
1690
+ inkSetTransient(null); // 结束后去除显示效果 (显示为空)
1691
+ }
1559
1692
  });
1560
1693
  }
1561
1694
  // 更新状态栏: 上下文进度 (2026-08-06: 每轮按当前 messageHistory 重算并写回 ContextManager,
@@ -50,6 +50,8 @@ const DEFAULTS = {
50
50
  backoffFactor: 2,
51
51
  maxSocialIntervalMs: 30 * 60_000,
52
52
  goalReevalMs: 60 * 60_000,
53
+ // 2026-08-10: 自动整理心跳
54
+ organizeIntervalMs: 30 * 60_000,
53
55
  };
54
56
  const MAX_BACKOFF_LEVEL = 6;
55
57
  export class AgentHeartbeat {
@@ -58,6 +60,9 @@ export class AgentHeartbeat {
58
60
  lastInitiated = new Map();
59
61
  beaconTimer = null;
60
62
  socialTimer = null;
63
+ // 2026-08-10: 自动整理心跳 timer + 重入锁 (上一轮没跑完不重复触发)
64
+ organizeTimer = null;
65
+ organizeRunning = false;
61
66
  started = false;
62
67
  // === 生命周期状态 ===
63
68
  phase = 'BOOTSTRAP';
@@ -78,6 +83,10 @@ export class AgentHeartbeat {
78
83
  onPeerAlive: options.onPeerAlive,
79
84
  onActivity: options.onActivity,
80
85
  onLifecycleChange: options.onLifecycleChange,
86
+ organizeEnabled: options.organizeEnabled ?? true,
87
+ organizeIntervalMs: options.organizeIntervalMs ?? DEFAULTS.organizeIntervalMs,
88
+ organize: options.organize,
89
+ onOrganizeEvent: options.onOrganizeEvent,
81
90
  beaconIntervalMs: options.beaconIntervalMs ?? DEFAULTS.beaconIntervalMs,
82
91
  socialIntervalMs: options.socialIntervalMs ?? DEFAULTS.socialIntervalMs,
83
92
  cooldownMs: options.cooldownMs ?? DEFAULTS.cooldownMs,
@@ -110,10 +119,15 @@ export class AgentHeartbeat {
110
119
  if (this.isSocialEnabled()) {
111
120
  this.scheduleSocial();
112
121
  }
122
+ // 2026-08-10: 自动整理心跳 — 与社交独立, 社交关闭也照跑
123
+ if (this.opts.organizeEnabled && this.opts.organize) {
124
+ this.scheduleOrganize();
125
+ }
113
126
  // 立即发一次 beacon, 让对端尽快看到自己
114
127
  this.tickBeacon().catch(() => { });
115
128
  console.log(`[heartbeat] 社交心跳已启动 (beacon=${this.opts.beaconIntervalMs}ms` +
116
- `${this.isSocialEnabled() ? `, social=${this.opts.socialIntervalMs}ms, cooldown=${this.opts.cooldownMs}ms` : ', social=关闭'} )`);
129
+ `${this.isSocialEnabled() ? `, social=${this.opts.socialIntervalMs}ms, cooldown=${this.opts.cooldownMs}ms` : ', social=关闭'}` +
130
+ `${this.opts.organizeEnabled && this.opts.organize ? `, organize=${this.opts.organizeIntervalMs}ms` : ', organize=关闭'} )`);
117
131
  }
118
132
  /** 优雅停止: 清理全部定时器 (供全局 runtime 的 SIGTERM/SIGINT 清理调用) */
119
133
  stop() {
@@ -121,8 +135,11 @@ export class AgentHeartbeat {
121
135
  clearInterval(this.beaconTimer);
122
136
  if (this.socialTimer)
123
137
  clearTimeout(this.socialTimer);
138
+ if (this.organizeTimer)
139
+ clearTimeout(this.organizeTimer);
124
140
  this.beaconTimer = null;
125
141
  this.socialTimer = null;
142
+ this.organizeTimer = null;
126
143
  this.started = false;
127
144
  this.setPhase('PAUSED');
128
145
  console.log('[heartbeat] 社交心跳已停止 (定时器已清理)');
@@ -192,6 +209,50 @@ export class AgentHeartbeat {
192
209
  });
193
210
  }, this.currentSocialInterval());
194
211
  }
212
+ // ===================== 自动整理心跳 (2026-08-10) =====================
213
+ // 与社交心跳并列的第三条心跳: 周期性整理 skills 经验 (候选进化) + 扫描遗留 skills.
214
+ // 与社交生命周期完全独立 — 社交关闭/退避 RESTING 不影响整理照跑.
215
+ /** 是否启用了自动整理 */
216
+ isOrganizeEnabled() {
217
+ return this.opts.enabled && this.opts.organizeEnabled && !!this.opts.organize;
218
+ }
219
+ scheduleOrganize() {
220
+ if (!this.started || !this.isOrganizeEnabled()) {
221
+ this.organizeTimer = null;
222
+ return;
223
+ }
224
+ this.organizeTimer = setTimeout(() => {
225
+ this.tickOrganize()
226
+ .catch((e) => console.warn('[heartbeat] organize tick 失败:', e?.message))
227
+ .finally(() => {
228
+ if (this.started && this.isOrganizeEnabled())
229
+ this.scheduleOrganize();
230
+ });
231
+ }, this.opts.organizeIntervalMs);
232
+ }
233
+ /** 跑一轮自动整理 (导出供测试/启动即跑: 每次打开后固定看一下 skills view) */
234
+ async tickOrganize() {
235
+ if (!this.isOrganizeEnabled() || this.organizeRunning)
236
+ return;
237
+ this.organizeRunning = true;
238
+ this.opts.onOrganizeEvent?.({ phase: 'start' });
239
+ try {
240
+ const self = await this.opts.self();
241
+ const r = await this.opts.organize({ self });
242
+ this.opts.onOrganizeEvent?.({
243
+ phase: 'end',
244
+ summary: r?.summary || (r?.done ? '完成' : ''),
245
+ });
246
+ return r;
247
+ }
248
+ catch (e) {
249
+ this.opts.onOrganizeEvent?.({ phase: 'error', error: e?.message || String(e) });
250
+ throw e;
251
+ }
252
+ finally {
253
+ this.organizeRunning = false;
254
+ }
255
+ }
195
256
  /** 社交决策 tick: 先评估生命周期, 再决定是否对存活 peer 发起对话 */
196
257
  async tickSocial() {
197
258
  this.opts.onActivity?.();
@@ -2332,6 +2332,49 @@ export async function createWebServer(port = 3000, options = {}) {
2332
2332
  ts: Date.now(),
2333
2333
  }, 'p2p-global');
2334
2334
  },
2335
+ // === 2026-08-10: 自动整理心跳 (与社交并列) ===
2336
+ // 周期性: ① 扫描 skills view 找遗留 skills ② 候选经验 LLM 完整进化 (不再只是记录工具).
2337
+ // 与社交生命周期独立 — 社交关闭 (BOLLOON_AGENT_HEARTBEAT_SOCIAL=0) 整理仍照跑.
2338
+ organizeEnabled: true,
2339
+ organizeIntervalMs: Number(process.env.BOLLOON_ORGANIZE_HEARTBEAT_MS) || 30 * 60_000,
2340
+ organize: async () => {
2341
+ try {
2342
+ watchdogRef?.recordActivity?.('agent-organize');
2343
+ }
2344
+ catch { }
2345
+ const { runAutoOrganize } = await import('../agents/skill-organizer.js');
2346
+ // 用第一个本地 channel 的 agent 做 LLM 完整经验进化 (拿不到则仅扫描)
2347
+ // 2026-08-10: getAgentForChannel 初始化可能挂起 → 8s 超时降级
2348
+ let llm;
2349
+ try {
2350
+ const channels = await loadChannels();
2351
+ const local = channels[0];
2352
+ if (local) {
2353
+ const agent = await Promise.race([
2354
+ getAgentForChannel(local.id, local.did || '', local.name, local.didDocRef).catch(() => null),
2355
+ new Promise((res) => setTimeout(() => res(null), 8000)),
2356
+ ]);
2357
+ if (agent && typeof agent.promptStream === 'function') {
2358
+ llm = (p) => agent.promptStream(p, () => { }, undefined, local.id);
2359
+ }
2360
+ }
2361
+ }
2362
+ catch { /* 无 agent → 仅扫描 */ }
2363
+ const r = await runAutoOrganize({ llm, source: 'server:organize-heartbeat', evolve: !!llm });
2364
+ const kTotal = r.knowledge?.totalHandled ?? 0;
2365
+ return { done: true, summary: `进化 ${r.evolved.length} 个 skill, 遗留 ${r.leftovers.length} 个, 知识层 ${kTotal} 项` };
2366
+ },
2367
+ onOrganizeEvent: (evt) => {
2368
+ if (evt?.phase === 'start') {
2369
+ console.log('[heartbeat] 自动整理开始 (skills 遗留扫描 + 经验进化)');
2370
+ }
2371
+ else if (evt?.phase === 'end') {
2372
+ console.log(`[heartbeat] 自动整理完成${evt.summary ? `: ${evt.summary}` : ''}`);
2373
+ }
2374
+ else if (evt?.phase === 'error') {
2375
+ console.warn(`[heartbeat] 自动整理失败 (non-fatal): ${evt?.error || ''}`);
2376
+ }
2377
+ },
2335
2378
  });
2336
2379
  agentHeartbeat.start();
2337
2380
  // 注册到全局, 让 24h HealthMonitor.checkHeartbeat 能观测到本智能体 (getDiscoveredAgents/isAntColonyEnabled)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.3.46",
3
+ "version": "0.3.48",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli-entry.js",