@bolloon/bolloon-agent 0.1.35 → 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 (194) hide show
  1. package/.playwright-mcp/page-2026-06-16T07-51-45-706Z.yml +130 -0
  2. package/.playwright-mcp/page-2026-06-16T07-56-44-116Z.yml +131 -0
  3. package/Bolloon.md +45 -4
  4. package/Dive-into/CITATION.cff +17 -0
  5. package/Dive-into/LICENSE +25 -0
  6. package/Dive-into/README.md +598 -0
  7. package/Dive-into/README_zh.md +599 -0
  8. package/Dive-into/assets/context.png +0 -0
  9. package/Dive-into/assets/extensibility.png +0 -0
  10. package/Dive-into/assets/iteration.png +0 -0
  11. package/Dive-into/assets/layered_architecture.png +0 -0
  12. package/Dive-into/assets/main_structure.png +0 -0
  13. package/Dive-into/assets/permission.png +0 -0
  14. package/Dive-into/assets/session_compact.png +0 -0
  15. package/Dive-into/assets/subagent.png +0 -0
  16. package/Dive-into/paper/Dive_into_Claude_Code.pdf +0 -0
  17. package/README.md +1 -1
  18. package/dist/agents/p2p-chat-tools.js +6 -6
  19. package/dist/agents/permission-mode.js +115 -0
  20. package/dist/agents/pi-sdk.js +435 -25
  21. package/dist/agents/pre-tool-validator.js +194 -0
  22. package/dist/agents/workflow-pivot-loop.js +113 -12
  23. package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +1 -1
  24. package/dist/bollharness/src/scripts/checks/check_doc_links.js +1 -1
  25. package/dist/bollharness/src/scripts/context_router.js +1 -1
  26. package/dist/bollharness/src/scripts/deploy-guard.js +1 -1
  27. package/dist/bollharness/src/scripts/guard_router.js +1 -1
  28. package/dist/bollharness/src/scripts/hooks/_hook_output.js +1 -1
  29. package/dist/bollharness/src/scripts/hooks/risk-tracker.js +1 -1
  30. package/dist/bollharness-integration/context-router.js +2 -2
  31. package/dist/bollharness-integration/guard-checker.js +1 -1
  32. package/dist/bootstrap/bootstrap.js +3 -0
  33. package/dist/bootstrap/context-collector.js +17 -1
  34. package/dist/bootstrap/context-hierarchy.js +218 -0
  35. package/dist/bootstrap/lifecycle-hooks.js +86 -24
  36. package/dist/bootstrap/project-context.js +10 -3
  37. package/dist/context-compaction/auto-compact.js +144 -0
  38. package/dist/context-compaction/budget-gate.js +28 -0
  39. package/dist/context-compaction/budget-reduce.js +35 -0
  40. package/dist/context-compaction/context-collapse.js +66 -0
  41. package/dist/context-compaction/index.js +21 -0
  42. package/dist/context-compaction/microcompact.js +51 -0
  43. package/dist/context-compaction/pipeline.js +123 -0
  44. package/dist/context-compaction/snip.js +45 -0
  45. package/dist/context-compaction/token-estimator.js +35 -0
  46. package/dist/context-compaction/types.js +19 -0
  47. package/dist/heartbeat/HealthMonitor.js +3 -2
  48. package/dist/index.js +38 -2
  49. package/dist/llm/llm-judgment-client.js +32 -30
  50. package/dist/llm/pi-ai.js +103 -16
  51. package/dist/llm/system-prompt/health.js +129 -0
  52. package/dist/llm/system-prompt/registry.js +246 -0
  53. package/dist/llm/system-prompt/strip-hibsml.js +51 -0
  54. package/dist/llm/tool-manifest/ask_user_input.js +35 -0
  55. package/dist/llm/tool-manifest/bash.js +23 -0
  56. package/dist/llm/tool-manifest/create_file.js +24 -0
  57. package/dist/llm/tool-manifest/fetch_sports_data.js +26 -0
  58. package/dist/llm/tool-manifest/image_search.js +24 -0
  59. package/dist/llm/tool-manifest/index.js +69 -0
  60. package/dist/llm/tool-manifest/mcp.js +43 -0
  61. package/dist/llm/tool-manifest/message_compose.js +31 -0
  62. package/dist/llm/tool-manifest/places.js +83 -0
  63. package/dist/llm/tool-manifest/present_files.js +21 -0
  64. package/dist/llm/tool-manifest/recipe.js +40 -0
  65. package/dist/llm/tool-manifest/recommend_apps.js +23 -0
  66. package/dist/llm/tool-manifest/str_replace.js +27 -0
  67. package/dist/llm/tool-manifest/types.js +7 -0
  68. package/dist/llm/tool-manifest/view.js +24 -0
  69. package/dist/llm/tool-manifest/weather.js +27 -0
  70. package/dist/llm/tool-manifest/web.js +51 -0
  71. package/dist/network/p2p-direct.js +23 -0
  72. package/dist/network/source-intent-broadcaster.js +203 -0
  73. package/dist/network/source-intent.js +100 -0
  74. package/dist/pi-ecosystem-judgment/adaptive-scan.js +48 -0
  75. package/dist/pi-ecosystem-judgment/injection-gate.js +54 -12
  76. package/dist/pi-ecosystem-judgment/value-injection.js +8 -2
  77. package/dist/security/context-router-tool.js +1 -1
  78. package/dist/security/input-scanner.js +223 -0
  79. package/dist/web/client.js +3677 -4161
  80. package/dist/web/components/p2p/P2PModal.js +188 -0
  81. package/dist/web/components/p2p/index.js +264 -226
  82. package/dist/web/components/p2p/p2p-modal.js +657 -0
  83. package/dist/web/components/p2p/p2p-tools.js +248 -0
  84. package/dist/web/index.html +58 -67
  85. package/dist/web/server.js +299 -117
  86. package/dist/web/style.css +531 -249
  87. package/dist/web/ui/message-renderer.js +463 -0
  88. package/dist/web/ui/step-timeline.js +375 -0
  89. package/lefthook.yml +4 -0
  90. package/package.json +1 -1
  91. package/scripts/auto-evolve-loop.ts +105 -0
  92. package/scripts/build-web.ts +35 -1
  93. package/scripts/validate-system-prompt.ts +142 -0
  94. package/src/agents/p2p-chat-tools.ts +6 -6
  95. package/src/agents/permission-mode.ts +127 -0
  96. package/src/agents/pi-sdk.ts +461 -28
  97. package/src/agents/pre-tool-validator.ts +213 -0
  98. package/src/agents/workflow-pivot-loop.ts +110 -19
  99. package/src/bollharness/CLAUDE.md +1 -1
  100. package/src/bollharness/README.md +2 -2
  101. package/src/bollharness/README.zh-CN.md +2 -2
  102. package/src/bollharness/reference/boll-reference/scripts/hooks/find-boll-root.sh +2 -2
  103. package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +2 -2
  104. package/src/bollharness/scripts/context-fragments/version-sources.md +1 -1
  105. package/src/bollharness/scripts/hooks/find-project-root.sh +4 -4
  106. package/src/bollharness/src/scripts/checks/check_doc_freshness.ts +1 -1
  107. package/src/bollharness/src/scripts/checks/check_doc_links.ts +1 -1
  108. package/src/bollharness/src/scripts/context_router.ts +1 -1
  109. package/src/bollharness/src/scripts/deploy-guard.ts +1 -1
  110. package/src/bollharness/src/scripts/guard_router.ts +1 -1
  111. package/src/bollharness/src/scripts/hooks/_hook_output.ts +1 -1
  112. package/src/bollharness/src/scripts/hooks/risk-tracker.ts +1 -1
  113. package/src/bollharness/templates/scaffold/.gitignore.append +1 -1
  114. package/src/bollharness/templates/scaffold/CLAUDE.md +2 -2
  115. package/src/bollharness-integration/context-router.ts +2 -2
  116. package/src/bollharness-integration/guard-checker.ts +1 -1
  117. package/src/bootstrap/bootstrap.ts +3 -0
  118. package/src/bootstrap/context-collector.ts +30 -1
  119. package/src/bootstrap/context-hierarchy.ts +283 -0
  120. package/src/bootstrap/lifecycle-hooks.ts +139 -26
  121. package/src/bootstrap/project-context.ts +11 -3
  122. package/src/context-compaction/auto-compact.ts +153 -0
  123. package/src/context-compaction/budget-gate.ts +32 -0
  124. package/src/context-compaction/budget-reduce.ts +37 -0
  125. package/src/context-compaction/context-collapse.ts +72 -0
  126. package/src/context-compaction/index.ts +24 -0
  127. package/src/context-compaction/microcompact.ts +54 -0
  128. package/src/context-compaction/pipeline.ts +133 -0
  129. package/src/context-compaction/snip.ts +51 -0
  130. package/src/context-compaction/token-estimator.ts +36 -0
  131. package/src/context-compaction/types.ts +99 -0
  132. package/src/heartbeat/HealthMonitor.ts +3 -2
  133. package/src/index.ts +36 -2
  134. package/src/llm/llm-judgment-client.ts +36 -35
  135. package/src/llm/pi-ai.ts +111 -16
  136. package/src/llm/system-prompt/health.ts +159 -0
  137. package/src/llm/system-prompt/layers/channel/local.md +14 -0
  138. package/src/llm/system-prompt/layers/channel/p2p-agent.md +18 -0
  139. package/src/llm/system-prompt/layers/channel/p2p-visitor.md +19 -0
  140. package/src/llm/system-prompt/layers/core/artifacts_storage.md +89 -0
  141. package/src/llm/system-prompt/layers/core/evenhandedness.md +21 -0
  142. package/src/llm/system-prompt/layers/core/hibs_reminders.md +15 -0
  143. package/src/llm/system-prompt/layers/core/identity.md +37 -0
  144. package/src/llm/system-prompt/layers/core/knowledge.md +17 -0
  145. package/src/llm/system-prompt/layers/core/memory_system.md +12 -0
  146. package/src/llm/system-prompt/layers/core/network_filesystem.md +28 -0
  147. package/src/llm/system-prompt/layers/core/refusal.md +37 -0
  148. package/src/llm/system-prompt/layers/core/tone.md +31 -0
  149. package/src/llm/system-prompt/layers/core/tools.thin.md +13 -0
  150. package/src/llm/system-prompt/layers/core/wellbeing.md +41 -0
  151. package/src/llm/system-prompt/layers/role/architect.md +20 -0
  152. package/src/llm/system-prompt/layers/role/expert.md +19 -0
  153. package/src/llm/system-prompt/layers/role/implementer.md +15 -0
  154. package/src/llm/system-prompt/layers/role/security.md +15 -0
  155. package/src/llm/system-prompt/layers/tool/artifacts.md +72 -0
  156. package/src/llm/system-prompt/layers/tool/bash.md +25 -0
  157. package/src/llm/system-prompt/layers/tool/hibs_api.md +171 -0
  158. package/src/llm/system-prompt/layers/tool/image_search.md +70 -0
  159. package/src/llm/system-prompt/layers/tool/manifest.md +89 -0
  160. package/src/llm/system-prompt/layers/tool/mcp_apps.md +53 -0
  161. package/src/llm/system-prompt/layers/tool/web_search.md +83 -0
  162. package/src/llm/system-prompt/registry.ts +325 -0
  163. package/src/llm/system-prompt/strip-hibsml.ts +52 -0
  164. package/src/llm/tool-manifest/ask_user_input.ts +37 -0
  165. package/src/llm/tool-manifest/bash.ts +25 -0
  166. package/src/llm/tool-manifest/create_file.ts +26 -0
  167. package/src/llm/tool-manifest/fetch_sports_data.ts +28 -0
  168. package/src/llm/tool-manifest/image_search.ts +26 -0
  169. package/src/llm/tool-manifest/index.ts +88 -0
  170. package/src/llm/tool-manifest/mcp.ts +46 -0
  171. package/src/llm/tool-manifest/message_compose.ts +33 -0
  172. package/src/llm/tool-manifest/places.ts +86 -0
  173. package/src/llm/tool-manifest/present_files.ts +23 -0
  174. package/src/llm/tool-manifest/recipe.ts +42 -0
  175. package/src/llm/tool-manifest/recommend_apps.ts +25 -0
  176. package/src/llm/tool-manifest/str_replace.ts +29 -0
  177. package/src/llm/tool-manifest/types.ts +52 -0
  178. package/src/llm/tool-manifest/view.ts +26 -0
  179. package/src/llm/tool-manifest/weather.ts +29 -0
  180. package/src/llm/tool-manifest/web.ts +54 -0
  181. package/src/network/p2p-direct.ts +22 -0
  182. package/src/network/source-intent-broadcaster.ts +242 -0
  183. package/src/network/source-intent.ts +167 -0
  184. package/src/security/context-router-tool.ts +1 -1
  185. package/src/security/input-scanner.ts +287 -0
  186. package/src/web/client.js +271 -2817
  187. package/src/web/client.ts +4326 -0
  188. package/src/web/index.html +58 -67
  189. package/src/web/server.ts +301 -141
  190. package/src/web/style.css +531 -249
  191. package/src/web/ui/message-renderer.ts +540 -0
  192. package/src/web/ui/step-timeline.ts +394 -0
  193. package/test-results/.last-run.json +6 -0
  194. 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,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);
@@ -1649,10 +1677,16 @@ async function main() {
1649
1677
 
1650
1678
  if (mode === 'web') {
1651
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
+ }
1652
1686
  const { createWebServer, openBrowser } = await import('./web/server.js');
1653
1687
 
1654
1688
  s.info(`启动 Web 服务端口 ${port}...`);
1655
- const { port: actualPort } = await createWebServer(port);
1689
+ const { port: actualPort } = await createWebServer(port, { selfImprove });
1656
1690
 
1657
1691
  s.success(`浏览器已打开 → http://localhost:${actualPort}`);
1658
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
@@ -29,6 +29,28 @@ export interface GenerateOptions {
29
29
  temperature?: number;
30
30
  maxTokens?: number;
31
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;
32
54
  }
33
55
 
34
56
  export class PiAIModel {
@@ -41,7 +63,7 @@ export class PiAIModel {
41
63
  }
42
64
 
43
65
  async chat(message: string, context?: string, signal?: AbortSignal): Promise<ChatResult> {
44
- const systemPrompt = this.buildSystemPrompt(context);
66
+ const systemPrompt = await this.buildSystemPromptAsync(context);
45
67
  const messages: ChatMessage[] = [
46
68
  { role: 'system', content: systemPrompt },
47
69
  { role: 'user', content: message }
@@ -51,6 +73,7 @@ export class PiAIModel {
51
73
  const response = await this.generateText({
52
74
  messages,
53
75
  temperature: 0.8,
76
+ maxTokens: 8192, // 2026-06-15: 提到 8192, 避免长回答 (含表格/代码块) 被截断
54
77
  signal,
55
78
  });
56
79
  return { reply: response };
@@ -60,7 +83,14 @@ export class PiAIModel {
60
83
  throw error; // 上层 try/catch 处理
61
84
  }
62
85
  console.error('PiAI chat error:', error);
63
- 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
+ };
64
94
  }
65
95
  }
66
96
 
@@ -106,7 +136,40 @@ export class PiAIModel {
106
136
  }
107
137
 
108
138
  private async generateText(options: GenerateOptions): Promise<string> {
109
- const { messages, temperature = 0.7, maxTokens = 4096, signal } = 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
+ }
110
173
 
111
174
  switch (this.provider) {
112
175
  case 'openai':
@@ -115,17 +178,17 @@ export class PiAIModel {
115
178
  case 'kimi':
116
179
  case 'glm':
117
180
  case 'qwen':
118
- return this.callOpenAI(messages, temperature, maxTokens, signal);
181
+ return this.callOpenAI(finalMessages, temperature, maxTokens, signal);
119
182
  case 'anthropic':
120
- return this.callAnthropic(messages, temperature, maxTokens, signal);
183
+ return this.callAnthropic(finalMessages, temperature, maxTokens, signal);
121
184
  case 'ollama':
122
- return this.callOllama(messages, temperature, signal);
185
+ return this.callOllama(finalMessages, temperature, signal);
123
186
  case 'openrouter':
124
- return this.callOpenRouter(messages, temperature, maxTokens, signal);
187
+ return this.callOpenRouter(finalMessages, temperature, maxTokens, signal);
125
188
  case 'gemini':
126
- return this.callGemini(messages, temperature, maxTokens, signal);
189
+ return this.callGemini(finalMessages, temperature, maxTokens, signal);
127
190
  case 'local':
128
- return this.callLocal(messages, temperature, signal);
191
+ return this.callLocal(finalMessages, temperature, signal);
129
192
  default:
130
193
  throw new Error(`Unsupported provider: ${this.provider}`);
131
194
  }
@@ -198,19 +261,23 @@ export class PiAIModel {
198
261
  throw new Error('OPENAI_API_KEY not set');
199
262
  }
200
263
 
264
+ const requestBody = {
265
+ model: this.mapModel(),
266
+ messages,
267
+ temperature,
268
+ max_tokens: maxTokens
269
+ };
201
270
  const response = await fetch(`${this.getBaseUrl()}/chat/completions`, {
202
271
  method: 'POST',
203
272
  headers: {
204
273
  'Content-Type': 'application/json',
205
274
  'Authorization': `Bearer ${apiKey}`
206
275
  },
207
- body: JSON.stringify({
208
- model: this.mapModel(),
209
- messages,
210
- temperature,
211
- max_tokens: maxTokens
212
- }),
213
- signal,
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,
214
281
  });
215
282
 
216
283
  if (!response.ok) {
@@ -355,7 +422,35 @@ export class PiAIModel {
355
422
  private async callLocal(messages: ChatMessage[], temperature: number, signal?: AbortSignal): Promise<string> {
356
423
  return this.callOllama(messages, temperature, signal);
357
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
+ }
450
+ }
358
451
 
452
+ // 同步版: 旧调用点 (buildSystemPrompt 是同步)
453
+ // 保留但内部用 sync fallback; 后续可改成 async
359
454
  private buildSystemPrompt(context?: string): string {
360
455
  const envDetails = this.getEnvironmentDetails();
361
456
  return `You are a friendly AI assistant in a P2P document collaboration network.
@@ -0,0 +1,159 @@
1
+ /**
2
+ * system-prompt health — 每层 lifecycle 状态扫描
3
+ *
4
+ * 严格对齐 deusyu 论文 "Harness Gardening 3-6 月" + walkinglabs Lifecycle 4-stage
5
+ * + 马书 ch05+ch13 systemPromptSection + 89 FF 的版本/owner 治理思想.
6
+ *
7
+ * 设计:
8
+ * - 输入: 完整 layer list (assembleSystemPrompt 返回的 layers[])
9
+ * - 输出: 每层 health 状态 (ok | stale | overdue-review | missing-frontmatter)
10
+ * - 失败静默: 任何 layer 读失败 → 标 'missing-frontmatter' 而非抛错
11
+ *
12
+ * 决策 (cross-repo_caveats 选边):
13
+ * - dynamic layers (function source) → 标 'ok' (last_reviewed_at = now), 视作 runtime-managed
14
+ * - frontmatter author 拒绝 'llm-judge' 之外的不合法值 → 标 'missing-frontmatter'
15
+ */
16
+
17
+ import type { PromptLayer, SectionMeta, AppliesTo } from './registry.js';
18
+
19
+ export type LayerHealth = 'ok' | 'stale' | 'overdue-review' | 'missing-frontmatter' | 'dynamic';
20
+
21
+ export interface LayerHealthEntry {
22
+ id: string;
23
+ health: LayerHealth;
24
+ ageDays: number;
25
+ ttlDays: number;
26
+ /** 距离过期还有多少天 (负数 = 已过期) */
27
+ remainingDays: number;
28
+ author: string | null;
29
+ lastReviewedAt: string | null;
30
+ notes?: string;
31
+ /** 该层是否在当前 context 下被装配 (assembleSystemPrompt 调用过) */
32
+ active: boolean;
33
+ }
34
+
35
+ export interface HealthReport {
36
+ scannedAt: string;
37
+ total: number;
38
+ okCount: number;
39
+ staleCount: number;
40
+ overdueCount: number;
41
+ missingCount: number;
42
+ dynamicCount: number;
43
+ entries: LayerHealthEntry[];
44
+ }
45
+
46
+ const DAY_MS = 24 * 60 * 60 * 1000;
47
+
48
+ /** 兼容运行时: dynamic layer 没有 meta, 视作 'dynamic' (last_reviewed = now) */
49
+ function isDynamic(l: PromptLayer): boolean {
50
+ return l.source === 'function';
51
+ }
52
+
53
+ /** 评估单层 health */
54
+ export function evaluateLayer(l: PromptLayer, now: Date = new Date()): LayerHealthEntry {
55
+ if (isDynamic(l)) {
56
+ return {
57
+ id: l.id,
58
+ health: 'dynamic',
59
+ ageDays: 0,
60
+ ttlDays: -1,
61
+ remainingDays: -1,
62
+ author: l.meta?.author ?? 'runtime',
63
+ lastReviewedAt: now.toISOString(),
64
+ active: false, // 由 caller (assembleSystemPrompt) 决定
65
+ };
66
+ }
67
+ if (!l.meta) {
68
+ // frontmatter 解析失败或缺失
69
+ return {
70
+ id: l.id,
71
+ health: 'missing-frontmatter',
72
+ ageDays: -1,
73
+ ttlDays: -1,
74
+ remainingDays: -1,
75
+ author: null,
76
+ lastReviewedAt: null,
77
+ active: false,
78
+ };
79
+ }
80
+ const reviewedAt = new Date(l.meta.lastReviewedAt).getTime();
81
+ if (isNaN(reviewedAt)) {
82
+ return {
83
+ id: l.id,
84
+ health: 'missing-frontmatter',
85
+ ageDays: -1,
86
+ ttlDays: l.meta.ttlDays,
87
+ remainingDays: -1,
88
+ author: l.meta.author,
89
+ lastReviewedAt: l.meta.lastReviewedAt,
90
+ active: false,
91
+ };
92
+ }
93
+ const ageMs = now.getTime() - reviewedAt;
94
+ const ageDays = Math.floor(ageMs / DAY_MS);
95
+ const remainingDays = l.meta.ttlDays - ageDays;
96
+ let health: LayerHealth;
97
+ if (remainingDays < 0) {
98
+ health = 'overdue-review';
99
+ } else if (remainingDays < l.meta.ttlDays * 0.2) {
100
+ health = 'stale'; // 剩余 < 20% ttl 触发告警
101
+ } else {
102
+ health = 'ok';
103
+ }
104
+ return {
105
+ id: l.id,
106
+ health,
107
+ ageDays,
108
+ ttlDays: l.meta.ttlDays,
109
+ remainingDays,
110
+ author: l.meta.author,
111
+ lastReviewedAt: l.meta.lastReviewedAt,
112
+ notes: l.meta.notes,
113
+ active: false, // caller 决定
114
+ };
115
+ }
116
+
117
+ /** 评估整组 layer (输入: assembleSystemPrompt 返回的 layers[]) */
118
+ export function evaluateLayers(layers: PromptLayer[], now: Date = new Date()): HealthReport {
119
+ const entries = layers.map((l) => evaluateLayer(l, now));
120
+ return summarize(entries, now);
121
+ }
122
+
123
+ /** 汇总: 给一批 entries 算 counts */
124
+ function summarize(entries: LayerHealthEntry[], now: Date): HealthReport {
125
+ const counts = { ok: 0, stale: 0, overdue: 0, missing: 0, dynamic: 0 };
126
+ for (const e of entries) {
127
+ if (e.health === 'ok') counts.ok++;
128
+ else if (e.health === 'stale') counts.stale++;
129
+ else if (e.health === 'overdue-review') counts.overdue++;
130
+ else if (e.health === 'missing-frontmatter') counts.missing++;
131
+ else if (e.health === 'dynamic') counts.dynamic++;
132
+ }
133
+ return {
134
+ scannedAt: now.toISOString(),
135
+ total: entries.length,
136
+ okCount: counts.ok,
137
+ staleCount: counts.stale,
138
+ overdueCount: counts.overdue,
139
+ missingCount: counts.missing,
140
+ dynamicCount: counts.dynamic,
141
+ entries,
142
+ };
143
+ }
144
+
145
+ /** 标记当前 context 下激活的 layer (在 assembleSystemPrompt 调用后用) */
146
+ export function markActive(report: HealthReport, activeIds: Set<string>): HealthReport {
147
+ return {
148
+ ...report,
149
+ entries: report.entries.map((e) => ({ ...e, active: activeIds.has(e.id) })),
150
+ };
151
+ }
152
+
153
+ // ============================================================
154
+ // 测试钩子
155
+ // ============================================================
156
+
157
+ export function _resetHealthForTest(): void {
158
+ // 无内部状态, 保留 API 一致
159
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ added_at: 2026-06-15
3
+ last_reviewed_at: 2026-06-15
4
+ ttl_days: 180
5
+ author: yuanjie
6
+ ---
7
+
8
+ <!-- channel.local@1.0.0 -->
9
+ # 本地渠道 (默认, bolloon 跑在用户本机)
10
+
11
+ 你跑在用户的本地设备上, 可直接读写用户文件系统. 信任度高.
12
+
13
+ 允许: 读写文件、执行命令、调用所有工具.
14
+ 边界: 仍遵守 core.refusal / core.wellbeing / 拒绝处理.
@@ -0,0 +1,18 @@
1
+ ---
2
+ added_at: 2026-06-15
3
+ last_reviewed_at: 2026-06-15
4
+ ttl_days: 180
5
+ author: yuanjie
6
+ ---
7
+
8
+ <!-- channel.p2p-agent@1.0.0 -->
9
+ # P2P 远程智能体 (另一个 bolloon 经 P2P 路由)
10
+
11
+ 你是远端 bolloon, 收到的是**另一个 bolloon 实例**的协作请求. 信任度高 (基于 P2P 节点身份验证, 不是基于人类).
12
+
13
+ 允许: 读 / 写, 协作推理, 共享上下文.
14
+
15
+ 边界:
16
+ - 身份验证优先: 先确认对方节点 ID + 公钥签名
17
+ - 共享上下文时避免把人类隐私数据 (judgment / persona) 外泄给未知节点
18
+ - 协作冲突时回退到 snapshot 重新协商, 不强推修改