@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.
- 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 +45 -4
- 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 +435 -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 +3 -0
- package/dist/bootstrap/context-collector.js +17 -1
- package/dist/bootstrap/context-hierarchy.js +218 -0
- package/dist/bootstrap/lifecycle-hooks.js +86 -24
- package/dist/bootstrap/project-context.js +10 -3
- 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 +38 -2
- package/dist/llm/llm-judgment-client.js +32 -30
- package/dist/llm/pi-ai.js +103 -16
- 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 +48 -0
- package/dist/pi-ecosystem-judgment/injection-gate.js +54 -12
- package/dist/pi-ecosystem-judgment/value-injection.js +8 -2
- package/dist/security/context-router-tool.js +1 -1
- package/dist/security/input-scanner.js +223 -0
- package/dist/web/client.js +3677 -4161
- 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 +58 -67
- package/dist/web/server.js +299 -117
- 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 +4 -0
- package/package.json +1 -1
- package/scripts/auto-evolve-loop.ts +105 -0
- package/scripts/build-web.ts +35 -1
- package/scripts/validate-system-prompt.ts +142 -0
- package/src/agents/p2p-chat-tools.ts +6 -6
- package/src/agents/permission-mode.ts +127 -0
- package/src/agents/pi-sdk.ts +461 -28
- 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 +3 -0
- package/src/bootstrap/context-collector.ts +30 -1
- package/src/bootstrap/context-hierarchy.ts +283 -0
- package/src/bootstrap/lifecycle-hooks.ts +139 -26
- package/src/bootstrap/project-context.ts +11 -3
- 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 +36 -2
- package/src/llm/llm-judgment-client.ts +36 -35
- package/src/llm/pi-ai.ts +111 -16
- 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/context-router-tool.ts +1 -1
- package/src/security/input-scanner.ts +287 -0
- package/src/web/client.js +271 -2817
- package/src/web/client.ts +4326 -0
- package/src/web/index.html +58 -67
- package/src/web/server.ts +301 -141
- 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/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
|
+
* Microcompact — 第 3 层: cache-aware 细粒度压缩
|
|
3
|
+
*
|
|
4
|
+
* 论文: 总是跑 (时间触发), 关键作用是保留 prompt cache 命中
|
|
5
|
+
* 实现 (简化版): 把老的 toolResult 折叠为 "(tool result: <name>, N chars)"
|
|
6
|
+
* - 保留最近 N 条 tool_result 完整 (默认 3)
|
|
7
|
+
* - 之前的折叠
|
|
8
|
+
*
|
|
9
|
+
* 关键: 不破坏 messageHistory 内存结构
|
|
10
|
+
*
|
|
11
|
+
* 失败静默: 异常 → 返回原 history
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_KEEP_RECENT = 3;
|
|
14
|
+
export function microcompact(history, opts = {}) {
|
|
15
|
+
try {
|
|
16
|
+
if (opts.skip)
|
|
17
|
+
return { history, applied: false, detail: 'skipped' };
|
|
18
|
+
const keepRecent = opts.microcompactKeepRecent ?? DEFAULT_KEEP_RECENT;
|
|
19
|
+
// 找所有 tool 消息的索引
|
|
20
|
+
const toolIndices = [];
|
|
21
|
+
for (let i = 0; i < history.length; i++) {
|
|
22
|
+
if (history[i].role === 'tool')
|
|
23
|
+
toolIndices.push(i);
|
|
24
|
+
}
|
|
25
|
+
if (toolIndices.length <= keepRecent) {
|
|
26
|
+
return { history, applied: false, detail: 'no old tool_results to fold' };
|
|
27
|
+
}
|
|
28
|
+
// 要折叠的 = 前 (total - keepRecent) 条
|
|
29
|
+
const toFold = new Set(toolIndices.slice(0, toolIndices.length - keepRecent));
|
|
30
|
+
let changed = false;
|
|
31
|
+
const out = history.map((m, i) => {
|
|
32
|
+
if (toFold.has(i) && m.role === 'tool') {
|
|
33
|
+
changed = true;
|
|
34
|
+
// 尝试从相邻的 assistant 消息拿 tool name
|
|
35
|
+
const name = m.toolName || 'unknown';
|
|
36
|
+
const originalLen = m.content?.length || 0;
|
|
37
|
+
return {
|
|
38
|
+
...m,
|
|
39
|
+
content: `(tool result: ${name}, ${originalLen} chars — folded by microcompact)`,
|
|
40
|
+
toolResult: undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return m;
|
|
44
|
+
});
|
|
45
|
+
return { history: out, applied: changed, detail: `folded ${toFold.size} old tool_results, kept last ${keepRecent}` };
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.warn('[compactor] microcompact failed (silent, returning original):', err);
|
|
49
|
+
return { history, applied: false, detail: 'error' };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Compaction Pipeline — 5 层编排
|
|
3
|
+
*
|
|
4
|
+
* 严格对齐 Claude Code 论文: 每次 LLM call 前顺序执行 5 个 shaper, 由便宜到重, 任一层 fit 就短路.
|
|
5
|
+
*
|
|
6
|
+
* 1. Budget Reduction (总是, 截断 > 阈值的单条消息)
|
|
7
|
+
* 2. Snip (feature flag BOLLOON_SNIP_ENABLED)
|
|
8
|
+
* 3. Microcompact (总是, 折叠老 tool_result)
|
|
9
|
+
* 4. Context Collapse (feature flag BOLLOON_CONTEXT_COLLAPSE, 读时虚拟投影)
|
|
10
|
+
* 5. Auto-Compact (LLM 摘要, 兜底)
|
|
11
|
+
*
|
|
12
|
+
* 短路: 任一层后 budget fit (< 0.8) → 返回该层结果, 不跑后续.
|
|
13
|
+
*
|
|
14
|
+
* 关键不变量:
|
|
15
|
+
* - 任何异常 → 整 pipeline 返回原 history (与现状一致)
|
|
16
|
+
* - 第 1-3 层不破坏 messageHistory 内存引用 (内容可改)
|
|
17
|
+
* - 第 4 层读时投影, memory 不变
|
|
18
|
+
* - 第 5 层破坏性, 折叠 N 对成 1 条
|
|
19
|
+
*
|
|
20
|
+
* 失败静默: 整 pipeline 用 try/catch 包, 任何 stage 失败不影响整体
|
|
21
|
+
*/
|
|
22
|
+
import { budgetGate } from './budget-gate.js';
|
|
23
|
+
import { budgetReduce } from './budget-reduce.js';
|
|
24
|
+
import { snip, isSnipEnabled } from './snip.js';
|
|
25
|
+
import { microcompact } from './microcompact.js';
|
|
26
|
+
import { contextCollapse, isContextCollapseEnabled } from './context-collapse.js';
|
|
27
|
+
import { autoCompact } from './auto-compact.js';
|
|
28
|
+
const DEFAULT_MAX_TOKENS = 8000;
|
|
29
|
+
export async function compactPipeline(history, opts = {}) {
|
|
30
|
+
const maxTokens = opts.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
31
|
+
const ctx = {
|
|
32
|
+
inputHistory: history,
|
|
33
|
+
history,
|
|
34
|
+
estimatedTokens: 0,
|
|
35
|
+
opts,
|
|
36
|
+
stages: [],
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
// Stage 1: Budget Reduction
|
|
40
|
+
let result = budgetReduce(history, opts);
|
|
41
|
+
ctx.history = result.history;
|
|
42
|
+
ctx.stages.push(stageRecord('budgetReduce', result, history));
|
|
43
|
+
let gate = budgetGate(ctx.history, maxTokens);
|
|
44
|
+
ctx.estimatedTokens = gate.estimatedTokens;
|
|
45
|
+
if (gate.fit)
|
|
46
|
+
return finalize(ctx, result.applied); // 短路但可能 budgetReduce 实际改了内容
|
|
47
|
+
// Stage 2: Snip (feature flag)
|
|
48
|
+
if (isSnipEnabled()) {
|
|
49
|
+
const before = ctx.history;
|
|
50
|
+
result = snip(ctx.history, opts);
|
|
51
|
+
ctx.history = result.history;
|
|
52
|
+
ctx.stages.push(stageRecord('snip', result, before));
|
|
53
|
+
gate = budgetGate(ctx.history, maxTokens);
|
|
54
|
+
ctx.estimatedTokens = gate.estimatedTokens;
|
|
55
|
+
if (gate.fit)
|
|
56
|
+
return finalize(ctx, true);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
ctx.stages.push({ stage: 'snip', applied: false, before: ctx.history.length, after: ctx.history.length, detail: 'feature flag off' });
|
|
60
|
+
}
|
|
61
|
+
// Stage 3: Microcompact
|
|
62
|
+
const before3 = ctx.history;
|
|
63
|
+
result = microcompact(ctx.history, opts);
|
|
64
|
+
ctx.history = result.history;
|
|
65
|
+
ctx.stages.push(stageRecord('microcompact', result, before3));
|
|
66
|
+
gate = budgetGate(ctx.history, maxTokens);
|
|
67
|
+
ctx.estimatedTokens = gate.estimatedTokens;
|
|
68
|
+
if (gate.fit)
|
|
69
|
+
return finalize(ctx, result.applied || true);
|
|
70
|
+
// Stage 4: Context Collapse (feature flag)
|
|
71
|
+
if (isContextCollapseEnabled()) {
|
|
72
|
+
const before4 = ctx.history;
|
|
73
|
+
result = contextCollapse(ctx.history, opts);
|
|
74
|
+
ctx.history = result.history;
|
|
75
|
+
ctx.stages.push(stageRecord('contextCollapse', result, before4));
|
|
76
|
+
gate = budgetGate(ctx.history, maxTokens);
|
|
77
|
+
ctx.estimatedTokens = gate.estimatedTokens;
|
|
78
|
+
if (gate.fit)
|
|
79
|
+
return finalize(ctx, true);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
ctx.stages.push({ stage: 'contextCollapse', applied: false, before: ctx.history.length, after: ctx.history.length, detail: 'feature flag off' });
|
|
83
|
+
}
|
|
84
|
+
// Stage 5: Auto-Compact (兜底)
|
|
85
|
+
const before5 = ctx.history;
|
|
86
|
+
result = await autoCompact(ctx.history, opts);
|
|
87
|
+
ctx.history = result.history;
|
|
88
|
+
ctx.stages.push(stageRecord('autoCompact', result, before5));
|
|
89
|
+
return finalize(ctx, true);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.warn('[compactor] pipeline failed (silent, returning original):', err);
|
|
93
|
+
return {
|
|
94
|
+
history,
|
|
95
|
+
estimatedTokens: 0,
|
|
96
|
+
stages: ctx.stages,
|
|
97
|
+
compacted: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function stageRecord(name, result, before) {
|
|
102
|
+
return {
|
|
103
|
+
stage: name,
|
|
104
|
+
applied: result.applied,
|
|
105
|
+
before: before.length,
|
|
106
|
+
after: result.history.length,
|
|
107
|
+
detail: result.detail,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function finalize(ctx, compacted) {
|
|
111
|
+
return {
|
|
112
|
+
history: ctx.history,
|
|
113
|
+
estimatedTokens: ctx.estimatedTokens,
|
|
114
|
+
stages: ctx.stages,
|
|
115
|
+
compacted,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// ============================================================
|
|
119
|
+
// 测试钩子
|
|
120
|
+
// ============================================================
|
|
121
|
+
export function _resetPipelineForTest() {
|
|
122
|
+
// pipeline 是 stateless, 但保留 API 一致性
|
|
123
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
const DEFAULT_KEEP_PAIRS = 20;
|
|
13
|
+
export function isSnipEnabled() {
|
|
14
|
+
return process.env.BOLLOON_SNIP_ENABLED === '1';
|
|
15
|
+
}
|
|
16
|
+
export function snip(history, opts = {}) {
|
|
17
|
+
try {
|
|
18
|
+
if (opts.skip || !isSnipEnabled()) {
|
|
19
|
+
return { history, applied: false, detail: isSnipEnabled() ? 'skipped' : 'feature flag off' };
|
|
20
|
+
}
|
|
21
|
+
const keepPairs = opts.snipKeepPairs ?? DEFAULT_KEEP_PAIRS;
|
|
22
|
+
// 从后往前数 N 对 user/assistant 边界
|
|
23
|
+
let pairsFound = 0;
|
|
24
|
+
let cutFrom = -1;
|
|
25
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
26
|
+
const m = history[i];
|
|
27
|
+
if (m.role === 'user') {
|
|
28
|
+
pairsFound++;
|
|
29
|
+
if (pairsFound >= keepPairs) {
|
|
30
|
+
cutFrom = i;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (cutFrom <= 0) {
|
|
36
|
+
return { history, applied: false, detail: 'history too short' };
|
|
37
|
+
}
|
|
38
|
+
const out = history.slice(cutFrom);
|
|
39
|
+
return { history: out, applied: true, detail: `kept last ${keepPairs} pairs, dropped ${cutFrom} older messages` };
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.warn('[compactor] snip failed (silent, returning original):', err);
|
|
43
|
+
return { history, applied: false, detail: 'error' };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
const CHARS_PER_TOKEN = 4;
|
|
12
|
+
export function estimateTokens(messages) {
|
|
13
|
+
let total = 0;
|
|
14
|
+
for (const m of messages) {
|
|
15
|
+
total += Math.ceil((m.content?.length || 0) / CHARS_PER_TOKEN);
|
|
16
|
+
if (m.toolResult) {
|
|
17
|
+
try {
|
|
18
|
+
const s = typeof m.toolResult === 'string' ? m.toolResult : JSON.stringify(m.toolResult);
|
|
19
|
+
total += Math.ceil(s.length / CHARS_PER_TOKEN);
|
|
20
|
+
}
|
|
21
|
+
catch { /* circular ref, ignore */ }
|
|
22
|
+
}
|
|
23
|
+
if (m.toolCall) {
|
|
24
|
+
try {
|
|
25
|
+
const s = typeof m.toolCall === 'string' ? m.toolCall : JSON.stringify(m.toolCall);
|
|
26
|
+
total += Math.ceil(s.length / CHARS_PER_TOKEN);
|
|
27
|
+
}
|
|
28
|
+
catch { /* ignore */ }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return total;
|
|
32
|
+
}
|
|
33
|
+
export function estimateStringTokens(s) {
|
|
34
|
+
return Math.ceil(s.length / CHARS_PER_TOKEN);
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
export {};
|
|
@@ -231,9 +231,10 @@ export class HealthMonitor {
|
|
|
231
231
|
async pingLLM(minimax) {
|
|
232
232
|
const start = Date.now();
|
|
233
233
|
try {
|
|
234
|
-
//
|
|
234
|
+
// 2026-06-15: signal 位置正确传 undefined (chat signature 是 (message, context?, signal?))
|
|
235
|
+
// 之前传 { maxTokens: 1 } 被当 signal, Node 22+ undici 强类型 AbortSignal 校验 throw
|
|
235
236
|
if (minimax.chat) {
|
|
236
|
-
await minimax.chat('ping', 'test',
|
|
237
|
+
await minimax.chat('ping', 'test', undefined);
|
|
237
238
|
}
|
|
238
239
|
else if (minimax.ping) {
|
|
239
240
|
await minimax.ping();
|
package/dist/index.js
CHANGED
|
@@ -978,6 +978,34 @@ async function runNonInteractive(args, comm) {
|
|
|
978
978
|
console.log(`✅ 结果已保存到: ${output}`);
|
|
979
979
|
return;
|
|
980
980
|
}
|
|
981
|
+
if (tool === 'prompt' && prompt) {
|
|
982
|
+
// 2026-06-15: --prompt "text" 走直接调 a.prompt(prompt) 路径, 避开 runToolCommand
|
|
983
|
+
// (case 'prompt' 内的 [text] = args 从 toolArgs 取值是空数组, 永远报"缺少 prompt 文本"是 CLI bug)
|
|
984
|
+
const startTime = Date.now();
|
|
985
|
+
const a = await getAgent();
|
|
986
|
+
try {
|
|
987
|
+
const response = await a.prompt(prompt);
|
|
988
|
+
const elapsed = Date.now() - startTime;
|
|
989
|
+
if (json) {
|
|
990
|
+
console.log(JSON.stringify({ success: true, response, elapsedMs: elapsed }, null, 2));
|
|
991
|
+
}
|
|
992
|
+
else {
|
|
993
|
+
console.log(response);
|
|
994
|
+
console.log(`\n耗时: ${elapsed}ms`);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
catch (e) {
|
|
998
|
+
const error = e?.message || String(e);
|
|
999
|
+
if (json) {
|
|
1000
|
+
console.log(JSON.stringify({ success: false, error }, null, 2));
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
console.log(`\n错误: ${error}`);
|
|
1004
|
+
}
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
}
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
981
1009
|
if (tool) {
|
|
982
1010
|
await runToolCommand(tool, toolArgs, !!json, comm);
|
|
983
1011
|
}
|
|
@@ -1367,7 +1395,9 @@ async function main() {
|
|
|
1367
1395
|
s.banner();
|
|
1368
1396
|
s.section('系统初始化');
|
|
1369
1397
|
const hasOpenAI = !!process.env.OPENAI_API_KEY;
|
|
1370
|
-
|
|
1398
|
+
// 2026-06-15: 修复 — 之前 anthropic 401 是因为 shell env 残留的旧 ANTHROPIC_API_KEY 抢了 provider 选择
|
|
1399
|
+
// 用 BOLLOON_LLM_PROVIDER env 显式覆盖, 否则还是按 env hasXxx 顺序自动选
|
|
1400
|
+
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY && !process.env.BOLLOON_LLM_PROVIDER;
|
|
1371
1401
|
const hasMinimax = !!process.env.MINIMAX_API_KEY;
|
|
1372
1402
|
const hasDeepSeek = !!process.env.DEEPSEEK_API_KEY;
|
|
1373
1403
|
const hasKimi = !!(process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY);
|
|
@@ -1447,9 +1477,15 @@ async function main() {
|
|
|
1447
1477
|
s.divider();
|
|
1448
1478
|
if (mode === 'web') {
|
|
1449
1479
|
const port = parseInt(process.env.PORT || '54188');
|
|
1480
|
+
// 2026-06-16: BOLLOON_DEV_MODE=1 或 selfImprove=true 启动项 → 开发者模式, 启用自迭代 (健康监控+自改总线)
|
|
1481
|
+
// 默认用户模式: 不自迭代, 自改卡片不自动出现, 仍可 POST /api/self-improve/trigger 手动触发
|
|
1482
|
+
const selfImprove = process.env.BOLLOON_DEV_MODE === '1' || process.env.BOLLOON_DEV_MODE === 'true';
|
|
1483
|
+
if (selfImprove) {
|
|
1484
|
+
console.log('[startup] BOLLOON_DEV_MODE=1, 开发者模式: 自迭代已启用');
|
|
1485
|
+
}
|
|
1450
1486
|
const { createWebServer, openBrowser } = await import('./web/server.js');
|
|
1451
1487
|
s.info(`启动 Web 服务端口 ${port}...`);
|
|
1452
|
-
const { port: actualPort } = await createWebServer(port);
|
|
1488
|
+
const { port: actualPort } = await createWebServer(port, { selfImprove });
|
|
1453
1489
|
s.success(`浏览器已打开 → http://localhost:${actualPort}`);
|
|
1454
1490
|
openBrowser(`http://localhost:${actualPort}`);
|
|
1455
1491
|
}
|
|
@@ -6,19 +6,31 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { llmConfigStore } from './config-store.js';
|
|
8
8
|
import { PiAIModel } from './pi-ai.js';
|
|
9
|
+
import * as fs from 'fs/promises';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const LAYERS_DIR = path.join(__dirname, 'system-prompt', 'layers');
|
|
14
|
+
/**
|
|
15
|
+
* 从 layer 读 .md 文件作为系统 prompt
|
|
16
|
+
* 失败时降级到兜底字符串 (不挂)
|
|
17
|
+
*/
|
|
18
|
+
async function loadRoleLayer(layerId, fallback) {
|
|
19
|
+
const [group, ...rest] = layerId.split('.');
|
|
20
|
+
const path2md = path.join(LAYERS_DIR, group, `${rest.join('.')}.md`);
|
|
21
|
+
try {
|
|
22
|
+
return await fs.readFile(path2md, 'utf-8');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
9
28
|
// 预定义的判断 Prompt 配置
|
|
10
29
|
export const JUDGMENT_PROMPTS = {
|
|
11
30
|
// 默认判断 Prompt
|
|
12
31
|
default: {
|
|
13
|
-
systemPrompt:
|
|
14
|
-
|
|
15
|
-
核心原则:
|
|
16
|
-
1. 第一性原理:追问问题的本质,而非表面现象
|
|
17
|
-
2. 多角度思考:考虑不同视角和影响因素
|
|
18
|
-
3. 上下文感知:理解对话历史和隐含意图
|
|
19
|
-
4. 动态评估:根据实际情况判断复杂性
|
|
20
|
-
|
|
21
|
-
你的判断会影响后续的智能体协作方式,请认真分析。`,
|
|
32
|
+
systemPrompt: '',
|
|
33
|
+
systemPromptResolver: () => loadRoleLayer('role.expert', '你是一个专业的 AI 任务分析专家. '),
|
|
22
34
|
userPromptTemplate: `【当前输入】
|
|
23
35
|
{user_input}
|
|
24
36
|
|
|
@@ -73,16 +85,8 @@ export const JUDGMENT_PROMPTS = {
|
|
|
73
85
|
},
|
|
74
86
|
// 架构相关问题
|
|
75
87
|
architecture: {
|
|
76
|
-
systemPrompt:
|
|
77
|
-
|
|
78
|
-
- 设计可扩展的系统架构
|
|
79
|
-
- 权衡技术方案
|
|
80
|
-
- 识别架构风险
|
|
81
|
-
|
|
82
|
-
你相信:
|
|
83
|
-
- 本质和实现必须分离
|
|
84
|
-
- 好的架构从简单规则中生长
|
|
85
|
-
- 复杂性应该被控制而非消除`,
|
|
88
|
+
systemPrompt: '',
|
|
89
|
+
systemPromptResolver: () => loadRoleLayer('role.architect', '你是一个经验丰富的系统架构师. '),
|
|
86
90
|
userPromptTemplate: `【架构设计问题】
|
|
87
91
|
{user_input}
|
|
88
92
|
|
|
@@ -108,11 +112,8 @@ export const JUDGMENT_PROMPTS = {
|
|
|
108
112
|
},
|
|
109
113
|
// 代码相关问题
|
|
110
114
|
code: {
|
|
111
|
-
systemPrompt:
|
|
112
|
-
|
|
113
|
-
- 编写清晰、可维护的代码
|
|
114
|
-
- 遵循最佳实践
|
|
115
|
-
- 处理边界情况`,
|
|
115
|
+
systemPrompt: '',
|
|
116
|
+
systemPromptResolver: () => loadRoleLayer('role.implementer', '你是一个高效的代码实现专家. '),
|
|
116
117
|
userPromptTemplate: `【代码问题】
|
|
117
118
|
{user_input}
|
|
118
119
|
|
|
@@ -134,11 +135,8 @@ export const JUDGMENT_PROMPTS = {
|
|
|
134
135
|
},
|
|
135
136
|
// 安全相关问题
|
|
136
137
|
security: {
|
|
137
|
-
systemPrompt:
|
|
138
|
-
|
|
139
|
-
- 评估威胁模型
|
|
140
|
-
- 设计安全方案
|
|
141
|
-
- 遵循安全最佳实践`,
|
|
138
|
+
systemPrompt: '',
|
|
139
|
+
systemPromptResolver: () => loadRoleLayer('role.security', '你是一个严格的安全专家. '),
|
|
142
140
|
userPromptTemplate: `【安全问题】
|
|
143
141
|
{user_input}
|
|
144
142
|
|
|
@@ -221,8 +219,12 @@ export class LLMJudgmentClient {
|
|
|
221
219
|
}
|
|
222
220
|
try {
|
|
223
221
|
// 调用 LLM
|
|
222
|
+
// 优先用 systemPromptResolver (从 layer 读), fallback 静态 systemPrompt
|
|
223
|
+
const systemPrompt = this.promptConfig.systemPromptResolver
|
|
224
|
+
? await this.promptConfig.systemPromptResolver()
|
|
225
|
+
: this.promptConfig.systemPrompt;
|
|
224
226
|
const messages = [
|
|
225
|
-
{ role: 'system', content:
|
|
227
|
+
{ role: 'system', content: systemPrompt },
|
|
226
228
|
{ role: 'user', content: userPrompt }
|
|
227
229
|
];
|
|
228
230
|
const response = await this.model.chat(userPrompt);
|
package/dist/llm/pi-ai.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
|
+
/**
|
|
4
|
+
* 外部 system 注入钩子.
|
|
5
|
+
* 调用方 (e.g. auto-evolve-loop) 用 setSystemPrependProvider() 注册一个返回字符串的函数,
|
|
6
|
+
* generateText 在拼 finalMessages 时, 把返回的字符串作为最前一个 system message.
|
|
7
|
+
*
|
|
8
|
+
* 用途: P2P 协作时把"行级 reserve 状态"实时塞给 LLM,
|
|
9
|
+
* 让 LLM 主动避开对方正在改的代码行.
|
|
10
|
+
*
|
|
11
|
+
* 返回 '' / null / undefined → 不注入.
|
|
12
|
+
*/
|
|
13
|
+
let _prependProvider = null;
|
|
14
|
+
export function setSystemPrependProvider(p) {
|
|
15
|
+
_prependProvider = p;
|
|
16
|
+
}
|
|
17
|
+
export function getSystemPrependProvider() {
|
|
18
|
+
return _prependProvider;
|
|
19
|
+
}
|
|
3
20
|
export class PiAIModel {
|
|
4
21
|
config;
|
|
5
22
|
provider;
|
|
@@ -8,7 +25,7 @@ export class PiAIModel {
|
|
|
8
25
|
this.provider = config.provider;
|
|
9
26
|
}
|
|
10
27
|
async chat(message, context, signal) {
|
|
11
|
-
const systemPrompt = this.
|
|
28
|
+
const systemPrompt = await this.buildSystemPromptAsync(context);
|
|
12
29
|
const messages = [
|
|
13
30
|
{ role: 'system', content: systemPrompt },
|
|
14
31
|
{ role: 'user', content: message }
|
|
@@ -17,6 +34,7 @@ export class PiAIModel {
|
|
|
17
34
|
const response = await this.generateText({
|
|
18
35
|
messages,
|
|
19
36
|
temperature: 0.8,
|
|
37
|
+
maxTokens: 8192, // 2026-06-15: 提到 8192, 避免长回答 (含表格/代码块) 被截断
|
|
20
38
|
signal,
|
|
21
39
|
});
|
|
22
40
|
return { reply: response };
|
|
@@ -27,7 +45,14 @@ export class PiAIModel {
|
|
|
27
45
|
throw error; // 上层 try/catch 处理
|
|
28
46
|
}
|
|
29
47
|
console.error('PiAI chat error:', error);
|
|
30
|
-
|
|
48
|
+
// 2026-06-15: 真实 error 信息 + 明确告诉 LLM "这是 API 错, 不要 retry"
|
|
49
|
+
// 旧版: "抱歉,AI服务暂时不可用。" → LLM 看到 isTooShort=false(< 50 但 > 0),
|
|
50
|
+
// needsMoreWork 不会触发, 但 hasError 模式 (含 "error" / "失败") 会判定要继续修
|
|
51
|
+
// 新版: 让 LLM 立即停止循环, 直接展示给用户
|
|
52
|
+
const errMsg = (error?.message || '').slice(0, 300);
|
|
53
|
+
return {
|
|
54
|
+
reply: `[AI 服务调用失败] ${errMsg}\n\n这是一个**底层 API 错误**(401 / 鉴权失败 / 网络中断 / 配额耗尽等),不是你的任务有问题。**请直接把这个错误消息回复给用户,不要再循环尝试。**`,
|
|
55
|
+
};
|
|
31
56
|
}
|
|
32
57
|
}
|
|
33
58
|
async summarize(text, context) {
|
|
@@ -69,7 +94,39 @@ export class PiAIModel {
|
|
|
69
94
|
}
|
|
70
95
|
}
|
|
71
96
|
async generateText(options) {
|
|
72
|
-
const { messages, temperature = 0.7, maxTokens = 4096, signal } = options;
|
|
97
|
+
const { messages, temperature = 0.7, maxTokens = 4096, signal, tools } = options;
|
|
98
|
+
// 工具清单: 代码侧 schema → 进 system prompt (作为额外 system message)
|
|
99
|
+
let finalMessages = messages;
|
|
100
|
+
// 1) 外部 prepend (e.g. P2P 行级 reserve 状态), 拼到最前
|
|
101
|
+
if (_prependProvider) {
|
|
102
|
+
try {
|
|
103
|
+
const pre = await _prependProvider();
|
|
104
|
+
if (pre && typeof pre === 'string' && pre.trim()) {
|
|
105
|
+
finalMessages = [{ role: 'system', content: pre }, ...finalMessages];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.warn('[pi-ai] systemPrepend 失败:', err?.message?.slice(0, 100));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (tools && tools.length > 0) {
|
|
113
|
+
try {
|
|
114
|
+
const { getToolManifest, formatForPrompt } = await import('./tool-manifest/index.js');
|
|
115
|
+
const manifests = tools
|
|
116
|
+
.map((id) => getToolManifest(id))
|
|
117
|
+
.filter((m) => m !== undefined);
|
|
118
|
+
if (manifests.length > 0) {
|
|
119
|
+
const toolPrompt = formatForPrompt(manifests);
|
|
120
|
+
finalMessages = [
|
|
121
|
+
{ role: 'system', content: toolPrompt },
|
|
122
|
+
...messages,
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.warn('[pi-ai] tool-manifest 加载失败:', err.message?.slice(0, 100));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
73
130
|
switch (this.provider) {
|
|
74
131
|
case 'openai':
|
|
75
132
|
case 'minimax':
|
|
@@ -77,17 +134,17 @@ export class PiAIModel {
|
|
|
77
134
|
case 'kimi':
|
|
78
135
|
case 'glm':
|
|
79
136
|
case 'qwen':
|
|
80
|
-
return this.callOpenAI(
|
|
137
|
+
return this.callOpenAI(finalMessages, temperature, maxTokens, signal);
|
|
81
138
|
case 'anthropic':
|
|
82
|
-
return this.callAnthropic(
|
|
139
|
+
return this.callAnthropic(finalMessages, temperature, maxTokens, signal);
|
|
83
140
|
case 'ollama':
|
|
84
|
-
return this.callOllama(
|
|
141
|
+
return this.callOllama(finalMessages, temperature, signal);
|
|
85
142
|
case 'openrouter':
|
|
86
|
-
return this.callOpenRouter(
|
|
143
|
+
return this.callOpenRouter(finalMessages, temperature, maxTokens, signal);
|
|
87
144
|
case 'gemini':
|
|
88
|
-
return this.callGemini(
|
|
145
|
+
return this.callGemini(finalMessages, temperature, maxTokens, signal);
|
|
89
146
|
case 'local':
|
|
90
|
-
return this.callLocal(
|
|
147
|
+
return this.callLocal(finalMessages, temperature, signal);
|
|
91
148
|
default:
|
|
92
149
|
throw new Error(`Unsupported provider: ${this.provider}`);
|
|
93
150
|
}
|
|
@@ -152,19 +209,23 @@ export class PiAIModel {
|
|
|
152
209
|
if (!apiKey) {
|
|
153
210
|
throw new Error('OPENAI_API_KEY not set');
|
|
154
211
|
}
|
|
212
|
+
const requestBody = {
|
|
213
|
+
model: this.mapModel(),
|
|
214
|
+
messages,
|
|
215
|
+
temperature,
|
|
216
|
+
max_tokens: maxTokens
|
|
217
|
+
};
|
|
155
218
|
const response = await fetch(`${this.getBaseUrl()}/chat/completions`, {
|
|
156
219
|
method: 'POST',
|
|
157
220
|
headers: {
|
|
158
221
|
'Content-Type': 'application/json',
|
|
159
222
|
'Authorization': `Bearer ${apiKey}`
|
|
160
223
|
},
|
|
161
|
-
body: JSON.stringify(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}),
|
|
167
|
-
signal,
|
|
224
|
+
body: JSON.stringify(requestBody),
|
|
225
|
+
// 2026-06-15: signal 字段防御 — Node 22+ undici 强类型 AbortSignal, 非 AbortSignal 实例
|
|
226
|
+
// (e.g. 误传的 { maxTokens: 1 } 对象) 会 throw "Expected signal to be AbortSignal".
|
|
227
|
+
// 若不是 AbortSignal, 退到无 signal 调用, fetch 自然支持 timeout 由外层控制.
|
|
228
|
+
signal: signal instanceof AbortSignal ? signal : undefined,
|
|
168
229
|
});
|
|
169
230
|
if (!response.ok) {
|
|
170
231
|
throw new Error(`OpenAI API error: ${response.status}`);
|
|
@@ -285,6 +346,32 @@ export class PiAIModel {
|
|
|
285
346
|
async callLocal(messages, temperature, signal) {
|
|
286
347
|
return this.callOllama(messages, temperature, signal);
|
|
287
348
|
}
|
|
349
|
+
// 注: callLocal 直接代理 ollama. 工具清单由 generateText 在外层拼到 messages, 这里 messages 已是 finalMessages.
|
|
350
|
+
async buildSystemPromptAsync(context) {
|
|
351
|
+
// 走 layer registry: 装配所有相关 layer (身份/行为/工具/角色/渠道)
|
|
352
|
+
try {
|
|
353
|
+
const { assembleSystemPrompt, SYSTEM_PROMPT_VERSION } = await import('./system-prompt/registry.js').catch(() => import('./system-prompt/registry.js'));
|
|
354
|
+
// channel = 本机 (PI SDK 直接调就是 local)
|
|
355
|
+
// role = 由 prompt context 决定 (默认 expert)
|
|
356
|
+
const ctx = context ? { channel: 'local', role: 'expert' } : { channel: 'local', role: 'expert' };
|
|
357
|
+
const result = await assembleSystemPrompt(ctx);
|
|
358
|
+
return `${result.text}\n\n## User Working Directory\n${context || process.cwd()}\n\n## bolloon-runtime\n${SYSTEM_PROMPT_VERSION} · layers: ${result.layerIds.join(',')}`;
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
// 降级: 旧硬编码 (layer registry 不可用时不挂)
|
|
362
|
+
console.warn('[pi-ai] layer registry 不可用, 降级:', err.message?.slice(0, 100));
|
|
363
|
+
const envDetails = this.getEnvironmentDetails();
|
|
364
|
+
return `You are a friendly AI assistant in a P2P document collaboration network.
|
|
365
|
+
|
|
366
|
+
## User Working Directory
|
|
367
|
+
${context || process.cwd()}
|
|
368
|
+
|
|
369
|
+
## Environment
|
|
370
|
+
${envDetails}`;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// 同步版: 旧调用点 (buildSystemPrompt 是同步)
|
|
374
|
+
// 保留但内部用 sync fallback; 后续可改成 async
|
|
288
375
|
buildSystemPrompt(context) {
|
|
289
376
|
const envDetails = this.getEnvironmentDetails();
|
|
290
377
|
return `You are a friendly AI assistant in a P2P document collaboration network.
|