@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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-Tool Validator — 4 步链式校验
|
|
3
|
+
*
|
|
4
|
+
* 严格对齐 Claude Code 论文 PreToolUse 4 步:
|
|
5
|
+
* 1. modeGate(tool, mode) — bypassPermissions → 直接放行(除 shell)
|
|
6
|
+
* 2. blacklistGate(tool, args) — 6 模式黑名单 (recursive rm, force push, dd 等)
|
|
7
|
+
* 3. shellGuardGate(tool, args) — **完整** shell-guard (checkCommand + checkWritePath), 永远生效
|
|
8
|
+
* 4. schemaGate(tool, args) — 第一版 stub: always allow (后续接入 tool-gate.ts)
|
|
9
|
+
*
|
|
10
|
+
* 每步独立 try/catch, 失败 = 跳过该步, 不阻塞 (与现有 lifecycle-hooks 风格一致)
|
|
11
|
+
*
|
|
12
|
+
* 失败静默: 任何 step 异常 → console.warn + 继续下一 step, 不抛错
|
|
13
|
+
*/
|
|
14
|
+
import { isShellTool } from './permission-mode.js';
|
|
15
|
+
import { checkCommand, checkWritePath } from './shell-guard.js';
|
|
16
|
+
const DANGEROUS_PATTERNS = [
|
|
17
|
+
{ re: /\brm\s+(-[a-z]*f[a-z]*\s+)?-[a-z]*r[a-z]*\s+\//, reason: '禁止递归删除根目录' },
|
|
18
|
+
{ re: /\bgit\s+push\s+.*--force\b/, reason: '禁止 force push' },
|
|
19
|
+
{ re: /\brm\s+-rf\s+~\//, reason: '禁止递归删除 home' },
|
|
20
|
+
{ re: /\bdd\s+if=.*\s+of=\/dev\//, reason: '禁止 dd 覆盖块设备' },
|
|
21
|
+
{ re: /\bcurl\s+.*\|\s*(ba)?sh\b/, reason: '禁止 curl|sh 直执行' },
|
|
22
|
+
{ re: />\s*\/dev\/sd[a-z]/, reason: '禁止写裸设备' },
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Step 1: modeGate
|
|
26
|
+
* - bypassPermissions + 非 shell → 直接放行 (但保留 shellGuardRetained 标记)
|
|
27
|
+
* - 其他 → 继续
|
|
28
|
+
*/
|
|
29
|
+
function modeGate(tool, mode) {
|
|
30
|
+
try {
|
|
31
|
+
if (mode === 'bypassPermissions' && !isShellTool(tool)) {
|
|
32
|
+
return { allowed: true, mode, rejectedBy: undefined, shellGuardRetained: false };
|
|
33
|
+
}
|
|
34
|
+
return null; // 不在这一步决定
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.warn('[pre-tool] modeGate failed (silent):', err);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Step 2: blacklistGate
|
|
43
|
+
* 6 模式黑名单 (现有 lifecycle-hooks.ts 行为)
|
|
44
|
+
*/
|
|
45
|
+
function blacklistGate(tool, args, mode) {
|
|
46
|
+
try {
|
|
47
|
+
if (!isShellTool(tool))
|
|
48
|
+
return null;
|
|
49
|
+
const cmd = String(args.command || args.cmd || '');
|
|
50
|
+
if (!cmd)
|
|
51
|
+
return null;
|
|
52
|
+
for (const { re, reason } of DANGEROUS_PATTERNS) {
|
|
53
|
+
if (re.test(cmd)) {
|
|
54
|
+
return { allowed: false, reason, rejectedBy: 'blacklist', mode };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.warn('[pre-tool] blacklistGate failed (silent):', err);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Step 3: shellGuardGate
|
|
66
|
+
* 永远跑 (绕过 mode), 是 bolloon 自身的安全底线
|
|
67
|
+
*
|
|
68
|
+
* 接完整 shell-guard.ts (checkCommand + checkWritePath):
|
|
69
|
+
* - checkCommand 走命令白名单 + 参数黑名单 (允许列表默认 git/node/npm/npx/tsx/tsc/vitest 等)
|
|
70
|
+
* - checkWritePath 走路径黑名单 (硬编码: pi-sdk.ts, shell-guard.ts, .env, .git/, .bolloon/, package.json, dist/ 等)
|
|
71
|
+
*
|
|
72
|
+
* 失败静默: 任何异常 → 跳过这步, 不阻塞
|
|
73
|
+
*/
|
|
74
|
+
function shellGuardGate(tool, args, mode) {
|
|
75
|
+
try {
|
|
76
|
+
if (!isShellTool(tool))
|
|
77
|
+
return null;
|
|
78
|
+
// 1. 命令检查 (含白名单 + arg 黑名单)
|
|
79
|
+
const cmd = String(args.command || args.cmd || '');
|
|
80
|
+
let bin = '';
|
|
81
|
+
if (cmd) {
|
|
82
|
+
// shell-guard 期望 (cmd, args[]) 形式
|
|
83
|
+
// 简单 split: 第一个 token 是 cmd, 之后是 args
|
|
84
|
+
const parts = cmd.split(/\s+/).filter(Boolean);
|
|
85
|
+
bin = parts[0] || '';
|
|
86
|
+
const rest = parts.slice(1);
|
|
87
|
+
if (bin) {
|
|
88
|
+
const cmdResult = checkCommand(bin, rest);
|
|
89
|
+
if (!cmdResult.allowed) {
|
|
90
|
+
return {
|
|
91
|
+
allowed: false,
|
|
92
|
+
reason: cmdResult.reason,
|
|
93
|
+
rejectedBy: 'shell-guard',
|
|
94
|
+
mode,
|
|
95
|
+
shellGuardRetained: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// 2. 写路径检查 — 从 2 个来源抽:
|
|
101
|
+
// a) 结构化字段 (args.path / args.target / args.file)
|
|
102
|
+
// b) 命令字符串里的 token (老测试传 args.command='cat pi-sdk.ts' 时也能拦)
|
|
103
|
+
const pathCandidates = [];
|
|
104
|
+
const structured = String(args.path || args.target || args.file || args.targetPath || args.destination || '');
|
|
105
|
+
if (structured)
|
|
106
|
+
pathCandidates.push(structured);
|
|
107
|
+
if (cmd) {
|
|
108
|
+
// 简单 tokenize: 按空白切, 取不含 shell 元字符的 token
|
|
109
|
+
const tokens = cmd.split(/\s+/).filter((t) => t && !/^[|>;&`$()<]/.test(t));
|
|
110
|
+
for (const t of tokens) {
|
|
111
|
+
if (t !== bin)
|
|
112
|
+
pathCandidates.push(t); // 跳过命令本身
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// 纯文件名 denylist (兜底 — checkWritePath 的 regex 要求 /src/xxx/ 锚定, 纯文件名拦不到)
|
|
116
|
+
// 只对**写/删/改**类命令生效 (rm, cp, mv, sed -i, echo >); 读类命令 (cat, head, tail, ls) 跳过
|
|
117
|
+
const READ_BINS = new Set(['cat', 'head', 'tail', 'wc', 'ls', 'echo', 'pwd', 'date', 'mkdir', 'touch', 'find', 'grep', 'git', 'node', 'npm', 'npx', 'tsx', 'tsc', 'vitest']);
|
|
118
|
+
const FILENAME_DENYLIST = [
|
|
119
|
+
'pi-sdk.ts', 'shell-guard.ts', 'shell-tool.ts',
|
|
120
|
+
'.env', 'package.json', 'tsconfig.json',
|
|
121
|
+
];
|
|
122
|
+
const isReadOperation = READ_BINS.has(bin);
|
|
123
|
+
for (const candidate of pathCandidates) {
|
|
124
|
+
// 2a. checkWritePath 只对写操作调用 (读类命令绕开, 避免 cat pi-sdk.ts 误拦)
|
|
125
|
+
if (!isReadOperation) {
|
|
126
|
+
const pathResult = checkWritePath(candidate);
|
|
127
|
+
if (!pathResult.allowed) {
|
|
128
|
+
return {
|
|
129
|
+
allowed: false,
|
|
130
|
+
reason: pathResult.reason,
|
|
131
|
+
rejectedBy: 'shell-guard',
|
|
132
|
+
mode,
|
|
133
|
+
shellGuardRetained: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// 2b. 纯文件名兜底 — 只对写操作生效
|
|
138
|
+
if (!isReadOperation) {
|
|
139
|
+
const baseName = candidate.split('/').pop() || '';
|
|
140
|
+
if (FILENAME_DENYLIST.includes(baseName)) {
|
|
141
|
+
return {
|
|
142
|
+
allowed: false,
|
|
143
|
+
reason: `shell-guard: 禁止 ${bin} 操作 ${candidate} (命中文件名硬编码黑名单)`,
|
|
144
|
+
rejectedBy: 'shell-guard',
|
|
145
|
+
mode,
|
|
146
|
+
shellGuardRetained: true,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null; // 通过
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.warn('[pre-tool] shellGuardGate failed (silent):', err);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Step 4: schemaGate
|
|
160
|
+
* 第一版 stub: always allow
|
|
161
|
+
*/
|
|
162
|
+
function schemaGate(_tool, _args, mode) {
|
|
163
|
+
return null; // 不在这一步决定
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 主入口: 4 步链式, 任何一步拒绝 → 返回, 否则最终放行
|
|
167
|
+
*/
|
|
168
|
+
export function validatePreToolUse(tool, args, mode) {
|
|
169
|
+
const steps = [
|
|
170
|
+
() => modeGate(tool, mode),
|
|
171
|
+
() => blacklistGate(tool, args, mode),
|
|
172
|
+
() => shellGuardGate(tool, args, mode),
|
|
173
|
+
() => schemaGate(tool, args, mode),
|
|
174
|
+
];
|
|
175
|
+
for (const step of steps) {
|
|
176
|
+
const result = step();
|
|
177
|
+
if (result && !result.allowed)
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
// 全部通过 → 放行
|
|
181
|
+
// bypassPermissions + shell 走到这里 = shellGuardGate 已经放过, 但要标记 shellGuardRetained
|
|
182
|
+
return {
|
|
183
|
+
allowed: true,
|
|
184
|
+
mode,
|
|
185
|
+
rejectedBy: undefined,
|
|
186
|
+
shellGuardRetained: isShellTool(tool),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// ============================================================
|
|
190
|
+
// 测试钩子
|
|
191
|
+
// ============================================================
|
|
192
|
+
export function _resetValidatorForTest() {
|
|
193
|
+
// validator 是 pure, 保留 API 一致
|
|
194
|
+
}
|
|
@@ -208,6 +208,19 @@ export class WorkflowPivotLoop {
|
|
|
208
208
|
type: 'error',
|
|
209
209
|
content: `❌ 未知工具: ${toolCall.name}`
|
|
210
210
|
});
|
|
211
|
+
// 2026-06-15: step-timeline — 未知工具也开/关一个 step 节点
|
|
212
|
+
this.emit({
|
|
213
|
+
type: 'step_start',
|
|
214
|
+
content: `未知工具 ${toolCall.name}`,
|
|
215
|
+
tool: toolCall.name,
|
|
216
|
+
args: toolCall.args || {},
|
|
217
|
+
});
|
|
218
|
+
this.emit({
|
|
219
|
+
type: 'step_error',
|
|
220
|
+
content: `未知工具 ${toolCall.name}`,
|
|
221
|
+
tool: toolCall.name,
|
|
222
|
+
error: 'Unknown tool',
|
|
223
|
+
});
|
|
211
224
|
this.messageHistory.push({
|
|
212
225
|
role: 'tool',
|
|
213
226
|
content: JSON.stringify({ success: false, error: `Unknown tool: ${toolCall.name}` })
|
|
@@ -219,8 +232,26 @@ export class WorkflowPivotLoop {
|
|
|
219
232
|
content: `🔧 执行: ${toolCall.name}`,
|
|
220
233
|
tool: toolCall.name
|
|
221
234
|
});
|
|
235
|
+
// 2026-06-15: step-timeline — 开节点
|
|
236
|
+
this.emit({
|
|
237
|
+
type: 'step_start',
|
|
238
|
+
content: `调用 ${toolCall.name}`,
|
|
239
|
+
tool: toolCall.name,
|
|
240
|
+
args: toolCall.args || {},
|
|
241
|
+
});
|
|
222
242
|
try {
|
|
223
243
|
const result = await tool.execute(toolCall.args ?? {});
|
|
244
|
+
// 2026-06-15: step-timeline — 关闭节点 (success / error)
|
|
245
|
+
this.emit({
|
|
246
|
+
type: result.success ? 'step_done' : 'step_error',
|
|
247
|
+
content: result.success
|
|
248
|
+
? `${toolCall.name} 成功`
|
|
249
|
+
: `${toolCall.name} 失败: ${result.error}`,
|
|
250
|
+
tool: toolCall.name,
|
|
251
|
+
success: result.success,
|
|
252
|
+
output: result.output,
|
|
253
|
+
error: result.error,
|
|
254
|
+
});
|
|
224
255
|
this.emit({
|
|
225
256
|
type: result.success ? 'status' : 'error',
|
|
226
257
|
content: result.success
|
|
@@ -305,9 +336,49 @@ export class WorkflowPivotLoop {
|
|
|
305
336
|
*/
|
|
306
337
|
extractPendingToolUses(content) {
|
|
307
338
|
const pending = [];
|
|
339
|
+
// Pattern 0: <tool_use>{...JSON...}</tool_use> (Anthropic 风格 + minimax 也用)
|
|
340
|
+
// 这次 LLM 输出: <tool_use>\n{"name": "read_document", "arguments": {"path": "/Users/.../README.md"}}\n</tool_use>
|
|
341
|
+
const toolUseRe = /<tool_use>\s*(\{[\s\S]*?\})\s*<\/tool_use>/g;
|
|
342
|
+
let match;
|
|
343
|
+
while ((match = toolUseRe.exec(content)) !== null) {
|
|
344
|
+
try {
|
|
345
|
+
const obj = JSON.parse(match[1]);
|
|
346
|
+
if (obj && obj.name && this.tools.has(obj.name)) {
|
|
347
|
+
const args = this.normalizeArgs(obj.arguments || {});
|
|
348
|
+
pending.push({ name: obj.name, args, description: '', parameters: {} });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
// JSON 解析失败, 继续下一 match
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Pattern 0b: <function_calls><invoke name="X"><parameter name="k">v</parameter>...</invoke></function_calls>
|
|
356
|
+
// 这次 minimax LLM 用这种 Anthropic 风格 XML
|
|
357
|
+
const fnCallsRe = /<function_calls>([\s\S]*?)<\/function_calls>/g;
|
|
358
|
+
while ((match = fnCallsRe.exec(content)) !== null) {
|
|
359
|
+
const block = match[1];
|
|
360
|
+
// 抓 <invoke name="X">...</invoke>
|
|
361
|
+
const invokeRe = /<invoke\s+name="(\w+)"\s*>([\s\S]*?)<\/invoke>/g;
|
|
362
|
+
let im;
|
|
363
|
+
while ((im = invokeRe.exec(block)) !== null) {
|
|
364
|
+
const name = im[1];
|
|
365
|
+
if (!this.tools.has(name))
|
|
366
|
+
continue;
|
|
367
|
+
// 抓 <parameter name="k">v</parameter> 列表
|
|
368
|
+
const args = {};
|
|
369
|
+
const paramRe = /<parameter\s+name="(\w+)"\s*>([\s\S]*?)<\/parameter>/g;
|
|
370
|
+
let pm;
|
|
371
|
+
while ((pm = paramRe.exec(im[2])) !== null) {
|
|
372
|
+
args[pm[1]] = pm[2].trim().replace(/^["']|['"]$/g, '');
|
|
373
|
+
}
|
|
374
|
+
// 避免重复添加
|
|
375
|
+
if (!pending.some(p => p.name === name)) {
|
|
376
|
+
pending.push({ name, args, description: '', parameters: {} });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
308
380
|
// Pattern 1: Chinese format "调用工具: tool_name(args)"
|
|
309
381
|
const pattern1 = /调用工具[::]\s*(\w+)\s*\(([^)]*)\)/g;
|
|
310
|
-
let match;
|
|
311
382
|
while ((match = pattern1.exec(content)) !== null) {
|
|
312
383
|
const name = match[1];
|
|
313
384
|
const argsStr = match[2];
|
|
@@ -357,22 +428,52 @@ export class WorkflowPivotLoop {
|
|
|
357
428
|
return args;
|
|
358
429
|
const pairs = argsStr.split(',').map(s => s.trim()).filter(Boolean);
|
|
359
430
|
for (const pair of pairs) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
431
|
+
// 2026-06-15: LLM 实际输出 3 种格式 — 全支持
|
|
432
|
+
// 1) JSON 风格: {"key":"value"} (服务器日志显示, 但 LLM 不会真输出完整 JSON)
|
|
433
|
+
// 2) key="value" 含双引号 (本次 read_document(path="/Users/..."))
|
|
434
|
+
// 3) key='value' 含单引号
|
|
435
|
+
// 4) key:value (老 Chinese 格式)
|
|
436
|
+
// 5) key value (positional 兜底)
|
|
437
|
+
let m = pair.match(/^["']?([\w-]+)["']?\s*=\s*["']([^"']*)["']$/);
|
|
438
|
+
if (m) {
|
|
439
|
+
args[m[1]] = m[2];
|
|
440
|
+
continue;
|
|
365
441
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
442
|
+
m = pair.match(/^["']?([\w-]+)["']?\s*[:=]\s*([^,]+)$/);
|
|
443
|
+
if (m) {
|
|
444
|
+
args[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, '');
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
m = pair.match(/^["']?([\w-]+)["']?\s*[:]\s*["']?([^"']*)["']?$/);
|
|
448
|
+
if (m) {
|
|
449
|
+
args[m[1]] = m[2].trim();
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
// positional 兜底
|
|
453
|
+
const parts = pair.split(/\s+/);
|
|
454
|
+
if (parts.length >= 2) {
|
|
455
|
+
args[parts[0]] = parts.slice(1).join(' ');
|
|
372
456
|
}
|
|
373
457
|
}
|
|
374
458
|
return args;
|
|
375
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* 把 tool_use JSON 里的 arguments (已经是对象) 转成 Record<string, string>
|
|
462
|
+
* JSON parser 直接给对象, 但 tool.execute 期望 Record<string, string>
|
|
463
|
+
* 非字符串值 JSON.stringify 一下
|
|
464
|
+
*/
|
|
465
|
+
normalizeArgs(args) {
|
|
466
|
+
const out = {};
|
|
467
|
+
for (const [k, v] of Object.entries(args || {})) {
|
|
468
|
+
if (v == null)
|
|
469
|
+
continue;
|
|
470
|
+
if (typeof v === 'string')
|
|
471
|
+
out[k] = v;
|
|
472
|
+
else
|
|
473
|
+
out[k] = JSON.stringify(v);
|
|
474
|
+
}
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
376
477
|
/**
|
|
377
478
|
* Build context from message history
|
|
378
479
|
*/
|
|
@@ -74,7 +74,7 @@ export function run(repoRoot, mode = "full") {
|
|
|
74
74
|
const findings = [];
|
|
75
75
|
const actualRoutes = countRouteDecorators(repoRoot);
|
|
76
76
|
const routesDoc = path.join(repoRoot, ".boll", "rules", "backend-routes.md");
|
|
77
|
-
const claudeMd = path.join(repoRoot, "
|
|
77
|
+
const claudeMd = path.join(repoRoot, "Bolloon.md");
|
|
78
78
|
if (fs.existsSync(routesDoc)) {
|
|
79
79
|
const routesContent = fs.readFileSync(routesDoc, "utf-8");
|
|
80
80
|
const routeClaims = routesContent.match(/-\s+`(?:GET|POST|PUT|PATCH|DELETE|WS)\s+/g) || [];
|
|
@@ -2,7 +2,7 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
export function run(repoRoot, mode = "full") {
|
|
4
4
|
const findings = [];
|
|
5
|
-
const docFiles = ["
|
|
5
|
+
const docFiles = ["Bolloon.md", "docs/ROADMAP.md", "README.md"];
|
|
6
6
|
const maxAgeDays = 7;
|
|
7
7
|
for (const docFile of docFiles) {
|
|
8
8
|
const fullPath = path.join(repoRoot, docFile);
|
|
@@ -17,7 +17,7 @@ export const CONTEXT_MAP = {
|
|
|
17
17
|
"scenes/": ["scene-fidelity", "two-language"],
|
|
18
18
|
"website/app/[scene]/": ["scene-fidelity", "two-language"],
|
|
19
19
|
"website/components/scene/": ["scene-fidelity", "two-language"],
|
|
20
|
-
"
|
|
20
|
+
"Bolloon.md": ["truth-source-hierarchy"],
|
|
21
21
|
"MEMORY.md": ["truth-source-hierarchy"],
|
|
22
22
|
"docs/INDEX.md": ["truth-source-hierarchy"],
|
|
23
23
|
"mcp-server/pyproject.toml": ["version-sources"],
|
|
@@ -109,7 +109,7 @@ function block(reason, host = null) {
|
|
|
109
109
|
Demo 正式: bash scripts/deploy-demo.sh <name> --channel prod --yes
|
|
110
110
|
Demo 内测: bash scripts/deploy-demo.sh <name> --channel preview --yes
|
|
111
111
|
Edge/Nginx: bash scripts/deploy-edge.sh --yes
|
|
112
|
-
详见
|
|
112
|
+
详见 Bolloon.md Development Commands。`;
|
|
113
113
|
}
|
|
114
114
|
else if (BRIDGE_VPS_HOSTS.includes(host ?? "")) {
|
|
115
115
|
guidance = `Bridge VPS 必须走 git pull 更新路径:
|
|
@@ -8,7 +8,7 @@ export const GUARD_MAP = {
|
|
|
8
8
|
"docs/issues/": ["check_issue_closure"],
|
|
9
9
|
"backend/product/routes/": ["check_doc_freshness"],
|
|
10
10
|
"docs/ROADMAP.md": ["check_doc_freshness"],
|
|
11
|
-
"
|
|
11
|
+
"Bolloon.md": ["check_doc_freshness"],
|
|
12
12
|
"docs/magic/": ["check_doc_freshness"],
|
|
13
13
|
".boll/rules/backend-routes.md": ["check_doc_freshness"],
|
|
14
14
|
".boll/settings.json": ["check_hook_installed"],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
process.stdout.write(`\n\n## Bollharness Context\n\n` +
|
|
3
3
|
`bollharness is an AI Agent Session Governance Framework.\n\n` +
|
|
4
|
-
`For more information, see
|
|
4
|
+
`For more information, see Bolloon.md in the project root.\n`);
|
|
5
5
|
process.exit(0);
|
|
6
6
|
export {};
|
|
@@ -6,7 +6,7 @@ const RISK_ORDER = { R0: 0, R1: 1, R2: 2, R3: 3, R4: 4 };
|
|
|
6
6
|
const RISK_ELEVATORS = [
|
|
7
7
|
["scripts/deploy", "R4"],
|
|
8
8
|
["backend/product/db/migration", "R4"],
|
|
9
|
-
["
|
|
9
|
+
["Bolloon.md", "R3"],
|
|
10
10
|
[".boll/settings.json", "R3"],
|
|
11
11
|
[".boll/skills/", "R3"],
|
|
12
12
|
[".boll/rules/", "R3"],
|
|
@@ -25,7 +25,7 @@ export const CONTEXT_MAP = {
|
|
|
25
25
|
'src/bollharness-integration/': ['truth-source-hierarchy', 'artifact-linkage'],
|
|
26
26
|
'docs/': ['truth-source-hierarchy', 'two-language'],
|
|
27
27
|
'docs/decisions/': ['truth-source-hierarchy', 'artifact-linkage'],
|
|
28
|
-
'
|
|
28
|
+
'Bolloon.md': ['truth-source-hierarchy', 'bridge-constitution'],
|
|
29
29
|
'README.md': ['truth-source-hierarchy', 'general-dev-principles'],
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
@@ -331,7 +331,7 @@ export const CONTEXT_FRAGMENTS = {
|
|
|
331
331
|
'project-governance': `# Project Governance
|
|
332
332
|
|
|
333
333
|
## Truth Sources
|
|
334
|
-
-
|
|
334
|
+
- Bolloon.md: Project overview and norms
|
|
335
335
|
- docs/: Architecture and decisions
|
|
336
336
|
- code/: Implementation
|
|
337
337
|
- issues/: Tracking and discussions
|
|
@@ -36,7 +36,7 @@ export const GUARD_MAP = {
|
|
|
36
36
|
'src/constraints/': ['check_api_types', 'check_versions'],
|
|
37
37
|
'src/social/': ['check_api_types', 'check_versions'],
|
|
38
38
|
'docs/': ['check_doc_freshness', 'check_doc_links'],
|
|
39
|
-
'
|
|
39
|
+
'Bolloon.md': ['check_doc_freshness', 'check_artifact_link'],
|
|
40
40
|
'README.md': ['check_doc_freshness'],
|
|
41
41
|
'docs/decisions/': ['check_artifact_link', 'check_versions'],
|
|
42
42
|
'.boll/': ['check_hook_installed'],
|
|
@@ -25,6 +25,7 @@ export async function bootstrapBolloon(opts = {}) {
|
|
|
25
25
|
};
|
|
26
26
|
try {
|
|
27
27
|
scanResult = await runAdaptiveScan();
|
|
28
|
+
const { suggestionHint } = await import('../pi-ecosystem-judgment/adaptive-scan.js');
|
|
28
29
|
await logEvolution({
|
|
29
30
|
ts: new Date().toISOString(),
|
|
30
31
|
action: 'accept', // 用 accept 表示"系统记录" (跟 reject 区分)
|
|
@@ -35,6 +36,7 @@ export async function bootstrapBolloon(opts = {}) {
|
|
|
35
36
|
decision: 'Bolloon 启动扫描',
|
|
36
37
|
reason: `本次启动扫描了 ${scanResult.judgmentsTotal} 条原则, ${scanResult.usageEntriesScanned} 条使用记录, 生成 ${scanResult.suggestions.length} 条建议`,
|
|
37
38
|
action: 'review',
|
|
39
|
+
hint: suggestionHint('unused', 'review', { usage7d: 0, usage30d: 0, daysSinceLastUse: 0, totalUsage: 0 }),
|
|
38
40
|
metrics: { usage7d: 0, usage30d: 0, daysSinceLastUse: 0, totalUsage: 0 },
|
|
39
41
|
scannedAt: scanResult.scannedAt,
|
|
40
42
|
},
|
|
@@ -50,6 +52,7 @@ export async function bootstrapBolloon(opts = {}) {
|
|
|
50
52
|
projectRoot: opts.cwd ?? process.cwd(),
|
|
51
53
|
projectName: 'unknown',
|
|
52
54
|
bolloonMd: null,
|
|
55
|
+
hierarchy: { managed: null, user: null, project: null, local: null, merged: '' },
|
|
53
56
|
git: null,
|
|
54
57
|
persona: null,
|
|
55
58
|
judgmentsSummary: { total: 0, active: 0, superseded: 0, rejected: 0, topValues: [] },
|
|
@@ -57,6 +57,20 @@ async function collectProject(cwd, bolloonMdMaxBytes) {
|
|
|
57
57
|
const bolloonMd = await safeReadFile(path.join(cwd, 'Bolloon.md'), bolloonMdMaxBytes);
|
|
58
58
|
return { projectName, bolloonMd };
|
|
59
59
|
}
|
|
60
|
+
async function collectHierarchy(cwd) {
|
|
61
|
+
// 动态 import 避免循环依赖
|
|
62
|
+
const { collectHierarchyLayers, mergeHierarchyLayers } = await import('./context-hierarchy.js');
|
|
63
|
+
try {
|
|
64
|
+
const layers = await collectHierarchyLayers({ cwd });
|
|
65
|
+
const merged = mergeHierarchyLayers(layers, { maxChars: DEFAULT_HIERARCHY_MERGED_MAX });
|
|
66
|
+
return { ...layers, merged };
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.warn('[context-collector] collectHierarchy failed (silent):', err);
|
|
70
|
+
return { managed: null, user: null, project: null, local: null, merged: '' };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const DEFAULT_HIERARCHY_MERGED_MAX = 2000; // 4000 → 2000 (P-Action 4), 跟 context-hierarchy.ts DEFAULT_MERGE_MAX_CHARS 对齐
|
|
60
74
|
async function collectGit(cwd, limit) {
|
|
61
75
|
const branchOut = await safeExecFile('git', ['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
|
|
62
76
|
if (!branchOut)
|
|
@@ -255,13 +269,14 @@ async function walkDir(dir, cb) {
|
|
|
255
269
|
export async function collectBolloonContext(opts) {
|
|
256
270
|
const { cwd, bolloonMdMaxBytes = 2000, gitCommitLimit = 5, topValuesLimit = 10, } = opts;
|
|
257
271
|
// 并行收集 (除 judgments 因为要动态 import + 依赖其它)
|
|
258
|
-
const [project, git, persona, skills, env, pending] = await Promise.all([
|
|
272
|
+
const [project, git, persona, skills, env, pending, hierarchy] = await Promise.all([
|
|
259
273
|
collectProject(cwd, bolloonMdMaxBytes),
|
|
260
274
|
collectGit(cwd, gitCommitLimit),
|
|
261
275
|
collectPersona(),
|
|
262
276
|
collectSkills(),
|
|
263
277
|
Promise.resolve(collectEnv()),
|
|
264
278
|
collectPending(opts),
|
|
279
|
+
collectHierarchy(cwd),
|
|
265
280
|
]);
|
|
266
281
|
// judgments 单独调 (内部 import)
|
|
267
282
|
const judgmentsSummary = await collectJudgmentsSummary(topValuesLimit);
|
|
@@ -269,6 +284,7 @@ export async function collectBolloonContext(opts) {
|
|
|
269
284
|
projectRoot: cwd,
|
|
270
285
|
projectName: project.projectName,
|
|
271
286
|
bolloonMd: project.bolloonMd,
|
|
287
|
+
hierarchy,
|
|
272
288
|
git,
|
|
273
289
|
persona,
|
|
274
290
|
judgmentsSummary,
|