@bolloon/bolloon-agent 0.1.1 → 0.1.2

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 (200) hide show
  1. package/bin/bolloon-cli.cjs +157 -0
  2. package/bin/bolloon-daemon.sh +207 -0
  3. package/bin/bolloon.cmd +11 -0
  4. package/dist/agents/constraint-layer.js +10 -15
  5. package/dist/agents/pi-sdk.js +433 -106
  6. package/dist/agents/protocol.js +82 -1
  7. package/dist/agents/subagent-manager.js +2 -2
  8. package/dist/agents/workflow-engine.js +15 -20
  9. package/dist/agents/workflow-pivot-loop.js +541 -0
  10. package/dist/bollharness/src/index.js +5 -0
  11. package/dist/bollharness/src/scripts/checks/check_adr_plan_numbering.js +6 -0
  12. package/dist/bollharness/src/scripts/checks/check_api_types.js +45 -0
  13. package/dist/bollharness/src/scripts/checks/check_artifact_link.js +146 -0
  14. package/dist/bollharness/src/scripts/checks/check_bridge_deps.js +6 -0
  15. package/dist/bollharness/src/scripts/checks/check_bugfix_binding.js +6 -0
  16. package/dist/bollharness/src/scripts/checks/check_bugfix_binding_ci.js +6 -0
  17. package/dist/bollharness/src/scripts/checks/check_doc_file_references.js +6 -0
  18. package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +135 -0
  19. package/dist/bollharness/src/scripts/checks/check_doc_links.js +31 -0
  20. package/dist/bollharness/src/scripts/checks/check_file_existence_claims.js +6 -0
  21. package/dist/bollharness/src/scripts/checks/check_fragment_integrity.js +34 -0
  22. package/dist/bollharness/src/scripts/checks/check_hook_installed.js +63 -0
  23. package/dist/bollharness/src/scripts/checks/check_issue_closure.js +41 -0
  24. package/dist/bollharness/src/scripts/checks/check_mcp_parity.js +6 -0
  25. package/dist/bollharness/src/scripts/checks/check_security.js +48 -0
  26. package/dist/bollharness/src/scripts/checks/check_skill_parity.js +6 -0
  27. package/dist/bollharness/src/scripts/checks/check_versions.js +6 -0
  28. package/dist/bollharness/src/scripts/checks/finding.js +13 -0
  29. package/dist/bollharness/src/scripts/checks/next_decision_number.js +20 -0
  30. package/dist/bollharness/src/scripts/checks/regenerate_magic_docs.js +6 -0
  31. package/dist/bollharness/src/scripts/ci/detect_rebaseline_triggers.js +8 -0
  32. package/dist/bollharness/src/scripts/ci/scan_subprocess_cfg.js +8 -0
  33. package/dist/bollharness/src/scripts/ci/scan_verify_artifacts.js +8 -0
  34. package/dist/bollharness/src/scripts/ci/scan_yaml_schema.js +8 -0
  35. package/dist/bollharness/src/scripts/context_router.js +67 -0
  36. package/dist/bollharness/src/scripts/deploy-guard.js +157 -0
  37. package/dist/bollharness/src/scripts/guard-feedback.js +192 -0
  38. package/dist/bollharness/src/scripts/guard_router.js +158 -0
  39. package/dist/bollharness/src/scripts/hooks/_hook_output.js +6 -0
  40. package/dist/bollharness/src/scripts/hooks/auto-python3.js +6 -0
  41. package/dist/bollharness/src/scripts/hooks/deploy-progress-on-session-end.js +6 -0
  42. package/dist/bollharness/src/scripts/hooks/failure-analyzer.js +6 -0
  43. package/dist/bollharness/src/scripts/hooks/gate-judgment-inject.js +92 -0
  44. package/dist/bollharness/src/scripts/hooks/gate-transition-judgment.js +63 -0
  45. package/dist/bollharness/src/scripts/hooks/inbox-ack.js +6 -0
  46. package/dist/bollharness/src/scripts/hooks/inbox-inject-on-start.js +6 -0
  47. package/dist/bollharness/src/scripts/hooks/inbox-validate.js +6 -0
  48. package/dist/bollharness/src/scripts/hooks/inbox-write-ledger.js +6 -0
  49. package/dist/bollharness/src/scripts/hooks/initializer-agent.js +6 -0
  50. package/dist/bollharness/src/scripts/hooks/loop-detection.js +73 -0
  51. package/dist/bollharness/src/scripts/hooks/owner-guard.js +6 -0
  52. package/dist/bollharness/src/scripts/hooks/precompact.js +6 -0
  53. package/dist/bollharness/src/scripts/hooks/review-agent-gatekeeper.js +6 -0
  54. package/dist/bollharness/src/scripts/hooks/risk-tracker.js +108 -0
  55. package/dist/bollharness/src/scripts/hooks/sanitize-on-read.js +6 -0
  56. package/dist/bollharness/src/scripts/hooks/session-reflection.js +7 -0
  57. package/dist/bollharness/src/scripts/hooks/session-start-magic-docs.js +7 -0
  58. package/dist/bollharness/src/scripts/hooks/session-start-reset-risk.js +7 -0
  59. package/dist/bollharness/src/scripts/hooks/session-start-toolkit-reminder.js +7 -0
  60. package/dist/bollharness/src/scripts/hooks/stop-evaluator.js +157 -0
  61. package/dist/bollharness/src/scripts/hooks/tool-call-counter.js +6 -0
  62. package/dist/bollharness/src/scripts/hooks/trace-analyzer.js +10 -0
  63. package/dist/bollharness/src/scripts/install/install-trust-token.js +7 -0
  64. package/dist/bollharness/src/scripts/install/multi_project_registry.js +9 -0
  65. package/dist/bollharness/src/scripts/install/phase2_auto.js +21 -0
  66. package/dist/bollharness/src/scripts/install/pre_commit_installer.js +6 -0
  67. package/dist/bollharness/src/scripts/install/tier_selector.js +7 -0
  68. package/dist/bollharness/src/scripts/install/transcript_miner.js +7 -0
  69. package/dist/bollharness/src/scripts/lib/claim_patterns.js +10 -0
  70. package/dist/bollharness/src/scripts/lib/sanitize_patterns.js +12 -0
  71. package/dist/bollharness/src/scripts/sanitize.js +6 -0
  72. package/dist/bollharness-integration/channel-judgment-engine.js +530 -0
  73. package/dist/bollharness-integration/context-chain-router.js +383 -0
  74. package/dist/bollharness-integration/context-router-judgment.js +13 -21
  75. package/dist/bollharness-integration/context-router.js +22 -64
  76. package/dist/bollharness-integration/gate-state-machine.js +14 -19
  77. package/dist/bollharness-integration/gate-transition-hooks.js +16 -61
  78. package/dist/bollharness-integration/guard-checker.js +21 -68
  79. package/dist/bollharness-integration/index.js +14 -124
  80. package/dist/bollharness-integration/integration.js +13 -20
  81. package/dist/bollharness-integration/llm-judgment-engine.js +569 -0
  82. package/dist/bollharness-integration/skill-adapter.js +18 -64
  83. package/dist/cli-entry.js +261 -0
  84. package/dist/constraint-runtime/src/commands.js +17 -7
  85. package/dist/constraint-runtime/src/constraint/budget.js +1 -6
  86. package/dist/constraint-runtime/src/constraint/permission.js +1 -6
  87. package/dist/constraint-runtime/src/models.js +1 -3
  88. package/dist/constraint-runtime/src/tools.js +17 -7
  89. package/dist/constraints/index.js +1 -7
  90. package/dist/documents/reader.js +8 -49
  91. package/dist/heartbeat/DaemonManager.js +242 -0
  92. package/dist/heartbeat/HealthMonitor.js +285 -0
  93. package/dist/heartbeat/StartupVerifier.js +205 -0
  94. package/dist/heartbeat/Watchdog.js +168 -0
  95. package/dist/heartbeat/index.js +84 -0
  96. package/dist/heartbeat/types.js +5 -0
  97. package/dist/index.js +381 -28
  98. package/dist/llm/config-store.js +31 -57
  99. package/dist/llm/llm-judgment-client.js +389 -0
  100. package/dist/llm/pi-ai.js +9 -52
  101. package/dist/network/agent-network.js +46 -90
  102. package/dist/network/hybrid-messenger.js +125 -0
  103. package/dist/network/iroh-bootstrap.js +38 -0
  104. package/dist/network/iroh-discovery.js +145 -0
  105. package/dist/network/iroh-integration.js +9 -16
  106. package/dist/network/iroh-transport.js +10 -48
  107. package/dist/network/p2p.js +23 -62
  108. package/dist/network/storage/adapters/json-adapter.js +4 -42
  109. package/dist/network/storage/index.js +147 -0
  110. package/dist/network/storage/types.js +14 -0
  111. package/dist/pi-ecosystem/index.js +233 -0
  112. package/dist/pi-ecosystem-colony/index.js +29 -90
  113. package/dist/pi-ecosystem-goals/index.js +20 -74
  114. package/dist/pi-ecosystem-judgment/decision.js +29 -47
  115. package/dist/pi-ecosystem-judgment/distillation.js +16 -29
  116. package/dist/pi-ecosystem-judgment/human-value-store.js +13 -60
  117. package/dist/pi-ecosystem-judgment/index.js +21 -74
  118. package/dist/pi-ecosystem-judgment/value-injection.js +26 -72
  119. package/dist/pi-ecosystem-mcp/index.js +24 -78
  120. package/dist/pi-ecosystem-subagents/index.js +20 -69
  121. package/dist/social/ant-colony/AdaptiveHeartbeat.js +3 -8
  122. package/dist/social/ant-colony/PheromoneEngine.js +11 -49
  123. package/dist/social/ant-colony/index.js +6 -0
  124. package/dist/social/ant-colony/types.js +4 -8
  125. package/dist/social/channels/ChannelManager.js +8 -46
  126. package/dist/social/channels/DiapChannelBridge.js +9 -47
  127. package/dist/social/channels/InterestMatcher.js +2 -7
  128. package/dist/social/channels/channel-agent-session.js +309 -0
  129. package/dist/social/channels/channel-heartbeat-agent.js +494 -0
  130. package/dist/social/channels/diap-doc-parser.js +204 -0
  131. package/dist/social/channels/harness-workflow-integrator.js +446 -0
  132. package/dist/social/channels/index.js +9 -0
  133. package/dist/social/channels/types.js +3 -7
  134. package/dist/social/global-shared-context.js +6 -47
  135. package/dist/social/heartbeat.js +29 -72
  136. package/dist/social/persona/enhanced-persona.js +299 -0
  137. package/dist/web/client.js +302 -136
  138. package/dist/web/components/p2p/index.js +159 -9
  139. package/dist/web/components/p2p/p2p-connection.js +136 -0
  140. package/dist/web/components/p2p/p2p-manager.js +24 -0
  141. package/dist/web/components/p2p/p2p-store-memory.js +1 -1
  142. package/dist/web/components/p2p/types.js +7 -0
  143. package/dist/web/index.html +5 -0
  144. package/dist/web/style.css +118 -0
  145. package/package.json +12 -6
  146. package/scripts/build-cli.js +206 -0
  147. package/scripts/postinstall.js +153 -0
  148. package/src/agents/pi-sdk.ts +347 -28
  149. package/src/agents/protocol.ts +95 -1
  150. package/src/agents/workflow-pivot-loop.ts +674 -0
  151. package/src/bollharness/CLAUDE.md +73 -0
  152. package/src/bollharness/README.md +143 -0
  153. package/src/bollharness/README.zh-CN.md +131 -0
  154. package/src/bollharness/reference/boll-reference/scripts/hooks/stop-evaluator.md +57 -0
  155. package/src/bollharness/scripts/context-fragments/artifact-linkage.md +14 -0
  156. package/src/bollharness/scripts/context-fragments/auth-consumers.md +17 -0
  157. package/src/bollharness/scripts/context-fragments/bridge-constitution.md +13 -0
  158. package/src/bollharness/scripts/context-fragments/catalyst-distributed.md +18 -0
  159. package/src/bollharness/scripts/context-fragments/closure-checklist.md +13 -0
  160. package/src/bollharness/scripts/context-fragments/contract-consumers.md +15 -0
  161. package/src/bollharness/scripts/context-fragments/db-shared-structures.md +15 -0
  162. package/src/bollharness/scripts/context-fragments/fixed-three-layers.md +19 -0
  163. package/src/bollharness/scripts/context-fragments/general-dev-principles.md +11 -0
  164. package/src/bollharness/scripts/context-fragments/issue-first.md +8 -0
  165. package/src/bollharness/scripts/context-fragments/mcp-parity.md +16 -0
  166. package/src/bollharness/scripts/context-fragments/pi-agent-operations.md +108 -0
  167. package/src/bollharness/scripts/context-fragments/protocol-consumers.md +15 -0
  168. package/src/bollharness/scripts/context-fragments/run-events-consumers.md +15 -0
  169. package/src/bollharness/scripts/context-fragments/scene-fidelity.md +13 -0
  170. package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +15 -0
  171. package/src/bollharness/scripts/context-fragments/two-language.md +15 -0
  172. package/src/bollharness/scripts/context-fragments/version-sources.md +14 -0
  173. package/src/bollharness/scripts/hooks/stop-evaluator.md +83 -0
  174. package/src/bollharness/templates/scaffold/CLAUDE.md +89 -0
  175. package/src/cli-entry.ts +304 -0
  176. package/src/heartbeat/DaemonManager.ts +283 -0
  177. package/src/heartbeat/HealthMonitor.ts +316 -0
  178. package/src/heartbeat/StartupVerifier.ts +223 -0
  179. package/src/heartbeat/Watchdog.ts +198 -0
  180. package/src/heartbeat/index.ts +108 -0
  181. package/src/heartbeat/types.ts +82 -0
  182. package/src/llm/config-store.ts +23 -5
  183. package/src/network/iroh-transport.ts +3 -3
  184. package/src/web/client.js +302 -136
  185. package/src/web/components/p2p/P2PModal.tsx +91 -3
  186. package/src/web/components/p2p/index.ts +171 -9
  187. package/src/web/components/p2p/p2p-connection.ts +153 -1
  188. package/src/web/components/p2p/p2p-manager.ts +39 -1
  189. package/src/web/components/p2p/p2p-store-memory.ts +1 -1
  190. package/src/web/components/p2p/p2p-tools.ts +315 -0
  191. package/src/web/components/p2p/types.ts +58 -0
  192. package/src/web/design.md +99 -0
  193. package/src/web/index.html +5 -0
  194. package/src/web/server.ts +353 -36
  195. package/src/web/style.css +118 -0
  196. package/tsconfig.cli.json +16 -0
  197. package/tsconfig.electron.json +1 -1
  198. package/tsconfig.json +1 -2
  199. package/dist/web/server.js +0 -1647
  200. package/dist/web/server.js.map +0 -1
package/src/web/server.ts CHANGED
@@ -51,6 +51,15 @@ interface Channel {
51
51
  didDocument?: any;
52
52
  createdAt: string;
53
53
  updatedAt: string;
54
+ currentSessionId?: string;
55
+ sessions?: SessionSummary[];
56
+ }
57
+
58
+ interface SessionSummary {
59
+ id: string;
60
+ createdAt: string;
61
+ messageCount: number;
62
+ preview: string;
54
63
  }
55
64
 
56
65
  interface SessionMessage {
@@ -315,7 +324,8 @@ interface SSEClient {
315
324
  }
316
325
 
317
326
  let sseClients: Set<SSEClient> = new Set();
318
- let channelSessions: Map<string, AgentSession> = new Map();
327
+ let channelSessions: Map<string, AgentSession> = new Map(); // key: channelId
328
+ let sessionMessages: Map<string, any[]> = new Map(); // key: channelId + sessionId
319
329
 
320
330
  async function getAgentForChannel(
321
331
  channelId: string,
@@ -323,10 +333,19 @@ async function getAgentForChannel(
323
333
  channelName?: string,
324
334
  channelDidDoc?: any
325
335
  ): Promise<AgentSession> {
326
- const existingSession = channelSessions.get(channelId);
336
+ // 获取当前 channel 的 currentSessionId
337
+ const channels = await loadChannels();
338
+ const channel = channels.find(c => c.id === channelId);
339
+ const currentSessionId = channel?.currentSessionId || 'default';
340
+ const sessionKey = `${channelId}:${currentSessionId}`;
341
+
342
+ console.log(`[Agent] 获取频道 ${channelId} 的 session, sessionKey = ${sessionKey}`);
343
+
344
+ const existingSession = channelSessions.get(sessionKey);
327
345
 
328
346
  // 如果已有 session,检查是否需要更新 identity
329
347
  if (existingSession) {
348
+ console.log(`[Agent] 找到现有 session: ${sessionKey}`);
330
349
  const currentIdentity = existingSession.getIdentity();
331
350
 
332
351
  // 如果当前 identity 没有真实 DID,或者 DID 与频道的 DID 不匹配,需要重建
@@ -362,17 +381,18 @@ async function getAgentForChannel(
362
381
  ipnsName: channelDidDoc?.ipnsName
363
382
  } : undefined;
364
383
 
384
+ console.log(`[Agent] 创建新 session: ${sessionKey}`);
365
385
  const session = await createAgentSession({
366
386
  cwd: process.cwd(),
367
- peerId: `channel-${channelId}`,
387
+ peerId: `channel-${channelId}:${currentSessionId}`,
368
388
  identityDoc
369
- });
370
- channelSessions.set(channelId, session);
389
+ }, true); // forceNew: true 强制创建新实例
390
+ channelSessions.set(sessionKey, session);
371
391
 
372
392
  if (channelDid) {
373
- console.log(`[Agent] 新建频道 ${channelId} session, DID = ${channelDid}, CID = ${channelDidDoc?.cid || '无'}`);
393
+ console.log(`[Agent] 新建频道 ${channelId} session, DID = ${channelDid}, sessionId = ${currentSessionId}`);
374
394
  } else {
375
- console.log(`[Agent] 新建频道 ${channelId} session, 使用默认身份`);
395
+ console.log(`[Agent] 新建频道 ${channelId} session, 使用默认身份, sessionId = ${currentSessionId}`);
376
396
  }
377
397
 
378
398
  return session;
@@ -541,12 +561,15 @@ export async function createWebServer(port: number = 3000) {
541
561
  }
542
562
  } else if (event.type === 'status' || event.type === 'tool') {
543
563
  broadcast({ type: 'status', tool: event.tool, content: event.content }, channelId);
544
- broadcast({ type: 'workflow_step', step: event.tool || '状态', content: event.content }, channelId);
564
+ broadcast({ type: 'workflow_step', step: event.tool || '系统', content: event.content }, channelId);
565
+ console.log(`[SSE 广播] workflow_step: step=${event.tool}, content="${event.content?.substring(0, 80)}..."`);
545
566
  } else if (event.type === 'error') {
546
567
  broadcast({ type: 'error', content: event.content }, channelId);
547
568
  }
548
569
  };
549
570
 
571
+ console.log(`[消息处理] 开始处理用户消息, channelId: ${channelId}`);
572
+
550
573
  // 将真实 DID 作为上下文前缀,让 AI 使用真实的 DID 而不是自己编造的
551
574
  const contextHint = realChannelDid ? `[系统上下文] 当前频道名称: ${realChannelName}, 你的真实 DID: ${realChannelDid}\n\n` : '';
552
575
  fullResponse = await agent.promptStream(contextHint + text, streamCallback);
@@ -672,6 +695,13 @@ app.get('/channels', async (_req, res) => {
672
695
  agentId,
673
696
  createdAt: new Date().toISOString(),
674
697
  updatedAt: new Date().toISOString(),
698
+ currentSessionId: `sess_${Date.now()}`,
699
+ sessions: [{
700
+ id: `sess_${Date.now()}`,
701
+ createdAt: new Date().toISOString(),
702
+ messageCount: 0,
703
+ preview: ''
704
+ }]
675
705
  };
676
706
 
677
707
  console.log(`[创建频道] 先保存频道 ID: ${id}`);
@@ -714,6 +744,85 @@ app.get('/channels', async (_req, res) => {
714
744
  }
715
745
  });
716
746
 
747
+ // 创建新 Session(在现有 Channel 下)
748
+ app.post('/channels/:channelId/sessions', async (req, res) => {
749
+ try {
750
+ const { channelId } = req.params;
751
+ const channels = await loadChannels();
752
+ const channel = channels.find(c => c.id === channelId);
753
+
754
+ if (!channel) {
755
+ return res.status(404).json({ error: 'Channel not found' });
756
+ }
757
+
758
+ const sessionId = `sess_${Date.now()}`;
759
+ const session: SessionSummary = {
760
+ id: sessionId,
761
+ createdAt: new Date().toISOString(),
762
+ messageCount: 0,
763
+ preview: ''
764
+ };
765
+
766
+ if (!channel.sessions) {
767
+ channel.sessions = [];
768
+ }
769
+ channel.sessions.push(session);
770
+ channel.currentSessionId = sessionId;
771
+ channel.updatedAt = new Date().toISOString();
772
+
773
+ await saveChannels(channels);
774
+ await saveSession({ channelId, sessionId, messages: [], lastUpdated: new Date().toISOString() });
775
+
776
+ res.json({ session, currentSessionId: sessionId });
777
+ } catch (err: any) {
778
+ console.error('[创建Session] 错误:', err);
779
+ res.status(500).json({ error: err.message });
780
+ }
781
+ });
782
+
783
+ // 获取 Channel 下的所有 Sessions
784
+ app.get('/channels/:channelId/sessions', async (req, res) => {
785
+ try {
786
+ const { channelId } = req.params;
787
+ const channels = await loadChannels();
788
+ const channel = channels.find(c => c.id === channelId);
789
+
790
+ if (!channel) {
791
+ return res.status(404).json({ error: 'Channel not found' });
792
+ }
793
+
794
+ res.json({
795
+ sessions: channel.sessions || [],
796
+ currentSessionId: channel.currentSessionId
797
+ });
798
+ } catch (err: any) {
799
+ console.error('[获取Sessions] 错误:', err);
800
+ res.status(500).json({ error: err.message });
801
+ }
802
+ });
803
+
804
+ // 切换 Session
805
+ app.post('/channels/:channelId/sessions/:sessionId/switch', async (req, res) => {
806
+ try {
807
+ const { channelId, sessionId } = req.params;
808
+ const channels = await loadChannels();
809
+ const channel = channels.find(c => c.id === channelId);
810
+
811
+ if (!channel) {
812
+ return res.status(404).json({ error: 'Channel not found' });
813
+ }
814
+
815
+ channel.currentSessionId = sessionId;
816
+ channel.updatedAt = new Date().toISOString();
817
+ await saveChannels(channels);
818
+
819
+ res.json({ ok: true, currentSessionId: sessionId });
820
+ } catch (err: any) {
821
+ console.error('[切换Session] 错误:', err);
822
+ res.status(500).json({ error: err.message });
823
+ }
824
+ });
825
+
717
826
  app.delete('/channels/:channelId', async (req, res) => {
718
827
  try {
719
828
  const { channelId } = req.params;
@@ -1229,18 +1338,29 @@ app.get('/channels', async (_req, res) => {
1229
1338
  createdAt: new Date().toISOString()
1230
1339
  };
1231
1340
 
1232
- // 发布到 IPFS
1233
- const formData = new FormData();
1234
- const blob = new Blob([JSON.stringify(nodeDoc)], { type: 'application/json' });
1235
- formData.append('file', blob, 'node-info.json');
1236
-
1237
- const ipfsRes = await fetch(`${IPFS_ENDPOINT}/api/v0/add`, {
1238
- method: 'POST',
1239
- body: formData
1240
- });
1241
- const ipfsResult = await ipfsRes.text();
1242
- const cidMatch = ipfsResult.match(/"Hash":"([^"]+)"/);
1243
- const cid = cidMatch ? cidMatch[1] : '';
1341
+ // 发布到 IPFS(可选,如果 IPFS 不可用则跳过)
1342
+ let cid = '';
1343
+ try {
1344
+ const formData = new FormData();
1345
+ const blob = new Blob([JSON.stringify(nodeDoc)], { type: 'application/json' });
1346
+ formData.append('file', blob, 'node-info.json');
1347
+
1348
+ const ipfsRes = await fetch(`${IPFS_ENDPOINT}/api/v0/add`, {
1349
+ method: 'POST',
1350
+ body: formData,
1351
+ signal: AbortSignal.timeout(5000)
1352
+ });
1353
+ const ipfsResult = await ipfsRes.text();
1354
+ const cidMatch = ipfsResult.match(/"Hash":"([^"]+)"/);
1355
+ cid = cidMatch ? cidMatch[1] : '';
1356
+ console.log(`[iroh API] CID 发布成功: ${cid.substring(0, 20)}...`);
1357
+ } catch (ipfsErr) {
1358
+ console.warn('[iroh API] IPFS 不可用,跳过 CID 发布:', (ipfsErr as Error).message);
1359
+ // 生成一个假的 CID 用于本地测试(格式:Qm + 44个随机字符)
1360
+ const randomPart = Array.from({ length: 44 }, () => Math.random().toString(36)[2]).join('').substring(0, 44);
1361
+ cid = `Qm${randomPart}`;
1362
+ console.log(`[iroh API] 使用本地 CID: ${cid.substring(0, 20)}...`);
1363
+ }
1244
1364
 
1245
1365
  irohNodeInfo = {
1246
1366
  did,
@@ -1300,7 +1420,7 @@ app.get('/channels', async (_req, res) => {
1300
1420
  });
1301
1421
  });
1302
1422
 
1303
- // 通过 CID 连接到其他节点
1423
+ // 通过 CID 或 Node ID 连接到其他节点
1304
1424
  app.post('/api/iroh/connect', async (req, res) => {
1305
1425
  try {
1306
1426
  const { cid } = req.body;
@@ -1313,21 +1433,38 @@ app.get('/channels', async (_req, res) => {
1313
1433
  return res.status(500).json({ error: 'iroh not initialized' });
1314
1434
  }
1315
1435
 
1316
- console.log(`[iroh API] 连接到 CID: ${cid}`);
1436
+ let targetNodeId: string;
1437
+ let nodeName = 'Unknown';
1317
1438
 
1318
- // IPFS 获取节点信息
1319
- const ipfsRes = await fetch(`${IPFS_ENDPOINT}/api/v0/cat?arg=${cid}`, {
1320
- method: 'POST'
1321
- });
1322
- const content = await ipfsRes.text();
1323
- const doc = JSON.parse(content);
1439
+ console.log(`[iroh API] 连接到: ${cid}`);
1324
1440
 
1325
- if (!doc.irohNodeId) {
1326
- return res.status(400).json({ error: '节点信息中不包含 irohNodeId' });
1327
- }
1441
+ // 检查是 Node ID(64字符十六进制)还是 CID
1442
+ const isNodeId = /^[a-f0-9]{64}$/i.test(cid);
1328
1443
 
1329
- const targetNodeId = doc.irohNodeId;
1330
- console.log(`[iroh API] 目标节点: ${targetNodeId.substring(0, 20)}...`);
1444
+ if (isNodeId) {
1445
+ // 直接使用 Node ID
1446
+ targetNodeId = cid;
1447
+ console.log(`[iroh API] 使用直接 Node ID: ${targetNodeId.substring(0, 20)}...`);
1448
+ } else {
1449
+ // 从 IPFS 获取节点信息
1450
+ try {
1451
+ const ipfsRes = await fetch(`${IPFS_ENDPOINT}/api/v0/cat?arg=${cid}`, {
1452
+ method: 'POST'
1453
+ });
1454
+ const content = await ipfsRes.text();
1455
+ const doc = JSON.parse(content);
1456
+
1457
+ if (!doc.irohNodeId) {
1458
+ return res.status(400).json({ error: '节点信息中不包含 irohNodeId' });
1459
+ }
1460
+
1461
+ targetNodeId = doc.irohNodeId;
1462
+ nodeName = doc.name || 'Unknown';
1463
+ console.log(`[iroh API] 从 IPFS 获取节点: ${targetNodeId.substring(0, 20)}...`);
1464
+ } catch {
1465
+ return res.status(400).json({ error: '无法从 CID 获取节点信息,请确认 CID 有效' });
1466
+ }
1467
+ }
1331
1468
 
1332
1469
  // 发送连接消息
1333
1470
  const message = JSON.stringify({
@@ -1348,7 +1485,7 @@ app.get('/channels', async (_req, res) => {
1348
1485
  res.json({
1349
1486
  ok: true,
1350
1487
  targetNodeId,
1351
- nodeName: doc.name || 'Unknown'
1488
+ nodeName
1352
1489
  });
1353
1490
  } else {
1354
1491
  console.log(`[iroh API] 连接失败(对方可能离线)`);
@@ -1780,6 +1917,181 @@ app.get('/channels', async (_req, res) => {
1780
1917
  }
1781
1918
  });
1782
1919
 
1920
+ // 获取持久连接列表
1921
+ app.get('/api/p2p/persistent-connections', async (_req, res) => {
1922
+ try {
1923
+ const sessionProvider = app.locals.sessionProvider;
1924
+ if (!sessionProvider) {
1925
+ return res.json([]);
1926
+ }
1927
+ const channels = sessionProvider.getAllChannels().filter((ch: any) => ch.peerId);
1928
+ res.json(
1929
+ channels.map((ch: any) => ({
1930
+ id: ch.id,
1931
+ peerId: ch.peerId || '',
1932
+ peerDid: ch.peerDid || '',
1933
+ peerName: ch.peerName || 'Unknown',
1934
+ cid: ch.cid || '',
1935
+ status: ch.peerId ? 'connected' : 'disconnected',
1936
+ lastConnectedAt: new Date(ch.updatedAt).getTime(),
1937
+ channelId: ch.id,
1938
+ isAutoConnect: false
1939
+ }))
1940
+ );
1941
+ } catch (err: any) {
1942
+ res.status(500).json({ error: err.message });
1943
+ }
1944
+ });
1945
+
1946
+ // 更新连接状态
1947
+ app.post('/api/p2p/connection-status', async (req, res) => {
1948
+ try {
1949
+ const { id, status, channelId } = req.body;
1950
+ const sessionProvider = app.locals.sessionProvider;
1951
+ if (sessionProvider && channelId) {
1952
+ await sessionProvider.setChannelInfo(channelId, {
1953
+ peerId: status === 'connected' ? (req.body.peerId || 'connected') : undefined
1954
+ });
1955
+ }
1956
+ res.json({ ok: true });
1957
+ } catch (err: any) {
1958
+ res.status(500).json({ error: err.message });
1959
+ }
1960
+ });
1961
+
1962
+ // 创建对话通道
1963
+ app.post('/api/p2p/create-channel', async (req, res) => {
1964
+ try {
1965
+ const { peerDid, peerName, cid, peerId } = req.body;
1966
+ const sessionProvider = app.locals.sessionProvider;
1967
+ if (!sessionProvider) {
1968
+ return res.status(500).json({ error: 'sessionProvider not available' });
1969
+ }
1970
+ const channel = await sessionProvider.getOrCreatePeerChannel(peerDid, peerName);
1971
+ await sessionProvider.setChannelInfo(channel.id, { peerId: peerId || '', cid: cid || '' });
1972
+ res.json({ channelId: channel.id });
1973
+ } catch (err: any) {
1974
+ res.status(500).json({ error: err.message });
1975
+ }
1976
+ });
1977
+
1978
+ // CID 解析
1979
+ app.post('/api/p2p/resolve-cid', async (req, res) => {
1980
+ try {
1981
+ const { cid } = req.body;
1982
+ const { DiapDocParser } = await import('../social/channels/diap-doc-parser.js');
1983
+ const parser = new DiapDocParser();
1984
+ const result = await parser.parseFromCID(cid);
1985
+ res.json(result);
1986
+ } catch (err: any) {
1987
+ res.status(500).json({ error: err.message });
1988
+ }
1989
+ });
1990
+
1991
+ // P2P 工具调用
1992
+ app.post('/api/p2p/tool-call', async (req, res) => {
1993
+ try {
1994
+ const { tool, targetDid, payload } = req.body;
1995
+
1996
+ let result;
1997
+ switch (tool) {
1998
+ case 'system_info':
1999
+ const { getLocalSystemInfo } = await import('./components/p2p/p2p-tools.js');
2000
+ result = getLocalSystemInfo();
2001
+ break;
2002
+ case 'file_list':
2003
+ const { getLocalFileList } = await import('./components/p2p/p2p-tools.js');
2004
+ result = getLocalFileList(payload?.path || '/');
2005
+ break;
2006
+ default:
2007
+ return res.status(400).json({ error: `Unknown tool: ${tool}` });
2008
+ }
2009
+
2010
+ res.json({ success: true, data: result });
2011
+ } catch (err: any) {
2012
+ res.status(500).json({ error: err.message });
2013
+ }
2014
+ });
2015
+
2016
+ // ==================== 24h Heartbeat System ====================
2017
+
2018
+ let healthMonitor: any = null;
2019
+ let watchdog: any = null;
2020
+
2021
+ // 延迟导入避免循环依赖
2022
+ try {
2023
+ const { createHealthMonitor, createWatchdog } = await import('../heartbeat/index.js');
2024
+ healthMonitor = createHealthMonitor();
2025
+ watchdog = createWatchdog();
2026
+
2027
+ console.log('[24h] Heartbeat modules loaded');
2028
+ } catch (err) {
2029
+ console.warn('[24h] Failed to load heartbeat modules:', err);
2030
+ }
2031
+
2032
+ // 健康检查端点
2033
+ app.get('/api/health', async (req, res) => {
2034
+ try {
2035
+ if (!healthMonitor) {
2036
+ res.status(503).json({ error: 'Health monitor not initialized' });
2037
+ return;
2038
+ }
2039
+
2040
+ const status = await healthMonitor.check();
2041
+
2042
+ // 记录心跳活跃
2043
+ healthMonitor.recordHeartbeat?.();
2044
+ watchdog?.recordActivity?.('health_check');
2045
+
2046
+ // 根据状态返回不同 HTTP 状态码
2047
+ const httpStatus = status.status === 'healthy' ? 200 :
2048
+ status.status === 'degraded' ? 200 : 503;
2049
+
2050
+ res.status(httpStatus).json(status);
2051
+ } catch (err: any) {
2052
+ res.status(500).json({ error: err.message });
2053
+ }
2054
+ });
2055
+
2056
+ // 看门狗状态
2057
+ app.get('/api/watchdog', async (req, res) => {
2058
+ try {
2059
+ if (!watchdog) {
2060
+ res.status(503).json({ error: 'Watchdog not initialized' });
2061
+ return;
2062
+ }
2063
+
2064
+ const state = watchdog.getState();
2065
+ res.json(state);
2066
+ } catch (err: any) {
2067
+ res.status(500).json({ error: err.message });
2068
+ }
2069
+ });
2070
+
2071
+ // 看门狗重置
2072
+ app.post('/api/watchdog/reset', async (req, res) => {
2073
+ try {
2074
+ if (watchdog) {
2075
+ watchdog.reset();
2076
+ }
2077
+ res.json({ ok: true });
2078
+ } catch (err: any) {
2079
+ res.status(500).json({ error: err.message });
2080
+ }
2081
+ });
2082
+
2083
+ // 启动看门狗监控
2084
+ if (watchdog) {
2085
+ watchdog.start();
2086
+ console.log('[24h] Watchdog started');
2087
+ }
2088
+
2089
+ // 定期健康检查(不阻塞主服务器启动)
2090
+ if (healthMonitor) {
2091
+ healthMonitor.startPeriodicCheck(60000);
2092
+ console.log('[24h] Health monitor periodic check started');
2093
+ }
2094
+
1783
2095
  return new Promise<{ app: express.Express; server: typeof server }>((resolve) => {
1784
2096
  server.listen(port, () => {
1785
2097
  console.log(`Web 服务器启动完成: http://localhost:${port}`);
@@ -1794,12 +2106,17 @@ app.get('/channels', async (_req, res) => {
1794
2106
  });
1795
2107
  }
1796
2108
 
1797
- function broadcast(data: object, channelId?: string) {
2109
+ function broadcast(data: { type: string; [key: string]: unknown }, channelId?: string) {
1798
2110
  const envelope = { ...data, channelId };
1799
2111
  const message = `data: ${JSON.stringify(envelope)}\n\n`;
2112
+ console.log(`[broadcast] type=${data.type}, channelId=${channelId}, clients=${sseClients.size}`);
1800
2113
  for (const client of sseClients) {
1801
2114
  if (!channelId || client.channelId === channelId) {
1802
- client.res.write(message);
2115
+ try {
2116
+ client.res.write(message);
2117
+ } catch (e: unknown) {
2118
+ console.error(`[broadcast] 写入失败:`, (e as Error).message);
2119
+ }
1803
2120
  }
1804
2121
  }
1805
2122
  }
package/src/web/style.css CHANGED
@@ -594,6 +594,21 @@ body {
594
594
  border-radius: 3px;
595
595
  }
596
596
 
597
+ /* Channel messages container */
598
+ .channel-messages {
599
+ display: none;
600
+ flex: 1;
601
+ overflow-y: auto;
602
+ padding: 24px;
603
+ flex-direction: column;
604
+ gap: 20px;
605
+ }
606
+
607
+ .channel-messages.active,
608
+ .channel-messages:not([style*="display: none"]) {
609
+ display: flex;
610
+ }
611
+
597
612
  .message {
598
613
  display: flex;
599
614
  flex-direction: column;
@@ -3231,6 +3246,109 @@ body {
3231
3246
  cursor: pointer;
3232
3247
  }
3233
3248
 
3249
+ /* Persistent Peers Section */
3250
+ .persistent-peers-section {
3251
+ margin: 16px 0;
3252
+ padding: 12px;
3253
+ background: var(--bg-secondary);
3254
+ border: 1px solid var(--border);
3255
+ border-radius: var(--radius);
3256
+ }
3257
+
3258
+ .persistent-peers-section h4 {
3259
+ margin-bottom: 12px;
3260
+ font-size: 14px;
3261
+ color: var(--text);
3262
+ }
3263
+
3264
+ .persistent-peer-item {
3265
+ display: flex;
3266
+ align-items: center;
3267
+ gap: 12px;
3268
+ padding: 12px;
3269
+ border: 1px solid var(--border);
3270
+ border-radius: 8px;
3271
+ margin-bottom: 8px;
3272
+ background: var(--bg-primary);
3273
+ transition: var(--transition);
3274
+ }
3275
+
3276
+ .persistent-peer-item:last-child {
3277
+ margin-bottom: 0;
3278
+ }
3279
+
3280
+ .persistent-peer-item.connected {
3281
+ border-color: var(--success);
3282
+ background: rgba(76, 175, 80, 0.05);
3283
+ }
3284
+
3285
+ .persistent-peer-item.disconnected {
3286
+ opacity: 0.8;
3287
+ }
3288
+
3289
+ .persistent-peer-item.reconnecting {
3290
+ border-color: var(--warning);
3291
+ animation: pulse 2s infinite;
3292
+ }
3293
+
3294
+ @keyframes pulse {
3295
+ 0%, 100% { opacity: 1; }
3296
+ 50% { opacity: 0.6; }
3297
+ }
3298
+
3299
+ .peer-status-indicator {
3300
+ flex-shrink: 0;
3301
+ }
3302
+
3303
+ .peer-status-indicator .dot {
3304
+ display: block;
3305
+ width: 10px;
3306
+ height: 10px;
3307
+ border-radius: 50%;
3308
+ }
3309
+
3310
+ .peer-status-indicator .dot.online {
3311
+ background: var(--success);
3312
+ box-shadow: 0 0 6px var(--success);
3313
+ }
3314
+
3315
+ .peer-status-indicator .dot.offline {
3316
+ background: var(--text-muted);
3317
+ }
3318
+
3319
+ .auto-badge {
3320
+ display: inline-block;
3321
+ padding: 2px 6px;
3322
+ font-size: 10px;
3323
+ background: var(--accent);
3324
+ color: var(--bg-primary);
3325
+ border-radius: 4px;
3326
+ margin-left: 6px;
3327
+ }
3328
+
3329
+ .peer-info .peer-name {
3330
+ display: flex;
3331
+ align-items: center;
3332
+ font-weight: 500;
3333
+ }
3334
+
3335
+ .peer-info .peer-meta {
3336
+ font-size: 12px;
3337
+ color: var(--text-secondary);
3338
+ display: flex;
3339
+ gap: 12px;
3340
+ margin-top: 4px;
3341
+ }
3342
+
3343
+ .btn-danger {
3344
+ background: var(--error);
3345
+ color: white;
3346
+ }
3347
+
3348
+ .btn-danger:hover {
3349
+ background: #c62828;
3350
+ }
3351
+
3234
3352
  /* Modal Small */
3235
3353
  .modal-sm {
3236
3354
  max-width: 300px !important;
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "CommonJS",
5
+ "moduleResolution": "node",
6
+ "outDir": "./bin",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src/cli-entry.ts"],
15
+ "exclude": ["node_modules"]
16
+ }
@@ -16,5 +16,5 @@
16
16
  "types": ["node"]
17
17
  },
18
18
  "include": ["src/electron.ts", "src/electron-preload.ts"],
19
- "exclude": ["node_modules", "dist", "src/constraint-runtime", "src/agents", "src/web"]
19
+ "exclude": ["node_modules", "dist", "src/constraint-runtime", "src/agents", "src/web", "src/bollharness"]
20
20
  }
package/tsconfig.json CHANGED
@@ -10,10 +10,9 @@
10
10
  "skipLibCheck": true,
11
11
  "forceConsistentCasingInFileNames": true,
12
12
  "resolveJsonModule": true,
13
- "ignoreDeprecations": "6.0",
14
13
  "jsx": "react-jsx",
15
14
  "jsxImportSource": "react"
16
15
  },
17
16
  "include": ["src/**/*"],
18
- "exclude": ["node_modules"]
17
+ "exclude": ["node_modules", "src/bollharness/node_modules"]
19
18
  }