@bolloon/bolloon-agent 0.1.1 → 0.1.3
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/bin/bolloon-cli.cjs +165 -0
- package/bin/bolloon-daemon.sh +207 -0
- package/bin/bolloon.cmd +11 -0
- package/dist/agents/constraint-layer.js +10 -15
- package/dist/agents/pi-sdk.js +433 -106
- package/dist/agents/protocol.js +82 -1
- package/dist/agents/subagent-manager.js +2 -2
- package/dist/agents/workflow-engine.js +15 -20
- package/dist/agents/workflow-pivot-loop.js +541 -0
- package/dist/bollharness/src/index.js +5 -0
- package/dist/bollharness/src/scripts/checks/check_adr_plan_numbering.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_api_types.js +45 -0
- package/dist/bollharness/src/scripts/checks/check_artifact_link.js +146 -0
- package/dist/bollharness/src/scripts/checks/check_bridge_deps.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_bugfix_binding.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_bugfix_binding_ci.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_doc_file_references.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +135 -0
- package/dist/bollharness/src/scripts/checks/check_doc_links.js +31 -0
- package/dist/bollharness/src/scripts/checks/check_file_existence_claims.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_fragment_integrity.js +34 -0
- package/dist/bollharness/src/scripts/checks/check_hook_installed.js +63 -0
- package/dist/bollharness/src/scripts/checks/check_issue_closure.js +41 -0
- package/dist/bollharness/src/scripts/checks/check_mcp_parity.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_security.js +48 -0
- package/dist/bollharness/src/scripts/checks/check_skill_parity.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_versions.js +6 -0
- package/dist/bollharness/src/scripts/checks/finding.js +13 -0
- package/dist/bollharness/src/scripts/checks/next_decision_number.js +20 -0
- package/dist/bollharness/src/scripts/checks/regenerate_magic_docs.js +6 -0
- package/dist/bollharness/src/scripts/ci/detect_rebaseline_triggers.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_subprocess_cfg.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_verify_artifacts.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_yaml_schema.js +8 -0
- package/dist/bollharness/src/scripts/context_router.js +67 -0
- package/dist/bollharness/src/scripts/deploy-guard.js +157 -0
- package/dist/bollharness/src/scripts/guard-feedback.js +192 -0
- package/dist/bollharness/src/scripts/guard_router.js +158 -0
- package/dist/bollharness/src/scripts/hooks/_hook_output.js +6 -0
- package/dist/bollharness/src/scripts/hooks/auto-python3.js +6 -0
- package/dist/bollharness/src/scripts/hooks/deploy-progress-on-session-end.js +6 -0
- package/dist/bollharness/src/scripts/hooks/failure-analyzer.js +6 -0
- package/dist/bollharness/src/scripts/hooks/gate-judgment-inject.js +92 -0
- package/dist/bollharness/src/scripts/hooks/gate-transition-judgment.js +63 -0
- package/dist/bollharness/src/scripts/hooks/inbox-ack.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-inject-on-start.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-validate.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-write-ledger.js +6 -0
- package/dist/bollharness/src/scripts/hooks/initializer-agent.js +6 -0
- package/dist/bollharness/src/scripts/hooks/loop-detection.js +73 -0
- package/dist/bollharness/src/scripts/hooks/owner-guard.js +6 -0
- package/dist/bollharness/src/scripts/hooks/precompact.js +6 -0
- package/dist/bollharness/src/scripts/hooks/review-agent-gatekeeper.js +6 -0
- package/dist/bollharness/src/scripts/hooks/risk-tracker.js +108 -0
- package/dist/bollharness/src/scripts/hooks/sanitize-on-read.js +6 -0
- package/dist/bollharness/src/scripts/hooks/session-reflection.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-magic-docs.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-reset-risk.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-toolkit-reminder.js +7 -0
- package/dist/bollharness/src/scripts/hooks/stop-evaluator.js +157 -0
- package/dist/bollharness/src/scripts/hooks/tool-call-counter.js +6 -0
- package/dist/bollharness/src/scripts/hooks/trace-analyzer.js +10 -0
- package/dist/bollharness/src/scripts/install/install-trust-token.js +7 -0
- package/dist/bollharness/src/scripts/install/multi_project_registry.js +9 -0
- package/dist/bollharness/src/scripts/install/phase2_auto.js +21 -0
- package/dist/bollharness/src/scripts/install/pre_commit_installer.js +6 -0
- package/dist/bollharness/src/scripts/install/tier_selector.js +7 -0
- package/dist/bollharness/src/scripts/install/transcript_miner.js +7 -0
- package/dist/bollharness/src/scripts/lib/claim_patterns.js +10 -0
- package/dist/bollharness/src/scripts/lib/sanitize_patterns.js +12 -0
- package/dist/bollharness/src/scripts/sanitize.js +6 -0
- package/dist/bollharness-integration/channel-judgment-engine.js +530 -0
- package/dist/bollharness-integration/context-chain-router.js +383 -0
- package/dist/bollharness-integration/context-router-judgment.js +13 -21
- package/dist/bollharness-integration/context-router.js +22 -64
- package/dist/bollharness-integration/gate-state-machine.js +14 -19
- package/dist/bollharness-integration/gate-transition-hooks.js +16 -61
- package/dist/bollharness-integration/guard-checker.js +21 -68
- package/dist/bollharness-integration/index.js +14 -124
- package/dist/bollharness-integration/integration.js +13 -20
- package/dist/bollharness-integration/llm-judgment-engine.js +569 -0
- package/dist/bollharness-integration/skill-adapter.js +18 -64
- package/dist/cli-entry.js +261 -0
- package/dist/constraint-runtime/src/commands.js +17 -7
- package/dist/constraint-runtime/src/constraint/budget.js +1 -6
- package/dist/constraint-runtime/src/constraint/permission.js +1 -6
- package/dist/constraint-runtime/src/models.js +1 -3
- package/dist/constraint-runtime/src/tools.js +17 -7
- package/dist/constraints/index.js +1 -7
- package/dist/documents/reader.js +8 -49
- package/dist/heartbeat/DaemonManager.js +242 -0
- package/dist/heartbeat/HealthMonitor.js +285 -0
- package/dist/heartbeat/StartupVerifier.js +205 -0
- package/dist/heartbeat/Watchdog.js +168 -0
- package/dist/heartbeat/index.js +84 -0
- package/dist/heartbeat/types.js +5 -0
- package/dist/index.js +381 -28
- package/dist/llm/config-store.js +31 -57
- package/dist/llm/llm-judgment-client.js +389 -0
- package/dist/llm/pi-ai.js +9 -52
- package/dist/network/agent-network.js +46 -90
- package/dist/network/hybrid-messenger.js +125 -0
- package/dist/network/iroh-bootstrap.js +38 -0
- package/dist/network/iroh-discovery.js +145 -0
- package/dist/network/iroh-integration.js +9 -16
- package/dist/network/iroh-transport.js +10 -48
- package/dist/network/p2p.js +23 -62
- package/dist/network/storage/adapters/json-adapter.js +4 -42
- package/dist/network/storage/index.js +147 -0
- package/dist/network/storage/types.js +14 -0
- package/dist/pi-ecosystem/index.js +233 -0
- package/dist/pi-ecosystem-colony/index.js +29 -90
- package/dist/pi-ecosystem-goals/index.js +20 -74
- package/dist/pi-ecosystem-judgment/decision.js +29 -47
- package/dist/pi-ecosystem-judgment/distillation.js +16 -29
- package/dist/pi-ecosystem-judgment/human-value-store.js +13 -60
- package/dist/pi-ecosystem-judgment/index.js +21 -74
- package/dist/pi-ecosystem-judgment/value-injection.js +26 -72
- package/dist/pi-ecosystem-mcp/index.js +24 -78
- package/dist/pi-ecosystem-subagents/index.js +20 -69
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +3 -8
- package/dist/social/ant-colony/PheromoneEngine.js +11 -49
- package/dist/social/ant-colony/index.js +6 -0
- package/dist/social/ant-colony/types.js +4 -8
- package/dist/social/channels/ChannelManager.js +8 -46
- package/dist/social/channels/DiapChannelBridge.js +9 -47
- package/dist/social/channels/InterestMatcher.js +2 -7
- package/dist/social/channels/channel-agent-session.js +309 -0
- package/dist/social/channels/channel-heartbeat-agent.js +494 -0
- package/dist/social/channels/diap-doc-parser.js +204 -0
- package/dist/social/channels/harness-workflow-integrator.js +446 -0
- package/dist/social/channels/index.js +9 -0
- package/dist/social/channels/types.js +3 -7
- package/dist/social/global-shared-context.js +6 -47
- package/dist/social/heartbeat.js +29 -72
- package/dist/social/persona/enhanced-persona.js +299 -0
- package/dist/web/client.js +302 -136
- package/dist/web/components/p2p/index.js +159 -9
- package/dist/web/components/p2p/p2p-connection.js +136 -0
- package/dist/web/components/p2p/p2p-manager.js +24 -0
- package/dist/web/components/p2p/p2p-store-memory.js +1 -1
- package/dist/web/components/p2p/types.js +7 -0
- package/dist/web/index.html +5 -0
- package/dist/web/style.css +118 -0
- package/package.json +12 -6
- package/scripts/build-cli.js +206 -0
- package/scripts/postinstall.js +153 -0
- package/src/agents/pi-sdk.ts +347 -28
- package/src/agents/protocol.ts +95 -1
- package/src/agents/workflow-pivot-loop.ts +674 -0
- package/src/bollharness/CLAUDE.md +73 -0
- package/src/bollharness/README.md +143 -0
- package/src/bollharness/README.zh-CN.md +131 -0
- package/src/bollharness/reference/boll-reference/scripts/hooks/stop-evaluator.md +57 -0
- package/src/bollharness/scripts/context-fragments/artifact-linkage.md +14 -0
- package/src/bollharness/scripts/context-fragments/auth-consumers.md +17 -0
- package/src/bollharness/scripts/context-fragments/bridge-constitution.md +13 -0
- package/src/bollharness/scripts/context-fragments/catalyst-distributed.md +18 -0
- package/src/bollharness/scripts/context-fragments/closure-checklist.md +13 -0
- package/src/bollharness/scripts/context-fragments/contract-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/db-shared-structures.md +15 -0
- package/src/bollharness/scripts/context-fragments/fixed-three-layers.md +19 -0
- package/src/bollharness/scripts/context-fragments/general-dev-principles.md +11 -0
- package/src/bollharness/scripts/context-fragments/issue-first.md +8 -0
- package/src/bollharness/scripts/context-fragments/mcp-parity.md +16 -0
- package/src/bollharness/scripts/context-fragments/pi-agent-operations.md +108 -0
- package/src/bollharness/scripts/context-fragments/protocol-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/run-events-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/scene-fidelity.md +13 -0
- package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +15 -0
- package/src/bollharness/scripts/context-fragments/two-language.md +15 -0
- package/src/bollharness/scripts/context-fragments/version-sources.md +14 -0
- package/src/bollharness/scripts/hooks/stop-evaluator.md +83 -0
- package/src/bollharness/templates/scaffold/CLAUDE.md +89 -0
- package/src/cli-entry.ts +304 -0
- package/src/heartbeat/DaemonManager.ts +283 -0
- package/src/heartbeat/HealthMonitor.ts +316 -0
- package/src/heartbeat/StartupVerifier.ts +223 -0
- package/src/heartbeat/Watchdog.ts +198 -0
- package/src/heartbeat/index.ts +108 -0
- package/src/heartbeat/types.ts +82 -0
- package/src/llm/config-store.ts +23 -5
- package/src/network/iroh-transport.ts +3 -3
- package/src/web/client.js +302 -136
- package/src/web/components/p2p/P2PModal.tsx +91 -3
- package/src/web/components/p2p/index.ts +171 -9
- package/src/web/components/p2p/p2p-connection.ts +153 -1
- package/src/web/components/p2p/p2p-manager.ts +39 -1
- package/src/web/components/p2p/p2p-store-memory.ts +1 -1
- package/src/web/components/p2p/p2p-tools.ts +315 -0
- package/src/web/components/p2p/types.ts +58 -0
- package/src/web/design.md +99 -0
- package/src/web/index.html +5 -0
- package/src/web/server.ts +353 -36
- package/src/web/style.css +118 -0
- package/tsconfig.cli.json +16 -0
- package/tsconfig.electron.json +1 -1
- package/tsconfig.json +1 -2
- package/dist/web/server.js +0 -1647
- package/dist/web/server.js.map +0 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { p2pManager } from './p2p-manager.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
P2PIdentity,
|
|
5
|
+
P2PMessage,
|
|
6
|
+
ConnectionHistoryEntry,
|
|
7
|
+
ConnectProgress,
|
|
8
|
+
PersistentConnection
|
|
9
|
+
} from './types.js';
|
|
4
10
|
|
|
5
11
|
type Tab = 'identity' | 'connect' | 'history' | 'messages';
|
|
6
12
|
type Status = 'idle' | 'connecting' | 'online' | 'error';
|
|
@@ -24,10 +30,16 @@ export function P2PModal({ visible, onClose }: P2PModalProps) {
|
|
|
24
30
|
const [messages, setMessages] = useState<P2PMessage[]>([]);
|
|
25
31
|
const [unreadCount, setUnreadCount] = useState(0);
|
|
26
32
|
const [peers, setPeers] = useState<{ nodeId: string; info: any }[]>([]);
|
|
33
|
+
const [persistentConnections, setPersistentConnections] = useState<PersistentConnection[]>([]);
|
|
27
34
|
const [toast, setToast] = useState<string | null>(null);
|
|
28
35
|
|
|
29
36
|
useEffect(() => {
|
|
30
37
|
if (visible && !initialized) {
|
|
38
|
+
console.log('[P2P Modal] visible=true, initialized=false, calling initP2P');
|
|
39
|
+
initP2P();
|
|
40
|
+
} else if (visible && initialized && !identity) {
|
|
41
|
+
// 已初始化但没有identity,可能是首次未触发
|
|
42
|
+
console.log('[P2P Modal] visible=true, initialized=true, but no identity, calling initP2P again');
|
|
31
43
|
initP2P();
|
|
32
44
|
}
|
|
33
45
|
}, [visible, initialized]);
|
|
@@ -64,6 +76,7 @@ export function P2PModal({ visible, onClose }: P2PModalProps) {
|
|
|
64
76
|
break;
|
|
65
77
|
case 'connect':
|
|
66
78
|
loadPeers();
|
|
79
|
+
loadPersistentConnections();
|
|
67
80
|
break;
|
|
68
81
|
}
|
|
69
82
|
}
|
|
@@ -93,6 +106,38 @@ export function P2PModal({ visible, onClose }: P2PModalProps) {
|
|
|
93
106
|
setPeers(connectedPeers);
|
|
94
107
|
}
|
|
95
108
|
|
|
109
|
+
async function loadPersistentConnections() {
|
|
110
|
+
try {
|
|
111
|
+
const connections = await p2pManager.getPersistentConnections();
|
|
112
|
+
setPersistentConnections(connections);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('[P2P Modal] 加载持久连接失败:', e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function handleToggleConnection(connection: PersistentConnection) {
|
|
119
|
+
const newStatus = connection.status === 'connected' ? 'disconnected' : 'connected';
|
|
120
|
+
const enable = newStatus === 'connected';
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const success = await p2pManager.toggleConnection(connection, enable);
|
|
124
|
+
if (success) {
|
|
125
|
+
await loadPersistentConnections();
|
|
126
|
+
loadPeers();
|
|
127
|
+
showToast(enable ? '正在连接...' : '已断开');
|
|
128
|
+
} else {
|
|
129
|
+
showToast('操作失败');
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
showToast('操作失败');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function handleOpenChannel(channelId: string) {
|
|
137
|
+
showToast(`打开通道: ${channelId}`);
|
|
138
|
+
onClose();
|
|
139
|
+
}
|
|
140
|
+
|
|
96
141
|
async function handleConnect() {
|
|
97
142
|
if (!connectInput.trim()) return;
|
|
98
143
|
|
|
@@ -101,13 +146,14 @@ export function P2PModal({ visible, onClose }: P2PModalProps) {
|
|
|
101
146
|
setConnectResult(null);
|
|
102
147
|
|
|
103
148
|
try {
|
|
104
|
-
const result = await p2pManager.
|
|
149
|
+
const result = await p2pManager.connectAndCreateChannel(connectInput, (p) => setProgress(p));
|
|
105
150
|
|
|
106
151
|
if (result.success) {
|
|
107
152
|
setConnectResult({ type: 'success', text: `已连接到 ${result.name || '节点'}` });
|
|
108
153
|
setConnectInput('');
|
|
109
154
|
await loadHistory();
|
|
110
155
|
loadPeers();
|
|
156
|
+
loadPersistentConnections();
|
|
111
157
|
} else {
|
|
112
158
|
setConnectResult({ type: 'error', text: result.error || '连接失败' });
|
|
113
159
|
}
|
|
@@ -248,8 +294,50 @@ export function P2PModal({ visible, onClose }: P2PModalProps) {
|
|
|
248
294
|
</div>
|
|
249
295
|
)}
|
|
250
296
|
|
|
297
|
+
{/* 持久连接列表 */}
|
|
298
|
+
<div className="persistent-peers-section">
|
|
299
|
+
<h4>持久连接 ({persistentConnections.length})</h4>
|
|
300
|
+
{persistentConnections.length === 0 ? (
|
|
301
|
+
<div className="empty-hint">暂无持久连接</div>
|
|
302
|
+
) : (
|
|
303
|
+
persistentConnections.map((conn) => (
|
|
304
|
+
<div key={conn.id} className={`persistent-peer-item ${conn.status}`}>
|
|
305
|
+
<div className="peer-status-indicator">
|
|
306
|
+
<span className={`dot ${conn.status === 'connected' ? 'online' : 'offline'}`}></span>
|
|
307
|
+
</div>
|
|
308
|
+
<div className="peer-info">
|
|
309
|
+
<div className="peer-name">
|
|
310
|
+
{conn.peerName || 'Unknown'}
|
|
311
|
+
{conn.isAutoConnect && <span className="auto-badge">自动</span>}
|
|
312
|
+
</div>
|
|
313
|
+
<div className="peer-meta">
|
|
314
|
+
<span>DID: {conn.peerDid?.substring(0, 16)}...</span>
|
|
315
|
+
<span>状态: {conn.status === 'connected' ? '已连接' : '未连接'}</span>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
<div className="peer-actions">
|
|
319
|
+
<button
|
|
320
|
+
className={`btn-sm ${conn.status === 'connected' ? 'btn-danger' : 'btn-primary'}`}
|
|
321
|
+
onClick={() => handleToggleConnection(conn)}
|
|
322
|
+
>
|
|
323
|
+
{conn.status === 'connected' ? '断开' : '连接'}
|
|
324
|
+
</button>
|
|
325
|
+
{conn.channelId && (
|
|
326
|
+
<button
|
|
327
|
+
className="btn-sm btn-secondary"
|
|
328
|
+
onClick={() => handleOpenChannel(conn.channelId!)}
|
|
329
|
+
>
|
|
330
|
+
对话
|
|
331
|
+
</button>
|
|
332
|
+
)}
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
))
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
|
|
251
339
|
<div className="peers-section">
|
|
252
|
-
<h4
|
|
340
|
+
<h4>当前连接 ({peers.length})</h4>
|
|
253
341
|
<div id="p2p-peers-list">
|
|
254
342
|
{peers.length === 0 && <div className="empty-hint">暂无连接</div>}
|
|
255
343
|
{peers.map((peer, i) => (
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
class P2PModalUI {
|
|
6
6
|
private modal: HTMLElement | null = null;
|
|
7
7
|
private overlay: HTMLElement | null = null;
|
|
8
|
+
private connectedPeers: Map<string, { nodeId: string; name: string; time: number }> = new Map();
|
|
8
9
|
|
|
9
10
|
constructor() {
|
|
10
11
|
this.createModal();
|
|
@@ -53,23 +54,54 @@ class P2PModalUI {
|
|
|
53
54
|
private async render(): Promise<void> {
|
|
54
55
|
if (!this.modal) return;
|
|
55
56
|
|
|
57
|
+
// 加载连接历史
|
|
58
|
+
await this.loadConnectionHistory();
|
|
59
|
+
|
|
56
60
|
// 获取身份信息
|
|
57
61
|
let identityHtml = '<div class="p2p-loading">加载中...</div>';
|
|
62
|
+
let identityData: any = { name: '未知', did: '未知', cid: '未知' };
|
|
58
63
|
try {
|
|
59
|
-
|
|
60
|
-
const
|
|
64
|
+
// 尝试获取 iroh 身份(包含 CID)
|
|
65
|
+
const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
|
|
66
|
+
const irohData = await irohResp.json();
|
|
67
|
+
if (irohData.cid) {
|
|
68
|
+
identityData = {
|
|
69
|
+
name: irohData.name || irohData.name?.split('-').slice(-1)[0] || 'Bolloon',
|
|
70
|
+
did: irohData.did || '未知',
|
|
71
|
+
cid: irohData.cid || '未知',
|
|
72
|
+
nodeId: irohData.irohNodeId ? irohData.irohNodeId.substring(0, 20) + '...' : '未知'
|
|
73
|
+
};
|
|
74
|
+
} else {
|
|
75
|
+
// 回退到 /api/identity
|
|
76
|
+
const resp = await fetch('/api/identity');
|
|
77
|
+
identityData = await resp.json();
|
|
78
|
+
identityData.cid = identityData.cid || '未初始化';
|
|
79
|
+
identityData.nodeId = identityData.nodeId || '未知';
|
|
80
|
+
}
|
|
61
81
|
identityHtml = `
|
|
62
82
|
<div class="p2p-section">
|
|
63
83
|
<h3>身份信息</h3>
|
|
64
84
|
<div class="p2p-info-row">
|
|
65
85
|
<span class="label">名称:</span>
|
|
66
|
-
<span class="value">${this.escapeHtml(
|
|
86
|
+
<span class="value">${this.escapeHtml(identityData.name || '未知')}</span>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="p2p-info-row">
|
|
89
|
+
<span class="label">CID:</span>
|
|
90
|
+
<span class="value mono">${this.escapeHtml(identityData.cid || '未知')}</span>
|
|
67
91
|
</div>
|
|
68
92
|
<div class="p2p-info-row">
|
|
69
93
|
<span class="label">DID:</span>
|
|
70
|
-
<span class="value mono">${this.escapeHtml(
|
|
94
|
+
<span class="value mono">${this.escapeHtml(identityData.did || '未知')}</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="p2p-info-row">
|
|
97
|
+
<span class="label">Node ID:</span>
|
|
98
|
+
<span class="value mono">${this.escapeHtml(identityData.nodeId || '未知')}</span>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="p2p-actions">
|
|
101
|
+
<button class="p2p-btn-secondary" id="p2p-copy-cid-btn">复制 CID</button>
|
|
102
|
+
<button class="p2p-btn-secondary" id="p2p-copy-did-btn">复制 DID</button>
|
|
103
|
+
<button class="p2p-btn-secondary" id="p2p-copy-nodeid-btn">复制 Node ID</button>
|
|
71
104
|
</div>
|
|
72
|
-
<button class="p2p-btn-primary" id="p2p-copy-btn">复制 DID</button>
|
|
73
105
|
</div>
|
|
74
106
|
`;
|
|
75
107
|
} catch {
|
|
@@ -99,6 +131,12 @@ class P2PModalUI {
|
|
|
99
131
|
</div>
|
|
100
132
|
<div id="p2p-connect-result" class="p2p-result"></div>
|
|
101
133
|
</div>
|
|
134
|
+
<div class="p2p-section">
|
|
135
|
+
<h3>已连接的节点</h3>
|
|
136
|
+
<div id="p2p-connected-peers" class="p2p-connected-peers">
|
|
137
|
+
暂无连接
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
102
140
|
</div>
|
|
103
141
|
<div class="p2p-tab-content" data-tab="messages">
|
|
104
142
|
<div class="p2p-section">
|
|
@@ -131,12 +169,27 @@ class P2PModalUI {
|
|
|
131
169
|
});
|
|
132
170
|
});
|
|
133
171
|
|
|
172
|
+
// 复制 CID
|
|
173
|
+
const copyCidBtn = this.modal.querySelector('#p2p-copy-cid-btn');
|
|
174
|
+
copyCidBtn?.addEventListener('click', async () => {
|
|
175
|
+
try {
|
|
176
|
+
const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
|
|
177
|
+
const data = await irohResp.json();
|
|
178
|
+
if (data.cid) {
|
|
179
|
+
await navigator.clipboard.writeText(data.cid);
|
|
180
|
+
this.showToast('CID 已复制到剪贴板');
|
|
181
|
+
}
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.error('复制失败:', e);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
134
187
|
// 复制 DID
|
|
135
|
-
const
|
|
136
|
-
|
|
188
|
+
const copyDidBtn = this.modal.querySelector('#p2p-copy-did-btn');
|
|
189
|
+
copyDidBtn?.addEventListener('click', async () => {
|
|
137
190
|
try {
|
|
138
|
-
const
|
|
139
|
-
const data = await
|
|
191
|
+
const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
|
|
192
|
+
const data = await irohResp.json();
|
|
140
193
|
if (data.did) {
|
|
141
194
|
await navigator.clipboard.writeText(data.did);
|
|
142
195
|
this.showToast('DID 已复制到剪贴板');
|
|
@@ -146,6 +199,21 @@ class P2PModalUI {
|
|
|
146
199
|
}
|
|
147
200
|
});
|
|
148
201
|
|
|
202
|
+
// 复制 Node ID
|
|
203
|
+
const copyNodeIdBtn = this.modal.querySelector('#p2p-copy-nodeid-btn');
|
|
204
|
+
copyNodeIdBtn?.addEventListener('click', async () => {
|
|
205
|
+
try {
|
|
206
|
+
const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
|
|
207
|
+
const data = await irohResp.json();
|
|
208
|
+
if (data.irohNodeId) {
|
|
209
|
+
await navigator.clipboard.writeText(data.irohNodeId);
|
|
210
|
+
this.showToast('Node ID 已复制到剪贴板');
|
|
211
|
+
}
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.error('复制失败:', e);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
149
217
|
// 连接按钮
|
|
150
218
|
const connectBtn = this.modal.querySelector('#p2p-connect-btn');
|
|
151
219
|
connectBtn?.addEventListener('click', () => this.handleConnect());
|
|
@@ -185,6 +253,23 @@ class P2PModalUI {
|
|
|
185
253
|
if (data.ok) {
|
|
186
254
|
result.innerHTML = `<div class="p2p-success">连接成功!节点: ${this.escapeHtml(data.nodeName || cid.substring(0, 16))}...</div>`;
|
|
187
255
|
result.className = 'p2p-result success';
|
|
256
|
+
// 清空输入框
|
|
257
|
+
input.value = '';
|
|
258
|
+
// 保存已连接节点到后端
|
|
259
|
+
try {
|
|
260
|
+
await fetch('/api/p2p/history', {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers: { 'Content-Type': 'application/json' },
|
|
263
|
+
body: JSON.stringify({
|
|
264
|
+
cid: cid, // 输入的 CID 或 Node ID
|
|
265
|
+
irohNodeId: data.targetNodeId,
|
|
266
|
+
did: data.targetNodeId, // 用 Node ID 作为 DID
|
|
267
|
+
name: data.nodeName || cid.substring(0, 16)
|
|
268
|
+
})
|
|
269
|
+
});
|
|
270
|
+
} catch (e) {
|
|
271
|
+
console.error('保存连接失败:', e);
|
|
272
|
+
}
|
|
188
273
|
} else {
|
|
189
274
|
result.innerHTML = `<div class="p2p-error">${this.escapeHtml(data.error || '连接失败')}</div>`;
|
|
190
275
|
result.className = 'p2p-result error';
|
|
@@ -196,6 +281,48 @@ class P2PModalUI {
|
|
|
196
281
|
}
|
|
197
282
|
}
|
|
198
283
|
|
|
284
|
+
private async loadConnectionHistory(): Promise<void> {
|
|
285
|
+
try {
|
|
286
|
+
const resp = await fetch('/api/p2p/history');
|
|
287
|
+
const history = await resp.json();
|
|
288
|
+
this.connectedPeers.clear();
|
|
289
|
+
for (const entry of history) {
|
|
290
|
+
this.connectedPeers.set(entry.irohNodeId || entry.did, {
|
|
291
|
+
nodeId: entry.irohNodeId || entry.did,
|
|
292
|
+
name: entry.name || 'Unknown',
|
|
293
|
+
time: entry.lastConnectedAt || Date.now()
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
this.updateConnectedPeersDisplay();
|
|
297
|
+
} catch (e) {
|
|
298
|
+
console.error('加载连接历史失败:', e);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private updateConnectedPeersDisplay(): void {
|
|
303
|
+
const container = document.getElementById('p2p-connected-peers');
|
|
304
|
+
if (!container) return;
|
|
305
|
+
|
|
306
|
+
if (this.connectedPeers.size === 0) {
|
|
307
|
+
container.innerHTML = '<div class="p2p-empty">暂无连接</div>';
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let html = '';
|
|
312
|
+
for (const [nodeId, info] of this.connectedPeers) {
|
|
313
|
+
const shortId = nodeId.substring(0, 16) + '...';
|
|
314
|
+
const time = new Date(info.time).toLocaleTimeString();
|
|
315
|
+
html += `
|
|
316
|
+
<div class="p2p-peer-item">
|
|
317
|
+
<span class="p2p-peer-name">${this.escapeHtml(info.name || shortId)}</span>
|
|
318
|
+
<span class="p2p-peer-id">${shortId}</span>
|
|
319
|
+
<span class="p2p-peer-time">${time}</span>
|
|
320
|
+
</div>
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
container.innerHTML = html;
|
|
324
|
+
}
|
|
325
|
+
|
|
199
326
|
private escapeHtml(str: string): string {
|
|
200
327
|
const div = document.createElement('div');
|
|
201
328
|
div.textContent = str;
|
|
@@ -390,6 +517,41 @@ style.textContent = `
|
|
|
390
517
|
color: var(--text-muted, #606058);
|
|
391
518
|
}
|
|
392
519
|
.p2p-error { color: var(--error, #ef4444); }
|
|
520
|
+
.p2p-connected-peers {
|
|
521
|
+
background: var(--bg, #1a1a18);
|
|
522
|
+
border: 1px solid var(--border, #3a3a36);
|
|
523
|
+
border-radius: 8px;
|
|
524
|
+
overflow: hidden;
|
|
525
|
+
}
|
|
526
|
+
.p2p-connected-peers .p2p-empty {
|
|
527
|
+
padding: 16px;
|
|
528
|
+
text-align: center;
|
|
529
|
+
color: var(--text-muted, #606058);
|
|
530
|
+
font-size: 14px;
|
|
531
|
+
}
|
|
532
|
+
.p2p-peer-item {
|
|
533
|
+
display: flex;
|
|
534
|
+
align-items: center;
|
|
535
|
+
gap: 8px;
|
|
536
|
+
padding: 12px 16px;
|
|
537
|
+
border-bottom: 1px solid var(--border, #3a3a36);
|
|
538
|
+
}
|
|
539
|
+
.p2p-peer-item:last-child { border-bottom: none; }
|
|
540
|
+
.p2p-peer-name {
|
|
541
|
+
font-size: 14px;
|
|
542
|
+
color: var(--text, #d8d8c8);
|
|
543
|
+
min-width: 80px;
|
|
544
|
+
}
|
|
545
|
+
.p2p-peer-id {
|
|
546
|
+
flex: 1;
|
|
547
|
+
font-family: 'JetBrains Mono', monospace;
|
|
548
|
+
font-size: 12px;
|
|
549
|
+
color: var(--text-secondary, #909088);
|
|
550
|
+
}
|
|
551
|
+
.p2p-peer-time {
|
|
552
|
+
font-size: 12px;
|
|
553
|
+
color: var(--text-muted, #606058);
|
|
554
|
+
}
|
|
393
555
|
`;
|
|
394
556
|
document.head.appendChild(style);
|
|
395
557
|
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
* P2P 连接管理
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
ConnectResult,
|
|
7
|
+
ConnectProgress,
|
|
8
|
+
ParsedInput,
|
|
9
|
+
ConnectedPeer,
|
|
10
|
+
PersistentConnection,
|
|
11
|
+
CIDResolveResult,
|
|
12
|
+
PersistentConnectionStatus
|
|
13
|
+
} from './types.js';
|
|
6
14
|
import { ConnectionStatus } from './types.js';
|
|
7
15
|
import { p2pStore } from './p2p-store-memory.js';
|
|
8
16
|
|
|
@@ -181,6 +189,150 @@ export class P2PConnectionManager {
|
|
|
181
189
|
this.reconnectTimers.clear();
|
|
182
190
|
this.peers.clear();
|
|
183
191
|
}
|
|
192
|
+
|
|
193
|
+
// 从 CID 解析身份
|
|
194
|
+
async resolveFromCID(cid: string): Promise<CIDResolveResult> {
|
|
195
|
+
try {
|
|
196
|
+
const res = await fetch('/api/p2p/resolve-cid', {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: { 'Content-Type': 'application/json' },
|
|
199
|
+
body: JSON.stringify({ cid })
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (res.ok) {
|
|
203
|
+
const data = await res.json();
|
|
204
|
+
return {
|
|
205
|
+
success: true,
|
|
206
|
+
did: data.doc?.id || data.doc?.did,
|
|
207
|
+
cid: cid,
|
|
208
|
+
name: data.doc?.name,
|
|
209
|
+
peerId: data.doc?.peerId
|
|
210
|
+
};
|
|
211
|
+
} else {
|
|
212
|
+
return { success: false, error: 'CID 解析失败' };
|
|
213
|
+
}
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return { success: false, error: (e as Error).message };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 获取所有持久连接
|
|
220
|
+
async getPersistentConnections(): Promise<PersistentConnection[]> {
|
|
221
|
+
try {
|
|
222
|
+
const res = await fetch('/api/p2p/persistent-connections');
|
|
223
|
+
if (res.ok) {
|
|
224
|
+
return await res.json();
|
|
225
|
+
}
|
|
226
|
+
return [];
|
|
227
|
+
} catch {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 切换连接状态
|
|
233
|
+
async toggleConnection(connection: PersistentConnection, enable: boolean): Promise<boolean> {
|
|
234
|
+
try {
|
|
235
|
+
if (enable) {
|
|
236
|
+
// 建立连接
|
|
237
|
+
const result = await this.connect(connection.cid);
|
|
238
|
+
if (result.success) {
|
|
239
|
+
await fetch('/api/p2p/connection-status', {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: { 'Content-Type': 'application/json' },
|
|
242
|
+
body: JSON.stringify({
|
|
243
|
+
id: connection.id,
|
|
244
|
+
status: 'connected',
|
|
245
|
+
channelId: connection.channelId
|
|
246
|
+
})
|
|
247
|
+
});
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
} else {
|
|
252
|
+
// 断开连接
|
|
253
|
+
if (connection.peerId) {
|
|
254
|
+
await this.disconnect(connection.peerId);
|
|
255
|
+
}
|
|
256
|
+
await fetch('/api/p2p/connection-status', {
|
|
257
|
+
method: 'POST',
|
|
258
|
+
headers: { 'Content-Type': 'application/json' },
|
|
259
|
+
body: JSON.stringify({
|
|
260
|
+
id: connection.id,
|
|
261
|
+
status: 'disconnected'
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 连接并创建对话通道
|
|
272
|
+
async connectAndCreateChannel(
|
|
273
|
+
input: string,
|
|
274
|
+
onProgress?: (progress: ConnectProgress) => void
|
|
275
|
+
): Promise<ConnectResult> {
|
|
276
|
+
const parsed = this.parseInput(input);
|
|
277
|
+
if (parsed.type === 'invalid') {
|
|
278
|
+
return { success: false, error: parsed.error };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
onProgress?.({ stage: 'validating', percent: 10, message: '验证输入格式...' });
|
|
282
|
+
|
|
283
|
+
let cid = '';
|
|
284
|
+
let did = '';
|
|
285
|
+
const value = parsed.value;
|
|
286
|
+
|
|
287
|
+
if (parsed.type === 'link' && typeof value !== 'string') {
|
|
288
|
+
cid = (value as { did?: string; cid?: string }).cid || '';
|
|
289
|
+
did = (value as { did?: string; cid?: string }).did || '';
|
|
290
|
+
} else if (parsed.type === 'cid' && typeof value === 'string') {
|
|
291
|
+
cid = value;
|
|
292
|
+
} else if (parsed.type === 'diapDoc' && typeof value !== 'string') {
|
|
293
|
+
cid = (value as { id?: string; did?: string; cid?: string }).cid || '';
|
|
294
|
+
did = (value as { id?: string; did?: string }).id || (value as { id?: string; did?: string }).did || '';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!cid) {
|
|
298
|
+
return { success: false, error: '未找到 CID' };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 如果没有 DID,从 CID 解析
|
|
302
|
+
if (!did) {
|
|
303
|
+
onProgress?.({ stage: 'resolving', percent: 30, message: '从 IPFS 解析身份...' });
|
|
304
|
+
const resolveResult = await this.resolveFromCID(cid);
|
|
305
|
+
if (resolveResult.success) {
|
|
306
|
+
did = resolveResult.did || '';
|
|
307
|
+
onProgress?.({ stage: 'identity_resolved', percent: 50, message: `身份解析完成: ${resolveResult.name}` });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 创建对话通道
|
|
312
|
+
onProgress?.({ stage: 'creating_channel', percent: 60, message: '创建对话通道...' });
|
|
313
|
+
try {
|
|
314
|
+
const channelRes = await fetch('/api/p2p/create-channel', {
|
|
315
|
+
method: 'POST',
|
|
316
|
+
headers: { 'Content-Type': 'application/json' },
|
|
317
|
+
body: JSON.stringify({
|
|
318
|
+
peerDid: did,
|
|
319
|
+
peerName: did.substring(0, 16),
|
|
320
|
+
cid: cid,
|
|
321
|
+
peerId: ''
|
|
322
|
+
})
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (!channelRes.ok) {
|
|
326
|
+
return { success: false, error: '创建对话通道失败' };
|
|
327
|
+
}
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return { success: false, error: (e as Error).message };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 执行连接
|
|
333
|
+
onProgress?.({ stage: 'connecting', percent: 80, message: '建立 P2P 连接...' });
|
|
334
|
+
return this.connect(input, onProgress);
|
|
335
|
+
}
|
|
184
336
|
}
|
|
185
337
|
|
|
186
338
|
export const p2pConnection = new P2PConnectionManager();
|
|
@@ -11,7 +11,9 @@ import type {
|
|
|
11
11
|
ConnectResult,
|
|
12
12
|
ConnectProgress,
|
|
13
13
|
P2PMessage,
|
|
14
|
-
ConnectionHistoryEntry
|
|
14
|
+
ConnectionHistoryEntry,
|
|
15
|
+
PersistentConnection,
|
|
16
|
+
CIDResolveResult
|
|
15
17
|
} from './types.js';
|
|
16
18
|
|
|
17
19
|
export class P2PManager {
|
|
@@ -96,6 +98,42 @@ export class P2PManager {
|
|
|
96
98
|
this.connection.destroy();
|
|
97
99
|
this.messages.destroy();
|
|
98
100
|
}
|
|
101
|
+
|
|
102
|
+
// 获取持久连接列表
|
|
103
|
+
async getPersistentConnections(): Promise<PersistentConnection[]> {
|
|
104
|
+
return this.connection.getPersistentConnections();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 更新连接状态
|
|
108
|
+
async updateConnectionStatus(
|
|
109
|
+
id: string,
|
|
110
|
+
status: string,
|
|
111
|
+
channelId?: string
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
await fetch('/api/p2p/connection-status', {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
body: JSON.stringify({ id, status, channelId })
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 切换连接状态
|
|
121
|
+
async toggleConnection(connection: PersistentConnection, enable: boolean): Promise<boolean> {
|
|
122
|
+
return this.connection.toggleConnection(connection, enable);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// CID 解析
|
|
126
|
+
async resolveFromCID(cid: string): Promise<CIDResolveResult> {
|
|
127
|
+
return this.connection.resolveFromCID(cid);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 连接并创建对话通道
|
|
131
|
+
async connectAndCreateChannel(
|
|
132
|
+
input: string,
|
|
133
|
+
onProgress?: (progress: ConnectProgress) => void
|
|
134
|
+
): Promise<ConnectResult> {
|
|
135
|
+
return this.connection.connectAndCreateChannel(input, onProgress);
|
|
136
|
+
}
|
|
99
137
|
}
|
|
100
138
|
|
|
101
139
|
// 全局单例
|
|
@@ -27,7 +27,7 @@ export class P2PStoreMemory {
|
|
|
27
27
|
// ==================== 连接历史 ====================
|
|
28
28
|
|
|
29
29
|
async addToHistory(entry: Omit<ConnectionHistoryEntry, 'id'>): Promise<string> {
|
|
30
|
-
const existingIndex = this.history.findIndex(h => h.
|
|
30
|
+
const existingIndex = this.history.findIndex(h => h.cid === entry.cid);
|
|
31
31
|
|
|
32
32
|
if (existingIndex >= 0) {
|
|
33
33
|
this.history[existingIndex] = {
|