@bolloon/bolloon-agent 0.1.34 → 0.1.36

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.
Files changed (226) hide show
  1. package/.auto-evolve-calls +1 -0
  2. package/.last-auto-evolve-baseline +1 -0
  3. package/.playwright-mcp/page-2026-06-16T07-51-45-706Z.yml +130 -0
  4. package/.playwright-mcp/page-2026-06-16T07-56-44-116Z.yml +131 -0
  5. package/Bolloon.md +144 -0
  6. package/Dive-into/CITATION.cff +17 -0
  7. package/Dive-into/LICENSE +25 -0
  8. package/Dive-into/README.md +598 -0
  9. package/Dive-into/README_zh.md +599 -0
  10. package/Dive-into/assets/context.png +0 -0
  11. package/Dive-into/assets/extensibility.png +0 -0
  12. package/Dive-into/assets/iteration.png +0 -0
  13. package/Dive-into/assets/layered_architecture.png +0 -0
  14. package/Dive-into/assets/main_structure.png +0 -0
  15. package/Dive-into/assets/permission.png +0 -0
  16. package/Dive-into/assets/session_compact.png +0 -0
  17. package/Dive-into/assets/subagent.png +0 -0
  18. package/Dive-into/paper/Dive_into_Claude_Code.pdf +0 -0
  19. package/README.md +1 -1
  20. package/dist/agents/p2p-chat-tools.js +6 -6
  21. package/dist/agents/permission-mode.js +115 -0
  22. package/dist/agents/pi-sdk.js +687 -25
  23. package/dist/agents/pre-tool-validator.js +194 -0
  24. package/dist/agents/workflow-pivot-loop.js +113 -12
  25. package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +1 -1
  26. package/dist/bollharness/src/scripts/checks/check_doc_links.js +1 -1
  27. package/dist/bollharness/src/scripts/context_router.js +1 -1
  28. package/dist/bollharness/src/scripts/deploy-guard.js +1 -1
  29. package/dist/bollharness/src/scripts/guard_router.js +1 -1
  30. package/dist/bollharness/src/scripts/hooks/_hook_output.js +1 -1
  31. package/dist/bollharness/src/scripts/hooks/risk-tracker.js +1 -1
  32. package/dist/bollharness-integration/context-router.js +2 -2
  33. package/dist/bollharness-integration/guard-checker.js +1 -1
  34. package/dist/bootstrap/bootstrap.js +117 -0
  35. package/dist/bootstrap/context-collector.js +312 -0
  36. package/dist/bootstrap/context-hierarchy.js +218 -0
  37. package/dist/bootstrap/lifecycle-hooks.js +171 -0
  38. package/dist/bootstrap/project-context.js +158 -0
  39. package/dist/context-compaction/auto-compact.js +144 -0
  40. package/dist/context-compaction/budget-gate.js +28 -0
  41. package/dist/context-compaction/budget-reduce.js +35 -0
  42. package/dist/context-compaction/context-collapse.js +66 -0
  43. package/dist/context-compaction/index.js +21 -0
  44. package/dist/context-compaction/microcompact.js +51 -0
  45. package/dist/context-compaction/pipeline.js +123 -0
  46. package/dist/context-compaction/snip.js +45 -0
  47. package/dist/context-compaction/token-estimator.js +35 -0
  48. package/dist/context-compaction/types.js +19 -0
  49. package/dist/heartbeat/HealthMonitor.js +3 -2
  50. package/dist/index.js +49 -2
  51. package/dist/llm/llm-judgment-client.js +32 -30
  52. package/dist/llm/pi-ai.js +125 -28
  53. package/dist/llm/system-prompt/health.js +129 -0
  54. package/dist/llm/system-prompt/registry.js +246 -0
  55. package/dist/llm/system-prompt/strip-hibsml.js +51 -0
  56. package/dist/llm/tool-manifest/ask_user_input.js +35 -0
  57. package/dist/llm/tool-manifest/bash.js +23 -0
  58. package/dist/llm/tool-manifest/create_file.js +24 -0
  59. package/dist/llm/tool-manifest/fetch_sports_data.js +26 -0
  60. package/dist/llm/tool-manifest/image_search.js +24 -0
  61. package/dist/llm/tool-manifest/index.js +69 -0
  62. package/dist/llm/tool-manifest/mcp.js +43 -0
  63. package/dist/llm/tool-manifest/message_compose.js +31 -0
  64. package/dist/llm/tool-manifest/places.js +83 -0
  65. package/dist/llm/tool-manifest/present_files.js +21 -0
  66. package/dist/llm/tool-manifest/recipe.js +40 -0
  67. package/dist/llm/tool-manifest/recommend_apps.js +23 -0
  68. package/dist/llm/tool-manifest/str_replace.js +27 -0
  69. package/dist/llm/tool-manifest/types.js +7 -0
  70. package/dist/llm/tool-manifest/view.js +24 -0
  71. package/dist/llm/tool-manifest/weather.js +27 -0
  72. package/dist/llm/tool-manifest/web.js +51 -0
  73. package/dist/network/p2p-direct.js +23 -0
  74. package/dist/network/source-intent-broadcaster.js +203 -0
  75. package/dist/network/source-intent.js +100 -0
  76. package/dist/pi-ecosystem-judgment/adaptive-scan.js +279 -0
  77. package/dist/pi-ecosystem-judgment/causal-judge.js +449 -0
  78. package/dist/pi-ecosystem-judgment/detect-hook.js +168 -0
  79. package/dist/pi-ecosystem-judgment/distill-prompt.js +226 -0
  80. package/dist/pi-ecosystem-judgment/evolve-judgment.js +170 -0
  81. package/dist/pi-ecosystem-judgment/human-value-pipeline.js +21 -0
  82. package/dist/pi-ecosystem-judgment/human-value-store.js +283 -22
  83. package/dist/pi-ecosystem-judgment/injection-gate.js +208 -0
  84. package/dist/pi-ecosystem-judgment/monitor-gate.js +188 -0
  85. package/dist/pi-ecosystem-judgment/value-injection.js +8 -2
  86. package/dist/security/builtin-guards.js +124 -0
  87. package/dist/security/context-router-tool.js +106 -0
  88. package/dist/security/input-scanner.js +223 -0
  89. package/dist/security/react-harness.js +143 -0
  90. package/dist/security/tool-gate.js +235 -0
  91. package/dist/utils/auto-evolve-policy.js +117 -0
  92. package/dist/utils/clamp.js +7 -0
  93. package/dist/utils/double.js +6 -0
  94. package/dist/web/client.js +3810 -3830
  95. package/dist/web/components/p2p/P2PModal.js +188 -0
  96. package/dist/web/components/p2p/index.js +264 -226
  97. package/dist/web/components/p2p/p2p-modal.js +657 -0
  98. package/dist/web/components/p2p/p2p-tools.js +248 -0
  99. package/dist/web/index.html +60 -49
  100. package/dist/web/server.js +827 -124
  101. package/dist/web/style.css +531 -249
  102. package/dist/web/ui/message-renderer.js +463 -0
  103. package/dist/web/ui/step-timeline.js +375 -0
  104. package/lefthook.yml +33 -0
  105. package/package.json +3 -2
  106. package/scripts/auto-evolve-loop.ts +481 -0
  107. package/scripts/auto-evolve-oneshot.sh +155 -0
  108. package/scripts/auto-evolve-snapshot.sh +136 -0
  109. package/scripts/build-web.ts +35 -1
  110. package/scripts/detect-schema-changes.sh +48 -0
  111. package/scripts/diff-reviewer.ts +159 -0
  112. package/scripts/validate-system-prompt.ts +142 -0
  113. package/scripts/weekly-report.ts +364 -0
  114. package/src/agents/p2p-chat-tools.ts +6 -6
  115. package/src/agents/permission-mode.ts +127 -0
  116. package/src/agents/pi-sdk.ts +741 -30
  117. package/src/agents/pre-tool-validator.ts +213 -0
  118. package/src/agents/workflow-pivot-loop.ts +110 -19
  119. package/src/bollharness/CLAUDE.md +1 -1
  120. package/src/bollharness/README.md +2 -2
  121. package/src/bollharness/README.zh-CN.md +2 -2
  122. package/src/bollharness/reference/boll-reference/scripts/hooks/find-boll-root.sh +2 -2
  123. package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +2 -2
  124. package/src/bollharness/scripts/context-fragments/version-sources.md +1 -1
  125. package/src/bollharness/scripts/hooks/find-project-root.sh +4 -4
  126. package/src/bollharness/src/scripts/checks/check_doc_freshness.ts +1 -1
  127. package/src/bollharness/src/scripts/checks/check_doc_links.ts +1 -1
  128. package/src/bollharness/src/scripts/context_router.ts +1 -1
  129. package/src/bollharness/src/scripts/deploy-guard.ts +1 -1
  130. package/src/bollharness/src/scripts/guard_router.ts +1 -1
  131. package/src/bollharness/src/scripts/hooks/_hook_output.ts +1 -1
  132. package/src/bollharness/src/scripts/hooks/risk-tracker.ts +1 -1
  133. package/src/bollharness/templates/scaffold/.gitignore.append +1 -1
  134. package/src/bollharness/templates/scaffold/CLAUDE.md +2 -2
  135. package/src/bollharness-integration/context-router.ts +2 -2
  136. package/src/bollharness-integration/guard-checker.ts +1 -1
  137. package/src/bootstrap/bootstrap.ts +135 -0
  138. package/src/bootstrap/context-collector.ts +371 -0
  139. package/src/bootstrap/context-hierarchy.ts +283 -0
  140. package/src/bootstrap/lifecycle-hooks.ts +289 -0
  141. package/src/bootstrap/project-context.ts +171 -0
  142. package/src/context-compaction/auto-compact.ts +153 -0
  143. package/src/context-compaction/budget-gate.ts +32 -0
  144. package/src/context-compaction/budget-reduce.ts +37 -0
  145. package/src/context-compaction/context-collapse.ts +72 -0
  146. package/src/context-compaction/index.ts +24 -0
  147. package/src/context-compaction/microcompact.ts +54 -0
  148. package/src/context-compaction/pipeline.ts +133 -0
  149. package/src/context-compaction/snip.ts +51 -0
  150. package/src/context-compaction/token-estimator.ts +36 -0
  151. package/src/context-compaction/types.ts +99 -0
  152. package/src/heartbeat/HealthMonitor.ts +3 -2
  153. package/src/index.ts +47 -2
  154. package/src/llm/llm-judgment-client.ts +36 -35
  155. package/src/llm/pi-ai.ts +135 -29
  156. package/src/llm/system-prompt/health.ts +159 -0
  157. package/src/llm/system-prompt/layers/channel/local.md +14 -0
  158. package/src/llm/system-prompt/layers/channel/p2p-agent.md +18 -0
  159. package/src/llm/system-prompt/layers/channel/p2p-visitor.md +19 -0
  160. package/src/llm/system-prompt/layers/core/artifacts_storage.md +89 -0
  161. package/src/llm/system-prompt/layers/core/evenhandedness.md +21 -0
  162. package/src/llm/system-prompt/layers/core/hibs_reminders.md +15 -0
  163. package/src/llm/system-prompt/layers/core/identity.md +37 -0
  164. package/src/llm/system-prompt/layers/core/knowledge.md +17 -0
  165. package/src/llm/system-prompt/layers/core/memory_system.md +12 -0
  166. package/src/llm/system-prompt/layers/core/network_filesystem.md +28 -0
  167. package/src/llm/system-prompt/layers/core/refusal.md +37 -0
  168. package/src/llm/system-prompt/layers/core/tone.md +31 -0
  169. package/src/llm/system-prompt/layers/core/tools.thin.md +13 -0
  170. package/src/llm/system-prompt/layers/core/wellbeing.md +41 -0
  171. package/src/llm/system-prompt/layers/role/architect.md +20 -0
  172. package/src/llm/system-prompt/layers/role/expert.md +19 -0
  173. package/src/llm/system-prompt/layers/role/implementer.md +15 -0
  174. package/src/llm/system-prompt/layers/role/security.md +15 -0
  175. package/src/llm/system-prompt/layers/tool/artifacts.md +72 -0
  176. package/src/llm/system-prompt/layers/tool/bash.md +25 -0
  177. package/src/llm/system-prompt/layers/tool/hibs_api.md +171 -0
  178. package/src/llm/system-prompt/layers/tool/image_search.md +70 -0
  179. package/src/llm/system-prompt/layers/tool/manifest.md +89 -0
  180. package/src/llm/system-prompt/layers/tool/mcp_apps.md +53 -0
  181. package/src/llm/system-prompt/layers/tool/web_search.md +83 -0
  182. package/src/llm/system-prompt/registry.ts +325 -0
  183. package/src/llm/system-prompt/strip-hibsml.ts +52 -0
  184. package/src/llm/tool-manifest/ask_user_input.ts +37 -0
  185. package/src/llm/tool-manifest/bash.ts +25 -0
  186. package/src/llm/tool-manifest/create_file.ts +26 -0
  187. package/src/llm/tool-manifest/fetch_sports_data.ts +28 -0
  188. package/src/llm/tool-manifest/image_search.ts +26 -0
  189. package/src/llm/tool-manifest/index.ts +88 -0
  190. package/src/llm/tool-manifest/mcp.ts +46 -0
  191. package/src/llm/tool-manifest/message_compose.ts +33 -0
  192. package/src/llm/tool-manifest/places.ts +86 -0
  193. package/src/llm/tool-manifest/present_files.ts +23 -0
  194. package/src/llm/tool-manifest/recipe.ts +42 -0
  195. package/src/llm/tool-manifest/recommend_apps.ts +25 -0
  196. package/src/llm/tool-manifest/str_replace.ts +29 -0
  197. package/src/llm/tool-manifest/types.ts +52 -0
  198. package/src/llm/tool-manifest/view.ts +26 -0
  199. package/src/llm/tool-manifest/weather.ts +29 -0
  200. package/src/llm/tool-manifest/web.ts +54 -0
  201. package/src/network/p2p-direct.ts +22 -0
  202. package/src/network/source-intent-broadcaster.ts +242 -0
  203. package/src/network/source-intent.ts +167 -0
  204. package/src/security/builtin-guards.ts +162 -0
  205. package/src/security/context-router-tool.ts +122 -0
  206. package/src/security/input-scanner.ts +287 -0
  207. package/src/security/react-harness.ts +177 -0
  208. package/src/security/tool-gate.ts +294 -0
  209. package/src/utils/auto-evolve-policy.ts +138 -0
  210. package/src/utils/clamp.ts +5 -0
  211. package/src/web/client.js +105 -2187
  212. package/src/web/client.ts +4326 -0
  213. package/src/web/index.html +60 -49
  214. package/src/web/server.ts +894 -148
  215. package/src/web/style.css +531 -249
  216. package/src/web/ui/message-renderer.ts +540 -0
  217. package/src/web/ui/step-timeline.ts +394 -0
  218. package/staging/auto-evolve/clean-001/.review-verdict +9 -0
  219. package/staging/auto-evolve/clean-001/clean-001.patch +14 -0
  220. package/staging/auto-evolve/e2e-001/.patch-id +1 -0
  221. package/staging/auto-evolve/e2e-001/.review-verdict +12 -0
  222. package/staging/auto-evolve/e2e-001/e2e-001.patch +11 -0
  223. package/staging/auto-evolve/test-bad/.review-verdict +12 -0
  224. package/staging/auto-evolve/test-bad/test-bad.patch +11 -0
  225. package/test-results/.last-run.json +6 -0
  226. package/test-results/src-test-web-loop-status-b-5734c-atus-bar-/346/230/276/347/244/272-spinner-/344/270/215/346/230/276/347/244/272/345/276/252/347/216/257/350/256/241/346/225/260/error-context.md +285 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Snip — 第 2 层: 裁掉老 history
3
+ *
4
+ * 论文: feature flag 关闭 (默认), 用户开启后才生效
5
+ * 开启环境变量: BOLLOON_SNIP_ENABLED=1
6
+ *
7
+ * 行为: 只保留最近 N 对 user/assistant (默认 20 对)
8
+ * 工具结果消息按其所属 round 一起保留
9
+ *
10
+ * 失败静默: 异常 → 返回原 history
11
+ */
12
+
13
+ import type { Message, StageResult, StageOptions } from './types.js';
14
+
15
+ const DEFAULT_KEEP_PAIRS = 20;
16
+
17
+ export function isSnipEnabled(): boolean {
18
+ return process.env.BOLLOON_SNIP_ENABLED === '1';
19
+ }
20
+
21
+ export function snip(history: Message[], opts: StageOptions = {}): StageResult {
22
+ try {
23
+ if (opts.skip || !isSnipEnabled()) {
24
+ return { history, applied: false, detail: isSnipEnabled() ? 'skipped' : 'feature flag off' };
25
+ }
26
+ const keepPairs = opts.snipKeepPairs ?? DEFAULT_KEEP_PAIRS;
27
+
28
+ // 从后往前数 N 对 user/assistant 边界
29
+ let pairsFound = 0;
30
+ let cutFrom = -1;
31
+ for (let i = history.length - 1; i >= 0; i--) {
32
+ const m = history[i];
33
+ if (m.role === 'user') {
34
+ pairsFound++;
35
+ if (pairsFound >= keepPairs) {
36
+ cutFrom = i;
37
+ break;
38
+ }
39
+ }
40
+ }
41
+
42
+ if (cutFrom <= 0) {
43
+ return { history, applied: false, detail: 'history too short' };
44
+ }
45
+ const out = history.slice(cutFrom);
46
+ return { history: out, applied: true, detail: `kept last ${keepPairs} pairs, dropped ${cutFrom} older messages` };
47
+ } catch (err) {
48
+ console.warn('[compactor] snip failed (silent, returning original):', err);
49
+ return { history, applied: false, detail: 'error' };
50
+ }
51
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Token Estimator — 4 字符 ≈ 1 token 的粗估
3
+ *
4
+ * 为什么不引 tiktoken 依赖:
5
+ * - bolloon 默认 npm 用户, 引依赖增加 install 体积
6
+ * - Claude Code 论文也指出: 精确 token 计数不是关键, 渐进压缩才是
7
+ * - 4 字符/token 在中文/英文混合场景下误差约 ±20%, 对预算判定足够
8
+ *
9
+ * 失败静默: 任何异常 → 返回估算值 (不抛错)
10
+ */
11
+
12
+ const CHARS_PER_TOKEN = 4;
13
+
14
+ export function estimateTokens(messages: Array<{ content: string; toolResult?: unknown; toolCall?: unknown }>): number {
15
+ let total = 0;
16
+ for (const m of messages) {
17
+ total += Math.ceil((m.content?.length || 0) / CHARS_PER_TOKEN);
18
+ if (m.toolResult) {
19
+ try {
20
+ const s = typeof m.toolResult === 'string' ? m.toolResult : JSON.stringify(m.toolResult);
21
+ total += Math.ceil(s.length / CHARS_PER_TOKEN);
22
+ } catch { /* circular ref, ignore */ }
23
+ }
24
+ if (m.toolCall) {
25
+ try {
26
+ const s = typeof m.toolCall === 'string' ? m.toolCall : JSON.stringify(m.toolCall);
27
+ total += Math.ceil(s.length / CHARS_PER_TOKEN);
28
+ } catch { /* ignore */ }
29
+ }
30
+ }
31
+ return total;
32
+ }
33
+
34
+ export function estimateStringTokens(s: string): number {
35
+ return Math.ceil(s.length / CHARS_PER_TOKEN);
36
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Context Compaction — 类型定义
3
+ *
4
+ * 严格对齐 Claude Code 论文 5 层压缩流水线:
5
+ * 1. Budget Reduction (单条消息大小限制)
6
+ * 2. Snip (裁掉老历史, feature flag 关闭)
7
+ * 3. Microcompact (cache-aware 细粒度压缩, 老 tool_result 折叠)
8
+ * 4. Context Collapse (读时虚拟投影, 非破坏)
9
+ * 5. Auto-Compact (LLM 摘要, 兜底)
10
+ *
11
+ * 关键不变量:
12
+ * - 第 1-3 层不破坏 messageHistory 内存结构, 只改 buildContext 输出
13
+ * - 第 4 层读时投影, 绝不改 messageHistory
14
+ * - 第 5 层破坏 messageHistory, 把 N 对折叠成 1 条 summary
15
+ *
16
+ * 类型设计: Message 在本模块内独立定义, 靠 structural typing 与 pi-sdk.ts 的 Message 兼容.
17
+ * 不 import pi-sdk 避免循环依赖.
18
+ */
19
+
20
+ export type Role = 'user' | 'assistant' | 'tool' | 'system';
21
+
22
+ export interface Message {
23
+ role: Role;
24
+ content: string;
25
+ toolCall?: { name: string; args: Record<string, unknown> };
26
+ toolResult?: unknown;
27
+ toolName?: string;
28
+ [key: string]: unknown;
29
+ }
30
+
31
+ // ============================================================
32
+ // 5 层 stage 共享的输入输出
33
+ // ============================================================
34
+
35
+ export interface StageOptions {
36
+ /** Token 预算 (默认 8000) */
37
+ maxTokens?: number;
38
+ /** 跳过本层 (per-stage 开关) */
39
+ skip?: boolean;
40
+ /** Snip 专用: 保留最近多少对 (默认 20) */
41
+ snipKeepPairs?: number;
42
+ /** Budget Reduction 专用: 单条消息上限 (默认 4000 字符) */
43
+ budgetReduceMaxChars?: number;
44
+ /** Microcompact 专用: 保留最近多少条完整 tool_result (默认 3) */
45
+ microcompactKeepRecent?: number;
46
+ /** Context Collapse 专用: 折叠前多少对 (默认 5) */
47
+ contextCollapseCollapsePairs?: number;
48
+ /** Auto-Compact 专用: 摘要前多少对 (默认 5) */
49
+ autoCompactCollapsePairs?: number;
50
+ /** Auto-Compact 专用: 注入 LLM 调用 (用于生成摘要) */
51
+ llmChat?: (systemPrompt: string, userPrompt: string, signal?: AbortSignal) => Promise<string>;
52
+ /** Auto-Compact 专用: 缓存 key 后缀 (通常 = channelId) */
53
+ cacheScope?: string;
54
+ /** Context Collapse 专用: 注入 LLM (虚拟投影需要 summary 文本) */
55
+ collapseLlmChat?: (systemPrompt: string, userPrompt: string) => Promise<string>;
56
+ }
57
+
58
+ export interface CompactionContext {
59
+ /** 输入: 原始 history */
60
+ inputHistory: Message[];
61
+ /** 当前已压缩的 history (经过前面 stage) */
62
+ history: Message[];
63
+ /** 估算的 token 数 */
64
+ estimatedTokens: number;
65
+ /** 选项 */
66
+ opts: StageOptions;
67
+ /** Pipeline 元数据, 记录每层做了什么 */
68
+ stages: Array<{ stage: string; applied: boolean; before: number; after: number; detail?: string }>;
69
+ }
70
+
71
+ export interface StageResult {
72
+ /** 压缩后的 history */
73
+ history: Message[];
74
+ /** 该 stage 是否做了改动 */
75
+ applied: boolean;
76
+ /** 详情 (供 audit / debug) */
77
+ detail?: string;
78
+ }
79
+
80
+ // ============================================================
81
+ // Budget Gate
82
+ // ============================================================
83
+
84
+ export interface BudgetReport {
85
+ fit: boolean;
86
+ estimatedTokens: number;
87
+ /** 占用比例 0-1 */
88
+ ratio: number;
89
+ /** 触发了哪一层 (>=0.8 = next layer, >1 = all layers) */
90
+ triggerNextLayer: boolean;
91
+ }
92
+
93
+ export interface PipelineResult {
94
+ history: Message[];
95
+ estimatedTokens: number;
96
+ stages: CompactionContext['stages'];
97
+ /** 是否至少跑了一层 */
98
+ compacted: boolean;
99
+ }
@@ -257,9 +257,10 @@ export class HealthMonitor implements HealthCheckProvider {
257
257
  private async pingLLM(minimax: any): Promise<number> {
258
258
  const start = Date.now();
259
259
  try {
260
- // 简单的 chat 测试
260
+ // 2026-06-15: signal 位置正确传 undefined (chat signature 是 (message, context?, signal?))
261
+ // 之前传 { maxTokens: 1 } 被当 signal, Node 22+ undici 强类型 AbortSignal 校验 throw
261
262
  if (minimax.chat) {
262
- await minimax.chat('ping', 'test', { maxTokens: 1 });
263
+ await minimax.chat('ping', 'test', undefined);
263
264
  } else if (minimax.ping) {
264
265
  await minimax.ping();
265
266
  }
package/src/index.ts CHANGED
@@ -1132,6 +1132,32 @@ async function runNonInteractive(
1132
1132
  return;
1133
1133
  }
1134
1134
 
1135
+ if (tool === 'prompt' && prompt) {
1136
+ // 2026-06-15: --prompt "text" 走直接调 a.prompt(prompt) 路径, 避开 runToolCommand
1137
+ // (case 'prompt' 内的 [text] = args 从 toolArgs 取值是空数组, 永远报"缺少 prompt 文本"是 CLI bug)
1138
+ const startTime = Date.now();
1139
+ const a = await getAgent();
1140
+ try {
1141
+ const response = await a.prompt(prompt);
1142
+ const elapsed = Date.now() - startTime;
1143
+ if (json) {
1144
+ console.log(JSON.stringify({ success: true, response, elapsedMs: elapsed }, null, 2));
1145
+ } else {
1146
+ console.log(response);
1147
+ console.log(`\n耗时: ${elapsed}ms`);
1148
+ }
1149
+ } catch (e: any) {
1150
+ const error = e?.message || String(e);
1151
+ if (json) {
1152
+ console.log(JSON.stringify({ success: false, error }, null, 2));
1153
+ } else {
1154
+ console.log(`\n错误: ${error}`);
1155
+ }
1156
+ process.exit(1);
1157
+ }
1158
+ return;
1159
+ }
1160
+
1135
1161
  if (tool) {
1136
1162
  await runToolCommand(tool, toolArgs, !!json, comm);
1137
1163
  } else if (prompt) {
@@ -1565,7 +1591,9 @@ async function main() {
1565
1591
  s.section('系统初始化');
1566
1592
 
1567
1593
  const hasOpenAI = !!process.env.OPENAI_API_KEY;
1568
- const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
1594
+ // 2026-06-15: 修复 — 之前 anthropic 401 是因为 shell env 残留的旧 ANTHROPIC_API_KEY 抢了 provider 选择
1595
+ // 用 BOLLOON_LLM_PROVIDER env 显式覆盖, 否则还是按 env hasXxx 顺序自动选
1596
+ const hasAnthropic = !!process.env.ANTHROPIC_API_KEY && !process.env.BOLLOON_LLM_PROVIDER;
1569
1597
  const hasMinimax = !!process.env.MINIMAX_API_KEY;
1570
1598
  const hasDeepSeek = !!process.env.DEEPSEEK_API_KEY;
1571
1599
  const hasKimi = !!(process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY);
@@ -1634,14 +1662,31 @@ async function main() {
1634
1662
 
1635
1663
  await bootstrapIroh(keypair, name);
1636
1664
 
1665
+ // Bolloon Bootstrap: 启动扫描 + Context 收集 + 挂定时任务
1666
+ // 失败静默 (主流程不被阻塞)
1667
+ try {
1668
+ const { bootstrapBolloon } = await import('./pi-ecosystem-judgment/human-value-pipeline.js');
1669
+ s.info('正在 bootstrap bolloon 上下文...');
1670
+ const bs = await bootstrapBolloon({ cwd: process.cwd() });
1671
+ s.info(`Bootstrap 完成 (${bs.durationMs}ms, ${bs.errors.length} 个非致命错误)`);
1672
+ } catch (err: any) {
1673
+ s.warn(`Bootstrap 失败 (非致命, 主流程继续): ${err.message}`);
1674
+ }
1675
+
1637
1676
  s.divider();
1638
1677
 
1639
1678
  if (mode === 'web') {
1640
1679
  const port = parseInt(process.env.PORT || '54188');
1680
+ // 2026-06-16: BOLLOON_DEV_MODE=1 或 selfImprove=true 启动项 → 开发者模式, 启用自迭代 (健康监控+自改总线)
1681
+ // 默认用户模式: 不自迭代, 自改卡片不自动出现, 仍可 POST /api/self-improve/trigger 手动触发
1682
+ const selfImprove = process.env.BOLLOON_DEV_MODE === '1' || process.env.BOLLOON_DEV_MODE === 'true';
1683
+ if (selfImprove) {
1684
+ console.log('[startup] BOLLOON_DEV_MODE=1, 开发者模式: 自迭代已启用');
1685
+ }
1641
1686
  const { createWebServer, openBrowser } = await import('./web/server.js');
1642
1687
 
1643
1688
  s.info(`启动 Web 服务端口 ${port}...`);
1644
- const { port: actualPort } = await createWebServer(port);
1689
+ const { port: actualPort } = await createWebServer(port, { selfImprove });
1645
1690
 
1646
1691
  s.success(`浏览器已打开 → http://localhost:${actualPort}`);
1647
1692
  openBrowser(`http://localhost:${actualPort}`);
@@ -7,10 +7,32 @@
7
7
 
8
8
  import { llmConfigStore, type ModelProvider } from './config-store.js';
9
9
  import { PiAIModel, type ChatMessage } from './pi-ai.js';
10
+ import * as fs from 'fs/promises';
11
+ import * as path from 'path';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const LAYERS_DIR = path.join(__dirname, 'system-prompt', 'layers');
16
+
17
+ /**
18
+ * 从 layer 读 .md 文件作为系统 prompt
19
+ * 失败时降级到兜底字符串 (不挂)
20
+ */
21
+ async function loadRoleLayer(layerId: string, fallback: string): Promise<string> {
22
+ const [group, ...rest] = layerId.split('.');
23
+ const path2md = path.join(LAYERS_DIR, group, `${rest.join('.')}.md`);
24
+ try {
25
+ return await fs.readFile(path2md, 'utf-8');
26
+ } catch {
27
+ return fallback;
28
+ }
29
+ }
10
30
 
11
31
  export interface JudgmentPromptConfig {
12
- // 系统 Prompt
32
+ // 系统 Prompt (静态字符串, 向后兼容)
13
33
  systemPrompt: string;
34
+ // 系统 Prompt resolver (优先于 systemPrompt; 异步从 layer 读)
35
+ systemPromptResolver?: () => Promise<string>;
14
36
 
15
37
  // 用户 Prompt 模板
16
38
  userPromptTemplate: string;
@@ -29,16 +51,8 @@ export interface JudgmentPromptConfig {
29
51
  export const JUDGMENT_PROMPTS: Record<string, JudgmentPromptConfig> = {
30
52
  // 默认判断 Prompt
31
53
  default: {
32
- systemPrompt: `你是一个专业的 AI 任务分析专家。你的职责是深入理解用户的问题,并给出精准的判断。
33
-
34
- 核心原则:
35
- 1. 第一性原理:追问问题的本质,而非表面现象
36
- 2. 多角度思考:考虑不同视角和影响因素
37
- 3. 上下文感知:理解对话历史和隐含意图
38
- 4. 动态评估:根据实际情况判断复杂性
39
-
40
- 你的判断会影响后续的智能体协作方式,请认真分析。`,
41
-
54
+ systemPrompt: '',
55
+ systemPromptResolver: () => loadRoleLayer('role.expert', '你是一个专业的 AI 任务分析专家. '),
42
56
  userPromptTemplate: `【当前输入】
43
57
  {user_input}
44
58
 
@@ -95,17 +109,8 @@ export const JUDGMENT_PROMPTS: Record<string, JudgmentPromptConfig> = {
95
109
 
96
110
  // 架构相关问题
97
111
  architecture: {
98
- systemPrompt: `你是一个经验丰富的系统架构师。你擅长:
99
- - 从需求中提取本质问题
100
- - 设计可扩展的系统架构
101
- - 权衡技术方案
102
- - 识别架构风险
103
-
104
- 你相信:
105
- - 本质和实现必须分离
106
- - 好的架构从简单规则中生长
107
- - 复杂性应该被控制而非消除`,
108
-
112
+ systemPrompt: '',
113
+ systemPromptResolver: () => loadRoleLayer('role.architect', '你是一个经验丰富的系统架构师. '),
109
114
  userPromptTemplate: `【架构设计问题】
110
115
  {user_input}
111
116
 
@@ -133,12 +138,8 @@ export const JUDGMENT_PROMPTS: Record<string, JudgmentPromptConfig> = {
133
138
 
134
139
  // 代码相关问题
135
140
  code: {
136
- systemPrompt: `你是一个高效的代码实现专家。你擅长:
137
- - 快速理解和实现需求
138
- - 编写清晰、可维护的代码
139
- - 遵循最佳实践
140
- - 处理边界情况`,
141
-
141
+ systemPrompt: '',
142
+ systemPromptResolver: () => loadRoleLayer('role.implementer', '你是一个高效的代码实现专家. '),
142
143
  userPromptTemplate: `【代码问题】
143
144
  {user_input}
144
145
 
@@ -162,12 +163,8 @@ export const JUDGMENT_PROMPTS: Record<string, JudgmentPromptConfig> = {
162
163
 
163
164
  // 安全相关问题
164
165
  security: {
165
- systemPrompt: `你是一个严格的安全专家。你擅长:
166
- - 识别安全风险
167
- - 评估威胁模型
168
- - 设计安全方案
169
- - 遵循安全最佳实践`,
170
-
166
+ systemPrompt: '',
167
+ systemPromptResolver: () => loadRoleLayer('role.security', '你是一个严格的安全专家. '),
171
168
  userPromptTemplate: `【安全问题】
172
169
  {user_input}
173
170
 
@@ -300,8 +297,12 @@ export class LLMJudgmentClient {
300
297
 
301
298
  try {
302
299
  // 调用 LLM
300
+ // 优先用 systemPromptResolver (从 layer 读), fallback 静态 systemPrompt
301
+ const systemPrompt = this.promptConfig.systemPromptResolver
302
+ ? await this.promptConfig.systemPromptResolver()
303
+ : this.promptConfig.systemPrompt;
303
304
  const messages: ChatMessage[] = [
304
- { role: 'system', content: this.promptConfig.systemPrompt },
305
+ { role: 'system', content: systemPrompt },
305
306
  { role: 'user', content: userPrompt }
306
307
  ];
307
308
 
package/src/llm/pi-ai.ts CHANGED
@@ -28,6 +28,29 @@ export interface GenerateOptions {
28
28
  messages: ChatMessage[];
29
29
  temperature?: number;
30
30
  maxTokens?: number;
31
+ signal?: AbortSignal;
32
+ /** 工具 id 列表 — 代码侧 (src/llm/tool-manifest/) 查 schema, prompt 里只嵌 oneLine + callExample */
33
+ tools?: string[];
34
+ }
35
+
36
+ /**
37
+ * 外部 system 注入钩子.
38
+ * 调用方 (e.g. auto-evolve-loop) 用 setSystemPrependProvider() 注册一个返回字符串的函数,
39
+ * generateText 在拼 finalMessages 时, 把返回的字符串作为最前一个 system message.
40
+ *
41
+ * 用途: P2P 协作时把"行级 reserve 状态"实时塞给 LLM,
42
+ * 让 LLM 主动避开对方正在改的代码行.
43
+ *
44
+ * 返回 '' / null / undefined → 不注入.
45
+ */
46
+ let _prependProvider: (() => string | null | undefined | Promise<string | null | undefined>) | null = null;
47
+ export function setSystemPrependProvider(
48
+ p: (() => string | null | undefined | Promise<string | null | undefined>) | null
49
+ ): void {
50
+ _prependProvider = p;
51
+ }
52
+ export function getSystemPrependProvider(): typeof _prependProvider {
53
+ return _prependProvider;
31
54
  }
32
55
 
33
56
  export class PiAIModel {
@@ -39,8 +62,8 @@ export class PiAIModel {
39
62
  this.provider = config.provider;
40
63
  }
41
64
 
42
- async chat(message: string, context?: string): Promise<ChatResult> {
43
- const systemPrompt = this.buildSystemPrompt(context);
65
+ async chat(message: string, context?: string, signal?: AbortSignal): Promise<ChatResult> {
66
+ const systemPrompt = await this.buildSystemPromptAsync(context);
44
67
  const messages: ChatMessage[] = [
45
68
  { role: 'system', content: systemPrompt },
46
69
  { role: 'user', content: message }
@@ -49,12 +72,25 @@ export class PiAIModel {
49
72
  try {
50
73
  const response = await this.generateText({
51
74
  messages,
52
- temperature: 0.8
75
+ temperature: 0.8,
76
+ maxTokens: 8192, // 2026-06-15: 提到 8192, 避免长回答 (含表格/代码块) 被截断
77
+ signal,
53
78
  });
54
79
  return { reply: response };
55
- } catch (error) {
80
+ } catch (error: any) {
81
+ // abort 不当作错误, 透传一个 sentinel 让上层能识别
82
+ if (signal?.aborted || error?.name === 'AbortError') {
83
+ throw error; // 上层 try/catch 处理
84
+ }
56
85
  console.error('PiAI chat error:', error);
57
- return { reply: '抱歉,AI服务暂时不可用。' };
86
+ // 2026-06-15: 真实 error 信息 + 明确告诉 LLM "这是 API 错, 不要 retry"
87
+ // 旧版: "抱歉,AI服务暂时不可用。" → LLM 看到 isTooShort=false(< 50 但 > 0),
88
+ // needsMoreWork 不会触发, 但 hasError 模式 (含 "error" / "失败") 会判定要继续修
89
+ // 新版: 让 LLM 立即停止循环, 直接展示给用户
90
+ const errMsg = (error?.message || '').slice(0, 300);
91
+ return {
92
+ reply: `[AI 服务调用失败] ${errMsg}\n\n这是一个**底层 API 错误**(401 / 鉴权失败 / 网络中断 / 配额耗尽等),不是你的任务有问题。**请直接把这个错误消息回复给用户,不要再循环尝试。**`,
93
+ };
58
94
  }
59
95
  }
60
96
 
@@ -100,7 +136,40 @@ export class PiAIModel {
100
136
  }
101
137
 
102
138
  private async generateText(options: GenerateOptions): Promise<string> {
103
- const { messages, temperature = 0.7, maxTokens = 4096 } = options;
139
+ const { messages, temperature = 0.7, maxTokens = 4096, signal, tools } = options;
140
+
141
+ // 工具清单: 代码侧 schema → 进 system prompt (作为额外 system message)
142
+ let finalMessages = messages;
143
+
144
+ // 1) 外部 prepend (e.g. P2P 行级 reserve 状态), 拼到最前
145
+ if (_prependProvider) {
146
+ try {
147
+ const pre = await _prependProvider();
148
+ if (pre && typeof pre === 'string' && pre.trim()) {
149
+ finalMessages = [{ role: 'system', content: pre }, ...finalMessages];
150
+ }
151
+ } catch (err: any) {
152
+ console.warn('[pi-ai] systemPrepend 失败:', err?.message?.slice(0, 100));
153
+ }
154
+ }
155
+
156
+ if (tools && tools.length > 0) {
157
+ try {
158
+ const { getToolManifest, formatForPrompt } = await import('./tool-manifest/index.js');
159
+ const manifests = tools
160
+ .map((id) => getToolManifest(id))
161
+ .filter((m): m is NonNullable<typeof m> => m !== undefined);
162
+ if (manifests.length > 0) {
163
+ const toolPrompt = formatForPrompt(manifests);
164
+ finalMessages = [
165
+ { role: 'system', content: toolPrompt },
166
+ ...messages,
167
+ ];
168
+ }
169
+ } catch (err: any) {
170
+ console.warn('[pi-ai] tool-manifest 加载失败:', err.message?.slice(0, 100));
171
+ }
172
+ }
104
173
 
105
174
  switch (this.provider) {
106
175
  case 'openai':
@@ -109,17 +178,17 @@ export class PiAIModel {
109
178
  case 'kimi':
110
179
  case 'glm':
111
180
  case 'qwen':
112
- return this.callOpenAI(messages, temperature, maxTokens);
181
+ return this.callOpenAI(finalMessages, temperature, maxTokens, signal);
113
182
  case 'anthropic':
114
- return this.callAnthropic(messages, temperature, maxTokens);
183
+ return this.callAnthropic(finalMessages, temperature, maxTokens, signal);
115
184
  case 'ollama':
116
- return this.callOllama(messages, temperature);
185
+ return this.callOllama(finalMessages, temperature, signal);
117
186
  case 'openrouter':
118
- return this.callOpenRouter(messages, temperature, maxTokens);
187
+ return this.callOpenRouter(finalMessages, temperature, maxTokens, signal);
119
188
  case 'gemini':
120
- return this.callGemini(messages, temperature, maxTokens);
189
+ return this.callGemini(finalMessages, temperature, maxTokens, signal);
121
190
  case 'local':
122
- return this.callLocal(messages, temperature);
191
+ return this.callLocal(finalMessages, temperature, signal);
123
192
  default:
124
193
  throw new Error(`Unsupported provider: ${this.provider}`);
125
194
  }
@@ -186,24 +255,29 @@ export class PiAIModel {
186
255
  return modelMap[this.provider];
187
256
  }
188
257
 
189
- private async callOpenAI(messages: ChatMessage[], temperature: number, maxTokens: number): Promise<string> {
258
+ private async callOpenAI(messages: ChatMessage[], temperature: number, maxTokens: number, signal?: AbortSignal): Promise<string> {
190
259
  const apiKey = this.getApiKey();
191
260
  if (!apiKey) {
192
261
  throw new Error('OPENAI_API_KEY not set');
193
262
  }
194
263
 
264
+ const requestBody = {
265
+ model: this.mapModel(),
266
+ messages,
267
+ temperature,
268
+ max_tokens: maxTokens
269
+ };
195
270
  const response = await fetch(`${this.getBaseUrl()}/chat/completions`, {
196
271
  method: 'POST',
197
272
  headers: {
198
273
  'Content-Type': 'application/json',
199
274
  'Authorization': `Bearer ${apiKey}`
200
275
  },
201
- body: JSON.stringify({
202
- model: this.mapModel(),
203
- messages,
204
- temperature,
205
- max_tokens: maxTokens
206
- })
276
+ body: JSON.stringify(requestBody),
277
+ // 2026-06-15: signal 字段防御 — Node 22+ undici 强类型 AbortSignal, 非 AbortSignal 实例
278
+ // (e.g. 误传的 { maxTokens: 1 } 对象) 会 throw "Expected signal to be AbortSignal".
279
+ // 若不是 AbortSignal, 退到无 signal 调用, fetch 自然支持 timeout 由外层控制.
280
+ signal: signal instanceof AbortSignal ? signal : undefined,
207
281
  });
208
282
 
209
283
  if (!response.ok) {
@@ -214,7 +288,7 @@ export class PiAIModel {
214
288
  return data.choices?.[0]?.message?.content || '';
215
289
  }
216
290
 
217
- private async callAnthropic(messages: ChatMessage[], temperature: number, maxTokens: number): Promise<string> {
291
+ private async callAnthropic(messages: ChatMessage[], temperature: number, maxTokens: number, signal?: AbortSignal): Promise<string> {
218
292
  const apiKey = this.getApiKey();
219
293
  if (!apiKey) {
220
294
  throw new Error('ANTHROPIC_API_KEY not set');
@@ -237,7 +311,8 @@ export class PiAIModel {
237
311
  system: systemMessage,
238
312
  temperature,
239
313
  max_tokens: maxTokens
240
- })
314
+ }),
315
+ signal,
241
316
  });
242
317
 
243
318
  if (!response.ok) {
@@ -248,7 +323,7 @@ export class PiAIModel {
248
323
  return data.content?.[0]?.text || '';
249
324
  }
250
325
 
251
- private async callOllama(messages: ChatMessage[], temperature: number): Promise<string> {
326
+ private async callOllama(messages: ChatMessage[], temperature: number, signal?: AbortSignal): Promise<string> {
252
327
  const response = await fetch(`${this.getBaseUrl()}/api/chat`, {
253
328
  method: 'POST',
254
329
  headers: {
@@ -259,7 +334,8 @@ export class PiAIModel {
259
334
  messages,
260
335
  temperature,
261
336
  stream: false
262
- })
337
+ }),
338
+ signal,
263
339
  });
264
340
 
265
341
  if (!response.ok) {
@@ -270,7 +346,7 @@ export class PiAIModel {
270
346
  return data.message?.content || '';
271
347
  }
272
348
 
273
- private async callOpenRouter(messages: ChatMessage[], temperature: number, maxTokens: number): Promise<string> {
349
+ private async callOpenRouter(messages: ChatMessage[], temperature: number, maxTokens: number, signal?: AbortSignal): Promise<string> {
274
350
  const apiKey = this.getApiKey();
275
351
  if (!apiKey) {
276
352
  throw new Error('OPENROUTER_API_KEY not set');
@@ -289,7 +365,8 @@ export class PiAIModel {
289
365
  messages,
290
366
  temperature,
291
367
  max_tokens: maxTokens
292
- })
368
+ }),
369
+ signal,
293
370
  });
294
371
 
295
372
  if (!response.ok) {
@@ -300,7 +377,7 @@ export class PiAIModel {
300
377
  return data.choices?.[0]?.message?.content || '';
301
378
  }
302
379
 
303
- private async callGemini(messages: ChatMessage[], temperature: number, maxTokens: number): Promise<string> {
380
+ private async callGemini(messages: ChatMessage[], temperature: number, maxTokens: number, signal?: AbortSignal): Promise<string> {
304
381
  const apiKey = this.getApiKey();
305
382
  if (!apiKey) {
306
383
  throw new Error('GEMINI_API_KEY not set');
@@ -329,7 +406,8 @@ export class PiAIModel {
329
406
  temperature,
330
407
  maxOutputTokens: maxTokens
331
408
  }
332
- })
409
+ }),
410
+ signal,
333
411
  }
334
412
  );
335
413
 
@@ -341,10 +419,38 @@ export class PiAIModel {
341
419
  return data.candidates?.[0]?.content?.parts?.[0]?.text || '';
342
420
  }
343
421
 
344
- private async callLocal(messages: ChatMessage[], temperature: number): Promise<string> {
345
- return this.callOllama(messages, temperature);
422
+ private async callLocal(messages: ChatMessage[], temperature: number, signal?: AbortSignal): Promise<string> {
423
+ return this.callOllama(messages, temperature, signal);
424
+ }
425
+ // 注: callLocal 直接代理 ollama. 工具清单由 generateText 在外层拼到 messages, 这里 messages 已是 finalMessages.
426
+
427
+ private async buildSystemPromptAsync(context?: string): Promise<string> {
428
+ // 走 layer registry: 装配所有相关 layer (身份/行为/工具/角色/渠道)
429
+ try {
430
+ const { assembleSystemPrompt, SYSTEM_PROMPT_VERSION } = await import(
431
+ './system-prompt/registry.js' as any
432
+ ).catch(() => import('./system-prompt/registry.js'));
433
+ // channel = 本机 (PI SDK 直接调就是 local)
434
+ // role = 由 prompt context 决定 (默认 expert)
435
+ const ctx = context ? { channel: 'local' as const, role: 'expert' as const } : { channel: 'local' as const, role: 'expert' as const };
436
+ const result = await assembleSystemPrompt(ctx);
437
+ return `${result.text}\n\n## User Working Directory\n${context || process.cwd()}\n\n## bolloon-runtime\n${SYSTEM_PROMPT_VERSION} · layers: ${result.layerIds.join(',')}`;
438
+ } catch (err: any) {
439
+ // 降级: 旧硬编码 (layer registry 不可用时不挂)
440
+ console.warn('[pi-ai] layer registry 不可用, 降级:', err.message?.slice(0, 100));
441
+ const envDetails = this.getEnvironmentDetails();
442
+ return `You are a friendly AI assistant in a P2P document collaboration network.
443
+
444
+ ## User Working Directory
445
+ ${context || process.cwd()}
446
+
447
+ ## Environment
448
+ ${envDetails}`;
449
+ }
346
450
  }
347
451
 
452
+ // 同步版: 旧调用点 (buildSystemPrompt 是同步)
453
+ // 保留但内部用 sync fallback; 后续可改成 async
348
454
  private buildSystemPrompt(context?: string): string {
349
455
  const envDetails = this.getEnvironmentDetails();
350
456
  return `You are a friendly AI assistant in a P2P document collaboration network.