@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,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* step-timeline.ts — 气泡内 4 状态步骤条 (2026-06-15)
|
|
3
|
+
*
|
|
4
|
+
* 职责 (单一):
|
|
5
|
+
* - 给每条 AI 消息挂一个可折叠/展开的步骤条
|
|
6
|
+
* - 状态机: idle ○ / active ● / done 🟢 / error 🔴
|
|
7
|
+
* - 摘要条 + 详情区共用同一份 Step[] 数据
|
|
8
|
+
* - 流式期间在 streaming 元素内, finalize 时搬到正式 message
|
|
9
|
+
* - 折叠状态按 channel 持久化 (localStorage)
|
|
10
|
+
*
|
|
11
|
+
* 状态 (模块私有, 每个 message 一个实例):
|
|
12
|
+
* - steps: Step[]
|
|
13
|
+
* - currentIndex: number
|
|
14
|
+
* - expanded: boolean (受 localStorage 影响)
|
|
15
|
+
*
|
|
16
|
+
* 输入 (从 message-renderer 调用):
|
|
17
|
+
* - createEmptyStepTimeline(channelId) -> HTMLElement
|
|
18
|
+
* - mountStepTimeline(messageEl, channelId) // 已有 message 上挂 timeline
|
|
19
|
+
* - pushStepToTimeline(timelineEl, type, data) // 订阅 step_start/done/error
|
|
20
|
+
* - migrateStepTimeline(fromEl, toEl) // finalize 时搬家
|
|
21
|
+
*
|
|
22
|
+
* 输出 (DOM):
|
|
23
|
+
* - .step-timeline / .step-timeline-summary / .step-timeline-body
|
|
24
|
+
* - .step-dot (摘要条圆点) / .step-timeline-node (详情区节点)
|
|
25
|
+
*/
|
|
26
|
+
const MAX_VISIBLE_NODES = 8;
|
|
27
|
+
const STORAGE_PREFIX = 'bolloon.stepTimeline.expanded.';
|
|
28
|
+
const stateMap = new WeakMap();
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// 工具: 持久化
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
function readExpanded(channelId) {
|
|
33
|
+
if (!channelId)
|
|
34
|
+
return false;
|
|
35
|
+
try {
|
|
36
|
+
return localStorage.getItem(STORAGE_PREFIX + channelId) === '1';
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeExpanded(channelId, expanded) {
|
|
43
|
+
if (!channelId)
|
|
44
|
+
return;
|
|
45
|
+
try {
|
|
46
|
+
localStorage.setItem(STORAGE_PREFIX + channelId, expanded ? '1' : '0');
|
|
47
|
+
}
|
|
48
|
+
catch { /* localStorage 写失败 (隐私模式), 静默 */ }
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// 工具: 摘要条 title
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
function computeTitle(steps) {
|
|
54
|
+
if (steps.length === 0)
|
|
55
|
+
return '执行步骤';
|
|
56
|
+
// 优先 active, 其次最后一个 error
|
|
57
|
+
const activeIdx = steps.findIndex(s => s.status === 'active');
|
|
58
|
+
if (activeIdx >= 0)
|
|
59
|
+
return `● 执行中 · ${steps[activeIdx].name}`;
|
|
60
|
+
const lastErrorIdx = (() => {
|
|
61
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
62
|
+
if (steps[i].status === 'error')
|
|
63
|
+
return i;
|
|
64
|
+
}
|
|
65
|
+
return -1;
|
|
66
|
+
})();
|
|
67
|
+
if (lastErrorIdx >= 0)
|
|
68
|
+
return `✗ 失败 · ${steps[lastErrorIdx].name}`;
|
|
69
|
+
return `✓ 已完成 · ${steps.length} 步`;
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// 核心: 渲染 (摘要条 + 详情区, 共用 steps 状态)
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
function render(timelineEl) {
|
|
75
|
+
const state = stateMap.get(timelineEl);
|
|
76
|
+
if (!state)
|
|
77
|
+
return;
|
|
78
|
+
const { steps, expanded, showAll } = state;
|
|
79
|
+
const titleEl = timelineEl.querySelector('[data-current-tool]');
|
|
80
|
+
if (titleEl)
|
|
81
|
+
titleEl.textContent = computeTitle(steps);
|
|
82
|
+
// 摘要条 dots
|
|
83
|
+
const dotsEl = timelineEl.querySelector('[data-dots]');
|
|
84
|
+
if (dotsEl) {
|
|
85
|
+
// diff: 复用现有 dot, 多余 remove, 缺少 append
|
|
86
|
+
const existing = Array.from(dotsEl.children);
|
|
87
|
+
for (let i = 0; i < steps.length; i++) {
|
|
88
|
+
const step = steps[i];
|
|
89
|
+
let dot = existing[i];
|
|
90
|
+
if (!dot) {
|
|
91
|
+
dot = document.createElement('span');
|
|
92
|
+
dot.className = 'step-dot';
|
|
93
|
+
dot.setAttribute('data-index', String(i));
|
|
94
|
+
dotsEl.appendChild(dot);
|
|
95
|
+
}
|
|
96
|
+
dot.setAttribute('data-status', step.status);
|
|
97
|
+
dot.setAttribute('title', `${step.name} · ${step.status}`);
|
|
98
|
+
}
|
|
99
|
+
while (dotsEl.children.length > steps.length) {
|
|
100
|
+
dotsEl.removeChild(dotsEl.lastChild);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// 详情区 list
|
|
104
|
+
const listEl = timelineEl.querySelector('[data-list]');
|
|
105
|
+
if (listEl) {
|
|
106
|
+
const hiddenCount = Math.max(0, steps.length - MAX_VISIBLE_NODES);
|
|
107
|
+
const visibleSteps = showAll ? steps : steps.slice(-MAX_VISIBLE_NODES);
|
|
108
|
+
const existing = Array.from(listEl.children);
|
|
109
|
+
let htmlIdx = 0;
|
|
110
|
+
// "+N 更多" 折叠行 (仅在 showAll=false 且有 hidden 时)
|
|
111
|
+
if (!showAll && hiddenCount > 0) {
|
|
112
|
+
let moreBtn = existing[htmlIdx];
|
|
113
|
+
if (!moreBtn || !moreBtn.classList.contains('step-timeline-more')) {
|
|
114
|
+
moreBtn = document.createElement('li');
|
|
115
|
+
moreBtn.className = 'step-timeline-more';
|
|
116
|
+
moreBtn.setAttribute('role', 'button');
|
|
117
|
+
moreBtn.textContent = `+ ${hiddenCount} 更多`;
|
|
118
|
+
moreBtn.onclick = () => {
|
|
119
|
+
state.showAll = true;
|
|
120
|
+
render(timelineEl);
|
|
121
|
+
};
|
|
122
|
+
if (existing[htmlIdx] && existing[htmlIdx] !== moreBtn) {
|
|
123
|
+
listEl.replaceChild(moreBtn, existing[htmlIdx]);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
listEl.appendChild(moreBtn);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
moreBtn.textContent = `+ ${hiddenCount} 更多`;
|
|
131
|
+
}
|
|
132
|
+
htmlIdx++;
|
|
133
|
+
}
|
|
134
|
+
for (let i = 0; i < visibleSteps.length; i++) {
|
|
135
|
+
const step = visibleSteps[i];
|
|
136
|
+
const realIndex = showAll ? i : (steps.length - visibleSteps.length + i);
|
|
137
|
+
let node = existing[htmlIdx];
|
|
138
|
+
if (!node || !node.classList.contains('step-timeline-node')) {
|
|
139
|
+
node = document.createElement('li');
|
|
140
|
+
node.className = 'step-timeline-node';
|
|
141
|
+
node.setAttribute('data-index', String(realIndex));
|
|
142
|
+
const marker = document.createElement('span');
|
|
143
|
+
marker.className = 'step-timeline-marker';
|
|
144
|
+
const label = document.createElement('span');
|
|
145
|
+
label.className = 'step-timeline-label';
|
|
146
|
+
const args = document.createElement('span');
|
|
147
|
+
args.className = 'step-timeline-args';
|
|
148
|
+
node.appendChild(marker);
|
|
149
|
+
node.appendChild(label);
|
|
150
|
+
node.appendChild(args);
|
|
151
|
+
if (existing[htmlIdx] && existing[htmlIdx] !== node) {
|
|
152
|
+
listEl.replaceChild(node, existing[htmlIdx]);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
listEl.appendChild(node);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
node.setAttribute('data-index', String(realIndex));
|
|
160
|
+
}
|
|
161
|
+
node.setAttribute('data-status', step.status);
|
|
162
|
+
const label = node.querySelector('.step-timeline-label');
|
|
163
|
+
if (label)
|
|
164
|
+
label.textContent = step.name;
|
|
165
|
+
const argsEl = node.querySelector('.step-timeline-args');
|
|
166
|
+
if (argsEl) {
|
|
167
|
+
const argStr = step.args && Object.keys(step.args).length > 0
|
|
168
|
+
? JSON.stringify(step.args).slice(0, 60)
|
|
169
|
+
: '';
|
|
170
|
+
argsEl.textContent = argStr;
|
|
171
|
+
argsEl.style.display = argStr ? '' : 'none';
|
|
172
|
+
}
|
|
173
|
+
htmlIdx++;
|
|
174
|
+
}
|
|
175
|
+
// 清理多余 li
|
|
176
|
+
while (listEl.children.length > htmlIdx) {
|
|
177
|
+
listEl.removeChild(listEl.lastChild);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// 折叠态: 空状态显示占位, 有节点就保持
|
|
181
|
+
if (steps.length === 0) {
|
|
182
|
+
timelineEl.setAttribute('data-empty', 'true');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
timelineEl.removeAttribute('data-empty');
|
|
186
|
+
}
|
|
187
|
+
// 折叠 class
|
|
188
|
+
const body = timelineEl.querySelector('[data-body]');
|
|
189
|
+
if (body) {
|
|
190
|
+
if (expanded) {
|
|
191
|
+
body.style.maxHeight = body.scrollHeight + 'px';
|
|
192
|
+
setTimeout(() => {
|
|
193
|
+
// 动画结束后清掉, 让内容自适应 (新 step 进来时能撑开)
|
|
194
|
+
if (state.expanded)
|
|
195
|
+
body.style.maxHeight = '';
|
|
196
|
+
}, 300);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
body.style.maxHeight = '0';
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const arrow = timelineEl.querySelector('.step-timeline-arrow');
|
|
203
|
+
if (arrow)
|
|
204
|
+
arrow.style.transform = expanded ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
205
|
+
// 自动滚到底 (新节点进来时, 详情区跟到底)
|
|
206
|
+
if (expanded && listEl) {
|
|
207
|
+
listEl.scrollTop = listEl.scrollHeight;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// 公共: 创建一个空 timeline (AI 消息创建时挂的占位)
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
export function createEmptyStepTimeline(channelId = null) {
|
|
214
|
+
const root = document.createElement('div');
|
|
215
|
+
root.className = 'step-timeline';
|
|
216
|
+
root.setAttribute('data-step-timeline', '');
|
|
217
|
+
root.setAttribute('data-empty', 'true');
|
|
218
|
+
// 摘要条
|
|
219
|
+
const summary = document.createElement('div');
|
|
220
|
+
summary.className = 'step-timeline-summary';
|
|
221
|
+
summary.setAttribute('data-summary', '');
|
|
222
|
+
const titleSpan = document.createElement('span');
|
|
223
|
+
titleSpan.className = 'step-timeline-title';
|
|
224
|
+
const titleContent = document.createElement('span');
|
|
225
|
+
titleContent.setAttribute('data-current-tool', '');
|
|
226
|
+
titleContent.textContent = '执行步骤';
|
|
227
|
+
titleSpan.appendChild(titleContent);
|
|
228
|
+
const dots = document.createElement('div');
|
|
229
|
+
dots.className = 'step-timeline-dots';
|
|
230
|
+
dots.setAttribute('data-dots', '');
|
|
231
|
+
const arrow = document.createElement('span');
|
|
232
|
+
arrow.className = 'step-timeline-arrow';
|
|
233
|
+
arrow.textContent = '▾';
|
|
234
|
+
summary.appendChild(titleSpan);
|
|
235
|
+
summary.appendChild(dots);
|
|
236
|
+
summary.appendChild(arrow);
|
|
237
|
+
summary.onclick = () => {
|
|
238
|
+
const state = stateMap.get(root);
|
|
239
|
+
if (!state)
|
|
240
|
+
return;
|
|
241
|
+
state.expanded = !state.expanded;
|
|
242
|
+
writeExpanded(state.channelId, state.expanded);
|
|
243
|
+
render(root);
|
|
244
|
+
};
|
|
245
|
+
// 详情区
|
|
246
|
+
const body = document.createElement('div');
|
|
247
|
+
body.className = 'step-timeline-body';
|
|
248
|
+
body.setAttribute('data-body', '');
|
|
249
|
+
const list = document.createElement('ul');
|
|
250
|
+
list.className = 'step-timeline-list';
|
|
251
|
+
list.setAttribute('data-list', '');
|
|
252
|
+
body.appendChild(list);
|
|
253
|
+
root.appendChild(summary);
|
|
254
|
+
root.appendChild(body);
|
|
255
|
+
// 初始折叠态
|
|
256
|
+
const initialExpanded = readExpanded(channelId);
|
|
257
|
+
stateMap.set(root, {
|
|
258
|
+
steps: [],
|
|
259
|
+
currentIndex: -1,
|
|
260
|
+
expanded: initialExpanded,
|
|
261
|
+
channelId: channelId || null,
|
|
262
|
+
showAll: false,
|
|
263
|
+
});
|
|
264
|
+
render(root);
|
|
265
|
+
return root;
|
|
266
|
+
}
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// 公共: 把 timeline 挂到 message 上 (初始化或补挂)
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
export function mountStepTimeline(messageEl, channelId) {
|
|
271
|
+
// 已经挂过 → 复用
|
|
272
|
+
const existing = messageEl.querySelector('[data-step-timeline]');
|
|
273
|
+
if (existing) {
|
|
274
|
+
const state = stateMap.get(existing);
|
|
275
|
+
if (state && channelId && !state.channelId)
|
|
276
|
+
state.channelId = channelId;
|
|
277
|
+
return existing;
|
|
278
|
+
}
|
|
279
|
+
// 找插入点: 在 .bubble 之后, .message-actions 之前
|
|
280
|
+
const bubble = messageEl.querySelector('.bubble');
|
|
281
|
+
const actions = messageEl.querySelector('.message-actions');
|
|
282
|
+
const timeline = createEmptyStepTimeline(channelId);
|
|
283
|
+
if (actions && actions.parentNode === messageEl) {
|
|
284
|
+
messageEl.insertBefore(timeline, actions);
|
|
285
|
+
}
|
|
286
|
+
else if (bubble && bubble.parentNode === messageEl) {
|
|
287
|
+
bubble.parentNode.insertBefore(timeline, bubble.nextSibling);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
messageEl.appendChild(timeline);
|
|
291
|
+
}
|
|
292
|
+
return timeline;
|
|
293
|
+
}
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// 公共: 推一个 step 事件 (start/done/error)
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
export function pushStepToTimeline(timelineEl, eventType, data) {
|
|
298
|
+
const state = stateMap.get(timelineEl);
|
|
299
|
+
if (!state)
|
|
300
|
+
return;
|
|
301
|
+
const { steps } = state;
|
|
302
|
+
if (eventType === 'step_start') {
|
|
303
|
+
state.currentIndex = steps.length;
|
|
304
|
+
steps.push({
|
|
305
|
+
name: data.tool,
|
|
306
|
+
status: 'active',
|
|
307
|
+
args: data.args,
|
|
308
|
+
eventType,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
// step_done / step_error — 找当前 active 节点关闭
|
|
313
|
+
// 优先匹配 currentIndex, 其次回退到最后一个 active
|
|
314
|
+
let targetIdx = state.currentIndex;
|
|
315
|
+
if (targetIdx < 0 || !steps[targetIdx] || steps[targetIdx].name !== data.tool) {
|
|
316
|
+
// 兜底: 找最后一个同名 active
|
|
317
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
318
|
+
if (steps[i].name === data.tool && steps[i].status === 'active') {
|
|
319
|
+
targetIdx = i;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (targetIdx < 0) {
|
|
325
|
+
// 没有 active 节点匹配, 当作新 error 节点
|
|
326
|
+
targetIdx = steps.length;
|
|
327
|
+
steps.push({ name: data.tool, status: 'error', eventType });
|
|
328
|
+
}
|
|
329
|
+
if (eventType === 'step_done') {
|
|
330
|
+
steps[targetIdx].status = data.success === false ? 'error' : 'done';
|
|
331
|
+
if (data.output)
|
|
332
|
+
steps[targetIdx].output = data.output;
|
|
333
|
+
if (data.error)
|
|
334
|
+
steps[targetIdx].error = data.error;
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
steps[targetIdx].status = 'error';
|
|
338
|
+
if (data.error)
|
|
339
|
+
steps[targetIdx].error = data.error;
|
|
340
|
+
}
|
|
341
|
+
steps[targetIdx].eventType = eventType;
|
|
342
|
+
state.currentIndex = -1;
|
|
343
|
+
}
|
|
344
|
+
render(timelineEl);
|
|
345
|
+
}
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
// 公共: finalize 时把 timeline 元素从 from 搬到 to (避免重渲)
|
|
348
|
+
// 接受 timelineEl 本身或 fromEl 内部的 timeline
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
export function migrateStepTimeline(fromEl, toEl) {
|
|
351
|
+
const fromTimeline = fromEl.querySelector('[data-step-timeline]');
|
|
352
|
+
const toTimeline = toEl.querySelector('[data-step-timeline]');
|
|
353
|
+
if (!fromTimeline)
|
|
354
|
+
return;
|
|
355
|
+
// 移除 toEl 的占位, 搬 fromEl 的过来
|
|
356
|
+
if (toTimeline && toTimeline !== fromTimeline) {
|
|
357
|
+
toTimeline.remove();
|
|
358
|
+
}
|
|
359
|
+
if (toTimeline !== fromTimeline) {
|
|
360
|
+
toEl.appendChild(fromTimeline);
|
|
361
|
+
}
|
|
362
|
+
// 重渲一次 (channelId 注入)
|
|
363
|
+
const state = stateMap.get(fromTimeline);
|
|
364
|
+
if (state) {
|
|
365
|
+
render(fromTimeline);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
// 公共: 拿到一个 message 内的 timeline (无则返回 null)
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
export function getStepTimeline(messageEl) {
|
|
372
|
+
if (!messageEl)
|
|
373
|
+
return null;
|
|
374
|
+
return messageEl.querySelector('[data-step-timeline]');
|
|
375
|
+
}
|
package/lefthook.yml
CHANGED
|
@@ -13,6 +13,10 @@ pre-commit:
|
|
|
13
13
|
tsc-check:
|
|
14
14
|
glob: "src/**/*.{ts,tsx}"
|
|
15
15
|
run: npx tsc --noEmit
|
|
16
|
+
# system-prompt 字符上限 + 版本标记 + hibsml 脱除 (layer 改动时触发)
|
|
17
|
+
validate-prompt-layers:
|
|
18
|
+
glob: "src/llm/system-prompt/layers/**/*.md"
|
|
19
|
+
run: npx tsx -r dotenv/config scripts/validate-system-prompt.ts
|
|
16
20
|
|
|
17
21
|
pre-push:
|
|
18
22
|
parallel: true
|
package/package.json
CHANGED
|
@@ -31,6 +31,64 @@ import * as path from 'path';
|
|
|
31
31
|
const pExec = promisify(execFile);
|
|
32
32
|
const REPO = process.cwd();
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* P2P 协作 broadcaster (行级 reserve + commit-intent 广播)
|
|
36
|
+
* 走现有 P2PDirect + 'bolloon-agent-harness' topic, 不开新端口.
|
|
37
|
+
* 失败 → 整个 loop 仍能跑 (broadcaster = null 时跳过), 不阻塞自迭代.
|
|
38
|
+
*/
|
|
39
|
+
let broadcaster: any = null;
|
|
40
|
+
async function initBroadcaster(): Promise<void> {
|
|
41
|
+
try {
|
|
42
|
+
const { P2PDirect } = await import('../src/network/p2p-direct.js');
|
|
43
|
+
const { SourceIntentBroadcaster } = await import('../src/network/source-intent-broadcaster.js');
|
|
44
|
+
const hostname = await pExec('hostname', []).then(r => r.stdout.trim()).catch(() => 'unknown');
|
|
45
|
+
const p2p = new P2PDirect({ name: 'auto-evolve', role: 'auto-evolve-agent' });
|
|
46
|
+
await p2p.start();
|
|
47
|
+
const sb = new SourceIntentBroadcaster(p2p, {
|
|
48
|
+
agent: `agent-${hostname}`,
|
|
49
|
+
topic: 'bolloon-agent-harness',
|
|
50
|
+
});
|
|
51
|
+
await sb.start();
|
|
52
|
+
|
|
53
|
+
// P2P Hook 4: 监听远端事件, 打印到日志 (聊天框能看)
|
|
54
|
+
sb.on('remoteReserve', (m: any) => {
|
|
55
|
+
console.log(`[p2p] 远端 ${m.agent} reserve ${m.file} 行 ${m.lines[0]}-${m.lines[1]} (task=${m.taskId})`);
|
|
56
|
+
});
|
|
57
|
+
sb.on('remoteConflict', (c: any) => {
|
|
58
|
+
console.log(`[p2p] 冲突: 远端 ${c.agent} 也想改 ${JSON.stringify(c.lines)} — 我方让步 / 改方向`);
|
|
59
|
+
});
|
|
60
|
+
sb.on('remoteRelease', (m: any) => {
|
|
61
|
+
console.log(`[p2p] 远端 ${m.agent} release ${m.file} 行 ${m.lines[0]}-${m.lines[1]}`);
|
|
62
|
+
});
|
|
63
|
+
sb.on('remoteCommit', (m: any) => {
|
|
64
|
+
console.log(`[p2p] 远端 ${m.agent} commit ${m.file} sha=${m.sha} diffHash=${m.diffHash}`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 把 live reserves 注入到 LLM 系统 prompt (pi-ai Hook)
|
|
68
|
+
try {
|
|
69
|
+
const pi = await import('../src/llm/pi-ai.js');
|
|
70
|
+
pi.setSystemPrependProvider(() => {
|
|
71
|
+
const live = sb.liveReserves();
|
|
72
|
+
if (live.length === 0) return null;
|
|
73
|
+
return [
|
|
74
|
+
'【行级 P2P 协调】其他智能体正在改的代码行:',
|
|
75
|
+
...live.map(r => ` - ${r.file} 行 ${r.lines[0]}-${r.lines[1]} (${r.agent}, task=${r.taskId})`),
|
|
76
|
+
'你即将改的代码行如果重叠, 请让出或改别的行.',
|
|
77
|
+
].join('\n');
|
|
78
|
+
});
|
|
79
|
+
console.log('[p2p] systemPrependProvider 已注册');
|
|
80
|
+
} catch (err: any) {
|
|
81
|
+
console.warn(`[p2p] systemPrependProvider 注册失败: ${err.message?.slice(0, 100)}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
broadcaster = sb;
|
|
85
|
+
console.log('[p2p] broadcaster 已启动');
|
|
86
|
+
} catch (err: any) {
|
|
87
|
+
console.warn(`[p2p] broadcaster 启动失败 (loop 继续): ${err.message?.slice(0, 200)}`);
|
|
88
|
+
broadcaster = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
34
92
|
interface VitestResult {
|
|
35
93
|
failed: number;
|
|
36
94
|
passed: number;
|
|
@@ -238,6 +296,28 @@ async function commitPatch(patchId: string): Promise<boolean> {
|
|
|
238
296
|
} catch { /* binary or removed */ }
|
|
239
297
|
}
|
|
240
298
|
await pExec('git', ['commit', '-m', `auto-evolve: ${patchId} (LLM 修复)`], { cwd: REPO });
|
|
299
|
+
|
|
300
|
+
// P2P Hook 2: 广播 commit-intent (供对方智能体做轻量审计 / 触发 push)
|
|
301
|
+
if (broadcaster) {
|
|
302
|
+
try {
|
|
303
|
+
const { stdout: shaOut } = await pExec('git', ['rev-parse', '--short', 'HEAD'], { cwd: REPO });
|
|
304
|
+
const sha = shaOut.trim();
|
|
305
|
+
for (const f of files) {
|
|
306
|
+
// 简化: 整文件视为 [1, 99999], diffHash 用 patchId
|
|
307
|
+
await broadcaster.broadcastCommitIntent({
|
|
308
|
+
taskId: patchId,
|
|
309
|
+
file: f,
|
|
310
|
+
lines: [1, 99999],
|
|
311
|
+
sha,
|
|
312
|
+
diffHash: patchId.slice(0, 16),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
console.log(`[p2p] commit-intent 广播: ${files.length} 个文件, sha=${sha}`);
|
|
316
|
+
} catch (err: any) {
|
|
317
|
+
console.warn(`[p2p] commit-intent 广播失败: ${err.message?.slice(0, 100)}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
241
321
|
return true;
|
|
242
322
|
} catch (err: any) {
|
|
243
323
|
console.error(`[loop] commit 失败: ${err.message?.slice(0, 200)}`);
|
|
@@ -266,6 +346,9 @@ async function main() {
|
|
|
266
346
|
if (args[i] === '--target' && args[i + 1]) targetFile = args[++i];
|
|
267
347
|
}
|
|
268
348
|
|
|
349
|
+
// P2P Hook 1: 启动 broadcaster (失败不阻塞 loop)
|
|
350
|
+
await initBroadcaster();
|
|
351
|
+
|
|
269
352
|
// 必须先有 baseline
|
|
270
353
|
if (!(await fs.stat('.last-auto-evolve-baseline').catch(() => null))) {
|
|
271
354
|
console.log('[loop] 没 baseline, 先打一个');
|
|
@@ -301,6 +384,28 @@ async function main() {
|
|
|
301
384
|
.replace('{{SOURCE}}', sourceCtx);
|
|
302
385
|
|
|
303
386
|
console.log('[loop] 调 LLM 修...');
|
|
387
|
+
|
|
388
|
+
// P2P Hook 3: best-effort reserve 我方即将改的文件 (行级粒度, 整文件 [1, 99999])
|
|
389
|
+
// 失败/冲突 → 仅 log, 不阻塞 loop (LLM 已经能从 systemPrepend 看到 liveReserves)
|
|
390
|
+
if (broadcaster) {
|
|
391
|
+
const filesToReserve = [firstTest, ...(srcCtx.match(/^=== SRC FILE \(imported\) ===\s*\n([^\n]+)$/m)?.[1] ? [srcCtx.match(/^=== SRC FILE \(imported\) ===\s*\n([^\n]+)$/m)![1]] : [])]
|
|
392
|
+
.map(p => p.replace(REPO + '/', ''))
|
|
393
|
+
.filter((p, i, a) => p && a.indexOf(p) === i);
|
|
394
|
+
for (const f of filesToReserve) {
|
|
395
|
+
if (!f || f === 'unknown') continue;
|
|
396
|
+
try {
|
|
397
|
+
const r = await broadcaster.reserve({ taskId: patchId || `iter-${iter}`, file: f, lines: [1, 99999] });
|
|
398
|
+
if (r.ok) {
|
|
399
|
+
console.log(`[p2p] reserve OK: ${f}`);
|
|
400
|
+
} else {
|
|
401
|
+
console.log(`[p2p] reserve 冲突: ${f} 已被 ${r.existing.agent} 占用, LLM 会避开`);
|
|
402
|
+
}
|
|
403
|
+
} catch (err: any) {
|
|
404
|
+
console.warn(`[p2p] reserve 失败 (继续): ${err.message?.slice(0, 80)}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
304
409
|
const llmOut = await callLLM(prompt);
|
|
305
410
|
const diff = extractDiff(llmOut);
|
|
306
411
|
if (!diff) {
|
package/scripts/build-web.ts
CHANGED
|
@@ -51,12 +51,46 @@ async function main() {
|
|
|
51
51
|
minify: false,
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
// 2026-06-15: 编译 message-renderer.ts (对话显示 UI 模块)
|
|
55
|
+
// esbuild 编译 .ts → dist/web/ui/message-renderer.js (ESM, 浏览器侧 <script type="module"> 加载)
|
|
56
|
+
console.log('[build-web] 编译 message-renderer.ts...');
|
|
57
|
+
await fs.mkdir(path.join(DIST_WEB, 'ui'), { recursive: true });
|
|
58
|
+
await esbuild.build({
|
|
59
|
+
entryPoints: [path.join(ROOT, 'src/web/ui/message-renderer.ts')],
|
|
60
|
+
outfile: path.join(DIST_WEB, 'ui/message-renderer.js'),
|
|
61
|
+
format: 'esm',
|
|
62
|
+
target: 'es2022',
|
|
63
|
+
platform: 'browser',
|
|
64
|
+
minify: false,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 2026-06-15: 编译 step-timeline.ts (气泡内 4 状态步骤条)
|
|
68
|
+
console.log('[build-web] 编译 step-timeline.ts...');
|
|
69
|
+
await esbuild.build({
|
|
70
|
+
entryPoints: [path.join(ROOT, 'src/web/ui/step-timeline.ts')],
|
|
71
|
+
outfile: path.join(DIST_WEB, 'ui/step-timeline.js'),
|
|
72
|
+
format: 'esm',
|
|
73
|
+
target: 'es2022',
|
|
74
|
+
platform: 'browser',
|
|
75
|
+
minify: false,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// 编译主客户端入口 (classic script, 非 module; 由 index.html 以 /client.js 加载)
|
|
79
|
+
console.log('[build-web] 编译 client.ts...');
|
|
80
|
+
await esbuild.build({
|
|
81
|
+
entryPoints: [path.join(ROOT, 'src/web/client.ts')],
|
|
82
|
+
outfile: path.join(DIST_WEB, 'client.js'),
|
|
83
|
+
format: 'iife',
|
|
84
|
+
target: 'es2022',
|
|
85
|
+
platform: 'browser',
|
|
86
|
+
minify: false,
|
|
87
|
+
});
|
|
88
|
+
|
|
54
89
|
// 复制静态文件
|
|
55
90
|
console.log('[build-web] 复制静态文件...');
|
|
56
91
|
await fs.copyFile(path.join(ROOT, 'src/web/index.html'), path.join(DIST_WEB, 'index.html'));
|
|
57
92
|
await fs.copyFile(path.join(ROOT, 'src/web/api-config.html'), path.join(DIST_WEB, 'api-config.html'));
|
|
58
93
|
await fs.copyFile(path.join(ROOT, 'src/web/style.css'), path.join(DIST_WEB, 'style.css'));
|
|
59
|
-
await fs.copyFile(path.join(ROOT, 'src/web/client.js'), path.join(DIST_WEB, 'client.js'));
|
|
60
94
|
// 复制 PWA manifest (index.html 里有 <link rel="manifest">, 否则浏览器会 404)
|
|
61
95
|
await fs.copyFile(path.join(ROOT, 'src/web/manifest.json'), path.join(DIST_WEB, 'manifest.json'));
|
|
62
96
|
// 复制 icons 目录 (manifest.json 里引用了 favicon 等)
|