@bolloon/bolloon-agent 0.1.40 → 0.1.42

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.
Files changed (38) hide show
  1. package/.comm/README.md +21 -0
  2. package/.comm/default/2026-06-17T08-23-00-017Z-8a735de8.md +7 -0
  3. package/CLAUDE.md +3 -0
  4. package/dist/agents/intent-classifier.js +99 -0
  5. package/dist/agents/pi-sdk.js +1364 -58
  6. package/dist/agents/pre-tool-validator.js +2 -1
  7. package/dist/agents/shell-guard.js +9 -3
  8. package/dist/agents/shell-tool.js +28 -13
  9. package/dist/agents/task-state.js +224 -0
  10. package/dist/agents/workflow-pivot-loop.js +30 -0
  11. package/dist/bollharness-integration/gate-state-machine.js +13 -0
  12. package/dist/bollharness-integration/integration.js +71 -0
  13. package/dist/bollharness-integration/skill-adapter.js +52 -127
  14. package/dist/bootstrap/context-hierarchy.js +6 -4
  15. package/dist/cli/interface.js +28 -0
  16. package/dist/documents/reader.js +14 -0
  17. package/dist/git-transport/chat-render.js +155 -0
  18. package/dist/git-transport/chat-repo.js +370 -0
  19. package/dist/git-transport/chat-types.js +23 -0
  20. package/dist/git-transport/chat-watch.js +102 -0
  21. package/dist/index.js +471 -25
  22. package/dist/llm/pi-ai.js +103 -27
  23. package/dist/network/local-inbox-bus.js +73 -0
  24. package/dist/network/p2p-direct.js +3 -4
  25. package/dist/pi-ecosystem-judgment/adaptive-scan.js +6 -2
  26. package/dist/pi-ecosystem-judgment/causal-judge.js +3 -3
  27. package/dist/pi-ecosystem-judgment/index.js +1 -1
  28. package/dist/security/tool-gate.js +11 -0
  29. package/dist/web/client.js +2876 -3770
  30. package/dist/web/components/p2p/index.js +226 -264
  31. package/dist/web/server.js +83 -15
  32. package/dist/web/ui/message-renderer.js +326 -442
  33. package/dist/web/ui/step-timeline.js +255 -351
  34. package/package.json +2 -2
  35. package/scripts/lefthook-helper.sh +69 -0
  36. package/dist/web/components/p2p/P2PModal.js +0 -188
  37. package/dist/web/components/p2p/p2p-modal.js +0 -657
  38. package/dist/web/components/p2p/p2p-tools.js +0 -248
@@ -769,6 +769,50 @@ async function handleV3P2PMessage(parsed, conn, comm) {
769
769
  }
770
770
  return;
771
771
  }
772
+ // v3 新增: --collab CLI 派任务过来, 对方 web 收到后跑 LLM 干活, 回结果
773
+ if (op === 'agent.collab.run') {
774
+ const { requestId, task, fromPublicKey, fromRole, timeoutMs } = parsed.payload || {};
775
+ if (!requestId || !task) {
776
+ console.warn(`[v3-collab] agent.collab.run 缺少 requestId/task`);
777
+ return;
778
+ }
779
+ const senderKey = fromPublicKey || peerKey;
780
+ console.log(`[v3-collab] 收到 ${senderKey.substring(0, 12)}... (${fromRole}) 的协作任务: "${task.substring(0, 60)}..."`);
781
+ const startTs = Date.now();
782
+ try {
783
+ // 调 LLM: 走 getAgentForChannel 拿 agent, 用 prompt() 拿 string 结果
784
+ const visitorHint = `[系统上下文] 协作任务来源: 远端 peer (P2P, publicKey=${senderKey.substring(0, 12)}..., role=${fromRole || 'unknown'}). 这是通过 P2P 派过来的协作任务, 回答后会自动回到对方那里. 简洁完成, 不需要反问.\n\n`;
785
+ const fullPrompt = `【本轮协作任务】\n${task}\n【任务结束】\n\n${visitorHint}`;
786
+ // 临时用 channelId = "__collab__" 拿个一次性 agent
787
+ const collabChannelId = `__collab_${senderKey.substring(0, 8)}__`;
788
+ const agent = await getAgentForChannel(collabChannelId, '', `collab-${senderKey.substring(0, 8)}`, undefined);
789
+ const resultText = await agent.prompt(fullPrompt);
790
+ const reply = JSON.stringify({
791
+ v: 3,
792
+ op: 'agent.collab.reply',
793
+ payload: {
794
+ requestId,
795
+ fromPublicKey: v3P2PRef?.getPublicKey() || '',
796
+ result: resultText || '(empty)',
797
+ durationMs: Date.now() - startTs,
798
+ },
799
+ });
800
+ await comm.sendToConnection(conn.id, reply);
801
+ console.log(`[v3-collab] 已回 reply (${Date.now() - startTs}ms, ${(resultText || '').length} chars)`);
802
+ }
803
+ catch (e) {
804
+ console.error(`[v3-collab] 处理失败:`, e.message);
805
+ try {
806
+ const errReply = JSON.stringify({
807
+ v: 3, op: 'agent.collab.reply',
808
+ payload: { requestId, fromPublicKey: v3P2PRef?.getPublicKey() || '', error: e.message, result: '' }
809
+ });
810
+ await comm.sendToConnection(conn.id, errReply);
811
+ }
812
+ catch { }
813
+ }
814
+ return;
815
+ }
772
816
  // v3 新增: 收到远端发来的 @-mention 跨渠道消息, 存到本地 target channel
773
817
  if (op === 'agent.cross.post') {
774
818
  const { targetChannelId, targetChannelName, originChannelId, originChannelName, text, fromPublicKey } = parsed.payload || {};
@@ -936,7 +980,14 @@ async function getAgentForChannel(channelId, channelDid, channelName, channelDid
936
980
  const session = await createAgentSession({
937
981
  cwd: process.cwd(),
938
982
  peerId: `channel-${channelId}:${currentSessionId}`,
939
- identityDoc
983
+ identityDoc,
984
+ // M2.3 (2026-06-17): 构造时从 session JSON 回灌历史 — 服务重启后 LLM 仍记得前面对话
985
+ // key 跟 server.ts:240 写入路径保持一致: ~/.bolloon/sessions/cache/<key>.json
986
+ loadSessionKey: sessionKey,
987
+ // M3.1 (2026-06-17): 默认启用 WorkflowPivotLoop — web 路径以前死代码, 现在用
988
+ // pivot loop 自带 quality scoring + task complexity analysis + 30 iters cap
989
+ // (老 ReAct 10000 iter cap 容易跑飞)
990
+ usePivotLoop: true,
940
991
  }, true); // forceNew: true 强制创建新实例
941
992
  channelSessions.set(sessionKey, session);
942
993
  if (channelDid) {
@@ -1334,20 +1385,31 @@ export async function createWebServer(port = 3000, options = {}) {
1334
1385
  app.get('/', (req, res) => {
1335
1386
  res.sendFile(join(webRoot, 'index.html'));
1336
1387
  });
1337
- app.get('/api-config', (req, res) => {
1338
- // 防御: sendFile 在文件缺失时会异步抛 NotFoundError, 这里用同步读 + send 兜底
1339
- const filePath = join(webRoot, 'api-config.html');
1340
- if (!fsSync.existsSync(filePath)) {
1341
- // 回退到 SPA 主页, 避免 404 崩溃
1342
- return res.status(404).type('text/plain').send('api-config.html not found; please run `npm run build:web`');
1343
- }
1344
- res.sendFile(filePath, (err) => {
1345
- if (err && !res.headersSent) {
1346
- console.error('[api-config] sendFile failed:', err.message);
1347
- res.status(500).type('text/plain').send('api-config.html send error: ' + err.message);
1388
+ // 2026-06-17: /api-config / 改用 fs.readFileSync + res.send 兑底,
1389
+ // 不再依赖 express.sendFile. 原因: npm 全局安装路径下, send library 偶发报
1390
+ // "Not Found" 即使 fs.existsSync+fs.stat 都成功. 现象: 页面 200 但内容是
1391
+ // "api-config.html send error: Not Found" 5xx 错误页. 同步读 + res.type().send
1392
+ // 可靠返回原始 HTML bytes.
1393
+ function serveStaticHtml(relPath, notFoundMsg, label) {
1394
+ return (_req, res) => {
1395
+ const filePath = join(webRoot, relPath);
1396
+ if (!fsSync.existsSync(filePath)) {
1397
+ return res.status(404).type('text/plain').send(notFoundMsg);
1348
1398
  }
1349
- });
1350
- });
1399
+ try {
1400
+ const html = fsSync.readFileSync(filePath);
1401
+ res.type('text/html; charset=utf-8').send(html);
1402
+ }
1403
+ catch (err) {
1404
+ console.error(`[${label}] readFileSync failed:`, err.message);
1405
+ if (!res.headersSent) {
1406
+ res.status(500).type('text/plain').send(`${relPath} read error: ${err.message}`);
1407
+ }
1408
+ }
1409
+ };
1410
+ }
1411
+ app.get('/', serveStaticHtml('index.html', 'index.html not found; please run `npm run build:web`', 'index'));
1412
+ app.get('/api-config', serveStaticHtml('api-config.html', 'api-config.html not found; please run `npm run build:web`', 'api-config'));
1351
1413
  // 全局兜底: 任何 next(err) 走到这里, 给出结构化 4xx/5xx 而不是默认 HTML
1352
1414
  app.use((err, req, res, _next) => {
1353
1415
  console.error('[server] unhandled error on', req.method, req.path, '-', err?.message || err);
@@ -1654,13 +1716,19 @@ export async function createWebServer(port = 3000, options = {}) {
1654
1716
  console.log(`[chat] aborted channel=${channelId}`);
1655
1717
  }
1656
1718
  else {
1657
- throw err;
1719
+ // M1.2 (2026-06-17): 不再 rethrow — 把错误塞到 fullResponse 让 broadcast 出来, 后续 session 仍保存
1720
+ // 旧行为: 抛到外层 catch, 触发 500 + 用户消息丢失 (session 不保存)
1721
+ console.error(`[chat] LLM 调用失败 channel=${channelId}:`, err);
1722
+ fullResponse = `[错误: LLM 调用失败] ${String(err?.message || err).slice(0, 300)}`;
1723
+ broadcast({ type: 'error', content: fullResponse }, channelId);
1658
1724
  }
1659
1725
  }
1660
1726
  // abort 模式: 给 partial 拼后缀
1661
1727
  if (runState.abortController?.signal.aborted && fullResponse.trim().length > 0) {
1662
1728
  fullResponse = fullResponse + '\n\n_[生成已中断]_';
1663
1729
  }
1730
+ // 2026-06-18: 修 lastFinalReply 没设 → /api/loop/inspect 永远返回空
1731
+ runState.lastFinalReply = fullResponse;
1664
1732
  // v3 新增: 解析 LLM 回复里的 @-mentions, 转发到目标 channel
1665
1733
  await routeMentionsInReply(channelId, fullResponse, localChannels, remoteChannels);
1666
1734
  broadcast({ type: 'ai', content: fullResponse }, channelId);