@bolloon/bolloon-agent 0.1.34 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/.auto-evolve-calls +1 -0
  2. package/.last-auto-evolve-baseline +1 -0
  3. package/.playwright-mcp/page-2026-06-16T07-51-45-706Z.yml +130 -0
  4. package/.playwright-mcp/page-2026-06-16T07-56-44-116Z.yml +131 -0
  5. package/Bolloon.md +144 -0
  6. package/Dive-into/CITATION.cff +17 -0
  7. package/Dive-into/LICENSE +25 -0
  8. package/Dive-into/README.md +598 -0
  9. package/Dive-into/README_zh.md +599 -0
  10. package/Dive-into/assets/context.png +0 -0
  11. package/Dive-into/assets/extensibility.png +0 -0
  12. package/Dive-into/assets/iteration.png +0 -0
  13. package/Dive-into/assets/layered_architecture.png +0 -0
  14. package/Dive-into/assets/main_structure.png +0 -0
  15. package/Dive-into/assets/permission.png +0 -0
  16. package/Dive-into/assets/session_compact.png +0 -0
  17. package/Dive-into/assets/subagent.png +0 -0
  18. package/Dive-into/paper/Dive_into_Claude_Code.pdf +0 -0
  19. package/README.md +1 -1
  20. package/dist/agents/p2p-chat-tools.js +6 -6
  21. package/dist/agents/permission-mode.js +115 -0
  22. package/dist/agents/pi-sdk.js +687 -25
  23. package/dist/agents/pre-tool-validator.js +194 -0
  24. package/dist/agents/workflow-pivot-loop.js +113 -12
  25. package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +1 -1
  26. package/dist/bollharness/src/scripts/checks/check_doc_links.js +1 -1
  27. package/dist/bollharness/src/scripts/context_router.js +1 -1
  28. package/dist/bollharness/src/scripts/deploy-guard.js +1 -1
  29. package/dist/bollharness/src/scripts/guard_router.js +1 -1
  30. package/dist/bollharness/src/scripts/hooks/_hook_output.js +1 -1
  31. package/dist/bollharness/src/scripts/hooks/risk-tracker.js +1 -1
  32. package/dist/bollharness-integration/context-router.js +2 -2
  33. package/dist/bollharness-integration/guard-checker.js +1 -1
  34. package/dist/bootstrap/bootstrap.js +117 -0
  35. package/dist/bootstrap/context-collector.js +312 -0
  36. package/dist/bootstrap/context-hierarchy.js +218 -0
  37. package/dist/bootstrap/lifecycle-hooks.js +171 -0
  38. package/dist/bootstrap/project-context.js +158 -0
  39. package/dist/context-compaction/auto-compact.js +144 -0
  40. package/dist/context-compaction/budget-gate.js +28 -0
  41. package/dist/context-compaction/budget-reduce.js +35 -0
  42. package/dist/context-compaction/context-collapse.js +66 -0
  43. package/dist/context-compaction/index.js +21 -0
  44. package/dist/context-compaction/microcompact.js +51 -0
  45. package/dist/context-compaction/pipeline.js +123 -0
  46. package/dist/context-compaction/snip.js +45 -0
  47. package/dist/context-compaction/token-estimator.js +35 -0
  48. package/dist/context-compaction/types.js +19 -0
  49. package/dist/heartbeat/HealthMonitor.js +3 -2
  50. package/dist/index.js +49 -2
  51. package/dist/llm/llm-judgment-client.js +32 -30
  52. package/dist/llm/pi-ai.js +125 -28
  53. package/dist/llm/system-prompt/health.js +129 -0
  54. package/dist/llm/system-prompt/registry.js +246 -0
  55. package/dist/llm/system-prompt/strip-hibsml.js +51 -0
  56. package/dist/llm/tool-manifest/ask_user_input.js +35 -0
  57. package/dist/llm/tool-manifest/bash.js +23 -0
  58. package/dist/llm/tool-manifest/create_file.js +24 -0
  59. package/dist/llm/tool-manifest/fetch_sports_data.js +26 -0
  60. package/dist/llm/tool-manifest/image_search.js +24 -0
  61. package/dist/llm/tool-manifest/index.js +69 -0
  62. package/dist/llm/tool-manifest/mcp.js +43 -0
  63. package/dist/llm/tool-manifest/message_compose.js +31 -0
  64. package/dist/llm/tool-manifest/places.js +83 -0
  65. package/dist/llm/tool-manifest/present_files.js +21 -0
  66. package/dist/llm/tool-manifest/recipe.js +40 -0
  67. package/dist/llm/tool-manifest/recommend_apps.js +23 -0
  68. package/dist/llm/tool-manifest/str_replace.js +27 -0
  69. package/dist/llm/tool-manifest/types.js +7 -0
  70. package/dist/llm/tool-manifest/view.js +24 -0
  71. package/dist/llm/tool-manifest/weather.js +27 -0
  72. package/dist/llm/tool-manifest/web.js +51 -0
  73. package/dist/network/p2p-direct.js +23 -0
  74. package/dist/network/source-intent-broadcaster.js +203 -0
  75. package/dist/network/source-intent.js +100 -0
  76. package/dist/pi-ecosystem-judgment/adaptive-scan.js +279 -0
  77. package/dist/pi-ecosystem-judgment/causal-judge.js +449 -0
  78. package/dist/pi-ecosystem-judgment/detect-hook.js +168 -0
  79. package/dist/pi-ecosystem-judgment/distill-prompt.js +226 -0
  80. package/dist/pi-ecosystem-judgment/evolve-judgment.js +170 -0
  81. package/dist/pi-ecosystem-judgment/human-value-pipeline.js +21 -0
  82. package/dist/pi-ecosystem-judgment/human-value-store.js +283 -22
  83. package/dist/pi-ecosystem-judgment/injection-gate.js +208 -0
  84. package/dist/pi-ecosystem-judgment/monitor-gate.js +188 -0
  85. package/dist/pi-ecosystem-judgment/value-injection.js +8 -2
  86. package/dist/security/builtin-guards.js +124 -0
  87. package/dist/security/context-router-tool.js +106 -0
  88. package/dist/security/input-scanner.js +223 -0
  89. package/dist/security/react-harness.js +143 -0
  90. package/dist/security/tool-gate.js +235 -0
  91. package/dist/utils/auto-evolve-policy.js +117 -0
  92. package/dist/utils/clamp.js +7 -0
  93. package/dist/utils/double.js +6 -0
  94. package/dist/web/client.js +3810 -3830
  95. package/dist/web/components/p2p/P2PModal.js +188 -0
  96. package/dist/web/components/p2p/index.js +264 -226
  97. package/dist/web/components/p2p/p2p-modal.js +657 -0
  98. package/dist/web/components/p2p/p2p-tools.js +248 -0
  99. package/dist/web/index.html +60 -49
  100. package/dist/web/server.js +827 -124
  101. package/dist/web/style.css +531 -249
  102. package/dist/web/ui/message-renderer.js +463 -0
  103. package/dist/web/ui/step-timeline.js +375 -0
  104. package/lefthook.yml +33 -0
  105. package/package.json +3 -2
  106. package/scripts/auto-evolve-loop.ts +481 -0
  107. package/scripts/auto-evolve-oneshot.sh +155 -0
  108. package/scripts/auto-evolve-snapshot.sh +136 -0
  109. package/scripts/build-web.ts +35 -1
  110. package/scripts/detect-schema-changes.sh +48 -0
  111. package/scripts/diff-reviewer.ts +159 -0
  112. package/scripts/validate-system-prompt.ts +142 -0
  113. package/scripts/weekly-report.ts +364 -0
  114. package/src/agents/p2p-chat-tools.ts +6 -6
  115. package/src/agents/permission-mode.ts +127 -0
  116. package/src/agents/pi-sdk.ts +741 -30
  117. package/src/agents/pre-tool-validator.ts +213 -0
  118. package/src/agents/workflow-pivot-loop.ts +110 -19
  119. package/src/bollharness/CLAUDE.md +1 -1
  120. package/src/bollharness/README.md +2 -2
  121. package/src/bollharness/README.zh-CN.md +2 -2
  122. package/src/bollharness/reference/boll-reference/scripts/hooks/find-boll-root.sh +2 -2
  123. package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +2 -2
  124. package/src/bollharness/scripts/context-fragments/version-sources.md +1 -1
  125. package/src/bollharness/scripts/hooks/find-project-root.sh +4 -4
  126. package/src/bollharness/src/scripts/checks/check_doc_freshness.ts +1 -1
  127. package/src/bollharness/src/scripts/checks/check_doc_links.ts +1 -1
  128. package/src/bollharness/src/scripts/context_router.ts +1 -1
  129. package/src/bollharness/src/scripts/deploy-guard.ts +1 -1
  130. package/src/bollharness/src/scripts/guard_router.ts +1 -1
  131. package/src/bollharness/src/scripts/hooks/_hook_output.ts +1 -1
  132. package/src/bollharness/src/scripts/hooks/risk-tracker.ts +1 -1
  133. package/src/bollharness/templates/scaffold/.gitignore.append +1 -1
  134. package/src/bollharness/templates/scaffold/CLAUDE.md +2 -2
  135. package/src/bollharness-integration/context-router.ts +2 -2
  136. package/src/bollharness-integration/guard-checker.ts +1 -1
  137. package/src/bootstrap/bootstrap.ts +135 -0
  138. package/src/bootstrap/context-collector.ts +371 -0
  139. package/src/bootstrap/context-hierarchy.ts +283 -0
  140. package/src/bootstrap/lifecycle-hooks.ts +289 -0
  141. package/src/bootstrap/project-context.ts +171 -0
  142. package/src/context-compaction/auto-compact.ts +153 -0
  143. package/src/context-compaction/budget-gate.ts +32 -0
  144. package/src/context-compaction/budget-reduce.ts +37 -0
  145. package/src/context-compaction/context-collapse.ts +72 -0
  146. package/src/context-compaction/index.ts +24 -0
  147. package/src/context-compaction/microcompact.ts +54 -0
  148. package/src/context-compaction/pipeline.ts +133 -0
  149. package/src/context-compaction/snip.ts +51 -0
  150. package/src/context-compaction/token-estimator.ts +36 -0
  151. package/src/context-compaction/types.ts +99 -0
  152. package/src/heartbeat/HealthMonitor.ts +3 -2
  153. package/src/index.ts +47 -2
  154. package/src/llm/llm-judgment-client.ts +36 -35
  155. package/src/llm/pi-ai.ts +135 -29
  156. package/src/llm/system-prompt/health.ts +159 -0
  157. package/src/llm/system-prompt/layers/channel/local.md +14 -0
  158. package/src/llm/system-prompt/layers/channel/p2p-agent.md +18 -0
  159. package/src/llm/system-prompt/layers/channel/p2p-visitor.md +19 -0
  160. package/src/llm/system-prompt/layers/core/artifacts_storage.md +89 -0
  161. package/src/llm/system-prompt/layers/core/evenhandedness.md +21 -0
  162. package/src/llm/system-prompt/layers/core/hibs_reminders.md +15 -0
  163. package/src/llm/system-prompt/layers/core/identity.md +37 -0
  164. package/src/llm/system-prompt/layers/core/knowledge.md +17 -0
  165. package/src/llm/system-prompt/layers/core/memory_system.md +12 -0
  166. package/src/llm/system-prompt/layers/core/network_filesystem.md +28 -0
  167. package/src/llm/system-prompt/layers/core/refusal.md +37 -0
  168. package/src/llm/system-prompt/layers/core/tone.md +31 -0
  169. package/src/llm/system-prompt/layers/core/tools.thin.md +13 -0
  170. package/src/llm/system-prompt/layers/core/wellbeing.md +41 -0
  171. package/src/llm/system-prompt/layers/role/architect.md +20 -0
  172. package/src/llm/system-prompt/layers/role/expert.md +19 -0
  173. package/src/llm/system-prompt/layers/role/implementer.md +15 -0
  174. package/src/llm/system-prompt/layers/role/security.md +15 -0
  175. package/src/llm/system-prompt/layers/tool/artifacts.md +72 -0
  176. package/src/llm/system-prompt/layers/tool/bash.md +25 -0
  177. package/src/llm/system-prompt/layers/tool/hibs_api.md +171 -0
  178. package/src/llm/system-prompt/layers/tool/image_search.md +70 -0
  179. package/src/llm/system-prompt/layers/tool/manifest.md +89 -0
  180. package/src/llm/system-prompt/layers/tool/mcp_apps.md +53 -0
  181. package/src/llm/system-prompt/layers/tool/web_search.md +83 -0
  182. package/src/llm/system-prompt/registry.ts +325 -0
  183. package/src/llm/system-prompt/strip-hibsml.ts +52 -0
  184. package/src/llm/tool-manifest/ask_user_input.ts +37 -0
  185. package/src/llm/tool-manifest/bash.ts +25 -0
  186. package/src/llm/tool-manifest/create_file.ts +26 -0
  187. package/src/llm/tool-manifest/fetch_sports_data.ts +28 -0
  188. package/src/llm/tool-manifest/image_search.ts +26 -0
  189. package/src/llm/tool-manifest/index.ts +88 -0
  190. package/src/llm/tool-manifest/mcp.ts +46 -0
  191. package/src/llm/tool-manifest/message_compose.ts +33 -0
  192. package/src/llm/tool-manifest/places.ts +86 -0
  193. package/src/llm/tool-manifest/present_files.ts +23 -0
  194. package/src/llm/tool-manifest/recipe.ts +42 -0
  195. package/src/llm/tool-manifest/recommend_apps.ts +25 -0
  196. package/src/llm/tool-manifest/str_replace.ts +29 -0
  197. package/src/llm/tool-manifest/types.ts +52 -0
  198. package/src/llm/tool-manifest/view.ts +26 -0
  199. package/src/llm/tool-manifest/weather.ts +29 -0
  200. package/src/llm/tool-manifest/web.ts +54 -0
  201. package/src/network/p2p-direct.ts +22 -0
  202. package/src/network/source-intent-broadcaster.ts +242 -0
  203. package/src/network/source-intent.ts +167 -0
  204. package/src/security/builtin-guards.ts +162 -0
  205. package/src/security/context-router-tool.ts +122 -0
  206. package/src/security/input-scanner.ts +287 -0
  207. package/src/security/react-harness.ts +177 -0
  208. package/src/security/tool-gate.ts +294 -0
  209. package/src/utils/auto-evolve-policy.ts +138 -0
  210. package/src/utils/clamp.ts +5 -0
  211. package/src/web/client.js +105 -2187
  212. package/src/web/client.ts +4326 -0
  213. package/src/web/index.html +60 -49
  214. package/src/web/server.ts +894 -148
  215. package/src/web/style.css +531 -249
  216. package/src/web/ui/message-renderer.ts +540 -0
  217. package/src/web/ui/step-timeline.ts +394 -0
  218. package/staging/auto-evolve/clean-001/.review-verdict +9 -0
  219. package/staging/auto-evolve/clean-001/clean-001.patch +14 -0
  220. package/staging/auto-evolve/e2e-001/.patch-id +1 -0
  221. package/staging/auto-evolve/e2e-001/.review-verdict +12 -0
  222. package/staging/auto-evolve/e2e-001/e2e-001.patch +11 -0
  223. package/staging/auto-evolve/test-bad/.review-verdict +12 -0
  224. package/staging/auto-evolve/test-bad/test-bad.patch +11 -0
  225. package/test-results/.last-run.json +6 -0
  226. 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,657 @@
1
+ /**
2
+ * P2P Modal Web Component
3
+ */
4
+ import { p2pManager } from './p2p-manager.js';
5
+ export class P2PModal extends HTMLElement {
6
+ shadow = this.attachShadow({ mode: 'open' });
7
+ activeTab = 'identity';
8
+ initialized = false;
9
+ connectedCallback() {
10
+ this.render();
11
+ this.attachEvents();
12
+ }
13
+ show() {
14
+ this.classList.add('show');
15
+ if (!this.initialized) {
16
+ this.initP2P();
17
+ this.initialized = true;
18
+ }
19
+ }
20
+ hide() {
21
+ this.classList.remove('show');
22
+ }
23
+ async initP2P() {
24
+ const statusIndicator = this.shadow.getElementById('p2p-status-indicator');
25
+ const statusText = this.shadow.getElementById('p2p-status-text');
26
+ statusIndicator?.classList.add('connecting');
27
+ if (statusText)
28
+ statusText.textContent = '初始化中...';
29
+ try {
30
+ const identity = await p2pManager.init();
31
+ this.updateIdentityDisplay(identity);
32
+ this.updateStatus('online', '已连接');
33
+ statusIndicator?.classList.add('online');
34
+ }
35
+ catch (e) {
36
+ this.updateStatus('error', '初始化失败');
37
+ }
38
+ }
39
+ updateStatus(status, text) {
40
+ const statusIndicator = this.shadow.getElementById('p2p-status-indicator');
41
+ const statusText = this.shadow.getElementById('p2p-status-text');
42
+ if (statusIndicator) {
43
+ statusIndicator.className = 'status-indicator ' + status;
44
+ }
45
+ if (statusText)
46
+ statusText.textContent = text;
47
+ }
48
+ updateIdentityDisplay(identity) {
49
+ const didEl = this.shadow.getElementById('p2p-did');
50
+ const cidEl = this.shadow.getElementById('p2p-cid');
51
+ const nodeIdEl = this.shadow.getElementById('p2p-node-id');
52
+ const shareLinkEl = this.shadow.getElementById('p2p-invite-link');
53
+ const sharePanel = this.shadow.getElementById('p2p-share-panel');
54
+ if (didEl && identity.did)
55
+ didEl.textContent = identity.did;
56
+ if (cidEl && identity.cid)
57
+ cidEl.textContent = identity.cid;
58
+ if (nodeIdEl && identity.irohNodeId)
59
+ nodeIdEl.textContent = identity.irohNodeId;
60
+ if (shareLinkEl && identity.did && identity.cid) {
61
+ shareLinkEl.value = `bolloon://connect?did=${encodeURIComponent(identity.did)}&cid=${encodeURIComponent(identity.cid)}`;
62
+ }
63
+ if (sharePanel)
64
+ sharePanel.style.display = 'block';
65
+ }
66
+ switchTab(tab) {
67
+ this.activeTab = tab;
68
+ this.render();
69
+ this.attachEvents();
70
+ this.loadActiveTab();
71
+ }
72
+ async loadActiveTab() {
73
+ switch (this.activeTab) {
74
+ case 'history':
75
+ await this.loadHistory();
76
+ break;
77
+ case 'messages':
78
+ await this.loadMessages();
79
+ break;
80
+ case 'connect':
81
+ await this.loadPeers();
82
+ break;
83
+ }
84
+ }
85
+ async loadHistory() {
86
+ const historyList = this.shadow.getElementById('p2p-history-list');
87
+ if (!historyList)
88
+ return;
89
+ try {
90
+ const history = await p2pManager.getHistory();
91
+ this.renderHistory(history, historyList);
92
+ }
93
+ catch (e) {
94
+ console.error('[P2P Modal] 加载历史失败:', e);
95
+ }
96
+ }
97
+ renderHistory(history, container) {
98
+ if (history.length === 0) {
99
+ container.innerHTML = '<div class="empty-hint">暂无连接历史</div>';
100
+ return;
101
+ }
102
+ const fragment = document.createDocumentFragment();
103
+ history.forEach(item => {
104
+ const div = document.createElement('div');
105
+ div.className = `history-item ${item.isPinned ? 'pinned' : ''}`;
106
+ div.innerHTML = `
107
+ <div class="history-item-icon">💬</div>
108
+ <div class="history-item-info">
109
+ <div class="history-item-name">
110
+ ${this.escapeHtml(item.name || 'Unknown')}
111
+ ${item.isPinned ? '<span class="pin-icon">📌</span>' : ''}
112
+ </div>
113
+ <div class="history-item-meta">
114
+ <span>上次: ${new Date(item.lastConnectedAt).toLocaleString()}</span>
115
+ <span>消息: ${item.totalMessages || 0}</span>
116
+ </div>
117
+ </div>
118
+ <div class="history-item-actions">
119
+ <button class="btn-sm btn-secondary" data-action="connect" data-cid="${this.escapeHtml(item.cid)}">连接</button>
120
+ <button class="btn-sm btn-secondary" data-action="pin" data-id="${item.id}" data-pinned="${!item.isPinned}">
121
+ ${item.isPinned ? '取消置顶' : '置顶'}
122
+ </button>
123
+ <button class="btn-sm btn-secondary" data-action="delete" data-id="${item.id}">删除</button>
124
+ </div>
125
+ `;
126
+ fragment.appendChild(div);
127
+ });
128
+ container.innerHTML = '';
129
+ container.appendChild(fragment);
130
+ this.attachHistoryEvents(container);
131
+ }
132
+ attachHistoryEvents(container) {
133
+ container.querySelectorAll('[data-action]').forEach(btn => {
134
+ btn.addEventListener('click', async (e) => {
135
+ const el = e.currentTarget;
136
+ const action = el.dataset.action;
137
+ if (action === 'connect') {
138
+ const cid = el.dataset.cid;
139
+ if (cid) {
140
+ const connectInput = this.shadow.getElementById('p2p-connect-input');
141
+ if (connectInput)
142
+ connectInput.value = cid;
143
+ this.switchTab('connect');
144
+ }
145
+ }
146
+ else if (action === 'pin') {
147
+ const id = el.dataset.id;
148
+ const pinned = el.dataset.pinned === 'true';
149
+ if (id)
150
+ await p2pManager.updateHistory(id, { isPinned: pinned });
151
+ await this.loadHistory();
152
+ }
153
+ else if (action === 'delete') {
154
+ const id = el.dataset.id;
155
+ if (id)
156
+ await p2pManager.deleteHistory(id);
157
+ await this.loadHistory();
158
+ }
159
+ });
160
+ });
161
+ }
162
+ async loadMessages() {
163
+ const messagesList = this.shadow.getElementById('p2p-messages-list');
164
+ const unreadBadge = this.shadow.getElementById('p2p-unread-badge');
165
+ if (!messagesList)
166
+ return;
167
+ try {
168
+ const messages = await p2pManager.getMessages();
169
+ this.renderMessages(messages, messagesList);
170
+ const unread = p2pManager.getUnreadCount();
171
+ if (unreadBadge) {
172
+ unreadBadge.textContent = String(unread);
173
+ unreadBadge.style.display = unread > 0 ? 'inline-flex' : 'none';
174
+ }
175
+ }
176
+ catch (e) {
177
+ console.error('[P2P Modal] 加载消息失败:', e);
178
+ }
179
+ }
180
+ renderMessages(messages, container) {
181
+ if (messages.length === 0) {
182
+ container.innerHTML = '<div class="empty-hint">暂无消息</div>';
183
+ return;
184
+ }
185
+ const fragment = document.createDocumentFragment();
186
+ messages.slice(-20).forEach(msg => {
187
+ const div = document.createElement('div');
188
+ div.className = `message-item ${!msg.isRead ? 'unread' : ''}`;
189
+ div.innerHTML = `
190
+ <div class="message-header">
191
+ <span class="message-sender">${this.escapeHtml(msg.fromName || msg.fromDid)}</span>
192
+ <span class="message-time">${new Date(msg.timestamp).toLocaleString()}</span>
193
+ </div>
194
+ <div class="message-content">${this.escapeHtml(msg.content.substring(0, 200))}</div>
195
+ `;
196
+ fragment.appendChild(div);
197
+ });
198
+ container.innerHTML = '';
199
+ container.appendChild(fragment);
200
+ }
201
+ async loadPeers() {
202
+ const peersList = this.shadow.getElementById('p2p-peers-list');
203
+ const peerCount = this.shadow.getElementById('p2p-peer-count');
204
+ if (!peersList)
205
+ return;
206
+ const peers = p2pManager.getConnectedPeers();
207
+ if (peerCount)
208
+ peerCount.textContent = String(peers.length);
209
+ if (peers.length === 0) {
210
+ peersList.innerHTML = '<div class="empty-hint">暂无连接</div>';
211
+ return;
212
+ }
213
+ const fragment = document.createDocumentFragment();
214
+ peers.forEach(peer => {
215
+ const div = document.createElement('div');
216
+ div.className = 'peer-item';
217
+ div.innerHTML = `
218
+ <div class="peer-status"><span class="dot online"></span></div>
219
+ <div class="peer-info">
220
+ <div class="peer-name">${this.escapeHtml(peer.info?.name || 'Unknown')}</div>
221
+ <div class="peer-meta">${(peer.nodeId || '').substring(0, 16)}...</div>
222
+ </div>
223
+ `;
224
+ fragment.appendChild(div);
225
+ });
226
+ peersList.innerHTML = '';
227
+ peersList.appendChild(fragment);
228
+ }
229
+ async handleConnect() {
230
+ const input = this.shadow.getElementById('p2p-connect-input');
231
+ const progressDiv = this.shadow.getElementById('p2p-connect-progress');
232
+ const progressFill = this.shadow.getElementById('p2p-progress-fill');
233
+ const progressText = this.shadow.getElementById('p2p-progress-text');
234
+ const resultDiv = this.shadow.getElementById('p2p-connect-result');
235
+ if (!input?.value.trim())
236
+ return;
237
+ if (progressDiv)
238
+ progressDiv.style.display = 'block';
239
+ const updateProgress = (progress) => {
240
+ if (progressFill)
241
+ progressFill.style.width = `${progress.percent}%`;
242
+ if (progressText)
243
+ progressText.textContent = progress.message;
244
+ };
245
+ try {
246
+ const result = await p2pManager.connect(input.value, updateProgress);
247
+ if (result.success) {
248
+ this.showResult(resultDiv, 'success', `已连接到 ${result.name || '节点'}`);
249
+ input.value = '';
250
+ await this.loadHistory();
251
+ await this.loadPeers();
252
+ }
253
+ else {
254
+ this.showResult(resultDiv, 'error', result.error || '连接失败');
255
+ }
256
+ }
257
+ catch (e) {
258
+ this.showResult(resultDiv, 'error', e.message);
259
+ }
260
+ finally {
261
+ setTimeout(() => {
262
+ if (progressDiv)
263
+ progressDiv.style.display = 'none';
264
+ }, 2000);
265
+ }
266
+ }
267
+ showResult(el, type, text) {
268
+ if (!el)
269
+ return;
270
+ el.className = `connect-result ${type} show`;
271
+ el.textContent = text;
272
+ }
273
+ escapeHtml(text) {
274
+ const div = document.createElement('div');
275
+ div.textContent = text;
276
+ return div.innerHTML;
277
+ }
278
+ showToast(message) {
279
+ const toast = document.createElement('div');
280
+ toast.className = 'toast';
281
+ toast.textContent = message;
282
+ document.body.appendChild(toast);
283
+ setTimeout(() => toast.remove(), 2000);
284
+ }
285
+ render() {
286
+ this.shadow.innerHTML = `
287
+ <style>
288
+ :host {
289
+ display: none;
290
+ position: fixed;
291
+ inset: 0;
292
+ background: rgba(0, 0, 0, 0.7);
293
+ z-index: 1000;
294
+ align-items: center;
295
+ justify-content: center;
296
+ }
297
+ :host(.show) {
298
+ display: flex;
299
+ }
300
+ .modal {
301
+ width: 90%;
302
+ max-width: 700px;
303
+ max-height: 90vh;
304
+ background: var(--bg-secondary, #1e1e2e);
305
+ border: 1px solid var(--border, #3a3a4a);
306
+ border-radius: 12px;
307
+ padding: 24px;
308
+ overflow-y: auto;
309
+ }
310
+ .modal-header {
311
+ display: flex;
312
+ justify-content: space-between;
313
+ align-items: center;
314
+ margin-bottom: 20px;
315
+ }
316
+ .modal-header h2 {
317
+ margin: 0;
318
+ font-size: 18px;
319
+ color: var(--text-primary, #e0e0e0);
320
+ }
321
+ .modal-close {
322
+ background: none;
323
+ border: none;
324
+ font-size: 24px;
325
+ cursor: pointer;
326
+ color: var(--text-secondary, #888);
327
+ }
328
+ .tabs {
329
+ display: flex;
330
+ gap: 4px;
331
+ margin-bottom: 20px;
332
+ border-bottom: 1px solid var(--border, #3a3a4a);
333
+ padding-bottom: 8px;
334
+ }
335
+ .tab {
336
+ padding: 8px 16px;
337
+ background: transparent;
338
+ border: none;
339
+ border-radius: 6px 6px 0 0;
340
+ color: var(--text-secondary, #888);
341
+ font-size: 14px;
342
+ cursor: pointer;
343
+ }
344
+ .tab:hover { color: var(--text-primary, #e0e0e0); }
345
+ .tab.active {
346
+ color: var(--accent, #7c3aed);
347
+ background: var(--bg-hover, #2a2a3a);
348
+ }
349
+ .tab-content { display: none; }
350
+ .tab-content.active { display: block; }
351
+ .identity-card {
352
+ background: var(--bg-hover, #2a2a3a);
353
+ border: 1px solid var(--border, #3a3a4a);
354
+ border-radius: 8px;
355
+ padding: 20px;
356
+ margin-bottom: 16px;
357
+ }
358
+ .status-row {
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 8px;
362
+ margin-bottom: 16px;
363
+ }
364
+ .status-indicator {
365
+ width: 10px;
366
+ height: 10px;
367
+ border-radius: 50%;
368
+ background: var(--text-muted, #666);
369
+ }
370
+ .status-indicator.online { background: var(--success, #22c55e); }
371
+ .status-indicator.connecting { background: var(--warning, #eab308); animation: pulse 1s infinite; }
372
+ .status-indicator.error { background: var(--error, #ef4444); }
373
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
374
+ .info-row {
375
+ display: flex;
376
+ align-items: center;
377
+ gap: 8px;
378
+ padding: 8px 0;
379
+ border-bottom: 1px solid var(--border, #3a3a4a);
380
+ }
381
+ .info-row:last-child { border-bottom: none; }
382
+ .info-label { min-width: 80px; color: var(--text-secondary, #888); font-size: 13px; }
383
+ .info-value { flex: 1; font-size: 13px; word-break: break-all; font-family: monospace; }
384
+ .copy-btn {
385
+ padding: 4px 8px;
386
+ background: var(--bg-primary, #252536);
387
+ border: 1px solid var(--border, #3a3a4a);
388
+ border-radius: 4px;
389
+ cursor: pointer;
390
+ font-size: 12px;
391
+ }
392
+ .copy-btn:hover { background: var(--accent, #7c3aed); color: white; }
393
+ .btn-primary {
394
+ padding: 12px 24px;
395
+ background: var(--accent, #7c3aed);
396
+ color: white;
397
+ border: none;
398
+ border-radius: 8px;
399
+ cursor: pointer;
400
+ font-size: 14px;
401
+ width: 100%;
402
+ }
403
+ .btn-primary:hover { opacity: 0.9; }
404
+ .share-panel {
405
+ background: var(--bg-hover, #2a2a3a);
406
+ border: 1px solid var(--border, #3a3a4a);
407
+ border-radius: 8px;
408
+ padding: 16px;
409
+ margin-bottom: 16px;
410
+ }
411
+ .share-panel h4 { margin: 0 0 12px 0; color: var(--text-primary, #e0e0e0); }
412
+ .share-actions { display: flex; gap: 8px; flex-wrap: wrap; }
413
+ .btn-secondary {
414
+ padding: 8px 16px;
415
+ background: var(--bg-primary, #252536);
416
+ border: 1px solid var(--border, #3a3a4a);
417
+ border-radius: 6px;
418
+ cursor: pointer;
419
+ font-size: 13px;
420
+ }
421
+ .btn-secondary:hover { border-color: var(--accent, #7c3aed); }
422
+ .btn-sm { padding: 6px 12px; font-size: 12px; }
423
+ .connect-form { display: flex; gap: 8px; margin-bottom: 16px; }
424
+ .connect-form input {
425
+ flex: 1;
426
+ padding: 12px 16px;
427
+ background: var(--bg-hover, #2a2a3a);
428
+ border: 1px solid var(--border, #3a3a4a);
429
+ border-radius: 8px;
430
+ color: var(--text-primary, #e0e0e0);
431
+ font-size: 14px;
432
+ }
433
+ .progress {
434
+ display: none;
435
+ margin-bottom: 16px;
436
+ padding: 16px;
437
+ background: var(--bg-hover, #2a2a3a);
438
+ border-radius: 8px;
439
+ }
440
+ .progress.show { display: block; }
441
+ .progress-bar {
442
+ height: 6px;
443
+ background: var(--bg-primary, #252536);
444
+ border-radius: 3px;
445
+ overflow: hidden;
446
+ margin-bottom: 8px;
447
+ }
448
+ .progress-fill {
449
+ height: 100%;
450
+ background: var(--accent, #7c3aed);
451
+ border-radius: 3px;
452
+ transition: width 0.3s;
453
+ }
454
+ .progress-text { color: var(--text-secondary, #888); font-size: 13px; }
455
+ .connect-result {
456
+ display: none;
457
+ padding: 12px;
458
+ border-radius: 8px;
459
+ font-size: 14px;
460
+ margin-bottom: 16px;
461
+ }
462
+ .connect-result.show { display: block; }
463
+ .connect-result.success { background: rgba(34, 197, 94, 0.1); border: 1px solid var(--success, #22c55e); color: var(--success, #22c55e); }
464
+ .connect-result.error { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--error, #ef4444); color: var(--error, #ef4444); }
465
+ .empty-hint { color: var(--text-muted, #666); font-size: 13px; padding: 32px; text-align: center; }
466
+ .history-item, .peer-item {
467
+ display: flex;
468
+ align-items: center;
469
+ gap: 12px;
470
+ padding: 12px;
471
+ background: var(--bg-hover, #2a2a3a);
472
+ border: 1px solid var(--border, #3a3a4a);
473
+ border-radius: 8px;
474
+ margin-bottom: 8px;
475
+ }
476
+ .history-item.pinned { border-left: 3px solid var(--accent, #7c3aed); }
477
+ .history-item-icon { font-size: 20px; }
478
+ .history-item-info { flex: 1; }
479
+ .history-item-name { font-weight: 500; display: flex; align-items: center; gap: 6px; }
480
+ .history-item-meta { font-size: 12px; color: var(--text-secondary, #888); display: flex; gap: 12px; margin-top: 4px; }
481
+ .history-item-actions { display: flex; gap: 8px; }
482
+ .peer-status { display: flex; align-items: center; }
483
+ .peer-status .dot { width: 8px; height: 8px; border-radius: 50%; }
484
+ .peer-status .dot.online { background: var(--success, #22c55e); }
485
+ .peer-info { flex: 1; }
486
+ .peer-name { font-weight: 500; }
487
+ .peer-meta { font-size: 12px; color: var(--text-secondary, #888); }
488
+ .message-item {
489
+ padding: 12px;
490
+ background: var(--bg-hover, #2a2a3a);
491
+ border: 1px solid var(--border, #3a3a4a);
492
+ border-radius: 8px;
493
+ margin-bottom: 8px;
494
+ }
495
+ .message-item.unread { border-left: 3px solid var(--accent, #7c3aed); }
496
+ .message-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
497
+ .message-sender { font-weight: 500; }
498
+ .message-time { font-size: 12px; color: var(--text-secondary, #888); }
499
+ .message-content { font-size: 14px; line-height: 1.5; }
500
+ .peers-section {
501
+ margin-top: 24px;
502
+ padding-top: 16px;
503
+ border-top: 1px solid var(--border, #3a3a4a);
504
+ }
505
+ .peers-section h4 { margin-bottom: 12px; color: var(--text-secondary, #888); font-size: 13px; }
506
+ .toolbar { margin-bottom: 12px; display: flex; justify-content: flex-end; }
507
+ </style>
508
+
509
+ <div class="modal">
510
+ <div class="modal-header">
511
+ <h2>P2P 网络</h2>
512
+ <button class="modal-close" id="modal-close">&times;</button>
513
+ </div>
514
+
515
+ <div class="tabs">
516
+ <button class="tab ${this.activeTab === 'identity' ? 'active' : ''}" data-tab="identity">我的身份</button>
517
+ <button class="tab ${this.activeTab === 'connect' ? 'active' : ''}" data-tab="connect">连接</button>
518
+ <button class="tab ${this.activeTab === 'history' ? 'active' : ''}" data-tab="history">历史记录</button>
519
+ <button class="tab ${this.activeTab === 'messages' ? 'active' : ''}" data-tab="messages">
520
+ 消息 <span id="p2p-unread-badge" class="unread-badge" style="display:none">0</span>
521
+ </button>
522
+ </div>
523
+
524
+ <!-- 我的身份 -->
525
+ <div class="tab-content ${this.activeTab === 'identity' ? 'active' : ''}" id="tab-identity">
526
+ <div class="identity-card">
527
+ <div class="status-row">
528
+ <span class="status-indicator" id="p2p-status-indicator"></span>
529
+ <span id="p2p-status-text">未初始化</span>
530
+ </div>
531
+ <div class="info-row">
532
+ <span class="info-label">DID:</span>
533
+ <code class="info-value" id="p2p-did">-</code>
534
+ <button class="copy-btn" data-copy="p2p-did">📋</button>
535
+ </div>
536
+ <div class="info-row">
537
+ <span class="info-label">CID:</span>
538
+ <code class="info-value" id="p2p-cid">-</code>
539
+ <button class="copy-btn" data-copy="p2p-cid">📋</button>
540
+ </div>
541
+ <div class="info-row">
542
+ <span class="info-label">Node ID:</span>
543
+ <code class="info-value" id="p2p-node-id">-</code>
544
+ <button class="copy-btn" data-copy="p2p-node-id">📋</button>
545
+ </div>
546
+ </div>
547
+
548
+ <button class="btn-primary" id="p2p-init-btn">初始化 P2P</button>
549
+
550
+ <div class="share-panel" id="p2p-share-panel" style="display:none">
551
+ <h4>分享给好友</h4>
552
+ <div class="share-actions">
553
+ <button class="btn-secondary" id="p2p-copy-link">📋 复制链接</button>
554
+ <button class="btn-secondary" id="p2p-export-file">📁 导出文件</button>
555
+ </div>
556
+ <div class="info-row" style="margin-top: 12px; display:none" id="p2p-share-link-row">
557
+ <input type="text" id="p2p-invite-link" readonly style="flex:1; padding:8px; background:var(--bg-primary); border:1px solid var(--border); border-radius:6px); color:var(--text-primary); font-size:12px; font-family:monospace;">
558
+ </div>
559
+ </div>
560
+ </div>
561
+
562
+ <!-- 连接 -->
563
+ <div class="tab-content ${this.activeTab === 'connect' ? 'active' : ''}" id="tab-connect">
564
+ <div class="connect-form">
565
+ <input type="text" id="p2p-connect-input" placeholder="粘贴 CID 或链接...">
566
+ <button class="btn-secondary" id="p2p-connect-btn">连接 ▶</button>
567
+ </div>
568
+ <div class="progress" id="p2p-connect-progress">
569
+ <div class="progress-bar"><div class="progress-fill" id="p2p-progress-fill"></div></div>
570
+ <span class="progress-text" id="p2p-progress-text">验证输入格式...</span>
571
+ </div>
572
+ <div class="connect-result" id="p2p-connect-result"></div>
573
+ </div>
574
+
575
+ <!-- 历史记录 -->
576
+ <div class="tab-content ${this.activeTab === 'history' ? 'active' : ''}" id="tab-history">
577
+ <div class="toolbar">
578
+ <button class="btn-secondary btn-sm" id="p2p-history-refresh">🔄 刷新</button>
579
+ </div>
580
+ <div id="p2p-history-list"></div>
581
+ </div>
582
+
583
+ <!-- 消息 -->
584
+ <div class="tab-content ${this.activeTab === 'messages' ? 'active' : ''}" id="tab-messages">
585
+ <div class="toolbar">
586
+ <button class="btn-secondary btn-sm" id="p2p-mark-all-read">全部已读</button>
587
+ </div>
588
+ <div id="p2p-messages-list"></div>
589
+ </div>
590
+
591
+ <!-- 已连接节点 -->
592
+ <div class="peers-section">
593
+ <h4>已连接节点 (<span id="p2p-peer-count">0</span>)</h4>
594
+ <div id="p2p-peers-list"></div>
595
+ </div>
596
+ </div>
597
+ `;
598
+ }
599
+ attachEvents() {
600
+ // 关闭按钮
601
+ this.shadow.getElementById('modal-close')?.addEventListener('click', () => this.hide());
602
+ // 点击外部关闭
603
+ this.addEventListener('click', (e) => {
604
+ if (e.target === this)
605
+ this.hide();
606
+ });
607
+ // 初始化按钮
608
+ this.shadow.getElementById('p2p-init-btn')?.addEventListener('click', () => this.initP2P());
609
+ // 复制按钮
610
+ this.shadow.querySelectorAll('[data-copy]').forEach(btn => {
611
+ btn.addEventListener('click', async (e) => {
612
+ const el = e.currentTarget;
613
+ const targetId = el.dataset.copy;
614
+ const targetEl = this.shadow.getElementById(targetId);
615
+ if (targetEl?.textContent && targetEl.textContent !== '-') {
616
+ await navigator.clipboard.writeText(targetEl.textContent);
617
+ this.showToast('已复制');
618
+ }
619
+ });
620
+ });
621
+ // 复制链接
622
+ this.shadow.getElementById('p2p-copy-link')?.addEventListener('click', async () => {
623
+ const shareLink = this.shadow.getElementById('p2p-invite-link');
624
+ if (shareLink?.value) {
625
+ await navigator.clipboard.writeText(shareLink.value);
626
+ this.showToast('链接已复制');
627
+ }
628
+ });
629
+ // 导出文件
630
+ this.shadow.getElementById('p2p-export-file')?.addEventListener('click', () => {
631
+ p2pManager.identity.exportIdentityFile();
632
+ });
633
+ // 标签页切换
634
+ this.shadow.querySelectorAll('.tab').forEach(btn => {
635
+ btn.addEventListener('click', () => {
636
+ const tab = btn.dataset.tab;
637
+ if (tab)
638
+ this.switchTab(tab);
639
+ });
640
+ });
641
+ // 连接按钮
642
+ this.shadow.getElementById('p2p-connect-btn')?.addEventListener('click', () => this.handleConnect());
643
+ // 回车连接
644
+ this.shadow.getElementById('p2p-connect-input')?.addEventListener('keypress', (e) => {
645
+ if (e.key === 'Enter')
646
+ this.handleConnect();
647
+ });
648
+ // 刷新历史
649
+ this.shadow.getElementById('p2p-history-refresh')?.addEventListener('click', () => this.loadHistory());
650
+ // 全部已读
651
+ this.shadow.getElementById('p2p-mark-all-read')?.addEventListener('click', async () => {
652
+ await p2pManager.messages.markAllRead();
653
+ this.loadMessages();
654
+ });
655
+ }
656
+ }
657
+ customElements.define('p2p-modal', P2PModal);