@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,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Harness — ReAct 循环的 hook 集中调度
|
|
3
|
+
*
|
|
4
|
+
* 包装:
|
|
5
|
+
* - bollharness-integration (BollharnessHooks + GateStateMachine)
|
|
6
|
+
* - tool-gate (8 道安全 gate)
|
|
7
|
+
* - builtin-guards (4 个内置 guard, 跟 gate 互补)
|
|
8
|
+
*
|
|
9
|
+
* 接入点 (pi-sdk.ts runReActLoop):
|
|
10
|
+
* - preToolCall: 调 tool.execute 前 (8-gate + guards)
|
|
11
|
+
* - postToolCall: tool 返 output 后 (output gate + secret leak)
|
|
12
|
+
* - sessionStart: ReAct 循环入口 (harness sessionStart)
|
|
13
|
+
* - sessionEnd: 循环结束 (harness sessionEnd + archive)
|
|
14
|
+
*
|
|
15
|
+
* 设计原则:
|
|
16
|
+
* - fail-open: 任何 hook 自身挂掉 = pass, 不阻塞主对话
|
|
17
|
+
* - 8-gate + 4-guard 全部 disabled 时 = 旧行为 (跟加 harness 之前一样)
|
|
18
|
+
* - all 结果记录到 harness session archive (供 UI 审计)
|
|
19
|
+
*/
|
|
20
|
+
import { createBollharnessIntegration, } from '../bollharness-integration/integration.js';
|
|
21
|
+
import { runToolGates, runOutputGate, } from './tool-gate.js';
|
|
22
|
+
import { routeContext } from './context-router-tool.js';
|
|
23
|
+
export class ReactHarness {
|
|
24
|
+
integration = null;
|
|
25
|
+
opts;
|
|
26
|
+
recentCalls = [];
|
|
27
|
+
toolCallCountInTurn = 0;
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.opts = {
|
|
30
|
+
harnessEnabled: options.harnessEnabled ?? true,
|
|
31
|
+
gateEnabled: options.gateEnabled ?? true,
|
|
32
|
+
maxToolCallsPerTurn: options.maxToolCallsPerTurn ?? 5,
|
|
33
|
+
};
|
|
34
|
+
if (this.opts.harnessEnabled) {
|
|
35
|
+
try {
|
|
36
|
+
this.integration = createBollharnessIntegration({
|
|
37
|
+
enabled: true,
|
|
38
|
+
guardsEnabled: true,
|
|
39
|
+
contextEnabled: true,
|
|
40
|
+
skillsEnabled: true,
|
|
41
|
+
gatesEnabled: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.warn('[react-harness] bollharness init failed (non-fatal):', err);
|
|
46
|
+
this.integration = null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** 每次 ReAct 循环开始调一次 (重置 turn 计数 + 触发 harness sessionStart) */
|
|
51
|
+
async onSessionStart(channelId) {
|
|
52
|
+
this.toolCallCountInTurn = 0;
|
|
53
|
+
this.recentCalls = [];
|
|
54
|
+
if (!this.integration)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
// BollharnessIntegration 自带 session 状态, 简单 log
|
|
58
|
+
console.log(`[react-harness] session start, channel=${channelId ?? 'n/a'}, currentGate=${this.integration.getCurrentGate()}`);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.warn('[react-harness] sessionStart failed (non-fatal):', err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** 调 tool 前的 hook (8-gate + builtin-guards + context-router advisory) */
|
|
65
|
+
async preToolCall(tool, args, channelId) {
|
|
66
|
+
if (!this.opts.gateEnabled) {
|
|
67
|
+
return { allowed: true, details: { allowed: true, details: [] } };
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const result = runToolGates({
|
|
71
|
+
tool,
|
|
72
|
+
args,
|
|
73
|
+
channelId,
|
|
74
|
+
toolCallCountInTurn: this.toolCallCountInTurn,
|
|
75
|
+
recentCalls: this.recentCalls,
|
|
76
|
+
});
|
|
77
|
+
if (result.allowed) {
|
|
78
|
+
this.toolCallCountInTurn++;
|
|
79
|
+
this.recentCalls.push({ tool, ts: Date.now() });
|
|
80
|
+
// Context router: advisory 路由 (不阻断, 仅返回 hint 供 pi-sdk 拼到 LLM 上下文)
|
|
81
|
+
// 失败静默, router 挂掉 = 不给 hint
|
|
82
|
+
try {
|
|
83
|
+
const route = routeContext({ channelId, predictedTool: tool });
|
|
84
|
+
this.lastRouteHint = route;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
console.warn('[react-harness] routeContext failed (non-fatal):', err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { allowed: result.allowed, reason: result.reason, details: result };
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.warn('[react-harness] preToolCall failed (fail-open):', err);
|
|
94
|
+
return { allowed: true, details: { allowed: true, details: [] } };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** 取最近一次 router 算出的 hint (供 pi-sdk 拼到 messageHistory 工具结果位) */
|
|
98
|
+
getLastRouteHint() {
|
|
99
|
+
return this.lastRouteHint ?? null;
|
|
100
|
+
}
|
|
101
|
+
/** 清空最近 hint (每轮 ReAct 循环结束重置) */
|
|
102
|
+
clearRouteHint() {
|
|
103
|
+
this.lastRouteHint = null;
|
|
104
|
+
}
|
|
105
|
+
/** 调 tool 后的 hook (审查 output) */
|
|
106
|
+
async postToolCall(tool, output, channelId) {
|
|
107
|
+
if (!this.opts.gateEnabled) {
|
|
108
|
+
return { allowed: true, details: { allowed: true, details: [] } };
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const result = runOutputGate(output);
|
|
112
|
+
if (!result.allowed) {
|
|
113
|
+
return { allowed: false, reason: result.reason, details: result };
|
|
114
|
+
}
|
|
115
|
+
return { allowed: true, details: result };
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.warn('[react-harness] postToolCall failed (fail-open):', err);
|
|
119
|
+
return { allowed: true, details: { allowed: true, details: [] } };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** ReAct 循环结束 */
|
|
123
|
+
async onSessionEnd() {
|
|
124
|
+
if (!this.integration)
|
|
125
|
+
return;
|
|
126
|
+
try {
|
|
127
|
+
// 归档: 当前无 operationLog (那是另接的), 仅 log 状态
|
|
128
|
+
const gate = this.integration.getCurrentGate();
|
|
129
|
+
console.log(`[react-harness] session end, finalGate=${gate}, toolCallsThisTurn=${this.toolCallCountInTurn}`);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
console.warn('[react-harness] sessionEnd failed (non-fatal):', err);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** 暴露给 UI 调试 (harness 状态) */
|
|
136
|
+
getHarnessSnapshot() {
|
|
137
|
+
return {
|
|
138
|
+
integration: this.integration !== null,
|
|
139
|
+
gateEnabled: this.opts.gateEnabled,
|
|
140
|
+
currentGate: this.integration ? this.integration.getCurrentGate() : 0,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Gate — 8 道安全 gate (调工具前/后的快速判断)
|
|
3
|
+
*
|
|
4
|
+
* 跟 harness-integration/gate-state-machine 区别:
|
|
5
|
+
* - 后者: 工程化工作流 8-gate (HARNESS-DEV 流程)
|
|
6
|
+
* - 本文件: 工具调用安全 8-gate (防越权 / 防注入 / 防越界)
|
|
7
|
+
*
|
|
8
|
+
* 每道 gate 独立可禁用, 调用方按顺序串联
|
|
9
|
+
* 任何 gate failed = 拒绝 tool 调用 + 返回 reason
|
|
10
|
+
*/
|
|
11
|
+
import { auditToolOutput } from './builtin-guards.js';
|
|
12
|
+
// ============================================================
|
|
13
|
+
// Gate 1: 白名单
|
|
14
|
+
// ============================================================
|
|
15
|
+
const TOOL_WHITELIST = new Set([
|
|
16
|
+
// 已有 tool (见 pi-sdk.ts registerTools)
|
|
17
|
+
'read_document', 'summarize_document', 'improve_document',
|
|
18
|
+
'list_peers', 'send_message', 'broadcast_message',
|
|
19
|
+
'get_identity', 'list_skills', 'create_judgment',
|
|
20
|
+
'shell_exec', 'shell',
|
|
21
|
+
'read', 'write', 'edit_file', 'list_files',
|
|
22
|
+
'list_sessions', 'get_session_state', 'list_messages',
|
|
23
|
+
'send_to_channel', 'create_channel',
|
|
24
|
+
// MCP 注册的工具
|
|
25
|
+
'mcp_tool',
|
|
26
|
+
]);
|
|
27
|
+
export const gateWhitelist = { gate: 'whitelist', allowed: true };
|
|
28
|
+
export function checkWhitelist(ctx) {
|
|
29
|
+
if (TOOL_WHITELIST.has(ctx.tool)) {
|
|
30
|
+
return gateWhitelist;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
gate: 'whitelist',
|
|
34
|
+
allowed: false,
|
|
35
|
+
reason: `工具 '${ctx.tool}' 不在白名单`,
|
|
36
|
+
evidence: `白名单: ${Array.from(TOOL_WHITELIST).slice(0, 5).join(', ')}...`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// ============================================================
|
|
40
|
+
// Gate 2: args 形状校验 (防 schema 注入)
|
|
41
|
+
// ============================================================
|
|
42
|
+
const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
43
|
+
export function checkSchema(ctx) {
|
|
44
|
+
// 1) 直接遍历 args (JSON.stringify 会丢 __proto__, 必须手动检查)
|
|
45
|
+
function walkKeys(obj, path = []) {
|
|
46
|
+
if (obj === null || typeof obj !== 'object')
|
|
47
|
+
return null;
|
|
48
|
+
for (const k of Object.keys(obj)) {
|
|
49
|
+
if (DANGEROUS_KEYS.includes(k)) {
|
|
50
|
+
return [...path, k].join('.');
|
|
51
|
+
}
|
|
52
|
+
const found = walkKeys(obj[k], [...path, k]);
|
|
53
|
+
if (found)
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const dangerousKey = walkKeys(ctx.args);
|
|
59
|
+
if (dangerousKey) {
|
|
60
|
+
return {
|
|
61
|
+
gate: 'schema',
|
|
62
|
+
allowed: false,
|
|
63
|
+
reason: `args 含 prototype pollution 风险 key: ${dangerousKey}`,
|
|
64
|
+
evidence: dangerousKey,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// 2) args 字符串过深 (>10000 字符) — 可能是 prompt injection 试图污染
|
|
68
|
+
const json = JSON.stringify(ctx.args);
|
|
69
|
+
if (json.length > 10000) {
|
|
70
|
+
return {
|
|
71
|
+
gate: 'schema',
|
|
72
|
+
allowed: false,
|
|
73
|
+
reason: `args 过长 (${json.length} 字符), 可能含 prompt injection`,
|
|
74
|
+
evidence: `前 100 字符: ${json.substring(0, 100)}`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return { gate: 'schema', allowed: true };
|
|
78
|
+
}
|
|
79
|
+
// ============================================================
|
|
80
|
+
// Gate 3: channel 权限 (channelId 是否被允许调该 tool)
|
|
81
|
+
// ============================================================
|
|
82
|
+
/** 工具 → 哪些 channelId 不允许 (黑名单模式, 留作扩展) */
|
|
83
|
+
const TOOL_CHANNEL_RESTRICT = {
|
|
84
|
+
// 'shell_exec': [/^ch_system_/], // 示例: 某些 system channel 禁 shell
|
|
85
|
+
};
|
|
86
|
+
export function checkChannel(ctx) {
|
|
87
|
+
if (!ctx.channelId) {
|
|
88
|
+
return { gate: 'channel', allowed: true };
|
|
89
|
+
}
|
|
90
|
+
const restrictions = TOOL_CHANNEL_RESTRICT[ctx.tool];
|
|
91
|
+
if (!restrictions) {
|
|
92
|
+
return { gate: 'channel', allowed: true };
|
|
93
|
+
}
|
|
94
|
+
for (const r of restrictions) {
|
|
95
|
+
if (typeof r === 'string' && r === ctx.channelId) {
|
|
96
|
+
return { gate: 'channel', allowed: false, reason: `channel '${ctx.channelId}' 不允许调 '${ctx.tool}'` };
|
|
97
|
+
}
|
|
98
|
+
if (r instanceof RegExp && r.test(ctx.channelId)) {
|
|
99
|
+
return { gate: 'channel', allowed: false, reason: `channel '${ctx.channelId}' 匹配禁模式 ${r}` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { gate: 'channel', allowed: true };
|
|
103
|
+
}
|
|
104
|
+
// ============================================================
|
|
105
|
+
// Gate 4: 速率限制 (同 tool 1min 内最多 N 次)
|
|
106
|
+
// ============================================================
|
|
107
|
+
const RATE_LIMIT_PER_MIN = 5;
|
|
108
|
+
export function checkRate(ctx) {
|
|
109
|
+
const recent = ctx.recentCalls ?? [];
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const window = 60_000;
|
|
112
|
+
const sameTool = recent.filter((c) => c.tool === ctx.tool && now - c.ts < window);
|
|
113
|
+
if (sameTool.length >= RATE_LIMIT_PER_MIN) {
|
|
114
|
+
return {
|
|
115
|
+
gate: 'rate',
|
|
116
|
+
allowed: false,
|
|
117
|
+
reason: `工具 '${ctx.tool}' 在 1 分钟内已被调 ${sameTool.length} 次 (上限 ${RATE_LIMIT_PER_MIN})`,
|
|
118
|
+
evidence: `最近 ${sameTool.length} 次调用时间: ${sameTool.map((c) => new Date(c.ts).toISOString()).join(', ')}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { gate: 'rate', allowed: true };
|
|
122
|
+
}
|
|
123
|
+
// ============================================================
|
|
124
|
+
// Gate 5: prompt injection 检测
|
|
125
|
+
// ============================================================
|
|
126
|
+
const INJECTION_PATTERNS = [
|
|
127
|
+
{ re: /\bignore (previous|all|above) (instructions?|prompts?)\b/i, label: 'ignore previous' },
|
|
128
|
+
{ re: /\b(disregard|forget) (everything|all|instructions?)\b/i, label: 'disregard' },
|
|
129
|
+
{ re: /\byou are now\b/i, label: 'role override' },
|
|
130
|
+
{ re: /\bnew instructions?:\s*\[/i, label: 'new instructions block' },
|
|
131
|
+
{ re: /<\|im_start\|>/, label: 'chatml tag' },
|
|
132
|
+
{ re: /<\|im_end\|>/, label: 'chatml tag' },
|
|
133
|
+
{ re: /\bSYSTEM:\s/i, label: 'system tag' },
|
|
134
|
+
];
|
|
135
|
+
export function checkInject(ctx) {
|
|
136
|
+
const concat = JSON.stringify(ctx.args);
|
|
137
|
+
for (const { re, label } of INJECTION_PATTERNS) {
|
|
138
|
+
if (re.test(concat)) {
|
|
139
|
+
return {
|
|
140
|
+
gate: 'inject',
|
|
141
|
+
allowed: false,
|
|
142
|
+
reason: `args 含 prompt injection 模式: ${label}`,
|
|
143
|
+
evidence: `匹配: ${label}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { gate: 'inject', allowed: true };
|
|
148
|
+
}
|
|
149
|
+
// ============================================================
|
|
150
|
+
// Gate 6: 输出审查 (tool 执行后, 审计 tool 返的字符串)
|
|
151
|
+
// 单独入口 auditOutput, 不是调 tool 前的 gate
|
|
152
|
+
// ============================================================
|
|
153
|
+
export function checkOutput(output) {
|
|
154
|
+
const hit = auditToolOutput(output);
|
|
155
|
+
if (!hit) {
|
|
156
|
+
return { gate: 'output', allowed: true };
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
gate: 'output',
|
|
160
|
+
allowed: hit.severity !== 'critical',
|
|
161
|
+
reason: hit.reason,
|
|
162
|
+
evidence: hit.evidence,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// ============================================================
|
|
166
|
+
// Gate 7: 链式调用限制 (单轮最多 5 个 tool, 防 agent 循环)
|
|
167
|
+
// ============================================================
|
|
168
|
+
const MAX_TOOL_CALLS_PER_TURN = 5;
|
|
169
|
+
export function checkChain(ctx) {
|
|
170
|
+
const count = ctx.toolCallCountInTurn ?? 0;
|
|
171
|
+
if (count >= MAX_TOOL_CALLS_PER_TURN) {
|
|
172
|
+
return {
|
|
173
|
+
gate: 'chain',
|
|
174
|
+
allowed: false,
|
|
175
|
+
reason: `单轮已调 ${count} 个 tool (上限 ${MAX_TOOL_CALLS_PER_TURN})`,
|
|
176
|
+
evidence: `当前轮 tool 调用次数: ${count}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return { gate: 'chain', allowed: true };
|
|
180
|
+
}
|
|
181
|
+
// ============================================================
|
|
182
|
+
// Gate 8: 黑名单 (复用 PreToolUse hook 已有 6 条规则, 重新实现于此)
|
|
183
|
+
// ============================================================
|
|
184
|
+
const DANGEROUS_CMD_PATTERNS = [
|
|
185
|
+
{ re: /\brm\s+(-[a-z]*f[a-z]*\s+)?-[a-z]*r[a-z]*\s+\//, reason: '禁止递归删除根目录' },
|
|
186
|
+
{ re: /\bgit\s+push\s+.*--force\b/, reason: '禁止 force push' },
|
|
187
|
+
{ re: /\brm\s+-rf\s+~\//, reason: '禁止递归删除 home' },
|
|
188
|
+
{ re: /\bdd\s+if=.*\s+of=\/dev\//, reason: '禁止 dd 覆盖块设备' },
|
|
189
|
+
{ re: /\bcurl\s+.*\|\s*(ba)?sh\b/, reason: '禁止 curl|sh 直执行' },
|
|
190
|
+
{ re: />\s*\/dev\/sd[a-z]/, reason: '禁止写裸设备' },
|
|
191
|
+
];
|
|
192
|
+
export function checkBlacklist(ctx) {
|
|
193
|
+
if (ctx.tool !== 'shell' && ctx.tool !== 'shell_exec' && ctx.tool !== 'bash') {
|
|
194
|
+
return { gate: 'blacklist', allowed: true };
|
|
195
|
+
}
|
|
196
|
+
const cmd = String(ctx.args.command || ctx.args.cmd || '');
|
|
197
|
+
for (const { re, reason } of DANGEROUS_CMD_PATTERNS) {
|
|
198
|
+
if (re.test(cmd)) {
|
|
199
|
+
return { gate: 'blacklist', allowed: false, reason, evidence: cmd.substring(0, 100) };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { gate: 'blacklist', allowed: true };
|
|
203
|
+
}
|
|
204
|
+
const TOOL_GATES = [
|
|
205
|
+
checkWhitelist,
|
|
206
|
+
checkSchema,
|
|
207
|
+
checkChannel,
|
|
208
|
+
checkRate,
|
|
209
|
+
checkInject,
|
|
210
|
+
checkChain, // 链式限制前置 (chain)
|
|
211
|
+
checkBlacklist,
|
|
212
|
+
// checkOutput 不在 tool.execute 前
|
|
213
|
+
];
|
|
214
|
+
export function runToolGates(ctx) {
|
|
215
|
+
const details = [];
|
|
216
|
+
for (const check of TOOL_GATES) {
|
|
217
|
+
try {
|
|
218
|
+
const r = check(ctx);
|
|
219
|
+
details.push(r);
|
|
220
|
+
if (!r.allowed) {
|
|
221
|
+
return { allowed: false, rejectedBy: r.gate, reason: r.reason, details };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
// 静默: 任意 gate 自身挂掉 = pass (fail-open)
|
|
226
|
+
console.warn(`[tool-gate] ${check.name} failed (non-fatal, allowing):`, err);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { allowed: true, details };
|
|
230
|
+
}
|
|
231
|
+
/** Post-call: 审查 tool 返的 output */
|
|
232
|
+
export function runOutputGate(output) {
|
|
233
|
+
const r = checkOutput(output);
|
|
234
|
+
return { allowed: r.allowed, rejectedBy: r.gate, reason: r.reason, details: [r] };
|
|
235
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Evolve Policy — 数据层自动进化统一网关
|
|
3
|
+
*
|
|
4
|
+
* 阶段 A: 数据层自动进化开关
|
|
5
|
+
*
|
|
6
|
+
* 单一事实源: 任何代码路径要"自动"修改 ~/.bolloon/ 下的数据层
|
|
7
|
+
* (judgments.json / persona.json / skills/ / agents.json),
|
|
8
|
+
* 都必须先调 isDataLayerAutoEvolveEnabled() / requireDataLayerAutoEvolve().
|
|
9
|
+
*
|
|
10
|
+
* 设计原则 (类 B 边界 + 8-gate 兼容):
|
|
11
|
+
* - 默认关闭 (fail-closed)
|
|
12
|
+
* - 3 种打开方式,任一为真即开:
|
|
13
|
+
* 1. env BOLLOON_AUTO_EVOLVE_DATA=1
|
|
14
|
+
* 2. ~/.bolloon/self-improve-policy.json dataLayerAutoEvolve: true
|
|
15
|
+
* 3. 类 B 自适应永远只读 (不查此开关) — 它天然不写库
|
|
16
|
+
* - 显式拒绝比默认拒绝多一道 log (审计 trail)
|
|
17
|
+
*
|
|
18
|
+
* 不变量:
|
|
19
|
+
* - 开关仅影响"无用户交互的自动写"路径
|
|
20
|
+
* - 用户在 UI 手动触发 / API 显式 accept 走原始路径, **不查此开关**
|
|
21
|
+
* (避免阻塞用户的正常接受操作)
|
|
22
|
+
* - 写入被拒绝时, 抛 AutoEvolveDisabledError (调用方选择 catch + fallback)
|
|
23
|
+
*/
|
|
24
|
+
import * as fs from 'fs/promises';
|
|
25
|
+
import * as os from 'os';
|
|
26
|
+
const ENV_KEY = 'BOLLOON_AUTO_EVOLVE_DATA';
|
|
27
|
+
const POLICY_PATH = () => (os.homedir() || process.env.HOME || '/tmp') + '/.bolloon/self-improve-policy.json';
|
|
28
|
+
export class AutoEvolveDisabledError extends Error {
|
|
29
|
+
constructor(reason) {
|
|
30
|
+
super(`[auto-evolve] 数据层自动进化已关闭: ${reason}`);
|
|
31
|
+
this.name = 'AutoEvolveDisabledError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let cachedPolicy = null;
|
|
35
|
+
const POLICY_TTL_MS = 5_000; // 5s 缓存, 避免每次写都读盘
|
|
36
|
+
/**
|
|
37
|
+
* 读 policy.json (带 5s 缓存)
|
|
38
|
+
*
|
|
39
|
+
* 注意: 必须保留完整 parsed 对象 — 调用方需要
|
|
40
|
+
* parsed.dataLayerAutoEvolve 等其他字段做 UI 展示.
|
|
41
|
+
*/
|
|
42
|
+
async function readPolicyFile() {
|
|
43
|
+
if (cachedPolicy && Date.now() - cachedPolicy.checkedAt < POLICY_TTL_MS && cachedPolicy.parsed) {
|
|
44
|
+
return cachedPolicy.parsed;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const content = await fs.readFile(POLICY_PATH(), 'utf-8');
|
|
48
|
+
const parsed = JSON.parse(content);
|
|
49
|
+
cachedPolicy = {
|
|
50
|
+
value: parsed.dataLayerAutoEvolve === true,
|
|
51
|
+
checkedAt: Date.now(),
|
|
52
|
+
parsed,
|
|
53
|
+
};
|
|
54
|
+
return parsed;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
cachedPolicy = { value: false, checkedAt: Date.now(), parsed: null };
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 核心查询: 数据层自动进化是否开启
|
|
63
|
+
*
|
|
64
|
+
* 任一为真:
|
|
65
|
+
* 1. env BOLLOON_AUTO_EVOLVE_DATA=1|true|yes
|
|
66
|
+
* 2. policy.json dataLayerAutoEvolve: true
|
|
67
|
+
*
|
|
68
|
+
* 同步读 env (env 永远先, 政策文件是补充).
|
|
69
|
+
*/
|
|
70
|
+
export function isDataLayerAutoEvolveEnabledSync() {
|
|
71
|
+
const env = (process.env[ENV_KEY] || '').toLowerCase();
|
|
72
|
+
if (env === '1' || env === 'true' || env === 'yes')
|
|
73
|
+
return true;
|
|
74
|
+
return false; // policy 文件需要 async, 同步版只看 env
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 异步版 (含 policy.json): 自动调用方用这个
|
|
78
|
+
*/
|
|
79
|
+
export async function isDataLayerAutoEvolveEnabled() {
|
|
80
|
+
if (isDataLayerAutoEvolveEnabledSync())
|
|
81
|
+
return true;
|
|
82
|
+
const p = await readPolicyFile();
|
|
83
|
+
return p?.dataLayerAutoEvolve === true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 硬性护栏: 自动写数据层前必须调, 否则抛错
|
|
87
|
+
*
|
|
88
|
+
* @param caller 写操作的来源 (日志用, e.g. "adaptive-scan.deprecate")
|
|
89
|
+
*/
|
|
90
|
+
export async function requireDataLayerAutoEvolve(caller) {
|
|
91
|
+
const enabled = await isDataLayerAutoEvolveEnabled();
|
|
92
|
+
if (!enabled) {
|
|
93
|
+
throw new AutoEvolveDisabledError(`${caller} 尝试自动写数据层, 但 ${ENV_KEY} 未设且 policy.dataLayerAutoEvolve !== true`);
|
|
94
|
+
}
|
|
95
|
+
console.log(`[auto-evolve] ✅ 允许自动写 (caller=${caller})`);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 软性查询 + 日志: 给 UI 状态栏 / 调试用
|
|
99
|
+
*/
|
|
100
|
+
export async function getAutoEvolveStatus() {
|
|
101
|
+
const env = process.env[ENV_KEY] || '';
|
|
102
|
+
const envOn = isDataLayerAutoEvolveEnabledSync();
|
|
103
|
+
const p = await readPolicyFile();
|
|
104
|
+
const policyOn = p?.dataLayerAutoEvolve === true;
|
|
105
|
+
return {
|
|
106
|
+
enabled: envOn || policyOn,
|
|
107
|
+
env,
|
|
108
|
+
policyFilePath: POLICY_PATH(),
|
|
109
|
+
policyValue: policyOn,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 清除 policy 缓存 (测试用 / 实时热改 policy.json 后)
|
|
114
|
+
*/
|
|
115
|
+
export function clearAutoEvolvePolicyCache() {
|
|
116
|
+
cachedPolicy = null;
|
|
117
|
+
}
|