@bolloon/bolloon-agent 0.3.48 → 0.3.49

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.
@@ -28,6 +28,12 @@ import { initDocumentReceiver } from './p2p-document-tools.js';
28
28
  import { DiscoveredAgentsManager, createSocialHeartbeat } from '../social/heartbeat.js';
29
29
  import { SkillRegistry } from '@bolloon/constraint-runtime';
30
30
  import { loadSkillsFromPaths, defaultSkillPaths } from './skill-loader.js';
31
+ /** 2026-08-10: unreported 逃生门判定 — LLM 反复不把工具结果写进回复时, 超过上限强制收尾 (防死循环) */
32
+ export function decideUnreported(unreported, retries, max) {
33
+ if (unreported <= 0)
34
+ return 'none';
35
+ return retries < max ? 'retry' : 'force-final';
36
+ }
31
37
  // 拆分后的子模块 — 重新导出保 backward compat
32
38
  export { TOOL_DEFINITIONS, } from './pi-sdk-types.js';
33
39
  export { PiSessionManager } from './pi-sdk-session-manager.js';
@@ -1124,6 +1130,11 @@ ${this.getToolDefinitions()}
1124
1130
  const MAX_TOOL_CALLS_PER_LOOP = 25; // 单轮循环总工具调用上限 → 注入 hint
1125
1131
  let totalToolCallsThisLoop = 0;
1126
1132
  const lastNTools = []; // 最近 MAX_IDEMPOTENT_TOOL 次工具名, 检测重复
1133
+ // 2026-08-10: unreported 循环逃生门 — LLM 反复不把工具结果写进回复时, 3 次后强制 final (不死板)
1134
+ const MAX_UNREPORTED_RETRIES = 3;
1135
+ let unreportedRetries = 0;
1136
+ // 2026-08-10: 工具失败时的终端逃生引导 (shell_exec 白名单命令可诊断环境/推进任务)
1137
+ const SHELL_ESCAPE_HINT = ' [逃生] 若工具无法响应/报错, 可用 shell_exec 跑终端命令诊断 (白名单: ls/cat/head/tail/pwd/git status/npm run test 等), 或调整参数换一种方式完成; 不要重复调用同一失败工具.';
1127
1138
  // 2026-08-08: final 前 review 续跑 — 目标对齐 + 需求深挖 (见 loop-review.ts)
1128
1139
  // 不潦草收尾: LLM 想 <final gen> 时先跑 1-2 次 review, 达成用户需求才放行.
1129
1140
  // 上限=2 次 (用户要求"运行一两次"), 结束后按用户需求为准.
@@ -1671,7 +1682,7 @@ ${toolDefs}
1671
1682
  // 2026-07-28: 注入 Observation + Reflection 替代旧 hardcode 提示
1672
1683
  const obs = buildObservation(toolCall.name, toolCall.args, { success: false, error: result.error });
1673
1684
  const ref = buildReflection(toolCall.name, result.error, totalErrors, lastFailedToolCount);
1674
- this.messageHistory.push({ role: 'system', content: formatObservationWithReflection(obs, ref) });
1685
+ this.messageHistory.push({ role: 'system', content: formatObservationWithReflection(obs, ref) + SHELL_ESCAPE_HINT });
1675
1686
  if (onStream)
1676
1687
  onStream({ type: 'status', content: `💡 Reflection: ${obs.summary} → ${ref[0]?.action || '放弃'}`, tool: 'system' });
1677
1688
  if (lastFailedToolCount >= MAX_SAME_TOOL_FAILURES) {
@@ -1695,7 +1706,7 @@ ${toolDefs}
1695
1706
  this.logToHarness(toolCall.name, toolCall.args, errorResult);
1696
1707
  const obs = buildObservation(toolCall.name, toolCall.args, errorResult);
1697
1708
  const ref = buildReflection(toolCall.name, errorResult.error, totalErrors, lastFailedToolCount);
1698
- this.messageHistory.push({ role: 'system', content: formatObservationWithReflection(obs, ref) });
1709
+ this.messageHistory.push({ role: 'system', content: formatObservationWithReflection(obs, ref) + SHELL_ESCAPE_HINT });
1699
1710
  if (onStream)
1700
1711
  onStream({ type: 'status', content: `💡 Reflection: ${obs.summary}`, tool: 'system' });
1701
1712
  console.error(`[PiAgent] 工具执行异常 (累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}): ${execError}`);
@@ -1728,18 +1739,33 @@ ${toolDefs}
1728
1739
  console.log(`[PiAgent] 回复包含工具结果内容, 清除 successfulToolResults (${this.successfulToolResults.length} 个)`);
1729
1740
  this.successfulToolResults = [];
1730
1741
  }
1731
- if (this.successfulToolResults.length > 0 && iteration < this.MAX_REACT_ITERATIONS) {
1742
+ // 2026-08-10: 逃生门 decideUnreported: 未达上限 再提示一次; 超限 → 清空积压强制 final (防死循环)
1743
+ const unreportedDecision = decideUnreported(this.successfulToolResults.length, unreportedRetries, MAX_UNREPORTED_RETRIES);
1744
+ if (unreportedDecision === 'retry' && iteration < this.MAX_REACT_ITERATIONS) {
1745
+ unreportedRetries++;
1732
1746
  const unreported = this.successfulToolResults.length;
1733
- console.log(`[PiAgent] LLM 想 final_gen 但还有 ${unreported} 个工具结果未汇报, push hint 让其继续`);
1747
+ console.log(`[PiAgent] LLM 想 final_gen 但还有 ${unreported} 个工具结果未汇报 (${unreportedRetries}/${MAX_UNREPORTED_RETRIES}), push hint 让其继续`);
1734
1748
  this.messageHistory.push({
1735
1749
  role: 'system',
1736
1750
  content: `[dive-into stop condition] 你之前已成功执行了 ${unreported} 个工具, 但当前回复里没把它们的结果告诉用户. 请基于已有的工具结果 (在 history 里) 写一个完整总结回复给用户, 用 <final gen> 结尾. 不要再调工具.`
1737
1751
  });
1738
1752
  if (onStream) {
1739
- onStream({ type: 'status', content: `🔄 还有 ${unreported} 个工具结果未汇报, 让 LLM 继续总结`, tool: 'system' });
1753
+ onStream({ type: 'status', content: `🔄 还有 ${unreported} 个工具结果未汇报, 让 LLM 继续总结 (${unreportedRetries}/${MAX_UNREPORTED_RETRIES})`, tool: 'system' });
1740
1754
  }
1741
1755
  continue;
1742
1756
  }
1757
+ else if (unreportedDecision === 'force-final') {
1758
+ // 反复提示仍未汇报超过上限 → 清空积压强制 final, 不再死循环
1759
+ console.log(`[PiAgent] unreported 循环超限 (${unreportedRetries} 次), 清空积压强制 final`);
1760
+ this.successfulToolResults = [];
1761
+ this.messageHistory.push({
1762
+ role: 'system',
1763
+ content: `[dive-into stop condition] 已多次提示汇报工具结果仍未完成 (超过 ${MAX_UNREPORTED_RETRIES} 次). 现在直接基于你已知的信息写最终回复给用户, 用 <final gen> 结尾, 不要再调任何工具.`
1764
+ });
1765
+ if (onStream) {
1766
+ onStream({ type: 'status', content: `🔄 工具结果汇报超限, 强制收尾`, tool: 'system' });
1767
+ }
1768
+ }
1743
1769
  lastQualityScore = this.estimateResponseQuality(reply);
1744
1770
  // 2026-07-29: 质量门 — 即使 LLM 声称完成, 质量太低也继续
1745
1771
  if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
package/dist/index.js CHANGED
@@ -572,16 +572,21 @@ async function startCLI(comm) {
572
572
  onStart: () => inkSetTransient(`${C_DIM}(`・ω・´) 自动整理经验中...${RESET}`),
573
573
  onEnd: (r) => {
574
574
  inkSetTransient(null); // 结束后去除显示效果 (显示为空)
575
+ // 2026-08-10: 整理结果统一进 bolloon 艺术字框 (renderMessageBox 圆角框, 与反思框同款)
576
+ const boxLines = [];
575
577
  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}`);
578
+ boxLines.push(`🧹 遗留 skills (${r.leftovers.length}): ${r.leftovers.slice(0, 8).map(l => l.name).join(', ')}${r.leftovers.length > 8 ? ' ...' : ''}`);
577
579
  }
578
580
  if (r && r.evolved.length > 0) {
579
- appendLine(`${C_OK}✨ 经验进化: ${r.evolved.join(', ')}${RESET}`);
581
+ boxLines.push(`✨ 经验进化: ${r.evolved.join(', ')}`);
580
582
  }
581
- // 2026-08-10: 知识层整理汇总 (Context OS/社交/智能体/judgeness/项目/画像/日志/目标)
583
+ // 知识层整理汇总 (Context OS/社交/智能体/judgeness/项目/画像/日志/目标)
582
584
  const kSections = (r?.knowledge?.sections || []).filter(s => s.handled > 0 || s.error);
583
585
  if (kSections.length > 0) {
584
- appendLine(`${C_DIM}🧠 知识整理: ${kSections.map(s => s.error ? `${s.label}✗` : `${s.label}✓`).join(' ')}${RESET}`);
586
+ boxLines.push(`🧠 知识整理: ${kSections.map(s => s.error ? `${s.label}✗` : `${s.label}✓`).join(' ')}`);
587
+ }
588
+ if (boxLines.length > 0) {
589
+ appendLine(renderMessageBox({ title: '自动整理完成', body: boxLines.join('\n'), color: C_ACCENT, maxLines: 10 }));
585
590
  }
586
591
  },
587
592
  onError: () => inkSetTransient(null),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.3.48",
3
+ "version": "0.3.49",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli-entry.js",