@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.
- package/.auto-evolve-calls +1 -0
- package/.last-auto-evolve-baseline +1 -0
- package/.playwright-mcp/page-2026-06-16T07-51-45-706Z.yml +130 -0
- package/.playwright-mcp/page-2026-06-16T07-56-44-116Z.yml +131 -0
- package/Bolloon.md +144 -0
- package/Dive-into/CITATION.cff +17 -0
- package/Dive-into/LICENSE +25 -0
- package/Dive-into/README.md +598 -0
- package/Dive-into/README_zh.md +599 -0
- package/Dive-into/assets/context.png +0 -0
- package/Dive-into/assets/extensibility.png +0 -0
- package/Dive-into/assets/iteration.png +0 -0
- package/Dive-into/assets/layered_architecture.png +0 -0
- package/Dive-into/assets/main_structure.png +0 -0
- package/Dive-into/assets/permission.png +0 -0
- package/Dive-into/assets/session_compact.png +0 -0
- package/Dive-into/assets/subagent.png +0 -0
- package/Dive-into/paper/Dive_into_Claude_Code.pdf +0 -0
- package/README.md +1 -1
- package/dist/agents/p2p-chat-tools.js +6 -6
- package/dist/agents/permission-mode.js +115 -0
- package/dist/agents/pi-sdk.js +687 -25
- package/dist/agents/pre-tool-validator.js +194 -0
- package/dist/agents/workflow-pivot-loop.js +113 -12
- package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +1 -1
- package/dist/bollharness/src/scripts/checks/check_doc_links.js +1 -1
- package/dist/bollharness/src/scripts/context_router.js +1 -1
- package/dist/bollharness/src/scripts/deploy-guard.js +1 -1
- package/dist/bollharness/src/scripts/guard_router.js +1 -1
- package/dist/bollharness/src/scripts/hooks/_hook_output.js +1 -1
- package/dist/bollharness/src/scripts/hooks/risk-tracker.js +1 -1
- package/dist/bollharness-integration/context-router.js +2 -2
- package/dist/bollharness-integration/guard-checker.js +1 -1
- package/dist/bootstrap/bootstrap.js +117 -0
- package/dist/bootstrap/context-collector.js +312 -0
- package/dist/bootstrap/context-hierarchy.js +218 -0
- package/dist/bootstrap/lifecycle-hooks.js +171 -0
- package/dist/bootstrap/project-context.js +158 -0
- package/dist/context-compaction/auto-compact.js +144 -0
- package/dist/context-compaction/budget-gate.js +28 -0
- package/dist/context-compaction/budget-reduce.js +35 -0
- package/dist/context-compaction/context-collapse.js +66 -0
- package/dist/context-compaction/index.js +21 -0
- package/dist/context-compaction/microcompact.js +51 -0
- package/dist/context-compaction/pipeline.js +123 -0
- package/dist/context-compaction/snip.js +45 -0
- package/dist/context-compaction/token-estimator.js +35 -0
- package/dist/context-compaction/types.js +19 -0
- package/dist/heartbeat/HealthMonitor.js +3 -2
- package/dist/index.js +49 -2
- package/dist/llm/llm-judgment-client.js +32 -30
- package/dist/llm/pi-ai.js +125 -28
- package/dist/llm/system-prompt/health.js +129 -0
- package/dist/llm/system-prompt/registry.js +246 -0
- package/dist/llm/system-prompt/strip-hibsml.js +51 -0
- package/dist/llm/tool-manifest/ask_user_input.js +35 -0
- package/dist/llm/tool-manifest/bash.js +23 -0
- package/dist/llm/tool-manifest/create_file.js +24 -0
- package/dist/llm/tool-manifest/fetch_sports_data.js +26 -0
- package/dist/llm/tool-manifest/image_search.js +24 -0
- package/dist/llm/tool-manifest/index.js +69 -0
- package/dist/llm/tool-manifest/mcp.js +43 -0
- package/dist/llm/tool-manifest/message_compose.js +31 -0
- package/dist/llm/tool-manifest/places.js +83 -0
- package/dist/llm/tool-manifest/present_files.js +21 -0
- package/dist/llm/tool-manifest/recipe.js +40 -0
- package/dist/llm/tool-manifest/recommend_apps.js +23 -0
- package/dist/llm/tool-manifest/str_replace.js +27 -0
- package/dist/llm/tool-manifest/types.js +7 -0
- package/dist/llm/tool-manifest/view.js +24 -0
- package/dist/llm/tool-manifest/weather.js +27 -0
- package/dist/llm/tool-manifest/web.js +51 -0
- package/dist/network/p2p-direct.js +23 -0
- package/dist/network/source-intent-broadcaster.js +203 -0
- package/dist/network/source-intent.js +100 -0
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +279 -0
- package/dist/pi-ecosystem-judgment/causal-judge.js +449 -0
- package/dist/pi-ecosystem-judgment/detect-hook.js +168 -0
- package/dist/pi-ecosystem-judgment/distill-prompt.js +226 -0
- package/dist/pi-ecosystem-judgment/evolve-judgment.js +170 -0
- package/dist/pi-ecosystem-judgment/human-value-pipeline.js +21 -0
- package/dist/pi-ecosystem-judgment/human-value-store.js +283 -22
- package/dist/pi-ecosystem-judgment/injection-gate.js +208 -0
- package/dist/pi-ecosystem-judgment/monitor-gate.js +188 -0
- package/dist/pi-ecosystem-judgment/value-injection.js +8 -2
- package/dist/security/builtin-guards.js +124 -0
- package/dist/security/context-router-tool.js +106 -0
- package/dist/security/input-scanner.js +223 -0
- package/dist/security/react-harness.js +143 -0
- package/dist/security/tool-gate.js +235 -0
- package/dist/utils/auto-evolve-policy.js +117 -0
- package/dist/utils/clamp.js +7 -0
- package/dist/utils/double.js +6 -0
- package/dist/web/client.js +3810 -3830
- package/dist/web/components/p2p/P2PModal.js +188 -0
- package/dist/web/components/p2p/index.js +264 -226
- package/dist/web/components/p2p/p2p-modal.js +657 -0
- package/dist/web/components/p2p/p2p-tools.js +248 -0
- package/dist/web/index.html +60 -49
- package/dist/web/server.js +827 -124
- package/dist/web/style.css +531 -249
- package/dist/web/ui/message-renderer.js +463 -0
- package/dist/web/ui/step-timeline.js +375 -0
- package/lefthook.yml +33 -0
- package/package.json +3 -2
- package/scripts/auto-evolve-loop.ts +481 -0
- package/scripts/auto-evolve-oneshot.sh +155 -0
- package/scripts/auto-evolve-snapshot.sh +136 -0
- package/scripts/build-web.ts +35 -1
- package/scripts/detect-schema-changes.sh +48 -0
- package/scripts/diff-reviewer.ts +159 -0
- package/scripts/validate-system-prompt.ts +142 -0
- package/scripts/weekly-report.ts +364 -0
- package/src/agents/p2p-chat-tools.ts +6 -6
- package/src/agents/permission-mode.ts +127 -0
- package/src/agents/pi-sdk.ts +741 -30
- package/src/agents/pre-tool-validator.ts +213 -0
- package/src/agents/workflow-pivot-loop.ts +110 -19
- package/src/bollharness/CLAUDE.md +1 -1
- package/src/bollharness/README.md +2 -2
- package/src/bollharness/README.zh-CN.md +2 -2
- package/src/bollharness/reference/boll-reference/scripts/hooks/find-boll-root.sh +2 -2
- package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +2 -2
- package/src/bollharness/scripts/context-fragments/version-sources.md +1 -1
- package/src/bollharness/scripts/hooks/find-project-root.sh +4 -4
- package/src/bollharness/src/scripts/checks/check_doc_freshness.ts +1 -1
- package/src/bollharness/src/scripts/checks/check_doc_links.ts +1 -1
- package/src/bollharness/src/scripts/context_router.ts +1 -1
- package/src/bollharness/src/scripts/deploy-guard.ts +1 -1
- package/src/bollharness/src/scripts/guard_router.ts +1 -1
- package/src/bollharness/src/scripts/hooks/_hook_output.ts +1 -1
- package/src/bollharness/src/scripts/hooks/risk-tracker.ts +1 -1
- package/src/bollharness/templates/scaffold/.gitignore.append +1 -1
- package/src/bollharness/templates/scaffold/CLAUDE.md +2 -2
- package/src/bollharness-integration/context-router.ts +2 -2
- package/src/bollharness-integration/guard-checker.ts +1 -1
- package/src/bootstrap/bootstrap.ts +135 -0
- package/src/bootstrap/context-collector.ts +371 -0
- package/src/bootstrap/context-hierarchy.ts +283 -0
- package/src/bootstrap/lifecycle-hooks.ts +289 -0
- package/src/bootstrap/project-context.ts +171 -0
- package/src/context-compaction/auto-compact.ts +153 -0
- package/src/context-compaction/budget-gate.ts +32 -0
- package/src/context-compaction/budget-reduce.ts +37 -0
- package/src/context-compaction/context-collapse.ts +72 -0
- package/src/context-compaction/index.ts +24 -0
- package/src/context-compaction/microcompact.ts +54 -0
- package/src/context-compaction/pipeline.ts +133 -0
- package/src/context-compaction/snip.ts +51 -0
- package/src/context-compaction/token-estimator.ts +36 -0
- package/src/context-compaction/types.ts +99 -0
- package/src/heartbeat/HealthMonitor.ts +3 -2
- package/src/index.ts +47 -2
- package/src/llm/llm-judgment-client.ts +36 -35
- package/src/llm/pi-ai.ts +135 -29
- package/src/llm/system-prompt/health.ts +159 -0
- package/src/llm/system-prompt/layers/channel/local.md +14 -0
- package/src/llm/system-prompt/layers/channel/p2p-agent.md +18 -0
- package/src/llm/system-prompt/layers/channel/p2p-visitor.md +19 -0
- package/src/llm/system-prompt/layers/core/artifacts_storage.md +89 -0
- package/src/llm/system-prompt/layers/core/evenhandedness.md +21 -0
- package/src/llm/system-prompt/layers/core/hibs_reminders.md +15 -0
- package/src/llm/system-prompt/layers/core/identity.md +37 -0
- package/src/llm/system-prompt/layers/core/knowledge.md +17 -0
- package/src/llm/system-prompt/layers/core/memory_system.md +12 -0
- package/src/llm/system-prompt/layers/core/network_filesystem.md +28 -0
- package/src/llm/system-prompt/layers/core/refusal.md +37 -0
- package/src/llm/system-prompt/layers/core/tone.md +31 -0
- package/src/llm/system-prompt/layers/core/tools.thin.md +13 -0
- package/src/llm/system-prompt/layers/core/wellbeing.md +41 -0
- package/src/llm/system-prompt/layers/role/architect.md +20 -0
- package/src/llm/system-prompt/layers/role/expert.md +19 -0
- package/src/llm/system-prompt/layers/role/implementer.md +15 -0
- package/src/llm/system-prompt/layers/role/security.md +15 -0
- package/src/llm/system-prompt/layers/tool/artifacts.md +72 -0
- package/src/llm/system-prompt/layers/tool/bash.md +25 -0
- package/src/llm/system-prompt/layers/tool/hibs_api.md +171 -0
- package/src/llm/system-prompt/layers/tool/image_search.md +70 -0
- package/src/llm/system-prompt/layers/tool/manifest.md +89 -0
- package/src/llm/system-prompt/layers/tool/mcp_apps.md +53 -0
- package/src/llm/system-prompt/layers/tool/web_search.md +83 -0
- package/src/llm/system-prompt/registry.ts +325 -0
- package/src/llm/system-prompt/strip-hibsml.ts +52 -0
- package/src/llm/tool-manifest/ask_user_input.ts +37 -0
- package/src/llm/tool-manifest/bash.ts +25 -0
- package/src/llm/tool-manifest/create_file.ts +26 -0
- package/src/llm/tool-manifest/fetch_sports_data.ts +28 -0
- package/src/llm/tool-manifest/image_search.ts +26 -0
- package/src/llm/tool-manifest/index.ts +88 -0
- package/src/llm/tool-manifest/mcp.ts +46 -0
- package/src/llm/tool-manifest/message_compose.ts +33 -0
- package/src/llm/tool-manifest/places.ts +86 -0
- package/src/llm/tool-manifest/present_files.ts +23 -0
- package/src/llm/tool-manifest/recipe.ts +42 -0
- package/src/llm/tool-manifest/recommend_apps.ts +25 -0
- package/src/llm/tool-manifest/str_replace.ts +29 -0
- package/src/llm/tool-manifest/types.ts +52 -0
- package/src/llm/tool-manifest/view.ts +26 -0
- package/src/llm/tool-manifest/weather.ts +29 -0
- package/src/llm/tool-manifest/web.ts +54 -0
- package/src/network/p2p-direct.ts +22 -0
- package/src/network/source-intent-broadcaster.ts +242 -0
- package/src/network/source-intent.ts +167 -0
- package/src/security/builtin-guards.ts +162 -0
- package/src/security/context-router-tool.ts +122 -0
- package/src/security/input-scanner.ts +287 -0
- package/src/security/react-harness.ts +177 -0
- package/src/security/tool-gate.ts +294 -0
- package/src/utils/auto-evolve-policy.ts +138 -0
- package/src/utils/clamp.ts +5 -0
- package/src/web/client.js +105 -2187
- package/src/web/client.ts +4326 -0
- package/src/web/index.html +60 -49
- package/src/web/server.ts +894 -148
- package/src/web/style.css +531 -249
- package/src/web/ui/message-renderer.ts +540 -0
- package/src/web/ui/step-timeline.ts +394 -0
- package/staging/auto-evolve/clean-001/.review-verdict +9 -0
- package/staging/auto-evolve/clean-001/clean-001.patch +14 -0
- package/staging/auto-evolve/e2e-001/.patch-id +1 -0
- package/staging/auto-evolve/e2e-001/.review-verdict +12 -0
- package/staging/auto-evolve/e2e-001/e2e-001.patch +11 -0
- package/staging/auto-evolve/test-bad/.review-verdict +12 -0
- package/staging/auto-evolve/test-bad/test-bad.patch +11 -0
- package/test-results/.last-run.json +6 -0
- 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
|
-
//
|
|
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',
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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(
|
|
181
|
+
return this.callOpenAI(finalMessages, temperature, maxTokens, signal);
|
|
113
182
|
case 'anthropic':
|
|
114
|
-
return this.callAnthropic(
|
|
183
|
+
return this.callAnthropic(finalMessages, temperature, maxTokens, signal);
|
|
115
184
|
case 'ollama':
|
|
116
|
-
return this.callOllama(
|
|
185
|
+
return this.callOllama(finalMessages, temperature, signal);
|
|
117
186
|
case 'openrouter':
|
|
118
|
-
return this.callOpenRouter(
|
|
187
|
+
return this.callOpenRouter(finalMessages, temperature, maxTokens, signal);
|
|
119
188
|
case 'gemini':
|
|
120
|
-
return this.callGemini(
|
|
189
|
+
return this.callGemini(finalMessages, temperature, maxTokens, signal);
|
|
121
190
|
case 'local':
|
|
122
|
-
return this.callLocal(
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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.
|